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
@@ -1,20 +1,7 @@
1
- import { j as Movie } from './Movie-CHcM8HNB.js';
1
+ import { k as Movie } from './Movie-C2rZFkrO.js';
2
2
  import 'pixi.js';
3
- import './types-DpK1vwcl.js';
3
+ import './types-BzIB65Vk.js';
4
4
 
5
- /** The bar's default stylesheet. Its colours and sizes are CSS custom properties (`--mc-accent`, `--mc-fg`, `--mc-track`, `--mc-bar-bg`, `--mc-track-height`, `--mc-font`): set them in your page CSS, or with the `theme` option. */
6
- declare const CONTROLLER_CSS = "\n.movie-controller-wrap { position: relative; display: inline-block; line-height: 0; }\n.movie-controller {\n position: absolute;\n pointer-events: none;\n font-family: var(--mc-font, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n opacity: 1;\n transition: opacity 200ms ease;\n}\n.movie-controller[data-state=\"hidden\"] { opacity: 0; }\n.movie-controller > * { pointer-events: auto; }\n\n.mc-progress {\n position: relative;\n width: 100%;\n height: 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n outline: none;\n}\n.mc-progress:focus-visible {\n outline: 2px solid var(--mc-accent, #007AFF);\n outline-offset: 2px;\n}\n.mc-progress::before {\n content: \"\"; position: absolute; left: 12px; right: 12px;\n height: var(--mc-track-height, 3px); background: var(--mc-track, rgba(255,255,255,0.25));\n transition: height 120ms ease;\n}\n.mc-progress:hover::before, .mc-progress.mc-scrubbing::before { height: calc(var(--mc-track-height, 3px) + 2px); }\n.mc-stops { position: absolute; left: 12px; right: 12px; top: 0; bottom: 0; pointer-events: none; }\n.mc-stop-tick { position: absolute; top: 50%; width: 7px; height: 7px; transform: translate(-50%, -50%); border-radius: 50%; background: var(--mc-fg, #fff); box-shadow: 0 0 0 1.5px rgba(0,0,0,0.5); }\n.mc-stop-page { width: 11px; height: 11px; box-shadow: 0 0 0 2.5px var(--mc-accent, #007AFF), 0 0 0 4px rgba(0,0,0,0.35); }\n.mc-progress-fill {\n position: absolute; left: 12px; top: 50%;\n height: var(--mc-track-height, 3px);\n width: calc((100% - 24px) * var(--mc-fill, 0));\n background: var(--mc-accent, #007AFF);\n transform: translateY(-50%);\n transition: height 120ms ease;\n pointer-events: none;\n}\n.mc-progress:hover .mc-progress-fill,\n.mc-progress.mc-scrubbing .mc-progress-fill { height: calc(var(--mc-track-height, 3px) + 2px); }\n.mc-progress-thumb {\n position: absolute; top: 50%;\n left: calc(12px + (100% - 24px) * var(--mc-fill, 0));\n width: 12px; height: 12px; border-radius: 50%;\n background: var(--mc-accent, #007AFF);\n transform: translate(-50%, -50%) scale(0);\n transition: transform 120ms ease;\n pointer-events: none;\n}\n.mc-progress:hover .mc-progress-thumb,\n.mc-progress.mc-scrubbing .mc-progress-thumb { transform: translate(-50%, -50%) scale(1); }\n\n.mc-bar {\n position: relative;\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 6px 12px 8px 12px;\n background: var(--mc-bar-bg, linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0) 100%));\n color: var(--mc-fg, #fff);\n line-height: 1;\n}\n.mc-btn {\n background: none; border: 0; padding: 0; margin: 0;\n width: 28px; height: 28px;\n display: inline-flex; align-items: center; justify-content: center;\n color: var(--mc-fg, #fff); opacity: 0.85; cursor: pointer;\n transition: opacity 120ms ease;\n}\n.mc-btn:hover { opacity: 1; }\n.mc-btn:disabled { opacity: 0.4; cursor: not-allowed; }\n.mc-time {\n font-size: 12px; font-family: Menlo, Monaco, monospace;\n color: var(--mc-fg, #fff); opacity: 0.85;\n min-width: 90px;\n}\n.mc-spacer { flex: 1; }\n\n.mc-volume { display: flex; align-items: center; }\n.mc-vol-slider {\n position: relative;\n width: 0;\n height: 28px;\n display: flex;\n align-items: center;\n cursor: pointer;\n margin-left: 0;\n outline: none;\n transition: width 200ms ease, margin-left 200ms ease;\n overflow: hidden;\n}\n.mc-volume:hover .mc-vol-slider,\n.mc-volume:focus-within .mc-vol-slider,\n.mc-vol-slider.mc-scrubbing {\n width: 70px;\n margin-left: 6px;\n}\n.mc-vol-slider:focus-visible { outline: 2px solid var(--mc-accent, #007AFF); outline-offset: 2px; }\n.mc-vol-slider::before {\n content: \"\"; position: absolute; left: 4px; right: 4px;\n height: 3px; background: var(--mc-track, rgba(255,255,255,0.25)); border-radius: 2px;\n}\n.mc-vol-fill {\n position: absolute; left: 4px; top: 50%;\n height: 3px;\n width: calc((100% - 8px) * var(--mc-volume, 1));\n background: var(--mc-fg, #fff);\n transform: translateY(-50%);\n border-radius: 2px;\n pointer-events: none;\n}\n.mc-vol-thumb {\n position: absolute; top: 50%;\n left: calc(4px + (100% - 8px) * var(--mc-volume, 1));\n width: 10px; height: 10px; border-radius: 50%;\n background: var(--mc-fg, #fff);\n transform: translate(-50%, -50%);\n pointer-events: none;\n}\n\n[data-mc-wrap]:fullscreen {\n width: 100vw; height: 100vh;\n display: flex; align-items: center; justify-content: center;\n background: #000;\n}\n[data-mc-wrap]:fullscreen > canvas {\n width: 100% !important;\n height: 100% !important;\n max-width: none !important;\n max-height: none !important;\n object-fit: contain;\n border-radius: 0;\n}\n\n.mc-settings-popover {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 12px;\n background: rgba(20, 20, 20, 0.96);\n border-radius: 8px;\n padding: 10px 12px;\n min-width: 180px;\n color: #fff;\n font-size: 12px;\n box-shadow: 0 4px 16px rgba(0,0,0,0.4);\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.mc-settings-popover[data-open=\"false\"] { display: none; }\n.mc-settings-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.mc-settings-row > span { color: rgba(255,255,255,0.85); }\n.mc-settings-row > select {\n font: inherit;\n background: rgba(255,255,255,0.1);\n color: #fff;\n border: 1px solid rgba(255,255,255,0.18);\n border-radius: 4px;\n padding: 2px 6px;\n cursor: pointer;\n}\n.mc-export-confirm {\n margin-top: 4px;\n width: 100%;\n background: var(--mc-accent, #007AFF);\n color: #fff;\n border: 0;\n border-radius: 4px;\n padding: 6px 10px;\n font: inherit;\n font-weight: 600;\n cursor: pointer;\n transition: background 120ms ease;\n}\n.mc-export-confirm:hover { background: #0066d6; }\n.mc-export-confirm:disabled { background: rgba(255,255,255,0.18); cursor: not-allowed; }\n\n.mc-export-overlay {\n position: absolute; inset: 0;\n background: rgba(0,0,0,0.7);\n display: flex; justify-content: center; align-items: center;\n z-index: 2;\n}\n.mc-export-panel {\n background: rgba(20,20,20,0.96); color: #fff;\n border-radius: 8px; padding: 16px 20px;\n text-align: center; min-width: 200px; max-width: 80%;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n}\n.mc-export-title { font-size: 13px; margin-bottom: 10px; opacity: 0.9; }\n.mc-export-track {\n width: 100%; height: 6px;\n background: rgba(255,255,255,0.15); border-radius: 3px;\n margin-bottom: 8px; overflow: hidden;\n}\n.mc-export-fill {\n height: 100%; width: 0%;\n background: linear-gradient(90deg, var(--mc-accent, #007AFF), var(--mc-accent, #0056CC));\n transition: width 0.3s ease; border-radius: 3px;\n}\n.mc-export-text {\n color: rgba(255,255,255,0.7); font-size: 11px;\n font-family: Menlo, Monaco, monospace;\n}\n";
7
- declare function formatTime(seconds: number): string;
8
- declare function frameToPercent(frame: number, totalFrames: number): number;
9
- declare function pxToFrame(clientX: number, rect: {
10
- left: number;
11
- width: number;
12
- }, totalFrames: number): number;
13
- declare function pxToFraction(clientX: number, rect: {
14
- left: number;
15
- width: number;
16
- }, inset?: number): number;
17
- declare function extensionForMimeType(mime: string): string;
18
5
  /**
19
6
  * The look of the bar. Each key sets a CSS custom property on the bar (so you can also set `--mc-accent` … in your own CSS, on the
20
7
  * canvas's parent or on `:root`). `null` puts a value back to the default.
@@ -37,22 +24,24 @@ interface ControllerOptions {
37
24
  canvas: HTMLCanvasElement;
38
25
  /** Colours and sizes of the bar, see {@link ControllerTheme}. Can be changed later with `controller.setTheme()`. */
