feat: build step 1 — scaffold, layout, header, footer, SEO; zero JavaScript
Build order step 1 (docs/01): scaffold, tokens, base layout, header, footer, SEO component, plus a temporary /type-scale/ proof sheet that step 2 deletes. THE FONTS WERE NEVER ON DISK. global.css declared six @font-face rules pointing at /fonts/*.woff2 and public/fonts/ did not exist, so every face had been silently falling back to Georgia and the system sans. Six cuts committed, 123,804 bytes, SIL OFL 1.1, provenance in docs/reference/fonts-provenance.md. ?v=1 on every URL because the deploy script serves them immutable for a year. ZERO JAVASCRIPT. The reveal was an inline IntersectionObserver in <head>; docs/05 specifies script-src 'self' with no unsafe-inline, so the only script on the site was the one thing the site's own CSP would refuse to execute. Replaced with animation-timeline: view() behind @supports. 0 script tags and 0 .js files in dist. The infinity mark is lifted verbatim from the deployed site's own smlMark loading thumbnail, not redrawn (Q32 asks whether a canonical vector exists). The proof sheet computes its contrast table from tokens.css rather than restating docs/02 — all eleven ratios reproduce the measured table exactly. Register: Canadian Tax Foundation added (§4, R10 widened); Q30 closed — SML Company Ltd is federally incorporated under the CBCA, and the footer publishes neither that nor the place of business; Q31 closed — Plausible, on EU-only data residency (D15 amended). ROLE constants added for "Director of Firm Operations" and "active litigation exposure" so step 3 does not hand-type them. Lighthouse unavailability now stated in six places rather than left as a control that had silently stopped existing (§7, R11). Both review agents ran twice. The second pass found four defects in the first pass's fixes, including the minifier bug written back into its own fix and a colour-alone repair that used the banned gold-on- cream pairing at 2.10:1. Measured in headless Chrome at thirteen widths with a seventh nav item injected: 0 overflow, 0 tap targets under 44x44, 0 focus-order inversions, state indicators at 12.29:1, 755 words of body text with no JavaScript. Opened: Q32-Q37. Closed: Q30, Q31. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012XquaEq4BgWMCwUqLEyNkF
This commit is contained in:
co-authored by
Claude Opus 5
parent
8f1df2c27c
commit
8134709548
@@ -25,7 +25,7 @@ system that preserves it while fixing what the old build got wrong.
|
||||
| Ad hoc spacing values | 8 px base scale | Consistent vertical rhythm; no magic numbers |
|
||||
| Gold used as a text colour on cream | Gold restricted to decorative and on-dark | **It fails WCAG AA at 2.10:1.** Measured, not assumed |
|
||||
| Scroll-reveal on every element, always on | Reveal on major sections only, gated behind `prefers-reduced-motion` | Motion that reads as confident rather than decorative; accessible by default |
|
||||
| 2.2 MB single file, ~1 MB of base64 logos | Optimized SVG mark, AVIF/WebP photography | The mark is geometry; it should be vector, not a 470 KB PNG |
|
||||
| 2.2 MB single file, ~1 MB of base64 logos *(both figures under review — `AGENTS.md` Q34; and "470 KB PNG", which this row used to assert, has no source anywhere in the repo and has been removed)* | Optimized SVG mark, AVIF/WebP photography | The mark is geometry, so it should be vector. That holds whatever the old file weighed |
|
||||
| React 18 dev build + Babel Standalone in the browser | Static HTML, near-zero JS | The reason the site is invisible to crawlers |
|
||||
|
||||
---
|
||||
@@ -116,7 +116,13 @@ the display end. Tokens `--text-xs` through `--text-6xl` in `tokens.css`.
|
||||
|
||||
Content width `1280px`; prose measure `68ch`; wide media `1440px`.
|
||||
Gutters: `24px` mobile, `48px` desktop.
|
||||
Section rhythm: `--space-9` (96px) mobile, `--space-11` (160px) desktop.
|
||||
Section rhythm: `--space-9` (96 px) mobile, `--space-11` (160 px) desktop —
|
||||
`--section-y` in `tokens.css`. *Corrected 2026-08-26:* the curve was
|
||||
`6vw + 2rem`, which reaches 160 px only at a **2133 px** viewport, so the
|
||||
desktop half of this line was never delivered (measured 108.8 px at 1280 px,
|
||||
128 px at 1600 px). It is now `9vw + 1rem`, which reaches 160 px at 1600 px. If
|
||||
you change the curve, re-measure — a `clamp()` whose upper bound is unreachable
|
||||
reads exactly like one that works.
|
||||
|
||||
Grid: 12 columns desktop, 6 tablet, 4 mobile, `--space-5` gutter.
|
||||
|
||||
@@ -131,11 +137,24 @@ deliberate and quiet.
|
||||
stagger is limited to card grids, and capped at six children.
|
||||
- Duration `600ms`, easing `cubic-bezier(.2,.7,.2,1)`. Transform and opacity
|
||||
only — never layout properties.
|
||||
- Implement with `IntersectionObserver` in one tiny inline script, or
|
||||
`animation-timeline: view()` where supported. Not a framework, not a library.
|
||||
- **Content is visible without JavaScript.** The reveal is an enhancement layered
|
||||
on top of already-rendered HTML. If the observer never runs, the page reads
|
||||
normally. The old build had this exactly backwards.
|
||||
- Implement with **`animation-timeline: view()`**, behind `@supports`. *Amended
|
||||
2026-08-26:* the `IntersectionObserver` alternative this line used to offer
|
||||
first is now ruled out, not merely second choice. It has to run inline in
|
||||
`<head>` to avoid a flash, and `05-backend-spec.md` specifies `script-src
|
||||
'self'` with no `unsafe-inline` — so the only script on the site would have
|
||||
been the one thing the site's own CSP refuses to execute, and a per-build hash
|
||||
drifts from the policy pinning it. The CSS route ships **zero** JavaScript.
|
||||
Not a framework, not a library, not a script.
|
||||
- **Content is visible without the feature.** The `@supports` gate is
|
||||
load-bearing, not defensive: without it, a browser that ignores
|
||||
`animation-timeline` runs the animation once against the document timeline at
|
||||
load; with it, that browser gets no animation and fully visible content. The
|
||||
old build had this exactly backwards and shipped a blank page.
|
||||
- **And without a print timeline.** A scroll-driven animation has no timeline
|
||||
when printing, so a revealed element renders at its `from` state — `opacity:
|
||||
0`. Measured 2026-08-26: before the print override existed, printing a page to
|
||||
PDF dropped four card headings from the output entirely. `/about/` is written
|
||||
to be printed by people evaluating an appointment.
|
||||
- Hover transitions `250ms`.
|
||||
|
||||
```css
|
||||
@@ -159,7 +178,7 @@ except the reveal of the hero.
|
||||
| Component | Notes |
|
||||
|---|---|
|
||||
| `InfinityMark` | Inline SVG, `currentColor`, `aria-hidden` when decorative. Never a PNG |
|
||||
| `SiteHeader` | Sticky, condenses on scroll. Practice dropdown as CSS-only `<details>` |
|
||||
| `SiteHeader` | **Sticky from 64rem (1024 px) up; static below it.** Practice dropdown as CSS-only `<details>`. *Amended 2026-08-26, both halves by measurement:* **(a)** the one-row header has to hold the brand, **seven** nav items and the CTA — Insights is the seventh and arrives on its own at build step 7 — and it does that at 1024 px with 32 px of clearance, not below. Under 64 rem the nav takes its own row and the header measures **137 px** at tablet widths and **185 px** at 320–375 px, which is more of a small viewport than a sticky header is worth, so it scrolls away there. *(This row first said 60 rem and "~115 px". Both were wrong — the threshold moved when the seven-item case was measured, and 115 px was never a height the header took at any width. Corrected against headless-Chrome measurements at thirteen widths.)* **(b)** "Condenses on scroll" is now **a hairline rule and a shadow, not a size change.** A `position: sticky` header stays in normal flow, so its layout box sits at the top of the document whatever the viewport shows; shrinking its padding shortens that box and lifts every page below it — a scroll-linked layout shift on every page, against the CLS < 0.05 budget in `04-seo-spec.md`. Implemented with `animation-timeline: scroll()`, **longhands only** — see the note in the component about what the minifier does to the `animation` shorthand |
|
||||
| `SiteFooter` | Three-column sitemap, contact block, designations, entity line |
|
||||
| `Eyebrow` | Mono label with optional maroon dot |
|
||||
| `SectionHeading` | Eyebrow + display heading + optional lede, one measure |
|
||||
|
||||
Reference in New Issue
Block a user