pixi-effects 0.35.0 → 0.37.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 (77) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +38 -9
  5. package/ai/tools/lookup.mjs +15 -2
  6. package/dist/{Base-pNq6sVGl.d.cts → Base-BlA6SeqJ.d.cts} +1 -1
  7. package/dist/{Base-CXsMdmJB.d.ts → Base-YqM0Ljvp.d.ts} +1 -1
  8. package/dist/Composition-5KZETM4J.js +7 -0
  9. package/dist/{Composition-NDXKQHB4.js.map → Composition-5KZETM4J.js.map} +1 -1
  10. package/dist/Composition-MHDI7UNZ.cjs +16 -0
  11. package/dist/{Composition-OLH7RC5Z.cjs.map → Composition-MHDI7UNZ.cjs.map} +1 -1
  12. package/dist/Controller.d.cts +2 -2
  13. package/dist/Controller.d.ts +2 -2
  14. package/dist/{Movie-VYtUN4TS.d.ts → Movie-CJN_gvLB.d.cts} +26 -2
  15. package/dist/{Movie-Db4QnMIH.d.cts → Movie-CfHaGmUP.d.ts} +26 -2
  16. package/dist/{Spans-QHHWC2PB.js → Spans-FSSO2HGX.js} +6 -6
  17. package/dist/{Spans-QHHWC2PB.js.map → Spans-FSSO2HGX.js.map} +1 -1
  18. package/dist/{Spans-QRMN2EZO.cjs → Spans-WVJH77BW.cjs} +11 -11
  19. package/dist/{Spans-QRMN2EZO.cjs.map → Spans-WVJH77BW.cjs.map} +1 -1
  20. package/dist/{blendModes-WIUCYIMC.cjs → blendModes-FD3THLNC.cjs} +5 -5
  21. package/dist/{blendModes-WIUCYIMC.cjs.map → blendModes-FD3THLNC.cjs.map} +1 -1
  22. package/dist/{blendModes-PP5NPBXY.js → blendModes-FMYO6SQN.js} +4 -4
  23. package/dist/{blendModes-PP5NPBXY.js.map → blendModes-FMYO6SQN.js.map} +1 -1
  24. package/dist/{chunk-ITDYFBB6.js → chunk-32MMNDDN.js} +7 -6
  25. package/dist/chunk-32MMNDDN.js.map +1 -0
  26. package/dist/{chunk-GL2AQFLQ.cjs → chunk-3SAS3ZE5.cjs} +7 -6
  27. package/dist/chunk-3SAS3ZE5.cjs.map +1 -0
  28. package/dist/{chunk-7TTCPHAV.cjs → chunk-E26M4NTL.cjs} +235 -49
  29. package/dist/chunk-E26M4NTL.cjs.map +1 -0
  30. package/dist/{chunk-XV4L72FW.js → chunk-IPQNT65V.js} +6 -6
  31. package/dist/chunk-IPQNT65V.js.map +1 -0
  32. package/dist/{chunk-IMQJVRMT.cjs → chunk-JN7JHNZR.cjs} +5 -5
  33. package/dist/{chunk-IMQJVRMT.cjs.map → chunk-JN7JHNZR.cjs.map} +1 -1
  34. package/dist/{chunk-2GNK6DPV.js → chunk-KNDEKWQO.js} +203 -18
  35. package/dist/chunk-KNDEKWQO.js.map +1 -0
  36. package/dist/{chunk-5IJENZOG.cjs → chunk-MJMOPWMK.cjs} +92 -92
  37. package/dist/chunk-MJMOPWMK.cjs.map +1 -0
  38. package/dist/{chunk-LEECUCTG.cjs → chunk-QHIOOK5S.cjs} +436 -29
  39. package/dist/chunk-QHIOOK5S.cjs.map +1 -0
  40. package/dist/{chunk-Y253JQOH.js → chunk-SBR75G73.js} +416 -11
  41. package/dist/chunk-SBR75G73.js.map +1 -0
  42. package/dist/{chunk-RTJD3ZSW.js → chunk-VNP7QCJE.js} +4 -4
  43. package/dist/{chunk-RTJD3ZSW.js.map → chunk-VNP7QCJE.js.map} +1 -1
  44. package/dist/index.cjs +41 -33
  45. package/dist/index.d.cts +47 -7
  46. package/dist/index.d.ts +47 -7
  47. package/dist/index.js +5 -5
  48. package/dist/loader.css +1 -1
  49. package/dist/present.cjs +5 -5
  50. package/dist/present.d.cts +2 -2
  51. package/dist/present.d.ts +2 -2
  52. package/dist/present.js +5 -5
  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-CavnUvd5.d.cts → types-DvnXXm33.d.cts} +67 -3
  58. package/dist/{types-CavnUvd5.d.ts → types-DvnXXm33.d.ts} +67 -3
  59. package/docs/api.md +5 -4
  60. package/docs/dsl.md +42 -5
  61. package/llms-full.txt +305 -22
  62. package/package.json +1 -1
  63. package/skills/pixi-effects/SKILL.md +5 -4
  64. package/skills/pixi-effects/reference/cheatsheet.md +9 -6
  65. package/skills/pixi-effects/reference/pitfalls.md +7 -0
  66. package/skills/pixi-effects/reference/recipes.md +237 -3
  67. package/skills/pixi-effects/template.html +6 -6
  68. package/dist/Composition-NDXKQHB4.js +0 -7
  69. package/dist/Composition-OLH7RC5Z.cjs +0 -16
  70. package/dist/chunk-2GNK6DPV.js.map +0 -1
  71. package/dist/chunk-5IJENZOG.cjs.map +0 -1
  72. package/dist/chunk-7TTCPHAV.cjs.map +0 -1
  73. package/dist/chunk-GL2AQFLQ.cjs.map +0 -1
  74. package/dist/chunk-ITDYFBB6.js.map +0 -1
  75. package/dist/chunk-LEECUCTG.cjs.map +0 -1
  76. package/dist/chunk-XV4L72FW.js.map +0 -1
  77. package/dist/chunk-Y253JQOH.js.map +0 -1
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunk5IJENZOG_cjs = require('./chunk-5IJENZOG.cjs');
4
- var chunkLEECUCTG_cjs = require('./chunk-LEECUCTG.cjs');
5
- var chunkIMQJVRMT_cjs = require('./chunk-IMQJVRMT.cjs');
3
+ var chunkMJMOPWMK_cjs = require('./chunk-MJMOPWMK.cjs');
4
+ var chunkQHIOOK5S_cjs = require('./chunk-QHIOOK5S.cjs');
5
+ var chunkJN7JHNZR_cjs = require('./chunk-JN7JHNZR.cjs');
6
+ var chunk3SAS3ZE5_cjs = require('./chunk-3SAS3ZE5.cjs');
6
7
  var chunkTWZNOUM7_cjs = require('./chunk-TWZNOUM7.cjs');
