pixi-effects 0.28.2 → 0.30.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 (93) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +11 -10
  3. package/ai/chat-template.html +5 -5
  4. package/ai/tools/check.mjs +18 -3
  5. package/ai/tools/lookup.mjs +208 -0
  6. package/ai/tools/pixi-effects.mjs +16 -5
  7. package/ai/tools/render.mjs +14 -9
  8. package/ai/tools/view.mjs +6 -2
  9. package/dist/Base-CB1_FPZD.d.cts +60 -0
  10. package/dist/Base-Ne74ztNl.d.ts +60 -0
  11. package/dist/Composition-MUKR3FYD.cjs +16 -0
  12. package/dist/{Composition-K6B3APM5.cjs.map → Composition-MUKR3FYD.cjs.map} +1 -1
  13. package/dist/Composition-RJGA7KDU.js +7 -0
  14. package/dist/{Composition-H7UNR5F5.js.map → Composition-RJGA7KDU.js.map} +1 -1
  15. package/dist/Controller.cjs +73 -45
  16. package/dist/Controller.cjs.map +1 -1
  17. package/dist/Controller.d.cts +16 -22
  18. package/dist/Controller.d.ts +16 -22
  19. package/dist/Controller.js +74 -40
  20. package/dist/Controller.js.map +1 -1
  21. package/dist/{Movie-CHcM8HNB.d.ts → Movie-C2rZFkrO.d.ts} +227 -119
  22. package/dist/{Movie-Cx5ozUGb.d.cts → Movie-DdnpLpw1.d.cts} +227 -119
  23. package/dist/{blendModes-HDOP6R7T.cjs → blendModes-ETMYWATF.cjs} +5 -5
  24. package/dist/{blendModes-HDOP6R7T.cjs.map → blendModes-ETMYWATF.cjs.map} +1 -1
  25. package/dist/{blendModes-PAVTW3BO.js → blendModes-RIVZXL5A.js} +4 -4
  26. package/dist/{blendModes-PAVTW3BO.js.map → blendModes-RIVZXL5A.js.map} +1 -1
  27. package/dist/{chunk-P4GY3Z4G.cjs → chunk-2DXPTKQZ.cjs} +6 -6
  28. package/dist/chunk-2DXPTKQZ.cjs.map +1 -0
  29. package/dist/{chunk-TYWFKCPY.js → chunk-4KB3GUEK.js} +5 -5
  30. package/dist/chunk-4KB3GUEK.js.map +1 -0
  31. package/dist/{chunk-5MZWXVTT.js → chunk-A3Q3SDSH.js} +35 -28
  32. package/dist/chunk-A3Q3SDSH.js.map +1 -0
  33. package/dist/chunk-CRQFDY7E.js +4182 -0
  34. package/dist/chunk-CRQFDY7E.js.map +1 -0
  35. package/dist/chunk-HREBQAY4.cjs +4206 -0
  36. package/dist/chunk-HREBQAY4.cjs.map +1 -0
  37. package/dist/{chunk-ZMAD3KJU.cjs → chunk-LYOKH7KE.cjs} +22 -3
  38. package/dist/chunk-LYOKH7KE.cjs.map +1 -0
  39. package/dist/{chunk-45QKLD6M.js → chunk-RCVZX2JP.js} +22 -3
  40. package/dist/chunk-RCVZX2JP.js.map +1 -0
  41. package/dist/{chunk-KBH33IQX.cjs → chunk-WHNADXXG.cjs} +121 -114
  42. package/dist/chunk-WHNADXXG.cjs.map +1 -0
  43. package/dist/{chunk-7VN2KI3U.js → chunk-ZAXYPXKW.js} +14 -6
  44. package/dist/chunk-ZAXYPXKW.js.map +1 -0
  45. package/dist/{chunk-MJW7KILM.cjs → chunk-ZVUO325O.cjs} +32 -24
  46. package/dist/chunk-ZVUO325O.cjs.map +1 -0
  47. package/dist/index.cjs +106 -4482
  48. package/dist/index.cjs.map +1 -1
  49. package/dist/index.d.cts +15 -60
  50. package/dist/index.d.ts +15 -60
  51. package/dist/index.js +6 -4457
  52. package/dist/index.js.map +1 -1
  53. package/dist/loader.css +1 -1
  54. package/dist/{Presenter.cjs → present.cjs} +370 -31
  55. package/dist/present.cjs.map +1 -0
  56. package/dist/present.d.cts +278 -0
  57. package/dist/present.d.ts +278 -0
  58. package/dist/{Presenter.js → present.js} +358 -32
  59. package/dist/present.js.map +1 -0
  60. package/dist/three.cjs +6 -6
  61. package/dist/three.d.cts +2 -2
  62. package/dist/three.d.ts +2 -2
  63. package/dist/three.js +2 -2
  64. package/dist/{types-DpK1vwcl.d.ts → types-BzIB65Vk.d.cts} +11 -265
  65. package/dist/{types-DpK1vwcl.d.cts → types-BzIB65Vk.d.ts} +11 -265
  66. package/docs/api.md +44 -77
  67. package/docs/dsl.md +16 -14
  68. package/llms-full.txt +121 -117
  69. package/llms.txt +3 -2
  70. package/package.json +5 -5
  71. package/skills/pixi-effects/SKILL.md +7 -7
  72. package/skills/pixi-effects/reference/cheatsheet.md +9 -9
  73. package/skills/pixi-effects/reference/direction.md +2 -0
  74. package/skills/pixi-effects/reference/pitfalls.md +6 -6
  75. package/skills/pixi-effects/reference/quickstart.md +29 -0
  76. package/skills/pixi-effects/reference/recipes.md +4 -4
  77. package/skills/pixi-effects/template.html +7 -7
  78. package/dist/Base-C6f0fE8j.d.ts +0 -109
  79. package/dist/Base-DI1FCGcH.d.cts +0 -109
  80. package/dist/Composition-H7UNR5F5.js +0 -7
  81. package/dist/Composition-K6B3APM5.cjs +0 -16
  82. package/dist/Presenter.cjs.map +0 -1
  83. package/dist/Presenter.d.cts +0 -122
  84. package/dist/Presenter.d.ts +0 -122
  85. package/dist/Presenter.js.map +0 -1
  86. package/dist/chunk-45QKLD6M.js.map +0 -1
  87. package/dist/chunk-5MZWXVTT.js.map +0 -1
  88. package/dist/chunk-7VN2KI3U.js.map +0 -1
  89. package/dist/chunk-KBH33IQX.cjs.map +0 -1
  90. package/dist/chunk-MJW7KILM.cjs.map +0 -1
  91. package/dist/chunk-P4GY3Z4G.cjs.map +0 -1
  92. package/dist/chunk-TYWFKCPY.js.map +0 -1
  93. package/dist/chunk-ZMAD3KJU.cjs.map +0 -1
