Slide deck
Single-file HTML decks on a fixed 1600×900 canvas: identical on a 4K monitor
and a hotel projector, zero dependencies, works over file://. Each slide
carries its presenter script inside itself; the same file serves the room, the
presenter popup, the printed handout, and the readable presenter edition.
Quick start
- Copy
assets/deck-template.html to the output path. It is a working deck —
open it in a browser to see the machinery.
- Replace the
<title>, topbar text, and the four example <section class="slide"> blocks with real slides.
- For the presenter edition, copy the file again and change
<body> to
<body class="presenter-file">. Never fork slide content between editions.
- Verify (both scripts need chromium on PATH):
python3 scripts/check-slides.py path/to/deck.html # fit audit — gates the build
python3 scripts/shoot-slides.py path/to/deck.html # PNG per slide
- Open the PNGs and look at them. The audit checks fit; only an eye catches
colliding labels, dead space, or bottom-heavy slides.
Rules that are load-bearing
- Slides are 1600×900 px. No
vw/vh/% for slide content — px only. The
stage transform does all scaling.
overflow:hidden on .slide is intentional. Content that doesn't fit is a
build bug: cut or split the slide, never enable scroll.
- Keep
justify-content:safe center on .sbody — plain center spills
overflow over the title where the audit can't see it.
- Every slide ends with
p.cite pinned at the bottom, even "Sources: internal
discussion". A bare bottom edge reads unfinished.
- No external resources — no CDN, web fonts, or remote images. Embed Chart.js
inline if charts are needed.
- Slide
<section>s are direct children of #deck only.
- Keep the literal
</body> out of new JS strings — the screenshot harness
patches before the last one.
Slide anatomy
<section class="slide" data-hour="Block 00:00-01:00" data-t="0:05" data-dur="8">
<p class="kicker">Eyebrow</p>
<h1>Title</h1>
<div class="sbody">…</div>
<p class="cite">Sources: …</p>
<div class="script">…presenter notes…</div>
</section>
data-hour labels the block (set on a block's first slide), data-t is the
planned clock position, data-dur the planned minutes — the presenter timer
flags overruns against them.
Authoring
Component catalogue, slide types, the svg.dia diagram system, retheming via
:root tokens, and the build-script pattern for 15+ slide decks:
read references/authoring.md.
Talk-track schema (.script blocks: say-exactly, do-now, ask,
expected, if-quiet, bridge, timing, …), presenter view, and the
presenter-file edition: read references/presenter-notes.md.
Editing the runtime or stylesheet, writing a deck generator, charts, print,
or the audit/screenshot internals: read references/runtime-contract.md.
Interaction patterns worth reaching for
.veil blurs a value until V is pressed — the room commits a guess before
seeing the answer. Pair with a .predict box.
.divider slides reset attention between blocks; .hourbar pips show
progress through the day.
.flow, .ladder, .orbit, .days, .roles, .check cover most
structural needs before any new CSS.
Common mistakes
| Symptom |
Cause |
Fix |
| Slide content clipped live |
skipped the fit audit |
run check-slides.py, cut content |
| Overflow paints over the title |
justify-content:center |
restore safe center |
| Slide off-centre / unscaled |
stage transform via CSS var() |
keep the JS inline-style transform |
| Chart renders blank |
responsive sizing in a display:none slide |
set data-w/data-h, bootstrap handles the rest |
| Presenter popup unstyled |
deck <style> isn't first in <head> |
reorder — the popup injects stylesheet #1 |
| Screenshot shows only slide 1 |
patch injected before first </body> |
shoot-slides.py already patches the last |
| "57 slides, all fit" but unreadable |
fit ≠ readable |
shoot-slides.py and view the PNGs |
1---2name: slide-deck3description: Use when creating or editing HTML presentation decks, slide decks, training or workshop slides, presenter decks, or speaker-notes documents — especially self-contained single-file decks that must run offline on unknown projectors, carry per-slide talk tracks, support a presenter-view window, or need rendered-image and overflow verification before delivery.4---56# Slide deck78Single-file HTML decks on a fixed 1600×900 canvas: identical on a 4K monitor9and a hotel projector, zero dependencies, works over `file://`. Each slide10carries its presenter script inside itself; the same file serves the room, the11presenter popup, the printed handout, and the readable presenter edition.1213## Quick start14151. Copy `assets/deck-template.html` to the output path. It is a working deck —16 open it in a browser to see the machinery.172. Replace the `<title>`, topbar text, and the four example `<section18 class="slide">` blocks with real slides.193. For the presenter edition, copy the file again and change `<body>` to20 `<body class="presenter-file">`. Never fork slide content between editions.214. Verify (both scripts need chromium on PATH):22 ```bash23 python3 scripts/check-slides.py path/to/deck.html # fit audit — gates the build24 python3 scripts/shoot-slides.py path/to/deck.html # PNG per slide25 ```265. Open the PNGs and look at them. The audit checks fit; only an eye catches27 colliding labels, dead space, or bottom-heavy slides.2829## Rules that are load-bearing3031- Slides are 1600×900 px. No `vw`/`vh`/`%` for slide content — px only. The32 stage transform does all scaling.33- `overflow:hidden` on `.slide` is intentional. Content that doesn't fit is a34 build bug: cut or split the slide, never enable scroll.35- Keep `justify-content:safe center` on `.sbody` — plain `center` spills36 overflow over the title where the audit can't see it.37- Every slide ends with `p.cite` pinned at the bottom, even "Sources: internal38 discussion". A bare bottom edge reads unfinished.39- No external resources — no CDN, web fonts, or remote images. Embed Chart.js40 inline if charts are needed.41- Slide `<section>`s are direct children of `#deck` only.42- Keep the literal `</body>` out of new JS strings — the screenshot harness43 patches before the last one.4445## Slide anatomy4647```html48<section class="slide" data-hour="Block 00:00-01:00" data-t="0:05" data-dur="8">49 <p class="kicker">Eyebrow</p>50 <h1>Title</h1>51 <div class="sbody">…</div>52 <p class="cite">Sources: …</p>53 <div class="script">…presenter notes…</div>54</section>55```5657`data-hour` labels the block (set on a block's first slide), `data-t` is the58planned clock position, `data-dur` the planned minutes — the presenter timer59flags overruns against them.6061## Authoring6263Component catalogue, slide types, the `svg.dia` diagram system, retheming via64`:root` tokens, and the build-script pattern for 15+ slide decks:65**read `references/authoring.md`**.6667Talk-track schema (`.script` blocks: `say-exactly`, `do-now`, `ask`,68`expected`, `if-quiet`, `bridge`, `timing`, …), presenter view, and the69presenter-file edition: **read `references/presenter-notes.md`**.7071Editing the runtime or stylesheet, writing a deck generator, charts, print,72or the audit/screenshot internals: **read `references/runtime-contract.md`**.7374## Interaction patterns worth reaching for7576- `.veil` blurs a value until V is pressed — the room commits a guess before77 seeing the answer. Pair with a `.predict` box.78- `.divider` slides reset attention between blocks; `.hourbar` pips show79 progress through the day.80- `.flow`, `.ladder`, `.orbit`, `.days`, `.roles`, `.check` cover most81 structural needs before any new CSS.8283## Common mistakes8485| Symptom | Cause | Fix |86|---|---|---|87| Slide content clipped live | skipped the fit audit | run check-slides.py, cut content |88| Overflow paints over the title | `justify-content:center` | restore `safe center` |89| Slide off-centre / unscaled | stage transform via CSS var() | keep the JS inline-style transform |90| Chart renders blank | responsive sizing in a `display:none` slide | set `data-w`/`data-h`, bootstrap handles the rest |91| Presenter popup unstyled | deck `<style>` isn't first in `<head>` | reorder — the popup injects stylesheet #1 |92| Screenshot shows only slide 1 | patch injected before first `</body>` | shoot-slides.py already patches the last |93| "57 slides, all fit" but unreadable | fit ≠ readable | shoot-slides.py and view the PNGs |