For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Migrate every raw value in the four shared v3 stylesheets (v3.css, product.css, responsive.css, trial.css) to var(--token) references from tokens.css, snapping off-scale values to their nearest token — with no meaningful visual change.
Architecture: This is Plan 2 of Sub-project B. Plan 1 (token foundation) is complete: tokens.css exists as the canonical :root, is linked first on all 30 pages, the duplicate :root blocks are gone, and a pre-migration visual baseline is captured in www/v3/dev/baseline/. This plan does the data-driven snap migration of the shared CSS only — the 30 pages' inline styles are Plan 3+. Migration proceeds one value class at a time (color → type/icon → radius → spacing) across all four files, so each commit is one concept and any visual regression is trivially bisectable. Every task is verified against the baseline with the diff harness.
Tech Stack: Static CSS (no preprocessor); tokens.css custom properties; Playwright + pixelmatch visual-regression harness (www/v3/dev/screenshot.mjs).
Worktree: /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive, branch website-v3-mobile-responsive. All paths below are relative to the worktree root.
The four files and their cascade order: tokens.css → v3.css → product.css → (responsive.css on the two homepages only / trial.css on the two trial pages only). tokens.css is the base layer and is not edited for value migration (only Task 1 finalizes its scales).
The token scales (defined in www/v3/css/tokens.css): semantic colors (--bg, --bg-2, --ink, --ink-2, --ink-3, --muted, --line, --line-2, --accent, --accent-ink, --accent-soft, --good, --warn, --red), categorical palette (--cat-1…--cat-5), families (--sans, --mono), weights (--weight-regular/medium/semibold = 400/500/600), font sizes (--text-9/10/11/12/13/14/15/16/19/24/32 + the display sizes added in Task 1), icons (--icon-sm/md/lg, --icon-stroke = 1.6, --icon-stroke-bold = 1.8), spacing (--space-2/4/6/8/10/12/16/20/24/32/40/48/64/80/120), radius (--radius-2/4/8/12/16, --radius-pill = 999px, --radius = 10px legacy).
Snap philosophy: every raw value is replaced by its nearest token. Because the scales kept the dominant values, most usages don't move; outliers shift 1–4px at most. Snap tables in each task are exhaustive for the values that actually occur in these four files (audited 2026-05-14) — apply them mechanically.
What is NOT migrated (intentionally out of scope for shared CSS):
clamp(...) display-heading font sizes — preserved verbatim (the spec's risk note: "the clamp() expressions themselves must be preserved, not flattened"). Tokenizing 10 distinct clamp expressions into 3 tokens would flatten distinct heading sizes — a redesign, which the spec forbids.em-relative values (font-size: 0.85em, padding: 0 0.08em) — relative units, not scale members.rgba() / hsl() literals — these are alpha overlays for shadows and gradient stops, not palette colors; the spec's "no raw hexes" criterion is about hex literals.border-radius: 50% — geometric (circle), not a px radius.width / height / positioning (top/left/inset) values — component-intrinsic dimensions, not the spacing rhythm. (E.g. .dot { width: 7px } stays — a 7px decorative dot is neither an icon nor spacing.)style= attributes and in-body <style> blocks — Plan 3+.Local server (required for the harness): serves www/ at http://127.0.0.1:8123. Verify/restart if down:
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8123/v3/de/ || \
(cd /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive/www && \
python3 -m http.server 8123 --bind 127.0.0.1 >/tmp/orcha-v3-www.log 2>&1 &)
The verification harness: cd www/v3/dev && node screenshot.mjs diff re-renders all 30 pages × 5 widths and pixel-compares against baseline/. After Task 1 it also writes a shots/<slug>-<width>.diff.png highlight image for every page/width that changed. The de-agenten/en-agenten pages are NOISY (JS-driven live console) — their diffs are reported separately and are never pass/fail; verify those two by eye if needed. shots/ and baseline/ are gitignored.
Reading a diff result:
.diff.png. A large or unexplained diff = a typo or a snap applied to the wrong property → fix it.padding/margin/gap value reflows everything below it, so a 2px change near the top of a page cascades into a large pixel count — this is expected and not a regression. Judge by the .diff.png images, not the percentage: an expected diff looks like content below a snapped element shifted uniformly by a couple px (a clean horizontal band of change); a bug looks like overlapping text, a mis-sized box, or a broken layout. If the diff images show the cumulative reflow is pervasive enough to be a genuinely noticeable change in the site's rhythm (not a 1–2px settle), STOP and check in with the user — it would mean the spacing scale dropped too many well-used values (e.g. 14px) and should gain them back before proceeding.Per-task verification is also gated by the responsive harness: node screenshot.mjs (no args) must still report no horizontal overflow on any page at any width — snapping must never reintroduce overflow.
tokens.css and the diff harnessFiles:
Modify: www/v3/css/tokens.css
Modify: www/v3/css/v3.css:10-20 (move the body[data-mode="systems"] block out)
Modify: www/v3/dev/screenshot.mjs
Step 1: Add the display font-size tokens to tokens.css
The discrete --text-* scale tops out at --text-32, but the shared CSS has four fixed display sizes above it (44px ×3, 48px ×2, 80px ×1, 96px ×1) that cannot snap to --text-32 without a large, visible change. Give them 1:1 tokens (value-suffixed, consistent with the rest of the file). They are not merged with each other — merging big display numbers risks a visible change, and the variation reduction comes from collapsing the half-pixel/one-off body sizes, not these.
In www/v3/css/tokens.css, immediately after the --text-32: 32px; line (currently line 55) and before the /* ── Icons ── */ comment, add:
/* ── Type — fixed display sizes (clamp()-based display headings stay inline) ── */
--text-44: 44px;
--text-48: 48px;
--text-80: 80px;
--text-96: 96px;
body[data-mode="systems"] theme block into tokens.cssv3.css:10-20 holds a theme override that redefines the semantic color tokens for body[data-mode="systems"]. These are token definitions (a theme variant), not raw-hex usages — they belong with the other definitions in tokens.css so it is the single source of truth. The hex values stay literal (they are the definitions). Moving the rule earlier in the cascade is safe: nothing else redefines these custom properties at body[...] specificity, and tokens.css loads before v3.css/product.css.
Delete these lines from www/v3/css/v3.css (lines 10-20, including the surrounding blank line so the file flows from ::selection straight to /* Layout helpers */):
/* Mode-specific overrides */
body[data-mode="systems"] {
--bg: #f2efe6;
--bg-2: #ebe7d9;
--bg-3: #dfdac6;
--ink: #0f0f0e;
--line: #d6cfb9;
--line-2: #bdb499;
--accent: #d64b1a;
--accent-ink: #fff8ef;
}
Then append to the end of www/v3/css/tokens.css (after the closing } of :root):
/* ── Theme override — "systems" mode. Token *definitions* (a theme variant),
so the hex values are intentionally literal here. ── */
body[data-mode="systems"] {
--bg: #f2efe6;
--bg-2: #ebe7d9;
--bg-3: #dfdac6;
--ink: #0f0f0e;
--line: #d6cfb9;
--line-2: #bdb499;
--accent: #d64b1a;
--accent-ink: #fff8ef;
}
diff harness to emit highlight imagesSo intended snap-drift can be reviewed (especially for the spacing task), make diff mode write a pixelmatch highlight PNG for every changed page/width.
In www/v3/dev/screenshot.mjs, change the node:fs import (line 9) from:
import { mkdirSync, existsSync, readFileSync } from "node:fs";
to:
import { mkdirSync, existsSync, readFileSync, writeFileSync } from "node:fs";
Then replace the else branch inside the diff block (currently lines 88-92):
} else {
const changed = pixelmatch(a.data, b.data, null, a.width, a.height, { threshold: 0.1 });
const pct = (changed / (a.width * a.height)) * 100;
if (changed > 0) sink.push(`${s} @ ${width}px — ${changed}px changed (${pct.toFixed(3)}%)`);
}
with:
} else {
const diff = new PNG({ width: a.width, height: a.height });
const changed = pixelmatch(a.data, b.data, diff.data, a.width, a.height, { threshold: 0.1 });
const pct = (changed / (a.width * a.height)) * 100;
if (changed > 0) {
writeFileSync(`${OUT}${s}-${width}.diff.png`, PNG.sync.write(diff));
sink.push(`${s} @ ${width}px — ${changed}px changed (${pct.toFixed(3)}%) → ${s}-${width}.diff.png`);
}
}
Ensure the server is up (see Shared context). Then:
cd www/v3/dev && node screenshot.mjs diff
Expected: no horizontal overflow on any page at any width; the 10 de-agenten/en-agenten entries under "known JS-dynamic pages (NOT pass/fail)"; and zero visual diff vs baseline (excluding known JS-dynamic pages) — rendering unchanged, exit 0.
This proves Step 1 (adding unused tokens — inert) and Step 2 (moving the theme block — same rule, same cascade outcome) changed nothing. If a non-noisy page diffs, the body[data-mode="systems"] move changed the cascade — recheck that the block was moved verbatim and that nothing in v3.css/product.css redefines those properties.
git add www/v3/css/tokens.css www/v3/css/v3.css www/v3/dev/screenshot.mjs
git commit -m "$(cat <<'EOF'
feat(v3): finalize token scales + diff-image output
Add fixed display font tokens (--text-44/48/80/96), move the
body[data-mode="systems"] theme block into tokens.css so all token
definitions live in one file, and make the diff harness emit
highlight PNGs for changed page/widths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Files: Modify www/v3/css/v3.css, www/v3/css/product.css, www/v3/css/trial.css. (responsive.css has no raw hex.)
Every hex literal in these files either equals an existing semantic token exactly, or is a now-redundant var(--token, #fallback) fallback (the var is always defined by tokens.css, so the fallback never applies — stripping it is zero-change), or is a one-off that snaps to the nearest semantic token. There are no genuinely-new colors here that need new tokens. rgba()/hsl() literals and the body[data-mode] definition hexes (now in tokens.css) are out of scope.
var(--token, #hex) fallbacks in trial.csstokens.css always defines these vars, so the hex fallbacks are dead. Replace each of these exact occurrences in www/v3/css/trial.css (the var name stays, only the , #hex is removed):
| Line | From | To |
|---|---|---|
| 17 | var(--line, #e5e5e5) |
var(--line) |
| 19 | var(--bg, #fff) |
var(--bg) |
| 34 | var(--ink-2, #555) |
var(--ink-2) |
| 42 | var(--line, #d4d4d4) |
var(--line) |
| 45 | var(--ink, #111) |
var(--ink) |
| 52 | var(--accent, #0a0a0a) |
var(--accent) |
| 63 | var(--muted, #888) |
var(--muted) |
| 85 | var(--ink, #111) |
var(--ink) |
| 86 | var(--bg, #fff) |
var(--bg) |
| 125 | var(--ink-2, #555) |
var(--ink-2) |
#fff with var(--bg)--bg is #ffffff, so this is exact. Four occurrences:
www/v3/css/product.css:117 — .btn-ghost[href*="calendar.app.google"] { background: #fff; } → background: var(--bg);
www/v3/css/v3.css:476 — .btn-ghost[href*="calendar.app.google"] { background: #fff; } → background: var(--bg);
www/v3/css/v3.css:1871 — background: #fff; → background: var(--bg);
www/v3/css/trial.css:44 — background: #fff; → background: var(--bg);
Step 3: Snap the two trial.css one-off colors to semantic tokens
These style runtime form-validation states; they are off-palette brighter variants of the semantic error/success colors. Per the spec, a new color used <2× snaps to the nearest existing token — --red and --good are exactly the semantic intent. (They affect only validation states not present in the baseline screenshots, so the harness diff for this is zero anyway.)
www/v3/css/trial.css:100 — color: #c0392b; → color: var(--red);
www/v3/css/trial.css:116 — color: #2ecc71; → color: var(--good);
Step 4: Confirm no hex literals remain (except rgba/hsl are fine — they have no #)
grep -nE '#[0-9a-fA-F]{3,8}\b' www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
Expected: no output. (The body[data-mode] hexes now live in tokens.css, which is intentionally not in this grep.)
Server up, then:
cd www/v3/dev && node screenshot.mjs diff && node screenshot.mjs
Expected: diff reports zero visual diff vs baseline (excluding known JS-dynamic pages) (every color change is an exact-equal token, a dead-fallback strip, or a non-screenshot validation state), exit 0; the plain run reports no horizontal overflow on any page at any width. If any non-noisy page diffs, a #fff was swapped on a non-white element or a fallback strip hit a var that isn't in tokens.css — check the .diff.png.
git add www/v3/css/v3.css www/v3/css/product.css www/v3/css/trial.css
git commit -m "$(cat <<'EOF'
refactor(v3): migrate shared-CSS colors to tokens
Strip redundant var() hex fallbacks in trial.css, replace standalone
#fff with var(--bg), snap trial.css's two off-palette validation
colors to var(--red)/var(--good). No raw hex left in the shared CSS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Files: Modify www/v3/css/v3.css, www/v3/css/product.css, www/v3/css/responsive.css, www/v3/css/trial.css.
Plan correction (during execution):
responsive.cssandtrial.cssdo carry type scalars — on-scale font-sizes (9/11/12/13/14/15/32px) and afont-weight: 500— that still need tokenizing. The Step 4 verification grep already spans all four files, so all four are in scope; this header originally listed only two.
font-size px valueApply this exhaustive snap table to every font-size: declaration in v3.css and product.css. clamp(...) expressions and 0.85em are left unchanged.
| Raw | Token | Raw | Token | |
|---|---|---|---|---|
8.5px |
var(--text-9) |
15px |
var(--text-15) |
|
9px |
var(--text-9) |
16px |
var(--text-16) |
|
9.5px |
var(--text-10) |
17px |
var(--text-16) |
|
10px |
var(--text-10) |
18px |
var(--text-19) |
|
10.5px |
var(--text-11) |
19px |
var(--text-19) |
|
11px |
var(--text-11) |
21px |
var(--text-19) |
|
11.5px |
var(--text-12) |
22px |
var(--text-24) |
|
12px |
var(--text-12) |
24px |
var(--text-24) |
|
12.5px |
var(--text-13) |
26px |
var(--text-24) |
|
13px |
var(--text-13) |
28px |
var(--text-32) |
|
13.5px |
var(--text-14) |
30px |
var(--text-32) |
|
14px |
var(--text-14) |
32px |
var(--text-32) |
|
14.5px |
var(--text-15) |
44px |
var(--text-44) |
|
48px |
var(--text-48) |
|||
80px |
var(--text-80) |
|||
96px |
var(--text-96) |
The off-scale values (8.5/9.5/10.5/11.5/12.5/13.5/14.5/17/18/21/22/26/28/30) and large fixed sizes are at these lines (from the audit) — but apply by value, not by line number, since earlier edits shift lines: product.css lines 162, 343, 352, 365, 400, 414, 417, 451, 455, 492, 502, 519, 596; v3.css lines 175, 271, 598, 651, 719, 750, 760, 775, 784, 798, 807, 842, 846, 892, 897, 976, 1000, 1031, 1074, 1096, 1102, 1134, 1152, 1204, 1219, 1248, 1255, 1293, 1377, 1402, 1445, 1473, 1534, 1585, 1650, 1705, 1784, 1789, 1814, 1827, 1843, 1850, 1920, 1944, 1998, 2040, 2258, 2279, 2295, 2307, 2372, 2597, 2611, 2636, 2644. The on-scale values (9/10/11/12/13/14/15/16/19/24/32px) also migrate to their var(--text-N) token.
font-weight value| Raw | Token |
|---|---|
400 |
var(--weight-regular) |
500 |
var(--weight-medium) |
600 |
var(--weight-semibold) |
700 |
var(--weight-semibold) |
The lone font-weight: 700 is at v3.css:1957 — snapping it to 600 is a spec-approved consolidation of the one stray weight.
stroke-widthTwo occurrences, both in v3.css:
stroke-width: 1.8; → stroke-width: var(--icon-stroke-bold);
stroke-width: 1.2; → stroke-width: var(--icon-stroke); (snaps 1.2 → 1.6)
Step 4: Confirm no off-token type/icon scalars remain
grep -nE 'font-size:[[:space:]]*[0-9.]+px' www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
grep -nE 'font-weight:[[:space:]]*[0-9]+' www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
grep -nE 'stroke-width:[[:space:]]*[0-9.]+' www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
Expected: no output from any of the three (only clamp() font-sizes and var(--token) references remain).
Server up, then:
cd www/v3/dev && node screenshot.mjs diff && node screenshot.mjs
Expected: widespread but cosmetic diffs. The audit's font-size long tail was mostly half-pixel values (10.5/12.5/13.5px etc.), so nearly every text element re-renders at a marginally different integer size. This (a) reflows content, so most pages report SIZE CHANGED (a small height delta — tens of px on multi-thousand-px pages — which pixelmatch cannot compare), and (b) where height happens to match, inflates the pixel-diff % (1–3%) because text covers a large fraction of the page and all text re-rendered.
This is not a regression — verify it by eye, not by exit code: open 2–3 baseline/<slug>-<width>.png vs shots/<slug>-<width>.png pairs (include a homepage and a high-% product page) — the layout, hierarchy and element positions must be visually identical. Open the .diff.png for the highest-% pages — the changed pixels must sit on text glyphs (re-rendered text), never as a displaced section band, overlapping content, or a mis-sized box. no horizontal overflow on any page at any width must still hold. If a .diff.png shows a whole section displaced or broken, a font-size was snapped on the wrong element or a clamp() was touched — fix it.
Harness note: once font-size/spacing snaps reflow content, full-page screenshot heights change and pixelmatch reports
SIZE CHANGEDinstead of a comparable diff. From Task 3 onward the harness is a "nothing catastrophic happened" gate (overflow check +.diff.pngreview of comparable pages), not a clean exit-0 gate —diffwill exit 1. This is expected; the spec's "near-zero meaningful diff" criterion is judged visually.
git add www/v3/css/v3.css www/v3/css/product.css
git commit -m "$(cat <<'EOF'
refactor(v3): migrate shared-CSS type & icon scalars to tokens
Snap every font-size, font-weight and stroke-width to its nearest
token; clamp() display headings preserved verbatim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Files: Modify www/v3/css/v3.css, www/v3/css/product.css. (responsive.css/trial.css have no off-token radius — verify in Step 3.)
border-radius valueApply this exhaustive snap table. border-radius: 50% and the existing var(--radius) are left unchanged. Ties are broken toward the more-used neighbouring step (concentrates mass).
| Raw | Token | Note |
|---|---|---|
1px |
var(--radius-2) |
nearest |
2px |
var(--radius-2) |
exact |
3px |
var(--radius-4) |
tie 2/4 → toward more-used 4 |
4px |
var(--radius-4) |
exact |
6px |
var(--radius-4) |
tie 4/8 → toward more-used 4 |
8px |
var(--radius-8) |
exact |
9px |
var(--radius) |
tie 8/10 → toward more-used 10 |
10px |
var(--radius) |
exact (legacy token = 10px) |
12px |
var(--radius-12) |
exact |
14px |
var(--radius-12) |
tie 12/16 → toward more-used 12 |
16px |
var(--radius-16) |
exact |
18px |
var(--radius-16) |
nearest |
999px |
var(--radius-pill) |
exact |
50% |
(unchanged) | geometric circle |
Multi-value border-radius — snap each px component:
14px 14px 0 0 → var(--radius-12) var(--radius-12) 0 0
0 14px 14px 0 → 0 var(--radius-12) var(--radius-12) 0
Step 2: Confirm no off-token radius remains
grep -nE 'border-radius:[^;]*[0-9]+(px|%)' www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
Expected: only lines whose numeric value is 50% (circles, intentionally kept). Everything else must be var(--radius*).
Server up, then:
cd www/v3/dev && node screenshot.mjs diff && node screenshot.mjs
Expected: small diffs confined to rounded corners — 14px→12px, 6px→4px, 3px→4px, 9px→10px, 18px→16px change corner curvature by 1–4px on the affected elements. Each reported page/width should localize (via .diff.png) to card/button/pill corners. No layout shift, no horizontal overflow still holds.
git add www/v3/css/v3.css www/v3/css/product.css
git commit -m "$(cat <<'EOF'
refactor(v3): migrate shared-CSS border-radius to tokens
Snap every border-radius to its nearest radius token (ties toward
the more-used step); border-radius:50% circles left as-is.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Files: Modify www/v3/css/v3.css, www/v3/css/product.css, www/v3/css/responsive.css, www/v3/css/trial.css.
This is the largest and riskiest task — re-read the "Reading a diff result → Spacing task" note in Shared context before starting.
Scope: padding, padding-top/right/bottom/left, margin, margin-top/right/bottom/left, gap, row-gap, column-gap. Not width/height/top/left/right/bottom/inset (component-intrinsic, out of scope).
Snap rule: each px component → nearest step on the spacing scale 2,4,6,8,10,12,16,20,24,32,40,48,64,80,120; ties round up (toward the larger step); 0, auto, %, vw/vh, em, calc(...), and existing var(...) are left unchanged; multi-value declarations snap each component independently.
padding / margin / gap px valueOn-scale values (2,4,6,8,10,12,16,20,24,32,40,48,64,80,120px) become their var(--space-N) token directly. Off-scale values present in the four files snap as follows:
| Raw | Token | Raw | Token | |
|---|---|---|---|---|
3px |
var(--space-4) |
26px |
var(--space-24) |
|
5px |
var(--space-6) |
28px |
var(--space-32) |
|
7px |
var(--space-8) |
30px |
var(--space-32) |
|
9px |
var(--space-10) |
34px |
var(--space-32) |
|
11px |
var(--space-12) |
36px |
var(--space-40) |
|
13px |
var(--space-12) |
38px |
var(--space-40) |
|
14px |
var(--space-16) |
44px |
var(--space-48) |
|
15px |
var(--space-16) |
56px |
var(--space-64) |
|
17px |
var(--space-16) |
60px |
var(--space-64) |
|
18px |
var(--space-20) |
72px |
var(--space-80) |
|
19px |
var(--space-20) |
88px |
var(--space-80) |
|
21px |
var(--space-20) |
100px |
var(--space-120) |
|
22px |
var(--space-24) |
140px |
var(--space-120) |
|
23px |
var(--space-24) |
160px |
var(--space-120) |
Worked examples of multi-value declarations (apply the same per-component logic everywhere):
padding: 24px; → padding: var(--space-24);padding: 4px 10px; → padding: var(--space-4) var(--space-10);padding: 24px 24px 22px; → padding: var(--space-24) var(--space-24) var(--space-24);padding: 8px 0; → padding: var(--space-8) 0;padding: 0 0.08em; → padding: 0 0.08em; (unchanged — 0 and em)margin: 0 auto; → margin: 0 auto; (unchanged)margin: 8px 0 0; → margin: var(--space-8) 0 0;margin: 88px 0 28px; → margin: var(--space-80) 0 var(--space-32);margin: 0 auto 8px; → margin: 0 auto var(--space-8);gap: 28px 24px; → gap: var(--space-32) var(--space-24);gap: 6px; → gap: var(--space-6);Work file-by-file. The audited off-scale values cluster around 14, 18, 22, 28, 36, 56, 5, 72 — 14px is the most frequent, so most reflow comes from 14→16.
grep -nE '(padding|margin|gap)[a-z-]*:[^;]*[0-9]+px' www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
Expected: no output — every padding/margin/gap px value is now 0, auto, an em/%/calc/var expression, or a var(--space-N) token.
Server up, then:
cd www/v3/dev && node screenshot.mjs diff && node screenshot.mjs
Expected: no horizontal overflow on any page at any width must still hold — this is the hard gate; snapping must never reintroduce overflow. The diff numeric percentages will be larger than in Tasks 2–4 because spacing snaps reflow content below them — judge by the .diff.png images: every change should read as content shifted uniformly by a small amount (clean horizontal bands), never as overlapping text, a mis-sized element, or a broken layout. Spot-check at least the two homepages and three section pages across widths. If the reflow is pervasive enough to be a genuinely noticeable change to the site's spacing rhythm — STOP and check in with the user about adding 14px (and possibly 18/28) back to the --space-* scale before continuing.
git add www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
git commit -m "$(cat <<'EOF'
refactor(v3): migrate shared-CSS spacing to tokens
Snap every padding/margin/gap px value to its nearest --space-* step
(ties round up); 0/auto/em/%/calc/var left as-is.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
Files: Modify www/v3/dev/foundation-baseline.md.
grep -nE '#[0-9a-fA-F]{3,8}\b' www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
grep -nE 'font-size:[[:space:]]*[0-9.]+px|font-weight:[[:space:]]*[0-9]+|stroke-width:[[:space:]]*[0-9.]+' www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
grep -nE 'border-radius:[^;]*[0-9]+px' www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
grep -nE '(padding|margin|gap)[a-z-]*:[^;]*[0-9]+px' www/v3/css/v3.css www/v3/css/product.css www/v3/css/responsive.css www/v3/css/trial.css
Expected: the first, second and fourth greps produce no output; the third produces only border-radius: ...50%... lines. Anything else is a missed value — go back to the relevant task and fix it.
Server up, then:
cd www/v3/dev && node screenshot.mjs diff && node screenshot.mjs
Expected: no horizontal overflow on any page at any width; diff reports only small, explainable, localized changes (the cumulative spacing reflow from Task 5 dominates the numbers — confirm via .diff.png that it is uniform settle, not breakage); the two agenten pages remain NOISY. This is the spec's "near-zero meaningful diff" success criterion: the site looks the same, with far fewer distinct values.
foundation-baseline.mdIn www/v3/dev/foundation-baseline.md, under the "Sub-project B" section, add a line after the Plan 1 entry:
- **Plan 2 — Shared-CSS Migration** (`2026-05-14-v3-design-tokens-shared-css.md`) — **complete.**
- `v3.css`, `product.css`, `responsive.css`, `trial.css` fully migrated to `var(--token)`:
every color, font-size, font-weight, stroke-width, border-radius and
padding/margin/gap is now a token reference or a deliberately-preserved
expression (`clamp()` display headings, `em`-relative values, `rgba()` overlays,
`border-radius:50%`). `tokens.css` gained `--text-44/48/80/96` and the
`body[data-mode="systems"]` theme block; the diff harness now emits `.diff.png`
highlight images.
- `node screenshot.mjs diff` shows only small localized snap-drift vs. the baseline;
`node screenshot.mjs` still reports zero overflow.
- Plan 3+ migrates the 30 pages' inline styles.
git add www/v3/dev/foundation-baseline.md
git commit -m "$(cat <<'EOF'
docs(v3): record shared-CSS token migration completion
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)"
tokens.css; link it first; remove duplicate :root; migrate v3.css, product.css, responsive.css, trial.css." The first three were Plan 1; this plan does the fourth (all four files, all five value classes: color, type, icon, spacing, radius). Stage 2 (page inline styles) is Plan 3+.clamp() display headings stay inline and verbatim rather than being flattened into 3 --text-display-* tokens — the spec's own risk note forbids flattening them, and 10 distinct expressions can't become 3 tokens without redesigning; the fixed display sizes get --text-44/48/80/96 instead. (2) "Near-zero diff" is read as "near-zero meaningful diff" — the spacing task's cumulative reflow makes literal pixel counts large even when nothing is wrong, so spacing verification is by diff-image review + the overflow gate, with an explicit STOP-and-ask checkpoint if the reflow is genuinely noticeable.--text-*, --weight-*, --icon-stroke*, --radius*, --space-*, semantic colors) all match tokens.css as read on 2026-05-14; --text-44/48/80/96 are added by Task 1 Step 1 before any task references them.