package/dist/loader.css CHANGED
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * pixi-effects loader: one line of HTML on the page, a few lines of CSS, no JavaScript of your own.
3
3
  *
4
- * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.28.2/dist/loader.css">
4
+ * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.30.0/dist/loader.css">
5
5
  * <div class="stage" style="position: relative"> <!-- any box the canvas sits in, with position: relative -->
6
6
  * <canvas id="stage" width="1280" height="720"></canvas>
7
7
  * <div class="pe-loader" data-label="LOADING"></div> <!-- or class="pe-loader pe-loader--bar" -->
@@ -1,8 +1,270 @@
1
1
  'use strict';
2
2
 
3
+ require('./chunk-HREBQAY4.cjs');
4
+ require('./chunk-WHNADXXG.cjs');
5
+ require('./chunk-ZVUO325O.cjs');
6
+ require('./chunk-2DXPTKQZ.cjs');
7
+ require('./chunk-LYOKH7KE.cjs');
3
8
  var chunkTWZNOUM7_cjs = require('./chunk-TWZNOUM7.cjs');
4
9
 
5
- // src/Presenter.ts
10
+ // src/present/stops.ts
11
+ function nextStopAfter(stops, frame) {
12
+ return stops.find((s) => s.frame > frame) ?? null;
13
+ }
14
+ function previousStopBefore(stops, frame) {
15
+ for (let i = stops.length - 1; i >= 0; i--) if (stops[i].frame < frame) return stops[i];
16
+ return null;
17
+ }
18
+ function stopAtOrBefore(stops, frame) {
19
+ for (let i = stops.length - 1; i >= 0; i--) if (stops[i].frame <= frame) return stops[i];
20
+ return null;
21
+ }
22
+ function pageStarts(stops) {
23
+ return stops.filter((s) => s.pageStart);
24
+ }
25
+ function pictureStops(stops, opts = {}) {
26
+ if (opts.which === "stops") return opts.pdf ? stops.filter((s) => s.pdf !== false) : [...stops];
27
+ const byPage = /* @__PURE__ */ new Map();
28
+ for (const s of stops) byPage.set(s.pageIndex, [...byPage.get(s.pageIndex) ?? [], s]);
29
+ const take = (list) => opts.pick === "first" ? list[0] : list[list.length - 1];
30
+ const picks = [];
31
+ for (const list of byPage.values()) {
32
+ const flagged = list.filter((s) => s.pdf === true);
33
+ const usable = list.filter((s) => s.pdf !== false);
34
+ const one = take(flagged.length ? flagged : usable.length ? usable : opts.pdf ? [] : list);
35
+ if (one) picks.push(one);
36
+ }
37
+ return picks;
38
+ }
39
+ function changedFraction(a, b, threshold = 12) {
40
+ const n = Math.min(a.length, b.length) >> 2;
41
+ if (n === 0) return 0;
42
+ let changed = 0;
43
+ for (let i = 0; i < n; i++) {
44
+ const k = i << 2;
45
+ if (Math.abs(a[k] - b[k]) > threshold || Math.abs(a[k + 1] - b[k + 1]) > threshold || Math.abs(a[k + 2] - b[k + 2]) > threshold) changed++;
46
+ }
47
+ return changed / n;
48
+ }
49
+
50
+ // src/present/navigation.ts
51
+ var listeners = /* @__PURE__ */ new WeakMap();
52
+ var toward = /* @__PURE__ */ new WeakMap();
53
+ function onStop(movie, fn) {
54
+ let set = listeners.get(movie);
55
+ if (!set) {
56
+ set = /* @__PURE__ */ new Set();
57
+ listeners.set(movie, set);
58
+ }
59
+ set.add(fn);
60
+ return () => {
61
+ set.delete(fn);
62
+ };
63
+ }
64
+ function emit(movie, stop) {
65
+ const e = { index: stop.index, stop, pageIndex: stop.pageIndex };
66
+ for (const fn of [...listeners.get(movie) ?? []]) fn(e);
67
+ }
68
+ var stopIndex = (movie) => stopAtOrBefore(movie.stops, movie.currentFrame)?.index ?? -1;
69
+ var currentStop = (movie) => stopAtOrBefore(movie.stops, movie.currentFrame);
70
+ var pageIndex = (movie) => currentStop(movie)?.pageIndex ?? -1;
71
+ var pageCount = (movie) => pageStarts(movie.stops).length;
72
+ async function next(movie) {
73
+ if (!movie.isReady) return;
74
+ const going = toward.get(movie);
75
+ if (going && movie.isPlaying) {
76
+ toward.delete(movie);
77
+ movie.pause();
78
+ await movie.gotoFrame(going.frame, true);
79
+ emit(movie, going);
80
+ return;
81
+ }
82
+ toward.delete(movie);
83
+ if (movie.currentFrame >= movie.totalFrames) return;
84
+ const target = nextStopAfter(movie.stops, movie.currentFrame);
85
+ if (!target) {
86
+ await movie.playUntil();
87
+ return;
88
+ }
89
+ toward.set(movie, target);
90
+ const landed = await movie.playUntil(target.frame / movie.frameRate);
91
+ if (toward.get(movie) === target) toward.delete(movie);
92
+ if (landed) emit(movie, target);
93
+ }
94
+ async function prev(movie) {
95
+ if (!movie.isReady) return;
96
+ toward.delete(movie);
97
+ movie.pause();
98
+ const target = previousStopBefore(movie.stops, movie.currentFrame);
99
+ await movie.gotoFrame(target ? target.frame : 0, true);
100
+ if (target) emit(movie, target);
101
+ }
102
+ async function goToStop(movie, index) {
103
+ if (!movie.isReady) return;
104
+ const stop = movie.stops[index];
105
+ if (!stop) {
106
+ console.warn(`pixi-effects: goToStop(movie, ${index}): there are ${movie.stops.length} stops (0 to ${movie.stops.length - 1})`);
107
+ return;
108
+ }
109
+ toward.delete(movie);
110
+ movie.pause();
111
+ await movie.gotoFrame(stop.frame, true);
112
+ emit(movie, stop);
113
+ }
114
+ async function goToPage(movie, page) {
115
+ const starts = pageStarts(movie.stops);
116
+ const stop = starts[page];
117
+ if (!stop) {
118
+ console.warn(`pixi-effects: goToPage(movie, ${page}): there are ${starts.length} pages (0 to ${starts.length - 1})`);
119
+ return;
120
+ }
121
+ await goToStop(movie, stop.index);
122
+ }
123
+
124
+ // src/present/pdf.ts
125
+ var enc = (s) => {
126
+ const out = new Uint8Array(s.length);
127
+ for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 255;
128
+ return out;
129
+ };
130
+ var num = (n) => String(Math.round(n * 1e3) / 1e3);
131
+ var textString = (s) => {
132
+ if (/^[\x20-\x7e]*$/.test(s)) return `(${s.replace(/[\\()]/g, (m) => `\\${m}`)})`;
133
+ let hex = "FEFF";
134
+ for (let i = 0; i < s.length; i++) hex += s.charCodeAt(i).toString(16).toUpperCase().padStart(4, "0");
135
+ return `<${hex}>`;
136
+ };
137
+ function buildPdf(pages, opts = {}) {
138
+ if (pages.length === 0) throw new Error("pixi-effects: a PDF needs at least one page");
139
+ const first = (i) => 3 + i * 3;
140
+ const objects = [];
141
+ objects[1] = [enc("<< /Type /Catalog /Pages 2 0 R >>")];
142
+ objects[2] = [enc(`<< /Type /Pages /Kids [${pages.map((_, i) => `${first(i)} 0 R`).join(" ")}] /Count ${pages.length} >>`)];
143
+ pages.forEach((p, i) => {
144
+ const w = p.width * 0.75, h = p.height * 0.75, n = first(i);
145
+ objects[n] = [enc(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${num(w)} ${num(h)}] /Resources << /XObject << /Im0 ${n + 2} 0 R >> >> /Contents ${n + 1} 0 R >>`)];
146
+ const content = `q ${num(w)} 0 0 ${num(h)} 0 0 cm /Im0 Do Q`;
147
+ objects[n + 1] = [enc(`<< /Length ${content.length} >>
148
+ stream
149
+ ${content}
150
+ endstream`)];
151
+ objects[n + 2] = [enc(`<< /Type /XObject /Subtype /Image /Width ${p.width} /Height ${p.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${p.jpeg.length} >>
152
+ stream
153
+ `), p.jpeg, enc("\nendstream")];
154
+ });
155
+ const infoNo = opts.title !== void 0 ? first(pages.length) : 0;
156
+ if (infoNo) objects[infoNo] = [enc(`<< /Title ${textString(opts.title)} /Producer (pixi-effects) >>`)];
157
+ const count = infoNo || first(pages.length);
158
+ const chunks = [enc("%PDF-1.4\n%\xE2\xE3\xCF\xD3\n")];
159
+ let offset = chunks[0].length;
160
+ const offsets = [];
161
+ for (let n = 1; n < count + (infoNo ? 1 : 0); n++) {
162
+ const body = objects[n];
163
+ if (!body) continue;
164
+ offsets[n] = offset;
165
+ const parts = [enc(`${n} 0 obj
166
+ `), ...body, enc("\nendobj\n")];
167
+ for (const part of parts) {
168
+ chunks.push(part);
169
+ offset += part.length;
170
+ }
171
+ }
172
+ const size = (infoNo || first(pages.length)) + (infoNo ? 1 : 0);
173
+ let xref = `xref
174
+ 0 ${size}
175
+ 0000000000 65535 f
176
+ `;
177
+ for (let n = 1; n < size; n++) xref += `${String(offsets[n] ?? 0).padStart(10, "0")} 00000 n
178
+ `;
179
+ xref += `trailer
180
+ << /Size ${size} /Root 1 0 R${infoNo ? ` /Info ${infoNo} 0 R` : ""} >>
181
+ startxref
182
+ ${offset}
183
+ %%EOF
184
+ `;
185
+ chunks.push(enc(xref));
186
+ const total = chunks.reduce((sum, c) => sum + c.length, 0);
187
+ const out = new Uint8Array(total);
188
+ let at = 0;
189
+ for (const c of chunks) {
190
+ out.set(c, at);
191
+ at += c.length;
192
+ }
193
+ return out;
194
+ }
195
+
196
+ // src/present/images.ts
197
+ function requireReady(movie, what) {
198
+ if (!movie.isReady) throw new Error(`pixi-effects: ${what}() needs a ready movie \u2014 await movie.init(...) first`);
199
+ }
200
+ async function stopImages(movie, opts = {}) {
201
+ chunkTWZNOUM7_cjs.warnUnknownOptions("stopImages()", opts, ["which", "pick", "scale", "type", "quality", "as", "motionBlur", "pdf", "onImage"]);
202
+ requireReady(movie, "stopImages");
203
+ if (movie.stops.length === 0) return [];
204
+ const picks = pictureStops(movie.stops, { which: opts.which, pick: opts.pick, pdf: opts.pdf });
205
+ const shot = { scale: opts.scale ?? 1, type: opts.type ?? "image/png", quality: opts.quality, as: opts.as ?? "blob", motionBlur: opts.motionBlur === void 0 ? false : opts.motionBlur };
206
+ return movie.peek(async () => {
207
+ const out = [];
208
+ for (const stop of picks) {
209
+ const image = await movie.snapshot(stop.frame, shot);
210
+ const item = { stop, page: stop.pageIndex, image };
211
+ out.push(item);
212
+ opts.onImage?.(item);
213
+ }
214
+ return out;
215
+ });
216
+ }
217
+ async function inspectStops(movie, opts = {}) {
218
+ chunkTWZNOUM7_cjs.warnUnknownOptions("inspectStops()", opts, ["lookback", "tolerance"]);
219
+ requireReady(movie, "inspectStops");
220
+ const out = { stops: [], issues: [] };
221
+ if (movie.stops.length === 0) return out;
222
+ const lookback = Math.max(1, Math.round(opts.lookback ?? 3));
223
+ const tolerance = opts.tolerance ?? 4e-3;
224
+ const small = Math.min(1, 320 / Math.max(1, movie.width));
225
+ const pixels = async (frame) => (await movie.snapshot(frame, { scale: small, motionBlur: false, as: "imageData" })).data;
226
+ return movie.peek(async () => {
227
+ for (const stop of movie.stops) {
228
+ const before = Math.max(0, stop.frame - lookback);
229
+ let moving = 0;
230
+ if (before < stop.frame) {
231
+ const now = await pixels(stop.frame);
232
+ moving = changedFraction(now, await pixels(before));
233
+ }
234
+ const settled = moving <= tolerance;
235
+ out.stops.push({ index: stop.index, page: stop.page, at: stop.at, frame: stop.frame, moving, settled });
236
+ if (!settled) {
237
+ out.issues.push(`stop ${stop.index + 1}${stop.page ? ` (page "${stop.page}")` : ""} at ${stop.at.toFixed(1)} s: the picture is still changing there (${moving < 0.1 ? (moving * 100).toFixed(1) : Math.round(moving * 100)}% of it changed in the last ${stop.frame - before} frames), so the audience, the page overview and a PDF see it mid-animation. Move the stop to after the animation has finished, or flag a settled stop of the page with \`pdf: true\`.`);
238
+ }
239
+ }
240
+ return out;
241
+ });
242
+ }
243
+ async function exportPDF(movie, opts = {}) {
244
+ chunkTWZNOUM7_cjs.warnUnknownOptions("exportPDF()", opts, ["which", "pick", "scale", "quality", "title", "motionBlur", "onImage"]);
245
+ requireReady(movie, "exportPDF");
246
+ if (movie.stops.length === 0) throw new Error("pixi-effects: exportPDF(): this movie has no stops, so there are no pages; add `stops` to the composition (or build it with deck())");
247
+ const scale = opts.scale ?? 1;
248
+ const images = await stopImages(movie, {
249
+ which: opts.which,
250
+ pick: opts.pick,
251
+ scale,
252
+ type: "image/jpeg",
253
+ quality: opts.quality ?? 0.92,
254
+ as: "blob",
255
+ motionBlur: opts.motionBlur,
256
+ pdf: true,
257
+ onImage: opts.onImage
258
+ });
259
+ const pages = await Promise.all(images.map(async (i) => ({
260
+ jpeg: new Uint8Array(await i.image.arrayBuffer()),
261
+ width: Math.max(1, Math.round(movie.width * scale)),
262
+ height: Math.max(1, Math.round(movie.height * scale))
263
+ })));
264
+ return new Blob([buildPdf(pages, { title: opts.title })], { type: "application/pdf" });
265
+ }
266
+
267
+ // src/present/Presenter.ts
6
268
  var OPTION_KEYS = ["canvas", "keyboard", "clickToAdvance", "swipe", "indicator", "autoAdvance", "loop", "accent", "onExit"];