39
26
  theme?: ControllerTheme;
40
- showExportButton?: boolean;
41
- enableKeyboardShortcuts?: boolean;
27
+ /** The download button and its panel. Default true. */
28
+ exportButton?: boolean;
29
+ /** Space, arrows, M, F, E … on the page (not while typing in a field). Default true. */
30
+ keyboard?: boolean;
42
31
  /** A click (or tap) on the picture plays / pauses, like a `<video>` element. Default true; `false` leaves the canvas alone. */
43
32
  clickToPlay?: boolean;
44
33
  /** A movie with `stops` gets a Present button that starts a {@link Presenter} (keys, click and swipe move through the stops). Default true; `false` keeps only the marks on the seek bar. */
45
- present?: boolean;
34
+ presentButton?: boolean;
46
35
  /** A movie with `stops`: the play button (and a click on the picture, and Space) plays to the next stop and pauses there, like a slide. Default true; `false` plays straight through to the end. */
47
36
  pauseAtStops?: boolean;
48
37
  className?: string;
49
38
  }
50
39
  interface ResolvedOptions {
51
40
  canvas: HTMLCanvasElement;
52
- showExportButton: boolean;
53
- enableKeyboardShortcuts: boolean;
41
+ exportButton: boolean;
42
+ keyboard: boolean;
54
43
  clickToPlay: boolean;
55
- present: boolean;
44
+ presentButton: boolean;
56
45
  pauseAtStops: boolean;
57
46
  className: string;
58
47
  }
