decklight 0.1.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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +257 -0
  3. package/SPEC.md +372 -0
  4. package/cli/bundle.mjs +388 -0
  5. package/cli/decklight.mjs +95 -0
  6. package/cli/edit.mjs +142 -0
  7. package/cli/init.mjs +215 -0
  8. package/cli/rec.mjs +538 -0
  9. package/dist/decklight.css +1068 -0
  10. package/dist/decklight.js +239 -0
  11. package/dist/decklight.js.map +7 -0
  12. package/docs/architecture.svg +94 -0
  13. package/docs/demo.svg +124 -0
  14. package/package.json +54 -0
  15. package/themes/README.md +61 -0
  16. package/themes/aliens.css +84 -0
  17. package/themes/apple2.css +126 -0
  18. package/themes/aurora.css +84 -0
  19. package/themes/berry.css +80 -0
  20. package/themes/blade-runner.css +83 -0
  21. package/themes/c64.css +85 -0
  22. package/themes/carbon.css +82 -0
  23. package/themes/citrus.css +80 -0
  24. package/themes/coastal.css +80 -0
  25. package/themes/cosmos.css +86 -0
  26. package/themes/dune.css +80 -0
  27. package/themes/eclipse.css +82 -0
  28. package/themes/ember.css +80 -0
  29. package/themes/fjord.css +80 -0
  30. package/themes/friends.css +82 -0
  31. package/themes/gallery.html +203 -0
  32. package/themes/gameboy.css +118 -0
  33. package/themes/genesis.css +84 -0
  34. package/themes/glacier.css +82 -0
  35. package/themes/godfather.css +82 -0
  36. package/themes/graphite.css +80 -0
  37. package/themes/harvest.css +80 -0
  38. package/themes/ibm-modern.css +83 -0
  39. package/themes/ibm-oldschool.css +84 -0
  40. package/themes/ink.css +82 -0
  41. package/themes/latte.css +80 -0
  42. package/themes/linen.css +80 -0
  43. package/themes/meadow.css +80 -0
  44. package/themes/metropolis.css +85 -0
  45. package/themes/miami-vice.css +84 -0
  46. package/themes/midnight.css +80 -0
  47. package/themes/mint.css +82 -0
  48. package/themes/moss.css +82 -0
  49. package/themes/obsidian.css +82 -0
  50. package/themes/orchid.css +82 -0
  51. package/themes/packs.json +89 -0
  52. package/themes/paper.css +83 -0
  53. package/themes/peony.css +84 -0
  54. package/themes/porcelain.css +82 -0
  55. package/themes/pulp-fiction.css +84 -0
  56. package/themes/reveal-beige.css +87 -0
  57. package/themes/reveal-black.css +83 -0
  58. package/themes/reveal-blood.css +84 -0
  59. package/themes/reveal-dracula.css +83 -0
  60. package/themes/reveal-league.css +88 -0
  61. package/themes/reveal-moon.css +83 -0
  62. package/themes/reveal-night.css +86 -0
  63. package/themes/reveal-serif.css +82 -0
  64. package/themes/reveal-simple.css +84 -0
  65. package/themes/reveal-sky.css +85 -0
  66. package/themes/reveal-solarized.css +83 -0
  67. package/themes/reveal-white.css +82 -0
  68. package/themes/sepia.css +81 -0
  69. package/themes/seriph.css +82 -0
  70. package/themes/severance.css +82 -0
  71. package/themes/slate.css +80 -0
  72. package/themes/snes.css +82 -0
  73. package/themes/star-wars.css +85 -0
  74. package/themes/storm.css +80 -0
  75. package/themes/stranger-things.css +84 -0
  76. package/themes/synthwave.css +90 -0
  77. package/themes/terminator.css +84 -0
  78. package/themes/velvet.css +80 -0
  79. package/tools/gemini-tts.mjs +140 -0
  80. package/tools/publish-site-voices.mjs +71 -0
  81. package/tools/voiceover-server.mjs +0 -0
  82. package/tools/voiceover.mjs +155 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Guillaume Philippart
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,257 @@
1
+ <p align="center">
2
+ <img src="docs/demo.svg" width="760" alt="An animated Decklight slide: the title, bullets and a diagram build in step by step, then the theme cycles from indigo light to dark to warm parchment while a generator toast appears.">
3
+ </p>
4
+
5
+ # Decklight
6
+
7
+ **The presentation library that can prove its deck works.**
8
+
9
+ Decklight is a presentation library in the Reveal.js tradition, designed to be **authored by AI agents and humans alike**. A deck is a single HTML file. The runtime is one JS + one CSS + one theme CSS. There is no build step, no dev server — double-click the file and present. And because agents can't eyeball a slide, every feature is designed to be **verifiable by a headless render**: clipped content marks itself in the DOM, every theme passes machine-checked contrast gates, terminal demos are recorded truth rather than screenshots.
10
+
11
+ `SPEC.md` is the contract; this README is the tour. For the tour that gives itself, open **`demo/showcase.html`** — a deck about Decklight where every slide live-demos the feature it describes, ending in a pop quiz.
12
+
13
+ ## Quick start
14
+
15
+ ```
16
+ npx decklight init "My Deck"
17
+ ```
18
+
19
+ Scaffolds a self-contained `deck.html` (double-click it, no server) plus `.claude/skills/decklight/` — so Claude Code (or anything reading `AGENTS.md`) has the full authoring contract on hand instead of guessing from Reveal.js memory or a web search.
20
+
21
+ Or hand-author the anatomy directly:
22
+
23
+ ```html
24
+ <!doctype html>
25
+ <html lang="en">
26
+ <head>
27
+ <meta charset="utf-8">
28
+ <link rel="stylesheet" href="decklight/dist/decklight.css">
29
+ <link rel="stylesheet" href="decklight/themes/aurora.css">
30
+ </head>
31
+ <body>
32
+ <div class="decklight">
33
+ <section>
34
+ <h2>A plain HTML slide</h2>
35
+ <p>With an auto-detected subtitle</p>
36
+ <ul data-build>
37
+ <li>First point</li>
38
+ <li>Second point — steps in on the next advance</li>
39
+ </ul>
40
+ <aside class="notes">Speaker notes. ⟨CLICK⟩ markers align with builds.</aside>
41
+ </section>
42
+ </div>
43
+ <script src="decklight/dist/decklight.js"></script>
44
+ <script>Decklight.init({ transition: 'fade' });</script>
45
+ </body>
46
+ </html>
47
+ ```
48
+
49
+ Open the file in a browser — `file://` works for everything, no server needed. See `demo/showcase.html` for the self-demonstrating tour, `demo/smoke.html` for the feature smoke deck, and `themes/gallery.html` to browse themes.
50
+
51
+ ## Authoring
52
+
53
+ - **HTML is the default**, markdown is opt-in per slide: `data-markdown` on the section, content in `<script type="text/template">`. CommonMark via bundled `marked`; `Note:` starts speaker notes; `::: build … :::` wraps content in a build container.
54
+ - **Speaker notes**: `<aside class="notes">` (HTML) or `Note:` (markdown). `⟨CLICK⟩` markers segment the notes; the speaker view highlights the segment matching the current build step.
55
+ - **Rehearse notes**: an optional `<aside class="rehearse">` (or `Rehearse:` in markdown) carries a cue-card variant of the notes — a few words per segment, same `⟨CLICK⟩` segmentation — for the speaker view's rehearse mode.
56
+ - **Subtitles**: the `<p>` immediately after a slide's leading `h1`/`h2` is auto-styled as the subtitle — one canonical look whether the slide is HTML- or markdown-authored. Opt out with `data-subtitle="none"`.
57
+ - **Overflow guardrail**: content that would clip gets a `console.warn` and a `data-overflow` attribute on the section — headless verification can assert `[data-overflow]` is absent.
58
+
59
+ ## Builds (Keynote-style progressive reveals)
60
+
61
+ The container opts in; the engine does the rest — one attribute on the container, zero classes on the items.
62
+
63
+ | Syntax | Meaning |
64
+ |---|---|
65
+ | `data-build` on a container (`ul`, `ol`, `table`, `svg`, `g`, `div.columns`) | each direct child becomes one step, in DOM order |
66
+ | `data-build` on a leaf (`p`, `img`, `pre`, `blockquote`) | the element itself is one step |
67
+ | `data-build="fade-up"` | entrance style: `fade` (default) · `fade-up` · `fade-down` · `zoom` · `pop` · `draw` · `highlight` · `none` |
68
+ | `data-build-order="3"` | explicit step index within the slide |
69
+ | `data-build-stay` | child of a build container that stays static |
70
+
71
+ Hidden steps use `visibility: hidden`, so layout never shifts. `#/<slide>/<step>` deep-links any build state. Complex widgets (code stepping, the terminal player) register **build providers** — `Decklight.registerBuildProvider(el, { count, apply(i), label(i) })` — and interleave their steps into the slide's sequence.
72
+
73
+ ## SVG diagrams
74
+
75
+ Inline SVG is the canonical diagram format, and it's first-class:
76
+
77
+ - **Theme-aware**: author with `var(--d-stroke)`, `var(--d-text)`, `var(--d-muted)`, `var(--d-accent)`, `var(--d-fill-1)`…`var(--d-fill-6)` and the diagram recolors across all themes — including generated ones.
78
+ - **Progressive**: `data-build` on the `<svg>` or a `<g>` steps its direct children; `data-build="draw"` animates strokes via dash-offset (authored dasharrays are restored afterwards).
79
+ - **Collision-proof**: the engine namespaces every `id` inside each inline SVG at init, rewriting `url(#…)` and `href="#…"` — the defs-collision bug class is eliminated.
80
+ - **Concept colors**: `data-concept="agent"` pins a recurring concept to one fill slot deck-wide — the Agent box is the same color on every slide, in every theme. Pin slots explicitly with `Decklight.init({ concepts: { agent: 3, kafka: 1 } })` (or any CSS color); untagged names get a stable hash fallback, and slot collisions warn in the console.
81
+
82
+ ## Motion
83
+
84
+ - **Slide transitions**: `none | fade | slide | scale | flip`, deck-level config with per-slide `data-transition` override.
85
+ - **Auto-animate** (Magic Move): `data-auto-animate` on adjacent sections; elements sharing `data-id` FLIP-morph position, size, opacity, color, radius and font-size — HTML and inline-SVG elements alike.
86
+ - **Element animations**: `data-animate="pulse | float | shake | spin | blink | bounce | swing | glow | breathe"` — looping attention effects that only run while the slide is active.
87
+ - All motion collapses under `prefers-reduced-motion`.
88
+
89
+ ## Code
90
+
91
+ - Syntax highlighting via bundled highlight.js (13 languages), themed entirely through the `--hl-*` tokens — no separate highlighter theme files.
92
+ - **Line stepping**: `data-lines="1|3-5|all"` on the `<pre>` steps highlight ranges as builds; non-highlighted lines dim to `--dim-opacity`. `data-lines-numbers` adds line numbers.
93
+
94
+ ## Terminal recordings
95
+
96
+ Event-based, asciinema-style — never video. You script it, `decklight rec` runs it in a real PTY and captures truthful output:
97
+
98
+ ```yaml
99
+ prompt: "$ "
100
+ redact: ["sk-[A-Za-z0-9-_]+"]
101
+ steps:
102
+ - cmd: export STAGE=demo
103
+ hide: true # runs, but omitted from playback
104
+ - cmd: git status
105
+ - cmd: myapp login
106
+ wait_for: "Logged in"
107
+ interact:
108
+ - expect: "Email: "
109
+ send: "demo@example.com\n"
110
+ - expect: "Password: "
111
+ send: { secret: "$APP_PASSWORD\n" } # sent for real; recorded as ▓▓▓
112
+ ```
113
+
114
+ - **Playback**: `<div class="terminal" data-cast="demo.cast.json" data-mode="step">` — each advance *types* the command (jittered keystrokes) then streams the recorded output; `data-mode="play"` gives timeline playback with speed control. Typing speed is authored with `data-type-speed` (a 1-slow to 10-fast scale) and changed live by the `⌨` titlebar button, a words-per-minute picker (persists per deck). Each keystroke lands with a subtle synthesized switch sound: pick your board with `data-type-sound="creamy|thocky|clacky|off"` (default creamy) or the `♪` titlebar button, which cycles the voices live (also persists per deck). ANSI colors render through an owned parser (16 named colors map to theme tokens; 256/truecolor pass through). `data-cast-inline="#id"` embeds the cast for `file://`. Terminals keep a stable 16:9-floored footprint and scroll internally.
115
+ - **Refreshable**: casts embed their own script — `decklight refresh` re-records everything and reports drift when your tools change.
116
+ - **Interop**: `decklight export` flattens to asciicast v2 (`agg` GIFs, asciinema.org); plain asciicast v2 files can be played directly, with `m` markers becoming steps.
117
+ - **Secrets**: redaction regexes, `hide:` steps, and `secret:` sends that are typed for real but stored as `▓▓▓`.
118
+
119
+ ## Theming
120
+
121
+ **61 shipped themes** organized into **packs** — Default, Classics (the twelve Reveal.js classics, Beamer's metropolis, a Slidev-style seriph), Old Machines (apple2, c64, gameboy, snes, genesis, ibm-oldschool), TV Series (miami-vice, friends, severance, stranger-things), and Movies (aliens, blade-runner, star-wars, terminator, godfather, pulp-fiction) — all on a strict token contract — canvas, typography, accent, blocks, code, diagram, and terminal tokens (about 50 in all), so one deck restyles *completely*, diagrams and terminals included. Every theme passes the WCAG AA gates (`test/contrast.mjs`) **and** the same R1–R8 palette rules the generator follows (`test/palette-rules.mjs`); where a rule would break a theme's identity — a brand's official colors, synthwave's neon, a duotone canvas — the theme declares a `rule-exception:` with a reason, and the grader prints it on every run.
122
+
123
+ - `T` opens the **theme picker**: packs first (drill in with Enter, `← packs` or Esc to come back, `✳ all themes` to flatten) beside a live preview of your current slide at its current step (a real embedded deck; it boots once, then candidates are postMessage-swapped in — no reload per candidate, which matters inside 600 KB bundles). **Type to filter** the list; `Backspace` edits, `Esc` clears then closes, `Enter` applies.
124
+ - `,` / `.` cycle themes within the current pack; crossing into another pack asks for confirmation (same key confirms, the opposite key or `Esc` cancels). `?theme=<name>` picks one at load; the applied choice persists per deck in localStorage.
125
+ - `[` / `]` cycle the deck's **type** through curated system stacks (sans, rounded, humanist, geometric, serifs, slab, mono — entry 0 restores the theme's own fonts). The override survives theme switches, persists per deck, and re-measures pinned titles on every change.
126
+
127
+ ### Theme generation
128
+
129
+ `⌃T` generates a brand-new, contract-complete theme and applies it instantly — press again to re-roll. Every token is derived with WCAG luminance math and iterated until it clears the same gates as the shipped themes: **a generated theme can never fail validation** (property-tested across hundreds of seeds).
130
+
131
+ Generation follows **codified palette rules** distilled from the most-loved editor themes — Solarized's selective contrast, Nord's dimmed pastels, Catppuccin's balance — and the 60-30-10 doctrine (R1–R8 in `src/core/themegen.js`, each enforced by an independent property test):
132
+
133
+ | Rule | What it guarantees |
134
+ |---|---|
135
+ | R1 limited palette | one base hue + the harmony's ≤5 accent hues, reused across every role — never a fresh hue per token |
136
+ | R2 quiet dominant areas | the canvas stays near-neutral; chroma belongs to small accents, not large surfaces |
137
+ | R3 dimmed pastels | vivid rolls are biased toward muted; saturation is hard-capped below neon |
138
+ | R4 one accent lightness band | accent-family colors share a lightness and saturation — no color shouts over its peers |
139
+ | R5 selective contrast | syntax roles differ by hue at similar brightness, not by brightness spikes |
140
+ | R6 no pure black or white | every neutral carries the base-hue tint |
141
+ | R7 gradients sparingly | ~15% of rolls, low-drift same-family washes only |
142
+ | R8 semantic anchors | terminal red/green/yellow keep their recognizable hue even when muted |
143
+
144
+ The picker's first row, **✨ Generate new…**, rolls candidates with live preview (`⌃T` re-rolls, `Enter` applies). `⌃⇧T` **saves** the applied roll: prompts for a name, persists it to localStorage, and downloads `<name>.css` — a normal theme file and the portable artifact (drop it into `themes/` and commit). Saved customs appear in the list tagged “custom” and survive reload. Previews for generated/custom themes travel as `?gen=<base64url tokens>`, applied statelessly at init — works on `file://` and inside bundles. Generation is seeded and deterministic; `instance.generateTheme()` does it programmatically.
145
+
146
+ *(Note: browsers on Windows/Linux reserve `Ctrl+T`; on macOS it reaches the page.)*
147
+
148
+ ## Presenting
149
+
150
+ | Key | Action |
151
+ |---|---|
152
+ | `→` `←` `Space` | next / previous build or slide |
153
+ | `Home` / `End` | first / last slide |
154
+ | `O` | overview grid |
155
+ | `S` | speaker view (again: rehearse mode) |
156
+ | `B` / `F` | blackout / fullscreen |
157
+ | `D` | debug log (events, theme/font/narration, errors) |
158
+ | `C` | captions — the current notes segment, YouTube-style |
159
+ | `T` | theme picker (type to filter) |
160
+ | `/` | command palette |
161
+ | `G` | slide finder (live preview) |
162
+ | `E` | edit speaker notes (with `decklight edit` running) |
163
+ | `V` | narration on/off |
164
+ | `N` | narration picker (tracks, live voice, tone) |
165
+ | `⇧V` | record offline narration (live voice) |
166
+ | `<` / `>` | voice speed, 0.25× steps (YouTube's shortcut) |
167
+ | `P` | pause / resume narration |
168
+ | `,` / `.` | cycle theme |
169
+ | `[` / `]` | cycle font |
170
+ | `⌃T` / `⌃⇧T` | generate a theme / save it |
171
+ | `M` | module menu (playlists & merged decks) |
172
+ | `?` | help overlay |
173
+
174
+ ### Narration
175
+
176
+ Two sources, one `V` toggle (`N` picks):
177
+
178
+ - **Recorded** — pre-rendered per-slide audio played in sync with slide changes. Generated by `tools/voiceover.mjs`, which pipes speaker notes through a local Ollama model (LLMs write text; they don't speak) and synthesizes with `--engine piper` (neural local TTS, fully offline) or `--engine gemini` (gemini-2.5-pro-tts on Vertex AI; needs `gcloud auth application-default login`). A deck can ship several takes — `narration: { files: [{ label, dir, ext }, …] }` (`ext` defaults to `m4a`) — and `N` switches between them, persisted per deck.
179
+ - **Live voice** — synthesized on the fly *as you present*. Run `decklight tts` (a local bridge to gemini-2.5-pro-tts; the browser can't hold Google credentials), then pick any of the 30 Gemini prebuilt voices and a delivery tone — professional, joyful, too serious, super excited… or **type your own instruction** ("Read like a noir detective") — from the `N` picker or the `/` palette's "Live voice…" command. Every voice **and tone** row has a **▶ preview** that speaks a test sentence (editable at the top of the voices view, ↺ restores the default *"Hey, this is Decklight"*) so you can audition before committing — tone previews speak the drafted voice in that delivery style, the custom-tone input previews a typed instruction before Enter commits it, and after the first preview the rest of the roster prefetches in the background (the default sentence's cache is kept for the whole session). Narration is **synced to builds**: each `⟨CLICK⟩` segment of the notes becomes its own clip, and when it ends the deck reveals the next build — after the last segment it **auto-advances** to the next slide. Pick a voice, press play, and the deck presents itself beat by beat; pressing `→` mid-sentence re-syncs the voice to wherever you went. Mark interactive slides (quizzes, exercises) with `data-narration="hold"` — narration never auto-advances off them, waiting for you instead. The spoken unit is a **sentence** — each one is its own synthesized, cached clip, so first audio lands fast — and while narration is on a **lookahead buffer** keeps the next 10 sentences synthesized in parallel in the background. `P` pauses/resumes mid-sentence, and captions follow the voice sentence by sentence.
180
+ - **⇧V records the live voice offline**: downloads every slide's narration as `slide-NN.wav`, **stitched from the sentence cache** — anything you've already played (or the lookahead buffer warmed) is reused at zero cost, and only unheard sentences synthesize. A progress bar estimates time remaining from the observed per-slide rate. Point `narration.files` at the folder with `ext: 'wav'` and the deck narrates without the bridge.
181
+
182
+ ### Chrome & navigation
183
+
184
+ - **Command palette** (`/`): every command with its shortcut, type-to-filter, Enter runs — argument commands drill into their pickers, `goto 27` (or just `27`) jumps to a slide, and unmatched text becomes a slide search.
185
+ - **Transcript** (palette → Transcript…): the deck's full spoken script in an overlay — click a title to jump — with one-click export to `.txt` or `.md`.
186
+ - **Edit mode** (`E`, with `decklight edit deck.html` running): a notes editor over the current slide — plain text with `⟨CLICK⟩` lines between beats — whose Save writes the `<aside class="notes">` back into the file; the server watches the deck and every connected browser **auto-reloads** (the hash keeps your slide/step). Editing the file in your own editor live-reloads too. Works from the printed URL *and* from a double-clicked `file://` deck — the player finds the server on its default port (override with `Decklight.init({ edit: { url } })`).
187
+ - **Slide finder** (`G`, palette → Find slide, or just type words in the palette): type words, get matching slides — title matches rank first, body matches follow — with a live preview of the selected slide on the right. `Enter` jumps. (Deliberately not `⌘F` — browser find stays sacred.)
188
+ - **Speaker view** (`S`): current + next thumbnails, notes with `⟨CLICK⟩` segments highlighted as builds land, elapsed timer, step list. Works on `file://`. Press `S` again for **rehearse mode** — big cue cards (the deck's rehearse notes) instead of full prose, so you practice recalling the material rather than reading it.
189
+ - **Overview** (`O`): scaled grid of every slide, arrow-key navigation.
190
+ - **Brand logo**: `logo: { onLight: '#logo-dark-art', onDark: '#logo-light-art' }` puts a mark on every slide; the engine picks the variant from the applied theme's real background luminance, so the right logo follows every theme switch — generated themes included. `data-logo` on a section swaps the corner mark for a large in-flow one above the slide's title (module openers, covers).
191
+ - **Pinned titles**: `pinTitles: true` keeps slide titles at one vertical position deck-wide instead of drifting with content height (title cards and quote slides stay centered; `data-pin` / `data-pin="none"` / `data-pin="<px>"` per slide). Subtitles join the pinned header.
192
+ - **Playlists**: `playlist: { modules: [{title, href}…], index }` chains decks at their boundaries; `M` opens the module menu. Merged single-file decks navigate by in-file markers instead — no page loads.
193
+ - **Print**: `?print` renders every slide with all builds complete, one per page — print to PDF from there.
194
+ - Touch swipe, hash deep-links (`#/<slide>/<step>`), `slideNumber`, prev/next chrome and progress bar via `controls`.
195
+
196
+ ## Single-file bundles
197
+
198
+ ```sh
199
+ decklight bundle deck.html -o out.html --themes all # one deck, self-contained
200
+ decklight bundle deck.html --all -o course.html # follow the playlist, merge EVERY module
201
+ ```
202
+
203
+ One HTML file with the runtime, structure CSS, chosen themes, casts, and images inlined — email it, it works offline, themes and the generator included. Merged bundles get in-file module navigation and per-module cast namespacing.
204
+
205
+ ## CLI
206
+
207
+ | Command | Purpose |
208
+ |---|---|
209
+ | `decklight init ["Title"]` | scaffold a self-contained starter deck + an agent skill (`.claude/skills/decklight/`, `AGENTS.md`) |
210
+ | `decklight rec script.term.yaml` | record a terminal cast in a real PTY |
211
+ | `decklight refresh <dir\|casts…>` | re-record embedded scripts, report drift |
212
+ | `decklight export cast.json` | flatten to asciicast v2 |
213
+ | `decklight bundle deck.html [--all]` | self-contained single-file HTML |
214
+ | `decklight tts` | live voice bridge — the player synthesizes narration through it |
215
+ | `decklight edit deck.html` | live-editing server — `E` edits notes back into the file, saves auto-reload |
216
+
217
+ `decklight help` for flags and examples. The runtime has **zero dependencies** (marked and highlight.js are bundled at build time); `node-pty` and `js-yaml` are CLI-only.
218
+
219
+ ## JS API
220
+
221
+ ```js
222
+ const deck = Decklight.init(config);
223
+ deck.next(); deck.prev(); deck.goto(slide, step);
224
+ deck.on('slide' | 'build' | 'ready', fn);
225
+ deck.state; // { slide, step, totalSlides }
226
+ deck.sync(); // re-scan a dynamically-edited DOM
227
+ deck.theme(name); // switch theme programmatically
228
+ deck.generateTheme(); // ⌃T programmatically; returns the autoname
229
+ deck.saveGeneratedTheme(name); // ⌃⇧T; a name argument skips the prompt
230
+ Decklight.registerBuildProvider(el, provider);
231
+ ```
232
+
233
+ ## Install on another machine
234
+
235
+ ```sh
236
+ git clone <this repo> && cd decklight
237
+ npm install # dev deps for building/recording; decks only need dist/ + themes/
238
+ npm run build
239
+ ```
240
+
241
+ Decks reference `dist/decklight.{js,css}` and one theme file — copy those three files (or a bundle) and nothing else.
242
+
243
+ ## Architecture
244
+
245
+ <p align="center">
246
+ <img src="docs/architecture.svg" width="860" alt="Decklight architecture: a single deck.html and a theme.css feed a zero-dependency browser runtime (engine, terminal player, markdown/svg/code, narration, overlays); two localhost servers sit beside it — decklight edit for live-reload note editing and decklight tts bridging to Vertex AI Gemini TTS; a node CLI records, refreshes, exports and bundles; and a verification band (WCAG gates, palette rules, headless render assertions, property tests) gates everything against SPEC.md.">
247
+ </p>
248
+
249
+ One HTML file and one theme stylesheet feed a **zero-dependency browser runtime**; everything with native dependencies or credentials lives in **localhost tools** (the CLI, the `edit` live-reload server, the `tts` bridge to Vertex AI); and a **verification band** — contrast gates, palette rules, headless render assertions, property tests — holds all of it to the `SPEC.md` contract.
250
+
251
+ ## Development
252
+
253
+ `npm test` (53 unit + property tests) · `node test/render.mjs` (headless-Chrome render assertions) · `node test/contrast.mjs` (WCAG theme gates) · `npm run verify` for the lot. The repo culture: every feature is verified end-to-end against a real render, not just unit-tested — see SPEC §10.
254
+
255
+ ## License
256
+
257
+ MIT