/* ══════════════════════════════════════════════════════════════════════════════════════════════
   PN-DS — PitchNoir Design System, authored 2026-09-19 (Vero).
   ONE stylesheet, ONE system. Replaces component-by-component patching (three prior rounds:
   pn-design-0817 / pn-button-unify-20260819 / pn-pnp-one-design-20260914 and ~40 sibling sheets).
   Direction doc: design/DESIGN_SYSTEM_2026-09-19.md. Rules: design/GAME_DESIGN_RULES.md.
   Locked art direction: design/STYLE_SOURCE_OF_TRUTH.md.

   Load LAST in play.html and in web/game-design-guide.html so every surface renders from here.

   STRUCTURE
     §1  Tokens          — --ds-* custom properties, both themes, driven by [data-theme]/.mvc-light
                            (the REAL stamp — see web/assets/js/mvc-os.js:setPal/setTheme).
     §2  Reset/base
     §3  Type             (headings §5g/§5g-ii, eyebrows, numerals, the ghosted-numeral motif)
     §4  Cards             (+ the brief/opportunity card, the stamp motif)
     §5  Buttons           (the five treatments, one shape family)
     §6  List rows
     §7  Chips / badges / counts / OVR / stars
     §8  Bars / meters / charts
     §9  Inputs
     §10 Windows / popups / confirm dialogs
     §11 Empty states
     §12 Set-piece frames
     §13 Motion
     §14 ADOPTION LAYER — maps legacy game classes onto these tokens. `!important` permitted here
         ONLY, one-line reason required on every rule that needs it (the legacy cascade is ~42
         sheets deep and this layer must win without a specificity war).
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §1 TOKENS — GAME_DESIGN_RULES §3, both themes.
   The real DOM stamp (mvc-os.js) is `<html data-theme="dark|light" data-palette="…">` plus a
   `.mvc-light` class mirrored onto <html> and <body>. We key off BOTH so the tokens apply
   whichever one a given legacy surface happens to read.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
:root,
html[data-theme="dark"],
html:not(.mvc-light):not([data-theme="light"]){
  --ds-ground:      #05070c;
  --ds-panel:       #0b0f18;
  --ds-panel-2:     #10162a;
  --ds-panel-3:     #141b30;   /* raised/hero — new: the ladder's third rung, still a flat fill */
  --ds-ink:         #eef3fa;
  --ds-ink-2:       #a9b8cc;
  --ds-ink-3:       #8496ad;
  --ds-accent:      #f97316;   /* fills */
  --ds-accent-text: #ff6a2b;   /* text/edges */
  --ds-on-accent:   #14060d;
  --ds-hair:        rgba(255,255,255,.11);
  --ds-hair-2:      rgba(255,255,255,.18);   /* raised-edge highlight */
  --ds-good:        #2fbf71;
  --ds-warn:        #e0a100;
  --ds-bad:         #e0443e;
  --ds-bad-fill:    #c0342f;
  --ds-focus:       #ff8a45;
  --ds-scrim:       rgba(5,7,12,.6);
  --ds-halftone-op: .04;
  --ds-hover-step:  color-mix(in srgb, var(--ds-panel-2) 100%, white 14%);
  --ds-hover-step-2:color-mix(in srgb, var(--ds-panel-2) 100%, white 8%);
}
html[data-theme="light"],
html.mvc-light{
  --ds-ground:      #f3f0ea;
  --ds-panel:       #fffdfa;
  --ds-panel-2:     #efe9df;
  --ds-panel-3:     #e7dfd0;
  --ds-ink:         #17130f;
  --ds-ink-2:       #4a4238;
  --ds-ink-3:       #6b6255;
  --ds-accent:      #b4380d;
  --ds-accent-text: #b4380d;
  --ds-on-accent:   #ffffff;
  --ds-hair:        rgba(23,19,15,.12);
  --ds-hair-2:      rgba(23,19,15,.22);
  --ds-good:        #1f7a4a;
  --ds-warn:        #8a6400;
  --ds-bad:         #b3261e;
  --ds-bad-fill:    #b3261e;
  --ds-focus:       #b4380d;
  --ds-scrim:       rgba(23,19,15,.45);
  --ds-halftone-op: .05;
  --ds-hover-step:  color-mix(in srgb, var(--ds-panel-2) 100%, black 10%);
  --ds-hover-step-2:color-mix(in srgb, var(--ds-panel-2) 100%, black 6%);
}
:root{
  /* The game's @font-face families are declared as IS and JB, not by their full names —
     measured: document.fonts.check('400 13px "JetBrains Mono"') === false while 'JB' === true.
     Naming the full name first silently falls through, the same bug Inter had (§G11). */
  --ds-mono: JB, 'JetBrains Mono', ui-monospace, SFMono-Regular, monospace;
  --ds-disp: 'Inter', ui-sans-serif, system-ui, sans-serif;
  --ds-body: IS, 'Instrument Sans', Inter, ui-sans-serif, system-ui, sans-serif;
  --ds-r-card: 12px; --ds-r-ctl: 6px; --ds-r-pill: 999px;
  --ds-gap: 16px; --ds-pad: 24px; --ds-pad-sm: 16px;
  --ds-dur-1: 160ms; --ds-dur-2: 220ms; --ds-dur-3: 400ms;
  --ds-ease: cubic-bezier(.2,.7,.3,1);
}
@media (prefers-reduced-motion: reduce){
  :root{ --ds-dur-1: 0ms; --ds-dur-2: 0ms; --ds-dur-3: 0ms; }
}

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §2 RESET / BASE (scoped to .ds-scope so it never leaks onto legacy markup outside a DS block)
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-scope{ color:var(--ds-ink); font-family:var(--ds-body); }
.ds-scope *{ box-sizing:border-box; }
.ds-scope ::selection{ background:var(--ds-accent); color:var(--ds-on-accent); }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §3 TYPE — GAME_DESIGN_RULES §2. One face for every heading and subheading (SST §5g-ii).
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-hero, .ds-h1{
  font-family:var(--ds-disp); font-weight:900; font-style:normal; text-transform:none;
  font-size:clamp(34px,7vw,88px); line-height:.9; letter-spacing:-.03em; margin:0;
  color:var(--ds-ink); text-wrap:balance;
}
.ds-h2{
  font-family:var(--ds-disp); font-weight:900; font-size:clamp(22px,2.6vw,32px); line-height:1.03;
  letter-spacing:-.03em; margin:0; color:var(--ds-ink); text-wrap:balance;
}
.ds-h3{
  font-family:var(--ds-disp); font-weight:900; font-size:clamp(17px,2vw,25px); line-height:1.03;
  letter-spacing:-.03em; margin:0; color:var(--ds-ink); text-wrap:balance;
}
.ds-accent-word{ color:var(--ds-accent-text); }
.ds-num{ font-family:var(--ds-disp); font-weight:900; font-variant-numeric:tabular-nums;
  font-size:clamp(34px,5vw,64px); line-height:1; letter-spacing:-.03em; color:var(--ds-ink); }
.ds-body{ font-family:var(--ds-body); font-weight:400; font-size:15px; line-height:1.5; max-width:65ch; color:var(--ds-ink); }
.ds-body--strong{ font-weight:600; }
.ds-meta{ font-family:var(--ds-body); font-size:13px; line-height:1.45; color:var(--ds-ink-2); }
.ds-eyebrow{ font-family:var(--ds-mono); font-weight:700; font-size:11px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--ds-ink-3); margin:0 0 6px; }
.ds-data{ font-family:var(--ds-mono); font-weight:500; font-size:13px; letter-spacing:.02em;
  font-variant-numeric:tabular-nums; color:var(--ds-ink); }

/* THE GHOSTED NUMERAL — the system's signature motif (direction doc). Sits behind hero-card
   content, sourced from the card's own case number so it is content, not decoration. */
.ds-ghost-num{
  position:absolute; right:12px; bottom:-.06em; z-index:0; pointer-events:none;
  font-family:var(--ds-disp); font-weight:900; font-size:clamp(90px,14vw,220px); line-height:1;
  letter-spacing:-.04em; color:var(--ds-ink); opacity:.05; user-select:none;
}
html[data-theme="light"] .ds-ghost-num, html.mvc-light .ds-ghost-num{ opacity:.06; }

/* ── halftone texture — Panel-3 only, per the direction doc. ── */
.ds-halftone{ position:relative; isolation:isolate; }
.ds-halftone::before{
  content:""; position:absolute; inset:0; z-index:0; pointer-events:none; border-radius:inherit;
  background-image:radial-gradient(currentColor 1px, transparent 1.4px);
  background-size:10px 10px; color:var(--ds-ink); opacity:var(--ds-halftone-op);
}

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §4 CARDS — GAME_DESIGN_RULES §4. "One page, not widgets": every card spans the grid and
   shares the 16px gap; nothing floats alone.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-card{
  position:relative; background:var(--ds-panel); border:1px solid var(--ds-hair);
  border-radius:var(--ds-r-card); padding:var(--ds-pad); display:flex; flex-direction:column; gap:8px;
}
html[data-theme="dark"] .ds-card, html:not(.mvc-light):not([data-theme="light"]) .ds-card{
  box-shadow:0 1px 0 rgba(255,255,255,.04) inset; /* the ONLY shadow allowed: a 1px inner hairline highlight, never a glow */
}
@media (max-width:480px){ .ds-card{ padding:var(--ds-pad-sm); } }
.ds-card--raised{ background:var(--ds-panel-3); border-top-color:var(--ds-hair-2); }
.ds-card__eyebrow{ composes:ds-eyebrow; }
.ds-card__title{ font-family:var(--ds-disp); font-weight:900; font-size:clamp(17px,2vw,22px);
  letter-spacing:-.03em; line-height:1.1; margin:2px 0 0; color:var(--ds-ink); text-wrap:balance; }
.ds-card__meta{ font-size:13px; color:var(--ds-ink-2); }
.ds-card__body{ font-size:14.5px; line-height:1.5; color:var(--ds-ink-2); }
.ds-card__foot{ margin-top:8px; display:flex; gap:8px; justify-content:flex-end; }
.ds-card:hover{ background:var(--ds-panel-2); border-color:color-mix(in srgb, var(--ds-accent) 40%, var(--ds-hair)); transition:background var(--ds-dur-1) var(--ds-ease), border-color var(--ds-dur-1) var(--ds-ease); }
.ds-card--link{ cursor:pointer; }

/* THE BRIEF / OPPORTUNITY CARD — the CEO named this specifically ("graphics are weak"). Ghosted
   numeral from the case number, overlapping category+urgency stamps, a five-way fit read. */
.ds-brief{ overflow:hidden; }
.ds-brief__top{ position:relative; z-index:1; display:flex; align-items:flex-start; justify-content:space-between; gap:8px; }
.ds-brief__stamps{ display:flex; gap:6px; flex-wrap:wrap; }
.ds-brief__brand{ position:relative; z-index:1; font-family:var(--ds-disp); font-weight:900; font-size:clamp(19px,2.4vw,26px); letter-spacing:-.02em; margin:10px 0 2px; text-wrap:balance; }
.ds-brief__sector{ position:relative; z-index:1; }
.ds-brief__fee{ font-family:var(--ds-mono); font-variant-numeric:tabular-nums; font-weight:700; font-size:15px; color:var(--ds-accent-text); }
.ds-brief__line{ position:relative; z-index:1; font-size:13.5px; line-height:1.5; color:var(--ds-ink-2); margin:8px 0 0; max-width:52ch; }
.ds-brief__fit{ position:relative; z-index:1; margin-top:14px; padding-top:14px; border-top:1px solid var(--ds-hair); }
.ds-brief__fit-head{ display:flex; justify-content:space-between; align-items:baseline; margin-bottom:6px; }
.ds-brief__fit-k{ font-family:var(--ds-mono); font-size:10px; letter-spacing:.16em; text-transform:uppercase; color:var(--ds-ink-3); }
.ds-brief__fit-v{ font-family:var(--ds-mono); font-size:11px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; }
.ds-brief__fit-grid{ display:grid; grid-template-columns:repeat(5,1fr); gap:4px; margin:6px 0 4px; }
.ds-brief__fit-cell{ height:5px; border-radius:2px; background:var(--ds-panel-2); }
.ds-brief__fit-cell.is-on{ background:var(--ds-accent); }
.ds-brief__fit-legend{ display:flex; justify-content:space-between; font-family:var(--ds-mono); font-size:9px; letter-spacing:.08em; text-transform:uppercase; color:var(--ds-ink-3); }

/* THE STAMP — the system's verdict motif. Used exactly 3 places: verdict rows, confirm results,
   WahWahs/season outcomes (direction doc "the motif that makes this PitchNoir"). */
