pixi-effects 0.11.0 → 0.13.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.
- package/CHANGELOG.md +28 -0
- package/README.md +11 -8
- package/ai/SKILL.md +2 -1
- package/ai/reference/cheatsheet.md +12 -1
- package/ai/reference/pitfalls.md +4 -0
- package/ai/reference/recipes.md +57 -0
- package/ai/template.html +8 -4
- package/ai/tools/check.mjs +17 -1
- package/ai/tools/render.mjs +13 -9
- package/dist/{Base-CwUxTfja.d.cts → Base-C6ZnJ9pD.d.cts} +1 -1
- package/dist/{Base-CyvSLoCP.d.ts → Base-CQOlB2VH.d.ts} +1 -1
- package/dist/Controller.cjs +132 -15
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +25 -3
- package/dist/Controller.d.ts +25 -3
- package/dist/Controller.js +132 -15
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-DSSNOF3T.d.cts → Movie-DnR4H1OO.d.ts} +93 -2
- package/dist/{Movie-nzmWsxWX.d.ts → Movie-Tl5dRLJl.d.cts} +93 -2
- package/dist/Presenter.cjs +671 -0
- package/dist/Presenter.cjs.map +1 -0
- package/dist/Presenter.d.cts +123 -0
- package/dist/Presenter.d.ts +123 -0
- package/dist/Presenter.js +669 -0
- package/dist/Presenter.js.map +1 -0
- package/dist/index.cjs +665 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -5
- package/dist/index.d.ts +165 -5
- package/dist/index.js +659 -37
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/{types-BXu28nJj.d.cts → types-CBSiUKau.d.cts} +56 -1
- package/dist/{types-BXu28nJj.d.ts → types-CBSiUKau.d.ts} +56 -1
- package/docs/api.md +80 -0
- package/docs/dsl.md +48 -0
- package/llms-full.txt +203 -2
- package/llms.txt +3 -3
- package/package.json +5 -1
package/dist/Controller.cjs
CHANGED
|
@@ -10,6 +10,7 @@ var ICONS = {
|
|
|
10
10
|
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>',
|
|
11
11
|
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>',
|
|
12
12
|
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>',
|
|
13
|
+
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>',
|
|
13
14
|
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>'
|
|
14
15
|
};
|
|
15
16
|
var STYLE_ATTR = "data-movie-controller";
|
|
@@ -47,6 +48,10 @@ var CONTROLLER_CSS = `
|
|
|
47
48
|
transition: height 120ms ease;
|
|
48
49
|
}
|
|
49
50
|
.mc-progress:hover::before, .mc-progress.mc-scrubbing::before { height: calc(var(--mc-track-height, 3px) + 2px); }
|
|
51
|
+
.mc-stops { position: absolute; left: 12px; right: 12px; top: 0; bottom: 0; pointer-events: none; }
|
|
52
|
+
.mc-stop-tick { position: absolute; top: 50%; width: 2px; height: calc(var(--mc-track-height, 3px) + 10px); transform: translate(-50%, -50%); background: var(--mc-fg, #fff); opacity: 0.9; border-radius: 1px; box-shadow: 0 0 0 1px rgba(0,0,0,0.55); }
|
|
53
|
+
.mc-stop-page { width: 4px; height: calc(var(--mc-track-height, 3px) + 20px); opacity: 1; }
|
|
54
|
+
.mc-stop-page::before { content: ''; position: absolute; left: 50%; top: -3px; width: 8px; height: 8px; transform: translateX(-50%); border-radius: 50%; background: var(--mc-fg, #fff); box-shadow: 0 0 0 1px rgba(0,0,0,0.55); }
|
|
50
55
|
.mc-progress-fill {
|
|
51
56
|
position: absolute; left: 12px; top: 50%;
|
|
52
57
|
height: var(--mc-track-height, 3px);
|
|
@@ -268,8 +273,10 @@ function pxToFraction(clientX, rect, inset = 0) {
|
|
|
268
273
|
const ratio = (clientX - innerLeft) / innerWidth;
|
|
269
274
|
return Math.min(Math.max(ratio, 0), 1);
|
|
270
275
|
}
|
|
276
|
+
var PDF_QUALITY = { "low": 0.6, "medium": 0.75, "high": 0.88, "very-high": 0.95 };
|
|
271
277
|
function extensionForMimeType(mime) {
|
|
272
278
|
const t = (mime || "").toLowerCase();
|
|
279
|
+
if (t.includes("pdf")) return "pdf";
|
|
273
280
|
if (t.includes("webm")) return "webm";
|
|
274
281
|
if (t.includes("quicktime") || t.includes("mov")) return "mov";
|
|
275
282
|
if (t.includes("matroska") || t.includes("mkv")) return "mkv";
|
|
@@ -316,6 +323,7 @@ var Controller = class _Controller {
|
|
|
316
323
|
wasPlayingBeforeScrub = false;
|
|
317
324
|
activePointerId = null;
|
|
318
325
|
isExporting = false;
|
|
326
|
+
exportPages = null;
|
|
319
327
|
exportOverlay = null;
|
|
320
328
|
exportFillEl = null;
|
|
321
329
|
exportTextEl = null;
|
|
@@ -323,6 +331,8 @@ var Controller = class _Controller {
|
|
|
323
331
|
settingsKeyHandler = null;
|
|
324
332
|
settingsOutsideHandler = null;
|
|
325
333
|
onReady = null;
|
|
334
|
+
presenter = null;
|
|
335
|
+
presentBtn = null;
|
|
326
336
|
onFrame = null;
|
|
327
337
|
onPlay = null;
|
|
328
338
|
onPause = null;
|
|
@@ -353,6 +363,8 @@ var Controller = class _Controller {
|
|
|
353
363
|
showExportButton: options.showExportButton ?? true,
|
|
354
364
|
enableKeyboardShortcuts: options.enableKeyboardShortcuts ?? true,
|
|
355
365
|
clickToPlay: options.clickToPlay ?? true,
|
|
366
|
+
present: options.present ?? true,
|
|
367
|
+
pauseAtStops: options.pauseAtStops ?? true,
|
|
356
368
|
className: options.className ?? "movie-controller"
|
|
357
369
|
};
|
|
358
370
|
installStyles();
|
|
@@ -371,6 +383,7 @@ var Controller = class _Controller {
|
|
|
371
383
|
}
|
|
372
384
|
this.root.setAttribute("data-state", "visible");
|
|
373
385
|
this.bindMovieEvents();
|
|
386
|
+
this.refreshStops();
|
|
374
387
|
this.bindPlayButton();
|
|
375
388
|
if (this.options.clickToPlay) this.bindCanvasClick();
|
|
376
389
|
this.bindScrubbing();
|
|
@@ -528,6 +541,7 @@ var Controller = class _Controller {
|
|
|
528
541
|
this.refreshTime(0);
|
|
529
542
|
this.refreshProgress(0);
|
|
530
543
|
this.refreshVolumeUI();
|
|
544
|
+
this.refreshStops();
|
|
531
545
|
};
|
|
532
546
|
this.onFrame = ({ frame, totalFrames }) => {
|
|
533
547
|
if (!this.isScrubbing) {
|
|
@@ -554,13 +568,92 @@ var Controller = class _Controller {
|
|
|
554
568
|
this.movie.on("pause", this.onPause);
|
|
555
569
|
this.movie.on("progress", this.onProgress);
|
|
556
570
|
}
|
|
571
|
+
/** The stops of a presentation as marks on the seek bar, and the Present button. */
|
|
572
|
+
refreshStops() {
|
|
573
|
+
this.progressEl.querySelector(".mc-stops")?.remove();
|
|
574
|
+
const stops = this.movie.stops ?? [];
|
|
575
|
+
this.refreshPdfChoices(stops.length > 0);
|
|
576
|
+
if (stops.length === 0) {
|
|
577
|
+
this.presentBtn?.remove();
|
|
578
|
+
this.presentBtn = null;
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const total = this.movie.totalFrames || 1;
|
|
582
|
+
const holder = document.createElement("div");
|
|
583
|
+
holder.className = "mc-stops";
|
|
584
|
+
for (const s of stops) {
|
|
585
|
+
const tick = document.createElement("div");
|
|
586
|
+
tick.className = s.pageStart ? "mc-stop-tick mc-stop-page" : "mc-stop-tick";
|
|
587
|
+
tick.style.left = `${s.frame / total * 100}%`;
|
|
588
|
+
if (s.page) tick.title = s.page;
|
|
589
|
+
holder.appendChild(tick);
|
|
590
|
+
}
|
|
591
|
+
this.progressEl.appendChild(holder);
|
|
592
|
+
if (this.options.present && !this.presentBtn && this.fullscreenBtn) {
|
|
593
|
+
const btn = document.createElement("button");
|
|
594
|
+
btn.className = "mc-btn mc-present";
|
|
595
|
+
btn.setAttribute("aria-label", "Present");
|
|
596
|
+
btn.title = "Present";
|
|
597
|
+
btn.innerHTML = ICONS.present;
|
|
598
|
+
btn.addEventListener("click", () => {
|
|
599
|
+
void this.startPresenting();
|
|
600
|
+
});
|
|
601
|
+
this.fullscreenBtn.parentElement?.insertBefore(btn, this.fullscreenBtn);
|
|
602
|
+
this.presentBtn = btn;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/** A deck can be downloaded as a PDF: the format list gets "PDF" choices while the movie has stops. */
|
|
606
|
+
refreshPdfChoices(has) {
|
|
607
|
+
const select = this.settingsFormatSelect;
|
|
608
|
+
if (!select) return;
|
|
609
|
+
const present = !!select.querySelector('option[value="pdf"]');
|
|
610
|
+
if (has && !present) {
|
|
611
|
+
for (const [value, label] of [["pdf", "PDF (pages)"], ["pdf-steps", "PDF (every step)"]]) {
|
|
612
|
+
const o = document.createElement("option");
|
|
613
|
+
o.value = value;
|
|
614
|
+
o.textContent = label;
|
|
615
|
+
select.appendChild(o);
|
|
616
|
+
}
|
|
617
|
+
} else if (!has && present) {
|
|
618
|
+
select.querySelectorAll('option[value="pdf"], option[value="pdf-steps"]').forEach((o) => o.remove());
|
|
619
|
+
if (this.exportFormat === "pdf" || this.exportFormat === "pdf-steps") {
|
|
620
|
+
this.exportFormat = "mp4";
|
|
621
|
+
select.value = "mp4";
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
/** Play, or pause: a movie with stops plays to the next stop (and from the end, again from the start). */
|
|
626
|
+
async togglePlay() {
|
|
627
|
+
if (this.movie.isPlaying) {
|
|
628
|
+
this.movie.pause();
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
await this.startPlaying();
|
|
632
|
+
}
|
|
633
|
+
/** Start playing: to the next stop for a movie with stops, to the end for any other (or with `pauseAtStops: false`). */
|
|
634
|
+
async startPlaying() {
|
|
635
|
+
if (this.options.pauseAtStops && (this.movie.stops?.length ?? 0) > 0) {
|
|
636
|
+
if (this.movie.currentFrame >= this.movie.totalFrames) await this.movie.gotoFrame(0, true);
|
|
637
|
+
await this.movie.next();
|
|
638
|
+
} else this.movie.play();
|
|
639
|
+
}
|
|
640
|
+
/** Hand the page over to a Presenter (fullscreen, keys, click and swipe move through the stops); Escape gives it back. */
|
|
641
|
+
async startPresenting() {
|
|
642
|
+
if (this.presenter || this.destroyed) return;
|
|
643
|
+
const { Presenter } = await import('./Presenter.cjs');
|
|
644
|
+
if (this.presenter || this.destroyed) return;
|
|
645
|
+
const p = new Presenter(this.movie, { canvas: this.options.canvas, onExit: () => this.stopPresenting() });
|
|
646
|
+
this.presenter = p;
|
|
647
|
+
await p.start();
|
|
648
|
+
}
|
|
649
|
+
stopPresenting() {
|
|
650
|
+
const p = this.presenter;
|
|
651
|
+
this.presenter = null;
|
|
652
|
+
p?.destroy();
|
|
653
|
+
}
|
|
557
654
|
bindPlayButton() {
|
|
558
655
|
this.playBtn.addEventListener("click", () => {
|
|
559
|
-
|
|
560
|
-
this.movie.pause();
|
|
561
|
-
} else {
|
|
562
|
-
this.movie.play();
|
|
563
|
-
}
|
|
656
|
+
void this.togglePlay();
|
|
564
657
|
});
|
|
565
658
|
}
|
|
566
659
|
/** Clicking the picture plays / pauses (a tap on a phone too). */
|
|
@@ -572,12 +665,12 @@ var Controller = class _Controller {
|
|
|
572
665
|
this.swallowCanvasClick = false;
|
|
573
666
|
};
|
|
574
667
|
this.canvasClickHandler = () => {
|
|
668
|
+
if (this.presenter) return;
|
|
575
669
|
if (this.swallowCanvasClick) {
|
|
576
670
|
this.swallowCanvasClick = false;
|
|
577
671
|
return;
|
|
578
672
|
}
|
|
579
|
-
|
|
580
|
-
else this.movie.play();
|
|
673
|
+
void this.togglePlay();
|
|
581
674
|
};
|
|
582
675
|
canvas.addEventListener("pointerdown", this.canvasPointerDownHandler);
|
|
583
676
|
canvas.addEventListener("click", this.canvasClickHandler);
|
|
@@ -733,7 +826,7 @@ var Controller = class _Controller {
|
|
|
733
826
|
} catch {
|
|
734
827
|
}
|
|
735
828
|
if (this.wasPlayingBeforeScrub) {
|
|
736
|
-
this.
|
|
829
|
+
void this.startPlaying();
|
|
737
830
|
this.refreshPlayIcon();
|
|
738
831
|
}
|
|
739
832
|
};
|
|
@@ -782,7 +875,7 @@ var Controller = class _Controller {
|
|
|
782
875
|
if (this.settingsFormatSelect) {
|
|
783
876
|
this.settingsFormatSelect.addEventListener("change", (e) => {
|
|
784
877
|
const v = e.target.value;
|
|
785
|
-
if (v === "mp4" || v === "webm" || v === "mov") this.exportFormat = v;
|
|
878
|
+
if (v === "mp4" || v === "webm" || v === "mov" || v === "pdf" || v === "pdf-steps") this.exportFormat = v;
|
|
786
879
|
});
|
|
787
880
|
}
|
|
788
881
|
if (this.settingsQualitySelect) {
|
|
@@ -832,9 +925,19 @@ var Controller = class _Controller {
|
|
|
832
925
|
this.movie.pause();
|
|
833
926
|
this.refreshPlayIcon();
|
|
834
927
|
}
|
|
835
|
-
this.
|
|
928
|
+
const pdf = this.exportFormat === "pdf" || this.exportFormat === "pdf-steps";
|
|
929
|
+
if (pdf) {
|
|
930
|
+
const stops = this.movie.stops ?? [];
|
|
931
|
+
const total = this.exportFormat === "pdf-steps" ? stops.filter((s) => s.pdf !== false).length : new Set(stops.map((s) => s.pageIndex)).size;
|
|
932
|
+
this.showExportOverlay("Exporting PDF...", `0% (0 / ${total} pages)`);
|
|
933
|
+
this.exportPages = { done: 0, total };
|
|
934
|
+
} else this.showExportOverlay();
|
|
836
935
|
try {
|
|
837
|
-
const blob = await this.movie.
|
|
936
|
+
const blob = pdf ? await this.movie.exportPDF({
|
|
937
|
+
which: this.exportFormat === "pdf-steps" ? "stops" : "pages",
|
|
938
|
+
quality: PDF_QUALITY[this.exportQuality],
|
|
939
|
+
onImage: () => this.pdfPageDone()
|
|
940
|
+
}) : await this.movie.render({
|
|
838
941
|
format: this.exportFormat,
|
|
839
942
|
video: { bitrate: this.exportQuality },
|
|
840
943
|
audio: { bitrate: this.exportQuality }
|
|
@@ -850,7 +953,7 @@ var Controller = class _Controller {
|
|
|
850
953
|
this.hideExportOverlay();
|
|
851
954
|
this.isExporting = false;
|
|
852
955
|
if (wasPlaying) {
|
|
853
|
-
this.
|
|
956
|
+
void this.startPlaying();
|
|
854
957
|
this.refreshPlayIcon();
|
|
855
958
|
}
|
|
856
959
|
}
|
|
@@ -873,16 +976,27 @@ var Controller = class _Controller {
|
|
|
873
976
|
const ts = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
874
977
|
return `movie-${ts}.${ext}`;
|
|
875
978
|
}
|
|
876
|
-
|
|
979
|
+
/** One more PDF page is ready: move the bar. */
|
|
980
|
+
pdfPageDone() {
|
|
981
|
+
const p = this.exportPages;
|
|
982
|
+
if (!p || !this.exportFillEl || !this.exportTextEl) return;
|
|
983
|
+
p.done++;
|
|
984
|
+
const percent = p.total ? Math.min(100, Math.round(p.done / p.total * 100)) : 100;
|
|
985
|
+
this.exportFillEl.style.width = `${percent}%`;
|
|
986
|
+
this.exportTextEl.textContent = `${percent}% (${p.done} / ${p.total} pages)`;
|
|
987
|
+
}
|
|
988
|
+
showExportOverlay(title = "Exporting video...", text = "0% (0 / 0 frames)") {
|
|
877
989
|
const overlay = document.createElement("div");
|
|
878
990
|
overlay.className = "mc-export-overlay";
|
|
879
991
|
overlay.innerHTML = `
|
|
880
992
|
<div class="mc-export-panel">
|
|
881
|
-
<div class="mc-export-title"
|
|
993
|
+
<div class="mc-export-title"></div>
|
|
882
994
|
<div class="mc-export-track"><div class="mc-export-fill"></div></div>
|
|
883
|
-
<div class="mc-export-text"
|
|
995
|
+
<div class="mc-export-text"></div>
|
|
884
996
|
</div>
|
|
885
997
|
`;
|
|
998
|
+
overlay.querySelector(".mc-export-title").textContent = title;
|
|
999
|
+
overlay.querySelector(".mc-export-text").textContent = text;
|
|
886
1000
|
this.root.appendChild(overlay);
|
|
887
1001
|
this.exportOverlay = overlay;
|
|
888
1002
|
this.exportFillEl = overlay.querySelector(".mc-export-fill");
|
|
@@ -895,6 +1009,7 @@ var Controller = class _Controller {
|
|
|
895
1009
|
this.exportFillEl = null;
|
|
896
1010
|
this.exportTextEl = null;
|
|
897
1011
|
}
|
|
1012
|
+
this.exportPages = null;
|
|
898
1013
|
}
|
|
899
1014
|
bindVisibility() {
|
|
900
1015
|
const wrap = this.wrapper;
|
|
@@ -937,6 +1052,7 @@ var Controller = class _Controller {
|
|
|
937
1052
|
}
|
|
938
1053
|
bindKeyboard() {
|
|
939
1054
|
this.keyHandler = (e) => {
|
|
1055
|
+
if (this.presenter) return;
|
|
940
1056
|
const target = e.target;
|
|
941
1057
|
if (target) {
|
|
942
1058
|
const tag = target.tagName;
|
|
@@ -998,6 +1114,7 @@ var Controller = class _Controller {
|
|
|
998
1114
|
destroy() {
|
|
999
1115
|
if (this.destroyed) return;
|
|
1000
1116
|
this.destroyed = true;
|
|
1117
|
+
this.stopPresenting();
|
|
1001
1118
|
if (this.onReady) {
|
|
1002
1119
|
this.movie.off("ready", this.onReady);
|
|
1003
1120
|
this.onReady = null;
|