pixi-effects 0.18.1 → 0.19.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 (52) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +6 -6
  3. package/ai/CHAT.md +1 -1
  4. package/ai/SKILL.md +1 -1
  5. package/ai/chat-template.html +4 -4
  6. package/ai/reference/cheatsheet.md +4 -4
  7. package/ai/reference/pitfalls.md +4 -0
  8. package/ai/reference/recipes.md +65 -0
  9. package/ai/template.html +5 -5
  10. package/ai/tools/check.mjs +14 -3
  11. package/dist/{Base-BQUodYZK.d.ts → Base-B4WbUJO2.d.ts} +1 -1
  12. package/dist/{Base-OHqp8qvX.d.cts → Base-i88XXaiP.d.cts} +1 -1
  13. package/dist/Composition-KYNPRWK3.cjs +14 -0
  14. package/dist/{Composition-6JMXWNVV.cjs.map → Composition-KYNPRWK3.cjs.map} +1 -1
  15. package/dist/Composition-QBKEEJBV.js +5 -0
  16. package/dist/{Composition-MLBHYYFF.js.map → Composition-QBKEEJBV.js.map} +1 -1
  17. package/dist/Controller.d.cts +2 -2
  18. package/dist/Controller.d.ts +2 -2
  19. package/dist/{Movie-DlHQ9C02.d.ts → Movie-8IrqL3kO.d.ts} +6 -1
  20. package/dist/{Movie-K2YuVAdg.d.cts → Movie-CpmdNadg.d.cts} +6 -1
  21. package/dist/Presenter.d.cts +2 -2
  22. package/dist/Presenter.d.ts +2 -2
  23. package/dist/{chunk-UXDR3ZDU.js → chunk-B3YB2TNY.js} +4 -3
  24. package/dist/chunk-B3YB2TNY.js.map +1 -0
  25. package/dist/{chunk-JW67MDHQ.cjs → chunk-CTZRFSVU.cjs} +27 -26
  26. package/dist/chunk-CTZRFSVU.cjs.map +1 -0
  27. package/dist/{chunk-ANO5R2E6.cjs → chunk-VTBT6NNE.cjs} +755 -48
  28. package/dist/chunk-VTBT6NNE.cjs.map +1 -0
  29. package/dist/{chunk-SG3Y66FN.js → chunk-YRXTKOV5.js} +757 -51
  30. package/dist/chunk-YRXTKOV5.js.map +1 -0
  31. package/dist/index.cjs +63 -36
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.d.cts +4 -4
  34. package/dist/index.d.ts +4 -4
  35. package/dist/index.js +40 -13
  36. package/dist/index.js.map +1 -1
  37. package/dist/loader.css +1 -1
  38. package/dist/three.cjs +5 -5
  39. package/dist/three.d.cts +2 -2
  40. package/dist/three.d.ts +2 -2
  41. package/dist/three.js +1 -1
  42. package/dist/{types-SS51lP6O.d.cts → types-bzLJ7aUx.d.cts} +16 -6
  43. package/dist/{types-SS51lP6O.d.ts → types-bzLJ7aUx.d.ts} +16 -6
  44. package/docs/dsl.md +36 -5
  45. package/llms-full.txt +110 -10
  46. package/package.json +1 -1
  47. package/dist/Composition-6JMXWNVV.cjs +0 -14
  48. package/dist/Composition-MLBHYYFF.js +0 -5
  49. package/dist/chunk-ANO5R2E6.cjs.map +0 -1
  50. package/dist/chunk-JW67MDHQ.cjs.map +0 -1
  51. package/dist/chunk-SG3Y66FN.js.map +0 -1
  52. package/dist/chunk-UXDR3ZDU.js.map +0 -1
