pixi-effects 0.16.1 → 0.16.2

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 (50) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +11 -9
  3. package/ai/SKILL.md +1 -1
  4. package/ai/chat-template.html +3 -3
  5. package/ai/reference/cheatsheet.md +7 -3
  6. package/ai/reference/pitfalls.md +1 -0
  7. package/ai/reference/recipes.md +23 -0
  8. package/ai/template.html +5 -5
  9. package/dist/{Base-qvqNxvyt.d.ts → Base-BQUodYZK.d.ts} +1 -1
  10. package/dist/{Base-DPEqyeB0.d.cts → Base-OHqp8qvX.d.cts} +1 -1
  11. package/dist/Composition-3WWNVRR5.cjs +14 -0
  12. package/dist/{Composition-ZZGSMQ4E.cjs.map → Composition-3WWNVRR5.cjs.map} +1 -1
  13. package/dist/Composition-VJWY2NDL.js +5 -0
  14. package/dist/{Composition-X2VZSVPD.js.map → Composition-VJWY2NDL.js.map} +1 -1
  15. package/dist/Controller.d.cts +2 -2
  16. package/dist/Controller.d.ts +2 -2
  17. package/dist/{Movie-BIGDIla0.d.ts → Movie-DilBC348.d.ts} +8 -2
  18. package/dist/{Movie-CJMfAizW.d.cts → Movie-oA0SUAyb.d.cts} +8 -2
  19. package/dist/Presenter.d.cts +2 -2
  20. package/dist/Presenter.d.ts +2 -2
  21. package/dist/{chunk-V54NBCWG.cjs → chunk-2ROVCPSL.cjs} +28 -27
  22. package/dist/chunk-2ROVCPSL.cjs.map +1 -0
  23. package/dist/{chunk-SPPNTV7E.cjs → chunk-FJA5VUOF.cjs} +30 -13
  24. package/dist/chunk-FJA5VUOF.cjs.map +1 -0
  25. package/dist/{chunk-I2YXLHIU.js → chunk-XTIHKODH.js} +5 -4
  26. package/dist/chunk-XTIHKODH.js.map +1 -0
  27. package/dist/{chunk-7D7RAQWM.js → chunk-ZNKQH356.js} +30 -13
  28. package/dist/chunk-ZNKQH356.js.map +1 -0
  29. package/dist/index.cjs +128 -38
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +37 -5
  32. package/dist/index.d.ts +37 -5
  33. package/dist/index.js +106 -17
  34. package/dist/index.js.map +1 -1
  35. package/dist/loader.css +1 -1
  36. package/dist/three.cjs +5 -5
  37. package/dist/three.d.cts +2 -2
  38. package/dist/three.d.ts +2 -2
  39. package/dist/three.js +1 -1
  40. package/dist/{types-C8XEUoqe.d.cts → types-SS51lP6O.d.cts} +7 -1
  41. package/dist/{types-C8XEUoqe.d.ts → types-SS51lP6O.d.ts} +7 -1
  42. package/docs/dsl.md +31 -1
  43. package/llms-full.txt +63 -5
  44. package/package.json +2 -1
  45. package/dist/Composition-X2VZSVPD.js +0 -5
  46. package/dist/Composition-ZZGSMQ4E.cjs +0 -14
  47. package/dist/chunk-7D7RAQWM.js.map +0 -1
  48. package/dist/chunk-I2YXLHIU.js.map +0 -1
  49. package/dist/chunk-SPPNTV7E.cjs.map +0 -1
  50. package/dist/chunk-V54NBCWG.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkV54NBCWG_cjs = require('./chunk-V54NBCWG.cjs');
4
- var chunkSPPNTV7E_cjs = require('./chunk-SPPNTV7E.cjs');
3
+ var chunk2ROVCPSL_cjs = require('./chunk-2ROVCPSL.cjs');
4
+ var chunkFJA5VUOF_cjs = require('./chunk-FJA5VUOF.cjs');
5
5
  var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
6
6
  var PIXI = require('pixi.js');
