diff --git a/docs/features/theming.md b/docs/features/theming.md
index 95ef774f9..25a4aa800 100644
--- a/docs/features/theming.md
+++ b/docs/features/theming.md
@@ -1,56 +1,201 @@
# Theming — the token file, the layering, and the "born grey" fix
-2026-07-24 theme-defaults pass. Fixes the recurring complaint that new/
-un-opted-in Bootstrap components (Modal, Dropdown, Popover, Toast, the plain
-`Card.Header` cap, `Offcanvas`, table-head/table-dark, the `Form.Select`
-chevron) rendered as flat Superhero-stock grey instead of the site's own dark
-navy chrome, by giving every Bootstrap variable override ONE canonical source
-instead of a scattered literal.
+Two rounds live in this doc: the 2026-07-24 **theme-defaults pass** (fixed
+"born grey" Bootstrap components by giving every override one canonical
+token source) and the 2026-07-24 **Tokyo-11 re-theme** (swapped the palette
+itself — this is the token file's first real "retheme the site" exercise,
+and the section below on what that surfaced supersedes anything the
+theme-defaults pass implied about how easy a repaint would be).
## The token file
[`frontend/src/styles/_theme-tokens.scss`](../../frontend/src/styles/_theme-tokens.scss)
-is the single source of truth for the site's palette and corner-radius
-tiers. It defines plain SCSS variables (`$theme-*`) with no Bootstrap
-dependency — nothing in this file imports Bootstrap or Bootswatch. Every
-value is the SPEC-display-left-rail.md §D.0 binding palette (itself the
-issue #302 palette), reproduced verbatim.
+is the single source of truth for the site's palette, corner-radius,
+spacing, and type tokens. It defines plain SCSS variables (`$theme-*`) with
+no Bootstrap dependency — nothing in this file imports Bootstrap or
+Bootswatch — plus (added by the Tokyo-11 pass) a `:root { --theme-*: ...; }`
+block that re-exposes the same values as CSS custom properties for
+styled-components/inline-style call sites that can't reach a SCSS variable
+(see "Runtime CSS-custom-property bridge" below).
+
+**Current palette: "Tokyo-11"** (2026-07-24, owner ruling on the
+theme-options palette-exploration study — 12 candidate palettes compared
+under a strict-AAA contrast bar). Palette 11, "Tokyo × orange-action +
+purple-accent": a Tokyo Night dark-navy base, a warm orange action colour,
+and an additive purple accent layer, with Semi (6/8/10px) corner radii. This
+supersedes the #302/`SPEC-display-left-rail.md` §D.0 palette the token file
+previously reproduced verbatim — see that spec's own §D.0 header for a
+pointer back to this file instead of a line-by-line color sync going
+forward (its sizing/spacing rows are untouched and remain authoritative).
Colour tokens (darkest → most-raised):
-| Token | Value | What it's for |
-| ----------------------- | --------- | ------------------------------------------------------------------------------------------------ |
-| `$theme-body-bg` | `#0f2537` | page background, the darkest layer |
-| `$theme-raised-bg` | `#22303f` | floating chrome one step off the page — rail-head/artist-line, the Sources list + filter inputs |
-| `$theme-panel-bg` | `#4e5d6c` | Superhero's native `$secondary`/`$gray-600` — Card bodies, D14 seticon, secondary buttons/badges |
-| `$theme-card-header-bg` | `#4e5d6b` | one hex digit off `$theme-panel-bg`, **by design** (owner ruling, 2026-07-23) |
-| `$theme-band-bg` | `#2b3e50` | the D14 confidence-strip token |
-| `$theme-divider` | `#16202b` | every rail block boundary |
-| `$theme-text` | `#ebebeb` | body text |
-| `$theme-muted` | `#8fa0b0` | muted text/placeholder |
-| `$theme-light` | `#abb6c2` | `btn-outline-light` family, the form-select indicator chevron |
-| `$theme-primary` | `#df6919` | primary accent (Superhero-native) |
-| `$theme-primary-hover` | `#be5915` | primary accent hover |
-| `$theme-success` | `#5cb85c` | success |
-| `$theme-danger` | `#d9534f` | danger |
-| `$theme-warning` | `#ffc107` | warning |
-| `$theme-info` | `#5bc0de` | info |
-| `$theme-input-border` | `#4e5d6c` | input border colour |
-
-Corner-radius tokens (added by the owner's same-round extension —
-"a future 'rounded buttons' ruling should be a token change like
-everything else"):
-
-| Token | Value | What it's for |
-| -------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `$theme-radius-none` | `0` | no rounding, ever — kept distinct from `-base` so a future `-base` bump can't accidentally round something that must stay square |
-| `$theme-radius-sm` | `0` | Bootstrap's `-sm` tier (small buttons/inputs) |
-| `$theme-radius-base` | `0` | the sitewide default tier (buttons/cards/inputs/`-lg`) |
-| `$theme-radius-pill` | `10px` | our OWN "status-pill" component convention (D14 statepill, etc) — see the "two different pills" note below; NOT wired into Bootstrap's `$border-radius-pill` |
-
-Both token families currently equal what the site already ships (flat `0`,
-plus the existing `10px` pill exception) — introducing them changed **zero
-rendered pixels**; see Verification below.
+| Token | Value | What it's for |
+| ----------------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------- |
+| `$theme-body-bg` | `#1a1b26` | page background, the darkest layer |
+| `$theme-raised-bg` | `#24283b` | floating chrome one step off the page — rail-head/artist-line, the Sources list + filter inputs |
+| `$theme-panel-bg` | `#2f3549` | Card bodies (incl. `AutofillCollapse`'s "demoted body"), the D14 seticon, secondary buttons/badges (`$secondary`) |
+| `$theme-card-header-bg` | `#2f3548` | one hex digit off `$theme-panel-bg`, mechanically preserving the #302 "deliberately distinct" pattern (see below) |
+| `$theme-band-bg` | `#222234` | the D14 confidence-strip token ("conf" in the study) |
+| `$theme-divider` | `#16161e` | every rail block boundary |
+| `$theme-text` | `#c0caf5` | body text — 7.54:1 on panel, 9.02:1 on raised (STRICT-AAA both) |
+| `$theme-muted` | `#a3aad0` | muted text/placeholder — WCAG/APCA audit remediation (2026-07-24, PR #432); see "Checked WCAG 2.2 criteria" below |
+| `$theme-light` | = `$theme-text` | not an audited study token — aliased to text, see the token file's own comment for why |
+| `$theme-primary` | `#ff9e64` | action colour — every button (8.40:1 STRICT-AAA with the flipped ink) |
+| `$theme-primary-hover` | `darken($theme-primary, 8%)` | derived by formula this round, not a hand-picked literal |
+| `$theme-success` | `#9ece6a` | success (9.35:1 STRICT-AAA) |
+| `$theme-danger` | `#f7768e` | danger (6.46:1 — AAA-LARGE only; see its own token-file comment) |
+| `$theme-warning` | `#e0af68` | warning (8.55:1 STRICT-AAA) |
+| `$theme-info` | `#7dcfff` | NOT a study-audited token — Tokyo Night's own cyan swatch, chosen to keep the pre-existing small "link" text AAA-safe |
+| `$theme-btn-ink` | `#1a1b26` | the "btn-ink" study token — the one dark ink used on every button variant |
+| `$theme-accent` | `#bb9af7` | additive, UI-role-only — see "Accent scope boundary" below |
+| `$theme-input-border` | = `$theme-panel-bg` | now a reference, not a coincidentally-equal literal |
+
+Corner-radius tokens — **"Semi"** tier (2026-07-24 Tokyo-11 ruling; was
+"Flat"/`0` under the #302 palette):
+
+| Token | Value | What it's for |
+| -------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------- |
+| `$theme-radius-none` | `0` | no rounding, ever — kept distinct from `-base` |
+| `$theme-radius-sm` | `6px` | Bootstrap's `-sm` tier (small buttons/inputs) |
+| `$theme-radius-base` | `6px` | the sitewide default tier — buttons/inputs, including their own `-lg` variants (the study's table doesn't grow buttons past 6px) |
+| `$theme-radius-card` | `8px` | **new this round** — cards/modals/popovers/dropdowns, wired to `$border-radius-lg` |
+| `$theme-radius-pill` | `10px` | our own "status-pill" component convention (D14 statepill, etc) — unchanged; see "two different pills" below |
+
+Both radius families changed from `0`/flat to the values above this round —
+see Verification below for how that was checked (this is NOT a
+zero-rendered-pixels pass the way the original theme-defaults round was;
+radii visibly changed everywhere Bootstrap's own radius vars reach).
+
+### Spacing scale (2026-07-24 owner extension)
+
+A 4/8-based ladder — `$theme-space-1` (4px) through `$theme-space-6`
+(32px), also exposed as `--theme-space-1`…`-6`. **Future-specs-only**: this
+scale governs new spec rounds going forward. It does **not** retroactively
+re-space any shipped surface — no component was touched to make an existing
+value land on it. Some existing values are off-scale and stay that way
+until their own spec is next revised for an unrelated reason:
+
+| Surface | Shipped value | On the 4/8 ladder? | Owning spec row |
+| ------------------------- | ------------- | --------------------------------- | --------------------------------------------- |
+| D14 band padding | `8px 10px` | `8px` yes, `10px` no | `SPEC-rail-delegacy.md` §D.1 `.d14` |
+| Rail-head padding | `8px 10px` | `8px` yes, `10px` no | `SPEC-rail-delegacy.md` §D.1 `.rail-head` |
+| Filters float panel width | `440px` | n/a (a width, not a spacing step) | `SPEC-rail-delegacy.md` §D (Filters panel) |
+| `.notthis` pill padding | `2px 10px` | `2px`/`10px` no | `SPEC-editor-polish.md` §D.2 `.notthis` (EP8) |
+
+### Type ramp (2026-07-24 owner extension)
+
+`$theme-font-2xs` (10px) through `$theme-font-xl` (18px) — same
+future-specs-only rule as spacing. Several existing rail/editor microtype
+sizes (10/11/12/13/14/15px) already happen to land on these exact steps —
+that's pre-existing coincidence in the shipped `SPEC-rail-delegacy.md`/
+`SPEC-editor-polish.md` values, not this pass re-specing anything to match.
+
+## Provenance convention (added this round — the doc had none before)
+
+**Finding from this being the doc's first real "retheme the site"
+exercise**: the pre-existing instructions here said nothing about how to
+record a palette's licensing provenance when swapping it — the task that
+drove the Tokyo-11 pass had to invent the convention below rather than
+follow one. Fixed in place:
+
+Whenever `_theme-tokens.scss`'s palette values change, the file's own
+top-of-file header comment must carry a **PROVENANCE** line: the palette's
+name, license, copyright holder, and an explicit "color values only, no
+code" statement, matching this fork's [external code provenance
+policy](../../CLAUDE.md) (values may be freely referenced; code reuse needs
+a bounded-absorption path, never applies to a handful of hex constants
+anyway). Any token pulling from a source OUTSIDE the named palette itself
+(this round's `$theme-info`, drawn from the same upstream project's cyan
+swatch but not one of the study's own 10 audited tokens) gets its own
+inline comment explaining the deviation and its separate provenance
+justification, right at that variable — not just in the file header.
+
+## Accent scope boundary (new token this round)
+
+`$theme-accent` is **additive** and **UI-role-only**. It is wired to:
+
+- the D14 confidence pill (`.statepill.suggested`) and score badge
+ (`.seticon .score`)
+- the Sources list toggle's ON state (`.rail-source-toggle .toggle-on`)
+- version-selection outlines (`.mpccard-highlight`, used by both
+ `SelectVersionResults.tsx`'s grid and `CardResultSet.tsx`'s search-result
+ highlight)
+- the print sheet's selected-slot outline (`PagePreview.tsx`)
+
+It is **never** applied to small paragraph/body text — the accent purple
+clears only 6.74:1 on the D14 band surface (below strict-AAA-normal's 7:1),
+"near-strict" per the study, legible on bold pill text/borders/outlines but
+not prose. Bootstrap's global `$link-color` is **deliberately not** routed
+to accent — it stays on `$primary` (orange), matching the pre-existing
+convention several components already use `var(--bs-primary)` for
+link-styled text (`Navbar.tsx`, `Footer.tsx`, `AuthWidget.tsx`). The
+handful of small cyan "info" links elsewhere (`DisplayPage.tsx`'s identify
+panel, `SharedDeckViewer.tsx`) stay on `$theme-info` instead, precisely
+because they're small text and accent isn't AAA-safe there — see
+`$theme-info`'s own token-file comment.
+
+## Runtime CSS-custom-property bridge (added this round)
+
+Two independent, parallel bridges expose token values to code that can't
+read a SCSS `$theme-*` variable directly (styled-components/emotion
+template literals, inline `style={}` props):
+
+1. **`var(--bs-*)`** — Bootstrap 5.3's own `_root.scss` (imported via the
+ Superhero/Bootstrap core chain below) automatically emits every
+ `$theme-colors` entry (`$primary`/`$secondary`/`$success`/`$info`/
+ `$warning`/`$danger`), `$body-bg`/`$body-color`, and the
+ `$border-*`/`$border-radius*` family as `--bs-*` custom properties, all
+ generated FROM the SCSS variables `styles.scss` sets from tokens. As
+ soon as `styles.scss` assigns `$primary: $theme-primary` (etc — see that
+ file's own "Tokyo-11: $theme-colors overrides" comment), every
+ `var(--bs-primary)` consumer picks up the new value automatically, with
+ zero additional wiring — this is true for `Navbar.tsx`/`Footer.tsx`/
+ `AuthWidget.tsx`'s existing links AND for every react-bootstrap
+ component's own internals (Bootstrap 5.3's `button-variant()` mixin
+ itself emits `--bs-btn-bg`/`--bs-btn-color`/etc scoped per `.btn-*`
+ rule, then reads them back via `var()` — so `.btn-primary`'s actual
+ paint IS already runtime-var-driven, not a compiled-in literal).
+2. **`var(--theme-*)`** — `_theme-tokens.scss`'s own `:root` block (see
+ that file) for the tokens Bootstrap has no equivalent for: raised-bg,
+ card-header-bg, band-bg, divider, muted, light, accent (+ its `-rgb`
+ triplet), btn-ink, primary-hover, the radius-card/spacing tokens.
+ Deliberately NOT duplicated here even though a component-friendly name
+ would be nice: `--bs-body-bg`/`--bs-body-color` (+`-rgb`)/`--bs-primary`
+ (+`-rgb`)/`--bs-secondary` (+`-rgb`, this literally IS
+ `$theme-panel-bg`)/`--bs-success`/`--bs-danger`/`--bs-warning`/
+ `--bs-info` (each +`-rgb`) already exist via bridge 1 — a second
+ `--theme-*` copy of any of those would be a second source of truth for
+ the same value, exactly what this bridge exists to prevent. Component
+ code reaches for `var(--bs-secondary)` (not a hypothetical
+ `--theme-panel-bg`) for the panel role, `var(--bs-body-color-rgb)` (not
+ a hypothetical `--theme-text-rgb`) for text-tinted translucent borders,
+ and so on.
+
+**Scope discipline**: this is a bridge, not a migration. It makes the
+_existing_ `--bs-*` surface Bootstrap already emits reachable and correct,
+and adds the _missing_ `--theme-*` surface for tokens with no Bootstrap
+equivalent — it does not convert every component's compiled SCSS/literal
+CSS onto `var()` wholesale. Concretely:
+
+- Component-local styled-components/inline styles compile to literal
+ colour values UNLESS they explicitly reference `var(--bs-*)` or
+ `var(--theme-*)` (this pass converted the surfaces enumerated in "What
+ this pass swept," below — not every hex literal that ever existed
+ sitewide).
+- **Known potential-divergence surface, flagged rather than converted**:
+ `custom.css`'s `.mpccard-highlight`/`.mpccard-hover` box-shadow glow uses
+ `rgba(var(--theme-accent-rgb), .6)`/`rgba(var(--bs-body-color-rgb), .3)` —
+ runtime-var-driven. If a future change ever needs those two effects to
+ differ in intensity or hue from the raw accent/text tokens (e.g. a
+ "selected" glow that should stay orange even after an accent-repainting
+ re-theme), that divergence would have to be an explicit new token, not a
+ silent literal edit to this file, since the whole point of the bridge is
+ that editing `_theme-tokens.scss` is enough.
+- Bootstrap's OWN internal component CSS (buttons, the border/radius
+ family) is inherently kept in sync by mechanism 1 above without any
+ fork-side literal to maintain at all — there is no divergence risk there
+ by construction.
## The layering
@@ -61,8 +206,13 @@ rule throughout: **every Bootstrap variable override assigns from a
1. `@import "theme-tokens";` — pulls in every `$theme-*` variable above.
2. A block of plain SCSS variable assignments (`$dark`, `$input-bg`,
- `$card-cap-bg`, `$modal-content-bg`, `$border-radius`, `$btn-border-radius`,
- etc.), each set to a `$theme-*` token.
+ `$card-cap-bg`, `$modal-content-bg`, `$border-radius`,
+ `$btn-border-radius`, `$primary`/`$secondary`/`$success`/`$danger`/
+ `$warning`/`$info`, `$body-bg`/`$body-color`, `$color-contrast-dark`,
+ `$min-contrast-ratio`, `$focus-ring-color`/`$focus-ring-opacity`/
+ `$focus-ring-width`, `$btn-close-width`/`$btn-close-color`, etc.), each
+ set to a `$theme-*` token (or, for the last two focus-ring inputs, a
+ literal opacity/width that isn't itself a palette colour).
3. `@import "~bootswatch/dist/superhero/variables";` then the Bootstrap
core imports.
@@ -71,23 +221,107 @@ own defaults with SCSS's `!default` flag — a variable already assigned
(step 2) is left alone by every later `!default` assignment downstream,
Bootswatch's or Bootstrap core's alike. This is the same mechanism the
pre-existing `$dark`/`$input-bg`/`$input-color` overrides (issue #302) always
-used; this pass just extends it and gives it one shared source file instead
-of inline literals.
+used; the theme-defaults pass extended it to the "born grey" components, and
+the Tokyo-11 pass extends it again to `$primary`/`$secondary`/`$success`/
+`$danger`/`$warning`/`$info`/`$body-bg`/`$body-color` — under #302 these
+happened to already equal Superhero's own stock defaults, so no override was
+ever needed; Tokyo-11 diverges from every one of them, surfacing the gap.
**To retheme the site**, edit `_theme-tokens.scss` only — every consumer
-(the Bootstrap variable overrides in `styles.scss`, and any fidelity spec
-asserting one of these values, see below) is meant to pick the new value up
-automatically or need a one-line spec update, never a hunt through component
-files for a scattered literal.
+(the Bootstrap variable overrides in `styles.scss`, the `var(--bs-*)`/
+`var(--theme-*)` runtime bridge, and any fidelity spec asserting one of
+these values, see below) is meant to pick the new value up automatically or
+need a one-line spec update, never a hunt through component files for a
+scattered literal. The Tokyo-11 pass is the first real test of that claim —
+see "What this pass swept" below for what still needed a manual per-file
+edit versus what really was free.
+
+## AAA contrast policy (owner-ruled 2026-07-24) + accessibility checklist
+
+**Contrast**: two thresholds, from the theme-options study's own AAA
+policy block, apply to every colour pairing in this palette:
+
+- **AAA-normal, 7:1** — required for body/paragraph/small text (anything
+ under ~18px or non-bold).
+- **AAA-large, 4.5:1** — the floor for large/bold UI text (buttons, pills,
+ headings).
-## The "born grey" inventory this pass fixed
+A pairing is **STRICT-AAA-everywhere** only when it clears 7:1 even on
+button/pill text. Tokyo-11 clears strict-AAA on body text (7.54/9.02:1) and
+on 3 of 4 button variants (primary 8.40, success 9.35, warning 8.55); danger
+(6.46:1 with the mandated `$theme-btn-ink`) is the one AAA-large-only
+exception — see `$theme-danger`'s own token-file comment. **APCA is
+advisory only** — this fork's binding bar is the WCAG 2.x relative-luminance
+ratio above, computed the same way the study computed it (worst-case
+surface, sRGB relative luminance); APCA numbers aren't tracked or asserted
+anywhere in this codebase today.
+
+**Checked WCAG 2.2 criteria** (a parallel audit, PR #432's report, is the
+enforcement pass for these — this doc states the policy, that audit is where
+conformance against it gets measured and tracked; cross-link from here
+rather than duplicating its findings). Four concrete fixes from that
+report's theme-layer findings were folded into this same pass:
+
+- **Target size (2.5.8, ≥ 24×24 CSS px)**:
+ - `.btn-close` (modal/offcanvas/toast dismiss ×, ~15 mounts sitewide)
+ measured a stock 21×21 — `$btn-close-width` lifted to `25px` in
+ `styles.scss`, a single theme-layer fix covering every mount. Self-
+ verified via a real `boundingBox()` read in the fidelity spec (issue
+ #434's own lesson: authored CSS isn't proof of the rendered size), not
+ just the authored variable.
+ - `.fbtoggle .btn` (the rail-head Front/Back segmented toggle,
+ `DisplayPage.tsx`) measured 51–55×23px, 1px under the floor with no
+ qualifying spacing exception — owner-ruled amendment (2026-07-24):
+ `min-height: 24px` added (padding unchanged, so the segment's visual
+ density doesn't change). Self-verified the same way (real
+ `boundingBox()`, not authored CSS).
+- **Focus visible / non-text contrast (2.4.7/2.4.11/1.4.11, ≥ 3:1)**:
+ Bootstrap's default focus ring (`rgba($primary, .25)`) measured
+ 1.18–1.56:1 against this theme's dark surfaces — a 25%-alpha ring's real
+ contrast depends on what's underneath it, which is exactly how it failed.
+ Fixed at the single shared `$focus-ring-*` variable family every
+ focus-visible surface sitewide derives from (`styles.scss`):
+ `$focus-ring-color: $theme-accent` (not `$primary` — a distinct UI-signal
+ colour, consistent with accent's UI-role contract) and
+ `$focus-ring-opacity: 1` (fully opaque, so the ring's contrast is simply
+ accent-vs-background, not blended with whatever's under a translucent
+ ring). Accent measures 7.39/6.30/5.26:1 against body/raised/panel — every
+ one clears 3:1 with real margin even on the toughest surface (panel).
+ `$focus-ring-width` bumped `0.25rem` (4px) → `2px` for a crisper outline.
+ Self-verified in the fidelity spec via the real `--bs-focus-ring-color`
+ custom property Bootstrap emits, computed against panel.
+- **Reduced motion**: `@media (prefers-reduced-motion: reduce)` disables
+ transitions/animations — already the convention this codebase's other
+ animated surfaces (e.g. `WhatsThatWords.tsx`) follow; this pass didn't
+ add any new motion.
+
+**WCAG-vs-APCA divergence note** (same audit): the audit computed
+`$theme-muted` (originally `#8c94bf`) at APCA Lc 44.1 on body — adequate
+only for large/bold text, but this token backs 10–13px legends/captions
+sitewide. Lightened to `#a3aad0` rather than split into a second
+"`-strong`" token (most call sites genuinely are small captions, and one
+token is simpler to keep in sync than auditing every call site's own
+font-weight/size to route it correctly). APCA itself isn't computable in
+this repo's toolchain (no APCA library available in this environment), so
+the remediation was verified via the WCAG proxy instead: contrast lifted
+from 5.78/4.93/4.11 (body/raised/panel) to 7.50/6.39/5.34 — now clears
+strict-AAA-normal on body, comes within a hair of it on raised, and clears
+AA-normal (4.5) with real margin even on panel. This is the fork's standing
+APCA policy going forward: **APCA numbers from an external audit are
+treated as directional signal, remediated via the WCAG relative-luminance
+formula this codebase can actually compute** — a real APCA library would
+need to land in this repo before APCA itself could become a binding,
+self-verified metric here.
+
+## The "born grey" inventory the 2026-07-24 theme-defaults pass fixed
Superhero's own `_variables.scss` sets several component defaults straight
-to `$gray-600` (`#4e5d6c`) — correct for the "panel" role (`$theme-panel-bg`
-is the _same_ value, deliberately), but wrong for surfaces that are
-conceptually "raised chrome" floating above the page, which should read
-`$theme-raised-bg` (`#22303f`) instead. Nothing had ever routed these away
-from the Superhero stock value, so every consumer was "born grey":
+to `$gray-600` (`#4e5d6c`, the #302 palette's own panel colour) — correct
+for the "panel" role under #302 (`$theme-panel-bg` was the _same_ value,
+deliberately), but wrong for surfaces that are conceptually "raised chrome"
+floating above the page, which should read `$theme-raised-bg` instead.
+Nothing had ever routed these away from the Superhero stock value, so every
+consumer was "born grey":
| Bootstrap variable | Was | Now | Real surfaces affected |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
@@ -100,16 +334,21 @@ from the Superhero stock value, so every consumer was "born grey":
| `$form-select-indicator-color` | `$gray-800` (Bootstrap core default, near-black) | `$theme-light` | the `
` chevron (e.g. Print Options' bleed-override select) — was nearly invisible against the dark `$input-bg` |
| the standalone `.offcanvas { background-color: $secondary; }` rule | a literal `$secondary` override, predating Superhero, that had started actively fighting the theme | removed — `$offcanvas-bg-color` now explicitly set to `$theme-body-bg` before the Superhero import, same pattern as the rest | the left/right rail's own outer `Offcanvas` chrome |
-**What's grey on purpose — do not "fix" this.** `$theme-panel-bg` (`#4e5d6c`)
-is Superhero's native `$secondary`/`$gray-600`, and it's the _correct,
-approved_ token for: Bootstrap Card bodies (including
-`AutofillCollapse`'s "demoted body" — SPEC-display-left-rail.md §D.1
-explicitly locks this to `#4e5d6c`), the D14 seticon, and `variant="secondary"`
-buttons/badges. `$card-bg` itself was deliberately left untouched by this
-pass — routing it to `$theme-raised-bg` would have been a **fidelity
-regression** against that exact spec-locked row. If a surface still looks
-grey after this pass, check whether it's one of these approved-panel
-surfaces before assuming it's a leftover default.
+**What's grey on purpose — do not "fix" this.** `$theme-panel-bg`
+(`#2f3549` under Tokyo-11; was `#4e5d6c`, Superhero's native
+`$secondary`/`$gray-600`, under #302) is the _correct, approved_ token for:
+Bootstrap Card bodies (including `AutofillCollapse`'s "demoted body" —
+`SPEC-display-left-rail.md` §D.1 explicitly locked this role, just not this
+exact hex any more), the D14 seticon, and `variant="secondary"`
+buttons/badges. Under #302 this token happened to equal Superhero's own
+`$gray-600` stock default, so no explicit `$secondary` override was needed;
+Tokyo-11 breaks that coincidence, so `styles.scss` now explicitly sets
+`$secondary: $theme-panel-bg` to keep this exact role intact under the new
+palette (see that file's own "Tokyo-11: $theme-colors overrides" comment).
+`$card-bg`itself remains deliberately untouched by either pass — routing it to`$theme-raised-bg` would be a fidelity regression against the
+spec-locked Card-body role. If a surface still looks grey/wrong after a
+retheme, check whether it's one of these approved-panel surfaces before
+assuming it's a leftover default.
**Two different pills.** Bootstrap's own `$border-radius-pill` (default
`50rem`, a true stadium shape) already backs a real, unrelated pill usage —
@@ -117,7 +356,8 @@ e.g. `SearchSettings.tsx`'s ``. `$theme-radius-pill` (`10px`)
is our own separate "status-pill" component convention (D14 `.statepill`,
etc.), applied as a literal at each call site, never through Bootstrap's
pill variable. Wiring `$border-radius-pill` to `10px` would have visibly
-reshaped the `` — deliberately not done.
+reshaped the `` — deliberately not done, and still not done by
+the Tokyo-11 radius pass.
**Known gaps, deliberately out of scope this round:**
@@ -135,6 +375,21 @@ reshaped the `` — deliberately not done.
codebase today (`AutofillTable.tsx`/`SourceSettings.tsx` both use the
plain, un-variant table) — future-proofing only, verified inert today.
+## What this pass swept (Tokyo-11, 2026-07-24)
+
+Beyond the token file and `styles.scss` wiring above, this pass migrated
+every hardcoded old-#302-palette hex literal it found outside the token
+file, across: `DisplayPage.tsx`, `SelectVersionResults.tsx`,
+`SourcesAccordion.tsx`, `ConfidenceElement.tsx`, `RequestedPrintingBadge.tsx`,
+`SlotActionsSection.tsx`, `Card.tsx`, `AutofillCollapse.tsx`,
+`PagePreview.tsx`, `custom.css`, `SourceSettings.tsx`,
+`SharedDeckViewer.tsx`, `AuthWidget.tsx` — each hex replaced with the
+matching `var(--bs-*)`/`var(--theme-*)` reference (or, in `ConfidenceElement.tsx`'s
+case, none needed — its only colour literals were danger-tint values now
+sourced from `var(--theme-danger-rgb)`). `Footer.tsx`/`Navbar.tsx` already
+used `var(--bs-primary)` before this pass (pre-existing precedent this
+pass's bridge extends) and needed no changes.
+
## Relationship to the fidelity specs
`DisplayLeftRailFidelity.spec.ts` asserts real `getComputedStyle` values for
@@ -142,11 +397,13 @@ several of these same tokens (e.g. the divider colour, the D14 band, the
AutofillCollapse header hex). Those assertions are the actual literal
values, not variable references — so **retheming means updating both**
`_theme-tokens.scss` and the relevant spec-file literal in the same change,
-never just one. This pass changed zero of those literals (see Verification),
-because every value it introduced already equalled what those specs already
-enforced.
+never just one. The Tokyo-11 pass updated every one of that spec's colour
+assertions to the new computed `rgb()`/`rgba()` values (comment-linked
+per-row to this ruling) — see that spec file's own header comment for the
+full list and docs/features/theming.md's Verification section below for how
+they were re-derived.
-## Verification (2026-07-24 pass)
+## Verification (2026-07-24 theme-defaults pass)
- `DisplayLeftRailFidelity.spec.ts` (11 tests) and `DisplayPage.spec.ts`
(33 tests) — both pass unchanged, before and after the corner-radius
@@ -162,3 +419,19 @@ enforced.
after, at the same coordinate in both. `variant="secondary"` buttons in
the same screenshots (e.g. "Close Without Saving") are pixel-identical
before/after, confirming the approved-panel surfaces weren't touched.
+
+## Verification (2026-07-24 Tokyo-11 pass)
+
+- Self-measured computed-style token-conformance extraction (Playwright,
+ 1400px and 390px viewports): every sampled body/raised/panel/band/
+ divider/text/primary/success/danger/warning/accent/btn-ink/radius surface
+ matched its binding hex/px value — see the PR body for the exact mismatch
+ count and sample list.
+- Contrast spot-assertions added to the fidelity spec for the AAA pairs
+ (text-on-panel, btn-ink-on-primary) — both pass their asserted thresholds.
+- `DisplayLeftRailFidelity.spec.ts` and `DisplayPage.spec.ts` — full suites
+ green with the updated literal values (colours/radii) described above.
+- `npx tsc --noEmit`, `npm test` (jest), `npx prettier@2.7.1 --check`,
+ `next build` — see the PR body for pass/fail and counts.
+- Screenshots: `/display` editor at 1400px and 390px, a modal, and the
+ `/print` page — paths in the PR body.
diff --git a/docs/proposals/mockups/proposal-h/SPEC-display-left-rail.md b/docs/proposals/mockups/proposal-h/SPEC-display-left-rail.md
index 84e9b0f71..191e5e3d3 100644
--- a/docs/proposals/mockups/proposal-h/SPEC-display-left-rail.md
+++ b/docs/proposals/mockups/proposal-h/SPEC-display-left-rail.md
@@ -183,6 +183,15 @@ Colours are the §D.0 #302 tokens.
### D.0 Palette tokens (all inherited — `styles.scss` #302 + Superhero)
+> **SUPERSEDED (2026-07-24, owner ruling — "Tokyo-11" re-theme).** The
+> table below is the #302 palette this section originally shipped with; it
+> is kept verbatim here as a historical record of what shipped at the time
+> this spec's other rows (sizing/spacing/I-N markers) were written and
+> verified against. It no longer reflects the site's actual colours. The
+> live, binding palette is [`docs/features/theming.md`](../../../features/theming.md)
+> — edit `_theme-tokens.scss` there, not this table, for any future
+> retheme.
+
| Token | Value | Token | Value |
| ---------------- | ------------------ | ------------------ | --------------------- |
| body bg | `#0f2537` | primary (hover) | `#df6919` (`#be5915`) |
diff --git a/frontend/src/components/AutofillCollapse.tsx b/frontend/src/components/AutofillCollapse.tsx
index 0a4f2f974..41debf099 100644
--- a/frontend/src/components/AutofillCollapse.tsx
+++ b/frontend/src/components/AutofillCollapse.tsx
@@ -111,7 +111,7 @@ export function AutofillCollapse({
// the two tokens are one hex digit apart by design, not by accident. Do not "fix"
// this back to `#4e5d6c` again; see SPEC-display-left-rail.md §D.0 for the explicit
// note distinguishing them.
- backgroundColor: headerBackground ?? "#4E5D6B",
+ backgroundColor: headerBackground ?? "var(--theme-card-header-bg)",
zIndex: zIndex + 1,
cursor: "pointer",
...(headerPadding != null ? { padding: headerPadding } : {}),
diff --git a/frontend/src/features/card/Card.tsx b/frontend/src/features/card/Card.tsx
index 01316d9a2..eb94cce4d 100644
--- a/frontend/src/features/card/Card.tsx
+++ b/frontend/src/features/card/Card.tsx
@@ -90,7 +90,7 @@ const ErrorPlaceholder = styled.div`
align-items: center;
justify-content: center;
gap: 0.4rem;
- background: #4e5d6c;
+ background: var(--bs-secondary);
color: rgba(255, 255, 255, 0.75);
text-align: center;
padding: 0.5rem;
@@ -488,7 +488,7 @@ interface CardProportionWrapperProps {
const CardProportionWrapperStyle = styled.div<{ $borderWidth?: number }>`
z-index: 0;
- background: #4e5d6c;
+ background: var(--bs-secondary);
border: solid ${(props) => props.$borderWidth ?? 0}px black;
`;
diff --git a/frontend/src/features/card/CardbackApplyPrompt.tsx b/frontend/src/features/card/CardbackApplyPrompt.tsx
index fbb4dc053..1d8dc43be 100644
--- a/frontend/src/features/card/CardbackApplyPrompt.tsx
+++ b/frontend/src/features/card/CardbackApplyPrompt.tsx
@@ -5,12 +5,18 @@
* per-slot picker) - one component, so the two surfaces can't drift (§C.2's own binding
* requirement). Never a second stacked modal - always rendered inline by its caller.
*
- * Token table E.2 (BINDING, #302 palette): `.cbprompt` panel `#22303f` / `1px #16202b` border +
- * left `3px #df6919`; `.applybtn` primary-tinted (`#df6919` border / `#ffb27d` text, hover fills
- * `#df6919`/`#fff`, done state `#5cb85c`/`#8fe08f`); `.defbtn` info-tinted (`#5bc0de` border /
- * `#8fd7ea` text, hover fills `#5bc0de`/`#062430`, same done state); `.trapnote` `#ffd76a`
- * (rail only); `.skip` `#8fa0b0` underline link (toolbar only, since the rail per-slot picker
- * already IS the "no modal, ever" surface - leaving the section collapsed is itself "not now").
+ * Token table E.2 (BINDING, #302 palette; re-themed to Tokyo-11, 2026-07-24 - see
+ * docs/features/theming.md - tokens and spec tables move together, same discipline
+ * DisplayLeftRailFidelity.spec.ts's own re-theme pass used): `.cbprompt` panel
+ * $theme-raised-bg / `1px` $theme-divider border + left `3px` $primary; `.applybtn`
+ * primary-tinted ($primary border/text, hover fills $primary/$theme-btn-ink - Tokyo-11's
+ * primary is light, so the filled-hover state needs the dark ink, not white -, done state
+ * $success/$success); `.defbtn` info-tinted ($info border/text, hover fills $info/
+ * $theme-btn-ink, same done state); `.trapnote` $warning (rail only); `.skip` $theme-muted
+ * underline link (toolbar only, since the rail per-slot picker already IS the "no modal,
+ * ever" surface - leaving the section collapsed is itself "not now"). Unlike the #302 palette,
+ * Tokyo-11's primary/info/success tokens are all light enough to use directly as text colour -
+ * no separate hand-picked tint literal is needed the way `#ffb27d`/`#8fd7ea` were.
*/
import styled from "@emotion/styled";
import React, { useState } from "react";
@@ -18,9 +24,9 @@ import React, { useState } from "react";
import { CustomBackSlotThumbnail } from "@/features/card/cardbackApply";
const Panel = styled.div`
- background: #22303f;
- border: 1px solid #16202b;
- border-left: 3px solid #df6919;
+ background: var(--theme-raised-bg);
+ border: 1px solid var(--theme-divider);
+ border-left: 3px solid var(--bs-primary);
padding: 10px 12px;
margin-top: 12px;
`;
@@ -28,7 +34,7 @@ const Panel = styled.div`
const Title = styled.div`
font-size: 13px;
font-weight: 700;
- color: #ebebeb;
+ color: var(--bs-body-color);
margin-bottom: 2px;
display: flex;
align-items: center;
@@ -37,7 +43,7 @@ const Title = styled.div`
const Intro = styled.div`
font-size: 12px;
- color: #8fa0b0;
+ color: var(--theme-muted);
margin-bottom: 10px;
`;
@@ -46,7 +52,7 @@ const Choice = styled.div`
align-items: center;
gap: 10px;
padding: 8px 0;
- border-top: 1px solid #16202b;
+ border-top: 1px solid var(--theme-divider);
&:first-of-type {
border-top: none;
@@ -59,19 +65,21 @@ const ChoiceLabel = styled.div`
.h {
font-size: 13px;
- color: #ebebeb;
+ color: var(--bs-body-color);
}
.s {
font-size: 11px;
- color: #8fa0b0;
+ color: var(--theme-muted);
}
`;
const ApplyButton = styled.button<{ $done: boolean }>`
background: transparent;
- border: 1px solid ${(props) => (props.$done ? "#5cb85c" : "#df6919")};
- color: ${(props) => (props.$done ? "#8fe08f" : "#ffb27d")};
+ border: 1px solid
+ ${(props) => (props.$done ? "var(--bs-success)" : "var(--bs-primary)")};
+ color: ${(props) =>
+ props.$done ? "var(--bs-success)" : "var(--bs-primary)"};
font-family: inherit;
font-size: 13px;
padding: 4px 10px;
@@ -81,15 +89,20 @@ const ApplyButton = styled.button<{ $done: boolean }>`
pointer-events: ${(props) => (props.$done ? "none" : "auto")};
&:hover {
- background: ${(props) => (props.$done ? "transparent" : "#df6919")};
- color: ${(props) => (props.$done ? "#8fe08f" : "#fff")};
+ background: ${(props) =>
+ props.$done ? "transparent" : "var(--bs-primary)"};
+ /* Tokyo-11 ink flip - primary is light, so the filled-hover state needs dark ink, not
+ white (was #fff under the #302 palette). */
+ color: ${(props) =>
+ props.$done ? "var(--bs-success)" : "var(--theme-btn-ink)"};
}
`;
const DefaultButton = styled.button<{ $done: boolean }>`
background: transparent;
- border: 1px solid ${(props) => (props.$done ? "#5cb85c" : "#5bc0de")};
- color: ${(props) => (props.$done ? "#8fe08f" : "#8fd7ea")};
+ border: 1px solid
+ ${(props) => (props.$done ? "var(--bs-success)" : "var(--bs-info)")};
+ color: ${(props) => (props.$done ? "var(--bs-success)" : "var(--bs-info)")};
font-family: inherit;
font-size: 13px;
padding: 4px 10px;
@@ -99,8 +112,11 @@ const DefaultButton = styled.button<{ $done: boolean }>`
pointer-events: ${(props) => (props.$done ? "none" : "auto")};
&:hover {
- background: ${(props) => (props.$done ? "transparent" : "#5bc0de")};
- color: ${(props) => (props.$done ? "#8fe08f" : "#062430")};
+ background: ${(props) => (props.$done ? "transparent" : "var(--bs-info)")};
+ /* Tokyo-11 ink flip - info is light too (was #062430 dark-navy-on-cyan under #302; the
+ new $theme-btn-ink token is the same idea, generalised across every filled-light role). */
+ color: ${(props) =>
+ props.$done ? "var(--bs-success)" : "var(--theme-btn-ink)"};
}
`;
@@ -109,7 +125,7 @@ const SkipRow = styled.div`
text-align: right;
a {
- color: #8fa0b0;
+ color: var(--theme-muted);
font-size: 12px;
text-decoration: underline;
cursor: pointer;
@@ -118,7 +134,7 @@ const SkipRow = styled.div`
const TrapNote = styled.div`
font-size: 11px;
- color: #ffd76a;
+ color: var(--bs-warning);
margin-top: 8px;
display: flex;
gap: 5px;
@@ -136,8 +152,8 @@ const ThumbPair = styled.div`
display: flex;
align-items: center;
gap: 3px;
- background: #16202b;
- border: 1px solid rgba(235, 235, 235, 0.15);
+ background: var(--theme-divider);
+ border: 1px solid rgba(var(--bs-body-color-rgb), 0.15);
padding: 3px;
`;
@@ -153,7 +169,7 @@ const ThumbImg = styled.div<{ $url: string | undefined }>`
const ThumbLabel = styled.div`
font-size: 9px;
- color: #8fa0b0;
+ color: var(--theme-muted);
max-width: 90px;
white-space: nowrap;
overflow: hidden;
diff --git a/frontend/src/features/card/RequestedPrintingBadge.tsx b/frontend/src/features/card/RequestedPrintingBadge.tsx
index 69a4f3928..a1fd53b6d 100644
--- a/frontend/src/features/card/RequestedPrintingBadge.tsx
+++ b/frontend/src/features/card/RequestedPrintingBadge.tsx
@@ -76,8 +76,8 @@ export function RequestedPrintingBadge({
style={{
display: "inline-block",
marginTop: "5px",
- background: "#ffc107",
- color: "#111",
+ background: "var(--bs-warning)",
+ color: "var(--theme-btn-ink)",
fontFamily: "monospace",
fontSize: "10px",
padding: "1px 7px",
diff --git a/frontend/src/features/display/ConfidenceElement.tsx b/frontend/src/features/display/ConfidenceElement.tsx
index a618e22cb..af2afe2cb 100644
--- a/frontend/src/features/display/ConfidenceElement.tsx
+++ b/frontend/src/features/display/ConfidenceElement.tsx
@@ -74,15 +74,17 @@ const NotThisPrintingButton = styled(Button)`
font-weight: 600;
line-height: 1.4;
border-radius: 10px;
- background: rgba(217, 83, 79, 0.12);
+ background: rgba(var(--bs-danger-rgb), 0.12);
color: #f0b3b1;
- border: 1px solid rgba(217, 83, 79, 0.55);
+ border: 1px solid rgba(var(--bs-danger-rgb), 0.55);
&:hover:not(:disabled),
&:focus:not(:disabled) {
- background: #d9534f;
- color: #fff;
- border-color: #d9534f;
+ background: var(--bs-danger);
+ /* Tokyo-11 ink flip: danger is a light red (6.46:1 with dark ink vs. 2.65:1 with white) -
+ see styles.scss's own $color-contrast-dark note. */
+ color: var(--theme-btn-ink);
+ border-color: var(--bs-danger);
}
&[data-confirmed="true"] {
diff --git a/frontend/src/features/display/DisplayPage.tsx b/frontend/src/features/display/DisplayPage.tsx
index 189ff2941..672360f0f 100644
--- a/frontend/src/features/display/DisplayPage.tsx
+++ b/frontend/src/features/display/DisplayPage.tsx
@@ -1207,15 +1207,15 @@ const ActionBarSearchGroup = styled.div`
// component-scoped-override discipline as `.rail-source-toggle`/`.cstack .form-select` above.
const ToolbarRoot = styled.div`
.btn-outline-secondary {
- background: #22303f;
- color: #ebebeb;
+ background: var(--theme-raised-bg);
+ color: var(--bs-body-color);
border: 1px solid #46586a;
}
.btn-outline-secondary:hover,
.btn-outline-secondary:focus {
- background: #22303f;
- color: #ebebeb;
- border-color: #abb6c2;
+ background: var(--theme-raised-bg);
+ color: var(--bs-body-color);
+ border-color: var(--theme-light);
}
`;
@@ -1259,19 +1259,19 @@ const ToolbarRoot = styled.div`
// `.sv{border-bottom:1px solid var(--divider)}`).
const RailRoot = styled.div`
.rail-head {
- background: #22303f;
- border-bottom: 1px solid #16202b;
+ background: var(--theme-raised-bg);
+ border-bottom: 1px solid var(--theme-divider);
padding: 8px 10px;
}
.artist-line {
- background: #22303f;
- border-bottom: 1px solid #16202b;
+ background: var(--theme-raised-bg);
+ border-bottom: 1px solid var(--theme-divider);
}
.sources {
- border-bottom: 1px solid #16202b;
+ border-bottom: 1px solid var(--theme-divider);
}
.select-version-wrapper {
- border-bottom: 1px solid #16202b;
+ border-bottom: 1px solid var(--theme-divider);
}
.select-version-heading {
margin: 0;
@@ -1294,8 +1294,8 @@ const RailRoot = styled.div`
flex-wrap: wrap;
margin: 0;
padding: 8px 10px;
- background: #2b3e50;
- border-bottom: 1px solid #16202b;
+ background: var(--theme-band-bg);
+ border-bottom: 1px solid var(--theme-divider);
font-size: 12px;
}
/* Deliberate radius exception (spec §0) - kept, not invented: the set icon's own circular
@@ -1309,14 +1309,14 @@ const RailRoot = styled.div`
display: inline-flex;
align-items: center;
justify-content: center;
- background: #4e5d6c;
+ background: var(--bs-secondary);
border: 1px solid #7f8fa0;
border-radius: 50%;
cursor: pointer;
}
.seticon .ss {
font-size: 15px;
- color: #ebebeb;
+ color: var(--bs-body-color);
}
.seticon .check {
position: absolute;
@@ -1324,9 +1324,11 @@ const RailRoot = styled.div`
bottom: -3px;
width: 15px;
height: 15px;
- background: #5cb85c;
- color: #fff;
- border: 2px solid #2b3e50;
+ background: var(--bs-success);
+ /* Tokyo-11 ink flip: success is a light green (9.35:1 with dark ink vs. 1.83:1 with white) -
+ see styles.scss's own $color-contrast-dark note for the same reasoning applied to buttons. */
+ color: var(--theme-btn-ink);
+ border: 2px solid var(--theme-band-bg);
border-radius: 50%;
display: flex;
align-items: center;
@@ -1334,13 +1336,17 @@ const RailRoot = styled.div`
font-size: 9px;
font-weight: 900;
}
+ /* Tokyo-11 accent wiring (2026-07-24 owner ruling) - the D14 score badge is one of the
+ study's named accent surfaces ("D14 confidence pill family + score badge"), REV from
+ primary/orange. Ink flips to $theme-btn-ink for the same reason as .check above (white on
+ the light accent purple is 2.31:1; dark ink is 7.39:1, STRICT-AAA). */
.seticon .score {
position: absolute;
right: -7px;
bottom: -7px;
- background: #df6919;
- color: #fff;
- border: 2px solid #2b3e50;
+ background: var(--theme-accent);
+ color: var(--theme-btn-ink);
+ border: 2px solid var(--theme-band-bg);
border-radius: 10px;
font-size: 9px;
font-weight: 800;
@@ -1354,7 +1360,7 @@ const RailRoot = styled.div`
}
.d14 .statepill {
padding: 1px 8px;
- border: 1px solid #4e5d6c;
+ border: 1px solid var(--bs-secondary);
border-radius: 10px;
font-size: 11px;
font-weight: 700;
@@ -1363,9 +1369,14 @@ const RailRoot = styled.div`
border-color: #3f7a2f;
color: #a7e08a;
}
+ /* Tokyo-11 accent wiring - the "suggested" pill is the other half of the D14 confidence pill
+ family the study names as an accent surface (REV from primary/orange). Border+text both
+ use the raw accent token (not a tinted derivative) - the study verifies 6.74:1 on this same
+ band surface, "near-strict," legible on pill text (unlike Nord's failed 3.67:1 attempt at
+ the same idea - see _theme-tokens.scss's own $theme-accent comment). */
.d14 .statepill.suggested {
- border-color: #df6919;
- color: #ffb27d;
+ border-color: var(--theme-accent);
+ color: var(--theme-accent);
}
.d14 .notthis {
margin-left: auto;
@@ -1409,8 +1420,18 @@ const RailRoot = styled.div`
font-size: 11px;
font-weight: 700;
}
+ /* Tokyo-11 accent wiring (2026-07-24) - the study names "selection/active chip + segmented +
+ toggle states" as an accent surface; this toggle's ON cell renders the library's own stock
+ btn-primary class (which now resolves to the orange action colour) - overridden here,
+ same component-scoped-selector discipline as the rest of this block, so ONLY the Sources
+ list toggle (not every other react-bootstrap-toggle mount sitewide) goes accent-purple. */
+ .rail-source-toggle .toggle-on {
+ background-color: var(--theme-accent) !important;
+ border-color: var(--theme-accent) !important;
+ color: var(--theme-btn-ink) !important;
+ }
.rail-source-toggle .toggle-off {
- color: #8fa0b0;
+ color: var(--theme-muted);
}
.rail-source-toggle .toggle-handle {
display: none;
@@ -1436,19 +1457,19 @@ const RailRoot = styled.div`
aspect-ratio: 63 / 88;
position: relative;
overflow: hidden;
- border: 1px solid rgba(235, 235, 235, 0.15);
+ border: 1px solid rgba(var(--bs-body-color-rgb), 0.15);
}
.subject img {
display: block;
}
.subject.empty {
background: transparent;
- border: 1px dashed #abb6c2;
+ border: 1px dashed var(--theme-light);
display: flex;
align-items: center;
justify-content: center;
text-align: center;
- color: #8fa0b0;
+ color: var(--theme-muted);
font-size: 9px;
padding: 4px;
line-height: 1.3;
@@ -1477,7 +1498,7 @@ const RailRoot = styled.div`
}
.rail-head .slot .face {
font-weight: 400;
- color: #8fa0b0;
+ color: var(--theme-muted);
font-size: 11px;
text-transform: uppercase;
margin-left: 6px;
@@ -1487,7 +1508,7 @@ const RailRoot = styled.div`
margin-top: 1px;
}
.rail-head .name.none {
- color: #8fa0b0;
+ color: var(--theme-muted);
font-style: italic;
}
/* EP6 (N) - the per-slot Front/Back segmented toggle, 'ToggleButtonGroup' restyled to the
@@ -1502,17 +1523,25 @@ const RailRoot = styled.div`
font-size: 11px;
font-weight: 700;
padding: 2px 12px;
- background: #22303f;
- color: #8fa0b0;
+ /* WCAG/APCA audit fold-in (2026-07-24, PR #432's report; owner-ruled amendment to
+ SPEC-editor-polish.md's binding .fbtoggle row, 2026-07-24) - the authored 2px 12px
+ padding/11px font rendered a measured 51-55x23px target, 1px under WCAG 2.2 SC 2.5.8's
+ 24px floor with no qualifying spacing exception. min-height (not more padding) closes
+ the gap without changing the segment's visual density. */
+ min-height: 24px;
+ background: var(--theme-raised-bg);
+ color: var(--theme-muted);
border-color: #6b7d8e;
border-radius: 0;
}
+ /* Tokyo-11 accent wiring (2026-07-24) - the study explicitly names "Front/Back + source
+ toggles" as an accent surface, REV from the prior info/cyan active state. */
.fbtoggle .btn.active,
.fbtoggle .btn:focus,
.fbtoggle .btn:hover {
- background: #5bc0de;
- color: #062430;
- border-color: #5bc0de;
+ background: var(--theme-accent);
+ color: var(--theme-btn-ink);
+ border-color: var(--theme-accent);
box-shadow: none;
}
/* EP4 (REV RD5, §D.1 '.slotacts-top .iact') - the compact icon row's OWN sizing lives in
@@ -1529,14 +1558,14 @@ const RailRoot = styled.div`
'.idhang', both '#2b3e50') rather than the rail-head's own '#22303f', since it's still
"about the currently-identified printing," the same subject D14 covers. */
.detmore-wrap {
- background: #2b3e50;
- border-bottom: 1px solid #16202b;
+ background: var(--theme-band-bg);
+ border-bottom: 1px solid var(--theme-divider);
padding: 8px 10px;
}
.detmore {
background: transparent;
border: none;
- color: #8fa0b0;
+ color: var(--theme-muted);
font-size: 11px;
cursor: pointer;
padding: 0;
@@ -1546,12 +1575,12 @@ const RailRoot = styled.div`
font-family: inherit;
}
.detmore:hover {
- color: #ebebeb;
+ color: var(--bs-body-color);
}
.detbody {
margin-top: 8px;
padding-top: 8px;
- border-top: 1px solid #16202b;
+ border-top: 1px solid var(--theme-divider);
font-size: 11px;
}
/* EP9 (N, §D.1 '.compare') - the Scryfall reference reveal, anchored beside the 116px subject
@@ -1572,7 +1601,7 @@ const RailRoot = styled.div`
z-index: 40;
width: 150px;
background: #0b1520;
- border: 1px solid #5bc0de;
+ border: 1px solid var(--bs-info);
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.6);
padding: 5px;
pointer-events: none;
@@ -1585,24 +1614,24 @@ const RailRoot = styled.div`
}
.compare .cap {
font-size: 9px;
- color: #8fa0b0;
+ color: var(--theme-muted);
margin-top: 4px;
}
.compare .cap b {
- color: #5bc0de;
+ color: var(--bs-info);
font-weight: 700;
}
- /* identify panel band (item 6) - hangs off D14, same surface (§2/#2b3e50) */
+ /* identify panel band (item 6) - hangs off D14, same surface (§2/band-bg) */
.idhang {
- background: #2b3e50;
- border-bottom: 1px solid #16202b;
+ background: var(--theme-band-bg);
+ border-bottom: 1px solid var(--theme-divider);
padding: 0 10px 8px;
}
.idtoggle {
background: transparent;
border: 1px solid #6b7d8e;
- color: #abb6c2;
+ color: var(--theme-light);
font-size: 12px;
padding: 3px 8px;
cursor: pointer;
@@ -1612,13 +1641,13 @@ const RailRoot = styled.div`
gap: 6px;
}
.idtoggle:hover {
- border-color: #abb6c2;
- color: #ebebeb;
+ border-color: var(--theme-light);
+ color: var(--bs-body-color);
}
.idbody {
margin-top: 8px;
- background: #22303f;
- border: 1px solid #16202b;
+ background: var(--theme-raised-bg);
+ border: 1px solid var(--theme-divider);
padding: 8px;
}
@@ -1629,18 +1658,18 @@ const RailRoot = styled.div`
gap: 6px;
margin-bottom: 6px;
font-size: 12px;
- color: #8fa0b0;
+ color: var(--theme-muted);
flex-wrap: wrap;
}
.svhead .n {
- color: #ebebeb;
+ color: var(--bs-body-color);
font-weight: 700;
}
/* EP7 (SPEC-editor-polish.md §D.4 '.sortsel', REV RD2) - 'max-width' 150px -> 172px. */
.sortsel {
- background: #22303f;
- color: #ebebeb;
- border: 1px solid #4e5d6c;
+ background: var(--theme-raised-bg);
+ color: var(--bs-body-color);
+ border: 1px solid var(--bs-secondary);
font-size: 12px;
padding: 3px 6px;
border-radius: 0;
@@ -1654,19 +1683,19 @@ const RailRoot = styled.div`
justify-content: center;
gap: 10px;
min-height: 140px;
- color: #8fa0b0;
+ color: var(--theme-muted);
font-size: 12px;
}
/* EP10 - tints the site's canonical spinner '--primary' inside the rail only (component-
scoped via this ancestor selector, same discipline as '.rail-source-toggle' above - it
cannot reach a 'Spinner' mounted anywhere outside this styled-component's own DOM scope). */
.vloading .spinner-border {
- color: #df6919;
+ color: var(--bs-primary);
}
.filtersbtn {
background: transparent;
- border: 1px solid #abb6c2;
- color: #abb6c2;
+ border: 1px solid var(--theme-light);
+ color: var(--theme-light);
font-size: 14px;
padding: 4px 8px;
cursor: pointer;
@@ -1676,8 +1705,8 @@ const RailRoot = styled.div`
gap: 5px;
}
.filtersbtn:hover {
- background: #abb6c2;
- color: #111;
+ background: var(--theme-light);
+ color: var(--theme-btn-ink);
}
/* Filters panel - one shared fieldset body, tier-conditional container (RD4/O3): phone = the
@@ -1687,8 +1716,8 @@ const RailRoot = styled.div`
position:fixed node isn't enough here) - those two classes' rules travel WITH that portal
component, duplicated in lockstep, not defined here. */
.fpanel {
- background: #22303f;
- border: 1px solid #16202b;
+ background: var(--theme-raised-bg);
+ border: 1px solid var(--theme-divider);
padding: 8px;
}
.fpanel.inline {
@@ -1707,17 +1736,17 @@ const RailRoot = styled.div`
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.05em;
- color: #8fa0b0;
+ color: var(--theme-muted);
margin-bottom: 4px;
}
.fsep {
height: 1px;
- background: #16202b;
+ background: var(--theme-divider);
margin: 9px -8px;
}
.implicit-note {
font-size: 10px;
- color: #8fa0b0;
+ color: var(--theme-muted);
margin-top: 7px;
display: flex;
gap: 5px;
@@ -1725,7 +1754,7 @@ const RailRoot = styled.div`
line-height: 1.4;
}
.implicit-note .ic {
- color: #5bc0de;
+ color: var(--bs-info);
flex: 0 0 auto;
}
@@ -1740,15 +1769,15 @@ const RailRoot = styled.div`
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.05em;
- color: #8fa0b0;
+ color: var(--theme-muted);
margin-bottom: 5px;
}
/* Component-scoped override of PrintOptionsSection's own Form.Select (same non-fork discipline
as .rail-source-toggle above - reused component, rail-scoped CSS only). */
.cstack .form-select {
- background: #22303f;
- color: #ebebeb;
- border: 1px solid #4e5d6c;
+ background: var(--theme-raised-bg);
+ color: var(--bs-body-color);
+ border: 1px solid var(--bs-secondary);
font-size: 13px;
padding: 4px 8px;
width: 100%;
@@ -1756,11 +1785,11 @@ const RailRoot = styled.div`
}
.cstack p.text-muted {
font-size: 10px;
- color: #8fa0b0;
+ color: var(--theme-muted);
margin-top: 4px;
}
.cs-foot {
- border-top: 1px solid #16202b;
+ border-top: 1px solid var(--theme-divider);
padding-top: 8px;
}
`;
@@ -1944,7 +1973,7 @@ const Rail = ({
fontSize: 10,
textTransform: "uppercase",
letterSpacing: "0.05em",
- color: "#8fa0b0",
+ color: "var(--theme-muted)",
marginBottom: 5,
}}
>
diff --git a/frontend/src/features/display/SlotActionsSection.tsx b/frontend/src/features/display/SlotActionsSection.tsx
index 170e55870..223b954bc 100644
--- a/frontend/src/features/display/SlotActionsSection.tsx
+++ b/frontend/src/features/display/SlotActionsSection.tsx
@@ -53,24 +53,25 @@ const IconAction = styled(Button)`
font-size: 14px;
line-height: 1;
background: transparent;
- color: #abb6c2;
- border: 1px solid #abb6c2;
+ color: var(--theme-light);
+ border: 1px solid var(--theme-light);
&:hover:not(:disabled),
&:focus:not(:disabled) {
- background: #abb6c2;
- color: #111;
+ background: var(--theme-light);
+ color: var(--theme-btn-ink);
}
&.danger {
color: #f0a6a3;
- border-color: #d9534f;
+ border-color: var(--bs-danger);
}
&.danger:hover:not(:disabled),
&.danger:focus:not(:disabled) {
- background: #d9534f;
- color: #fff;
+ background: var(--bs-danger);
+ /* Tokyo-11 ink flip - danger is light, dark ink reads far better (6.46:1 vs. 2.65:1). */
+ color: var(--theme-btn-ink);
}
`;
diff --git a/frontend/src/features/display/SlotCardbackControl.tsx b/frontend/src/features/display/SlotCardbackControl.tsx
index c6e97f223..a1645555f 100644
--- a/frontend/src/features/display/SlotCardbackControl.tsx
+++ b/frontend/src/features/display/SlotCardbackControl.tsx
@@ -37,7 +37,7 @@ const Thumb = styled.div<{ $url: string | undefined }>`
flex: 0 0 54px;
width: 54px;
aspect-ratio: 63 / 88;
- border: 1px solid rgba(235, 235, 235, 0.15);
+ border: 1px solid rgba(var(--bs-body-color-rgb), 0.15);
position: relative;
background-color: #2a2320;
background-image: ${(props) =>
@@ -70,12 +70,12 @@ const Meta = styled.div`
.bname {
font-size: 13px;
- color: #ebebeb;
+ color: var(--bs-body-color);
}
.bsub {
font-size: 11px;
- color: #8fa0b0;
+ color: var(--theme-muted);
}
`;
diff --git a/frontend/src/features/display/SourcesAccordion.tsx b/frontend/src/features/display/SourcesAccordion.tsx
index 06457fd92..6cf43cda1 100644
--- a/frontend/src/features/display/SourcesAccordion.tsx
+++ b/frontend/src/features/display/SourcesAccordion.tsx
@@ -174,8 +174,8 @@ export function SourcesAccordion() {
headerPadding="7px 10px"
// EP3 (SPEC-editor-polish.md §D.3) - de-greys the shared component's own `#4E5D6B`
// default for this ONE caller only (see AutofillCollapse.tsx's own comment).
- headerBackground="#22303f"
- bodyBackground="#22303f"
+ headerBackground="var(--theme-raised-bg)"
+ bodyBackground="var(--theme-raised-bg)"
title={