# Typography Guide — Agent Instructions

This file is the single source of truth for how typography works in this design system. It covers fonts, the token system, usage rules, and guidelines for extending the scale.

---

## Fonts

| Variable | Family | Weights | Use |
|----------|--------|---------|-----|
| `--font-sans` | Figtree, system-ui, sans-serif | 400 (Regular), 500 (Medium), 600 (SemiBold), 700 (Bold) | All UI text and presentations |
| `--font-mono` | Geist Mono, monospace | 100–900 (variable) | Code, terminal, technical content, presentation numeric labels |

### Hosted Font Files

Figtree is an open-source font (SIL Open Font License) by Erik D. Kennedy. The design system hosts the variable font file so agents and downstream projects can use it without a local install.

| Weight axis | File | Hosted URL |
|-------------|------|------------|
| 300–900 (variable) | `Figtree-VariableFont_wght.ttf` | `https://mirror-physics.github.io/fractal/design/fonts/Figtree-VariableFont_wght.ttf` |

> If `Figtree-VariableFont_wght.ttf` is missing from `design/fonts/`, the hosted URL will 404 and `--font-sans` will fall back to `system-ui`. Drop the file in to enable the hosted version.

#### For web / CSS

```css
@font-face {
  font-family: 'Figtree';
  src: url('https://mirror-physics.github.io/fractal/design/fonts/Figtree-VariableFont_wght.ttf') format('truetype-variations');
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}
```

#### For matplotlib / Python

Download the variable font and register it with matplotlib's font manager:

```python
from matplotlib import font_manager
import urllib.request, os, tempfile

FONT_DIR = os.path.join(tempfile.gettempdir(), 'fractal_fonts')
os.makedirs(FONT_DIR, exist_ok=True)

name = 'Figtree-VariableFont_wght.ttf'
path = os.path.join(FONT_DIR, name)
if not os.path.exists(path):
    urllib.request.urlretrieve(
        f'https://mirror-physics.github.io/fractal/design/fonts/{name}', path
    )
font_manager.fontManager.addfont(path)
```

After registration, matplotlib will resolve `'Figtree'` in `rcParams['font.family']` and in the `fractal.mplstyle` sheet.

---

## Type Scale

The system uses **four size tiers** plus **two display sizes**:

| Size | Rem | Px (at 16px base) | Tier |
|------|-----|--------------------|------|
| 1.75rem | 28px | Display |
| 1.25rem | 20px | Display |
| 1rem | 16px | 01 — chat/messaging only |
| 0.875rem | 14px | 02 — primary app UI |
| 0.75rem | 12px | 03 — small/dense UI |
| 0.625rem | 10px | 04 — incidental metadata (mono only) |

**Important:** Tier 01 (1rem) is reserved for chat message content — user messages, agent messages, and their associated headers. Everything else in the app uses tier 02 (0.875rem) or lower. Tier 04 (0.625rem) is restricted to monospaced, non-interactive metadata — values the user glances at to confirm, not reads.

---

## Token Reference

### Display

| Token | Family | Size | Weight | Line-height | Extra |
|-------|--------|------|--------|-------------|-------|
| `display-01` | sans | 1.75rem | 600 | 1.2 | letter-spacing: -0.02em |
| `display-02` | sans | 1.25rem | 600 | 1.3 | — |

Use for hero text, welcome screens, and page-level headings. These are rare in the app — most section headings should use `header-01` or `header-02`.

### Headers

| Token | Family | Size | Weight | Line-height |
|-------|--------|------|--------|-------------|
| `header-01` | sans | 1rem | 500 | 1.4 |
| `header-02` | sans | 0.875rem | 500 | 1.4 |
| `header-03` | sans | 0.75rem | 500 | 1.4 |

Headers use weight 500 (Medium) to distinguish from body text at the same size. `header-01` is for chat context only. `header-02` is the default for all app UI headers.

### Body

| Token | Family | Size | Weight | Line-height |
|-------|--------|------|--------|-------------|
| `body-01` | sans | 1rem | 400 | 1.5 |
| `body-02` | sans | 0.875rem | 400 | 1.4 |

`body-01` has line-height 1.5 (not 1.4) because it is used in chat messages where longer-form reading benefits from more breathing room. `body-02` is the workhorse — use it for all general app text.

### Caption

| Token | Family | Size | Weight | Line-height |
|-------|--------|------|--------|-------------|
| `caption-01` | sans | 0.75rem | 400 | 1.4 |

Use for timestamps, metadata, footnotes, and secondary information.

### Label

| Token | Family | Size | Weight | Line-height | Extra |
|-------|--------|------|--------|-------------|-------|
| `label-01` | sans | 0.75rem | 600 | 1.4 | uppercase, letter-spacing: 0.5px |

Use for badges, tags, status indicators, and any small text that needs to command attention at a glance. The uppercase + spacing distinguishes labels from captions.

### Meta