@@ -98,6 +87,11 @@ declare class Controller {
98
87
  private settingsOutsideHandler;
99
88
  private onReady;
100
89
  private presenter;
90
+ /** `pixi-effects/present`, loaded as soon as the movie has stops (a press of play must not wait for it). */
91
+ private present;
92
+ private presentLoading;
93
+ private playStarting;
94
+ private loadPresent;
101
95
  private presentBtn;
102
96
  private onFrame;
103
97
  private onPlay;
@@ -183,4 +177,4 @@ declare class Controller {
183
177
  destroy(): void;
184
178
  }
185
179
 
186
- export { CONTROLLER_CSS, Controller, type ControllerOptions, type ControllerTheme, extensionForMimeType, formatTime, frameToPercent, pxToFraction, pxToFrame };
180
+ export { Controller, type ControllerOptions, type ControllerTheme };
@@ -1,17 +1,6 @@
1
1
  import { warnUnknownOptions } from './chunk-BXOCEZ4U.js';
2
2
 
3
- // src/Controller.ts
4
- var ICONS = {
5
- play: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M3 2 L13 8 L3 14 Z"/></svg>',
6
- pause: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><rect x="3" y="2" width="3.5" height="12"/><rect x="9.5" y="2" width="3.5" height="12"/></svg>',
7
- volumeOn: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M2 6 H5 L9 2 V14 L5 10 H2 Z"/><path d="M11 5 Q13 8 11 11" fill="none" stroke="currentColor" stroke-width="1.2"/><path d="M12.5 3.5 Q15.5 8 12.5 12.5" fill="none" stroke="currentColor" stroke-width="1.2"/></svg>',
8
- volumeOff: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M2 6 H5 L9 2 V14 L5 10 H2 Z"/><path d="M11 5 L15 11 M15 5 L11 11" fill="none" stroke="currentColor" stroke-width="1.4"/></svg>',
9
- download: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2 V11 M4 7 L8 11 L12 7 M3 13 H13"/></svg>',
10
- fullscreenEnter: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 6 V2 H6 M14 6 V2 H10 M2 10 V14 H6 M14 10 V14 H10"/></svg>',
11
- present: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 2.5 H14 V10.5 H2 Z M8 10.5 V13.5 M5 13.5 H11"/></svg>',
12
- fullscreenExit: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2 V6 H2 M10 2 V6 H14 M6 14 V10 H2 M10 14 V10 H14"/></svg>'
13
- };
14
- var STYLE_ATTR = "data-movie-controller";
3
+ // src/controller/css.ts
15
4
  var CONTROLLER_CSS = `
16
5
  .movie-controller-wrap { position: relative; display: inline-block; line-height: 0; }
17
6
  .movie-controller {
@@ -230,22 +219,8 @@ var CONTROLLER_CSS = `
230
219
  font-family: Menlo, Monaco, monospace;
231
220
  }
