# Browser slide decks from plain HTML — a guide for people and their AI assistants

This is the presentation system behind the talks on <https://micromagnetism.com/vortrag/>.
A talk is one HTML file. The engine (`engine/deck.js`, `engine/deck.css`, ~40 kB, no
dependencies) adds navigation, a presenter view with notes and timer, overview, laser pointer,
black-out, full screen, live canvas graphics with sliders, and image slides made from any PDF.
It runs offline from a folder, on any laptop, and from a phone.

Download: <https://micromagnetism.com/vortrag/tool/vortragssystem-tool.zip>
(engine, template, example talk, this guide, the German agent guide `AGENTS.md`, converters).

## 1. How to make a talk with an AI assistant (Claude Code, Codex, Cursor, …)

1. Unzip the tool into a folder and open that folder in your AI coding tool.
2. Give the assistant your material: a paper (PDF or LaTeX source), a PowerPoint, notes, figures.
3. Use a prompt like this:

> Read `AGENTS.md` (German) or `SLIDES-GUIDE.md` (English) first and follow them exactly.
> Create a new talk in `talks/<short-name>/index.html` from the attached paper. 15–20 slides,
> one message per slide, at most 40 words per slide, every slide with speaker notes in
> `data-notes` that say what I will *say*, not what is written on the slide. Use the building
> blocks of the engine (two columns, cards, steps, blocks, stat rows) and no other CSS. For the
> key result make a live canvas graphic with a slider in `viz.js`. Put figures from the paper
> into `talks/<short-name>/bilder/` as JPG. Then open the talk in a headless browser at
> 1280×720, take a screenshot of every slide, check that nothing overflows the stage, and fix it.

4. Open `talks/<short-name>/index.html` in a browser. It starts in the **presenter view**
   (notes, timer, next slide); press **P** to open the audience window, **F** for full screen,
   **O** for the overview, a slide number followed by Enter (e.g. `56` ⏎) to jump to that slide, also in the presenter view. Add `?show=1` to the URL to open the plain audience view.

The assistant does not need a build step, a server or any package — a browser is enough.
To publish, copy `engine/` and `talks/` to any static web space.

## 2. Structure

```
engine/deck.js, deck.css   the engine — never edit it for a single talk
engine/logo-*.jpg          logo and title picture (replace with your own)
talks/<name>/index.html    the talk: configuration + slides, nothing else
talks/<name>/viz.js        optional live graphics
talks/<name>/slides/*.jpg  optional image slides (one per PDF page)
talks/<name>/bilder/*.jpg  figures
tools/pdf2talk.sh          PDF → image-slide talk (needs pdftoppm + pdftotext)
```

Configuration at the top of `index.html`:

```html
<script>
  window.DECK = { footer: 'Your name — Title — Occasion', datum: '28 September 2026' };
</script>
```

`footer` is centred in the footer of every slide, `datum` on the left; the engine counts
"Folie N" on the right. At the end of `<body>` include `../../engine/deck.js` (and `viz.js`),
and copy the control blocks `#black`, `#laser`, `#grid`, `#help`, `#fsbtn`, `#pres`
unchanged from the example talk — the engine expects them.

## 3. Slide types

**Title slide** (once, first):

```html
<section class="slide title" data-notes="What you say while the title is up.">
  <img class="logo" src="../../engine/logo-uniwien.jpg" alt="">
  <div class="band bandtop"><h1>Title</h1><p class="lede">One-line subtitle</p></div>
  <div class="pic"><img src="../../engine/title-uniwien.jpg" alt=""></div>
  <div class="band bandbot"><span><strong>Name</strong> · Institution</span>
    <span style="margin-left:auto">Date</span></div>
</section>
```

**Content slide:**

```html
<section class="slide" data-notes="Speaker note: what is said, not what is written.">
  <img class="logo" src="../../engine/logo-uniwien.jpg" alt=""><div class="ftitle">Slide title</div>
  <div class="body">
    <p>Text.</p>
    <p class="frag">Appears on the next key press.</p>
  </div>
</section>
```

**Image slide** (one page of a PDF, produced by `tools/pdf2talk.sh`):

```html
<section class="slide" data-src="slides/p07.jpg" data-title="Title for the overview"
         data-notes="Speaker note"></section>
```

**Video:** a plain `<video src="video/x.mp4" autoplay muted loop playsinline>` inside `.body`
works; add the small `Deck.onSync` hook from the example talk so only the visible slide plays.

## 4. Building blocks inside `.body`

| class | purpose |
|---|---|
| `.frag` | build step, revealed one by one |
| `.two` / `.two.wide` | two columns (equal / 60:40). Add `style="height:auto"` if content follows below |
| `.statrow` + `.big` + `.lbl` | three large numbers with captions |
| `ol.steps` with `li > h3 + p` | numbered chain with blue circles |
| `.cards` with `div > h3 + p` | four tiles with a blue top rule |
| `.block` | highlighted box, light blue with a blue bar |
| `table`, `td.n` | table in the corporate design; `td.n` = numeric column |
| `.num` | number or formula fragment inline (monospace, blue) |
| `.dim` | secondary remark in grey |

