--- /** * The page shell. Every route renders through this. * * Metadata is not optional and not a prop this layout can default: it forwards * whatever it is given straight to SEO.astro, which throws if the title or * description is out of the range docs/04-seo-spec.md sets. */ import '../styles/global.css'; import type { Props as SeoProps } from '../components/SEO.astro'; import SEO from '../components/SEO.astro'; import SiteHeader from '../components/SiteHeader.astro'; import SiteFooter from '../components/SiteFooter.astro'; import { SITE } from '../data/site'; export type Props = SeoProps; // SITE.locale is `en_CA` — Open Graph's underscore form. The lang attribute // takes the BCP 47 hyphen form. One source, two spellings, no second constant. const lang = SITE.locale.replace('_', '-'); --- { /* No SVG favicon. The mark is a shaded ribbon, not flat vector paths, so there is no honest SVG of it to serve — see InfinityMark.astro and AGENTS.md Q38. The .ico carries 16/32/48, and is what crawlers request at the root regardless of what is declared here. */ } { /* Only the two faces used above the fold, per docs/02. Fonts are always fetched in CORS mode, so a preload without `crossorigin` is a second, wasted request rather than a warmed cache. */ } { /* No script tag. Not "no framework", not "minimal JS" — none. The reveal used to be an inline IntersectionObserver here. It ran before first paint so nothing flashed, and it took its own class back off if anything threw. It was still wrong: docs/05-backend-spec.md specifies `script-src 'self'` with no `unsafe-inline`, so the single script on the site was the one thing the site's own CSP would refuse to execute — and a per-build hash drifts from the policy that is supposed to pin it. `animation-timeline: view()` in global.css does the same job in CSS. See the Reveal block there for why the @supports gate is load-bearing. */ }