deckrun 1.3.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,892 @@
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, four type sizes, and customizable heading and body fonts
8
+ - **Presenter tools** - laser pointer, drawing pen, blank canvas, blackout mode, and `?` for shortcuts
9
+ - **Export** - Markdown, HTML, headless-rendered PDF, or a standalone presenter-ready HTML page
10
+ - **Local-first** - binds only to `127.0.0.1`; nothing is uploaded, and your work stays in browser local storage until export
11
+
12
+ ![Slide showing a code-heavy presentation with syntax highlighting](https://github.com/user-attachments/assets/07b0659c-f82c-44b2-8ecd-815dfd081c49)
13
+
14
+ ## Installation
15
+
16
+ Install globally from npm:
17
+
18
+ ```bash
19
+ npm install -g deckrun
20
+ ```
21
+
22
+ Or run it without installing:
23
+
24
+ ```bash
25
+ npx deckrun # open the editor
26
+ npx deckrun slides.md # present a file
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ```bash
32
+ # Write a new deck in the built-in editor
33
+ deckrun
34
+
35
+ # Serve on the default port 7890 and open the browser
36
+ deckrun slides.md
37
+
38
+ # Present a self-contained HTML page instead of a Markdown deck
39
+ deckrun page.html
40
+
41
+ # Serve on a custom port
42
+ deckrun slides.md -p 3000
43
+
44
+ # Start the server without opening a browser tab
45
+ deckrun slides.md --no-open
46
+
47
+ # Show a launch overlay that enters fullscreen on the first key or click
48
+ deckrun slides.md --fullscreen
49
+
50
+ # Pick one of the fourteen themes, at one of four type sizes
51
+ deckrun slides.md --theme paper
52
+ deckrun slides.md --theme paper --size xl
53
+
54
+ # Or set the two faces yourself
55
+ deckrun slides.md --theme tokyo --head-font playfair --body-font lora
56
+
57
+ deckrun --list-themes
58
+ deckrun --list-sizes
59
+ deckrun --list-fonts
60
+ ```
61
+
62
+ On start, the CLI prints the slide count and the local URL:
63
+
64
+ ```text
65
+ 8 slides from slides.md
66
+ present → http://127.0.0.1:7890 (Ctrl+C to stop)
67
+ ```
68
+
69
+ With no file, it starts the editor instead:
70
+
71
+ ```text
72
+ editor → http://127.0.0.1:7890 (Ctrl+C to stop)
73
+ write on the left, live deck on the right. autosaves to your browser.
74
+ Cmd/Ctrl+K inserts anything · Cmd/Ctrl+Shift+L switches theme · Cmd/Ctrl+Enter presents
75
+ ```
76
+
77
+ The server binds to `127.0.0.1` only, so the deck is never exposed on the network. Stop it with `Ctrl+C`.
78
+
79
+ ### CLI options
80
+
81
+ | Option | Default | Description |
82
+ | --------------------- | ------- | ------------------------------------------------------ |
83
+ | `[file]` | | Markdown or HTML file to present. Omit it to open the editor. |
84
+ | `-p, --port <number>` | `7890` | Port to serve the presentation on |
85
+ | `--no-open` | `false` | Start the HTTP server without opening the browser |
86
+ | `--fullscreen` | `false` | Prompt to enter fullscreen on the first key or click |
87
+ | `--theme <name>` | `midnight` | Any of the fourteen themes, by id |
88
+ | `--size <name>` | `m` | Type size: `s`, `m`, `l`, or `xl` |
89
+ | `--head-font <name>` | | Override the theme's heading and title face |
90
+ | `--body-font <name>` | | Override the theme's body face |
91
+ | `--list-themes` | | Print every theme with its mood and blurb, then exit |
92
+ | `--list-sizes` | | Print every type size with what it is for, then exit |
93
+ | `--list-fonts` | | Print every face and its kind, then exit |
94
+ | `-v, --version` | | Print the version number |
95
+ | `-h, --help` | | Print help for the command |
96
+
97
+ An unknown `--theme`, `--size`, 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` and `--size` set the starting look and `Cmd Shift L` opens the picker to change either one live.
98
+
99
+ `--size`, `--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.
100
+
101
+ ## The editor
102
+
103
+ 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.
104
+
105
+ ```bash
106
+ deckrun
107
+ deckrun --theme paper --size l # start the editor in a given look
108
+ deckrun --head-font syne # and a face of your own
109
+ deckrun -p 3000 # editor on another port
110
+ ```
111
+
112
+ 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.
113
+
114
+ ### The two bars
115
+
116
+ The top bar is for decisions: the deck name, the library, and then everything
117
+ that changes what the deck looks like — `theme`, `font`, and the `S M L XL`
118
+ type size — with `guide`, `insert`, `new`, `export`, and `present` beside
119
+ them.
120
+
121
+ The bottom bar is for counts and status: caret position, word count, slide
122
+ count, a tip line, and any message the editor has for you. Nothing that only
123
+ tells you where you are belongs in a bar you reach for to act.
124
+
125
+ The top bar wraps rather than scrolls when a window is narrow, since a
126
+ scrollable bar would clip the menus that hang out of it. Before it wraps it
127
+ sheds the keyboard hints, then `new` and `guide`, both of which the command
128
+ palette also carries.
129
+
130
+ `new` reopens the same start screen shown on a first run — pick Markdown or
131
+ HTML from there to bring another deck or doc into the library without losing
132
+ the one you have open.
133
+
134
+ ### Writing
135
+
136
+ - The caret drives the preview. Move it into a slide and the preview follows.
137
+ - The gutter labels each `---` with the slide it starts, so you can see the deck's shape while you type.
138
+ - 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.
139
+ - Enter continues the list you are in, and continues numbering. Enter on an empty item ends the list.
140
+ - Tab inserts two spaces.
141
+ - `Alt Up` and `Alt Down` hop the caret between slides.
142
+
143
+ ### Discovering what a slide can hold
144
+
145
+ Three surfaces exist so you never have to remember the syntax:
146
+
147
+ - 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.
148
+ - Command palette (`Cmd K`): the same catalogue, searchable, plus the actions. Type "split", "embed", or "notes" and hit enter.
149
+ - 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.
150
+
151
+ A tip line in the status bar cycles through the rest.
152
+
153
+ ### Images
154
+
155
+ 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.
156
+
157
+ 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.
158
+
159
+ ### HTML documents
160
+
161
+ 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`.
162
+
163
+ 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.
164
+
165
+ 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.
166
+
167
+ 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 it from the file's own directory, exactly like a Markdown deck's images, so `<img src="diagram.png">` next to `page.html` resolves normally.
168
+
169
+ 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.
170
+
171
+ ### The deck library
172
+
173
+ 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.
174
+
175
+ - 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.
176
+ - Decks are listed most recently edited first, so the one you want is usually at the top.
177
+ - The open deck is saved before another one loads, so switching never costs you an edit.
178
+ - Duplicate copies an entry into the library and opens the copy. Delete asks first and is permanent.
179
+ - `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.
180
+ - 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.
181
+ - Rename with the name field in the top bar. That name is also the export filename.
182
+
183
+ 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.
184
+
185
+ ### Saving
186
+
187
+ 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.
188
+
189
+ - Nothing is uploaded. The server is on `127.0.0.1` and only ever sees Markdown you are actively previewing.
190
+ - 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.
191
+ - 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.
192
+ - A browser that blocks storage outright, like a private window, is detected at startup and says so instead of pretending to save.
193
+ - Export writes a copy out of the browser. Source, PDF, and a presenter-ready page are all in the `export` menu.
194
+
195
+ ### Exporting
196
+
197
+ 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.
198
+
199
+ | Format | Shortcut | Result |
200
+ | --------------------- | ------------- | ------------------------------------------------------------------- |
201
+ | Markdown / Source | `Cmd S` | The plain text you see in the editor — `.md` for a deck, `.html` for a doc |
202
+ | 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 |
203
+ | HTML / Presenter Page | | One standalone `.html` page: the deck, or the doc wrapped with the presenter tool belt |
204
+
205
+ 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.
206
+
207
+ 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.
208
+
209
+ 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.
210
+
211
+ 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:
212
+
213
+ - Fonts and syntax highlighting load from a CDN, so a viewer needs a connection to see them exactly as you do. The theme's colors and its backdrop are inline, so those hold up offline.
214
+ - Images and videos referenced by path stay on your disk. Ship them alongside, or host the page where those paths resolve.
215
+
216
+ 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.
217
+
218
+ ### Preview controls
219
+
220
+ - Single mode scales one slide to fit the pane, with speaker notes underneath when the slide has them.
221
+ - Grid mode (`Cmd G`) lays out the whole deck. Click any slide to jump the caret to it.
222
+ - Drag the divider to resize the panes. Double-click it to snap back to an even split. The position is remembered.
223
+
224
+ ### Editor shortcuts
225
+
226
+ | Keys | Action |
227
+ | ------------------- | ------------------------- |
228
+ | `Cmd K` | Command palette |
229
+ | `Cmd O` | Deck library |
230
+ | `Cmd /` | Guide drawer |
231
+ | `Cmd Enter` | Present in a new tab |
232
+ | `Cmd S` | Export Markdown |
233
+ | `Cmd Shift S` | Export PDF |
234
+ | `Cmd D` | New slide |
235
+ | `Cmd B` | Bold |
236
+ | `Cmd I` | Italic |
237
+ | `Cmd E` | Inline code |
238
+ | `Cmd G` | Toggle grid preview |
239
+ | `Cmd Shift L` | Theme and type size picker |
240
+ | `Alt Up`, `Alt Down`| Previous and next slide |
241
+ | `Esc` | Close a menu, the palette, or the guide |
242
+
243
+ `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.
244
+
245
+ On Windows and Linux, `Ctrl` replaces `Cmd`.
246
+
247
+ ### Editor routes
248
+
249
+ The editor adds a few endpoints under `/__`, all local:
250
+
251
+ | Route | Purpose |
252
+ | ------------------ | ------------------------------------------------------------------ |
253
+ | `/__preview` | The iframe that renders slides with the deck stylesheet |
254
+ | `/__parse` | POST Markdown, get back rendered slides and notes |
255
+ | `/__present` | POST Markdown, get back the path to a freshly built deck |
256
+ | `/__pdf` | POST Markdown, get back a rendered PDF |
257
+ | `/__present-doc` | POST an HTML doc, get back the path to its presenter-wrapped page |
258
+ | `/__pdf-doc` | POST an HTML doc, get back a rendered PDF of the doc itself |
259
+ | `/__fetch-doc` | POST a public URL, get back that page's HTML, fetched server-side |
260
+ | `/?deck=<n>` | A built deck or stashed doc, kept in memory. The last eight are retained |
261
+
262
+ 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.
263
+
264
+ 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.
265
+
266
+ ## Slide authoring
267
+
268
+ ### Slide separators
269
+
270
+ Separate slides with `---` on a line of its own. Leading and trailing spaces or tabs on that line are allowed:
271
+
272
+ ```markdown
273
+ # First slide
274
+
275
+ Introduction text goes here.
276
+
277
+ ---
278
+
279
+ ## Second slide
280
+
281
+ Content for the next slide.
282
+
283
+ ---
284
+
285
+ # Conclusion
286
+ ```
287
+
288
+ 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.
289
+
290
+ Two more things `---` does that catch people out:
291
+
292
+ - 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.
293
+ - It is the only slide break. For a horizontal rule inside a slide, write `***` or `___`.
294
+
295
+ ### Page title
296
+
297
+ 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.
298
+
299
+ ### Markdown support
300
+
301
+ 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.
302
+
303
+ Each element is styled for projection rather than reading:
304
+
305
+ | Element | Treatment |
306
+ | --------------- | ------------------------------------------------------------------ |
307
+ | `h1` | Largest, mauve, intended for section and title slides |
308
+ | `h2` | Blue, the default slide title |
309
+ | `h3` | Sky blue subheading |
310
+ | `h4` | Teal, smallest heading |
311
+ | Bold | Peach, for the single term that must land |
312
+ | Italic | Muted subtext, for asides |
313
+ | Inline code | Green on a bordered surface chip |
314
+ | List markers | Mauve bullets and numbers |
315
+ | Tables | Lavender headers, mauve underline, zebra-striped rows |
316
+ | Blockquotes | Mauve left rule on a tinted background |
317
+ | Links | Blue with an offset underline |
318
+
319
+ 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.
320
+
321
+ An example that exercises most of the above:
322
+
323
+ ```markdown
324
+ ## System Architecture
325
+
326
+ > Any fool can write code that a computer can understand. Good programmers write code that humans can understand. - Martin Fowler
327
+
328
+ - Ingestion pipeline with backpressure controls
329
+ - Memory-mapped buffer storage
330
+ - Zero-copy ring buffer
331
+ - Page-aligned disk persistence
332
+
333
+ | Service | Port | Protocol |
334
+ | ----------- | ---- | -------- |
335
+ | api-gateway | 8080 | HTTP/2 |
336
+ | worker-pool | 9090 | gRPC |
337
+ ```
338
+
339
+ ### Code blocks
340
+
341
+ Fenced code blocks are highlighted by Highlight.js in the browser. Tag the language so the grammar is picked correctly:
342
+
343
+ ````markdown
344
+ ```typescript
345
+ interface Slide {
346
+ html: string;
347
+ bgImage?: PositionedImage;
348
+ rightImage?: PositionedImage;
349
+ leftImage?: PositionedImage;
350
+ notes?: string;
351
+ }
352
+
353
+ function parseSlides(markdown: string): Slide[] {
354
+ return markdown
355
+ .split(/\n---\n/)
356
+ .filter(Boolean)
357
+ .map(raw => processSlide(raw));
358
+ }
359
+ ```
360
+ ````
361
+
362
+ Code blocks scroll horizontally when a line is too long, so long lines never reflow mid-presentation.
363
+
364
+ ### Embeds and inline HTML
365
+
366
+ Raw HTML passes through untouched, so anything the browser can render can live on a slide.
367
+
368
+ ```markdown
369
+ <iframe src="https://www.youtube.com/embed/VIDEO_ID" allowfullscreen></iframe>
370
+
371
+ <video src="demo.mp4" controls muted loop></video>
372
+
373
+ Press <kbd>Cmd</kbd> <kbd>K</kbd> to open the palette, and latency drops to <mark>4.1ms</mark>.
374
+ ```
375
+
376
+ | Element | Treatment |
377
+ | ---------- | ---------------------------------------------------------------------------- |
378
+ | `iframe` | Forced to full width at a 16:9 ratio, bordered and rounded |
379
+ | `video` | Centered, capped at 60% of the slide height, aspect ratio preserved |
380
+ | `kbd` | Rendered as a physical key cap in lavender |
381
+ | `mark` | Yellow underline on a tinted background |
382
+
383
+ 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.
384
+
385
+ ### Speaker notes
386
+
387
+ Attach notes to a slide with an HTML comment carrying a `note:` or `notes:` directive:
388
+
389
+ ```markdown
390
+ ## Deployment Strategy
391
+
392
+ Rolling deployment with zero downtime.
393
+
394
+ <!-- notes: Review database migration rollout steps before advancing. -->
395
+ ```
396
+
397
+ Every such comment is stripped from the slide, so notes never leak into the projected output or the PDF export. There is no presenter window yet, so the notes are not displayed anywhere either. See [Not supported yet](#not-supported-yet).
398
+
399
+ ### Image layout directives
400
+
401
+ Image placement is controlled by the Markdown title attribute, the quoted string after the URL:
402
+
403
+ ```markdown
404
+ ![Architecture Diagram](diagram.png "right")
405
+ ![Benchmark Graph](benchmark.png "left")
406
+ ![Background Graphic](backdrop.png "bg")
407
+ ![Telemetry Dashboard](dashboard.png "right opacity:0.8")
408
+ ![Inline Figure](figure.png)
409
+ ```
410
+
411
+ | Directive | Description |
412
+ | ----------- | ------------------------------------------------------------------------------ |
413
+ | `right` | Split layout: content on the left, image fills the right panel |
414
+ | `left` | Split layout: image fills the left panel, content on the right |
415
+ | `bg` | Background layout: image covers the slide canvas beneath the text |
416
+ | `opacity:N` | Opacity between `0.0` and `1.0`, combinable with `left`, `right`, or `bg` |
417
+
418
+ Notes on how directives are parsed:
419
+
420
+ - 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.
421
+ - Precedence is `right`, then `left`, then `bg`, when more than one appears.
422
+ - `opacity` accepts `opacity:0.8`, `opacity=0.8`, or `opacity 0.8`, and clamps to the `0.0` to `1.0` range.
423
+ - One positioned image of each kind applies per slide. If two `right` images appear, the last one wins.
424
+ - A positioned image is lifted out of the text flow, so its position in the Markdown source does not matter.
425
+
426
+ 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.
427
+
428
+ ## Themes
429
+
430
+ Fourteen themes, eight dark and six light. A theme is not just a palette: each
431
+ one brings its own display, body, and monospace faces, its own Highlight.js
432
+ grammar colors, and its own animated geometry behind the slides.
433
+
434
+ | id | mood | what it is |
435
+ | ------------ | ----- | ------------------------------------------------------------------- |
436
+ | `midnight` | dark | Catppuccin Mocha. Violet on deep indigo, drifting orbs |
437
+ | `tokyo` | dark | Tokyo Night. Neon cyan over a wireframe grid |
438
+ | `nord` | dark | Arctic frost blue on polar slate, with slow contour waves |
439
+ | `dracula` | dark | Purple and hot pink over charcoal, lit by a gradient mesh |
440
+ | `gruvbox` | dark | Warm amber and moss on retro brown, hatched like graph paper |
441
+ | `rosepine` | dark | Rosé Pine. Muted iris and gold on plum, under a slow aurora |
442
+ | `forest` | dark | Everforest sage on deep pine, rippling in concentric rings |
443
+ | `neon` | dark | Electric cyan and magenta on true black, raked by light beams |
444
+ | `daylight` | light | Catppuccin Latte, contrast-tuned for a projector. Dot matrix |
445
+ | `arctic` | light | Nord inverted. Frost blue on cool paper, with contour waves |
446
+ | `solarized` | light | The classic low-glare cream, paired with Lora for long prose |
447
+ | `paper` | light | Crimson serif on warm cream. Editorial, print-first, very legible |
448
+ | `rosequartz` | light | Rosé Pine Dawn. Blush and iris on linen, with soft orbs |
449
+ | `swiss` | light | Black on white, one red. Heavy grotesk, tight tracking, hard grid |
450
+
451
+ ```bash
452
+ deckrun slides.md --theme paper
453
+ deckrun --list-themes
454
+ ```
455
+
456
+ `dark` and `light` are kept as aliases for `midnight` and `daylight`, so older
457
+ commands and scripts keep working. So are `mocha`, `latte`, `tokyo-night`,
458
+ `rose-pine`, and `rose-quartz`.
459
+
460
+ In a file-backed deck the theme is baked into the page at launch, so switching
461
+ means restarting with a different flag. In the editor it is live: `Cmd Shift L`
462
+ opens a picker where the arrow keys preview each theme on the real deck as you
463
+ move, `Enter` keeps the one you land on, and `Esc` puts back the one you had.
464
+ The same picker carries the type size. Both choices are remembered per browser
465
+ and travel into the deck you present and the PDF you export.
466
+
467
+ ### Backdrops
468
+
469
+ Every theme names one of ten backdrop patterns, drawn behind the slides in the
470
+ theme's own accent colors and drifting slowly enough to read as depth rather
471
+ than as motion: `orbs`, `grid`, `dots`, `topo`, `beams`, `rings`, `waves`,
472
+ `mesh`, `aurora`, and `none`.
473
+
474
+ The whole backdrop is CSS custom properties and gradients — no canvas, no
475
+ images, no JavaScript — so it survives into the PDF, where it is re-attached to
476
+ each page (a fixed element does not repeat across a paged medium). Anyone who
477
+ has `prefers-reduced-motion` set gets the geometry without the drift.
478
+
479
+ ### Type size
480
+
481
+ Any theme can be set at four sizes, so the two choices compose instead of
482
+ multiplying into fifty-six presets:
483
+
484
+ | id | name | what it is for |
485
+ | ---- | ------- | --------------------------------------------------------------- |
486
+ | `s` | small | More on a slide. Dense reference decks, close screens. |
487
+ | `m` | medium | The default. Reads from the middle of a normal room. |
488
+ | `l` | large | A wide room, or a talk of a handful of lines a slide. |
489
+ | `xl` | x-large | Readable from the back row. Expect three or four lines a slide. |
490
+
491
+ ```bash
492
+ deckrun slides.md --size xl
493
+ deckrun --list-sizes
494
+ ```
495
+
496
+ The scale is not one multiplier over everything. Headings and prose pull in
497
+ different directions as a deck grows: at `xl` the point is to get the *reading*
498
+ text to the back row, and the headings are already legible, so prose grows
499
+ further than they do — 1.38× against 1.24×. At `s` the reverse. Leading tightens
500
+ as the type grows so lines stay in one block, and the slide gives back some of
501
+ its padding so the extra size has somewhere to go. `m` is exactly 1 across the
502
+ board, so it is the sizing the stylesheet states literally and the other three
503
+ are honest multiples of it.
504
+
505
+ Sizes are set in `SIZE_SPECS` in `src/themes.ts`, in the same shape as the
506
+ themes: a display, body, and code multiplier, a leading factor, and the slide
507
+ padding. List indents, bullet markers, and the blockquote glyph are all in `em`,
508
+ so they track whatever size is on rather than drifting into the text.
509
+
510
+ In the editor the size lives in the same picker as the theme (`Cmd Shift L`),
511
+ where `[` and `]` step it. Unlike the theme, which is previewed and can be
512
+ abandoned with `Esc`, a size click sticks right away. It is remembered per
513
+ browser, applies to every theme, and travels into the deck you present and the
514
+ PDF you export.
515
+
516
+ ### Faces
517
+
518
+ Headings use the theme's display face, body copy its body face, and code, key
519
+ caps, and the presenter chrome its monospace face. Faces come from Google
520
+ Fonts in a single request; the browser only downloads the files a rendered
521
+ slide actually paints.
522
+
523
+ The heading and the body face can each be replaced without leaving the theme,
524
+ and they are chosen **separately** — a serif heading over a sans body, or the
525
+ reverse, is a setting rather than a fork:
526
+
527
+ ```bash
528
+ deckrun slides.md --theme tokyo --head-font playfair --body-font lora
529
+ deckrun slides.md --body-font newsreader # heading stays the theme's
530
+ deckrun --list-fonts
531
+ ```
532
+
533
+ Twenty faces, grouped sans, serif, and mono: `inter`, `spaceGrotesk`, `sora`,
534
+ `manrope`, `figtree`, `outfit`, `archivo`, `syne`, `bricolage`, `workSans`,
535
+ `plexSans`, `fraunces`, `playfair`, `newsreader`, `lora`, `plexMono`,
536
+ `jetbrains`, `firaCode`, `spaceMono`, `sourceCodePro`. Their human names work
537
+ too, so `--head-font "Playfair Display"` is the same as `--head-font playfair`.
538
+
539
+ The monospace face is not overridable on purpose: code wants the face the
540
+ palette's syntax highlighting was chosen against.
541
+
542
+ Overrides are attributes, not a second palette. `[data-head]` and `[data-body]`
543
+ carry the same specificity as `[data-theme]` and are emitted after it, so
544
+ source order decides and the override wins. No attribute means the theme's own
545
+ face — which is why there is no "default" entry to keep in step with anything,
546
+ and why a face the deck does not recognise falls back silently rather than
547
+ leaving a slide unstyled.
548
+
549
+ In the editor the two live in one `font` menu in the top bar, side by side:
550
+ heading on the left, body on the right, every row set in the face it offers.
551
+ Picking does not close the menu, because choosing a heading and then a body is
552
+ one decision. Both are remembered per browser and travel into the deck you
553
+ present and the PDF you export.
554
+
555
+ ### Type and motion
556
+
557
+ Slides also assemble rather than appear: each top-level block on a slide rises
558
+ into place a beat after the one above it. That stagger is turned off in the
559
+ editor preview, where the slide is rebuilt on every keystroke, and in the PDF.
560
+ `prefers-reduced-motion` turns it off everywhere.
561
+
562
+ ### Writing your own theme
563
+
564
+ Themes live in `src/themes.ts`. Append one entry to `SPECS` and it appears
565
+ everywhere at once: the `--theme` flag, `--list-themes`, the editor's picker,
566
+ the live preview, the HTML export, and the PDF. Nothing else needs touching.
567
+
568
+ An entry is four things:
569
+
570
+ - **`neutrals`** — an eleven-step ramp from the page's outermost background
571
+ (`crust`) to its strongest foreground (`text`). Light themes run the same
572
+ direction: `crust` is still the backdrop, `text` is still the ink.
573
+ - **`accents`** — eleven hues, named after the Catppuccin slots so existing
574
+ palettes port across by copy and paste. They are the deck's paint box:
575
+ `<mark>`, list markers, table headers, and the five pen colors all draw from
576
+ here.
577
+ - **`roles`** — which three of those colors lead. `accent` carries h1, the
578
+ caret, focus rings, and every piece of chrome; `accent2` carries h2 and
579
+ links; `accent3` carries h3. Point them at any neutral or accent key — that
580
+ is how `nord` leads with frost blue while `gruvbox` leads with amber.
581
+ - **`type`** — a display face, a body face, a mono face, and the weight,
582
+ tracking, and casing the display face wants. Faces come from the `FONTS`
583
+ catalog at the top of the file; add an entry there to use a new one.
584
+
585
+ Plus `decor` for the backdrop and `hljs` for the code stylesheet.
586
+
587
+ ```ts
588
+ seafoam: {
589
+ label: "seafoam",
590
+ mood: "dark",
591
+ blurb: "Pale green on graphite, with a dot matrix.",
592
+ neutrals: { crust: "#0f1413", mantle: "#141a19", /* … eleven in all */ },
593
+ accents: { teal: "#7fd6c1", blue: "#78b7d0", /* … eleven in all */ },
594
+ roles: { accent: "teal", accent2: "blue", accent3: "green" },
595
+ type: { display: "sora", body: "inter", mono: "jetbrains", weight: 700 },
596
+ decor: "dots",
597
+ hljs: `${HL}atom-one-dark.min.css`,
598
+ },
599
+ ```
600
+
601
+ Everything else is derived from those four inputs — the accent tints, the
602
+ overlay scrims, the shadows (black on dark themes, tinted with the ink color on
603
+ light ones), the hairlines, the h1 rule gradient, and the backdrop's own
604
+ colors. That is deliberate: a new theme cannot fall out of step with itself,
605
+ and there is nothing to hand-tune per surface.
606
+
607
+ The palette is emitted as CSS custom properties, once per theme under a
608
+ `[data-theme]` selector in the editor and preview and once on `:root` in a
609
+ built deck, so overriding a single value in a fork stays a one-line change.
610
+
611
+ ## Navigation and controls
612
+
613
+ ### Keyboard shortcuts
614
+
615
+ | Key | Action |
616
+ | ------------------------------------ | ---------------------------------------- |
617
+ | `Right`, `Down`, `Space`, `PageDown` | Advance to the next slide |
618
+ | `Left`, `Up`, `Backspace`, `PageUp` | Return to the previous slide |
619
+ | `Home` | Jump to the first slide |
620
+ | `End` | Jump to the last slide |
621
+ | `O` | Toggle the overview grid |
622
+ | `F` | Toggle fullscreen |
623
+ | `L` | Toggle the laser pointer |
624
+ | `D` | Toggle the pen and draw on the slide |
625
+ | `C` | Toggle a blank canvas over the slide |
626
+ | `B` | Black out the screen |
627
+ | `?`, `H` | Show every control |
628
+ | `Escape` | Close whatever is open, one layer at a time |
629
+
630
+ While the pen is down, these keys are live as well:
631
+
632
+ | Key | Action |
633
+ | -------------------------- | ------------------------------------- |
634
+ | `1` … `5` | Pick the pen color |
635
+ | `E` | Toggle the eraser |
636
+ | `[`, `]` | Thinner, thicker |
637
+ | `Ctrl+Z`, `Cmd+Z` | Undo the last stroke |
638
+ | `X` | Clear this slide's annotations |
639
+
640
+ They only bind while the pen is down, so the letters stay free for everything else the rest of the time.
641
+
642
+ ### Mouse and touch
643
+
644
+ - Click the arrow buttons on either side of the screen. They dim at the first and last slide.
645
+ - Swipe horizontally on a touchscreen. A swipe longer than 50 pixels advances or goes back.
646
+ - Click any thumbnail in the overview to jump to that slide.
647
+
648
+ ### Overview mode
649
+
650
+ 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.
651
+
652
+ Arrow keys do not move the selection inside the overview. Navigation happens by clicking.
653
+
654
+ ### The footer controls
655
+
656
+ 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.
657
+
658
+ ### Laser pointer
659
+
660
+ 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.
661
+
662
+ ### Drawing on slides
663
+
664
+ Press `D` for a pen and draw straight onto the slide with the mouse, a trackpad, a pen tablet, or a finger.
665
+
666
+ - Annotations are held per slide, so you can mark up slide 3, keep going, and come back to find it as you left it.
667
+ - 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.
668
+ - Strokes are stored in fractions of the viewport, not pixels, so resizing the window or entering fullscreen keeps them where you drew them.
669
+ - `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.
670
+ - Nothing is written to disk. Reloading the deck starts clean, and the PDF export never contains annotations.
671
+
672
+ ### Blank canvas
673
+
674
+ 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.
675
+
676
+ ### Blacking out the screen
677
+
678
+ 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.
679
+
680
+ ### Fullscreen
681
+
682
+ 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.
683
+
684
+ ## Visual design
685
+
686
+ - Terminal aesthetics throughout, set in IBM Plex Mono with a blinking mauve cursor in the top right corner.
687
+ - Direction-aware transitions. Slides slide in from the right going forward and from the left going back, over 380ms.
688
+ - A HUD at the bottom with a gradient progress bar and a current-slide counter.
689
+ - 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.
690
+ - A keyboard hint that appears on load and fades after four seconds.
691
+ - A footer tool strip for the laser pointer, pen, blank canvas, blackout, and the controls overlay.
692
+
693
+ ## PDF export
694
+
695
+ 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.
696
+
697
+ - `@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.
698
+ - `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.
699
+ - 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.
700
+ - The HUD, arrows, overview, cursor, pets, keyboard hint, fullscreen prompt, annotation canvas, laser pointer, blackout, and controls overlay are all hidden.
701
+ - Speaker notes are stripped at parse time, so they never reach the PDF.
702
+
703
+ Loading any deck with `?print=1` on the URL opens the print dialog once fonts and highlighting have settled. That is the editor's fallback when there is no browser to drive, and it works on a file-backed deck too:
704
+
705
+ ```bash
706
+ deckrun slides.md --no-open
707
+ # then open http://127.0.0.1:7890/?print=1
708
+ ```
709
+
710
+ ## Local asset server
711
+
712
+ `deckrun` serves the generated HTML at `/` and everything else relative to the directory holding the Markdown file. In editor mode there is no Markdown file, so 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.
713
+
714
+ - 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`.
715
+ - Requests that resolve outside the Markdown file's directory return `403`. Missing files return `404`.
716
+ - If the requested port is taken, the server falls back to a random free port and prints the URL it settled on.
717
+
718
+ Google Fonts, Highlight.js, and the pet sprites load from CDNs, so a first run needs network access. Once the browser has cached them, the deck renders offline apart from the pets.
719
+
720
+ ## Generating decks and docs with Claude Code
721
+
722
+ ### Markdown decks
723
+
724
+ Any tool that writes Markdown can write a `deckrun` deck. If you use Claude Code, the `blog-to-slides` skill turns a blog post, article, or long-form note into a deck in exactly this format: `---` separators, `## Title` per slide, language-tagged code blocks, and ASCII diagrams where a picture beats a paragraph.
725
+
726
+ ```text
727
+ turn https://arpitbhayani.me/blogs/wal into slides
728
+ ```
729
+
730
+ Then present the file it writes:
731
+
732
+ ```bash
733
+ deckrun wal-slides.md
734
+ ```
735
+
736
+ Or open the editor and drop the file onto it, which is the faster loop when you still want to cut a few slides:
737
+
738
+ ```bash
739
+ deckrun
740
+ ```
741
+
742
+ The skill is a personal Claude Code skill and is not bundled with this package. Add it under `~/.claude/skills/blog-to-slides/SKILL.md` to make it available across projects.
743
+
744
+ One caveat: the skill emits LaTeX for formulas, which `deckrun` does not render. Rewrite formulas as inline code or a code block, or drop them.
745
+
746
+ ### HTML documents
747
+
748
+ For a self-contained HTML doc instead of a Markdown deck, use the [`ape-present`](https://github.com/arpitbbhayani/ape-skills) skill. It turns a blog post into a single presentation-worthy HTML page — a readable long-form document with animated diagrams and just enough text to carry the idea — which is exactly the kind of doc `deckrun`'s presenter mode is built for.
749
+
750
+ ```text
751
+ ape present https://arpitbhayani.me/blogs/wal
752
+ ```
753
+
754
+ Then present the page it writes:
755
+
756
+ ```bash
757
+ deckrun wal.html
758
+ ```
759
+
760
+ Like `blog-to-slides`, this is a personal Claude Code skill from the same [ape-skills](https://github.com/arpitbbhayani/ape-skills) collection and is not bundled with this package.
761
+
762
+ ## Complete deck template
763
+
764
+ A deck exercising split layouts, background images, opacity, syntax highlighting, tables, and speaker notes:
765
+
766
+ ````markdown
767
+ # Scaling Distributed Systems
768
+
769
+ Building resilient, event-driven architectures in production.
770
+
771
+ ![Cover Background](assets/cover.png "bg opacity:0.25")
772
+
773
+ <!-- notes: Introduce the talk and set context on modern distributed scale. -->
774
+
775
+ ---
776
+
777
+ ## Architectural Overview
778
+
779
+ - Microservices communicate over gRPC for low-latency RPCs
780
+ - Events stream through Apache Kafka for durable message logs
781
+ - Read replicas scale consumer queries horizontally
782
+
783
+ ![Architecture Diagram](assets/architecture.png "right opacity:0.95")
784
+
785
+ <!-- notes: Walk through the request path from gateway to storage engine. -->
786
+
787
+ ---
788
+
789
+ ## Consumer Worker Implementation
790
+
791
+ ```go
792
+ func (w *Worker) ProcessEvent(ctx context.Context, msg *kafka.Message) error {
793
+ ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
794
+ defer cancel()
795
+
796
+ if err := w.store.Save(ctx, msg.Value); err != nil {
797
+ return fmt.Errorf("failed to persist event: %w", err)
798
+ }
799
+ return nil
800
+ }
801
+ ```
802
+
803
+ <!-- notes: Emphasize context timeout handling on message persistence. -->
804
+
805
+ ---
806
+
807
+ ## Performance Benchmark
808
+
809
+ | Configuration | Throughput (req/s) | p99 Latency (ms) |
810
+ | -------------- | ------------------ | ---------------- |
811
+ | Single node | 12,400 | 18.2 |
812
+ | 3-node cluster | 35,100 | 6.4 |
813
+ | 5-node cluster | 58,900 | 4.1 |
814
+
815
+ ---
816
+
817
+ # Summary
818
+
819
+ - Favor asynchronous message passing for decoupled services
820
+ - Apply database timeouts at the connection and role layer
821
+ - Use structured event logs for auditing state mutations
822
+ ````
823
+
824
+ ## Examples
825
+
826
+ Two decks ship in `examples/`:
827
+
828
+ - `examples/example-1.md` is a feature walkthrough covering syntax, split layouts, opacity, and shortcuts
829
+ - `examples/example-2.md` is a full technical talk on databases and agentic AI
830
+
831
+ ```bash
832
+ # Run the feature showcase deck
833
+ deckrun examples/example-1.md
834
+
835
+ # Or open the editor and drag either file onto it to edit
836
+ deckrun
837
+
838
+ # Run the technical talk in light theme on port 3000
839
+ deckrun examples/example-2.md -p 3000 --theme solarized --size l
840
+
841
+ # Run fullscreen without opening a browser
842
+ deckrun examples/example-1.md --fullscreen --no-open
843
+ ```
844
+
845
+ ## Not supported yet
846
+
847
+ Worth knowing before you plan a talk around them:
848
+
849
+ - No presenter view while presenting. The editor shows the notes for the slide you are on, but the presented deck has no second window, no next-slide peek, and no timer.
850
+ - 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.
851
+ - No LaTeX or math rendering, and no Mermaid diagrams. Use fenced code blocks or ASCII diagrams.
852
+ - No incremental reveal of bullets within a slide.
853
+ - No slide-level transition or layout overrides beyond the image directives.
854
+ - No `file://` mode. The deck always runs through the local HTTP server.
855
+ - The editor does not upload or embed images. It stores Markdown, and images load by path from the folder you launched in.
856
+ - 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.
857
+
858
+ ## Development
859
+
860
+ ```bash
861
+ # Install dependencies
862
+ npm install
863
+
864
+ # Compile TypeScript to dist/
865
+ npm run build
866
+
867
+ # Run straight from source
868
+ npm run dev -- examples/example-1.md
869
+
870
+ # Run the editor from source
871
+ npm run dev
872
+ ```
873
+
874
+ `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 on Node 20 and up — tsx does. Tested on Node 18, 20, 22, and 24.
875
+
876
+ The source:
877
+
878
+ - `src/index.ts` is the CLI, the HTTP server, the editor routes, and port selection
879
+ - `src/parser.ts` splits slides, extracts notes, and resolves image directives
880
+ - `src/themes.ts` is the theme registry and the type scale: palettes, font catalog, backdrop patterns, size presets, and the CSS they all emit
881
+ - `src/generate.ts` holds the slide CSS, the presenter chrome, and the deck runtime
882
+ - `src/preview.ts` is the editor's preview iframe, sharing the slide CSS with the deck
883
+ - `src/editor.ts` is the editor page: highlighting, palette, guide, nudges, autosave
884
+ - `src/editor-content.ts` is the snippet registry, tips, and welcome deck that the guide, the palette, and the nudges all read from
885
+
886
+ 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.
887
+
888
+ Release steps live in [PUBLISHING.md](PUBLISHING.md).
889
+
890
+ ## License
891
+
892
+ MIT. See [LICENSE](LICENSE).