7
7
  var gsap = require('gsap');
@@ -867,16 +867,16 @@ function expandTransitions(spec) {
867
867
  if (toUses > 1) {
868
868
  throw new Error(`pixi-effects: ${tag} sequence "${t.to}" is used as \`to\` in more than one transition`);
869
869
  }
870
- const tStart = chunkSPPNTV7E_cjs.resolveAt(t.at, parentDuration);
870
+ const tStart = chunkFJA5VUOF_cjs.resolveAt(t.at, parentDuration);
871
871
  const tEnd = tStart + t.duration;
872
872
  const fromSeq = fromEntry.seq;
873
- const fromStart = chunkSPPNTV7E_cjs.resolveAt(fromSeq.at, parentDuration);
873
+ const fromStart = chunkFJA5VUOF_cjs.resolveAt(fromSeq.at, parentDuration);
874
874
  const fromEnd = fromStart + (fromSeq.duration ?? parentDuration);
875
875
  if (tStart < fromStart - EPS || tEnd > fromEnd + EPS) {
876
876
  throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`from\` "${t.from}" (lives [${fromStart}, ${fromEnd}], so it ends at ${fromEnd})`);
877
877
  }
878
878
  const toSeq = toEntry.seq;
879
- const toStart = chunkSPPNTV7E_cjs.resolveAt(toSeq.at, parentDuration);
879
+ const toStart = chunkFJA5VUOF_cjs.resolveAt(toSeq.at, parentDuration);
880
880
  const toEnd = toStart + (toSeq.duration ?? parentDuration);
881
881
  if (tStart < toStart - EPS || tEnd > toEnd + EPS) {
882
882
  throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`to\` "${t.to}" (lives [${toStart}, ${toEnd}], so it starts at ${toStart})`);
@@ -1216,9 +1216,9 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1216
1216
  onCanvas = inter === 0 && area > 0 ? "none" : inter >= area - 1e-6 ? "full" : "partial";
1217
1217
  if (area === 0) onCanvas = "none";
1218
1218
  }
1219
- const keys = chunkSPPNTV7E_cjs.collectPropKeys(seq.spec);
1219
+ const keys = chunkFJA5VUOF_cjs.collectPropKeys(seq.spec);
1220
1220
  const carried = carriers.some((c) => {
1221
- const k = chunkSPPNTV7E_cjs.collectPropKeys(c.spec);
1221
+ const k = chunkFJA5VUOF_cjs.collectPropKeys(c.spec);
1222
1222
  return (c.spec.keyframes ?? []).length > 0 && (k.has("x") || k.has("y") || k.has("rotation") || k.has("scale"));
1223
1223
  });
1224
1224
  const moving = parentMoving || carried || (seq.spec.keyframes ?? []).length > 0 && (keys.has("x") || keys.has("y"));
@@ -1239,7 +1239,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1239
1239
  const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
1240
1240
  if (group) scene.set(info, group);
1241
1241
  if (zero || seq.showsNothingYet) scaledToNothing.add(info);
1242
- if (seq instanceof chunkV54NBCWG_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
1242
+ if (seq instanceof chunk2ROVCPSL_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
1243
1243
  });
1244
1244
  };
1245
1245
  walk2(root, "", false, true);
@@ -1300,10 +1300,10 @@ function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions) {
1300
1300
  const start = parentStart + (seq.at ?? 0);
1301
1301
  const end = Math.min(parentEnd, seq.duration !== void 0 ? start + seq.duration : parentEnd);
1302
1302
  const span = end - start;
1303
- const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkSPPNTV7E_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
1303
+ const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkFJA5VUOF_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
1304
1304
  const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq) };
1305
1305
  const inner = [];
1306
- const isComp = seq instanceof chunkV54NBCWG_cjs.CompositionSequence;
1306
+ const isComp = seq instanceof chunk2ROVCPSL_cjs.CompositionSequence;
1307
1307
  if (isComp) walk(seq, start, end, depth + 1, prefix + name + "/", inner, transitions);
