pixi-effects 0.45.0 → 0.47.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 (72) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +2 -2
  5. package/ai/tools/lookup.mjs +6 -1
  6. package/ai/tools/pixi-effects.mjs +1 -0
  7. package/ai/tools/render.mjs +17 -1
  8. package/dist/Composition-NSY7A32Y.js +7 -0
  9. package/dist/{Composition-YGBQX3HZ.js.map → Composition-NSY7A32Y.js.map} +1 -1
  10. package/dist/Composition-PME3P5LP.cjs +16 -0
  11. package/dist/{Composition-N6BOROJ2.cjs.map → Composition-PME3P5LP.cjs.map} +1 -1
  12. package/dist/Controller.d.cts +2 -2
  13. package/dist/Controller.d.ts +2 -2
  14. package/dist/{Movie-C-d4zN-N.d.cts → Movie-BqrAWN0g.d.cts} +6 -1
  15. package/dist/{Movie-lMSS9JwX.d.ts → Movie-sV8nJr_f.d.ts} +6 -1
  16. package/dist/{Spans-XTIWYSNO.cjs → Spans-2TZCNREQ.cjs} +9 -9
  17. package/dist/{Spans-XTIWYSNO.cjs.map → Spans-2TZCNREQ.cjs.map} +1 -1
  18. package/dist/{Spans-6L2ZVECX.js → Spans-TESJPQKX.js} +4 -4
  19. package/dist/{Spans-6L2ZVECX.js.map → Spans-TESJPQKX.js.map} +1 -1
  20. package/dist/{chunk-O66JGPVV.cjs → chunk-23SXKSUI.cjs} +18 -4
  21. package/dist/chunk-23SXKSUI.cjs.map +1 -0
  22. package/dist/{chunk-K24FF4BT.js → chunk-4SEL6W7H.js} +18 -4
  23. package/dist/chunk-4SEL6W7H.js.map +1 -0
  24. package/dist/{chunk-3HZV3UHU.cjs → chunk-DKCMJILW.cjs} +67 -65
  25. package/dist/chunk-DKCMJILW.cjs.map +1 -0
  26. package/dist/{chunk-4QVQ5UJJ.js → chunk-DM2OLQOJ.js} +5 -6
  27. package/dist/chunk-DM2OLQOJ.js.map +1 -0
  28. package/dist/{chunk-ICI6ACHY.js → chunk-FPV3NPYC.js} +8 -6
  29. package/dist/chunk-FPV3NPYC.js.map +1 -0
  30. package/dist/{chunk-3IO5RWE4.cjs → chunk-I6VUQ2CR.cjs} +188 -74
  31. package/dist/chunk-I6VUQ2CR.cjs.map +1 -0
  32. package/dist/{chunk-X62H3RBG.cjs → chunk-LALEBV63.cjs} +5 -6
  33. package/dist/chunk-LALEBV63.cjs.map +1 -0
  34. package/dist/{chunk-Z6JQO7GX.js → chunk-W46VWAZC.js} +146 -32
  35. package/dist/chunk-W46VWAZC.js.map +1 -0
  36. package/dist/index.cjs +35 -35
  37. package/dist/index.d.cts +5 -3
  38. package/dist/index.d.ts +5 -3
  39. package/dist/index.js +4 -4
  40. package/dist/loader.css +1 -1
  41. package/dist/present.cjs +4 -4
  42. package/dist/present.d.cts +2 -2
  43. package/dist/present.d.ts +2 -2
  44. package/dist/present.js +4 -4
  45. package/dist/sound.cjs +2 -2
  46. package/dist/sound.js +1 -1
  47. package/dist/three.cjs +194 -15
  48. package/dist/three.cjs.map +1 -1
  49. package/dist/three.d.cts +75 -4
  50. package/dist/three.d.ts +75 -4
  51. package/dist/three.js +191 -13
  52. package/dist/three.js.map +1 -1
  53. package/dist/{types-C5xo0fzb.d.cts → types-CTSrPvY3.d.cts} +64 -58
  54. package/dist/{types-C5xo0fzb.d.ts → types-CTSrPvY3.d.ts} +64 -58
  55. package/docs/api.md +2 -1
  56. package/docs/dsl.md +25 -1
  57. package/llms-full.txt +65 -6
  58. package/package.json +1 -1
  59. package/skills/pixi-effects/SKILL.md +1 -1
  60. package/skills/pixi-effects/reference/cheatsheet.md +3 -3
  61. package/skills/pixi-effects/reference/recipes.md +34 -0
  62. package/skills/pixi-effects/template.html +6 -6
  63. package/dist/Composition-N6BOROJ2.cjs +0 -16
  64. package/dist/Composition-YGBQX3HZ.js +0 -7
  65. package/dist/chunk-3HZV3UHU.cjs.map +0 -1
  66. package/dist/chunk-3IO5RWE4.cjs.map +0 -1
  67. package/dist/chunk-4QVQ5UJJ.js.map +0 -1
  68. package/dist/chunk-ICI6ACHY.js.map +0 -1
  69. package/dist/chunk-K24FF4BT.js.map +0 -1
  70. package/dist/chunk-O66JGPVV.cjs.map +0 -1
  71. package/dist/chunk-X62H3RBG.cjs.map +0 -1
  72. package/dist/chunk-Z6JQO7GX.js.map +0 -1
