pixi-effects 0.19.0 → 0.20.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 (69) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +6 -6
  3. package/ai/SKILL.md +1 -0
  4. package/ai/chat-template.html +3 -3
  5. package/ai/reference/cheatsheet.md +7 -6
  6. package/ai/reference/pitfalls.md +5 -0
  7. package/ai/reference/recipes.md +52 -0
  8. package/ai/template.html +5 -5
  9. package/ai/tools/check.mjs +15 -3
  10. package/dist/{Base-B4WbUJO2.d.ts → Base-CzfLGoDr.d.ts} +9 -1
  11. package/dist/{Base-i88XXaiP.d.cts → Base-wYJQ2g5Q.d.cts} +9 -1
  12. package/dist/Composition-6PA4C4TV.js +5 -0
  13. package/dist/{Composition-QBKEEJBV.js.map → Composition-6PA4C4TV.js.map} +1 -1
  14. package/dist/Composition-LL2XXU5N.cjs +14 -0
  15. package/dist/{Composition-KYNPRWK3.cjs.map → Composition-LL2XXU5N.cjs.map} +1 -1
  16. package/dist/Controller.cjs +2 -2
  17. package/dist/Controller.d.cts +2 -2
  18. package/dist/Controller.d.ts +2 -2
  19. package/dist/Controller.js +1 -1
  20. package/dist/{Movie-8IrqL3kO.d.ts → Movie-CBxGH5ms.d.ts} +8 -2
  21. package/dist/{Movie-CpmdNadg.d.cts → Movie-CzzJw8A-.d.cts} +8 -2
  22. package/dist/Presenter.cjs +2 -2
  23. package/dist/Presenter.d.cts +2 -2
  24. package/dist/Presenter.d.ts +2 -2
  25. package/dist/Presenter.js +1 -1
  26. package/dist/{chunk-YRXTKOV5.js → chunk-DQHXVNOI.js} +282 -23
  27. package/dist/chunk-DQHXVNOI.js.map +1 -0
  28. package/dist/{chunk-CTZRFSVU.cjs → chunk-FFYA3UBU.cjs} +144 -37
  29. package/dist/chunk-FFYA3UBU.cjs.map +1 -0
  30. package/dist/{chunk-DKGG7WD4.js → chunk-MNM56B3X.js} +6 -3
  31. package/dist/chunk-MNM56B3X.js.map +1 -0
  32. package/dist/{chunk-VTBT6NNE.cjs → chunk-NO26HWR7.cjs} +294 -30
  33. package/dist/chunk-NO26HWR7.cjs.map +1 -0
  34. package/dist/{chunk-B3YB2TNY.js → chunk-TSPMKV2F.js} +120 -13
  35. package/dist/chunk-TSPMKV2F.js.map +1 -0
  36. package/dist/{chunk-UQBGW5DO.cjs → chunk-WTRKDIKH.cjs} +6 -3
  37. package/dist/chunk-WTRKDIKH.cjs.map +1 -0
  38. package/dist/index.cjs +213 -90
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +4 -4
  41. package/dist/index.d.ts +4 -4
  42. package/dist/index.js +154 -31
  43. package/dist/index.js.map +1 -1
  44. package/dist/loader.css +1 -1
  45. package/dist/{music-4HDFULNR.js → music-CFEC7HKL.js} +3 -3
  46. package/dist/{music-4HDFULNR.js.map → music-CFEC7HKL.js.map} +1 -1
  47. package/dist/{music-3OF5C6X5.cjs → music-UAN7DXK4.cjs} +7 -7
  48. package/dist/{music-3OF5C6X5.cjs.map → music-UAN7DXK4.cjs.map} +1 -1
  49. package/dist/{sfx-MZJZXYY3.cjs → sfx-EX4PUBMV.cjs} +8 -8
  50. package/dist/{sfx-MZJZXYY3.cjs.map → sfx-EX4PUBMV.cjs.map} +1 -1
  51. package/dist/{sfx-S37DDC66.js → sfx-U3GM2MED.js} +3 -3
  52. package/dist/{sfx-S37DDC66.js.map → sfx-U3GM2MED.js.map} +1 -1
  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-bzLJ7aUx.d.ts → types-YmUaViL1.d.cts} +24 -0
  58. package/dist/{types-bzLJ7aUx.d.cts → types-YmUaViL1.d.ts} +24 -0
  59. package/docs/dsl.md +22 -0
  60. package/llms-full.txt +87 -6
  61. package/package.json +3 -1
  62. package/dist/Composition-KYNPRWK3.cjs +0 -14
  63. package/dist/Composition-QBKEEJBV.js +0 -5
  64. package/dist/chunk-B3YB2TNY.js.map +0 -1
  65. package/dist/chunk-CTZRFSVU.cjs.map +0 -1
  66. package/dist/chunk-DKGG7WD4.js.map +0 -1
  67. package/dist/chunk-UQBGW5DO.cjs.map +0 -1
  68. package/dist/chunk-VTBT6NNE.cjs.map +0 -1
  69. package/dist/chunk-YRXTKOV5.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkUQBGW5DO_cjs = require('./chunk-UQBGW5DO.cjs');
3
+ var chunkWTRKDIKH_cjs = require('./chunk-WTRKDIKH.cjs');
4
4
  var PIXI = require('pixi.js');
