pixi-effects 0.34.0 → 0.36.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 (79) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +26 -7
  5. package/ai/tools/lookup.mjs +14 -2
  6. package/dist/{Base-CXsMdmJB.d.ts → Base-CdF7AJop.d.ts} +1 -1
  7. package/dist/{Base-pNq6sVGl.d.cts → Base-CmiFiwQM.d.cts} +1 -1
  8. package/dist/Composition-BMRCE5FH.js +7 -0
  9. package/dist/{Composition-NDXKQHB4.js.map → Composition-BMRCE5FH.js.map} +1 -1
  10. package/dist/Composition-XRLYR6GL.cjs +16 -0
  11. package/dist/{Composition-OLH7RC5Z.cjs.map → Composition-XRLYR6GL.cjs.map} +1 -1
  12. package/dist/Controller.cjs +130 -1
  13. package/dist/Controller.cjs.map +1 -1
  14. package/dist/Controller.d.cts +24 -2
  15. package/dist/Controller.d.ts +24 -2
  16. package/dist/Controller.js +130 -1
  17. package/dist/Controller.js.map +1 -1
  18. package/dist/{Movie-f0tQG9y5.d.ts → Movie-Bh1HPdKw.d.cts} +30 -2
  19. package/dist/{Movie-CL6SsAWe.d.cts → Movie-D5WFfZdZ.d.ts} +30 -2
  20. package/dist/{Spans-QRMN2EZO.cjs → Spans-4IRBSOLI.cjs} +11 -11
  21. package/dist/{Spans-QRMN2EZO.cjs.map → Spans-4IRBSOLI.cjs.map} +1 -1
  22. package/dist/{Spans-QHHWC2PB.js → Spans-MWP2FX3T.js} +6 -6
  23. package/dist/{Spans-QHHWC2PB.js.map → Spans-MWP2FX3T.js.map} +1 -1
  24. package/dist/{blendModes-WIUCYIMC.cjs → blendModes-KU7XLV7U.cjs} +5 -5
  25. package/dist/{blendModes-WIUCYIMC.cjs.map → blendModes-KU7XLV7U.cjs.map} +1 -1
  26. package/dist/{blendModes-PP5NPBXY.js → blendModes-OOI3T6BE.js} +4 -4
  27. package/dist/{blendModes-PP5NPBXY.js.map → blendModes-OOI3T6BE.js.map} +1 -1
  28. package/dist/{chunk-ITDYFBB6.js → chunk-2JFFKHXO.js} +5 -5
  29. package/dist/chunk-2JFFKHXO.js.map +1 -0
  30. package/dist/{chunk-GL2AQFLQ.cjs → chunk-42ML5AKN.cjs} +5 -5
  31. package/dist/chunk-42ML5AKN.cjs.map +1 -0
  32. package/dist/{chunk-XV4L72FW.js → chunk-AZ6AINSW.js} +5 -5
  33. package/dist/{chunk-XV4L72FW.js.map → chunk-AZ6AINSW.js.map} +1 -1
  34. package/dist/{chunk-FVZEBYLW.cjs → chunk-CC7SUR7Q.cjs} +237 -54
  35. package/dist/chunk-CC7SUR7Q.cjs.map +1 -0
  36. package/dist/{chunk-Y253JQOH.js → chunk-G3DX65HO.js} +110 -10
  37. package/dist/chunk-G3DX65HO.js.map +1 -0
  38. package/dist/{chunk-LEECUCTG.cjs → chunk-KJSEQVBV.cjs} +129 -29
  39. package/dist/chunk-KJSEQVBV.cjs.map +1 -0
  40. package/dist/{chunk-IMQJVRMT.cjs → chunk-NEUPEENI.cjs} +5 -5
  41. package/dist/{chunk-IMQJVRMT.cjs.map → chunk-NEUPEENI.cjs.map} +1 -1
  42. package/dist/{chunk-67LG7MN5.js → chunk-VF56PRAA.js} +205 -22
  43. package/dist/chunk-VF56PRAA.js.map +1 -0
  44. package/dist/{chunk-RTJD3ZSW.js → chunk-WIIU42FH.js} +4 -4
  45. package/dist/{chunk-RTJD3ZSW.js.map → chunk-WIIU42FH.js.map} +1 -1
  46. package/dist/{chunk-5IJENZOG.cjs → chunk-Y5UWFH6Y.cjs} +91 -91
  47. package/dist/{chunk-5IJENZOG.cjs.map → chunk-Y5UWFH6Y.cjs.map} +1 -1
  48. package/dist/index.cjs +33 -33
  49. package/dist/index.d.cts +20 -6
  50. package/dist/index.d.ts +20 -6
  51. package/dist/index.js +5 -5
  52. package/dist/loader.css +1 -1
  53. package/dist/present.cjs +5 -5
  54. package/dist/present.d.cts +2 -2
  55. package/dist/present.d.ts +2 -2
  56. package/dist/present.js +5 -5
  57. package/dist/three.cjs +6 -6
  58. package/dist/three.d.cts +2 -2
  59. package/dist/three.d.ts +2 -2
  60. package/dist/three.js +2 -2
  61. package/dist/{types-CavnUvd5.d.ts → types-B6zDTU2V.d.cts} +14 -1
  62. package/dist/{types-CavnUvd5.d.cts → types-B6zDTU2V.d.ts} +14 -1
  63. package/docs/api.md +15 -4
  64. package/docs/dsl.md +7 -5
  65. package/llms-full.txt +95 -18
  66. package/package.json +1 -1
  67. package/skills/pixi-effects/SKILL.md +3 -3
  68. package/skills/pixi-effects/reference/cheatsheet.md +7 -6
  69. package/skills/pixi-effects/reference/pitfalls.md +3 -0
  70. package/skills/pixi-effects/reference/recipes.md +60 -0
  71. package/skills/pixi-effects/template.html +6 -6
  72. package/dist/Composition-NDXKQHB4.js +0 -7
  73. package/dist/Composition-OLH7RC5Z.cjs +0 -16
  74. package/dist/chunk-67LG7MN5.js.map +0 -1
  75. package/dist/chunk-FVZEBYLW.cjs.map +0 -1
  76. package/dist/chunk-GL2AQFLQ.cjs.map +0 -1
  77. package/dist/chunk-ITDYFBB6.js.map +0 -1
  78. package/dist/chunk-LEECUCTG.cjs.map +0 -1
  79. package/dist/chunk-Y253JQOH.js.map +0 -1
