pixi-effects 0.4.0 → 0.6.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 (61) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/README.md +11 -6
  3. package/ai/SKILL.md +7 -4
  4. package/ai/reference/cheatsheet.md +18 -5
  5. package/ai/reference/pitfalls.md +16 -11
  6. package/ai/reference/recipes.md +153 -1
  7. package/ai/template.html +6 -3
  8. package/ai/tools/check.mjs +385 -0
  9. package/dist/{Base-DhY9oMxr.d.ts → Base-4vNLl4tH.d.cts} +6 -1
  10. package/dist/{Base-5R7C5pHR.d.cts → Base-CD4AoT5-.d.ts} +6 -1
  11. package/dist/Composition-7SO5WYHU.js +4 -0
  12. package/dist/{Composition-RGTRZ2JX.js.map → Composition-7SO5WYHU.js.map} +1 -1
  13. package/dist/Composition-Y5VZSDDP.cjs +13 -0
  14. package/dist/{Composition-D7RCAGEA.cjs.map → Composition-Y5VZSDDP.cjs.map} +1 -1
  15. package/dist/Controller.cjs +47 -15
  16. package/dist/Controller.cjs.map +1 -1
  17. package/dist/Controller.d.cts +5 -2
  18. package/dist/Controller.d.ts +5 -2
  19. package/dist/Controller.js +47 -15
  20. package/dist/Controller.js.map +1 -1
  21. package/dist/{Movie-DRZtIGU7.d.ts → Movie-CADDBfpl.d.cts} +58 -2
  22. package/dist/{Movie-CghFXprP.d.cts → Movie-Ch2SUI1h.d.ts} +58 -2
  23. package/dist/{chunk-QGBOC5YL.cjs → chunk-4AGJF5RK.cjs} +33 -21
  24. package/dist/chunk-4AGJF5RK.cjs.map +1 -0
  25. package/dist/{chunk-ZQX7WIYL.js → chunk-7IJHJXDI.js} +380 -64
  26. package/dist/chunk-7IJHJXDI.js.map +1 -0
  27. package/dist/{chunk-A574IA4F.js → chunk-B2FTJLNB.js} +18 -6
  28. package/dist/chunk-B2FTJLNB.js.map +1 -0
  29. package/dist/chunk-DKGG7WD4.js +61 -0
  30. package/dist/chunk-DKGG7WD4.js.map +1 -0
  31. package/dist/{chunk-6BQ6IEW2.cjs → chunk-KQM2ZZMK.cjs} +380 -63
  32. package/dist/chunk-KQM2ZZMK.cjs.map +1 -0
  33. package/dist/chunk-UQBGW5DO.cjs +64 -0
  34. package/dist/chunk-UQBGW5DO.cjs.map +1 -0
  35. package/dist/index.cjs +333 -89
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +54 -5
  38. package/dist/index.d.ts +54 -5
  39. package/dist/index.js +317 -75
  40. package/dist/index.js.map +1 -1
  41. package/dist/sfx-MZJZXYY3.cjs +446 -0
  42. package/dist/sfx-MZJZXYY3.cjs.map +1 -0
  43. package/dist/sfx-S37DDC66.js +434 -0
  44. package/dist/sfx-S37DDC66.js.map +1 -0
  45. package/dist/three.cjs +5 -5
  46. package/dist/three.d.cts +2 -2
  47. package/dist/three.d.ts +2 -2
  48. package/dist/three.js +1 -1
  49. package/dist/{types-CRE9WKd4.d.ts → types-DrW9QeKU.d.cts} +118 -12
  50. package/dist/{types-CRE9WKd4.d.cts → types-DrW9QeKU.d.ts} +118 -12
  51. package/docs/api.md +37 -0
  52. package/docs/dsl.md +70 -10
  53. package/llms-full.txt +300 -30
  54. package/llms.txt +1 -1
  55. package/package.json +2 -1
  56. package/dist/Composition-D7RCAGEA.cjs +0 -13
  57. package/dist/Composition-RGTRZ2JX.js +0 -4
  58. package/dist/chunk-6BQ6IEW2.cjs.map +0 -1
  59. package/dist/chunk-A574IA4F.js.map +0 -1
  60. package/dist/chunk-QGBOC5YL.cjs.map +0 -1
  61. package/dist/chunk-ZQX7WIYL.js.map +0 -1
