@aivorynet/slaide 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/LICENSE +201 -0
  3. package/README.md +87 -0
  4. package/bin/slaide.js +25 -0
  5. package/dist/assets.d.ts +10 -0
  6. package/dist/assets.js +54 -0
  7. package/dist/assets.js.map +1 -0
  8. package/dist/cli.d.ts +2 -0
  9. package/dist/cli.js +433 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/compare/index.d.ts +24 -0
  12. package/dist/compare/index.js +246 -0
  13. package/dist/compare/index.js.map +1 -0
  14. package/dist/compare/ssim.d.ts +2 -0
  15. package/dist/compare/ssim.js +46 -0
  16. package/dist/compare/ssim.js.map +1 -0
  17. package/dist/compiler/chrome.d.ts +7 -0
  18. package/dist/compiler/chrome.js +94 -0
  19. package/dist/compiler/chrome.js.map +1 -0
  20. package/dist/compiler/compile.d.ts +2 -0
  21. package/dist/compiler/compile.js +432 -0
  22. package/dist/compiler/compile.js.map +1 -0
  23. package/dist/compiler/markdown.d.ts +26 -0
  24. package/dist/compiler/markdown.js +439 -0
  25. package/dist/compiler/markdown.js.map +1 -0
  26. package/dist/compiler/tokens.d.ts +13 -0
  27. package/dist/compiler/tokens.js +127 -0
  28. package/dist/compiler/tokens.js.map +1 -0
  29. package/dist/container.d.ts +47 -0
  30. package/dist/container.js +309 -0
  31. package/dist/container.js.map +1 -0
  32. package/dist/engine-entry.d.ts +1 -0
  33. package/dist/engine-entry.js +71 -0
  34. package/dist/engine-entry.js.map +1 -0
  35. package/dist/export-keynote/keynote.d.ts +10 -0
  36. package/dist/export-keynote/keynote.js +58 -0
  37. package/dist/export-keynote/keynote.js.map +1 -0
  38. package/dist/export-pptx/embed-fonts.d.ts +5 -0
  39. package/dist/export-pptx/embed-fonts.js +159 -0
  40. package/dist/export-pptx/embed-fonts.js.map +1 -0
  41. package/dist/export-pptx/inject-anim.d.ts +28 -0
  42. package/dist/export-pptx/inject-anim.js +150 -0
  43. package/dist/export-pptx/inject-anim.js.map +1 -0
  44. package/dist/export-pptx/pptx.d.ts +10 -0
  45. package/dist/export-pptx/pptx.js +425 -0
  46. package/dist/export-pptx/pptx.js.map +1 -0
  47. package/dist/export-pptx/transitions.d.ts +16 -0
  48. package/dist/export-pptx/transitions.js +82 -0
  49. package/dist/export-pptx/transitions.js.map +1 -0
  50. package/dist/import/emit.d.ts +5 -0
  51. package/dist/import/emit.js +274 -0
  52. package/dist/import/emit.js.map +1 -0
  53. package/dist/import/index.d.ts +21 -0
  54. package/dist/import/index.js +192 -0
  55. package/dist/import/index.js.map +1 -0
  56. package/dist/import/pptx.d.ts +117 -0
  57. package/dist/import/pptx.js +1043 -0
  58. package/dist/import/pptx.js.map +1 -0
  59. package/dist/import/raster-com.d.ts +27 -0
  60. package/dist/import/raster-com.js +113 -0
  61. package/dist/import/raster-com.js.map +1 -0
  62. package/dist/import/raster-extension.d.ts +35 -0
  63. package/dist/import/raster-extension.js +27 -0
  64. package/dist/import/raster-extension.js.map +1 -0
  65. package/dist/index.d.ts +63 -0
  66. package/dist/index.js +174 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/install/detect.d.ts +17 -0
  69. package/dist/install/detect.js +82 -0
  70. package/dist/install/detect.js.map +1 -0
  71. package/dist/install/index.d.ts +20 -0
  72. package/dist/install/index.js +126 -0
  73. package/dist/install/index.js.map +1 -0
  74. package/dist/install/registry.d.ts +33 -0
  75. package/dist/install/registry.js +169 -0
  76. package/dist/install/registry.js.map +1 -0
  77. package/dist/install/skills.d.ts +17 -0
  78. package/dist/install/skills.js +72 -0
  79. package/dist/install/skills.js.map +1 -0
  80. package/dist/master-io.d.ts +7 -0
  81. package/dist/master-io.js +55 -0
  82. package/dist/master-io.js.map +1 -0
  83. package/dist/mcp/server.d.ts +3 -0
  84. package/dist/mcp/server.js +114 -0
  85. package/dist/mcp/server.js.map +1 -0
  86. package/dist/optimize/export-optimize.d.ts +21 -0
  87. package/dist/optimize/export-optimize.js +167 -0
  88. package/dist/optimize/export-optimize.js.map +1 -0
  89. package/dist/parser/frontmatter.d.ts +8 -0
  90. package/dist/parser/frontmatter.js +45 -0
  91. package/dist/parser/frontmatter.js.map +1 -0
  92. package/dist/parser/parse.d.ts +2 -0
  93. package/dist/parser/parse.js +184 -0
  94. package/dist/parser/parse.js.map +1 -0
  95. package/dist/render/anim.d.ts +50 -0
  96. package/dist/render/anim.js +213 -0
  97. package/dist/render/anim.js.map +1 -0
  98. package/dist/render/charts.d.ts +3 -0
  99. package/dist/render/charts.js +171 -0
  100. package/dist/render/charts.js.map +1 -0
  101. package/dist/render/css.d.ts +3 -0
  102. package/dist/render/css.js +183 -0
  103. package/dist/render/css.js.map +1 -0
  104. package/dist/render/echarts-lib.generated.d.ts +1 -0
  105. package/dist/render/echarts-lib.generated.js +7 -0
  106. package/dist/render/echarts-lib.generated.js.map +1 -0
  107. package/dist/render/html.d.ts +5 -0
  108. package/dist/render/html.js +186 -0
  109. package/dist/render/html.js.map +1 -0
  110. package/dist/render/inject.d.ts +12 -0
  111. package/dist/render/inject.js +19 -0
  112. package/dist/render/inject.js.map +1 -0
  113. package/dist/render/mermaid-lib.generated.d.ts +1 -0
  114. package/dist/render/mermaid-lib.generated.js +7 -0
  115. package/dist/render/mermaid-lib.generated.js.map +1 -0
  116. package/dist/render/runtime.d.ts +1 -0
  117. package/dist/render/runtime.js +393 -0
  118. package/dist/render/runtime.js.map +1 -0
  119. package/dist/render-pdf/pdf.d.ts +9 -0
  120. package/dist/render-pdf/pdf.js +53 -0
  121. package/dist/render-pdf/pdf.js.map +1 -0
  122. package/dist/render-png/montage.d.ts +14 -0
  123. package/dist/render-png/montage.js +75 -0
  124. package/dist/render-png/montage.js.map +1 -0
  125. package/dist/render-png/shoot.d.ts +25 -0
  126. package/dist/render-png/shoot.js +79 -0
  127. package/dist/render-png/shoot.js.map +1 -0
  128. package/dist/scaffold.d.ts +9 -0
  129. package/dist/scaffold.js +36 -0
  130. package/dist/scaffold.js.map +1 -0
  131. package/dist/types.d.ts +212 -0
  132. package/dist/types.js +7 -0
  133. package/dist/types.js.map +1 -0
  134. package/dist/util.d.ts +6 -0
  135. package/dist/util.js +28 -0
  136. package/dist/util.js.map +1 -0
  137. package/package.json +66 -0
  138. package/skills/slaide/SKILL.md +64 -0
  139. package/skills/slaide/agents/openai.yaml +12 -0
  140. package/skills/slaide/examples/branded-deck.slaide +198 -0
  141. package/skills/slaide/examples/branded-theme.slaide.yaml +187 -0
  142. package/skills/slaide/examples/sample.slaide +98 -0
  143. package/skills/slaide/reference.md +177 -0
  144. package/skills/slaide/themes.md +171 -0
  145. package/themes/aurora.slaide.yaml +136 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mermaid-lib.generated.js","sourceRoot":"","sources":["../../src/render/mermaid-lib.generated.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,sCAAsC;AACtC,mEAAmE;AACnE,sDAAsD;AACtD,kFAAkF;AAClF,MAAM,CAAC,MAAM,eAAe,GAC1B,0vxvIAA0vxvI,CAAC"}