7
8
  var pixi_js = require('pixi.js');
8
9
  var mediabunny = require('mediabunny');
@@ -392,6 +393,7 @@ function findSilences(channels, sampleRate, opts = {}) {
392
393
  }
393
394
 
394
395
  // src/core/inspectAudio.ts
396
+ var BURIED_DB = 12;
395
397
  var INAUDIBLE_DB = -50;
396
398
  var db = (x) => Math.round(10 * Math.max(-120, 20 * Math.log10(x))) / 10;
397
399
  var mono = (chans) => {
@@ -477,6 +479,7 @@ function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
477
479
  return measure(Array.from({ length: b.numberOfChannels }, (_, c) => b.getChannelData(c)), b.sampleRate);
478
480
  };
479
481
  const scoreNotes = [];
482
+ const buried = [];
480
483
  const list = [...sources].sort((a, b) => a.start - b.start).map((s) => {
481
484
  const layer = s.layer ?? "an audio layer";
482
485
  const source = s.source ?? "audio";
@@ -504,8 +507,22 @@ function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
504
507
  const over = s.music.seconds - plays;
505
508
  if (over > 2 && over / s.music.seconds > 0.15) scoreNotes.push(`${layer} (${source}): the score is ${Math.round(s.music.seconds * 10) / 10}s long but only ${Math.round(plays * 100) / 100}s of it play, so it is cut off mid-phrase. Write fewer bars (or a faster tempo) so it ends with the movie`);
506
509
  }
507
- return { layer, source, start: s.start, end: s.end, peakDb, sound, ...s.music ? { music: s.music } : {} };
510
+ let belowMixDb;
511
+ if (!clock && !s.sourceMap && s.start < movieDuration && ownDb > INAUDIBLE_DB) {
512
+ const at = s.start + sound.loudestAt;
513
+ const around = db(peakIn(Math.max(0, at - 0.05), Math.min(movieDuration, at + 0.05)));
514
+ belowMixDb = Math.max(0, Math.round((around - ownDb) * 10) / 10);
515
+ if (!s.music && source.startsWith("sfx") && sound.length <= 4 && belowMixDb >= BURIED_DB) {
516
+ buried.push({ layer, source, ownDb, topVolume, around, belowMixDb, at: Math.round(at * 100) / 100 });
517
+ }
518
+ }
519
+ return { layer, source, start: s.start, end: s.end, peakDb, sound, ...belowMixDb !== void 0 ? { belowMixDb } : {}, ...s.music ? { music: s.music } : {} };
508
520
  });
521
+ if (buried.length > 0) {
522
+ const worst = buried.reduce((a, b) => b.belowMixDb > a.belowMixDb ? b : a);
523
+ const distinct = new Set(buried.map((x) => (x.layer === "an audio layer" ? `#${buried.indexOf(x)}` : x.layer) + x.source)).size;
524
+ scoreNotes.push(`${distinct === 1 ? "1 sound effect is" : `${distinct} sound effects are`} buried (${BURIED_DB} dB or more under the loudest sample of the mix around its loudest moment${buried.length > distinct ? `, ${buried.length} times in all` : ""}): the worst is ${worst.layer} (${worst.source}), its own peak ${worst.ownDb} dBFS at volume ${Math.round(worst.topVolume * 100) / 100} against ${worst.around} dBFS at ${worst.at} s, ${worst.belowMixDb} dB louder. If it is meant to be heard, raise its volume (an accent over music needs 2 to 3) or lower what plays there; every source's belowMixDb has its own number`);
525
+ }
509
526
  const whole = measureLoudness(chans, sr);
510
527
  const silences = findSilences(chans, sr, { minLength: 1 });
511
528
  if (whole.clippedSamples > 0) issues.push(`${whole.clippedSamples} sample(s) of the mix are at full scale (clipped) \u2014 lower the volume of the layers that play loudest`);
@@ -679,12 +696,14 @@ async function reviewMovie(movie, opts = {}) {
679
696
  if (r.issues.length) perFrame.push({ frame, issues: r.issues });
680
697
  }
681
698
  const grouped = splitIssues(groupIssues(perFrame), !!opts.strict);
699
+ const canvas = movie.canvasReport();
700
+ for (const r of canvas) if (!r.ok) grouped.problems.push({ message: r.problem, count: 1, firstFrame: 0, lastFrame: 0, kind: "canvas" });
682
701
  const fonts = movie.inspectFonts();
683
702
  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 });
684
703
  for (const m of fonts.missing) {
685
704
  (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 });
686
705
  }
687
- return { frames: frames.length, problems: grouped.problems, review: grouped.review, fonts, audio: movie.audioBuffer ? movie.inspectAudio() : null, at };
706
+ return { frames: frames.length, problems: grouped.problems, review: grouped.review, canvas, fonts, audio: movie.audioBuffer ? movie.inspectAudio() : null, at };
688
707
  }
689
708
 
690
709
  // src/core/onion.ts
@@ -1490,8 +1509,8 @@ function expandTransitions(spec) {
1490
1509
  }
1491
1510
  const transitions = out.transitions;
1492
1511
  const sequences = out.sequences ?? [];
1493
- const remap = out.duration !== void 0 ? chunkLEECUCTG_cjs.timeRemapOf(out) : null;
1494
- const parentDuration = remap ? chunkLEECUCTG_cjs.contentLength(remap, out.duration) : out.duration ?? Infinity;
1512
+ const remap = out.duration !== void 0 ? chunkQHIOOK5S_cjs.timeRemapOf(out) : null;
1513
+ const parentDuration = remap ? chunkQHIOOK5S_cjs.contentLength(remap, out.duration) : out.duration ?? Infinity;
1495
1514
  const compW = out.width;