| Token | Family | Size | Weight | Line-height |
|-------|--------|------|--------|-------------|
| `meta-01` | mono | 0.625rem | 400 | 1.4 |

Use for incidental metadata — supplementary numeric/technical detail that the user glances at to confirm a known value, not to learn new information. Examples: timestamps, file sizes, lattice parameters, coordinate readouts, hex values, log entry metadata. Always monospaced. Never used for interactive or actionable text.

### Code

| Token | Family | Size | Weight | Line-height |
|-------|--------|------|--------|-------------|
| `code-01` | mono | 1rem | 400 | 1.5 |
| `code-02` | mono | 0.875rem | 400 | 1.4 |
| `code-03` | mono | 0.75rem | 400 | 1.4 |

Code tokens mirror the sans tiers. `code-01` is for code blocks within chat messages. `code-02` is for code in the app UI (editors, previews). `code-03` is for dense contexts (tool call results, inline code in captions).

---

## Usage Rules

### Tier discipline

- **Tier 01 (1rem):** `body-01`, `header-01`, `code-01` — chat messages and their immediate context only.
- **Tier 02 (0.875rem):** `body-02`, `header-02`, `code-02` — the default for all app UI.
- **Tier 03 (0.75rem):** `caption-01`, `header-03`, `code-03`, `label-01` — secondary info, dense UI, small metadata.
- **Tier 04 (0.625rem):** `meta-01` — incidental metadata only. Mono, non-interactive, reference-not-reading.
- **Display:** `display-01`, `display-02` — hero/welcome screens, page-level headings. Use sparingly.

### Weight discipline

Only three weights are used. **The maximum is SemiBold (600). Never use Bold (700).**

| Weight | Name | Used in |
|--------|------|---------|
| 400 | Regular | body, caption, code |
| 500 | Medium | headers |
| 600 | SemiBold | labels, display tokens, slide titles, table headers — the heaviest weight allowed |

Do not use weight 300 (Light), 700 (Bold), 800 (Heavy), or 900 (Black). Keep the weight range tight.

In CSS, write `font-weight: 600` explicitly — never `font-weight: bold` (which most browsers map to 700). In `pptxgenjs`, do not pass `bold: true` on text runs (it emits `b="1"` and PowerPoint renders that at 700 weight in any font that has a Bold variant). Reach for SemiBold by either:

- specifying the SemiBold face explicitly: `fontFace: 'Figtree SemiBold'`, **or**
- accepting Medium (500) for that run if SemiBold isn't needed for the contrast.

### Pairing rules

- A header and its adjacent body text should be the same tier: `header-02` pairs with `body-02`, `header-01` pairs with `body-01`.
- Code within body text should match: `code-02` inside `body-02` contexts, `code-01` inside `body-01` contexts.
- Captions and labels always sit below their parent context — use `caption-01` beneath `body-02` elements.

### Line-height rationale

| Line-height | Used in | Reason |
|-------------|---------|--------|
| 1.2 | `display-01` | Tight — large text needs less leading |
| 1.3 | `display-02` | Slightly looser for mid-size headings |
| 1.4 | Most tokens | Default — compact but readable for UI text |
| 1.5 | `body-01`, `code-01` | Generous — chat messages are longer-form reading |

---

## Mapping Old Classes to Tokens

For reference, here is how the previous custom classes map to the new token system:

| Old Class | Token |
|-----------|-------|
| `.chat-content-user`, `.chat-content-assistant`, `.chat-input` | `body-01` |
| `.chat-header-tab`, `.session-starting-text`, `.mp-label` | `header-02` |
| `.tool-call-name` | `header-02` |
| `.chat-message-system`, `.mp-input`, `.tool-call-preview` | `body-02` |
| `.terminal-welcome-text` | `display-01` |
| `.chat-welcome-hero h2` | `display-02` |
| `.code-editor`, `.terminal-input`, `.terminal-input-prompt` | `code-02` |
| `.terminal-indented`, `.terminal-tool-param`, `.claude-session-path` | `code-03` |
| `.tool-call-result-content` | `code-03` |
| `.mp-badge` | `label-01` |
| `.mp-footer`, `.terminal-tool-label` | `caption-01` |
| `.markdown-content code`, `.chat-pre-content` | `code-02` |

---

## Adding New Tokens

Before adding a new typographic token, check if an existing one covers the use case. The system is intentionally small — 13 tokens should cover the vast majority of UI needs.

If a new token is genuinely needed:

1. **It must fit the size tiers.** Do not introduce arbitrary sizes like 13px or 15px. Use the established scale: 0.625rem, 0.75rem, 0.875rem, 1rem, 1.25rem, 1.75rem.
2. **It must use an existing weight.** 400, 500, 600, or 700 only.
3. **It must follow the naming convention.** `{category}-{tier}` where category is one of: display, header, body, caption, label, meta, code.
4. **It must have a clear, distinct role.** If you can't explain how it differs from an existing token in one sentence, it's probably not needed.
5. **Document it** by adding it to this guide in the appropriate section.
