From 937f0a8f886b4b9ce0a7eae916435207d885adde Mon Sep 17 00:00:00 2001 From: Paul Keen <125715+pftg@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:37:57 +0200 Subject: [PATCH] ADR-0003 accepted: palette is LIGHT; Phase 1a planned (20.02) Paul resolved the one open decision on a lightning demo of the peer set. Every services company reviewed ships light - thoughtbot, Test Double (a Rails consultancy selling to technical buyers), Basecamp (dark is a user preference, not the brand) - and the decisive signal is Linear, the poster child for dark product aesthetics, shipping a LIGHT marketing site. Dark marketing belongs to products bought by developers evaluating a tool; light belongs to services bought on trust. The literature supplies the mechanism: light reads as more open and trustworthy, and holds a comprehension edge at small font sizes - our exact case, since our humans are on phones (28% of GSC clicks from 6% of impressions). The dark case lost on a distinction worth keeping: vibe-code-rescue is our best page and it is obsidian, but its quality is STRUCTURAL not chromatic, and every structural element already shipped in the light blog. Dark stays deliberately in three places - cover art, one proof band per page, and vibe-code-rescue as a dark-variant campaign page. 20.02 plans Phase 1a with the surface measured, not estimated: 161 var(--color-primary) + 52 literals, 8 #0066d6 rule sites, 55 scoped !important workarounds across four files, 262 macOS / 137 Linux baselines. Sequenced so the token promotion ships zero-delta first, making the deletion a value change rather than a rename. The phase's success signal is that all 55 !importants can go - if they can't, the replacement is wrong. Co-Authored-By: Claude Opus 5 (1M context) --- .okf/log.md | 26 ++++ docs/adr/0003-site-design-system.md | 67 +++++++--- .../20-29-strategy/20.02-phase-1a-plan.md | 121 ++++++++++++++++++ .../2608-site-design-system/README.md | 16 ++- 4 files changed, 206 insertions(+), 24 deletions(-) create mode 100644 docs/projects/2608-site-design-system/20-29-strategy/20.02-phase-1a-plan.md diff --git a/.okf/log.md b/.okf/log.md index e619e0939c..00b658dfca 100644 --- a/.okf/log.md +++ b/.okf/log.md @@ -2331,3 +2331,29 @@ extends it past tools to interfaces: exhaust the UI before declaring blocked. Still genuinely open and now correctly owned by 2608 Phase 0.1, not Paul: `page_view` is still marked a key event, so 4,063 page views read as conversions and bury any real one. + +## 2026-08-20 - Palette resolved: LIGHT, and Phase 1a is written + +Ran a lightning demo instead of arguing taste. The peer set decided it: every +services company reviewed ships light (thoughtbot, Test Double - a Rails +consultancy selling to technical buyers - and Basecamp, where dark is a user +preference rather than the brand), and the decisive signal is Linear, the +poster child for dark product aesthetics, shipping a LIGHT marketing site. +Dark marketing belongs to products bought by developers evaluating a tool; +light belongs to services bought on trust. The literature supplies the +mechanism: light reads as more open/trustworthy, dark "weakens a trusting +emotional connection", and light holds a comprehension edge at SMALL font +sizes - our exact case, since our humans are on phones. + +The dark case was real and lost on a distinction worth keeping: our best page +(/services/vibe-code-rescue/) is obsidian, but its quality is STRUCTURAL, not +chromatic - proof chips in fold one, one repeated CTA, artifact cards, ~4,300px +- and every one of those already shipped in the light blog. Dark stays in three +places by design: cover art, one proof band per page, and vibe-code-rescue as a +dark-variant campaign page. + +Phase 1a written up (20.02) with the surface measured rather than estimated: +161 var(--color-primary) + 52 literals, 8 #0066d6 rule sites, and 55 scoped +!important workarounds across four files that exist ONLY until that anchor rule +dies. Success signal for the phase is exactly that - if all 55 can't go, the +replacement isn't right yet. diff --git a/docs/adr/0003-site-design-system.md b/docs/adr/0003-site-design-system.md index 593f4b3541..8805dc0d47 100644 --- a/docs/adr/0003-site-design-system.md +++ b/docs/adr/0003-site-design-system.md @@ -2,7 +2,7 @@ **ADR-0003** **Date:** 2026-08-20 -**Status:** Proposed +**Status:** Accepted (palette resolved 2026-08-20) ## Title @@ -85,23 +85,54 @@ twice, and is already tokenised. What is missing is that the other seventeen bundles never got it. That makes this **extraction and propagation**, not a redesign — logo, ruby, display face and the cover system all unchanged. -### The one open decision: light or dark - -The two reference pages differ on page background, and this ADR does **not** -settle it. Both readings are defensible: - -- **Light** — the course page is written for the ICP most explicitly (a - non-technical founder), and light chrome reads calmer and less "for - developers" to an anxious buyer. -- **Dark** — `/services/vibe-code-rescue/` is the landing page for the live - Validating bet, it is the strongest page on the site, and dark unifies site - chrome with the blog cover system for the first time. - -Everything else in this ADR is palette-independent: the scales, the section -rhythm, the proof placement, the CTA hierarchy and the blue deletion all hold -either way. **Paul decides the palette; the rest ships regardless.** The -prototype currently shows the light reading, and a dark variant is a token swap -against the same components, not a second design. +### Palette: LIGHT — resolved 2026-08-20 (Paul) + +The two reference pages disagreed on page background, and this ADR originally +left it open. **Resolved: light**, decided on a lightning demo of the peer set +plus the readability/trust literature. + +**The split is audience-shaped, not taste-shaped.** Dark marketing belongs to +products bought by *developers evaluating a tool*; light belongs to services +bought on *trust*. Every services peer reviewed is light — thoughtbot, +Test Double (a Rails consultancy selling to technical buyers, still light), +Basecamp (light default, dark merely a user preference) — and the decisive +signal is **Linear**, the poster child for dark product aesthetics, shipping a +*light* marketing site. Dark is where you work; light is where you decide. +Vercel runs both, and its buyer is a developer. + +The mechanism is documented, not just fashionable: users read light-mode sites +as more trustworthy and open, dark "weakens a trusting emotional connection", +and light holds a comprehension edge **at small font sizes** — which is our +exact case, because our humans are on phones (28% of GSC clicks from 6% of +impressions). + +**The dark case, and why it lost.** `/services/vibe-code-rescue/` is the best +page on the site and it is obsidian. But its quality is **structural, not +chromatic** — proof chips in fold one, one repeated CTA, artifact cards instead +of stock photography, ~4,300px. Every one of those is palette-independent and +every one already shipped in the *light* blog. Dark also unifies chrome with +the cover system, which is real, but it buys visual tidiness with the one thing +an anxious, burned founder is shopping for. + +### Where dark deliberately stays + +Light is the default, not a monopoly. Three surfaces keep it: + +1. **Blog cover art** — obsidian, unchanged. It reads precisely *because* it is + now the only dark thing on the page. +2. **One dark band per page**, spent on the strongest proof (already specified + above). +3. **`/services/vibe-code-rescue/`** keeps its dark treatment as a **campaign + landing page** — a dark variant of the same tokens and components, not a + second design system. The system governs chrome and shared components; a bet + landing page may run the dark variant. + +Tiebreak, recorded last because it should not decide the question: light is +also the cheaper path, since the blog already shipped light. + +**Consequence:** Phase 1a is unblocked — and with it the deletion of +`--color-primary` / the late-cascade `#0066d6` anchor rule, which retires every +scoped `!important` workaround the blog currently carries. ## Decision diff --git a/docs/projects/2608-site-design-system/20-29-strategy/20.02-phase-1a-plan.md b/docs/projects/2608-site-design-system/20-29-strategy/20.02-phase-1a-plan.md new file mode 100644 index 0000000000..3a90c24ac8 --- /dev/null +++ b/docs/projects/2608-site-design-system/20-29-strategy/20.02-phase-1a-plan.md @@ -0,0 +1,121 @@ +# 20.02 — Phase 1a: site-wide recolour (the light palette, promoted) + +**Date:** 2026-08-20 +**Status:** Ready to execute — unblocked by the palette decision +**Decisions:** [ADR-0003](../../../adr/0003-site-design-system.md) (palette: +**light**, resolved 2026-08-20) · [ADR-0004](../../../adr/0004-static-site-experimentation.md) +(gates: A qualitative, B guardrails, C reversibility — **no A/B**) +**Predecessor:** [20.01 rollout plan](20.01-rollout-plan.md) §Phase 1a + +Phase 1a is the **style-only** half of the site-wide chrome work: colour moves, +layout does not. Spatial changes (the ~200px dead fold, nav 7→5, the space +scale) stay in **1b** so that reverting one leaves a coherent site — the +substitute for statistical confidence, per ADR-0004. + +--- + +## Measured surface (2026-08-20) + +| Thing | Count | Command | +|---|---|---| +| `var(--color-primary)` references | **161** | `grep -rn 'var(--color-primary' themes layouts` | +| `#1a8cff` literals | **52** | `grep -rni '1a8cff' themes layouts` | +| `#0066d6` rule sites (the anchor monster) | **8** | `grep -rn '0066d6' themes/beaver/assets/css/*.css` | +| Scoped `!important` workarounds waiting on this | **55** | across `blog-list.css`, `single-post.css`, `blog-single.css`, `vibe-code-rescue.css` | +| Screenshot baselines | **262 macOS / 137 Linux** | `find test/fixtures/screenshots/{macos,linux} -name '*.png'` | + +`rr-` tokens currently live in three page files +(`single-post.css`, `pages/blog-single.css`, `pages/blog-list.css`) and are +consumed only by blog surfaces. + +--- + +## Work, in dependency order + +### 1a.1 — Promote the tokens (no visual change) + +Move the `rr-` ramp into `foundations/css-variables.css` (loaded inline +site-wide via the `navigation` bundle) under their final names, and leave the +old `rr-` names as aliases pointing at them. **Renders identically**, so no +baseline churn — this is the same zero-delta trick Phase 0 used, and it makes +1a.2 a value change rather than a name change. + +Ship alone. Gate: `bin/qtest --changed` reports no visual-affecting change. + +### 1a.2 — Delete `--color-primary` + +The 213-site codemod (161 `var()` + 52 literals). Each consumer resolves to a +new token by role, **not** by find-and-replace: + +| Old use | New | +|---|---| +| link / accent text | `--color-ruby` | +| body / heading text that happened to be blue | `--ink-900` / `--ink-700` | +| borders, dividers | `--line` | +| section backgrounds | `--surface-raised` | + +**Verify on rendered output, not source.** The 2026-08-14 lesson holds: a text +ratchet globbing source missed three defects that existed only after compose. +Grep the built HTML/CSS in `_dest/` for `1a8cff` and `0066d6` and expect zero. + +### 1a.3 — Retire the `#0066d6` anchor rule + +`a:not(.btn):not(...)×7 { color: #0066d6 }` — ~8 class-levels of specificity, +shipped after page slices, which is why **three** pages fight it with scoped +`!important`. Replace with role-scoped link colours, then **delete all 55 +workarounds** and confirm the pages still render correctly. This is the phase's +biggest debt payoff and its clearest success signal: if the `!important`s can't +all go, the replacement isn't right yet. + +### 1a.4 — The rest of the recolour + +Footer onto `surface-ink`; one eyebrow style; three button roles; tags to ink +site-wide (blog already done — this brings the other 17 bundles in line). + +--- + +## Gates (ADR-0004; no A/B is available at ~9.7 human sessions/day) + +- **A — qualitative, before merge.** Full-page screenshots of every affected + template at 1440×900 **and 390×844**, reviewed not just captured; mobile + weighted at least as heavily (28% of clicks from 6% of impressions). Both + `bin/test` and `bin/dtest` legs green. After ship: ≥20 Clarity sessions read, + watching for the recolour's characteristic failure — **a link that no longer + looks like a link**. +- **B — guardrails, 28/28.** GSC average position on the top 20 URLs (prefix + property), Bing+DDG sessions as the human proxy, LCP. Thresholds written into + the PR **before** merge. +- **C — reversibility.** One PR; revert restores the previous palette whole and + leaves the site coherent. + +**Contrast is a hard gate, not a guardrail.** Every new text/background pair +must clear WCAG AA (4.5:1 normal, 3:1 large) — `--color-ruby` `#cc342d` is +5.1:1 on white and passes both directions; anything that doesn't clear gets a +darker role token rather than a waiver. + +--- + +## Baselines + +Every phase step re-records with `bin/record-baselines ...` (Phase 0.2), +never a bare `FORCE_SCREENSHOT_UPDATE`. Linux legs go through the CI dispatch +(`gh workflow run test.yml --ref -f update-baselines=true`) — local ARM +Docker records plant false drift. + +Expect 1a.2–1a.4 to move most of the 262 macOS baselines. That is the phase's +real cost and the reason 1a.1 ships separately. + +--- + +## Not in this phase + +Spatial changes (1b), page-bundle rewrites (2.4 homepage, 2.5 single-service), +the typeface, the cover system, and the PostCSS pipeline. The blog surfaces are +already on the light palette and need only the `!important` cleanup in 1a.3. + +## Open + +Nothing blocking. The whole-blog engagement read (28d from the +`e1fa5409d` deploy, [40.01](../40-49-measurement/40.01-blog-engagement-baseline.md)) +runs in parallel and does **not** gate this phase — per Paul's 2026-08-20 +pivot, measurement follows the rebuild rather than gating each step. diff --git a/docs/projects/2608-site-design-system/README.md b/docs/projects/2608-site-design-system/README.md index 3460e5c8c9..acb94d373d 100644 --- a/docs/projects/2608-site-design-system/README.md +++ b/docs/projects/2608-site-design-system/README.md @@ -32,8 +32,11 @@ mobile; and a lead conversion event exists so the next change can be measured. `/services/vibe-code-rescue/`. They agree on structure (proof in fold 1, one repeated CTA, artifacts not stock photos, ~4,300px, no blue) and disagree on one thing: light vs dark background. This is extraction, not a rebrand. -- **Open decision, blocks Phase 1a only:** which palette. See open question 0 in - the plan. +- **Palette resolved 2026-08-20: LIGHT** (Paul, on a lightning demo of the peer + set — every services peer is light, and even Linear ships a light marketing + site). Dark stays in three places: blog cover art, one dark proof band per + page, and `/services/vibe-code-rescue/` as a dark-variant campaign page. + Phase 1a is unblocked — see [20.02](20-29-strategy/20.02-phase-1a-plan.md). ## Current state @@ -46,11 +49,12 @@ engagement** vs site avg 33–40% / 28–34s. | Phase | What | Gate | Status | |---|---|---|---| | 0 (slim) | record-baselines wrapper, blog scroll/CTA events, coverage, baseline doc | events verified firing | **merged #489**; GA4 key-event toggles are agent-doable via the GA4 UI (see #495), not Paul's | -| 2.1 | `blog-list` restyle + tag pages | A + B + C | **shipped 2026-08-20** (PR pending) | -| 2.2 | posts: article-end CTA, ink tags (measure/full-bleed/code-ink deferred to 1a) | A + B + C | **shipped 2026-08-20** (PR pending) | -| 2.2b | **Whole-blog rebuild** (post template: full-bleed cover, ink code blocks, meta-above-title; mobile covers on lists) | A + C | **in progress** (Paul 2026-08-20: no measure gates) | +| 2.1 | `blog-list` restyle + tag pages | A + B + C | **merged #487** | +| 2.2 | posts: article-end CTA, ink tags | A + B + C | **merged #487/#489** | +| 2.2b | Whole-blog rebuild: post header, ink code, wide + responsive mobile covers | A + C | **merged #494** | | — | Whole-blog measure (28d after rebuild deploys, [40.01](40-49-measurement/40.01-blog-engagement-baseline.md) protocol) | informational | after rebuild | -| 1a/1b | Site-wide chrome (recolour, then spatial) | A + B + C | after the read | +| **1a** | Site-wide recolour: token promotion, `--color-primary` deletion, retire the `#0066d6` rule (+55 `!important`s) | A + B + C | **ready** — [20.02](20-29-strategy/20.02-phase-1a-plan.md) | +| 1b | Spatial chrome (dead fold, nav 7→5, space scale) | A + B + C | after 1a | | 2.4/2.5 | homepage, single-service | A + B + C, GSC gate on homepage | after the read | | 3 | Content: real numbers, sample report | A + cold-eyes review | parallel |