232
221
  `;
233
- var styleRefCount = 0;
234
- function installStyles() {
235
- if (styleRefCount === 0) {
236
- const el = document.createElement("style");
237
- el.setAttribute(STYLE_ATTR, "");
238
- el.textContent = CONTROLLER_CSS;
239
- document.head.appendChild(el);
240
- }
241
- styleRefCount++;
242
- }
243
- function uninstallStyles() {
244
- styleRefCount = Math.max(0, styleRefCount - 1);
245
- if (styleRefCount === 0) {
246
- document.head.querySelectorAll(`style[${STYLE_ATTR}]`).forEach((n) => n.remove());
247
- }
248
- }
222
+
223
+ // src/controller/helpers.ts
249
224
  function formatTime(seconds) {
250
225
  const s = Math.max(0, Math.floor(seconds));
251
226
  const m = Math.floor(s / 60);
@@ -270,7 +245,6 @@ function pxToFraction(clientX, rect, inset = 0) {
270
245
  const ratio = (clientX - innerLeft) / innerWidth;
271
246
  return Math.min(Math.max(ratio, 0), 1);
272
247
  }
273
- var PDF_QUALITY = { "low": 0.6, "medium": 0.75, "high": 0.88, "very-high": 0.95 };
274
248
  function extensionForMimeType(mime) {
275
249
  const t = (mime || "").toLowerCase();
276
250
  if (t.includes("pdf")) return "pdf";
@@ -279,6 +253,36 @@ function extensionForMimeType(mime) {
279
253
  if (t.includes("matroska") || t.includes("mkv")) return "mkv";
280
254
  return "mp4";
281
255
  }
256
+
257
+ // src/Controller.ts
258
+ var ICONS = {
259
+ play: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M3 2 L13 8 L3 14 Z"/></svg>',
260
+ pause: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><rect x="3" y="2" width="3.5" height="12"/><rect x="9.5" y="2" width="3.5" height="12"/></svg>',
261
+ volumeOn: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M2 6 H5 L9 2 V14 L5 10 H2 Z"/><path d="M11 5 Q13 8 11 11" fill="none" stroke="currentColor" stroke-width="1.2"/><path d="M12.5 3.5 Q15.5 8 12.5 12.5" fill="none" stroke="currentColor" stroke-width="1.2"/></svg>',
262
+ volumeOff: '<svg viewBox="0 0 16 16" width="16" height="16" fill="currentColor"><path d="M2 6 H5 L9 2 V14 L5 10 H2 Z"/><path d="M11 5 L15 11 M15 5 L11 11" fill="none" stroke="currentColor" stroke-width="1.4"/></svg>',
263
+ download: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M8 2 V11 M4 7 L8 11 L12 7 M3 13 H13"/></svg>',
264
+ fullscreenEnter: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 6 V2 H6 M14 6 V2 H10 M2 10 V14 H6 M14 10 V14 H10"/></svg>',
265
+ present: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M2 2.5 H14 V10.5 H2 Z M8 10.5 V13.5 M5 13.5 H11"/></svg>',
266
+ fullscreenExit: '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2 V6 H2 M10 2 V6 H14 M6 14 V10 H2 M10 14 V10 H14"/></svg>'
267
+ };
268
+ var STYLE_ATTR = "data-movie-controller";
269
+ var styleRefCount = 0;
270
+ function installStyles() {
271
+ if (styleRefCount === 0) {
272
+ const el = document.createElement("style");
273
+ el.setAttribute(STYLE_ATTR, "");
274
+ el.textContent = CONTROLLER_CSS;
275
+ document.head.appendChild(el);
276
+ }
277
+ styleRefCount++;
278
+ }
279
+ function uninstallStyles() {
280
+ styleRefCount = Math.max(0, styleRefCount - 1);
281
+ if (styleRefCount === 0) {
282
+ document.head.querySelectorAll(`style[${STYLE_ATTR}]`).forEach((n) => n.remove());
283
+ }
284
+ }
285
+ var PDF_QUALITY = { "low": 0.6, "medium": 0.75, "high": 0.88, "very-high": 0.95 };
282
286
  var THEME_PROPERTIES = {
283
287
  accent: "--mc-accent",
284
288
  foreground: "--mc-fg",
@@ -287,6 +291,7 @@ var THEME_PROPERTIES = {
287
291
  trackHeight: "--mc-track-height",
288
292
  font: "--mc-font"
289
293
  };
294
+ var CONTROLLER_OPTION_KEYS = ["canvas", "theme", "exportButton", "keyboard", "clickToPlay", "presentButton", "pauseAtStops", "className"];
290
295
  var Controller = class _Controller {
291
296
  movie;
292
297
  options;
@@ -329,6 +334,16 @@ var Controller = class _Controller {
329
334
  settingsOutsideHandler = null;
330
335
  onReady = null;
331
336
  presenter = null;
337
+ /** `pixi-effects/present`, loaded as soon as the movie has stops (a press of play must not wait for it). */
338
+ present = null;
339
+ presentLoading = null;
340
+ playStarting = false;
341
+ loadPresent() {
342
+ return this.presentLoading ??= import('./present.js').then((m) => this.present = m, (err) => {
343
+ this.presentLoading = null;
344
+ throw err;
345
+ });
346
+ }
332
347
  presentBtn = null;
333
348
  onFrame = null;
334
349
  onPlay = null;
@@ -354,13 +369,14 @@ var Controller = class _Controller {
354
369
  if (!options || !options.canvas) {
355
370
  throw new Error("Controller requires options.canvas (HTMLCanvasElement).");
356
371
  }
372
+ warnUnknownOptions("Controller", options, CONTROLLER_OPTION_KEYS);
357
373
  this.movie = movie;
358
374
  this.options = {
359
375
  canvas: options.canvas,
360
- showExportButton: options.showExportButton ?? true,
361
- enableKeyboardShortcuts: options.enableKeyboardShortcuts ?? true,
376
+ exportButton: options.exportButton ?? true,
377
+ keyboard: options.keyboard ?? true,
362
378
  clickToPlay: options.clickToPlay ?? true,
363
- present: options.present ?? true,
379
+ presentButton: options.presentButton ?? true,
364
380
  pauseAtStops: options.pauseAtStops ?? true,
365
381
  className: options.className ?? "movie-controller"
366
382
  };
@@ -389,10 +405,10 @@ var Controller = class _Controller {
389
405
  this.bindExportPopover();
390
406
  this.bindFullscreen();
391
407
  this.bindVisibility();
392
- if (this.options.enableKeyboardShortcuts) this.bindKeyboard();
408
+ if (this.options.keyboard) this.bindKeyboard();
393
409
  }
394
410
  buildBar() {
395
- const popoverHtml = this.options.showExportButton ? `
411
+ const popoverHtml = this.options.exportButton ? `
396
412
  <div class="mc-settings-popover" role="dialog" aria-label="Export settings" data-open="false">
397
413
  <label class="mc-settings-row">
398
414
  <span>Format</span>
@@ -431,7 +447,7 @@ var Controller = class _Controller {
431
447
  </div>
432
448
  <span class="mc-time">0:00 / 0:00</span>
433
449
  <div class="mc-spacer"></div>
434
- ${this.options.showExportButton ? `<button class="mc-btn mc-export" aria-label="Export" aria-expanded="false">${ICONS.download}</button>` : ""}
450
+ ${this.options.exportButton ? `<button class="mc-btn mc-export" aria-label="Export" aria-expanded="false">${ICONS.download}</button>` : ""}
435
451
  <button class="mc-btn mc-fullscreen" aria-label="Enter fullscreen">${ICONS.fullscreenEnter}</button>
436
452
  </div>
437
453
  `;
