# shared/fonts/ Source `.woff2` files for the typography baked into every tool. These are the *raw* font bytes; the actual `@font-face` declarations live in `shared/fonts.css` as base64 data URIs so single-file HTML tools render identically offline (`file://`) and online with no CDN dependency. ## Files - `ibm-plex-sans-400.woff2` — UI body text (regular weight) - `ibm-plex-sans-600.woff2` — UI emphasis (semibold) - `source-serif-4-600.woff2` — display/headings (semibold) Latin subsets only. ~60 KB total raw; ~80 KB once base64-inlined. ## Regenerating `shared/fonts.css` Run from the repo root: ```sh python3 - <<'PY' import base64, pathlib fonts = [ ('IBM Plex Sans', 400, 'ibm-plex-sans-400.woff2'), ('IBM Plex Sans', 600, 'ibm-plex-sans-600.woff2'), ('Source Serif 4', 600, 'source-serif-4-600.woff2'), ] out = pathlib.Path('shared/fonts.css') lines = ['/* shared/fonts.css — base64-inlined woff2. Generated by', ' * shared/fonts/README.md instructions; do NOT edit by hand. */', ''] for family, weight, fn in fonts: b64 = base64.b64encode((pathlib.Path('shared/fonts') / fn).read_bytes()).decode('ascii') lines += ['@font-face {', f" font-family: '{family}';", ' font-style: normal', f' font-weight: {weight};', ' font-display: swap;', f" src: url(data:font/woff2;base64,{b64}) format('woff2');", '}', ''] out.write_text('\n'.join(lines)) PY ``` ## Adding or swapping a font 1. Download the new `.woff2` into this directory (latin subset only — keep the bundle small). 2. Update the `fonts` list in the snippet above to include the new family + weight + filename. 3. Re-run the regen snippet. 4. Update `--font` or `--font-display` tokens in `shared/base.css`. 5. `./build` and verify every tool's `dist/*.html` still includes `@font-face` (three by default). ## Sourcing Originally downloaded from the `@fontsource` npm packages via jsdelivr: - IBM Plex Sans 400/600: `@fontsource/ibm-plex-sans@5.0.18` - Source Serif 4 600: `@fontsource/source-serif-4@5.0.5` Both licenses are SIL Open Font License 1.1. Fontsource bundles only the latin subset by default, which is exactly what we want.