Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .okf/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
67 changes: 49 additions & 18 deletions docs/adr/0003-site-design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

**ADR-0003**
**Date:** 2026-08-20
**Status:** Proposed
**Status:** Accepted (palette resolved 2026-08-20)

## Title

Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <glob>...` (Phase 0.2),
never a bare `FORCE_SCREENSHOT_UPDATE`. Linux legs go through the CI dispatch
(`gh workflow run test.yml --ref <branch> -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.
16 changes: 10 additions & 6 deletions docs/projects/2608-site-design-system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 |

Expand Down
Loading