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
- import { suggestName } from './chunk-DKGG7WD4.js';
1
+ import { suggestName, warnUnknownOptions } from './chunk-MNM56B3X.js';
2
2
  import * as PIXI from 'pixi.js';
3
- import { Rectangle, Container, GraphicsPath, Graphics, Assets, Texture, Sprite, Text, Filter, GlProgram, defaultFilterVert, GpuProgram, UniformGroup, FillGradient, Color } from 'pixi.js';
3
+ import { Rectangle, Container, GraphicsPath, Graphics, Assets, Texture, Sprite, FillGradient, Text, Filter, GlProgram, defaultFilterVert, GpuProgram, UniformGroup, Color } from 'pixi.js';
4
4
  import { gsap } from 'gsap';
5
5
 
6
6
  // src/expr/random.ts
@@ -37,6 +37,113 @@ function noise(x, seed = 0) {
37
37
  const u = f * f * f * (f * (f * 6 - 15) + 10);
38
38
  return a + (b - a) * u;
39
39
  }
40
+ var SETTLE_TOL = 5e-3;
41
+ var DEFAULTS = { mass: 1, stiffness: 100, damping: 10 };
42
+ function springResponse(t, { mass: m, stiffness: k, damping: c }) {
43
+ if (t <= 0) return 0;
44
+ const w0 = Math.sqrt(k / m);
45
+ const zeta = c / (2 * Math.sqrt(k * m));
46
+ if (Math.abs(zeta - 1) < 1e-6) return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
47
+ if (zeta < 1) {
48
+ const wd = w0 * Math.sqrt(1 - zeta * zeta);
49
+ return 1 - Math.exp(-zeta * w0 * t) * (Math.cos(wd * t) + zeta * w0 / wd * Math.sin(wd * t));
50
+ }
51
+ const s = w0 * Math.sqrt(zeta * zeta - 1);
52
+ const r1 = -zeta * w0 + s, r2 = -zeta * w0 - s;
53
+ return 1 - (r2 * Math.exp(r1 * t) - r1 * Math.exp(r2 * t)) / (r2 - r1);
54
+ }
55
+ function complete(p) {
56
+ return { mass: p.mass ?? DEFAULTS.mass, stiffness: p.stiffness ?? DEFAULTS.stiffness, damping: p.damping ?? DEFAULTS.damping };
57
+ }
58
+ function springDuration(params = {}, tol = SETTLE_TOL) {
59
+ const p = complete(params);
60
+ const w0 = Math.sqrt(p.stiffness / p.mass);
61
+ const zeta = p.damping / (2 * Math.sqrt(p.stiffness * p.mass));
62
+ const bad = (t) => Math.abs(1 - springResponse(t, p)) > tol;
63
+ let lo, hi;
64
+ if (zeta < 1 - 1e-6) {
65
+ const wd = w0 * Math.sqrt(1 - zeta * zeta);
66
+ const decay = Math.PI * zeta / Math.sqrt(1 - zeta * zeta);
67
+ const n = Math.max(1, Math.ceil(Math.log(1 / tol) / decay));
68
+ lo = (n - 1) * Math.PI / wd;
69
+ hi = n * Math.PI / wd;
70
+ } else {
71
+ lo = 0;
72
+ hi = 1 / w0;
73
+ while (bad(hi)) {
74
+ lo = hi;
75
+ hi *= 2;
76
+ }
77
+ }
78
+ for (let i = 0; i < 60; i++) {
79
+ const mid = (lo + hi) / 2;
80
+ if (bad(mid)) lo = mid;
81
+ else hi = mid;
82
+ }
83
+ return hi;
84
+ }
85
+ function springEase(params = {}) {
86
+ const sp = complete(params);
87
+ const T = springDuration(sp);
88
+ const end = springResponse(T, sp);
89
+ return (p) => p <= 0 ? 0 : p >= 1 ? 1 : springResponse(p * T, sp) / end;
90
+ }
91
+ var SPRING_PRESETS = {
92
+ gentle: { mass: 1, stiffness: 100, damping: 15 },
93
+ // zeta .75, one soft overshoot
94
+ snappy: { mass: 1, stiffness: 300, damping: 28 },
95
+ // zeta .81, quick, barely rings
96
+ bouncy: { mass: 1, stiffness: 170, damping: 10 },
97
+ // zeta .38
98
+ wobbly: { mass: 1, stiffness: 120, damping: 6 },
99
+ // zeta .27, rings several times
100
+ slow: { mass: 1, stiffness: 60, damping: 18 }
101
+ // zeta 1.16, no overshoot
102
+ };
103
+ var SPRING_RE = /^spring(?:\.(\w+)|\(([^)]*)\))?$/;
104
+ function springProblem(name) {
105
+ const m = SPRING_RE.exec(name.trim());
106
+ if (!m) return 'expected "spring(mass, stiffness, damping)" or "spring.<preset>"';
107
+ if (m[1] && !SPRING_PRESETS[m[1]]) return `no preset "${m[1]}" (presets: ${Object.keys(SPRING_PRESETS).join(", ")})`;
108
+ if (m[2] !== void 0 && m[2].trim() !== "") {
109
+ const nums = m[2].split(",").map((s) => Number(s.trim()));
110
+ 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";
111
+ }
112
+ return null;
113
+ }
114
+ function parseSpring(name) {
115
+ if (typeof name !== "string" || !/^\s*spring/.test(name) || springProblem(name)) return null;
116
+ const m = SPRING_RE.exec(name.trim());
117
+ if (m[1]) return SPRING_PRESETS[m[1]];
118
+ const [mass, stiffness, damping] = (m[2] ?? "").split(",").map((s) => s.trim()).filter(Boolean).map(Number);
119
+ return complete({ mass, stiffness, damping });
120
+ }
121
+ var registered = false;
122
+ function registerSpringEases() {
123
+ if (registered) return;
124
+ registered = true;
125
+ const base = springEase();
126
+ const ease = (p) => base(p);
127
+ ease.config = (mass, stiffness, damping) => {
128
+ const bad = [mass, stiffness, damping].some((v) => v !== void 0 && !(typeof v === "number" && v > 0 && Number.isFinite(v)));
129
+ return springEase(bad ? {} : { mass, stiffness, damping });
130
+ };
131
+ gsap.registerEase("spring", ease);
132
+ for (const [name, p] of Object.entries(SPRING_PRESETS)) gsap.registerEase(`spring.${name}`, springEase(p));
133
+ }
134
+ var autoWarned = /* @__PURE__ */ new Set();
135
+ function kfDuration(kf) {
136
+ if (kf.duration !== "auto") return kf.duration ?? 0;
137
+ const sp = parseSpring(kf.ease);
138
+ if (sp) return Math.round(springDuration(sp) * 1e3) / 1e3;
139
+ const name = String(kf.ease ?? "none");
140
+ if (!/^\s*spring/.test(name) && !autoWarned.has(name)) {
141
+ autoWarned.add(name);
142
+ 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.`);
143
+ }
144
+ return 0.5;
145
+ }
146
+ registerSpringEases();
40
147
 
41
148
  // src/expr/Parser.ts
42
149
  var FUNCS = {
@@ -289,11 +396,77 @@ function looksLikeColorString(s) {
289
396
  if (s.charCodeAt(0) === 35) return true;
290
397
  return /^(rgb|hsl|oklab|oklch|lab|lch)\b/i.test(s);
291
398
  }
399
+ function cubicBezierEase(x1, y1, x2, y2) {
400
+ const cx = 3 * x1, bx = 3 * (x2 - x1) - cx, ax = 1 - cx - bx;
401
+ const cy = 3 * y1, by = 3 * (y2 - y1) - cy, ay = 1 - cy - by;
402
+ const X = (t) => ((ax * t + bx) * t + cx) * t;
403
+ const Y = (t) => ((ay * t + by) * t + cy) * t;
404
+ const dX = (t) => (3 * ax * t + 2 * bx) * t + cx;
405
+ return (p) => {
406
+ if (p <= 0) return 0;
407
+ if (p >= 1) return 1;
408
+ let t = p;
409
+ for (let i = 0; i < 8; i++) {
410
+ const err = X(t) - p;
411
+ if (Math.abs(err) < 1e-9) return Y(t);
412
+ const d = dX(t);
413
+ if (Math.abs(d) < 1e-6) break;
414
+ t -= err / d;
415
+ }
416
+ let lo = 0, hi = 1;
417
+ t = p;
418
+ for (let i = 0; i < 60; i++) {
419
+ const x = X(t);
420
+ if (Math.abs(x - p) < 1e-10) break;
421
+ if (x < p) lo = t;
422
+ else hi = t;
423
+ t = (lo + hi) / 2;
424
+ }
425
+ return Y(t);
426
+ };
427
+ }
428
+ var NUM = String.raw`\s*(-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)\s*`;
429
+ var RE = new RegExp(`^\\s*cubic-bezier\\(${NUM},${NUM},${NUM},${NUM}\\)\\s*$`, "i");
430
+ function cubicBezierProblem(name) {
431
+ const m = RE.exec(name);
432
+ if (!m) return 'expected "cubic-bezier(x1, y1, x2, y2)" with four numbers, like CSS';
433
+ const x1 = Number(m[1]), x2 = Number(m[3]);
434
+ if (x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1) return "x1 and x2 must be between 0 and 1 (y1 and y2 may overshoot)";
435
+ return null;
436
+ }
437
+ var registered2 = false;
438
+ function registerCubicBezierEase() {
439
+ if (registered2) return;
440
+ registered2 = true;
441
+ const base = ((p) => p);
442
+ base.config = (x1, y1, x2, y2) => {
443
+ const bad = [x1, y1, x2, y2].some((v) => typeof v !== "number" || !Number.isFinite(v)) || x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1;
444
+ return bad ? (p) => p : cubicBezierEase(x1, y1, x2, y2);
445
+ };
446
+ gsap.registerEase("cubic-bezier", base);
447
+ }
448
+ registerCubicBezierEase();
449
+
450
+ // src/core/ease.ts
292
451
  var FAMILIES = ["power1", "power2", "power3", "power4", "back", "elastic", "bounce", "circ", "expo", "sine"];
293
452
  var EASE_NAMES = ["none", ...FAMILIES.flatMap((f) => [`${f}.in`, `${f}.out`, `${f}.inOut`])];
294
453
  var warned = /* @__PURE__ */ new Set();
295
454
  function checkEase(ease, where) {
296
455
  if (typeof ease !== "string") return;
456
+ if (/^\s*spring/.test(ease)) {
457
+ const problem = springProblem(ease);
458
+ if (!problem || warned.has(ease)) return;
459
+ warned.add(ease);
460
+ console.warn(`pixi-effects: ${where}: unknown ease "${ease}" \u2014 ${problem}. GSAP would run it as its default ease, power1.out.`);
461
+ return;
462
+ }
463
+ if (/^\s*cubic-bezier/i.test(ease)) {
464
+ const problem = cubicBezierProblem(ease);
465
+ if (!problem || warned.has(ease)) return;
466
+ warned.add(ease);
467
+ console.warn(`pixi-effects: ${where}: unknown ease "${ease}" \u2014 ${problem}. It runs as 'none' (a straight line).`);
468
+ return;
469
+ }
297
470
  const found = gsap.parseEase(ease);
298
471
  if (typeof found === "function") return;
299
472
  if (warned.has(ease)) return;
@@ -334,7 +507,7 @@ function resolveAt(at, duration) {
334
507
  }
335
508
  function normalizeKeyframe(kf, parentDuration) {
336
509
  const at = resolveAt(kf.at, parentDuration);
337
- const duration = kf.duration ?? 0;
510
+ const duration = kfDuration(kf);
338
511
  const ease = kf.ease ?? "none";
339
512
  checkEase(ease, "a keyframe");
340
513
  const hasSet = !!kf.set;
@@ -764,8 +937,217 @@ var ChromaKeyFilter = class extends Filter {
764
937
  u[2] = rgb[2];
765
938
  }
766
939
  };
940
+ var HASH_GL = `
941
+ uint hash(uint x) { x ^= x >> 16; x *= 0x7feb352du; x ^= x >> 15; x *= 0x846ca68bu; x ^= x >> 16; return x; }
942
+ // one gaussian-ish value (sum of the four bytes of a hash), mean 0, std 1
943
+ float gauss(ivec2 p, uint k) {
944
+ uint h = hash(uint(p.x) + hash(uint(p.y) + hash(k)));
945
+ float s = float(h & 255u) + float((h >> 8) & 255u) + float((h >> 16) & 255u) + float(h >> 24);
946
+ return (s / 255.0 - 2.0) * 1.7320508;
947
+ }
948
+ // the grain value at a pixel: cells of size pixels, smoothly blended
949
+ float cell(vec2 pos, float size, uint k) {
950
+ vec2 q = pos / size - 0.5;
951
+ vec2 i = floor(q);
952
+ vec2 f = fract(q);
953
+ ivec2 p = ivec2(i);
954
+ 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);
955
+ float v = mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
956
+ vec2 wx = vec2(1.0 - f.x, f.x), wy = vec2(1.0 - f.y, f.y);
957
+ return v * inversesqrt(dot(wx, wx) * dot(wy, wy)); // blending lowers the spread: keep std exactly 1 at every pixel
958
+ }`;
959
+ var GL_VERTEX = `#version 300 es
960
+ in vec2 aPosition;
961
+ out vec2 vTextureCoord;
962
+ uniform vec4 uInputSize;
963
+ uniform vec4 uOutputFrame;
964
+ uniform vec4 uOutputTexture;
965
+ void main(void) {
966
+ vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;
967
+ position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;
968
+ position.y = position.y * (2.0 * uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;
969
+ gl_Position = vec4(position, 0.0, 1.0);
970
+ vTextureCoord = aPosition * (uOutputFrame.zw * uInputSize.zw);
971
+ }`;
972
+ var GL_FRAGMENT2 = `#version 300 es
973
+ precision highp float;
974
+ precision highp int;
975
+ in vec2 vTextureCoord;
976
+ uniform sampler2D uTexture;
977
+ uniform vec4 uInputSize;
978
+ uniform float uAmount;
979
+ uniform float uSize;
980
+ uniform float uSeed;
981
+ uniform float uFrame;
982
+ uniform float uColor;
983
+ out vec4 finalColor;
984
+ ${HASH_GL}
985
+ void main(void) {
986
+ vec4 raw = texture(uTexture, vTextureCoord);
987
+ vec3 rgb = raw.a > 0.0 ? raw.rgb / raw.a : vec3(0.0);
988
+ float L = dot(rgb, vec3(0.2126, 0.7152, 0.0722));
989
+ float w = clamp(4.0 * L * (1.0 - L), 0.0, 1.0);
990
+ vec2 pos = vTextureCoord * uInputSize.xy;
991
+ uint k = hash(uint(uSeed) * 0x9E3779B1u + uint(uFrame));
992
+ float n0 = cell(pos, uSize, k);
993
+ vec3 n = vec3(n0);
994
+ if (uColor > 0.0) {
995
+ vec3 nc = vec3(cell(pos, uSize, k + 1u), cell(pos, uSize, k + 2u), cell(pos, uSize, k + 3u));
996
+ n = mix(n, nc, uColor);
997
+ }
998
+ rgb = clamp(rgb + n * (uAmount * w), 0.0, 1.0);
999
+ finalColor = vec4(rgb * raw.a, raw.a);
1000
+ }`;
1001
+ var WGSL_SOURCE2 = `
1002
+ struct GlobalFilterUniforms {
1003
+ uInputSize: vec4<f32>,
1004
+ uInputPixel: vec4<f32>,
1005
+ uInputClamp: vec4<f32>,
1006
+ uOutputFrame: vec4<f32>,
1007
+ uGlobalFrame: vec4<f32>,
1008
+ uOutputTexture: vec4<f32>,
1009
+ };
1010
+ struct GrainUniforms {
1011
+ uAmount: f32,
1012
+ uSize: f32,
1013
+ uSeed: f32,
1014
+ uFrame: f32,
1015
+ uColor: f32,
1016
+ };
1017
+ @group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms;
1018
+ @group(0) @binding(1) var uTexture: texture_2d<f32>;
1019
+ @group(0) @binding(2) var uSampler : sampler;
1020
+ @group(1) @binding(0) var<uniform> grainUniforms : GrainUniforms;
1021
+
1022
+ struct VSOutput {
1023
+ @builtin(position) position: vec4<f32>,
1024
+ @location(0) uv : vec2<f32>,
1025
+ };
1026
+ fn filterVertexPosition(aPosition : vec2<f32>) -> vec4<f32> {
1027
+ var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;
1028
+ position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;
1029
+ position.y = position.y * (2.0 * gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;
1030
+ return vec4<f32>(position, 0.0, 1.0);
1031
+ }
1032
+ fn filterTextureCoord(aPosition : vec2<f32>) -> vec2<f32> {
1033
+ return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);
1034
+ }
1035
+ @vertex
1036
+ fn mainVertex(
1037
+ @location(0) aPosition : vec2<f32>,
1038
+ ) -> VSOutput {
1039
+ return VSOutput(
1040
+ filterVertexPosition(aPosition),
1041
+ filterTextureCoord(aPosition),
1042
+ );
1043
+ }
1044
+
1045
+ fn hash(xIn: u32) -> u32 {
1046
+ var x = xIn;
1047
+ x = x ^ (x >> 16u); x = x * 0x7feb352du; x = x ^ (x >> 15u); x = x * 0x846ca68bu; x = x ^ (x >> 16u);
1048
+ return x;
1049
+ }
1050
+ fn gauss(p: vec2<i32>, k: u32) -> f32 {
1051
+ let h = hash(u32(p.x) + hash(u32(p.y) + hash(k)));
1052
+ let s = f32(h & 255u) + f32((h >> 8u) & 255u) + f32((h >> 16u) & 255u) + f32(h >> 24u);
1053
+ return (s / 255.0 - 2.0) * 1.7320508;
1054
+ }
1055
+ fn cell(pos: vec2<f32>, size: f32, k: u32) -> f32 {
1056
+ let q = pos / size - vec2<f32>(0.5);
1057
+ let i = floor(q);
1058
+ let f = fract(q);
1059
+ let p = vec2<i32>(i);
1060
+ let a = gauss(p, k);
1061
+ let b = gauss(p + vec2<i32>(1, 0), k);
1062
+ let c = gauss(p + vec2<i32>(0, 1), k);
1063
+ let d = gauss(p + vec2<i32>(1, 1), k);
1064
+ let v = mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
1065
+ let wx = vec2<f32>(1.0 - f.x, f.x);
1066
+ let wy = vec2<f32>(1.0 - f.y, f.y);
1067
+ return v * inverseSqrt(dot(wx, wx) * dot(wy, wy));
1068
+ }
1069
+
1070
+ @fragment
1071
+ fn mainFragment(@location(0) uv : vec2<f32>) -> @location(0) vec4<f32> {
1072
+ let raw = textureSample(uTexture, uSampler, uv);
1073
+ var rgb = vec3<f32>(0.0);
1074
+ if (raw.a > 0.0) { rgb = raw.rgb / raw.a; }
1075
+ let L = dot(rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
1076
+ let w = clamp(4.0 * L * (1.0 - L), 0.0, 1.0);
1077
+ let pos = uv * gfu.uInputSize.xy;
1078
+ let k = hash(u32(grainUniforms.uSeed) * 0x9E3779B1u + u32(grainUniforms.uFrame));
1079
+ let n0 = cell(pos, grainUniforms.uSize, k);
1080
+ var n = vec3<f32>(n0);
1081
+ if (grainUniforms.uColor > 0.0) {
1082
+ let nc = vec3<f32>(cell(pos, grainUniforms.uSize, k + 1u), cell(pos, grainUniforms.uSize, k + 2u), cell(pos, grainUniforms.uSize, k + 3u));
1083
+ n = mix(n, nc, grainUniforms.uColor);
1084
+ }
1085
+ rgb = clamp(rgb + n * (grainUniforms.uAmount * w), vec3<f32>(0.0), vec3<f32>(1.0));
1086
+ return vec4<f32>(rgb * raw.a, raw.a);
1087
+ }`;
1088
+ var GRAIN_KEYS = ["amount", "size", "seed", "fps", "color"];
1089
+ var GrainFilter = class extends Filter {
1090
+ _fps;
1091
+ constructor(options = {}) {
1092
+ warnUnknownOptions("grain filter", options, GRAIN_KEYS);
1093
+ const { amount = 0.08, size = 1.5, seed = 0, fps = 24, color = 0 } = options;
1094
+ super({
1095
+ glProgram: GlProgram.from({ vertex: GL_VERTEX, fragment: GL_FRAGMENT2, name: "grain-filter" }),
1096
+ gpuProgram: GpuProgram.from({ vertex: { source: WGSL_SOURCE2, entryPoint: "mainVertex" }, fragment: { source: WGSL_SOURCE2, entryPoint: "mainFragment" } }),
1097
+ resources: {
1098
+ grainUniforms: new UniformGroup({
1099
+ uAmount: { value: amount, type: "f32" },
1100
+ uSize: { value: Math.max(size, 0.25), type: "f32" },
1101
+ uSeed: { value: Math.floor(seed), type: "f32" },
1102
+ uFrame: { value: 0, type: "f32" },
1103
+ uColor: { value: color, type: "f32" }
1104
+ })
1105
+ }
1106
+ });
1107
+ this._fps = fps;
1108
+ }
1109
+ get u() {
1110
+ return this.resources.grainUniforms.uniforms;
1111
+ }
1112
+ get amount() {
1113
+ return this.u.uAmount;
1114
+ }
1115
+ set amount(v) {
1116
+ this.u.uAmount = v;
1117
+ }
1118
+ get size() {
1119
+ return this.u.uSize;
1120
+ }
1121
+ set size(v) {
1122
+ this.u.uSize = Math.max(v, 0.25);
1123
+ }
1124
+ get seed() {
1125
+ return this.u.uSeed;
1126
+ }
1127
+ set seed(v) {
1128
+ this.u.uSeed = Math.floor(v);
1129
+ }
1130
+ get color() {
1131
+ return this.u.uColor;
1132
+ }
1133
+ set color(v) {
1134
+ this.u.uColor = v;
1135
+ }
1136
+ get fps() {
1137
+ return this._fps;
1138
+ }
1139
+ set fps(v) {
1140
+ this._fps = v;
1141
+ }
1142
+ /** The movie calls this with the time (s) of the frame it is about to draw: the grain is new every 1/fps s. */
1143
+ setTime(t) {
1144
+ this.u.uFrame = this._fps > 0 ? Math.floor(t * this._fps + 1e-3) : 0;
1145
+ }
1146
+ };
1147
+
1148
+ // src/filters/named.ts
767
1149
  var CORE = { blur: "BlurFilter", noise: "NoiseFilter", alpha: "AlphaFilter" };
768
- var CORE_TYPES = [...Object.keys(CORE), "colorMatrix"];
1150
+ var CORE_TYPES = [...Object.keys(CORE), "colorMatrix", "grain"];
769
1151
  var LIBRARY_TYPES = [
770
1152
  "adjustment",
771
1153
  "advancedBloom",
@@ -882,6 +1264,7 @@ function colorGradientParams(params) {
882
1264
  }
883
1265
  function createNamedFilter(type, params) {
884
1266
  if (lower(type) === "colormatrix") return colorMatrix(params);
1267
+ if (lower(type) === "grain") return new GrainFilter(params);
885
1268
  const core = Object.entries(CORE).find(([k]) => lower(k) === lower(type));
886
1269
  if (core) return new PIXI[core[1]](params);
887
1270
  if (!isLibraryType(type)) throw unknownType(type);
@@ -973,13 +1356,21 @@ var Sequence = class {
973
1356
  pathRouters() {
974
1357
  return {};
975
1358
  }
1359
+ /**
1360
+ * The `initial` and `keyframes` that go onto the display object. A layer whose `time` runs a clock (video, composition) takes `time`
1361
+ * out of them: it is not a property of the display object.
1362
+ */
1363
+ displayProps() {
1364
+ return { initial: this.spec.initial, keyframes: this.spec.keyframes };
1365
+ }
976
1366
  bindTimeline(timeline, offset = 0) {
977
1367
  if (!this.target) return;
978
1368
  const scope = this.scope();
979
1369
  const routers = this.pathRouters();
980
1370
  const startTime = offset + this.at;
981
- applyInitial(this.target, this.spec.initial, scope, [], routers);
982
- applyKeyframes(timeline, this.target, this.spec.keyframes, this.duration, scope, [], startTime, routers);
1371
+ const { initial, keyframes } = this.displayProps();
1372
+ applyInitial(this.target, initial, scope, [], routers);
1373
+ applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime, routers);
983
1374
  const endTime = startTime + this.duration;
984
1375
  this.absoluteStart = startTime;
985
1376
  if (this.keepHidden) return;
@@ -1030,6 +1421,141 @@ function collectPropKeys(spec) {
1030
1421
  return out;
1031
1422
  }
1032
1423
 
1424
+ // src/core/layerKeys.ts
1425
+ var COMMON = ["type", "name", "parent", "at", "duration", "initial", "keyframes", "filters", "filterArea", "mask", "maskInverted", "blendMode", "threeD", "hideBehindCamera"];
1426
+ var VIDEO = [...COMMON, "asset", "loop", "audio", "volume", "speed"];
1427
+ var IMAGE = [...COMMON, "asset", "colorSpace"];
1428
+ var TEXT = [...COMMON, "text", "format", "style", "colorSpace", "fillGradient"];
1429
+ var AUDIO = [...COMMON, "volume", "asset", "loop", "sfx", "music", "speed"];
1430
+ var COMPOSITION = [...COMMON, "width", "height", "sequences", "transitions", "speed"];
1431
+ var CAMERA = [...COMMON];
1432
+ var NULL = [...COMMON];
1433
+ var SHAPE = [...COMMON, "shape", "fillGradient", "colorSpace", "strokeCap", "strokeJoin"];
1434
+ var TRIM = ["trimStart", "trimEnd", "trimEach"];
1435
+ var RECT = [...SHAPE, ...TRIM, "width", "height", "cornerRadius", "anchorX", "anchorY"];
1436
+ var CIRCLE = [...SHAPE, ...TRIM, "radius", "anchorX", "anchorY"];
1437
+ var ELLIPSE = [...SHAPE, ...TRIM, "radiusX", "radiusY", "anchorX", "anchorY"];
1438
+ var ARC = [...SHAPE, "radius", "innerRadius", "startAngle", "endAngle"];
1439
+ var LINE = [...SHAPE, ...TRIM, "from", "to"];
1440
+ var POLYGON = [...SHAPE, ...TRIM, "points", "open"];
1441
+ var PATH = [...SHAPE, ...TRIM, "d", "morphTo", "morph", "morphPoints"];
1442
+ var SHAPES = { rect: RECT, circle: CIRCLE, ellipse: ELLIPSE, arc: ARC, line: LINE, polygon: POLYGON, path: PATH };
1443
+ var BY_TYPE = { video: VIDEO, image: IMAGE, text: TEXT, audio: AUDIO, composition: COMPOSITION, camera: CAMERA, null: NULL };
1444
+ function kindName(spec) {
1445
+ return spec.type === "shape" && spec.shape ? `${spec.shape} shape` : `${spec.type} layer`;
1446
+ }
1447
+ function layerKeys(spec) {
1448
+ if (spec.type === "shape") return spec.shape && SHAPES[spec.shape] || null;
1449
+ return BY_TYPE[spec.type] ?? null;
1450
+ }
1451
+ function kindsWithKey(key) {
1452
+ const out = [];
1453
+ for (const [shape, keys] of Object.entries(SHAPES)) if (keys.includes(key)) out.push(`${shape} shape`);
1454
+ for (const [type, keys] of Object.entries(BY_TYPE)) if (keys.includes(key)) out.push(`${type} layer`);
1455
+ return out;
1456
+ }
1457
+ var PROP_KEYS = /* @__PURE__ */ new Set([
1458
+ // transform and look (GSAP + PixiPlugin names)
1459
+ "x",
1460
+ "y",
1461
+ "z",
1462
+ "alpha",
1463
+ "rotation",
1464
+ "rotationX",
1465
+ "rotationY",
1466
+ "width",
1467
+ "height",
1468
+ "visible",
1469
+ "autoAlpha",
1470
+ "scale",
1471
+ "scaleX",
1472
+ "scaleY",
1473
+ "anchor",
1474
+ "anchorX",
1475
+ "anchorY",
1476
+ "pivot",
1477
+ "pivotX",
1478
+ "pivotY",
1479
+ "skew",
1480
+ "skewX",
1481
+ "skewY",
1482
+ "position",
1483
+ "positionX",
1484
+ "positionY",
1485
+ "tint",
1486
+ "colorize",
1487
+ "colorizeAmount",
1488
+ "blur",
1489
+ "blurX",
1490
+ "blurY",
1491
+ "blurPadding",
1492
+ // shapes
1493
+ "fillColor",
1494
+ "fillAlpha",
1495
+ "strokeColor",
1496
+ "strokeAlpha",
1497
+ "strokeWidth",
1498
+ "lineColor",
1499
+ "lineAlpha",
1500
+ "cornerRadius",
1501
+ "radius",
1502
+ "radiusX",
1503
+ "radiusY",
1504
+ "innerRadius",
1505
+ "startAngle",
1506
+ "endAngle",
1507
+ "trimStart",
1508
+ "trimEnd",
1509
+ "trimEach",
1510
+ "morph",
1511
+ "fillGradient",
1512
+ "strokeCap",
1513
+ "strokeJoin",
1514
+ // text, audio, video
1515
+ "fill",
1516
+ "value",
1517
+ "visibleChars",
1518
+ "text",
1519
+ "volume",
1520
+ "time",
1521
+ // camera
1522
+ "lookAtX",
1523
+ "lookAtY",
1524
+ "lookAtZ",
1525
+ "fov",
1526
+ "offsetX",
1527
+ "offsetY",
1528
+ "offsetZ",
1529
+ "lookOffsetX",
1530
+ "lookOffsetY",
1531
+ "lookOffsetZ"
1532
+ ]);
1533
+ var STYLE_KEYS = [
1534
+ "fontSize",
1535
+ "fontFamily",
1536
+ "fontWeight",
1537
+ "fontStyle",
1538
+ "fontVariant",
1539
+ "fill",
1540
+ "stroke",
1541
+ "dropShadow",
1542
+ "letterSpacing",
1543
+ "lineHeight",
1544
+ "leading",
1545
+ "align",
1546
+ "wordWrap",
1547
+ "wordWrapWidth",
1548
+ "breakWords",
1549
+ "whiteSpace",
1550
+ "padding",
1551
+ "textBaseline",
1552
+ "trim",
1553
+ "lineJoin",
1554
+ "miterLimit",
1555
+ "filters",
1556
+ "tagStyles"
1557
+ ];
1558
+
1033
1559
  // src/core/lint.ts
1034
1560
  var defaultWarn = (m) => console.warn(m);
1035
1561
  function describeLayer(spec) {
@@ -1068,6 +1594,191 @@ function lintText(spec, warn = defaultWarn) {
1068
1594
  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').`);
1069
1595
  }
1070
1596
  }
