stagecraft 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 (133) hide show
  1. package/AGENT.md +792 -0
  2. package/LICENSE +21 -0
  3. package/README.md +210 -0
  4. package/bin/cli.js +51 -0
  5. package/bin/export.js +137 -0
  6. package/bin/init.js +52 -0
  7. package/bin/lib/edit-ops.js +405 -0
  8. package/bin/serve.js +278 -0
  9. package/dist/stagecraft.bundle.css +4443 -0
  10. package/dist/stagecraft.bundle.js +7621 -0
  11. package/dist/themes/brand.bundle.css +5262 -0
  12. package/dist/themes/neon.bundle.css +5289 -0
  13. package/dist/themes/paper.bundle.css +5276 -0
  14. package/dist/themes/phosphor.bundle.css +4443 -0
  15. package/dist/themes/shopware.bundle.css +5850 -0
  16. package/examples/closing-card.js +74 -0
  17. package/examples/orchestration-graph.js +156 -0
  18. package/examples/terminal-log.js +109 -0
  19. package/examples/token-stream.js +96 -0
  20. package/examples/whoami.js +90 -0
  21. package/package.json +41 -0
  22. package/src/components/activity-list.js +75 -0
  23. package/src/components/agenda.js +79 -0
  24. package/src/components/bar-chart.js +162 -0
  25. package/src/components/before-after.js +135 -0
  26. package/src/components/bento.js +73 -0
  27. package/src/components/big-number.js +87 -0
  28. package/src/components/callout.js +75 -0
  29. package/src/components/checklist.js +81 -0
  30. package/src/components/code-block.js +141 -0
  31. package/src/components/code-diff.js +98 -0
  32. package/src/components/compare.js +85 -0
  33. package/src/components/counter.js +80 -0
  34. package/src/components/cta.js +69 -0
  35. package/src/components/cycle.js +146 -0
  36. package/src/components/definition.js +96 -0
  37. package/src/components/donut-chart.js +179 -0
  38. package/src/components/full-image.js +82 -0
  39. package/src/components/funnel.js +111 -0
  40. package/src/components/gauge.js +147 -0
  41. package/src/components/heatmap.js +141 -0
  42. package/src/components/image-grid.js +80 -0
  43. package/src/components/image-text.js +96 -0
  44. package/src/components/kinetic-text.js +72 -0
  45. package/src/components/kpi.js +106 -0
  46. package/src/components/line-chart.js +215 -0
  47. package/src/components/manifesto.js +104 -0
  48. package/src/components/marquee.js +63 -0
  49. package/src/components/matrix2x2.js +151 -0
  50. package/src/components/pillars.js +80 -0
  51. package/src/components/pricing.js +90 -0
  52. package/src/components/process-flow.js +133 -0
  53. package/src/components/progress.js +136 -0
  54. package/src/components/punchline.js +82 -0
  55. package/src/components/pyramid.js +107 -0
  56. package/src/components/qanda.js +60 -0
  57. package/src/components/quote.js +70 -0
  58. package/src/components/roadmap.js +130 -0
  59. package/src/components/section-card.js +45 -0
  60. package/src/components/shift-arrow.js +41 -0
  61. package/src/components/spark-line.js +147 -0
  62. package/src/components/spotlight.js +85 -0
  63. package/src/components/statement.js +106 -0
  64. package/src/components/stats.js +91 -0
  65. package/src/components/steps.js +83 -0
  66. package/src/components/swot.js +110 -0
  67. package/src/components/team-grid.js +87 -0
  68. package/src/components/testimonial.js +99 -0
  69. package/src/components/timeline.js +91 -0
  70. package/src/components/tip.js +63 -0
  71. package/src/components/venn.js +198 -0
  72. package/src/edit-mode.js +1256 -0
  73. package/src/engine.js +823 -0
  74. package/src/helpers.js +169 -0
  75. package/src/transitions.js +101 -0
  76. package/starter/index.html +40 -0
  77. package/starter/slides/00-title.js +12 -0
  78. package/starter/stagecraft.config.js +8 -0
  79. package/themes/brand/base.css +4 -0
  80. package/themes/brand/components-business.css +173 -0
  81. package/themes/brand/components-chart.css +65 -0
  82. package/themes/brand/components-content.css +126 -0
  83. package/themes/brand/components-data.css +162 -0
  84. package/themes/brand/components-diagram.css +115 -0
  85. package/themes/brand/components-layout.css +112 -0
  86. package/themes/brand/components.css +46 -0
  87. package/themes/brand/manifest.json +20 -0
  88. package/themes/brand/tokens.css +20 -0
  89. package/themes/brand/transitions.css +4 -0
  90. package/themes/neon/base.css +10 -0
  91. package/themes/neon/components-business.css +189 -0
  92. package/themes/neon/components-chart.css +70 -0
  93. package/themes/neon/components-content.css +112 -0
  94. package/themes/neon/components-data.css +160 -0
  95. package/themes/neon/components-diagram.css +109 -0
  96. package/themes/neon/components-layout.css +87 -0
  97. package/themes/neon/components.css +87 -0
  98. package/themes/neon/manifest.json +21 -0
  99. package/themes/neon/tokens.css +17 -0
  100. package/themes/neon/transitions.css +13 -0
  101. package/themes/paper/base.css +9 -0
  102. package/themes/paper/components-business.css +196 -0
  103. package/themes/paper/components-chart.css +74 -0
  104. package/themes/paper/components-content.css +108 -0
  105. package/themes/paper/components-data.css +168 -0
  106. package/themes/paper/components-diagram.css +89 -0
  107. package/themes/paper/components-layout.css +105 -0
  108. package/themes/paper/components.css +60 -0
  109. package/themes/paper/manifest.json +10 -0
  110. package/themes/paper/tokens.css +21 -0
  111. package/themes/paper/transitions.css +11 -0
  112. package/themes/phosphor/base.css +511 -0
  113. package/themes/phosphor/components-business.css +818 -0
  114. package/themes/phosphor/components-chart.css +415 -0
  115. package/themes/phosphor/components-content.css +530 -0
  116. package/themes/phosphor/components-data.css +824 -0
  117. package/themes/phosphor/components-diagram.css +427 -0
  118. package/themes/phosphor/components-layout.css +450 -0
  119. package/themes/phosphor/components.css +223 -0
  120. package/themes/phosphor/manifest.json +11 -0
  121. package/themes/phosphor/tokens.css +17 -0
  122. package/themes/phosphor/transitions.css +213 -0
  123. package/themes/shopware/base.css +94 -0
  124. package/themes/shopware/components-business.css +344 -0
  125. package/themes/shopware/components-chart.css +121 -0
  126. package/themes/shopware/components-content.css +169 -0
  127. package/themes/shopware/components-data.css +219 -0
  128. package/themes/shopware/components-diagram.css +129 -0
  129. package/themes/shopware/components-layout.css +166 -0
  130. package/themes/shopware/components.css +83 -0
  131. package/themes/shopware/manifest.json +21 -0
  132. package/themes/shopware/tokens.css +68 -0
  133. package/themes/shopware/transitions.css +22 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Nögel
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
13
+ all 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
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # Stagecraft
2
+
3
+ **Cinematic, agent-authored presentations in a single HTML file.**
4
+
5
+ ▶ **[Live demo](https://noegel.io/stagecraft/)** — the deck about Stagecraft, built with Stagecraft. Auto-deployed from `main` via GitHub Pages.
6
+
7
+ A small JavaScript library for building animated slide decks that live in a single HTML file. Designed so an LLM can generate a deck that doesn't look like an LLM generated it.
8
+
9
+ ## Why
10
+
11
+ Generic slide libraries produce generic decks. Stagecraft is built around a different premise: **the agent is a creative director, not a form-filler**. The library ships a catalog of building blocks (50 components across 7 families) AND five bespoke example slides — `examples/token-stream.js`, `examples/orchestration-graph.js`, `examples/terminal-log.js` — that show what the bar looks like. `AGENT.md` is the manifesto.
12
+
13
+ ## Quick start
14
+
15
+ ```bash
16
+ mkdir my-deck && cd my-deck
17
+ npm init -y
18
+ npm install stagecraft
19
+ npx stagecraft init
20
+ ```
21
+
22
+ This scaffolds:
23
+
24
+ ```
25
+ my-deck/
26
+ ├── index.html # opens directly in a browser to present
27
+ ├── stagecraft.config.js # the manifest: theme + slide order + transitions
28
+ ├── slides/
29
+ │ └── 00-title.js
30
+ └── AGENT.md # the manifesto the agent reads
31
+ ```
32
+
33
+ Present mode (no setup, no infrastructure):
34
+ ```bash
35
+ open index.html
36
+ ```
37
+
38
+ ## Two modes, one HTML file
39
+
40
+ ### Presentation mode
41
+
42
+ Open `index.html` directly. Use arrow keys, space, or page-up/down to navigate. `F` for fullscreen, `S` for storyboard, `1-9` to jump to a section, `R` to replay the current slide, `P` to open the presenter view.
43
+
44
+ ### Edit mode (dev server)
45
+
46
+ ```bash
47
+ npx stagecraft serve
48
+ ```
49
+
50
+ The browser detects the server and unlocks the editing affordances:
51
+
52
+ - **Drag slides** in the storyboard to reorder
53
+ - **Click transitions** between storyboard tiles to pick from 15 effects with hover preview
54
+ - **Click any text** on a Layer-2 component to inline-edit (writes back to source via AST roundtrip)
55
+ - **Shift+Click** any element to pin a `// @note[stage-key=...]:` comment for the agent
56
+ - **N** in present mode opens a slide-level note dialog
57
+ - **🎙 button** per tile in storyboard: speaker notes (shown in presenter view, hidden from audience)
58
+ - **× button** per tile: delete slide
59
+ - **+ tile** at the end of the grid: add a new slide from a template
60
+ - **Theme picker** at the top of the storyboard: live-switch between Phosphor/Paper/Neon/Brand/Shopware
61
+ - **📋 Process notes** copies a ready-made agent prompt to the clipboard
62
+
63
+ When you ask the agent to "process my notes":
64
+ 1. `grep -rn '@note' slides/`
65
+ 2. Read each note + its slide
66
+ 3. Edit the slide
67
+ 4. Delete the `@note:` lines
68
+
69
+ That's the whole loop.
70
+
71
+ ## Presenter view (multi-monitor)
72
+
73
+ Press `P` during a presentation → a new window opens with the presenter view:
74
+
75
+ - **Left:** current slide (live)
76
+ - **Right:** next slide thumbnail
77
+ - **Below:** speaker notes + elapsed timer + wall clock + reset
78
+
79
+ Drag the presenter window to your laptop screen, drag the main window to the beamer (or vice versa), full-screen the main one. Both windows stay synchronized via the browser's `BroadcastChannel` API: any nav action in either window updates the other.
80
+
81
+ ## 50 components in 7 families
82
+
83
+ | Family | Count | Examples |
84
+ |---|---|---|
85
+ | **Core** | 6 | KineticText, SectionCard, ActivityList, Compare, Counter, ShiftArrow |
86
+ | **Layout** | 6 | ImageText, FullImage, Quote, BigNumber, Stats, Bento |
87
+ | **Diagram** | 5 | Pillars, Timeline, Pyramid, Cycle, Funnel |
88
+ | **Chart** | 5 | Matrix2x2, BarChart, Progress, ProcessFlow, Venn |
89
+ | **Data-viz** | 10 | KPI, DonutChart, LineChart, Gauge, SparkLine, Heatmap, Roadmap, SWOT, CodeBlock, CodeDiff |
90
+ | **Business** | 10 | Pricing, Testimonial, TeamGrid, Agenda, Checklist, Steps, CTA, Callout, Tip, BeforeAfter |
91
+ | **Content** | 8 | Statement, QandA, Manifesto, Punchline, Definition, ImageGrid, Spotlight, Marquee |
92
+
93
+ 50 anchors total + 5 bespoke cookbook examples (`examples/`). See `AGENT.md` for the full catalog with code examples.
94
+
95
+ ## 15 transitions
96
+
97
+ Set in the manifest:
98
+ ```js
99
+ Stage.deck({
100
+ slides: [
101
+ { src: 'slides/00-title.js' },
102
+ { src: 'slides/01-shift.js', transition: 'glitch' }
103
+ ]
104
+ });
105
+ ```
106
+
107
+ Built-in: `cut · fade · slide · dissolve · glitch · wipe · zoom-in · zoom-out · flip · iris · shutter · push · typewriter · shatter` (plus the `cut`-preview-only animation).
108
+
109
+ Themes override visuals. Unknown name → falls back to `fade`. Hover any transition in the picker to play it once.
110
+
111
+ ## 5 themes
112
+
113
+ | Theme | Vibe | Typography |
114
+ |---|---|---|
115
+ | **Phosphor** | Cinematic, technical, dark. Phosphor-green + film grain | JetBrains Mono |
116
+ | **Paper** | Academic, restrained, light. Navy accent, no glow | Inter + Source Serif 4 |
117
+ | **Neon** | Cyberpunk, magenta + cyan, heavy glow | Space Grotesk + JetBrains Mono |
118
+ | **Brand** | Corporate, schlicht. Blue accent, GitHub-dark adjacent | Inter |
119
+ | **Shopware** | Official Shopware brand. Light, Meteor design tokens, brand-blue `#0870ff` | Inter |
120
+
121
+ Switch live from the edit-mode storyboard, or set `data-theme="..."` on `<html>` plus link the matching theme CSS bundle.
122
+
123
+ ## Speaker notes
124
+
125
+ Slides can carry notes for the presenter view:
126
+
127
+ ```js
128
+ Stage.register(Stage.KineticText({
129
+ section: 2,
130
+ lines: [...]
131
+ }), {
132
+ notes: 'Open by reminding them about last quarter\'s incident. Pause for ~5s after "the shift".'
133
+ });
134
+ ```
135
+
136
+ Notes are plain strings, displayed in the presenter window. Hidden from the audience. Edit them via the 🎙 button on each storyboard tile.
137
+
138
+ ## Export to PDF
139
+
140
+ ```bash
141
+ npm install --save-dev playwright pdf-lib
142
+ npx playwright install chromium
143
+ npx stagecraft export pdf --out my-deck.pdf
144
+ ```
145
+
146
+ Renders each slide at 1920×1080, concatenates into a single PDF. Skips transitions; respects each slide's `init()` animation timing (waits 1.2s by default; tune with `--wait`).
147
+
148
+ ## Visual regression tests
149
+
150
+ ```bash
151
+ npm install --save-dev playwright pixelmatch pngjs
152
+ npx playwright install chromium
153
+ npm run test:visual:update # one-time: capture baseline
154
+ # ... make changes ...
155
+ npm run test:visual # diff against baseline
156
+ ```
157
+
158
+ Catches the kind of bugs where a Venn label drifts or a Heatmap layout breaks under refactor. Diffs land in `tests/visual/diff/`.
159
+
160
+ ## Accessibility
161
+
162
+ Stagecraft respects `prefers-reduced-motion`:
163
+ - All CSS transitions/animations reduced to ~0ms globally
164
+ - JS animations (typewriter, particle emission, stagger) shortcut to final state immediately
165
+
166
+ The reduced-motion preference is OS-level — Stagecraft itself doesn't expose a toggle. If a component's animation is essential to the message, give it a non-animated fallback.
167
+
168
+ ## Architecture (4 layers + convention)
169
+
170
+ ```
171
+ Layer 0 Engine Stage.register, deck loader, navigation, steps, storyboard, transitions
172
+ Layer 1 Primitives staggerIn, typewriter, emitParticle, revealByDataStep, ...
173
+ Layer 2 Components 50 building blocks across 7 families
174
+ Layer 3 Cookbook 5 bespoke examples in examples/ — the real teachers
175
+ Layer 4 Convention AGENT.md — calibrates the agent's taste
176
+ ```
177
+
178
+ See `docs/specs/2026-05-22-stagecraft-sdk-design.md` for the full design rationale.
179
+
180
+ ## Bundle vs. source
181
+
182
+ - Source: `src/` (~70 files). What you read, edit, contribute against.
183
+ - Bundle: `dist/stagecraft.bundle.js` + `dist/themes/<name>.bundle.css`. Generated by `npm run build`. One script tag instead of 60+. Consumers from npm load these directly via `node_modules/stagecraft/dist/...`.
184
+
185
+ ## Deploying a deck to the web
186
+
187
+ Stagecraft decks are static HTML. Drop them anywhere:
188
+
189
+ ### gh-pages
190
+ ```bash
191
+ # in your deck repo
192
+ git checkout -b gh-pages
193
+ npm run build # if your index.html uses src/ paths, copy node_modules/stagecraft into the repo first
194
+ git add -A
195
+ git commit -m "deploy"
196
+ git push origin gh-pages
197
+ # Enable Pages in repo settings → branch: gh-pages
198
+ ```
199
+
200
+ ### Netlify / Vercel / S3 / anything that serves static files
201
+
202
+ Same idea: the deck is just HTML + JS + CSS. No runtime dependencies for present mode.
203
+
204
+ ## Status
205
+
206
+ v0.2 — feature-complete, multi-theme, multi-monitor, exportable. Pre-publication: dogfooding against a real production deck post-talk before tagging 1.0.
207
+
208
+ ## License
209
+
210
+ MIT — see `LICENSE`.
package/bin/cli.js ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * stagecraft — dispatcher CLI.
6
+ * stagecraft init scaffold a new project
7
+ * stagecraft serve start dev server with edit mode
8
+ */
9
+
10
+ import { spawn } from 'node:child_process';
11
+ import path from 'node:path';
12
+ import url from 'node:url';
13
+
14
+ const cmd = process.argv[2];
15
+ const rest = process.argv.slice(3);
16
+ const HERE = path.dirname(url.fileURLToPath(import.meta.url));
17
+
18
+ function run(script) {
19
+ const child = spawn('node', [path.join(HERE, script), ...rest], { stdio: 'inherit' });
20
+ child.on('exit', code => process.exit(code ?? 0));
21
+ }
22
+
23
+ switch (cmd) {
24
+ case 'init': run('init.js'); break;
25
+ case 'serve': run('serve.js'); break;
26
+ case 'export':
27
+ if (rest[0] !== 'pdf') {
28
+ console.error('Only `export pdf` is supported.');
29
+ process.exit(1);
30
+ }
31
+ rest.shift();
32
+ run('export.js');
33
+ break;
34
+ case '--help':
35
+ case '-h':
36
+ case undefined:
37
+ console.log(`Stagecraft — cinematic, agent-authored presentations.
38
+
39
+ Usage:
40
+ stagecraft init scaffold a new project
41
+ stagecraft serve [--port N] [--root DIR] dev server (edit mode)
42
+ stagecraft export pdf [--out deck.pdf] [--root DIR] render deck to PDF
43
+ (needs playwright + pdf-lib)
44
+
45
+ Without the dev server, open index.html in a browser to present.
46
+ Press P during a presentation to open the presenter view in a second window.`);
47
+ break;
48
+ default:
49
+ console.error(`Unknown command: ${cmd}`);
50
+ process.exit(1);
51
+ }
package/bin/export.js ADDED
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * stagecraft export pdf — render the deck to a PDF.
6
+ *
7
+ * Usage:
8
+ * npx stagecraft export pdf [--out deck.pdf] [--root .] [--port N]
9
+ *
10
+ * Requires (peer): playwright + pdf-lib.
11
+ * npm install --save-dev playwright pdf-lib
12
+ * npx playwright install chromium
13
+ *
14
+ * Strategy:
15
+ * 1) start the local dev server (no edit-mode UI needed; just static serve)
16
+ * 2) launch headless chromium
17
+ * 3) navigate to each slide via #hash, wait for animations to settle
18
+ * 4) page.pdf() per slide, then concatenate via pdf-lib
19
+ * 5) write to --out
20
+ */
21
+
22
+ import http from 'node:http';
23
+ import fs from 'node:fs';
24
+ import path from 'node:path';
25
+ import url from 'node:url';
26
+ import { spawn } from 'node:child_process';
27
+
28
+ // --- args ---
29
+ const args = process.argv.slice(2);
30
+ let outPath = 'deck.pdf';
31
+ let rootArg = '.';
32
+ let port = 4040;
33
+ let waitMs = 1200;
34
+ for (let i = 0; i < args.length; i++) {
35
+ if (args[i] === '--out') outPath = args[++i];
36
+ else if (args[i] === '--root') rootArg = args[++i];
37
+ else if (args[i] === '--port') port = parseInt(args[++i], 10);
38
+ else if (args[i] === '--wait') waitMs = parseInt(args[++i], 10);
39
+ }
40
+
41
+ const ROOT = path.resolve(rootArg);
42
+
43
+ // --- soft-import optional deps ---
44
+ async function softImport(name) {
45
+ try { return await import(name); }
46
+ catch (e) {
47
+ console.error(`\n[stagecraft] PDF export requires ${name}.`);
48
+ console.error(` Install with: npm install --save-dev playwright pdf-lib`);
49
+ console.error(` Then: npx playwright install chromium`);
50
+ process.exit(1);
51
+ }
52
+ }
53
+
54
+ const { chromium } = await softImport('playwright');
55
+ const { PDFDocument } = await softImport('pdf-lib');
56
+
57
+ // --- start the dev server as a child process (avoid port conflicts with running serve) ---
58
+ console.log(`[stagecraft] starting server on :${port}…`);
59
+ const HERE = path.dirname(url.fileURLToPath(import.meta.url));
60
+ const child = spawn('node', [path.join(HERE, 'serve.js'), '--root', ROOT, '--port', String(port)], {
61
+ stdio: ['ignore', 'pipe', 'pipe']
62
+ });
63
+
64
+ // Wait for "http://localhost:" line in the server output
65
+ let serverReady = false;
66
+ child.stdout.on('data', (b) => {
67
+ const s = b.toString();
68
+ if (s.includes('http://localhost:')) serverReady = true;
69
+ });
70
+ child.stderr.on('data', b => process.stderr.write(b));
71
+
72
+ await waitFor(() => serverReady, 5000);
73
+
74
+ // --- launch headless browser ---
75
+ const browser = await chromium.launch();
76
+ const ctx = await browser.newContext({
77
+ viewport: { width: 1920, height: 1080 },
78
+ deviceScaleFactor: 1
79
+ });
80
+ const page = await ctx.newPage();
81
+
82
+ const baseUrl = `http://localhost:${port}/`;
83
+ console.log(`[stagecraft] navigating to ${baseUrl}…`);
84
+ await page.goto(baseUrl);
85
+
86
+ // Discover slide count from the runtime
87
+ await page.waitForFunction(() => window.Stage && window.Stage.slides && window.Stage.slides.length > 0, {
88
+ timeout: 10000
89
+ });
90
+ const slideCount = await page.evaluate(() => window.Stage.slides.length);
91
+ console.log(`[stagecraft] ${slideCount} slides to export`);
92
+
93
+ // --- iterate slides, page.pdf() per slide, merge ---
94
+ const out = await PDFDocument.create();
95
+
96
+ for (let i = 0; i < slideCount; i++) {
97
+ process.stdout.write(`[stagecraft] slide ${String(i + 1).padStart(2, '0')}/${slideCount}…\r`);
98
+ await page.goto(`${baseUrl}#${i}`);
99
+ // Hide chrome that doesn't belong in print
100
+ await page.evaluate(() => {
101
+ document.querySelectorAll('.ui, .welcome').forEach(n => n.style.display = 'none');
102
+ });
103
+ // Wait for the slide to render + animations to settle
104
+ await page.waitForTimeout(waitMs);
105
+ const pdfBytes = await page.pdf({
106
+ width: '1920px',
107
+ height: '1080px',
108
+ printBackground: true,
109
+ pageRanges: '1',
110
+ preferCSSPageSize: false,
111
+ margin: { top: 0, bottom: 0, left: 0, right: 0 }
112
+ });
113
+ const tmp = await PDFDocument.load(pdfBytes);
114
+ const [copied] = await out.copyPages(tmp, [0]);
115
+ out.addPage(copied);
116
+ }
117
+
118
+ const finalBytes = await out.save();
119
+ fs.writeFileSync(outPath, finalBytes);
120
+ console.log(`\n[stagecraft] wrote ${outPath} (${finalBytes.length} bytes, ${slideCount} pages)`);
121
+
122
+ await browser.close();
123
+ child.kill();
124
+ process.exit(0);
125
+
126
+ // --- helpers ---
127
+ function waitFor(pred, ms) {
128
+ return new Promise((resolve, reject) => {
129
+ const t0 = Date.now();
130
+ function tick() {
131
+ if (pred()) return resolve();
132
+ if (Date.now() - t0 > ms) return reject(new Error('timeout waiting for server'));
133
+ setTimeout(tick, 80);
134
+ }
135
+ tick();
136
+ });
137
+ }
package/bin/init.js ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * stagecraft init — scaffold a new stagecraft project in the current directory.
6
+ *
7
+ * Copies the starter/ template + AGENT.md to cwd.
8
+ */
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import url from 'node:url';
13
+
14
+ const HERE = path.dirname(url.fileURLToPath(import.meta.url));
15
+ const PKG_ROOT = path.resolve(HERE, '..');
16
+ const SRC = path.join(PKG_ROOT, 'starter');
17
+ const TARGET = process.cwd();
18
+
19
+ function copyRecursive(src, dst) {
20
+ const stat = fs.statSync(src);
21
+ if (stat.isDirectory()) {
22
+ if (!fs.existsSync(dst)) fs.mkdirSync(dst, { recursive: true });
23
+ for (const entry of fs.readdirSync(src)) {
24
+ copyRecursive(path.join(src, entry), path.join(dst, entry));
25
+ }
26
+ } else {
27
+ if (fs.existsSync(dst)) {
28
+ console.log(` skip (exists): ${path.relative(TARGET, dst)}`);
29
+ return;
30
+ }
31
+ fs.copyFileSync(src, dst);
32
+ console.log(` + ${path.relative(TARGET, dst)}`);
33
+ }
34
+ }
35
+
36
+ console.log(`[stagecraft] scaffold → ${TARGET}`);
37
+ copyRecursive(SRC, TARGET);
38
+
39
+ // Also copy AGENT.md to project root
40
+ const agentMd = path.join(PKG_ROOT, 'AGENT.md');
41
+ if (fs.existsSync(agentMd)) {
42
+ const dst = path.join(TARGET, 'AGENT.md');
43
+ if (!fs.existsSync(dst)) {
44
+ fs.copyFileSync(agentMd, dst);
45
+ console.log(` + AGENT.md`);
46
+ }
47
+ }
48
+
49
+ console.log('\nDone. Next steps:');
50
+ console.log(' npx stagecraft serve # dev server with edit mode');
51
+ console.log(' open index.html # presentation only');
52
+ console.log(' read AGENT.md # the manifesto');