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: Establish the design-token foundation — capture a visual baseline, create the canonical tokens.css with every token scale, link it site-wide, and consolidate the duplicated :root blocks — with verified zero visual change.
Architecture: A new www/v3/css/tokens.css holds one canonical :root with every token scale (type, weight, color, icon, spacing, radius). It is linked first on all 30 pages, before v3.css. The duplicated :root blocks in v3.css and product.css are removed. The screenshot.mjs harness gains baseline + diff modes for visual-regression checking. This plan only sets up the system — it must not change rendering; the per-value migration to var(--token) is in follow-up plans.
Tech Stack: Static HTML/CSS, vanilla JS. Playwright (Chromium) + pixelmatch/pngjs for visual-regression.
This is Plan 1 of sub-project B (spec: docs/superpowers/specs/2026-05-14-v3-design-tokens-design.md). It delivers the token foundation — the scales are defined and codified, the baseline is captured, the verification tooling exists. Follow-up plans, written once tokens.css and the baseline exist:
v3.css, product.css, responsive.css, trial.css) to var(--token).This plan changes no rendering. Adding tokens.css (whose color values are identical to the current :roots) and removing the now-redundant :root blocks is rendering-neutral; Task 6's diff proves it.
Numeric scales use value-suffixed names (--text-13, --space-16, --radius-8) — this is a deliberate refinement of the spec's --text-*/--space-*/--radius-* shorthand: value-suffixed names make the data-driven snap migration an unambiguous mechanical lookup (font-size: 13px → var(--text-13); an off-scale 13.5px → its nearest, var(--text-14)). Semantic tokens that are already semantic (colors, weights, icon sizes) keep semantic names.
/Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive, branch website-v3-mobile-responsive. Run all commands from here. Working tree is clean.www/ at http://127.0.0.1:8123. Restart if down:
pkill -f "http.server 8123"; 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 &)
www/v3/dev/ already has package.json + playwright installed. This plan adds pixelmatch + pngjs.v3.css and product.css each contain a :root block. They define an identical color-token set; v3.css's additionally has --radius: 10px. Stylesheet load order on every page is v3.css then product.css (homepage adds responsive.css; trial pages add trial.css). --serif is an alias of --sans — kept in tokens.css (removing it is deferred to a migration plan, only if confirmed unused).| File | Status | Responsibility |
|---|---|---|
www/v3/css/tokens.css |
create | The canonical :root — all color tokens (consolidated from the two existing :roots) + the new --text-* / --weight-* / --icon-* / --space-* / --radius-* scales + --cat-1..5. |
www/v3/css/v3.css |
modify | Remove its :root block (now in tokens.css). |
www/v3/css/product.css |
modify | Remove its :root block (now in tokens.css). |
all 30 page .html files |
modify | Add <link rel="stylesheet" href="…/tokens.css?v=checklist"> as the first stylesheet. |
www/v3/dev/screenshot.mjs |
modify | Add baseline and diff modes (pixelmatch-based visual regression). |
www/v3/dev/package.json |
modify | Add pixelmatch + pngjs deps. |
www/v3/dev/baseline/ |
create (gitignored) | The pre-migration screenshot baseline. |
Plan correction (during execution, 2026-05-14): the harness as first written below was non-deterministic — looping CSS animations and JS-driven content made every diff non-reproducible (Task 6's first sweep). Two fixes were applied to
screenshot.mjsand committed: (1) before each screenshot, freeze CSS animations/transitions (page.addStyleTagwithanimation-duration:0setc. +reducedMotion: "reduce"+ a 200ms settle) — this cleared 28/30 pages to exact zero diff; (2) aNOISYset (de-agenten,en-agenten) whose diffs are reported but excluded from pass/fail — those pages run a JS-driven live agent console that mutates the DOM on timers and cannot be pixel-diffed deterministically; they are verified by manual spot-check. The committedscreenshot.mjsis the source of truth.
Files: Modify www/v3/dev/package.json, www/v3/dev/screenshot.mjs, www/v3/dev/.gitignore
package.jsonIn www/v3/dev/package.json, change the devDependencies block to:
"devDependencies": {
"playwright": "^1.48.0",
"pixelmatch": "^6.0.0",
"pngjs": "^7.0.0"
}
Then run: cd /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive/www/v3/dev && npm install
Expected: pixelmatch and pngjs install with no errors.
baseline/ to .gitignorewww/v3/dev/.gitignore currently contains node_modules/ and shots/. Add a third line so it reads:
node_modules/
shots/
baseline/
www/v3/dev/screenshot.mjs with baseline + diff modesReplace the entire file with:
// Responsive + visual-regression harness for the v3 website.
// Modes:
// node screenshot.mjs → overflow check, screenshots to ./shots
// node screenshot.mjs baseline → screenshots to ./baseline (the pre-migration ground truth)
// node screenshot.mjs diff → screenshots to ./shots, pixel-diff each vs ./baseline
// node screenshot.mjs <filter> → as default, comma-list slug-substring filter
// node screenshot.mjs diff <filter> → as diff, filtered
import { chromium } from "playwright";
import { mkdirSync, existsSync, readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { PNG } from "pngjs";
import pixelmatch from "pixelmatch";
const BASE = process.env.BASE || "http://127.0.0.1:8123";
const WIDTHS = [320, 375, 768, 1024, 1280];
const HERE = fileURLToPath(new URL("./", import.meta.url));
const argv = process.argv.slice(2);
const MODE = (argv[0] === "baseline" || argv[0] === "diff") ? argv[0] : "check";
const FILTER = ((MODE === "check" ? argv[0] : argv[1]) || "").split(",").filter(Boolean);
const OUT = MODE === "baseline" ? `${HERE}baseline/` : `${HERE}shots/`;
const BASELINE = `${HERE}baseline/`;
const PAGES = [
"/v3/de/", "/v3/en/",
"/v3/de/produkt/abschluss.html", "/v3/de/produkt/debitoren.html", "/v3/de/produkt/dokumente.html",
"/v3/de/produkt/fpa.html", "/v3/de/produkt/freigaben.html", "/v3/de/produkt/kreditoren.html",
"/v3/de/produkt/spesen.html", "/v3/de/produkt/varianz.html", "/v3/de/produkt/vertraege.html",
"/v3/en/produkt/abschluss.html", "/v3/en/produkt/debitoren.html", "/v3/en/produkt/dokumente.html",
"/v3/en/produkt/fpa.html", "/v3/en/produkt/freigaben.html", "/v3/en/produkt/kreditoren.html",
"/v3/en/produkt/spesen.html", "/v3/en/produkt/varianz.html", "/v3/en/produkt/vertraege.html",
"/v3/de/agenten/", "/v3/de/implementierung/", "/v3/de/roi/", "/v3/de/steuerberater/", "/v3/de/trial/",
"/v3/en/agenten/", "/v3/en/implementierung/", "/v3/en/roi/", "/v3/en/steuerberater/", "/v3/en/trial/",
];
const slug = (p) => {
if (p === "/v3/de/") return "de-home";
if (p === "/v3/en/") return "en-home";
return p.replace(/^\/v3\//, "").replace(/index\.html$/, "").replace(/\.html$/, "")
.replace(/\/$/, "").replace(/\//g, "-");
};
mkdirSync(OUT, { recursive: true });
const pages = FILTER.length ? PAGES.filter((p) => FILTER.some((f) => slug(p).includes(f))) : PAGES;
const browser = await chromium.launch();
const overflows = [];
const diffs = [];
try {
for (const path of pages) {
const s = slug(path);
for (const width of WIDTHS) {
const page = await browser.newPage({ viewport: { width, height: 900 } });
try {
await page.goto(BASE + path, { waitUntil: "networkidle" });
const overflow = await page.evaluate(() => {
const de = document.documentElement;
return de.scrollWidth - de.clientWidth;
});
const file = `${OUT}${s}-${width}.png`;
await page.screenshot({ path: file, fullPage: true });
if (overflow > 1) overflows.push(`${s} @ ${width}px (+${overflow}px)`);
if (MODE === "diff") {
const basePath = `${BASELINE}${s}-${width}.png`;
if (!existsSync(basePath)) {
diffs.push(`${s} @ ${width}px — NO BASELINE`);
} else {
const a = PNG.sync.read(readFileSync(basePath));
const b = PNG.sync.read(readFileSync(file));
if (a.width !== b.width || a.height !== b.height) {
diffs.push(`${s} @ ${width}px — SIZE CHANGED ${a.width}x${a.height} → ${b.width}x${b.height}`);
} 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) diffs.push(`${s} @ ${width}px — ${changed}px changed (${pct.toFixed(3)}%)`);
}
}
}
} finally {
await page.close();
}
}
}
} finally {
await browser.close();
}
console.log(`\nmode=${MODE} ${pages.length} pages x ${WIDTHS.length} widths = ${pages.length * WIDTHS.length} screenshots`);
if (MODE === "baseline") {
console.log(`baseline written to ./baseline/`);
process.exit(0);
}
let fail = false;
if (overflows.length) {
fail = true;
console.log(`\n${overflows.length} OVERFLOW FAILURES:`);
for (const o of overflows) console.log(` ${o}`);
} else {
console.log(`\nno horizontal overflow on any page at any width`);
}
if (MODE === "diff") {
if (diffs.length) {
fail = true;
console.log(`\n${diffs.length} VISUAL DIFFS vs baseline:`);
for (const d of diffs) console.log(` ${d}`);
} else {
console.log(`zero visual diff vs baseline — rendering unchanged`);
}
}
process.exit(fail ? 1 : 0);
Ensure the server is running, then:
cd /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive/www/v3/dev && node screenshot.mjs de-home
Expected: runs de-home × 5 widths, prints mode=check, all ok (the homepage is responsive), exit 0.
git add www/v3/dev/screenshot.mjs www/v3/dev/package.json www/v3/dev/package-lock.json www/v3/dev/.gitignore
git commit -m "test(v3): add baseline + visual-diff modes to the harness"
Files: none committed (baseline/ is gitignored) — this task produces the ground-truth artifact.
With the server running and no token changes yet made (the working tree must be clean except the Task 1 commit):
cd /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive/www/v3/dev && node screenshot.mjs baseline
Expected: mode=baseline, 30 pages × 5 widths = 150 screenshots written to www/v3/dev/baseline/, exit 0. This is the canonical "site as it looks today" — every later migration stage diffs against it.
ls www/v3/dev/baseline/*.png | wc -l
Expected: 150.
diff mode reports zero against the fresh baselinecd /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive/www/v3/dev && node screenshot.mjs diff de-home
Expected: mode=diff, de-home 5 widths, zero visual diff vs baseline (diffing the baseline against an immediate re-render of the unchanged site must be zero), exit 0. If it reports diffs, the harness or rendering is non-deterministic — stop and investigate before proceeding.
(No commit — baseline/ is gitignored.)
tokens.cssFiles: Create www/v3/css/tokens.css
www/v3/css/tokens.css/* ═══════════════════════════════════════════════════════════════
tokens.css — the v3 design-token system (sub-project B, 2026-05-14)
The single canonical :root. Linked FIRST on every v3 page, before
v3.css / product.css. Scales are data-driven (kept the well-used
values from the audited distribution); migration snaps every raw
value to its nearest token. See docs/superpowers/specs/.
═══════════════════════════════════════════════════════════════ */
:root {
/* ── Color — semantic (consolidated from the former v3.css + product.css :roots) ── */
--bg: #ffffff;
--bg-2: #f5f3ee;
--bg-3: #f5f3ee; /* aliased to --bg-2 */
--ink: #111111;
--ink-2: #2b2b2b;
--ink-3: #5a5a56;
--muted: #8a8680;
--line: #dcd7cb;
--line-2: #c9c3b4;
--accent: #006EC7;
--accent-ink: #ffffff;
--accent-soft: #e6f0fb;
--good: #2e5d3b;
--warn: #8a5a1a;
--red: #8a2a1a;
/* ── Color — categorical palette (was raw hex #D49B70 etc.) ── */
--cat-1: #D49B70;
--cat-2: #BC7B95;
--cat-3: #A185BB;
--cat-4: #7A9BC0;
--cat-5: #6DAF9F;
/* ── Type — font families ── */
--sans: "Geist", "Helvetica Neue", Helvetica, Arial, sans-serif;
--mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
--serif: "Geist", "Helvetica Neue", Helvetica, Arial, sans-serif; /* legacy alias of --sans */
/* ── Type — font weights ── */
--weight-regular: 400;
--weight-medium: 500;
--weight-semibold: 600;
/* ── Type — discrete font-size scale (px). Display headings keep their
own clamp() expressions and are not tokenized here. ── */
--text-9: 9px;
--text-10: 10px;
--text-11: 11px;
--text-12: 12px;
--text-13: 13px;
--text-14: 14px;
--text-15: 15px;
--text-16: 16px;
--text-19: 19px;
--text-24: 24px;
--text-32: 32px;
/* ── Icons ── */
--icon-sm: 14px;
--icon-md: 16px;
--icon-lg: 18px;
--icon-stroke: 1.6;
--icon-stroke-bold: 1.8;
/* ── Spacing scale (px) ── */
--space-2: 2px;
--space-4: 4px;
--space-6: 6px;
--space-8: 8px;
--space-10: 10px;
--space-12: 12px;
--space-16: 16px;
--space-20: 20px;
--space-24: 24px;
--space-32: 32px;
--space-40: 40px;
--space-48: 48px;
--space-64: 64px;
--space-80: 80px;
--space-120: 120px;
/* ── Border radius ── */
--radius-2: 2px;
--radius-4: 4px;
--radius-8: 8px;
--radius-12: 12px;
--radius-16: 16px;
--radius-pill: 999px;
--radius: 10px; /* legacy token, still referenced as var(--radius); migrated later */
}
node -e "const c=require('fs').readFileSync('www/v3/css/tokens.css','utf8'); const o=(c.match(/{/g)||[]).length,cl=(c.match(/}/g)||[]).length; console.log('braces',o,cl,o===cl?'OK':'MISMATCH')"
Expected: braces 1 1 OK.
git add www/v3/css/tokens.css
git commit -m "feat(v3): add tokens.css — the canonical design-token system"
tokens.css first on all 30 pagesFiles: Modify all 30 page .html files.
tokens.css link before v3.css on every pageEvery page has, in <head>, a line linking v3.css. Only the two homepages (de/index.html, en/index.html) are one directory deep and use href="../css/v3.css?v=checklist"; all other pages — product pages and section pages (de/roi/index.html, de/agenten/index.html, …) — are two directories deep and use href="../../css/v3.css?v=checklist". For each of the 30 pages, insert a tokens.css link immediately before its v3.css link, with the matching relative path.
Apply with this script (it inserts the correct relative path per page depth and is idempotent — it skips any page that already links tokens.css):
cd /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive/www/v3
for f in de/index.html en/index.html \
de/produkt/abschluss.html de/produkt/debitoren.html de/produkt/dokumente.html de/produkt/fpa.html \
de/produkt/freigaben.html de/produkt/kreditoren.html de/produkt/spesen.html de/produkt/varianz.html de/produkt/vertraege.html \
en/produkt/abschluss.html en/produkt/debitoren.html en/produkt/dokumente.html en/produkt/fpa.html \
en/produkt/freigaben.html en/produkt/kreditoren.html en/produkt/spesen.html en/produkt/varianz.html en/produkt/vertraege.html \
de/agenten/index.html de/implementierung/index.html de/roi/index.html de/steuerberater/index.html de/trial/index.html \
en/agenten/index.html en/implementierung/index.html en/roi/index.html en/steuerberater/index.html en/trial/index.html; do
if grep -q 'css/tokens.css' "$f"; then echo "skip (already linked): $f"; continue; fi
case "$f" in
de/index.html|en/index.html) rel="../css" ;; # the two homepages are 1 level deep
*) rel="../../css" ;; # product + section pages are 2 levels deep
esac
perl -0pi -e "s{(\\s*)(<link rel=\"stylesheet\" href=\"\\Q$rel\\E/v3\\.css\\?v=checklist\">)}{\$1<link rel=\"stylesheet\" href=\"$rel/tokens.css?v=checklist\">\$1\$2}" "$f"
echo "linked: $f"
done
cd /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive/www/v3
grep -lc 'css/tokens.css' de/index.html en/index.html de/produkt/*.html en/produkt/*.html de/*/index.html en/*/index.html 2>/dev/null | grep -c ':1' ; echo "↑ pages linking tokens.css (expect 30)"
# confirm order on a sample of each depth: tokens.css must appear on an earlier line than v3.css
for f in de/index.html de/produkt/kreditoren.html de/roi/index.html en/produkt/abschluss.html; do
t=$(grep -n 'css/tokens.css' "$f" | cut -d: -f1); v=$(grep -n 'css/v3.css' "$f" | cut -d: -f1)
echo "$f tokens@$t v3@$v $([ "$t" -lt "$v" ] && echo OK || echo BAD)"
done
Expected: 30 pages link tokens.css; all four samples report OK.
cd /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive
git add www/v3/de www/v3/en
git commit -m "feat(v3): link tokens.css first on all 30 pages"
:root blocks from v3.css and product.cssFiles: Modify www/v3/css/v3.css, www/v3/css/product.css
:root block from product.cssIn www/v3/css/product.css, delete this exact block (it is near the top of the file):
:root {
--bg: #ffffff;
--bg-2: #f5f3ee;
/* --bg-3 aliased to --bg-2; previously a darker third sand tone, dropped to reduce beige intensity */
--bg-3: #f5f3ee;
--ink: #111111;
--ink-2: #2b2b2b;
--ink-3: #5a5a56;
--muted: #8a8680;
--line: #dcd7cb;
--line-2: #c9c3b4;
--accent: #006EC7;
--accent-ink: #ffffff;
--accent-soft: #e6f0fb;
--good: #2e5d3b;
--warn: #8a5a1a;
--red: #8a2a1a;
--serif: "Geist", "Helvetica Neue", Helvetica, Arial, sans-serif;
--sans: "Geist", "Helvetica Neue", Helvetica, Arial, sans-serif;
--mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}
(All of those values are now provided by tokens.css, which loads first.)
:root block from v3.cssIn www/v3/css/v3.css, find its :root block (indented two spaces) and delete it. It is structurally:
:root {
--bg: #ffffff;
... (the same tokens, plus --radius: 10px) ...
}
Read the file to get the block's exact text (it spans from the :root { line to its matching }), then delete that exact block. Do not delete anything else. Every token it defined — including --radius: 10px — is provided by tokens.css.
:root remains in v3.css / product.css, and braces balancecd /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive
grep -c ':root' www/v3/css/v3.css www/v3/css/product.css # expect 0 for both
grep -c ':root' www/v3/css/tokens.css # expect 1
node -e "for(const f of ['www/v3/css/v3.css','www/v3/css/product.css','www/v3/css/tokens.css']){const c=require('fs').readFileSync(f,'utf8');const o=(c.match(/{/g)||[]).length,cl=(c.match(/}/g)||[]).length;console.log(f,o===cl?'OK':'MISMATCH '+o+'/'+cl)}"
Expected: v3.css and product.css → 0 :root; tokens.css → 1; all three brace-balanced OK.
git add www/v3/css/v3.css www/v3/css/product.css
git commit -m "refactor(v3): remove duplicate :root blocks — tokens.css is now canonical"
Files: none (verification only — unless a regression fix is needed).
With the server running:
cd /Users/maximilianbrandstaetter/Orcha/.claude/worktrees/website-v3-mobile-responsive/www/v3/dev && node screenshot.mjs diff
Expected (with the deterministic harness from Task 1): mode=diff, 30 pages × 5 widths, no horizontal overflow on any page at any width, the 10 de-agenten/en-agenten entries listed under "known JS-dynamic pages (NOT pass/fail)", and zero visual diff vs baseline (excluding known JS-dynamic pages) — rendering unchanged, exit 0.
This is the core check: adding tokens.css (identical color values) and removing the redundant :root blocks must not change a single pixel on the 28 deterministic pages. If a non-NOISY page reports a diff, it is a real regression — tokens.css is missing a token that one of the removed :root blocks provided, or the link order is wrong. Fix tokens.css / the link order and re-run. For the NOISY agenten pages, manually open a shots/de-agenten-1280.png and confirm the page renders correctly (header, sections, footer all present) — the foundation change is mechanically neutral, so any agenten diff is JS-content jitter, not a regression.
git add -A
git commit -m "fix(v3): tokens foundation regression fix"
If Step 1 was clean, skip this step.
Append to www/v3/dev/foundation-baseline.md a short "Sub-project B — Plan 1 (Token Foundation) complete" section: tokens.css is the canonical :root, linked first on all 30 pages; the duplicated :root blocks are removed; the visual baseline is captured in www/v3/dev/baseline/; the diff mode reports zero change. Note that Plans 2+ migrate raw values to var(--token). Commit:
git add www/v3/dev/foundation-baseline.md
git commit -m "docs(v3): record token-foundation completion"
Checked against docs/superpowers/specs/2026-05-14-v3-design-tokens-design.md:
tokens.css created (Task 3), linked first on all 30 pages (Task 4), duplicated :roots removed (Task 5). ✔tokens.css — type (--text-9..32), weights (--weight-*), families (--sans/--mono/--serif), icons (--icon-*), spacing (--space-*), radius (--radius-* + legacy --radius), categorical palette (--cat-1..5), all semantic colors. Value-suffixed naming for numeric scales is documented as a deliberate refinement of the spec's --text-* shorthand. Display clamp() headings deliberately not tokenized here (spec §Risks: clamp expressions must be preserved; v3.css and product.css disagree on the values, so unifying them is a visual decision for a later plan, not the rendering-neutral foundation). ✔baseline + diff modes; Task 2 captures the baseline; Task 6 runs the full diff. ✔Placeholder scan: none — tokens.css is given in full; the harness rewrite is given in full; the :root removal for product.css is given exactly, and for v3.css it is precisely identified ("from :root { to its matching }") because that block carries the same content plus --radius — the executor reads the exact text, which is bounded and unambiguous.
Type/name consistency: token names used consistently (--text-N, --space-N, --radius-N, --icon-{sm,md,lg}, --weight-{regular,medium,semibold}, --cat-N). screenshot.mjs modes (baseline/diff/check), PAGES, slug(), OUT, BASELINE all internally consistent. The PAGES list and slug() match the harness from sub-project A (homepages → de-home/en-home).