@@ -1,6 +1,6 @@
1
- import { CompositionSequence, maskSourceOf } from './chunk-ICI6ACHY.js';
2
- import { EXPRESSION_NAMES, createTimeline, lengthen, ensureFilterLibrary, checkEase, parseEase, homeDistance, DEFAULT_FOV, random, noise, flattenSvgPath, trimPolylines, rand, DEG, timeRemapOf, contentLength, resolveAt, parseSpring, springDuration, spansMeta } from './chunk-4QVQ5UJJ.js';
3
- import { analyzeAudio, fft, resampleThrough, measureLoudness, playSpan } from './chunk-K24FF4BT.js';
1
+ import { CompositionSequence, maskSourceOf } from './chunk-FPV3NPYC.js';
2
+ import { EXPRESSION_NAMES, createTimeline, lengthen, ensureFilterLibrary, checkEase, parseEase, homeDistance, DEFAULT_FOV, random, noise, flattenSvgPath, trimPolylines, rand, DEG, timeRemapOf, contentLength, resolveAt, parseSpring, springDuration, spansMeta } from './chunk-DM2OLQOJ.js';
3
+ import { analyzeAudio, fft, resampleThrough, measureLoudness, playSpan } from './chunk-4SEL6W7H.js';
4
4
  import { usesAdvancedBlend, enableAdvancedBlend } from './chunk-VNP7QCJE.js';
5
5
  import { describeLayer } from './chunk-32MMNDDN.js';
6
6
  import { warnUnknownOptions, suggestName } from './chunk-BXOCEZ4U.js';
@@ -141,18 +141,86 @@ async function mixdown(audios, totalDuration, sampleRate = 44100, pace) {
141
141
  }
142
142
  var KNEE = 0.9;
143
143
  var LOUDNESS_CEILING_DB = -1;