1308
1308
  entries.push({ row, inner, groupable: !isComp });
1309
1309
  });
@@ -1851,7 +1851,7 @@ var Movie = class {
1851
1851
  height: options.composition.height ?? this.height,
1852
1852
  duration: options.composition.duration ?? this.duration
1853
1853
  } : void 0;
1854
- await chunkSPPNTV7E_cjs.ensureFilterLibrary(seededComposition);
1854
+ await chunkFJA5VUOF_cjs.ensureFilterLibrary(seededComposition);
1855
1855
  const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
1856
1856
  const rootSeqSpec = {
1857
1857
  type: "composition",
@@ -1861,7 +1861,7 @@ var Movie = class {
1861
1861
  ...userComposition
1862
1862
  };
1863
1863
  if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
1864
- const composition = new chunkV54NBCWG_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
1864
+ const composition = new chunk2ROVCPSL_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
1865
1865
  await composition.build();
1866
1866
  this._rootSequence = composition;
1867
1867
  if (composition.target) root.addChild(composition.target);
@@ -2140,7 +2140,8 @@ var Movie = class {
2140
2140
  _renderNow() {
2141
2141
  const app = this.app;
2142
2142
  if (!app?.renderer) return;
2143
- this._rootSequence?.syncFrame();
2143
+ if (!this._synced) this._rootSequence?.syncFrame();
2144
+ this._synced = false;
2144
2145
  PIXI.Culler.shared.cull(app.stage, app.renderer.screen, false);
2145
2146
  app.renderer.render({ container: app.stage });
2146
2147
  }
@@ -2149,9 +2150,17 @@ var Movie = class {
2149
2150
  throw new Error(`pixi-effects: movie.${what}() needs a ready movie \u2014 await movie.init(...) first`);
2150
2151
  }
2151
2152
  }
2152
- /** Projects every `threeD` layer for the current frame (see src/space). */
2153
+ /** True when the layers were already synced to the playhead for the frame about to be drawn (so `_renderNow` need not do it again). */
2154
+ _synced = false;
2155
+ /**
2156
+ * Projects every `threeD` layer for the current frame (see src/space). A threeD card is drawn into its own texture HERE, before the stage is
2157
+ * drawn, so the shapes inside it must be brought up to the playhead first: syncing only in `_renderNow` left every card showing the state of
2158
+ * the frame before (after a jump seek, a card whose children were animated showed a stale picture).
2159
+ */
2153
2160
  _updateSpace(t = this._timeOf(this.currentFrame)) {
2154
2161
  if (!this._rootSequence || !this.app) return;
2162
+ this._rootSequence.syncFrame();
2163
+ this._synced = true;
2155
2164
  this._rootSequence.updateSpace(t, this.app.renderer);
2156
2165
  }
2157
2166
  /** The motion blur an operation uses: its own option when given (`false` is off), otherwise the movie's. */
@@ -2456,6 +2465,9 @@ function splitText(text, style = {}, opts = {}) {
2456
2465
  align = "left";
2457
2466
  }
2458
2467
  const x0 = opts.x ?? 0, y0 = opts.y ?? 0;
2468
+ for (const [n, v] of [["x", x0], ["y", y0]]) {
2469
+ if (typeof v !== "number" || !Number.isFinite(v)) throw new Error(`splitText(): ${n} must be a number of pixels (e.g. ${n}: 640), got ${JSON.stringify(v)}: the pieces are placed by measuring, so an expression such as 'GW/2' cannot be used here. \`y\` is the top of the line.`);
2470
+ }
2459
2471
  if (text === "") return [];
2460
2472
  const ts = new PIXI.TextStyle(style);
2461
2473
  const lineHeight = PIXI.CanvasTextMetrics.measureText("M", ts).lineHeight ?? PIXI.CanvasTextMetrics.measureText("M", ts).height;
@@ -2617,17 +2629,17 @@ function orbit(opts) {
2617
2629
  throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
2618
2630
  }
2619
2631
  const [cx, cy] = opts.center ?? [width / 2, height / 2];
2620
- const fixedR = opts.radius ?? chunkSPPNTV7E_cjs.homeDistance(height, fov ?? chunkSPPNTV7E_cjs.DEFAULT_FOV);
2632
+ const fixedR = opts.radius ?? chunkFJA5VUOF_cjs.homeDistance(height, fov ?? chunkFJA5VUOF_cjs.DEFAULT_FOV);
2621
2633
  const start = opts.start ?? -degrees / 2;
2622
2634
  const steps = Math.max(1, Math.round(duration * stepsPerSecond));
2623
2635
  const easeFn = parseEase(ease);
2624
2636
  const dollyEase = dolly ? parseEase(dolly.ease ?? ease) : null;
2625
2637
  const pos = (i) => {
2626
2638
  const p = i / steps;
2627
- const a = (start + degrees * easeFn(p)) * chunkSPPNTV7E_cjs.DEG;
2639
+ const a = (start + degrees * easeFn(p)) * chunkFJA5VUOF_cjs.DEG;
2628
2640
  if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
2629
2641
  const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
2630
- const R = chunkSPPNTV7E_cjs.homeDistance(height, f);
2642
+ const R = chunkFJA5VUOF_cjs.homeDistance(height, f);
2631
2643
  return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
2632
2644
  };
2633
2645
  const keyframes = [];
@@ -2682,7 +2694,7 @@ function particles(opts) {
2682
2694
  const round3 = (v) => Math.round(v * 1e6) / 1e6;
2683
2695
  const out = [];
2684
2696
  for (let i = 0; i < count; i++) {
2685
- const r = chunkSPPNTV7E_cjs.random(seed * 1000003 + i * 7919 + 17);
2697
+ const r = chunkFJA5VUOF_cjs.random(seed * 1000003 + i * 7919 + 17);
2686
2698
  const pick = ([a2, b]) => a2 + r() * (b - a2);
2687
2699
  const born = at + r() * emit;
2688
2700
  const lifetime = pick(life);
@@ -2695,7 +2707,7 @@ function particles(opts) {
2695
2707
  const turn = spin ? pick(spin) : 0;
2696
2708
  const startAngle = spin ? r() * 360 : 0;
2697
2709
  const swayAmp = sway?.amp ?? 0, swayFreq = sway?.freq ?? 1;
2698
- const n0 = sway ? chunkSPPNTV7E_cjs.noise(0, seed * 1000003 + i) : 0;
2710
+ const n0 = sway ? chunkFJA5VUOF_cjs.noise(0, seed * 1000003 + i) : 0;
2699
2711
  const at_ = (t) => {
2700
2712
  const axis = (p0, v0, acc) => {
2701
2713
  if (drag > 0) {
@@ -2704,7 +2716,7 @@ function particles(opts) {
2704
2716
  }
2705
2717
  return p0 + v0 * t + 0.5 * acc * t * t;
2706
2718
  };
2707
- const sw = sway ? swayAmp * (chunkSPPNTV7E_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
2719
+ const sw = sway ? swayAmp * (chunkFJA5VUOF_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
2708
2720
  return { x: axis(x0, vx, wind) + sw, y: axis(y0, vy, gravity) };
2709
2721
  };
2710
2722
  const kfs = [];
@@ -2766,7 +2778,7 @@ function followPath(opts) {
2766
2778
  if (steps > MAX_STEPS) throw new Error(`followPath(): ${steps} samples is too many (max ${MAX_STEPS}); lower frameRate or duration`);
2767
2779
  const segments = [];
2768
2780
  let total = 0;
2769
- for (const line of chunkSPPNTV7E_cjs.flattenSvgPath(d)) {
2781
+ for (const line of chunkFJA5VUOF_cjs.flattenSvgPath(d)) {
2770
2782
  const p = line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
2771
2783
  for (let i = 2; i < p.length; i += 2) {
2772
2784
  const len = Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
@@ -2816,6 +2828,78 @@ function followPath(opts) {
2816
2828
  return out;
2817
2829
  }
2818
2830
 
2831
+ // src/presets/cameraPath.ts
2832
+ var KEYS4 = ["points", "at", "duration", "ease", "look", "lookAhead", "smooth", "from", "to", "frameRate"];
2833
+ var MAX_STEPS2 = 4e3;
2834
+ var PER_SPAN = 48;
2835
+ function cameraPath(opts) {
2836
+ chunkUQBGW5DO_cjs.warnUnknownOptions("cameraPath()", opts, KEYS4);
2837
+ const { points, at = 0, duration, ease = "none", look = "ahead", lookAhead = 0.08, smooth = true, from = 0, to = 1, frameRate = 30 } = opts;
2838
+ if (!Array.isArray(points) || points.length < 2) throw new Error("cameraPath(): points needs at least two points: [[x, y, z], [x, y, z], \u2026]");
2839
+ points.forEach((p, i) => {
2840
+ if (!Array.isArray(p) || p.length !== 3 || !p.every(Number.isFinite)) throw new Error(`cameraPath(): points[${i}] must be [x, y, z] numbers, got ${JSON.stringify(p)}`);
2841
+ });
2842
+ if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`cameraPath(): duration must be a positive number of seconds, got ${duration}`);
2843
+ if (!(Number.isFinite(frameRate) && frameRate > 0)) throw new Error(`cameraPath(): frameRate must be a positive number (samples per second), got ${frameRate}`);
2844
+ if (look !== "ahead" && !(Array.isArray(look) && look.length === 3 && look.every(Number.isFinite))) throw new Error(`cameraPath(): look must be 'ahead' or [x, y, z] numbers, got ${JSON.stringify(look)}`);
2845
+ if (!(Number.isFinite(lookAhead) && lookAhead >= 0 && lookAhead <= 1)) throw new Error(`cameraPath(): lookAhead must be a fraction of the route between 0 and 1, got ${lookAhead}`);
2846
+ for (const [n, v] of [["from", from], ["to", to]]) {
2847
+ if (!(Number.isFinite(v) && v >= 0 && v <= 1)) throw new Error(`cameraPath(): ${n} must be a fraction of the route between 0 and 1, got ${v}`);
2848
+ }
2849
+ const steps = Math.max(1, Math.round(duration * frameRate));
2850
+ if (steps > MAX_STEPS2) throw new Error(`cameraPath(): ${steps} samples is too many (max ${MAX_STEPS2}); lower frameRate or duration`);
2851
+ const curve = [];
2852
+ if (smooth && points.length > 2) {
2853
+ const pts = points;
2854
+ for (let i = 0; i < pts.length - 1; i++) {
2855
+ const p0 = pts[Math.max(i - 1, 0)], p1 = pts[i], p2 = pts[i + 1], p3 = pts[Math.min(i + 2, pts.length - 1)];
2856
+ for (let s = 0; s < PER_SPAN; s++) curve.push(catmullRom(p0, p1, p2, p3, s / PER_SPAN));
2857
+ }
2858
+ curve.push(pts[pts.length - 1]);
2859
+ } else curve.push(...points);
2860
+ const cum = [0];
2861
+ for (let i = 1; i < curve.length; i++) cum.push(cum[i - 1] + dist(curve[i - 1], curve[i]));
2862
+ const total = cum[cum.length - 1];
2863
+ if (!(total > 0)) throw new Error("cameraPath(): the route has no length (the points are all the same)");
2864
+ const find = (s) => {
2865
+ let lo = 0, hi = cum.length - 1;
2866
+ while (lo < hi - 1) {
2867
+ const mid = lo + hi >> 1;
2868
+ if (cum[mid] <= s) lo = mid;
2869
+ else hi = mid;
2870
+ }
2871
+ return lo;
2872
+ };
2873
+ const along = (fraction) => {
2874
+ const s = fraction * total;
2875
+ if (s >= total) {
2876
+ const a2 = curve[curve.length - 2], b2 = curve[curve.length - 1], len = dist(a2, b2) || 1, extra = s - total;
2877
+ return [b2[0] + (b2[0] - a2[0]) / len * extra, b2[1] + (b2[1] - a2[1]) / len * extra, b2[2] + (b2[2] - a2[2]) / len * extra];
2878
+ }
2879
+ const i = find(Math.max(s, 0)), span = cum[i + 1] - cum[i], t = span > 0 ? (s - cum[i]) / span : 0;
2880
+ const a = curve[i], b = curve[i + 1];
2881
+ return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
2882
+ };
2883
+ const shape = parseEase(ease);
2884
+ const round3 = (v) => Math.round(v * 1e6) / 1e6;
2885
+ const sample = (k) => {
2886
+ const p = from + (to - from) * shape(k / steps);
2887
+ const pos = along(Math.min(Math.max(p, 0), 1));
2888
+ const target = look === "ahead" ? along(Math.max(p + (to >= from ? lookAhead : -lookAhead), 0)) : look;
2889
+ return { x: round3(pos[0]), y: round3(pos[1]), z: round3(pos[2]), lookAtX: round3(target[0]), lookAtY: round3(target[1]), lookAtZ: round3(target[2]) };
2890
+ };
2891
+ const out = [{ at, set: sample(0) }];
2892
+ const step = duration / steps;
2893
+ for (let k = 1; k <= steps; k++) out.push({ at: round3(at + (k - 1) * step), to: sample(k), duration: round3(step), ease: "none" });
2894
+ return out;
2895
+ }
2896
+ var dist = (a, b) => Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
2897
+ function catmullRom(p0, p1, p2, p3, t) {
2898
+ const t2 = t * t, t3 = t2 * t;
2899
+ const c = (a, b, d, e) => 0.5 * (2 * b + (d - a) * t + (2 * a - 5 * b + 4 * d - e) * t2 + (3 * b - a - 3 * d + e) * t3);
2900
+ return [c(p0[0], p1[0], p2[0], p3[0]), c(p0[1], p1[1], p2[1], p3[1]), c(p0[2], p1[2], p2[2], p3[2])];
2901
+ }
2902
+
2819
2903
  // src/presets/stagger.ts
2820
2904
  var STAGGER_KEYS = ["each", "amount", "from", "grid", "ease", "seed"];
2821
2905
  var FROM_NAMES = ["start", "end", "center", "edges", "random"];
@@ -2835,11 +2919,11 @@ function stagger(items, options = {}) {
2835
2919
  if (grid && !(Array.isArray(grid) && grid.length === 2 && grid.every((g) => Number.isInteger(g) && g > 0) && grid[0] * grid[1] >= n)) {
2836
2920
  throw new Error(`stagger(): grid must be [columns, rows] and hold all ${n} items, got ${JSON.stringify(grid)}`);
2837
2921
  }
2838
- const dist = distances(n, from, grid, seed);
2839
- const max = dist.reduce((m, d) => Math.max(m, d), 0);
2922
+ const dist2 = distances(n, from, grid, seed);
2923
+ const max = dist2.reduce((m, d) => Math.max(m, d), 0);
2840
2924
  const total = amount !== void 0 ? amount : (each ?? 0.1) * max;
2841
2925
  const shape = parseEase(ease);
2842
- const delays = dist.map((d) => max === 0 ? 0 : Math.round(total * shape(d / max) * 1e6) / 1e6);
2926
+ const delays = dist2.map((d) => max === 0 ? 0 : Math.round(total * shape(d / max) * 1e6) / 1e6);
2843
2927
  if (isCount) return delays;
2844
2928
  return items.map((layer, i) => ({ ...layer, at: Math.round(((layer.at ?? 0) + delays[i]) * 1e6) / 1e6 }));
2845
2929
  }
@@ -2847,7 +2931,7 @@ function distances(n, from, grid, seed) {
2847
2931
  if (n === 0) return [];
2848
2932
  if (from === "random") {
2849
2933
  const order = Array.from({ length: n }, (_, i) => i);
2850
- const r = chunkSPPNTV7E_cjs.random(seed);
2934
+ const r = chunkFJA5VUOF_cjs.random(seed);
2851
2935
  for (let i = n - 1; i > 0; i--) {
2852
2936
  const j = Math.floor(r() * (i + 1));
2853
2937
  [order[i], order[j]] = [order[j], order[i]];
@@ -2882,7 +2966,7 @@ function distances(n, from, grid, seed) {
2882
2966
 
2883
2967
  // src/presets/wiggle.ts
2884
2968
  var WIGGLE_KEYS = ["at", "duration", "freq", "seed", "ease", "props"];
2885
- var MAX_STEPS2 = 2e3;
2969
+ var MAX_STEPS3 = 2e3;
2886
2970
  function wiggle(opts) {
2887
2971
  chunkUQBGW5DO_cjs.warnUnknownOptions("wiggle()", opts, WIGGLE_KEYS);
2888
2972
  const { at = 0, duration, freq = 3, seed = 0, ease = "sine.inOut", props } = opts;
@@ -2896,7 +2980,7 @@ function wiggle(opts) {
2896
2980
  if (p.around === void 0 || p.around === null || p.around === "") throw new Error(`wiggle(): props.${n}.around is missing`);
2897
2981
  }
2898
2982
  const steps = Math.ceil(duration * freq - 1e-9);
2899
- if (steps > MAX_STEPS2) throw new Error(`wiggle(): ${steps} steps is too many (max ${MAX_STEPS2}); lower freq or duration`);
2983
+ if (steps > MAX_STEPS3) throw new Error(`wiggle(): ${steps} steps is too many (max ${MAX_STEPS3}); lower freq or duration`);
2900
2984
  const set = {};
2901
2985
  for (const n of names) set[n] = props[n].around;
2902
2986
  const out = [{ at, set }];
@@ -2909,7 +2993,7 @@ function wiggle(opts) {
2909
2993
  to[n] = p.around;
2910
2994
  return;
2911
2995
  }
2912
- const off = (chunkSPPNTV7E_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
2996
+ const off = (chunkFJA5VUOF_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
2913
2997
  to[n] = typeof p.around === "number" ? p.around + off : `(${p.around}) ${off < 0 ? "-" : "+"} ${Math.abs(off)}`;
2914
2998
  });
2915
2999
  out.push({ at: at + s * stepLen, to, duration: stepLen, ease });
@@ -2918,7 +3002,7 @@ function wiggle(opts) {
2918
3002
  }
2919
3003
 
2920
3004
  // src/presets/animateText.ts
2921
- var KEYS4 = ["by", "x", "y", "align", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
3005
+ var KEYS5 = ["by", "x", "y", "align", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
2922
3006
  var PRESETS = {
2923
3007
  rise: { from: { y: 40, alpha: 0 }, duration: 0.5, ease: "back.out(2)" },
2924
3008
  drop: { from: { y: -50, alpha: 0 }, duration: 0.7, ease: "bounce.out" },
@@ -2930,8 +3014,13 @@ var PRESETS = {
2930
3014
  };
2931
3015
  var PRESET_NAMES = Object.keys(PRESETS);
2932
3016
  function animateText(text, style, options) {
2933
- chunkUQBGW5DO_cjs.warnUnknownOptions("animateText()", options, KEYS4);
3017
+ chunkUQBGW5DO_cjs.warnUnknownOptions("animateText()", options, KEYS5);
2934
3018
  const { by = "chars", x = 0, y = 0, align = "left", name = "text", at = 0, duration, idle, styleFor } = options;
3019
+ for (const [n, v] of [["x", x], ["y", y]]) {
3020
+ if (typeof v !== "number" || !Number.isFinite(v)) {
3021
+ throw new Error(`animateText(): ${n} must be a number of pixels (e.g. ${n}: 640), got ${JSON.stringify(v)}. The letters are measured once, when the layers are made, so an expression such as 'GW/2' cannot be used here: compute the number (W / 2) yourself. \`y\` is the TOP of the line.`);
3022
+ }
3023
+ }
2935
3024
  if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`animateText(): duration must be a positive number of seconds (how long until the last piece is gone), got ${duration}`);
2936
3025
  const pieces = splitText(text, style, { by, x, y, align });
2937
3026
  if (pieces.length === 0) return [];
@@ -3014,11 +3103,11 @@ function resolveTween(spec, which) {
3014
3103
  }
3015
3104
 
3016
3105
  // src/presets/react.ts
3017
- var KEYS5 = ["at", "duration", "props", "audioOffset", "loop", "frameRate"];
3106
+ var KEYS6 = ["at", "duration", "props", "audioOffset", "loop", "frameRate"];
3018
3107
  var PROP_KEYS = ["base", "amount", "band", "beats", "decay", "attack", "release", "invert", "curve"];
3019
- var MAX_STEPS3 = 4e3;
3108
+ var MAX_STEPS4 = 4e3;
3020
3109
  function react(env, options) {
3021
- chunkUQBGW5DO_cjs.warnUnknownOptions("react()", options, KEYS5);
3110
+ chunkUQBGW5DO_cjs.warnUnknownOptions("react()", options, KEYS6);
3022
3111
  const { at = 0, duration, props, audioOffset = 0, loop = false } = options;
3023
3112
  const frameRate = options.frameRate ?? env.frameRate;
3024
3113
  if (!(Number.isFinite(duration) && duration > 0)) throw new Error(`react(): duration must be a positive number of seconds, got ${duration}`);
@@ -3033,7 +3122,7 @@ function react(env, options) {
3033
3122
  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}`);
3034
3123
  }
3035
3124
  const steps = Math.max(1, Math.round(duration * frameRate));
3036
- if (steps > MAX_STEPS3) throw new Error(`react(): too many keyframes (${steps} steps, max ${MAX_STEPS3}); lower frameRate (now ${frameRate}) or duration`);
3125
+ if (steps > MAX_STEPS4) throw new Error(`react(): too many keyframes (${steps} steps, max ${MAX_STEPS4}); lower frameRate (now ${frameRate}) or duration`);
3037
3126
  const wrap = (a) => loop && env.duration > 0 ? (a % env.duration + env.duration) % env.duration : a;
3038
3127
  const beatPulse = (a, decay) => {
3039
3128
  const beats = env.beats;
@@ -3369,24 +3458,25 @@ function withFade(spec, opts) {
3369
3458
 
3370
3459
  Object.defineProperty(exports, "noise", {
3371
3460
  enumerable: true,
3372
- get: function () { return chunkSPPNTV7E_cjs.noise; }
3461
+ get: function () { return chunkFJA5VUOF_cjs.noise; }
3373
3462
  });
3374
3463
  Object.defineProperty(exports, "rand", {
3375
3464
  enumerable: true,
3376
- get: function () { return chunkSPPNTV7E_cjs.rand; }
3465
+ get: function () { return chunkFJA5VUOF_cjs.rand; }
3377
3466
  });
3378
3467
  Object.defineProperty(exports, "random", {
3379
3468
  enumerable: true,
3380
- get: function () { return chunkSPPNTV7E_cjs.random; }
3469
+ get: function () { return chunkFJA5VUOF_cjs.random; }
3381
3470
  });
3382
3471
  Object.defineProperty(exports, "registerSequenceType", {
3383
3472
  enumerable: true,
3384
- get: function () { return chunkSPPNTV7E_cjs.registerSequenceType; }
3473
+ get: function () { return chunkFJA5VUOF_cjs.registerSequenceType; }
3385
3474
  });
3386
3475
  exports.Movie = Movie;
3387
3476
  exports.animateText = animateText;
3388
3477
  exports.audioEnvelope = audioEnvelope;
3389
3478
  exports.bpmEnvelope = bpmEnvelope;
3479
+ exports.cameraPath = cameraPath;
3390
3480
  exports.computeEnvelope = computeEnvelope;
3391
3481
  exports.deck = deck;
3392
3482
  exports.dismissLoader = dismissLoader;