@@ -575,6 +591,8 @@ var Controller = class _Controller {
575
591
  this.presentBtn = null;
576
592
  return;
577
593
  }
594
+ this.loadPresent().catch(() => {
595
+ });
578
596
  const total = this.movie.totalFrames || 1;
579
597
  const holder = document.createElement("div");
580
598
  holder.className = "mc-stops";
@@ -586,7 +604,7 @@ var Controller = class _Controller {
586
604
  holder.appendChild(tick);
587
605
  }
588
606
  this.progressEl.appendChild(holder);
589
- if (this.options.present && !this.presentBtn && this.fullscreenBtn) {
607
+ if (this.options.presentButton && !this.presentBtn && this.fullscreenBtn) {
590
608
  const btn = document.createElement("button");
591
609
  btn.className = "mc-btn mc-present";
592
610
  btn.setAttribute("aria-label", "Present");
@@ -631,13 +649,29 @@ var Controller = class _Controller {
631
649
  async startPlaying() {
632
650
  if (this.options.pauseAtStops && (this.movie.stops?.length ?? 0) > 0) {
633
651
  if (this.movie.currentFrame >= this.movie.totalFrames) await this.movie.gotoFrame(0, true);
634
- await this.movie.next();
652
+ let present = this.present;
653
+ if (!present) {
654
+ if (this.playStarting) return;
655
+ this.playStarting = true;
656
+ try {
657
+ present = await this.loadPresent();
658
+ } catch (err) {
659
+ console.warn("pixi-effects: could not load pixi-effects/present, playing straight through:", err);
660
+ } finally {
661
+ this.playStarting = false;
662
+ }
663
+ }
664
+ if (!present) {
665
+ this.movie.play();
666
+ return;
667
+ }
668
+ await present.next(this.movie);
635
669
  } else this.movie.play();
636
670
  }
637
671
  /** Hand the page over to a Presenter (fullscreen, keys, click and swipe move through the stops); Escape gives it back. */
638
672
  async startPresenting() {
639
673
  if (this.presenter || this.destroyed) return;
640
- const { Presenter } = await import('./Presenter.js');
674
+ const { Presenter } = await this.loadPresent();
641
675
  if (this.presenter || this.destroyed) return;
642
676
  const p = new Presenter(this.movie, { canvas: this.options.canvas, onExit: () => this.stopPresenting() });
643
677
  this.presenter = p;
@@ -935,7 +969,7 @@ var Controller = class _Controller {
935
969
  this.exportPages = { done: 0, total };
936
970
  } else this.showExportOverlay();
937
971
  try {
938
- const blob = pdf ? await this.movie.exportPDF({
972
+ const blob = pdf ? await (await this.loadPresent()).exportPDF(this.movie, {
939
973
  which: this.exportFormat === "pdf-steps" ? "stops" : "pages",
940
974
  quality: PDF_QUALITY[this.exportQuality],
941
975
  onImage: () => this.pdfPageDone()
@@ -1217,6 +1251,6 @@ var Controller = class _Controller {
1217
1251
  }
1218
1252
  };
1219
1253
 
1220
- export { CONTROLLER_CSS, Controller, extensionForMimeType, formatTime, frameToPercent, pxToFraction, pxToFrame };
1254
+ export { Controller };
1221
1255
  //# sourceMappingURL=Controller.js.map
1222
1256
  //# sourceMappingURL=Controller.js.map