# AGENTS.md — SGT Design System

Instructions for AI coding agents working in this repository, or consuming this
design system from another project. Humans: read `CLAUDE.md` for the full
component reference; this file is the short contract.

**What this is:** the single source of truth for every Sergeant AG brand asset
that renders as HTML/CSS — websites, Reveal.js decks, emails, PDFs, print
templates. Framework-free CSS with custom properties, no build step for
consumers. Deployed at <https://brand.sergeant.agency>.

---

## Hard rules

1. **Never hand-edit `tokens.css`.** It is generated. Author in `tokens/`
   (DTCG JSON), then run `npm run build`. An edit to `tokens.css` is silently
   destroyed by the next build.
2. **Never write a raw hex, px, ms or easing curve into a component
   stylesheet.** If a value does not exist yet, add a token in `tokens/` and
   reference it as `var(--…)`. Every existing value is listed in
   [`docs/tokens.md`](./docs/tokens.md).
3. **Never change a legacy token's value.** `--sgt-*`, `--text-*`, `--lh-*`,
   `--sp-*`, `--radius-*`, `--shadow-*`, `--glass-*`, `--ease-*`, `--dur-*` are
   frozen — live decks and sites depend on the exact values. Add new tokens
   instead; the additive `--sgt-`-prefixed tiers exist for that.
4. **Never edit `sgt-presentation.css` (v3) except to fix a shipped deck.** It
   is frozen legacy. New decks use `sgt-presentation-v4.css` + `sgt-ds-slides.css`.
5. **Never load `sgt-presentation.css` together with `sgt-ds-core.css`.** They
   define `sgt-btn` / `sgt-card` / `sgt-pullquote` with different values.
6. **Run `npm run verify` before committing any token change.** It fails the
   build if a legacy variable moved or if the generated docs drift from
   `tokens.css`.
7. **Do not deploy.** Deployment is a human decision — see *Deploying* below.

## Brand constraints (these are design decisions, not preferences)

| Rule | Value | Why |
|---|---|---|
| Font weight | `400` everywhere; `500` only for small labels | Hierarchy comes from size, never weight. **Never 700 for headlines.** |
| Letter-spacing | `0.01em` (`--tracking-default`) on all text | Never negative. |
| Minimum font size | `18px` (`--text-md`) | Nothing smaller in any presentation. |
| Light slide background | `#faf8f5` warm white (v3) / `#ffffff` (v4) | Not pure white in v3 decks. |
| Logo | always the SVG, never type-set | White SVG on dark, black SVG on light. Do not brightness-filter the white one. |

---

## Where do I change what?

| I want to… | Edit | Then |
|---|---|---|
| add/change a colour, size, spacing, radius, shadow, motion value | `tokens/` | `npm run build && npm run verify` |
| add/change a general-purpose component (button, card, table…) | `sgt-ds-core.css` | — |
| add/change a slide-only component | `sgt-ds-slides.css` | — |
| change the living documentation page | `index.html` | — |
| change the token reference tables | **nothing** — `docs/` is generated | `npm run build` |
| fix a bug in an already-shipped v3 deck | `sgt-presentation.css` | keep the v3 naming scheme |

---

## File map

**Foundation**

| File | Role |
|---|---|
| `tokens/` | **Source of truth.** DTCG JSON, three tiers: `primitive/` (raw, OKLCH colours), `semantic/` (intent, light/dark modes), `component/` (tier 3). |
| `tokens.css` | **Generated.** Every legacy var at its exact historical value, the mobile `@media` swap, plus the additive fluid/semantic/component tiers. Wrapped in `@layer tokens`. |
| `docs/tokens.md` | **Generated.** Full human-readable token reference. |
| `docs/tokens.json` | **Generated.** Flat `{"--css-var": {value, type, tier, api, path, …}}` map — read this instead of parsing CSS. |
| `build.js`, `build/` | Style Dictionary v5 build + docs generator + verification gate. Node ≥ 22, ESM. |
| `fonts.css` | Self-hosted DM Sans (`@font-face`, woff2 in `assets/fonts/`). **Required** — see *Fonts*. |

**Stylesheets**

| File | Role |
|---|---|
| `sgt-ds-core.css` | DOM-agnostic components: typography, buttons, cards, tags/pills, tables, dividers, pull quote, forms, layout helpers, department blocks. Works in any HTML context. |
| `sgt-ds-slides.css` | Slide-only components (glass, bento, phase, step, arch-box, donut, bars, verdict, callout, gradients). Load after core. |
| `sgt-presentation-v4.css` | Reveal.js v4 stylesheet — **preferred** for new decks. |
| `sgt-presentation.css` | Reveal.js v3 — **frozen legacy**, existing decks only. |
| `sgt-marp.css` | Marp CLI theme. Standalone by design (Marp cannot `@import` shared CSS at render time). |
| `index.html` | Living documentation page. `ds.html` redirects here. |
| `template-v4.html`, `template.html`, `showcase-v3.html` | Deck starters and the v3 component showcase. |

---

## Consuming the design system

### Web page, email, PDF template

