What changed, and why
The warm-ledger look was doing its job too well: text ink passed AA everywhere, but the structure around it sat below perceptual thresholds. Hairlines measured 1.26:1 against the paper, the card tint 1.10:1, the message-pip rings 1.79:1 — and in dark mode the positive and negative semantic inks were isoluminant at 1.04:1, with the "bad" ink reading as the terracotta accent (1.16:1). Wins looked like losses, and the run's scoreboard was the faintest thing on screen.
This PR is a CSS-and-template pass over 30 files. No store, server, or mechanics code changes: every number the game computes is untouched. The changes cluster into a token retune, a set of shared primitives, per-surface legibility fixes, one real rendering bug (the Record overlay's seal), and a few charter-compatible amplifications of the dramatic beats. All of it stays inside the design charter: hairlines, whitespace, and type carry structure; one terracotta accent; semantic color rides dots and signed numbers.
| load-bearing pair | before (light / dark) | after (light / dark) |
|---|---|---|
hairline on page (interactive --ew-edge) | 1.26 / 1.30 | 3.25 / 4.06 |
track ground (--ew-gauge) | 1.26 / 1.30 | 3.44 / 4.43 |
ok vs bad ink | 1.42 / 1.04 | 1.66 / 1.96 |
accent vs bad | 1.83 / 1.16 | 2.15 / 1.24 + hue split |
| unspent pip ring | 1.79 / 1.88 | 4.55 / 5.68 |
The token retune
Everything flows from assets/css/main.css. Three semantic inks move: light --ew-bad deepens to #6f231b, and dark --ew-ok/--ew-bad split by luminance (#90ca7e / #d95c4e) so a loss no longer wears the accent's hue at the accent's brightness. Five tokens are new, each because one value couldn't serve two masters: --ew-edge separates interactive boundaries from decorative hairlines, --ew-gauge gives every track a visible denominator, --ew-raise and --ew-raise-line lift floating overlays a full surface step off the page, and --ew-scrim replaces the two hardcoded black modal backdrops with a warm umber wash. An eight-step --ew-fs-* ladder replaces roughly twenty drifting font sizes.
The retuned :root and .dark blocks, plus the type ladder.
assets/css/main.css · 738 lines
⋯ 16 lines hidden (lines 1–16)
⋯ 5 lines hidden (lines 57–61)
⋯ 16 lines hidden (lines 75–90)
⋯ 622 lines hidden (lines 117–738)
The Record overlay bug — two layers deep
The one genuine rendering bug this pass found: the summonable Record — the player's only always-available view of past and current game state — was being buried under a viewport-sized seal.
Layer one, production: CodexSeal.vue sized its own root at width:100%; height:100% in scoped CSS. Every caller sizes that same element with an equal-specificity scoped class (the record header's 26px .rec-seal, the ripple stamp, the sending letter-seal), and the tie resolved in built-CSS order — sometimes in CodexSeal's favor. The default sizing now sits behind :where(), which has zero specificity, so a caller's size class deterministically wins.
Layer two, dev: the component opened with a template comment. Vue keeps template comments in dev builds, which made the component multi-root — and Vue silently drops attribute fallthrough on multi-root components, so the caller's class="rec-seal" never landed on the svg at all. The comment moved into the script docblock (same fix in RunsBalance.vue, which takes a class from the masthead).
Zero-specificity sizing, the --seal-fill re-ink hook, and the multi-root warning.
components/codex/CodexSeal.vue · 70 lines
⋯ 35 lines hidden (lines 1–35)
The climax states its number
"History bends" is the leaf the whole turn builds toward, and its one number — how far this dispatch bent the timeline — had no durable statement. The valence-colored delta chip faded to nothing in 1.25 seconds, timed exactly while the eye was on the type-on headline in the opposite column; what remained was a 12.5px faint-mono equation. The big percentage beside it is the cumulative total, which only coincides with the swing on turn one.
The gauge column now leads with a persistent, sign-classed swing line under a "THIS DISPATCH" label. The equation lifts to readable ink with its final term in the swing's valence (mirrored in the ledger), the chip's dwell stretches to 2.6s with a hold, and the headline and stamp scale up to own the beat.
The persistent swing statement and the one valence ink that drives it.
components/codex/LeafRipple.vue · 297 lines
⋯ 35 lines hidden (lines 1–35)
⋯ 24 lines hidden (lines 71–94)
⋯ 190 lines hidden (lines 108–297)
Read-length chip dwell; crack ink off the gauge's own base color.
components/ProgressTracker.vue · 303 lines
⋯ 92 lines hidden (lines 1–92)
⋯ 31 lines hidden (lines 100–130)
⋯ 116 lines hidden (lines 143–258)
The scoreboard reads at a glance
The masthead is the persistent instrument panel, and during the reveal leaves its pips are the only message-ammo display on screen. The track grounds on --ew-gauge; unspent pips get a 1.5px --ew-faint ring and spent pips fill with quiet ink — informational, not an affordance, so the accent stays reserved for the interactive. The record toggle grows from an unlabeled 15px glyph among three siblings into a labeled 40px pill. And dark mode's --codex-card unifies with the page: the old lightened mix split the masthead strip from the leaves, which is what sank every hairline on it to ~1:1.
The labeled record pill, the dark surface unification, and the pip rings.
pages/play.vue · 1286 lines
⋯ 83 lines hidden (lines 1–83)
⋯ 598 lines hidden (lines 100–697)
⋯ 297 lines hidden (lines 708–1004)
⋯ 254 lines hidden (lines 1033–1286)
Honest inks: outcomes, verdicts, and the die
Ochre had been doing double duty as "pre-send caution" and "Failure outcome" — a real loss under-read as advice. The rule is now single-purpose: ochre marks pre-send risk levers only; every outcome that costs you reads oxblood. That sweeps the die face, the outcome line, the dice legend, and both craft-class maps (reckless is oxblood everywhere; vague reads quiet — its tilt carries the sign).
The dice verdict also learned to explain itself. The die face keeps the natural roll (settled decision), but the line beneath now reconciles: 12 ✒+2 → 14 · Success — face 12 beside "Success" on a 14-band read as a contradiction until the tilt is spelled out. The verdict word itself speaks at display scale in the outcome ink, and the legend lights the struck band while the other four rows recede.
The two-tier verdict and the outcome-ink map, Failure now oxblood.
components/DiceRoller.vue · 465 lines
⋯ 62 lines hidden (lines 1–62)
⋯ 164 lines hidden (lines 91–254)
⋯ 190 lines hidden (lines 276–465)
The struck legend band, and the die scaled to centerpiece size on its own leaf only.
components/codex/LeafDice.vue · 182 lines
⋯ 8 lines hidden (lines 1–8)
⋯ 90 lines hidden (lines 47–136)
⋯ 27 lines hidden (lines 156–182)
The judgment tilt, and masterful's tier
The craft chip on the judgment leaf displayed both +2 and −2 in the accent — the scoped rule's color out-specified any utility recolor, so the sign never read. The color now binds in the template (ew-ok / ew-bad by sign) and the scoped rule deliberately carries none. The statement itself re-tiers: a 22px signed number with an 11px caps tail, sitting under the 46px serif grade.
Masterful — the top grade — was visually identical to sharp. It now earns an illuminated first letter and a one-shot expanding ring on reveal; the reduced-motion path skips the ring and lets the static accent initial carry the tier alone.
Sign-classed tilt, the masterful flourish, and its reduced-motion story.
components/codex/LeafJudgment.vue · 161 lines
⋯ 11 lines hidden (lines 1–11)
⋯ 55 lines hidden (lines 43–97)
⋯ 8 lines hidden (lines 121–128)
⋯ 1 line hidden (lines 161–161)
Teaching surfaces and blocked notices
The dispatch leaf's whole teaching layer — wager, chain, stake, seal cue, turn counter — was one undifferentiated 10–11px faint voice. It lifts to a 13px note register with quiet-ink bodies; the turn counter warns in ochre on the final dispatch (a pre-send risk, so ochre is correct there). The disabled send seal adopts the flat-inert idiom through --seal-fill instead of ghosting, and failed-send errors get an oxblood rule so infra failure stops dressing like decoration.
The four hardcoded red-300 moderation notices in the picker and the Archive move onto .ew-blocked — same layout, token inks, honest at 10:1 instead of invisible at 1.76:1.
The disabled seal: re-inked wax at full opacity, never a ghost; sealing pulses terracotta.
components/codex/LeafDispatch.vue · 914 lines
⋯ 777 lines hidden (lines 1–777)
⋯ 114 lines hidden (lines 801–914)
One of the four blocked notices on the new token idiom.
components/FigurePicker.vue · 878 lines
⋯ 183 lines hidden (lines 1–183)
⋯ 685 lines hidden (lines 194–878)
Measure, overlays, and the end of the run
Desktop leaves shared no measure: phone-scale type floated in an ~1180px folio. LeafFrame now centers a 760px measure at ≥1024px and steps the folio type up; the Chronicle and the figure's reply set their prose at book measure (70ch / 60ch), and the reply's action — the consequence — reads in full ink instead of muted. The sending corridor doubles its stroke weight and letter size so the held-breath beat has presence. Floating overlays (coach mark, fiction notice, run packs) sit on the new raise surface with warm scrims. And the end screen strikes the brand seal itself: glowing terracotta on a win, a cold faint seal that failed to take on a defeat.
The shared desktop measure.
components/codex/LeafFrame.vue · 157 lines
⋯ 116 lines hidden (lines 1–116)
⋯ 24 lines hidden (lines 134–157)
CodexSeal as the verdict emblem; defeat re-inks via --seal-fill, no glow.