144
+ var MAX_LIMIT_DB = 10;
145
+ function limitInto(source, out, gain, ceil, sampleRate) {
146
+ const n = source[0]?.length ?? 0;
147
+ const req = new Float32Array(n);
148
+ for (let i = 0; i < n; i++) {
149
+ let p = 0;
150
+ for (const c of source) {
151
+ const a = Math.abs(c[i]) * gain;
152
+ if (a > p) p = a;
153
+ }
154
+ req[i] = p > ceil ? ceil / p : 1;
155
+ }
156
+ const L = Math.max(1, Math.round(3e-3 * sampleRate));
157
+ const mn = new Float32Array(n), dq = new Int32Array(n);
158
+ let head = 0, tail = 0;
159
+ for (let i = n - 1; i >= 0; i--) {
160
+ while (tail > head && req[dq[tail - 1]] >= req[i]) tail--;
161
+ dq[tail++] = i;
162
+ while (dq[head] > i + L) head++;
163
+ mn[i] = req[dq[head]];
164
+ }
165
+ const release = 1 - Math.exp(-1 / (0.08 * sampleRate));
166
+ let run = 0, prev = 1, lowest = 1, lowestAt = 0;
167
+ const gains = new Float32Array(n);
168
+ for (let i = 0; i < n; i++) {
169
+ run += mn[i];
170
+ if (i > L) run -= mn[i - L - 1];
171
+ const avg = run / Math.min(i + 1, L + 1);
172
+ const g = Math.min(avg, prev + (1 - prev) * release);
173
+ gains[i] = g;
174
+ prev = g;
175
+ if (g < lowest) {
176
+ lowest = g;
177
+ lowestAt = i;
178
+ }
179
+ }
180
+ for (let ch = 0; ch < source.length; ch++) {
181
+ const s = source[ch], o = out[ch];
182
+ for (let i = 0; i < n; i++) {
183
+ const v = s[i] * gain * gains[i];
184
+ o[i] = v > ceil ? ceil : v < -ceil ? -ceil : v;
185
+ }
186
+ }
187
+ return { reductionDb: lowest < 1 ? -20 * Math.log10(lowest) : 0, at: lowestAt / sampleRate };
188
+ }
144
189
  function normalizeMix(buffer, target) {
145
190
  const chans = [];
146
191
  for (let ch = 0; ch < buffer.numberOfChannels; ch++) chans.push(buffer.getChannelData(ch));
147
- const m = measureLoudness(chans, buffer.sampleRate, { truePeak: false });
192
+ const sr = buffer.sampleRate;
193
+ const m = measureLoudness(chans, sr, { truePeak: false });
148
194
  if (m.integratedLufs === null) return { target, gainDb: 0, reachedLufs: null, limited: false };
195
+ const original = chans.map((c) => Float32Array.from(c));
149
196
  let gainDb = target - m.integratedLufs;
150
- const room = LOUDNESS_CEILING_DB - m.samplePeakDb;
151
- const limited = gainDb > room;
152
- if (limited) gainDb = room;
153
- const gain = 10 ** (gainDb / 20);
154
- for (const d of chans) for (let i = 0; i < d.length; i++) d[i] = d[i] * gain;
155
- return { target, gainDb: Math.round(gainDb * 10) / 10, reachedLufs: Math.round((m.integratedLufs + gainDb) * 10) / 10, limited };
197
+ let ceilDb = LOUDNESS_CEILING_DB;
198
+ let applied = { reductionDb: 0, at: 0 }, measured = m;
199
+ for (let round2 = 0; round2 < 8; round2++) {
200
+ const cap = MAX_LIMIT_DB + ceilDb - m.samplePeakDb;
201
+ if (gainDb > cap) gainDb = cap;
202
+ applied = limitInto(original, chans, 10 ** (gainDb / 20), 10 ** (ceilDb / 20), sr);
203
+ measured = measureLoudness(chans, sr);
204
+ const overTp = measured.truePeakDb - LOUDNESS_CEILING_DB;
205
+ if (overTp > 0.02) {
206
+ ceilDb -= overTp + 0.02;
207
+ continue;
208
+ }
209
+ const deficit = target - measured.integratedLufs;
210
+ if (Math.abs(deficit) <= 0.15) break;
211
+ const next = Math.min(gainDb + deficit, cap);
212
+ if (Math.abs(next - gainDb) < 0.05) break;
213
+ gainDb = next;
214
+ }
215
+ const limited = measured.integratedLufs < target - 0.15;
216
+ const reduction = Math.round(applied.reductionDb * 10) / 10;
217
+ return {
218
+ target,
219
+ gainDb: Math.round(gainDb * 10) / 10,
220
+ reachedLufs: measured.integratedLufs,
221
+ limited,
222
+ ...reduction >= 0.1 ? { limiterDb: reduction, limiterAt: Math.round(applied.at * 100) / 100 } : {}
223
+ };
156
224
  }
