`styles.css` line 1 carried a malformed rule for the life of the site:
@font-face{font-family:Manrope;src:url('https://fonts.googleapis.com/css2?...')}
`src:` in an @font-face must point at a font binary. That URL returns a CSS
stylesheet, so no browser could ever load a face from it. Every
`font-family:Manrope,Arial,sans-serif` fell through to Arial, and 'DM Mono' was
never declared as a family at all, so it fell through to generic monospace. The
intended typography has never once been seen.
Task 02 spotted this and was told to default to deleting the dead rule and
declaring the stacks that actually render. It recorded that decision, deferred
the deletion to "future component tasks", and nothing picked it up. The human
has now chosen the other branch: the real fonts.
Self-hosted rather than linked from fonts.googleapis.com because
scripts/audit-ui.mjs rejects any external <link>/<script>, and because the site
is presented in workshop rooms with unreliable networks. Latin and latin-ext
subsets only — the site is EN and PT-BR, so the cyrillic, greek and vietnamese
subsets Google also serves are dropped. Manrope ships as one variable file
covering 400-800. 89 KB total across six faces, all SIL OFL.
One public/fonts/fonts.css serves both trees, with relative url()s that each
consumer resolves against that file's own location: BaseLayout.astro links it
for Astro pages, the legacy root styles.css @imports it.
This changes how every page renders. That is the point, and it is the one
sanctioned visual change in the migration — screenshots taken before today show
Arial and are no longer a valid baseline. The three governing documents that
said "do not add a webfont" are updated so the next design-system-keeper does
not undo this.
Adds .stylelintignore, mirroring .prettierignore's legacy list for the same
reason: staging the minified styles.css to change one declaration produced ~180
declaration-block-single-line-max-declarations errors and blocked the commit.
public/fonts/fonts.css is deliberately excluded from that ignore list.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5.7 KiB
Context: the design system (as it actually is)
Read this before touching CSS. Everything here was extracted from the current files, not assumed.
There is no single palette. There are three.
The same semantic names carry different values depending on which stylesheet loaded them:
| Token | styles.css, rules/styles.css |
chapters.css, skills-review/styles.css |
hands-on/*/styles.css |
|---|---|---|---|
--paper |
#f5f4f1 |
#f6f3ed |
#f4f3ef |
--ink |
#172f42 |
#122534 |
#173044 |
--muted |
#697b89 |
#65717a |
#687d8c |
--line |
#d8dee2 |
#d0d5d2 |
#d5dde1 |
--blue |
#527f9f |
#215675 |
#5683a1 |
--gold |
#efc76b |
#ebbf58 |
#efc86d |
--accent |
#7c78a8 |
— | — |
--deep |
#102536 |
— | — |
--red |
— | #a7483f (chapters only) |
— |
--violet |
— | #6b668f (review desk only) |
— |
Most deltas are a few units per channel — drift, not intent. --blue is the
exception: #527f9f vs #215675 is a visible difference and may be deliberate.
Decision required before any component work (task 02). Options:
- Canonicalize to one palette. Recommended. The sub-perceptual deltas
collapse; only
--blueneeds a human's eye on a before/after screenshot. - Keep three named surfaces (
--surface-guide,--surface-chapter,--surface-lab) if the drift turns out to be intentional per section.
Do not "just pick one" silently in the middle of another task. This is its own reviewed change with visual diffs attached.
The typography — fixed 2026-09-05
styles.css line 1 used to read:
@font-face {
font-family: Manrope;
src: url('https://fonts.googleapis.com/css2?family=DM+Mono&family=Manrope:wght@400;600;700;800&display=swap');
}
src: in an @font-face must point at a font binary. That URL returns a CSS
stylesheet, so no browser could load a face from it. For the whole life of the
site, every font-family:Manrope,Arial,sans-serif rendered as Arial and
every font:… 'DM Mono',monospace rendered as the generic monospace face —
'DM Mono' was never declared as a family at all.
This was escalated and the human chose the real fonts. Manrope and DM Mono
are now self-hosted in public/fonts/, latin and latin-ext subsets only, under
the SIL Open Font License. One fonts.css serves both trees: Astro links it
from BaseLayout.astro, the legacy root styles.css @imports it. Self-hosted
rather than linked from Google because scripts/audit-ui.mjs rejects any
external <link>/<script>, and because the site is presented in workshop
rooms with unreliable networks.
This changed how every page renders, deliberately. It is the one sanctioned visual change in the migration. Screenshots taken before 2026-09-05 show Arial and are no longer a valid baseline.
Georgia, serif for emphasis (h1 em, .hero em) is untouched and still real.
Type scale
Georgia, serif is used deliberately for emphasis (h1 em, .hero em) and is
real — it is a system font, so it does render. Keep it.
Sizes are all clamp(), roughly:
| Role | Value |
|---|---|
Display / h1 |
clamp(56px,9vw,126px) |
Section h2 |
clamp(36px,5vw,65px) |
| Sub-head | clamp(24px,3vw,38px) |
| Pull-quote | clamp(22px,3vw,36px) |
| Body | 15px/1.6 … 18px |
| Eyebrow / label | 10–11px monospace, letter-spacing:.08–.1em, uppercase |
There are 14+ distinct clamp triples doing near-identical jobs. Collapse to a
named scale (--step-0 … --step-6) during tokenization; the visual result
should be unchanged within a pixel or two at common viewports.
Breakpoints
Sixteen distinct max-widths are in use: 420, 520, 530, 560, 600, 620, 720, 800,
850, 880, 900, 1000, 1050, 1100 — plus min-width:1600px and
min-width:2200px.
Collapse to a named set (suggested: 560 / 800 / 1100 / 1600 / 2200) and prove equivalence with screenshots at the old breakpoint values, since that is where regressions will hide.
@media(prefers-reduced-motion:reduce) is already respected in several
stylesheets. Keep it — see ../rules/animation.md.
House style worth preserving
The visual identity is editorial-print: flat colour blocks, hairline 1px
rules, uppercase monospace eyebrows with wide tracking, very tight negative
letter-spacing on display type (-.06em … -.08em), grid layouts with
gap:1px over a background colour to fake borders, and near-zero border-radius.
That last trick (gap:1px + parent background) is used everywhere. It is
intentional. Do not replace it with border.