pixi-effects 0.19.0 → 0.20.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 (69) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +6 -6
  3. package/ai/SKILL.md +1 -0
  4. package/ai/chat-template.html +3 -3
  5. package/ai/reference/cheatsheet.md +7 -6
  6. package/ai/reference/pitfalls.md +5 -0
  7. package/ai/reference/recipes.md +52 -0
  8. package/ai/template.html +5 -5
  9. package/ai/tools/check.mjs +15 -3
  10. package/dist/{Base-B4WbUJO2.d.ts → Base-CzfLGoDr.d.ts} +9 -1
  11. package/dist/{Base-i88XXaiP.d.cts → Base-wYJQ2g5Q.d.cts} +9 -1
  12. package/dist/Composition-6PA4C4TV.js +5 -0
  13. package/dist/{Composition-QBKEEJBV.js.map → Composition-6PA4C4TV.js.map} +1 -1
  14. package/dist/Composition-LL2XXU5N.cjs +14 -0
  15. package/dist/{Composition-KYNPRWK3.cjs.map → Composition-LL2XXU5N.cjs.map} +1 -1
  16. package/dist/Controller.cjs +2 -2
  17. package/dist/Controller.d.cts +2 -2
  18. package/dist/Controller.d.ts +2 -2
  19. package/dist/Controller.js +1 -1
  20. package/dist/{Movie-8IrqL3kO.d.ts → Movie-CBxGH5ms.d.ts} +8 -2
  21. package/dist/{Movie-CpmdNadg.d.cts → Movie-CzzJw8A-.d.cts} +8 -2
  22. package/dist/Presenter.cjs +2 -2
  23. package/dist/Presenter.d.cts +2 -2
  24. package/dist/Presenter.d.ts +2 -2
  25. package/dist/Presenter.js +1 -1
  26. package/dist/{chunk-YRXTKOV5.js → chunk-DQHXVNOI.js} +282 -23
  27. package/dist/chunk-DQHXVNOI.js.map +1 -0
  28. package/dist/{chunk-CTZRFSVU.cjs → chunk-FFYA3UBU.cjs} +144 -37
  29. package/dist/chunk-FFYA3UBU.cjs.map +1 -0
  30. package/dist/{chunk-DKGG7WD4.js → chunk-MNM56B3X.js} +6 -3
  31. package/dist/chunk-MNM56B3X.js.map +1 -0
  32. package/dist/{chunk-VTBT6NNE.cjs → chunk-NO26HWR7.cjs} +294 -30
  33. package/dist/chunk-NO26HWR7.cjs.map +1 -0
  34. package/dist/{chunk-B3YB2TNY.js → chunk-TSPMKV2F.js} +120 -13
  35. package/dist/chunk-TSPMKV2F.js.map +1 -0
  36. package/dist/{chunk-UQBGW5DO.cjs → chunk-WTRKDIKH.cjs} +6 -3
  37. package/dist/chunk-WTRKDIKH.cjs.map +1 -0
  38. package/dist/index.cjs +213 -90
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +4 -4
  41. package/dist/index.d.ts +4 -4
  42. package/dist/index.js +154 -31
  43. package/dist/index.js.map +1 -1
  44. package/dist/loader.css +1 -1
  45. package/dist/{music-4HDFULNR.js → music-CFEC7HKL.js} +3 -3
  46. package/dist/{music-4HDFULNR.js.map → music-CFEC7HKL.js.map} +1 -1
  47. package/dist/{music-3OF5C6X5.cjs → music-UAN7DXK4.cjs} +7 -7
  48. package/dist/{music-3OF5C6X5.cjs.map → music-UAN7DXK4.cjs.map} +1 -1
  49. package/dist/{sfx-MZJZXYY3.cjs → sfx-EX4PUBMV.cjs} +8 -8
  50. package/dist/{sfx-MZJZXYY3.cjs.map → sfx-EX4PUBMV.cjs.map} +1 -1
  51. package/dist/{sfx-S37DDC66.js → sfx-U3GM2MED.js} +3 -3
  52. package/dist/{sfx-S37DDC66.js.map → sfx-U3GM2MED.js.map} +1 -1
  53. package/dist/three.cjs +6 -6
  54. package/dist/three.d.cts +2 -2
  55. package/dist/three.d.ts +2 -2
  56. package/dist/three.js +2 -2
  57. package/dist/{types-bzLJ7aUx.d.ts → types-YmUaViL1.d.cts} +24 -0
  58. package/dist/{types-bzLJ7aUx.d.cts → types-YmUaViL1.d.ts} +24 -0
  59. package/docs/dsl.md +22 -0
  60. package/llms-full.txt +87 -6
  61. package/package.json +3 -1
  62. package/dist/Composition-KYNPRWK3.cjs +0 -14
  63. package/dist/Composition-QBKEEJBV.js +0 -5
  64. package/dist/chunk-B3YB2TNY.js.map +0 -1
  65. package/dist/chunk-CTZRFSVU.cjs.map +0 -1
  66. package/dist/chunk-DKGG7WD4.js.map +0 -1
  67. package/dist/chunk-UQBGW5DO.cjs.map +0 -1
  68. package/dist/chunk-VTBT6NNE.cjs.map +0 -1
  69. package/dist/chunk-YRXTKOV5.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var chunkCTZRFSVU_cjs = require('./chunk-CTZRFSVU.cjs');
4
- var chunkVTBT6NNE_cjs = require('./chunk-VTBT6NNE.cjs');
5
- var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
3
+ var chunkFFYA3UBU_cjs = require('./chunk-FFYA3UBU.cjs');
4
+ var chunkNO26HWR7_cjs = require('./chunk-NO26HWR7.cjs');
5
+ var chunkWTRKDIKH_cjs = require('./chunk-WTRKDIKH.cjs');
6
6
  var PIXI = require('pixi.js');
7
7
  var gsap = require('gsap');
8
8
  var PixiPlugin = require('gsap/PixiPlugin');
@@ -85,6 +85,85 @@ async function loadAssetBundle(assets, audioContext) {
85
85
  return await PIXI.Assets.loadBundle("pixi-effects");
86
86
  }
87
87
 
88
+ // src/core/audioRemap.ts
89
+ function gainAt(a, u) {
90
+ let prevT = a.start, prevV = a.initialVolume ?? 1;
91
+ if (u <= prevT) return prevV;
92
+ for (const kf of a.volumeKeyframes ?? []) {
93
+ if (u <= kf.time) return kf.time === prevT ? kf.value : prevV + (kf.value - prevV) * ((u - prevT) / (kf.time - prevT));
94
+ prevT = kf.time;
95
+ prevV = kf.value;
96
+ }
97
+ return prevV;
98
+ }
99
+ var CELL = 1 / 1e3;
100
+ function resampleThrough(a, left, right, srcRate, outRate, total) {
101
+ const nCell = Math.ceil(total / CELL) + 2;
102
+ const U = new Float64Array(nCell);
103
+ let first = -1, last = -1;
104
+ for (let k = 0; k < nCell; k++) {
105
+ const u = a.warp ? a.warp(k * CELL) : k * CELL;
106
+ U[k] = u;
107
+ if (Number.isFinite(u) && u >= a.start && u < a.end) {
108
+ if (first < 0) first = k;
109
+ last = k;
110
+ }
111
+ }
112
+ if (first < 0) return null;
113
+ const lo = Math.max(0, (first - 1) * CELL), hi = Math.min(total, (last + 2) * CELL);
114
+ const n = Math.max(1, Math.ceil((hi - lo) * outRate));
115
+ const L = new Float32Array(n), R = new Float32Array(n);
116
+ const dur = left.length / srcRate;
117
+ let prevPos = NaN;
118
+ for (let i = 0; i < n; i++) {
119
+ const T = lo + i / outRate;
120
+ const x = T / CELL, k = Math.min(Math.floor(x), nCell - 2), f = x - k;
121
+ const u0 = U[k], u1 = U[k + 1];
122
+ if (!(Number.isFinite(u0) && Number.isFinite(u1))) {
123
+ prevPos = NaN;
124
+ continue;
125
+ }
126
+ const u = u0 * (1 - f) + u1 * f;
127
+ if (u < a.start || u >= a.end) {
128
+ prevPos = NaN;
129
+ continue;
130
+ }
131
+ const raw = a.sourceMap ? a.sourceMap(u) : u - a.start;
132
+ let pos = raw;
133
+ if (a.loop) pos = (pos % dur + dur) % dur;
134
+ const rate = Number.isNaN(prevPos) ? 1 : Math.abs(raw - prevPos) * outRate;
135
+ prevPos = raw;
136
+ if (pos < 0 || pos >= dur) continue;
137
+ const g = gainAt(a, u) * Math.min(1, rate / 0.02);
138
+ const sp = pos * srcRate, j = Math.floor(sp), fr = sp - j, j1 = Math.min(j + 1, left.length - 1);
139
+ L[i] = (left[j] * (1 - fr) + left[j1] * fr) * g;
140
+ R[i] = (right[j] * (1 - fr) + right[j1] * fr) * g;
141
+ }
142
+ return { L, R, at: lo };
143
+ }
144
+ function playSpan(a, total) {
145
+ if (!a.warp) return { start: a.start, end: a.end };
146
+ let first = -1, last = -1;
147
+ const n = Math.ceil(total / CELL) + 1;
148
+ for (let k = 0; k <= n; k++) {
149
+ const u = a.warp(k * CELL);
150
+ if (Number.isFinite(u) && u >= a.start && u < a.end) {
151
+ if (first < 0) first = k;
152
+ last = k;
153
+ }
154
+ }
155
+ return first < 0 ? { start: total, end: total } : { start: first * CELL, end: (last + 1) * CELL };
156
+ }
157
+ function clockReach(a, total) {
158
+ let reach = -Infinity;
159
+ const n = Math.ceil(total / CELL) + 1;
160
+ for (let k = 0; k <= n; k++) {
161
+ const u = a.warp ? a.warp(k * CELL) : k * CELL;
162
+ if (Number.isFinite(u) && u > reach) reach = u;
163
+ }
164
+ return reach;
165
+ }
166
+
88
167
  // src/core/AudioMixer.ts