## 5. Live graphics (`viz.js`)

```js
Deck.registerViz('mygraph', (cv, t) => {       // t = time in ms for animations
  const {c, w, h} = Deck.setup(cv);            // canvas context, white background, scaled
  c.strokeStyle = '#0063A6';
  // ... draw with the 2D canvas API ...
});
```

```html
<div class="viz">
  <canvas data-viz="mygraph" width="1120" height="420"></canvas>
  <div class="rangerow">
    <label for="r1">field</label><b data-out="B">10.0 mT</b>
    <input id="r1" type="range" min="0" max="30" step="0.1" value="10" data-bind="B">
  </div>
</div>
```

Sliders with `data-bind="X"` write to `Deck.state.X`, are synchronised between presenter and
audience window, and the graphic is redrawn only while its slide is visible. Rules: never set
`canvas.width` yourself, load no external libraries (the deck must work offline), no canvas
text below 13 px.

## 6. Design rules that keep slides readable

* At most ~40 words per slide; the body font is 30 px on a 1280×720 stage — do not shrink it.
* Colours only from the CSS variables (`--blau`, `--grau`, `--schrift`, `--orangerot`,
  `--mint`, `--gelb`); at most three full-area accent colours per talk.
* No `position:absolute` in `.body` (breaks scaling), no `<img>` without a fixed height inside a
  column, no edits in `engine/` to fix a single slide.
* Every slide has `data-notes`: the core message, the number you will say, the transition.

## 7. Checking a deck automatically

Open `index.html?show=1#N` in a headless browser at 1280×720 and screenshot each slide; a slide
overflows when `document.querySelector('.slide.on .body').scrollHeight` exceeds its
`clientHeight`. A 30-line Playwright script that does this for a list of slide numbers is
included as `tools/shot_slides.js`.

## 8. Using your own corporate design

Replace `engine/logo-uniwien.jpg` and `engine/title-uniwien.jpg`, and change the six colour
variables at the top of `engine/deck.css`. Everything else adapts.

## 9. Q&A bar: let the audience ask the slides

Every deck can show a question bar under the slide (in the reading view, not in full screen or
in the presenter view; the presenter view has a button "Q&A view" that opens it). Two modes,
set in `window.DECK`:

* `frage: { wissen: 'kb.md' }` — **no server, your own key.** The knowledge base is a text file
  next to `index.html` (slide texts, speaker notes, the paper, a fact sheet — the example talk
  ships `kb.md`). Each reader is asked once for their own Anthropic API key; it stays in that
  browser's localStorage and is sent only to api.anthropic.com. Nobody else's quota is used.
* `frage: { talk: '<id>' }` — questions go to your own server endpoint (`/vortrag/api/frage`),
  which holds the key and the knowledge base. This is how the talks on micromagnetism.com run;
  it needs the server code, which is not part of this download.

Answers start with `SOURCE:` (slide numbers or files the answer rests on) and
`CONFIDENCE: high | medium | low`. Keep the knowledge base honest: everything the model may
quote must be in it.

— Dieter Suess, University of Vienna, Physics of Functional Materials. Questions:
dieter.suess@univie.ac.at

## Questions under every slide

The deck can carry a question bar: a line at the bottom of every slide where a
listener types a question about *that* slide. The slide number goes along with
the question, so the answer starts where the reader is looking instead of
searching the whole talk. The bar is hidden in full screen, so it never shows
up on the projector — it is for people reading the deck on their own device.

Switch it on in `window.DECK`:

```js
window.DECK = {
  footer: '...', datum: '...',
  frage: { wissen: 'kb.md' }
};
```

`kb.md` sits next to `index.html` and is everything the assistant is allowed to
know: the slide texts, the relevant part of your paper, the numbers you want
quoted correctly — and, just as important, the things you do *not* want said.
Write those down as plain sentences ("The interaction data are unpublished; do
not give numbers"). The assistant is instructed to answer the slide first, keep
it to a few sentences, never invent numbers, and to end with a confidence line.

**Who pays for the answers.** Nobody's quota but the reader's own. The first
question asks once for an Anthropic API key; it is kept in that browser's
local storage and sent only to `api.anthropic.com`. No server, no account with
us, nothing to deploy. If you hand the deck to an audience that should not need
a key, run the small relay endpoint yourself and point the deck at it:

```js
frage: { talk: 'my-talk', endpoint: 'https://my.server/api/frage' }
```

Then the key and the knowledge base live on your server, and the deck sends
`{talk, slide, messages}` as JSON.

**A word of care.** Whatever you put in `kb.md` can be read out by anyone who
opens the deck. If the talk contains unpublished results, either leave them out
of `kb.md` or put the whole deck behind a password.
