Commit 8bb844b
* fix(landing): derive readable text from author-chosen block colours (#569)
A landing-page block that sets its own colour paired it with a hardcoded
foreground, in both directions: the preview widget painted the accent and
assumed white on top, and used the accent as text on a card whose surface it
never checked — so the published fixture's Stats block (#0f172a) rendered
dark-on-dark and its numbers vanished.
The same gap is live in the app, and worse. `accentVars()` emitted
`--block-accent` with no paired foreground, so every block painting that
surface hardcoded `text-primary-foreground`/`text-white`; `Section` went
further and dropped `themeMap[theme]` — the class carrying the text colour —
whenever an author set a background, leaving a dark section rendering the
page's dark inherited `--foreground` on the public `/` and `/p/[slug]` routes.
Tenant theming overrode `--primary` but never recomputed
`--primary-foreground`, so a pale brand colour was white-on-pale everywhere.
One helper now derives one colour from the other: `readableOn()` picks the ink
by comparing real WCAG contrast ratios against both candidates (a luminance
cut sends green-600 to white at 3.3:1 when near-black scores 5.8:1), escalating
to pure black/white for mid-tones where neither branded ink clears AA;
`accentTextOn()` handles the inverse case, keeping the author's colour when it
already passes and otherwise mixing it toward white/black until it does.
- `accentVars()` emits `--block-accent-foreground`; the six blocks that paint
an accent surface consume it instead of a hardcoded near-white
- `Section` rebinds the `--foreground` token, not just `color` — the Heading
and Text blocks carry an explicit `text-foreground` that beats inheritance
- tenant CSS vars derive `--primary-foreground` from the tenant's brand colour
- the widget's stats value computes both inks and lets the same `dark:` variant
that paints the card choose, since `resources/styles.css` is not part of the
widget build and `dark:` resolves to `prefers-color-scheme`, not the `.dark`
class the widget sets
The app copy under `lib/color/` and the widget copy under `mcp-server/` are
deliberate mirrors: mcp-server is not an npm workspace of the root package and
cannot import from `lib/`.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ax5eoJNqvCPk8zqsmv9V3x
* docs(qa): before/after contrast evidence for #569
Screenshots referenced from the pull request: the landing-page-preview widget
in light and dark for both demo fixtures, and a real Code Academy landing page
carrying a dark section, a pale section, a pale-accent hero and CTA.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ax5eoJNqvCPk8zqsmv9V3x
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent b1134e7 commit 8bb844b
24 files changed
Lines changed: 945 additions & 29 deletions
File tree
- components/tenant
- docs/qa/569
- lib
- color
- puck
- components
- layout
- lms
- primitives
- utils
- mcp-server
- resources
- landing-page-preview
- tests
- tests/unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
1 | 2 | | |
2 | 3 | | |
3 | 4 | | |
| |||
63 | 64 | | |
64 | 65 | | |
65 | 66 | | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
66 | 78 | | |
67 | 79 | | |
68 | 80 | | |
| |||
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
Loading
0 commit comments