# Font swap without layout shift (`font-swap`)

Web fonts that arrive without moving a single line: a local font resized to the web font's exact footprint shows first, then swaps invisibly. With an overlay proof and an in-browser calculator for new fonts.

| | |
|---|---|
| Tag | Flow |
| Size | 0.7 KB gzipped own code (JS 0.0 KB + CSS 0.7 KB; 1.2 KB raw) |
| Dependency | none (vanilla + native APIs) |
| Best for | Every site that uses a web font (all of them) |
| Demo | `index.html` in this folder (live: https://wbl-code-lib.pages.dev/modules/font-swap/) |

## What it does
The pattern is CSS only: preload the woff2, declare it with font-display: swap, declare a 'Name Fallback' face on local(Arial) with size-adjust, ascent-override, descent-override and line-gap-override, and list both. The demo overlays the web font, the adjusted fallback and plain Arial so you can see the match. calc.js measures any font file against a local font in the browser (canvas: letter-frequency-weighted widths and the font's own ascent/descent) and prints the @font-face, no Python or fontTools needed.

## When to use
- Any font that isn't already in the builder's font_fallbacks.json (123 families are; the builder's fonts.py writes these faces automatically).
- Hand-built client sites outside the builder.

## Don't
- font-display: optional for headings (text can stay in the fallback for the whole visit).
- Preload more than the 1-2 fonts the first screen uses.
- Skip the override and hope: a 56 px headline swapping Arial → a wide display face moves the whole hero (Venti measured CLS 0.13).

## Reduced motion, no JS, accessibility
- Nothing animates.
- No JS: the CSS works on its own; calc.js is a tool, not part of the site.
- Text is visible from the first paint (swap), never invisible while loading.

## Notes
- Measure the exact file you ship. The builder's font_fallbacks.json has Archivo at 101.82% (computed from its own Archivo file); this library's variable latin subset measures 98.07% with calc.js, and with 101.82% the demo's lines visibly drifted. Inter Tight measured 96.64%, identical to the builder's fontTools number, which validates the calculator.
- The demo overlay is the quick test: red must sit on black line for line.

## Relation to the effects libraries
New as a module; the builder already applies the same method via build/font_fallbacks.py. This is the standalone, documented version plus the browser calculator.

## Use it
Copy this folder to the site as `/wbl/font-swap/` (keep `vendor/` if there is one), then:

```html
<!-- Font swap without layout shift (WBL Code Library) -->
<!-- in <head>, before the stylesheet: -->
<link rel="preload" href="/fonts/Archivo-latin.woff2" as="font" type="font/woff2" crossorigin>
<style>
@font-face{font-family:'Archivo';font-weight:400 800;font-display:swap;src:url(/fonts/Archivo-latin.woff2) format('woff2')}
@font-face{font-family:'Archivo Fallback';src:local('Arial'),local('ArialMT'),local('Helvetica'),local('Liberation Sans');
  size-adjust:98.07%;ascent-override:89.73%;descent-override:21.41%;line-gap-override:0%}
h1,h2,h3{font-family:'Archivo','Archivo Fallback',sans-serif}
</style>
```

Colours come from CSS variables with safe defaults: `--wbl-accent`, `--wbl-on-accent`, `--wbl-ink`, `--wbl-paper`,
`--wbl-card`, `--wbl-line`, `--wbl-muted`, `--wbl-band`, `--wbl-radius`, `--wbl-head` (see the library README for the
one-line bridge to the preview builder's `--a / --ink / --bg …`).

## License
Our code: WBL-owned, original. No third-party code.
