pixi-effects 0.17.0 → 0.18.1

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 (43) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +6 -6
  3. package/ai/CHAT.md +3 -0
  4. package/ai/SKILL.md +2 -2
  5. package/ai/chat-template.html +8 -4
  6. package/ai/reference/cheatsheet.md +3 -1
  7. package/ai/reference/pitfalls.md +1 -1
  8. package/ai/template.html +5 -5
  9. package/ai/tools/check.mjs +25 -109
  10. package/ai/tools/render.mjs +1 -0
  11. package/dist/Composition-6JMXWNVV.cjs +14 -0
  12. package/dist/{Composition-LMVEASHF.cjs.map → Composition-6JMXWNVV.cjs.map} +1 -1
  13. package/dist/Composition-MLBHYYFF.js +5 -0
  14. package/dist/{Composition-HYAOYPAX.js.map → Composition-MLBHYYFF.js.map} +1 -1
  15. package/dist/Controller.d.cts +1 -1
  16. package/dist/Controller.d.ts +1 -1
  17. package/dist/{Movie-DHJRrUfI.d.ts → Movie-DlHQ9C02.d.ts} +46 -2
  18. package/dist/{Movie-CoxF2Z7t.d.cts → Movie-K2YuVAdg.d.cts} +46 -2
  19. package/dist/Presenter.d.cts +1 -1
  20. package/dist/Presenter.d.ts +1 -1
  21. package/dist/{chunk-LBJRXU7N.cjs → chunk-ANO5R2E6.cjs} +14 -4
  22. package/dist/chunk-ANO5R2E6.cjs.map +1 -0
  23. package/dist/{chunk-ETAIVOHP.cjs → chunk-JW67MDHQ.cjs} +26 -26
  24. package/dist/{chunk-ETAIVOHP.cjs.map → chunk-JW67MDHQ.cjs.map} +1 -1
  25. package/dist/{chunk-ORTYBA42.js → chunk-SG3Y66FN.js} +14 -4
  26. package/dist/chunk-SG3Y66FN.js.map +1 -0
  27. package/dist/{chunk-Q3IVBYT7.js → chunk-UXDR3ZDU.js} +3 -3
  28. package/dist/{chunk-Q3IVBYT7.js.map → chunk-UXDR3ZDU.js.map} +1 -1
  29. package/dist/index.cjs +152 -31
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +2 -2
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +129 -8
  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.js +1 -1
  38. package/llms-full.txt +6 -3
  39. package/package.json +1 -1
  40. package/dist/Composition-HYAOYPAX.js +0 -5
  41. package/dist/Composition-LMVEASHF.cjs +0 -14
  42. package/dist/chunk-LBJRXU7N.cjs.map +0 -1
  43. package/dist/chunk-ORTYBA42.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
- var chunkETAIVOHP_cjs = require('./chunk-ETAIVOHP.cjs');
4
- var chunkLBJRXU7N_cjs = require('./chunk-LBJRXU7N.cjs');
3
+ var chunkJW67MDHQ_cjs = require('./chunk-JW67MDHQ.cjs');
4
+ var chunkANO5R2E6_cjs = require('./chunk-ANO5R2E6.cjs');
5
5
  var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
6
6
  var PIXI = require('pixi.js');
7
7
  var gsap = require('gsap');
