# zerp consumer instructions for LLMs
`@emirotin/zerp` is a zero-config presentation framework. Slides are authored in HTML, Markdown, or a mix. The deck source of truth is `slides/`.
## Scope for agents
This is the complete authoring guide for a downstream presentation project using the public `@emirotin/zerp` interface.
- The deck owns its package manifest, authored files under `slides/`, and deck-local assets. Use the documented deck contract, classes, tokens, and CLI; do not depend on implementation details.
- Add `@emirotin/zerp` as a development dependency with the project's package manager: `pnpm add -D @emirotin/zerp`, `npm install -D @emirotin/zerp`, `yarn add -D @emirotin/zerp`, or `bun add -d @emirotin/zerp`.
- Run the installed `zerp` binary through the project's package manager, rather than a global or one-off download. This guide writes that runner as `zerp`; use `pnpm exec zerp`, `npm exec -- zerp`, `yarn exec zerp`, or `bun run zerp`.
- Author slide sources and assets, never the generated `index.html`. Do not recreate navigation or default presentation behavior in the deck.
- Inspect the worktree before editing, preserve unrelated changes, and validate the resulting deck before handoff.
## Agent authoring loop
1. Discover the deck before referring to a slide by number: run `zerp slides . --json` and confirm the source file, in-file ordinal, and heading.
2. Edit only the relevant `slides/**/*.html`, `slides/**/*.md`, or assets under `slides/`. Prefer built-in classes, Markdown for text-first slides, and HTML for layout-heavy or interactive slides.
3. Run `zerp check .` after every authoring change and fix every error and warning. Run `zerp check . --theme both` after layout or interaction changes — it checks at the deck's declared design size (or 1920×1080 if it declares none), and the same command also gates frame/layout geometry, so there is nothing further to run. Pass `--size WxH` only to check at a size other than the deck's own.
4. Use `zerp serve .` for live inspection; use the consuming project's browser or screenshot tooling, when available, for close visual inspection. Check both dark and light themes.
5. Run `zerp build . --theme system` for the handoff artifact. Never hand-edit the generated `index.html`.
## Consuming the framework
The public deck contract requires a `slides/` directory. A project that manages its local `zerp` dependency typically looks like:
```text
my-deck/
package.json
slides/
00-title.md
10-content.html
images/
cover.jpg
```
The main commands from the deck directory are:
```bash
zerp slides . --json
zerp check .
zerp check . --theme both
zerp serve .
zerp build . --theme system
zerp print . -o deck.pdf
```
## Deck contract
- Put slides in `slides/**/*.html` and/or `slides/**/*.md`; files are ordered lexicographically (`00-`, `10-`, `20-` prefixes).
- Assets live under `slides/` too; use relative paths (`./images/foo.jpg`) — they are rewritten at build time.
- Each `.html` file contains one or more `
` blocks. Each `.md` file is auto-wrapped; separate multiple slides with `---` on its own line.
- The page `` is derived from the first slide's highest-level heading (h1 before h2, regardless of order); style/script-only files contribute no slides and are skipped. Override with the `title` build option; falls back to the deck folder name.
- A deck renders at a design size — 1920×1080 by default, or whatever it declares (see "Deck configuration" below) — and scales to fit the real window at runtime. Author against that fixed canvas; never against the actual viewport.
- Never hand-edit the generated `index.html`.
## Slide numbering
**"Slide N" always means the 1-based deck position** — the number the runtime counter shows to presenter and audience. File name prefixes only order files; a file may contain several slides, so a prefix is NOT a slide number. The runtime counter, the URL hash (`#N`), `zerp check` findings, and `zerp slides` all use deck positions.
- Before editing "slide N", run `zerp slides .` (add `--json` for tooling): it prints each slide's position, source file, in-file ordinal, and title. Confirm the target by its heading.
- Every composed inner `.slide` carries source-tracing attributes: `data-zerp-src` (source file), `data-zerp-src-slide` (`i/n` within that file), `data-zerp-index` (deck position). It is wrapped in a framework-owned `
` frame; the frame owns visibility and the inner `.slide` owns layout.
- Treat `data-zerp-slide`, `data-zerp-slide-active`, and their display rules as reserved framework state. Put custom layout rules on `.slide` or a class on the inner root, not on the frame.
- In a running deck, press `s` to toggle a source badge showing the active slide's position and source file.
## Design system — colors
Colors come from CSS custom properties (design tokens). The framework ships a dark and a light theme; tokens flip automatically. **Never hardcode hex/rgb colors. Never invent new colors. Pick tokens by meaning.**
Neutrals:
- `--zerp-bg` page background · `--zerp-surface` cards/panels · `--zerp-border` borders
- `--zerp-text` body text · `--zerp-muted` secondary text — use at body size (≥1.25em/20px), or at smaller sizes only with weight 700
- `--zerp-faint` decorative glyphs ONLY (arrows, dividers) — **never use for text**
Accent hues (each in 4 roles): `blue green orange purple red amber teal`
- `--zerp-` — colored text on the page/surface (use at body size or larger)
- `--zerp--solid` + `--zerp-on-solid` — filled backgrounds (badges, fills) and text on them
- `--zerp--tint` + `--zerp--on-tint` — subtle washes (highlight cards/rows) and text on them
Semantic aliases: `--zerp-accent` (blue), `--zerp-ok` (green), `--zerp-warn` (amber), `--zerp-danger` (red). Prefer semantic names when the color carries meaning; hue names for categorical coding.
## Typography rules
- Body text is 1.25em (~20px). **Never set text below 1em; `.sm` (0.9×) is the only sanctioned shrink.** Prefer trimming content over shrinking text.
- `.sm` is for inline spans within body text; on a bare paragraph it lands below the 16px floor and `zerp check` will flag it.
- Do not combine `.sm` with `.muted` — small muted text fails contrast.
- For a detail line under a heading or bold statement use `.sub` (1em, bold, muted — safe by construction). This is the idiom for the second line of a `.key-thought` and for card sub-text.
- Colored text classes are bold by design (emphasis). Do not fight the weight.
- Decorative glyphs (arrows, ▲/▼ markers) may be small: mark them `aria-hidden="true"` — screen readers and `zerp check` skip them. Keep the real information in text.
- Captions: use `.caption` or `` — already sized and colored safely.
- Direct slide children space themselves automatically (14px rhythm). Do not add margins for ordinary spacing; use an inline margin only to deliberately tighten or widen one specific gap. For a width-capped centered block use `.narrow` (or `margin-inline: auto`; a full `margin: 0 auto` kills the automatic rhythm).
- Utility classes reliably override component defaults (component "soft defaults" are declared with `:where()`): `h3.accent` recolors a heading, `.value.red` recolors a stat, `p.xl` enlarges a paragraph, `.year.xl` enlarges a timeline year. Say what you mean with classes instead of inline styles.
## Built-in classes
Emphasis: `.accent .ok .warn .danger .blue .green .orange .purple .red .amber .teal` (bold colored text) · `.muted` · `.mono` · `.lg .xl .sm .sub` · `.raised` (elevation shadow for images/cards)
Layout: `.cols-2 .cols-3 .cols-4` (equal grids) · `.row` (centered flex row) · `.stack` (column) · `.spread` (space-between) · `.center` (center text — safe on headings, paragraphs, and containers) · `.narrow` (width-capped centered block) · `.grow` · `.slide.top` (top-align a busy slide)
Components:
- `.card` — surface box; add `.tint-` for a colored wash, `.edge-` (or `.edge-ok/.edge-warn/.edge-danger/.edge-accent`) to recolor the border by meaning — works on `.key-thought`, `.timeline .item`, and `.steps` cells too
- `.stat` (`.value` + `.label` children) inside `.stat-row` — big numbers; color a value with a hue class (`.value.red`); for stat×operator compositions (`a × b = c`) use `.row` instead so operators center vertically
- `.concept` (`.icon` + `.label` children) — definition-slide header: big emoji icon over a shouty accent label; follow with a plain or `.muted` tagline paragraph
- `.slide.title` — title-slide recipe: `h1` + `h3` (auto-accent) + `.meta` block for presenter/date
- `.compare` — two children side by side with a "vs" divider (`data-vs="→"` to customize)
- `.flow` — process row, arrows auto-inserted between children (works for text spans and for `.card` children)
- `.steps` — auto-numbered card grid (children: plain divs with `h3` + `p`)
- `.timeline` with `.item` (`.year`, `.label`) — milestones
- `.key-thought` — boxed takeaway (one per slide max)
- `.pill` — inline badge; combine with `.ok/.warn/.danger/.accent` or `.tint-`
- `.interactive-badge` — marks slides that react to ↓/↑ · `.step-hint` — the "↓ do X" affordance line for stepped slides
- `.block-label` — small top-left section label
- `.img-row` — row of images; wrap each img in a `figure` + `figcaption` for captioned entries · `figure` + `figcaption` for single captioned images · `figure.corner` — pin a small reference figure (poster, book cover) to the slide's top-right, out of the content flow
- `table` is styled by default (add `.mono` for numeric tables, `.compact` for dense centered data grids — combine as `class="mono compact"`; `.tint-green`/`.tint-red` on `
` to highlight rows); side-by-side tables go in a `.row`, which lays them out by its gap
- `blockquote`, `ol` (numbered takeaways), `ul` (arrow bullets), `code`, `pre`, `kbd` are styled by default
## Slide recipes
Title: `# Big title` + `### subtitle` (Markdown). Section divider: `.block-label` + `h1` + `h3`. Bullets: `##` + `-` list. Two-column: `
` with text and image/card. Big numbers: `.stat-row` of `.stat`s. Comparison: `.compare` of two `.card`s. Process: `.flow` of spans. Steps: `.steps` of divs. History: `.timeline`. Quote: Markdown `>`. Takeaways: Markdown ordered list. Data: Markdown table. Closing: `# Thanks` + contact in `.muted`.
Prefer Markdown for text-first slides; HTML for layout-heavy or interactive slides.
## Theming
- Decks are theme-neutral: tokens adapt to dark/light automatically. The presenter picks the default via `zerp build --theme dark|light|system` (default: `system`); viewers override with the sun/moon toggle or the `t` key. The first press pins the opposite scheme (persisted in localStorage); the next press returns to the deck default and clears the pin.
- Because both themes ship, anything you author must work in both — that is why hardcoded colors are forbidden.
## Printing and PDF export
```bash
zerp print . -o deck.pdf
```
`zerp print` renders the deck directly to a PDF at its design size — no browser print dialog, no manual page-size setup.
- Printing emits **one page per slide, in deck order**. Presentation chrome (nav, counter, progress bar, theme toggle, source badge) is never printed.
- Steps print in their **final state**: `data-step` content is present, `data-until-step` content is absent. A printed deck shows every reveal.
- Print with a **page size equal to the deck's design size** (its `zerp.size`, or 1920×1080 if it declares none) and backgrounds enabled. Pagination is size- and theme-agnostic: one slide fills exactly one page at any page size, in dark or light.
- **Page capacity is physical**: content that overflows a slide is clipped at the bottom of the page, not reflowed onto a second page. Fix overflow the same way as on screen (trim content, or top-align with `.slide.top`); `zerp check` still gates it.
- If a deck will additionally be exported to PPTX by a downstream pipeline, `llms-pptx.txt` (shipped alongside this file) has the export-specific authoring restrictions; the caller includes it alongside this guide when that target applies.
## Validation loop (required)
After authoring or editing slides, ALWAYS run:
```bash
zerp check .
zerp check . --theme both # checks at the deck's design size by default
# add --json for a machine-readable report (per-slide entries include the
# source file); --theme dark|light|both and --only category,... narrow it;
# pass --size WxH only to check at a size other than the deck's own
```
`zerp check` opens each theme in a real headless browser and needs a Chromium-class one: run `zerp install-browser` once to download a managed Chromium, or set `CHROME_BIN` to an existing Chrome/Chromium. Without one, `zerp check` errors and names the remedy — `zerp install-browser`, or the `CHROME_BIN` path if that is the problem. The whole session (launch, navigation, font activation, probe) is bounded by `--timeout ms` / `ZERP_VERIFY_TIMEOUT_MS`, 20000ms by default — raise it on a slow or loaded host. A timed-out session yields no report, which is a check that did not run, not a check that passed. To skip the per-run browser launch entirely, point `--browser-endpoint url` / `ZERP_BROWSER_ENDPOINT` at an already-running browser (`http(s)://` CDP, `ws(s)://` playwright); check borrows it and never closes it.
Fix every `✗` error and `⚠` warning (font too small, text contrast too low, a surface whose background blends into what is behind it, a label sealed inside an `
```
A script that builds a diagram at runtime follows the same rule: emit the shapes into the SVG and the labels into sibling HTML, so the words stay checkable static markup.
## Characters and glyph coverage
A built deck carries its fonts inside itself, as inlined subsets of Montserrat and Roboto Mono plus a one-glyph face for `→`. That is what makes it a single offline file, and it is also a hard edge: **a character no bundled subset covers is drawn by whatever font the viewing machine falls back to** — a different shape, weight and width on every OS, and re-resolved again by the reader's machine once the deck is exported to a format whose text runs carry one font face. Latin, Latin-Extended, Cyrillic and Vietnamese are available to both the body and display families by default; Greek is available to Roboto Mono only.
`zerp check` finds fallback text by asking the browser itself what font painted each glyph (Chrome DevTools Protocol `CSS.getPlatformFontsForNode`), not by consulting a static coverage table. Since zerp inlines every font it ships as an `@font-face`, any font the browser reports as not custom (`isCustomFont: false`) means it fell back — that is a direct answer, not an inference from a `unicode-range`. The finding is one `[glyph]` `⚠` per element, naming the slide, the element and the fallback family it fell back to.
This is per element, not per deck or per font role: Greek is available to Roboto Mono and not to Montserrat, so `Δ` in a `` block is fine while the same character in an `
` is not. Bundling a subset for one role does not cover another role.
- Latin, Latin-Extended and Cyrillic text is covered. Common typography is covered: `— – … ‘ ’ “ ” · × − ↑ ↓ € ™`.
- Mathematical and geometric symbols mostly are not: `≈ ≠ ≤ ≥ ▲ ▼ ◆ ★ ✓ ✗`. Say `~`, `<=`, `+`/`−`, or use words.
- `→` is covered everywhere, because zerp bundles a face for it and its own `ul` markers use it.
- Generated content (`::before`/`::after`) is judged for glyph coverage too — a deck that overrides a framework marker's glyph can get a finding for text the browser never actually draws — but not for contrast or type-size: the probe records no pseudo-element color or size, and judging generated content against the originating element's would describe a color/size pair the browser never painted together.
- `` inside inline SVG is excluded from glyph judging along with contrast, type-size and surface (see "Diagrams and inline SVG" for why); the separate `[svg-text]` warning still fires for any non-`aria-hidden` `