@@ -741,6 +741,11 @@ var Sequence = class {
741
741
  * (not a normal child) by PIXI when `target.mask = maskSequence.target`.
742
742
  */
743
743
  maskSequence = null;
744
+ /**
745
+ * Set by the parent composition on a sprite-based (image / video) mask. PIXI hides such a mask with
746
+ * `renderable = false`; the lifespan toggles must not switch it back on, or the mask is drawn as a picture.
747
+ */
748
+ keepHidden = false;
744
749
  /**
745
750
  * Absolute start time on the global timeline (offset + at), recorded by
746
751
  * bindTimeline. Movie._awaitVideoFrames uses it to determine the correct
@@ -785,6 +790,7 @@ var Sequence = class {
785
790
  applyKeyframes(timeline, this.target, this.spec.keyframes, this.duration, scope, [], startTime, routers);
786
791
  const endTime = startTime + this.duration;
787
792
  this.absoluteStart = startTime;
793
+ if (this.keepHidden) return;
788
794
  this.target.renderable = startTime <= 0;
789
795
  timeline.set(this.target, { renderable: true }, startTime);
790
796
  timeline.set(this.target, { renderable: false }, endTime);
@@ -837,6 +843,7 @@ function lintTiming(spec, parentDuration, warn = defaultWarn) {
837
843
  if (at >= parentDuration) {
838
844
  warn(`pixi-effects: ${who} starts at ${at}s, after its composition ends (${parentDuration}s), so it is never visible`);
839
845
  }
846
+ if (spec.type === "audio" && spec.sfx !== void 0 && spec.duration === void 0) return;
840
847
  const duration = spec.duration ?? parentDuration;
841
848
  (spec.keyframes ?? []).forEach((kf, i) => {
842
849
  const kat = kf.at ?? 0;
@@ -854,7 +861,12 @@ function summarizeWarnings(messages, shown = 3) {
854
861
  }
855
862
  function lintText(spec, warn = defaultWarn) {
856
863
  if (spec.type !== "text") return;
857
- if (collectPropKeys(spec).has("value") && !(spec.text ?? "").includes("{value}")) {
864
+ const strings = [spec.text ?? "", ...(spec.keyframes ?? []).map((kf) => String(kf.set?.text ?? ""))];
865
+ const hasPlaceholder = strings.some((t) => t.includes("{value}"));
866
+ if (hasPlaceholder && !collectPropKeys(spec).has("value")) {
867
+ warn(`pixi-effects: ${describeLayer(spec)}: the text has a {value} placeholder but nothing sets or animates \`value\`, so it prints 0. Set initial: { value: \u2026 } or animate it; {value} is reserved for counters (there is no way to print a literal "{value}").`);
868
+ }
869
+ if (collectPropKeys(spec).has("value") && !hasPlaceholder) {
858
870
  warn(`pixi-effects: ${describeLayer(spec)}: \`value\` is set or animated but the text has no {value} placeholder, so nothing shows it. Use text: '{value}' (or e.g. '{value} users').`);
859
871
  }
860
872
  }
@@ -1048,6 +1060,8 @@ function revertibleSet(timeline, at, read, write, value) {
1048
1060
  }
1049
1061
 
1050
1062
  // src/expr/colorInterp.ts
1063
+ var CHROMA_FLOOR = 0.03;
1064
+ var CHROMA_FULL = 0.08;
1051
1065
  function buildColorInterp(from, to, space) {
1052
1066
  const fromRGBA = parseToRGBA(from);
1053
1067
  const toRGBA = parseToRGBA(to);
@@ -1064,8 +1078,9 @@ function buildColorInterp(from, to, space) {
1064
1078
  }
1065
1079
  const fromLCH = oklabToOklch(fromOk);
1066
1080
  const toLCH = oklabToOklch(toOk);
1067
- if (fromLCH.C < 1e-4) fromLCH.h = toLCH.h;
1068
- if (toLCH.C < 1e-4) toLCH.h = fromLCH.h;
1081
+ const fromHue = fromLCH.h, toHue = toLCH.h;
1082
+ fromLCH.h = borrowHue(fromHue, fromLCH.C, toHue);
1083
+ toLCH.h = borrowHue(toHue, toLCH.C, fromHue);
1069
1084
  const hueDelta = shortestHueDelta(fromLCH.h, toLCH.h);
1070
1085
  return (t) => {
1071
1086
  const L = lerp(fromLCH.L, toLCH.L, t);
@@ -1076,6 +1091,11 @@ function buildColorInterp(from, to, space) {
1076
1091
  return rgbaString(oklabToRgb(ab), alpha);
1077
1092
  };
1078
1093
  }
1094
+ function borrowHue(own, chroma, other) {
1095
+ const u = Math.min(1, Math.max(0, (chroma - CHROMA_FLOOR) / (CHROMA_FULL - CHROMA_FLOOR)));
1096
+ const keep = u * u * (3 - 2 * u);
1097
+ return own + shortestHueDelta(own, other) * (1 - keep);
1098
+ }
1079
1099
  function parseToRGBA(input) {
1080
1100
  if (typeof input === "number") {
1081
1101
  return { r: (input >> 16 & 255) / 255, g: (input >> 8 & 255) / 255, b: (input & 255) / 255, a: 1 };
@@ -1260,6 +1280,7 @@ var ImageSequence = class extends Sequence {
1260
1280
  }
1261
1281
  this.absoluteStart = startTime;
1262
1282
  const endTime = startTime + this.duration;
1283
+ if (this.keepHidden) return;
1263
1284
  this.target.renderable = startTime <= 0;
1264
1285
  timeline.set(this.target, { renderable: true }, startTime);
1265
1286
  timeline.set(this.target, { renderable: false }, endTime);
@@ -1304,19 +1325,31 @@ var STYLE_OPAQUE_KEYS = [
1304
1325
  ];
1305
1326
  function formatValue(v, f) {
1306
1327
  const fixed = v.toFixed(Math.max(0, Math.floor(f?.decimals ?? 0)));
1307
- if (!f?.grouping) return fixed;
1308
- const [int, frac] = fixed.split(".");
1309
- return int.replace(/\B(?=(\d{3})+(?!\d))/g, ",") + (frac !== void 0 ? "." + frac : "");
1328
+ const pad = Math.max(0, Math.floor(f?.pad ?? 0));
1329
+ if (!f?.grouping && !pad) return fixed;
1330
+ const [signed, frac] = fixed.split(".");
1331
+ const sign = signed.startsWith("-") ? "-" : "";
1332
+ let int = sign ? signed.slice(1) : signed;
1333
+ if (pad) int = int.padStart(pad, "0");
1334
+ if (f?.grouping) int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
1335
+ return sign + int + (frac !== void 0 ? "." + frac : "");
1310
1336
  }
1311
1337
  var TextSequence = class extends Sequence {
1312
- /** Current number for the `{value}` placeholder (tweened by `value` keyframes). */
1313
- _value = { value: 0 };
1338
+ /** `value`: the number for the `{value}` placeholder; `visibleChars`: how many characters show (typewriter). Both tweened by keyframes. */
1339
+ _counters = { value: 0, visibleChars: 0 };
1340
+ /** The string now on the layer (before `{value}` / `visibleChars`): `text`, or the last `set: { text }`. */
1341
+ _template = "";
1342
+ /** `visibleChars` is in play (initial or animated): otherwise the whole text shows. */
1343
+ _typed = false;
1314
1344
  _lastText = "";
1315
- /** Rewrite the displayed string from the template and the current `value`. */
1345
+ /** A typewriter that has typed nothing yet: the layer is empty on purpose (inspect must not call that "no size"). */
1346
+ get showsNothingYet() {
1347
+ return this._typed && this._lastText === "" && this._template !== "";
1348
+ }
1349
+ /** Rewrite the displayed string from the template, the current `value` and `visibleChars`. */
1316
1350
  _refreshText() {
1317
- const template = this.spec.text ?? "";
1318
- if (!template.includes("{value}")) return;
1319
- const next = template.split("{value}").join(formatValue(this._value.value, this.spec.format));
1351
+ let next = this._template.includes("{value}") ? this._template.split("{value}").join(formatValue(this._counters.value, this.spec.format)) : this._template;
1352
+ if (this._typed) next = Array.from(next).slice(0, Math.max(0, Math.floor(this._counters.visibleChars))).join("");
1320
1353
  if (next === this._lastText) return;
1321
1354
  this._lastText = next;
1322
1355
  this.target.text = next;
@@ -1328,8 +1361,10 @@ var TextSequence = class extends Sequence {
1328
1361
  fill: "#ffffff",
1329
1362
  align: "center"
1330
1363
  };
1364
+ const initialProps = this.spec.initial ?? {};
1365
+ this._template = String(initialProps.text ?? this.spec.text ?? "");
1331
1366
  const text = new pixi_js.Text({
1332
- text: this.spec.text ?? "",
1367
+ text: this._template,
1333
1368
  style: baseStyle,
1334
1369
  label: this.spec.name
1335
1370
  });
@@ -1341,10 +1376,10 @@ var TextSequence = class extends Sequence {
1341
1376
  this.duration = this.parent?.duration ?? this.root.duration;
1342
1377
  }
1343
1378
  if (this.spec.style) {
1344
- const scope = this.scope();
1379
+ const scope2 = this.scope();
1345
1380
  const resolved = normalizeProps(
1346
1381
  this.spec.style,
1347
- scope,
1382
+ scope2,
1348
1383
  { skipKeys: STYLE_OPAQUE_KEYS }
1349
1384
  );
1350
1385
  for (const k of Object.keys(resolved)) {
@@ -1353,22 +1388,36 @@ var TextSequence = class extends Sequence {
1353
1388
  this.intrinsicWidth = text.width;
1354
1389
  this.intrinsicHeight = text.height;
1355
1390
  }
1356
- const initialValue = this.spec.initial?.value;
1357
- if (initialValue !== void 0) this._value.value = resolveNumber(initialValue, this.scope());
1391
+ const scope = this.scope();
1392
+ if (initialProps.value !== void 0) this._counters.value = resolveNumber(initialProps.value, scope);
1393
+ this._typed = initialProps.visibleChars !== void 0 || animates(this.spec.keyframes, "visibleChars");
1394
+ if (initialProps.visibleChars !== void 0) this._counters.visibleChars = resolveNumber(initialProps.visibleChars, scope);
1395
+ const typed = this._typed;
1396
+ this._typed = false;
1358
1397
  this._refreshText();
1359
1398
  if (this._lastText !== "") {
1360
1399
  this.intrinsicWidth = text.width;
1361
1400
  this.intrinsicHeight = text.height;
1362
1401
  }
1402
+ this._typed = typed;
1403
+ this._refreshText();
1363
1404
  this.buildFilters();
1364
1405
  }
1365
1406
  bindTimeline(timeline, offset = 0) {
1366
1407
  if (!this.target) return;
1367
1408
  const colorSpace = this.spec.colorSpace ?? "rgb";
1368
- const stripped = stripKeys2(this.spec, ["fill", "value"]);
1409
+ const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars"]);
1369
1410
  runSuperWithStrippedSpec(this, stripped, timeline, offset);
1370
- bindFillKeyframes(timeline, this.target, this.spec.keyframes ?? [], this.duration, offset + this.at, colorSpace);
1371
- bindValueKeyframes(timeline, this._value, () => this._refreshText(), this.spec.keyframes ?? [], this.duration, offset + this.at, this.scope());
1411
+ const keyframes = this.spec.keyframes ?? [];
1412
+ const origin = offset + this.at;
1413
+ bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
1414
+ const refresh = () => this._refreshText();
1415
+ bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
1416
+ bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
1417
+ bindTextKeyframes(timeline, keyframes, this.duration, origin, () => this._template, (t) => {
1418
+ this._template = t;
1419
+ refresh();
1420
+ }, describeLayer(this.spec));
1372
1421
  }
1373
1422
  };
1374
1423
  function runSuperWithStrippedSpec(inst, strippedSpec, timeline, offset) {
@@ -1404,11 +1453,11 @@ function resolveNumber(v, scope) {
1404
1453
  const out = normalizeProps({ v }, scope).v;
1405
1454
  return typeof out === "number" ? out : Number(out) || 0;
1406
1455
  }
1407
- function bindValueKeyframes(timeline, state, refresh, keyframes, parentDuration, origin, scope) {
1456
+ function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDuration, origin, scope) {
1408
1457
  for (const kf of keyframes) {
1409
- const setV = kf.set?.value;
1410
- const fromV = kf.from?.value;
1411
- const toV = kf.to?.value;
1458
+ const setV = kf.set?.[key];
1459
+ const fromV = kf.from?.[key];
1460
+ const toV = kf.to?.[key];
1412
1461
  if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
1413
1462
  const at = origin + resolveAt(kf.at, parentDuration);
1414
1463
  const duration = kf.duration ?? 0;
@@ -1416,25 +1465,40 @@ function bindValueKeyframes(timeline, state, refresh, keyframes, parentDuration,
1416
1465
  const loop = loopVars(kf);
1417
1466
  if (setV !== void 0) {
1418
1467
  const v = resolveNumber(setV, scope);
1419
- revertibleSet(timeline, at, () => state.value, (n) => {
1420
- state.value = n;
1468
+ revertibleSet(timeline, at, () => state[key], (n) => {
1469
+ state[key] = n;
1421
1470
  refresh();
1422
1471
  }, v);
1423
1472
  }
1424
1473
  if (toV !== void 0 && fromV !== void 0) {
1425
1474
  timeline.fromTo(
1426
1475
  state,
1427
- { value: resolveNumber(fromV, scope) },
1428
- { value: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
1476
+ { [key]: resolveNumber(fromV, scope) },
1477
+ { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
1429
1478
  at
1430
1479
  );
1431
1480
  } else if (toV !== void 0) {
1432
- timeline.to(state, { value: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
1481
+ timeline.to(state, { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
1433
1482
  } else if (fromV !== void 0) {
1434
- timeline.from(state, { value: resolveNumber(fromV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
1483
+ timeline.from(state, { [key]: resolveNumber(fromV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
1435
1484
  }
1436
1485
  }
1437
1486
  }
1487
+ function bindTextKeyframes(timeline, keyframes, parentDuration, origin, read, write, who) {
1488
+ for (const kf of keyframes) {
1489
+ for (const [what, props] of [["to", kf.to], ["from", kf.from]]) {
1490
+ if (props && props.text !== void 0) {
1491
+ console.warn(`pixi-effects: ${who}: a keyframe's ${what}: { text } cannot tween a string: use set: { text: '\u2026' } at the time it should change`);
1492
+ }
1493
+ }
1494
+ const t = kf.set?.text;
1495
+ if (t === void 0) continue;
1496
+ revertibleSet(timeline, origin + resolveAt(kf.at, parentDuration), read, write, String(t));
1497
+ }
1498
+ }
1499
+ function animates(keyframes, key) {
1500
+ return (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((p) => p && p[key] !== void 0));
1501
+ }
1438
1502
  function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, colorSpace) {
1439
1503
  const state = { fill: text.style.fill };
1440
1504
  const writeFill = () => {
@@ -1478,28 +1542,64 @@ function pickFill(props) {
1478
1542
  }
1479
1543
  var AudioSequence = class extends Sequence {
1480
1544
  _audioBuffer = null;
1545
+ _synth = null;
1546
+ _source = "";
1481
1547
  async build() {
1482
- const data = await pixi_js.Assets.get(this.spec.asset);
1548
+ this.target = null;
1549
+ if (this.spec.sfx !== void 0) return this._buildSfx(this.spec);
1550
+ const spec = this.spec;
1551
+ this._source = `asset "${spec.asset}"`;
1552
+ const data = await pixi_js.Assets.get(spec.asset);
1483
1553
  this._audioBuffer = data.audioBuffer;
1484
1554
  if (this.duration === void 0) {
1485
1555
  const rest = Math.max(0, (this.parent?.duration ?? this.root.duration) - this.at);
1486
- this.duration = this.spec.duration ?? (this.spec.loop ? rest : data.duration) ?? this.root.duration;
1556
+ this.duration = spec.duration ?? (spec.loop ? rest : data.duration) ?? this.root.duration;
1487
1557
  }
1488
- if (!this.spec.loop && data.duration !== void 0 && this.duration > data.duration + 0.05) {
1558
+ if (!spec.loop && data.duration !== void 0 && this.duration > data.duration + 0.05) {
1489
1559
  console.warn(
1490
- `pixi-effects: ${describeLayer(this.spec)}: audio asset "${this.spec.asset}" is ${data.duration.toFixed(1)}s but the layer lasts ${this.duration.toFixed(1)}s, so it goes silent after ${data.duration.toFixed(1)}s. Add loop: true, or shorten the layer's duration.`
1560
+ `pixi-effects: ${describeLayer(spec)}: audio asset "${spec.asset}" is ${data.duration.toFixed(1)}s but the layer lasts ${this.duration.toFixed(1)}s, so it goes silent after ${data.duration.toFixed(1)}s. Add loop: true, or shorten the layer's duration.`
1491
1561
  );
1492
1562
  }
1493
- this.target = null;
1563
+ }
1564
+ async _buildSfx(spec) {
1565
+ const who = describeLayer(spec);
1566
+ const raw = spec;
1567
+ if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and sfx \u2014 the sfx plays; remove one`);
1568
+ if (raw.loop) console.warn(`pixi-effects: ${who}: loop has no effect on an sfx \u2014 to repeat it, add one audio layer per hit (a JS loop)`);
1569
+ const { resolveSfx, renderSfx, sfxKey } = await import('./sfx-MZJZXYY3.cjs');
1570
+ const sfx = resolveSfx(spec.sfx, spec.duration, who);
1571
+ if (!sfx) return;
1572
+ this.duration = sfx.length;
1573
+ this._source = `sfx "${sfx.preset ?? "custom"}"`;
1574
+ this._synth = { key: sfxKey(sfx), render: (sr) => renderSfx(sfx, sr) };
1575
+ (spec.keyframes ?? []).forEach((kf, i) => {
1576
+ if ((kf.at ?? 0) >= sfx.length) {
1577
+ console.warn(`pixi-effects: ${who}: keyframes[${i}] starts at ${kf.at}s, after the sound ends (${sfx.length}s), so it never plays`);
1578
+ } else if ((kf.at ?? 0) < 0 && sfx.length + (kf.at ?? 0) < 0) {
1579
+ console.warn(`pixi-effects: ${who}: keyframes[${i}] starts ${-kf.at}s before the END of a ${sfx.length}s sound, which is before the sound begins (a negative \`at\` counts back from the end of the layer). Use a shorter \`at\`, or stretch the sound with \`duration\`.`);
1580
+ }
1581
+ });
1494
1582
  }
1495
1583
  bindTimeline(_timeline) {
1496
1584
  }
1497
1585
  collectAudio(out, baseTime) {
1498
- if (!this._audioBuffer) return;
1499
- const initialVolume = this.spec.volume ?? 1;
1500
- const volumeKeyframes = [];
1501
- const dur = this.duration;
1586
+ if (!this._audioBuffer && !this._synth) return;
1587
+ const initialVolume = this.spec.volume ?? this.spec.initial?.volume ?? 1;
1502
1588
  const t0 = baseTime + this.at;
1589
+ const dur = this.duration;
1590
+ out.push({
1591
+ ...this._audioBuffer ? { buffer: this._audioBuffer } : { synth: this._synth },
1592
+ layer: describeLayer(this.spec),
1593
+ source: this._source,
1594
+ loop: !this._synth && !!this.spec.loop,
1595
+ start: t0,
1596
+ end: t0 + dur,
1597
+ initialVolume,
1598
+ volumeKeyframes: this._volumePoints(t0, dur, initialVolume)
1599
+ });
1600
+ }
1601
+ _volumePoints(t0, dur, initialVolume) {
1602
+ const volumeKeyframes = [];
1503
1603
  const keyframes = (this.spec.keyframes ?? []).map((raw) => normalizeKeyframe(raw, dur)).filter((kf) => [kf.set, kf.to, kf.from].some((p) => p && "volume" in p)).sort((x, y) => x.at - y.at);
1504
1604
  let current = initialVolume;
1505
1605
  const point = (time, value) => {
@@ -1527,17 +1627,11 @@ var AudioSequence = class extends Sequence {
1527
1627
  point(end, current);
1528
1628
  }
1529
1629
  }
1530
- out.push({
1531
- buffer: this._audioBuffer,
1532
- loop: !!this.spec.loop,
1533
- start: baseTime + this.at,
1534
- end: baseTime + this.at + dur,
1535
- initialVolume,
1536
- volumeKeyframes
1537
- });
1630
+ return volumeKeyframes;
1538
1631
  }
1539
1632
  destroy() {
1540
1633
  this._audioBuffer = null;
1634
+ this._synth = null;
1541
1635
  }
1542
1636
  };
1543
1637
 
@@ -1686,6 +1780,8 @@ var VideoSequence = class extends Sequence {
1686
1780
  const initialVolume = this.spec.volume ?? 1;
1687
1781
  out.push({
1688
1782
  buffer: this._audioBuffer,
1783
+ layer: describeLayer(this.spec),
1784
+ source: `video "${this.spec.asset}"`,
1689
1785
  loop: !!this.spec.loop,
1690
1786
  start: baseTime + this.at,
1691
1787
  end: baseTime + this.at + this.duration,
@@ -1766,19 +1862,107 @@ function makeGradientFill(g) {
1766
1862
  }
1767
1863
  return new pixi_js.FillGradient(gradientOptions(g));
1768
1864
  }
1865
+ var CURVE_STEPS = 24;
1866
+ var ELLIPSE_STEPS = 128;
1867
+ function flattenSvgPath(d) {
1868
+ const prims = new pixi_js.GraphicsPath(d).shapePath.shapePrimitives;
1869
+ const out = [];
1870
+ for (const { shape } of prims) {
1871
+ const poly = shape;
1872
+ if (poly.points && poly.points.length >= 4) out.push({ pts: poly.points.slice(), closed: poly.closePath === true });
1873
+ }
1874
+ return out;
1875
+ }
1876
+ function rectOutline(x, y, w, h, radius) {
1877
+ const r = Math.max(0, Math.min(radius, Math.abs(w) / 2, Math.abs(h) / 2));
1878
+ if (r === 0) return { pts: [x, y, x + w, y, x + w, y + h, x, y + h], closed: true };
1879
+ const pts = [];
1880
+ const corner = (cx, cy, from) => {
1881
+ for (let i = 0; i <= CURVE_STEPS; i++) {
1882
+ const a = from + i / CURVE_STEPS * (Math.PI / 2);
1883
+ pts.push(cx + r * Math.cos(a), cy + r * Math.sin(a));
1884
+ }
1885
+ };
1886
+ corner(x + w - r, y + r, -Math.PI / 2);
1887
+ corner(x + w - r, y + h - r, 0);
1888
+ corner(x + r, y + h - r, Math.PI / 2);
1889
+ corner(x + r, y + r, Math.PI);
1890
+ const startAt = pts.length - 2;
1891
+ const rotated = pts.slice(startAt).concat(pts.slice(0, startAt));
1892
+ return { pts: rotated, closed: true };
1893
+ }
1894
+ function ellipseOutline(cx, cy, rx, ry) {
1895
+ const pts = [];
1896
+ for (let i = 0; i < ELLIPSE_STEPS; i++) {
1897
+ const a = -Math.PI / 2 + i / ELLIPSE_STEPS * Math.PI * 2;
1898
+ pts.push(cx + rx * Math.cos(a), cy + ry * Math.sin(a));
1899
+ }
1900
+ return { pts, closed: true };
1901
+ }
1902
+ function trimPolylines(lines, start, end) {
1903
+ const s = Math.max(0, Math.min(1, start)), e = Math.max(0, Math.min(1, end));
1904
+ if (!(e > s)) return [];
1905
+ const lengths = lines.map(polylineLength);
1906
+ const total = lengths.reduce((a, b) => a + b, 0);
1907
+ if (!(total > 0)) return [];
1908
+ const from = s * total, to = e * total;
1909
+ const out = [];
1910
+ let offset = 0;
1911
+ lines.forEach((line, i) => {
1912
+ const len = lengths[i];
1913
+ const a = Math.max(from - offset, 0), b = Math.min(to - offset, len);
1914
+ offset += len;
1915
+ if (!(b > a) || len === 0) return;
1916
+ if (line.closed && a <= 0 && b >= len) {
1917
+ out.push({ pts: line.pts.slice(), closed: true });
1918
+ return;
1919
+ }
1920
+ out.push({ pts: slice(line, a, b), closed: false });
1921
+ });
1922
+ return out;
1923
+ }
1924
+ function points(line) {
1925
+ return line.closed ? [...line.pts, line.pts[0], line.pts[1]] : line.pts;
1926
+ }
1927
+ function polylineLength(line) {
1928
+ const p = points(line);
1929
+ let len = 0;
1930
+ for (let i = 2; i < p.length; i += 2) len += Math.hypot(p[i] - p[i - 2], p[i + 1] - p[i - 1]);
1931
+ return len;
1932
+ }
1933
+ function slice(line, a, b) {
1934
+ const p = points(line);
1935
+ const out = [];
1936
+ let at = 0;
1937
+ for (let i = 2; i < p.length; i += 2) {
1938
+ const x0 = p[i - 2], y0 = p[i - 1], x1 = p[i], y1 = p[i + 1];
1939
+ const seg = Math.hypot(x1 - x0, y1 - y0);
1940
+ const segEnd = at + seg;
1941
+ if (segEnd > a && at < b && seg > 0) {
1942
+ const t0 = Math.max(a - at, 0) / seg, t1 = Math.min(b - at, seg) / seg;
1943
+ if (out.length === 0) out.push(x0 + (x1 - x0) * t0, y0 + (y1 - y0) * t0);
1944
+ out.push(x0 + (x1 - x0) * t1, y0 + (y1 - y0) * t1);
1945
+ }
1946
+ at = segEnd;
1947
+ if (at >= b) break;
1948
+ }
1949
+ return out;
1950
+ }
1769
1951
 
1770
1952
  // src/sequences/Shape.ts
1771
1953
  var STYLE_KEYS = ["fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth"];
1772
1954
  var COLOR_KEYS = /* @__PURE__ */ new Set(["fillColor", "strokeColor"]);
1955
+ var TRIM_KEYS = ["trimStart", "trimEnd"];
1773
1956
  var GEOMETRY_KEYS_BY_SHAPE = {
1774
- rect: ["width", "height", "cornerRadius", "anchorX", "anchorY"],
1775
- circle: ["radius", "anchorX", "anchorY"],
1776
- ellipse: ["radiusX", "radiusY", "anchorX", "anchorY"],
1777
- line: [],
1778
- polygon: [],
1779
- path: []
1957
+ rect: ["width", "height", "cornerRadius", "anchorX", "anchorY", ...TRIM_KEYS],
1958
+ circle: ["radius", "anchorX", "anchorY", ...TRIM_KEYS],
1959
+ ellipse: ["radiusX", "radiusY", "anchorX", "anchorY", ...TRIM_KEYS],
1960
+ arc: ["radius", "innerRadius", "startAngle", "endAngle"],
1961
+ line: [...TRIM_KEYS],
1962
+ polygon: [...TRIM_KEYS],
1963
+ path: [...TRIM_KEYS]
1780
1964
  };
1781
- var ANCHORED_SHAPES = /* @__PURE__ */ new Set(["rect", "circle", "ellipse"]);
1965
+ var ANCHORED_SHAPES = /* @__PURE__ */ new Set(["rect", "circle", "ellipse", "arc"]);
1782
1966
  var ShapeSequence = class extends Sequence {
1783
1967
  _state = {
1784
1968
  fillColor: void 0,
@@ -1805,12 +1989,30 @@ var ShapeSequence = class extends Sequence {
1805
1989
  for (const k of geomKeys) this._liveKeys.add(k);
1806
1990
  seedGeometry(this._state, this.spec, scope);
1807
1991
  seedStyle(this._state, this.spec.initial ?? {}, scope);
1992
+ seedStrokeStyle(this._state, this.spec, this.spec.initial ?? {}, describeLayer(this.spec));
1993
+ if (this.spec.shape === "arc") {
1994
+ this._state.startAngle ??= 0;
1995
+ this._state.endAngle ??= 360;
1996
+ const top = this.spec, ini = this.spec.initial ?? {};
1997
+ if ("trimStart" in top || "trimEnd" in top || "trimStart" in ini || "trimEnd" in ini) {
1998
+ console.warn(`pixi-effects: ${describeLayer(this.spec)}: trimStart / trimEnd do not apply to an arc: animate its endAngle (and startAngle) instead`);
1999
+ }
2000
+ } else {
2001
+ this._state.trimStart ??= 0;
2002
+ this._state.trimEnd ??= 1;
2003
+ }
1808
2004
  const gradientSpec = this.spec.fillGradient ?? this.spec.initial?.fillGradient;
1809
2005
  if (gradientSpec) this._state.fillStyle = makeGradientFill(gradientSpec);
1810
2006
  this._drawGeometry = makeDrawGeometry(this.spec, scope, this._state);
2007
+ this._outline = makeOutline(this.spec, scope, this._state);
2008
+ const { trimStart, trimEnd } = this._state;
2009
+ this._state.trimStart = 0;
2010
+ this._state.trimEnd = 1;
1811
2011
  this._drawGeometry(graphics);
1812
2012
  applyState(graphics, this._state);
1813
2013
  const bounds = graphics.getLocalBounds();
2014
+ this._state.trimStart = trimStart;
2015
+ this._state.trimEnd = trimEnd;
1814
2016
  this.intrinsicWidth = bounds.width;
1815
2017
  this.intrinsicHeight = bounds.height;
1816
2018
  if (!ANCHORED_SHAPES.has(this.spec.shape)) {
@@ -1828,14 +2030,37 @@ var ShapeSequence = class extends Sequence {
1828
2030
  }
1829
2031
  this._redraw();
1830
2032
  };
2033
+ this._redraw();
1831
2034
  this.buildFilters();
1832
2035
  }
1833
2036
  _fresh = false;
2037
+ _outline = null;
1834
2038
  _redraw() {
1835
2039
  const graphics = this.target;
2040
+ const s = this._state;
1836
2041
  graphics.clear();
1837
- this._drawGeometry(graphics);
1838
- applyState(graphics, this._state);
2042
+ const trimmed = (s.trimStart ?? 0) > 0 || (s.trimEnd ?? 1) < 1;
2043
+ if (!trimmed || !this._outline || !(s.strokeColor !== void 0 && s.strokeWidth > 0)) {
2044
+ this._drawGeometry(graphics);
2045
+ applyState(graphics, s);
2046
+ return;
2047
+ }
2048
+ if (s.fillStyle || s.fillColor !== void 0) {
2049
+ this._drawGeometry(graphics);
2050
+ applyFill(graphics, s);
2051
+ graphics.beginPath();
2052
+ }
2053
+ const parts = trimPolylines(this._outline(), s.trimStart ?? 0, s.trimEnd ?? 1);
2054
+ if (parts.length === 0) return;
2055
+ for (const part of parts) {
2056
+ if (part.closed) {
2057
+ graphics.poly(part.pts, true);
2058
+ continue;
2059
+ }
2060
+ graphics.moveTo(part.pts[0], part.pts[1]);
2061
+ for (let i = 2; i < part.pts.length; i += 2) graphics.lineTo(part.pts[i], part.pts[i + 1]);
2062
+ }
2063
+ applyStroke(graphics, s);
1839
2064
  }
1840
2065
  /**
1841
2066
  * Draw from the tweened `_state` before the culler runs. The culler measures the geometry as last
@@ -1873,6 +2098,21 @@ function seedGeometry(state, spec, scope) {
1873
2098
  if (v !== void 0) state[k] = num(v, scope);
1874
2099
  }
1875
2100
  }
2101
+ var STROKE_CAPS = ["butt", "round", "square"];
2102
+ var STROKE_JOINS = ["miter", "round", "bevel"];
2103
+ function seedStrokeStyle(state, spec, initial, who) {
2104
+ const top = spec;
2105
+ const ini = initial;
2106
+ const pick = (key, valid) => {
2107
+ const v = top[key] ?? ini[key];
2108
+ if (v === void 0) return void 0;
2109
+ if (valid.includes(String(v))) return String(v);
2110
+ console.warn(`pixi-effects: ${who}: ${key} "${v}" is not one of ${valid.join(", ")}; ignored`);
2111
+ return void 0;
2112
+ };
2113
+ state.strokeCap = pick("strokeCap", STROKE_CAPS);
2114
+ state.strokeJoin = pick("strokeJoin", STROKE_JOINS);
2115
+ }
1876
2116
  function seedStyle(state, initial, scope) {
1877
2117
  for (const k of STYLE_KEYS) {
1878
2118
  const v = initial[k];
@@ -1881,8 +2121,8 @@ function seedStyle(state, initial, scope) {
1881
2121
  }
1882
2122
  }
1883
2123
  function stripGradient(props) {
1884
- if (!props || !("fillGradient" in props)) return props;
1885
- const { fillGradient: _drop, ...rest } = props;
2124
+ if (!props || !("fillGradient" in props || "strokeCap" in props || "strokeJoin" in props)) return props;
2125
+ const { fillGradient: _drop, strokeCap: _cap, strokeJoin: _join, ...rest } = props;
1886
2126
  return rest;
1887
2127
  }
1888
2128
  function stripLive(props, liveKeys) {
@@ -1923,13 +2163,25 @@ function resolveLiveValue(key, value, scope) {
1923
2163
  return numOrZero(value, scope);
1924
2164
  }
1925
2165
  function applyState(g, s) {
2166
+ applyFill(g, s);
2167
+ applyStroke(g, s);
2168
+ }
2169
+ function applyFill(g, s) {
1926
2170
  if (s.fillStyle) {
1927
2171
  g.fill(s.fillStyle);
1928
2172
  } else if (s.fillColor !== void 0) {
1929
2173
  g.fill({ color: s.fillColor, alpha: s.fillAlpha });
1930
2174
  }
2175
+ }
2176
+ function applyStroke(g, s) {
1931
2177
  if (s.strokeColor !== void 0 && s.strokeWidth > 0) {
1932
- g.stroke({ color: s.strokeColor, alpha: s.strokeAlpha, width: s.strokeWidth });
2178
+ g.stroke({
2179
+ color: s.strokeColor,
2180
+ alpha: s.strokeAlpha,
2181
+ width: s.strokeWidth,
2182
+ ...s.strokeCap ? { cap: s.strokeCap } : {},
2183
+ ...s.strokeJoin ? { join: s.strokeJoin } : {}
2184
+ });
1933
2185
  }
1934
2186
  }
1935
2187
  function drawRectFromState(g, s) {
@@ -1956,6 +2208,34 @@ function drawEllipseFromState(g, s) {
1956
2208
  const ay = s.anchorY ?? 0.5;
1957
2209
  g.ellipse((0.5 - ax) * 2 * rx, (0.5 - ay) * 2 * ry, rx, ry);
1958
2210
  }
2211
+ var DEG2 = Math.PI / 180;
2212
+ function drawArcFromState(g, s) {
2213
+ const r = s.radius ?? 0;
2214
+ const ri = Math.max(0, Math.min(s.innerRadius ?? 0, r));
2215
+ const startDeg = s.startAngle ?? 0;
2216
+ let endDeg = s.endAngle ?? 360;
2217
+ if (!(r > 0) || endDeg === startDeg) return;
2218
+ const filled = !!s.fillStyle || s.fillColor !== void 0;
2219
+ if (Math.abs(endDeg - startDeg) >= 360) {
2220
+ if (!filled) {
2221
+ g.circle(0, 0, r);
2222
+ return;
2223
+ }
2224
+ endDeg = startDeg + Math.sign(endDeg - startDeg) * 359.999;
2225
+ }
2226
+ const a0 = startDeg * DEG2, a1 = endDeg * DEG2;
2227
+ const ccw = a1 < a0;
2228
+ if (filled) g.moveTo(r * Math.cos(a0), r * Math.sin(a0));
2229
+ g.arc(0, 0, r, a0, a1, ccw);
2230
+ if (!filled) return;
2231
+ if (ri > 0) {
2232
+ g.lineTo(ri * Math.cos(a1), ri * Math.sin(a1));
2233
+ g.arc(0, 0, ri, a1, a0, !ccw);
2234
+ } else {
2235
+ g.lineTo(0, 0);
2236
+ }
2237
+ g.closePath();
2238
+ }
1959
2239
  function makeLineDraw(spec, scope) {
1960
2240
  const fx = num(spec.from[0], scope);
1961
2241
  const fy = num(spec.from[1], scope);
@@ -1984,7 +2264,8 @@ function makePathDraw(spec) {
1984
2264
  var STATE_DRAWERS = {
1985
2265
  rect: drawRectFromState,
1986
2266
  circle: drawCircleFromState,
1987
- ellipse: drawEllipseFromState
2267
+ ellipse: drawEllipseFromState,
2268
+ arc: drawArcFromState
1988
2269
  };
1989
2270
  function makeDrawGeometry(spec, scope, state) {
1990
2271
  const stateDrawer = STATE_DRAWERS[spec.shape];
@@ -2000,6 +2281,41 @@ function makeDrawGeometry(spec, scope, state) {
2000
2281
  return () => {
2001
2282
  };
2002
2283
  }
2284
+ function makeOutline(spec, scope, s) {
2285
+ switch (spec.shape) {
2286
+ case "rect":
2287
+ return () => {
2288
+ const w = s.width ?? 0, h = s.height ?? 0;
2289
+ return [rectOutline(0 - (s.anchorX ?? 0.5) * w, 0 - (s.anchorY ?? 0.5) * h, w, h, s.cornerRadius ?? 0)];
2290
+ };
2291
+ case "circle":
2292
+ return () => {
2293
+ const r = s.radius ?? 0;
2294
+ return [ellipseOutline((0.5 - (s.anchorX ?? 0.5)) * 2 * r, (0.5 - (s.anchorY ?? 0.5)) * 2 * r, r, r)];
2295
+ };
2296
+ case "ellipse":
2297
+ return () => {
2298
+ const rx = s.radiusX ?? 0, ry = s.radiusY ?? 0;
2299
+ return [ellipseOutline((0.5 - (s.anchorX ?? 0.5)) * 2 * rx, (0.5 - (s.anchorY ?? 0.5)) * 2 * ry, rx, ry)];
2300
+ };
2301
+ case "line": {
2302
+ const lines = [{ pts: [num(spec.from[0], scope), num(spec.from[1], scope), num(spec.to[0], scope), num(spec.to[1], scope)], closed: false }];
2303
+ return () => lines;
2304
+ }
2305
+ case "polygon": {
2306
+ const pts = [];
2307
+ for (const [x, y] of spec.points) pts.push(num(x, scope), num(y, scope));
2308
+ const lines = pts.length >= 4 ? [{ pts, closed: !spec.open }] : [];
2309
+ return () => lines;
2310
+ }
2311
+ case "path": {
2312
+ let lines = null;
2313
+ return () => lines ??= flattenSvgPath(spec.d);
2314
+ }
2315
+ default:
2316
+ return null;
2317
+ }
2318
+ }
2003
2319
  function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration, scope, offset, colorSpace) {
2004
2320
  for (const kf of keyframes) {
2005
2321
  const at = offset + resolveAt(kf.at, parentDuration);
@@ -2073,7 +2389,7 @@ function numOrZero(v, scope) {
2073
2389
  var _CompositionSequence = null;
2074
2390
  async function getCompositionSequence() {
2075
2391
  if (!_CompositionSequence) {
2076
- _CompositionSequence = (await import('./Composition-D7RCAGEA.cjs')).CompositionSequence;
2392
+ _CompositionSequence = (await import('./Composition-Y5VZSDDP.cjs')).CompositionSequence;
2077
2393
  }
2078
2394
  return _CompositionSequence;
2079
2395
  }
@@ -2113,6 +2429,7 @@ async function buildSequenceTree(specs, parent, root) {
2113
2429
  exports.CameraSequence = CameraSequence;
2114
2430
  exports.DEFAULT_FOV = DEFAULT_FOV;
2115
2431
  exports.DEG = DEG;
2432
+ exports.NEAR = NEAR;
2116
2433
  exports.Sequence = Sequence;
2117
2434
  exports.buildScope = buildScope;
2118
2435
  exports.buildSequenceTree = buildSequenceTree;
@@ -2128,5 +2445,5 @@ exports.projectLayer = projectLayer;
2128
2445
  exports.registerSequenceType = registerSequenceType;
2129
2446
  exports.resolveAt = resolveAt;
2130
2447
  exports.summarizeWarnings = summarizeWarnings;
2131
- //# sourceMappingURL=chunk-6BQ6IEW2.cjs.map
2132
- //# sourceMappingURL=chunk-6BQ6IEW2.cjs.map
2448
+ //# sourceMappingURL=chunk-KQM2ZZMK.cjs.map
2449
+ //# sourceMappingURL=chunk-KQM2ZZMK.cjs.map