@@ -466,19 +466,22 @@ function inspectFonts(root) {
466
466
  if (face.status === "error") failed.add(face.family.replace(/^["']|["']$/g, ""));
467
467
  });
468
468
  const missing = [];
469
+ const used = /* @__PURE__ */ new Set();
469
470
  const walk2 = (seq, index) => {
470
471
  const spec = seq.spec;
471
472
  if (spec.type === "text") {
472
473
  const raw = spec.style?.fontFamily;
473
474
  const family2 = Array.isArray(raw) ? raw.join(", ") : typeof raw === "string" ? raw : "";
474
475
  const list = family2.split(",").map((f) => f.trim()).filter(Boolean);
476
+ for (const f of list) used.add(f.replace(/^["']|["']$/g, "").toLowerCase());
475
477
  if (list.length && !list.some(fontAvailable) && !list.some((f) => failed.has(f.replace(/^["']|["']$/g, "")))) missing.push({ layer: spec.name ?? `text#${index}`, family: family2 });
476
478
  }
477
479
  if (seq.maskSequence) walk2(seq.maskSequence, index);
478
480
  (seq._children ?? []).forEach((child, i) => walk2(child, i));
479
481
  };
480
482
  walk2(root, 0);
481
- return { missing, failed: [...failed] };
483
+ const all = [...failed];
484
+ return { missing, failed: all.filter((f) => used.has(f.toLowerCase())), failedUnused: all.filter((f) => !used.has(f.toLowerCase())) };
482
485
  }
483
486
 
484
487
  // src/core/scenes.ts
@@ -486,6 +489,92 @@ function namedScenes(rows) {
486
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 }));
487
490
  }
488
491
 
492
+ // src/core/review.ts
493
+ function sampleFrames({ totalFrames, frameRate, scenes = [], step = 0.25, cap = 240 }) {
494
+ const last = totalFrames;
495
+ const clamp2 = (f) => Math.max(0, Math.min(last, Math.round(f)));
496
+ const edges = /* @__PURE__ */ new Set([0, last]);
497
+ for (const s of scenes) {
498
+ edges.add(clamp2(s.start * frameRate));
499
+ edges.add(clamp2(s.end * frameRate - 1));
500
+ }
501
+ const room = Math.max(0, cap - edges.size);
502
+ const seconds = totalFrames / frameRate;
503
+ const wanted = Math.floor(seconds / step) + 1;
504
+ const stride = wanted <= room ? step : seconds / Math.max(1, room);
505
+ const frames = new Set(edges);
506
+ for (let t = stride; frames.size < cap && t < seconds; t += stride) frames.add(clamp2(t * frameRate));
507
+ return [...frames].sort((a, b) => a - b);
508
+ }
509
+ var AT_HELP = "use seconds (3.5), a percentage (50%), a frame (f120) or a layer's start / mid / end (name@end)";
510
+ function resolveAtList(list, ctx) {
511
+ const { frameRate: fps, totalFrames, duration, rows } = ctx;
512
+ const out = [], used = /* @__PURE__ */ new Set();
513
+ 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]);
514
+ for (const raw of String(list).split(",").map((s) => s.trim()).filter(Boolean)) {
515
+ let frame, label, m;
516
+ if (m = raw.match(/^(.+)@(start|mid|end)$/)) {
517
+ const row = named.find((r) => r.name === m[1]);
518
+ 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" : ""})`);
519
+ const t = m[2] === "start" ? row.start : m[2] === "mid" ? (row.start + row.end) / 2 : row.end - 1 / fps;
520
+ frame = Math.round(t * fps);
521
+ label = `${m[1]}-${m[2]}`;
522
+ } else if (m = raw.match(/^(\d+(?:\.\d+)?)%$/)) {
523
+ frame = Math.round(Number(m[1]) / 100 * totalFrames);
524
+ label = `${m[1]}pct`;
525
+ } else if (m = raw.match(/^f(\d+)$/i)) {
526
+ frame = Number(m[1]);
527
+ label = `f${m[1]}`;
528
+ } else if (/^\d+(\.\d+)?$/.test(raw)) {
529
+ frame = Math.round(Number(raw) * fps);
530
+ label = `${Number(raw).toFixed(2)}s`;
531
+ } else throw new Error(`--at: cannot read "${raw}": ${AT_HELP}`);
532
+ if (frame > totalFrames || frame < 0) throw new Error(`--at "${raw}" is past the end: the movie is ${duration} s (${totalFrames} frames)`);
533
+ label = label.replace(/[\\/:*?"<>|]+/g, "-");
534
+ let unique = label, n = 2;
535
+ while (used.has(unique)) unique = `${label}-${n++}`;
536
+ used.add(unique);
537
+ out.push({ label: unique, frame });
538
+ }
539
+ return out;
540
+ }
541
+ function groupIssues(perFrame) {
542
+ const groups = /* @__PURE__ */ new Map();
543
+ for (const { frame, issues } of perFrame) {
544
+ for (const message of issues) {
545
+ const key = message.replace(/-?\d+(\.\d+)?/g, "#");
546
+ const g = groups.get(key);
547
+ if (g) {
548
+ g.count++;
549
+ g.lastFrame = frame;
550
+ } else groups.set(key, { message, count: 1, firstFrame: frame, lastFrame: frame });
551
+ }
552
+ }
553
+ return [...groups.values()];
554
+ }
555
+ function splitIssues(groups, strict = false) {
556
+ if (strict) return { problems: groups, review: [] };
557
+ const overlap = (g) => /\boverlap\b/.test(g.message);
558
+ return { problems: groups.filter((g) => !overlap(g)), review: groups.filter(overlap) };
559
+ }
560
+ async function reviewMovie(movie, opts = {}) {
561
+ const rows = movie.timelineData().rows;
562
+ const at = opts.at ? resolveAtList(opts.at, { frameRate: movie.frameRate, totalFrames: movie.totalFrames, duration: movie.duration, rows }) : [];
563
+ const frames = sampleFrames({ totalFrames: movie.totalFrames, frameRate: movie.frameRate, scenes: namedScenes(rows) });
564
+ const perFrame = [];
565
+ for (const frame of frames) {
566
+ const r = await movie.inspect(frame, { layers: "none" });
567
+ if (r.issues.length) perFrame.push({ frame, issues: r.issues });
568
+ }
569
+ const grouped = splitIssues(groupIssues(perFrame), !!opts.strict);
570
+ const fonts = movie.inspectFonts();
571
+ for (const f of fonts.failedUnused) grouped.review.push({ message: `web font "${f}" failed to load, but no text layer uses it (a broken or unused @font-face)`, count: 1, firstFrame: 0, lastFrame: 0 });
572
+ for (const m of fonts.missing) {
573
+ (opts.strict ? grouped.problems : grouped.review).push({ message: `layer "${m.layer}": none of the fonts "${m.family}" is available here (it is drawn in a fallback font)`, count: 1, firstFrame: 0, lastFrame: 0 });
574
+ }
575
+ return { frames: frames.length, problems: grouped.problems, review: grouped.review, fonts, audio: movie.audioBuffer ? movie.inspectAudio() : null, at };
576
+ }
577
+
489
578
  // src/core/onion.ts
490
579
  function onionTimes(from, to, count) {
491
580
  if (count <= 1) return [from];
@@ -522,9 +611,9 @@ function resolveRange(range2, ctx) {
522
611
  if (range2 === void 0) return { fromFrame: 0, toFrame: totalFrames, fromSec: 0, toSec: duration, partial: false };
523
612
  let from, to;
524
613
  if (typeof range2 === "string") {
525
- const rows = (ctx.rows ?? []).filter((r) => r.depth === 0);
614
+ 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]);
526
615
  const row = rows.find((r) => r.name === range2);
527
- if (!row) throw new Error(`pixi-effects: render({ range: "${range2}" }): no layer named "${range2}" at the top level (names: ${rows.map((r) => r.name).join(", ") || "none"})`);
616
+ 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" : ""})`);
528
617
  from = row.start;
529
618
  to = row.end;
530
619
  } else {
@@ -689,7 +778,7 @@ async function exportFrames(movie, options = {}) {
689
778
  }
690
779
  await canvasSource.close();
691
780
  await output.finalize();
692
- if (mb) await movie.gotoFrame(movie.totalFrames, true);
781
+ if (mb) await movie.gotoFrame(range2.toFrame, true);
693
782
  return new Blob([output.target.buffer], { type: output.format.mimeType });
694
783
  } finally {
695
784
  movie.app.ticker.start();
@@ -1136,16 +1225,16 @@ function expandTransitions(spec) {
1136
1225
  if (toUses > 1) {
1137
1226
  throw new Error(`pixi-effects: ${tag} sequence "${t.to}" is used as \`to\` in more than one transition`);
1138
1227
  }
1139
- const tStart = chunkLBJRXU7N_cjs.resolveAt(t.at, parentDuration);
1228
+ const tStart = chunkANO5R2E6_cjs.resolveAt(t.at, parentDuration);
1140
1229
  const tEnd = tStart + t.duration;
1141
1230
  const fromSeq = fromEntry.seq;
1142
- const fromStart = chunkLBJRXU7N_cjs.resolveAt(fromSeq.at, parentDuration);
1231
+ const fromStart = chunkANO5R2E6_cjs.resolveAt(fromSeq.at, parentDuration);
1143
1232
  const fromEnd = fromStart + (fromSeq.duration ?? parentDuration);
1144
1233
  if (tStart < fromStart - EPS || tEnd > fromEnd + EPS) {
1145
1234
  throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`from\` "${t.from}" (lives [${fromStart}, ${fromEnd}], so it ends at ${fromEnd})`);
1146
1235
  }
1147
1236
  const toSeq = toEntry.seq;
1148
- const toStart = chunkLBJRXU7N_cjs.resolveAt(toSeq.at, parentDuration);
1237
+ const toStart = chunkANO5R2E6_cjs.resolveAt(toSeq.at, parentDuration);
1149
1238
  const toEnd = toStart + (toSeq.duration ?? parentDuration);
1150
1239
  if (tStart < toStart - EPS || tEnd > toEnd + EPS) {
1151
1240
  throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`to\` "${t.to}" (lives [${toStart}, ${toEnd}], so it starts at ${toStart})`);
@@ -1485,9 +1574,9 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1485
1574
  onCanvas = inter === 0 && area > 0 ? "none" : inter >= area - 1e-6 ? "full" : "partial";
1486
1575
  if (area === 0) onCanvas = "none";
1487
1576
  }
1488
- const keys = chunkLBJRXU7N_cjs.collectPropKeys(seq.spec);
1577
+ const keys = chunkANO5R2E6_cjs.collectPropKeys(seq.spec);
1489
1578
  const carried = carriers.some((c) => {
1490
- const k = chunkLBJRXU7N_cjs.collectPropKeys(c.spec);
1579
+ const k = chunkANO5R2E6_cjs.collectPropKeys(c.spec);
1491
1580
  return (c.spec.keyframes ?? []).length > 0 && (k.has("x") || k.has("y") || k.has("rotation") || k.has("scale"));
1492
1581
  });
1493
1582
  const moving = parentMoving || carried || (seq.spec.keyframes ?? []).length > 0 && (keys.has("x") || keys.has("y"));
@@ -1508,7 +1597,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1508
1597
  const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
1509
1598
  if (group) scene.set(info, group);
1510
1599
  if (zero || seq.showsNothingYet) scaledToNothing.add(info);
1511
- if (seq instanceof chunkETAIVOHP_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
1600
+ if (seq instanceof chunkJW67MDHQ_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, moving, zero);
1512
1601
  });
1513
1602
  };
1514
1603
  walk2(root, "", false, true);
@@ -1569,10 +1658,10 @@ function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions) {
1569
1658
  const start = parentStart + (seq.at ?? 0);
1570
1659
  const end = Math.min(parentEnd, seq.duration !== void 0 ? start + seq.duration : parentEnd);
1571
1660
  const span = end - start;
1572
- const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkLBJRXU7N_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
1661
+ const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkANO5R2E6_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
1573
1662
  const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq) };
1574
1663
  const inner = [];
1575
- const isComp = seq instanceof chunkETAIVOHP_cjs.CompositionSequence;
1664
+ const isComp = seq instanceof chunkJW67MDHQ_cjs.CompositionSequence;
1576
1665
  if (isComp) walk(seq, start, end, depth + 1, prefix + name + "/", inner, transitions);
1577
1666
  entries.push({ row, inner, groupable: !isComp });
1578
1667
  });
