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/cli/init.mjs ADDED
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * decklight init — scaffold a starter deck, plus an agent skill so Claude
4
+ * Code (and any AGENTS.md-reading agent) knows the authoring contract
5
+ * without a web search or a guess from Reveal.js memory.
6
+ *
7
+ * decklight init ["My Deck"] [-o deck.html] [--dir path] [--force] [--no-skill]
8
+ *
9
+ * The deck is fully self-contained (runtime + theme inlined, like
10
+ * `decklight bundle` produces) — double-click it, it presents, no sibling
11
+ * files. The skill is regenerated every run (it's derived, not authored
12
+ * content) so re-running after an upgrade refreshes it; the deck file is
13
+ * only touched with --force.
14
+ */
15
+
16
+ import fs from 'node:fs';
17
+ import path from 'node:path';
18
+ import { fileURLToPath } from 'node:url';
19
+
20
+ const here = path.dirname(fileURLToPath(import.meta.url));
21
+ const PKG_ROOT = path.resolve(here, '..');
22
+ const PKG = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
23
+
24
+ function fail(msg) {
25
+ process.stderr.write(`decklight init: ${msg}\n`);
26
+ process.exit(1);
27
+ }
28
+
29
+ const scriptSafe = (s) => s.replace(/<\/script/gi, '<\\/script').replace(/<!--/g, '<\\u0021--');
30
+
31
+ const STARTER_THEME = 'aurora';
32
+
33
+ function starterDeck(title) {
34
+ const css = fs.readFileSync(path.join(PKG_ROOT, 'dist/decklight.css'), 'utf8');
35
+ const theme = fs.readFileSync(path.join(PKG_ROOT, 'themes', `${STARTER_THEME}.css`), 'utf8');
36
+ return `<!doctype html>
37
+ <html lang="en">
38
+ <head>
39
+ <meta charset="utf-8">
40
+ <title>${title}</title>
41
+ <style>
42
+ ${css}
43
+ </style>
44
+ <style data-theme="${STARTER_THEME}">
45
+ ${theme}
46
+ </style>
47
+ </head>
48
+ <body>
49
+ <div class="decklight">
50
+
51
+ <section>
52
+ <h1>${title}</h1>
53
+ <p>Made with Decklight — press → to advance, ? for every key</p>
54
+ <aside class="notes">
55
+ <p>Welcome. This is the title slide — say a line or two about what this deck covers.</p>
56
+ </aside>
57
+ </section>
58
+
59
+ <section>
60
+ <h2>A slide with a build</h2>
61
+ <ul data-build="fade-up">
62
+ <li>One attribute on the container: <code>data-build</code></li>
63
+ <li>Each direct child becomes one build step, in document order</li>
64
+ <li>Speaker notes segment with ⟨CLICK⟩ to match — see below</li>
65
+ </ul>
66
+ <aside class="notes">
67
+ <p>The container opts in and the engine does the rest — not a single class on the items themselves.</p>
68
+ <p>⟨CLICK⟩</p>
69
+ <p>Every press of the arrow reveals the next one, in order.</p>
70
+ <p>⟨CLICK⟩</p>
71
+ <p>And that's it — replace this slide's content, duplicate the section for more, and you have a deck.</p>
72
+ </aside>
73
+ </section>
74
+
75
+ </div>
76
+ <script>${scriptSafe(fs.readFileSync(path.join(PKG_ROOT, 'dist/decklight.js'), 'utf8').replace(/\/\/# sourceMappingURL=.*$/m, ''))}</script>
77
+ <script>Decklight.init({});</script>
78
+ </body>
79
+ </html>
80
+ `;
81
+ }
82
+
83
+ function specSlice() {
84
+ const spec = fs.readFileSync(path.join(PKG_ROOT, 'SPEC.md'), 'utf8');
85
+ const cut = spec.indexOf('\n## 10. Repository layout & tooling');
86
+ return (cut > 0 ? spec.slice(0, cut) : spec).trimEnd() + '\n';
87
+ }
88
+
89
+ function skillMd() {
90
+ return `---
91
+ name: decklight
92
+ description: Author and edit Decklight presentations — single-file HTML decks with Keynote-style builds, theme-aware SVG diagrams, 61 built-in themes, truthful terminal recordings, and live TTS narration. Use whenever creating or editing a Decklight deck (a .html file with a <div class="decklight"> of <section> slides) in this project.
93
+ ---
94
+
95
+ Decklight decks are one HTML file: no build step, no bundler, no server to
96
+ author. A deck is \`<div class="decklight">\` containing \`<section>\` slides;
97
+ the runtime is one JS file + one CSS file + one theme CSS file.
98
+
99
+ **Full authoring contract**: read [reference.md](reference.md) in this same
100
+ skill directory before authoring or editing a slide — it covers builds,
101
+ speaker notes segmentation (⟨CLICK⟩), SVG diagrams, theming, motion, code
102
+ blocks, terminal recordings, narration, and the public JS API. It's sliced
103
+ straight from Decklight's SPEC.md (v${PKG.version}), so it won't drift from
104
+ the installed runtime's actual behavior — trust it over prior training.
105
+
106
+ **Minimal skeleton** (see \`deck.html\` in this project for a worked example
107
+ with a build and notes already wired):
108
+
109
+ \`\`\`html
110
+ <div class="decklight">
111
+ <section>
112
+ <h1>Title</h1>
113
+ <aside class="notes"><p>What you'd say on this slide.</p></aside>
114
+ </section>
115
+ </div>
116
+ \`\`\`
117
+
118
+ **CLI** (\`npx decklight <command>\`, no install needed):
119
+ - \`decklight edit deck.html\` — serve with live reload; **E** in the browser edits speaker notes back into the file
120
+ - \`decklight rec script.term.yaml\` — record a truthful terminal cast in a real PTY, for \`<div class="terminal">\`
121
+ - \`decklight bundle deck.html --themes all\` — flatten into one self-contained file to hand off or publish
122
+ - \`decklight tts\` — live voice bridge so the deck can narrate itself on the fly
123
+ - \`decklight init\` — regenerate this skill after upgrading Decklight (deck file untouched unless \`--force\`)
124
+
125
+ Speaker notes drive both live narration and the transcript/caption
126
+ features, so write them even for decks that will only ever be read: split
127
+ multi-beat notes with a bare \`⟨CLICK⟩\` line so narration and build steps
128
+ stay in sync (§8 in the reference).
129
+ `;
130
+ }
131
+
132
+ const AGENTS_MARKER = '<!-- decklight:skill -->';
133
+
134
+ function agentsSection() {
135
+ return `${AGENTS_MARKER}
136
+ ## Decklight decks
137
+
138
+ This project contains a Decklight presentation (a single-file HTML deck —
139
+ see \`.claude/skills/decklight/reference.md\` for the full authoring
140
+ contract: builds, notes, SVG diagrams, themes, terminals, narration).
141
+ Read that file before adding or editing slides.
142
+ ${AGENTS_MARKER}
143
+ `;
144
+ }
145
+
146
+ export async function initMain(argv = process.argv.slice(2)) {
147
+ if (argv.includes('--help') || argv.includes('-h')) {
148
+ process.stdout.write(`decklight init — scaffold a starter deck + agent skill
149
+
150
+ Usage:
151
+ decklight init ["Deck Title"] [-o deck.html] [--dir path] [--force] [--no-skill]
152
+
153
+ Options:
154
+ -o <file> deck output path (default: deck.html)
155
+ --dir <path> target directory (default: current directory)
156
+ --force overwrite an existing deck file (default: refuses)
157
+ --no-skill skip .claude/skills/decklight/ and AGENTS.md
158
+
159
+ Always writes/refreshes the skill files (they're generated from the
160
+ installed version's SPEC.md, so re-running after an upgrade updates them)
161
+ unless --no-skill is given. The deck file is only touched with --force.
162
+ `);
163
+ process.exit(0);
164
+ }
165
+
166
+ let title = null, outFile = 'deck.html', dir = '.', force = false, withSkill = true;
167
+ const args = [...argv];
168
+ for (let i = 0; i < args.length; i++) {
169
+ const a = args[i];
170
+ if (a === '-o') outFile = args[++i];
171
+ else if (a === '--dir') dir = args[++i];
172
+ else if (a === '--force') force = true;
173
+ else if (a === '--no-skill') withSkill = false;
174
+ else if (!a.startsWith('-')) title = title ?? a;
175
+ else fail(`unknown argument: ${a}`);
176
+ }
177
+ title = title || 'My Deck';
178
+
179
+ const root = path.resolve(dir);
180
+ fs.mkdirSync(root, { recursive: true });
181
+
182
+ const deckPath = path.resolve(root, outFile);
183
+ if (fs.existsSync(deckPath) && !force) {
184
+ fail(`${path.relative('.', deckPath) || outFile} already exists — pass --force to overwrite`);
185
+ }
186
+ fs.writeFileSync(deckPath, starterDeck(title));
187
+ process.stdout.write(`created ${path.relative('.', deckPath) || outFile}\n`);
188
+
189
+ if (!withSkill) return;
190
+
191
+ const skillDir = path.join(root, '.claude', 'skills', 'decklight');
192
+ fs.mkdirSync(skillDir, { recursive: true });
193
+ fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillMd());
194
+ fs.writeFileSync(path.join(skillDir, 'reference.md'), specSlice());
195
+ process.stdout.write(`wrote .claude/skills/decklight/{SKILL.md,reference.md} (v${PKG.version})\n`);
196
+
197
+ const agentsPath = path.join(root, 'AGENTS.md');
198
+ const section = agentsSection();
199
+ if (!fs.existsSync(agentsPath)) {
200
+ fs.writeFileSync(agentsPath, `# Agent notes\n\n${section}`);
201
+ process.stdout.write('created AGENTS.md\n');
202
+ } else {
203
+ const existing = fs.readFileSync(agentsPath, 'utf8');
204
+ const markerRe = new RegExp(`${AGENTS_MARKER}[\\s\\S]*?${AGENTS_MARKER}\\n?`);
205
+ if (markerRe.test(existing)) {
206
+ fs.writeFileSync(agentsPath, existing.replace(markerRe, section));
207
+ process.stdout.write('refreshed the Decklight section in AGENTS.md\n');
208
+ } else {
209
+ fs.writeFileSync(agentsPath, existing.replace(/\n*$/, '\n\n') + section);
210
+ process.stdout.write('appended a Decklight section to AGENTS.md\n');
211
+ }
212
+ }
213
+ }
214
+
215
+ if (import.meta.url === `file://${process.argv[1]}`) await initMain();