@@ -1,8 +1,8 @@
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 chunkY5UWFH6Y_cjs = require('./chunk-Y5UWFH6Y.cjs');
4
+ var chunkKJSEQVBV_cjs = require('./chunk-KJSEQVBV.cjs');
5
+ var chunkNEUPEENI_cjs = require('./chunk-NEUPEENI.cjs');
6
6
  var chunkTWZNOUM7_cjs = require('./chunk-TWZNOUM7.cjs');
7
7
  var pixi_js = require('pixi.js');
8
8
  var mediabunny = require('mediabunny');
@@ -392,6 +392,7 @@ function findSilences(channels, sampleRate, opts = {}) {
392
392
  }
393
393
 
394
394
  // src/core/inspectAudio.ts
395
+ var BURIED_DB = 12;
395
396
  var INAUDIBLE_DB = -50;
396
397
  var db = (x) => Math.round(10 * Math.max(-120, 20 * Math.log10(x))) / 10;
397
398
  var mono = (chans) => {
@@ -477,6 +478,7 @@ function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
477
478
  return measure(Array.from({ length: b.numberOfChannels }, (_, c) => b.getChannelData(c)), b.sampleRate);
478
479
  };
479
480
  const scoreNotes = [];
481
+ const buried = [];
480
482
  const list = [...sources].sort((a, b) => a.start - b.start).map((s) => {
481
483
  const layer = s.layer ?? "an audio layer";
482
484
  const source = s.source ?? "audio";
@@ -504,8 +506,22 @@ function analyzeAudio(mix, sources, stats, movieDuration, opts = {}) {
504
506
  const over = s.music.seconds - plays;
505
507
  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
508
  }
507
- return { layer, source, start: s.start, end: s.end, peakDb, sound, ...s.music ? { music: s.music } : {} };
509
+ let belowMixDb;
510
+ if (!clock && !s.sourceMap && s.start < movieDuration && ownDb > INAUDIBLE_DB) {
511
+ const at = s.start + sound.loudestAt;
512
+ const around = db(peakIn(Math.max(0, at - 0.05), Math.min(movieDuration, at + 0.05)));
513
+ belowMixDb = Math.max(0, Math.round((around - ownDb) * 10) / 10);
514
+ if (!s.music && source.startsWith("sfx") && sound.length <= 4 && belowMixDb >= BURIED_DB) {
515
+ buried.push({ layer, source, ownDb, topVolume, around, belowMixDb, at: Math.round(at * 100) / 100 });
516
+ }
517
+ }
518
+ return { layer, source, start: s.start, end: s.end, peakDb, sound, ...belowMixDb !== void 0 ? { belowMixDb } : {}, ...s.music ? { music: s.music } : {} };
508
519
  });
520
+ if (buried.length > 0) {
521
+ const worst = buried.reduce((a, b) => b.belowMixDb > a.belowMixDb ? b : a);
522
+ const names = new Set(buried.map((x) => x.layer + x.source));
523
+ scoreNotes.push(`${names.size === 1 ? "1 sound effect is" : `${names.size} sound effects are`} buried (${BURIED_DB} dB or more under the loudest sample of the mix around its loudest moment${buried.length > names.size ? `, ${buried.length} times in all` : ""}): the worst is ${worst.layer} (${worst.source}), its own peak ${worst.ownDb} dBFS at volume ${worst.topVolume} 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`);
524
+ }
509
525
  const whole = measureLoudness(chans, sr);
510
526
  const silences = findSilences(chans, sr, { minLength: 1 });
511
527
  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`);
@@ -1490,8 +1506,8 @@ function expandTransitions(spec) {
1490
1506
  }
1491
1507
  const transitions = out.transitions;
1492
1508
  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;
1509
+ const remap = out.duration !== void 0 ? chunkKJSEQVBV_cjs.timeRemapOf(out) : null;
1510
+ const parentDuration = remap ? chunkKJSEQVBV_cjs.contentLength(remap, out.duration) : out.duration ?? Infinity;
1495
1511
  const compW = out.width;
1496
1512
  const compH = out.height;
1497
1513
  const threeDByName = new Set(sequences.filter((s) => s.name && s.threeD).map((s) => s.name));
@@ -1559,16 +1575,16 @@ function expandTransitions(spec) {
1559
1575
  if (toUses > 1) {
1560
1576
  throw new Error(`pixi-effects: ${tag} sequence "${t.to}" is used as \`to\` in more than one transition`);
1561
1577
  }
1562
- const tStart = chunkLEECUCTG_cjs.resolveAt(t.at, parentDuration);
1578
+ const tStart = chunkKJSEQVBV_cjs.resolveAt(t.at, parentDuration);
1563
1579
  const tEnd = tStart + t.duration;
1564
1580
  const fromSeq = fromEntry.seq;
1565
- const fromStart = chunkLEECUCTG_cjs.resolveAt(fromSeq.at, parentDuration);
1581
+ const fromStart = chunkKJSEQVBV_cjs.resolveAt(fromSeq.at, parentDuration);
1566
1582
  const fromEnd = fromStart + (fromSeq.duration ?? parentDuration);
1567
1583
  if (tStart < fromStart - EPS || tEnd > fromEnd + EPS) {
1568
1584
  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
1585
  }
1570
1586
  const toSeq = toEntry.seq;
1571
- const toStart = chunkLEECUCTG_cjs.resolveAt(toSeq.at, parentDuration);
1587
+ const toStart = chunkKJSEQVBV_cjs.resolveAt(toSeq.at, parentDuration);
1572
1588
  const toEnd = toStart + (toSeq.duration ?? parentDuration);
