pixi-effects 0.18.1 → 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 (70) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +6 -6
  3. package/ai/CHAT.md +1 -1
  4. package/ai/SKILL.md +2 -1
  5. package/ai/chat-template.html +4 -4
  6. package/ai/reference/cheatsheet.md +10 -9
  7. package/ai/reference/pitfalls.md +9 -0
  8. package/ai/reference/recipes.md +117 -0
  9. package/ai/template.html +5 -5
  10. package/ai/tools/check.mjs +29 -6
  11. package/dist/{Base-BQUodYZK.d.ts → Base-CzfLGoDr.d.ts} +9 -1
  12. package/dist/{Base-OHqp8qvX.d.cts → Base-wYJQ2g5Q.d.cts} +9 -1
  13. package/dist/Composition-6PA4C4TV.js +5 -0
  14. package/dist/{Composition-MLBHYYFF.js.map → Composition-6PA4C4TV.js.map} +1 -1
  15. package/dist/Composition-LL2XXU5N.cjs +14 -0
  16. package/dist/{Composition-6JMXWNVV.cjs.map → Composition-LL2XXU5N.cjs.map} +1 -1
  17. package/dist/Controller.cjs +2 -2
  18. package/dist/Controller.d.cts +2 -2
  19. package/dist/Controller.d.ts +2 -2
  20. package/dist/Controller.js +1 -1
  21. package/dist/{Movie-DlHQ9C02.d.ts → Movie-CBxGH5ms.d.ts} +13 -2
  22. package/dist/{Movie-K2YuVAdg.d.cts → Movie-CzzJw8A-.d.cts} +13 -2
  23. package/dist/Presenter.cjs +2 -2
  24. package/dist/Presenter.d.cts +2 -2
  25. package/dist/Presenter.d.ts +2 -2
  26. package/dist/Presenter.js +1 -1
  27. package/dist/{chunk-SG3Y66FN.js → chunk-DQHXVNOI.js} +1029 -64
  28. package/dist/chunk-DQHXVNOI.js.map +1 -0
  29. package/dist/{chunk-JW67MDHQ.cjs → chunk-FFYA3UBU.cjs} +144 -36
  30. package/dist/chunk-FFYA3UBU.cjs.map +1 -0
  31. package/dist/{chunk-DKGG7WD4.js → chunk-MNM56B3X.js} +6 -3
  32. package/dist/chunk-MNM56B3X.js.map +1 -0
  33. package/dist/{chunk-ANO5R2E6.cjs → chunk-NO26HWR7.cjs} +1036 -65
  34. package/dist/chunk-NO26HWR7.cjs.map +1 -0
  35. package/dist/{chunk-UXDR3ZDU.js → chunk-TSPMKV2F.js} +121 -13
  36. package/dist/chunk-TSPMKV2F.js.map +1 -0
  37. package/dist/{chunk-UQBGW5DO.cjs → chunk-WTRKDIKH.cjs} +6 -3
  38. package/dist/chunk-WTRKDIKH.cjs.map +1 -0
  39. package/dist/index.cjs +246 -96
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +4 -4
  42. package/dist/index.d.ts +4 -4
  43. package/dist/index.js +187 -37
  44. package/dist/index.js.map +1 -1
  45. package/dist/loader.css +1 -1
  46. package/dist/{music-4HDFULNR.js → music-CFEC7HKL.js} +3 -3
  47. package/dist/{music-4HDFULNR.js.map → music-CFEC7HKL.js.map} +1 -1
  48. package/dist/{music-3OF5C6X5.cjs → music-UAN7DXK4.cjs} +7 -7
  49. package/dist/{music-3OF5C6X5.cjs.map → music-UAN7DXK4.cjs.map} +1 -1
  50. package/dist/{sfx-MZJZXYY3.cjs → sfx-EX4PUBMV.cjs} +8 -8
  51. package/dist/{sfx-MZJZXYY3.cjs.map → sfx-EX4PUBMV.cjs.map} +1 -1
  52. package/dist/{sfx-S37DDC66.js → sfx-U3GM2MED.js} +3 -3
  53. package/dist/{sfx-S37DDC66.js.map → sfx-U3GM2MED.js.map} +1 -1
  54. package/dist/three.cjs +6 -6
  55. package/dist/three.d.cts +2 -2
  56. package/dist/three.d.ts +2 -2
  57. package/dist/three.js +2 -2
  58. package/dist/{types-SS51lP6O.d.ts → types-YmUaViL1.d.cts} +40 -6
  59. package/dist/{types-SS51lP6O.d.cts → types-YmUaViL1.d.ts} +40 -6
  60. package/docs/dsl.md +58 -5
  61. package/llms-full.txt +196 -15
  62. package/package.json +3 -1
  63. package/dist/Composition-6JMXWNVV.cjs +0 -14
  64. package/dist/Composition-MLBHYYFF.js +0 -5
  65. package/dist/chunk-ANO5R2E6.cjs.map +0 -1
  66. package/dist/chunk-DKGG7WD4.js.map +0 -1
  67. package/dist/chunk-JW67MDHQ.cjs.map +0 -1
  68. package/dist/chunk-SG3Y66FN.js.map +0 -1
  69. package/dist/chunk-UQBGW5DO.cjs.map +0 -1
  70. package/dist/chunk-UXDR3ZDU.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
 
@@ -58,6 +58,113 @@ function noise(x, seed = 0) {
58
58
  const u = f * f * f * (f * (f * 6 - 15) + 10);
59
59
  return a + (b - a) * u;
60
60
  }
61
+ var SETTLE_TOL = 5e-3;
62
+ var DEFAULTS = { mass: 1, stiffness: 100, damping: 10 };
63
+ function springResponse(t, { mass: m, stiffness: k, damping: c }) {
64
+ if (t <= 0) return 0;
65
+ const w0 = Math.sqrt(k / m);
66
+ const zeta = c / (2 * Math.sqrt(k * m));
67
+ if (Math.abs(zeta - 1) < 1e-6) return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
68
+ if (zeta < 1) {
69
+ const wd = w0 * Math.sqrt(1 - zeta * zeta);
70
+ return 1 - Math.exp(-zeta * w0 * t) * (Math.cos(wd * t) + zeta * w0 / wd * Math.sin(wd * t));
71
+ }
72
+ const s = w0 * Math.sqrt(zeta * zeta - 1);
73
+ const r1 = -zeta * w0 + s, r2 = -zeta * w0 - s;
74
+ return 1 - (r2 * Math.exp(r1 * t) - r1 * Math.exp(r2 * t)) / (r2 - r1);
75
+ }
76
+ function complete(p) {
77
+ return { mass: p.mass ?? DEFAULTS.mass, stiffness: p.stiffness ?? DEFAULTS.stiffness, damping: p.damping ?? DEFAULTS.damping };
78
+ }
79
+ function springDuration(params = {}, tol = SETTLE_TOL) {
80
+ const p = complete(params);
81
+ const w0 = Math.sqrt(p.stiffness / p.mass);
82
+ const zeta = p.damping / (2 * Math.sqrt(p.stiffness * p.mass));
83
+ const bad = (t) => Math.abs(1 - springResponse(t, p)) > tol;
84
+ let lo, hi;
85
+ if (zeta < 1 - 1e-6) {
86
+ const wd = w0 * Math.sqrt(1 - zeta * zeta);
87
+ const decay = Math.PI * zeta / Math.sqrt(1 - zeta * zeta);
88
+ const n = Math.max(1, Math.ceil(Math.log(1 / tol) / decay));
89
+ lo = (n - 1) * Math.PI / wd;
90
+ hi = n * Math.PI / wd;
91
+ } else {
92
+ lo = 0;
93
+ hi = 1 / w0;
94
+ while (bad(hi)) {
95
+ lo = hi;
96
+ hi *= 2;
97
+ }
98
+ }
99
+ for (let i = 0; i < 60; i++) {
100
+ const mid = (lo + hi) / 2;
101
+ if (bad(mid)) lo = mid;
102
+ else hi = mid;
103
+ }
104
+ return hi;
105
+ }
106
+ function springEase(params = {}) {
107
+ const sp = complete(params);
108
+ const T = springDuration(sp);
109
+ const end = springResponse(T, sp);
110
+ return (p) => p <= 0 ? 0 : p >= 1 ? 1 : springResponse(p * T, sp) / end;
111
+ }
112
+ var SPRING_PRESETS = {
113
+ gentle: { mass: 1, stiffness: 100, damping: 15 },
114
+ // zeta .75, one soft overshoot
115
+ snappy: { mass: 1, stiffness: 300, damping: 28 },
116
+ // zeta .81, quick, barely rings
117
+ bouncy: { mass: 1, stiffness: 170, damping: 10 },
118
+ // zeta .38
119
+ wobbly: { mass: 1, stiffness: 120, damping: 6 },
120
+ // zeta .27, rings several times
121
+ slow: { mass: 1, stiffness: 60, damping: 18 }
122
+ // zeta 1.16, no overshoot
123
+ };
124
+ var SPRING_RE = /^spring(?:\.(\w+)|\(([^)]*)\))?$/;
125
+ function springProblem(name) {
126
+ const m = SPRING_RE.exec(name.trim());
127
+ if (!m) return 'expected "spring(mass, stiffness, damping)" or "spring.<preset>"';
128
+ if (m[1] && !SPRING_PRESETS[m[1]]) return `no preset "${m[1]}" (presets: ${Object.keys(SPRING_PRESETS).join(", ")})`;
129
+ if (m[2] !== void 0 && m[2].trim() !== "") {
130
+ const nums = m[2].split(",").map((s) => Number(s.trim()));
131
+ if (nums.length > 3 || nums.some((n) => !Number.isFinite(n) || n <= 0)) return "mass, stiffness and damping must be 1 to 3 numbers greater than 0";
132
+ }
133
+ return null;
134
+ }
135
+ function parseSpring(name) {
136
+ if (typeof name !== "string" || !/^\s*spring/.test(name) || springProblem(name)) return null;
137
+ const m = SPRING_RE.exec(name.trim());
138
+ if (m[1]) return SPRING_PRESETS[m[1]];
139
+ const [mass, stiffness, damping] = (m[2] ?? "").split(",").map((s) => s.trim()).filter(Boolean).map(Number);
140
+ return complete({ mass, stiffness, damping });
141
+ }
142
+ var registered = false;
143
+ function registerSpringEases() {
144
+ if (registered) return;
145
+ registered = true;
146
+ const base = springEase();
147
+ const ease = (p) => base(p);
148
+ ease.config = (mass, stiffness, damping) => {
149
+ const bad = [mass, stiffness, damping].some((v) => v !== void 0 && !(typeof v === "number" && v > 0 && Number.isFinite(v)));
150
+ return springEase(bad ? {} : { mass, stiffness, damping });
151
+ };
152
+ gsap.gsap.registerEase("spring", ease);
153
+ for (const [name, p] of Object.entries(SPRING_PRESETS)) gsap.gsap.registerEase(`spring.${name}`, springEase(p));
154
+ }
155
+ var autoWarned = /* @__PURE__ */ new Set();
156
+ function kfDuration(kf) {
157
+ if (kf.duration !== "auto") return kf.duration ?? 0;
158
+ const sp = parseSpring(kf.ease);
159
+ if (sp) return Math.round(springDuration(sp) * 1e3) / 1e3;
160
+ const name = String(kf.ease ?? "none");
161
+ if (!/^\s*spring/.test(name) && !autoWarned.has(name)) {
162
+ autoWarned.add(name);
163
+ console.warn(`pixi-effects: keyframe duration 'auto' needs a spring ease (ease: 'spring(1, 170, 12)' or 'spring.bouncy'), got ease ${JSON.stringify(name)} \u2014 using 0.5 s.`);
164
+ }
165
+ return 0.5;
166
+ }
167
+ registerSpringEases();
61
168
 
