# Performance — Speed Is Design > A beautiful page that loads in 5 seconds reads as broken. Speed is not engineering garnish — it is part of the aesthetic. Quiet, fast, immediate: the same words that describe good design describe good performance. Agents over-ship: three fonts, a framework, and a chat widget for a page that could be HTML and 40KB of CSS. This file is the counterweight. --- ## Budgets (decide before building) | Metric | Budget | Why | |---|---|---| | **LCP** | < 2.5s (mobile, 4G throttled) | The "is this page real?" moment | | **INP** | < 200ms | Interaction feels instant, not sluggish | | **CLS** | < 0.1 | Nothing jumps while reading | | Page weight — marketing page | < 1 MB, and < 300 KB on the wire critical path | Respect the visitor | | Page weight — content page | < 500 KB | Text is cheap; bloat is chosen | | Fonts | ≤ 2 families, ≤ 4 files total, ≤ ~300 KB | See below | | JS — mostly-static page | ≤ 50 KB, or **none** | If CSS can do it, CSS does it | If a requirement breaks the budget, say so and cut the requirement — don't ship the slow version silently. --- ## Fonts (the #1 agent-made slowdown) The full setup is in `typography.md` §Loading Fonts. The floor: ```html ``` ```css @font-face { font-family: 'Inter'; src: url('/fonts/InterVariable.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; unicode-range: U+0000-00FF; /* subset to what you actually use */ } ``` Rules: - **Variable font > family of static weights.** One file, every weight. - Load **woff2 only.** No ttf, no eot, no woff fallback chain from 2015. - `font-display: swap` (or `optional` for non-critical faces) — invisible text is a broken page. - Preload **only** the display face used above the fold. Preloading everything defeats preloading. - Google Fonts is acceptable for demos; self-host for production — privacy, one fewer origin, no third-party CSS chain. - **Fallback metrics** kill the swap "jump" (`size-adjust`, `ascent-override`) — CLS goes to near zero: ```css @font-face { font-family: 'Inter-fallback'; src: local('Arial'); size-adjust: 107%; ascent-override: 90%; descent-override: 22%; } ``` --- ## Images Agents love full-bleed PNGs. Kill them: 1. **Format:** AVIF > WebP > JPEG. PNG only for flat graphics that SVG can't do. 2. **Responsive:** every content image ships `srcset` + `sizes`: ```html Halftone studio — shelving system installed for Mira Almeida, Lisbon ``` 3. **Reserve space:** `width` + `height` attributes (or CSS `aspect-ratio`) on **every** image. Unreserved images are the top cause of CLS. 4. **Lazy-load below the fold; never lazy-load the LCP image.** The hero image gets the opposite treatment: ```html ``` 5. Hero/background images ≤ 200 KB after compression. If it can't compress, it should be CSS or SVG — see `imagery.md`. 6. `prefers-reduced-data` exists; treat giant decorative media as optional, not mandatory. --- ## CSS - **One stylesheet** for a marketing page, hand-written, token-driven (`color.md`, `layout.md`). It will be smaller than any utility purge. - No `@import` chains (serialized downloads). `` in `head`, once. - The examples in `examples/` embed CSS in a single HTML file for portability. **In production, split it out** — page cacheability matters from visitor two onward. - Critical CSS is a last resort for heavy pages, not a default. A 30KB stylesheet doesn't need inlining logic. - `content-visibility: auto` on long below-the-fold sections is free render speed: ```css .section { content-visibility: auto; contain-intrinsic-size: auto 600px; } ``` --- ## JavaScript — Ship None If You Can Ask in order: 1. **Does this need JS at all?** Menus (`
`), accordions (`
`), carousels (scroll-snap), tabs (radio inputs), dialogs (``), theme toggle (no — server/inline), hover reveals (CSS). 2. If yes — **progressive enhancement**: the content works with JS disabled, JS upgrades it. 3. If a framework is already justified by the brief (real app state, product UI), fine — but a landing page in a SPA is slop with extra steps. Rules when JS is used: ```html ``` - `defer` / `async` / `type="module"` — never a blocking `