1496
1515
  const compH = out.height;
1497
1516
  const threeDByName = new Set(sequences.filter((s) => s.name && s.threeD).map((s) => s.name));
@@ -1559,16 +1578,16 @@ function expandTransitions(spec) {
1559
1578
  if (toUses > 1) {
1560
1579
  throw new Error(`pixi-effects: ${tag} sequence "${t.to}" is used as \`to\` in more than one transition`);
1561
1580
  }
1562
- const tStart = chunkLEECUCTG_cjs.resolveAt(t.at, parentDuration);
1581
+ const tStart = chunkQHIOOK5S_cjs.resolveAt(t.at, parentDuration);
1563
1582
  const tEnd = tStart + t.duration;
1564
1583
  const fromSeq = fromEntry.seq;
1565
- const fromStart = chunkLEECUCTG_cjs.resolveAt(fromSeq.at, parentDuration);
1584
+ const fromStart = chunkQHIOOK5S_cjs.resolveAt(fromSeq.at, parentDuration);
1566
1585
  const fromEnd = fromStart + (fromSeq.duration ?? parentDuration);
1567
1586
  if (tStart < fromStart - EPS || tEnd > fromEnd + EPS) {
1568
1587
  throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`from\` "${t.from}" (lives [${fromStart}, ${fromEnd}], so it ends at ${fromEnd})`);
1569
1588
  }
1570
1589
  const toSeq = toEntry.seq;
1571
- const toStart = chunkLEECUCTG_cjs.resolveAt(toSeq.at, parentDuration);
1590
+ const toStart = chunkQHIOOK5S_cjs.resolveAt(toSeq.at, parentDuration);
1572
1591
  const toEnd = toStart + (toSeq.duration ?? parentDuration);