1597
+ var REMAPPABLE = /* @__PURE__ */ new Set(["video", "audio", "composition"]);
1598
+ var REMAP_NAMES = /* @__PURE__ */ new Set(["time", "timeRemap", "reverse"]);
1599
+ var TIME_BAGS = ["set", "to", "from"];
1600
+ function lintRemap(spec, warn) {
1601
+ const s = spec;
1602
+ const who = describeLayer(spec);
1603
+ const kfs = s.keyframes ?? [];
1604
+ const bags = [s.initial, ...kfs.flatMap((kf) => TIME_BAGS.map((b) => kf[b]))];
1605
+ const timeInInitial = !!s.initial && s.initial.time !== void 0;
1606
+ const timeInKeyframes = kfs.some((kf) => TIME_BAGS.some((b) => kf[b] && kf[b].time !== void 0));
1607
+ const timed = timeInInitial || timeInKeyframes;
1608
+ if (bags.some((bag) => bag && bag.speed !== void 0)) {
1609
+ 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' }])`);
1610
+ }
1611
+ if (!REMAPPABLE.has(spec.type)) {
1612
+ 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)`);
1613
+ return;
1614
+ }
1615
+ if (s.reverse !== void 0) warn(`pixi-effects: ${who}: "reverse" is not a key: write speed: -1 to play backward`);
1616
+ if (s.timeRemap !== void 0) warn(`pixi-effects: ${who}: "timeRemap" is not a key: animate time in keyframes (from / to / set), or write speed`);
1617
+ if (s.time !== void 0) warn(`pixi-effects: ${who}: "time" belongs in initial or keyframes (initial: { time: 2 }), not on the layer`);
1618
+ if (spec.type === "audio" && (s.sfx !== void 0 || s.music !== void 0) && (s.speed !== void 0 || timed)) {
1619
+ 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`);
1620
+ return;
1621
+ }
1622
+ if (s.speed !== void 0 && (typeof s.speed !== "number" || !Number.isFinite(s.speed) || s.speed === 0)) {
1623
+ 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`);
1624
+ }
1625
+ if (s.speed !== void 0 && timeInKeyframes) {
1626
+ warn(`pixi-effects: ${who}: speed and keyframed time are both set; time wins and speed is ignored. Use one of them`);
1627
+ }
1628
+ if (timeInInitial && typeof s.initial.time !== "number") {
1629
+ warn(`pixi-effects: ${who}: initial.time must be a number of seconds, got ${JSON.stringify(s.initial.time)}`);
1630
+ }
1631
+ kfs.forEach((kf, i) => {
1632
+ for (const b of TIME_BAGS) {
1633
+ const v = kf[b]?.time;
1634
+ 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)}`);
1635
+ }
1636
+ });
1637
+ }
1638
+ function lintKeys(spec, warn = defaultWarn) {
1639
+ const who = describeLayer(spec);
1640
+ const valid = layerKeys(spec);
1641
+ if (valid) {
1642
+ for (const key of Object.keys(spec)) {
1643
+ if (valid.includes(key) || spec[key] === void 0) continue;
1644
+ if (REMAPPABLE.has(spec.type) && REMAP_NAMES.has(key)) continue;
1645
+ const guess = suggestName(key, valid.filter((k) => k !== "type"));
1646
+ const owners = kindsWithKey(key);
1647
+ const belongs = owners.length ? ` (it belongs to ${owners.slice(0, 3).join(", ")})` : "";
1648
+ const hint = guess ? ` \u2014 did you mean "${guess}"?${belongs}` : belongs;
1649
+ warn(`pixi-effects: ${who}: "${key}" is not a ${kindName(spec)} key${hint}. Valid keys: ${valid.join(", ")}`);
1650
+ }
1651
+ }
1652
+ lintRemap(spec, warn);
1653
+ const said = /* @__PURE__ */ new Set();
1654
+ const bags = [spec.initial, ...(spec.keyframes ?? []).flatMap((kf) => [kf.set, kf.to, kf.from])];
1655
+ for (const bag of bags) {
1656
+ for (const key of Object.keys(bag ?? {})) {
1657
+ if (bag[key] === void 0) continue;
1658
+ if (PROP_KEYS.has(key) || key.includes(".") || /^gradient/i.test(key) || said.has(key) || key === "speed") continue;
1659
+ said.add(key);
1660
+ const guess = suggestName(key, [...PROP_KEYS]);
1661
+ 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`);
1662
+ }
1663
+ }
1664
+ if (spec.type === "text" && spec.style && typeof spec.style === "object") {
1665
+ for (const key of Object.keys(spec.style)) {
1666
+ if (STYLE_KEYS.includes(key) || spec.style[key] === void 0) continue;
1667
+ const guess = suggestName(key, STYLE_KEYS);
1668
+ 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(", ")}`);
1669
+ }
1670
+ }
1671
+ }
1672
+ var BAGS = ["set", "to", "from"];
1673
+ function timeRemapOf(spec, prop = "time") {
1674
+ const hasInitial = !!spec.initial && "time" in spec.initial;
1675
+ const hasKf = (spec.keyframes ?? []).some((kf) => BAGS.some((b) => kf[b] && "time" in kf[b]));
1676
+ const speed = typeof spec.speed === "number" && Number.isFinite(spec.speed) && spec.speed !== 0 ? spec.speed : void 0;
1677
+ if (!hasInitial && !hasKf && speed === void 0) return null;
1678
+ const timeKfs = [];
1679
+ const restKfs = [];
1680
+ for (const kf of spec.keyframes ?? []) {
1681
+ const t = { ...kf }, r = { ...kf };
1682
+ let touched = false, left = false;
1683
+ for (const b of BAGS) {
1684
+ const bag = kf[b];
1685
+ if (!bag) {
1686
+ delete t[b];
1687
+ delete r[b];
1688
+ continue;
1689
+ }
1690
+ const { time, ...others } = bag;
1691
+ if ("time" in bag) {
1692
+ touched = true;
1693
+ t[b] = { [prop]: time };
1694
+ } else delete t[b];
1695
+ if (Object.keys(others).length) {
1696
+ r[b] = others;
1697
+ left = true;
1698
+ } else delete r[b];
1699
+ }
1700
+ if (touched) timeKfs.push(t);
1701
+ if (!touched || left) restKfs.push(r);
1702
+ }
1703
+ let restInitial = spec.initial;
1704
+ let start;
1705
+ if (hasInitial) {
1706
+ const { time, ...others } = spec.initial;
1707
+ start = typeof time === "number" && Number.isFinite(time) ? time : void 0;
1708
+ restInitial = others;
1709
+ }
1710
+ return { timeKfs, restKfs: restKfs.length ? restKfs : void 0, restInitial, start, speed };
1711
+ }
1712
+ function resolveFromEnd(remap, length, prop) {
1713
+ const fix = (v) => typeof v === "number" && v < 0 ? Math.max(0, length + v) : v;
1714
+ const timeKfs = remap.timeKfs.map((kf) => {
1715
+ const out = { ...kf };
1716
+ for (const b of BAGS) {
1717
+ const bag = kf[b];
1718
+ if (bag && prop in bag) out[b] = { ...bag, [prop]: fix(bag[prop]) };
1719
+ }
1720
+ return out;
1721
+ });
1722
+ return { ...remap, timeKfs, start: remap.start === void 0 ? void 0 : fix(remap.start) };
1723
+ }
1724
+ function remapOf(spec, prop, length) {
1725
+ const r = timeRemapOf(spec, prop);
1726
+ return r && length !== null ? resolveFromEnd(r, length, prop) : r;
1727
+ }
1728
+ function contentLength(remap, duration) {
1729
+ let m = duration;
1730
+ if (remap.speed !== void 0) m = Math.max(m, Math.abs(remap.speed) * duration);
1731
+ if (remap.start !== void 0) m = Math.max(m, remap.start + (remap.speed ?? 1) * duration);
1732
+ for (const kf of remap.timeKfs) {
1733
+ for (const b of BAGS) {
1734
+ const bag = kf[b];
1735
+ const v = bag && Object.values(bag)[0];
1736
+ if (typeof v === "number") m = Math.max(m, v);
1737
+ }
1738
+ }
1739
+ return m;
1740
+ }
1741
+ function bindClock(timeline, clock, prop, remap, duration, at, scope) {
1742
+ if (remap.timeKfs.length) {
1743
+ if (remap.start !== void 0) timeline.set(clock, { [prop]: remap.start }, at);
1744
+ applyKeyframes(timeline, clock, remap.timeKfs, duration, scope, [], at);
1745
+ return;
1746
+ }
1747
+ const s = remap.speed ?? 1;
1748
+ const from = remap.start ?? (s < 0 ? -s * duration : 0);
1749
+ timeline.fromTo(clock, { [prop]: from }, { [prop]: from + s * duration, duration, ease: "none" }, at);
1750
+ }
1751
+ function clockTable(remap, prop, duration, at, scope, to) {
1752
+ const probe = gsap.timeline({ paused: true });
1753
+ const clock = { [prop]: remap.start ?? 0 };
1754
+ bindClock(probe, clock, prop, remap, duration, at, scope);
1755
+ probe.time(probe.duration());
1756
+ probe.time(0);
1757
+ const h = 1 / 1e3;
1758
+ const n = Math.max(2, Math.ceil(to / h) + 2);
1759
+ const tab = new Float64Array(n);
1760
+ let min = Infinity, max = -Infinity;
1761
+ for (let k = 0; k < n; k++) {
1762
+ probe.time(k * h);
1763
+ const v = clock[prop];
1764
+ tab[k] = v;
1765
+ const t = k * h;
1766
+ if (t >= at && t <= at + duration) {
1767
+ if (v < min) min = v;
1768
+ if (v > max) max = v;
1769
+ }
1770
+ }
1771
+ probe.kill();
1772
+ return {
1773
+ at(t) {
1774
+ const x = Math.min(Math.max(t / h, 0), n - 1.000001);
1775
+ const k = Math.floor(x), f = x - k;
1776
+ return tab[k] * (1 - f) + tab[k + 1] * f;
1777
+ },
1778
+ min: Number.isFinite(min) ? min : 0,
1779
+ max: Number.isFinite(max) ? max : 0
1780
+ };
1781
+ }
1071
1782
 