5
5
  var gsap = require('gsap');
6
6
 
@@ -417,6 +417,58 @@ function looksLikeColorString(s) {
417
417
  if (s.charCodeAt(0) === 35) return true;
418
418
  return /^(rgb|hsl|oklab|oklch|lab|lch)\b/i.test(s);
419
419
  }
420
+ function cubicBezierEase(x1, y1, x2, y2) {
421
+ const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx;
422
+ const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
423
+ const X = (t) => ((ax * t + bx) * t + cx) * t;
424
+ const Y = (t) => ((ay * t + by) * t + cy) * t;
425
+ const dX = (t) => (3 * ax * t + 2 * bx) * t + cx;
426
+ return (p) => {
427
+ if (p <= 0) return 0;
428
+ if (p >= 1) return 1;
429
+ let t = p;
430
+ for (let i = 0; i < 8; i++) {
431
+ const err = X(t) - p;
432
+ if (Math.abs(err) < 1e-9) return Y(t);
433
+ const d = dX(t);
434
+ if (Math.abs(d) < 1e-6) break;
435
+ t -= err / d;
436
+ }
437
+ let lo = 0, hi = 1;
438
+ t = p;
439
+ for (let i = 0; i < 60; i++) {
440
+ const x = X(t);
441
+ if (Math.abs(x - p) < 1e-10) break;
442
+ if (x < p) lo = t;
443
+ else hi = t;
444
+ t = (lo + hi) / 2;
445
+ }
446
+ return Y(t);
447
+ };
448
+ }
449
+ var NUM = String.raw`\s*(-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)\s*`;
450
+ var RE = new RegExp(`^\\s*cubic-bezier\\(${NUM},${NUM},${NUM},${NUM}\\)\\s*$`, "i");
451
+ function cubicBezierProblem(name) {
452
+ const m = RE.exec(name);
453
+ if (!m) return 'expected "cubic-bezier(x1, y1, x2, y2)" with four numbers, like CSS';
454
+ const x1 = Number(m[1]), x2 = Number(m[3]);
455
+ if (x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1) return "x1 and x2 must be between 0 and 1 (y1 and y2 may overshoot)";
456
+ return null;
457
+ }
458
+ var registered2 = false;
459
+ function registerCubicBezierEase() {
460
+ if (registered2) return;
461
+ registered2 = true;
462
+ const base = ((p) => p);
463
+ base.config = (x1, y1, x2, y2) => {
464
+ const bad = [x1, y1, x2, y2].some((v) => typeof v !== "number" || !Number.isFinite(v)) || x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1;
465
+ return bad ? (p) => p : cubicBezierEase(x1, y1, x2, y2);
466
+ };
467
+ gsap.gsap.registerEase("cubic-bezier", base);
468
+ }
469
+ registerCubicBezierEase();
470
+
471
+ // src/core/ease.ts
420
472
  var FAMILIES = ["power1", "power2", "power3", "power4", "back", "elastic", "bounce", "circ", "expo", "sine"];
421
473
  var EASE_NAMES = ["none", ...FAMILIES.flatMap((f) => [`${f}.in`, `${f}.out`, `${f}.inOut`])];
422
474
  var warned = /* @__PURE__ */ new Set();
@@ -429,6 +481,13 @@ function checkEase(ease, where) {
429
481
  console.warn(`pixi-effects: ${where}: unknown ease "${ease}" \u2014 ${problem}. GSAP would run it as its default ease, power1.out.`);
430
482
  return;
431
483
  }
484
+ if (/^\s*cubic-bezier/i.test(ease)) {
485
+ const problem = cubicBezierProblem(ease);
486
+ if (!problem || warned.has(ease)) return;
487
+ warned.add(ease);
488
+ console.warn(`pixi-effects: ${where}: unknown ease "${ease}" \u2014 ${problem}. It runs as 'none' (a straight line).`);
489
+ return;
490
+ }
432
491
  const found = gsap.gsap.parseEase(ease);
433
492
  if (typeof found === "function") return;
434
493
  if (warned.has(ease)) return;
@@ -441,7 +500,7 @@ function checkEase(ease, where) {
441
500
  console.warn(`pixi-effects: ${where}: "${ease}" is a family, not an ease \u2014 write ${ease.toLowerCase()}.out, ${ease.toLowerCase()}.in or ${ease.toLowerCase()}.inOut`);
442
501
  return;
443
502
  }
444
- const guess = chunkUQBGW5DO_cjs.suggestName(ease, EASE_NAMES);
503
+ const guess = chunkWTRKDIKH_cjs.suggestName(ease, EASE_NAMES);
445
504
  console.warn(`pixi-effects: ${where}: unknown ease "${ease}"${guess ? ` \u2014 did you mean "${guess}"?` : ""} (GSAP would run it as its default ease, power1.out). Examples: ${EASE_NAMES.slice(0, 7).join(", ")}, back.out(1.7), elastic.out(1, 0.3), steps(8)`);
446
505
  }
447
506
 
