@dustfeather/deckrun 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,1073 @@
1
+ # deckrun
2
+
3
+ Write slides in Markdown or bring a self-contained HTML document, run a local server, and present either in the browser.
4
+
5
+ - **Two formats** - Markdown decks with slide-by-slide presentation, or self-contained HTML documents with continuous scrolling
6
+ - **Live editor** - edit alongside a live preview, with autosave and a library of all your decks and docs
7
+ - **14 themes** - unique palettes, typography, animated backdrops, and customizable heading and body fonts
8
+ - **Templates and motion** - four composition templates and five transitions, switchable without touching the Markdown
9
+ - **Technical content** - KaTeX equations and Mermaid diagrams in preview, presentation, HTML, and PDF
10
+ - **Incremental reveals** - step through bullets, prose, equations, code, or diagrams without duplicating slides
11
+ - **Deck linting** - catch empty slides, broken fences/math, dense content, image issues, and invalid reveal markers locally or in CI
12
+ - **Presenter tools** - laser pointer, drawing pen, blank canvas, blackout mode, and `?` for shortcuts
13
+ - **Highlights and comments** - marker-pen text in the preview or on the projector and hang a note off it, kept in the browser session and gone when the tab closes
14
+ - **Notes while presenting** - present from the editor and its preview and notes panel follow the deck tab, so the editor is your notes screen
15
+ - **Export** - Markdown, HTML, headless-rendered PDF, or a standalone presenter-ready HTML page
16
+ - **Local-first** - binds only to `127.0.0.1`; nothing is uploaded, and your work stays in browser local storage until export
17
+
18
+ ![Slide showing a code-heavy presentation with syntax highlighting](https://github.com/user-attachments/assets/e61d23e7-3f35-46c9-95a0-ce4976b6179c)
19
+
20
+ ## Quick start
21
+
22
+ Check out the sample Markdown source in [`examples/example-2.md`](https://raw.githubusercontent.com/dustfeather/deckrun/refs/heads/master/examples/example-2.md) and load it directly to see how `deckrun` presents it (no installation required):
23
+
24
+ ```bash
25
+ npx @dustfeather/deckrun
26
+ ```
27
+
28
+ ## Installation
29
+
30
+ Install with a single command. On Linux and macOS the installer bootstraps
31
+ Node.js (>= 26) automatically if it's missing, then installs deckrun from npm:
32
+
33
+ ```bash
34
+ curl -fsSL https://raw.githubusercontent.com/dustfeather/deckrun/master/install.sh | sh
35
+ ```
36
+
37
+ On Windows, the PowerShell installer does the same (installs the Node.js LTS
38
+ via winget, or downloads it, when needed):
39
+
40
+ ```powershell
41
+ irm https://raw.githubusercontent.com/dustfeather/deckrun/master/install.ps1 | iex
42
+ ```
43
+
44
+ Or install globally from npm directly:
45
+
46
+ ```bash
47
+ npm install -g @dustfeather/deckrun
48
+ ```
49
+
50
+ Or run it without installing:
51
+
52
+ ```bash
53
+ npx @dustfeather/deckrun # open the editor
54
+ npx @dustfeather/deckrun slides.md # open a local file in the editor, present from there
55
+ npx @dustfeather/deckrun <url> # open a public Markdown or HTML URL in the editor
56
+ ```
57
+
58
+ ## Usage
59
+
60
+ ```bash
61
+ # Write a new deck in the built-in editor
62
+ deckrun
63
+
64
+ # Open a file in the editor on the default port 7890. The editor saves back
65
+ # to the file, edits made to the file on disk reload the editor, and
66
+ # Cmd/Ctrl+Enter presents.
67
+ deckrun slides.md
68
+
69
+ # Open a self-contained HTML page instead of a Markdown deck
70
+ deckrun page.html
71
+
72
+ # Serve on a custom port
73
+ deckrun slides.md -p 3000
74
+
75
+ # Start the server without opening a browser tab
76
+ deckrun slides.md --no-open
77
+
78
+ # Open the file without watching it for changes on disk
79
+ deckrun slides.md --no-watch
80
+
81
+ # Show a launch overlay that enters fullscreen on the first key or click
82
+ deckrun slides.md --fullscreen
83
+
84
+ # Pick one of the fourteen themes
85
+ deckrun slides.md --theme paper
86
+
87
+ # Or set the two faces yourself
88
+ deckrun slides.md --theme tokyo --head-font playfair --body-font lora
89
+
90
+ # Recompose the same Markdown and choose how slides move
91
+ deckrun slides.md --template editorial --transition fade
92
+
93
+ # Check one or more decks before presenting or committing them
94
+ deckrun lint slides.md
95
+ deckrun lint talks/*.md --format json
96
+
97
+ deckrun --list-themes
98
+ deckrun --list-fonts
99
+ deckrun --list-templates
100
+ deckrun --list-transitions
101
+ ```
102
+
103
+ On start, the CLI prints the slide count and the local URL:
104
+
105
+ ```text
106
+ 8 slides from slides.md · opening in the editor
107
+ editor → http://127.0.0.1:7890 (Ctrl+C to stop)
108
+ write on the left, live deck on the right. saves back to slides.md.
109
+ edits to slides.md on disk reload the editor as well.
110
+ ```
111
+
112
+ A file passed on the CLI opens straight into the editor, backed by the file
113
+ rather than the browser library: what you type autosaves back to the file,
114
+ and edits made to the file on disk — from your own editor, a build step, an
115
+ agent — reload the deckrun editor and its live preview. Pass `--no-watch`
116
+ to turn off the disk watching. Presenting (`Cmd/Ctrl+Enter`) works exactly
117
+ as it does for library decks. A document fetched from a URL opens the same
118
+ way, but read-only toward its origin: nothing is written back, so download
119
+ or duplicate it to keep changes.
120
+
121
+ With no file or URL, it starts the editor on its browser library instead:
122
+
123
+ ```text
124
+ editor → http://127.0.0.1:7890 (Ctrl+C to stop)
125
+ write on the left, live deck on the right. autosaves to your browser.
126
+ Cmd/Ctrl+K inserts anything · Cmd/Ctrl+Shift+L switches theme · Cmd/Ctrl+Enter presents
127
+ ```
128
+
129
+ The server binds to `127.0.0.1` only and answers a request only when its `Host` header names `127.0.0.1`, `localhost` or `[::1]` on that port, so it is not reachable from another machine and cannot be reached through DNS rebinding either. It is still reachable by anything else running on your own machine. Stop it with `Ctrl+C`.
130
+
131
+ ### CLI options
132
+
133
+ | Option | Default | Description |
134
+ | --------------------- | ------- | ------------------------------------------------------ |
135
+ | `[file]` | | Markdown file, HTML file, or public URL to open in the editor. Omit it for a blank editor. |
136
+ | `-p, --port <number>` | `7890` | Port to serve the presentation on |
137
+ | `--no-open` | `false` | Start the HTTP server without opening the browser |
138
+ | `--no-watch` | `false` | Do not watch the opened file for changes on disk |
139
+ | `--fullscreen` | `false` | Prompt to enter fullscreen on the first key or click |
140
+ | `--theme <name>` | `nord` | Any of the fourteen themes, by id |
141
+ | `--head-font <name>` | | Override the theme's heading and title face |
142
+ | `--body-font <name>` | | Override the theme's body face |
143
+ | `--template <name>` | `classic` | Composition: `classic`, `minimal`, `editorial`, or `spotlight` |
144
+ | `--transition <name>` | `slide` | Motion: `slide`, `fade`, `zoom`, `lift`, or `none` |
145
+ | `--list-themes` | | Print every theme with its mood and blurb, then exit |
146
+ | `--list-fonts` | | Print every face and its kind, then exit |
147
+ | `--list-templates` | | Print every composition template, then exit |
148
+ | `--list-transitions` | | Print every slide transition, then exit |
149
+ | `-v, --version` | | Print the version number |
150
+ | `-h, --help` | | Print help for the command |
151
+
152
+ An unknown `--theme` or font is an error rather than a silent fallback, so a typo does not quietly hand you the default. `dark` and `light` still name the two original palettes. In the editor, `--theme` sets the starting look and `Cmd Shift L` opens the picker to change it live.
153
+
154
+ `--head-font` and `--body-font` apply to Markdown decks only. An HTML doc brings its own typography; passing any of them alongside an `.html`/`.htm` file prints a notice and is otherwise ignored.
155
+
156
+ ### `deckrun lint`
157
+
158
+ The lint command performs fast, browser-free checks and reports the source
159
+ line, slide number, severity, and rule id for each problem:
160
+
161
+ ```bash
162
+ deckrun lint slides.md
163
+ deckrun lint intro.md architecture.md
164
+ deckrun lint slides.md --format json
165
+ deckrun lint slides.md --max-warnings -1
166
+ ```
167
+
168
+ It catches empty decks/slides, unclosed code fences and display math, untagged
169
+ code fences, excessive prose or bullets, overly long headings, missing image
170
+ alt text, invalid image opacity, and malformed or excessive reveal markers.
171
+ Errors fail the command. Warnings also fail by default, making the command
172
+ useful in CI; `--max-warnings N` changes that threshold and `-1` allows any
173
+ number of warnings. Pass `-` as the file to lint standard input.
174
+
175
+ ## The editor
176
+
177
+ Run `deckrun` with no file and it serves an editor instead of a deck. A deck already in this browser resumes with no extra step, exactly as before. The first time you run it — or any time you choose "new" from the library with nothing open yet — you land on a start screen instead: a new Markdown deck, a new or uploaded HTML doc, or the library. Pick Markdown and you get the usual pane pair, Markdown on the left and the live deck on the right, plus a library of every deck and doc you have written.
178
+
179
+ ```bash
180
+ deckrun
181
+ deckrun --theme paper # start the editor in a given look
182
+ deckrun --head-font syne # and a face of your own
183
+ deckrun -p 3000 # editor on another port
184
+ ```
185
+
186
+ The preview is not an approximation. Every keystroke is parsed by the same parser the CLI uses, and the slides render inside an iframe fixed at 1600x900 with the deck's own stylesheet. Pressing present POSTs the Markdown back to the server, which builds the deck exactly as `deckrun file.md` would. The output is byte-identical.
187
+
188
+ ### The two bars
189
+
190
+ The top bar is for decisions: the deck name, the library, and then everything
191
+ that changes what the deck looks like — `template`, `theme`, and `font` — with
192
+ `guide`, `insert`, `new`, `export`, and `present` beside them.
193
+
194
+ The bottom bar is for counts and status: caret position, word count, slide
195
+ count, a tip line, and any message the editor has for you. Nothing that only
196
+ tells you where you are belongs in a bar you reach for to act.
197
+
198
+ The top bar wraps rather than scrolls when a window is narrow, since a
199
+ scrollable bar would clip the menus that hang out of it. Before it wraps it
200
+ sheds the keyboard hints, then `new` and `guide`, both of which the command
201
+ palette also carries.
202
+
203
+ `new` reopens the same start screen shown on a first run — pick Markdown or
204
+ HTML from there to bring another deck or doc into the library without losing
205
+ the one you have open.
206
+
207
+ ### Writing
208
+
209
+ - The caret drives the preview. Move it into a slide and the preview follows.
210
+ - The gutter labels each `---` with the slide it starts, so you can see the deck's shape while you type.
211
+ - Markdown is syntax-highlighted in place: headings by level, bold, italic, inline code, links, image directives, fences, tables, notes, and raw HTML each get their own color.
212
+ - Enter continues the list you are in, and continues numbering. Enter on an empty item ends the list.
213
+ - Tab inserts two spaces.
214
+ - `Alt Left` / `Alt Right` (or `Alt Up` / `Alt Down`) hop the caret between slides.
215
+
216
+ ### Discovering what a slide can hold
217
+
218
+ Three surfaces exist so you never have to remember the syntax:
219
+
220
+ - Guide drawer (`Cmd /`): every slide layout, text style, list, table, code block, image directive, and embed, grouped and explained, each with an insert button that drops it at your caret.
221
+ - Command palette (`Cmd K`): the same catalogue, searchable, plus the actions. Type "split", "embed", or "notes" and hit enter.
222
+ - Contextual nudges: a prompt appears in the editor when the document suggests one. A slide that overflows its own canvas, a code fence with no language tag, an image that could be a split layout, a deck with no speaker notes, a slide carrying too many bullets. Each nudge inserts the fix or dismisses for good.
223
+
224
+ A tip line in the status bar cycles through the rest.
225
+
226
+ The Markdown start screen also carries template and transition choices. Pick
227
+ them before creating or importing a deck, or use the `template` menu later.
228
+ Templates are CSS compositions rather than source transformations, so changing
229
+ one after the deck is finished immediately recomposes every slide while the
230
+ Markdown remains byte-for-byte unchanged.
231
+
232
+ ### Images
233
+
234
+ Images are referenced by path, exactly as in a file-based deck. The editor serves the directory you launched in, so launch `deckrun` next to your diagrams and `![Diagram](diagram.png "right")` resolves.
235
+
236
+ The guide and the palette carry every directive, so the layouts are one keystroke away rather than something to remember. Images are not uploaded or embedded: the editor keeps Markdown, and the files stay on disk where you put them.
237
+
238
+ ### HTML documents
239
+
240
+ Alongside Markdown decks, deckrun can present a second kind of document: a self-contained HTML page with no slide boundaries — a single continuous doc, not a series of slides. There is no blank-slate option: get one into the editor from the start screen by uploading a `.html` file or pointing it at a public HTML URL (fetched server-side, so the page's own CORS policy does not matter), by dropping a `.html` file onto the editor, or on the CLI with `deckrun page.html`.
241
+
242
+ Editing is a plain source pane on the left and a live preview on the right — no syntax highlighting, gutter, guide drawer, or command palette, since there are no slide-authoring directives to catalogue. The preview updates from the textarea directly, with no server round trip.
243
+
244
+ Presenting wraps the doc in an iframe and layers the tool belt that still makes sense with no slides — laser pointer, pen, blank canvas, blackout, fullscreen, and `?` for controls — on top of it. There is no HUD, slide counter, overview grid, or arrow-key navigation, since there is nothing to count or step through.
245
+
246
+ A doc authored in the browser editor is expected to be self-contained: inline styles and scripts, and assets from a CDN or a `data:` URI rather than a relative local path, since editor-mode present and PDF serve it from an in-memory copy, not from a folder on disk. A file passed on the CLI does not have that restriction — `deckrun page.html` serves assets from the file's own directory, exactly like a Markdown deck's images, so `<img src="diagram.png">` next to `page.html` resolves normally.
247
+
248
+ If a doc runs its own script that listens for keyboard input — an embedded framework, a game, a chart with its own shortcuts — it may end up racing deckrun's own listener for a key, since both are attached to the same page. Presenter shortcuts are best-effort in that case, not guaranteed to win.
249
+
250
+ ### The deck library
251
+
252
+ Every deck or doc you write is kept in this browser, not just the last one. The `decks` button in the top bar shows how many there are, and `Cmd O` opens the library. Markdown decks and HTML docs share one list.
253
+
254
+ - Each row shows the name, its kind, a size — slide count for a deck, character count for a doc — and when you last touched it. Click one, or use the arrows and enter, to open it.
255
+ - Decks are listed most recently edited first, so the one you want is usually at the top.
256
+ - The open deck is saved before another one loads, so switching never costs you an edit.
257
+ - Duplicate copies an entry into the library and opens the copy. Delete asks first and is permanent.
258
+ - `new`, in the top bar or from the library, opens the start screen rather than immediately clearing the pane — pick Markdown or HTML there, blank or uploaded, or an HTML doc by URL.
259
+ - Uploading a file from the start screen, or dropping a `.md`/`.html` file onto the editor, lands it as a new entry of the matching kind. Name collisions get a numeric suffix rather than overwriting.
260
+ - Rename with the name field in the top bar. That name is also the export filename.
261
+
262
+ Storage layout: an index under `deckrun.decks.v1` holds metadata only, and each entry's content lives under `deckrun.deck.<id>`. Listing your library never reads that text.
263
+
264
+ ### Saving
265
+
266
+ The open deck autosaves to `localStorage` half a second after you stop typing, the way a local-first drawing tool does. A successful save says nothing — it is automatic and it is reliable, and a clock ticking in the corner is not information. Only a failure speaks up, in the status bar and in a toast.
267
+
268
+ - Nothing is uploaded. The server is on `127.0.0.1` and only ever sees Markdown you are actively previewing.
269
+ - Storage is scoped to the origin, which includes the port. Decks written on `:7890` are not visible on `:3000`, so stay on the default port or pass the same `-p` each time.
270
+ - Browsers cap `localStorage` near 5 MB across all your decks. Past that the save fails loudly and tells you to download or delete, rather than quietly losing work.
271
+ - A browser that blocks storage outright, like a private window, is detected at startup and says so instead of pretending to save.
272
+ - Export writes a copy out of the browser. Source, PDF, and a presenter-ready page are all in the `export` menu.
273
+
274
+ ### Exporting
275
+
276
+ The `export` button in the top bar opens a menu with three formats. All three are named from the name field, and the menu relabels itself for an HTML doc.
277
+
278
+ | Format | Shortcut | Result |
279
+ | --------------------- | ------------- | ------------------------------------------------------------------- |
280
+ | Markdown / Source | `Cmd S` | The plain text you see in the editor — `.md` for a deck, `.html` for a doc |
281
+ | PDF | `Cmd Shift S` | A real `.pdf` file — one 16:9 page per slide for a deck, or the doc's own pages for an HTML doc |
282
+ | HTML / Presenter Page | | One standalone `.html` page: the deck, or the doc wrapped with the presenter tool belt |
283
+
284
+ PDF export does not hand you a print dialog. The server drives a headless browser over the built deck and streams back the finished file, so there is nothing to configure and nothing to get wrong. Pages are 13.333in by 7.5in, the standard widescreen slide size, with no margins: the theme, its backdrop geometry, code block surfaces, table fills, and background images all come through, and the HUD, arrows, cursor, pets, and speaker notes are stripped.
285
+
286
+ It uses a Chromium-family browser already on your machine and installs nothing. Chrome, Chromium, Edge, and Brave are found automatically in their usual locations; `DECKRUN_BROWSER`, `CHROME_PATH`, or `PUPPETEER_EXECUTABLE_PATH` points at one somewhere else, checked in that order. A render takes a few seconds, and only one runs at a time.
287
+
288
+ With no such browser on the machine, the editor falls back to opening the deck with the print dialog up and says so. That route now produces the same pages, because the print stylesheet sets the page box itself.
289
+
290
+ The HTML export is the same page `deckrun` serves: styles and the navigation runtime are inlined, so it opens from disk, and keyboard, touch, overview, and fullscreen all still work. Two things do not travel with it, since it is one file rather than a bundle:
291
+
292
+ - Fonts, syntax highlighting, KaTeX, and Mermaid load from pinned CDNs in a standalone export, so a viewer needs a connection to see them exactly as you do. The theme, template, transitions, colors, and backdrop are inline.
293
+ - Images and videos referenced by path stay on your disk. Ship them alongside, or host the page where those paths resolve.
294
+
295
+ An HTML doc's PDF is not paginated to 16:9 slides — it prints the doc's own `@page`/print CSS (or Chrome's defaults if it has none), exactly as if you had opened the file yourself and pressed print. There is no presenter chrome to strip, since the doc is printed on its own, without the tool-belt wrapper.
296
+
297
+ ### Preview controls
298
+
299
+ - Single mode scales one slide to fit the pane, with speaker notes underneath when the slide has them.
300
+ - Grid mode (`Cmd G`) lays out the whole deck. Click any slide to jump the caret to it.
301
+ - Drag the divider to resize the panes. Double-click it to snap back to an even split. The position is remembered.
302
+
303
+ ### Editor shortcuts
304
+
305
+ | Keys | Action |
306
+ | ------------------- | ------------------------- |
307
+ | `Cmd K` | Command palette |
308
+ | `Cmd O` | Deck library |
309
+ | `Cmd /` | Guide drawer |
310
+ | `Cmd Enter` | Present in a new tab |
311
+ | `Cmd S` | Export Markdown |
312
+ | `Cmd Shift S` | Export PDF |
313
+ | `Cmd D` | New slide |
314
+ | `Cmd B` | Bold |
315
+ | `Cmd I` | Italic |
316
+ | `Cmd E` | Inline code |
317
+ | `Cmd G` | Toggle grid preview |
318
+ | `Cmd Shift L` | Theme picker |
319
+ | `Alt Left`, `Alt Right`, `Alt Up`, `Alt Down`| Previous and next slide |
320
+ | `Esc` | Close a menu, the palette, or the guide |
321
+
322
+ Selecting text in the preview offers to highlight it; see [Highlighting and comments](#highlighting-and-comments).
323
+
324
+ `Cmd K` (palette), `Cmd /` (guide), `Cmd D/B/I/E` (snippets), and `Cmd G` (grid) are Markdown-only — an HTML doc's source is plain text with no snippet catalog or grid view. Library, present, and both exports stay wired the same for either kind.
325
+
326
+ On Windows and Linux, `Ctrl` replaces `Cmd`.
327
+
328
+ ### Editor routes
329
+
330
+ The editor adds a few endpoints under `/__`, all local:
331
+
332
+ | Route | Purpose |
333
+ | ------------------ | ------------------------------------------------------------------ |
334
+ | `/__preview` | The iframe that renders slides with the deck stylesheet |
335
+ | `/__parse` | POST Markdown, get back rendered slides and notes |
336
+ | `/__present` | POST Markdown, get back the path to a freshly built deck |
337
+ | `/__pdf` | POST Markdown, get back a rendered PDF |
338
+ | `/__present-doc` | POST an HTML doc, get back the path to its presenter-wrapped page |
339
+ | `/__pdf-doc` | POST an HTML doc, get back a rendered PDF of the doc itself |
340
+ | `/__fetch-doc` | POST a public URL, get back that page's HTML, fetched server-side |
341
+ | `/?deck=<n>` | A built deck or stashed doc, kept in memory. The last eight are retained |
342
+
343
+ A built deck is served from `/` rather than a subpath on purpose. Served from `/__deck/1`, a slide's `![](diagram.png)` would resolve against `/__deck/` and 404.
344
+
345
+ Everything else on the URL is served from the directory you launched in, so `![](./diagram.png)` and `<video src="clip.mp4">` work against local files without inlining them.
346
+
347
+ ## Slide authoring
348
+
349
+ ### Slide separators
350
+
351
+ Separate slides with `---` on a line of its own. Leading and trailing spaces or tabs on that line are allowed:
352
+
353
+ ```markdown
354
+ # First slide
355
+
356
+ Introduction text goes here.
357
+
358
+ ---
359
+
360
+ ## Second slide
361
+
362
+ Content for the next slide.
363
+
364
+ ---
365
+
366
+ # Conclusion
367
+ ```
368
+
369
+ Empty slides are dropped, so trailing separators are harmless. A `---` on the very first line is not a separator, which means YAML frontmatter is not supported.
370
+
371
+ Two more things `---` does that catch people out:
372
+
373
+ - It breaks a slide even inside a fenced code block. A `---` line in your code sample will split the deck there, so use a different separator in sample output.
374
+ - It is the only slide break. For a horizontal rule inside a slide, write `***` or `___`.
375
+
376
+ ### Page title
377
+
378
+ The rendered page title comes from the first heading of the first slide, with any inline markup stripped. If the first slide has no heading, the Markdown filename is used instead.
379
+
380
+ ### Markdown support
381
+
382
+ Slides are rendered with [marked](https://github.com/markedjs/marked), so standard Markdown works: headings, paragraphs, ordered and unordered lists with nesting, tables, blockquotes, horizontal rules, links, inline code, bold, and italic.
383
+
384
+ Each element is styled for projection rather than reading:
385
+
386
+ | Element | Treatment |
387
+ | --------------- | ------------------------------------------------------------------ |
388
+ | `h1` | Largest, mauve, intended for section and title slides |
389
+ | `h2` | Blue, the default slide title |
390
+ | `h3` | Sky blue subheading |
391
+ | `h4` | Teal, smallest heading |
392
+ | Bold | Peach, for the single term that must land |
393
+ | Italic | Muted subtext, for asides |
394
+ | Inline code | Green on a bordered surface chip |
395
+ | List markers | Mauve bullets and numbers |
396
+ | Tables | Lavender headers, mauve underline, zebra-striped rows |
397
+ | Blockquotes | Mauve left rule on a tinted background |
398
+ | Links | Blue with an offset underline |
399
+
400
+ Font sizes use `clamp()` against the viewport, so the same deck reads correctly on a laptop and on a projector without changes. Content that overflows a slide is clipped rather than scrolled, which is a deliberate nudge to split the slide.
401
+
402
+ An example that exercises most of the above:
403
+
404
+ ```markdown
405
+ ## System Architecture
406
+
407
+ > Any fool can write code that a computer can understand. Good programmers write code that humans can understand. - Martin Fowler
408
+
409
+ - Ingestion pipeline with backpressure controls
410
+ - Memory-mapped buffer storage
411
+ - Zero-copy ring buffer
412
+ - Page-aligned disk persistence
413
+
414
+ | Service | Port | Protocol |
415
+ | ----------- | ---- | -------- |
416
+ | api-gateway | 8080 | HTTP/2 |
417
+ | worker-pool | 9090 | gRPC |
418
+ ```
419
+
420
+ ### Code blocks
421
+
422
+ Fenced code blocks are highlighted by Highlight.js in the browser. Tag the language so the grammar is picked correctly:
423
+
424
+ ````markdown
425
+ ```typescript
426
+ interface Slide {
427
+ html: string;
428
+ bgImage?: PositionedImage;
429
+ rightImage?: PositionedImage;
430
+ leftImage?: PositionedImage;
431
+ notes?: string;
432
+ }
433
+
434
+ function parseSlides(markdown: string): Slide[] {
435
+ return markdown
436
+ .split(/\n---\n/)
437
+ .filter(Boolean)
438
+ .map(raw => processSlide(raw));
439
+ }
440
+ ```
441
+ ````
442
+
443
+ Code blocks scroll horizontally when a line is too long, so long lines never reflow mid-presentation.
444
+
445
+ ### Math and Mermaid diagrams
446
+
447
+ KaTeX renders inline math with single dollar delimiters and display math with
448
+ double dollars. The bracket forms `\(...\)` and `\[...\]` work too:
449
+
450
+ ```markdown
451
+ The amortized cost is $O(1)$ per operation.
452
+
453
+ $$
454
+ T(n) = T(n/2) + O(n) = O(n)
455
+ $$
456
+ ```
457
+
458
+ Mermaid diagrams use an ordinary language-tagged fence:
459
+
460
+ ````markdown
461
+ ```mermaid
462
+ sequenceDiagram
463
+ Client->>API: Request
464
+ API->>Store: Read
465
+ Store-->>API: Result
466
+ API-->>Client: Response
467
+ ```
468
+ ````
469
+
470
+ Both render in the live preview, a presented deck, standalone HTML, and PDF.
471
+ The editor waits for the equation or diagram before measuring slide overflow,
472
+ and PDF rendering uses the copies installed with deckrun rather than waiting on
473
+ a CDN. Invalid source stays visible as an error on the slide instead of
474
+ silently disappearing.
475
+
476
+ ### Incremental reveals
477
+
478
+ Append `{reveal}` to a bullet or another Markdown block. Forward navigation
479
+ reveals each marked block before advancing to the next slide; backward
480
+ navigation hides revealed blocks before returning to the previous slide:
481
+
482
+ ```markdown
483
+ ## Three stages
484
+
485
+ - Parse the Markdown
486
+ - Build semantic HTML {reveal}
487
+ - Render the final slide {reveal}
488
+ ```
489
+
490
+ Put the marker on its own line to reveal the entire block after it. This works
491
+ for paragraphs, blockquotes, equations, code fences, and Mermaid diagrams:
492
+
493
+ ````markdown
494
+ {reveal}
495
+ ```mermaid
496
+ graph LR
497
+ Markdown --> HTML --> PDF
498
+ ```
499
+ ````
500
+
501
+ The editor preview and grid show the complete slide so you can author and
502
+ detect overflow against the final state. Overview thumbnails and PDF also show
503
+ all content; reveals are interactive only while presenting. Reduced-motion
504
+ preferences keep the reveal but remove its movement.
505
+
506
+ ### Embeds and inline HTML
507
+
508
+ Raw HTML passes through untouched, so anything the browser can render can live on a slide.
509
+
510
+ ```markdown
511
+ <iframe src="https://www.youtube.com/embed/VIDEO_ID" allowfullscreen></iframe>
512
+
513
+ <video src="demo.mp4" controls muted loop></video>
514
+
515
+ Press <kbd>Cmd</kbd> <kbd>K</kbd> to open the palette, and latency drops to <mark>4.1ms</mark>.
516
+ ```
517
+
518
+ | Element | Treatment |
519
+ | ---------- | ---------------------------------------------------------------------------- |
520
+ | `iframe` | Forced to full width at a 16:9 ratio, bordered and rounded |
521
+ | `video` | Centered, capped at 60% of the slide height, aspect ratio preserved |
522
+ | `kbd` | Rendered as a physical key cap in lavender |
523
+ | `mark` | Yellow underline on a tinted background |
524
+
525
+ Local videos are served from the folder you launched in, so `demo.mp4` next to your Markdown just works. Embeds need network access at presentation time, and they do not survive a PDF export.
526
+
527
+ ### Speaker notes
528
+
529
+ Attach notes to a slide with an HTML comment carrying a `note:` or `notes:` directive:
530
+
531
+ ```markdown
532
+ ## Deployment Strategy
533
+
534
+ Rolling deployment with zero downtime.
535
+
536
+ <!-- notes: Review database migration rollout steps before advancing. -->
537
+ ```
538
+
539
+ Every such comment is stripped from the slide, so notes are never rendered into the projected output, the PDF export, or the standalone HTML export — and the exports do not carry the notes text at all. When you present from the editor, the notes of the slide on screen are shown in the editor's notes panel instead.
540
+
541
+ ### Image layout directives
542
+
543
+ Image placement is controlled by the Markdown title attribute, the quoted string after the URL:
544
+
545
+ ```markdown
546
+ ![Architecture Diagram](diagram.png "right")
547
+ ![Benchmark Graph](benchmark.png "left")
548
+ ![Background Graphic](backdrop.png "bg")
549
+ ![Telemetry Dashboard](dashboard.png "right opacity:0.8")
550
+ ![Inline Figure](figure.png)
551
+ ```
552
+
553
+ | Directive | Description |
554
+ | ----------- | ------------------------------------------------------------------------------ |
555
+ | `right` | Split layout: content on the left, image fills the right panel |
556
+ | `left` | Split layout: image fills the left panel, content on the right |
557
+ | `bg` | Background layout: image covers the slide canvas beneath the text |
558
+ | `opacity:N` | Opacity between `0.0` and `1.0`, combinable with `left`, `right`, or `bg` |
559
+
560
+ Notes on how directives are parsed:
561
+
562
+ - Matching is case-insensitive and substring-based, so reserve the title attribute for directives. A title like `"Left panel of the gateway"` is read as a `left` directive.
563
+ - Precedence is `right`, then `left`, then `bg`, when more than one appears.
564
+ - `opacity` accepts `opacity:0.8`, `opacity=0.8`, or `opacity 0.8`, and clamps to the `0.0` to `1.0` range.
565
+ - One positioned image of each kind applies per slide. If two `right` images appear, the last one wins.
566
+ - A positioned image is lifted out of the text flow, so its position in the Markdown source does not matter.
567
+
568
+ Images with no directive stay inline, centered in the document flow and capped at 55% of the viewport height. Panel images are capped at 78% and keep their aspect ratio with a drop shadow.
569
+
570
+ ## Themes
571
+
572
+ Nineteen themes, twelve dark and seven light. A theme is not just a palette: each
573
+ one brings its own display, body, and monospace faces, its own Highlight.js
574
+ grammar colors, and its own animated geometry behind the slides.
575
+
576
+ | id | mood | what it is |
577
+ | ----------------------- | ----- | ------------------------------------------------------------------- |
578
+ | `midnight` | dark | Catppuccin Mocha. Violet on deep indigo, drifting orbs |
579
+ | `tokyo` | dark | Tokyo Night. Neon cyan over a wireframe grid |
580
+ | `nord` | dark | Arctic frost blue on polar slate, with slow contour waves |
581
+ | `dracula` | dark | Purple and hot pink over charcoal, lit by a gradient mesh |
582
+ | `gruvbox` | dark | Warm amber and moss on retro brown, hatched like graph paper |
583
+ | `rosepine` | dark | Rosé Pine. Muted iris and gold on plum, under a slow aurora |
584
+ | `forest` | dark | Everforest sage on deep pine, rippling in concentric rings |
585
+ | `onedarkpro` | dark | Atom's One Dark Pro. Muted blue-grey with cyan and violet accents |
586
+ | `catppuccin-frappe` | dark | Official Catppuccin Frappé. Muted mauve on soft slate blue |
587
+ | `catppuccin-macchiato` | dark | Official Catppuccin Macchiato. Balanced mauve on deep indigo |
588
+ | `catppuccin-mocha` | dark | Official Catppuccin Mocha. Full-strength mauve on the darkest base |
589
+ | `neon` | dark | Electric cyan and magenta on true black, raked by light beams |
590
+ | `daylight` | light | Catppuccin Latte, contrast-tuned for a projector. Dot matrix |
591
+ | `arctic` | light | Nord inverted. Frost blue on cool paper, with contour waves |
592
+ | `solarized` | light | The classic low-glare cream, paired with Lora for long prose |
593
+ | `paper` | light | Crimson serif on warm cream. Editorial, print-first, very legible |
594
+ | `rosequartz` | light | Rosé Pine Dawn. Blush and iris on linen, with soft orbs |
595
+ | `swiss` | light | Black on white, one red. Heavy grotesk, tight tracking, hard grid |
596
+ | `catppuccin-latte` | light | Official Catppuccin Latte. Warm mauve on soft cream |
597
+
598
+ ```bash
599
+ deckrun slides.md --theme paper
600
+ deckrun --list-themes
601
+ ```
602
+
603
+ `dark` and `light` are kept as aliases for `midnight` and `daylight`, so older
604
+ commands and scripts keep working. So are `mocha`, `latte`, `tokyo-night`,
605
+ `rose-pine`, and `rose-quartz`.
606
+
607
+ In a file-backed deck the theme is baked into the page at launch, so switching
608
+ means restarting with a different flag. In the editor it is live: `Cmd Shift L`
609
+ opens a picker where the arrow keys preview each theme on the real deck as you
610
+ move, `Enter` keeps the one you land on, and `Esc` puts back the one you had.
611
+ The choice is remembered per browser and travels into the deck you present and
612
+ the PDF you export.
613
+
614
+ ### Backdrops
615
+
616
+ Every theme names one of ten backdrop patterns, drawn behind the slides in the
617
+ theme's own accent colors and drifting slowly enough to read as depth rather
618
+ than as motion: `orbs`, `grid`, `dots`, `topo`, `beams`, `rings`, `waves`,
619
+ `mesh`, `aurora`, and `none`.
620
+
621
+ The whole backdrop is CSS custom properties and gradients — no canvas, no
622
+ images, no JavaScript — so it survives into the PDF, where it is re-attached to
623
+ each page (a fixed element does not repeat across a paged medium). Anyone who
624
+ has `prefers-reduced-motion` set gets the geometry without the drift.
625
+
626
+ ### Faces
627
+
628
+ Headings use the theme's display face, body copy its body face, and code, key
629
+ caps, and the presenter chrome its monospace face. Faces come from Google
630
+ Fonts in a single request; the browser only downloads the files a rendered
631
+ slide actually paints.
632
+
633
+ The heading and the body face can each be replaced without leaving the theme,
634
+ and they are chosen **separately** — a serif heading over a sans body, or the
635
+ reverse, is a setting rather than a fork:
636
+
637
+ ```bash
638
+ deckrun slides.md --theme tokyo --head-font playfair --body-font lora
639
+ deckrun slides.md --body-font newsreader # heading stays the theme's
640
+ deckrun --list-fonts
641
+ ```
642
+
643
+ Twenty faces, grouped sans, serif, and mono: `inter`, `spaceGrotesk`, `sora`,
644
+ `manrope`, `figtree`, `outfit`, `archivo`, `syne`, `bricolage`, `workSans`,
645
+ `plexSans`, `fraunces`, `playfair`, `newsreader`, `lora`, `plexMono`,
646
+ `jetbrains`, `firaCode`, `spaceMono`, `sourceCodePro`. Their human names work
647
+ too, so `--head-font "Playfair Display"` is the same as `--head-font playfair`.
648
+
649
+ The monospace face is not overridable on purpose: code wants the face the
650
+ palette's syntax highlighting was chosen against.
651
+
652
+ Overrides are attributes, not a second palette. `[data-head]` and `[data-body]`
653
+ carry the same specificity as `[data-theme]` and are emitted after it, so
654
+ source order decides and the override wins. No attribute means the theme's own
655
+ face — which is why there is no "default" entry to keep in step with anything,
656
+ and why a face the deck does not recognise falls back silently rather than
657
+ leaving a slide unstyled.
658
+
659
+ In the editor the two live in one `font` menu in the top bar, side by side:
660
+ heading on the left, body on the right, every row set in the face it offers.
661
+ Picking does not close the menu, because choosing a heading and then a body is
662
+ one decision. Both are remembered per browser and travel into the deck you
663
+ present and the PDF you export.
664
+
665
+ ### Templates and transitions
666
+
667
+ Templates compose with themes. A theme owns color and type; a template owns
668
+ spacing, alignment, rules, image treatment, and the overall reading rhythm:
669
+
670
+ | id | composition |
671
+ | ----------- | ------------------------------------------------------------------ |
672
+ | `classic` | The original balanced deckrun layout |
673
+ | `minimal` | Quiet surfaces, wider margins, fewer decorative treatments |
674
+ | `editorial` | Strong rules and magazine-like reading rhythm |
675
+ | `spotlight` | Centered, high-impact composition for concise keynote slides |
676
+
677
+ Pick one on the editor start screen or switch it later from the `template`
678
+ menu. Switching only changes the root `data-template` attribute and CSS, so an
679
+ existing deck transforms instantly and its Markdown is untouched.
680
+
681
+ The same menu carries five independent transitions: `slide`, `fade`, `zoom`,
682
+ `lift`, and `none`. `prefers-reduced-motion` suppresses their spatial motion,
683
+ and print/PDF disables them entirely.
684
+
685
+ ```bash
686
+ deckrun slides.md --template spotlight --transition zoom
687
+ deckrun --list-templates
688
+ deckrun --list-transitions
689
+ ```
690
+
691
+ ### Type and motion
692
+
693
+ Slides also assemble rather than appear: each top-level block on a slide rises
694
+ into place a beat after the one above it. That stagger is turned off in the
695
+ editor preview, where the slide is rebuilt on every keystroke, and in the PDF.
696
+ `prefers-reduced-motion` turns it off everywhere.
697
+
698
+ ### Writing your own theme
699
+
700
+ Themes live in `src/themes.ts`. Append one entry to `SPECS` and it appears
701
+ everywhere at once: the `--theme` flag, `--list-themes`, the editor's picker,
702
+ the live preview, the HTML export, and the PDF. Nothing else needs touching.
703
+
704
+ An entry is four things:
705
+
706
+ - **`neutrals`** — an eleven-step ramp from the page's outermost background
707
+ (`crust`) to its strongest foreground (`text`). Light themes run the same
708
+ direction: `crust` is still the backdrop, `text` is still the ink.
709
+ - **`accents`** — eleven hues, named after the Catppuccin slots so existing
710
+ palettes port across by copy and paste. They are the deck's paint box:
711
+ `<mark>`, list markers, table headers, and the five pen colors all draw from
712
+ here.
713
+ - **`roles`** — which three of those colors lead. `accent` carries h1, the
714
+ caret, focus rings, and every piece of chrome; `accent2` carries h2 and
715
+ links; `accent3` carries h3. Point them at any neutral or accent key — that
716
+ is how `nord` leads with frost blue while `gruvbox` leads with amber.
717
+ - **`type`** — a display face, a body face, a mono face, and the weight,
718
+ tracking, and casing the display face wants. Faces come from the `FONTS`
719
+ catalog at the top of the file; add an entry there to use a new one.
720
+
721
+ Plus `decor` for the backdrop and `hljs` for the code stylesheet.
722
+
723
+ ```ts
724
+ seafoam: {
725
+ label: "seafoam",
726
+ mood: "dark",
727
+ blurb: "Pale green on graphite, with a dot matrix.",
728
+ neutrals: { crust: "#0f1413", mantle: "#141a19", /* … eleven in all */ },
729
+ accents: { teal: "#7fd6c1", blue: "#78b7d0", /* … eleven in all */ },
730
+ roles: { accent: "teal", accent2: "blue", accent3: "green" },
731
+ type: { display: "sora", body: "inter", mono: "jetbrains", weight: 700 },
732
+ decor: "dots",
733
+ hljs: `${HL}atom-one-dark.min.css`,
734
+ },
735
+ ```
736
+
737
+ Everything else is derived from those four inputs — the accent tints, the
738
+ overlay scrims, the shadows (black on dark themes, tinted with the ink color on
739
+ light ones), the hairlines, the h1 rule gradient, and the backdrop's own
740
+ colors. That is deliberate: a new theme cannot fall out of step with itself,
741
+ and there is nothing to hand-tune per surface.
742
+
743
+ The palette is emitted as CSS custom properties, once per theme under a
744
+ `[data-theme]` selector in the editor and preview and once on `:root` in a
745
+ built deck, so overriding a single value in a fork stays a one-line change.
746
+
747
+ ## Navigation and controls
748
+
749
+ ### Keyboard shortcuts
750
+
751
+ | Key | Action |
752
+ | ------------------------------------ | ---------------------------------------- |
753
+ | `Right`, `Down`, `Space`, `PageDown` | Advance to the next reveal or slide |
754
+ | `Left`, `Up`, `Backspace`, `PageUp` | Return to the previous reveal or slide |
755
+ | `Home` | Jump to the first slide |
756
+ | `End` | Jump to the last slide |
757
+ | `O` | Toggle the overview grid |
758
+ | `F` | Toggle fullscreen |
759
+ | `L` | Toggle the laser pointer |
760
+ | `D` | Toggle the pen and draw on the slide |
761
+ | `C` | Toggle a blank canvas over the slide |
762
+ | `B` | Black out the screen |
763
+ | `?`, `H` | Show every control |
764
+ | `Escape` | Close whatever is open, one layer at a time |
765
+
766
+ While the pen is down, these keys are live as well:
767
+
768
+ | Key | Action |
769
+ | -------------------------- | ------------------------------------- |
770
+ | `1` … `5` | Pick the pen color |
771
+ | `E` | Toggle the eraser |
772
+ | `[`, `]` | Thinner, thicker |
773
+ | `Ctrl+Z`, `Cmd+Z` | Undo the last stroke |
774
+ | `X` | Clear this slide's annotations |
775
+
776
+ They only bind while the pen is down, so the letters stay free for everything else the rest of the time.
777
+
778
+ ### Mouse and touch
779
+
780
+ - Click the arrow buttons on either side of the screen. They dim at the first and last slide.
781
+ - Swipe horizontally on a touchscreen. A swipe longer than 50 pixels advances or goes back.
782
+ - Click any thumbnail in the overview to jump to that slide.
783
+
784
+ ### Overview mode
785
+
786
+ Press `O` or `Escape` for a grid of live thumbnails of every slide. Each thumbnail is a scaled clone of the real slide, so code highlighting, tables, and images all appear as they will on screen. The current slide is outlined in blue. Click a thumbnail to jump there with the correct transition direction, or press `O` or `Escape` to return without moving.
787
+
788
+ Arrow keys do not move the selection inside the overview. Navigation happens by clicking.
789
+
790
+ ### The footer controls
791
+
792
+ The bar along the bottom of a presented deck carries a button for each presenter tool, so a talk driven by a clicker or a trackpad needs no keyboard at all. `? controls` opens the same overlay `?` does: every key the deck listens for, grouped by what it does. An active tool lights up, and the pen strip with its color swatches, thickness, eraser, and clear button appears only while the pen is down.
793
+
794
+ ### Laser pointer
795
+
796
+ Press `L` and the mouse cursor becomes a soft red dot with a glow around it, sized to be visible from the back of a room. The real cursor is hidden while it is on, so nothing competes with the dot. It works over anything: slides, the blank canvas, annotations already drawn. Press `L` again, or `Escape`, to put it away.
797
+
798
+ ### Drawing on slides
799
+
800
+ Press `D` for a pen and draw straight onto the slide with the mouse, a trackpad, a pen tablet, or a finger.
801
+
802
+ - Annotations are held per slide, so you can mark up slide 3, keep going, and come back to find it as you left it.
803
+ - Arrow keys still navigate while the pen is down. Annotations that are merely on display do not swallow clicks, which keeps the nav arrows and footer buttons working.
804
+ - Strokes are stored in fractions of the viewport, not pixels, so resizing the window or entering fullscreen keeps them where you drew them.
805
+ - `Ctrl+Z` (`Cmd+Z`) removes the last stroke, `X` clears the slide, and `E` gives you an eraser that rubs out what it passes over rather than clearing everything.
806
+ - Nothing is written to disk. Reloading the deck starts clean, and the PDF export never contains annotations.
807
+
808
+ ### Blank canvas
809
+
810
+ Press `C` and the same canvas paints itself opaque over the slide: a blank board for the diagram you did not plan for, in the deck's own background color. The pen arms itself when it opens. `C` again, or `Escape`, brings the slide back with whatever you drew still on it, since both modes share one board per slide. Navigation still works behind it, so every slide has its own blank board.
811
+
812
+ ### Highlighting and comments
813
+
814
+ Select text — in the editor's preview, in a presented deck, or in a presented HTML doc — and a small bar appears with `highlight` and `highlight + comment`. Click a highlight to edit its comment or remove it; one that carries a comment gets a dot after it and shows the comment on hover.
815
+
816
+ - **The pen is not a theme colour.** A highlight is marker yellow with its own dark ink on all fourteen palettes, and a commented one is marker cyan. A tint of the accent would read as part of the design; the point of a highlight is that you can find it across a room, on a dark theme or a light one. Syntax colouring gives way to the ink inside a mark, so highlighted code stays readable.
817
+ - **They are session state, nothing more.** Highlights live in the browser session. They survive a reload and follow you into the tab `present` opens, and the moment that tab or the browser closes they are gone. Nothing reaches disk, the server, or `localStorage`, and the first time you make one the editor says so.
818
+ - **The editor and the projector share them.** Highlight while writing and the marks are already on the slides when you present, on the right slide and with their comments intact. Both tabs stay in step for as long as they are open.
819
+ - **They are anchored to the text, not to a position.** Edit the paragraph above a highlight and it stays put. Delete the sentence it was on and it quietly drops out rather than landing somewhere wrong.
820
+ - **Each document keeps its own.** Switching decks in the library switches the marks with them, and deleting a deck deletes its highlights too.
821
+ - **Exports never carry them.** Downloaded HTML, the standalone presenter page, and PDF export are all built server-side from the source, so nobody's reading marks end up in a file you hand over.
822
+
823
+ Works the same on Markdown decks and on HTML docs. In a deck, a highlight belongs to the slide it was made on; in a doc, to the document. Rendered equations and Mermaid diagrams are skipped — splitting either one apart would break the render — so highlighting covers prose, headings, tables, and code.
824
+
825
+ ### Blacking out the screen
826
+
827
+ Press `B` to drop the screen to black, for the moment when the room should be looking at you rather than the slide. While it is up, keys cannot move the deck by accident: only `B`, `Escape`, `Space`, `Enter`, or a click brings it back.
828
+
829
+ ### Fullscreen
830
+
831
+ Press `F` at any time to toggle fullscreen. Browsers only grant fullscreen from a user gesture, which is what `--fullscreen` works around: it shows a launch overlay that requests fullscreen on the first key or click, so the deck opens fullscreen without a manual step.
832
+
833
+ ### Notes while presenting
834
+
835
+ Present **from the editor** (`Cmd+Enter`) and the deck opens in its own tab; the editor tab stays yours. Its preview and speaker-notes panel follow the deck's current slide, so the deck goes on the projector and the notes stay on your screen.
836
+
837
+ - A `following deck · n` chip appears in the status bar, showing the slide the deck is on. It follows however you advance the deck — keys, footer arrows, overview.
838
+ - Typing in the editor (or clicking the chip) stops the following and hands the screen back to the caret. Presenting again turns it back on.
839
+ - The link is live: it survives a reload of either tab.
840
+ - The notes shown are the slide's `<!-- notes: ... -->`, the same text the audience never sees.
841
+
842
+ A deck run straight from a file has no editor to follow it; present it through the editor instead (`deckrun`, then drag the file onto it, then `Cmd+Enter`) to get the notes during the talk.
843
+
844
+ ## Visual design
845
+
846
+ - Terminal aesthetics throughout, set in IBM Plex Mono with a blinking mauve cursor in the top right corner.
847
+ - Direction-aware transitions. Slides slide in from the right going forward and from the left going back, over 380ms.
848
+ - A HUD at the bottom with a gradient progress bar and a current-slide counter.
849
+ - Three pixel pets, picked at random from [vscode-pets](https://github.com/tonybaloney/vscode-pets) and scattered along the bottom edge, at least 100 pixels apart.
850
+ - A keyboard hint that appears on load and fades after four seconds.
851
+ - A footer tool strip for the laser pointer, pen, blank canvas, blackout, and the controls overlay.
852
+
853
+ ## PDF export
854
+
855
+ From the editor, press `Cmd Shift S` or pick PDF from the `export` menu, and a finished `.pdf` downloads. From a presented deck, print it with `Ctrl+P` or `Cmd+P`. Both produce the same pages.
856
+
857
+ - `@page` sets the page box to 13.333in by 7.5in with no margins, so every slide is one full-bleed 16:9 page. There is no orientation to choose.
858
+ - `print-color-adjust: exact` keeps the theme, its backdrop, the code block surfaces, the table fills, and background images, whether or not "Background graphics" is ticked in the dialog.
859
+ - Slides are sized in absolute units for print. Viewport units resolve against the page box in paged media, which is why a deck laid out in `vw` and `vh` came out as clipped portrait pages.
860
+ - The HUD, arrows, overview, cursor, pets, keyboard hint, fullscreen prompt, annotation canvas, laser pointer, blackout, and controls overlay are all hidden.
861
+ - Speaker notes are stripped at parse time, and the notes payload is left out of PDF and standalone builds, so they never reach the PDF or an exported file.
862
+ - Incremental fragments are fully revealed, so printed and exported slides never omit content.
863
+
864
+ Loading any presented deck with `&print=1` on its URL opens the print dialog once fonts and highlighting have settled. That is the editor's fallback when there is no browser to drive.
865
+
866
+ ## Local asset server
867
+
868
+ `deckrun` serves the editor at `/` and everything else relative to the directory holding the opened file. Launched without a file, it serves the directory you launched in. Local images, diagrams, videos, and fonts load over `http://` instead of `file://`, which avoids CORS restrictions on local assets.
869
+
870
+ - Served types include HTML, CSS, JS, JSON, PNG, JPEG, GIF, SVG, WebP, AVIF, ICO, MP4, WebM, WOFF, WOFF2, and TTF. Anything else is sent as `application/octet-stream`.
871
+ - Requests that resolve outside the Markdown file's directory return `403`. Missing files return `404`.
872
+ - If the requested port is taken, the server falls back to a random free port and prints the URL it settled on.
873
+
874
+ KaTeX and Mermaid are served from the copies installed with deckrun, which
875
+ keeps live preview and PDF rendering independent of the network. Google Fonts,
876
+ Highlight.js, and the pet sprites still load from CDNs, so a first run needs
877
+ network access for those visual extras.
878
+
879
+ ## Generating decks and docs with agents
880
+
881
+ ### Markdown decks
882
+
883
+ Any tool that writes Markdown can write a `deckrun` deck, because the format is
884
+ plain Markdown with a small set of conventions: `---` separators between slides,
885
+ `## Title` at the top of each one, language-tagged code fences, and ASCII
886
+ diagrams where a picture beats a paragraph. An LLM given those four rules and a
887
+ source article produces a deck that presents without hand-editing.
888
+
889
+ ```text
890
+ turn <article url> into a deckrun deck: --- between slides, ## Title per slide,
891
+ language-tagged code fences, speaker notes under a > blockquote
892
+ ```
893
+
894
+ Then present the file it writes:
895
+
896
+ ```bash
897
+ deckrun wal-slides.md
898
+ ```
899
+
900
+ Or open the editor and drop the file onto it, which is the faster loop when you
901
+ still want to cut a few slides:
902
+
903
+ ```bash
904
+ deckrun
905
+ ```
906
+
907
+ Formulas emitted as dollar-delimited LaTeX render with KaTeX. Mermaid fences
908
+ from generated Markdown render as diagrams as well.
909
+
910
+ ### HTML documents
911
+
912
+ For a long-form read rather than a slide deck, hand `deckrun` a self-contained
913
+ HTML file instead. Presenter mode scrolls it continuously, so a generated
914
+ article — one page, inline styles, animated diagrams — works as-is:
915
+
916
+ ```bash
917
+ deckrun wal.html
918
+ ```
919
+
920
+ Everything the page needs must be inline or CDN-loaded; `deckrun` serves the
921
+ file as a single document and does not bundle sibling assets for it.
922
+
923
+ ## Complete deck template
924
+
925
+ A deck exercising split layouts, background images, opacity, syntax highlighting, tables, and speaker notes:
926
+
927
+ ````markdown
928
+ # Scaling Distributed Systems
929
+
930
+ Building resilient, event-driven architectures in production.
931
+
932
+ ![Cover Background](assets/cover.png "bg opacity:0.25")
933
+
934
+ <!-- notes: Introduce the talk and set context on modern distributed scale. -->
935
+
936
+ ---
937
+
938
+ ## Architectural Overview
939
+
940
+ - Microservices communicate over gRPC for low-latency RPCs
941
+ - Events stream through Apache Kafka for durable message logs
942
+ - Read replicas scale consumer queries horizontally
943
+
944
+ ![Architecture Diagram](assets/architecture.png "right opacity:0.95")
945
+
946
+ <!-- notes: Walk through the request path from gateway to storage engine. -->
947
+
948
+ ---
949
+
950
+ ## Consumer Worker Implementation
951
+
952
+ ```go
953
+ func (w *Worker) ProcessEvent(ctx context.Context, msg *kafka.Message) error {
954
+ ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
955
+ defer cancel()
956
+
957
+ if err := w.store.Save(ctx, msg.Value); err != nil {
958
+ return fmt.Errorf("failed to persist event: %w", err)
959
+ }
960
+ return nil
961
+ }
962
+ ```
963
+
964
+ <!-- notes: Emphasize context timeout handling on message persistence. -->
965
+
966
+ ---
967
+
968
+ ## Performance Benchmark
969
+
970
+ | Configuration | Throughput (req/s) | p99 Latency (ms) |
971
+ | -------------- | ------------------ | ---------------- |
972
+ | Single node | 12,400 | 18.2 |
973
+ | 3-node cluster | 35,100 | 6.4 |
974
+ | 5-node cluster | 58,900 | 4.1 |
975
+
976
+ ---
977
+
978
+ # Summary
979
+
980
+ - Favor asynchronous message passing for decoupled services
981
+ - Apply database timeouts at the connection and role layer
982
+ - Use structured event logs for auditing state mutations
983
+ ````
984
+
985
+ ## Examples
986
+
987
+ Two decks ship in `examples/`:
988
+
989
+ - `examples/example-1.md` is a feature walkthrough covering syntax, split layouts, opacity, and shortcuts
990
+ - `examples/example-2.md` is a full technical talk on databases and agentic AI
991
+
992
+ ```bash
993
+ # Open the feature showcase deck in the editor
994
+ deckrun examples/example-1.md
995
+
996
+ # Or open the blank editor and drag either file onto it to import
997
+ deckrun
998
+
999
+ # Open the technical talk in light theme on port 3000
1000
+ deckrun examples/example-2.md -p 3000 --theme solarized
1001
+
1002
+ # Present fullscreen on the first key or click, without opening a browser
1003
+ deckrun examples/example-1.md --fullscreen --no-open
1004
+ ```
1005
+
1006
+ ## Not supported yet
1007
+
1008
+ Worth knowing before you plan a talk around them:
1009
+
1010
+ - No live reload in file mode. Editing the file needs a restart of the CLI. The editor previews as you type, so use it for the writing loop.
1011
+ - Templates and transitions currently apply to the whole deck rather than one slide at a time.
1012
+ - No `file://` mode. The deck always runs through the local HTTP server.
1013
+ - The editor does not upload or embed images. It stores Markdown, and images load by path from the folder you launched in.
1014
+ - The library lives in one browser and one origin. It does not sync between browsers or machines, so download anything you cannot afford to lose.
1015
+
1016
+ ## Development
1017
+
1018
+ ```bash
1019
+ # Install dependencies
1020
+ npm install
1021
+
1022
+ # Compile TypeScript to dist/
1023
+ npm run build
1024
+
1025
+ # Run straight from source
1026
+ npm run dev -- examples/example-1.md
1027
+
1028
+ # Run the editor from source
1029
+ npm run dev
1030
+ ```
1031
+
1032
+ Commits are gated by `.githooks/pre-commit`, which `npm install` wires up by
1033
+ pointing `core.hooksPath` at `.githooks`. It runs `npm test` when the commit touches
1034
+ `src/`, `test/`, `package.json` or `tsconfig.json`, and `npm audit --audit-level=low`
1035
+ on **every** commit. The audit is unscoped on purpose: an advisory is a property
1036
+ of the tree this project ships, not of the diff in front of you, and the ones
1037
+ that matter almost always land against a lockfile nobody touched that week.
1038
+ Whatever it reports gets fixed then and there, regardless of which commit
1039
+ introduced it. Each gate reports on its own; skip one with `DECKRUN_SKIP_TESTS=1`
1040
+ or `DECKRUN_SKIP_AUDIT=1` — the latter is for an advisory with no published fix,
1041
+ or for working offline — or both with `DECKRUN_SKIP_HOOKS=1`.
1042
+
1043
+ `package.json` carries an `allowScripts` allowlist. Since npm v12, dependency
1044
+ install scripts do not run unless they are listed there; `esbuild` (via `tsx`)
1045
+ needs its `postinstall` to fetch a platform binary, so `npm run dev` breaks
1046
+ without the entry. Add to it with `npm approve-scripts <pkg> --allow-scripts-pin`
1047
+ after reviewing what the script does.
1048
+
1049
+ `dev` runs the TypeScript through [tsx](https://tsx.is). The `"module": "NodeNext"` setting means the source imports carry `.js` extensions, and neither `ts-node --esm` nor Node's own type stripping remaps those back to the `.ts` files — tsx does. Node 26 is the floor, and the version CI runs.
1050
+
1051
+ The source:
1052
+
1053
+ - `src/index.ts` is the CLI, the HTTP server, the editor routes, and port selection
1054
+ - `src/parser.ts` splits slides, extracts notes, and resolves image directives
1055
+ - `src/themes.ts` is the theme registry: palettes, font catalog, backdrop patterns, and the CSS they all emit
1056
+ - `src/presentation-options.ts` is the composition-template and transition registry
1057
+ - `src/fragments.ts` contains incremental-reveal styles and DOM preparation shared by preview and presentation
1058
+ - `src/lint.ts` implements the static deck authoring rules behind `deckrun lint`
1059
+ - `src/rich-content.ts` detects and renders KaTeX and Mermaid content, with a shared readiness signal
1060
+ - `src/generate.ts` holds the slide CSS, the presenter chrome, and the deck runtime
1061
+ - `src/preview.ts` is the editor's preview iframe, sharing the slide CSS with the deck
1062
+ - `src/editor.ts` is the editor page: highlighting, palette, guide, nudges, autosave
1063
+ - `src/editor-content.ts` is the snippet registry, tips, and welcome deck that the guide, the palette, and the nudges all read from
1064
+
1065
+ The deck and the editor preview share `RESET_CSS`, `SLIDE_CSS`, and `DECOR_CSS` out of `generate.ts`, and their palettes out of `themes.ts`, which is what keeps the preview honest. Change a slide style once and both move together.
1066
+
1067
+ Release steps live in [PUBLISHING.md](PUBLISHING.md).
1068
+
1069
+ ## License
1070
+
1071
+ MIT. See [LICENSE](LICENSE). Portions of this project derive from an upstream
1072
+ MIT-licensed project whose notice is reproduced in
1073
+ [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md).