7
269
  var NEXT_KEYS = /* @__PURE__ */ new Set(["ArrowRight", "ArrowDown", " ", "Enter", "PageDown"]);
8
270
  var PREV_KEYS = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowUp", "Backspace", "PageUp"]);
@@ -129,6 +391,7 @@ var Presenter = class {
129
391
  wasWrapAttr = false;
130
392
  onKey = (e) => this.handleKey(e);
131
393
  onStop = (e) => this.handleStop(e);
394
+ offStop = null;
132
395
  onSeeked = () => this.refresh();
133
396
  onFrame = () => this.mirror();
134
397
  onEnded = () => this.handleEnded();
@@ -202,7 +465,7 @@ var Presenter = class {
202
465
  canvas.addEventListener("pointermove", this.onPointerMove);
203
466
  if (this.options.keyboard) document.addEventListener("keydown", this.onKey);
204
467
  document.addEventListener("fullscreenchange", this.onFullscreenChange);
205
- movie.on("stop", this.onStop);
468
+ this.offStop = onStop(movie, this.onStop);
206
469
  movie.on("seeked", this.onSeeked);
207
470
  movie.on("ended", this.onEnded);
208
471
  this.refresh();
@@ -226,11 +489,11 @@ var Presenter = class {
226
489
  }
227
490
  next() {
228
491
  this.cancelAdvance();
229
- return this.run(() => this.movie.next());
492
+ return this.run(() => next(this.movie));
230
493
  }
231
494
  prev() {
232
495
  this.cancelAdvance();
233
- return this.run(() => this.movie.prev());
496
+ return this.run(() => prev(this.movie));
234
497
  }
235
498
  /** Black out (`'black'`), white out (`'white'`) or bring the picture back (`'off'`). */
236
499
  setCover(mode) {
@@ -248,7 +511,8 @@ var Presenter = class {
248
511
  canvas.removeEventListener("pointerup", this.onPointerUp);
249
512
  canvas.removeEventListener("pointermove", this.onPointerMove);
250
513
  this.closePresenterView();
251
- this.movie.off("stop", this.onStop);
514
+ this.offStop?.();
515
+ this.offStop = null;
252
516
  this.movie.off("seeked", this.onSeeked);
253
517
  this.movie.off("ended", this.onEnded);
254
518
  for (const t of [this.idleTimer, this.digitsTimer, this.advanceTimer, this.loopTimer]) if (t) clearTimeout(t);
@@ -321,19 +585,19 @@ var Presenter = class {
321
585
  this.loopTimer = null;
322
586
  void this.run(async () => {
323
587
  await this.movie.gotoFrame(0, true);
324
- await this.movie.next();
588
+ await next(this.movie);
325
589
  });
326
590
  }, 800);
327
591
  }
328
592
  refresh() {
329
593
  const m = this.movie;
330
- const pages = m.pageCount ?? 0, stops = m.stops?.length ?? 0;
594
+ const pages = pageCount(m), stops = m.stops?.length ?? 0;
331
595
  if (this.counterEl) {
332
- this.counterEl.textContent = pages > 0 ? `${m.pageIndex >= 0 ? m.pageIndex + 1 : "\u2013"} / ${pages}` : "";
596
+ this.counterEl.textContent = pages > 0 ? `${pageIndex(m) >= 0 ? pageIndex(m) + 1 : "\u2013"} / ${pages}` : "";
333
597
  this.counterEl.style.display = pages > 0 ? "" : "none";
334
598
  }
335
- if (this.titleEl) this.titleEl.textContent = m.currentStop?.page ?? "";
336
- if (this.progressEl) this.progressEl.style.setProperty("--mp-fill", String(stops > 0 ? (m.stopIndex + 1) / stops : 0));
599
+ if (this.titleEl) this.titleEl.textContent = currentStop(m)?.page ?? "";
600
+ if (this.progressEl) this.progressEl.style.setProperty("--mp-fill", String(stops > 0 ? (stopIndex(m) + 1) / stops : 0));
337
601
  this.refreshView();
338
602
  }
339
603
  wake() {
@@ -391,7 +655,7 @@ var Presenter = class {
391
655
  this.showDigits();
392
656
  this.navigate(() => {
393
657
  this.cancelAdvance();
394
- void this.run(() => this.movie.goToPage(page));
658
+ void this.run(() => goToPage(this.movie, page));
395
659
  });
396
660
  } else if (k === "Backspace" && this.digits) {
397
661
  this.digits = this.digits.slice(0, -1);
@@ -400,11 +664,11 @@ var Presenter = class {
400
664
  else if (PREV_KEYS.has(k)) this.navigate(() => void this.prev());
401
665
  else if (k === "Home") this.navigate(() => {
402
666
  this.cancelAdvance();
403
- void this.run(() => this.movie.goToPage(0));
667
+ void this.run(() => goToPage(this.movie, 0));
404
668
  });
405
669
  else if (k === "End") this.navigate(() => {
406
670
  this.cancelAdvance();
407
- void this.run(() => this.movie.goToStop(Math.max(0, (this.movie.stops?.length ?? 1) - 1)));
671
+ void this.run(() => goToStop(this.movie, Math.max(0, (this.movie.stops?.length ?? 1) - 1)));
408
672
  });
409
673
  else if (k === "b" || k === "B" || k === ".") this.setCover(this.coverEl.getAttribute("data-mode") === "black" ? "off" : "black");
410
674
  else if (k === "w" || k === "W" || k === ",") this.setCover(this.coverEl.getAttribute("data-mode") === "white" ? "off" : "white");
@@ -507,18 +771,18 @@ var Presenter = class {
507
771
  if (!v || v.win.closed) return;
508
772
  const m = this.movie;
509
773
  const stops = m.stops ?? [];
510
- const pages = m.pageCount ?? 0;
511
- const cur = m.stopIndex ?? -1;
512
- v.els.count.textContent = pages > 0 ? `${m.pageIndex >= 0 ? m.pageIndex + 1 : "\u2013"} / ${pages}` : "";
513
- v.els.title.textContent = m.currentStop?.page ?? (cur < 0 ? stops[0]?.page ?? "" : "");
514
- const upTo = cur < 0 ? 0 : cur, pageIndex = stops[upTo]?.pageIndex;
774
+ const pages = pageCount(m);
775
+ const cur = stopIndex(m);
776
+ v.els.count.textContent = pages > 0 ? `${pageIndex(m) >= 0 ? pageIndex(m) + 1 : "\u2013"} / ${pages}` : "";
777
+ v.els.title.textContent = currentStop(m)?.page ?? (cur < 0 ? stops[0]?.page ?? "" : "");
778
+ const upTo = cur < 0 ? 0 : cur, pageIndex2 = stops[upTo]?.pageIndex;
515
779
  let notes = "";
516
- for (const s of stops) if (s.pageIndex === pageIndex && s.index <= upTo && s.notes) notes = s.notes;
780
+ for (const s of stops) if (s.pageIndex === pageIndex2 && s.index <= upTo && s.notes) notes = s.notes;
517
781
  v.els.notes.textContent = notes;
518
782
  v.els.notes.setAttribute("data-empty", String(notes === ""));
519
- const next = stops[cur + 1];
520
- v.els.nextbox.setAttribute("data-end", String(!next));
521
- const src = next ? this.thumbs.get(next.index) : void 0;
783
+ const next2 = stops[cur + 1];
784
+ v.els.nextbox.setAttribute("data-end", String(!next2));
785
+ const src = next2 ? this.thumbs.get(next2.index) : void 0;
522
786
  if (src) v.els.next.setAttribute("src", src);
523
787
  else v.els.next.removeAttribute("src");
524
788
  }
@@ -546,10 +810,11 @@ var Presenter = class {
546
810
  * Make a picture of every stop, once. Taking them moves the playhead through the stops and back, which the audience would see:
547
811
  * `start()` does it before anything is shown (behind a "Preparing" cover), and an overview opened before that does it on the spot.
548
812
  */
813
+ /** Make the page pictures now (the overview and the presenter view use them): `start()` does it before the audience sees anything. */
549
814
  ensureThumbs() {
550
815
  this.thumbsPromise ??= (async () => {
551
816
  try {
552
- await this.movie.stopImages({
817
+ await stopImages(this.movie, {
553
818
  which: "stops",
554
819
  as: "dataURL",
555
820
  type: "image/jpeg",
@@ -572,8 +837,8 @@ var Presenter = class {
572
837
  const stops = (this.movie.stops ?? []).filter((s) => s.pageIndex === page);
573
838
  return stops.length ? this.thumbs.get(stops[stops.length - 1].index) : void 0;
574
839
  }
575
- onThumb(stopIndex) {
576
- const stop = this.movie.stops?.[stopIndex];
840
+ onThumb(stopIndex2) {
841
+ const stop = this.movie.stops?.[stopIndex2];
577
842
  if (stop) this.fillThumb(stop.pageIndex);
578
843
  this.refreshView();
579
844
  }
@@ -584,18 +849,18 @@ var Presenter = class {
584
849
  this.gridEl.style.setProperty("--mp-cols", String(cols));
585
850
  this.gridEl.style.setProperty("--mp-ratio", `${width} / ${height}`);
586
851
  this.gridEl.innerHTML = "";
587
- const current = Math.max(0, this.movie.pageIndex ?? 0);
852
+ const current = Math.max(0, pageIndex(this.movie));
588
853
  pages.forEach((s, i) => {
589
854
  const cell = document.createElement("div");
590
855
  cell.className = "mp-cell";
591
- cell.setAttribute("data-current", String(i === this.movie.pageIndex));
856
+ cell.setAttribute("data-current", String(i === pageIndex(this.movie)));
592
857
  cell.setAttribute("data-selected", String(i === current));
593
858
  cell.innerHTML = `<div class="mp-cell-label"><span class="mp-cell-num">${i + 1}</span><span class="mp-cell-name"></span></div>`;
594
859
  cell.querySelector(".mp-cell-name").textContent = s.page ?? "";
595
860
  cell.addEventListener("click", () => {
596
861
  this.overviewEl.setAttribute("data-open", "false");
597
862
  this.cancelAdvance();
598
- void this.run(() => this.movie.goToPage(i));
863
+ void this.run(() => goToPage(this.movie, i));
599
864
  });
600
865
  this.gridEl.appendChild(cell);
601
866
  this.fillThumb(i);
@@ -636,7 +901,7 @@ var Presenter = class {
636
901
  const page = this.overviewSel;
637
902
  this.overviewEl.setAttribute("data-open", "false");
638
903
  this.cancelAdvance();
639
- void this.run(() => this.movie.goToPage(page));
904
+ void this.run(() => goToPage(this.movie, page));
640
905
  return true;
641
906
  }
642
907
  case "Escape":
@@ -666,6 +931,80 @@ var Presenter = class {
666
931
  }
667
932
  };
668
933
 
934
+ // src/present/deck.ts
935
+ var PAGE_KEYS = ["name", "duration", "sequences", "stops", "notes", "advance", "initial", "keyframes", "filters", "filterArea", "blendMode", "mask"];
936
+ var DECK_KEYS = ["pages", "transition", "width", "height", "frameRate", "background", "canvas", "assets", "poster", "motionBlur", "loader"];
937
+ function deck(options) {
938
+ if (!options || !Array.isArray(options.pages) || options.pages.length === 0) throw new Error("deck(): pages must be a list with at least one page: { pages: [{ name, duration, stops, sequences }] }");
939
+ chunkTWZNOUM7_cjs.warnUnknownOptions("deck()", options, DECK_KEYS);
940
+ const { pages, transition, ...movieOptions } = options;
941
+ pages.forEach((p, i) => {
942
+ if (!p || typeof p !== "object") throw new Error(`deck(): pages[${i}] must be an object`);
943
+ chunkTWZNOUM7_cjs.warnUnknownOptions(`deck() pages[${i}]`, p, PAGE_KEYS);
944
+ if (!(typeof p.duration === "number" && Number.isFinite(p.duration) && p.duration > 0)) throw new Error(`deck(): pages[${i}].duration must be a positive number of seconds, got ${String(p.duration)}`);
945
+ if (!Array.isArray(p.sequences)) throw new Error(`deck(): pages[${i}].sequences must be a list of layers (it may be empty), got ${typeof p.sequences}`);
946
+ });
947
+ let overlap = 0;
948
+ if (transition) {
949
+ if (!(typeof transition.duration === "number" && transition.duration > 0)) throw new Error("deck(): transition needs a duration in seconds: { kind: 'crossfade', duration: 0.6 }");
950
+ const shortest = Math.min(...pages.map((p) => p.duration));
951
+ if (pages.length > 1 && transition.duration >= shortest) throw new Error(`deck(): the transition (${transition.duration} s) must be shorter than every page (the shortest is ${shortest} s)`);
952
+ overlap = pages.length > 1 ? transition.duration : 0;
953
+ }
954
+ const sequences = [];
955
+ const transitions = [];
956
+ const stops = [];
957
+ let start = 0;
958
+ pages.forEach((page, i) => {
959
+ const id = `page-${i + 1}`;
960
+ const label = page.name ? `"${page.name}"` : `pages[${i}]`;
961
+ const isLast = i === pages.length - 1;
962
+ const entries = (page.stops ?? [isLast ? page.duration : page.duration - overlap]).map((e, k) => {
963
+ if (e && typeof e === "object") chunkTWZNOUM7_cjs.warnUnknownOptions(`deck() page ${label} stops[${k}]`, e, ["at", "pdf"]);
964
+ return typeof e === "object" && e !== null ? e : { at: e };
965
+ });
966
+ const local = entries.map((e) => e.at);
967
+ local.forEach((s, k) => {
968
+ if (!(typeof s === "number" && Number.isFinite(s) && s > 0 && s <= page.duration + 1e-9)) throw new Error(`deck(): page ${label} has stop ${String(s)}, outside the page (0 to ${page.duration} s)`);
969
+ if (k > 0 && s <= local[k - 1]) throw new Error(`deck(): page ${label} stops must be in order, earliest first (got ${local.join(", ")})`);
970
+ });
971
+ const { name: _n, duration: _d, sequences: inner, stops: _s, notes: _no, advance: _a, ...layerProps } = page;
972
+ sequences.push({ type: "composition", name: id, at: round(start), duration: page.duration, ...layerProps, sequences: inner });
973
+ local.forEach((s, k) => {
974
+ const stop = { at: round(start + s) };
975
+ if (k === 0) {
976
+ stop.page = page.name ?? true;
977
+ if (page.notes !== void 0) stop.notes = page.notes;
978
+ }
979
+ if (k === local.length - 1 && page.advance !== void 0) stop.advance = page.advance;
980
+ if (entries[k].pdf !== void 0) stop.pdf = entries[k].pdf;
981
+ stops.push(stop);
982
+ });
983
+ if (transition && !isLast) {
984
+ const next2 = start + page.duration - overlap;
985
+ transitions.push({ ...transition, from: id, to: `page-${i + 2}`, at: round(next2), duration: transition.duration });
986
+ }
987
+ start = start + page.duration - (isLast ? 0 : overlap);
988
+ });
989
+ const composition = { sequences, stops };
990
+ if (transitions.length > 0) composition.transitions = transitions;
991
+ return { ...movieOptions, duration: round(start), composition };
992
+ }
993
+ var round = (v) => Math.round(v * 1e6) / 1e6;
994
+
669
995
  exports.Presenter = Presenter;
670
- //# sourceMappingURL=Presenter.cjs.map
671
- //# sourceMappingURL=Presenter.cjs.map
996
+ exports.currentStop = currentStop;
997
+ exports.deck = deck;
998
+ exports.exportPDF = exportPDF;
999
+ exports.goToPage = goToPage;
1000
+ exports.goToStop = goToStop;
1001
+ exports.inspectStops = inspectStops;
1002
+ exports.next = next;
1003
+ exports.onStop = onStop;
1004
+ exports.pageCount = pageCount;
1005
+ exports.pageIndex = pageIndex;
1006
+ exports.prev = prev;
1007
+ exports.stopImages = stopImages;
1008
+ exports.stopIndex = stopIndex;
1009
+ //# sourceMappingURL=present.cjs.map
1010
+ //# sourceMappingURL=present.cjs.map