```html
<link rel="stylesheet" href="…/SGT-Branding/fonts.css">
<link rel="stylesheet" href="…/SGT-Branding/tokens.css">
<link rel="stylesheet" href="…/SGT-Branding/sgt-ds-core.css">

<body class="sgt-body">
  <h1 class="sgt-h1">Headline.</h1>
  <p  class="sgt-copy">Body text.</p>
  <a  class="sgt-btn purple" href="#">CTA</a>
</body>
```

From another origin, the same three files work over HTTPS:

```css
@import url("https://brand.sergeant.agency/fonts.css");
@import url("https://brand.sergeant.agency/tokens.css");
@import url("https://brand.sergeant.agency/sgt-ds-core.css");
```

### New Reveal.js deck (v4)

```html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/reveal.css">
<link rel="stylesheet" href="../../SGT-Branding/fonts.css">
<link rel="stylesheet" href="../../SGT-Branding/sgt-ds-core.css">
<link rel="stylesheet" href="../../SGT-Branding/sgt-presentation-v4.css">
<link rel="stylesheet" href="../../SGT-Branding/sgt-ds-slides.css">
<!-- No reveal.js theme. No sgt-presentation.css. -->
```

Start from `template-v4.html`. Reveal config: `center: false, controls: false,
progress: false, margin: 0.02`.

### Fonts

`sgt-ds-core.css` does **not** load DM Sans from the Google Fonts CDN — link
`fonts.css` first. It self-hosts weights 300 / 400 / 400 italic / 500 and
resolves its woff2 paths relative to itself, so it also works cross-origin.
Without it, components fall back to the system sans stack in `--sgt-font`.

Decks (`sgt-presentation*.css`, `sgt-marp.css`) still load DM Sans their own way.

---

## Naming conventions

Go-forward style is **a base class plus bare modifier words**, used by both
`sgt-ds-core.css` and `sgt-ds-slides.css`:

| Kind | Go-forward | Old v3 (frozen, maintenance only) |
|---|---|---|
| Button | `.sgt-btn.purple` | `.sgt-btn.btn-purple` |
| Tag / Pill | `.sgt-tag.strategy` | `.sgt-tag-strategy` |
| Verdict dot | `.sgt-dot.green` | `.sgt-dot-green` |
| Bar fill | `.sgt-bar-fill.strategy` | `.sgt-bar-fill.bar-strategy` |
| Callout box | `.sgt-callout` | `.sgt-highlight` |

All classes are prefixed `sgt-` and opt-in. **No element selector is styled
globally** — keep it that way, the DS has to survive being dropped into Drupal,
Shopify and email clients.

Token naming in `tokens/` is `category.property.variant.state`, which becomes
`--sgt-category-property-variant-state`. Legacy names keep their historical
un-prefixed form via `$extensions["agency.legacy"]`.

---

## Cascade layers

`tokens.css` declares the order once:

```css
@layer reset, tokens, base, components, utilities;
```

`sgt-ds-core.css` places its rules in `base` (`.sgt-body`, typography),
`components` (buttons, cards, tags, tables, dividers, pull quote, forms) and
`utilities` (layout helpers, department blocks) — matching its own source order,
so nothing changes inside the DS.

Consequence for consumers: **unlayered CSS in your page beats the design system
without `!important`.** To override from inside a layer, declare your own layer
after `utilities`.

The `prefers-reduced-motion` block at the end of `sgt-ds-core.css` is
deliberately **unlayered**, because `!important` inverts layer priority and an
accessibility opt-out has to win.

`sgt-presentation*.css` and `sgt-ds-slides.css` are intentionally unlayered.

---

## Dark mode

Dark mode is **opt-in** and only affects the semantic `--sgt-color-*` tokens,
which are emitted as `light-dark(light, dark)`:

```html
<html class="sgt-theme">            <!-- follow the OS -->
<html data-sgt-theme="dark">        <!-- force dark -->
<html data-sgt-theme="light">       <!-- force light -->
```

Without the opt-in, `color-scheme` is never set and every page renders light —
exactly as before. Legacy tokens have no dark variant by design.

---

## Build & verify

```bash
npm install     # once, Node >= 22
npm run build   # tokens/ -> tokens.css + docs/tokens.md + docs/tokens.json
npm run docs    # docs only
npm run verify  # gate: legacy vars byte-identical + docs in sync with tokens.css
```

`npm run verify` compares the freshly generated `tokens.css` against
`git show HEAD:tokens.css`, so **commit token changes and their generated output
together**, and run `verify` before you commit, not after.

Output is deterministic — no timestamps. A rebuild without token changes must
produce an empty `git diff`. If it does not, something is wrong.

## Deploying

Since Phase B this repo has a `node_modules/` on disk (~57 MB, git-ignored, not
tracked). Never point `wrangler pages deploy` at the working directory — it would
upload all of it. The working procedure is `git archive HEAD` into a scratch
folder, then deploy **that folder** to the `sgt-design-system` Cloudflare Pages
project. As a side effect the deploy always matches committed state, so commit
first.

**Agents must not deploy.** Prepare the change, report it, and let a human
trigger the deploy.