@@ -1051,7 +1110,7 @@ var GRAIN_KEYS = ["amount", "size", "seed", "fps", "color"];
1051
1110
  var GrainFilter = class extends PIXI.Filter {
1052
1111
  _fps;
1053
1112
  constructor(options = {}) {
1054
- chunkUQBGW5DO_cjs.warnUnknownOptions("grain filter", options, GRAIN_KEYS);
1113
+ chunkWTRKDIKH_cjs.warnUnknownOptions("grain filter", options, GRAIN_KEYS);
1055
1114
  const { amount = 0.08, size = 1.5, seed = 0, fps = 24, color = 0 } = options;
1056
1115
  super({
1057
1116
  glProgram: PIXI.GlProgram.from({ vertex: GL_VERTEX, fragment: GL_FRAGMENT2, name: "grain-filter" }),
@@ -1103,7 +1162,7 @@ var GrainFilter = class extends PIXI.Filter {
1103
1162
  }
1104
1163
  /** The movie calls this with the time (s) of the frame it is about to draw: the grain is new every 1/fps s. */
1105
1164
  setTime(t) {
1106
- this.u.uFrame = this._fps > 0 ? Math.floor(t * this._fps + 1e-6) : 0;
1165
+ this.u.uFrame = this._fps > 0 ? Math.floor(t * this._fps + 1e-3) : 0;
1107
1166
  }
1108
1167
  };
1109
1168
 
@@ -1159,7 +1218,7 @@ function isNamedFilterType(t) {
1159
1218
  return !NOT_NAMED.has(t);
1160
1219
  }
1161
1220
  function unknownType(t) {
1162
- const guess = chunkUQBGW5DO_cjs.suggestName(t, FILTER_TYPES);
1221
+ const guess = chunkWTRKDIKH_cjs.suggestName(t, FILTER_TYPES);
1163
1222
  return new Error(`pixi-effects: unknown filter type "${t}"${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${[...FILTER_TYPES, "chromaKey", "custom"].join(", ")})`);
1164
1223
  }
1165
1224
  var library = null;
@@ -1208,7 +1267,7 @@ function colorMatrix(params) {
1208
1267
  const preset = params.preset;
1209
1268
  if (preset !== void 0) {
1210
1269
  if (!MATRIX_PRESETS.includes(preset)) {
1211
- const guess = chunkUQBGW5DO_cjs.suggestName(preset, MATRIX_PRESETS);
1270
+ const guess = chunkWTRKDIKH_cjs.suggestName(preset, MATRIX_PRESETS);
1212
1271
  throw new Error(`pixi-effects: colorMatrix preset "${preset}" is not one of ${MATRIX_PRESETS.join(", ")}${guess ? ` \u2014 did you mean "${guess}"?` : ""}`);
1213
1272
  }
1214
1273
  f[preset === "grayscale" ? "desaturate" : preset].call(f);
@@ -1318,13 +1377,21 @@ var Sequence = class {
1318
1377
  pathRouters() {
1319
1378
  return {};
1320
1379
  }
1380
+ /**
1381
+ * The `initial` and `keyframes` that go onto the display object. A layer whose `time` runs a clock (video, composition) takes `time`
1382
+ * out of them: it is not a property of the display object.
1383
+ */
1384
+ displayProps() {
1385
+ return { initial: this.spec.initial, keyframes: this.spec.keyframes };
1386
+ }
1321
1387
  bindTimeline(timeline, offset = 0) {
1322
1388
  if (!this.target) return;
1323
1389
  const scope = this.scope();
1324
1390
  const routers = this.pathRouters();
1325
1391
  const startTime = offset + this.at;
1326
- applyInitial(this.target, this.spec.initial, scope, [], routers);
1327
- applyKeyframes(timeline, this.target, this.spec.keyframes, this.duration, scope, [], startTime, routers);
1392
+ const { initial, keyframes } = this.displayProps();
1393
+ applyInitial(this.target, initial, scope, [], routers);
1394
+ applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime, routers);
1328
1395
  const endTime = startTime + this.duration;
1329
1396
  this.absoluteStart = startTime;
1330
1397
  if (this.keepHidden) return;
@@ -1377,11 +1444,11 @@ function collectPropKeys(spec) {
1377
1444
 
1378
1445
  // src/core/layerKeys.ts
1379
1446
  var COMMON = ["type", "name", "parent", "at", "duration", "initial", "keyframes", "filters", "filterArea", "mask", "maskInverted", "blendMode", "threeD", "hideBehindCamera"];
1380
- var VIDEO = [...COMMON, "asset", "loop", "audio", "volume"];
1447
+ var VIDEO = [...COMMON, "asset", "loop", "audio", "volume", "speed"];
1381
1448
  var IMAGE = [...COMMON, "asset", "colorSpace"];
1382
1449
  var TEXT = [...COMMON, "text", "format", "style", "colorSpace", "fillGradient"];
1383
- var AUDIO = [...COMMON, "volume", "asset", "loop", "sfx", "music"];
1384
- var COMPOSITION = [...COMMON, "width", "height", "sequences", "transitions"];
1450
+ var AUDIO = [...COMMON, "volume", "asset", "loop", "sfx", "music", "speed"];
1451
+ var COMPOSITION = [...COMMON, "width", "height", "sequences", "transitions", "speed"];
1385
1452
  var CAMERA = [...COMMON];
1386
1453
  var NULL = [...COMMON];
1387
1454
  var SHAPE = [...COMMON, "shape", "fillGradient", "colorSpace", "strokeCap", "strokeJoin"];
@@ -1471,6 +1538,7 @@ var PROP_KEYS = /* @__PURE__ */ new Set([
1471
1538
  "visibleChars",
1472
1539
  "text",
1473
1540
  "volume",
1541
+ "time",
1474
1542
  // camera
1475
1543
  "lookAtX",
1476
1544
  "lookAtY",
@@ -1547,38 +1615,191 @@ function lintText(spec, warn = defaultWarn) {
1547
1615
  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').`);
1548
1616
  }
1549
1617
  }
1618
+ var REMAPPABLE = /* @__PURE__ */ new Set(["video", "audio", "composition"]);
1619
+ var REMAP_NAMES = /* @__PURE__ */ new Set(["time", "timeRemap", "reverse"]);
1620
+ var TIME_BAGS = ["set", "to", "from"];
1621
+ function lintRemap(spec, warn) {
1622
+ const s = spec;
1623
+ const who = describeLayer(spec);
1624
+ const kfs = s.keyframes ?? [];
1625
+ const bags = [s.initial, ...kfs.flatMap((kf) => TIME_BAGS.map((b) => kf[b]))];
1626
+ const timeInInitial = !!s.initial && s.initial.time !== void 0;
1627
+ const timeInKeyframes = kfs.some((kf) => TIME_BAGS.some((b) => kf[b] && kf[b].time !== void 0));
1628
+ const timed = timeInInitial || timeInKeyframes;
1629
+ if (bags.some((bag) => bag && bag.speed !== void 0)) {
1630
+ warn(`pixi-effects: ${who}: speed is a fixed setting, not an animatable property: write it on the layer (speed: 2); to change the speed over time, animate time (keyframes: [{ at: 0, from: { time: 0 }, to: { time: 4 }, duration: 2, ease: 'power2.in' }])`);
1631
+ }
1632
+ if (!REMAPPABLE.has(spec.type)) {
1633
+ if (timed) warn(`pixi-effects: ${who}: "time" only works on video, audio and composition layers; put this layer in a composition and remap that (it is ignored here)`);
1634
+ return;
1635
+ }
1636
+ if (s.reverse !== void 0) warn(`pixi-effects: ${who}: "reverse" is not a key: write speed: -1 to play backward`);
1637
+ if (s.timeRemap !== void 0) warn(`pixi-effects: ${who}: "timeRemap" is not a key: animate time in keyframes (from / to / set), or write speed`);
1638
+ if (s.time !== void 0) warn(`pixi-effects: ${who}: "time" belongs in initial or keyframes (initial: { time: 2 }), not on the layer`);
1639
+ if (spec.type === "audio" && (s.sfx !== void 0 || s.music !== void 0) && (s.speed !== void 0 || timed)) {
1640
+ warn(`pixi-effects: ${who}: speed and time do not apply to a synthesised sound (sfx, music); change an sfx's pitch with pitch (semitones), or put it in a composition and remap that`);
1641
+ return;
1642
+ }
1643
+ if (s.speed !== void 0 && (typeof s.speed !== "number" || !Number.isFinite(s.speed) || s.speed === 0)) {
1644
+ warn(`pixi-effects: ${who}: speed ${JSON.stringify(s.speed)} cannot be used: write a number other than 0 (negative plays backward); to hold a moment, key time to the same value twice`);
1645
+ }
1646
+ if (s.speed !== void 0 && timeInKeyframes) {
1647
+ warn(`pixi-effects: ${who}: speed and keyframed time are both set; time wins and speed is ignored. Use one of them`);
1648
+ }
1649
+ if (timeInInitial && typeof s.initial.time !== "number") {
1650
+ warn(`pixi-effects: ${who}: initial.time must be a number of seconds, got ${JSON.stringify(s.initial.time)}`);
1651
+ }
1652
+ kfs.forEach((kf, i) => {
1653
+ for (const b of TIME_BAGS) {
1654
+ const v = kf[b]?.time;
1655
+ if (v !== void 0 && typeof v !== "number" && typeof v !== "string") warn(`pixi-effects: ${who}: keyframes[${i}].${b}.time must be a number of seconds, got ${JSON.stringify(v)}`);
1656
+ }
1657
+ });
1658
+ }
1550
1659
  function lintKeys(spec, warn = defaultWarn) {
1551
1660
  const who = describeLayer(spec);
1552
1661
  const valid = layerKeys(spec);
1553
1662
  if (valid) {
1554
1663
  for (const key of Object.keys(spec)) {
1555
1664
  if (valid.includes(key) || spec[key] === void 0) continue;
1556
- const guess = chunkUQBGW5DO_cjs.suggestName(key, valid.filter((k) => k !== "type"));
1665
+ if (REMAPPABLE.has(spec.type) && REMAP_NAMES.has(key)) continue;
1666
+ const guess = chunkWTRKDIKH_cjs.suggestName(key, valid.filter((k) => k !== "type"));
1557
1667
  const owners = kindsWithKey(key);
1558
1668
  const belongs = owners.length ? ` (it belongs to ${owners.slice(0, 3).join(", ")})` : "";
1559
1669
  const hint = guess ? ` \u2014 did you mean "${guess}"?${belongs}` : belongs;
1560
1670
  warn(`pixi-effects: ${who}: "${key}" is not a ${kindName(spec)} key${hint}. Valid keys: ${valid.join(", ")}`);
1561
1671
  }
1562
1672
  }
1673
+ lintRemap(spec, warn);
1563
1674
  const said = /* @__PURE__ */ new Set();
1564
1675
  const bags = [spec.initial, ...(spec.keyframes ?? []).flatMap((kf) => [kf.set, kf.to, kf.from])];
1565
1676
  for (const bag of bags) {
1566
1677
  for (const key of Object.keys(bag ?? {})) {
1567
1678
  if (bag[key] === void 0) continue;
1568
- if (PROP_KEYS.has(key) || key.includes(".") || /^gradient/i.test(key) || said.has(key)) continue;
1679
+ if (PROP_KEYS.has(key) || key.includes(".") || /^gradient/i.test(key) || said.has(key) || key === "speed") continue;
1569
1680
  said.add(key);
1570
- const guess = chunkUQBGW5DO_cjs.suggestName(key, [...PROP_KEYS]);
1681
+ const guess = chunkWTRKDIKH_cjs.suggestName(key, [...PROP_KEYS]);
1571
1682
  warn(`pixi-effects: ${who}: "${key}" is not an animatable property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (in initial, set, to or from). See the cheatsheet for the properties of this kind of layer`);
1572
1683
  }
1573
1684
  }
1574
1685
  if (spec.type === "text" && spec.style && typeof spec.style === "object") {
1575
1686
  for (const key of Object.keys(spec.style)) {
1576
1687
  if (STYLE_KEYS.includes(key) || spec.style[key] === void 0) continue;
1577
- const guess = chunkUQBGW5DO_cjs.suggestName(key, STYLE_KEYS);
1688
+ const guess = chunkWTRKDIKH_cjs.suggestName(key, STYLE_KEYS);
1578
1689
  warn(`pixi-effects: ${who}: style.${key} is not a text style key${guess ? ` \u2014 did you mean "${guess}"?` : ""} (it is ignored). Keys: ${STYLE_KEYS.join(", ")}`);
1579
1690
  }
1580
1691
  }
1581
1692
  }
1693
+ var BAGS = ["set", "to", "from"];
1694
+ function timeRemapOf(spec, prop = "time") {
1695
+ const hasInitial = !!spec.initial && "time" in spec.initial;
1696
+ const hasKf = (spec.keyframes ?? []).some((kf) => BAGS.some((b) => kf[b] && "time" in kf[b]));
1697
+ const speed = typeof spec.speed === "number" && Number.isFinite(spec.speed) && spec.speed !== 0 ? spec.speed : void 0;
1698
+ if (!hasInitial && !hasKf && speed === void 0) return null;
1699
+ const timeKfs = [];
1700
+ const restKfs = [];
1701
+ for (const kf of spec.keyframes ?? []) {
1702
+ const t = { ...kf }, r = { ...kf };
1703
+ let touched = false, left = false;
1704
+ for (const b of BAGS) {
1705
+ const bag = kf[b];
1706
+ if (!bag) {
1707
+ delete t[b];
1708
+ delete r[b];
1709
+ continue;
1710
+ }
1711
+ const { time, ...others } = bag;
1712
+ if ("time" in bag) {
1713
+ touched = true;
1714
+ t[b] = { [prop]: time };
1715
+ } else delete t[b];
1716
+ if (Object.keys(others).length) {
1717
+ r[b] = others;
1718
+ left = true;
1719
+ } else delete r[b];
1720
+ }
1721
+ if (touched) timeKfs.push(t);
1722
+ if (!touched || left) restKfs.push(r);
1723
+ }
1724
+ let restInitial = spec.initial;
1725
+ let start;
1726
+ if (hasInitial) {
1727
+ const { time, ...others } = spec.initial;
1728
+ start = typeof time === "number" && Number.isFinite(time) ? time : void 0;
1729
+ restInitial = others;
1730
+ }
1731
+ return { timeKfs, restKfs: restKfs.length ? restKfs : void 0, restInitial, start, speed };
1732
+ }
1733
+ function resolveFromEnd(remap, length, prop) {
1734
+ const fix = (v) => typeof v === "number" && v < 0 ? Math.max(0, length + v) : v;
1735
+ const timeKfs = remap.timeKfs.map((kf) => {
1736
+ const out = { ...kf };
1737
+ for (const b of BAGS) {
1738
+ const bag = kf[b];
1739
+ if (bag && prop in bag) out[b] = { ...bag, [prop]: fix(bag[prop]) };
1740
+ }
1741
+ return out;
1742
+ });
1743
+ return { ...remap, timeKfs, start: remap.start === void 0 ? void 0 : fix(remap.start) };
1744
+ }
1745
+ function remapOf(spec, prop, length) {
1746
+ const r = timeRemapOf(spec, prop);
1747
+ return r && length !== null ? resolveFromEnd(r, length, prop) : r;
1748
+ }
1749
+ function contentLength(remap, duration) {
1750
+ let m = duration;
1751
+ if (remap.speed !== void 0) m = Math.max(m, Math.abs(remap.speed) * duration);
1752
+ if (remap.start !== void 0) m = Math.max(m, remap.start + (remap.speed ?? 1) * duration);
1753
+ for (const kf of remap.timeKfs) {
1754
+ for (const b of BAGS) {
1755
+ const bag = kf[b];
1756
+ const v = bag && Object.values(bag)[0];
1757
+ if (typeof v === "number") m = Math.max(m, v);
1758
+ }
1759
+ }
1760
+ return m;
1761
+ }
1762
+ function bindClock(timeline, clock, prop, remap, duration, at, scope) {
1763
+ if (remap.timeKfs.length) {
1764
+ if (remap.start !== void 0) timeline.set(clock, { [prop]: remap.start }, at);
1765
+ applyKeyframes(timeline, clock, remap.timeKfs, duration, scope, [], at);
1766
+ return;
1767
+ }
1768
+ const s = remap.speed ?? 1;
1769
+ const from = remap.start ?? (s < 0 ? -s * duration : 0);
1770
+ timeline.fromTo(clock, { [prop]: from }, { [prop]: from + s * duration, duration, ease: "none" }, at);
1771
+ }
1772
+ function clockTable(remap, prop, duration, at, scope, to) {
1773
+ const probe = gsap.gsap.timeline({ paused: true });
1774
+ const clock = { [prop]: remap.start ?? 0 };
1775
+ bindClock(probe, clock, prop, remap, duration, at, scope);
1776
+ probe.time(probe.duration());
1777
+ probe.time(0);
1778
+ const h = 1 / 1e3;
1779
+ const n = Math.max(2, Math.ceil(to / h) + 2);
1780
+ const tab = new Float64Array(n);
1781
+ let min = Infinity, max = -Infinity;
1782
+ for (let k = 0; k < n; k++) {
1783
+ probe.time(k * h);
1784
+ const v = clock[prop];
1785
+ tab[k] = v;
1786
+ const t = k * h;
1787
+ if (t >= at && t <= at + duration) {
1788
+ if (v < min) min = v;
1789
+ if (v > max) max = v;
1790
+ }
1791
+ }
1792
+ probe.kill();
1793
+ return {
1794
+ at(t) {
1795
+ const x = Math.min(Math.max(t / h, 0), n - 1.000001);
1796
+ const k = Math.floor(x), f = x - k;
1797
+ return tab[k] * (1 - f) + tab[k + 1] * f;
1798
+ },
1799
+ min: Number.isFinite(min) ? min : 0,
1800
+ max: Number.isFinite(max) ? max : 0
1801
+ };
1802
+ }
1582
1803
 
1583
1804
  // src/space/math.ts
1584
1805
  var DEG = Math.PI / 180;
@@ -2108,7 +2329,7 @@ function validateGradientKeyframes(spec, who, warn = (m) => console.warn(m), kin
2108
2329
  const p = value;
2109
2330
  for (const k of Object.keys(p)) {
2110
2331
  if (PATCH_KEYS.includes(k)) continue;
2111
- const guess = chunkUQBGW5DO_cjs.suggestName(k, PATCH_KEYS.filter((x) => x !== "type"));
2332
+ const guess = chunkWTRKDIKH_cjs.suggestName(k, PATCH_KEYS.filter((x) => x !== "type"));
2112
2333
  say(`fillGradient.${k} is not a gradient property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${PATCH_KEYS.join(", ")}); it is ignored`);
2113
2334
  }
2114
2335
  if (p.type !== void 0 && start && p.type !== start.type) say("fillGradient.type cannot change over time (linear <-> radial); it is ignored");
@@ -2479,6 +2700,7 @@ var AudioSequence = class extends Sequence {
2479
2700
  _audioBuffer = null;
2480
2701
  _synth = null;
2481
2702
  _source = "";
2703
+ _remap = null;
2482
2704
  async build() {
2483
2705
  this.target = null;
2484
2706
  if (this.spec.music !== void 0) return this._buildMusic(this.spec);
@@ -2487,13 +2709,16 @@ var AudioSequence = class extends Sequence {
2487
2709
  this._source = `asset "${spec.asset}"`;
2488
2710
  const data = await PIXI.Assets.get(spec.asset);
2489
2711
  this._audioBuffer = data.audioBuffer;
2712
+ this._remap = remapOf(spec, "time", data.duration ?? null);
2713
+ const constant = this._remap && this._remap.timeKfs.length === 0 ? Math.abs(this._remap.speed ?? 1) : 1;
2490
2714
  if (this.duration === void 0) {
2491
2715
  const rest = Math.max(0, (this.parent?.duration ?? this.root.duration) - this.at);
2492
- this.duration = spec.duration ?? (spec.loop ? rest : data.duration) ?? this.root.duration;
2716
+ this.duration = spec.duration ?? (spec.loop ? rest : data.duration !== void 0 ? data.duration / constant : void 0) ?? this.root.duration;
2493
2717
  }
2494
- if (!spec.loop && data.duration !== void 0 && this.duration > data.duration + 0.05) {
2718
+ const playable = this._remap && this._remap.timeKfs.length > 0 ? Infinity : (data.duration ?? Infinity) / constant;
2719
+ if (!spec.loop && Number.isFinite(playable) && this.duration > playable + 0.05) {
2495
2720
  console.warn(
2496
- `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.`
2721
+ `pixi-effects: ${describeLayer(spec)}: audio asset "${spec.asset}" plays for ${playable.toFixed(1)}s (${data.duration.toFixed(1)}s at speed ${constant}) but the layer lasts ${this.duration.toFixed(1)}s, so it goes silent after ${playable.toFixed(1)}s. Add loop: true, or shorten the layer's duration.`
2497
2722
  );
2498
2723
  }
2499
2724
  }
@@ -2502,7 +2727,7 @@ var AudioSequence = class extends Sequence {
2502
2727
  const raw = spec;
2503
2728
  if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and sfx \u2014 the sfx plays; remove one`);
2504
2729
  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)`);
2505
- const { resolveSfx, renderSfx, sfxKey } = await import('./sfx-MZJZXYY3.cjs');
2730
+ const { resolveSfx, renderSfx, sfxKey } = await import('./sfx-EX4PUBMV.cjs');
2506
2731
  const sfx = resolveSfx(spec.sfx, spec.duration, who);
2507
2732
  if (!sfx) return;
2508
2733
  this.duration = sfx.length;
@@ -2521,7 +2746,7 @@ var AudioSequence = class extends Sequence {
2521
2746
  const raw = spec;
2522
2747
  if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and music \u2014 the music plays; remove one`);
2523
2748
  if (raw.sfx !== void 0) console.warn(`pixi-effects: ${who}: has both sfx and music \u2014 the music plays; remove one`);
2524
- const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-3OF5C6X5.cjs');
2749
+ const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-UAN7DXK4.cjs');
2525
2750
  const music = resolveMusic(spec.music, who);
2526
2751
  if (!music) return;
2527
2752
  const natural = musicLength(music);
@@ -2547,7 +2772,9 @@ var AudioSequence = class extends Sequence {
2547
2772
  const initialVolume = this.spec.volume ?? this.spec.initial?.volume ?? 1;
2548
2773
  const t0 = baseTime + this.at;
2549
2774
  const dur = this.duration;
2775
+ const sourceMap = this._remap ? clockTable(this._remap, "time", dur, t0, this.scope(), t0 + dur).at : void 0;
2550
2776
  out.push({
2777
+ ...sourceMap ? { sourceMap } : {},
2551
2778
  ...this._audioBuffer ? { buffer: this._audioBuffer } : { synth: this._synth },
2552
2779
  layer: describeLayer(this.spec),
2553
2780
  source: this._source,
@@ -2606,7 +2833,7 @@ var FrameCache = class {
2606
2833
  this.capacity = options.capacity ?? 30;
2607
2834
  }
2608
2835
  _key(time) {
2609
- return Math.round(time * 1e3);
2836
+ return Math.round(time * 1e6);
2610
2837
  }
2611
2838
  async getFrameAt(time) {
2612
2839
  const key = this._key(time);
@@ -2671,6 +2898,15 @@ var FrameCache = class {
2671
2898
  }
2672
2899
  };
2673
2900
 
2901
+ // src/core/sourceTime.ts
2902
+ var FRAME_EPS = 5e-5;
2903
+ function sourceLookup(v, sourceDuration, loop) {
2904
+ if (!(sourceDuration > 0)) return Math.max(0, v);
2905
+ const w = v + FRAME_EPS;
2906
+ if (loop) return (w % sourceDuration + sourceDuration) % sourceDuration;
2907
+ return Math.min(Math.max(w, 0), sourceDuration);
2908
+ }
2909
+
2674
2910
  // src/sequences/Video.ts
2675
2911
  var VideoSequence = class extends Sequence {
2676
2912
  _sourceDuration = 0;
@@ -2679,9 +2915,15 @@ var VideoSequence = class extends Sequence {
2679
2915
  _canvas = null;
2680
2916
  _ctx = null;
2681
2917
  _drawSeq = 0;
2918
+ /** The playhead value (seconds in the file) the timeline last wrote: already remapped, and local inside a remapped composition. */
2919
+ _cur = 0;
2920
+ _remap = null;
2921
+ /** Movie asks this layer to draw the frame its own timeline is at, not the one for a time it computed. */
2922
+ selfTimed = true;
2682
2923
  async build() {
2683
2924
  const data = await PIXI.Assets.get(this.spec.asset);
2684
2925
  this._sourceDuration = data.duration;
2926
+ this._remap = remapOf(this.spec, "currentTime", this._sourceDuration);
2685
2927
  this._audioBuffer = this.spec.audio !== false ? data.audioBuffer : null;
2686
2928
  this._cache = new FrameCache(data.sink, { capacity: 30 });
2687
2929
  const probeFrame = await this._cache.getFrameAt(0);
@@ -2700,7 +2942,8 @@ var VideoSequence = class extends Sequence {
2700
2942
  this.intrinsicWidth = w;
2701
2943
  this.intrinsicHeight = h;
2702
2944
  if (this.duration === void 0) {
2703
- this.duration = this.spec.duration ?? data.duration ?? this.root.duration;
2945
+ const constant = this._remap && this._remap.timeKfs.length === 0 ? Math.abs(this._remap.speed ?? 1) : 1;
2946
+ this.duration = this.spec.duration ?? (data.duration ?? this.root.duration * constant) / constant;
2704
2947
  }
2705
2948
  let currentTime = 0;
2706
2949
  const seq = this;
@@ -2710,8 +2953,9 @@ var VideoSequence = class extends Sequence {
2710
2953
  },
2711
2954
  set(v) {
2712
2955
  currentTime = v;
2956
+ seq._cur = v;
2713
2957
  const mySeq = ++seq._drawSeq;
2714
- const lookup = seq.spec.loop && seq._sourceDuration > 0 ? v % seq._sourceDuration : v;
2958
+ const lookup = sourceLookup(v, seq._sourceDuration, !!seq.spec.loop);
2715
2959
  seq._cache.getFrameAt(lookup).then((frame) => {
2716
2960
  if (mySeq !== seq._drawSeq) return;
2717
2961
  if (!frame || !seq._ctx) return;
@@ -2724,21 +2968,33 @@ var VideoSequence = class extends Sequence {
2724
2968
  });
2725
2969
  this.buildFilters();
2726
2970
  }
2971
+ displayProps() {
2972
+ const r = this._remap;
2973
+ return r ? { initial: r.restInitial, keyframes: r.restKfs } : super.displayProps();
2974
+ }
2727
2975
  bindTimeline(timeline, offset = 0) {
2728
2976
  super.bindTimeline(timeline, offset);
2977
+ const at = offset + this.at;
2978
+ if (this._remap) {
2979
+ bindClock(timeline, this.target, "currentTime", this._remap, this.duration, at, this.scope());
2980
+ return;
2981
+ }
2729
2982
  const playDuration = this.spec.loop ? this.duration : Math.min(this.duration, this._sourceDuration);
2730
2983
  timeline.fromTo(
2731
2984
  this.target,
2732
2985
  { currentTime: 0 },
2733
2986
  { currentTime: playDuration, duration: playDuration, ease: "none" },
2734
- offset + this.at
2987
+ at
2735
2988
  // offset = start of the enclosing composition(s); dropping it misplaced videos inside nested compositions
2736
2989
  );
2737
2990
  }
2738
2991
  collectAudio(out, baseTime) {
2739
2992
  if (!this._audioBuffer) return;
2740
2993
  const initialVolume = this.spec.volume ?? 1;
2994
+ const t0 = baseTime + this.at;
2995
+ const sourceMap = this._remap ? clockTable(this._remap, "currentTime", this.duration, t0, this.scope(), t0 + this.duration).at : void 0;
2741
2996
  out.push({
2997
+ ...sourceMap ? { sourceMap } : {},
2742
2998
  buffer: this._audioBuffer,
2743
2999
  layer: describeLayer(this.spec),
2744
3000
  source: `video "${this.spec.asset}"`,
@@ -2749,9 +3005,12 @@ var VideoSequence = class extends Sequence {
2749
3005
  volumeKeyframes: []
2750
3006
  });
2751
3007
  }
2752
- async awaitFrameAt(time) {
3008
+ /** Draw the frame the playhead is at NOW (`_cur`). The argument (a time Movie worked out for layers that need it) is not used. */
3009
+ async awaitFrameAt(_time) {
3010
+ const time = this._cur;
3011
+ if (!this.keepHidden && !this.target?.renderable) return;
2753
3012
  const mySeq = ++this._drawSeq;
2754
- const lookup = this.spec.loop && this._sourceDuration > 0 ? time % this._sourceDuration : time;
3013
+ const lookup = sourceLookup(time, this._sourceDuration, !!this.spec.loop);
2755
3014
  let frame = null;
2756
3015
  try {
2757
3016
  frame = await this._cache.getFrameAt(lookup);
@@ -3481,7 +3740,7 @@ function numOrZero(v, scope) {
3481
3740
  var _CompositionSequence = null;
3482
3741
  async function getCompositionSequence() {
3483
3742
  if (!_CompositionSequence) {
3484
- _CompositionSequence = (await import('./Composition-KYNPRWK3.cjs')).CompositionSequence;
3743
+ _CompositionSequence = (await import('./Composition-LL2XXU5N.cjs')).CompositionSequence;
3485
3744
  }
3486
3745
  return _CompositionSequence;
3487
3746
  }
@@ -3525,11 +3784,14 @@ exports.DEG = DEG;
3525
3784
  exports.NEAR = NEAR;
3526
3785
  exports.NullSequence = NullSequence;
3527
3786
  exports.Sequence = Sequence;
3787
+ exports.bindClock = bindClock;
3528
3788
  exports.buildScope = buildScope;
3529
3789
  exports.buildSequenceTree = buildSequenceTree;
3530
3790
  exports.cameraBasis = cameraBasis;
3531
3791
  exports.checkEase = checkEase;
3792
+ exports.clockTable = clockTable;
3532
3793
  exports.collectPropKeys = collectPropKeys;
3794
+ exports.contentLength = contentLength;
3533
3795
  exports.describeLayer = describeLayer;
3534
3796
  exports.ensureFilterLibrary = ensureFilterLibrary;
3535
3797
  exports.evaluateExpr = evaluateExpr;
@@ -3544,7 +3806,9 @@ exports.projectLayer = projectLayer;
3544
3806
  exports.rand = rand;
3545
3807
  exports.random = random;
3546
3808
  exports.registerSequenceType = registerSequenceType;
3809
+ exports.remapOf = remapOf;
3547
3810
  exports.resolveAt = resolveAt;
3548
3811
  exports.summarizeWarnings = summarizeWarnings;
3549
- //# sourceMappingURL=chunk-VTBT6NNE.cjs.map
3550
- //# sourceMappingURL=chunk-VTBT6NNE.cjs.map
3812
+ exports.timeRemapOf = timeRemapOf;
3813
+ //# sourceMappingURL=chunk-NO26HWR7.cjs.map
3814
+ //# sourceMappingURL=chunk-NO26HWR7.cjs.map