@@ -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 = {
@@ -315,6 +422,13 @@ var EASE_NAMES = ["none", ...FAMILIES.flatMap((f) => [`${f}.in`, `${f}.out`, `${
315
422
  var warned = /* @__PURE__ */ new Set();
316
423
  function checkEase(ease, where) {
317
424
  if (typeof ease !== "string") return;
425
+ if (/^\s*spring/.test(ease)) {
426
+ const problem = springProblem(ease);
427
+ if (!problem || warned.has(ease)) return;
428
+ warned.add(ease);
429
+ console.warn(`pixi-effects: ${where}: unknown ease "${ease}" \u2014 ${problem}. GSAP would run it as its default ease, power1.out.`);
430
+ return;
431
+ }
318
432
  const found = gsap.gsap.parseEase(ease);
319
433
  if (typeof found === "function") return;
320
434
  if (warned.has(ease)) return;
@@ -355,7 +469,7 @@ function resolveAt(at, duration) {
355
469
  }
356
470
  function normalizeKeyframe(kf, parentDuration) {
357
471
  const at = resolveAt(kf.at, parentDuration);
358
- const duration = kf.duration ?? 0;
472
+ const duration = kfDuration(kf);
359
473
  const ease = kf.ease ?? "none";
360
474
  checkEase(ease, "a keyframe");
361
475
  const hasSet = !!kf.set;
@@ -785,8 +899,217 @@ var ChromaKeyFilter = class extends PIXI.Filter {
785
899
  u[2] = rgb[2];
786
900
  }
787
901
  };
902
+ var HASH_GL = `
903
+ uint hash(uint x) { x ^= x >> 16; x *= 0x7feb352du; x ^= x >> 15; x *= 0x846ca68bu; x ^= x >> 16; return x; }
904
+ // one gaussian-ish value (sum of the four bytes of a hash), mean 0, std 1
905
+ float gauss(ivec2 p, uint k) {
906
+ uint h = hash(uint(p.x) + hash(uint(p.y) + hash(k)));
907
+ float s = float(h & 255u) + float((h >> 8) & 255u) + float((h >> 16) & 255u) + float(h >> 24);
908
+ return (s / 255.0 - 2.0) * 1.7320508;
909
+ }
910
+ // the grain value at a pixel: cells of size pixels, smoothly blended
911
+ float cell(vec2 pos, float size, uint k) {
912
+ vec2 q = pos / size - 0.5;
913
+ vec2 i = floor(q);
914
+ vec2 f = fract(q);
915
+ ivec2 p = ivec2(i);
916
+ 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);
917
+ float v = mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
918
+ vec2 wx = vec2(1.0 - f.x, f.x), wy = vec2(1.0 - f.y, f.y);
919
+ return v * inversesqrt(dot(wx, wx) * dot(wy, wy)); // blending lowers the spread: keep std exactly 1 at every pixel
920
+ }`;
921
+ var GL_VERTEX = `#version 300 es
922
+ in vec2 aPosition;
923
+ out vec2 vTextureCoord;
924
+ uniform vec4 uInputSize;
925
+ uniform vec4 uOutputFrame;
926
+ uniform vec4 uOutputTexture;
927
+ void main(void) {
928
+ vec2 position = aPosition * uOutputFrame.zw + uOutputFrame.xy;
929
+ position.x = position.x * (2.0 / uOutputTexture.x) - 1.0;
930
+ position.y = position.y * (2.0 * uOutputTexture.z / uOutputTexture.y) - uOutputTexture.z;
931
+ gl_Position = vec4(position, 0.0, 1.0);
932
+ vTextureCoord = aPosition * (uOutputFrame.zw * uInputSize.zw);
933
+ }`;
934
+ var GL_FRAGMENT2 = `#version 300 es
935
+ precision highp float;
936
+ precision highp int;
937
+ in vec2 vTextureCoord;
938
+ uniform sampler2D uTexture;
939
+ uniform vec4 uInputSize;
940
+ uniform float uAmount;
941
+ uniform float uSize;
942
+ uniform float uSeed;
943
+ uniform float uFrame;
944
+ uniform float uColor;
945
+ out vec4 finalColor;
946
+ ${HASH_GL}
947
+ void main(void) {
948
+ vec4 raw = texture(uTexture, vTextureCoord);
949
+ vec3 rgb = raw.a > 0.0 ? raw.rgb / raw.a : vec3(0.0);
950
+ float L = dot(rgb, vec3(0.2126, 0.7152, 0.0722));
951
+ float w = clamp(4.0 * L * (1.0 - L), 0.0, 1.0);
952
+ vec2 pos = vTextureCoord * uInputSize.xy;
953
+ uint k = hash(uint(uSeed) * 0x9E3779B1u + uint(uFrame));
954
+ float n0 = cell(pos, uSize, k);
955
+ vec3 n = vec3(n0);
956
+ if (uColor > 0.0) {
957
+ vec3 nc = vec3(cell(pos, uSize, k + 1u), cell(pos, uSize, k + 2u), cell(pos, uSize, k + 3u));
958
+ n = mix(n, nc, uColor);
959
+ }
960
+ rgb = clamp(rgb + n * (uAmount * w), 0.0, 1.0);
961
+ finalColor = vec4(rgb * raw.a, raw.a);
962
+ }`;
963
+ var WGSL_SOURCE2 = `
964
+ struct GlobalFilterUniforms {
965
+ uInputSize: vec4<f32>,
966
+ uInputPixel: vec4<f32>,
967
+ uInputClamp: vec4<f32>,
968
+ uOutputFrame: vec4<f32>,
969
+ uGlobalFrame: vec4<f32>,
970
+ uOutputTexture: vec4<f32>,
971
+ };
972
+ struct GrainUniforms {
973
+ uAmount: f32,
974
+ uSize: f32,
975
+ uSeed: f32,
976
+ uFrame: f32,
977
+ uColor: f32,
978
+ };
979
+ @group(0) @binding(0) var<uniform> gfu: GlobalFilterUniforms;
980
+ @group(0) @binding(1) var uTexture: texture_2d<f32>;
981
+ @group(0) @binding(2) var uSampler : sampler;
982
+ @group(1) @binding(0) var<uniform> grainUniforms : GrainUniforms;
983
+
984
+ struct VSOutput {
985
+ @builtin(position) position: vec4<f32>,
986
+ @location(0) uv : vec2<f32>,
987
+ };
988
+ fn filterVertexPosition(aPosition : vec2<f32>) -> vec4<f32> {
989
+ var position = aPosition * gfu.uOutputFrame.zw + gfu.uOutputFrame.xy;
990
+ position.x = position.x * (2.0 / gfu.uOutputTexture.x) - 1.0;
991
+ position.y = position.y * (2.0 * gfu.uOutputTexture.z / gfu.uOutputTexture.y) - gfu.uOutputTexture.z;
992
+ return vec4<f32>(position, 0.0, 1.0);
993
+ }
994
+ fn filterTextureCoord(aPosition : vec2<f32>) -> vec2<f32> {
995
+ return aPosition * (gfu.uOutputFrame.zw * gfu.uInputSize.zw);
996
+ }
997
+ @vertex
998
+ fn mainVertex(
999
+ @location(0) aPosition : vec2<f32>,
1000
+ ) -> VSOutput {
1001
+ return VSOutput(
1002
+ filterVertexPosition(aPosition),
1003
+ filterTextureCoord(aPosition),
1004
+ );
1005
+ }
1006
+
1007
+ fn hash(xIn: u32) -> u32 {
1008
+ var x = xIn;
1009
+ x = x ^ (x >> 16u); x = x * 0x7feb352du; x = x ^ (x >> 15u); x = x * 0x846ca68bu; x = x ^ (x >> 16u);
1010
+ return x;
1011
+ }
1012
+ fn gauss(p: vec2<i32>, k: u32) -> f32 {
1013
+ let h = hash(u32(p.x) + hash(u32(p.y) + hash(k)));
1014
+ let s = f32(h & 255u) + f32((h >> 8u) & 255u) + f32((h >> 16u) & 255u) + f32(h >> 24u);
1015
+ return (s / 255.0 - 2.0) * 1.7320508;
1016
+ }
1017
+ fn cell(pos: vec2<f32>, size: f32, k: u32) -> f32 {
1018
+ let q = pos / size - vec2<f32>(0.5);
1019
+ let i = floor(q);
1020
+ let f = fract(q);
1021
+ let p = vec2<i32>(i);
1022
+ let a = gauss(p, k);
1023
+ let b = gauss(p + vec2<i32>(1, 0), k);
1024
+ let c = gauss(p + vec2<i32>(0, 1), k);
1025
+ let d = gauss(p + vec2<i32>(1, 1), k);
1026
+ let v = mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
1027
+ let wx = vec2<f32>(1.0 - f.x, f.x);
1028
+ let wy = vec2<f32>(1.0 - f.y, f.y);
1029
+ return v * inverseSqrt(dot(wx, wx) * dot(wy, wy));
1030
+ }
1031
+
1032
+ @fragment
1033
+ fn mainFragment(@location(0) uv : vec2<f32>) -> @location(0) vec4<f32> {
1034
+ let raw = textureSample(uTexture, uSampler, uv);
1035
+ var rgb = vec3<f32>(0.0);
1036
+ if (raw.a > 0.0) { rgb = raw.rgb / raw.a; }
1037
+ let L = dot(rgb, vec3<f32>(0.2126, 0.7152, 0.0722));
1038
+ let w = clamp(4.0 * L * (1.0 - L), 0.0, 1.0);
1039
+ let pos = uv * gfu.uInputSize.xy;
1040
+ let k = hash(u32(grainUniforms.uSeed) * 0x9E3779B1u + u32(grainUniforms.uFrame));
1041
+ let n0 = cell(pos, grainUniforms.uSize, k);
1042
+ var n = vec3<f32>(n0);
1043
+ if (grainUniforms.uColor > 0.0) {
1044
+ let nc = vec3<f32>(cell(pos, grainUniforms.uSize, k + 1u), cell(pos, grainUniforms.uSize, k + 2u), cell(pos, grainUniforms.uSize, k + 3u));
1045
+ n = mix(n, nc, grainUniforms.uColor);
1046
+ }
1047
+ rgb = clamp(rgb + n * (grainUniforms.uAmount * w), vec3<f32>(0.0), vec3<f32>(1.0));
1048
+ return vec4<f32>(rgb * raw.a, raw.a);
1049
+ }`;
1050
+ var GRAIN_KEYS = ["amount", "size", "seed", "fps", "color"];
1051
+ var GrainFilter = class extends PIXI.Filter {
1052
+ _fps;
1053
+ constructor(options = {}) {
1054
+ chunkUQBGW5DO_cjs.warnUnknownOptions("grain filter", options, GRAIN_KEYS);
1055
+ const { amount = 0.08, size = 1.5, seed = 0, fps = 24, color = 0 } = options;
1056
+ super({
1057
+ glProgram: PIXI.GlProgram.from({ vertex: GL_VERTEX, fragment: GL_FRAGMENT2, name: "grain-filter" }),
1058
+ gpuProgram: PIXI.GpuProgram.from({ vertex: { source: WGSL_SOURCE2, entryPoint: "mainVertex" }, fragment: { source: WGSL_SOURCE2, entryPoint: "mainFragment" } }),
1059
+ resources: {
1060
+ grainUniforms: new PIXI.UniformGroup({
1061
+ uAmount: { value: amount, type: "f32" },
1062
+ uSize: { value: Math.max(size, 0.25), type: "f32" },
1063
+ uSeed: { value: Math.floor(seed), type: "f32" },
1064
+ uFrame: { value: 0, type: "f32" },
1065
+ uColor: { value: color, type: "f32" }
1066
+ })
1067
+ }
1068
+ });
1069
+ this._fps = fps;
1070
+ }
1071
+ get u() {
1072
+ return this.resources.grainUniforms.uniforms;
1073
+ }
1074
+ get amount() {
1075
+ return this.u.uAmount;
1076
+ }
1077
+ set amount(v) {
1078
+ this.u.uAmount = v;
1079
+ }
1080
+ get size() {
1081
+ return this.u.uSize;
1082
+ }
1083
+ set size(v) {
1084
+ this.u.uSize = Math.max(v, 0.25);
1085
+ }
1086
+ get seed() {
1087
+ return this.u.uSeed;
1088
+ }
1089
+ set seed(v) {
1090
+ this.u.uSeed = Math.floor(v);
1091
+ }
1092
+ get color() {
1093
+ return this.u.uColor;
1094
+ }
1095
+ set color(v) {
1096
+ this.u.uColor = v;
1097
+ }
1098
+ get fps() {
1099
+ return this._fps;
1100
+ }
1101
+ set fps(v) {
1102
+ this._fps = v;
1103
+ }
1104
+ /** The movie calls this with the time (s) of the frame it is about to draw: the grain is new every 1/fps s. */
1105
+ setTime(t) {
1106
+ this.u.uFrame = this._fps > 0 ? Math.floor(t * this._fps + 1e-6) : 0;
1107
+ }
1108
+ };
1109
+
1110
+ // src/filters/named.ts
788
1111
  var CORE = { blur: "BlurFilter", noise: "NoiseFilter", alpha: "AlphaFilter" };
789
- var CORE_TYPES = [...Object.keys(CORE), "colorMatrix"];
1112
+ var CORE_TYPES = [...Object.keys(CORE), "colorMatrix", "grain"];
790
1113
  var LIBRARY_TYPES = [
791
1114
  "adjustment",
792
1115
  "advancedBloom",
@@ -903,6 +1226,7 @@ function colorGradientParams(params) {
903
1226
  }
904
1227
  function createNamedFilter(type, params) {
905
1228
  if (lower(type) === "colormatrix") return colorMatrix(params);
1229
+ if (lower(type) === "grain") return new GrainFilter(params);
906
1230
  const core = Object.entries(CORE).find(([k]) => lower(k) === lower(type));
907
1231
  if (core) return new PIXI__namespace[core[1]](params);
908
1232
  if (!isLibraryType(type)) throw unknownType(type);
@@ -1051,6 +1375,140 @@ function collectPropKeys(spec) {
1051
1375
  return out;
1052
1376
  }
1053
1377
 
1378
+ // src/core/layerKeys.ts
1379
+ var COMMON = ["type", "name", "parent", "at", "duration", "initial", "keyframes", "filters", "filterArea", "mask", "maskInverted", "blendMode", "threeD", "hideBehindCamera"];
1380
+ var VIDEO = [...COMMON, "asset", "loop", "audio", "volume"];
1381
+ var IMAGE = [...COMMON, "asset", "colorSpace"];
1382
+ var TEXT = [...COMMON, "text", "format", "style", "colorSpace", "fillGradient"];
1383
+ var AUDIO = [...COMMON, "volume", "asset", "loop", "sfx", "music"];
1384
+ var COMPOSITION = [...COMMON, "width", "height", "sequences", "transitions"];
1385
+ var CAMERA = [...COMMON];
1386
+ var NULL = [...COMMON];
1387
+ var SHAPE = [...COMMON, "shape", "fillGradient", "colorSpace", "strokeCap", "strokeJoin"];
1388
+ var TRIM = ["trimStart", "trimEnd", "trimEach"];
1389
+ var RECT = [...SHAPE, ...TRIM, "width", "height", "cornerRadius", "anchorX", "anchorY"];
1390
+ var CIRCLE = [...SHAPE, ...TRIM, "radius", "anchorX", "anchorY"];
1391
+ var ELLIPSE = [...SHAPE, ...TRIM, "radiusX", "radiusY", "anchorX", "anchorY"];
1392
+ var ARC = [...SHAPE, "radius", "innerRadius", "startAngle", "endAngle"];
1393
+ var LINE = [...SHAPE, ...TRIM, "from", "to"];
1394
+ var POLYGON = [...SHAPE, ...TRIM, "points", "open"];
1395
+ var PATH = [...SHAPE, ...TRIM, "d", "morphTo", "morph", "morphPoints"];
1396
+ var SHAPES = { rect: RECT, circle: CIRCLE, ellipse: ELLIPSE, arc: ARC, line: LINE, polygon: POLYGON, path: PATH };
1397
+ var BY_TYPE = { video: VIDEO, image: IMAGE, text: TEXT, audio: AUDIO, composition: COMPOSITION, camera: CAMERA, null: NULL };
1398
+ function kindName(spec) {
1399
+ return spec.type === "shape" && spec.shape ? `${spec.shape} shape` : `${spec.type} layer`;
1400
+ }
1401
+ function layerKeys(spec) {
1402
+ if (spec.type === "shape") return spec.shape && SHAPES[spec.shape] || null;
1403
+ return BY_TYPE[spec.type] ?? null;
1404
+ }
1405
+ function kindsWithKey(key) {
1406
+ const out = [];
1407
+ for (const [shape, keys] of Object.entries(SHAPES)) if (keys.includes(key)) out.push(`${shape} shape`);
1408
+ for (const [type, keys] of Object.entries(BY_TYPE)) if (keys.includes(key)) out.push(`${type} layer`);
1409
+ return out;
1410
+ }
1411
+ var PROP_KEYS = /* @__PURE__ */ new Set([
1412
+ // transform and look (GSAP + PixiPlugin names)
1413
+ "x",
1414
+ "y",
1415
+ "z",
1416
+ "alpha",
1417
+ "rotation",
1418
+ "rotationX",
1419
+ "rotationY",
1420
+ "width",
1421
+ "height",
1422
+ "visible",
1423
+ "autoAlpha",
1424
+ "scale",
1425
+ "scaleX",
1426
+ "scaleY",
1427
+ "anchor",
1428
+ "anchorX",
1429
+ "anchorY",
1430
+ "pivot",
1431
+ "pivotX",
1432
+ "pivotY",
1433
+ "skew",
1434
+ "skewX",
1435
+ "skewY",
1436
+ "position",
1437
+ "positionX",
1438
+ "positionY",
1439
+ "tint",
1440
+ "colorize",
1441
+ "colorizeAmount",
1442
+ "blur",
1443
+ "blurX",
1444
+ "blurY",
1445
+ "blurPadding",
1446
+ // shapes
1447
+ "fillColor",
1448
+ "fillAlpha",
1449
+ "strokeColor",
1450
+ "strokeAlpha",
1451
+ "strokeWidth",
1452
+ "lineColor",
1453
+ "lineAlpha",
1454
+ "cornerRadius",
1455
+ "radius",
1456
+ "radiusX",
1457
+ "radiusY",
1458
+ "innerRadius",
1459
+ "startAngle",
1460
+ "endAngle",
1461
+ "trimStart",
1462
+ "trimEnd",
1463
+ "trimEach",
1464
+ "morph",
1465
+ "fillGradient",
1466
+ "strokeCap",
1467
+ "strokeJoin",
1468
+ // text, audio, video
1469
+ "fill",
1470
+ "value",
1471
+ "visibleChars",
1472
+ "text",
1473
+ "volume",
1474
+ // camera
1475
+ "lookAtX",
1476
+ "lookAtY",
1477
+ "lookAtZ",
1478
+ "fov",
1479
+ "offsetX",
1480
+ "offsetY",
1481
+ "offsetZ",
1482
+ "lookOffsetX",
1483
+ "lookOffsetY",
1484
+ "lookOffsetZ"
1485
+ ]);
1486
+ var STYLE_KEYS = [
1487
+ "fontSize",
1488
+ "fontFamily",
1489
+ "fontWeight",
1490
+ "fontStyle",
1491
+ "fontVariant",
1492
+ "fill",
1493
+ "stroke",
1494
+ "dropShadow",
1495
+ "letterSpacing",
1496
+ "lineHeight",
1497
+ "leading",
1498
+ "align",
1499
+ "wordWrap",
1500
+ "wordWrapWidth",
1501
+ "breakWords",
1502
+ "whiteSpace",
1503
+ "padding",
1504
+ "textBaseline",
1505
+ "trim",
1506
+ "lineJoin",
1507
+ "miterLimit",
1508
+ "filters",
1509
+ "tagStyles"
1510
+ ];
1511
+
1054
1512
  // src/core/lint.ts
1055
1513
  var defaultWarn = (m) => console.warn(m);
1056
1514
  function describeLayer(spec) {
@@ -1089,6 +1547,38 @@ function lintText(spec, warn = defaultWarn) {
1089
1547
  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
1548
  }
1091
1549
  }
1550
+ function lintKeys(spec, warn = defaultWarn) {
1551
+ const who = describeLayer(spec);
1552
+ const valid = layerKeys(spec);
1553
+ if (valid) {
1554
+ for (const key of Object.keys(spec)) {
1555
+ if (valid.includes(key) || spec[key] === void 0) continue;
1556
+ const guess = chunkUQBGW5DO_cjs.suggestName(key, valid.filter((k) => k !== "type"));
1557
+ const owners = kindsWithKey(key);
1558
+ const belongs = owners.length ? ` (it belongs to ${owners.slice(0, 3).join(", ")})` : "";
1559
+ const hint = guess ? ` \u2014 did you mean "${guess}"?${belongs}` : belongs;
1560
+ warn(`pixi-effects: ${who}: "${key}" is not a ${kindName(spec)} key${hint}. Valid keys: ${valid.join(", ")}`);
1561
+ }
1562
+ }
1563
+ const said = /* @__PURE__ */ new Set();
1564
+ const bags = [spec.initial, ...(spec.keyframes ?? []).flatMap((kf) => [kf.set, kf.to, kf.from])];
1565
+ for (const bag of bags) {
1566
+ for (const key of Object.keys(bag ?? {})) {
1567
+ if (bag[key] === void 0) continue;
1568
+ if (PROP_KEYS.has(key) || key.includes(".") || /^gradient/i.test(key) || said.has(key)) continue;
1569
+ said.add(key);
1570
+ const guess = chunkUQBGW5DO_cjs.suggestName(key, [...PROP_KEYS]);
1571
+ warn(`pixi-effects: ${who}: "${key}" is not an animatable property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (in initial, set, to or from). See the cheatsheet for the properties of this kind of layer`);
1572
+ }
1573
+ }
1574
+ if (spec.type === "text" && spec.style && typeof spec.style === "object") {
1575
+ for (const key of Object.keys(spec.style)) {
1576
+ if (STYLE_KEYS.includes(key) || spec.style[key] === void 0) continue;
1577
+ const guess = chunkUQBGW5DO_cjs.suggestName(key, STYLE_KEYS);
1578
+ warn(`pixi-effects: ${who}: style.${key} is not a text style key${guess ? ` \u2014 did you mean "${guess}"?` : ""} (it is ignored). Keys: ${STYLE_KEYS.join(", ")}`);
1579
+ }
1580
+ }
1581
+ }
1092
1582
 
1093
1583
  // src/space/math.ts
1094
1584
  var DEG = Math.PI / 180;
@@ -1414,6 +1904,7 @@ function shortestHueDelta(from, to) {
1414
1904
  }
1415
1905
 
1416
1906
  // src/expr/colorTween.ts
1907
+ var css = (c) => typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
1417
1908
  function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, at, colorSpace, onUpdate, loop = {}) {
1418
1909
  let interp = null;
1419
1910
  const proxy = { p: 0 };
@@ -1428,7 +1919,7 @@ function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, a
1428
1919
  onStart: () => {
1429
1920
  const start = fromValue !== void 0 ? fromValue : target[key];
1430
1921
  if (start === void 0) return;
1431
- interp = colorSpace === "rgb" ? gsap.gsap.utils.interpolate(start, toValue) : buildColorInterp(start, toValue, colorSpace);
1922
+ interp = colorSpace === "rgb" ? gsap.gsap.utils.interpolate(css(start), css(toValue)) : buildColorInterp(start, toValue, colorSpace);
1432
1923
  },
1433
1924
  onUpdate: () => {
1434
1925
  if (interp) {
@@ -1472,7 +1963,7 @@ var ImageSequence = class extends Sequence {
1472
1963
  if (initTint !== void 0) this.target.tint = initTint;
1473
1964
  for (const kf of this.spec.keyframes ?? []) {
1474
1965
  const at = startTime + resolveAt(kf.at, this.duration);
1475
- const duration = kf.duration ?? 0;
1966
+ const duration = kfDuration(kf);
1476
1967
  const ease = kf.ease ?? "none";
1477
1968
  const fromTint = kf.from?.tint;
1478
1969
  const toTint = kf.to?.tint;
@@ -1529,6 +2020,207 @@ function stripKeysInKeyframe(kf, keys) {
1529
2020
  from: stripKeys(kf.from, keys)
1530
2021
  };
1531
2022
  }
2023
+
2024
+ // src/sequences/gradient.ts
2025
+ function cssColor(c) {
2026
+ return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
2027
+ }
2028
+ function normalizedStops(g) {
2029
+ const stops = g.stops ?? [];
2030
+ if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
2031
+ return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
2032
+ }
2033
+ function gradientOptions(g) {
2034
+ const colorStops = normalizedStops(g);
2035
+ const a = (g.angle ?? 90) * Math.PI / 180;
2036
+ const dx = Math.cos(a), dy = Math.sin(a);
2037
+ const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2038
+ return {
2039
+ type: "linear",
2040
+ start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
2041
+ end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
2042
+ colorStops,
2043
+ textureSpace: "local"
2044
+ };
2045
+ }
2046
+ function paintRadial(ctx, size, g) {
2047
+ const stops = normalizedStops(g);
2048
+ const [cx, cy] = g.center ?? [0.5, 0.5];
2049
+ const grad = ctx.createRadialGradient(cx * size, cy * size, (g.innerRadius ?? 0) * size, cx * size, cy * size, (g.radius ?? 0.5) * size);
2050
+ for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
2051
+ ctx.fillStyle = grad;
2052
+ ctx.fillRect(0, 0, size, size);
2053
+ }
2054
+
2055
+ // src/sequences/gradientAnim.ts
2056
+ var PATCH_KEYS = ["type", "angle", "center", "innerRadius", "radius", "stops"];
2057
+ function gradStateFrom(g) {
2058
+ return {
2059
+ type: g.type ?? "linear",
2060
+ angle: g.angle ?? 90,
2061
+ center: [...g.center ?? [0.5, 0.5]],
2062
+ innerRadius: g.innerRadius ?? 0,
2063
+ radius: g.radius ?? 0.5,
2064
+ stops: (g.stops ?? []).map((s) => Array.isArray(s) ? { offset: s[0], color: cssColor(s[1]) } : { offset: s.offset, color: cssColor(s.color) })
2065
+ };
2066
+ }
2067
+ function mergeGrad(base, patch) {
2068
+ const out = { ...base, center: [...base.center], stops: base.stops.map((s) => ({ ...s })) };
2069
+ if (patch === null || typeof patch !== "object" || Array.isArray(patch)) return out;
2070
+ const p = patch;
2071
+ if (typeof p.angle === "number") out.angle = p.angle;
2072
+ if (Array.isArray(p.center)) out.center = [Number(p.center[0]), Number(p.center[1])];
2073
+ if (typeof p.innerRadius === "number") out.innerRadius = p.innerRadius;
2074
+ if (typeof p.radius === "number") out.radius = p.radius;
2075
+ if (p.stops !== void 0) {
2076
+ const next = gradStateFrom({ stops: p.stops }).stops;
2077
+ if (next.length === base.stops.length) out.stops = next;
2078
+ }
2079
+ return out;
2080
+ }
2081
+ var objectHint = "a keyframe's fillGradient must be an object like { angle: 200, stops: [[0, '#f00'], [1, '#00f']] }";
2082
+ function validateGradientKeyframes(spec, who, warn = (m) => console.warn(m), kind = "shape") {
2083
+ const said = /* @__PURE__ */ new Set();
2084
+ const say = (m) => {
2085
+ if (!said.has(m)) {
2086
+ said.add(m);
2087
+ warn(`pixi-effects: ${who}: ${m}`);
2088
+ }
2089
+ };
2090
+ const startSpec = spec.fillGradient ?? spec.initial?.fillGradient;
2091
+ const start = startSpec && typeof startSpec === "object" && Array.isArray(startSpec.stops) ? gradStateFrom(startSpec) : null;
2092
+ let animated = false;
2093
+ if (kind === "text") {
2094
+ const radial = (g) => !!g && typeof g === "object" && ["type", "center", "innerRadius", "radius"].some((k) => g[k] !== void 0 && !(k === "type" && g.type === "linear"));
2095
+ const asked = radial(startSpec) || (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => radial(b?.fillGradient)));
2096
+ 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");
2097
+ }
2098
+ for (const kf of spec.keyframes ?? []) {
2099
+ for (const bag of [kf.set, kf.to, kf.from]) {
2100
+ if (!bag) continue;
2101
+ for (const [key, value] of Object.entries(bag)) {
2102
+ if (key === "fillGradient") {
2103
+ animated = true;
2104
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2105
+ say(objectHint);
2106
+ continue;
2107
+ }
2108
+ const p = value;
2109
+ for (const k of Object.keys(p)) {
2110
+ if (PATCH_KEYS.includes(k)) continue;
2111
+ const guess = chunkUQBGW5DO_cjs.suggestName(k, PATCH_KEYS.filter((x) => x !== "type"));
2112
+ say(`fillGradient.${k} is not a gradient property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${PATCH_KEYS.join(", ")}); it is ignored`);
2113
+ }
2114
+ if (p.type !== void 0 && start && p.type !== start.type) say("fillGradient.type cannot change over time (linear <-> radial); it is ignored");
2115
+ if (p.stops !== void 0 && start) {
2116
+ const n = Array.isArray(p.stops) ? p.stops.length : 0;
2117
+ 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`);
2118
+ }
2119
+ } else if (/^fillGradient\./i.test(key) || /^gradient/i.test(key)) {
2120
+ const tail = key.replace(/^fillGradient\./i, "").replace(/^gradient/i, "");
2121
+ const sub = tail ? tail[0].toLowerCase() + tail.slice(1) : "angle";
2122
+ 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`);
2123
+ }
2124
+ }
2125
+ }
2126
+ }
2127
+ 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)");
2128
+ }
2129
+ function tweenGradient(timeline, holder, fromPatch, toPatch, duration, ease, at, colorSpace, onChange, loop = {}) {
2130
+ let a = null, b = null;
2131
+ let resting = null;
2132
+ let colors = [];
2133
+ const proxy = { p: 0 };
2134
+ timeline.fromTo(proxy, { p: 0 }, {
2135
+ p: 1,
2136
+ duration,
2137
+ ease,
2138
+ ...loop,
2139
+ onStart: () => {
2140
+ const live = holder.grad;
2141
+ a = fromPatch !== void 0 ? mergeGrad(live, fromPatch) : live;
2142
+ if (toPatch === void 0) resting ??= live;
2143
+ b = toPatch !== void 0 ? mergeGrad(a, toPatch) : resting;
2144
+ 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));
2145
+ },
2146
+ onUpdate: () => {
2147
+ if (!a || !b) return;
2148
+ const t = proxy.p;
2149
+ const l = (x, y) => x + (y - x) * t;
2150
+ holder.grad = {
2151
+ type: a.type,
2152
+ angle: l(a.angle, b.angle),
2153
+ center: [l(a.center[0], b.center[0]), l(a.center[1], b.center[1])],
2154
+ innerRadius: l(a.innerRadius, b.innerRadius),
2155
+ radius: l(a.radius, b.radius),
2156
+ stops: a.stops.map((s, i) => ({ offset: l(s.offset, b.stops[i].offset), color: colors[i](t) }))
2157
+ };
2158
+ onChange();
2159
+ }
2160
+ }, at);
2161
+ }
2162
+ var PAINT_SIZE = 512;
2163
+ var GradientPainter = class {
2164
+ constructor(size = PAINT_SIZE) {
2165
+ this.size = size;
2166
+ this.canvas.width = size;
2167
+ this.canvas.height = size;
2168
+ this.tex = PIXI.Texture.from(this.canvas);
2169
+ globalThis.__gradientPainters = (globalThis.__gradientPainters ?? 0) + 1;
2170
+ }
2171
+ size;
2172
+ canvas = document.createElement("canvas");
2173
+ tex;
2174
+ /** Paint `g` and return what `Graphics.fill(...)` takes. */
2175
+ paint(g) {
2176
+ const ctx = this.canvas.getContext("2d"), s = this.size;
2177
+ if (!ctx) return new PIXI.FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((x) => [x.offset, x.color]) }));
2178
+ ctx.clearRect(0, 0, s, s);
2179
+ if (g.type === "radial") {
2180
+ paintRadial(ctx, s, { stops: g.stops.map((x) => [x.offset, x.color]), center: g.center, innerRadius: g.innerRadius, radius: g.radius });
2181
+ } else {
2182
+ const a = g.angle * Math.PI / 180, dx = Math.cos(a), dy = Math.sin(a);
2183
+ const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2184
+ const grad = ctx.createLinearGradient((0.5 - dx * h) * s, (0.5 - dy * h) * s, (0.5 + dx * h) * s, (0.5 + dy * h) * s);
2185
+ for (const st of g.stops) grad.addColorStop(Math.min(1, Math.max(0, st.offset)), st.color);
2186
+ ctx.fillStyle = grad;
2187
+ ctx.fillRect(0, 0, s, s);
2188
+ }
2189
+ this.tex.source.update();
2190
+ return { texture: this.tex, textureSpace: "local" };
2191
+ }
2192
+ /** Give the texture and the canvas back (a layer that is destroyed must not leave them). */
2193
+ destroy() {
2194
+ this.tex.destroy(true);
2195
+ this.canvas.width = this.canvas.height = 0;
2196
+ globalThis.__gradientPainters = Math.max(0, (globalThis.__gradientPainters ?? 1) - 1);
2197
+ }
2198
+ };
2199
+ function bindGradientKeyframes(timeline, holder, keyframes, duration, offset, colorSpace, onChange) {
2200
+ const bag = (b) => b?.fillGradient;
2201
+ for (const kf of keyframes) {
2202
+ const at = offset + resolveAt(kf.at, duration);
2203
+ const setV = bag(kf.set), fromV = bag(kf.from), toV = bag(kf.to);
2204
+ if (setV !== void 0) {
2205
+ revertibleSet(
2206
+ timeline,
2207
+ at,
2208
+ () => ({ state: holder.grad }),
2209
+ (v) => {
2210
+ holder.grad = v.state ?? mergeGrad(holder.grad, v.patch);
2211
+ onChange();
2212
+ },
2213
+ { patch: setV }
2214
+ );
2215
+ }
2216
+ if (fromV !== void 0 || toV !== void 0) {
2217
+ tweenGradient(timeline, holder, fromV, toV, kfDuration(kf), kf.ease ?? "none", at, colorSpace, onChange, loopVars(kf));
2218
+ }
2219
+ }
2220
+ }
2221
+ var hasGradientKeys = (keyframes) => (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => b && b.fillGradient !== void 0));
2222
+
2223
+ // src/sequences/Text.ts
1532
2224
  var STYLE_OPAQUE_KEYS = [
1533
2225
  "fontFamily",
1534
2226
  "fill",
@@ -1563,6 +2255,21 @@ var TextSequence = class extends Sequence {
1563
2255
  /** `visibleChars` is in play (initial or animated): otherwise the whole text shows. */
1564
2256
  _typed = false;
1565
2257
  _lastText = "";
2258
+ /** A gradient fill for the text (`fillGradient`, static or animated): rebuilt as a FillGradient on each change (the text is re-rasterised). */
2259
+ _grad = null;
2260
+ _fillGradient = null;
2261
+ _applyGradient() {
2262
+ const g = this._grad.grad;
2263
+ const next = new PIXI.FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((s) => [s.offset, s.color]) }));
2264
+ this.target.style.fill = next;
2265
+ this._fillGradient?.destroy();
2266
+ this._fillGradient = next;
2267
+ }
2268
+ destroy() {
2269
+ super.destroy();
2270
+ this._fillGradient?.destroy();
2271
+ this._fillGradient = null;
2272
+ }
1566
2273
  /** A typewriter that has typed nothing yet: the layer is empty on purpose (inspect must not call that "no size"). */
1567
2274
  get showsNothingYet() {
1568
2275
  return this._typed && this._lastText === "" && this._template !== "";
@@ -1609,6 +2316,12 @@ var TextSequence = class extends Sequence {
1609
2316
  this.intrinsicWidth = text.width;
1610
2317
  this.intrinsicHeight = text.height;
1611
2318
  }
2319
+ validateGradientKeyframes(this.spec, describeLayer(this.spec), void 0, "text");
2320
+ const gradientSpec = this.spec.fillGradient ?? initialProps.fillGradient;
2321
+ if (gradientSpec) {
2322
+ this._grad = { grad: gradStateFrom(gradientSpec) };
2323
+ this._applyGradient();
2324
+ }
1612
2325
  const scope = this.scope();
1613
2326
  if (initialProps.value !== void 0) this._counters.value = resolveNumber(initialProps.value, scope);
1614
2327
  this._typed = initialProps.visibleChars !== void 0 || animates(this.spec.keyframes, "visibleChars");
@@ -1627,11 +2340,12 @@ var TextSequence = class extends Sequence {
1627
2340
  bindTimeline(timeline, offset = 0) {
1628
2341
  if (!this.target) return;
1629
2342
  const colorSpace = this.spec.colorSpace ?? "rgb";
1630
- const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars"]);
2343
+ const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars", "fillGradient"]);
1631
2344
  runSuperWithStrippedSpec(this, stripped, timeline, offset);
1632
2345
  const keyframes = this.spec.keyframes ?? [];
1633
2346
  const origin = offset + this.at;
1634
2347
  bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
2348
+ if (this._grad) bindGradientKeyframes(timeline, this._grad, keyframes, this.duration, origin, colorSpace, () => this._applyGradient());
1635
2349
  const refresh = () => this._refreshText();
1636
2350
  bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
1637
2351
  bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
@@ -1681,7 +2395,7 @@ function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDu
1681
2395
  const toV = kf.to?.[key];
1682
2396
  if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
1683
2397
  const at = origin + resolveAt(kf.at, parentDuration);
1684
- const duration = kf.duration ?? 0;
2398
+ const duration = kfDuration(kf);
1685
2399
  const ease = kf.ease ?? "none";
1686
2400
  const loop = loopVars(kf);
1687
2401
  if (setV !== void 0) {
@@ -1727,7 +2441,7 @@ function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, co
1727
2441
  };
1728
2442
  for (const kf of keyframes) {
1729
2443
  const at = offset + resolveAt(kf.at, parentDuration);
1730
- const duration = kf.duration ?? 0;
2444
+ const duration = kfDuration(kf);
1731
2445
  const ease = kf.ease ?? "none";
1732
2446
  const setFill = pickFill(kf.set);
1733
2447
  const fromFill = pickFill(kf.from);
@@ -2061,39 +2775,6 @@ var VideoSequence = class extends Sequence {
2061
2775
  super.destroy();
2062
2776
  }
2063
2777
  };
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
2778
  var RADIAL_SIZE = 256;
2098
2779
  function makeGradientFill(g) {
2099
2780
  if (g.type === "radial") {
@@ -2285,7 +2966,7 @@ function morphAt(pairs, t) {
2285
2966
  }
2286
2967
 
2287
2968
  // src/sequences/Shape.ts
2288
- var STYLE_KEYS = ["fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth"];
2969
+ var STYLE_KEYS2 = ["fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth"];
2289
2970
  var COLOR_KEYS = /* @__PURE__ */ new Set(["fillColor", "strokeColor"]);
2290
2971
  var TRIM_KEYS = ["trimStart", "trimEnd"];
2291
2972
  var GEOMETRY_KEYS_BY_SHAPE = {
@@ -2311,7 +2992,7 @@ var ShapeSequence = class extends Sequence {
2311
2992
  // by `Container.onRender`.
2312
2993
  _drawGeometry = () => {
2313
2994
  };
2314
- _liveKeys = new Set(STYLE_KEYS);
2995
+ _liveKeys = new Set(STYLE_KEYS2);
2315
2996
  async build() {
2316
2997
  const graphics = new PIXI.Graphics({ label: this.spec.name });
2317
2998
  graphics.cullable = true;
@@ -2337,7 +3018,15 @@ var ShapeSequence = class extends Sequence {
2337
3018
  this._state.trimEnd ??= 1;
2338
3019
  }
2339
3020
  const gradientSpec = this.spec.fillGradient ?? this.spec.initial?.fillGradient;
2340
- if (gradientSpec) this._state.fillStyle = makeGradientFill(gradientSpec);
3021
+ validateGradientKeyframes(this.spec, describeLayer(this.spec));
3022
+ if (gradientSpec) {
3023
+ if (hasGradientKeys(this.spec.keyframes)) {
3024
+ this._grad = { grad: gradStateFrom(gradientSpec) };
3025
+ this._painter = new GradientPainter();
3026
+ this._gradDirty = true;
3027
+ this._liveKeys.add("fillGradient");
3028
+ } else this._state.fillStyle = makeGradientFill(gradientSpec);
3029
+ }
2341
3030
  const morph = this._setupMorph();
2342
3031
  this._drawGeometry = makeDrawGeometry(this.spec, scope, this._state, morph);
2343
3032
  this._outline = makeOutline(this.spec, scope, this._state, morph);
@@ -2396,11 +3085,23 @@ var ShapeSequence = class extends Sequence {
2396
3085
  this._state.morph ??= 0;
2397
3086
  return { pairs, to: String(top.morphTo) };
2398
3087
  }
3088
+ destroy() {
3089
+ super.destroy();
3090
+ this._painter?.destroy();
3091
+ this._painter = null;
3092
+ }
2399
3093
  _fresh = false;
3094
+ _grad = null;
3095
+ _painter = null;
3096
+ _gradDirty = false;
2400
3097
  _outline = null;
2401
3098
  _redraw() {
2402
3099
  const graphics = this.target;
2403
3100
  const s = this._state;
3101
+ if (this._grad && this._gradDirty) {
3102
+ s.fillStyle = this._painter.paint(this._grad.grad);
3103
+ this._gradDirty = false;
3104
+ }
2404
3105
  graphics.clear();
2405
3106
  const trimmed = (s.trimStart ?? 0) > 0 || (s.trimEnd ?? 1) < 1;
2406
3107
  if (!trimmed || !this._outline || !(s.strokeColor !== void 0 && s.strokeWidth > 0)) {
@@ -2446,6 +3147,9 @@ var ShapeSequence = class extends Sequence {
2446
3147
  applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime);
2447
3148
  const colorSpace = this.spec.colorSpace ?? "rgb";
2448
3149
  bindLiveKeyframes(timeline, this._state, this._liveKeys, this.spec.keyframes ?? [], this.duration, scope, startTime, colorSpace);
3150
+ if (this._grad) bindGradientKeyframes(timeline, this._grad, this.spec.keyframes ?? [], this.duration, startTime, colorSpace, () => {
3151
+ this._gradDirty = true;
3152
+ });
2449
3153
  this.absoluteStart = startTime;
2450
3154
  const endTime = startTime + this.duration;
2451
3155
  this.target.renderable = startTime <= 0;
@@ -2482,7 +3186,7 @@ function seedStrokeStyle(state, spec, initial, who) {
2482
3186
  }
2483
3187
  }
2484
3188
  function seedStyle(state, initial, scope) {
2485
- for (const k of STYLE_KEYS) {
3189
+ for (const k of STYLE_KEYS2) {
2486
3190
  const v = initial[k];
2487
3191
  if (v === void 0) continue;
2488
3192
  state[k] = COLOR_KEYS.has(k) ? v : numOrZero(v, scope);
@@ -2705,13 +3409,14 @@ function makeOutline(spec, scope, s, morph) {
2705
3409
  function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration, scope, offset, colorSpace) {
2706
3410
  for (const kf of keyframes) {
2707
3411
  const at = offset + resolveAt(kf.at, parentDuration);
2708
- const duration = kf.duration ?? 0;
3412
+ const duration = kfDuration(kf);
2709
3413
  const ease = kf.ease ?? "none";
2710
3414
  const loop = loopVars(kf);
2711
3415
  if (kf.set) {
2712
3416
  const live = pickLive(kf.set, liveKeys);
2713
3417
  if (live) {
2714
3418
  for (const [k, v] of Object.entries(live)) {
3419
+ if (k === "fillGradient") continue;
2715
3420
  const resolved = resolveLiveValue(k, v, scope);
2716
3421
  const live2 = state;
2717
3422
  revertibleSet(timeline, at, () => live2[k], (v2) => {
@@ -2727,6 +3432,7 @@ function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration,
2727
3432
  ...toLive ? Object.keys(toLive) : []
2728
3433
  ]);
2729
3434
  for (const k of allKeys) {
3435
+ if (k === "fillGradient") continue;
2730
3436
  const key = k;
2731
3437
  const fromRaw = fromLive?.[key];
2732
3438
  const toRaw = toLive?.[key];
@@ -2775,7 +3481,7 @@ function numOrZero(v, scope) {
2775
3481
  var _CompositionSequence = null;
2776
3482
  async function getCompositionSequence() {
2777
3483
  if (!_CompositionSequence) {
2778
- _CompositionSequence = (await import('./Composition-6JMXWNVV.cjs')).CompositionSequence;
3484
+ _CompositionSequence = (await import('./Composition-KYNPRWK3.cjs')).CompositionSequence;
2779
3485
  }
2780
3486
  return _CompositionSequence;
2781
3487
  }
@@ -2830,6 +3536,7 @@ exports.evaluateExpr = evaluateExpr;
2830
3536
  exports.flattenSvgPath = flattenSvgPath;
2831
3537
  exports.homeCamera = homeCamera;
2832
3538
  exports.homeDistance = homeDistance;
3539
+ exports.lintKeys = lintKeys;
2833
3540
  exports.lintText = lintText;
2834
3541
  exports.lintTiming = lintTiming;
2835
3542
  exports.noise = noise;
@@ -2839,5 +3546,5 @@ exports.random = random;
2839
3546
  exports.registerSequenceType = registerSequenceType;
2840
3547
  exports.resolveAt = resolveAt;
2841
3548
  exports.summarizeWarnings = summarizeWarnings;
2842
- //# sourceMappingURL=chunk-ANO5R2E6.cjs.map
2843
- //# sourceMappingURL=chunk-ANO5R2E6.cjs.map
3549
+ //# sourceMappingURL=chunk-VTBT6NNE.cjs.map
3550
+ //# sourceMappingURL=chunk-VTBT6NNE.cjs.map