1573
1589
  if (tStart < toStart - EPS || tEnd > toEnd + EPS) {
1574
1590
  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 +1644,7 @@ function wrapAsFullComposition(seq, compW, compH) {
1628
1644
  delete inner.at;
1629
1645
  delete inner.duration;
1630
1646
  const m = inner.mask;
1631
- const carriesMatte = m !== void 0 && chunk5IJENZOG_cjs.maskSourceOf(m).kind === "refs";
1647
+ const carriesMatte = m !== void 0 && chunkY5UWFH6Y_cjs.maskSourceOf(m).kind === "refs";
1632
1648
  if (carriesMatte) delete inner.mask;
1633
1649
  return {
1634
1650
  ...carriesMatte ? { mask: m } : {},
@@ -1884,6 +1900,20 @@ function rejectReservedNameCollision(filters, seqName) {
1884
1900
  }
1885
1901
 
1886
1902
  // src/core/inspect.ts
1903
+ function chainOf(node) {
1904
+ const chain = [];
1905
+ for (let n = node; n && n.parent; n = n.parent) {
1906
+ const i = n.parent.children?.indexOf(n);
1907
+ if (i === void 0 || i < 0) return null;
1908
+ chain.unshift(i);
1909
+ }
1910
+ return chain.length ? chain : null;
1911
+ }
1912
+ function drawnAfter(a, b) {
1913
+ if (!a || !b) return null;
1914
+ for (let i = 0; i < Math.min(a.length, b.length); i++) if (a[i] !== b[i]) return a[i] > b[i];
1915
+ return null;
1916
+ }
1887
1917
  var FAINT = 0.3;
1888
1918
  var intersection = (a, b) => {
1889
1919
  const w = Math.min(a.x + a.width, b.x + b.width) - Math.max(a.x, b.x);
@@ -1907,8 +1937,8 @@ function motionOf(keyframes, local, lives, keys = (k) => GEOMETRY.has(k)) {
1907
1937
  let length = 0;
1908
1938
  if (typeof kf.duration === "number") length = kf.duration;
1909
1939
  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;
1940
+ const sp = chunkKJSEQVBV_cjs.parseSpring(kf.ease);
1941
+ length = sp ? Math.round(chunkKJSEQVBV_cjs.springDuration(sp) * 1e3) / 1e3 : 0.5;
1912
1942
  }
1913
1943
  if (!(length > 0)) continue;
1914
1944
  const kat = kf.at ?? 0;
@@ -1927,7 +1957,11 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1927
1957
  const scene = /* @__PURE__ */ new Map();
1928
1958
  const scaledToNothing = /* @__PURE__ */ new Set();
1929
1959
  const hasMotion = /* @__PURE__ */ new Set();
1930
- const walk2 = (comp, prefix, insideThreeD, parentVisible, parentScene, parentMotion = NO_MOTION, parentZero = false) => {
1960
+ const kinds = /* @__PURE__ */ new Map();
1961
+ const specs = /* @__PURE__ */ new Map();
1962
+ const chains = /* @__PURE__ */ new Map();
1963
+ const chainAlpha = /* @__PURE__ */ new Map();
1964
+ const walk2 = (comp, prefix, insideThreeD, parentVisible, parentScene, parentMotion = NO_MOTION, parentZero = false, parentAlpha = 1) => {
1931
1965
  const blending = /* @__PURE__ */ new Set();
1932
1966
  const now = comp.childTime(time);
1933
1967
  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 +2005,14 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1971
2005
  if (threeD) info.depthBlur = Number(depthBlur.toFixed(2));
1972
2006
  if (threeD && light !== void 0) info.light = Number(light.toFixed(2));
1973
2007
  layers.push(info);
2008
+ chainAlpha.set(info, parentAlpha * alpha);
2009
+ chains.set(info, chainOf(display));
2010
+ specs.set(info, seq.spec);
2011
+ kinds.set(info, seq.spec.type === "shape" ? String(seq.spec.shape) : seq.spec.type);
1974
2012
  if (motion.ever) hasMotion.add(info);
1975
2013
  let shown = bounds ? clip(bounds, view) : null;
1976
2014
  const maskTarget = seq.maskSequence?.target;
1977
- const maskSrc = chunk5IJENZOG_cjs.maskSourceOf(seq.spec.mask);
2015
+ const maskSrc = chunkY5UWFH6Y_cjs.maskSourceOf(seq.spec.mask);
1978
2016
  if (shown && maskTarget && !(maskSrc.kind === "inline" && maskSrc.invert)) {
1979
2017
  const m = maskTarget.getBounds();
1980
2018
  shown = clip(shown, { x: m.x, y: m.y, width: m.width, height: m.height });
@@ -1983,7 +2021,7 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
1983
2021
  const group = parentScene ?? (name !== void 0 && blending.has(name) ? name : void 0);
1984
2022
  if (group) scene.set(info, group);
1985
2023
  if (zero || seq.showsNothingYet) scaledToNothing.add(info);
1986
- if (seq instanceof chunk5IJENZOG_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, motion, zero);
2024
+ if (seq instanceof chunkY5UWFH6Y_cjs.CompositionSequence) walk2(seq, prefix + label + "/", insideThreeD || threeD, visible, group, motion, zero, parentAlpha * alpha);
1987
2025
  });
1988
2026
  };
1989
2027
  walk2(root, "", false, true);
@@ -2019,6 +2057,42 @@ function inspectScene(root, frame, time, canvas, opts = {}) {
2019
2057
  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
2058
  }
2021
2059
  }
2060
+ const FILLED = /* @__PURE__ */ new Set(["rect", "circle", "ellipse", "image", "video"]);
2061
+ for (const t of texts) {
2062
+ const tb = seen.get(t);
2063
+ if (!tb) continue;
2064
+ const area = tb.width * tb.height;
2065
+ if (!(area > 0)) continue;
2066
+ for (const o of layers) {
2067
+ if (o === t || o.type === "text" || !o.visible || !o.bounds) continue;
2068
+ if (o.path.startsWith(t.path + "/") || t.path.startsWith(o.path + "/")) continue;
2069
+ const kind = kinds.get(o) ?? o.type, spec = specs.get(o) ?? {};
2070
+ const initial = spec.initial ?? {};
2071
+ const hairline = (kind === "line" || kind === "rect") && Math.min(o.bounds.width, o.bounds.height) < 12 && Math.max(o.bounds.width, o.bounds.height) >= 40;
2072
+ const soft = spec.blendMode !== void 0 && spec.blendMode !== "normal" || Array.isArray(spec.filters) && spec.filters.length > 0;
2073
+ const filled = kind === "image" || kind === "video" || initial.fillColor !== void 0 || spec.fillGradient !== void 0 || initial.fillGradient !== void 0;
2074
+ if (o.threeD || t.threeD) continue;
2075
+ const over = drawnAfter(chains.get(o) ?? null, chains.get(t) ?? null) ?? layers.indexOf(o) > layers.indexOf(t);
2076
+ const opacity = chainAlpha.get(o) ?? o.alpha;
2077
+ const hides = FILLED.has(kind) && !hairline && filled && !soft && over && opacity >= 0.8;
2078
+ const cuts = hairline && !soft && over && opacity >= FAINT;
2079
+ if (!hides && !cuts) continue;
2080
+ const st = scene.get(t), so = scene.get(o);
2081
+ if (st && so && st !== so) continue;
2082
+ const ob = seen.get(o);
2083
+ if (!ob) continue;
2084
+ const covered = intersection(tb, ob) / area;
2085
+ if (cuts) {
2086
+ const horizontal = ob.width >= ob.height;
2087
+ const mid = horizontal ? ob.y + ob.height / 2 : ob.x + ob.width / 2;
2088
+ 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;
2089
+ 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);
2090
+ if (mid < lo || mid > hi || along < 0.3 * (horizontal ? tb.width : tb.height)) continue;
2091
+ } else if (covered < 0.1) continue;
2092
+ const word = kind === "image" || kind === "video" ? kind : "shape";
2093
+ 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)`);
2094
+ }
2095
+ }
2022
2096
  const mode = opts.layers ?? "all";
2023
2097
  const listed = mode === "none" ? [] : mode === "visible" ? layers.filter((l) => l.visible) : layers;
2024
2098
  return { frame, time, canvas, summary: { layers: layers.length, visible: layers.filter((l) => l.visible).length }, issues, layers: listed };
@@ -2031,7 +2105,7 @@ var round = (n) => Math.round(n * 1e6) / 1e6;
2031
2105
  var family = (name) => name.replace(/\d+/g, "#");
2032
2106
  function detailOf(seq) {
2033
2107
  const s = seq.spec;
2034
- const spans = chunkLEECUCTG_cjs.spansMeta.get(seq.spec)?.preview;
2108
+ const spans = chunkKJSEQVBV_cjs.spansMeta.get(seq.spec)?.preview;
2035
2109
  if (spans) return spans.length > 60 ? spans.slice(0, 57) + "\u2026" : spans;
2036
2110
  if (typeof s.text === "string") return s.text.length > 60 ? s.text.slice(0, 57) + "\u2026" : s.text;
2037
2111
  if (typeof s.asset === "string") return `asset \u201C${s.asset}\u201D`;
@@ -2047,10 +2121,10 @@ function walk(comp, parentStart, parentEnd, depth, prefix, rows, transitions, lo
2047
2121
  const start = parentStart + (seq.at ?? 0);
2048
2122
  const end = Math.min(parentEnd, seq.duration !== void 0 ? start + seq.duration : parentEnd);
2049
2123
  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);
2124
+ const keys = (seq.spec.keyframes ?? []).map((kf) => round(start + chunkKJSEQVBV_cjs.resolveAt(kf.at, span))).sort((a, b) => a - b).slice(0, MAX_KEYS);
2051
2125
  const row = { path: prefix + name, name, type: seq.spec.type, depth, start: round(start), end: round(end), keys, detail: detailOf(seq), ...local ? { local } : {} };
2052
2126
  const inner = [];
2053
- const isComp = seq instanceof chunk5IJENZOG_cjs.CompositionSequence;
2127
+ const isComp = seq instanceof chunkY5UWFH6Y_cjs.CompositionSequence;
2054
2128
  if (isComp) {
2055
2129
  const label = seq.remapLabel();
2056
2130
  walk(seq, label ? 0 : start, label ? seq.contentSpan : end, depth + 1, prefix + name + "/", inner, transitions, label ? `${name} ${label}` : local);
@@ -2209,7 +2283,7 @@ function sheetLayout(n, columns, cellW, srcW, srcH, labelH) {
2209
2283
  }
2210
2284
 
2211
2285
  // src/core/vars.ts
2212
- var RESERVED_NAMES = ["w", "h", "W", "H", "GW", "GH", "t", "d", "T", "contain", "cover", "value", ...chunkLEECUCTG_cjs.EXPRESSION_NAMES];
2286
+ var RESERVED_NAMES = ["w", "h", "W", "H", "GW", "GH", "t", "d", "T", "contain", "cover", "value", ...chunkKJSEQVBV_cjs.EXPRESSION_NAMES];
2213
2287
  var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
2214
2288
  var isPlain = (v) => !!v && typeof v === "object" && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
2215
2289
  function normalizeVars(raw, where, warn = (m) => console.warn(m)) {
@@ -2645,6 +2719,10 @@ var Movie = class {
2645
2719
  _silent = 0;
2646
2720
  /** render() / peek() in flight: a rebuild does not swap the composition under them. */
2647
2721
  _busy = 0;
2722
+ /** > 0 while the movie is moving its scene to another moment (a seek, a motion-blurred frame, the poster): a thumbnail does not start then. */
2723
+ _moving = 0;
2724
+ /** The thumbnail being drawn, if any: a seek waits for it (it has the scene on another moment for a few microtasks). */
2725
+ _thumbRun = null;
2648
2726
  _idleWaiters = [];
2649
2727
  /** What went into the mix, and its peak before limiting — kept for inspectAudio(). */
2650
2728
  _audioSources = [];
@@ -2692,6 +2770,7 @@ var Movie = class {
2692
2770
  if (this._posterFrame === null || !this.timeline) return;
2693
2771
  const back = this.currentFrame;
2694
2772
  this.currentFrame = this._posterFrame;
2773
+ this._moving++;
2695
2774
  try {
2696
2775
  this.timeline.time(this._timeOf(this._posterFrame));
2697
2776
  await this._awaitVideoFrames();
@@ -2699,6 +2778,7 @@ var Movie = class {
2699
2778
  this._renderNow();
2700
2779
  } finally {
2701
2780
  this.currentFrame = back;
2781
+ this._moving--;
2702
2782
  }
2703
2783
  this._atPoster = true;
2704
2784
  }
@@ -2742,8 +2822,8 @@ var Movie = class {
2742
2822
  this.stops = normalizeStops(applyVars(options.composition, this._vars)?.stops, this.duration, this.frameRate);
2743
2823
  this._posterFrame = poster.frame;
2744
2824
  this.background = options.background ?? "#000000";
2745
- this.timeline = chunkLEECUCTG_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
2746
- chunkLEECUCTG_cjs.lengthen(this.timeline, this.duration);
2825
+ this.timeline = chunkKJSEQVBV_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
2826
+ chunkKJSEQVBV_cjs.lengthen(this.timeline, this.duration);
2747
2827
  const baseAppOptions = {
2748
2828
  width: this.width,
2749
2829
  height: this.height,
@@ -2837,8 +2917,8 @@ var Movie = class {
2837
2917
  height: sizeWins ? height : spec.height ?? height,
2838
2918
  duration: spec.duration ?? this.duration
2839
2919
  } : void 0;
2840
- await chunkLEECUCTG_cjs.ensureFilterLibrary(seededComposition);
2841
- if (chunkIMQJVRMT_cjs.usesAdvancedBlend(seededComposition)) await chunkIMQJVRMT_cjs.enableAdvancedBlend(this.app.renderer);
2920
+ await chunkKJSEQVBV_cjs.ensureFilterLibrary(seededComposition);
2921
+ if (chunkNEUPEENI_cjs.usesAdvancedBlend(seededComposition)) await chunkNEUPEENI_cjs.enableAdvancedBlend(this.app.renderer);
2842
2922
  const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
2843
2923
  const rootSeqSpec = {
2844
2924
  type: "composition",
@@ -2848,7 +2928,7 @@ var Movie = class {
2848
2928
  ...userComposition
2849
2929
  };
2850
2930
  if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
2851
- const sequence = new chunk5IJENZOG_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
2931
+ const sequence = new chunkY5UWFH6Y_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
2852
2932
  lp?.begin("build", countLayers(rootSeqSpec));
2853
2933
  try {
2854
2934
  await sequence.build();
@@ -2984,8 +3064,8 @@ var Movie = class {
2984
3064
  if (!changed) return { reason, ms: 0, audio: "none", frame: this.currentFrame, playing: this.isPlaying, unchanged: true };
2985
3065
  sequence = await this._construct(composition, vars, width, height, void 0, this._resized || sizeChanged);
2986
3066
  alive();
2987
- const timeline = chunkLEECUCTG_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
2988
- chunkLEECUCTG_cjs.lengthen(timeline, this.duration);
3067
+ const timeline = chunkKJSEQVBV_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
3068
+ chunkKJSEQVBV_cjs.lengthen(timeline, this.duration);
2989
3069
  sequence.bindTimeline(timeline);
2990
3070
  const hadSound = this.audioBuffer !== null;
2991
3071
  const audio = await this._mixSound(sequence, this._ensureAudioContext());
@@ -3085,12 +3165,18 @@ var Movie = class {
3085
3165
  this.emit("error", { where, message: error instanceof Error ? error.message : String(error), error });
3086
3166
  }
3087
3167
  async _goto(frame) {
3088
- this._atPoster = false;
3089
- this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames));
3090
- this.timeline.time(this._timeOf(this.currentFrame));
3091
- await this._awaitVideoFrames();
3092
- this._updateSpace();
3093
- this._renderNow();
3168
+ if (this._thumbRun) await this._thumbRun;
3169
+ this._moving++;
3170
+ try {
3171
+ this._atPoster = false;
3172
+ this.currentFrame = Math.max(0, Math.min(frame, this.totalFrames));
3173
+ this.timeline.time(this._timeOf(this.currentFrame));
3174
+ await this._awaitVideoFrames();
3175
+ this._updateSpace();
3176
+ this._renderNow();
3177
+ } finally {
3178
+ this._moving--;
3179
+ }
3094
3180
  this.emit("frame", { frame: this.currentFrame, totalFrames: this.totalFrames });
3095
3181
  }
3096
3182
  /**
@@ -3138,6 +3224,69 @@ var Movie = class {
3138
3224
  if (o.as === "imageData") return out.getContext("2d").getImageData(0, 0, out.width, out.height);
3139
3225
  return encodeCanvas(out, o.type, o.as, o.quality);
3140
3226
  }
3227
+ /**
3228
+ * Why `thumbnail()` is off for this movie: `'variables'` (it declares `vars`: the picture depends on them and changes when they do) or `'video'` (a video
3229
+ * layer: its frame must be waited for, which would seek the playing video). `null`: it works.
3230
+ */
3231
+ get thumbnailsOff() {
3232
+ if (this._declared.size > 0) return "variables";
3233
+ if (!this._rootSequence) return null;
3234
+ const found = [];
3235
+ collectVideoSequences(this._rootSequence, found);
3236
+ return found.some((v) => v.spec?.type === "video") ? "video" : null;
3237
+ }
3238
+ /**
3239
+ * A small picture of any frame, drawn WITHOUT moving the playhead or changing what is on the canvas, so it can be made while the movie plays or is
3240
+ * paused (a seek-bar preview). The scene is put on that moment, drawn, copied, and put back, all within the microtasks of one task: the browser never
3241
+ * shows the in-between. Gives `null` when it cannot be made right now: the movie has thumbnails off (see `thumbnailsOff`), is moving somewhere else
3242
+ * (a seek, a render, a peek), or is another thumbnail's turn. It is the picture `snapshot()` gives at that size (without motion blur).
3243
+ */
3244
+ async thumbnail(frame, opts = {}) {
3245
+ chunkTWZNOUM7_cjs.warnUnknownOptions("movie.thumbnail()", opts, ["width", "scale"]);
3246
+ this._requireReady("thumbnail");
3247
+ if (opts.width !== void 0 && !(opts.width > 0 && opts.width <= this.width * 4)) throw new Error(`pixi-effects: movie.thumbnail(): width must be above 0 and at most ${this.width * 4} px, got ${opts.width}`);
3248
+ if (opts.scale !== void 0 && !(opts.scale > 0 && opts.scale <= 4)) throw new Error(`pixi-effects: movie.thumbnail(): scale must be above 0 and at most 4, got ${opts.scale}`);
3249
+ if (this.thumbnailsOff || this._moving > 0 || this._busy > 0 || this._thumbRun) return null;
3250
+ const w = Math.max(1, Math.round(opts.width ?? (opts.scale !== void 0 ? this.width * opts.scale : 160)));
3251
+ const h = Math.max(1, Math.round(w * this.height / this.width));
3252
+ let done;
3253
+ this._thumbRun = new Promise((r) => {
3254
+ done = r;
3255
+ });
3256
+ try {
3257
+ return await this._drawThumbnail(clampFrame(frame, this.totalFrames), w, h);
3258
+ } finally {
3259
+ this._thumbRun = null;
3260
+ done();
3261
+ }
3262
+ }
3263
+ async _drawThumbnail(frame, w, h) {
3264
+ const stage = this.app.canvas;
3265
+ const out = document.createElement("canvas");
3266
+ out.width = w;
3267
+ out.height = h;
3268
+ const keep = this.timeline.time();
3269
+ const t = this._timeOf(frame);
3270
+ this._nominalTime = t;
3271
+ try {
3272
+ this.timeline.time(t);
3273
+ await this._awaitVideoFrames(t);
3274
+ this._updateSpace(t);
3275
+ this._renderNow();
3276
+ out.getContext("2d").drawImage(stage, 0, 0, w, h);
3277
+ } finally {
3278
+ this._nominalTime = keep;
3279
+ try {
3280
+ this.timeline.time(keep);
3281
+ await this._awaitVideoFrames(keep);
3282
+ this._updateSpace(keep);
3283
+ this._renderNow();
3284
+ } finally {
3285
+ this._nominalTime = null;
3286
+ }
3287
+ }
3288
+ return out;
3289
+ }
3141
3290
  async onionSkin(opts = {}) {
3142
3291
  chunkTWZNOUM7_cjs.warnUnknownOptions("movie.onionSkin()", opts, ["from", "to", "count", "scale", "as", "motionBlur"]);
3143
3292
  this._requireReady("onionSkin");
@@ -3316,9 +3465,11 @@ var Movie = class {
3316
3465
  async _exposeFrame(frame, mb, target, settle) {
3317
3466
  const g = target.getContext("2d");
3318
3467
  const stage = this.app.canvas;
3468
+ if (this._thumbRun) await this._thumbRun;
3319
3469
  this._atPoster = false;
3320
3470
  const times = blurTimes(frame, this.frameRate, mb, Math.max(0, this.duration - END_MARGIN));
3321
3471
  this._nominalTime = this._timeOf(Math.max(0, Math.min(frame, this.totalFrames)));
3472
+ this._moving++;
3322
3473
  try {
3323
3474
  for (let k = 0; k < times.length; k++) {
3324
3475
  const t = times[k];
@@ -3332,6 +3483,7 @@ var Movie = class {
3332
3483
  }
3333
3484
  } finally {
3334
3485
  this._nominalTime = null;
3486
+ this._moving--;
3335
3487
  }
3336
3488
  g.globalAlpha = 1;
3337
3489
  if (settle) await this._goto(frame);
@@ -3627,7 +3779,7 @@ function countLayers(spec) {
3627
3779
  n += 1 + countLayers(s);
3628
3780
  const spans = s.spans;
3629
3781
  if (Array.isArray(spans)) n += spans.length;
3630
- const src = chunk5IJENZOG_cjs.maskSourceOf(mask);
3782
+ const src = chunkY5UWFH6Y_cjs.maskSourceOf(mask);
3631
3783
  if (src.kind === "inline") n += 1 + countLayers(src.spec);
3632
3784
  }
3633
3785
  return n;
@@ -3640,7 +3792,7 @@ function measureText(text, style = {}) {
3640
3792
  return { width: m.width, height: m.height, lines: text.split("\n").length };
3641
3793
  }
3642
3794
  function splitText(text, style = {}, opts = {}) {
3643
- chunkTWZNOUM7_cjs.warnUnknownOptions("splitText()", opts, ["by", "x", "y", "align"]);
3795
+ chunkTWZNOUM7_cjs.warnUnknownOptions("splitText()", opts, ["by", "x", "y", "align", "vertical"]);
3644
3796
  let by = opts.by ?? "chars";
3645
3797
  if (!BY.includes(by)) {
3646
3798
  const g = chunkTWZNOUM7_cjs.suggestName(String(by), BY);
@@ -3659,6 +3811,7 @@ function splitText(text, style = {}, opts = {}) {
3659
3811
  }
3660
3812
  if (text === "") return [];
3661
3813
  const ts = new pixi_js.TextStyle(style);
3814
+ if (opts.vertical) return splitVertical(text, style, ts, x0, y0, align, by);
3662
3815
  const lineHeight = pixi_js.CanvasTextMetrics.measureText("M", ts).lineHeight ?? pixi_js.CanvasTextMetrics.measureText("M", ts).height;
3663
3816
  const sentinel = widthOf("|", ts);
3664
3817
  const out = [];
@@ -3682,11 +3835,41 @@ function splitText(text, style = {}, opts = {}) {
3682
3835
  });
3683
3836
  return out;
3684
3837
  }
3838
+ var PUNCT = new Set("\u3001\u3002\uFF0C\uFF0E\uFF64\uFF61");
3839
+ 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");
3840
+ 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");
3841
+ function splitVertical(text, style, ts, right, top, align, by) {
3842
+ if (by !== "chars") console.warn(`pixi-effects: splitText(): vertical text has only a "chars" form (by "${by}" has no vertical layout); using "chars"`);
3843
+ const em = typeof style.fontSize === "number" ? style.fontSize : Number(ts.fontSize) || 26;
3844
+ const spacing = typeof style.letterSpacing === "number" ? style.letterSpacing : 0;
3845
+ const cell = em + spacing;
3846
+ const lineHeight = pixi_js.CanvasTextMetrics.measureText("M", ts).lineHeight ?? pixi_js.CanvasTextMetrics.measureText("M", ts).height;
3847
+ const out = [];
3848
+ text.split("\n").forEach((line, lineNo) => {
3849
+ const chars = Array.from(line);
3850
+ const start = align === "center" ? top - chars.length * cell / 2 : align === "right" ? top - chars.length * cell : top;
3851
+ const column = right - lineHeight / 2 - lineNo * lineHeight;
3852
+ chars.forEach((ch, i) => {
3853
+ if (/\s/.test(ch)) return;
3854
+ let cx = column, cy = start + i * cell + cell / 2;
3855
+ if (PUNCT.has(ch)) {
3856
+ cx += 0.5 * em;
3857
+ cy -= 0.5 * em;
3858
+ } else if (SMALL.has(ch)) {
3859
+ cx += 0.1 * em;
3860
+ cy -= 0.1 * em;
3861
+ }
3862
+ const width = widthOf(ch, ts);
3863
+ 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 });
3864
+ });
3865
+ });
3866
+ return out;
3867
+ }
3685
3868
 
3686
3869
  // src/presets/_ease.ts
3687
3870
  function parseEase2(name) {
3688
- chunkLEECUCTG_cjs.checkEase(name, "a preset");
3689
- return chunkLEECUCTG_cjs.parseEase(name);
3871
+ chunkKJSEQVBV_cjs.checkEase(name, "a preset");
3872
+ return chunkKJSEQVBV_cjs.parseEase(name);
3690
3873
  }
3691
3874
 
3692
3875
  // src/presets/kenBurns.ts
@@ -3821,17 +4004,17 @@ function orbit(opts) {
3821
4004
  throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
3822
4005
  }
3823
4006
  const [cx, cy] = opts.center ?? [width / 2, height / 2];
3824
- const fixedR = opts.radius ?? chunkLEECUCTG_cjs.homeDistance(height, fov ?? chunkLEECUCTG_cjs.DEFAULT_FOV);
4007
+ const fixedR = opts.radius ?? chunkKJSEQVBV_cjs.homeDistance(height, fov ?? chunkKJSEQVBV_cjs.DEFAULT_FOV);
3825
4008
  const start = opts.start ?? -degrees / 2;
3826
4009
  const steps = Math.max(1, Math.round(duration * stepsPerSecond));
3827
4010
  const easeFn = parseEase2(ease);
3828
4011
  const dollyEase = dolly ? parseEase2(dolly.ease ?? ease) : null;
3829
4012
  const pos = (i) => {
3830
4013
  const p = i / steps;
3831
- const a = (start + degrees * easeFn(p)) * chunkLEECUCTG_cjs.DEG;
4014
+ const a = (start + degrees * easeFn(p)) * chunkKJSEQVBV_cjs.DEG;
3832
4015
  if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
3833
4016
  const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
3834
- const R = chunkLEECUCTG_cjs.homeDistance(height, f);
4017
+ const R = chunkKJSEQVBV_cjs.homeDistance(height, f);
3835
4018
  return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
3836
4019
  };
3837
4020
  const keyframes = [];
@@ -3886,7 +4069,7 @@ function particles(opts) {
3886
4069
  const round2 = (v) => Math.round(v * 1e6) / 1e6;
3887
4070
  const out = [];
3888
4071
  for (let i = 0; i < count; i++) {
3889
- const r = chunkLEECUCTG_cjs.random(seed * 1000003 + i * 7919 + 17);
4072
+ const r = chunkKJSEQVBV_cjs.random(seed * 1000003 + i * 7919 + 17);
3890
4073
  const pick = ([a2, b]) => a2 + r() * (b - a2);
3891
4074
  const born = at + r() * emit;
3892
4075
  const lifetime = pick(life);
@@ -3899,7 +4082,7 @@ function particles(opts) {
3899
4082
  const turn = spin ? pick(spin) : 0;
3900
4083
  const startAngle = spin ? r() * 360 : 0;
3901
4084
  const swayAmp = sway?.amp ?? 0, swayFreq = sway?.freq ?? 1;
3902
- const n0 = sway ? chunkLEECUCTG_cjs.noise(0, seed * 1000003 + i) : 0;
4085
+ const n0 = sway ? chunkKJSEQVBV_cjs.noise(0, seed * 1000003 + i) : 0;
3903
4086
  const at_ = (t) => {
3904
4087
  const axis = (p0, v0, acc) => {
3905
4088
  if (drag > 0) {
@@ -3908,7 +4091,7 @@ function particles(opts) {
3908
4091
  }
3909
4092
  return p0 + v0 * t + 0.5 * acc * t * t;
3910
4093
  };
3911
- const sw = sway ? swayAmp * (chunkLEECUCTG_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
4094
+ const sw = sway ? swayAmp * (chunkKJSEQVBV_cjs.noise(t * swayFreq, seed * 1000003 + i) - n0) : 0;
3912
4095
  return { x: axis(x0, vx, wind) + sw, y: axis(y0, vy, gravity) };
3913
4096
  };
3914
4097
  const kfs = [];
@@ -3970,7 +4153,7 @@ function followPath(opts) {
3970
4153
  if (steps > MAX_STEPS) throw new Error(`followPath(): ${steps} samples is too many (max ${MAX_STEPS}); lower frameRate or duration`);
3971
4154
  const segments = [];
3972
4155
  let total = 0;
3973
- for (const line of chunkLEECUCTG_cjs.flattenSvgPath(d)) {
4156
+ for (const line of chunkKJSEQVBV_cjs.flattenSvgPath(d)) {
3974
4157
  const p = line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
3975
4158
  for (let i = 2; i < p.length; i += 2) {
3976
4159
  const len = Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
@@ -4124,7 +4307,7 @@ function distances(n, from, grid, seed) {
4124
4307
  if (n === 0) return [];
4125
4308
  if (from === "random") {
4126
4309
  const order = Array.from({ length: n }, (_, i) => i);
4127
- const r = chunkLEECUCTG_cjs.random(seed);
4310
+ const r = chunkKJSEQVBV_cjs.random(seed);
4128
4311
  for (let i = n - 1; i > 0; i--) {
4129
4312
  const j = Math.floor(r() * (i + 1));
4130
4313
  [order[i], order[j]] = [order[j], order[i]];
@@ -4186,7 +4369,7 @@ function wiggle(opts) {
4186
4369
  to[n] = p.around;
4187
4370
  return;
4188
4371
  }
4189
- const off = (chunkLEECUCTG_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
4372
+ const off = (chunkKJSEQVBV_cjs.rand(seed * 7919 + pi * 104729 + s * 31 + 1) * 2 - 1) * p.amp;
4190
4373
  to[n] = typeof p.around === "number" ? p.around + off : `(${p.around}) ${off < 0 ? "-" : "+"} ${Math.abs(off)}`;
4191
4374
  });
4192
4375
  out.push({ at: at + s * stepLen, to, duration: stepLen, ease });
@@ -4197,7 +4380,7 @@ function wiggle(opts) {
4197
4380
  // src/presets/animateText.ts
4198
4381
  var TWEEN_KEYS = ["preset", "from", "to", "duration", "ease"];
4199
4382
  var TIME_KEYS = ["at", "delay", "start", "begin", "time"];
4200
- var KEYS5 = ["by", "x", "y", "align", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
4383
+ var KEYS5 = ["by", "x", "y", "align", "vertical", "name", "at", "duration", "in", "out", "stagger", "idle", "styleFor"];
4201
4384
  var PRESETS = {
4202
4385
  rise: { from: { y: 40, alpha: 0 }, duration: 0.5, ease: "back.out(2)" },
4203
4386
  drop: { from: { y: -50, alpha: 0 }, duration: 0.7, ease: "bounce.out" },
@@ -4210,14 +4393,14 @@ var PRESETS = {
4210
4393
  var PRESET_NAMES = Object.keys(PRESETS);
4211
4394
  function animateText(text, style, options) {
4212
4395
  chunkTWZNOUM7_cjs.warnUnknownOptions("animateText()", options, KEYS5);
4213
- const { by = "chars", x = 0, y = 0, align = "left", name = "text", at = 0, duration, idle, styleFor } = options;
4396
+ const { by = "chars", x = 0, y = 0, align = "left", vertical = false, name = "text", at = 0, duration, idle, styleFor } = options;
4214
4397
  for (const [n, v] of [["x", x], ["y", y]]) {
4215
4398
  if (typeof v !== "number" || !Number.isFinite(v)) {
4216
4399
  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.`);
4217
4400
  }
4218
4401
  }