@@ -0,0 +1 @@
1
+ export declare const RUNTIME_JS: string;
@@ -0,0 +1,393 @@
1
+ // Copyright 2026 AIVory, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // Client runtime, emitted as an inline <script>. Vanilla JS, no dependencies.
4
+ // Operates on server-rendered DOM: .sl-stage > .sl-slide[data-index][data-transition].
5
+ export const RUNTIME_JS = String.raw `
6
+ (function(){
7
+ var stage = document.querySelector('.sl-stage');
8
+ if(!stage) return;
9
+ var vp = document.querySelector('.sl-viewport');
10
+ var slides = Array.prototype.slice.call(document.querySelectorAll('.sl-slide'));
11
+ var CW = parseFloat(stage.dataset.w)||1280, CH = parseFloat(stage.dataset.h)||720;
12
+ var notes = (window.__SLAIDE_NOTES__)||[];
13
+ var cur = 0, step = 0, busy = false;
14
+ var zoomFactor = 1, panX = 0, panY = 0;
15
+ // When false, taps don't advance and drag-pan is off (for interactive embeds,
16
+ // kiosk mode, or a host that drives navigation itself). Default: on.
17
+ var navEnabled = true;
18
+
19
+ // True when keyboard focus is in an editable field, so navigation keys (Space,
20
+ // arrows) belong to the field and must not be hijacked for slide control.
21
+ function isTyping(){
22
+ var a = document.activeElement;
23
+ return !!(a && (a.isContentEditable || /^(input|textarea|select)$/i.test(a.tagName||'')));
24
+ }
25
+
26
+ function buildCount(i){ return parseInt(slides[i].dataset.builds||'0',10); }
27
+
28
+ function applyBuilds(slide, n){
29
+ var els = slide.querySelectorAll('[data-build]');
30
+ for(var i=0;i<els.length;i++){
31
+ var b = parseInt(els[i].getAttribute('data-build'),10);
32
+ els[i].classList.toggle('sl-shown', b<=n);
33
+ }
34
+ }
35
+
36
+ function clampPan(s, vw, vh){
37
+ // Limit pan to the overflow on each axis so the scaled stage can't drift
38
+ // off-screen (only meaningful when zoomed in past fit).
39
+ var ox = Math.max(0, (CW*s - vw)/2), oy = Math.max(0, (CH*s - vh)/2);
40
+ panX = Math.max(-ox, Math.min(ox, panX));
41
+ panY = Math.max(-oy, Math.min(oy, panY));
42
+ }
43
+ // Docked panels (a left slide navigator, an optional right side panel) reserve gutters
44
+ // via the --sl-dock-left / --sl-dock-right CSS vars; the stage centers in the *remaining*
45
+ // width and shifts right by the left gutter. 0 (unset) means full-bleed, as before.
46
+ function dockVar(name){
47
+ var v = getComputedStyle(document.documentElement).getPropertyValue(name);
48
+ var n = parseFloat(v); return isFinite(n) ? n : 0;
49
+ }
50
+ function scale(){
51
+ var dl = dockVar('--sl-dock-left'), dr = dockVar('--sl-dock-right');
52
+ var vw = window.innerWidth - dl - dr, vh = window.innerHeight;
53
+ var fit = Math.min(vw/CW, vh/CH);
54
+ var s = fit * zoomFactor;
55
+ if(zoomFactor<=1){ panX = panY = 0; } else { clampPan(s, vw, vh); }
56
+ var tx = dl + (vw - CW*s)/2 + panX, ty = (vh - CH*s)/2 + panY;
57
+ stage.style.transform = 'translate('+tx+'px,'+ty+'px) scale('+s+')';
58
+ }
59
+
60
+ function updateChrome(){
61
+ var prog = document.querySelector('.sl-progress');
62
+ if(prog) prog.style.width = (((cur)/(Math.max(1,slides.length-1)))*100)+'%';
63
+ var ctr = document.querySelector('.sl-counter');
64
+ if(ctr) ctr.textContent = (cur+1)+' / '+slides.length;
65
+ var np = document.querySelector('.sl-notes');
66
+ if(np && np.classList.contains('sl-open')) np.innerHTML = notes[cur] ? notes[cur] : '<em style="opacity:.5">No notes for this slide.</em>';
67
+ var hash = '#'+(cur+1)+(step>0?('.'+step):'');
68
+ if(location.hash !== hash) history.replaceState(null,'',hash);
69
+ // Single chokepoint every navigation path passes through — let the injected
70
+ // viewer chrome (ribbon) sync its counter + thumbnail highlight reactively.
71
+ try{ document.dispatchEvent(new CustomEvent('slaide:change',{detail:{index:cur,step:step,count:slides.length}})); }catch(e){}
72
+ }
73
+
74
+ // The transition name IS the CSS class infix (sl-anim-<name>-in/out), defined once
75
+ // in anim.ts. Direction is handled by an extra sl-anim-rev modifier (see goTo), so
76
+ // adding a transition never touches this file. 'none'/'morph' are the two specials.
77
+ function animType(slide){
78
+ var t = slide.dataset.transition || 'fade';
79
+ if(t==='none') return null;
80
+ if(t==='morph') return 'morph';
81
+ return t;
82
+ }
83
+
84
+ function activate(i, full){
85
+ slides.forEach(function(s,idx){ s.classList.toggle('sl-active', idx===i); });
86
+ step = full ? buildCount(i) : 0;
87
+ applyBuilds(slides[i], step);
88
+ }
89
+
90
+ function morph(oldEl, newEl, after){
91
+ if(!document.startViewTransition){ after(); return; }
92
+ // pair shared [data-morph] ids transiently to avoid duplicate VT names
93
+ var oldMap={}, pairs=[];
94
+ oldEl.querySelectorAll('[data-morph]').forEach(function(e){ oldMap[e.dataset.morph]=e; });
95
+ newEl.querySelectorAll('[data-morph]').forEach(function(e){
96
+ var o = oldMap[e.dataset.morph];
97
+ if(o){ o.style.viewTransitionName='vt-'+e.dataset.morph; e.style.viewTransitionName='vt-'+e.dataset.morph; pairs.push(o); pairs.push(e); }
98
+ });
99
+ var vt = document.startViewTransition(function(){ after(); });
100
+ vt.finished.finally(function(){ pairs.forEach(function(e){ e.style.viewTransitionName=''; }); });
101
+ }
102
+
103
+ function goTo(i, dir){
104
+ if(i<0||i>=slides.length||i===cur||busy) return;
105
+ var oldSlide = slides[cur], newSlide = slides[i];
106
+ var type = animType(newSlide);
107
+
108
+ if(type==='morph'){
109
+ morph(oldSlide, newSlide, function(){ activate(i, dir<0); });
110
+ cur=i; updateChrome(); sync(); return;
111
+ }
112
+ if(!type){ activate(i, dir<0); cur=i; updateChrome(); sync(); return; }
113
+
114
+ busy = true;
115
+ var inC='sl-anim-'+type+'-in', outC='sl-anim-'+type+'-out';
116
+ newSlide.classList.add('sl-active', inC);
117
+ oldSlide.classList.add(outC);
118
+ if(dir<0){ newSlide.classList.add('sl-anim-rev'); oldSlide.classList.add('sl-anim-rev'); }
119
+ step = dir<0 ? buildCount(i) : 0; applyBuilds(newSlide, step);
120
+ var done=0;
121
+ function endOld(){ oldSlide.classList.remove('sl-active', outC, 'sl-anim-rev'); oldSlide.removeEventListener('animationend', endOld); next(); }
122
+ function endNew(){ newSlide.classList.remove(inC, 'sl-anim-rev'); newSlide.removeEventListener('animationend', endNew); next(); }
123
+ function next(){ if(++done>=2){ busy=false; } }
124
+ oldSlide.addEventListener('animationend', endOld);
125
+ newSlide.addEventListener('animationend', endNew);
126
+ // safety timeout
127
+ setTimeout(function(){ if(busy){ oldSlide.classList.remove('sl-active',outC,'sl-anim-rev'); newSlide.classList.remove(inC,'sl-anim-rev'); busy=false; } }, 1200);
128
+ cur=i; updateChrome(); sync();
129
+ }
130
+
131
+ function forward(){
132
+ if(busy) return;
133
+ if(blanked) blank('');
134
+ if(step < buildCount(cur)){ step++; applyBuilds(slides[cur], step); updateChrome(); sync(); return; }
135
+ goTo(cur+1, 1);
136
+ }
137
+ function backward(){
138
+ if(busy) return;
139
+ if(blanked) blank('');
140
+ if(step > 0){ step--; applyBuilds(slides[cur], step); updateChrome(); sync(); return; }
141
+ goTo(cur-1, -1);
142
+ }
143
+
144
+ function toggle(sel){ var e=document.querySelector(sel); if(e) e.classList.toggle('sl-open'); updateChrome(); }
145
+
146
+ // ---- present extras: screen blank (b/w), presenter view, cross-window sync ----------------
147
+ var blanked = '';
148
+ function blank(kind){
149
+ blanked = kind;
150
+ // While presenting on a second screen, blank the AUDIENCE (projector), not our own control
151
+ // screen: the web presenter window, or the native main that opened an audience window, relays.
152
+ if(AM_PRESENTER || window.__slvHasAudience){
153
+ if(chan){ try{ chan.postMessage({ blank: kind }); }catch(e){} } // web → audience window
154
+ if(window.__slvBlank){ try{ window.__slvBlank(kind); }catch(e){} } // native → Rust → audience window
155
+ return;
156
+ }
157
+ var el = document.getElementById('sl-blank');
158
+ if(!el){ el = document.createElement('div'); el.id = 'sl-blank';
159
+ el.addEventListener('click', function(){ blank(''); }); document.body.appendChild(el); }
160
+ el.className = kind ? ('sl-blank sl-blank-' + kind) : '';
161
+ }
162
+
163
+ // Same-origin sync between the audience window and the presenter window, keyed to this deck.
164
+ var chan = (typeof BroadcastChannel !== 'undefined') ? new BroadcastChannel('slaide:present:' + location.pathname) : null;
165
+ var applyingRemote = false, remoteT = 0;
166
+ function publish(){
167
+ if(applyingRemote) return;
168
+ if(chan){ try{ chan.postMessage({ cur: cur, step: step }); }catch(e){} } // web: cross-tab sync
169
+ if(window.__slvNav){ try{ window.__slvNav(cur, step); }catch(e){} } // native: host relays to the audience window
170
+ }
171
+ function applyRemote(d){
172
+ if(!d || typeof d.cur !== 'number') return;
173
+ if(busy){ clearTimeout(remoteT); remoteT = setTimeout(function(){ applyRemote(d); }, 120); return; } // wait out an in-flight transition
174
+ applyingRemote = true;
175
+ try{
176
+ if(d.cur !== cur){ goTo(d.cur, d.cur > cur ? 1 : -1); }
177
+ if(typeof d.step === 'number' && d.step !== step){ step = Math.max(0, Math.min(buildCount(cur), d.step)); applyBuilds(slides[cur], step); updateChrome(); }
178
+ if(AM_PRESENTER) renderPresenter();
179
+ } finally { applyingRemote = false; }
180
+ }
181
+ if(chan){ chan.onmessage = function(ev){
182
+ var d = ev.data || {};
183
+ if(typeof d.blank === 'string'){ blank(d.blank); return; } // audience: blank/restore the projector
184
+ applyRemote(d);
185
+ }; }
186
+ function sync(){ publish(); if(AM_PRESENTER && document.getElementById('sl-pv')) renderPresenter(); }
187
+
188
+ // ---- presenter view: a second window with the current + next slide, notes, timer + clock ----
189
+ var AM_PRESENTER = /[?&]slvview=presenter/.test(location.search);
190
+ // The audience window (native second-screen present): follows the main window only — no chrome,
191
+ // no local navigation. (Match 'present' exactly, not the 'presenter' prefix.)
192
+ var AM_AUDIENCE = /[?&]slvview=present([&#]|$)/.test(location.search);
193
+ var presenterWin = null, pvStart = 0;
194
+ function presenter(){
195
+ if(AM_PRESENTER) return; // this IS the presenter window
196
+ var u = location.href.replace(/#.*$/, '');
197
+ u += (u.indexOf('?') >= 0 ? '&' : '?') + 'slvview=presenter';
198
+ u += '#' + (cur + 1) + (step ? '.' + step : ''); // open at our current position
199
+ try{ presenterWin = window.open(u, 'slaide-presenter', 'width=1180,height=760,menubar=no,toolbar=no'); }catch(e){}
200
+ }
201
+ function renderSlideInto(frame, idx){
202
+ if(!frame) return; frame.innerHTML = '';
203
+ if(idx == null || idx < 0){ frame.className = 'sl-pv-frame'; return; }
204
+ if(idx >= slides.length){ frame.className = 'sl-pv-frame sl-pv-end'; frame.textContent = 'End of deck'; return; }
205
+ frame.className = 'sl-pv-frame';
206
+ var box = document.createElement('div');
207
+ box.style.cssText = 'position:absolute;top:0;left:0;width:' + CW + 'px;height:' + CH + 'px;transform-origin:0 0;transform:scale(' + ((frame.clientWidth || 480) / CW) + ');';
208
+ var clone = slides[idx].cloneNode(true);
209
+ clone.removeAttribute('id'); clone.classList.add('sl-active'); clone.classList.remove('sl-anim-rev');
210
+ clone.style.cssText += ';position:absolute;top:0;left:0;display:block;visibility:visible;opacity:1;transform:none;';
211
+ applyBuilds(clone, buildCount(idx)); // preview fully built
212
+ box.appendChild(clone); frame.appendChild(box);
213
+ }
214
+ function pad(n){ return (n < 10 ? '0' : '') + n; }
215
+ function fmt(ms){ var s = Math.floor(ms / 1000), h = Math.floor(s / 3600); return (h ? pad(h) + ':' : '') + pad(Math.floor(s / 60) % 60) + ':' + pad(s % 60); }
216
+ function renderPresenter(){
217
+ if(!document.getElementById('sl-pv')) return;
218
+ renderSlideInto(document.getElementById('sl-pv-cur-frame'), cur);
219
+ renderSlideInto(document.getElementById('sl-pv-next-frame'), cur + 1);
220
+ var nb = document.getElementById('sl-pv-notes-body');
221
+ if(nb){ var n = notes[cur]; nb.innerHTML = (n && String(n).trim()) ? String(n) : '<span class="sl-pv-dim">No notes for this slide.</span>'; }
222
+ var cc = document.getElementById('sl-pv-count'); if(cc) cc.textContent = (cur + 1) + ' / ' + slides.length;
223
+ }
224
+ function buildPresenter(){
225
+ document.body.classList.add('sl-presenter');
226
+ var pv = document.createElement('div'); pv.id = 'sl-pv';
227
+ pv.innerHTML =
228
+ '<div class="sl-pv-main">' +
229
+ '<div class="sl-pv-cur"><div class="sl-pv-lbl">Current</div><div class="sl-pv-frame" id="sl-pv-cur-frame"></div></div>' +
230
+ '<div class="sl-pv-side">' +
231
+ '<div class="sl-pv-next"><div class="sl-pv-lbl">Next</div><div class="sl-pv-frame" id="sl-pv-next-frame"></div></div>' +
232
+ '<div class="sl-pv-notes"><div class="sl-pv-lbl">Notes</div><div id="sl-pv-notes-body"></div></div>' +
233
+ '</div>' +
234
+ '</div>' +
235
+ '<div class="sl-pv-bar">' +
236
+ '<button class="sl-pv-btn" id="sl-pv-prev" title="Previous (Left)">&#8249;</button>' +
237
+ '<button class="sl-pv-btn" id="sl-pv-nextb" title="Next (Right)">&#8250;</button>' +
238
+ '<span class="sl-pv-count" id="sl-pv-count"></span>' +
239
+ '<span class="sl-pv-timer" id="sl-pv-timer">00:00</span>' +
240
+ '<span class="sl-pv-clock" id="sl-pv-clock"></span>' +
241
+ '</div>';
242
+ document.body.appendChild(pv);
243
+ document.getElementById('sl-pv-prev').addEventListener('click', backward);
244
+ document.getElementById('sl-pv-nextb').addEventListener('click', forward);
245
+ pvStart = Date.now();
246
+ setInterval(function(){
247
+ var t = document.getElementById('sl-pv-timer'); if(t) t.textContent = fmt(Date.now() - pvStart);
248
+ var c = document.getElementById('sl-pv-clock'); if(c){ var d = new Date(); c.textContent = pad(d.getHours()) + ':' + pad(d.getMinutes()); }
249
+ }, 1000);
250
+ window.addEventListener('resize', renderPresenter);
251
+ renderPresenter();
252
+ }
253
+ (function injectPvCss(){
254
+ var css = '#sl-blank{position:fixed;inset:0;z-index:2147483646;cursor:none;display:none;}'
255
+ + '#sl-blank.sl-blank-b{display:block;background:#000;}#sl-blank.sl-blank-w{display:block;background:#fff;}'
256
+ + 'body.sl-presenter>*:not(#sl-pv){display:none !important;}'
257
+ + '#sl-pv{position:fixed;inset:0;z-index:2147483645;display:flex;flex-direction:column;background:#0d0d12;color:#e9e9f1;font:14px/1.45 system-ui,-apple-system,"Segoe UI",sans-serif;}'
258
+ + '.sl-pv-main{flex:1;display:flex;min-height:0;gap:18px;padding:18px;}'
259
+ + '.sl-pv-cur{flex:1.7;display:flex;flex-direction:column;min-width:0;}'
260
+ + '.sl-pv-side{flex:1;display:flex;flex-direction:column;gap:14px;min-width:0;}'
261
+ + '.sl-pv-lbl{font:600 10.5px system-ui;letter-spacing:.12em;text-transform:uppercase;color:#7d8398;margin:0 0 7px;}'
262
+ + '.sl-pv-frame{position:relative;width:100%;aspect-ratio:' + CW + '/' + CH + ';background:#000;border-radius:11px;overflow:hidden;box-shadow:0 10px 34px -12px rgba(0,0,0,.7);}'
263
+ + '.sl-pv-frame.sl-pv-end{display:flex;align-items:center;justify-content:center;color:#5f6478;font-weight:600;letter-spacing:.04em;}'
264
+ + '.sl-pv-next{flex:none;}'
265
+ + '.sl-pv-notes{flex:1;min-height:0;display:flex;flex-direction:column;}'
266
+ + '#sl-pv-notes-body{flex:1;overflow:auto;background:#16161f;border-radius:11px;padding:15px 17px;font-size:17px;line-height:1.5;}'
267
+ + '.sl-pv-dim{color:#6b7086;}'
268
+ + '.sl-pv-bar{flex:none;height:58px;display:flex;align-items:center;gap:16px;padding:0 22px;background:#15151e;border-top:1px solid rgba(255,255,255,.08);}'
269
+ + '.sl-pv-btn{all:unset;cursor:pointer;width:42px;height:38px;display:inline-flex;align-items:center;justify-content:center;border-radius:9px;background:rgba(255,255,255,.06);color:#e9e9f1;font-size:22px;line-height:1;}'
270
+ + '.sl-pv-btn:hover{background:rgba(255,255,255,.13);}'
271
+ + '.sl-pv-count{font-weight:600;font-size:15px;}'
272
+ + '.sl-pv-timer{font-variant-numeric:tabular-nums;font-size:19px;font-weight:600;}'
273
+ + '.sl-pv-clock{margin-left:auto;color:#7d8398;font-variant-numeric:tabular-nums;font-size:15px;}';
274
+ var st = document.createElement('style'); st.textContent = css; document.head.appendChild(st);
275
+ })();
276
+
277
+ document.addEventListener('keydown', function(e){
278
+ // Presentation shortcuts fire regardless of focus (PowerPoint parity): F5 presents from the
279
+ // top, Shift+F5 from the current slide, Esc always stops. Handled before the typing guard so
280
+ // they work even mid-edit, and before nav so Esc-to-stop wins over Esc-to-close-panels.
281
+ if(e.key === 'F5'){ e.preventDefault(); setPresenting(true, e.shiftKey); return; }
282
+ if(e.key === 'Escape'){ // Esc clears a blank screen first, then stops presenting
283
+ if(blanked){ e.preventDefault(); blank(''); return; }
284
+ if(isPresenting()){ e.preventDefault(); setPresenting(false); return; }
285
+ }
286
+ if(AM_AUDIENCE) return; // the audience window only mirrors — no local nav
287
+ // When focus is in an editable field, keystrokes belong to it (typing a space,
288
+ // moving the caret) — don't hijack them for slide navigation.
289
+ if(isTyping()) return;
290
+ switch(e.key){
291
+ case 'ArrowRight': case 'ArrowUp': case ' ': case 'PageDown': forward(); e.preventDefault(); break;
292
+ case 'ArrowLeft': case 'ArrowDown': case 'PageUp': backward(); e.preventDefault(); break;
293
+ case 'Home': goTo(0,1); break;
294
+ case 'End': goTo(slides.length-1,1); break;
295
+ case 'n': case 'N': toggle('.sl-notes'); break;
296
+ case 'f': case 'F': setPresenting(!isPresenting(), true); break;
297
+ case 'p': case 'P': presenter(); break;
298
+ case 'b': case 'B': blank(blanked==='b'?'':'b'); e.preventDefault(); break;
299
+ case 'w': case 'W': blank(blanked==='w'?'':'w'); e.preventDefault(); break;
300
+ case '?': case 'h': toggle('.sl-help'); break;
301
+ case 'Escape': document.querySelectorAll('.sl-open').forEach(function(e){e.classList.remove('sl-open');}); break;
302
+ }
303
+ });
304
+ // ---- drag-to-pan (only when zoomed in past fit) -------------------------
305
+ var dragging=false, dragMoved=false, dragX=0, dragY=0;
306
+ if(vp){
307
+ vp.addEventListener('pointerdown', function(e){
308
+ if(zoomFactor<=1 || !navEnabled) return;
309
+ dragging=true; dragMoved=false; dragX=e.clientX; dragY=e.clientY;
310
+ try{ vp.setPointerCapture(e.pointerId); }catch(_){}
311
+ vp.style.cursor='grabbing';
312
+ });
313
+ vp.addEventListener('pointermove', function(e){
314
+ if(!dragging) return;
315
+ var dx=e.clientX-dragX, dy=e.clientY-dragY;
316
+ if(Math.abs(dx)+Math.abs(dy)>3) dragMoved=true;
317
+ panX+=dx; panY+=dy; dragX=e.clientX; dragY=e.clientY; scale();
318
+ });
319
+ function endDrag(e){ if(!dragging) return; dragging=false; vp.style.cursor=zoomFactor>1?'grab':''; try{ vp.releasePointerCapture(e.pointerId); }catch(_){} }
320
+ vp.addEventListener('pointerup', endDrag);
321
+ vp.addEventListener('pointercancel', endDrag);
322
+ vp.addEventListener('click', function(e){
323
+ if(!navEnabled || isTyping()) return; // nav disabled, or focus is in a field
324
+ if(dragMoved){ dragMoved=false; return; } // finished a pan, not a tap
325
+ var sel = window.getSelection && window.getSelection();
326
+ if(sel && !sel.isCollapsed && String(sel)) return; // user is selecting text — let them, don't advance
327
+ if(e.target.closest('a')) return;
328
+ if(e.target.closest('.sl-notes,.sl-help,.sl-counter')) return;
329
+ forward();
330
+ });
331
+ }
332
+ window.addEventListener('resize', scale);
333
+
334
+ // ---- present mode — Fullscreen API by default; a host (native viewer / web ribbon) can take
335
+ // it over via window.__slvPresentHook (the desktop app drives an OS-window fullscreen over
336
+ // IPC, where document.fullscreenElement never sets — hence the class-based check below).
337
+ function isPresenting(){
338
+ return !!document.fullscreenElement
339
+ || document.body.classList.contains('sl-presenting')
340
+ || document.body.classList.contains('slv-presenting');
341
+ }
342
+ function setPresenting(on, fromCurrent){
343
+ if(on && !fromCurrent) goTo(0, 1); // F5 / present button → start from the top
344
+ if(typeof window.__slvPresentHook === 'function'){ window.__slvPresentHook(!!on); return; }
345
+ if(on){ if(!document.fullscreenElement && document.documentElement.requestFullscreen) document.documentElement.requestFullscreen(); }
346
+ else if(document.fullscreenElement && document.exitFullscreen){ document.exitFullscreen(); }
347
+ }
348
+ document.addEventListener('fullscreenchange', function(){
349
+ document.body.classList.toggle('sl-presenting', !!document.fullscreenElement);
350
+ });
351
+ var presentBtn = document.querySelector('.sl-present-toggle');
352
+ if(presentBtn) presentBtn.addEventListener('click', function(){ setPresenting(!isPresenting(), true); });
353
+
354
+ // Public control API (embedding + testing).
355
+ window.slaide = {
356
+ next: forward,
357
+ prev: backward,
358
+ goTo: function(i){ goTo(i, i>cur?1:-1); },
359
+ relayout: function(){ scale(); }, // recompute the stage fit (e.g. after the dock opens/closes)
360
+ show: function(i){ if(i<0||i>=slides.length) return; activate(i, true); cur=i; updateChrome(); },
361
+ setInteractive: function(on){ navEnabled = on!==false; },
362
+ toggleNotes: function(){ toggle('.sl-notes'); },
363
+ toggleHelp: function(){ toggle('.sl-help'); },
364
+ zoom: function(f){
365
+ zoomFactor = (f==='fit'||!f) ? 1 : Math.max(0.25, Math.min(8, f));
366
+ if(zoomFactor===1){ panX=panY=0; }
367
+ if(vp) vp.style.cursor = zoomFactor>1 ? 'grab' : '';
368
+ scale();
369
+ return zoomFactor;
370
+ },
371
+ zoomBy: function(d){ return window.slaide.zoom(zoomFactor*d); },
372
+ pan: function(dx,dy){ panX+=dx; panY+=dy; scale(); },
373
+ present: function(on, fromCurrent){ setPresenting(on!==false, fromCurrent); },
374
+ presenter: presenter, // open the separate presenter view
375
+ blank: function(k){ blank(k==='b'||k==='w' ? k : ''); }, // 'b' | 'w' | '' (clear)
376
+ __applyRemote: function(c, s){ applyRemote({ cur: c, step: s }); }, // host pushes the audience window in sync
377
+ get zoomLevel(){ return zoomFactor; },
378
+ get index(){ return cur; },
379
+ get count(){ return slides.length; }
380
+ };
381
+
382
+ // initial state from hash
383
+ var m = (location.hash||'').match(/#(\d+)(?:\.(\d+))?/);
384
+ if(m){ cur = Math.min(slides.length-1, Math.max(0, parseInt(m[1],10)-1)); }
385
+ slides.forEach(function(s,idx){ s.classList.toggle('sl-active', idx===cur); });
386
+ step = m && m[2] ? Math.min(buildCount(cur), parseInt(m[2],10)) : 0;
387
+ applyBuilds(slides[cur], step);
388
+ scale(); updateChrome();
389
+ if(AM_PRESENTER) buildPresenter(); // this window is the presenter view
390
+ if(AM_AUDIENCE){ document.body.classList.add('slv-presenting'); navEnabled = false; } // clean mirror, host-driven
391
+ })();
392
+ `;
393
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/render/runtime.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,sCAAsC;AACtC,8EAA8E;AAC9E,uFAAuF;AACvF,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmYnC,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface PdfOptions {
2
+ /** Output PDF path. */
3
+ out: string;
4
+ }
5
+ export declare function renderPdfFromHtml(html: string, out: string, canvas?: {
6
+ width: number;
7
+ height: number;
8
+ }, injected?: import('playwright').Browser): Promise<string>;
9
+ export declare function renderPdf(deckPath: string, opts: PdfOptions): Promise<string>;
@@ -0,0 +1,53 @@
1
+ // Copyright 2026 AIVory, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // @slaide/render-pdf — Playwright headless-Chromium print of the web runtime
4
+ // in print/collapse mode. Reuses the same settled-state markup as the runtime,
5
+ // so web and PDF cannot structurally diverge.
6
+ import { renderDeckHtml } from '../index.js';
7
+ import { readFileSync } from 'node:fs';
8
+ import { dirname, resolve } from 'node:path';
9
+ async function loadPlaywright() {
10
+ try {
11
+ return await import('playwright');
12
+ }
13
+ catch {
14
+ throw new Error('PDF export needs Playwright. Install it:\n' +
15
+ ' npm install playwright\n' +
16
+ ' npx playwright install chromium');
17
+ }
18
+ }
19
+ export async function renderPdfFromHtml(html, out, canvas, injected) {
20
+ const { chromium } = await loadPlaywright();
21
+ // Use a pooled browser when one is injected (engine-service) — and never close it then;
22
+ // otherwise launch a throwaway one for this single export (CLI path), unchanged.
23
+ const browser = injected ?? (await chromium.launch());
24
+ try {
25
+ // Match the viewport to the canvas so layout-measured content (e.g. charts sizing to
26
+ // their slot) is measured at the real design size, not the default 1280×720.
27
+ const page = await browser.newPage(canvas ? { viewport: { width: canvas.width, height: canvas.height } } : {});
28
+ await page.setContent(html, { waitUntil: 'networkidle' });
29
+ // Ensure web fonts are ready before printing.
30
+ await page.evaluate(() => document.fonts?.ready);
31
+ // Render charts (lazy) and wait until they settle so they appear in the PDF.
32
+ await page.evaluate(() => window.__slaideCharts?.renderAll());
33
+ await page
34
+ .waitForFunction(() => window.__slaideChartsReady === true, { timeout: 8000 })
35
+ .catch(() => { });
36
+ await page.pdf({
37
+ path: out,
38
+ preferCSSPageSize: true,
39
+ printBackground: true,
40
+ });
41
+ return out;
42
+ }
43
+ finally {
44
+ if (!injected)
45
+ await browser.close();
46
+ }
47
+ }
48
+ export async function renderPdf(deckPath, opts) {
49
+ const source = readFileSync(deckPath, 'utf8');
50
+ const { html, ir } = renderDeckHtml(source, dirname(resolve(deckPath)), { mode: 'print', inline: true });
51
+ return renderPdfFromHtml(html, opts.out, ir.canvas);
52
+ }
53
+ //# sourceMappingURL=pdf.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf.js","sourceRoot":"","sources":["../../src/render-pdf/pdf.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,sCAAsC;AACtC,6EAA6E;AAC7E,+EAA+E;AAC/E,8CAA8C;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAO7C,KAAK,UAAU,cAAc;IAC3B,IAAI,CAAC;QACH,OAAO,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,4CAA4C;YAC1C,4BAA4B;YAC5B,mCAAmC,CACtC,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAY,EACZ,GAAW,EACX,MAA0C,EAC1C,QAAuC;IAEvC,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,cAAc,EAAE,CAAC;IAC5C,wFAAwF;IACxF,iFAAiF;IACjF,MAAM,OAAO,GAAG,QAAQ,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC;QACH,qFAAqF;QACrF,6EAA6E;QAC7E,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,CAChC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAC3E,CAAC;QACF,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,CAAC,CAAC;QAC1D,8CAA8C;QAC9C,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAE,QAAgB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC1D,6EAA6E;QAC7E,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAE,MAAc,CAAC,cAAc,EAAE,SAAS,EAAE,CAAC,CAAC;QACvE,MAAM,IAAI;aACP,eAAe,CAAC,GAAG,EAAE,CAAE,MAAc,CAAC,mBAAmB,KAAK,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;aACtF,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnB,MAAM,IAAI,CAAC,GAAG,CAAC;YACb,IAAI,EAAE,GAAG;YACT,iBAAiB,EAAE,IAAI;YACvB,eAAe,EAAE,IAAI;SACtB,CAAC,CAAC;QACH,OAAO,GAAG,CAAC;IACb,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,QAAQ;YAAE,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACvC,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAgB,EAAE,IAAgB;IAChE,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9C,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;IACzG,OAAO,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;AACtD,CAAC"}
@@ -0,0 +1,14 @@
1
+ export interface MontageOptions {
2
+ out?: string;
3
+ cols?: number;
4
+ tileWidth?: number;
5
+ quality?: number;
6
+ /** A pooled Chromium browser to reuse; when omitted a throwaway one is launched + closed. */
7
+ browser?: import('playwright').Browser;
8
+ }
9
+ export declare function montageDeck(deckPath: string, opts?: MontageOptions): Promise<{
10
+ out: string;
11
+ slides: number;
12
+ width: number;
13
+ height: number;
14
+ }>;
@@ -0,0 +1,75 @@
1
+ // Copyright 2026 AIVory, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ // @slaide/render-png — tile every slide into ONE small contact sheet. This is the token-cheap
4
+ // "see it" loop: an agent reads a single image of all slides to catch overlapping/clipped text
5
+ // and broken layout, instead of N full-res shots. Defaults to JPEG (smaller + faster to read);
6
+ // pass a .png path for lossless. Both the tiling and the encode happen in the browser, so there
7
+ // is no image-codec dependency.
8
+ import { renderDeckHtml } from '../index.js';
9
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
10
+ import { dirname, resolve } from 'node:path';
11
+ async function loadPlaywright() {
12
+ try {
13
+ return await import('playwright');
14
+ }
15
+ catch {
16
+ throw new Error('montage needs Playwright. Install it:\n npm install playwright\n npx playwright install chromium');
17
+ }
18
+ }
19
+ export async function montageDeck(deckPath, opts = {}) {
20
+ const out = opts.out ?? 'out/montage.jpg';
21
+ const cols = Math.max(1, opts.cols ?? 3);
22
+ const tileW = opts.tileWidth ?? 360;
23
+ const gap = 8;
24
+ const jpeg = /\.jpe?g$/i.test(out);
25
+ const quality = Math.max(1, Math.min(100, opts.quality ?? 80));
26
+ const src = readFileSync(deckPath, 'utf8');
27
+ const { html, ir } = renderDeckHtml(src, dirname(resolve(deckPath)), { mode: 'web', inline: true });
28
+ const { chromium } = await loadPlaywright();
29
+ const browser = opts.browser ?? (await chromium.launch());
30
+ try {
31
+ const page = await browser.newPage({ viewport: { width: ir.canvas.width, height: ir.canvas.height }, deviceScaleFactor: 1 });
32
+ const doc = html.replace('</head>', '<style>.sl-counter,.sl-progress,.sl-notes,.sl-help,.sl-present-toggle{display:none !important;}</style></head>');
33
+ await page.setContent(doc, { waitUntil: 'networkidle' });
34
+ await page.evaluate(() => document.fonts?.ready);
35
+ await page.evaluate(() => window.__slaideCharts?.renderAll());
36
+ await page.waitForFunction(() => window.__slaideChartsReady === true, { timeout: 8000 }).catch(() => { });
37
+ await page.waitForTimeout(400);
38
+ // Capture each slide (builds settled) as a light JPEG, then tile + encode in one canvas.
39
+ const shots = [];
40
+ for (let i = 0; i < ir.slides.length; i++) {
41
+ await page.evaluate((n) => window.slaide.show(n), i);
42
+ await page.waitForTimeout(150);
43
+ shots.push((await page.screenshot({ type: 'jpeg', quality: 90 })).toString('base64'));
44
+ }
45
+ if (!shots.length)
46
+ throw new Error('montage: deck rendered no slides');
47
+ const dataUrl = (await page.evaluate(async ({ shots, cols, tileW, gap, jpeg, quality }) => {
48
+ const imgs = await Promise.all(shots.map((b64) => new Promise((res) => { const im = new Image(); im.onload = () => res(im); im.src = 'data:image/jpeg;base64,' + b64; })));
49
+ const tileH = Math.round((imgs[0].naturalHeight / imgs[0].naturalWidth) * tileW);
50
+ const rows = Math.ceil(imgs.length / cols);
51
+ const W = cols * tileW + (cols + 1) * gap;
52
+ const H = rows * tileH + (rows + 1) * gap;
53
+ const cv = document.createElement('canvas');
54
+ cv.width = W;
55
+ cv.height = H;
56
+ const ctx = cv.getContext('2d');
57
+ ctx.imageSmoothingEnabled = true;
58
+ ctx.imageSmoothingQuality = 'high';
59
+ ctx.fillStyle = '#f8f9fb';
60
+ ctx.fillRect(0, 0, W, H);
61
+ imgs.forEach((im, i) => { const cx = gap + (i % cols) * (tileW + gap), cy = gap + Math.floor(i / cols) * (tileH + gap); ctx.drawImage(im, cx, cy, tileW, tileH); });
62
+ return jpeg ? cv.toDataURL('image/jpeg', quality / 100) : cv.toDataURL('image/png');
63
+ }, { shots, cols, tileW, gap, jpeg, quality }));
64
+ mkdirSync(dirname(resolve(out)), { recursive: true });
65
+ writeFileSync(out, Buffer.from(dataUrl.split(',')[1], 'base64'));
66
+ const tileH = Math.round((ir.canvas.height / ir.canvas.width) * tileW);
67
+ const rows = Math.ceil(shots.length / cols);
68
+ return { out, slides: shots.length, width: cols * tileW + (cols + 1) * gap, height: rows * tileH + (rows + 1) * gap };
69
+ }
70
+ finally {
71
+ if (!opts.browser)
72
+ await browser.close();
73
+ }
74
+ }
75
+ //# sourceMappingURL=montage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"montage.js","sourceRoot":"","sources":["../../src/render-png/montage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { DeckIR } from '../types.js';
2
+ export interface ShootOptions {
3
+ /** Output directory (default 'out/shots'). One PNG per slide is written there. */
4
+ out?: string;
5
+ /** Capture width/height in CSS px (default: the deck's canvas size). */
6
+ width?: number;
7
+ height?: number;
8
+ /** deviceScaleFactor for crisper output (default 1). */
9
+ scale?: number;
10
+ /** Hide the runtime's page counter / progress bar so they don't pollute output. */
11
+ hideChrome?: boolean;
12
+ /** Filename prefix (default 'slide' → slide-01.png, slide-02.png, …). */
13
+ tag?: string;
14
+ /** Image format (default 'png'). 'jpeg' is smaller + faster — good for the see-it loop. */
15
+ format?: 'png' | 'jpeg';
16
+ /** JPEG quality 1-100 (default 82); ignored for PNG. */
17
+ quality?: number;
18
+ /** A pooled Chromium browser to reuse (engine-service). When set it is NOT closed here;
19
+ * when omitted, a throwaway browser is launched and closed for this call (CLI path). */
20
+ browser?: import('playwright').Browser;
21
+ }
22
+ /** Render a self-contained web-deck HTML + its IR to per-slide PNGs (builds settled). */
23
+ export declare function shootHtml(html: string, ir: DeckIR, opts?: ShootOptions): Promise<string[]>;
24
+ /** Render every slide of a deck file to <out>/<tag>-NN.png. Returns the file paths. */
25
+ export declare function shootDeck(deckPath: string, opts?: ShootOptions): Promise<string[]>;