1072
1783
  // src/space/math.ts
1073
1784
  var DEG = Math.PI / 180;
@@ -1393,6 +2104,7 @@ function shortestHueDelta(from, to) {
1393
2104
  }
1394
2105
 
1395
2106
  // src/expr/colorTween.ts
2107
+ var css = (c) => typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
1396
2108
  function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, at, colorSpace, onUpdate, loop = {}) {
1397
2109
  let interp = null;
1398
2110
  const proxy = { p: 0 };
@@ -1407,7 +2119,7 @@ function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, a
1407
2119
  onStart: () => {
1408
2120
  const start = fromValue !== void 0 ? fromValue : target[key];
1409
2121
  if (start === void 0) return;
1410
- interp = colorSpace === "rgb" ? gsap.utils.interpolate(start, toValue) : buildColorInterp(start, toValue, colorSpace);
2122
+ interp = colorSpace === "rgb" ? gsap.utils.interpolate(css(start), css(toValue)) : buildColorInterp(start, toValue, colorSpace);
1411
2123
  },
1412
2124
  onUpdate: () => {
1413
2125
  if (interp) {
@@ -1451,7 +2163,7 @@ var ImageSequence = class extends Sequence {
1451
2163
  if (initTint !== void 0) this.target.tint = initTint;
1452
2164
  for (const kf of this.spec.keyframes ?? []) {
1453
2165
  const at = startTime + resolveAt(kf.at, this.duration);
1454
- const duration = kf.duration ?? 0;
2166
+ const duration = kfDuration(kf);
1455
2167
  const ease = kf.ease ?? "none";
1456
2168
  const fromTint = kf.from?.tint;
1457
2169
  const toTint = kf.to?.tint;
@@ -1508,6 +2220,207 @@ function stripKeysInKeyframe(kf, keys) {
1508
2220
  from: stripKeys(kf.from, keys)
1509
2221
  };
1510
2222
  }
2223
+
2224
+ // src/sequences/gradient.ts
2225
+ function cssColor(c) {
2226
+ return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
2227
+ }
2228
+ function normalizedStops(g) {
2229
+ const stops = g.stops ?? [];
2230
+ if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
2231
+ return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
2232
+ }
2233
+ function gradientOptions(g) {
2234
+ const colorStops = normalizedStops(g);
2235
+ const a = (g.angle ?? 90) * Math.PI / 180;
2236
+ const dx = Math.cos(a), dy = Math.sin(a);
2237
+ const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2238
+ return {
2239
+ type: "linear",
2240
+ start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
2241
+ end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
2242
+ colorStops,
2243
+ textureSpace: "local"
2244
+ };
2245
+ }
2246
+ function paintRadial(ctx, size, g) {
2247
+ const stops = normalizedStops(g);
2248
+ const [cx, cy] = g.center ?? [0.5, 0.5];
2249
+ const grad = ctx.createRadialGradient(cx * size, cy * size, (g.innerRadius ?? 0) * size, cx * size, cy * size, (g.radius ?? 0.5) * size);
2250
+ for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
2251
+ ctx.fillStyle = grad;
2252
+ ctx.fillRect(0, 0, size, size);
2253
+ }
2254
+
2255
+ // src/sequences/gradientAnim.ts
2256
+ var PATCH_KEYS = ["type", "angle", "center", "innerRadius", "radius", "stops"];
2257
+ function gradStateFrom(g) {
2258
+ return {
2259
+ type: g.type ?? "linear",
2260
+ angle: g.angle ?? 90,
2261
+ center: [...g.center ?? [0.5, 0.5]],
2262
+ innerRadius: g.innerRadius ?? 0,
2263
+ radius: g.radius ?? 0.5,
2264
+ stops: (g.stops ?? []).map((s) => Array.isArray(s) ? { offset: s[0], color: cssColor(s[1]) } : { offset: s.offset, color: cssColor(s.color) })
2265
+ };
2266
+ }
2267
+ function mergeGrad(base, patch) {
2268
+ const out = { ...base, center: [...base.center], stops: base.stops.map((s) => ({ ...s })) };
2269
+ if (patch === null || typeof patch !== "object" || Array.isArray(patch)) return out;
2270
+ const p = patch;
2271
+ if (typeof p.angle === "number") out.angle = p.angle;
2272
+ if (Array.isArray(p.center)) out.center = [Number(p.center[0]), Number(p.center[1])];
2273
+ if (typeof p.innerRadius === "number") out.innerRadius = p.innerRadius;
2274
+ if (typeof p.radius === "number") out.radius = p.radius;
2275
+ if (p.stops !== void 0) {
2276
+ const next = gradStateFrom({ stops: p.stops }).stops;
2277
+ if (next.length === base.stops.length) out.stops = next;
2278
+ }
2279
+ return out;
2280
+ }
2281
+ var objectHint = "a keyframe's fillGradient must be an object like { angle: 200, stops: [[0, '#f00'], [1, '#00f']] }";
2282
+ function validateGradientKeyframes(spec, who, warn = (m) => console.warn(m), kind = "shape") {
2283
+ const said = /* @__PURE__ */ new Set();
2284
+ const say = (m) => {
2285
+ if (!said.has(m)) {
2286
+ said.add(m);
2287
+ warn(`pixi-effects: ${who}: ${m}`);
2288
+ }
2289
+ };
2290
+ const startSpec = spec.fillGradient ?? spec.initial?.fillGradient;
2291
+ const start = startSpec && typeof startSpec === "object" && Array.isArray(startSpec.stops) ? gradStateFrom(startSpec) : null;
2292
+ let animated = false;
2293
+ if (kind === "text") {
2294
+ const radial = (g) => !!g && typeof g === "object" && ["type", "center", "innerRadius", "radius"].some((k) => g[k] !== void 0 && !(k === "type" && g.type === "linear"));
2295
+ const asked = radial(startSpec) || (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => radial(b?.fillGradient)));
2296
+ 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");
2297
+ }
2298
+ for (const kf of spec.keyframes ?? []) {
2299
+ for (const bag of [kf.set, kf.to, kf.from]) {
2300
+ if (!bag) continue;
2301
+ for (const [key, value] of Object.entries(bag)) {
2302
+ if (key === "fillGradient") {
2303
+ animated = true;
2304
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2305
+ say(objectHint);
2306
+ continue;
2307
+ }
2308
+ const p = value;
2309
+ for (const k of Object.keys(p)) {
2310
+ if (PATCH_KEYS.includes(k)) continue;
2311
+ const guess = suggestName(k, PATCH_KEYS.filter((x) => x !== "type"));
2312
+ say(`fillGradient.${k} is not a gradient property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${PATCH_KEYS.join(", ")}); it is ignored`);
2313
+ }
2314
+ if (p.type !== void 0 && start && p.type !== start.type) say("fillGradient.type cannot change over time (linear <-> radial); it is ignored");
2315
+ if (p.stops !== void 0 && start) {
2316
+ const n = Array.isArray(p.stops) ? p.stops.length : 0;
2317
+ 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`);
2318
+ }
2319
+ } else if (/^fillGradient\./i.test(key) || /^gradient/i.test(key)) {
2320
+ const tail = key.replace(/^fillGradient\./i, "").replace(/^gradient/i, "");
2321
+ const sub = tail ? tail[0].toLowerCase() + tail.slice(1) : "angle";
2322
+ 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`);
2323
+ }
2324
+ }
2325
+ }
2326
+ }
2327
+ 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)");
2328
+ }
2329
+ function tweenGradient(timeline, holder, fromPatch, toPatch, duration, ease, at, colorSpace, onChange, loop = {}) {
2330
+ let a = null, b = null;
2331
+ let resting = null;
2332
+ let colors = [];
2333
+ const proxy = { p: 0 };
2334
+ timeline.fromTo(proxy, { p: 0 }, {
2335
+ p: 1,
2336
+ duration,
2337
+ ease,
2338
+ ...loop,
2339
+ onStart: () => {
2340
+ const live = holder.grad;
2341
+ a = fromPatch !== void 0 ? mergeGrad(live, fromPatch) : live;
2342
+ if (toPatch === void 0) resting ??= live;
2343
+ b = toPatch !== void 0 ? mergeGrad(a, toPatch) : resting;
2344
+ colors = a.stops.map((s, i) => colorSpace === "rgb" ? gsap.utils.interpolate(s.color, b.stops[i].color) : buildColorInterp(s.color, b.stops[i].color, colorSpace));
2345
+ },
2346
+ onUpdate: () => {
2347
+ if (!a || !b) return;
2348
+ const t = proxy.p;
2349
+ const l = (x, y) => x + (y - x) * t;
2350
+ holder.grad = {
2351
+ type: a.type,
2352
+ angle: l(a.angle, b.angle),
2353
+ center: [l(a.center[0], b.center[0]), l(a.center[1], b.center[1])],
2354
+ innerRadius: l(a.innerRadius, b.innerRadius),
2355
+ radius: l(a.radius, b.radius),
2356
+ stops: a.stops.map((s, i) => ({ offset: l(s.offset, b.stops[i].offset), color: colors[i](t) }))
2357
+ };
2358
+ onChange();
2359
+ }
2360
+ }, at);
2361
+ }
2362
+ var PAINT_SIZE = 512;
2363
+ var GradientPainter = class {
2364
+ constructor(size = PAINT_SIZE) {
2365
+ this.size = size;
2366
+ this.canvas.width = size;
2367
+ this.canvas.height = size;
2368
+ this.tex = Texture.from(this.canvas);
2369
+ globalThis.__gradientPainters = (globalThis.__gradientPainters ?? 0) + 1;
2370
+ }
2371
+ size;
2372
+ canvas = document.createElement("canvas");
2373
+ tex;
2374
+ /** Paint `g` and return what `Graphics.fill(...)` takes. */
2375
+ paint(g) {
2376
+ const ctx = this.canvas.getContext("2d"), s = this.size;
2377
+ if (!ctx) return new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((x) => [x.offset, x.color]) }));
2378
+ ctx.clearRect(0, 0, s, s);
2379
+ if (g.type === "radial") {
2380
+ paintRadial(ctx, s, { stops: g.stops.map((x) => [x.offset, x.color]), center: g.center, innerRadius: g.innerRadius, radius: g.radius });
2381
+ } else {
2382
+ const a = g.angle * Math.PI / 180, dx = Math.cos(a), dy = Math.sin(a);
2383
+ const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2384
+ const grad = ctx.createLinearGradient((0.5 - dx * h) * s, (0.5 - dy * h) * s, (0.5 + dx * h) * s, (0.5 + dy * h) * s);
2385
+ for (const st of g.stops) grad.addColorStop(Math.min(1, Math.max(0, st.offset)), st.color);
2386
+ ctx.fillStyle = grad;
2387
+ ctx.fillRect(0, 0, s, s);
2388
+ }
2389
+ this.tex.source.update();
2390
+ return { texture: this.tex, textureSpace: "local" };
2391
+ }
2392
+ /** Give the texture and the canvas back (a layer that is destroyed must not leave them). */
2393
+ destroy() {
2394
+ this.tex.destroy(true);
2395
+ this.canvas.width = this.canvas.height = 0;
2396
+ globalThis.__gradientPainters = Math.max(0, (globalThis.__gradientPainters ?? 1) - 1);
2397
+ }
2398
+ };
2399
+ function bindGradientKeyframes(timeline, holder, keyframes, duration, offset, colorSpace, onChange) {
2400
+ const bag = (b) => b?.fillGradient;
2401
+ for (const kf of keyframes) {
2402
+ const at = offset + resolveAt(kf.at, duration);
2403
+ const setV = bag(kf.set), fromV = bag(kf.from), toV = bag(kf.to);
2404
+ if (setV !== void 0) {
2405
+ revertibleSet(
2406
+ timeline,
2407
+ at,
2408
+ () => ({ state: holder.grad }),
2409
+ (v) => {
2410
+ holder.grad = v.state ?? mergeGrad(holder.grad, v.patch);
2411
+ onChange();
2412
+ },
2413
+ { patch: setV }
2414
+ );
2415
+ }
2416
+ if (fromV !== void 0 || toV !== void 0) {
2417
+ tweenGradient(timeline, holder, fromV, toV, kfDuration(kf), kf.ease ?? "none", at, colorSpace, onChange, loopVars(kf));
2418
+ }
2419
+ }
2420
+ }
2421
+ var hasGradientKeys = (keyframes) => (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => b && b.fillGradient !== void 0));
2422
+
2423
+ // src/sequences/Text.ts
1511
2424
  var STYLE_OPAQUE_KEYS = [
1512
2425
  "fontFamily",
1513
2426
  "fill",
@@ -1542,6 +2455,21 @@ var TextSequence = class extends Sequence {
1542
2455
  /** `visibleChars` is in play (initial or animated): otherwise the whole text shows. */
1543
2456
  _typed = false;
1544
2457
  _lastText = "";
2458
+ /** A gradient fill for the text (`fillGradient`, static or animated): rebuilt as a FillGradient on each change (the text is re-rasterised). */
2459
+ _grad = null;
2460
+ _fillGradient = null;
2461
+ _applyGradient() {
2462
+ const g = this._grad.grad;
2463
+ const next = new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((s) => [s.offset, s.color]) }));
2464
+ this.target.style.fill = next;
2465
+ this._fillGradient?.destroy();
2466
+ this._fillGradient = next;
2467
+ }
2468
+ destroy() {
2469
+ super.destroy();
2470
+ this._fillGradient?.destroy();
2471
+ this._fillGradient = null;
2472
+ }
1545
2473
  /** A typewriter that has typed nothing yet: the layer is empty on purpose (inspect must not call that "no size"). */