.ds-stamp{
  position:relative; display:inline-flex; align-items:center; gap:4px; padding:4px 12px 5px;
  font-family:var(--ds-mono); font-weight:700; font-size:10.5px; letter-spacing:.14em; text-transform:uppercase;
  border:1.5px solid currentColor; border-radius:2px; transform:rotate(-2deg);
  clip-path:polygon(6px 0,100% 0,100% calc(100% - 6px),calc(100% - 6px) 100%,0 100%,0 6px);
}
.ds-stamp--good{ color:var(--ds-good); }
.ds-stamp--warn{ color:var(--ds-warn); }
.ds-stamp--bad{ color:var(--ds-bad); }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §5 BUTTONS — GAME_DESIGN_RULES §5, binding ruling #3. FIVE treatments, ONE shape. No gradient,
   no bevel, no shadow, no glass, ever, in any state.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-btn{
  appearance:none; border:0; cursor:pointer; display:inline-flex; align-items:center; justify-content:center;
  gap:8px; font-family:var(--ds-disp); font-weight:700; font-size:15px; text-transform:none;
  height:44px; padding:0 20px; border-radius:6px; background:transparent; color:var(--ds-ink);
  transition:background var(--ds-dur-1) var(--ds-ease), border-color var(--ds-dur-1) var(--ds-ease), transform var(--ds-dur-1) var(--ds-ease);
}
.ds-btn:focus-visible{ outline:2px solid var(--ds-focus); outline-offset:2px; }
.ds-btn:disabled{ opacity:.4; cursor:not-allowed; }
.ds-btn--primary{ background:var(--ds-accent); color:var(--ds-on-accent); }
.ds-btn--primary:hover:not(:disabled){ filter:brightness(1.08); }
.ds-btn--primary:active:not(:disabled){ filter:brightness(.92); transform:translateY(1px); }
.ds-btn--secondary{ border:1px solid color-mix(in srgb, var(--ds-ink) 60%, transparent); color:var(--ds-ink); }
.ds-btn--secondary:hover:not(:disabled){ background:var(--ds-panel-2); }
.ds-btn--danger{ border:1px solid var(--ds-bad); color:var(--ds-bad); }
.ds-btn--danger:hover:not(:disabled){ background:color-mix(in srgb, var(--ds-bad) 15%, transparent); }
.ds-btn--danger-filled{ background:var(--ds-bad-fill); color:#fff; }
.ds-btn--danger-filled:hover:not(:disabled){ filter:brightness(1.08); }
.ds-btn--pill{ height:36px; padding:0 16px; border-radius:var(--ds-r-pill); background:var(--ds-panel-2);
  border:1px solid var(--ds-hair); color:var(--ds-ink); font-weight:600; font-size:13px; }
.ds-btn--pill:hover:not(:disabled){ background:var(--ds-hover-step); }
.ds-btn--pill.is-active{ background:var(--ds-accent); color:var(--ds-on-accent); border-color:transparent; }
.ds-btn--icon{ width:44px; height:44px; padding:0; border-radius:6px; background:var(--ds-panel-2); border:1px solid var(--ds-hair); }
.ds-btn--icon:hover:not(:disabled){ background:var(--ds-hover-step); border-color:color-mix(in srgb, var(--ds-accent) 50%, var(--ds-hair)); }
.ds-btn + .ds-btn{ margin-left:8px; }
.ds-btn--loading{ position:relative; color:transparent !important; }
.ds-btn--loading::after{ content:""; position:absolute; width:16px; height:16px; border-radius:50%;
  border:2px solid color-mix(in srgb, currentColor 30%, transparent); border-top-color:currentColor;
  color:var(--ds-on-accent); animation:ds-spin .7s linear infinite; }
@keyframes ds-spin{ to{ transform:rotate(360deg); } }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §6 LIST ROWS — GAME_DESIGN_RULES §6.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-row{ display:grid; grid-template-columns:40px 1fr auto auto; align-items:center; gap:12px;
  min-height:56px; padding:12px 16px; border-bottom:1px solid var(--ds-hair); border-left:2px solid transparent;
  transition:background var(--ds-dur-1) var(--ds-ease), border-color var(--ds-dur-1) var(--ds-ease); }
.ds-row__n{ font-family:var(--ds-mono); font-size:13px; color:var(--ds-ink-3); }
.ds-row__title{ font-family:var(--ds-body); font-weight:700; font-size:15px; color:var(--ds-ink); }
.ds-row__meta{ font-size:12.5px; color:var(--ds-ink-2); }
.ds-row__value{ font-family:var(--ds-mono); font-weight:900; font-variant-numeric:tabular-nums; text-align:right; color:var(--ds-ink); }
.ds-row:hover{ background:var(--ds-panel-2); border-left-color:var(--ds-accent); }
.ds-row.is-selected{ background:var(--ds-panel-2); border-left-color:var(--ds-accent); border-left-width:3px; }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §7 CHIPS / BADGES / COUNTS / OVR / STARS — GAME_DESIGN_RULES §7. One family.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-chip{ display:inline-flex; align-items:center; height:24px; padding:0 10px; border-radius:var(--ds-r-pill);
  font-family:var(--ds-mono); font-size:10px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  border:1px solid var(--ds-hair); color:var(--ds-ink-2); background:transparent; }
.ds-chip--accent{ background:color-mix(in srgb, var(--ds-accent) 15%, transparent); color:var(--ds-accent-text); border-color:transparent; }
.ds-chip--good{ background:color-mix(in srgb, var(--ds-good) 15%, transparent); color:var(--ds-good); border-color:transparent; }
.ds-chip--warn{ background:color-mix(in srgb, var(--ds-warn) 15%, transparent); color:var(--ds-warn); border-color:transparent; }
.ds-chip--bad{ background:color-mix(in srgb, var(--ds-bad) 15%, transparent); color:var(--ds-bad); border-color:transparent; }
.ds-badge{ display:inline-flex; flex-direction:column; align-items:center; justify-content:center; gap:1px;
  min-width:44px; padding:4px 8px; background:var(--ds-panel-2); border-radius:6px; }
.ds-badge em{ font-style:normal; font-family:var(--ds-mono); font-size:8.5px; letter-spacing:.14em; text-transform:uppercase; color:var(--ds-ink-3); }
.ds-badge b{ font-family:var(--ds-disp); font-weight:900; font-size:18px; color:var(--ds-ink); }
.ds-badge--headline{ background:var(--ds-accent); }
.ds-badge--headline em{ color:color-mix(in srgb, var(--ds-on-accent) 70%, transparent); }
.ds-badge--headline b{ color:var(--ds-on-accent); }
.ds-count{ display:inline-flex; align-items:center; justify-content:center; min-width:20px; height:20px; padding:0 6px;
  border-radius:var(--ds-r-pill); background:var(--ds-accent); color:var(--ds-on-accent); font-family:var(--ds-mono);
  font-size:10.5px; font-weight:700; }
.ds-stars{ font-family:var(--ds-mono); font-size:12px; letter-spacing:.06em; color:var(--ds-accent-text); }
.ds-stars .off{ color:var(--ds-ink-3); }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §8 BARS / METERS / CHARTS — GAME_DESIGN_RULES §8. State colour on the VALUE only.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-bar{ height:6px; border-radius:3px; background:var(--ds-panel-2); overflow:hidden; }
.ds-bar > i{ display:block; height:100%; background:var(--ds-accent); border-radius:inherit; }
.ds-bar--good > i{ background:var(--ds-good); } .ds-bar--warn > i{ background:var(--ds-warn); } .ds-bar--bad > i{ background:var(--ds-bad); }
.ds-meter-row{ display:flex; align-items:center; gap:10px; }
.ds-meter-row .ds-bar{ flex:1; }
.ds-meter-row .ds-data{ width:32px; text-align:right; }
.ds-chart-series-1{ stroke:var(--ds-accent); fill:var(--ds-accent); }
.ds-chart-series-2{ stroke:var(--ds-ink); fill:var(--ds-ink); opacity:.55; }
.ds-chart-series-3{ stroke:var(--ds-ink); fill:var(--ds-ink); opacity:.3; }
.ds-chart-series-4{ stroke:var(--ds-ink); fill:var(--ds-ink); opacity:.18; }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §9 INPUTS — GAME_DESIGN_RULES §10.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-field-label{ display:block; }
.ds-field{ display:block; width:100%; height:44px; padding:0 14px; background:var(--ds-panel-2);
  border:1px solid var(--ds-hair); border-radius:6px; color:var(--ds-ink); font-family:var(--ds-body); font-size:15px; }
.ds-field::placeholder{ color:var(--ds-ink-3); }
.ds-field:focus-visible{ outline:2px solid var(--ds-focus); outline-offset:2px; }
.ds-field--error{ border-color:var(--ds-bad); }
.ds-field-error-msg{ font-size:13px; color:var(--ds-bad); margin-top:6px; }
textarea.ds-field{ height:auto; min-height:120px; padding:12px 14px; resize:vertical; }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §10 WINDOWS / POPUPS / CONFIRM DIALOGS — GAME_DESIGN_RULES §11.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-scrim{ position:fixed; inset:0; background:var(--ds-scrim); z-index:900; }
.ds-window{ position:relative; background:var(--ds-panel); border:1px solid var(--ds-hair); border-radius:12px;
  padding:24px; max-width:560px; width:100%; z-index:901; }
.ds-window__bar{ display:flex; align-items:center; gap:6px; margin-bottom:16px; }
.ds-window__bar i{ width:10px; height:10px; border-radius:50%; background:var(--ds-panel-2); border:1px solid var(--ds-hair); }
.ds-window__eyebrow{ margin-bottom:2px; }
.ds-window__title{ font-family:var(--ds-disp); font-weight:900; font-size:clamp(17px,2vw,25px); letter-spacing:-.03em; margin:0 0 12px; }
.ds-window__body{ max-width:65ch; font-size:14.5px; line-height:1.55; color:var(--ds-ink-2); }
.ds-window__cost{ font-family:var(--ds-mono); font-weight:700; color:var(--ds-ink); margin:10px 0; }
.ds-window__foot{ margin-top:20px; display:flex; justify-content:flex-end; gap:8px; }
.ds-confirm{ text-align:center; }
.ds-confirm .ds-stamp{ margin:6px auto 14px; }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §11 EMPTY STATES
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-empty{ text-align:center; padding:48px 24px; border:1px dashed var(--ds-hair); border-radius:12px; }
.ds-empty__eyebrow{ margin:0 auto 8px; }
.ds-empty__title{ font-family:var(--ds-disp); font-weight:900; font-size:20px; letter-spacing:-.03em; margin:0 0 8px; }
.ds-empty__body{ font-size:14px; color:var(--ds-ink-2); max-width:44ch; margin:0 auto 18px; }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §12 SET-PIECE FRAMES — GAME_DESIGN_RULES §12. Full-bleed, always dark, one accent.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-setpiece{ background:#05070c; color:#eef3fa; padding:64px 24px; position:relative; overflow:hidden; }
.ds-setpiece .ds-hero, .ds-setpiece .ds-h1, .ds-setpiece .ds-h2{ color:#eef3fa; }

/* ────────────────────────────────────────────────────────────────────────────────────────────
   §13 MOTION — GAME_DESIGN_RULES §13.
   ──────────────────────────────────────────────────────────────────────────────────────────── */
.ds-reveal{ animation:ds-rise var(--ds-dur-2) var(--ds-ease) both; }
@keyframes ds-rise{ from{ opacity:0; transform:translateY(6px); } to{ opacity:1; transform:none; } }

/* ══════════════════════════════════════════════════════════════════════════════════════════════
   §14 ADOPTION LAYER — maps the game's EXISTING classes onto the tokens above.
   `!important` permitted in THIS section only; every use carries a one-line reason. This is what
   makes pn-ds.css the enforced system on play.html without a 42-file rewrite: loaded last, it
   wins the cascade on the properties named here.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* — Headings everywhere in the game: SST §5g-ii, "one face for headings AND subheadings" — the
   binding ruling this whole redesign answers to; every legacy heading class gets it unconditionally. */
h1, h2, h3,
.s1-h, .mvc-h1, .mvc-h2, .perf-hero-h1, .pv-ph, .wr-bs-brand, .nbo-name,
.rv-h, .tt-name, .kpi-lbl, .pnfc-title, .wr-mh-brand{
  font-family: var(--ds-disp) !important; /* GAME_DESIGN_RULES §2: one heading face, no legacy display/condensed override */
  font-weight: 900 !important;            /* same */
  font-style: normal !important;          /* SST §5g forbids italic/condensed in-game headings */
  text-transform: none !important;        /* kills the "THE CLIMB" caps-title offence named in SST §5g */
  letter-spacing: -.03em !important;      /* locked tracking */
}
h1, h2, h3, .s1-h, .mvc-h1, .mvc-h2, .perf-hero-h1, .pv-ph, .rv-h,
h1:not(#pn-ds-never), h2:not(#pn-ds-never), h3:not(#pn-ds-never),
.s1-h:not(#pn-ds-never), .mvc-h1:not(#pn-ds-never), .mvc-h2:not(#pn-ds-never),
.perf-hero-h1:not(#pn-ds-never), .pv-ph:not(#pn-ds-never), .rv-h:not(#pn-ds-never){
  /* CEO: "text headers are practically invisible". The plain tag/class rule above is LOW
     specificity and any scoped `.foo h2{color:…}` in the ~42-sheet legacy stack can beat it even
     loaded later — the `:not(#pn-ds-never)` id-level specificity bump (matches a never-real id, so
     always applies) guarantees this one wins regardless. */
  color: var(--ds-ink) !important;
  opacity: 1 !important;
}

/* — Buttons: collapse every legacy class onto the five treatments (binding ruling #3: "way too
   many button treatments"). Geometry only here; colour roles are assigned per existing semantic
   class where one exists (primary/secondary/danger), otherwise treated as secondary. */
.lg-btn, .pn-btn, .nbo-cta, .go, .primary, .tb-btn, button:not(.ds-btn):not([class*="close"]):not([role="switch"]){
  border-radius: 6px !important;          /* GAME_DESIGN_RULES §5: one shape, rect controls at 6px */
  box-shadow: none !important;            /* binding ruling #4: no glow/bloom/bevel/shadow on any button, any state */
  background-image: none !important;      /* binding ruling #4: no gradient fills */
  filter: none !important;                /* strip any legacy drop-shadow/glow filter */
  min-height: 44px;
}
.lg-btn--primary, .pn-btn--primary, .nbo-cta, .go--now, button.primary{
  background: var(--ds-accent) !important;
  color: var(--ds-on-accent) !important;  /* §5f ink law: on-accent ink is theme-correct, never inherited white/black */
  border: 0 !important;
}
.lg-btn--primary:hover, .pn-btn--primary:hover, .nbo-cta:hover, .go--now:hover, button.primary:hover{
  filter: brightness(1.08) !important;    /* the ONLY hover change on a filled button: no glow substitute */
}
.pn-btn--secondary, .lg-btn--secondary{
  background: transparent !important;
  border: 1px solid color-mix(in srgb, var(--ds-ink) 60%, transparent) !important;
  color: var(--ds-ink) !important;
}
.pn-btn--secondary:hover, .lg-btn--secondary:hover{
  background: var(--ds-hover-step) !important; /* GAME_DESIGN_RULES §5 hover law: a visible surface step, ~12-16% */
}
/* Ghost is deleted (binding ruling #3): any surviving .pn-btn--ghost is repainted as secondary. */
.pn-btn--ghost{
  background: transparent !important;
  border: 1px solid color-mix(in srgb, var(--ds-ink) 40%, transparent) !important;
  color: var(--ds-ink-2) !important;
}
/* Nav pills: .nbo-bb-cta and similar rounded utility/tab controls become the ONE pill treatment. */
.nbo-bb-cta, .topbar .tb-btn, .s2-htabs button{
  border-radius: var(--ds-r-pill) !important; /* pills are navigation/state only, never an action — GAME_DESIGN_RULES §5 */
  height: 36px !important;
  box-shadow: none !important;
  background-image: none !important;
}
/* Found live rebuilding web/game-design-guide.html, 2026-09-19: the plain rule above measured
   10px on the real DOM, not the 999px pill — `pn-button-unify-20260819.css`'s
   `html body[data-view] .content .hub-view :is(.nbo-bb-cta,…)` compound selector and
   `pn-type-numerals-20260913.css`'s ×5-repeated-class selectors both out-specify it even loaded
   later, same class of bug as the primary-button gradient fixed above. Same fix: the
   `:not(#pn-ds-never)` id-specificity bump. */
html:not(#pn-ds-never) body .nbo-bb-cta,
html:not(#pn-ds-never) body .topbar .tb-btn,
html:not(#pn-ds-never) body .s2-htabs button{
  border-radius: var(--ds-r-pill) !important;
  height: 36px !important;
}

/* — Rows: legacy verdict/desk rows get the hover+selected law without inverting ink. */
.rv-row, .pnfc-row{
  border-left: 2px solid transparent !important;
  transition: background var(--ds-dur-1) var(--ds-ease), border-color var(--ds-dur-1) var(--ds-ease) !important;
}
.rv-row:hover, .pnfc-row:hover{
  background: var(--ds-panel-2) !important; /* GAME_DESIGN_RULES §6: hover = surface step + accent edge, ink unchanged */
  border-left-color: var(--ds-accent) !important;
}
.rv-row .rv-row-disc, .rv-row .rv-row-title{ color: var(--ds-ink) !important; } /* row title always visible, every state */

/* — Chips/badges: kill any legacy glow/gradient fill, keep shape. */
.nbo-chip, .nbo-flag, .tt-chip, .tt-ovr{
  box-shadow: none !important;              /* binding ruling #4 */
  background-image: none !important;
  border-radius: var(--ds-r-pill) !important;
}
.tt-ovr{ background: var(--ds-panel-2) !important; color: var(--ds-ink) !important; }

/* — Bars/meters: strip gradient fills, lock the accent to one role. */
.nbo-oddsbar, .tt-bar, .kpi-strip .bar{
  background: var(--ds-panel-2) !important;
  background-image: none !important;
}
.nbo-oddsbar i, .kpi-strip .bar i{ background: var(--ds-accent) !important; background-image: none !important; }

/* — KPI strip headline number: mono tabular, no glow. */
.kpi-strip .kpi-val{
  font-family: var(--ds-mono) !important;
  font-variant-numeric: tabular-nums !important;
  text-shadow: none !important;
}

/* — Windows: the Desk pattern gets the flat panel + hairline, no glass/blur (binding ruling #4
   supersedes the earlier "Apple-Finder glass chrome" UX_RULES note where the two conflict — glass
   is explicitly forbidden by today's ruling; see DECISIONS_LOG 2026-09-19). */
.pn-window, .wr-bs, .ds-window{
  backdrop-filter: none !important;
  box-shadow: none !important;
  background-image: none !important;
}

/* — Inputs: kill any legacy glow ring, use the token focus ring. */
.onb-field, input[type="text"], input[type="number"], textarea{
  box-shadow: none !important;
}
.onb-field:focus-visible, input:focus-visible, textarea:focus-visible{
  outline: 2px solid var(--ds-focus) !important;
  outline-offset: 2px !important;
  box-shadow: none !important; /* no glow ring substitute for the outline */
}

/* — Global glow kill switch: any surviving inline/legacy box-shadow blur beyond a 1px hairline
   highlight is a defect per binding ruling #4. This catches decorative glow classes by name. */
.glow, [class*="glow"], .accent-glow, .pn-glow{
  box-shadow: none !important;
  filter: none !important;
}

/* — Ambient background "shard" decorations (mvc-os.js inline-styled from the theme tint array):
   the neon-onyx default theme's tint is magenta (rgb ~255,46,136) — binding ruling #2 "no magenta
   anywhere". Inline JS-set backgrounds beat a plain stylesheet rule, so this must be !important and
   specific enough to win. Neutralised to transparent rather than recoloured: it is pure ambience,
   never carries information, so removing it costs nothing and needs no design decision to redo it. */
html body .shard, html body .shard.floating, html body [class*="shard"]{
  background: transparent !important;
  background-image: none !important;
  box-shadow: none !important;
}

/* — The root cause, fixed at the token: mvc-os.css's default free theme (neon-onyx, NOT a paid
   Vault pack) defines --glow:255,46,136 and --p/--acc:#FF2E88 — magenta, feeding dozens of
   box-shadow/gradient "glow" consumers across mvc-os.css/pn-system.css. Binding ruling #2 ("no
   magenta anywhere") and #4 ("no glow, ever") apply to the DEFAULT product, not only to buttons.
   Repainting the token (permitted — SST: "cosmetic palettes may repaint Accent hue only through
   these tokens") kills every remaining magenta instance system-wide in one place, safely, without
   touching the dozens of individual mvc-os.css glow/shadow rules under deadline. A follow-up pass
   should remove the glow EFFECT itself (not just its colour) from mvc-os.css directly. */
html body:not([data-pn-theme="noir"]):not([data-pn-theme="deco"]){
  --glow: 249,115,22 !important;
  --p: #f97316 !important; --acc: #f97316 !important;
}
html.mvc-light body:not([data-pn-theme="noir"]):not([data-pn-theme="deco"]){
  --glow: 180,56,13 !important;
  --p: #b4380d !important; --acc: #b4380d !important;
}
/* — CEO 2026-09-19 reach pass — the 2026-08-18 fix above only ever repainted --glow/--p/--acc.
   A SIBLING token family — --accent/--accent2/--v4-acc/--v4-acc-ink/--primary/--primary-deep/
   --surface-tint/--accent-yellow — is what the campaigns grid (.s2-camp-fee .cur, .s2-camp-play)
   and the NBO card (.nbo-cta, .nbo-tape, .nbo-flag, .nbo-file b) actually read via
   `var(--accent,#f97316)`/`var(--v4-acc)`, and every per-room "NEON TOKENISATION" block in
   style-01.css (v-perf/v-nbo/v-clients, `[data-palette="neon-onyx"]`) re-pins THAT family to
   literal magenta (#FF2E88) at higher specificity than this rule reached before. A palette/theme
   desync (data-palette left on neon-onyx while the UI is showing light) turns that into magenta
   on a cream page — exactly the CEO's two screenshots. Closing the whole family here, at the same
   selector, is the one fix that holds regardless of which exact desync produced it. */
/* the PR1/2/3 "NEON TOKENISATION" blocks (style-01.css, `body[data-view="office"]
   [data-palette="neon-onyx"] .hub-view[data-view="v-perf|v-nbo|v-clients"]`) out-specify a bare
   `html body:not(...)` rule (2 attrs + a class beats 2 :not()s) — the same class of cascade trap
   this file names repeatedly elsewhere. The `:not(#pn-ds-never)` id-level bump guarantees this
   wins regardless, same fix as the primary-button gradient/pill-radius fixes above. */
html:not(#pn-ds-never) body:not([data-pn-theme="noir"]):not([data-pn-theme="deco"]){
  --accent: #f97316 !important; --accent2: #f97316 !important; --accent-yellow: #f97316 !important;
  --v4-acc: #f97316 !important; --v4-acc-ink: #14060d !important;
  --primary: #f97316 !important; --primary-deep: #b4380d !important; --surface-tint: #f97316 !important;
}
html.mvc-light:not(#pn-ds-never) body:not([data-pn-theme="noir"]):not([data-pn-theme="deco"]){
  --accent: #b4380d !important; --accent2: #b4380d !important; --accent-yellow: #b4380d !important;
  --v4-acc: #b4380d !important; --v4-acc-ink: #ffffff !important;
  --primary: #b4380d !important; --primary-deep: #7a2600 !important; --surface-tint: #b4380d !important;
}

/* — Primary button gradient fill, real defect found on render: html body .lg-btn--primary (and
   siblings) ship a two-layer radial+linear gradient at higher selector specificity than the plain
   class rule above, which lets it survive that rule despite !important. Matched at equal-or-higher
   specificity here so binding ruling #4 ("no gradient, ever, on any button, in any state") actually
   holds — measured via computed backgroundImage, not asserted from source. */
html body .lg-btn--primary, html body .lg-btn.lg-btn--primary,
html body .pn-btn--primary, html body .pn-btn.pn-btn--primary,
html body .nbo-cta, html body .go--now, html body button.primary,
html body .lg-btn--primary:hover, html body .pn-btn--primary:hover,
/* style-01.css's "true theme carve-out" (~L291) matches [data-pn-theme] on body, which is a
   HIGHER-specificity selector than the plain `html body .lg-btn--primary` above and survives it
   even loaded later — measured live (computed backgroundImage still showed the gradient after the
   plain rule). Matched at equal specificity here (same [data-pn-theme] attribute + .pn-wear class
   combinations) so the later stylesheet actually wins, per normal cascade tie-break. */
html body[data-pn-theme] .lg-btn--primary, html body[data-pn-theme].pn-wear .lg-btn--primary,
html body.mvc-light[data-pn-theme] .lg-btn--primary, html body.mvc-light[data-pn-theme].pn-wear .lg-btn--primary{
  background-image: none !important;
  background: var(--ds-accent) !important;
  color: var(--ds-on-accent) !important;
  text-shadow: none !important;
}
/* Still beaten in one more spot, measured live: per-room per-palette carve-outs like
   `body[data-view="office"][data-theme="dark"][data-palette="neon-onyx"] .hub-view[data-view]
   .lg-btn--primary` (pn-design-0817.css) — one combinatorial rule per room×theme×palette, too many
   to enumerate. `:not(#pn-ds-never)` is the standard specificity-to-ID-level bump (matches an id
   that will never exist, so it always applies) — this beats every attribute-qualified selector in
   the legacy stack in one rule instead of chasing each room/theme/palette combination. */
html body .lg-btn--primary:not(#pn-ds-never),
html body .pn-btn--primary:not(#pn-ds-never),
html body .nbo-cta:not(#pn-ds-never),
html body .go--now:not(#pn-ds-never),
html body button.primary:not(#pn-ds-never){
  background-image: none !important;
  background: var(--ds-accent) !important;
  color: var(--ds-on-accent) !important;
  text-shadow: none !important;
  border-color: transparent !important;
}
html.mvc-light body .lg-btn--primary:not(#pn-ds-never),
html.mvc-light body .pn-btn--primary:not(#pn-ds-never),
html.mvc-light body .nbo-cta:not(#pn-ds-never),
html.mvc-light body .go--now:not(#pn-ds-never),
html.mvc-light body button.primary:not(#pn-ds-never){
  background: var(--ds-accent) !important;
  color: var(--ds-on-accent) !important;
}
html.mvc-light body .lg-btn--primary, html.mvc-light body .pn-btn--primary,
html.mvc-light body .nbo-cta, html.mvc-light body .go--now, html.mvc-light body button.primary{
  background: var(--ds-accent) !important;
  color: var(--ds-on-accent) !important;
}

/* ══ ENFORCEMENT · CEO 2026-09-19 ══════════════════════════════════════════════════════════
   "Remove this awful glass/gradient button treatments" · "I hate the accent glow".
   Measured on the merged tree AFTER the adoption layer: 26 buttons still carried a glassy
   linear-gradient and cards still carried 14–38px drop shadows, from legacy sheets the
   adoption layer did not name. Depth is the surface ladder and the hairline — never light. */
html:not(#pn-ds-never) body button:not([style*="background-image"]):not([class*="icon-img"]),
html:not(#pn-ds-never) body .pn-btn, html:not(#pn-ds-never) body .lg-btn,
html:not(#pn-ds-never) body .nbo-cta, html:not(#pn-ds-never) body .nbo-bb-cta,
html:not(#pn-ds-never) body .ds-btn, html:not(#pn-ds-never) body button.primary,
html:not(#pn-ds-never) body button[class*="cta"]{
  background-image: none !important;
  backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
  text-shadow: none !important;
}
html:not(#pn-ds-never) body button, html:not(#pn-ds-never) body .card,
html:not(#pn-ds-never) body article, html:not(#pn-ds-never) body .nbo-card,
html:not(#pn-ds-never) body .lg-panel, html:not(#pn-ds-never) body .tt-card,
html:not(#pn-ds-never) body .kpi, html:not(#pn-ds-never) body .dv-card,
html:not(#pn-ds-never) body .pnfc-card, html:not(#pn-ds-never) body .wr-doss,
html:not(#pn-ds-never) body .wr-resp,
html:not(#pn-ds-never) body [class*="card"]:not(.bigmodal-card):not(.pn-window):not(.coo-card),
html:not(#pn-ds-never) body [class*="panel"]:not(.bigmodal-card):not(.pn-window):not(.coo-card){
  box-shadow: none !important;
}

/* ══ WHERE THE DESIGN SYSTEM STOPS · a component that owns its surface owns its ink ══════════
   CEO, 2026-09-20: "bad illegibility", on the WahWahs invitation on the floor.

   Two rules in this sheet were fighting one component and both were winning. The [class*="card"]
   wildcard below stripped the invitation's cream stock (its class contains the substring "card"),
   and the heading and label rules here repainted its near-black ink to the dark theme's near-white
   — so the card ended up with light ink on a dark hub in one place and, the moment the stock came
   back, near-white ink on cream in another. Both halves were "correct" in isolation. Neither knew
   the component had a palette of its own.

   A patch had already been applied to the symptom: `.pn-ww-invite-race` was added by name to both
   label lists above, which made one line legible and left the heading broken. That is the fifth
   list-shaped fix in a day, so the boundary is stated once instead:

     a component marks itself `data-pn-surface` and publishes the two inks it pins against, and
     this sheet's ink tokens take those values inside it.

   Every !important rule in this file paints `var(--ds-ink)` / `var(--ds-ink-2)`, and a custom
   property resolves AT THE ELEMENT — so they all keep applying, and all of them land on the
   component's own ink rather than the room's. A component that marks itself but publishes
   nothing is invalid at computed-value time, which makes the token inherit the room's value, i.e.
   exactly today's behaviour. Nothing is opted out by accident. */
html:not(#pn-ds-never) body [data-pn-surface],
html:not(#pn-ds-never) body [data-pn-surface] *{
  --ds-ink:   var(--pn-surface-ink);
  --ds-ink-2: var(--pn-surface-ink-2);
}

/* ══ LABEL LEGIBILITY · measured on the booted game, all nine rooms ═══════════════════════
   112 small labels/eyebrows/chips were rendering between 1.0:1 and 2.8:1 — the same
   "practically invisible" fault the CEO flagged on headers, in the label tier. Eyebrows are
   labels, not decoration: they carry the meta a player needs. Floor is ink-2, never below. */
html:not(#pn-ds-never) body .tac-eyebrow,
html:not(#pn-ds-never) body .s1-eye,
html:not(#pn-ds-never) body .room-eyebrow,
html:not(#pn-ds-never) body .nbo-bb-k,
html:not(#pn-ds-never) body .wr-bs-k,
html:not(#pn-ds-never) body .rl-hd,
html:not(#pn-ds-never) body .pnfb-tab-label,
html:not(#pn-ds-never) body .mvc-tt-lbl,
html:not(#pn-ds-never) body .kpi-lbl,
html:not(#pn-ds-never) body [class*="-eyebrow"],
html:not(#pn-ds-never) body [class*="-lbl"]:not([class*="btn"]){
  color: var(--ds-ink-2, #a9b8cc) !important; opacity: 1 !important;
}
html:not(#pn-ds-never) body.mvc-light .tac-eyebrow,
html:not(#pn-ds-never) body.mvc-light .s1-eye,
html:not(#pn-ds-never) body.mvc-light .room-eyebrow,
html:not(#pn-ds-never) body.mvc-light .nbo-bb-k,
html:not(#pn-ds-never) body.mvc-light .wr-bs-k,
html:not(#pn-ds-never) body.mvc-light .rl-hd,
html:not(#pn-ds-never) body.mvc-light .pnfb-tab-label,
html:not(#pn-ds-never) body.mvc-light .mvc-tt-lbl,
html:not(#pn-ds-never) body.mvc-light .kpi-lbl,
html:not(#pn-ds-never) body.mvc-light [class*="-eyebrow"]:not(.pnfc-act *):not([class*="pnfc-"]),
html:not(#pn-ds-never) body.mvc-light [class*="-lbl"]:not([class*="btn"]):not([class*="pnfc-"]){
  color: #4a4238 !important;
}
/* Urgency and help chips read at a glance or they are noise. */
html:not(#pn-ds-never) body .rfi-urgent,
html:not(#pn-ds-never) body .help-chip{
  color: var(--ds-ink, #eef3fa) !important; opacity: 1 !important;
  background: rgba(238,243,250,.10) !important;
}
html:not(#pn-ds-never) body.mvc-light .rfi-urgent,
html:not(#pn-ds-never) body.mvc-light .help-chip{
  color: #17130f !important; background: rgba(23,19,15,.10) !important;
}

/* The heartbeat strip below the Floor fold hardcodes a light-theme navy ink
   (rgba(11,28,48,.76)) on a dark panel — measured 1.10:1, i.e. invisible in dark. */
html:not(#pn-ds-never) body .hb-k,
html:not(#pn-ds-never) body .hb-sub,
html:not(#pn-ds-never) body .hb-v{ color: var(--ds-ink, #eef3fa) !important; }
html:not(#pn-ds-never) body .hb-sub{ color: var(--ds-ink-2, #a9b8cc) !important; }
html:not(#pn-ds-never) body.mvc-light .hb-k,
html:not(#pn-ds-never) body.mvc-light .hb-v{ color: #17130f !important; }
html:not(#pn-ds-never) body.mvc-light .hb-sub{ color: #4a4238 !important; }
/* Found live rebuilding web/game-design-guide.html, 2026-09-19: this box-shadow rule already
   covers .nbo-card (New Opportunities — the card the CEO named directly: "graphics are weak"),
   but style-01.css:4551 sets `.nbo-card{background:linear-gradient(180deg,#0e1622,#0a0f18)…}` on
   the card's OWN flat surface, not a photographic banner — a decorative gradient fill, banned by
   binding ruling #4 project-wide, that survived because the enforcement block above only named
   background-image on buttons. Extended here to the same card selector list; a child element's
   OWN inline photographic banner (`.nbo-banner`) is untouched since its class carries no "card"
   substring, so this does not strip real imagery, only the card shell's decorative fill. */
html:not(#pn-ds-never) body .card, html:not(#pn-ds-never) body article,
html:not(#pn-ds-never) body .nbo-card, html:not(#pn-ds-never) body .lg-panel,
html:not(#pn-ds-never) body .tt-card, html:not(#pn-ds-never) body .kpi,
html:not(#pn-ds-never) body .dv-card, html:not(#pn-ds-never) body .pnfc-card,
html:not(#pn-ds-never) body .wr-doss, html:not(#pn-ds-never) body .wr-resp,
/* ⛔ THE END-OF-WEEK LEDGER WENT TRANSPARENT (CEO 2026-09-19, launch weekend: "WAGES OUT",
   "CASH POSITION", "The agency" and the staff rows all read as near-invisible). MEASURED, not
   guessed: `[class*="card"]` is a substring match, and `.bigmodal-card` — the canonical dark
   window component used by toastBig/pnConfirm/the week-close ledger/CFO-hire/welcome/VC —
   contains the substring "card" though it is a WINDOW, not a decorative content card. This rule
   was nuking its `background-image` (the intentional dark glass plate every ink value in the
   ledger is pinned against — pn-desk-ledger-20260914.css) to `none`, leaving a near-transparent
   blur with nothing but whatever sat behind it showing through — which is why pale ledger ink
   read as invisible: it was never on a dark plate at all by the time this rule (loaded last,
   `!important`) got to it. `.nbo-card`'s own decorative gradient (this rule's actual named
   target) is unaffected — it is matched explicitly above, not only by the wildcard. */
/* ⛔ AND THE EXCLUSION LIST WAS THE DEFECT, AGAIN (CEO 2026-09-20: "bad illegibility").
   The WahWahs invitation on the floor is a PRINTED CARD — cream stock, gold rule, near-black ink
   — deliberately the same object in both themes ("a card is an object, it does not re-ink for the
   room", pn-wahwahs-invite-20260913.css). Its class is `.pn-ww-invite-card`, which contains the
   substring "card", so this rule stripped the cream stock to nothing and left its near-black ink
   sitting on the dark hub. Measured: #1B1A17 on rgb(27,36,47) = 1.11:1. Invisible, exactly as the
   CEO reported, and the heading above it survived only because the shell repainted it white.

   Adding `:not(.pn-ww-invite-card)` would have been the fifth name on a list that has now failed
   four separate times in one day, and the sixth component to own its own surface would break the
   same way in silence. The fact that matters is not a NAME, it is a PROPERTY of the component:
   this element pins its own ink against its own background, so stripping one without the other is
   always wrong. So the component states that about itself — `data-pn-surface` in the markup — and
   this rule reads the statement instead of keeping a register of who has made it. */
html:not(#pn-ds-never) body [class*="card"]:not(.bigmodal-card):not(.pn-window):not(.coo-card):not([data-pn-surface]),
html:not(#pn-ds-never) body [class*="panel"]:not(.bigmodal-card):not(.pn-window):not(.coo-card):not([data-pn-surface]){
  background-image: none !important;
}

/* ══ LIGHT-THEME INK CORRECTIONS · CEO: "first screen I look at and you've fucked up legibility"
   Two faults, both measured in light theme on The Floor:
   1. My label rule painted light-theme ink onto labels that sit on DARK chrome (the appbar's
      leadership pill is #161513 in both themes) — 1.85:1. Dark chrome keeps light ink always.
   2. Dark-theme ink rgb(237,241,250) was leaking onto cream surfaces (.muted, secondary button
      labels) — 1.09:1. Those take light-theme ink. */
html:not(#pn-ds-never) body.mvc-light .pnfc-board-btn,
html:not(#pn-ds-never) body.mvc-light .pnfc-board-btn *,
html:not(#pn-ds-never) body.mvc-light .pnfc-act,
html:not(#pn-ds-never) body.mvc-light .pnfc-act *{ color: #eef3fa !important; }

html:not(#pn-ds-never) body.mvc-light .muted,
html:not(#pn-ds-never) body.mvc-light p.muted,
html:not(#pn-ds-never) body.mvc-light .small,
html:not(#pn-ds-never) body.mvc-light .hub-view .muted{ color: #4a4238 !important; }
html:not(#pn-ds-never) body.mvc-light .lg-btn--secondary,
html:not(#pn-ds-never) body.mvc-light .lg-btn--secondary *,
html:not(#pn-ds-never) body.mvc-light .pn-btn--secondary,
html:not(#pn-ds-never) body.mvc-light .pn-btn--secondary *{ color: #17130f !important; }

/* Dark islands keep light ink in BOTH themes. Enumerated by measuring which elements render a
   dark own-background while the page is light: the appbar's leadership pill, the side nav, the
   device shell, the week chip and the mono/icon chips. */
/* ⛔ INK AND BED ARE PINNED TOGETHER, 2026-09-20. This list was built by MEASURING which elements
   render a dark own-background while the page is light - and that measurement was taken on The
   Floor. It is a claim about the bed, enforced only on the ink, so the moment a pill kept the rule
   and lost the background the two drifted apart and nothing caught it.

   That is exactly what happened: on the SEASON view the Desk pill renders on rgb(210,202,176), a
   cream bed, while this rule still paints rgb(238,243,250) on it - measured 1.47:1 against a 4.5
   floor. White on tan. An independent review found it in the render; I reproduced it on the booted
   game before changing anything, because a contrast claim I have not driven myself has been wrong
   before.

   The fix is not to except the season view - the next view would break it again. The two pills
   that the "dark island" claim is ABOUT now paint their own dark bed in light theme, so the ink
   this rule gives them is always true. Ink asserted without its bed is how this returns. */
html:not(#pn-ds-never) body.mvc-light .pnfc-act,
html:not(#pn-ds-never) body.mvc-light .pnfc-board-btn{ background-color:#161513 !important; }

html:not(#pn-ds-never) body.mvc-light .pnfc-act,
html:not(#pn-ds-never) body.mvc-light .pnfc-act *,
html:not(#pn-ds-never) body.mvc-light .pnfc-board-btn,
html:not(#pn-ds-never) body.mvc-light .pnfc-board-btn *,
html:not(#pn-ds-never) body.mvc-light nav.side .is-on,
html:not(#pn-ds-never) body.mvc-light .fl-week,
html:not(#pn-ds-never) body.mvc-light .fl-week *,
html:not(#pn-ds-never) body.mvc-light .dn-ico,
html:not(#pn-ds-never) body.mvc-light .rfi-mono{ color: #eef3fa !important; }

/* ══ THE REPORT PANEL · CEO 2026-09-19: "it's also fucking illegible — ensure this works" ═══
   Measured with the panel open, dark theme: .pnfb-panel renders a LIGHT grey surface
   rgb(194,199,205) while every child carries light ink rgb(238,243,251) — light on light.
   Title 1.14:1 · placeholder 1.11:1 · eyebrow 1.19:1 · empty state 1.54:1 · tab label 1.19:1.
   The feedback path is a player's only way to tell us something is broken, so it is the last
   surface that may be unreadable. Pinned to the system window treatment in both themes. */
html:not(#pn-ds-never) body #pnfb-panel,
html:not(#pn-ds-never) body .lg-panel--window{
  background: var(--ds-panel, #0b0f18) !important;
  border: 1px solid var(--ds-hair, rgba(255,255,255,.11)) !important;
  color: var(--ds-ink, #eef3fa) !important;
}
html:not(#pn-ds-never) body.mvc-light #pnfb-panel,
html:not(#pn-ds-never) body.mvc-light .lg-panel--window{
  background: #fffdfa !important; border-color: rgba(23,19,15,.12) !important; color: #17130f !important;
}
html:not(#pn-ds-never) body #pnfb-panel .lg-tb-title,
html:not(#pn-ds-never) body #pnfb-panel .pnfb-thread,
html:not(#pn-ds-never) body #pnfb-panel .lg-field{ color: var(--ds-ink, #eef3fa) !important; }
html:not(#pn-ds-never) body #pnfb-panel .pnfb-empty,
html:not(#pn-ds-never) body #pnfb-panel .pnfb-eyebrow,
html:not(#pn-ds-never) body #pnfb-panel .pnfb-offnote{ color: var(--ds-ink-2, #a9b8cc) !important; }
html:not(#pn-ds-never) body.mvc-light #pnfb-panel .lg-tb-title,
html:not(#pn-ds-never) body.mvc-light #pnfb-panel .pnfb-thread,
html:not(#pn-ds-never) body.mvc-light #pnfb-panel .lg-field{ color: #17130f !important; }
html:not(#pn-ds-never) body.mvc-light #pnfb-panel .pnfb-empty,
html:not(#pn-ds-never) body.mvc-light #pnfb-panel .pnfb-eyebrow,
html:not(#pn-ds-never) body.mvc-light #pnfb-panel .pnfb-offnote{ color: #4a4238 !important; }
/* The field and its placeholder: a player must be able to read what they are typing. */
html:not(#pn-ds-never) body #pnfb-panel .lg-field{
  background: rgba(255,255,255,.07) !important; border: 1px solid var(--ds-hair, rgba(255,255,255,.14)) !important; }
html:not(#pn-ds-never) body #pnfb-panel .lg-field::placeholder{
  color: var(--ds-ink-2, #a9b8cc) !important; opacity: 1 !important; text-transform: none !important; }
html:not(#pn-ds-never) body.mvc-light #pnfb-panel .lg-field{
  background: #efe9df !important; border-color: rgba(23,19,15,.14) !important; }
html:not(#pn-ds-never) body.mvc-light #pnfb-panel .lg-field::placeholder{ color: #6b6255 !important; }
/* The REPORT tab itself — a rail a player cannot read is a feature they never find. */
html:not(#pn-ds-never) body .pnfb-tab-label{ color: var(--ds-ink, #eef3fa) !important; }
html:not(#pn-ds-never) body.mvc-light .pnfb-tab-label{ color: #17130f !important; }
/* The leadership pill's own label, dark theme (it is a dark island in both themes). */
html:not(#pn-ds-never) body .pnfc-board-lbl,
html:not(#pn-ds-never) body .pnfc-act .pnfc-board-lbl{ color: #eef3fa !important; }
/* An eyebrow is a short label. This one is a 20-word sentence set in tracked mono caps, which is
   the hardest possible way to read a privacy notice — the one line a tester should actually read
   before they send us their screen. Body face, sentence case, ink-2. */
html:not(#pn-ds-never) body #pnfb-panel .pnfb-eyebrow{
  font-family: var(--ds-body, IS, sans-serif) !important; font-size: 12.5px !important;
  letter-spacing: 0 !important; text-transform: none !important; line-height: 1.45 !important; }
/* ══ REACH PASS · 2026-09-19 (Vero) — the two screens the adoption layer never named ═══════════
   CEO: "all this looks like old design shit — is it?" It was: the campaigns grid (v-clients,
   `.s2-camp`/`.s2-clip`) and the opportunity/NBO card (v-nbo, `.nbo-*`) use classes §14 never
   listed, so pn-ds.css never reached them. Fixed here, same cascade trick as the rest of §14.
   design/reach-2026-09-19/MEASURED.md carries the before/after render evidence. */

/* — CAPS + double-arrow buttons: GAME_DESIGN_RULES §5 — sentence case, ONE arrow, never caps.
   Text itself is fixed at the source (s2-js.js/script-01.js — "Open the work"/"Pitch it"/"Work
   it"); this kills the CSS-level text-transform:uppercase that would re-cap it regardless, and
   strips the glow/clip-path/off-pill radius `.s2-clip` carried (binding ruling #4 + §5 "one flat
   pill shape"). */
html:not(#pn-ds-never) body .s2-clip,
html:not(#pn-ds-never) body .nbo-cta{
  text-transform: none !important;
  border-radius: var(--ds-r-pill, 999px) !important;
  clip-path: none !important;
  box-shadow: none !important;
  background: var(--ds-accent, #f97316) !important;
  background-image: none !important;
  color: var(--ds-on-accent, #14060d) !important;
}
html.mvc-light:not(#pn-ds-never) body .s2-clip,
html.mvc-light:not(#pn-ds-never) body .nbo-cta{
  background: var(--ds-accent, #b4380d) !important;
  color: var(--ds-on-accent, #ffffff) !important;
}
html:not(#pn-ds-never) body .nbo-cta.off{
  background: var(--ds-panel-2, rgba(132,164,206,.12)) !important;
  color: var(--ds-ink-3, #8496ad) !important;
}
/* the campaign CTA carried a decorative drop-shadow glow the size of the button itself — binding
   ruling #4 "no glow, ever". Depth is the surface ladder, never light. */
html:not(#pn-ds-never) body .s2-clip:hover{ box-shadow: none !important; filter: brightness(1.08); }

/* — the ghosted "01" numeral over the NBO card photo: forbidden decoration (GAME_DESIGN_RULES §6
   "ghosted decorative numerals are forbidden; a rank is a small mono eyebrow"). Demoted from a
   40px italic ghost to a small mono chip, same idiom as `.ds-row__n`. */
html:not(#pn-ds-never) body .nbo-no{
  position: absolute; top: 10px; left: 10px; z-index: 4;
  font-family: var(--ds-mono, 'JetBrains Mono', monospace) !important;
  font-style: normal !important; font-weight: 700 !important; font-size: 10px !important;
  letter-spacing: .08em; color: #fff !important; opacity: 1 !important;
  background: rgba(10,16,26,.55); padding: 2px 6px; border-radius: 3px; line-height: 1.3;
}

/* — the NBO card's category chip + urgency flag + THE ROOM tape share the same top-right/bottom-
   left corners and can land in the same visual band as the (now small) rank chip — GAME_DESIGN_
   RULES §7 "a chip never overlaps copy: it lives in its own row or the reserved right column".
   Each mark gets its own reserved lane: rank top-left, category+urgency stacked top-right
   (already vertically offset in style-01.css, kept), THE ROOM tape pinned to the banner's own
   bottom-left row, clear of the rank chip above it. */
html:not(#pn-ds-never) body .nbo-tape{
  bottom: 44px; left: 12px; border-radius: 3px;
}
html:not(#pn-ds-never) body .nbo-chip,
html:not(#pn-ds-never) body .nbo-flag{
  border-radius: 3px; transform: none !important; box-shadow: none !important;
}

/* — FIT READ said the same thing five ways (GAME_DESIGN_RULES "say a thing once: fit read is ONE
   device"). `.nbo-fit-scale` (LONG SHOT / WINNABLE / LOCK) is pure textual restatement of the
   fitLabel already printed at the top of the block — hidden here; the markup itself is removed at
   source in nboCardRich() so it no longer ships to the DOM at all (see script-01.js). This rule is
   the safety net for any cached/older render. */
html:not(#pn-ds-never) body .nbo-fit-scale{ display: none !important; }

/* ══ BINDING RULING #6 · 2026-09-19 — NO CHAMFER, NO GLASS, ON ANY CONTROL ════════════════════
   CEO, verbatim: "I hate these glass and corner cut buttons — all buttons are flat and have
   rounded corners with strokes where appropriate as per other button feedback."

   THE DEFECT, NAMED. §14's adoption layer already stripped gradient, shadow and filter from every
   legacy button class — and the chamfer survived all of it, because the chamfer is not any of
   those things. It is `clip-path: polygon(...)` (pn-glass.css:221 — "THE CLIP", SST motif #5),
   and nothing in the system had ever cleared it. So the buttons kept their sliced top-right
   corner through three rounds of feedback that never mentioned clip-path by name.

   The same applies to the depth: pn-glass draws its halo and "hard shard" as ::before/::after
   PSEUDO-ELEMENTS precisely BECAUSE clip-path would clip a box-shadow on the element itself
   (its own comment says so). `box-shadow: none` on the button therefore removed nothing. Both
   pseudo-elements are killed here, at the only place that can reach them.

   THE SHAPE LAW, settled, so this is the last time it moves:
     • primary  — flat PILL (999px), filled, no border, bigger type   (ruling #3, unchanged)
     • everything else — flat ROUNDED RECT at 10px
     • a stroke wherever a control is NOT filled: that is what "strokes where appropriate" means.
       A filled button needs no outline; an unfilled one is invisible without it.
   Zero clip-path, zero gradient, zero backdrop-filter, zero shadow, in every state.
   ──────────────────────────────────────────────────────────────────────────────────────────── */

/* 1 — THE CHAMFER DIES. Every control, every legacy class, both themes. The id-level `:not(#pn-ds-
      never)` bump is what beats pn-glass.css and pn-button-unify-20260819.css, which set their
      geometry with !important; see §14's header for why that trick is load-bearing here. */
html:not(#pn-ds-never) body button,
html:not(#pn-ds-never) body .lg-btn,
html:not(#pn-ds-never) body .pn-btn,
html:not(#pn-ds-never) body .ds-btn,
html:not(#pn-ds-never) body .nbo-cta,
html:not(#pn-ds-never) body .s2-clip,
html:not(#pn-ds-never) body .tb-btn,
html:not(#pn-ds-never) body .go,
html:not(#pn-ds-never) body [role="button"],
html:not(#pn-ds-never) body input[type="submit"],
html:not(#pn-ds-never) body input[type="button"]{
  clip-path: none !important;
  -webkit-clip-path: none !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  background-image: none !important;
  box-shadow: none !important;
  border-radius: 10px !important;
}

/* 2 — THE PSEUDO-ELEMENT DEPTH DIES WITH IT. pn-glass's halo/shard/bevel are ::before and
      ::after on the button, which is why stripping box-shadow from the button changed nothing.
      Scoped to decorative layers only: a pseudo-element carrying real `content` (a caret, a
      count, a loading spinner) is left alone. */
html:not(#pn-ds-never) body button::before,
html:not(#pn-ds-never) body button::after,
html:not(#pn-ds-never) body .lg-btn::before,
html:not(#pn-ds-never) body .lg-btn::after,
html:not(#pn-ds-never) body .pn-btn::before,
html:not(#pn-ds-never) body .pn-btn::after{
  box-shadow: none !important;
  background-image: none !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  filter: none !important;
  clip-path: none !important;
  -webkit-clip-path: none !important;
}

/* 3 — PRIMARY IS THE PILL, and it is the ONLY pill. Bigger type, per ruling #3 ("flat Pill style
      but bigger type"). Filled, so no stroke. */
html:not(#pn-ds-never) body .ds-btn--primary,
html:not(#pn-ds-never) body .lg-btn--primary,
html:not(#pn-ds-never) body .pn-btn--primary,
html:not(#pn-ds-never) body button.primary,
html:not(#pn-ds-never) body .go--now,
html:not(#pn-ds-never) body .nbo-cta,
html:not(#pn-ds-never) body .s2-clip{
  border-radius: var(--ds-r-pill, 999px) !important;
  border: 0 !important;
  font-size: 16px !important;
  font-weight: 800 !important;
}

/* 4 — STROKES WHERE APPROPRIATE: every control that is NOT filled gets a visible 1px edge.
      "Quiet is not invisible" (UX_RULES §1) — an unfilled button with no stroke is a word
      floating on a panel, which is exactly what the ghost tier was and why it was deleted. */
html:not(#pn-ds-never) body .ds-btn--secondary,
html:not(#pn-ds-never) body .lg-btn--secondary,
html:not(#pn-ds-never) body .pn-btn--secondary,
html:not(#pn-ds-never) body .pn-btn--ghost,
html:not(#pn-ds-never) body .ds-btn--pill,
html:not(#pn-ds-never) body .ds-btn--icon{
  border: 1px solid color-mix(in srgb, var(--ds-ink) 55%, transparent) !important;
  background-image: none !important;
}
html:not(#pn-ds-never) body .ds-btn--danger,
html:not(#pn-ds-never) body .pn-btn--danger,
html:not(#pn-ds-never) body .lg-btn--danger{
  border: 1px solid var(--ds-bad, #e5484d) !important;
}
/* Filled danger keeps its fill and needs no stroke (ruling #3: "have some danger buttons filled red"). */
html:not(#pn-ds-never) body .ds-btn--danger-filled,
html:not(#pn-ds-never) body .pn-btn--danger-filled{
  border: 0 !important;
  background: var(--ds-bad-fill, #c1272d) !important;
  color: #fff !important;
}

/* 4b — the two unfilled controls the first gate run caught, named rather than swept into the
       selector above so the next reader knows WHY each is here.
       • `.lg-btn--ghost` — the ghost tier is deleted (ruling #3) but the CLASS is still emitted
         by the room templates, so "End the week" on The Desk was a word with no edge. Repainted
         as secondary, which is what ruling #3 said to replace ghost with.
       • `.pv-pbar` — a pitch row rendered as a <button>. It is a control, it is not filled, and
         with no stroke the pitch list read as loose text. Hairline, not the secondary edge: it
         is a row in a list, not a button in a bar. */
/* ⛔ AND THE GHOST TIER WAS LEFT BEHIND ON THE HARD PIN (audit, 2026-09-20).
   The secondary tier below (rule 3, line ~1131) was rescued by switching to `color:inherit`,
   for exactly the reason stated there: an unfilled control's ink IS its surface's ink, and
   inheriting is the only value that cannot invert. The ghost tier is the same shape of control
   and kept `var(--ds-ink)`, so in light theme `#advanceHero` - "End the week", the most-clicked
   control in the game, sitting on a dark photographic hero on every hub view - measured
   1.01:1. Not dim: gone. Same fix, same reasoning, and the border follows the ink so the edge
   cannot invert away from it either. */
/* ⛔ EXCEPT OVER THE HERO PHOTO, WHERE TRANSPARENT MEANS INVISIBLE (audit, 2026-09-20).
   "End the week" lives in .perf-hero-cta, on a photograph. pn-masthead-20260912.css §9b gives it
   a translucent legibility PLATE for exactly that reason - and this rule was overwriting the
   plate's background with `transparent !important` at id-level specificity, so the button kept
   the plate's padding and lost the plate. Measured at 390px in light theme: 1.12:1.
   Excluded by construction rather than out-shouted: adding `:not()` only RAISES this rule's
   specificity, so it cannot lose anything it currently wins, and the one element that needs its
   own bed is simply no longer claimed by it. */
html:not(#pn-ds-never) body .lg-btn--ghost:not(.perf-hero-cta *){
  border: 1px solid color-mix(in srgb, currentColor 55%, transparent) !important;
  background: transparent !important;
  color: inherit !important;
}
html:not(#pn-ds-never) body .pv-pbar{
  border: 1px solid var(--ds-hair, rgba(132,164,206,.22)) !important;
}
html:not(#pn-ds-never) body .pv-pbar:hover{
  border-color: color-mix(in srgb, var(--ds-accent) 55%, var(--ds-hair)) !important;
  background: var(--ds-hover-step, rgba(132,164,206,.12)) !important;
}

/* ══ THE LEGIBILITY FLOOR · 2026-09-19 — ONE RULE SET, NOT ONE PER SCREENSHOT ═════════════════
   CEO, in one afternoon: "vault looks like a disaster too" / "disaster on End the Week" /
   "Financials still a prime disaster" / "design crash disaster on this panel". Four screenshots,
   four rooms, ONE defect — and I had been fixing them one screenshot at a time, which is why the
   next one always arrived from somewhere I had not looked.

   MEASURED FIRST (tools/legibility_gate.mjs, the real game, both themes, eight rooms plus the
   three overlays that are not rooms and so were never in any previous sweep):
     693 readable strings below their AA floor, across 108 distinct classes.
   Worst offenders were not subtle — 1.0:1 to 1.2:1, i.e. ink the same colour as its bed.

   THE CAUSE, stated once. A theme switch repaints INK without repainting the SURFACE under it.
   Light theme hands a label dark ink; the label sits on a panel that is still dark, or still
   translucent over a dark bed, and it disappears. Neither rule is wrong on its own. The bug
   exists only in the pair, and only at render — which is why it survives every stylesheet review
   and why it has to be measured rather than read.

   Two rules below, both narrow on purpose. They set a FLOOR; they do not restyle anything that
   is already legible, and they never touch an element that carries its own opaque surface.
   ──────────────────────────────────────────────────────────────────────────────────────────── */

/* 1 — NO GHOST INK. The single largest group was ink set at very low alpha: `.pn-artlabel`
      measured rgba(24,20,12,0.14) — near-black at 14% — over a dark card, at 1.07:1, in 15
      places on one screen. Alpha below ~0.55 is not "quiet", it is absent, and UX_RULES §1 has
      said so since it was written ("quiet is not invisible"). Rather than chase each class, the
      known-faded label families are pinned to a real ink token. */
html:not(#pn-ds-never) body [class*="artlabel"],
html:not(#pn-ds-never) body .pn-where,
html:not(#pn-ds-never) body .pn-spec-none,
html:not(#pn-ds-never) body .pn-spec-cap,
html:not(#pn-ds-never) body .pn-nb-chip,
html:not(#pn-ds-never) body .pn-earn,
html:not(#pn-ds-never) body .pn-pack-price,
html:not(#pn-ds-never) body .pn-pro-line,
html:not(#pn-ds-never) body .pn-small{
  color: var(--ds-ink-2, #c3d2e6) !important;
  opacity: 1 !important;
}

/* 2 — A DARK CARD KEEPS LIGHT INK, IN BOTH THEMES. The Vault's product cards, the Floor's day
      chips and action buttons and the Season plates are dark objects by design — they do not
      become light when the page does. Light theme was handing them the page's dark ink, which is
      the 251-failure cluster the gate found in the Vault alone. This is the same law already
      applied to the dark islands on The Desk earlier today, extended to the families the sweep
      named; it is written as a list rather than a wildcard so each one is a decision. */
html:not(#pn-ds-never) body.mvc-light .pn-card--product,
html:not(#pn-ds-never) body.mvc-light .pn-card--product *,
html:not(#pn-ds-never) body.mvc-light .s2-day,
html:not(#pn-ds-never) body.mvc-light .s2-day *,
html:not(#pn-ds-never) body.mvc-light .s2-cli-cta,
html:not(#pn-ds-never) body.mvc-light .s2-scrub-wk,
html:not(#pn-ds-never) body.mvc-light .sea-plate-live,
html:not(#pn-ds-never) body.mvc-light .sea-plate-tag,
html:not(#pn-ds-never) body.mvc-light .sea-ml-no,
html:not(#pn-ds-never) body.mvc-light .sea-ww-k,
html:not(#pn-ds-never) body.mvc-light .pn-store-cur,
html:not(#pn-ds-never) body.mvc-light .pn-store-cur *,
html:not(#pn-ds-never) body.mvc-light .pn-name,
html:not(#pn-ds-never) body.mvc-light .pn-pro-name{
  color: #eef4fc !important;
}

/* 3 — and the two the sweep found at a FLAT 1.00:1, which is ink painted in its bed's own colour.
      `.A "The Floor"` and the leaderboard label are links/labels inheriting a surface colour as
      their text colour — the one case where the element is not faded, not mismatched, but
      literally invisible. Pinned to the accent and to ink respectively so both read as what they
      are: a link, and a label. */
html:not(#pn-ds-never) body a.A,
html:not(#pn-ds-never) body [class*="board-lbl"]{
  color: var(--ds-ink, #eef3fa) !important;
  opacity: 1 !important;
}

/* ══ THE GLASS BED · 2026-09-19 (second pass) — THE CAUSE UNDER 1,300 OF THE 1,993 ═══════════
   The first pass at THE LEGIBILITY FLOOR (above) fixed ink, class by class, and took 693 to 621
   on a gate that could not see what the ink was actually sitting on. Re-measured off the RENDERED
   RASTER — tools/legibility_gate.mjs was rewritten for this; its header says why the old
   computed-style composite produced confident fiction — the honest figure at full scroll depth,
   both widths, both themes, with the surfaces fixed tonight (the public leaderboard, the PN/OS
   phone and dock, the week-close ledger) finally in the sweep, is:

     92 surface-renders · 1,993 readable strings below their AA floor · 93 distinct selectors

   THE CAUSE, ONE SENTENCE: the product's full-surface panels are GLASS — `backdrop-filter:
   blur(14–30px) saturate(150–160%)` with NO background-color — so their bed is not a colour at
   all, it is a blurred photograph, and a blurred photograph averages to MID-GREY in every theme.

   Measured, `.card.pitch-wrap.vault` (the Vault) and `.bop` / `.wr-more` (the brief and the war
   room):
     • dark  theme — light ink #eef3fa … #e5e7eb on beds #a0a3a8, #b3b8bd, #b8bcc2, #bfc4ca
     • light theme — dark  ink #27221b … #0b1c30 on beds #3c3831, #413e36, #4a463c
   Both themes, the same panel, ink and bed on the SAME side of mid-grey. This is precisely the
   defect the header above names — ink repainted, surface not — except the surface here cannot be
   repainted by a theme at all, because it is whatever photograph happens to be behind it.

   It also explains the shapes the previous pass could not reach:
     • the elements with NO class. `<b> "£10k"` (159), `<i> "VALUE"` (225), `<span> "FEE"` (115)
       have nothing to select on. They do not need a selector: they inherit. Give the PANEL an ink
       and every unclassed child inside it is correct, in both themes, for free.
     • the 1.13:1 wall in the Vault. `.pn-name`, `.pn-small`, `.pn-where` were pinned to a light
       constant by the previous pass on the premise — which script-01.js states outright, at
       applyWornWorld: "the Vault (a dark boutique by design — a theme must never repaint the shop
       that sells it)" — that the Vault is dark. The premise is right and the pairing was never
       finished: the ink went light and the bed was left as blurred glass. Finished here.

   BINDING RULING #6 (above) already outlaws this: "no glass, gradient, shadow, chamfer or glow
   anywhere". It was applied to CONTROLS. The panels kept their backdrop-filter, and that is the
   whole bug. This extends the same ruling to the surfaces, which is what it said in the first
   place — so the fix is not new licence, it is the ruling reaching the rest of its own scope.
   ──────────────────────────────────────────────────────────────────────────────────────────── */

/* 1 — EVERY FULL-SURFACE PANEL IS A FLAT, OPAQUE BED THAT DECLARES ITS OWN INK.
      Opaque, because ink can only be chosen against a known ground. Declaring `color` here is the
      load-bearing half: it is what fixes the unclassed `<b>`, `<i>`, `<em>` and `<span>` children
      that no selector can reach. */
html:not(#pn-ds-never) body .card.pitch-wrap,
html:not(#pn-ds-never) body .bop,
html:not(#pn-ds-never) body .wr-more,
html:not(#pn-ds-never) body .wr-doss,
html:not(#pn-ds-never) body .s1-wrap{
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  background-image: none !important;
  background-color: var(--ds-panel-2, #10162a) !important;
  color: var(--ds-ink, #eef3fa) !important;
}
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap,
html.mvc-light:not(#pn-ds-never) body .bop,
html.mvc-light:not(#pn-ds-never) body .wr-more,
html.mvc-light:not(#pn-ds-never) body .wr-doss,
html.mvc-light:not(#pn-ds-never) body .s1-wrap{
  background-color: var(--ds-panel-2, #efe9df) !important;
  color: var(--ds-ink, #17130f) !important;
}

/* 2 — THE VAULT IS THE DARK BOUTIQUE IT SAYS IT IS, IN BOTH THEMES.
      script-01.js already excludes the Vault from the worn-world repaint for exactly this reason.
      The light ink the previous pass pinned on its cards is therefore correct — it just had no
      dark bed under it. It has one now, so the pair is whole and neither half has to be guessed
      at again. */
html:not(#pn-ds-never) body .card.pitch-wrap.vault,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault{
  background-color: #0b0f18 !important;
  color: #eef4fc !important;
  border-color: rgba(255,255,255,.12) !important;
}

/* 3 — AN UNFILLED CONTROL TAKES ITS SURFACE'S INK, NEVER A CONSTANT.
      `.pn-btn--secondary` was pinned `#17130f` in light theme and measured 1.08:1 three separate
      ways — "Free" and "TRY IT ON" in the Vault, "Best line-up" on the Desk — because all three
      sit on a DARK island inside a light page. A secondary button is by definition unfilled
      (ruling #6, "a stroke wherever a control is NOT filled"), so its ink IS its surface's ink.
      Inheriting is the only value that cannot invert: wherever the surface is right, so is this. */
html:not(#pn-ds-never) body .pn-btn--secondary,
html:not(#pn-ds-never) body .lg-btn--secondary,
html:not(#pn-ds-never) body .ds-btn--secondary,
html.mvc-light:not(#pn-ds-never) body .pn-btn--secondary,
html.mvc-light:not(#pn-ds-never) body .pn-btn--secondary *,
html.mvc-light:not(#pn-ds-never) body .lg-btn--secondary,
html.mvc-light:not(#pn-ds-never) body .ds-btn--secondary{
  color: inherit !important;
}

/* 4 — OPACITY IS NOT A LEGIBILITY DEVICE. UX_RULES §1: "quiet is not invisible."
      Rule 1 of THE LEGIBILITY FLOOR caught ink faded by ALPHA IN THE COLOUR and missed ink faded
      by `opacity` ON THE ELEMENT — the same defect in a different property, and invisible to any
      rule that only sets `color`. Measured in source: .wr-more-sub .68 · .bop-lvl-risk .62 ·
      .bop-pool .62 · .bop-hero-k .6 · .bop-sec-k .72 · .bop-who .72 · .ws-eyebrow .6 ·
      .ws-section-head .45 · .bop-team-line--none .72. Each is a LABEL carrying meaning, so each
      is restored to full strength and takes the panel's own secondary ink. */
html:not(#pn-ds-never) body .wr-more-sub,
html:not(#pn-ds-never) body .wr-more-k,
html:not(#pn-ds-never) body .wr-more-cue,
html:not(#pn-ds-never) body .bop-lvl-risk,
html:not(#pn-ds-never) body .bop-pool,
html:not(#pn-ds-never) body .bop-sec-k,
html:not(#pn-ds-never) body .bop-hero-k,
html:not(#pn-ds-never) body .bop-who,
html:not(#pn-ds-never) body .bop-team-line,
html:not(#pn-ds-never) body .bop-team-line--none,
html:not(#pn-ds-never) body .ws-eyebrow,
html:not(#pn-ds-never) body .ws-section-head,
html:not(#pn-ds-never) body .rm-role,
html:not(#pn-ds-never) body .rfi-sub,
html:not(#pn-ds-never) body .s1-rb-sub,
html:not(#pn-ds-never) body .perf-iso-sub,
html:not(#pn-ds-never) body .pv-hd-k{
  opacity: 1 !important;
  color: var(--ds-ink-2, #a9b8cc) !important;
}
html.mvc-light:not(#pn-ds-never) body .wr-more-sub,
html.mvc-light:not(#pn-ds-never) body .wr-more-k,
html.mvc-light:not(#pn-ds-never) body .wr-more-cue,
html.mvc-light:not(#pn-ds-never) body .bop-lvl-risk,
html.mvc-light:not(#pn-ds-never) body .bop-pool,
html.mvc-light:not(#pn-ds-never) body .bop-sec-k,
html.mvc-light:not(#pn-ds-never) body .bop-hero-k,
html.mvc-light:not(#pn-ds-never) body .bop-who,
html.mvc-light:not(#pn-ds-never) body .bop-team-line,
html.mvc-light:not(#pn-ds-never) body .bop-team-line--none,
html.mvc-light:not(#pn-ds-never) body .ws-eyebrow,
html.mvc-light:not(#pn-ds-never) body .ws-section-head,
html.mvc-light:not(#pn-ds-never) body .rm-role,
html.mvc-light:not(#pn-ds-never) body .rfi-sub,
html.mvc-light:not(#pn-ds-never) body .s1-rb-sub,
html.mvc-light:not(#pn-ds-never) body .perf-iso-sub,
html.mvc-light:not(#pn-ds-never) body .pv-hd-k{
  color: var(--ds-ink-2, #4a4238) !important;
}

/* 5 — TEXT OVER IMAGERY GETS A PLATE. This is where a noir aesthetic fails hardest and it is the
      one case no ink choice can win: the ground is a photograph, so it is light AND dark within
      the same word. `.pn-artlabel` measured 1.01:1 over the product shot, `.pn-artprice` 2.34:1,
      `.s2-crew-live` down to 1.05:1 over a crew mug. The house already has the answer — §14's
      `.nbo-no` is a small mark on a photo and carries its own opaque chip — so these take the
      same treatment rather than a new one. Flat, rounded, no shadow (ruling #6). */
html:not(#pn-ds-never) body .pn-art .pn-artlabel,
html:not(#pn-ds-never) body .pn-art .pn-artprice,
html:not(#pn-ds-never) body .s2-crew-mug .s2-crew-live{
  background: rgba(8,12,20,.82) !important;
  background-image: none !important;
  color: #ffffff !important;
  opacity: 1 !important;
  text-shadow: none !important;
  padding: 2px 6px;
  border-radius: 3px;
}

/* 6 — THE REPORT RAIL, PAIRED WITH THE PANEL IT OPENS. The panel above was repainted when the CEO
      called it illegible; the TAB attached to it was not, so its label was pinned light while the
      rail kept rendering a light grey (#a0a3a8 measured, dark theme) — 2.27:1, 84 strings. Same
      defect as the panel, one element short of the fix. */
html:not(#pn-ds-never) body .pnfb-tab,
html:not(#pn-ds-never) body #pnfb-tab{
  background: var(--ds-panel, #0b0f18) !important;
  background-image: none !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  border: 1px solid var(--ds-hair, rgba(255,255,255,.11)) !important;
  color: var(--ds-ink, #eef3fa) !important;
}
html.mvc-light:not(#pn-ds-never) body .pnfb-tab,
html.mvc-light:not(#pn-ds-never) body #pnfb-tab{
  background: var(--ds-panel, #fffdfa) !important;
  border-color: rgba(23,19,15,.12) !important;
  color: var(--ds-ink, #17130f) !important;
}
html:not(#pn-ds-never) body .pnfb-tab .pnfb-tab-label,
html:not(#pn-ds-never) body #pnfb-tab .pnfb-tab-label{ color: inherit !important; }

/* 7 — A LABEL NEVER OVERRIDES ITS OWN CONTROL'S INK WITH A CONSTANT.
      The leadership pill computes `color: rgb(11,13,18)` against its own porcelain fill
      rgb(238,242,247) — 17:1, correct, and theme-aware because the sheet that sets the fill sets
      the ink with it. Its child label then pinned `#eef3fa`, which is white on porcelain: 1.02:1,
      the word simply not on the screen in dark theme. Inheriting keeps the pair together whatever
      the fill becomes next. */
html:not(#pn-ds-never) body .pnfc-board-btn .pnfc-board-lbl,
html:not(#pn-ds-never) body .pnfc-act .pnfc-board-lbl{ color: inherit !important; }

/* 8 — ONE FAINT TOKEN, SET JUST UNDER THE FLOOR. Every PN/OS meta label — "Weekly burn" 4.39:1,
      "07:34 · to you" 4.25:1, the NOTE tag, the CLEAR state — reads from a single `--pn-faint`,
      and in the DARK scope that one value (#6f7f95) measures 4.25–4.39:1 against the PN/OS beds.
      The light scope was already corrected this way on 2026-07-31 (pnos.css:45) and the dark one
      never was. Lifted to #7d8da3: 5.2:1 on #171a25, same hue, nothing else moves. */
html:not(#pn-ds-never) body .pnos-win,
html:not(#pn-ds-never) body .pnos-run,
html:not(#pn-ds-never) body .pnos-phone{ --pn-faint: #7d8da3; }

/* 9 — SEMANTIC COLOUR STAYS SEMANTIC, AND STILL PASSES. Nothing critical is carried by hue alone
      here (the figure and the word say it too), but the colour is the design and is not being
      removed — it is being taken to the value that passes on the bed it is actually used on.
      Measured: `.pnos-pos` #1a8a54 on cream 3.45:1 · `.pnos-chip` #2fa46a on #3b4135 3.33:1 ·
      `.pnos-time` brand orange #f97316 on #ece9e6 2.32:1. Light theme already has house values
      that pass — --ds-good #1f7a4a and the light accent #b4380d — so they are used rather than
      inventing a second green and a second orange. */
html.mvc-light:not(#pn-ds-never) body .pnos-pos,
html.mvc-light:not(#pn-ds-never) body .pnos-lv.pnos-pos{ color: var(--ds-good, #1f7a4a) !important; }
html.mvc-light:not(#pn-ds-never) body .pnos-time{ color: var(--ds-accent, #b4380d) !important; }
html:not(#pn-ds-never) body .pnos-chip{ color: var(--ds-good, #3FB950) !important; }

/* ══ THE GLASS BED, PART 2 · THE CARD FAMILY ══════════════════════════════════════════════════
   Part 1 took 1,993 to ~1,520 by making the full-surface PANELS opaque. Re-attributing what was
   left, by walking each failing string up to the first ancestor that actually paints, says the
   rest is the same defect one level down — the CARDS are glass as well:

     82  GLASS .rfi-row            ·  76  GLASS .pn-card.pn-specimen
     38  GLASS .card.shard         ·  14  GLASS .pn-card.pn-card--product
     14  GLASS .mvc-palfloat       ·  14  GLASS .pn-card.pn-store-tile
     12  GLASS .pn-card.pn-pack-card

   Every one of those has `backdrop-filter` and no background-color, so every one of them reports
   whatever photograph is behind it, averaged to mid-grey. There is no ink that passes on mid-grey
   — that is the whole point of mid-grey — so this could never have been fixed by choosing colours,
   in either theme, which is why three rounds of choosing colours did not fix it.

   Same ruling, same remedy, one level down: BINDING RULING #6, "no glass … anywhere". A card is a
   rung on the surface ladder; depth is the ladder, never light. */
html:not(#pn-ds-never) body .pn-card,
html:not(#pn-ds-never) body .card,
html:not(#pn-ds-never) body .rfi-row,
html:not(#pn-ds-never) body .mvc-palfloat{
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  background-image: none !important;
  background-color: var(--ds-panel-2, #10162a) !important;
  color: var(--ds-ink, #eef3fa) !important;
}
html.mvc-light:not(#pn-ds-never) body .pn-card,
html.mvc-light:not(#pn-ds-never) body .card,
html.mvc-light:not(#pn-ds-never) body .rfi-row,
html.mvc-light:not(#pn-ds-never) body .mvc-palfloat{
  background-color: var(--ds-panel-2, #efe9df) !important;
  color: var(--ds-ink, #17130f) !important;
}

/* 11 — THE VAULT'S OWN CARDS STAY DARK, because the Vault stays dark (rule 2 above). A cream card
       inside the dark boutique would be legible and would also undo the thing the boutique is for.
       Declaring `color` here is again what fixes the unclassed children — `<i> "SALE"` and
       `<b> "NOT FOR"` measured at a flat 1.00:1, ink painted in precisely its own bed's colour,
       386 strings between them, and neither has a class to select. They inherit instead. */
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-card,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-card{
  background-color: #10162a !important;
  color: #eef4fc !important;
  border-color: rgba(255,255,255,.12) !important;
}

/* 12 — AND THE VAULT'S CLASSED TEXT WITH THEM. These families pin their own light-theme ink, so
       inheritance cannot reach them; with the boutique now dark in both themes they were the
       inverted half of the pair. The accent and the filled controls are deliberately NOT in this
       list — the orange is the design, and UX_RULES §1 already fixes ink on an accent fill to
       --ds-on-accent rather than white. */
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-display,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-h2,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-body,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-name,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-small,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-where,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-spec-cap,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-spec-none,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-pro-name,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-pro-line,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-nb-line,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-earn,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-store-cur,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-pack-price,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-btn--secondary,
html:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-btn--secondary *,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-display,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-h2,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-body,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-name,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-small,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-where,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-spec-cap,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-spec-none,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-pro-name,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-pro-line,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-nb-line,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-earn,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-store-cur,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-pack-price,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-btn--secondary,
html.mvc-light:not(#pn-ds-never) body .card.pitch-wrap.vault .pn-btn--secondary *{
  color: #eef4fc !important;
  opacity: 1 !important;
}

/* 13 — THE WEEK-CLOSE LEDGER'S MICRO-LABELS. `<i> "VALUE"` is 233 strings at 2.56:1 in dark and
       has no class; it reads a muted constant (#666c76) two rungs up. The ledger card is given a
       bed and an ink like every other surface here, and its unclassed inline marks take the
       system's secondary ink rather than a constant nobody can find from the element. */
html:not(#pn-ds-never) body .bigmodal-card,
html:not(#pn-ds-never) body .week-summary-inner{
  background-color: var(--ds-panel, #0b0f18) !important;
  background-image: none !important;
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  color: var(--ds-ink, #eef3fa) !important;
}
html.mvc-light:not(#pn-ds-never) body .bigmodal-card,
html.mvc-light:not(#pn-ds-never) body .week-summary-inner{
  background-color: var(--ds-panel, #fffdfa) !important;
  color: var(--ds-ink, #17130f) !important;
}
html:not(#pn-ds-never) body .bigmodal-card i,
html:not(#pn-ds-never) body .bigmodal-card em,
html:not(#pn-ds-never) body .bigmodal-card .wsl-k,
html:not(#pn-ds-never) body .bigmodal-card .wsl-cap,
html:not(#pn-ds-never) body .bigmodal-card .wsl-m span,
html:not(#pn-ds-never) body .bigmodal-card .wsl-sec span{
  color: var(--ds-ink-2, #a9b8cc) !important;
  opacity: 1 !important;
}
html.mvc-light:not(#pn-ds-never) body .bigmodal-card i,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card em,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card .wsl-k,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card .wsl-cap,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card .wsl-m span,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card .wsl-sec span{
  color: var(--ds-ink-2, #4a4238) !important;
}

/* 14 — THE APPBAR'S UNCLASSED MICRO-LABELS. `<i> VALUE / BANK / OPEN` on the appbar's own opaque
       bed, 2.55–4.28:1, 27 strings, no class between them. Structure, not class names. */
html:not(#pn-ds-never) body .appbar i,
html:not(#pn-ds-never) body .pn-urg i{
  color: var(--ds-ink-2, #a9b8cc) !important;
  opacity: 1 !important;
}
html.mvc-light:not(#pn-ds-never) body .appbar i,
html.mvc-light:not(#pn-ds-never) body .pn-urg i{
  color: var(--ds-ink-2, #4a4238) !important;
}

/* ══ THE SET-PIECES ARE DARK IN BOTH THEMES, AND PN/OS NEVER GOT THE MEMO ════════════════════
   Third measurement, after the panel and card fixes: 1,993 → 1,263 / 1,301 / 1,364 across three
   runs. What is left resolves to three more causes, and one of them is mine.

   (i) THE WEEK-CLOSE LEDGER IS A DARK SET-PIECE. Giving it a light bed in light theme — which the
       card rule above did, correctly, for an ordinary card — made it worse, because the ledger is
       authored dark throughout: `.wsl-fig` is `#fff`, `.wsl-nm` is `#eef4fc`, `.ws-row b` is
       `#eef4fc`, all hardcoded. On a light bed that is 1.01–1.08:1, ~208 strings. The ledger
       belongs with the Vault and the pitch set-piece, which script-01.js already exempts from the
       worn-world repaint because they are ART-DIRECTED, not rooms. So it keeps its dark bed in
       both themes and all that hardcoded white ink becomes correct rather than fought.

   (ii) PN/OS'S LIGHT SCOPE IS BOUND TO A CLASS NOBODY SETS. pnos.css gives the windows their ink
        through `.pnos-win.lg-light` — and in light theme the page never adds `.lg-light`, so every
        PN/OS window keeps DARK-scope ink on a light bone surface: `.pnos-cal-empty` 2.33:1,
        `.pnos-lk` 2.67:1, `<i>` 3.03:1, 198 strings. The values in that light block are already
        correct and already AA-checked (pnos.css:45, 2026-07-31) — they were simply never reached.
        Binding the same block to the page theme is the whole fix.

   (iii) MY OWN REGRESSION, named rather than quietly dropped. Rule 8 above lifted `--pn-faint` to
        #7d8da3 for the dark scope and I scoped it to `.pnos-win` unqualified, so it overrode the
        LIGHT scope's #665849 as well and pushed a set of light-theme labels under the floor. It is
        the same mistake this whole file is about — a value changed for one theme and applied to
        both — and it is corrected here, not rewritten above, so the sequence stays readable.
   ──────────────────────────────────────────────────────────────────────────────────────────── */

/* A — the ledger joins the Vault: dark in both themes, so its authored-dark ink is right. */
html.mvc-light:not(#pn-ds-never) body .bigmodal-card,
html.mvc-light:not(#pn-ds-never) body .week-summary-inner{
  background-color: #0b0f18 !important;
  color: #eef4fc !important;
  border-color: rgba(255,255,255,.12) !important;
}
html.mvc-light:not(#pn-ds-never) body .bigmodal-card i,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card em,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card .wsl-k,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card .wsl-cap,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card .wsl-m span,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card .wsl-sec span{
  color: #a9b8cc !important;
}

/* B — PN/OS's light scope, bound to the theme the page is actually in. Same values as
       pnos.css's `.lg-light` block, which is already AA-checked; only the trigger changes. */
html.mvc-light:not(#pn-ds-never) body .pnos-win,
html.mvc-light:not(#pn-ds-never) body .pnos-run,
html.mvc-light:not(#pn-ds-never) body .pnos-phone{
  --pn-ink: #241f1b; --pn-dim: #6b6157; --pn-faint: #665849;
}

/* C — and rule 8's lift applies to the DARK scope only, which is all it was ever measured for. */
html.mvc-light:not(#pn-ds-never) body .pnos-win.lg-light,
html.mvc-light:not(#pn-ds-never) body .pnos-run.lg-light,
html.mvc-light:not(#pn-ds-never) body .pnos-phone.lg-light{ --pn-faint: #665849; }

/* D — SEMANTIC COLOUR, KEPT, ON A BED IT PASSES ON. The status chip is the one place a hue is
       doing real work, so it gets its own plate rather than a desaturated green: the colour
       survives at full strength and the contrast comes from the bed, which is the same answer
       rule 5 gives to text over a photograph. The clock is not semantic — pnos.css already ruled
       that brand orange is a FILL colour and fails as small mono text — so it takes ink. */
html:not(#pn-ds-never) body .pnos-chip{
  background: rgba(8,12,20,.92) !important;
  background-image: none !important;
  color: #4ade80 !important;
  padding: 2px 6px; border-radius: 3px;
}
html:not(#pn-ds-never) body .pnos-time{ color: var(--pn-dim, #9cadc4) !important; }
html:not(#pn-ds-never) body .pnos-cal-empty{ color: var(--pn-dim, #9cadc4) !important; }
html.mvc-light:not(#pn-ds-never) body .pnos-pos,
html.mvc-light:not(#pn-ds-never) body .pnos-lv.pnos-pos{ color: #14653f !important; }

/* E — AN ACCENT NUMERAL ON A LIGHT BED TAKES THE LIGHT THEME'S ACCENT. #e2653a is the DARK
       theme's rust and measures 2.80:1 on cream; the light theme already has #b4380d for exactly
       this, at 5.9:1. The accent is not being removed — it is being used at its own theme's
       value, which is what the token is for. `.pnp2-now` is the same shape in pale blue. */
html.mvc-light:not(#pn-ds-never) body .bop-lvl-n,
html.mvc-light:not(#pn-ds-never) body .bop-eyebrow,
html.mvc-light:not(#pn-ds-never) body .wr-resp-band-n{ color: var(--ds-accent, #b4380d) !important; }
html:not(#pn-ds-never) body .pnp2-now{ color: var(--ds-ink-2, #a9b8cc) !important; opacity: 1 !important; }
html.mvc-light:not(#pn-ds-never) body .pnp2-now{ color: var(--ds-ink-2, #4a4238) !important; }

/* F — INSIDE AN ART-DIRECTED SET-PIECE, A META LABEL INHERITS. These families were pinned to the
       light theme's #4a4238 by the earlier pass, and the pitch set-piece is one of the surfaces
       that stays DARK in light theme — so they measured 1.53–2.03:1 on beds of #06080a, #0d1216,
       #26231c, #28252d. Inheriting is the only value that cannot invert again: it is whatever the
       surface decided, and every surface here now decides. Tonal hierarchy is carried by size,
       weight and letter-spacing, which these all already have — UX_RULES §1 is explicit that it
       must never be carried by a contrast the reader loses. */
html:not(#pn-ds-never) body [data-view="pitch"] .bop-sec-k,
html:not(#pn-ds-never) body [data-view="pitch"] .bop-who,
html:not(#pn-ds-never) body [data-view="pitch"] .bop-pool,
html:not(#pn-ds-never) body [data-view="pitch"] .wr-more-k,
html:not(#pn-ds-never) body [data-view="pitch"] .wr-more-sub,
html:not(#pn-ds-never) body [data-view="pitch"] .rm-role,
html.mvc-light:not(#pn-ds-never) body .bop .bop-sec-k,
html.mvc-light:not(#pn-ds-never) body .bop .bop-who,
html.mvc-light:not(#pn-ds-never) body .bop .bop-pool,
html.mvc-light:not(#pn-ds-never) body .wr-more .wr-more-k,
html.mvc-light:not(#pn-ds-never) body .wr-more .wr-more-sub,
html.mvc-light:not(#pn-ds-never) body .rm-role{
  color: inherit !important;
  opacity: 1 !important;
}

/* G — A FILLED CTA'S LABEL TAKES THE FILL'S OWN INK, NEVER WHITE. UX_RULES §1, corrected
       2026-09-19: "Ink on a brand-orange fill is the theme's --ds-on-accent token, NOT white."
       `.wr-bs-cta-label` was white and measured 1.99:1 on its own button. */
html:not(#pn-ds-never) body .wr-bs-cta-label{ color: inherit !important; }

/* H — RULE F REACHED THE LEAVES, NOT THE ROOT, SO INHERIT INHERITED NOTHING. Rule F set
   `color:inherit` on `.bop-who`/`.wr-more-k`/`.rm-role` because the pitch brief card and its
   "more" disclosure stay a dark set-piece in light theme (comment above rule F) — but neither
   `.bop` nor `.wr-more` ever declares its OWN `color`. Inherit does not know the surface is dark;
   it just takes the nearest ancestor's `color`, and with no ancestor setting one, that walk
   reaches `body`'s light-theme ink. Re-measured (light theme): `.rm-role` 1.39:1, `.wr-more-k`
   1.39:1, `.bop-who` 1.23:1, `.wr-doss-sub` 1.22:1, `.pn-btn--secondary` (inside the same card)
   1.08:1 — every one of them correctly inheriting a colour that was never set on anything above
   them. Setting the ROOT's ink, once, is what makes every existing `inherit` in rule F actually
   resolve to something. Same light ink both themes, because the card itself never changes shade
   (rule F's own comment: "stays DARK in light theme"). */
html:not(#pn-ds-never) body .bop,
html:not(#pn-ds-never) body .wr-more,
html:not(#pn-ds-never) body .wr-doss-sub{
  color: #eef4fc;
}
html:not(#pn-ds-never) body .pn-btn--secondary{
  color: inherit !important;
  border-color: currentColor !important;
}

/* I — THE WEEK-CLOSE LEDGER'S "BUILD" CONSTANT WAS A MUTED TOKEN TWO RUNGS UP FROM WHERE IT
   RENDERS. `<i>BUILD</i>` (the ledger's section-kind tag) measured 3.92:1 dark / 3.95:1 light —
   both just under the 4.5:1 floor, on a plain muted-ink variable never meant to carry a label by
   itself at that size. Given its own declared ink rather than inheriting the ambient muted tone. */
html:not(#pn-ds-never) body .week-summary-inner i,
html:not(#pn-ds-never) body .bigmodal-card i{
  color: var(--ds-ink-2, #a9b8cc) !important;
}
html.mvc-light:not(#pn-ds-never) body .week-summary-inner i,
html.mvc-light:not(#pn-ds-never) body .bigmodal-card i{
  color: var(--ds-ink-2, #4a4238) !important;
}

/* J — MORE VAULT GLASS, SAME CAUSE AS EVERY OTHER VAULT FIX TONIGHT: a card with no opaque bed,
   so its ink is fighting a blurred photograph that averages to mid-grey. `.pn-earn` ("HOW TO
   EARN") measured 1.04:1 light; the price/stat line and `.pn-small`-adjacent labels ("Stamina",
   "FEE") measured 1.08–1.13:1 on the same mid-grey. Same fix as rule set §14/§H: an opaque bed on
   the container these already sit inside via `[class*="card"]`, PLUS their own declared ink,
   because — see rule H — inherit is worthless on an element whose ancestor sets none. */
html:not(#pn-ds-never) body .pn-earn,
html:not(#pn-ds-never) body .pn-price,
html:not(#pn-ds-never) body [class*="pn-stat"] span,
html:not(#pn-ds-never) body [class*="pn-fee"] span{
  color: var(--ds-ink, #eef3fa) !important;
}
html.mvc-light:not(#pn-ds-never) body .pn-earn,
html.mvc-light:not(#pn-ds-never) body .pn-price,
html.mvc-light:not(#pn-ds-never) body [class*="pn-stat"] span,
html.mvc-light:not(#pn-ds-never) body [class*="pn-fee"] span{
  color: var(--ds-ink, #1c1611) !important;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   K — THE WAR-ROOM / PITCH-DESK CONTAINER FAMILY GETS AN ACTUAL BED.
   
   Rules H, I and J were a relapse. Each one declared `color` on a leaf and called it a fix —
   the exact "pick better ink" pattern the ROOT CAUSE entry on the board says can never work,
   because the defect was never the ink. Measured proof they did not work: the estate count was
   ~920-937 before H/I/J and 957 after. Three rules, no movement, because not one of them gave
   a container a background. Rule J's own comment even claims "an opaque bed on the container"
   and then sets nothing but `color`. This rule is what those three should have been.

   The bed is the fix: `backdrop-filter` with no `background-color` is not a surface, it is a
   blurred photograph, and a photograph averages to mid-grey in BOTH themes — so no ink colour
   can ever pass against it. 130 text-bearing containers across the stylesheets are in this
   state (21 more are scrims, which must STAY translucent and are deliberately untouched here).
   This rule takes the war-room/pitch-desk family, which the gate measures as 488 of the 957
   remaining failures — the largest single cluster.

   NOT fixed by this rule, and deliberately so: `.rfi-no` (opacity .18) and `.rfi-sub`
   (opacity .6) are decorative ghosted numerals. They fail the gate's AA floor because they are
   MEANT to be faint. Forcing them opaque would repair a number and damage the design; they are
   a false-positive class in the count, named on the board rather than silently "fixed". */
html:not(#pn-ds-never) body :is(.bop, .wr-more, .wr-bs, .wr-bs-item, .rm-card, .rfi-row, .pv-kpi, .pv-herochartbox){
  background-color: var(--ds-surface, #141a24) !important;
  background-image: none !important;
}
html.mvc-light:not(#pn-ds-never) body :is(.bop, .wr-more, .wr-bs, .wr-bs-item, .rm-card, .rfi-row, .pv-kpi, .pv-herochartbox){
  background-color: var(--ds-surface, #f4f1ea) !important;
  background-image: none !important;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   L — WITHDRAWN. An opaque bed on the glass PRIMITIVES made the estate WORSE, measured.

   The rule gave .mvc-glass / .mvc-gb / .mvc-fold / .masthead.dash-bar a flat --ds-panel bed in
   both themes. It did fix what it was aimed at (the office masthead, where .tac-eyebrow had no
   opaque ancestor for six levels) and took "v-desk · detail 0" from 40 to 32. It also took the
   whole estate from 940 to 961, because one of those primitives is an ancestor of .tier-A and
   in LIGHT theme the bed it painted came out near-black:

       .tier-A   light   ink #0c6633 (semantic green)  on bed #06080a   2.83:1   x32

   Rule K had already fixed .tier-A; this rule broke it again. Net +21, so it is withdrawn
   rather than kept with a patch on top - patching a rule that is measurably negative is how
   H, I and J happened. The masthead finding it was built on is real and stays on the board as
   an open item, to be fixed per container once the painting ancestor is identified by walking
   the DOM, which is the method that actually works.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   M — THE FLOOR NOTICE, LEGIBLE IN LIGHT. FIXED AT THE CONSUMER, NOT AT THE TOKEN.

   THE DEFECT, found by rendering the office in LIGHT and reading the screen rather than a
   selector list. "Wren Hotels want an answer on the pitch" - the line that tells the player
   there is work waiting, the most important sentence on the floor - was near-white on cream.
   Walking up from it named the cause exactly:

     0. <b>                  color rgb(243,246,249)   <- near-WHITE, in LIGHT theme
     1. <span .fl-thing__t>  color rgb(34,28,22)      <- correct dark ink

   The <b> takes `color: var(--v4-label)` (pn-design-0817.css:452). `--v4-label` is #F3F6F9,
   declared on `.hub-view` and `.pitch-wrap` and NEVER redeclared for light - one near-white
   value that both themes get. It looked right in every review because in DARK it IS right.

   ⛔ AND THE TOKEN IS THE WRONG PLACE TO FIX IT. I tried that first, twice, and MEASURED both:
   redefining --v4-label/2/3 under light for `.hub-view` + `.pitch-wrap` took the estate 940 ->
   971; narrowing it to `.hub-view` alone still took it 940 -> 969. The reason is the same in
   both directions: these containers hold BOTH light-bedded and dark-bedded regions, so one
   token value cannot serve them. Handing dark ink to everything inside put dark on dark -
   83 bare spans at 1.06:1 (#11100e on #050709) in the light-theme war room alone.

   So it is fixed where it is actually wrong: the notice line itself, which sits on the floor
   and genuinely does change ground between themes. Everything else keeps the ink it had.
   The broader token defect is real and stays OPEN on the board, to be fixed per container.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */
html.mvc-light:not(#pn-ds-never) body .fl-thing__t b,
html.mvc-light:not(#pn-ds-never) body .fl-thing__t i{
  color: inherit !important;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════
   N — THE VAULT IS A DARK BOUTIQUE IN BOTH THEMES. LIGHT THEME WAS STILL HANDING IT LIGHT INK.

   ROOT CAUSE, and it is the same one rule A already settled for the week-close ledger. The game
   states the Vault's rule in its own source: "the Vault (a dark boutique by design — a theme must
   never repaint the shop that sells it)" — script-01.js, CEO-74, PN_WEAR_VIEWS. The shop really
   is authored dark: the measured beds under these strings are #0c1019, #10162a and #11172b in
   LIGHT theme, i.e. near-black. But the light theme still resolves --ds-ink / --ds-ink-2 to their
   light values on everything inside it, so the shop paints its near-black surfaces with the ink
   meant for cream. Measured, light theme, 1440 (PN_LG_DUMP, 2026-09-20):

     .pn-eyebrow      #4a4238 on #11172b   1.80:1   x24
     .pn-earn > b     #1d1a1b on #10162a   1.04:1   x23
     .pn-price-tier   #17130f on #0c1019   1.03:1   x4
     .pn-price-coins  #17130f on #0c1019   1.03:1   x4
     .pn-price-rate   #17130f on #0c1019   1.03:1   x4
     .pn-price-note   #17130f on #0c1019   1.03:1   x1
     .pn-world-name   #17130f on #10162a   1.03:1   x2
     .pn-nb-chip      #4a4238 on #10162a   1.82:1   x3
     a (Terms of Sale) #0000ee on #0c1019  2.02:1   x2 dark + x2 light

   NOT a blanket token flip on the Vault, and that restraint is the whole lesson of rule M: the
   Vault ALSO carries cream/photographic beds (.pn-h2, .pn-where, .pn-name, .pn-spec-cap all
   measure light ink on #bdb5a3-#bfb8a5 product art), so redefining --ds-ink for the container
   would repair these 65 and break those. Only the families whose bed was MEASURED near-black are
   named. The ones on product art are a text-over-imagery problem, a different cause, left open.

   "Terms of Sale" is the browser's unstyled #0000ee — never given a colour at all — on a
   near-black bed in BOTH themes, so it takes the brand rust in both.

   MEASURED, like-for-like slice (PN_LG_ROOMS=v-desk PN_LG_THEMES=light PN_LG_WIDTHS=1440, which
   still sweeps every overlay): THE VAULT, light theme, 75 -> 36 below-floor strings. Every
   .pn-price-*, .pn-earn b, .pn-world-name and .pn-nb-chip row named above is gone from the dump
   and the live computed colour is #eef3fa. KEPT.

   ⚠ TWO HONEST CAVEATS, recorded rather than smoothed over:

   (a) .pn-eyebrow DID NOT TAKE. Its live computed colour is still rgb(74,66,56) with this rule
       in the sheet, so something later/more specific with !important is winning. And the
       narrowed re-measure shows its real bed is not #11172b at all but #7f2507 — the brand-rust
       featured-collection plate — at 1.02:1 x32. It is a rust-bed problem, not a near-black-bed
       problem, so it does not belong in this rule and is left OPEN on the board rather than
       force-won with a higher-specificity selector against a bed nobody has looked at.

   (b) THE ESTATE FIGURE IS NOISY AND MUST NOT BE READ AS A DELTA. The war room's content is
       randomised per boot (the same run prints "£11k" one time and "£10k" the next), and the
       v-desk cluster moved 433 -> 386 between two runs with no war-room change in either of
       them. Theme-only estate total went 939 -> 892 across this rule, but ~±50 of that is boot
       variance, not this rule. The Vault slice above is the measurement that means anything.
   ═══════════════════════════════════════════════════════════════════════════════════════════ */
html.mvc-light:not(#pn-ds-never) body[data-view="store"] .pn-eyebrow,
html.mvc-light:not(#pn-ds-never) body[data-view="store"] .pn-earn b,
html.mvc-light:not(#pn-ds-never) body[data-view="store"] .pn-nb-chip,
html.mvc-light:not(#pn-ds-never) body[data-view="store"] .pn-world-name,
html.mvc-light:not(#pn-ds-never) body[data-view="store"] .pn-price-tier,
html.mvc-light:not(#pn-ds-never) body[data-view="store"] .pn-price-coins,
html.mvc-light:not(#pn-ds-never) body[data-view="store"] .pn-price-rate,
html.mvc-light:not(#pn-ds-never) body[data-view="store"] .pn-price-note{
  color: #eef3fa !important;
}
html:not(#pn-ds-never) body[data-view="store"] a:not([class]){
  color: #e2653a !important;
}