157
225
  function limitMix(buffer, audios) {
158
226
  let peak = 0;
@@ -1632,7 +1700,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1632
1700
  const name = seq.spec.name;
1633
1701
  const label = name ?? `${seq.spec.type}#${i}`;
1634
1702
  const alpha = (t?.alpha ?? 1) * carriers.reduce((a, c) => a * (c.target?.alpha ?? 1), 1);
1635
- const alive = (threeD ? display.visible : t?.renderable ?? true) && carriers.every((c) => c.target?.renderable ?? true);
1703
+ const alive = (threeD ? display.visible : (t?.renderable ?? true) && t?.visible !== false) && carriers.every((c) => c.target?.renderable ?? true);
1636
1704
  const visible = parentVisible && alive && alpha > 0;
1637
1705
  let bounds = null;
1638
1706
  if (!insideThreeD) {
@@ -1812,7 +1880,22 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1812
1880
  return true;
1813
1881
  };
1814
1882
  const inside = (b, p) => p.x >= b.x && p.x <= b.x + b.width && p.y >= b.y && p.y <= b.y + b.height;
1815
- const walk3 = (comp, prefix, parentVisible) => {
1883
+ const hiddenSources = /* @__PURE__ */ new Map();
1884
+ const forwards = [];
1885
+ const rays = /* @__PURE__ */ new Map();
1886
+ const rayOf = (seq, d, local, lb) => {
1887
+ if (typeof seq.pickAt !== "function") return null;
1888
+ if (!rays.has(seq)) rays.set(seq, seq.pickAt((local.x - lb.x) / lb.width, (local.y - lb.y) / lb.height));
1889
+ return rays.get(seq);
1890
+ };
1891
+ const forwardThrough = (seq, d, path, local, lb) => {
1892
+ const shows = seq.screenOf;
1893
+ if (!shows || !inside(lb, local)) return;
1894
+ const r = rayOf(seq, d, local, lb);
1895
+ const shown = r?.uv ? shows.call(seq, r.material) : null;
1896
+ if (r?.uv && shown) forwards.push({ source: shown.source, uv: r.uv, via: { path, object: String(r.object) }, order: orderOf(d) });
1897
+ };
1898
+ const walk3 = (comp, prefix, parentVisible, at = { x, y }, orderBase, via) => {
1816
1899
  comp.layers().forEach(({ seq, display, threeD, carriers, quad }, i) => {
1817
1900
  const d = display;
1818
1901
  if (!d) return;
@@ -1820,16 +1903,25 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1820
1903
  const name = seq.spec.name;
1821
1904
  const path = prefix + (name ?? `${seq.spec.type}#${i}`);
1822
1905
  const alpha = (t?.alpha ?? 1) * carriers.reduce((a, c) => a * (c.target?.alpha ?? 1), 1);
1823
- const alive = (threeD ? d.visible : t?.renderable ?? true) && carriers.every((c) => c.target?.renderable ?? true);
1906
+ const alive = (threeD ? d.visible : (t?.renderable ?? true) && t?.visible !== false) && carriers.every((c) => c.target?.renderable ?? true);
1824
1907
  const visible = parentVisible && alive && alpha > 0;
1825
- if (!visible) return;
1908
+ if (!visible) {
1909
+ if (seq instanceof CompositionSequence && !threeD && parentVisible && alpha > 0 && t?.visible === false && (t?.renderable ?? true) && carriers.every((c) => c.target?.renderable ?? true)) hiddenSources.set(seq, path);
1910
+ return;
1911
+ }
1826
1912
  const isComposition = seq instanceof CompositionSequence;
1827
1913
  if (isComposition && !threeD) {
1828
- walk3(seq, path + "/", visible);
1914
+ walk3(seq, path + "/", visible, at, orderBase, via);
1829
1915
  return;
1830
1916
  }
1831
- if (!wanted(name, path)) return;
1832
- const point = { x, y };
1917
+ if (!wanted(name, path)) {
1918
+ if (!threeD) {
1919
+ const lp = d.toLocal(at);
1920
+ forwardThrough(seq, d, path, lp, d.getLocalBounds());
1921
+ }
1922
+ return;
1923
+ }
1924
+ const point = at;
1833
1925
  let hit = null;
1834
1926
  if (threeD) {
1835
1927
  if (quad && pointInQuad(quad, d.toLocal(point).x, d.toLocal(point).y)) hit = { name, path, type: seq.spec.type, kind: "card", point };
@@ -1837,7 +1929,10 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1837
1929
  const local = d.toLocal(point);
1838
1930
  const lb = d.getLocalBounds();
1839
1931
  if (inside(lb, local)) {
1840
- const three = typeof seq.pickAt === "function" ? seq.pickAt((local.x - lb.x) / lb.width, (local.y - lb.y) / lb.height) : void 0;
1932
+ const isThree = typeof seq.pickAt === "function";
1933
+ const ray = isThree ? rayOf(seq, d, local, lb) : void 0;
1934
+ if (isThree) forwardThrough(seq, d, path, local, lb);
1935
+ const three = ray ? { object: ray.object, point: ray.point, distance: ray.distance, object3d: ray.object3d } : isThree ? null : void 0;
1841
1936
  if (three) {
1842
1937
  const planeOf = seq.dragPlane;
1843
1938
  if (planeOf) {
@@ -1872,10 +1967,16 @@ function pickScene(root, x, y, opts = {}, deps = {}) {
1872
1967
  const maskTarget = seq.maskSequence?.target;
1873
1968
  const maskSrc = maskSourceOf(seq.spec.mask);
1874
1969
  if (maskTarget && !(maskSrc.kind === "inline" && maskSrc.invert) && !inside(maskTarget.getBounds(), point)) return;
1875
- found.push({ hit, order: orderOf(d) });
1970
+ found.push({ hit: via ? { ...hit, point: { x, y }, via } : hit, order: orderBase ? [...orderBase, ...orderOf(d)] : orderOf(d) });
1876
1971
  });
1877
1972
  };
1878
1973
  walk3(root, "", true);
1974
+ for (const f of forwards) {
1975
+ const path = hiddenSources.get(f.source);
1976
+ if (path === void 0) continue;
1977
+ const g = f.source.target.toGlobal({ x: f.uv.u * f.source.intrinsicWidth, y: (1 - f.uv.v) * f.source.intrinsicHeight });
1978
+ walk3(f.source, path + "/", true, g, f.order, f.via);
1979
+ }
1879
1980
  return found.sort((a, b) => above(a.order, b.order)).map((f) => f.hit);
1880
1981
  }
1881
1982
  var cache = /* @__PURE__ */ new WeakMap();
@@ -2818,6 +2919,7 @@ var Movie = class {
2818
2919
  frameRate: this.frameRate,
2819
2920
  vars: numericVars(vars),
2820
2921
  ...gpuField,
2922
+ ...this.app?.renderer ? { pixiRenderer: this.app.renderer } : {},
2821
2923
  ...reuse ? { reuse } : {},
2822
2924
  ...lp ? { onLayerBuilt: async () => {
2823
2925
  lp.tick();
@@ -4412,20 +4514,25 @@ function animateText(text, style, options) {
4412
4514
  const pieces = splitText(text, style, { by, x, y, align, ...vertical ? { vertical } : {} });
4413
4515
  if (pieces.length === 0) return [];
4414
4516
  const lineHeight = measureText("M", style).height;
4415
- const inTween = options.in === false ? null : resolveTween(options.in ?? "rise", "in");
4416
- const outTween = options.out === void 0 || options.out === false ? null : resolveTween(options.out, "out");
4517
+ const given = (v) => v === false || v === "none" ? void 0 : v;
4518
+ const inSpec = options.in === void 0 ? "rise" : given(options.in), outSpec = given(options.out);
4519
+ const inTween = inSpec === void 0 ? null : resolveTween(inSpec, "in");
4520
+ const outTween = outSpec === void 0 ? null : resolveTween(outSpec, "out");
4417
4521
  const delays = stagger(pieces.length, options.stagger ?? { each: 0.04 });
4418
4522
  const wave = Math.max(...delays);
4419
- const need = (inTween?.duration ?? 0) + (outTween?.duration ?? 0) + wave;
4523
+ const outDelays = outTween?.stagger ? stagger(pieces.length, outTween.stagger) : delays;
4524
+ const outWave = Math.max(...outDelays);
4525
+ const lead = Math.max(...delays.map((d, i) => d - outDelays[i]));
4526
+ const need = (inTween?.duration ?? 0) + (outTween ? outTween.duration + outWave + lead : wave);
4420
4527
  if (duration < need - 1e-9) {
4421
4528
  throw new Error(`animateText(): duration ${duration} is too short for this wave: the entrance${outTween ? ", the exit" : ""} and the stagger need at least ${+need.toFixed(3)} s`);
4422
4529
  }
4423
4530
  const end = at + duration;
4424
4531
  if (outTween?.at !== void 0) {
4425
4532
  if (outTween.at < at - 1e-9) console.warn(`pixi-effects: animateText(): out.at ${outTween.at} is before the layer starts (at ${at}): the exit starts with the piece; use out.at \u2265 ${at}`);
4426
- const latest = end - outTween.duration - wave;
4427
- if (outTween.at > latest + 1e-9) console.warn(`pixi-effects: animateText(): out.at ${outTween.at} leaves after the layer ends (the exit and the wave take ${+(outTween.duration + wave).toFixed(3)} s and the layer ends at ${+end.toFixed(3)} s): use out.at \u2264 ${+latest.toFixed(3)}, or a longer duration`);
4428
- if (inTween && outTween.at - at < inTween.duration - 1e-9) console.warn(`pixi-effects: animateText(): out.at ${outTween.at} is before the entrance has finished (it takes ${inTween.duration} s from at ${at}): use out.at \u2265 ${+(at + inTween.duration).toFixed(3)}`);
4533
+ const latest = end - outTween.duration - outWave;
4534
+ if (outTween.at > latest + 1e-9) console.warn(`pixi-effects: animateText(): out.at ${outTween.at} leaves after the layer ends (the exit and the wave take ${+(outTween.duration + outWave).toFixed(3)} s and the layer ends at ${+end.toFixed(3)} s): use out.at \u2264 ${+latest.toFixed(3)}, or a longer duration`);
4535
+ if (inTween && outTween.at - at < inTween.duration + lead - 1e-9) console.warn(`pixi-effects: animateText(): out.at ${outTween.at} is before the entrance has finished (it takes ${inTween.duration} s from at ${at}): use out.at \u2265 ${+(at + inTween.duration + lead).toFixed(3)}`);
4429
4536
  }
4430
4537
  return pieces.map((p, i) => {
4431
4538
  const cx = p.cx ?? p.x + p.width / 2, cy = p.cy ?? p.y + lineHeight / 2;
@@ -4445,8 +4552,8 @@ function animateText(text, style, options) {
4445
4552
  }
4446
4553
  let idleEnd = len;
4447
4554
  if (outTween) {
4448
- const begin = outTween.at !== void 0 ? void 0 : outTween.duration + (wave - delays[i]);
4449
- const local = outTween.at !== void 0 ? Math.max(0, outTween.at - at) : -begin;
4555
+ const begin = outTween.at !== void 0 ? void 0 : outTween.duration + (outWave - outDelays[i]);
4556
+ const local = outTween.at !== void 0 ? Math.max(0, outTween.at + outDelays[i] - start) : -begin;
4450
4557
  idleEnd = outTween.at !== void 0 ? local : len - begin;
4451
4558
  keyframes.push({
4452
4559
  at: local,
@@ -4482,6 +4589,10 @@ function resolveTween(spec, which) {
4482
4589
  if (typeof spec === "object" && spec) {
4483
4590
  for (const key of Object.keys(spec)) {
4484
4591
  if (TWEEN_KEYS.includes(key)) continue;
4592
+ if (key === "stagger") {
4593
+ if (which === "in") console.warn("pixi-effects: animateText(): in.stagger is ignored: the entrance wave is the options' `stagger` (only `out` can have a wave of its own: out: { stagger: 0 } leaves all at once)");
4594
+ continue;
4595
+ }
4485
4596
  if (which === "out" && key === "at") {
4486
4597
  if (typeof spec.at !== "number" || !Number.isFinite(spec.at)) console.warn(`pixi-effects: animateText(): out.at must be a number of seconds (the composition second the exit starts at), got ${JSON.stringify(spec.at)}; ignored`);
4487
4598
  continue;
@@ -4493,7 +4604,7 @@ function resolveTween(spec, which) {
4493
4604
  }
4494
4605
  if (t.preset !== void 0 && !(t.preset in PRESETS)) {
4495
4606
  const guess = suggestName(String(t.preset), PRESET_NAMES);
4496
- throw new Error(`animateText(): ${which} "${t.preset}" is not a preset${guess ? `; did you mean "${guess}"?` : ""} (presets: ${PRESET_NAMES.map((n) => `"${n}"`).join(", ")})`);
4607
+ throw new Error(`animateText(): ${which} "${t.preset}" is not a preset${guess ? `; did you mean "${guess}"?` : ""} (presets: ${PRESET_NAMES.map((n) => `"${n}"`).join(", ")}; for no ${which === "out" ? "exit: omit `out` or write out: false (the pieces stay until the layer ends)" : "entrance: in: false"})`);
4497
4608
  }
4498
4609
  const base = t.preset ? PRESETS[t.preset] : void 0;
4499
4610
  const from = { ...base?.from ?? {}, ...t.from ?? {} };
@@ -4502,11 +4613,14 @@ function resolveTween(spec, which) {
4502
4613
  for (const k of Object.keys(from)) {
4503
4614
  if (!known.has(k) && to[k] === void 0) throw new Error(`animateText(): ${which}.from has "${k}", which has no resting value; give ${which}.to: { ${k}: \u2026 }`);
4504
4615
  }
4505
- if (Object.keys(from).length === 0) throw new Error(`animateText(): ${which} needs a preset or a from`);
4616
+ if (Object.keys(from).length === 0) {
4617
+ throw new Error(`animateText(): ${which} needs a preset or a from${which === "out" && Object.keys(to).length ? ` \u2014 ${which} takes \`from\`: the state the piece leaves to (out: { from: { y: 18, alpha: 0 } }); \`to\` is only the resting value of a property that has none` : ""}`);
4618
+ }
4506
4619
  let ease = t.ease ?? base?.ease ?? "power2.out";
4507
4620
  if (which === "out" && t.ease === void 0) ease = ease.replace(".out", ".in");
4508
4621
  const at = which === "out" && typeof t.at === "number" && Number.isFinite(t.at) ? t.at : void 0;
4509
- return { from, to: { ...to }, duration: t.duration ?? base?.duration ?? 0.5, ease, ...at !== void 0 ? { at } : {} };
4622
+ const wave = which === "out" && t.stagger !== void 0 ? t.stagger === 0 ? { each: 0 } : t.stagger : void 0;
4623
+ return { from, to: { ...to }, duration: t.duration ?? base?.duration ?? 0.5, ease, ...at !== void 0 ? { at } : {}, ...wave ? { stagger: wave } : {} };
4510
4624
  }
4511
4625
 
4512
4626
  // src/presets/react.ts
@@ -4814,5 +4928,5 @@ function withFade(spec, opts) {
4814
4928
  }
4815
4929
 
4816
4930
  export { Movie, animateText, audioEnvelope, bpmEnvelope, cameraPath, computeEnvelope, dismissLoader, failLoader, followPath, kenBurns, measureText, musicBuffer, musicEnvelope, musicEvents, orbit, parseEase2 as parseEase, particles, react, resolveLoader, samplePath, splitText, stagger, wiggle, withFade };
4817
- //# sourceMappingURL=chunk-Z6JQO7GX.js.map
4818
- //# sourceMappingURL=chunk-Z6JQO7GX.js.map
4931
+ //# sourceMappingURL=chunk-W46VWAZC.js.map
4932
+ //# sourceMappingURL=chunk-W46VWAZC.js.map