pixi-effects 0.35.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 (75) hide show
  1. package/CHANGELOG.md +18 -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.d.cts +2 -2
  13. package/dist/Controller.d.ts +2 -2
  14. package/dist/{Movie-VYtUN4TS.d.ts → Movie-Bh1HPdKw.d.cts} +6 -1
  15. package/dist/{Movie-Db4QnMIH.d.cts → Movie-D5WFfZdZ.d.ts} +6 -1
  16. package/dist/{Spans-QRMN2EZO.cjs → Spans-4IRBSOLI.cjs} +11 -11
  17. package/dist/{Spans-QRMN2EZO.cjs.map → Spans-4IRBSOLI.cjs.map} +1 -1
  18. package/dist/{Spans-QHHWC2PB.js → Spans-MWP2FX3T.js} +6 -6
  19. package/dist/{Spans-QHHWC2PB.js.map → Spans-MWP2FX3T.js.map} +1 -1
  20. package/dist/{blendModes-WIUCYIMC.cjs → blendModes-KU7XLV7U.cjs} +5 -5
  21. package/dist/{blendModes-WIUCYIMC.cjs.map → blendModes-KU7XLV7U.cjs.map} +1 -1
  22. package/dist/{blendModes-PP5NPBXY.js → blendModes-OOI3T6BE.js} +4 -4
  23. package/dist/{blendModes-PP5NPBXY.js.map → blendModes-OOI3T6BE.js.map} +1 -1
  24. package/dist/{chunk-ITDYFBB6.js → chunk-2JFFKHXO.js} +5 -5
  25. package/dist/chunk-2JFFKHXO.js.map +1 -0
  26. package/dist/{chunk-GL2AQFLQ.cjs → chunk-42ML5AKN.cjs} +5 -5
  27. package/dist/chunk-42ML5AKN.cjs.map +1 -0
  28. package/dist/{chunk-XV4L72FW.js → chunk-AZ6AINSW.js} +5 -5
  29. package/dist/{chunk-XV4L72FW.js.map → chunk-AZ6AINSW.js.map} +1 -1
  30. package/dist/{chunk-7TTCPHAV.cjs → chunk-CC7SUR7Q.cjs} +153 -48
  31. package/dist/chunk-CC7SUR7Q.cjs.map +1 -0
  32. package/dist/{chunk-Y253JQOH.js → chunk-G3DX65HO.js} +110 -10
  33. package/dist/chunk-G3DX65HO.js.map +1 -0
  34. package/dist/{chunk-LEECUCTG.cjs → chunk-KJSEQVBV.cjs} +129 -29
  35. package/dist/chunk-KJSEQVBV.cjs.map +1 -0
  36. package/dist/{chunk-IMQJVRMT.cjs → chunk-NEUPEENI.cjs} +5 -5
  37. package/dist/{chunk-IMQJVRMT.cjs.map → chunk-NEUPEENI.cjs.map} +1 -1
  38. package/dist/{chunk-2GNK6DPV.js → chunk-VF56PRAA.js} +121 -16
  39. package/dist/chunk-VF56PRAA.js.map +1 -0
  40. package/dist/{chunk-RTJD3ZSW.js → chunk-WIIU42FH.js} +4 -4
  41. package/dist/{chunk-RTJD3ZSW.js.map → chunk-WIIU42FH.js.map} +1 -1
  42. package/dist/{chunk-5IJENZOG.cjs → chunk-Y5UWFH6Y.cjs} +91 -91
  43. package/dist/{chunk-5IJENZOG.cjs.map → chunk-Y5UWFH6Y.cjs.map} +1 -1
  44. package/dist/index.cjs +33 -33
  45. package/dist/index.d.cts +20 -6
  46. package/dist/index.d.ts +20 -6
  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.ts → types-B6zDTU2V.d.cts} +14 -1
  58. package/dist/{types-CavnUvd5.d.cts → types-B6zDTU2V.d.ts} +14 -1
  59. package/docs/api.md +5 -4
  60. package/docs/dsl.md +7 -5
  61. package/llms-full.txt +85 -18
  62. package/package.json +1 -1
  63. package/skills/pixi-effects/SKILL.md +3 -3
  64. package/skills/pixi-effects/reference/cheatsheet.md +7 -6
  65. package/skills/pixi-effects/reference/pitfalls.md +3 -0
  66. package/skills/pixi-effects/reference/recipes.md +60 -0
  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-7TTCPHAV.cjs.map +0 -1
  72. package/dist/chunk-GL2AQFLQ.cjs.map +0 -1
  73. package/dist/chunk-ITDYFBB6.js.map +0 -1
  74. package/dist/chunk-LEECUCTG.cjs.map +0 -1
  75. 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)) {
@@ -2748,8 +2822,8 @@ var Movie = class {
2748
2822
  this.stops = normalizeStops(applyVars(options.composition, this._vars)?.stops, this.duration, this.frameRate);
2749
2823
  this._posterFrame = poster.frame;
2750
2824
  this.background = options.background ?? "#000000";
2751
- this.timeline = chunkLEECUCTG_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
2752
- 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);
2753
2827
  const baseAppOptions = {
2754
2828
  width: this.width,
2755
2829
  height: this.height,
@@ -2843,8 +2917,8 @@ var Movie = class {
2843
2917
  height: sizeWins ? height : spec.height ?? height,
2844
2918
  duration: spec.duration ?? this.duration
2845
2919
  } : void 0;
2846
- await chunkLEECUCTG_cjs.ensureFilterLibrary(seededComposition);
2847
- 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);
2848
2922
  const userComposition = seededComposition ? expandTransitions(seededComposition) : void 0;
2849
2923
  const rootSeqSpec = {
2850
2924
  type: "composition",
@@ -2854,7 +2928,7 @@ var Movie = class {
2854
2928
  ...userComposition
2855
2929
  };
2856
2930
  if (userComposition) carryTransitionWindows(userComposition, rootSeqSpec);
2857
- const sequence = new chunk5IJENZOG_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
2931
+ const sequence = new chunkY5UWFH6Y_cjs.CompositionSequence(rootSeqSpec, null, rootShape);
2858
2932
  lp?.begin("build", countLayers(rootSeqSpec));
2859
2933
  try {
2860
2934
  await sequence.build();
@@ -2990,8 +3064,8 @@ var Movie = class {
2990
3064
  if (!changed) return { reason, ms: 0, audio: "none", frame: this.currentFrame, playing: this.isPlaying, unchanged: true };
2991
3065
  sequence = await this._construct(composition, vars, width, height, void 0, this._resized || sizeChanged);
2992
3066
  alive();
2993
- const timeline = chunkLEECUCTG_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
2994
- chunkLEECUCTG_cjs.lengthen(timeline, this.duration);
3067
+ const timeline = chunkKJSEQVBV_cjs.createTimeline({ paused: true, defaults: { ease: "none" } });
3068
+ chunkKJSEQVBV_cjs.lengthen(timeline, this.duration);
2995
3069
  sequence.bindTimeline(timeline);
2996
3070
  const hadSound = this.audioBuffer !== null;
2997
3071
  const audio = await this._mixSound(sequence, this._ensureAudioContext());
@@ -3705,7 +3779,7 @@ function countLayers(spec) {
3705
3779
  n += 1 + countLayers(s);
3706
3780
  const spans = s.spans;
3707
3781
  if (Array.isArray(spans)) n += spans.length;
3708
- const src = chunk5IJENZOG_cjs.maskSourceOf(mask);
3782
+ const src = chunkY5UWFH6Y_cjs.maskSourceOf(mask);
3709
3783
  if (src.kind === "inline") n += 1 + countLayers(src.spec);
3710
3784
  }
3711
3785
  return n;
@@ -3718,7 +3792,7 @@ function measureText(text, style = {}) {
3718
3792
  return { width: m.width, height: m.height, lines: text.split("\n").length };
3719
3793
  }
3720
3794
  function splitText(text, style = {}, opts = {}) {
3721
- chunkTWZNOUM7_cjs.warnUnknownOptions("splitText()", opts, ["by", "x", "y", "align"]);
3795
+ chunkTWZNOUM7_cjs.warnUnknownOptions("splitText()", opts, ["by", "x", "y", "align", "vertical"]);
3722
3796
  let by = opts.by ?? "chars";
3723
3797
  if (!BY.includes(by)) {
3724
3798
  const g = chunkTWZNOUM7_cjs.suggestName(String(by), BY);
@@ -3737,6 +3811,7 @@ function splitText(text, style = {}, opts = {}) {
3737
3811
  }
3738
3812
  if (text === "") return [];
3739
3813
  const ts = new pixi_js.TextStyle(style);
3814
+ if (opts.vertical) return splitVertical(text, style, ts, x0, y0, align, by);
3740
3815
  const lineHeight = pixi_js.CanvasTextMetrics.measureText("M", ts).lineHeight ?? pixi_js.CanvasTextMetrics.measureText("M", ts).height;
3741
3816
  const sentinel = widthOf("|", ts);
3742
3817
  const out = [];
@@ -3760,11 +3835,41 @@ function splitText(text, style = {}, opts = {}) {
3760
3835
  });
3761
3836
  return out;
3762
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
+ }
3763
3868
 
3764
3869
  // src/presets/_ease.ts
3765
3870
  function parseEase2(name) {
3766
- chunkLEECUCTG_cjs.checkEase(name, "a preset");
3767
- return chunkLEECUCTG_cjs.parseEase(name);
3871
+ chunkKJSEQVBV_cjs.checkEase(name, "a preset");
3872
+ return chunkKJSEQVBV_cjs.parseEase(name);
3768
3873
  }
3769
3874
 
3770
3875
  // src/presets/kenBurns.ts
@@ -3899,17 +4004,17 @@ function orbit(opts) {
3899
4004
  throw new Error("pixi-effects: orbit() cannot combine `radius` with `dollyZoom` \u2014 the radius follows the animated fov");
3900
4005
  }
3901
4006
  const [cx, cy] = opts.center ?? [width / 2, height / 2];
3902
- 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);
3903
4008
  const start = opts.start ?? -degrees / 2;
3904
4009
  const steps = Math.max(1, Math.round(duration * stepsPerSecond));
3905
4010
  const easeFn = parseEase2(ease);
3906
4011
  const dollyEase = dolly ? parseEase2(dolly.ease ?? ease) : null;
3907
4012
  const pos = (i) => {
3908
4013
  const p = i / steps;
3909
- const a = (start + degrees * easeFn(p)) * chunkLEECUCTG_cjs.DEG;
4014
+ const a = (start + degrees * easeFn(p)) * chunkKJSEQVBV_cjs.DEG;
3910
4015
  if (!dolly) return { x: cx + fixedR * Math.sin(a), z: fixedR * Math.cos(a) };
3911
4016
  const f = dolly.from + (dolly.to - dolly.from) * dollyEase(p);
3912
- const R = chunkLEECUCTG_cjs.homeDistance(height, f);
4017
+ const R = chunkKJSEQVBV_cjs.homeDistance(height, f);
3913
4018
  return { x: cx + R * Math.sin(a), z: R * Math.cos(a), fov: f };
3914
4019
  };
3915
4020
  const keyframes = [];
@@ -3964,7 +4069,7 @@ function particles(opts) {
3964
4069
  const round2 = (v) => Math.round(v * 1e6) / 1e6;
3965
4070
  const out = [];
3966
4071
  for (let i = 0; i < count; i++) {
3967
- const r = chunkLEECUCTG_cjs.random(seed * 1000003 + i * 7919 + 17);
4072
+ const r = chunkKJSEQVBV_cjs.random(seed * 1000003 + i * 7919 + 17);
3968
4073
  const pick = ([a2, b]) => a2 + r() * (b - a2);
3969
4074
  const born = at + r() * emit;
3970
4075
  const lifetime = pick(life);
@@ -3977,7 +4082,7 @@ function particles(opts) {
3977
4082
  const turn = spin ? pick(spin) : 0;
3978
4083
  const startAngle = spin ? r() * 360 : 0;
3979
4084
  const swayAmp = sway?.amp ?? 0, swayFreq = sway?.freq ?? 1;
3980
- const n0 = sway ? chunkLEECUCTG_cjs.noise(0, seed * 1000003 + i) : 0;
4085
+ const n0 = sway ? chunkKJSEQVBV_cjs.noise(0, seed * 1000003 + i) : 0;
3981
4086
  const at_ = (t) => {
3982
4087
  const axis = (p0, v0, acc) => {
3983
4088
  if (drag > 0) {
@@ -3986,7 +4091,7 @@ function particles(opts) {
3986
4091
  }
3987
4092
  return p0 + v0 * t + 0.5 * acc * t * t;
3988
4093
  };
3989
- 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;
3990
4095
  return { x: axis(x0, vx, wind) + sw, y: axis(y0, vy, gravity) };
3991
4096
  };
3992
4097
  const kfs = [];
@@ -4048,7 +4153,7 @@ function followPath(opts) {
4048
4153
  if (steps > MAX_STEPS) throw new Error(`followPath(): ${steps} samples is too many (max ${MAX_STEPS}); lower frameRate or duration`);
4049
4154
  const segments = [];
4050
4155
  let total = 0;
4051
- for (const line of chunkLEECUCTG_cjs.flattenSvgPath(d)) {
4156
+ for (const line of chunkKJSEQVBV_cjs.flattenSvgPath(d)) {
4052
4157
  const p = line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
4053
4158
  for (let i = 2; i < p.length; i += 2) {
4054
4159
  const len = Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
@@ -4202,7 +4307,7 @@ function distances(n, from, grid, seed) {
4202
4307
  if (n === 0) return [];
4203
4308
  if (from === "random") {
4204
4309
  const order = Array.from({ length: n }, (_, i) => i);
4205
- const r = chunkLEECUCTG_cjs.random(seed);
4310
+ const r = chunkKJSEQVBV_cjs.random(seed);
4206
4311
  for (let i = n - 1; i > 0; i--) {
4207
4312
  const j = Math.floor(r() * (i + 1));
4208
4313
  [order[i], order[j]] = [order[j], order[i]];
@@ -4264,7 +4369,7 @@ function wiggle(opts) {
4264
4369
  to[n] = p.around;
4265
4370
  return;
4266
4371
  }
4267
- 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;
4268
4373
  to[n] = typeof p.around === "number" ? p.around + off : `(${p.around}) ${off < 0 ? "-" : "+"} ${Math.abs(off)}`;
4269
4374
  });
4270
4375
  out.push({ at: at + s * stepLen, to, duration: stepLen, ease });
@@ -4275,7 +4380,7 @@ function wiggle(opts) {
4275
4380
  // src/presets/animateText.ts
4276
4381
  var TWEEN_KEYS = ["preset", "from", "to", "duration", "ease"];
4277
4382
  var TIME_KEYS = ["at", "delay", "start", "begin", "time"];
4278
- 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"];
4279
4384
  var PRESETS = {
4280
4385
  rise: { from: { y: 40, alpha: 0 }, duration: 0.5, ease: "back.out(2)" },
4281
4386
  drop: { from: { y: -50, alpha: 0 }, duration: 0.7, ease: "bounce.out" },
@@ -4288,14 +4393,14 @@ var PRESETS = {
4288
4393
  var PRESET_NAMES = Object.keys(PRESETS);
4289
4394
  function animateText(text, style, options) {
4290
4395
  chunkTWZNOUM7_cjs.warnUnknownOptions("animateText()", options, KEYS5);
4291
- 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;
4292
4397
  for (const [n, v] of [["x", x], ["y", y]]) {
4293
4398
  if (typeof v !== "number" || !Number.isFinite(v)) {
4294
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.`);
4295
4400
  }
4296
4401
  }
4297
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}`);
4298
- const pieces = splitText(text, style, { by, x, y, align });
4403
+ const pieces = splitText(text, style, { by, x, y, align, ...vertical ? { vertical } : {} });
4299
4404
  if (pieces.length === 0) return [];
4300
4405
  const lineHeight = measureText("M", style).height;
4301
4406
  const inTween = options.in === false ? null : resolveTween(options.in ?? "rise", "in");
@@ -4314,12 +4419,12 @@ function animateText(text, style, options) {
4314
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)}`);
4315
4420
  }
4316
4421
  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 };
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 };
4319
4424
  const start = at + delays[i];
4320
4425
  const len = end - start;
4321
4426
  const keyframes = [];
4322
- 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;
4323
4428
  if (inTween) {
4324
4429
  keyframes.push({
4325
4430
  at: 0,
@@ -4358,7 +4463,7 @@ function animateText(text, style, options) {
4358
4463
  style: styleFor ? { ...style, ...styleFor(p) } : style,
4359
4464
  at: start,
4360
4465
  duration: len,
4361
- 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 } : {} },
4362
4467
  keyframes
4363
4468
  };
4364
4469
  });
@@ -4722,5 +4827,5 @@ exports.splitText = splitText;
4722
4827
  exports.stagger = stagger;
4723
4828
  exports.wiggle = wiggle;
4724
4829
  exports.withFade = withFade;
4725
- //# sourceMappingURL=chunk-7TTCPHAV.cjs.map
4726
- //# sourceMappingURL=chunk-7TTCPHAV.cjs.map
4830
+ //# sourceMappingURL=chunk-CC7SUR7Q.cjs.map
4831
+ //# sourceMappingURL=chunk-CC7SUR7Q.cjs.map