89
168
  async function mixdown(audios, totalDuration, sampleRate = 44100) {
90
169
  if (audios.length === 0) return null;
@@ -106,6 +185,18 @@ async function mixdown(audios, totalDuration, sampleRate = 44100) {
106
185
  for (const a of audios) {
107
186
  const buffer = bufferOf(a);
108
187
  if (!buffer) continue;
188
+ if (a.warp || a.sourceMap) {
189
+ const r = resampleThrough(a, buffer.getChannelData(0), buffer.numberOfChannels > 1 ? buffer.getChannelData(1) : buffer.getChannelData(0), buffer.sampleRate, sampleRate, totalDuration);
190
+ if (!r) continue;
191
+ const out = ctx.createBuffer(2, r.L.length, sampleRate);
192
+ out.getChannelData(0).set(r.L);
193
+ out.getChannelData(1).set(r.R);
194
+ const rendered = ctx.createBufferSource();
195
+ rendered.buffer = out;
196
+ rendered.connect(ctx.destination);
197
+ rendered.start(r.at);
198
+ continue;
199
+ }
109
200
  const begin = Math.max(0, a.start);
110
201
  const skipped = begin - a.start;
111
202
  if (skipped > 0) {
@@ -144,7 +235,7 @@ function limitMix(buffer, audios) {
144
235
  }
145
236
  const peakAt = peakIndex / buffer.sampleRate;
146
237
  if (peak > 1) {
147
- const playing = audios.filter((a) => a.start <= peakAt && peakAt < a.end).map((a) => a.layer ?? "an audio layer");
238
+ const playing = audios.map((a) => ({ a, ...playSpan(a, buffer.duration) })).filter((s) => s.start <= peakAt && peakAt < s.end).map((s) => s.a.layer ?? "an audio layer");
148
239
  console.warn(
149
240
  `pixi-effects: the audio mix peaks at ${peak.toFixed(2)} (${(20 * Math.log10(peak)).toFixed(1)} dBFS) at ${peakAt.toFixed(2)}s, so it was limited (it would distort). Playing there: ${[...new Set(playing)].join(", ")}. Lower their volume.`
150
241
  );
@@ -339,6 +430,13 @@ function measure(chans, sampleRate) {
339
430
  };
340
431
  }
341
432
  function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
433
+ const warped = /* @__PURE__ */ new Map();
434
+ sources = sources.map((s) => {
435
+ if (!s.warp) return s;
436
+ const moved = { ...s, ...playSpan(s, movieDuration) };
437
+ warped.set(moved, { localEnd: s.end, reach: clockReach(s, movieDuration) });
438
+ return moved;
439
+ });
342
440
  if (!mix) {
343
441
  return {
344
442
  duration: movieDuration,
@@ -405,8 +503,15 @@ function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
405
503
  } else if (ownDb < INAUDIBLE_DB || peakDb < INAUDIBLE_DB) {
406
504
  issues.push(`${layer} (${source}) is inaudible from ${s.start.toFixed(2)}s to ${s.end.toFixed(2)}s (its own peak ${ownDb} dBFS at volume ${topVolume}; mix peak there ${peakDb} dBFS) \u2014 check its volume`);
407
505
  }
408
- if (s.synth && s.end > movieDuration + 1e-6 && s.start < movieDuration) {
409
- issues.push(`${layer} (${source}) is cut off by the end of the movie at ${movieDuration}s (it runs to ${s.end.toFixed(2)}s)`);
506
+ const clock = warped.get(s);
507
+ if (s.synth && s.start < movieDuration) {
508
+ if (clock) {
509
+ if (clock.reach < clock.localEnd - 1e-3) {
510
+ issues.push(`${layer} (${source}) is cut off: its composition's clock only reaches ${clock.reach.toFixed(2)}s (the sound is ${clock.localEnd.toFixed(2)}s long), because the clock stops or the movie ends first \u2014 give the layer a shorter duration, or let the clock run on`);
511
+ }
512
+ } else if (s.end > movieDuration + 1e-6) {
513
+ issues.push(`${layer} (${source}) is cut off by the end of the movie at ${movieDuration}s (it runs to ${s.end.toFixed(2)}s)`);
514
+ }
410
515
  }
411
516
  return { layer, source, start: s.start, end: s.end, peakDb, sound };
412
517
  });
@@ -486,14 +591,20 @@ function inspectFonts(root) {
486
591
 
487
592
  // src/core/scenes.ts
488
593
  function namedScenes(rows) {
489
- return rows.filter((r) => r.depth === 0 && r.type === "composition" && !/[#×]/.test(r.name) && r.end - r.start >= 1).map((r) => ({ name: r.name, start: r.start, end: r.end }));
594
+ return rows.filter((r) => !r.local && r.depth === 0 && r.type === "composition" && !/[#×]/.test(r.name) && r.end - r.start >= 1).map((r) => ({ name: r.name, start: r.start, end: r.end }));
490
595
  }
491
596
 
492
597
  // src/core/timeFilters.ts
493
- function collectTimeFilters(seq, out = []) {
494
- for (const f of seq.filters ?? []) if (typeof f.setTime === "function") out.push(f);
495
- if (seq.maskSequence) collectTimeFilters(seq.maskSequence, out);
496
- for (const child of seq._children ?? []) collectTimeFilters(child, out);
598
+ function collectTimeFilters(seq, out = [], clock = null) {
599
+ for (const f of seq.filters ?? []) {
600
+ if (typeof f.setTime === "function") {
601
+ const tf = f;
602
+ out.push(clock ? { setTime: () => tf.setTime(clock()) } : tf);
603
+ }
604
+ }
605
+ if (seq.maskSequence) collectTimeFilters(seq.maskSequence, out, clock);
606
+ const own = seq.localClock?.() ?? clock;
607
+ for (const child of seq._children ?? []) collectTimeFilters(child, out, own);
497
608
  return out;
498
609
  }
499
610
 
@@ -518,11 +629,13 @@ var AT_HELP = "use seconds (3.5), a percentage (50%), a frame (f120) or a layer'
518
629
  function resolveAtList(list, ctx) {
519
630
  const { frameRate: fps, totalFrames, duration, rows } = ctx;
520
631
  const out = [], used = /* @__PURE__ */ new Set();
521
- const named = rows.flatMap((r) => r.parts && r.partNames ? r.partNames.map((name, i) => ({ name, start: r.parts[i].start, end: r.parts[i].end })) : [r]);
632
+ const named = rows.filter((r) => !r.local).flatMap((r) => r.parts && r.partNames ? r.partNames.map((name, i) => ({ name, start: r.parts[i].start, end: r.parts[i].end })) : [r]);
522
633
  for (const raw of String(list).split(",").map((s) => s.trim()).filter(Boolean)) {
523
634
  let frame, label, m;
524
635
  if (m = raw.match(/^(.+)@(start|mid|end)$/)) {
525
636
  const row = named.find((r) => r.name === m[1]);
637
+ const inside = row ? void 0 : rows.find((r) => r.local && r.name === m[1]);
638
+ if (inside) throw new Error(`--at "${raw}": layer "${m[1]}" is inside a time-remapped composition (${inside.local}), so its times are that composition's local seconds, not the movie's; name the composition (${inside.local.replace(/ (×.*|time keyframes)$/, "")}@${m[2]}) or give seconds`);
526
639
  if (!row) throw new Error(`--at "${raw}": no layer named "${m[1]}" (names: ${named.slice(0, 40).map((r) => r.name).join(", ")}${named.length > 40 ? ", \u2026" : ""})`);
527
640
  const t = m[2] === "start" ? row.start : m[2] === "mid" ? (row.start + row.end) / 2 : row.end - 1 / fps;
528
641
  frame = Math.round(t * fps);
@@ -602,7 +715,7 @@ var DEFAULTS = { samples: 8, shutter: 0.5 };
602
715
  function resolveMotionBlur(spec, where) {
603
716
  if (spec === void 0 || spec === null || spec === false) return null;
604
717
  const o = spec === true ? {} : typeof spec === "number" ? { samples: spec } : spec;
605
- if (typeof spec === "object") chunkUQBGW5DO_cjs.warnUnknownOptions(`${where} motionBlur`, o, ["samples", "shutter"]);
718
+ if (typeof spec === "object") chunkWTRKDIKH_cjs.warnUnknownOptions(`${where} motionBlur`, o, ["samples", "shutter"]);
606
719
  const samples = o.samples ?? DEFAULTS.samples, shutter = o.shutter ?? DEFAULTS.shutter;
607
720
  if (!(Number.isInteger(samples) && samples >= 2 && samples <= 64)) throw new Error(`pixi-effects: ${where}: motionBlur samples must be a whole number from 2 to 64, got ${samples}`);
608
721
  if (!(Number.isFinite(shutter) && shutter > 0 && shutter <= 1)) throw new Error(`pixi-effects: ${where}: motionBlur shutter must be a fraction of the frame time above 0 and up to 1 (0.5 is a 180\xB0 shutter), got ${shutter}`);
@@ -619,7 +732,7 @@ function resolveRange(range2, ctx) {
619
732
  if (range2 === void 0) return { fromFrame: 0, toFrame: totalFrames, fromSec: 0, toSec: duration, partial: false };
620
733
  let from, to;
621
734
  if (typeof range2 === "string") {
622
- const rows = (ctx.rows ?? []).filter((r) => r.depth === 0).flatMap((r) => r.parts && r.partNames ? r.partNames.map((name, i) => ({ name, start: r.parts[i].start, end: r.parts[i].end })) : [r]);
735
+ const rows = (ctx.rows ?? []).filter((r) => r.depth === 0 && !r.local).flatMap((r) => r.parts && r.partNames ? r.partNames.map((name, i) => ({ name, start: r.parts[i].start, end: r.parts[i].end })) : [r]);
623
736
  const row = rows.find((r) => r.name === range2);
624
737
  if (!row) throw new Error(`pixi-effects: render({ range: "${range2}" }): no layer named "${range2}" at the top level (names: ${rows.slice(0, 40).map((r) => r.name).join(", ") || "none"}${rows.length > 40 ? ", \u2026" : ""})`);
625
738
  from = row.start;
@@ -1165,7 +1278,8 @@ function expandTransitions(spec) {
1165
1278
  }
1166
1279
  const transitions = out.transitions;
1167
1280
  const sequences = out.sequences ?? [];
1168
- const parentDuration = out.duration ?? Infinity;
1281
+ const remap = out.duration !== void 0 ? chunkNO26HWR7_cjs.timeRemapOf(out) : null;
1282
+ const parentDuration = remap ? chunkNO26HWR7_cjs.contentLength(remap, out.duration) : out.duration ?? Infinity;
1169
1283
  const compW = out.width;
1170
1284
  const compH = out.height;
1171
1285
  const threeDByName = new Set(sequences.filter((s) => s.name && s.threeD).map((s) => s.name));
@@ -1233,16 +1347,16 @@ function expandTransitions(spec) {
1233
1347
  if (toUses > 1) {
1234
1348
  throw new Error(`pixi-effects: ${tag} sequence "${t.to}" is used as \`to\` in more than one transition`);
1235
1349
  }
1236
- const tStart = chunkVTBT6NNE_cjs.resolveAt(t.at, parentDuration);
1350
+ const tStart = chunkNO26HWR7_cjs.resolveAt(t.at, parentDuration);
1237
1351
  const tEnd = tStart + t.duration;
1238
1352
  const fromSeq = fromEntry.seq;
1239
- const fromStart = chunkVTBT6NNE_cjs.resolveAt(fromSeq.at, parentDuration);
1353
+ const fromStart = chunkNO26HWR7_cjs.resolveAt(fromSeq.at, parentDuration);
1240
1354
  const fromEnd = fromStart + (fromSeq.duration ?? parentDuration);
1241
1355
  if (tStart < fromStart - EPS || tEnd > fromEnd + EPS) {
1242
1356
  throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`from\` "${t.from}" (lives [${fromStart}, ${fromEnd}], so it ends at ${fromEnd})`);
1243
1357
  }
1244
1358
  const toSeq = toEntry.seq;
1245
- const toStart = chunkVTBT6NNE_cjs.resolveAt(toSeq.at, parentDuration);
1359
+ const toStart = chunkNO26HWR7_cjs.resolveAt(toSeq.at, parentDuration);
1246
1360
  const toEnd = toStart + (toSeq.duration ?? parentDuration);
1247
1361
  if (tStart < toStart - EPS || tEnd > toEnd + EPS) {
1248
1362
  throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`to\` "${t.to}" (lives [${toStart}, ${toEnd}], so it starts at ${toStart})`);
@@ -1556,9 +1670,10 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1556
1670
  const scaledToNothing = /* @__PURE__ */ new Set();
1557
1671
  const walk2 = (comp, prefix, insideThreeD, parentVisible, parentScene, parentMoving = false, parentZero = false) => {
1558
1672
  const blending = /* @__PURE__ */ new Set();
1673
+ const now = comp.childTime(time);
1559
1674
  for (const w of transitionWindowsOf(comp.spec)) {
1560
- const start = (comp.absoluteStart ?? 0) + w.start, end = (comp.absoluteStart ?? 0) + w.end;
1561
- if (time >= start - 1e-9 && time <= end + 1e-9) {
1675
+ const start = comp.childBase + w.start, end = comp.childBase + w.end;
1676
+ if (now >= start - 1e-9 && now <= end + 1e-9) {
1562
1677
  blending.add(w.from);
1563
1678
  blending.add(w.to);
1564
1679
  }
@@ -1582,9 +1697,9 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1582
1697
  onCanvas = inter === 0 && area > 0 ? "none" : inter >= area - 1e-6 ? "full" : "partial";
1583
1698
  if (area === 0) onCanvas = "none";
1584
1699
  }
1585
- const keys = chunkVTBT6NNE_cjs.collectPropKeys(seq.spec);
1700
+ const keys = chunkNO26HWR7_cjs.collectPropKeys(seq.spec);
1586
1701
  const carried = carriers.some((c) => {
1587
- const k = chunkVTBT6NNE_cjs.collectPropKeys(c.spec);
1702
+ const k = chunkNO26HWR7_cjs.collectPropKeys(c.spec);
1588
1703
  return (c.spec.keyframes ?? []).length > 0 && (k.has("x") || k.has("y") || k.has("rotation") || k.has("scale"));
1589
1704
  });
1590
1705
  const moving = parentMoving || carried || (seq.spec.keyframes ?? []).length > 0 && (keys.has("x") || keys.has("y"));
@@ -1605,7 +1720,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1605
1720
  const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
1606
1721
  if (group) scene.set(info, group);
1607
1722
  if (zero || seq.showsNothingYet) scaledToNothing.add(info);
1608
- if (seq instanceof chunkCTZRFSVU_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
1723
+ if (seq instanceof chunkFFYA3UBU_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
1609
1724
  });
1610
1725
  };
1611
1726
  walk2(root, "", false, true);
@@ -1659,23 +1774,26 @@ function detailOf(seq) {
1659
1774
  if (s.music && typeof s.music === "object") return "music";
1660
1775
  return void 0;
1661
1776
  }
1662
- function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions) {
1777
+ function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions, local) {
1663
1778
  const entries = [];
1664
1779
  comp._children.forEach((seq, i) => {
1665
1780
  const name = seq.spec.name ?? `${seq.spec.type}#${i}`;
1666
1781
  const start = parentStart + (seq.at ?? 0);
1667
1782
  const end = Math.min(parentEnd, seq.duration !== void 0 ? start + seq.duration : parentEnd);
1668
1783
  const span = end - start;
1669
- const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkVTBT6NNE_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
1670
- const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq) };
1784
+ const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkNO26HWR7_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
1785
+ const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq), ...local ? { local } : {} };
1671
1786
  const inner = [];
1672
- const isComp = seq instanceof chunkCTZRFSVU_cjs.CompositionSequence;
1673
- if (isComp) walk(seq, start, end, depth + 1, prefix + name + "/", inner, transitions);
1787
+ const isComp = seq instanceof chunkFFYA3UBU_cjs.CompositionSequence;
1788
+ if (isComp) {
1789
+ const label = seq.remapLabel();
1790
+ walk(seq, label ? 0 : start, label ? seq.contentSpan : end, depth + 1, prefix + name + "/", inner, transitions, label ? `${name} ${label}` : local);
1791
+ }
1674
1792
  entries.push({ row, inner, groupable: !isComp });
1675
1793
  });
1676
1794
  rows.push(...groupFamilies(entries));
1677
1795
  for (const w of transitionWindowsOf(comp.spec)) {
1678
- transitions.push({ from: prefix + w.from, to: prefix + w.to, start: round(parentStart + w.start), end: round(parentStart + w.end) });
1796
+ transitions.push({ from: prefix + w.from, to: prefix + w.to, start: round(parentStart + w.start), end: round(parentStart + w.end), ...local ? { local } : {} });
1679
1797
  }
1680
1798
  }
1681
1799
  function groupFamilies(entries) {
@@ -1716,7 +1834,8 @@ function groupFamilies(entries) {
1716
1834
  function collectTimeline(root, duration) {
1717
1835
  const rows = [];
1718
1836
  const transitions = [];
1719
- walk(root, 0, duration, 0, "", rows, transitions);
1837
+ const label = root.remapLabel();
1838
+ walk(root, 0, label ? root.contentSpan : duration, 0, "", rows, transitions, label ? `movie ${label}` : void 0);
1720
1839
  return { duration, rows, transitions };
1721
1840
  }
1722
1841
  var COLORS = {
@@ -1749,12 +1868,12 @@ function timelineSvg(data, opts = {}) {
1749
1868
  }
1750
1869
  if (Math.abs(data.duration % step) > 1e-9) out.push(`<text class="tick" x="${num(x(data.duration))}" y="${TOP - 12}" text-anchor="middle">${num(data.duration)} s</text>`);
1751
1870
  for (const t of data.transitions) {
1752
- out.push(`<g><title>transition ${esc(t.from)} \u2192 ${esc(t.to)} (${num(t.start)}\u2013${num(t.end)} s)</title><rect class="transition" x="${num(x(t.start))}" y="${TOP - 4}" width="${num(Math.max(2, x(t.end) - x(t.start)))}" height="${H - TOP - PAD + 4}"/></g>`);
1871
+ out.push(`<g><title>transition ${esc(t.from)} \u2192 ${esc(t.to)} (${num(t.start)}\u2013${num(t.end)} s${t.local ? `, local time of ${esc(t.local)}` : ""})</title><rect class="transition" x="${num(x(t.start))}" y="${TOP - 4}" width="${num(Math.max(2, x(t.end) - x(t.start)))}" height="${H - TOP - PAD + 4}"/></g>`);
1753
1872
  }
1754
1873
  data.rows.forEach((r, i) => {
1755
1874
  const y = TOP + i * ROW;
1756
1875
  const color = COLORS[r.type] ?? "#9aa7b8";
1757
- const tip = `${r.path} \xB7 ${r.type} \xB7 ${num(r.start)}\u2013${num(r.end)} s (${num(r.end - r.start)} s)${r.keys.length ? ` \xB7 ${r.keys.length} keyframe${r.keys.length > 1 ? "s" : ""}` : ""}${r.detail ? ` \xB7 ${r.detail}` : ""}`;
1876
+ const tip = `${r.path} \xB7 ${r.type} \xB7 ${num(r.start)}\u2013${num(r.end)} s (${num(r.end - r.start)} s)${r.keys.length ? ` \xB7 ${r.keys.length} keyframe${r.keys.length > 1 ? "s" : ""}` : ""}${r.detail ? ` \xB7 ${r.detail}` : ""}${r.local ? ` \xB7 local time of ${r.local}` : ""}`;
1758
1877
  out.push(`<g class="row" data-start="${num(r.start)}"><title>${esc(tip)}</title>`);
1759
1878
  out.push(`<rect class="band" x="${PAD}" y="${y}" width="${LABEL + CHART}" height="${ROW}" ${i % 2 ? 'fill-opacity=".05"' : 'fill-opacity="0"'}/>`);
1760
1879
  if (withLabels) out.push(`<text class="label" x="${PAD + 6 + r.depth * 14}" y="${y + 15}">${esc(r.name)}</text>`);
@@ -1948,7 +2067,7 @@ function normalizeStops(raw, duration, frameRate) {
1948
2067
  console.warn(`pixi-effects: stops[${i}] ${JSON.stringify(r)} is not a time in seconds (or { at, page?, notes?, advance?, pdf? }); ignored`);
1949
2068
  return;
1950
2069
  }
1951
- if (typeof r === "object") chunkUQBGW5DO_cjs.warnUnknownOptions(`stops[${i}]`, r, KEYS);
2070
+ if (typeof r === "object") chunkWTRKDIKH_cjs.warnUnknownOptions(`stops[${i}]`, r, KEYS);
1952
2071
  const at = spec.at < 0 ? duration + spec.at : spec.at;
1953
2072
  if (at < -1e-9 || at > duration + 1e-9) {
1954
2073
  console.warn(`pixi-effects: stops[${i}] at ${spec.at} s is outside the movie (0 to ${duration} s); ignored`);
@@ -2178,7 +2297,7 @@ var Movie = class {
2178
2297
  return this._audioContext;
2179
2298
  }
2180
2299
  async init(options = {}) {
2181
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.init()", options, ["width", "height", "duration", "frameRate", "background", "canvas", "assets", "composition", "poster", "motionBlur", "loader"]);
2300
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.init()", options, ["width", "height", "duration", "frameRate", "background", "canvas", "assets", "composition", "poster", "motionBlur", "loader"]);
2182
2301
  this._initState = "pending";
2183
2302
  try {
2184
2303
  this.width = options.width ?? 1920;
@@ -2227,7 +2346,7 @@ var Movie = class {
2227
2346
  height: options.composition.height ?? this.height,
2228
2347
  duration: options.composition.duration ?? this.duration
2229
2348
  } : void 0;
2230
- await chunkVTBT6NNE_cjs.ensureFilterLibrary(seededComposition);
2349
+ await chunkNO26HWR7_cjs.ensureFilterLibrary(seededComposition);
2231
2350
  const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
2232
2351
  const rootSeqSpec = {
2233
2352
  type: "composition",
@@ -2237,7 +2356,7 @@ var Movie = class {
2237
2356
  ...userComposition
2238
2357
  };
2239
2358
  if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
2240
- const composition = new chunkCTZRFSVU_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
2359
+ const composition = new chunkFFYA3UBU_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
2241
2360
  await composition.build();
2242
2361
  this._rootSequence = composition;
2243
2362
  if (composition.target) root.addChild(composition.target);
@@ -2311,15 +2430,17 @@ var Movie = class {
2311
2430
  async _awaitVideoFrames(t = this._timeOf(this.currentFrame)) {
2312
2431
  if (!this._rootSequence) return;
2313
2432
  const collected = [];
2314
- collectVideoSequences(this._rootSequence, collected);
2433
+ const clocks = /* @__PURE__ */ new Map();
2434
+ collectVideoSequences(this._rootSequence, collected, clocks);
2315
2435
  await Promise.all(collected.map((v) => {
2316
- const local = t - (v.absoluteStart ?? v.at);
2436
+ if (v.selfTimed) return v.awaitFrameAt(t);
2437
+ const local = (clocks.get(v)?.() ?? t) - (v.absoluteStart ?? v.at);
2317
2438
  if (local < 0 || local > (v.duration ?? 0)) return Promise.resolve();
2318
2439
  return v.awaitFrameAt(local);
2319
2440
  }));
2320
2441
  }
2321
2442
  async snapshot(frame = this.currentFrame, opts = {}) {
2322
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as", "motionBlur"]);
2443
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.snapshot()", opts, ["scale", "type", "as", "motionBlur"]);
2323
2444
  this._requireReady("snapshot");
2324
2445
  return this._captureFrame(clampFrame(frame, this.totalFrames), { scale: opts.scale ?? 1, type: opts.type ?? "image/png", as: opts.as ?? "blob", motionBlur: opts.motionBlur });
2325
2446
  }
@@ -2348,7 +2469,7 @@ var Movie = class {
2348
2469
  return encodeCanvas(out, o.type, o.as, o.quality);
2349
2470
  }
2350
2471
  async stopImages(opts = {}) {
2351
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.stopImages()", opts, ["which", "pick", "scale", "type", "quality", "as", "motionBlur", "pdf", "onImage"]);
2472
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.stopImages()", opts, ["which", "pick", "scale", "type", "quality", "as", "motionBlur", "pdf", "onImage"]);
2352
2473
  this._requireReady("stopImages");
2353
2474
  if (this.stops.length === 0) return [];
2354
2475
  const picks = pictureStops(this.stops, { which: opts.which, pick: opts.pick, pdf: opts.pdf });
@@ -2380,7 +2501,7 @@ var Movie = class {
2380
2501
  * animation. `issues` says which stop, which page and what to do; leaves the playhead where it was.
2381
2502
  */
2382
2503
  async inspectStops(opts = {}) {
2383
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.inspectStops()", opts, ["lookback", "tolerance"]);
2504
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.inspectStops()", opts, ["lookback", "tolerance"]);
2384
2505
  this._requireReady("inspectStops");
2385
2506
  const out = { stops: [], issues: [] };
2386
2507
  if (this.stops.length === 0) return out;
@@ -2414,7 +2535,7 @@ var Movie = class {
2414
2535
  * JPEG at the canvas size. Needs `composition.stops`. A transparent background comes out black: give the movie a `background`.
2415
2536
  */
2416
2537
  async exportPDF(opts = {}) {
2417
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.exportPDF()", opts, ["which", "pick", "scale", "quality", "title", "motionBlur", "onImage"]);
2538
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.exportPDF()", opts, ["which", "pick", "scale", "quality", "title", "motionBlur", "onImage"]);
2418
2539
  this._requireReady("exportPDF");
2419
2540
  if (this.stops.length === 0) throw new Error("pixi-effects: movie.exportPDF(): this movie has no stops, so there are no pages; add `stops` to the composition (or build it with deck())");
2420
2541
  const scale = opts.scale ?? 1;
@@ -2437,7 +2558,7 @@ var Movie = class {
2437
2558
  return new Blob([buildPdf(pages, { title: opts.title })], { type: "application/pdf" });
2438
2559
  }
2439
2560
  async onionSkin(opts = {}) {
2440
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.onionSkin()", opts, ["from", "to", "count", "scale", "as", "motionBlur"]);
2561
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.onionSkin()", opts, ["from", "to", "count", "scale", "as", "motionBlur"]);
2441
2562
  this._requireReady("onionSkin");
2442
2563
  const from = opts.from ?? 0, to = opts.to ?? this.duration, count = opts.count ?? 8, scale = opts.scale ?? 1;
2443
2564
  if (!(Number.isInteger(count) && count >= 1 && count <= 64)) throw new Error(`pixi-effects: movie.onionSkin(): count must be a whole number from 1 to 64, got ${count}`);
@@ -2468,7 +2589,7 @@ var Movie = class {
2468
2589
  return encodeCanvas(out, "image/png", opts.as ?? "blob");
2469
2590
  }
2470
2591
  async contactSheet(opts = {}) {
2471
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.contactSheet()", opts, ["frames", "times", "count", "columns", "cellWidth", "as", "motionBlur"]);
2592
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.contactSheet()", opts, ["frames", "times", "count", "columns", "cellWidth", "as", "motionBlur"]);
2472
2593
  this._requireReady("contactSheet");
2473
2594
  const mb = this._blurFor(opts.motionBlur, "movie.contactSheet()");
2474
2595
  const blurred = mb ? this._blurCanvas() : null;
@@ -2505,7 +2626,7 @@ var Movie = class {
2505
2626
  * the canvas, cut by an edge, empty, or overlapping other text. Seeks there and stays there.
2506
2627
  */
2507
2628
  async inspect(frame = this.currentFrame, opts = { layers: "visible" }) {
2508
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.inspect()", opts, ["layers"]);
2629
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.inspect()", opts, ["layers"]);
2509
2630
  this._requireReady("inspect");
2510
2631
  const f = clampFrame(frame, this.totalFrames);
2511
2632
  await this.gotoFrame(f, true);
@@ -2521,12 +2642,12 @@ var Movie = class {
2521
2642
  }
2522
2643
  /** The timeline as one self-contained HTML page (an inline SVG, no scripts): layers as bars on a time axis, ◆ keyframes, transition bands. Open it in a browser. */
2523
2644
  timelineChart(opts = {}) {
2524
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.timelineChart()", opts, ["title"]);
2645
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.timelineChart()", opts, ["title"]);
2525
2646
  return timelineHtml(this.timelineData(), opts);
2526
2647
  }
2527
2648
  /** Just the chart: one `<svg>` whose geometry is in `data-*` attributes (a viewer puts a playhead on it: `pixi-effects-view`). */
2528
2649
  timelineSvg(opts = {}) {
2529
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.timelineSvg()", opts, ["chartWidth", "labels"]);
2650
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.timelineSvg()", opts, ["chartWidth", "labels"]);
2530
2651
  return timelineSvg(this.timelineData(), opts);
2531
2652
  }
2532
2653
  /**
@@ -2543,7 +2664,7 @@ var Movie = class {
2543
2664
  * scene's first and last frame and a frame every 0.25 s), fonts and the sound. See `ReviewReport`. Page warnings are in `window.__logs`. The movie is left as it was.
2544
2665
  */
2545
2666
  async review(opts = {}) {
2546
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.review()", opts, ["at", "strict"]);
2667
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.review()", opts, ["at", "strict"]);
2547
2668
  this._requireReady("review");
2548
2669
  const back = this.currentFrame, wasAtPoster = this._atPoster;
2549
2670
  try {
@@ -2563,7 +2684,7 @@ var Movie = class {
2563
2684
  return inspectFonts(this._rootSequence);
2564
2685
  }
2565
2686
  inspectAudio(opts = {}) {
2566
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.inspectAudio()", opts, ["window", "scenes"]);
2687
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.inspectAudio()", opts, ["window", "scenes"]);
2567
2688
  this._requireReady("inspectAudio");
2568
2689
  const scenes = opts.scenes ?? namedScenes(this.timelineData().rows);
2569
2690
  return analyzeAudio(this.audioBuffer, this._audioSources, this._mixStats, this.duration, { ...opts, scenes });
@@ -2841,7 +2962,7 @@ var Movie = class {
2841
2962
  return this.muted;
2842
2963
  }
2843
2964
  async render(options) {
2844
- chunkUQBGW5DO_cjs.warnUnknownOptions("movie.render()", options, ["format", "video", "audio", "motionBlur", "range", "scale", "draft"]);
2965
+ chunkWTRKDIKH_cjs.warnUnknownOptions("movie.render()", options, ["format", "video", "audio", "motionBlur", "range", "scale", "draft"]);
2845
2966
  try {
2846
2967
  return await this._quietly(() => exportFrames(this, options));
2847
2968
  } catch (err) {
@@ -2880,13 +3001,15 @@ function safeRun(fn) {
2880
3001
  console.warn("pixi-effects: cleanup step threw, continuing:", e);
2881
3002
  }
2882
3003
  }
2883
- function collectVideoSequences(seq, out) {
3004
+ function collectVideoSequences(seq, out, clocks, clock = null) {
2884
3005
  if (typeof seq.awaitFrameAt === "function") {
2885
3006
  out.push(seq);
3007
+ if (clock && clocks) clocks.set(seq, clock);
2886
3008
  }
2887
- if (seq.maskSequence) collectVideoSequences(seq.maskSequence, out);
3009
+ if (seq.maskSequence) collectVideoSequences(seq.maskSequence, out, clocks, clock);
3010
+ const own = seq.localClock?.() ?? clock;
2888
3011
  for (const child of seq._children ?? []) {
2889
- collectVideoSequences(child, out);
3012
+ collectVideoSequences(child, out, clocks, own);
2890
3013
  }
2891
3014
  }
2892
3015
  var BY = ["chars", "words", "lines"];
@@ -2897,16 +3020,16 @@ function measureText(text, style = {}) {
2897
3020
  return { width: m.width, height: m.height, lines: text.split("\n").length };
2898
3021
  }
2899
3022
  function splitText(text, style = {}, opts = {}) {
2900
- chunkUQBGW5DO_cjs.warnUnknownOptions("splitText()", opts, ["by", "x", "y", "align"]);
3023
+ chunkWTRKDIKH_cjs.warnUnknownOptions("splitText()", opts, ["by", "x", "y", "align"]);
2901
3024
  let by = opts.by ?? "chars";
2902
3025
  if (!BY.includes(by)) {
2903
- const g = chunkUQBGW5DO_cjs.suggestName(String(by), BY);
3026
+ const g = chunkWTRKDIKH_cjs.suggestName(String(by), BY);
2904
3027
  console.warn(`pixi-effects: splitText(): by "${by}" is not one of ${BY.map((b) => `"${b}"`).join(", ")}${g ? ` \u2014 did you mean "${g}"?` : ""}; using "chars"`);
2905
3028
  by = "chars";
2906
3029
  }
2907
3030
  let align = opts.align ?? "left";
2908
3031
  if (!ALIGN.includes(align)) {
2909
- const g = chunkUQBGW5DO_cjs.suggestName(String(align), ALIGN);
3032
+ const g = chunkWTRKDIKH_cjs.suggestName(String(align), ALIGN);
2910
3033
  console.warn(`pixi-effects: splitText(): align "${align}" is not one of ${ALIGN.join(", ")}${g ? ` \u2014 did you mean "${g}"?` : ""}; using "left"`);
2911
3034
  align = "left";
2912
3035
  }
@@ -2943,7 +3066,7 @@ function splitText(text, style = {}, opts = {}) {
2943
3066
  // src/presets/kenBurns.ts
2944
3067
  var KEN_BURNS_KEYS = ["asset", "name", "at", "duration", "fit", "ease", "motion", "origin", "zoom", "direction", "angle", "from", "to"];
2945
3068
  function kenBurns(opts) {
2946
- chunkUQBGW5DO_cjs.warnUnknownOptions("kenBurns()", opts, KEN_BURNS_KEYS);
3069
+ chunkWTRKDIKH_cjs.warnUnknownOptions("kenBurns()", opts, KEN_BURNS_KEYS);
2947
3070
  const {
2948
3071
  asset,
2949
3072
  name,
@@ -3053,14 +3176,14 @@ function clamp(v, lo, hi) {
3053
3176
  return Math.max(lo, Math.min(hi, v));
3054
3177
  }
3055
3178
  function parseEase(name) {
3056
- chunkVTBT6NNE_cjs.checkEase(name, "a preset");
3179
+ chunkNO26HWR7_cjs.checkEase(name, "a preset");
3057
3180
  return gsap.gsap.parseEase(name);
3058
3181
  }
3059
3182
 
3060
3183
  // src/presets/orbit.ts
3061
3184
  var ORBIT_KEYS = ["duration", "degrees", "start", "radius", "center", "width", "height", "fov", "ease", "dollyZoom", "at", "name", "stepsPerSecond"];
3062
3185
  function orbit(opts) {
3063
- chunkUQBGW5DO_cjs.warnUnknownOptions("orbit()", opts, ORBIT_KEYS);
3186
+ chunkWTRKDIKH_cjs.warnUnknownOptions("orbit()", opts, ORBIT_KEYS);
3064
3187
  const {
3065
3188
  duration,
3066
3189
  degrees,
@@ -3076,17 +3199,17 @@ function orbit(opts) {
3076
3199
  throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
3077
3200
  }
3078
3201
  const [cx, cy] = opts.center ?? [width / 2, height / 2];
3079
- const fixedR = opts.radius ?? chunkVTBT6NNE_cjs.homeDistance(height, fov ?? chunkVTBT6NNE_cjs.DEFAULT_FOV);
3202
+ const fixedR = opts.radius ?? chunkNO26HWR7_cjs.homeDistance(height, fov ?? chunkNO26HWR7_cjs.DEFAULT_FOV);
3080
3203
  const start = opts.start ?? -degrees / 2;
3081
3204
  const steps = Math.max(1, Math.round(duration * stepsPerSecond));
3082
3205
  const easeFn = parseEase(ease);
3083
3206
  const dollyEase = dolly ? parseEase(dolly.ease ?? ease) : null;
3084
3207
  const pos = (i) => {
3085
3208
  const p = i / steps;
3086
- const a = (start + degrees * easeFn(p)) * chunkVTBT6NNE_cjs.DEG;
3209
+ const a = (start + degrees * easeFn(p)) * chunkNO26HWR7_cjs.DEG;
3087
3210
  if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
3088
3211
  const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
3089
- const R = chunkVTBT6NNE_cjs.homeDistance(height, f);
3212
+ const R = chunkNO26HWR7_cjs.homeDistance(height, f);
3090
3213
  return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
3091
3214
  };
3092
3215
  const keyframes = [];
@@ -3118,7 +3241,7 @@ var KEYS2 = ["count", "at", "emit", "life", "area", "angle", "speed", "gravity",
3118
3241
  var SHAPES = ["circle", "rect", "star"];
3119
3242
  var MAX_KEYFRAMES = 3e4;
3120
3243
  function particles(opts) {
3121
- chunkUQBGW5DO_cjs.warnUnknownOptions("particles()", opts, KEYS2);
3244
+ chunkWTRKDIKH_cjs.warnUnknownOptions("particles()", opts, KEYS2);
3122
3245
  const { count, at = 0, emit = 0, gravity = 0, wind = 0, drag = 0, sway, fade, colors = ["#ffffff"], shape = "circle", seed = 0, name = "particle", template, sampleRate = 20 } = opts;
3123
3246
  if (!(Number.isInteger(count) && count > 0)) throw new Error(`particles(): count must be a whole number of particles (at least 1), got ${count}`);
3124
3247
  const life = range("life", opts.life, 1e-6);
@@ -3126,7 +3249,7 @@ function particles(opts) {
3126
3249
  if (!(Number.isFinite(emit) && emit >= 0)) throw new Error(`particles(): emit must be a number of seconds >= 0, got ${emit}`);
3127
3250
  if (!(Number.isFinite(sampleRate) && sampleRate > 0)) throw new Error(`particles(): sampleRate must be a positive number (samples per second), got ${sampleRate}`);
3128
3251
  if (!SHAPES.includes(shape)) {
3129
- const g = chunkUQBGW5DO_cjs.suggestName(String(shape), SHAPES);
3252
+ const g = chunkWTRKDIKH_cjs.suggestName(String(shape), SHAPES);
3130
3253
  throw new Error(`particles(): shape "${shape}" is not one of ${SHAPES.map((s) => `"${s}"`).join(", ")}${g ? `; did you mean "${g}"?` : ""}`);
3131
3254
  }
3132
3255
  const angle = range("angle", opts.angle ?? [0, 360], -Infinity);
@@ -3141,7 +3264,7 @@ function particles(opts) {
3141
3264
  const round3 = (v) => Math.round(v * 1e6) / 1e6;
3142
3265
  const out = [];
3143
3266
  for (let i = 0; i < count; i++) {
3144
- const r = chunkVTBT6NNE_cjs.random(seed * 1000003 + i * 7919 + 17);
3267
+ const r = chunkNO26HWR7_cjs.random(seed * 1000003 + i * 7919 + 17);
3145
3268
  const pick = ([a2, b]) => a2 + r() * (b - a2);
3146
3269
  const born = at + r() * emit;
3147
3270
  const lifetime = pick(life);
@@ -3154,7 +3277,7 @@ function particles(opts) {
3154
3277
  const turn = spin ? pick(spin) : 0;
3155
3278
  const startAngle = spin ? r() * 360 : 0;
3156
3279
  const swayAmp = sway?.amp ?? 0, swayFreq = sway?.freq ?? 1;
3157
- const n0 = sway ? chunkVTBT6NNE_cjs.noise(0, seed * 1000003 + i) : 0;
3280
+ const n0 = sway ? chunkNO26HWR7_cjs.noise(0, seed * 1000003 + i) : 0;
3158
3281
  const at_ = (t) => {
3159
3282
  const axis = (p0, v0, acc) => {
3160
3283
  if (drag > 0) {
@@ -3163,7 +3286,7 @@ function particles(opts) {
3163
3286
  }
3164
3287
  return p0 + v0 * t + 0.5 * acc * t * t;
3165
3288
  };
3166
- const sw = sway ? swayAmp * (chunkVTBT6NNE_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
3289
+ const sw = sway ? swayAmp * (chunkNO26HWR7_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
3167
3290
  return { x: axis(x0, vx, wind) + sw, y: axis(y0, vy, gravity) };
3168
3291
  };
3169
3292
  const kfs = [];
@@ -3213,7 +3336,7 @@ function range(name, v, min) {
3213
3336
  var KEYS3 = ["d", "at", "duration", "ease", "from", "to", "orient", "rotate", "frameRate"];
3214
3337
  var MAX_STEPS = 2e3;
3215
3338
  function followPath(opts) {
3216
- chunkUQBGW5DO_cjs.warnUnknownOptions("followPath()", opts, KEYS3);
3339
+ chunkWTRKDIKH_cjs.warnUnknownOptions("followPath()", opts, KEYS3);
3217
3340
  const { d, at = 0, duration, ease = "none", from = 0, to = 1, orient = false, rotate = 0, frameRate = 30 } = opts;
3218
3341
  if (typeof d !== "string" || d.trim() === "") throw new Error('followPath(): d must be SVG path data, e.g. "M 100 600 C 300 100 900 100 1180 600"');
3219
3342
  if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`followPath(): duration must be a positive number of seconds, got ${duration}`);
@@ -3225,7 +3348,7 @@ function followPath(opts) {
3225
3348
  if (steps > MAX_STEPS) throw new Error(`followPath(): ${steps} samples is too many (max ${MAX_STEPS}); lower frameRate or duration`);
3226
3349
  const segments = [];
3227
3350
  let total = 0;
3228
- for (const line of chunkVTBT6NNE_cjs.flattenSvgPath(d)) {
3351
+ for (const line of chunkNO26HWR7_cjs.flattenSvgPath(d)) {
3229
3352
  const p = line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
3230
3353
  for (let i = 2; i < p.length; i += 2) {
3231
3354
  const len = Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
@@ -3280,7 +3403,7 @@ var KEYS4 = ["points", "at", "duration", "ease", "look", "lookAhead", "smooth",
3280
3403
  var MAX_STEPS2 = 4e3;
3281
3404
  var PER_SPAN = 48;
3282
3405
  function cameraPath(opts) {
3283
- chunkUQBGW5DO_cjs.warnUnknownOptions("cameraPath()", opts, KEYS4);
3406
+ chunkWTRKDIKH_cjs.warnUnknownOptions("cameraPath()", opts, KEYS4);
3284
3407
  const { points, at = 0, duration, ease = "none", look = "ahead", lookAhead = 0.08, smooth = true, from = 0, to = 1, frameRate = 30 } = opts;
3285
3408
  if (!Array.isArray(points) || points.length < 2) throw new Error("cameraPath(): points needs at least two points: [[x, y, z], [x, y, z], \u2026]");
3286
3409
  points.forEach((p, i) => {
@@ -3354,7 +3477,7 @@ function stagger(items, options = {}) {
3354
3477
  const isCount = typeof items === "number";
3355
3478
  const n = isCount ? items : items.length;
3356
3479
  if (!Number.isInteger(n) || n < 0) throw new Error(`stagger(): count must be a whole number of items, got ${n}`);
3357
- chunkUQBGW5DO_cjs.warnUnknownOptions("stagger()", options, STAGGER_KEYS);
3480
+ chunkWTRKDIKH_cjs.warnUnknownOptions("stagger()", options, STAGGER_KEYS);
3358
3481
  const { each, amount, from = "start", grid, ease = "none", seed = 0 } = options;
3359
3482
  if (each !== void 0 && amount !== void 0) throw new Error("stagger(): give `each` (seconds between neighbours) or `amount` (seconds in total), not both");
3360
3483
  if (each !== void 0 && !(Number.isFinite(each) && each >= 0)) throw new Error(`stagger(): each must be a number of seconds >= 0, got ${each}`);
@@ -3379,7 +3502,7 @@ function distances(n, from, grid, seed) {
3379
3502
  if (n === 0) return [];
3380
3503
  if (from === "random") {
3381
3504
  const order = Array.from({ length: n }, (_, i) => i);
3382
- const r = chunkVTBT6NNE_cjs.random(seed);
3505
+ const r = chunkNO26HWR7_cjs.random(seed);
3383
3506
  for (let i = n - 1; i > 0; i--) {
3384
3507
  const j = Math.floor(r() * (i + 1));
3385
3508
  [order[i], order[j]] = [order[j], order[i]];
@@ -3416,7 +3539,7 @@ function distances(n, from, grid, seed) {
3416
3539
  var WIGGLE_KEYS = ["at", "duration", "freq", "seed", "ease", "props"];
3417
3540
  var MAX_STEPS3 = 2e3;
3418
3541
  function wiggle(opts) {
3419
- chunkUQBGW5DO_cjs.warnUnknownOptions("wiggle()", opts, WIGGLE_KEYS);
3542
+ chunkWTRKDIKH_cjs.warnUnknownOptions("wiggle()", opts, WIGGLE_KEYS);
3420
3543
  const { at = 0, duration, freq = 3, seed = 0, ease = "sine.inOut", props } = opts;
3421
3544
  if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`wiggle(): duration must be a positive number of seconds, got ${duration}`);
3422
3545
  if (!(Number.isFinite(freq) && freq > 0)) throw new Error(`wiggle(): freq must be a positive number (steps per second), got ${freq}`);
@@ -3441,7 +3564,7 @@ function wiggle(opts) {
3441
3564
  to[n] = p.around;
3442
3565
  return;
3443
3566
  }
3444
- const off = (chunkVTBT6NNE_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
3567
+ const off = (chunkNO26HWR7_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
3445
3568
  to[n] = typeof p.around === "number" ? p.around + off : `(${p.around}) ${off < 0 ? "-" : "+"} ${Math.abs(off)}`;
3446
3569
  });
3447
3570
  out.push({ at: at + s * stepLen, to, duration: stepLen, ease });
@@ -3464,7 +3587,7 @@ var PRESETS = {
3464
3587
  };
3465
3588
  var PRESET_NAMES = Object.keys(PRESETS);
3466
3589
  function animateText(text, style, options) {
3467
- chunkUQBGW5DO_cjs.warnUnknownOptions("animateText()", options, KEYS5);
3590
+ chunkWTRKDIKH_cjs.warnUnknownOptions("animateText()", options, KEYS5);
3468
3591
  const { by = "chars", x = 0, y = 0, align = "left", name = "text", at = 0, duration, idle, styleFor } = options;
3469
3592
  for (const [n, v] of [["x", x], ["y", y]]) {
3470
3593
  if (typeof v !== "number" || !Number.isFinite(v)) {
@@ -3540,11 +3663,11 @@ function resolveTween(spec, which) {
3540
3663
  if (TWEEN_KEYS.includes(key)) continue;
3541
3664
  if (TIME_KEYS.includes(key)) {
3542
3665
  console.warn(`pixi-effects: animateText(): ${which}.${key} ("${key}") is ignored: ${which === "out" ? "an out leaves from the END of the layer (at + duration), so to make the text gone at a time T set duration to T \u2212 at" : "the entrance starts when the piece starts (the options' at, plus the stagger)"}`);
3543
- } else chunkUQBGW5DO_cjs.warnUnknownOptions(`animateText() ${which}`, { [key]: 1 }, TWEEN_KEYS);
3666
+ } else chunkWTRKDIKH_cjs.warnUnknownOptions(`animateText() ${which}`, { [key]: 1 }, TWEEN_KEYS);
3544
3667
  }
3545
3668
  }
3546
3669
  if (t.preset !== void 0 && !(t.preset in PRESETS)) {
3547
- const guess = chunkUQBGW5DO_cjs.suggestName(String(t.preset), PRESET_NAMES);
3670
+ const guess = chunkWTRKDIKH_cjs.suggestName(String(t.preset), PRESET_NAMES);
3548
3671
  throw new Error(`animateText(): ${which} "${t.preset}" is not a preset${guess ? `; did you mean "${guess}"?` : ""} (presets: ${PRESET_NAMES.map((n) => `"${n}"`).join(", ")})`);
3549
3672
  }
3550
3673
  const base = t.preset ? PRESETS[t.preset] : void 0;
@@ -3565,7 +3688,7 @@ var KEYS6 = ["at", "duration", "props", "audioOffset", "loop", "frameRate"];
3565
3688
  var PROP_KEYS = ["base", "amount", "band", "beats", "decay", "attack", "release", "invert", "curve"];
3566
3689
  var MAX_STEPS4 = 4e3;
3567
3690
  function react(env, options) {
3568
- chunkUQBGW5DO_cjs.warnUnknownOptions("react()", options, KEYS6);
3691
+ chunkWTRKDIKH_cjs.warnUnknownOptions("react()", options, KEYS6);
3569
3692
  const { at = 0, duration, props, audioOffset = 0, loop = false } = options;
3570
3693
  const frameRate = options.frameRate ?? env.frameRate;
3571
3694
  if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`react(): duration must be a positive number of seconds, got ${duration}`);
@@ -3574,7 +3697,7 @@ function react(env, options) {
3574
3697
  if (names.length === 0) throw new Error("react(): props is empty; name at least one property, e.g. props: { scale: { base: 1, amount: 0.3 } }");
3575
3698
  for (const n of names) {
3576
3699
  const p = props[n];
3577
- chunkUQBGW5DO_cjs.warnUnknownOptions(`react() props.${n}`, p, PROP_KEYS);
3700
+ chunkWTRKDIKH_cjs.warnUnknownOptions(`react() props.${n}`, p, PROP_KEYS);
3578
3701
  if (!Number.isFinite(p.amount)) throw new Error(`react(): props.${n}.amount must be a number, got ${p.amount}`);
3579
3702
  if (!p.beats && p.band !== void 0 && !env.series[p.band]) throw new Error(`react(): props.${n}: band "${p.band}" is not in this envelope (it has: ${Object.keys(env.series).join(", ")})`);
3580
3703
  if (p.beats && !(p.decay === void 0 || Number.isFinite(p.decay) && p.decay > 0)) throw new Error(`react(): props.${n}.decay must be a positive number of seconds, got ${p.decay}`);
@@ -3681,7 +3804,7 @@ function tempo(beats) {
3681
3804
  return Math.round(60 / median * 10) / 10;
3682
3805
  }
3683
3806
  function computeEnvelope(audio, options = {}) {
3684
- chunkUQBGW5DO_cjs.warnUnknownOptions("audioEnvelope()", options, OPTION_KEYS);
3807
+ chunkWTRKDIKH_cjs.warnUnknownOptions("audioEnvelope()", options, OPTION_KEYS);
3685
3808
  const frameRate = options.frameRate ?? 30;
3686
3809
  if (!(Number.isFinite(frameRate) && frameRate > 0)) throw new Error(`pixi-effects: audioEnvelope(): frameRate must be a positive number, got ${frameRate}`);
3687
3810
  const bands = options.bands ?? DEFAULT_BANDS;
@@ -3805,7 +3928,7 @@ async function audioEnvelope(source, options = {}) {
3805
3928
 
3806
3929
  // src/audio/musicEnvelope.ts
3807
3930
  async function musicEnvelope(music, options = {}) {
3808
- const { resolveMusic, renderMusic, musicLength, musicBeatTimes, TempoMap } = await import('./music-3OF5C6X5.cjs');
3931
+ const { resolveMusic, renderMusic, musicLength, musicBeatTimes, TempoMap } = await import('./music-UAN7DXK4.cjs');
3809
3932
  const messages = [];
3810
3933
  const m = resolveMusic(music, "musicEnvelope()", (msg) => messages.push(msg));
3811
3934
  if (!m) throw new Error(`pixi-effects: musicEnvelope(): ${messages.join(" ") || "the music has nothing to play"}`);
@@ -3818,7 +3941,7 @@ async function musicEnvelope(music, options = {}) {
3818
3941
 
3819
3942
  // src/audio/musicBuffer.ts
3820
3943
  async function musicBuffer(music, options = {}) {
3821
- const { resolveMusic, renderMusic, musicLength } = await import('./music-3OF5C6X5.cjs');
3944
+ const { resolveMusic, renderMusic, musicLength } = await import('./music-UAN7DXK4.cjs');
3822
3945
  const messages = [];
3823
3946
  const m = resolveMusic(music, "musicBuffer()", (msg) => messages.push(msg));
3824
3947
  if (!m) throw new Error(`pixi-effects: musicBuffer(): ${messages.join(" ") || "the music has nothing to play"}`);
@@ -3838,11 +3961,11 @@ var PAGE_KEYS = ["name", "duration", "sequences", "stops", "notes", "advance", "
3838
3961
  var DECK_KEYS = ["pages", "transition", "width", "height", "frameRate", "background", "canvas", "assets", "poster", "motionBlur", "loader"];
3839
3962
  function deck(options) {
3840
3963
  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 }] }");
3841
- chunkUQBGW5DO_cjs.warnUnknownOptions("deck()", options, DECK_KEYS);
3964
+ chunkWTRKDIKH_cjs.warnUnknownOptions("deck()", options, DECK_KEYS);
3842
3965
  const { pages, transition, ...movieOptions } = options;
3843
3966
  pages.forEach((p, i) => {
3844
3967
  if (!p || typeof p !== "object") throw new Error(`deck(): pages[${i}] must be an object`);
3845
- chunkUQBGW5DO_cjs.warnUnknownOptions(`deck() pages[${i}]`, p, PAGE_KEYS);
3968
+ chunkWTRKDIKH_cjs.warnUnknownOptions(`deck() pages[${i}]`, p, PAGE_KEYS);
3846
3969
  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)}`);
3847
3970
  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}`);
3848
3971
  });
@@ -3862,7 +3985,7 @@ function deck(options) {
3862
3985
  const label = page.name ? `"${page.name}"` : `pages[${i}]`;
3863
3986
  const isLast = i === pages.length - 1;
3864
3987
  const entries = (page.stops ?? [isLast ? page.duration : page.duration - overlap]).map((e, k) => {
3865
- if (e && typeof e === "object") chunkUQBGW5DO_cjs.warnUnknownOptions(`deck() page ${label} stops[${k}]`, e, ["at", "pdf"]);
3988
+ if (e && typeof e === "object") chunkWTRKDIKH_cjs.warnUnknownOptions(`deck() page ${label} stops[${k}]`, e, ["at", "pdf"]);
3866
3989
  return typeof e === "object" && e !== null ? e : { at: e };
3867
3990
  });
3868
3991
  const local = entries.map((e) => e.at);
@@ -3896,7 +4019,7 @@ var round2 = (v) => Math.round(v * 1e6) / 1e6;
3896
4019
 
3897
4020
  // src/transforms/withFade.ts
3898
4021
  function withFade(spec, opts) {
3899
- chunkUQBGW5DO_cjs.warnUnknownOptions("withFade()", opts, ["in", "out"]);
4022
+ chunkWTRKDIKH_cjs.warnUnknownOptions("withFade()", opts, ["in", "out"]);
3900
4023
  if (opts.in != null && opts.in > 0) {
3901
4024
  spec.initial = { ...spec.initial ?? {}, alpha: 0 };
3902
4025
  const kf = { at: 0, to: { alpha: 1 }, duration: opts.in };
@@ -3916,19 +4039,19 @@ function withFade(spec, opts) {
3916
4039
 
3917
4040
  Object.defineProperty(exports, "noise", {
3918
4041
  enumerable: true,
3919
- get: function () { return chunkVTBT6NNE_cjs.noise; }
4042
+ get: function () { return chunkNO26HWR7_cjs.noise; }
3920
4043
  });
3921
4044
  Object.defineProperty(exports, "rand", {
3922
4045
  enumerable: true,
3923
- get: function () { return chunkVTBT6NNE_cjs.rand; }
4046
+ get: function () { return chunkNO26HWR7_cjs.rand; }
3924
4047
  });
3925
4048
  Object.defineProperty(exports, "random", {
3926
4049
  enumerable: true,
3927
- get: function () { return chunkVTBT6NNE_cjs.random; }
4050
+ get: function () { return chunkNO26HWR7_cjs.random; }
3928
4051
  });
3929
4052
  Object.defineProperty(exports, "registerSequenceType", {
3930
4053
  enumerable: true,
3931
- get: function () { return chunkVTBT6NNE_cjs.registerSequenceType; }
4054
+ get: function () { return chunkNO26HWR7_cjs.registerSequenceType; }
3932
4055
  });
3933
4056
  exports.Movie = Movie;
3934
4057
  exports.animateText = animateText;