# Self-hosted fonts — Mokosh design integration

These WOFF2 files are bundled with the extension and loaded at runtime
via the `@font-face` rules in `../tokens.css`. MV3 CSP (`style-src 'self'`
+ `font-src 'self'`) forbids remote `@import` URLs at runtime; the
production `tokens.css` therefore references local `./fonts/*.woff2`
paths exclusively.

## What's bundled

| File                              | Family           | Weight axis | Style  | Size  | Source                                   |
|-----------------------------------|------------------|-------------|--------|-------|------------------------------------------|
| `Lora-VariableFont.woff2`         | Lora             | 400-700     | normal | ~49KB | cyrealtype/Lora-Cyrillic (Cyreal, OFL)   |
| `Lora-Italic-VariableFont.woff2`  | Lora             | 400-700     | italic | ~53KB | cyrealtype/Lora-Cyrillic (Cyreal, OFL)   |
| `IBMPlexSans-Regular.woff2`       | IBM Plex Sans    | 400 fixed   | normal | ~24KB | IBM/plex (master, OFL)                   |
| `IBMPlexSans-Medium.woff2`        | IBM Plex Sans    | 500 fixed   | normal | ~25KB | IBM/plex                                 |
| `IBMPlexSans-SemiBold.woff2`      | IBM Plex Sans    | 600 fixed   | normal | ~25KB | IBM/plex                                 |
| `IBMPlexSans-Bold.woff2`          | IBM Plex Sans    | 700 fixed   | normal | ~23KB | IBM/plex                                 |
| `IBMPlexMono-Regular.woff2`       | IBM Plex Mono    | 400 fixed   | normal | ~15KB | IBM/plex                                 |
| `IBMPlexMono-Medium.woff2`        | IBM Plex Mono    | 500 fixed   | normal | ~15KB | IBM/plex                                 |

Total bundle: ~236 KB after subsetting.

## R2 designer substitution (2026-05-19)

The original D-05 design pairing called for **Newsreader** as the display
serif. Per designer reply 2026-05-19 (followed up at
`.planning/intel/brand-decisions-v1-followup-display-font.md`),
**Lora** replaces Newsreader because:

1. Lora ships with full **Cyrillic basic** coverage via the Cyreal
   foundry release (designed by Olga Karpushina). Newsreader's Cyrillic
   was partial / inconsistent across weights.
2. Lora is a single **variable font** (axis `wght` 400-700) — one face
   covers the entire range rather than four separate static weights.
3. License is the same SIL OFL-1.1 + free for commercial use.

Both normal and italic upstream files are bundled separately because
Lora-Cyrillic releases italic as its own variable file (axis `wght`,
same range). Verified via the upstream GitHub contents API at execute
time per RESEARCH §A5.

## Subset coverage

All faces are subsetted via `pyftsubset` to:

```
U+0020-007E   (Latin core: ASCII printable)
U+00A0-00FF   (Latin-1 supplement: nbsp, ©, ®, ™, etc.)
U+0131        (dotless i)
U+0152-0153   (Œœ)
U+0301        (combining acute accent)
U+0400-045F   (Cyrillic basic: full Russian + Ukrainian alphabet)
U+0490-0491   (Ґґ — Ukrainian)
U+04B0-04B1   (Ұұ — Kazakh)
U+2116        (№ — common Russian punctuation)
```

This is sufficient for the operator-facing copy specified in
`_locales/{en,ru}/messages.json` and the welcome page hero (per
brand-decisions-v1.md D-08 tagline).

## License

All faces ship under SIL OFL-1.1:

- `LICENSE-Lora.txt` — verbatim copy of the Lora-Cyrillic OFL.txt header
  + Lora Project Authors copyright.
- `LICENSE-IBM-Plex.txt` — verbatim copy of IBM/plex LICENSE.txt + IBM
  Corp. copyright.

Per OFL-1.1 §4 (Reserved Font Name): keep the original family names
(`Lora`, `IBM Plex Sans`, `IBM Plex Mono`) when distributing the
subsetted derivatives, since neither family name is being modified.

## Regeneration recipe

Network access required (upstream is GitHub).

```bash
SCRATCH=$(mktemp -d)

# Lora variable + italic (Cyreal foundry)
curl -L -o "${SCRATCH}/Lora-VariableFont_wght.ttf" \
  'https://raw.githubusercontent.com/cyrealtype/Lora-Cyrillic/main/fonts/variable/Lora%5Bwght%5D.ttf'
curl -L -o "${SCRATCH}/Lora-Italic-VariableFont_wght.ttf" \
  'https://raw.githubusercontent.com/cyrealtype/Lora-Cyrillic/main/fonts/variable/Lora-Italic%5Bwght%5D.ttf'

# IBM Plex Sans (4 weights)
for v in Regular Medium SemiBold Bold; do
  curl -L -o "${SCRATCH}/IBMPlexSans-${v}.ttf" \
    "https://raw.githubusercontent.com/IBM/plex/master/packages/plex-sans/fonts/complete/ttf/IBMPlexSans-${v}.ttf"
done

# IBM Plex Mono (2 weights)
for v in Regular Medium; do
  curl -L -o "${SCRATCH}/IBMPlexMono-${v}.ttf" \
    "https://raw.githubusercontent.com/IBM/plex/master/packages/plex-mono/fonts/complete/ttf/IBMPlexMono-${v}.ttf"
done

# Run the subsetter
scripts/subset-fonts.sh "${SCRATCH}"

# Verify outputs
ls -la src/shared/fonts/*.woff2
du -ch src/shared/fonts/*.woff2 | tail -1
```

After regeneration, run the test gate:

```bash
SKIP_BUILD=1 npx vitest run tests/build/fonts-present.test.ts
```

## Why not Google Fonts CDN at runtime

MV3 manifest CSP enforces `style-src 'self'` and `font-src 'self'`. A
`@import url(https://fonts.googleapis.com/...)` line in `tokens.css`
would fail at runtime with a `Content Security Policy: The page's
settings blocked the loading of a resource...` error.

The handoff `tokens.css` in `.planning/intel/design-incoming/` carries a
preview-only Google Fonts `@import` (line 12) marked as "loaded from
Google Fonts in PREVIEW HTML only" — `src/shared/tokens.css` replaces
that line with the seven local `@font-face` rules pointing at this
directory.

## See also

- `.planning/phases/01-stabilize-video-pipeline/01-12-PLAN.md` —
  authoritative plan for this design integration
- `.planning/phases/01-stabilize-video-pipeline/01-12-RESEARCH.md` —
  research basis (typography pairing, R2 substitution rationale,
  subsetting recipe)
- `.planning/intel/brand-decisions-v1.md` D-05 — typography pairing
- `.planning/intel/brand-decisions-v1-followup-display-font.md` — R2
  Lora substitution context (designer reply 2026-05-19)