1546
2474
  get showsNothingYet() {
1547
2475
  return this._typed && this._lastText === "" && this._template !== "";
@@ -1588,6 +2516,12 @@ var TextSequence = class extends Sequence {
1588
2516
  this.intrinsicWidth = text.width;
1589
2517
  this.intrinsicHeight = text.height;
1590
2518
  }
2519
+ validateGradientKeyframes(this.spec, describeLayer(this.spec), void 0, "text");
2520
+ const gradientSpec = this.spec.fillGradient ?? initialProps.fillGradient;
2521
+ if (gradientSpec) {
2522
+ this._grad = { grad: gradStateFrom(gradientSpec) };
2523
+ this._applyGradient();
2524
+ }
1591
2525
  const scope = this.scope();
1592
2526
  if (initialProps.value !== void 0) this._counters.value = resolveNumber(initialProps.value, scope);
1593
2527
  this._typed = initialProps.visibleChars !== void 0 || animates(this.spec.keyframes, "visibleChars");
@@ -1606,11 +2540,12 @@ var TextSequence = class extends Sequence {
1606
2540
  bindTimeline(timeline, offset = 0) {
1607
2541
  if (!this.target) return;
1608
2542
  const colorSpace = this.spec.colorSpace ?? "rgb";
1609
- const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars"]);
2543
+ const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars", "fillGradient"]);
1610
2544
  runSuperWithStrippedSpec(this, stripped, timeline, offset);
1611
2545
  const keyframes = this.spec.keyframes ?? [];
1612
2546
  const origin = offset + this.at;
1613
2547
  bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
2548
+ if (this._grad) bindGradientKeyframes(timeline, this._grad, keyframes, this.duration, origin, colorSpace, () => this._applyGradient());
1614
2549
  const refresh = () => this._refreshText();
1615
2550
  bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
1616
2551
  bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
@@ -1660,7 +2595,7 @@ function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDu
1660
2595
  const toV = kf.to?.[key];
1661
2596
  if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
1662
2597
  const at = origin + resolveAt(kf.at, parentDuration);
1663
- const duration = kf.duration ?? 0;
2598
+ const duration = kfDuration(kf);
1664
2599
  const ease = kf.ease ?? "none";
1665
2600
  const loop = loopVars(kf);
1666
2601
  if (setV !== void 0) {
@@ -1706,7 +2641,7 @@ function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, co
1706
2641
  };
1707
2642
  for (const kf of keyframes) {
1708
2643
  const at = offset + resolveAt(kf.at, parentDuration);
1709
- const duration = kf.duration ?? 0;
2644
+ const duration = kfDuration(kf);
1710
2645
  const ease = kf.ease ?? "none";
1711
2646
  const setFill = pickFill(kf.set);
1712
2647
  const fromFill = pickFill(kf.from);
@@ -1744,6 +2679,7 @@ var AudioSequence = class extends Sequence {
1744
2679
  _audioBuffer = null;
1745
2680
  _synth = null;
1746
2681
  _source = "";
2682
+ _remap = null;
1747
2683
  async build() {
1748
2684
  this.target = null;
1749
2685
  if (this.spec.music !== void 0) return this._buildMusic(this.spec);
@@ -1752,13 +2688,16 @@ var AudioSequence = class extends Sequence {
1752
2688
  this._source = `asset "${spec.asset}"`;
1753
2689
  const data = await Assets.get(spec.asset);
1754
2690
  this._audioBuffer = data.audioBuffer;
2691
+ this._remap = remapOf(spec, "time", data.duration ?? null);
2692
+ const constant = this._remap && this._remap.timeKfs.length === 0 ? Math.abs(this._remap.speed ?? 1) : 1;
1755
2693
  if (this.duration === void 0) {
1756
2694
  const rest = Math.max(0, (this.parent?.duration ?? this.root.duration) - this.at);
1757
- this.duration = spec.duration ?? (spec.loop ? rest : data.duration) ?? this.root.duration;
2695
+ this.duration = spec.duration ?? (spec.loop ? rest : data.duration !== void 0 ? data.duration / constant : void 0) ?? this.root.duration;
1758
2696
  }
1759
- if (!spec.loop && data.duration !== void 0 && this.duration > data.duration + 0.05) {
2697
+ const playable = this._remap && this._remap.timeKfs.length > 0 ? Infinity : (data.duration ?? Infinity) / constant;
2698
+ if (!spec.loop && Number.isFinite(playable) && this.duration > playable + 0.05) {
1760
2699
  console.warn(
1761
- `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.`
2700
+ `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.`
1762
2701
  );
1763
2702
  }
1764
2703
  }
@@ -1767,7 +2706,7 @@ var AudioSequence = class extends Sequence {
1767
2706
  const raw = spec;
1768
2707
  if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and sfx \u2014 the sfx plays; remove one`);
1769
2708
  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)`);
1770
- const { resolveSfx, renderSfx, sfxKey } = await import('./sfx-S37DDC66.js');
2709
+ const { resolveSfx, renderSfx, sfxKey } = await import('./sfx-U3GM2MED.js');
1771
2710
  const sfx = resolveSfx(spec.sfx, spec.duration, who);
1772
2711
  if (!sfx) return;
1773
2712
  this.duration = sfx.length;
@@ -1786,7 +2725,7 @@ var AudioSequence = class extends Sequence {
1786
2725
  const raw = spec;
1787
2726
  if (raw.asset !== void 0) console.warn(`pixi-effects: ${who}: has both asset and music \u2014 the music plays; remove one`);
1788
2727
  if (raw.sfx !== void 0) console.warn(`pixi-effects: ${who}: has both sfx and music \u2014 the music plays; remove one`);
1789
- const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-4HDFULNR.js');
2728
+ const { resolveMusic, renderMusic, musicLength, musicKey } = await import('./music-CFEC7HKL.js');
1790
2729
  const music = resolveMusic(spec.music, who);
1791
2730
  if (!music) return;
1792
2731
  const natural = musicLength(music);
@@ -1812,7 +2751,9 @@ var AudioSequence = class extends Sequence {
1812
2751
  const initialVolume = this.spec.volume ?? this.spec.initial?.volume ?? 1;
1813
2752
  const t0 = baseTime + this.at;
1814
2753
  const dur = this.duration;
2754
+ const sourceMap = this._remap ? clockTable(this._remap, "time", dur, t0, this.scope(), t0 + dur).at : void 0;
1815
2755
  out.push({
2756
+ ...sourceMap ? { sourceMap } : {},
1816
2757
  ...this._audioBuffer ? { buffer: this._audioBuffer } : { synth: this._synth },
1817
2758
  layer: describeLayer(this.spec),
1818
2759
  source: this._source,
@@ -1871,7 +2812,7 @@ var FrameCache = class {
1871
2812
  this.capacity = options.capacity ?? 30;
1872
2813
  }
1873
2814
  _key(time) {
1874
- return Math.round(time * 1e3);
2815
+ return Math.round(time * 1e6);
1875
2816
  }
1876
2817
  async getFrameAt(time) {
1877
2818
  const key = this._key(time);
@@ -1936,6 +2877,15 @@ var FrameCache = class {
1936
2877
  }
1937
2878
  };
1938
2879
 
2880
+ // src/core/sourceTime.ts
2881
+ var FRAME_EPS = 5e-5;
2882
+ function sourceLookup(v, sourceDuration, loop) {
2883
+ if (!(sourceDuration > 0)) return Math.max(0, v);
2884
+ const w = v + FRAME_EPS;
2885
+ if (loop) return (w % sourceDuration + sourceDuration) % sourceDuration;
2886
+ return Math.min(Math.max(w, 0), sourceDuration);
2887
+ }
2888
+
1939
2889
  // src/sequences/Video.ts
1940
2890
  var VideoSequence = class extends Sequence {
1941
2891
  _sourceDuration = 0;
@@ -1944,9 +2894,15 @@ var VideoSequence = class extends Sequence {
1944
2894
  _canvas = null;
1945
2895
  _ctx = null;
1946
2896
  _drawSeq = 0;
2897
+ /** The playhead value (seconds in the file) the timeline last wrote: already remapped, and local inside a remapped composition. */
2898
+ _cur = 0;
2899
+ _remap = null;
2900
+ /** Movie asks this layer to draw the frame its own timeline is at, not the one for a time it computed. */
2901
+ selfTimed = true;
1947
2902
  async build() {
1948
2903
  const data = await Assets.get(this.spec.asset);
1949
2904
  this._sourceDuration = data.duration;
2905
+ this._remap = remapOf(this.spec, "currentTime", this._sourceDuration);
1950
2906
  this._audioBuffer = this.spec.audio !== false ? data.audioBuffer : null;
1951
2907
  this._cache = new FrameCache(data.sink, { capacity: 30 });
1952
2908
  const probeFrame = await this._cache.getFrameAt(0);
@@ -1965,7 +2921,8 @@ var VideoSequence = class extends Sequence {
1965
2921
  this.intrinsicWidth = w;
1966
2922
  this.intrinsicHeight = h;
1967
2923
  if (this.duration === void 0) {
1968
- this.duration = this.spec.duration ?? data.duration ?? this.root.duration;
2924
+ const constant = this._remap && this._remap.timeKfs.length === 0 ? Math.abs(this._remap.speed ?? 1) : 1;
2925
+ this.duration = this.spec.duration ?? (data.duration ?? this.root.duration * constant) / constant;
1969
2926
  }
1970
2927
  let currentTime = 0;
1971
2928
  const seq = this;
@@ -1975,8 +2932,9 @@ var VideoSequence = class extends Sequence {
1975
2932
  },
1976
2933
  set(v) {
1977
2934
  currentTime = v;
2935
+ seq._cur = v;
1978
2936
  const mySeq = ++seq._drawSeq;
1979
- const lookup = seq.spec.loop && seq._sourceDuration > 0 ? v % seq._sourceDuration : v;
2937
+ const lookup = sourceLookup(v, seq._sourceDuration, !!seq.spec.loop);
1980
2938
  seq._cache.getFrameAt(lookup).then((frame) => {
1981
2939
  if (mySeq !== seq._drawSeq) return;
1982
2940
  if (!frame || !seq._ctx) return;
@@ -1989,21 +2947,33 @@ var VideoSequence = class extends Sequence {
1989
2947
  });
1990
2948
  this.buildFilters();
1991
2949
  }
2950
+ displayProps() {
2951
+ const r = this._remap;
2952
+ return r ? { initial: r.restInitial, keyframes: r.restKfs } : super.displayProps();
2953
+ }
1992
2954
  bindTimeline(timeline, offset = 0) {
1993
2955
  super.bindTimeline(timeline, offset);
2956
+ const at = offset + this.at;
2957
+ if (this._remap) {
2958
+ bindClock(timeline, this.target, "currentTime", this._remap, this.duration, at, this.scope());
2959
+ return;
2960
+ }
1994
2961
  const playDuration = this.spec.loop ? this.duration : Math.min(this.duration, this._sourceDuration);
1995
2962
  timeline.fromTo(
1996
2963
  this.target,
1997
2964
  { currentTime: 0 },
1998
2965
  { currentTime: playDuration, duration: playDuration, ease: "none" },
1999
- offset + this.at
2966
+ at
2000
2967
  // offset = start of the enclosing composition(s); dropping it misplaced videos inside nested compositions
2001
2968
  );
2002
2969
  }
2003
2970
  collectAudio(out, baseTime) {
2004
2971
  if (!this._audioBuffer) return;
2005
2972
  const initialVolume = this.spec.volume ?? 1;
2973
+ const t0 = baseTime + this.at;
2974
+ const sourceMap = this._remap ? clockTable(this._remap, "currentTime", this.duration, t0, this.scope(), t0 + this.duration).at : void 0;
2006
2975
  out.push({
2976
+ ...sourceMap ? { sourceMap } : {},
2007
2977
  buffer: this._audioBuffer,
2008
2978
  layer: describeLayer(this.spec),
2009
2979
  source: `video "${this.spec.asset}"`,
@@ -2014,9 +2984,12 @@ var VideoSequence = class extends Sequence {
2014
2984
  volumeKeyframes: []
2015
2985
  });
2016
2986
  }
2017
- async awaitFrameAt(time) {
2987
+ /** Draw the frame the playhead is at NOW (`_cur`). The argument (a time Movie worked out for layers that need it) is not used. */
2988
+ async awaitFrameAt(_time) {
2989
+ const time = this._cur;
2990
+ if (!this.keepHidden && !this.target?.renderable) return;
2018
2991
  const mySeq = ++this._drawSeq;
2019
- const lookup = this.spec.loop && this._sourceDuration > 0 ? time % this._sourceDuration : time;
2992
+ const lookup = sourceLookup(time, this._sourceDuration, !!this.spec.loop);
2020
2993
  let frame = null;
2021
2994
  try {
2022
2995
  frame = await this._cache.getFrameAt(lookup);
@@ -2040,39 +3013,6 @@ var VideoSequence = class extends Sequence {
2040
3013
  super.destroy();
2041
3014
  }
2042
3015
  };
2043
-
2044
- // src/sequences/gradient.ts
2045
- function cssColor(c) {
2046
- return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
2047
- }
2048
- function normalizedStops(g) {
2049
- const stops = g.stops ?? [];
2050
- if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
2051
- return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
2052
- }
2053
- function gradientOptions(g) {
2054
- const colorStops = normalizedStops(g);
2055
- const a = (g.angle ?? 90) * Math.PI / 180;
2056
- const dx = Math.cos(a), dy = Math.sin(a);
2057
- const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2058
- return {
2059
- type: "linear",
2060
- start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
2061
- end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
2062
- colorStops,
2063
- textureSpace: "local"
2064
- };
2065
- }
2066
- function paintRadial(ctx, size, g) {
2067
- const stops = normalizedStops(g);
2068
- const [cx, cy] = g.center ?? [0.5, 0.5];
2069
- const grad = ctx.createRadialGradient(cx * size, cy * size, (g.innerRadius ?? 0) * size, cx * size, cy * size, (g.radius ?? 0.5) * size);
2070
- for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
2071
- ctx.fillStyle = grad;
2072
- ctx.fillRect(0, 0, size, size);
2073
- }
2074
-
2075
- // src/sequences/gradientFill.ts
2076
3016
  var RADIAL_SIZE = 256;
2077
3017
  function makeGradientFill(g) {
2078
3018
  if (g.type === "radial") {
@@ -2264,7 +3204,7 @@ function morphAt(pairs, t) {
2264
3204
  }
2265
3205
 
2266
3206
  // src/sequences/Shape.ts
2267
- var STYLE_KEYS = ["fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth"];
3207
+ var STYLE_KEYS2 = ["fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth"];
2268
3208
  var COLOR_KEYS = /* @__PURE__ */ new Set(["fillColor", "strokeColor"]);
2269
3209
  var TRIM_KEYS = ["trimStart", "trimEnd"];
2270
3210
  var GEOMETRY_KEYS_BY_SHAPE = {
@@ -2290,7 +3230,7 @@ var ShapeSequence = class extends Sequence {
2290
3230
  // by `Container.onRender`.
2291
3231
  _drawGeometry = () => {
2292
3232
  };
2293
- _liveKeys = new Set(STYLE_KEYS);
3233
+ _liveKeys = new Set(STYLE_KEYS2);
2294
3234
  async build() {
2295
3235
  const graphics = new Graphics({ label: this.spec.name });
2296
3236
  graphics.cullable = true;
@@ -2316,7 +3256,15 @@ var ShapeSequence = class extends Sequence {
2316
3256
  this._state.trimEnd ??= 1;
2317
3257
  }
2318
3258
  const gradientSpec = this.spec.fillGradient ?? this.spec.initial?.fillGradient;
2319
- if (gradientSpec) this._state.fillStyle = makeGradientFill(gradientSpec);
3259
+ validateGradientKeyframes(this.spec, describeLayer(this.spec));
3260
+ if (gradientSpec) {
3261
+ if (hasGradientKeys(this.spec.keyframes)) {
3262
+ this._grad = { grad: gradStateFrom(gradientSpec) };
3263
+ this._painter = new GradientPainter();
3264
+ this._gradDirty = true;
3265
+ this._liveKeys.add("fillGradient");
3266
+ } else this._state.fillStyle = makeGradientFill(gradientSpec);
3267
+ }
2320
3268
  const morph = this._setupMorph();
2321
3269
  this._drawGeometry = makeDrawGeometry(this.spec, scope, this._state, morph);
2322
3270
  this._outline = makeOutline(this.spec, scope, this._state, morph);
@@ -2375,11 +3323,23 @@ var ShapeSequence = class extends Sequence {
2375
3323
  this._state.morph ??= 0;
2376
3324
  return { pairs, to: String(top.morphTo) };
2377
3325
  }
3326
+ destroy() {
3327
+ super.destroy();
3328
+ this._painter?.destroy();
3329
+ this._painter = null;
3330
+ }
2378
3331
  _fresh = false;
3332
+ _grad = null;
3333
+ _painter = null;
3334
+ _gradDirty = false;
2379
3335
  _outline = null;
2380
3336
  _redraw() {
2381
3337
  const graphics = this.target;
2382
3338
  const s = this._state;
3339
+ if (this._grad && this._gradDirty) {
3340
+ s.fillStyle = this._painter.paint(this._grad.grad);
3341
+ this._gradDirty = false;
3342
+ }
2383
3343
  graphics.clear();
2384
3344
  const trimmed = (s.trimStart ?? 0) > 0 || (s.trimEnd ?? 1) < 1;
2385
3345
  if (!trimmed || !this._outline || !(s.strokeColor !== void 0 && s.strokeWidth > 0)) {
@@ -2425,6 +3385,9 @@ var ShapeSequence = class extends Sequence {
2425
3385
  applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime);
2426
3386
  const colorSpace = this.spec.colorSpace ?? "rgb";
2427
3387
  bindLiveKeyframes(timeline, this._state, this._liveKeys, this.spec.keyframes ?? [], this.duration, scope, startTime, colorSpace);
3388
+ if (this._grad) bindGradientKeyframes(timeline, this._grad, this.spec.keyframes ?? [], this.duration, startTime, colorSpace, () => {
3389
+ this._gradDirty = true;
3390
+ });
2428
3391
  this.absoluteStart = startTime;
2429
3392
  const endTime = startTime + this.duration;
2430
3393
  this.target.renderable = startTime <= 0;
@@ -2461,7 +3424,7 @@ function seedStrokeStyle(state, spec, initial, who) {
2461
3424
  }
2462
3425
  }
2463
3426
  function seedStyle(state, initial, scope) {
2464
- for (const k of STYLE_KEYS) {
3427
+ for (const k of STYLE_KEYS2) {
2465
3428
  const v = initial[k];
2466
3429
  if (v === void 0) continue;
2467
3430
  state[k] = COLOR_KEYS.has(k) ? v : numOrZero(v, scope);
@@ -2684,13 +3647,14 @@ function makeOutline(spec, scope, s, morph) {
2684
3647
  function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration, scope, offset, colorSpace) {
2685
3648
  for (const kf of keyframes) {
2686
3649
  const at = offset + resolveAt(kf.at, parentDuration);
2687
- const duration = kf.duration ?? 0;
3650
+ const duration = kfDuration(kf);
2688
3651
  const ease = kf.ease ?? "none";
2689
3652
  const loop = loopVars(kf);
2690
3653
  if (kf.set) {
2691
3654
  const live = pickLive(kf.set, liveKeys);
2692
3655
  if (live) {
2693
3656
  for (const [k, v] of Object.entries(live)) {
3657
+ if (k === "fillGradient") continue;
2694
3658
  const resolved = resolveLiveValue(k, v, scope);
2695
3659
  const live2 = state;
2696
3660
  revertibleSet(timeline, at, () => live2[k], (v2) => {
@@ -2706,6 +3670,7 @@ function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration,
2706
3670
  ...toLive ? Object.keys(toLive) : []
2707
3671
  ]);
2708
3672
  for (const k of allKeys) {
3673
+ if (k === "fillGradient") continue;
2709
3674
  const key = k;
2710
3675
  const fromRaw = fromLive?.[key];
2711
3676
  const toRaw = toLive?.[key];
@@ -2754,7 +3719,7 @@ function numOrZero(v, scope) {
2754
3719
  var _CompositionSequence = null;
2755
3720
  async function getCompositionSequence() {
2756
3721
  if (!_CompositionSequence) {
2757
- _CompositionSequence = (await import('./Composition-MLBHYYFF.js')).CompositionSequence;
3722
+ _CompositionSequence = (await import('./Composition-6PA4C4TV.js')).CompositionSequence;
2758
3723
  }
2759
3724
  return _CompositionSequence;
2760
3725
  }
@@ -2792,6 +3757,6 @@ async function buildSequenceTree(specs, parent, root) {
2792
3757
  return out;
2793
3758
  }
2794
3759
 
2795
- export { CameraSequence, DEFAULT_FOV, DEG, NEAR, NullSequence, Sequence, buildScope, buildSequenceTree, cameraBasis, checkEase, collectPropKeys, describeLayer, ensureFilterLibrary, evaluateExpr, flattenSvgPath, homeCamera, homeDistance, lintText, lintTiming, noise, projectLayer, rand, random, registerSequenceType, resolveAt, summarizeWarnings };
2796
- //# sourceMappingURL=chunk-SG3Y66FN.js.map
2797
- //# sourceMappingURL=chunk-SG3Y66FN.js.map
3760
+ export { CameraSequence, DEFAULT_FOV, DEG, NEAR, NullSequence, Sequence, bindClock, buildScope, buildSequenceTree, cameraBasis, checkEase, clockTable, collectPropKeys, contentLength, describeLayer, ensureFilterLibrary, evaluateExpr, flattenSvgPath, homeCamera, homeDistance, lintKeys, lintText, lintTiming, noise, projectLayer, rand, random, registerSequenceType, remapOf, resolveAt, summarizeWarnings, timeRemapOf };
3761
+ //# sourceMappingURL=chunk-DQHXVNOI.js.map
3762
+ //# sourceMappingURL=chunk-DQHXVNOI.js.map