@@ -1605,7 +1694,8 @@ function groupFamilies(entries) {
1605
1694
  start: Math.min(...members.map((x) => x.start)),
1606
1695
  end: Math.max(...members.map((x) => x.end)),
1607
1696
  keys: members.flatMap((x) => x.keys).sort((a, b) => a - b).slice(0, MAX_KEYS),
1608
- parts: members.map((x) => ({ start: x.start, end: x.end }))
1697
+ parts: members.map((x) => ({ start: x.start, end: x.end })),
1698
+ partNames: members.map((x) => x.name)
1609
1699
  });
1610
1700
  } else {
1611
1701
  out.push(e.row, ...e.inner);
@@ -2120,7 +2210,7 @@ var Movie = class {
2120
2210
  height: options.composition.height ?? this.height,
2121
2211
  duration: options.composition.duration ?? this.duration
2122
2212
  } : void 0;
2123
- await chunkLBJRXU7N_cjs.ensureFilterLibrary(seededComposition);
2213
+ await chunkANO5R2E6_cjs.ensureFilterLibrary(seededComposition);
2124
2214
  const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
2125
2215
  const rootSeqSpec = {
2126
2216
  type: "composition",
@@ -2130,7 +2220,7 @@ var Movie = class {
2130
2220
  ...userComposition
2131
2221
  };
2132
2222
  if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
2133
- const composition = new chunkETAIVOHP_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
2223
+ const composition = new chunkJW67MDHQ_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
2134
2224
  await composition.build();
2135
2225
  this._rootSequence = composition;
2136
2226
  if (composition.target) root.addChild(composition.target);
@@ -2425,6 +2515,27 @@ var Movie = class {
2425
2515
  * The soundtrack, measured: when every audio layer plays, how loud the mix is over time, and `issues`
2426
2516
  * (limiting, inaudible layers, sounds cut off by the end). Use it to CHECK sound you cannot hear.
2427
2517
  */
2518
+ /** The pictures to look at for moments such as `3.5`, `50%`, `f120`, `title@end` (comma-separated). A mistake says what could not be read. */
2519
+ resolveAt(list) {
2520
+ this._requireReady("resolveAt");
2521
+ return resolveAtList(list, { frameRate: this.frameRate, totalFrames: this.totalFrames, duration: this.duration, rows: this.timelineData().rows });
2522
+ }
2523
+ /**
2524
+ * One call that looks at everything `pixi-effects-check` looks at inside the page: layout issues over the whole timeline (every
2525
+ * 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.
2526
+ */
2527
+ async review(opts = {}) {
2528
+ chunkUQBGW5DO_cjs.warnUnknownOptions("movie.review()", opts, ["at", "strict"]);
2529
+ this._requireReady("review");
2530
+ const back = this.currentFrame, wasAtPoster = this._atPoster;
2531
+ try {
2532
+ return await reviewMovie(this, opts);
2533
+ } finally {
2534
+ this.currentFrame = back;
2535
+ if (wasAtPoster) await this._showPoster();
2536
+ else await this._quietly(() => this.gotoFrame(back, true));
2537
+ }
2538
+ }
2428
2539
  /**
2429
2540
  * The fonts of the text layers: `missing` lists layers none of whose `fontFamily` entries is available in this browser (they are
2430
2541
  * drawn in a fallback font), `failed` lists web fonts (`@font-face`) whose file could not be loaded.
@@ -2916,7 +3027,7 @@ function clamp(v, lo, hi) {
2916
3027
  return Math.max(lo, Math.min(hi, v));
2917
3028
  }
2918
3029
  function parseEase(name) {
2919
- chunkLBJRXU7N_cjs.checkEase(name, "a preset");
3030
+ chunkANO5R2E6_cjs.checkEase(name, "a preset");
2920
3031
  return gsap.gsap.parseEase(name);
2921
3032
  }
2922
3033
 
@@ -2939,17 +3050,17 @@ function orbit(opts) {
2939
3050
  throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
2940
3051
  }
2941
3052
  const [cx, cy] = opts.center ?? [width / 2, height / 2];
2942
- const fixedR = opts.radius ?? chunkLBJRXU7N_cjs.homeDistance(height, fov ?? chunkLBJRXU7N_cjs.DEFAULT_FOV);
3053
+ const fixedR = opts.radius ?? chunkANO5R2E6_cjs.homeDistance(height, fov ?? chunkANO5R2E6_cjs.DEFAULT_FOV);
2943
3054
  const start = opts.start ?? -degrees / 2;
2944
3055
  const steps = Math.max(1, Math.round(duration * stepsPerSecond));
2945
3056
  const easeFn = parseEase(ease);
2946
3057
  const dollyEase = dolly ? parseEase(dolly.ease ?? ease) : null;
2947
3058
  const pos = (i) => {
2948
3059
  const p = i / steps;
2949
- const a = (start + degrees * easeFn(p)) * chunkLBJRXU7N_cjs.DEG;
3060
+ const a = (start + degrees * easeFn(p)) * chunkANO5R2E6_cjs.DEG;
2950
3061
  if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
2951
3062
  const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
2952
- const R = chunkLBJRXU7N_cjs.homeDistance(height, f);
3063
+ const R = chunkANO5R2E6_cjs.homeDistance(height, f);
2953
3064
  return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
2954
3065
  };
2955
3066
  const keyframes = [];
@@ -3004,7 +3115,7 @@ function particles(opts) {
3004
3115
  const round3 = (v) => Math.round(v * 1e6) / 1e6;
3005
3116
  const out = [];
3006
3117
  for (let i = 0; i < count; i++) {
3007
- const r = chunkLBJRXU7N_cjs.random(seed * 1000003 + i * 7919 + 17);
3118
+ const r = chunkANO5R2E6_cjs.random(seed * 1000003 + i * 7919 + 17);
3008
3119
  const pick = ([a2, b]) => a2 + r() * (b - a2);
3009
3120
  const born = at + r() * emit;
3010
3121
  const lifetime = pick(life);
@@ -3017,7 +3128,7 @@ function particles(opts) {
3017
3128
  const turn = spin ? pick(spin) : 0;
3018
3129
  const startAngle = spin ? r() * 360 : 0;
3019
3130
  const swayAmp = sway?.amp ?? 0, swayFreq = sway?.freq ?? 1;
3020
- const n0 = sway ? chunkLBJRXU7N_cjs.noise(0, seed * 1000003 + i) : 0;
3131
+ const n0 = sway ? chunkANO5R2E6_cjs.noise(0, seed * 1000003 + i) : 0;
3021
3132
  const at_ = (t) => {
3022
3133
  const axis = (p0, v0, acc) => {
3023
3134
  if (drag > 0) {
@@ -3026,7 +3137,7 @@ function particles(opts) {
3026
3137
  }
3027
3138
  return p0 + v0 * t + 0.5 * acc * t * t;
3028
3139
  };
3029
- const sw = sway ? swayAmp * (chunkLBJRXU7N_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
3140
+ const sw = sway ? swayAmp * (chunkANO5R2E6_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
3030
3141
  return { x: axis(x0, vx, wind) + sw, y: axis(y0, vy, gravity) };
3031
3142
  };
3032
3143
  const kfs = [];
@@ -3088,7 +3199,7 @@ function followPath(opts) {
3088
3199
  if (steps > MAX_STEPS) throw new Error(`followPath(): ${steps} samples is too many (max ${MAX_STEPS}); lower frameRate or duration`);
3089
3200
  const segments = [];
3090
3201
  let total = 0;
3091
- for (const line of chunkLBJRXU7N_cjs.flattenSvgPath(d)) {
3202
+ for (const line of chunkANO5R2E6_cjs.flattenSvgPath(d)) {
3092
3203
  const p = line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
3093
3204
  for (let i = 2; i < p.length; i += 2) {
3094
3205
  const len = Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
@@ -3241,7 +3352,7 @@ function distances(n, from, grid, seed) {
3241
3352
  if (n === 0) return [];
3242
3353
  if (from === "random") {
3243
3354
  const order = Array.from({ length: n }, (_, i) => i);
3244
- const r = chunkLBJRXU7N_cjs.random(seed);
3355
+ const r = chunkANO5R2E6_cjs.random(seed);
3245
3356
  for (let i = n - 1; i > 0; i--) {
3246
3357
  const j = Math.floor(r() * (i + 1));
3247
3358
  [order[i], order[j]] = [order[j], order[i]];
@@ -3303,7 +3414,7 @@ function wiggle(opts) {
3303
3414
  to[n] = p.around;
3304
3415
  return;
3305
3416
  }
3306
- const off = (chunkLBJRXU7N_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
3417
+ const off = (chunkANO5R2E6_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
3307
3418
  to[n] = typeof p.around === "number" ? p.around + off : `(${p.around}) ${off < 0 ? "-" : "+"} ${Math.abs(off)}`;
3308
3419
  });
3309
3420
  out.push({ at: at + s * stepLen, to, duration: stepLen, ease });
@@ -3312,6 +3423,8 @@ function wiggle(opts) {
3312
3423
  }
3313
3424
 
3314
3425
  // src/presets/animateText.ts
3426
+ var TWEEN_KEYS = ["preset", "from", "to", "duration", "ease"];
3427
+ var TIME_KEYS = ["at", "delay", "start", "begin", "time"];
3315
3428
  var KEYS5 = ["by", "x", "y", "align", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
3316
3429
  var PRESETS = {
3317
3430
  rise: { from: { y: 40, alpha: 0 }, duration: 0.5, ease: "back.out(2)" },
@@ -3395,6 +3508,14 @@ function animateText(text, style, options) {
3395
3508
  }
3396
3509
  function resolveTween(spec, which) {
3397
3510
  const t = typeof spec === "string" ? { preset: spec } : spec;
3511
+ if (typeof spec === "object" && spec) {
3512
+ for (const key of Object.keys(spec)) {
3513
+ if (TWEEN_KEYS.includes(key)) continue;
3514
+ if (TIME_KEYS.includes(key)) {
3515
+ 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)"}`);
3516
+ } else chunkUQBGW5DO_cjs.warnUnknownOptions(`animateText() ${which}`, { [key]: 1 }, TWEEN_KEYS);
3517
+ }
3518
+ }
3398
3519
  if (t.preset !== void 0 && !(t.preset in PRESETS)) {
3399
3520
  const guess = chunkUQBGW5DO_cjs.suggestName(String(t.preset), PRESET_NAMES);
3400
3521
  throw new Error(`animateText(): ${which} "${t.preset}" is not a preset${guess ? `; did you mean "${guess}"?` : ""} (presets: ${PRESET_NAMES.map((n) => `"${n}"`).join(", ")})`);
@@ -3768,19 +3889,19 @@ function withFade(spec, opts) {
3768
3889
 
3769
3890
  Object.defineProperty(exports, "noise", {
3770
3891
  enumerable: true,
3771
- get: function () { return chunkLBJRXU7N_cjs.noise; }
3892
+ get: function () { return chunkANO5R2E6_cjs.noise; }
3772
3893
  });
3773
3894
  Object.defineProperty(exports, "rand", {
3774
3895
  enumerable: true,
3775
- get: function () { return chunkLBJRXU7N_cjs.rand; }
3896
+ get: function () { return chunkANO5R2E6_cjs.rand; }
3776
3897
  });
3777
3898
  Object.defineProperty(exports, "random", {
3778
3899
  enumerable: true,
3779
- get: function () { return chunkLBJRXU7N_cjs.random; }
3900
+ get: function () { return chunkANO5R2E6_cjs.random; }
3780
3901
  });
3781
3902
  Object.defineProperty(exports, "registerSequenceType", {
3782
3903
  enumerable: true,
3783
- get: function () { return chunkLBJRXU7N_cjs.registerSequenceType; }
3904
+ get: function () { return chunkANO5R2E6_cjs.registerSequenceType; }
3784
3905
  });
3785
3906
  exports.Movie = Movie;
3786
3907
  exports.animateText = animateText;