4219
4402
  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}`);
4220
- const pieces = splitText(text, style, { by, x, y, align });
4403
+ const pieces = splitText(text, style, { by, x, y, align, ...vertical ? { vertical } : {} });
4221
4404
  if (pieces.length === 0) return [];
4222
4405
  const lineHeight = measureText("M", style).height;
4223
4406
  const inTween = options.in === false ? null : resolveTween(options.in ?? "rise", "in");
@@ -4236,12 +4419,12 @@ function animateText(text, style, options) {
4236
4419
  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)}`);
4237
4420
  }
4238
4421
  return pieces.map((p, i) => {
4239
- const cx = p.x + p.width / 2, cy = p.y + lineHeight / 2;
4240
- const rest = { x: cx, y: cy, alpha: 1, scale: 1, scaleX: 1, scaleY: 1, rotation: 0 };
4422
+ const cx = p.cx ?? p.x + p.width / 2, cy = p.cy ?? p.y + lineHeight / 2;
4423
+ const rest = { x: cx, y: cy, alpha: 1, scale: 1, scaleX: 1, scaleY: 1, rotation: p.rotation ?? 0 };
4241
4424
  const start = at + delays[i];
4242
4425
  const len = end - start;
4243
4426
  const keyframes = [];
4244
- const place = (key, v) => key === "x" || key === "y" ? rest[key] + v : v;
4427
+ const place = (key, v) => key === "x" || key === "y" || key === "rotation" ? rest[key] + v : v;
4245
4428
  if (inTween) {
4246
4429
  keyframes.push({
4247
4430
  at: 0,
@@ -4280,7 +4463,7 @@ function animateText(text, style, options) {
4280
4463
  style: styleFor ? { ...style, ...styleFor(p) } : style,
4281
4464
  at: start,
4282
4465
  duration: len,
4283
- initial: { x: cx, y: cy, anchorX: 0.5, anchorY: 0.5 },
4466
+ initial: { x: cx, y: cy, anchorX: 0.5, anchorY: 0.5, ...rest.rotation !== 0 ? { rotation: rest.rotation } : {} },
4284
4467
  keyframes
4285
4468
  };
4286
4469
  });
@@ -4644,5 +4827,5 @@ exports.splitText = splitText;
4644
4827
  exports.stagger = stagger;
4645
4828
  exports.wiggle = wiggle;
4646
4829
  exports.withFade = withFade;
4647
- //# sourceMappingURL=chunk-FVZEBYLW.cjs.map
4648
- //# sourceMappingURL=chunk-FVZEBYLW.cjs.map
4830
+ //# sourceMappingURL=chunk-CC7SUR7Q.cjs.map
4831
+ //# sourceMappingURL=chunk-CC7SUR7Q.cjs.map