62
169
  // src/expr/Parser.ts
63
170
  var FUNCS = {
@@ -310,11 +417,77 @@ function looksLikeColorString(s) {
310
417
  if (s.charCodeAt(0) === 35) return true;
311
418
  return /^(rgb|hsl|oklab|oklch|lab|lch)\b/i.test(s);
312
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
313
472
  var FAMILIES = ["power1", "power2", "power3", "power4", "back", "elastic", "bounce", "circ", "expo", "sine"];
314
473
  var EASE_NAMES = ["none", ...FAMILIES.flatMap((f) => [`${f}.in`, `${f}.out`, `${f}.inOut`])];
315
474
  var warned = /* @__PURE__ */ new Set();
316
475
  function checkEase(ease, where) {
317
476
  if (typeof ease !== "string") return;
477
+ if (/^\s*spring/.test(ease)) {
478
+ const problem = springProblem(ease);
479
+ if (!problem || warned.has(ease)) return;
480
+ warned.add(ease);
481
+ console.warn(`pixi-effects: ${where}: unknown ease "${ease}" \u2014 ${problem}. GSAP would run it as its default ease, power1.out.`);
482
+ return;
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
+ }
318
491
  const found = gsap.gsap.parseEase(ease);
319
492
  if (typeof found === "function") return;
320
493
  if (warned.has(ease)) return;
@@ -327,7 +500,7 @@ function checkEase(ease, where) {
327
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`);
328
501
  return;
329
502
  }
330
- const guess = chunkUQBGW5DO_cjs.suggestName(ease, EASE_NAMES);
503
+ const guess = chunkWTRKDIKH_cjs.suggestName(ease, EASE_NAMES);
331
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)`);
332
505
  }
333
506
 
@@ -355,7 +528,7 @@ function resolveAt(at, duration) {
355
528
  }
356
529
  function normalizeKeyframe(kf, parentDuration) {
357
530
  const at = resolveAt(kf.at, parentDuration);
358
- const duration = kf.duration ?? 0;
531
+ const duration = kfDuration(kf);
359
532
  const ease = kf.ease ?? "none";
360
533
  checkEase(ease, "a keyframe");
361
534
  const hasSet = !!kf.set;
@@ -785,8 +958,217 @@ var ChromaKeyFilter = class extends PIXI.Filter {
785
958
  u[2] = rgb[2];
786
959
  }
787
960
  };
961
+ var HASH_GL = `
962
+ uint hash(uint x) { x ^= x >> 16; x *= 0x7feb352du; x ^= x >> 15; x *= 0x846ca68bu; x ^= x >> 16; return x; }
963
+ // one gaussian-ish value (sum of the four bytes of a hash), mean 0, std 1
964
+ float gauss(ivec2 p, uint k) {
965
+ uint h = hash(uint(p.x) + hash(uint(p.y) + hash(k)));
966
+ float s = float(h & 255u) + float((h >> 8) & 255u) + float((h >> 16) & 255u) + float(h >> 24);
967
+ return (s / 255.0 - 2.0) * 1.7320508;
968
+ }
969
+ // the grain value at a pixel: cells of size pixels, smoothly blended
970
+ float cell(vec2 pos, float size, uint k) {
971
+ vec2 q = pos / size - 0.5;
972
+ vec2 i = floor(q);
973
+ vec2 f = fract(q);
974
+ ivec2 p = ivec2(i);
975
+ float a = gauss(p, k), b = gauss(p + ivec2(1, 0), k), c = gauss(p + ivec2(0, 1), k), d = gauss(p + ivec2(1, 1), k);
976
+ float v = mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
977
+ vec2 wx = vec2(1.0 - f.x, f.x), wy = vec2(1.0 - f.y, f.y);
978
+ return v * inversesqrt(dot(wx, wx) * dot(wy, wy)); // blending lowers the spread: keep std exactly 1 at every pixel
979
+ }`;
980
+ var GL_VERTEX = `#version 300 es
981
+ in vec2 aPosition;
982
+ out vec2 vTextureCoord;
983
+ uniform vec4 uInputSize;
984
+ uniform vec4 uOutputFrame;
985
+ uniform vec4 uOutputTexture;
986
+ void main(void) {
987
+ vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;
988
+ position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;
989
+ position.y = position.y * (2.0 * uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;
990
+ gl_Position = vec4(position, 0.0, 1.0);
991
+ vTextureCoord = aPosition * (uOutputFrame.zw * uInputSize.zw);
992
+ }`;
993
+ var GL_FRAGMENT2 = `#version 300 es
994
+ precision highp float;
995
+ precision highp int;
996
+ in vec2 vTextureCoord;
997
+ uniform sampler2D uTexture;
998
+ uniform vec4 uInputSize;
999
+ uniform float uAmount;
1000
+ uniform float uSize;
1001
+ uniform float uSeed;
1002
+ uniform float uFrame;
1003
+ uniform float uColor;
1004
+ out vec4 finalColor;
1005
+ ${HASH_GL}
1006
+ void main(void) {
1007
+ vec4 raw = texture(uTexture, vTextureCoord);
1008
+ vec3 rgb = raw.a > 0.0 ? raw.rgb / raw.a : vec3(0.0);
1009
+ float L = dot(rgb, vec3(0.2126, 0.7152, 0.0722));
1010
+ float w = clamp(4.0 * L * (1.0 - L), 0.0, 1.0);
1011
+ vec2 pos = vTextureCoord * uInputSize.xy;
1012
+ uint k = hash(uint(uSeed) * 0x9E3779B1u + uint(uFrame));
1013
+ float n0 = cell(pos, uSize, k);
1014
+ vec3 n = vec3(n0);
1015
+ if (uColor > 0.0) {
1016
+ vec3 nc = vec3(cell(pos, uSize, k + 1u), cell(pos, uSize, k + 2u), cell(pos, uSize, k + 3u));
1017
+ n = mix(n, nc, uColor);
1018
+ }
1019
+ rgb = clamp(rgb + n * (uAmount * w), 0.0, 1.0);
1020
+ finalColor = vec4(rgb * raw.a, raw.a);
1021
+ }`;
1022
+ var WGSL_SOURCE2 = `
1023
+ struct GlobalFilterUniforms {
1024
+ uInputSize: vec4<f32>,
1025
+ uInputPixel: vec4<f32>,
1026
+ uInputClamp: vec4<f32>,
1027
+ uOutputFrame: vec4<f32>,
1028
+ uGlobalFrame: vec4<f32>,
1029
+ uOutputTexture: vec4<f32>,
1030
+ };
1031
+ struct GrainUniforms {
1032
+ uAmount: f32,
1033
+ uSize: f32,
1034
+ uSeed: f32,
1035
+ uFrame: f32,
1036
+ uColor: f32,
1037
+ };
1038
+ @group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms;
1039
+ @group(0) @binding(1) var uTexture: texture_2d<f32>;
1040
+ @group(0) @binding(2) var uSampler : sampler;
1041
+ @group(1) @binding(0) var<uniform> grainUniforms : GrainUniforms;
1042
+
1043
+ struct VSOutput {
1044
+ @builtin(position) position: vec4<f32>,
1045
+ @location(0) uv : vec2<f32>,
1046
+ };
1047
+ fn filterVertexPosition(aPosition : vec2<f32>) -> vec4<f32> {
1048
+ var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;
1049
+ position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;
1050
+ position.y = position.y * (2.0 * gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;
1051
+ return vec4<f32>(position, 0.0, 1.0);
1052
+ }
1053
+ fn filterTextureCoord(aPosition : vec2<f32>) -> vec2<f32> {
1054
+ return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);
1055
+ }
1056
+ @vertex
1057
+ fn mainVertex(
1058
+ @location(0) aPosition : vec2<f32>,
1059
+ ) -> VSOutput {
1060
+ return VSOutput(
1061
+ filterVertexPosition(aPosition),
1062
+ filterTextureCoord(aPosition),
1063
+ );
1064
+ }
1065
+
1066
+ fn hash(xIn: u32) -> u32 {
1067
+ var x = xIn;
1068
+ x = x ^ (x >> 16u); x = x * 0x7feb352du; x = x ^ (x >> 15u); x = x * 0x846ca68bu; x = x ^ (x >> 16u);
1069
+ return x;
1070
+ }
1071
+ fn gauss(p: vec2<i32>, k: u32) -> f32 {
1072
+ let h = hash(u32(p.x) + hash(u32(p.y) + hash(k)));
1073
+ let s = f32(h & 255u) + f32((h >> 8u) & 255u) + f32((h >> 16u) & 255u) + f32(h >> 24u);
1074
+ return (s / 255.0 - 2.0) * 1.7320508;
1075
+ }
1076
+ fn cell(pos: vec2<f32>, size: f32, k: u32) -> f32 {
1077
+ let q = pos / size - vec2<f32>(0.5);
1078
+ let i = floor(q);
1079
+ let f = fract(q);
1080
+ let p = vec2<i32>(i);
1081
+ let a = gauss(p, k);
1082
+ let b = gauss(p + vec2<i32>(1, 0), k);
1083
+ let c = gauss(p + vec2<i32>(0, 1), k);
1084
+ let d = gauss(p + vec2<i32>(1, 1), k);
1085
+ let v = mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
1086
+ let wx = vec2<f32>(1.0 - f.x, f.x);
1087
+ let wy = vec2<f32>(1.0 - f.y, f.y);
1088
+ return v * inverseSqrt(dot(wx, wx) * dot(wy, wy));
1089
+ }
1090
+
1091
+ @fragment
1092
+ fn mainFragment(@location(0) uv : vec2<f32>) -> @location(0) vec4<f32> {
1093
+ let raw = textureSample(uTexture, uSampler, uv);
1094
+ var rgb = vec3<f32>(0.0);
1095
+ if (raw.a > 0.0) { rgb = raw.rgb / raw.a; }
1096
+ let L = dot(rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
1097
+ let w = clamp(4.0 * L * (1.0 - L), 0.0, 1.0);
1098
+ let pos = uv * gfu.uInputSize.xy;
1099
+ let k = hash(u32(grainUniforms.uSeed) * 0x9E3779B1u + u32(grainUniforms.uFrame));
1100
+ let n0 = cell(pos, grainUniforms.uSize, k);
1101
+ var n = vec3<f32>(n0);
1102
+ if (grainUniforms.uColor > 0.0) {
1103
+ let nc = vec3<f32>(cell(pos, grainUniforms.uSize, k + 1u), cell(pos, grainUniforms.uSize, k + 2u), cell(pos, grainUniforms.uSize, k + 3u));
1104
+ n = mix(n, nc, grainUniforms.uColor);
1105
+ }
1106
+ rgb = clamp(rgb + n * (grainUniforms.uAmount * w), vec3<f32>(0.0), vec3<f32>(1.0));
1107
+ return vec4<f32>(rgb * raw.a, raw.a);
1108
+ }`;
1109
+ var GRAIN_KEYS = ["amount", "size", "seed", "fps", "color"];
1110
+ var GrainFilter = class extends PIXI.Filter {
1111
+ _fps;
1112
+ constructor(options = {}) {
1113
+ chunkWTRKDIKH_cjs.warnUnknownOptions("grain filter", options, GRAIN_KEYS);
1114
+ const { amount = 0.08, size = 1.5, seed = 0, fps = 24, color = 0 } = options;
1115
+ super({
1116
+ glProgram: PIXI.GlProgram.from({ vertex: GL_VERTEX, fragment: GL_FRAGMENT2, name: "grain-filter" }),
1117
+ gpuProgram: PIXI.GpuProgram.from({ vertex: { source: WGSL_SOURCE2, entryPoint: "mainVertex" }, fragment: { source: WGSL_SOURCE2, entryPoint: "mainFragment" } }),
1118
+ resources: {
1119
+ grainUniforms: new PIXI.UniformGroup({
1120
+ uAmount: { value: amount, type: "f32" },
1121
+ uSize: { value: Math.max(size, 0.25), type: "f32" },
1122
+ uSeed: { value: Math.floor(seed), type: "f32" },
1123
+ uFrame: { value: 0, type: "f32" },
1124
+ uColor: { value: color, type: "f32" }
1125
+ })
1126
+ }
1127
+ });
1128
+ this._fps = fps;
1129
+ }
1130
+ get u() {
1131
+ return this.resources.grainUniforms.uniforms;
1132
+ }
1133
+ get amount() {
1134
+ return this.u.uAmount;
1135
+ }
1136
+ set amount(v) {
1137
+ this.u.uAmount = v;
1138
+ }
1139
+ get size() {
1140
+ return this.u.uSize;
1141
+ }
1142
+ set size(v) {
1143
+ this.u.uSize = Math.max(v, 0.25);
1144
+ }
1145
+ get seed() {
1146
+ return this.u.uSeed;
1147
+ }
1148
+ set seed(v) {
1149
+ this.u.uSeed = Math.floor(v);
1150
+ }
1151
+ get color() {
1152
+ return this.u.uColor;
1153
+ }
1154
+ set color(v) {
1155
+ this.u.uColor = v;
1156
+ }
1157
+ get fps() {
1158
+ return this._fps;
1159
+ }
1160
+ set fps(v) {
1161
+ this._fps = v;
1162
+ }
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. */
1164
+ setTime(t) {
1165
+ this.u.uFrame = this._fps > 0 ? Math.floor(t * this._fps + 1e-3) : 0;
1166
+ }
1167
+ };
1168
+
1169
+ // src/filters/named.ts
788
1170
  var CORE = { blur: "BlurFilter", noise: "NoiseFilter", alpha: "AlphaFilter" };
789
- var CORE_TYPES = [...Object.keys(CORE), "colorMatrix"];
1171
+ var CORE_TYPES = [...Object.keys(CORE), "colorMatrix", "grain"];
790
1172
  var LIBRARY_TYPES = [
791
1173
  "adjustment",
792
1174
  "advancedBloom",
@@ -836,7 +1218,7 @@ function isNamedFilterType(t) {
836
1218
  return !NOT_NAMED.has(t);
837
1219
  }
838
1220
  function unknownType(t) {
839
- const guess = chunkUQBGW5DO_cjs.suggestName(t, FILTER_TYPES);
1221
+ const guess = chunkWTRKDIKH_cjs.suggestName(t, FILTER_TYPES);
840
1222
  return new Error(`pixi-effects: unknown filter type "${t}"${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${[...FILTER_TYPES, "chromaKey", "custom"].join(", ")})`);
841
1223
  }
842
1224
  var library = null;
@@ -885,7 +1267,7 @@ function colorMatrix(params) {
885
1267
  const preset = params.preset;
886
1268
  if (preset !== void 0) {
887
1269
  if (!MATRIX_PRESETS.includes(preset)) {
888
- const guess = chunkUQBGW5DO_cjs.suggestName(preset, MATRIX_PRESETS);
1270
+ const guess = chunkWTRKDIKH_cjs.suggestName(preset, MATRIX_PRESETS);
889
1271
  throw new Error(`pixi-effects: colorMatrix preset "${preset}" is not one of ${MATRIX_PRESETS.join(", ")}${guess ? ` \u2014 did you mean "${guess}"?` : ""}`);
890
1272
  }
891
1273
  f[preset === "grayscale" ? "desaturate" : preset].call(f);
@@ -903,6 +1285,7 @@ function colorGradientParams(params) {
903
1285
  }
904
1286
  function createNamedFilter(type, params) {
905
1287
  if (lower(type) === "colormatrix") return colorMatrix(params);
1288
+ if (lower(type) === "grain") return new GrainFilter(params);
906
1289
  const core = Object.entries(CORE).find(([k]) => lower(k) === lower(type));
907
1290
  if (core) return new PIXI__namespace[core[1]](params);
908
1291
  if (!isLibraryType(type)) throw unknownType(type);
@@ -994,13 +1377,21 @@ var Sequence = class {
994
1377
  pathRouters() {
995
1378
  return {};
996
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
+ }
997
1387
  bindTimeline(timeline, offset = 0) {
998
1388
  if (!this.target) return;
999
1389
  const scope = this.scope();
1000
1390
  const routers = this.pathRouters();
1001
1391
  const startTime = offset + this.at;
1002
- applyInitial(this.target, this.spec.initial, scope, [], routers);
1003
- 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);
1004
1395
  const endTime = startTime + this.duration;
1005
1396
  this.absoluteStart = startTime;
1006
1397
  if (this.keepHidden) return;
@@ -1051,6 +1442,141 @@ function collectPropKeys(spec) {
1051
1442
  return out;
1052
1443
  }
1053
1444
 
1445
+ // src/core/layerKeys.ts
1446
+ var COMMON = ["type", "name", "parent", "at", "duration", "initial", "keyframes", "filters", "filterArea", "mask", "maskInverted", "blendMode", "threeD", "hideBehindCamera"];
1447
+ var VIDEO = [...COMMON, "asset", "loop", "audio", "volume", "speed"];
1448
+ var IMAGE = [...COMMON, "asset", "colorSpace"];
1449
+ var TEXT = [...COMMON, "text", "format", "style", "colorSpace", "fillGradient"];
1450
+ var AUDIO = [...COMMON, "volume", "asset", "loop", "sfx", "music", "speed"];
1451
+ var COMPOSITION = [...COMMON, "width", "height", "sequences", "transitions", "speed"];
1452
+ var CAMERA = [...COMMON];
1453
+ var NULL = [...COMMON];
1454
+ var SHAPE = [...COMMON, "shape", "fillGradient", "colorSpace", "strokeCap", "strokeJoin"];
1455
+ var TRIM = ["trimStart", "trimEnd", "trimEach"];
1456
+ var RECT = [...SHAPE, ...TRIM, "width", "height", "cornerRadius", "anchorX", "anchorY"];
1457
+ var CIRCLE = [...SHAPE, ...TRIM, "radius", "anchorX", "anchorY"];
1458
+ var ELLIPSE = [...SHAPE, ...TRIM, "radiusX", "radiusY", "anchorX", "anchorY"];
1459
+ var ARC = [...SHAPE, "radius", "innerRadius", "startAngle", "endAngle"];
1460
+ var LINE = [...SHAPE, ...TRIM, "from", "to"];
1461
+ var POLYGON = [...SHAPE, ...TRIM, "points", "open"];
1462
+ var PATH = [...SHAPE, ...TRIM, "d", "morphTo", "morph", "morphPoints"];
1463
+ var SHAPES = { rect: RECT, circle: CIRCLE, ellipse: ELLIPSE, arc: ARC, line: LINE, polygon: POLYGON, path: PATH };
1464
+ var BY_TYPE = { video: VIDEO, image: IMAGE, text: TEXT, audio: AUDIO, composition: COMPOSITION, camera: CAMERA, null: NULL };
1465
+ function kindName(spec) {
1466
+ return spec.type === "shape" && spec.shape ? `${spec.shape} shape` : `${spec.type} layer`;
1467
+ }
1468
+ function layerKeys(spec) {
1469
+ if (spec.type === "shape") return spec.shape && SHAPES[spec.shape] || null;
1470
+ return BY_TYPE[spec.type] ?? null;
1471
+ }
1472
+ function kindsWithKey(key) {
1473
+ const out = [];
1474
+ for (const [shape, keys] of Object.entries(SHAPES)) if (keys.includes(key)) out.push(`${shape} shape`);
1475
+ for (const [type, keys] of Object.entries(BY_TYPE)) if (keys.includes(key)) out.push(`${type} layer`);
1476
+ return out;
1477
+ }
1478
+ var PROP_KEYS = /* @__PURE__ */ new Set([
1479
+ // transform and look (GSAP + PixiPlugin names)
1480
+ "x",
1481
+ "y",
1482
+ "z",
1483
+ "alpha",
1484
+ "rotation",
1485
+ "rotationX",
1486
+ "rotationY",
1487
+ "width",
1488
+ "height",
1489
+ "visible",
1490
+ "autoAlpha",
1491
+ "scale",
1492
+ "scaleX",
1493
+ "scaleY",
1494
+ "anchor",
1495
+ "anchorX",
1496
+ "anchorY",
1497
+ "pivot",
1498
+ "pivotX",
1499
+ "pivotY",
1500
+ "skew",
1501
+ "skewX",
1502
+ "skewY",
1503
+ "position",
1504
+ "positionX",
1505
+ "positionY",
1506
+ "tint",
1507
+ "colorize",
1508
+ "colorizeAmount",
1509
+ "blur",
1510
+ "blurX",
1511
+ "blurY",
1512
+ "blurPadding",
1513
+ // shapes
1514
+ "fillColor",
1515
+ "fillAlpha",
1516
+ "strokeColor",
1517
+ "strokeAlpha",
1518
+ "strokeWidth",
1519
+ "lineColor",
1520
+ "lineAlpha",
1521
+ "cornerRadius",
1522
+ "radius",
1523
+ "radiusX",
1524
+ "radiusY",
1525
+ "innerRadius",
1526
+ "startAngle",
1527
+ "endAngle",
1528
+ "trimStart",
1529
+ "trimEnd",
1530
+ "trimEach",
1531
+ "morph",
1532
+ "fillGradient",
1533
+ "strokeCap",
1534
+ "strokeJoin",
1535
+ // text, audio, video
1536
+ "fill",
1537
+ "value",
1538
+ "visibleChars",
1539
+ "text",
1540
+ "volume",
1541
+ "time",
1542
+ // camera
1543
+ "lookAtX",
1544
+ "lookAtY",
1545
+ "lookAtZ",
1546
+ "fov",
1547
+ "offsetX",
1548
+ "offsetY",
1549
+ "offsetZ",
1550
+ "lookOffsetX",
1551
+ "lookOffsetY",
1552
+ "lookOffsetZ"
1553
+ ]);
1554
+ var STYLE_KEYS = [
1555
+ "fontSize",
1556
+ "fontFamily",
1557
+ "fontWeight",
1558
+ "fontStyle",
1559
+ "fontVariant",
1560
+ "fill",
1561
+ "stroke",
1562
+ "dropShadow",
1563
+ "letterSpacing",
1564
+ "lineHeight",
1565
+ "leading",
1566
+ "align",
1567
+ "wordWrap",
1568
+ "wordWrapWidth",
1569
+ "breakWords",
1570
+ "whiteSpace",
1571
+ "padding",
1572
+ "textBaseline",
1573
+ "trim",
1574
+ "lineJoin",
1575
+ "miterLimit",
1576
+ "filters",
1577
+ "tagStyles"
1578
+ ];
1579
+
1054
1580
  // src/core/lint.ts
1055
1581
  var defaultWarn = (m) => console.warn(m);
1056
1582
  function describeLayer(spec) {
@@ -1089,6 +1615,191 @@ function lintText(spec, warn = defaultWarn) {
1089
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').`);
1090
1616
  }
1091
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
+ }
1659
+ function lintKeys(spec, warn = defaultWarn) {
1660
+ const who = describeLayer(spec);
1661
+ const valid = layerKeys(spec);
1662
+ if (valid) {
1663
+ for (const key of Object.keys(spec)) {
1664
+ if (valid.includes(key) || spec[key] === void 0) continue;
1665
+ if (REMAPPABLE.has(spec.type) && REMAP_NAMES.has(key)) continue;
1666
+ const guess = chunkWTRKDIKH_cjs.suggestName(key, valid.filter((k) => k !== "type"));
1667
+ const owners = kindsWithKey(key);
1668
+ const belongs = owners.length ? ` (it belongs to ${owners.slice(0, 3).join(", ")})` : "";
1669
+ const hint = guess ? ` \u2014 did you mean "${guess}"?${belongs}` : belongs;
1670
+ warn(`pixi-effects: ${who}: "${key}" is not a ${kindName(spec)} key${hint}. Valid keys: ${valid.join(", ")}`);
1671
+ }
1672
+ }
1673
+ lintRemap(spec, warn);
1674
+ const said = /* @__PURE__ */ new Set();
1675
+ const bags = [spec.initial, ...(spec.keyframes ?? []).flatMap((kf) => [kf.set, kf.to, kf.from])];
1676
+ for (const bag of bags) {
1677
+ for (const key of Object.keys(bag ?? {})) {
1678
+ if (bag[key] === void 0) continue;
1679
+ if (PROP_KEYS.has(key) || key.includes(".") || /^gradient/i.test(key) || said.has(key) || key === "speed") continue;
1680
+ said.add(key);
1681
+ const guess = chunkWTRKDIKH_cjs.suggestName(key, [...PROP_KEYS]);
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`);
1683
+ }
1684
+ }
1685
+ if (spec.type === "text" && spec.style && typeof spec.style === "object") {
1686
+ for (const key of Object.keys(spec.style)) {
1687
+ if (STYLE_KEYS.includes(key) || spec.style[key] === void 0) continue;
1688
+ const guess = chunkWTRKDIKH_cjs.suggestName(key, STYLE_KEYS);
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(", ")}`);
1690
+ }
1691
+ }
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
+ }
1092
1803
 
1093
1804
  // src/space/math.ts
1094
1805
  var DEG = Math.PI / 180;
@@ -1414,6 +2125,7 @@ function shortestHueDelta(from, to) {
1414
2125
  }
1415
2126
 
1416
2127
  // src/expr/colorTween.ts
2128
+ var css = (c) => typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
1417
2129
  function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, at, colorSpace, onUpdate, loop = {}) {
1418
2130
  let interp = null;
1419
2131
  const proxy = { p: 0 };
@@ -1428,7 +2140,7 @@ function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, a
1428
2140
  onStart: () => {
1429
2141
  const start = fromValue !== void 0 ? fromValue : target[key];
1430
2142
  if (start === void 0) return;
1431
- interp = colorSpace === "rgb" ? gsap.gsap.utils.interpolate(start, toValue) : buildColorInterp(start, toValue, colorSpace);
2143
+ interp = colorSpace === "rgb" ? gsap.gsap.utils.interpolate(css(start), css(toValue)) : buildColorInterp(start, toValue, colorSpace);
1432
2144
  },
1433
2145
  onUpdate: () => {
1434
2146
  if (interp) {
@@ -1472,7 +2184,7 @@ var ImageSequence = class extends Sequence {
1472
2184
  if (initTint !== void 0) this.target.tint = initTint;
1473
2185
  for (const kf of this.spec.keyframes ?? []) {
1474
2186
  const at = startTime + resolveAt(kf.at, this.duration);
1475
- const duration = kf.duration ?? 0;
2187
+ const duration = kfDuration(kf);
1476
2188
  const ease = kf.ease ?? "none";
1477
2189
  const fromTint = kf.from?.tint;
1478
2190
  const toTint = kf.to?.tint;
@@ -1529,6 +2241,207 @@ function stripKeysInKeyframe(kf, keys) {
1529
2241
  from: stripKeys(kf.from, keys)
1530
2242
  };
1531
2243
  }
2244
+
2245
+ // src/sequences/gradient.ts
2246
+ function cssColor(c) {
2247
+ return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
2248
+ }
2249
+ function normalizedStops(g) {
2250
+ const stops = g.stops ?? [];
2251
+ if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
2252
+ return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
2253
+ }
2254
+ function gradientOptions(g) {
2255
+ const colorStops = normalizedStops(g);
2256
+ const a = (g.angle ?? 90) * Math.PI / 180;
2257
+ const dx = Math.cos(a), dy = Math.sin(a);
2258
+ const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2259
+ return {
2260
+ type: "linear",
2261
+ start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
2262
+ end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
2263
+ colorStops,
2264
+ textureSpace: "local"
2265
+ };
2266
+ }
2267
+ function paintRadial(ctx, size, g) {
2268
+ const stops = normalizedStops(g);
2269
+ const [cx, cy] = g.center ?? [0.5, 0.5];
2270
+ const grad = ctx.createRadialGradient(cx * size, cy * size, (g.innerRadius ?? 0) * size, cx * size, cy * size, (g.radius ?? 0.5) * size);
2271
+ for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
2272
+ ctx.fillStyle = grad;
2273
+ ctx.fillRect(0, 0, size, size);
2274
+ }
2275
+
2276
+ // src/sequences/gradientAnim.ts
2277
+ var PATCH_KEYS = ["type", "angle", "center", "innerRadius", "radius", "stops"];
2278
+ function gradStateFrom(g) {
2279
+ return {
2280
+ type: g.type ?? "linear",
2281
+ angle: g.angle ?? 90,
2282
+ center: [...g.center ?? [0.5, 0.5]],
2283
+ innerRadius: g.innerRadius ?? 0,
2284
+ radius: g.radius ?? 0.5,
2285
+ stops: (g.stops ?? []).map((s) => Array.isArray(s) ? { offset: s[0], color: cssColor(s[1]) } : { offset: s.offset, color: cssColor(s.color) })
2286
+ };
2287
+ }
2288
+ function mergeGrad(base, patch) {
2289
+ const out = { ...base, center: [...base.center], stops: base.stops.map((s) => ({ ...s })) };
2290
+ if (patch === null || typeof patch !== "object" || Array.isArray(patch)) return out;
2291
+ const p = patch;
2292
+ if (typeof p.angle === "number") out.angle = p.angle;
2293
+ if (Array.isArray(p.center)) out.center = [Number(p.center[0]), Number(p.center[1])];
2294
+ if (typeof p.innerRadius === "number") out.innerRadius = p.innerRadius;
2295
+ if (typeof p.radius === "number") out.radius = p.radius;
2296
+ if (p.stops !== void 0) {
2297
+ const next = gradStateFrom({ stops: p.stops }).stops;
2298
+ if (next.length === base.stops.length) out.stops = next;
2299
+ }
2300
+ return out;
2301
+ }
2302
+ var objectHint = "a keyframe's fillGradient must be an object like { angle: 200, stops: [[0, '#f00'], [1, '#00f']] }";
2303
+ function validateGradientKeyframes(spec, who, warn = (m) => console.warn(m), kind = "shape") {
2304
+ const said = /* @__PURE__ */ new Set();
2305
+ const say = (m) => {
2306
+ if (!said.has(m)) {
2307
+ said.add(m);
2308
+ warn(`pixi-effects: ${who}: ${m}`);
2309
+ }
2310
+ };
2311
+ const startSpec = spec.fillGradient ?? spec.initial?.fillGradient;
2312
+ const start = startSpec && typeof startSpec === "object" && Array.isArray(startSpec.stops) ? gradStateFrom(startSpec) : null;
2313
+ let animated = false;
2314
+ if (kind === "text") {
2315
+ const radial = (g) => !!g && typeof g === "object" && ["type", "center", "innerRadius", "radius"].some((k) => g[k] !== void 0 && !(k === "type" && g.type === "linear"));
2316
+ const asked = radial(startSpec) || (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => radial(b?.fillGradient)));
2317
+ if (asked) say("a text gradient is linear: type, center, innerRadius and radius are ignored (only angle and stops apply); use a shape for a radial gradient");
2318
+ }
2319
+ for (const kf of spec.keyframes ?? []) {
2320
+ for (const bag of [kf.set, kf.to, kf.from]) {
2321
+ if (!bag) continue;
2322
+ for (const [key, value] of Object.entries(bag)) {
2323
+ if (key === "fillGradient") {
2324
+ animated = true;
2325
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2326
+ say(objectHint);
2327
+ continue;
2328
+ }
2329
+ const p = value;
2330
+ for (const k of Object.keys(p)) {
2331
+ if (PATCH_KEYS.includes(k)) continue;
2332
+ const guess = chunkWTRKDIKH_cjs.suggestName(k, PATCH_KEYS.filter((x) => x !== "type"));
2333
+ say(`fillGradient.${k} is not a gradient property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${PATCH_KEYS.join(", ")}); it is ignored`);
2334
+ }
2335
+ if (p.type !== void 0 && start && p.type !== start.type) say("fillGradient.type cannot change over time (linear <-> radial); it is ignored");
2336
+ if (p.stops !== void 0 && start) {
2337
+ const n = Array.isArray(p.stops) ? p.stops.length : 0;
2338
+ if (n !== start.stops.length) say(`a fillGradient keyframe must give the same number of stops as the gradient has (${start.stops.length}, got ${n}); the stops are left as they were`);
2339
+ }
2340
+ } else if (/^fillGradient\./i.test(key) || /^gradient/i.test(key)) {
2341
+ const tail = key.replace(/^fillGradient\./i, "").replace(/^gradient/i, "");
2342
+ const sub = tail ? tail[0].toLowerCase() + tail.slice(1) : "angle";
2343
+ say(`"${key}" is not an animatable property: write fillGradient: { ${PATCH_KEYS.includes(sub) ? sub : "angle"} } (the whole gradient is one object) in the keyframe's to / from / set`);
2344
+ }
2345
+ }
2346
+ }
2347
+ }
2348
+ if (animated && !start) say("a keyframe animates fillGradient but the layer has none to start from: give it a fillGradient first (top level or in initial)");
2349
+ }
2350
+ function tweenGradient(timeline, holder, fromPatch, toPatch, duration, ease, at, colorSpace, onChange, loop = {}) {
2351
+ let a = null, b = null;
2352
+ let resting = null;
2353
+ let colors = [];
2354
+ const proxy = { p: 0 };
2355
+ timeline.fromTo(proxy, { p: 0 }, {
2356
+ p: 1,
2357
+ duration,
2358
+ ease,
2359
+ ...loop,
2360
+ onStart: () => {
2361
+ const live = holder.grad;
2362
+ a = fromPatch !== void 0 ? mergeGrad(live, fromPatch) : live;
2363
+ if (toPatch === void 0) resting ??= live;
2364
+ b = toPatch !== void 0 ? mergeGrad(a, toPatch) : resting;
2365
+ colors = a.stops.map((s, i) => colorSpace === "rgb" ? gsap.gsap.utils.interpolate(s.color, b.stops[i].color) : buildColorInterp(s.color, b.stops[i].color, colorSpace));
2366
+ },
2367
+ onUpdate: () => {
2368
+ if (!a || !b) return;
2369
+ const t = proxy.p;
2370
+ const l = (x, y) => x + (y - x) * t;
2371
+ holder.grad = {
2372
+ type: a.type,
2373
+ angle: l(a.angle, b.angle),
2374
+ center: [l(a.center[0], b.center[0]), l(a.center[1], b.center[1])],
2375
+ innerRadius: l(a.innerRadius, b.innerRadius),
2376
+ radius: l(a.radius, b.radius),
2377
+ stops: a.stops.map((s, i) => ({ offset: l(s.offset, b.stops[i].offset), color: colors[i](t) }))
2378
+ };
2379
+ onChange();
2380
+ }
2381
+ }, at);
2382
+ }
2383
+ var PAINT_SIZE = 512;
2384
+ var GradientPainter = class {
2385
+ constructor(size = PAINT_SIZE) {
2386
+ this.size = size;
2387
+ this.canvas.width = size;
2388
+ this.canvas.height = size;
2389
+ this.tex = PIXI.Texture.from(this.canvas);
2390
+ globalThis.__gradientPainters = (globalThis.__gradientPainters ?? 0) + 1;
2391
+ }
2392
+ size;
2393
+ canvas = document.createElement("canvas");
2394
+ tex;
2395
+ /** Paint `g` and return what `Graphics.fill(...)` takes. */
2396
+ paint(g) {
2397
+ const ctx = this.canvas.getContext("2d"), s = this.size;
2398
+ if (!ctx) return new PIXI.FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((x) => [x.offset, x.color]) }));
2399
+ ctx.clearRect(0, 0, s, s);
2400
+ if (g.type === "radial") {
2401
+ paintRadial(ctx, s, { stops: g.stops.map((x) => [x.offset, x.color]), center: g.center, innerRadius: g.innerRadius, radius: g.radius });
2402
+ } else {
2403
+ const a = g.angle * Math.PI / 180, dx = Math.cos(a), dy = Math.sin(a);
2404
+ const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2405
+ const grad = ctx.createLinearGradient((0.5 - dx * h) * s, (0.5 - dy * h) * s, (0.5 + dx * h) * s, (0.5 + dy * h) * s);
2406
+ for (const st of g.stops) grad.addColorStop(Math.min(1, Math.max(0, st.offset)), st.color);
2407
+ ctx.fillStyle = grad;
2408
+ ctx.fillRect(0, 0, s, s);
2409
+ }
2410
+ this.tex.source.update();
2411
+ return { texture: this.tex, textureSpace: "local" };
2412
+ }
2413
+ /** Give the texture and the canvas back (a layer that is destroyed must not leave them). */
2414
+ destroy() {
2415
+ this.tex.destroy(true);
2416
+ this.canvas.width = this.canvas.height = 0;
2417
+ globalThis.__gradientPainters = Math.max(0, (globalThis.__gradientPainters ?? 1) - 1);
2418
+ }
2419
+ };
2420
+ function bindGradientKeyframes(timeline, holder, keyframes, duration, offset, colorSpace, onChange) {
2421
+ const bag = (b) => b?.fillGradient;
2422
+ for (const kf of keyframes) {
2423
+ const at = offset + resolveAt(kf.at, duration);
2424
+ const setV = bag(kf.set), fromV = bag(kf.from), toV = bag(kf.to);
2425
+ if (setV !== void 0) {
2426
+ revertibleSet(
2427
+ timeline,
2428
+ at,
2429
+ () => ({ state: holder.grad }),
2430
+ (v) => {
2431
+ holder.grad = v.state ?? mergeGrad(holder.grad, v.patch);
2432
+ onChange();
2433
+ },
2434
+ { patch: setV }
2435
+ );
2436
+ }
2437
+ if (fromV !== void 0 || toV !== void 0) {
2438
+ tweenGradient(timeline, holder, fromV, toV, kfDuration(kf), kf.ease ?? "none", at, colorSpace, onChange, loopVars(kf));
2439
+ }
2440
+ }
2441
+ }
2442
+ var hasGradientKeys = (keyframes) => (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => b && b.fillGradient !== void 0));
2443
+
2444
+ // src/sequences/Text.ts
1532
2445
  var STYLE_OPAQUE_KEYS = [
1533
2446
  "fontFamily",
1534
2447
  "fill",
@@ -1563,6 +2476,21 @@ var TextSequence = class extends Sequence {
1563
2476
  /** `visibleChars` is in play (initial or animated): otherwise the whole text shows. */
1564
2477
  _typed = false;
1565
2478
  _lastText = "";
2479
+ /** A gradient fill for the text (`fillGradient`, static or animated): rebuilt as a FillGradient on each change (the text is re-rasterised). */
2480
+ _grad = null;
2481
+ _fillGradient = null;
2482
+ _applyGradient() {
2483
+ const g = this._grad.grad;
2484
+ const next = new PIXI.FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((s) => [s.offset, s.color]) }));
2485
+ this.target.style.fill = next;
2486
+ this._fillGradient?.destroy();
2487
+ this._fillGradient = next;
2488
+ }
2489
+ destroy() {
2490
+ super.destroy();
2491
+ this._fillGradient?.destroy();
2492
+ this._fillGradient = null;
2493
+ }
1566
2494
  /** A typewriter that has typed nothing yet: the layer is empty on purpose (inspect must not call that "no size"). */
1567
2495
  get showsNothingYet() {
1568
2496
  return this._typed && this._lastText === "" && this._template !== "";
@@ -1609,6 +2537,12 @@ var TextSequence = class extends Sequence {
1609
2537
  this.intrinsicWidth = text.width;
1610
2538
  this.intrinsicHeight = text.height;
1611
2539
  }
2540
+ validateGradientKeyframes(this.spec, describeLayer(this.spec), void 0, "text");
2541
+ const gradientSpec = this.spec.fillGradient ?? initialProps.fillGradient;
2542
+ if (gradientSpec) {
2543
+ this._grad = { grad: gradStateFrom(gradientSpec) };
2544
+ this._applyGradient();
2545
+ }
1612
2546
  const scope = this.scope();
1613
2547
  if (initialProps.value !== void 0) this._counters.value = resolveNumber(initialProps.value, scope);
1614
2548
  this._typed = initialProps.visibleChars !== void 0 || animates(this.spec.keyframes, "visibleChars");
@@ -1627,11 +2561,12 @@ var TextSequence = class extends Sequence {
1627
2561
  bindTimeline(timeline, offset = 0) {
1628
2562
  if (!this.target) return;
1629
2563
  const colorSpace = this.spec.colorSpace ?? "rgb";
1630
- const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars"]);
2564
+ const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars", "fillGradient"]);
1631
2565
  runSuperWithStrippedSpec(this, stripped, timeline, offset);
1632
2566
  const keyframes = this.spec.keyframes ?? [];
1633
2567
  const origin = offset + this.at;
1634
2568
  bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
2569
+ if (this._grad) bindGradientKeyframes(timeline, this._grad, keyframes, this.duration, origin, colorSpace, () => this._applyGradient());
1635
2570
  const refresh = () => this._refreshText();
1636
2571
  bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
1637
2572
  bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
@@ -1681,7 +2616,7 @@ function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDu
1681
2616
  const toV = kf.to?.[key];
1682
2617
  if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
1683
2618
  const at = origin + resolveAt(kf.at, parentDuration);
1684
- const duration = kf.duration ?? 0;
2619
+ const duration = kfDuration(kf);
1685
2620
  const ease = kf.ease ?? "none";
1686
2621
  const loop = loopVars(kf);
1687
2622
  if (setV !== void 0) {
@@ -1727,7 +2662,7 @@ function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, co
1727
2662
  };
1728
2663
  for (const kf of keyframes) {
1729
2664
  const at = offset + resolveAt(kf.at, parentDuration);
1730
- const duration = kf.duration ?? 0;
2665
+ const duration = kfDuration(kf);
1731
2666
  const ease = kf.ease ?? "none";
1732
2667
  const setFill = pickFill(kf.set);
1733
2668
  const fromFill = pickFill(kf.from);
@@ -1765,6 +2700,7 @@ var AudioSequence = class extends Sequence {
1765
2700
  _audioBuffer = null;
1766
2701
  _synth = null;
1767
2702
  _source = "";
2703
+ _remap = null;
1768
2704
  async build() {
1769
2705
  this.target = null;
1770
2706
  if (this.spec.music !== void 0) return this._buildMusic(this.spec);
@@ -1773,13 +2709,16 @@ var AudioSequence = class extends Sequence {
1773
2709
  this._source = `asset "${spec.asset}"`;
1774
2710
  const data = await PIXI.Assets.get(spec.asset);
1775
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;
1776
2714
  if (this.duration === void 0) {
1777
2715
  const rest = Math.max(0, (this.parent?.duration ?? this.root.duration) - this.at);
1778
- 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;
1779
2717
  }
1780
- 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) {
1781
2720
  console.warn(
1782
- `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.`
1783
2722
  );
1784
2723
  }
1785
2724
  }
@@ -1788,7 +2727,7 @@ var AudioSequence = class extends Sequence {
1788
2727
  const raw = spec;
1789
2728
  if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and sfx \u2014 the sfx plays; remove one`);
1790
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)`);
1791
- const { resolveSfx, renderSfx, sfxKey } = await import('./sfx-MZJZXYY3.cjs');
2730
+ const { resolveSfx, renderSfx, sfxKey } = await import('./sfx-EX4PUBMV.cjs');
1792
2731
  const sfx = resolveSfx(spec.sfx, spec.duration, who);
1793
2732
  if (!sfx) return;
1794
2733
  this.duration = sfx.length;
@@ -1807,7 +2746,7 @@ var AudioSequence = class extends Sequence {
1807
2746
  const raw = spec;
1808
2747
  if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and music \u2014 the music plays; remove one`);
1809
2748
  if (raw.sfx !== void 0) console.warn(`pixi-effects: ${who}: has both sfx and music \u2014 the music plays; remove one`);
1810
- const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-3OF5C6X5.cjs');
2749
+ const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-UAN7DXK4.cjs');
1811
2750
  const music = resolveMusic(spec.music, who);
1812
2751
  if (!music) return;
1813
2752
  const natural = musicLength(music);
@@ -1833,7 +2772,9 @@ var AudioSequence = class extends Sequence {
1833
2772
  const initialVolume = this.spec.volume ?? this.spec.initial?.volume ?? 1;
1834
2773
  const t0 = baseTime + this.at;
1835
2774
  const dur = this.duration;
2775
+ const sourceMap = this._remap ? clockTable(this._remap, "time", dur, t0, this.scope(), t0 + dur).at : void 0;
1836
2776
  out.push({
2777
+ ...sourceMap ? { sourceMap } : {},
1837
2778
  ...this._audioBuffer ? { buffer: this._audioBuffer } : { synth: this._synth },
1838
2779
  layer: describeLayer(this.spec),
1839
2780
  source: this._source,
@@ -1892,7 +2833,7 @@ var FrameCache = class {
1892
2833
  this.capacity = options.capacity ?? 30;
1893
2834
  }
1894
2835
  _key(time) {
1895
- return Math.round(time * 1e3);
2836
+ return Math.round(time * 1e6);
1896
2837
  }
1897
2838
  async getFrameAt(time) {
1898
2839
  const key = this._key(time);
@@ -1957,6 +2898,15 @@ var FrameCache = class {
1957
2898
  }
1958
2899
  };
1959
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
+
1960
2910
  // src/sequences/Video.ts
1961
2911
  var VideoSequence = class extends Sequence {
1962
2912
  _sourceDuration = 0;
@@ -1965,9 +2915,15 @@ var VideoSequence = class extends Sequence {
1965
2915
  _canvas = null;
1966
2916
  _ctx = null;
1967
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;
1968
2923
  async build() {
1969
2924
  const data = await PIXI.Assets.get(this.spec.asset);
1970
2925
  this._sourceDuration = data.duration;
2926
+ this._remap = remapOf(this.spec, "currentTime", this._sourceDuration);
1971
2927
  this._audioBuffer = this.spec.audio !== false ? data.audioBuffer : null;
1972
2928
  this._cache = new FrameCache(data.sink, { capacity: 30 });
1973
2929
  const probeFrame = await this._cache.getFrameAt(0);
@@ -1986,7 +2942,8 @@ var VideoSequence = class extends Sequence {
1986
2942
  this.intrinsicWidth = w;
1987
2943
  this.intrinsicHeight = h;
1988
2944
  if (this.duration === void 0) {
1989
- 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;
1990
2947
  }
1991
2948
  let currentTime = 0;
1992
2949
  const seq = this;
@@ -1996,8 +2953,9 @@ var VideoSequence = class extends Sequence {
1996
2953
  },
1997
2954
  set(v) {
1998
2955
  currentTime = v;
2956
+ seq._cur = v;
1999
2957
  const mySeq = ++seq._drawSeq;
2000
- const lookup = seq.spec.loop && seq._sourceDuration > 0 ? v % seq._sourceDuration : v;
2958
+ const lookup = sourceLookup(v, seq._sourceDuration, !!seq.spec.loop);
2001
2959
  seq._cache.getFrameAt(lookup).then((frame) => {
2002
2960
  if (mySeq !== seq._drawSeq) return;
2003
2961
  if (!frame || !seq._ctx) return;
@@ -2010,21 +2968,33 @@ var VideoSequence = class extends Sequence {
2010
2968
  });
2011
2969
  this.buildFilters();
2012
2970
  }
2971
+ displayProps() {
2972
+ const r = this._remap;
2973
+ return r ? { initial: r.restInitial, keyframes: r.restKfs } : super.displayProps();
2974
+ }
2013
2975
  bindTimeline(timeline, offset = 0) {
2014
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
+ }
2015
2982
  const playDuration = this.spec.loop ? this.duration : Math.min(this.duration, this._sourceDuration);
2016
2983
  timeline.fromTo(
2017
2984
  this.target,
2018
2985
  { currentTime: 0 },
2019
2986
  { currentTime: playDuration, duration: playDuration, ease: "none" },
2020
- offset + this.at
2987
+ at
2021
2988
  // offset = start of the enclosing composition(s); dropping it misplaced videos inside nested compositions
2022
2989
  );
2023
2990
  }
2024
2991
  collectAudio(out, baseTime) {
2025
2992
  if (!this._audioBuffer) return;
2026
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;
2027
2996
  out.push({
2997
+ ...sourceMap ? { sourceMap } : {},
2028
2998
  buffer: this._audioBuffer,
2029
2999
  layer: describeLayer(this.spec),
2030
3000
  source: `video "${this.spec.asset}"`,
@@ -2035,9 +3005,12 @@ var VideoSequence = class extends Sequence {
2035
3005
  volumeKeyframes: []
2036
3006
  });
2037
3007
  }
2038
- 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;
2039
3012
  const mySeq = ++this._drawSeq;
2040
- const lookup = this.spec.loop && this._sourceDuration > 0 ? time % this._sourceDuration : time;
3013
+ const lookup = sourceLookup(time, this._sourceDuration, !!this.spec.loop);
2041
3014
  let frame = null;
2042
3015
  try {
2043
3016
  frame = await this._cache.getFrameAt(lookup);
@@ -2061,39 +3034,6 @@ var VideoSequence = class extends Sequence {
2061
3034
  super.destroy();
2062
3035
  }
2063
3036
  };
2064
-
2065
- // src/sequences/gradient.ts
2066
- function cssColor(c) {
2067
- return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
2068
- }
2069
- function normalizedStops(g) {
2070
- const stops = g.stops ?? [];
2071
- if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
2072
- return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
2073
- }
2074
- function gradientOptions(g) {
2075
- const colorStops = normalizedStops(g);
2076
- const a = (g.angle ?? 90) * Math.PI / 180;
2077
- const dx = Math.cos(a), dy = Math.sin(a);
2078
- const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2079
- return {
2080
- type: "linear",
2081
- start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
2082
- end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
2083
- colorStops,
2084
- textureSpace: "local"
2085
- };
2086
- }
2087
- function paintRadial(ctx, size, g) {
2088
- const stops = normalizedStops(g);
2089
- const [cx, cy] = g.center ?? [0.5, 0.5];
2090
- const grad = ctx.createRadialGradient(cx * size, cy * size, (g.innerRadius ?? 0) * size, cx * size, cy * size, (g.radius ?? 0.5) * size);
2091
- for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
2092
- ctx.fillStyle = grad;
2093
- ctx.fillRect(0, 0, size, size);
2094
- }
2095
-
2096
- // src/sequences/gradientFill.ts
2097
3037
  var RADIAL_SIZE = 256;
2098
3038
  function makeGradientFill(g) {
2099
3039
  if (g.type === "radial") {
@@ -2285,7 +3225,7 @@ function morphAt(pairs, t) {
2285
3225
  }
2286
3226
 
2287
3227
  // src/sequences/Shape.ts
2288
- var STYLE_KEYS = ["fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth"];
3228
+ var STYLE_KEYS2 = ["fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth"];
2289
3229
  var COLOR_KEYS = /* @__PURE__ */ new Set(["fillColor", "strokeColor"]);
2290
3230
  var TRIM_KEYS = ["trimStart", "trimEnd"];
2291
3231
  var GEOMETRY_KEYS_BY_SHAPE = {
@@ -2311,7 +3251,7 @@ var ShapeSequence = class extends Sequence {
2311
3251
  // by `Container.onRender`.
2312
3252
  _drawGeometry = () => {
2313
3253
  };
2314
- _liveKeys = new Set(STYLE_KEYS);
3254
+ _liveKeys = new Set(STYLE_KEYS2);
2315
3255
  async build() {
2316
3256
  const graphics = new PIXI.Graphics({ label: this.spec.name });
2317
3257
  graphics.cullable = true;
@@ -2337,7 +3277,15 @@ var ShapeSequence = class extends Sequence {
2337
3277
  this._state.trimEnd ??= 1;
2338
3278
  }
2339
3279
  const gradientSpec = this.spec.fillGradient ?? this.spec.initial?.fillGradient;
2340
- if (gradientSpec) this._state.fillStyle = makeGradientFill(gradientSpec);
3280
+ validateGradientKeyframes(this.spec, describeLayer(this.spec));
3281
+ if (gradientSpec) {
3282
+ if (hasGradientKeys(this.spec.keyframes)) {
3283
+ this._grad = { grad: gradStateFrom(gradientSpec) };
3284
+ this._painter = new GradientPainter();
3285
+ this._gradDirty = true;
3286
+ this._liveKeys.add("fillGradient");
3287
+ } else this._state.fillStyle = makeGradientFill(gradientSpec);
3288
+ }
2341
3289
  const morph = this._setupMorph();
2342
3290
  this._drawGeometry = makeDrawGeometry(this.spec, scope, this._state, morph);
2343
3291
  this._outline = makeOutline(this.spec, scope, this._state, morph);
@@ -2396,11 +3344,23 @@ var ShapeSequence = class extends Sequence {
2396
3344
  this._state.morph ??= 0;
2397
3345
  return { pairs, to: String(top.morphTo) };
2398
3346
  }
3347
+ destroy() {
3348
+ super.destroy();
3349
+ this._painter?.destroy();
3350
+ this._painter = null;
3351
+ }
2399
3352
  _fresh = false;
3353
+ _grad = null;
3354
+ _painter = null;
3355
+ _gradDirty = false;
2400
3356
  _outline = null;
2401
3357
  _redraw() {
2402
3358
  const graphics = this.target;
2403
3359
  const s = this._state;
3360
+ if (this._grad && this._gradDirty) {
3361
+ s.fillStyle = this._painter.paint(this._grad.grad);
3362
+ this._gradDirty = false;
3363
+ }
2404
3364
  graphics.clear();
2405
3365
  const trimmed = (s.trimStart ?? 0) > 0 || (s.trimEnd ?? 1) < 1;
2406
3366
  if (!trimmed || !this._outline || !(s.strokeColor !== void 0 && s.strokeWidth > 0)) {
@@ -2446,6 +3406,9 @@ var ShapeSequence = class extends Sequence {
2446
3406
  applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime);
2447
3407
  const colorSpace = this.spec.colorSpace ?? "rgb";
2448
3408
  bindLiveKeyframes(timeline, this._state, this._liveKeys, this.spec.keyframes ?? [], this.duration, scope, startTime, colorSpace);
3409
+ if (this._grad) bindGradientKeyframes(timeline, this._grad, this.spec.keyframes ?? [], this.duration, startTime, colorSpace, () => {
3410
+ this._gradDirty = true;
3411
+ });
2449
3412
  this.absoluteStart = startTime;
2450
3413
  const endTime = startTime + this.duration;
2451
3414
  this.target.renderable = startTime <= 0;
@@ -2482,7 +3445,7 @@ function seedStrokeStyle(state, spec, initial, who) {
2482
3445
  }
2483
3446
  }
2484
3447
  function seedStyle(state, initial, scope) {
2485
- for (const k of STYLE_KEYS) {
3448
+ for (const k of STYLE_KEYS2) {
2486
3449
  const v = initial[k];
2487
3450
  if (v === void 0) continue;
2488
3451
  state[k] = COLOR_KEYS.has(k) ? v : numOrZero(v, scope);
@@ -2705,13 +3668,14 @@ function makeOutline(spec, scope, s, morph) {
2705
3668
  function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration, scope, offset, colorSpace) {
2706
3669
  for (const kf of keyframes) {
2707
3670
  const at = offset + resolveAt(kf.at, parentDuration);
2708
- const duration = kf.duration ?? 0;
3671
+ const duration = kfDuration(kf);
2709
3672
  const ease = kf.ease ?? "none";
2710
3673
  const loop = loopVars(kf);
2711
3674
  if (kf.set) {
2712
3675
  const live = pickLive(kf.set, liveKeys);
2713
3676
  if (live) {
2714
3677
  for (const [k, v] of Object.entries(live)) {
3678
+ if (k === "fillGradient") continue;
2715
3679
  const resolved = resolveLiveValue(k, v, scope);
2716
3680
  const live2 = state;
2717
3681
  revertibleSet(timeline, at, () => live2[k], (v2) => {
@@ -2727,6 +3691,7 @@ function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration,
2727
3691
  ...toLive ? Object.keys(toLive) : []
2728
3692
  ]);
2729
3693
  for (const k of allKeys) {
3694
+ if (k === "fillGradient") continue;
2730
3695
  const key = k;
2731
3696
  const fromRaw = fromLive?.[key];
2732
3697
  const toRaw = toLive?.[key];
@@ -2775,7 +3740,7 @@ function numOrZero(v, scope) {
2775
3740
  var _CompositionSequence = null;
2776
3741
  async function getCompositionSequence() {
2777
3742
  if (!_CompositionSequence) {
2778
- _CompositionSequence = (await import('./Composition-6JMXWNVV.cjs')).CompositionSequence;
3743
+ _CompositionSequence = (await import('./Composition-LL2XXU5N.cjs')).CompositionSequence;
2779
3744
  }
2780
3745
  return _CompositionSequence;
2781
3746
  }
@@ -2819,17 +3784,21 @@ exports.DEG = DEG;
2819
3784
  exports.NEAR = NEAR;
2820
3785
  exports.NullSequence = NullSequence;
2821
3786
  exports.Sequence = Sequence;
3787
+ exports.bindClock = bindClock;
2822
3788
  exports.buildScope = buildScope;
2823
3789
  exports.buildSequenceTree = buildSequenceTree;
2824
3790
  exports.cameraBasis = cameraBasis;
2825
3791
  exports.checkEase = checkEase;
3792
+ exports.clockTable = clockTable;
2826
3793
  exports.collectPropKeys = collectPropKeys;
3794
+ exports.contentLength = contentLength;
2827
3795
  exports.describeLayer = describeLayer;
2828
3796
  exports.ensureFilterLibrary = ensureFilterLibrary;
2829
3797
  exports.evaluateExpr = evaluateExpr;
2830
3798
  exports.flattenSvgPath = flattenSvgPath;
2831
3799
  exports.homeCamera = homeCamera;
2832
3800
  exports.homeDistance = homeDistance;
3801
+ exports.lintKeys = lintKeys;
2833
3802
  exports.lintText = lintText;
2834
3803
  exports.lintTiming = lintTiming;
2835
3804
  exports.noise = noise;
@@ -2837,7 +3806,9 @@ exports.projectLayer = projectLayer;
2837
3806
  exports.rand = rand;
2838
3807
  exports.random = random;
2839
3808
  exports.registerSequenceType = registerSequenceType;
3809
+ exports.remapOf = remapOf;
2840
3810
  exports.resolveAt = resolveAt;
2841
3811
  exports.summarizeWarnings = summarizeWarnings;
2842
- //# sourceMappingURL=chunk-ANO5R2E6.cjs.map
2843
- //# sourceMappingURL=chunk-ANO5R2E6.cjs.map
3812
+ exports.timeRemapOf = timeRemapOf;
3813
+ //# sourceMappingURL=chunk-NO26HWR7.cjs.map
3814
+ //# sourceMappingURL=chunk-NO26HWR7.cjs.map