1573
1592
  if (tStart < toStart - EPS || tEnd > toEnd + EPS) {
1574
1593
  throw new Error(`pixi-effects: ${tag} window [${tStart}, ${tEnd}] is not covered by \`to\` "${t.to}" (lives [${toStart}, ${toEnd}], so it starts at ${toStart})`);
@@ -1628,7 +1647,7 @@ function wrapAsFullComposition(seq, compW, compH) {
1628
1647
  delete inner.at;
1629
1648
  delete inner.duration;
1630
1649
  const m = inner.mask;
1631
- const carriesMatte = m !== void 0 && chunk5IJENZOG_cjs.maskSourceOf(m).kind === "refs";
1650
+ const carriesMatte = m !== void 0 && chunkMJMOPWMK_cjs.maskSourceOf(m).kind === "refs";
1632
1651
  if (carriesMatte) delete inner.mask;
1633
1652
  return {
1634
1653
  ...carriesMatte ? { mask: m } : {},
@@ -1883,7 +1902,49 @@ function rejectReservedNameCollision(filters, seqName) {
1883
1902
  }
1884
1903
  }
1885
1904
 
1905
+ // src/core/canvasCheck.ts
1906
+ var isCanvas = (s) => typeof s.checkDeterminism === "function";
1907
+ var describeDiff = (d) => `${d.t} s${d.pixels >= 0 ? ` (${d.pixels} channel values differ, by up to ${d.maxDiff} of 255)` : ""}`;
1908
+ function canvasReport(root) {
1909
+ const out = [];
1910
+ const visit = (seq) => {
1911
+ if (isCanvas(seq)) {
1912
+ const length = seq.duration ?? 0;
1913
+ const moments = [0.15, 0.4, 0.65, 0.9].map((f) => Math.round(f * length * 1e3) / 1e3);
1914
+ const { differs, drawMs, threw } = seq.checkDeterminism(moments);
1915
+ const name = chunk3SAS3ZE5_cjs.describeLayer(seq.spec);
1916
+ const problem = threw.length ? `${name} could not draw at ${threw.map((t) => `${t} s`).join(", ")} (its draw threw, or the layer failed to build): it shows a magenta checkerboard there; the warning above says why, so the layer cannot be called deterministic` : differs.length ? `${name} does not draw the same picture for the same time (checked at ${differs.map(describeDiff).join(", ")}; drawn in two orders after one draw that was thrown away): usually its draw uses Math.random, Date.now or state that outlives the call (a value it keeps from the last call), so playback, a seek and an export would not agree: use info.random() (seeded) and derive everything from t. If it uses none of these, look at the canvases it draws from: make sprites and masks with info.canvas(w, h) (not document.createElement) and draw them into the picture before the strokes that depend on them` : "";
1917
+ out.push({ name: seq.spec.name ?? name, moments, drawMs, ok: !differs.length && !threw.length, problem });
1918
+ }
1919
+ if (seq instanceof chunkMJMOPWMK_cjs.CompositionSequence) walk2(seq);
1920
+ };
1921
+ const walk2 = (comp) => {
1922
+ for (const { seq } of comp.layers()) {
1923
+ visit(seq);
1924
+ const mask = seq.maskSequence;
1925
+ if (mask) visit(mask);
1926
+ }
1927
+ };
1928
+ walk2(root);
1929
+ return out;
1930
+ }
1931
+ var canvasProblems = (root) => canvasReport(root).filter((r) => !r.ok).map((r) => r.problem);
1932
+
1886
1933
  // src/core/inspect.ts
1934
+ function chainOf(node) {
1935
+ const chain = [];
1936
+ for (let n = node; n && n.parent; n = n.parent) {
1937
+ const i = n.parent.children?.indexOf(n);
1938
+ if (i === void 0 || i < 0) return null;
1939
+ chain.unshift(i);
1940
+ }
1941
+ return chain.length ? chain : null;
1942
+ }
1943
+ function drawnAfter(a, b) {
1944
+ if (!a || !b) return null;
1945
+ for (let i = 0; i < Math.min(a.length, b.length); i++) if (a[i] !== b[i]) return a[i] > b[i];
1946
+ return null;
1947
+ }
1887
1948
  var FAINT = 0.3;
1888
1949
  var intersection = (a, b) => {
1889
1950
  const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
@@ -1907,8 +1968,8 @@ function motionOf(keyframes, local, lives, keys = (k) => GEOMETRY.has(k)) {
1907
1968
  let length = 0;
1908
1969
  if (typeof kf.duration === "number") length = kf.duration;
1909
1970
  else if (kf.duration === "auto") {
1910
- const sp = chunkLEECUCTG_cjs.parseSpring(kf.ease);
1911
- length = sp ? Math.round(chunkLEECUCTG_cjs.springDuration(sp) * 1e3) / 1e3 : 0.5;
1971
+ const sp = chunkQHIOOK5S_cjs.parseSpring(kf.ease);
1972
+ length = sp ? Math.round(chunkQHIOOK5S_cjs.springDuration(sp) * 1e3) / 1e3 : 0.5;
1912
1973
  }
1913
1974
  if (!(length > 0)) continue;
1914
1975
  const kat = kf.at ?? 0;
@@ -1927,7 +1988,11 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1927
1988
  const scene = /* @__PURE__ */ new Map();
1928
1989
  const scaledToNothing = /* @__PURE__ */ new Set();
1929
1990
  const hasMotion = /* @__PURE__ */ new Set();
1930
- const walk2 = (comp, prefix, insideThreeD, parentVisible, parentScene, parentMotion = NO_MOTION, parentZero = false) => {
1991
+ const kinds = /* @__PURE__ */ new Map();
1992
+ const specs = /* @__PURE__ */ new Map();
1993
+ const chains = /* @__PURE__ */ new Map();
1994
+ const chainAlpha = /* @__PURE__ */ new Map();
1995
+ const walk2 = (comp, prefix, insideThreeD, parentVisible, parentScene, parentMotion = NO_MOTION, parentZero = false, parentAlpha = 1) => {
1931
1996
  const blending = /* @__PURE__ */ new Set();
1932
1997
  const now = comp.childTime(time);
1933
1998
  const cameraMotion = (comp.spec.sequences ?? []).filter((q) => q.type === "camera").reduce((m, q) => either(m, motionOf(q.keyframes, now - comp.childBase - (q.at ?? 0), q.duration ?? comp.duration ?? Infinity, () => true)), NO_MOTION);
@@ -1971,10 +2036,14 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1971
2036
  if (threeD) info.depthBlur = Number(depthBlur.toFixed(2));
1972
2037
  if (threeD && light !== void 0) info.light = Number(light.toFixed(2));
1973
2038
  layers.push(info);
2039
+ chainAlpha.set(info, parentAlpha * alpha);
2040
+ chains.set(info, chainOf(display));
2041
+ specs.set(info, seq.spec);
2042
+ kinds.set(info, seq.spec.type === "shape" ? String(seq.spec.shape) : seq.spec.type);
1974
2043
  if (motion.ever) hasMotion.add(info);
1975
2044
  let shown = bounds ? clip(bounds, view) : null;
1976
2045
  const maskTarget = seq.maskSequence?.target;
1977
- const maskSrc = chunk5IJENZOG_cjs.maskSourceOf(seq.spec.mask);
2046
+ const maskSrc = chunkMJMOPWMK_cjs.maskSourceOf(seq.spec.mask);
1978
2047
  if (shown && maskTarget && !(maskSrc.kind === "inline" && maskSrc.invert)) {
1979
2048
  const m = maskTarget.getBounds();
1980
2049
  shown = clip(shown, { x: m.x, y: m.y, width: m.width, height: m.height });
@@ -1983,7 +2052,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1983
2052
  const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
1984
2053
  if (group) scene.set(info, group);
1985
2054
  if (zero || seq.showsNothingYet) scaledToNothing.add(info);
1986
- if (seq instanceof chunk5IJENZOG_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, motion, zero);
2055
+ if (seq instanceof chunkMJMOPWMK_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, motion, zero, parentAlpha * alpha);
1987
2056
  });
1988
2057
  };
1989
2058
  walk2(root, "", false, true);
@@ -2019,6 +2088,42 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
2019
2088
  if (share > 0.25) issues.push(`text layers "${texts[i].path}" and "${texts[j].path}" overlap by ${Math.round(share * 100)}% of the smaller one`);
2020
2089
  }
2021
2090
  }
2091
+ const FILLED = /* @__PURE__ */ new Set(["rect", "circle", "ellipse", "image", "video"]);
2092
+ for (const t of texts) {
2093
+ const tb = seen.get(t);
2094
+ if (!tb) continue;
2095
+ const area = tb.width * tb.height;
2096
+ if (!(area > 0)) continue;
2097
+ for (const o of layers) {
2098
+ if (o === t || o.type === "text" || !o.visible || !o.bounds) continue;
2099
+ if (o.path.startsWith(t.path + "/") || t.path.startsWith(o.path + "/")) continue;
2100
+ const kind = kinds.get(o) ?? o.type, spec = specs.get(o) ?? {};
2101
+ const initial = spec.initial ?? {};
2102
+ const hairline = (kind === "line" || kind === "rect") && Math.min(o.bounds.width, o.bounds.height) < 12 && Math.max(o.bounds.width, o.bounds.height) >= 40;
2103
+ const soft = spec.blendMode !== void 0 && spec.blendMode !== "normal" || Array.isArray(spec.filters) && spec.filters.length > 0;
2104
+ const filled = kind === "image" || kind === "video" || initial.fillColor !== void 0 || spec.fillGradient !== void 0 || initial.fillGradient !== void 0;
2105
+ if (o.threeD || t.threeD) continue;
2106
+ const over = drawnAfter(chains.get(o) ?? null, chains.get(t) ?? null) ?? layers.indexOf(o) > layers.indexOf(t);
2107
+ const opacity = chainAlpha.get(o) ?? o.alpha;
2108
+ const hides = FILLED.has(kind) && !hairline && filled && !soft && over && opacity >= 0.8;
2109
+ const cuts = hairline && !soft && over && opacity >= FAINT;
2110
+ if (!hides && !cuts) continue;
2111
+ const st = scene.get(t), so = scene.get(o);
2112
+ if (st && so && st !== so) continue;
2113
+ const ob = seen.get(o);
2114
+ if (!ob) continue;
2115
+ const covered = intersection(tb, ob) / area;
2116
+ if (cuts) {
2117
+ const horizontal = ob.width >= ob.height;
2118
+ const mid = horizontal ? ob.y + ob.height / 2 : ob.x + ob.width / 2;
2119
+ const lo = horizontal ? tb.y + tb.height * 0.2 : tb.x + tb.width * 0.2, hi = horizontal ? tb.y + tb.height * 0.8 : tb.x + tb.width * 0.8;
2120
+ const along = horizontal ? Math.min(ob.x + ob.width, tb.x + tb.width) - Math.max(ob.x, tb.x) : Math.min(ob.y + ob.height, tb.y + tb.height) - Math.max(ob.y, tb.y);
2121
+ if (mid < lo || mid > hi || along < 0.3 * (horizontal ? tb.width : tb.height)) continue;
2122
+ } else if (covered < 0.1) continue;
2123
+ const word = kind === "image" || kind === "video" ? kind : "shape";
2124
+ issues.push(`text layer "${t.path}" and ${word} layer "${o.path}" overlap: it covers ${Math.round(covered * 100)}% of the text (drawn ${over ? "over" : "under"} it)`);
2125
+ }
2126
+ }
2022
2127
  const mode = opts.layers ?? "all";
2023
2128
  const listed = mode === "none" ? [] : mode === "visible" ? layers.filter((l) => l.visible) : layers;
2024
2129
  return { frame, time, canvas, summary: { layers: layers.length, visible: layers.filter((l) => l.visible).length }, issues, layers: listed };
@@ -2031,7 +2136,7 @@ var round = (n) => Math.round(n * 1e6) / 1e6;
2031
2136
  var family = (name) => name.replace(/\d+/g, "#");
2032
2137
  function detailOf(seq) {
2033
2138
  const s = seq.spec;
2034
- const spans = chunkLEECUCTG_cjs.spansMeta.get(seq.spec)?.preview;
2139
+ const spans = chunkQHIOOK5S_cjs.spansMeta.get(seq.spec)?.preview;
2035
2140
  if (spans) return spans.length > 60 ? spans.slice(0, 57) + "\u2026" : spans;
2036
2141
  if (typeof s.text === "string") return s.text.length > 60 ? s.text.slice(0, 57) + "\u2026" : s.text;
2037
2142
  if (typeof s.asset === "string") return `asset \u201C${s.asset}\u201D`;
@@ -2047,10 +2152,10 @@ function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions, lo
2047
2152
  const start = parentStart + (seq.at ?? 0);
2048
2153
  const end = Math.min(parentEnd, seq.duration !== void 0 ? start + seq.duration : parentEnd);
2049
2154
  const span = end - start;
2050
- const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkLEECUCTG_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
2155
+ const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkQHIOOK5S_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
2051
2156
  const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq), ...local ? { local } : {} };
2052
2157
  const inner = [];
2053
- const isComp = seq instanceof chunk5IJENZOG_cjs.CompositionSequence;
2158
+ const isComp = seq instanceof chunkMJMOPWMK_cjs.CompositionSequence;
2054
2159
  if (isComp) {
2055
2160
  const label = seq.remapLabel();
2056
2161
  walk(seq, label ? 0 : start, label ? seq.contentSpan : end, depth + 1, prefix + name + "/", inner, transitions, label ? `${name} ${label}` : local);
@@ -2209,7 +2314,7 @@ function sheetLayout(n, columns, cellW, srcW, srcH, labelH) {
2209
2314
  }
2210
2315
 
2211
2316
  // src/core/vars.ts
2212
- var RESERVED_NAMES = ["w", "h", "W", "H", "GW", "GH", "t", "d", "T", "contain", "cover", "value", ...chunkLEECUCTG_cjs.EXPRESSION_NAMES];
2317
+ var RESERVED_NAMES = ["w", "h", "W", "H", "GW", "GH", "t", "d", "T", "contain", "cover", "value", ...chunkQHIOOK5S_cjs.EXPRESSION_NAMES];
2213
2318
  var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
2214
2319
  var isPlain = (v) => !!v && typeof v === "object" && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
2215
2320
  function normalizeVars(raw, where, warn = (m) => console.warn(m)) {
@@ -2748,8 +2853,8 @@ var Movie = class {
2748
2853
  this.stops = normalizeStops(applyVars(options.composition, this._vars)?.stops, this.duration, this.frameRate);
2749
2854
  this._posterFrame = poster.frame;
2750
2855
  this.background = options.background ?? "#000000";
2751
- this.timeline = chunkLEECUCTG_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
2752
- chunkLEECUCTG_cjs.lengthen(this.timeline, this.duration);
2856
+ this.timeline = chunkQHIOOK5S_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
2857
+ chunkQHIOOK5S_cjs.lengthen(this.timeline, this.duration);
2753
2858
  const baseAppOptions = {
2754
2859
  width: this.width,
2755
2860
  height: this.height,
@@ -2843,8 +2948,8 @@ var Movie = class {
2843
2948
  height: sizeWins ? height : spec.height ?? height,
2844
2949
  duration: spec.duration ?? this.duration
2845
2950
  } : void 0;
2846
- await chunkLEECUCTG_cjs.ensureFilterLibrary(seededComposition);
2847
- if (chunkIMQJVRMT_cjs.usesAdvancedBlend(seededComposition)) await chunkIMQJVRMT_cjs.enableAdvancedBlend(this.app.renderer);
2951
+ await chunkQHIOOK5S_cjs.ensureFilterLibrary(seededComposition);
2952
+ if (chunkJN7JHNZR_cjs.usesAdvancedBlend(seededComposition)) await chunkJN7JHNZR_cjs.enableAdvancedBlend(this.app.renderer);
2848
2953
  const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
2849
2954
  const rootSeqSpec = {
2850
2955
  type: "composition",
@@ -2854,7 +2959,7 @@ var Movie = class {
2854
2959
  ...userComposition
2855
2960
  };
2856
2961
  if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
2857
- const sequence = new chunk5IJENZOG_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
2962
+ const sequence = new chunkMJMOPWMK_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
2858
2963
  lp?.begin("build", countLayers(rootSeqSpec));
2859
2964
  try {
2860
2965
  await sequence.build();
@@ -2990,8 +3095,8 @@ var Movie = class {
2990
3095
  if (!changed) return { reason, ms: 0, audio: "none", frame: this.currentFrame, playing: this.isPlaying, unchanged: true };
2991
3096
  sequence = await this._construct(composition, vars, width, height, void 0, this._resized || sizeChanged);
2992
3097
  alive();
2993
- const timeline = chunkLEECUCTG_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
2994
- chunkLEECUCTG_cjs.lengthen(timeline, this.duration);
3098
+ const timeline = chunkQHIOOK5S_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
3099
+ chunkQHIOOK5S_cjs.lengthen(timeline, this.duration);
2995
3100
  sequence.bindTimeline(timeline);
2996
3101
  const hadSound = this.audioBuffer !== null;
2997
3102
  const audio = await this._mixSound(sequence, this._ensureAudioContext());
@@ -3288,6 +3393,16 @@ var Movie = class {
3288
3393
  await this.gotoFrame(f, true);
3289
3394
  return inspectScene(this._rootSequence, f, f / this.frameRate, { width: this.width, height: this.height }, opts);
3290
3395
  }
3396
+ /** The canvas layers whose same frame is not the same picture (what `check` reports as a problem): one sentence each. */
3397
+ canvasProblems() {
3398
+ this._requireReady("canvasProblems");
3399
+ return canvasProblems(this._rootSequence);
3400
+ }
3401
+ /** Every canvas layer as the determinism check saw it: the moments drawn, the usual time of a draw in ms, and whether the same time gave the same picture (`check` prints it even when all pass). */
3402
+ canvasReport() {
3403
+ this._requireReady("canvasReport");
3404
+ return canvasReport(this._rootSequence);
3405
+ }
3291
3406
  /**
3292
3407
  * Every layer as a row with absolute start / end / keyframe times (seconds), plus the transition windows. Runs of
3293
3408
  * similar layers (`pop-1` … `pop-12`) are one row with a `parts` span each. Read it, or draw it with `timelineChart()`.
@@ -3705,7 +3820,7 @@ function countLayers(spec) {
3705
3820
  n += 1 + countLayers(s);
3706
3821
  const spans = s.spans;
3707
3822
  if (Array.isArray(spans)) n += spans.length;
3708
- const src = chunk5IJENZOG_cjs.maskSourceOf(mask);
3823
+ const src = chunkMJMOPWMK_cjs.maskSourceOf(mask);
3709
3824
  if (src.kind === "inline") n += 1 + countLayers(src.spec);
3710
3825
  }
3711
3826
  return n;
@@ -3718,7 +3833,7 @@ function measureText(text, style = {}) {
3718
3833
  return { width: m.width, height: m.height, lines: text.split("\n").length };
3719
3834
  }
3720
3835
  function splitText(text, style = {}, opts = {}) {
3721
- chunkTWZNOUM7_cjs.warnUnknownOptions("splitText()", opts, ["by", "x", "y", "align"]);
3836
+ chunkTWZNOUM7_cjs.warnUnknownOptions("splitText()", opts, ["by", "x", "y", "align", "vertical"]);
3722
3837
  let by = opts.by ?? "chars";
3723
3838
  if (!BY.includes(by)) {
3724
3839
  const g = chunkTWZNOUM7_cjs.suggestName(String(by), BY);
@@ -3737,6 +3852,7 @@ function splitText(text, style = {}, opts = {}) {
3737
3852
  }
3738
3853
  if (text === "") return [];
3739
3854
  const ts = new pixi_js.TextStyle(style);
3855
+ if (opts.vertical) return splitVertical(text, style, ts, x0, y0, align, by);
3740
3856
  const lineHeight = pixi_js.CanvasTextMetrics.measureText("M", ts).lineHeight ?? pixi_js.CanvasTextMetrics.measureText("M", ts).height;
3741
3857
  const sentinel = widthOf("|", ts);
3742
3858
  const out = [];
@@ -3760,11 +3876,41 @@ function splitText(text, style = {}, opts = {}) {
3760
3876
  });
3761
3877
  return out;
3762
3878
  }
3879
+ var PUNCT = new Set("\u3001\u3002\uFF0C\uFF0E\uFF64\uFF61");
3880
+ var SMALL = new Set("\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308E\u30A1\u30A3\u30A5\u30A7\u30A9\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6");
3881
+ var TURNED = new Set("\u30FC\u2015\u2014\uFF0D\uFF5E\u301C\u2026\u2025\uFF08\uFF09()\uFF3B\uFF3D[]\uFF5B\uFF5D{}\u300C\u300D\u300E\u300F\u3008\u3009\u300A\u300B\u3010\u3011\u3014\u3015<>\u226A\u226B");
3882
+ function splitVertical(text, style, ts, right, top, align, by) {
3883
+ if (by !== "chars") console.warn(`pixi-effects: splitText(): vertical text has only a "chars" form (by "${by}" has no vertical layout); using "chars"`);
3884
+ const em = typeof style.fontSize === "number" ? style.fontSize : Number(ts.fontSize) || 26;
3885
+ const spacing = typeof style.letterSpacing === "number" ? style.letterSpacing : 0;
3886
+ const cell = em + spacing;
3887
+ const lineHeight = pixi_js.CanvasTextMetrics.measureText("M", ts).lineHeight ?? pixi_js.CanvasTextMetrics.measureText("M", ts).height;
3888
+ const out = [];
3889
+ text.split("\n").forEach((line, lineNo) => {
3890
+ const chars = Array.from(line);
3891
+ const start = align === "center" ? top - chars.length * cell / 2 : align === "right" ? top - chars.length * cell : top;
3892
+ const column = right - lineHeight / 2 - lineNo * lineHeight;
3893
+ chars.forEach((ch, i) => {
3894
+ if (/\s/.test(ch)) return;
3895
+ let cx = column, cy = start + i * cell + cell / 2;
3896
+ if (PUNCT.has(ch)) {
3897
+ cx += 0.5 * em;
3898
+ cy -= 0.5 * em;
3899
+ } else if (SMALL.has(ch)) {
3900
+ cx += 0.1 * em;
3901
+ cy -= 0.1 * em;
3902
+ }
3903
+ const width = widthOf(ch, ts);
3904
+ out.push({ text: ch, x: cx - width / 2, y: cy - lineHeight / 2, width, index: out.length, line: lineNo, cx, cy, rotation: TURNED.has(ch) ? 90 : 0 });
3905
+ });
3906
+ });
3907
+ return out;
3908
+ }
3763
3909
 
3764
3910
  // src/presets/_ease.ts
3765
3911
  function parseEase2(name) {
3766
- chunkLEECUCTG_cjs.checkEase(name, "a preset");
3767
- return chunkLEECUCTG_cjs.parseEase(name);
3912
+ chunkQHIOOK5S_cjs.checkEase(name, "a preset");
3913
+ return chunkQHIOOK5S_cjs.parseEase(name);
3768
3914
  }
3769
3915
 
3770
3916
  // src/presets/kenBurns.ts
@@ -3899,17 +4045,17 @@ function orbit(opts) {
3899
4045
  throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
3900
4046
  }
3901
4047
  const [cx, cy] = opts.center ?? [width / 2, height / 2];
3902
- const fixedR = opts.radius ?? chunkLEECUCTG_cjs.homeDistance(height, fov ?? chunkLEECUCTG_cjs.DEFAULT_FOV);
4048
+ const fixedR = opts.radius ?? chunkQHIOOK5S_cjs.homeDistance(height, fov ?? chunkQHIOOK5S_cjs.DEFAULT_FOV);
3903
4049
  const start = opts.start ?? -degrees / 2;
3904
4050
  const steps = Math.max(1, Math.round(duration * stepsPerSecond));
3905
4051
  const easeFn = parseEase2(ease);
3906
4052
  const dollyEase = dolly ? parseEase2(dolly.ease ?? ease) : null;
3907
4053
  const pos = (i) => {
3908
4054
  const p = i / steps;
3909
- const a = (start + degrees * easeFn(p)) * chunkLEECUCTG_cjs.DEG;
4055
+ const a = (start + degrees * easeFn(p)) * chunkQHIOOK5S_cjs.DEG;
3910
4056
  if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
3911
4057
  const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
3912
- const R = chunkLEECUCTG_cjs.homeDistance(height, f);
4058
+ const R = chunkQHIOOK5S_cjs.homeDistance(height, f);
3913
4059
  return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
3914
4060
  };
3915
4061
  const keyframes = [];
@@ -3964,7 +4110,7 @@ function particles(opts) {
3964
4110
  const round2 = (v) => Math.round(v * 1e6) / 1e6;
3965
4111
  const out = [];
3966
4112
  for (let i = 0; i < count; i++) {
3967
- const r = chunkLEECUCTG_cjs.random(seed * 1000003 + i * 7919 + 17);
4113
+ const r = chunkQHIOOK5S_cjs.random(seed * 1000003 + i * 7919 + 17);
3968
4114
  const pick = ([a2, b]) => a2 + r() * (b - a2);
3969
4115
  const born = at + r() * emit;
3970
4116
  const lifetime = pick(life);
@@ -3977,7 +4123,7 @@ function particles(opts) {
3977
4123
  const turn = spin ? pick(spin) : 0;
3978
4124
  const startAngle = spin ? r() * 360 : 0;
3979
4125
  const swayAmp = sway?.amp ?? 0, swayFreq = sway?.freq ?? 1;
3980
- const n0 = sway ? chunkLEECUCTG_cjs.noise(0, seed * 1000003 + i) : 0;
4126
+ const n0 = sway ? chunkQHIOOK5S_cjs.noise(0, seed * 1000003 + i) : 0;
3981
4127
  const at_ = (t) => {
3982
4128
  const axis = (p0, v0, acc) => {
3983
4129
  if (drag > 0) {
@@ -3986,7 +4132,7 @@ function particles(opts) {
3986
4132
  }
3987
4133
  return p0 + v0 * t + 0.5 * acc * t * t;
3988
4134
  };
3989
- const sw = sway ? swayAmp * (chunkLEECUCTG_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
4135
+ const sw = sway ? swayAmp * (chunkQHIOOK5S_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
3990
4136
  return { x: axis(x0, vx, wind) + sw, y: axis(y0, vy, gravity) };
3991
4137
  };
3992
4138
  const kfs = [];
@@ -4048,7 +4194,7 @@ function followPath(opts) {
4048
4194
  if (steps > MAX_STEPS) throw new Error(`followPath(): ${steps} samples is too many (max ${MAX_STEPS}); lower frameRate or duration`);
4049
4195
  const segments = [];
4050
4196
  let total = 0;
4051
- for (const line of chunkLEECUCTG_cjs.flattenSvgPath(d)) {
4197
+ for (const line of chunkQHIOOK5S_cjs.flattenSvgPath(d)) {
4052
4198
  const p = line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
4053
4199
  for (let i = 2; i < p.length; i += 2) {
4054
4200
  const len = Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
@@ -4098,6 +4244,45 @@ function followPath(opts) {
4098
4244
  return out;
4099
4245
  }
4100
4246
 
4247
+ // src/core/pathSample.ts
4248
+ function samplePath(d) {
4249
+ if (typeof d !== "string" || d.trim() === "") throw new Error('samplePath(): d must be SVG path data, e.g. "M 40 300 C 200 20 400 20 560 300"');
4250
+ const lines = chunkQHIOOK5S_cjs.flattenSvgPath(d);
4251
+ const segments = [];
4252
+ let length = 0;
4253
+ for (const line of lines) {
4254
+ const p = line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
4255
+ for (let i = 2; i < p.length; i += 2) {
4256
+ const len = Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
4257
+ if (len === 0) continue;
4258
+ segments.push({ x0: p[i - 2], y0: p[i - 1], x1: p[i], y1: p[i + 1], start: length, len });
4259
+ length += len;
4260
+ }
4261
+ }
4262
+ if (!(length > 0)) throw new Error(`samplePath(): the path "${d}" has no length (it needs at least two different points)`);
4263
+ const find = (s) => {
4264
+ let lo = 0, hi = segments.length - 1;
4265
+ while (lo < hi) {
4266
+ const mid = lo + hi >> 1;
4267
+ if (segments[mid].start + segments[mid].len < s) lo = mid + 1;
4268
+ else hi = mid;
4269
+ }
4270
+ return segments[lo];
4271
+ };
4272
+ return {
4273
+ length,
4274
+ at(u) {
4275
+ const s = Math.min(Math.max(Number.isFinite(u) ? u : 0, 0), 1) * length;
4276
+ const g = find(s);
4277
+ const t = Math.min(Math.max((s - g.start) / g.len, 0), 1);
4278
+ return { x: g.x0 + (g.x1 - g.x0) * t, y: g.y0 + (g.y1 - g.y0) * t, angle: Math.atan2(g.y1 - g.y0, g.x1 - g.x0) };
4279
+ },
4280
+ trim(start = 0, end = 1) {
4281
+ return chunkQHIOOK5S_cjs.trimPolylines(lines, start, end).map((l) => l.closed ? [...l.pts, l.pts[0], l.pts[1]] : l.pts);
4282
+ }
4283
+ };
4284
+ }
4285
+
4101
4286
  // src/presets/cameraPath.ts
4102
4287
  var KEYS4 = ["points", "at", "duration", "ease", "look", "lookAhead", "smooth", "from", "to", "frameRate"];
4103
4288
  var MAX_STEPS2 = 4e3;
@@ -4202,7 +4387,7 @@ function distances(n, from, grid, seed) {
4202
4387
  if (n === 0) return [];
4203
4388
  if (from === "random") {
4204
4389
  const order = Array.from({ length: n }, (_, i) => i);
4205
- const r = chunkLEECUCTG_cjs.random(seed);
4390
+ const r = chunkQHIOOK5S_cjs.random(seed);
4206
4391
  for (let i = n - 1; i > 0; i--) {
4207
4392
  const j = Math.floor(r() * (i + 1));
4208
4393
  [order[i], order[j]] = [order[j], order[i]];
@@ -4264,7 +4449,7 @@ function wiggle(opts) {
4264
4449
  to[n] = p.around;
4265
4450
  return;
4266
4451
  }
4267
- const off = (chunkLEECUCTG_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
4452
+ const off = (chunkQHIOOK5S_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
4268
4453
  to[n] = typeof p.around === "number" ? p.around + off : `(${p.around}) ${off < 0 ? "-" : "+"} ${Math.abs(off)}`;
4269
4454
  });
4270
4455
  out.push({ at: at + s * stepLen, to, duration: stepLen, ease });
@@ -4275,7 +4460,7 @@ function wiggle(opts) {
4275
4460
  // src/presets/animateText.ts
4276
4461
  var TWEEN_KEYS = ["preset", "from", "to", "duration", "ease"];
4277
4462
  var TIME_KEYS = ["at", "delay", "start", "begin", "time"];
4278
- var KEYS5 = ["by", "x", "y", "align", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
4463
+ var KEYS5 = ["by", "x", "y", "align", "vertical", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
4279
4464
  var PRESETS = {
4280
4465
  rise: { from: { y: 40, alpha: 0 }, duration: 0.5, ease: "back.out(2)" },
4281
4466
  drop: { from: { y: -50, alpha: 0 }, duration: 0.7, ease: "bounce.out" },
@@ -4288,14 +4473,14 @@ var PRESETS = {
4288
4473
  var PRESET_NAMES = Object.keys(PRESETS);
4289
4474
  function animateText(text, style, options) {
4290
4475
  chunkTWZNOUM7_cjs.warnUnknownOptions("animateText()", options, KEYS5);
4291
- const { by = "chars", x = 0, y = 0, align = "left", name = "text", at = 0, duration, idle, styleFor } = options;
4476
+ const { by = "chars", x = 0, y = 0, align = "left", vertical = false, name = "text", at = 0, duration, idle, styleFor } = options;
4292
4477
  for (const [n, v] of [["x", x], ["y", y]]) {
4293
4478
  if (typeof v !== "number" || !Number.isFinite(v)) {
4294
4479
  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.`);
4295
4480
  }
4296
4481
  }
4297
4482
  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}`);
4298
- const pieces = splitText(text, style, { by, x, y, align });
4483
+ const pieces = splitText(text, style, { by, x, y, align, ...vertical ? { vertical } : {} });
4299
4484
  if (pieces.length === 0) return [];
4300
4485
  const lineHeight = measureText("M", style).height;
4301
4486
  const inTween = options.in === false ? null : resolveTween(options.in ?? "rise", "in");
@@ -4314,12 +4499,12 @@ function animateText(text, style, options) {
4314
4499
  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)}`);
4315
4500
  }
4316
4501
  return pieces.map((p, i) => {
4317
- const cx = p.x + p.width / 2, cy = p.y + lineHeight / 2;
4318
- const rest = { x: cx, y: cy, alpha: 1, scale: 1, scaleX: 1, scaleY: 1, rotation: 0 };
4502
+ const cx = p.cx ?? p.x + p.width / 2, cy = p.cy ?? p.y + lineHeight / 2;
4503
+ const rest = { x: cx, y: cy, alpha: 1, scale: 1, scaleX: 1, scaleY: 1, rotation: p.rotation ?? 0 };
4319
4504
  const start = at + delays[i];
4320
4505
  const len = end - start;
4321
4506
  const keyframes = [];
4322
- const place = (key, v) => key === "x" || key === "y" ? rest[key] + v : v;
4507
+ const place = (key, v) => key === "x" || key === "y" || key === "rotation" ? rest[key] + v : v;
4323
4508
  if (inTween) {
4324
4509
  keyframes.push({
4325
4510
  at: 0,
@@ -4358,7 +4543,7 @@ function animateText(text, style, options) {
4358
4543
  style: styleFor ? { ...style, ...styleFor(p) } : style,
4359
4544
  at: start,
4360
4545
  duration: len,
4361
- initial: { x: cx, y: cy, anchorX: 0.5, anchorY: 0.5 },
4546
+ initial: { x: cx, y: cy, anchorX: 0.5, anchorY: 0.5, ...rest.rotation !== 0 ? { rotation: rest.rotation } : {} },
4362
4547
  keyframes
4363
4548
  };
4364
4549
  });
@@ -4718,9 +4903,10 @@ exports.parseEase = parseEase2;
4718
4903
  exports.particles = particles;
4719
4904
  exports.react = react;
4720
4905
  exports.resolveLoader = resolveLoader;
4906
+ exports.samplePath = samplePath;
4721
4907
  exports.splitText = splitText;
4722
4908
  exports.stagger = stagger;
4723
4909
  exports.wiggle = wiggle;
4724
4910
  exports.withFade = withFade;
4725
- //# sourceMappingURL=chunk-7TTCPHAV.cjs.map
4726
- //# sourceMappingURL=chunk-7TTCPHAV.cjs.map
4911
+ //# sourceMappingURL=chunk-E26M4NTL.cjs.map
4912
+ //# sourceMappingURL=chunk-E26M4NTL.cjs.map