pixi-effects 0.2.0 → 0.3.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 (51) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +15 -5
  3. package/ai/SKILL.md +44 -0
  4. package/ai/reference/cheatsheet.md +120 -0
  5. package/ai/reference/pitfalls.md +54 -0
  6. package/ai/reference/recipes.md +375 -0
  7. package/ai/template.html +82 -0
  8. package/ai/tools/save-image.py +24 -0
  9. package/dist/{Base-B1Tdn0Cv.d.cts → Base-BDw6dKRB.d.cts} +1 -1
  10. package/dist/{Base-Bh_VSusi.d.ts → Base-Ba4Ta4ap.d.ts} +1 -1
  11. package/dist/Composition-6FDH5OPM.cjs +13 -0
  12. package/dist/{Composition-BI5HZJFL.cjs.map → Composition-6FDH5OPM.cjs.map} +1 -1
  13. package/dist/Composition-FEYAUMFI.js +4 -0
  14. package/dist/{Composition-IO7ZN32J.js.map → Composition-FEYAUMFI.js.map} +1 -1
  15. package/dist/Controller.d.cts +2 -2
  16. package/dist/Controller.d.ts +2 -2
  17. package/dist/Movie-DMpaT67V.d.ts +179 -0
  18. package/dist/Movie-W8ISiEBB.d.cts +179 -0
  19. package/dist/{chunk-H55V3U56.js → chunk-DIJG2RSF.js} +28 -11
  20. package/dist/chunk-DIJG2RSF.js.map +1 -0
  21. package/dist/{chunk-VJCDG6YG.js → chunk-PN5A6QA7.js} +248 -64
  22. package/dist/chunk-PN5A6QA7.js.map +1 -0
  23. package/dist/{chunk-7OIWYXGV.cjs → chunk-SIRVULFF.cjs} +251 -62
  24. package/dist/chunk-SIRVULFF.cjs.map +1 -0
  25. package/dist/{chunk-64IHCYYN.cjs → chunk-ZL262ZRM.cjs} +37 -20
  26. package/dist/chunk-ZL262ZRM.cjs.map +1 -0
  27. package/dist/index.cjs +324 -24
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +61 -31
  30. package/dist/index.d.ts +61 -31
  31. package/dist/index.js +321 -22
  32. package/dist/index.js.map +1 -1
  33. package/dist/three.cjs +5 -5
  34. package/dist/three.d.cts +2 -2
  35. package/dist/three.d.ts +2 -2
  36. package/dist/three.js +1 -1
  37. package/dist/{types-CNBilhpz.d.cts → types-3c8Vymgw.d.cts} +53 -6
  38. package/dist/{types-CNBilhpz.d.ts → types-3c8Vymgw.d.ts} +53 -6
  39. package/docs/api.md +333 -0
  40. package/docs/dsl.md +1017 -0
  41. package/llms-full.txt +1966 -0
  42. package/llms.txt +30 -0
  43. package/package.json +4 -3
  44. package/dist/Composition-BI5HZJFL.cjs +0 -13
  45. package/dist/Composition-IO7ZN32J.js +0 -4
  46. package/dist/Movie-CcR6h2jO.d.cts +0 -82
  47. package/dist/Movie-D-n8glA6.d.ts +0 -82
  48. package/dist/chunk-64IHCYYN.cjs.map +0 -1
  49. package/dist/chunk-7OIWYXGV.cjs.map +0 -1
  50. package/dist/chunk-H55V3U56.js.map +0 -1
  51. package/dist/chunk-VJCDG6YG.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { Rectangle, Container, Graphics, Assets, Texture, Sprite, Text, Filter, GlProgram, defaultFilterVert, GpuProgram, UniformGroup, Color, GraphicsPath } from 'pixi.js';
1
+ import { Rectangle, Container, Graphics, Assets, Texture, Sprite, Text, Filter, GlProgram, defaultFilterVert, GpuProgram, UniformGroup, FillGradient, Color, GraphicsPath } from 'pixi.js';
2
2
  import { gsap } from 'gsap';
3
3
 
4
4
  // src/sequences/Base.ts
@@ -244,6 +244,23 @@ function looksLikeColorString(s) {
244
244
  }
245
245
 
246
246
  // src/core/Timeline.ts
247
+ function loopVars(kf) {
248
+ const out = {};
249
+ let repeat = 0;
250
+ if (kf.repeat !== void 0) {
251
+ if (Number.isFinite(kf.repeat) && kf.repeat >= 0) {
252
+ repeat = Math.floor(kf.repeat);
253
+ out.repeat = repeat;
254
+ } else {
255
+ console.warn(`pixi-effects: keyframe repeat ${String(kf.repeat)} ignored \u2014 repeat must be a finite count >= 0 (the timeline needs a fixed length; for endless motion repeat many times).`);
256
+ }
257
+ }
258
+ if (repeat > 0) {
259
+ if (kf.yoyo !== void 0) out.yoyo = kf.yoyo;
260
+ if (kf.repeatDelay !== void 0 && kf.repeatDelay > 0) out.repeatDelay = kf.repeatDelay;
261
+ }
262
+ return out;
263
+ }
247
264
  function resolveAt(at, duration) {
248
265
  if (at === void 0 || at === null) return 0;
249
266
  return at < 0 ? duration + at : at;
@@ -261,7 +278,7 @@ function normalizeKeyframe(kf, parentDuration) {
261
278
  else if (hasFrom) kind = "from";
262
279
  else if (hasTo) kind = "to";
263
280
  else kind = "to";
264
- return { at, duration, ease, kind, set: kf.set, from: kf.from, to: kf.to };
281
+ return { at, duration, ease, loop: loopVars(kf), kind, set: kf.set, from: kf.from, to: kf.to };
265
282
  }
266
283
  function partitionProps(props) {
267
284
  const ownProps = {};
@@ -371,27 +388,27 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
371
388
  const resolved = normalizeProps(kf.to, scope, { skipKeys });
372
389
  const { rest, routed } = splitRouted(resolved, routers);
373
390
  for (const r of routed)
374
- timeline.to(r.target, { [r.prop]: r.value, duration: kf.duration, ease: kf.ease }, at);
391
+ timeline.to(r.target, { [r.prop]: r.value, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
375
392
  const { ownProps, filterProps } = partitionProps(rest);
376
393
  if (Object.keys(ownProps).length > 0)
377
- timeline.to(target, { ...pixiwrap(ownProps), duration: kf.duration, ease: kf.ease }, at);
394
+ timeline.to(target, { ...pixiwrap(ownProps), duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
378
395
  for (const [name, props] of Object.entries(filterProps)) {
379
396
  const f = findFilter(target, name);
380
397
  if (!f) continue;
381
- timeline.to(f, { ...props, duration: kf.duration, ease: kf.ease }, at);
398
+ timeline.to(f, { ...props, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
382
399
  }
383
400
  } else if (kf.kind === "from") {
384
401
  const resolved = normalizeProps(kf.from, scope, { skipKeys });
385
402
  const { rest, routed } = splitRouted(resolved, routers);
386
403
  for (const r of routed)
387
- timeline.from(r.target, { [r.prop]: r.value, duration: kf.duration, ease: kf.ease }, at);
404
+ timeline.from(r.target, { [r.prop]: r.value, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
388
405
  const { ownProps, filterProps } = partitionProps(rest);
389
406
  if (Object.keys(ownProps).length > 0)
390
- timeline.from(target, { ...pixiwrap(ownProps), duration: kf.duration, ease: kf.ease }, at);
407
+ timeline.from(target, { ...pixiwrap(ownProps), duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
391
408
  for (const [name, props] of Object.entries(filterProps)) {
392
409
  const f = findFilter(target, name);
393
410
  if (!f) continue;
394
- timeline.from(f, { ...props, duration: kf.duration, ease: kf.ease }, at);
411
+ timeline.from(f, { ...props, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
395
412
  }
396
413
  } else {
397
414
  const fromResolved = normalizeProps(kf.from, scope, { skipKeys });
@@ -406,15 +423,15 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
406
423
  timeline.fromTo(
407
424
  r.target,
408
425
  { [r.prop]: f.value },
409
- { [r.prop]: r.value, duration: kf.duration, ease: kf.ease },
426
+ { [r.prop]: r.value, duration: kf.duration, ease: kf.ease, ...kf.loop },
410
427
  at
411
428
  );
412
429
  } else {
413
- timeline.to(r.target, { [r.prop]: r.value, duration: kf.duration, ease: kf.ease }, at);
430
+ timeline.to(r.target, { [r.prop]: r.value, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
414
431
  }
415
432
  }
416
433
  for (const f of fromByKey.values())
417
- timeline.from(f.target, { [f.prop]: f.value, duration: kf.duration, ease: kf.ease }, at);
434
+ timeline.from(f.target, { [f.prop]: f.value, duration: kf.duration, ease: kf.ease, ...kf.loop }, at);
418
435
  const fromSplit = partitionProps(fromRouted.rest);
419
436
  const toSplit = partitionProps(toRouted.rest);
420
437
  const ownKeys = /* @__PURE__ */ new Set([...Object.keys(fromSplit.ownProps), ...Object.keys(toSplit.ownProps)]);
@@ -422,7 +439,7 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
422
439
  timeline.fromTo(
423
440
  target,
424
441
  { ...pixiwrap(fromSplit.ownProps) },
425
- { ...pixiwrap(toSplit.ownProps), duration: kf.duration, ease: kf.ease },
442
+ { ...pixiwrap(toSplit.ownProps), duration: kf.duration, ease: kf.ease, ...kf.loop },
426
443
  at
427
444
  );
428
445
  }
@@ -436,7 +453,7 @@ function applyKeyframes(timeline, target, keyframes, parentDuration, scope, skip
436
453
  timeline.fromTo(
437
454
  f,
438
455
  { ...fromSplit.filterProps[name] ?? {} },
439
- { ...toSplit.filterProps[name] ?? {}, duration: kf.duration, ease: kf.ease },
456
+ { ...toSplit.filterProps[name] ?? {}, duration: kf.duration, ease: kf.ease, ...kf.loop },
440
457
  at
441
458
  );
442
459
  }
@@ -761,9 +778,9 @@ var Sequence = class {
761
778
  if (!this.target) return;
762
779
  const scope = this.scope();
763
780
  const routers = this.pathRouters();
764
- applyInitial(this.target, this.spec.initial, scope, [], routers);
765
- applyKeyframes(timeline, this.target, this.spec.keyframes, this.duration, scope, [], offset, routers);
766
781
  const startTime = offset + this.at;
782
+ applyInitial(this.target, this.spec.initial, scope, [], routers);
783
+ applyKeyframes(timeline, this.target, this.spec.keyframes, this.duration, scope, [], startTime, routers);
767
784
  const endTime = startTime + this.duration;
768
785
  this.absoluteStart = startTime;
769
786
  this.target.renderable = startTime <= 0;
@@ -787,6 +804,46 @@ var Sequence = class {
787
804
  }
788
805
  };
789
806
 
807
+ // src/space/specKeys.ts
808
+ function collectPropKeys(spec) {
809
+ const out = /* @__PURE__ */ new Set();
810
+ for (const k of Object.keys(spec.initial ?? {})) out.add(k);
811
+ for (const kf of spec.keyframes ?? []) {
812
+ for (const bag of [kf.set, kf.to, kf.from]) {
813
+ for (const k of Object.keys(bag ?? {})) out.add(k);
814
+ }
815
+ }
816
+ return out;
817
+ }
818
+
819
+ // src/core/lint.ts
820
+ var defaultWarn = (m) => console.warn(m);
821
+ function describeLayer(spec) {
822
+ return spec.name ? `layer "${spec.name}"` : `unnamed ${spec.type} layer`;
823
+ }
824
+ function lintTiming(spec, parentDuration, warn = defaultWarn) {
825
+ const who = describeLayer(spec);
826
+ const at = spec.at ?? 0;
827
+ if (at >= parentDuration) {
828
+ warn(`pixi-effects: ${who} starts at ${at}s, after its composition ends (${parentDuration}s), so it is never visible`);
829
+ }
830
+ const duration = spec.duration ?? parentDuration;
831
+ (spec.keyframes ?? []).forEach((kf, i) => {
832
+ const kat = kf.at ?? 0;
833
+ if (kat >= duration) {
834
+ warn(
835
+ `pixi-effects: ${who}: keyframes[${i}] starts at ${kat}s, after the layer ends (duration ${duration}s), so it never plays. Keyframe \`at\` is measured from the start of this layer, not from the start of the composition.`
836
+ );
837
+ }
838
+ });
839
+ }
840
+ function lintText(spec, warn = defaultWarn) {
841
+ if (spec.type !== "text") return;
842
+ if (collectPropKeys(spec).has("value") && !(spec.text ?? "").includes("{value}")) {
843
+ 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').`);
844
+ }
845
+ }
846
+
790
847
  // src/space/math.ts
791
848
  var DEG = Math.PI / 180;
792
849
  var DEFAULT_FOV = 40;
@@ -898,20 +955,6 @@ function projectLayer(t, frame, b) {
898
955
  const origin = projectPoint(b, { x: t.x, y: t.y, z: t.z });
899
956
  return { corners, depth: origin.depth, visible };
900
957
  }
901
-
902
- // src/space/specKeys.ts
903
- function collectPropKeys(spec) {
904
- const out = /* @__PURE__ */ new Set();
905
- for (const k of Object.keys(spec.initial ?? {})) out.add(k);
906
- for (const kf of spec.keyframes ?? []) {
907
- for (const bag of [kf.set, kf.to, kf.from]) {
908
- for (const k of Object.keys(bag ?? {})) out.add(k);
909
- }
910
- }
911
- return out;
912
- }
913
-
914
- // src/space/CameraSequence.ts
915
958
  var CameraSequence = class extends Sequence {
916
959
  /** True when `z` is never specified anywhere: z then follows `fov`. */
917
960
  autoZ = true;
@@ -1094,7 +1137,7 @@ function shortestHueDelta(from, to) {
1094
1137
  }
1095
1138
 
1096
1139
  // src/expr/colorTween.ts
1097
- function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, at, colorSpace, onUpdate) {
1140
+ function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, at, colorSpace, onUpdate, loop = {}) {
1098
1141
  let interp = null;
1099
1142
  const proxy = { p: 0 };
1100
1143
  timeline.fromTo(
@@ -1104,6 +1147,7 @@ function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, a
1104
1147
  p: 1,
1105
1148
  duration,
1106
1149
  ease,
1150
+ ...loop,
1107
1151
  onStart: () => {
1108
1152
  const start = fromValue !== void 0 ? fromValue : target[key];
1109
1153
  if (start === void 0) return;
@@ -1142,14 +1186,15 @@ var ImageSequence = class extends Sequence {
1142
1186
  return;
1143
1187
  }
1144
1188
  const scope = this.scope();
1189
+ const startTime = offset + this.at;
1145
1190
  const initial = stripKeys(this.spec.initial, ["tint"]);
1146
1191
  const keyframes = this.spec.keyframes ? this.spec.keyframes.map((kf) => stripKeysInKeyframe(kf, ["tint"])) : void 0;
1147
1192
  applyInitial(this.target, initial, scope);
1148
- applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], offset);
1193
+ applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime);
1149
1194
  const initTint = this.spec.initial?.tint;
1150
1195
  if (initTint !== void 0) this.target.tint = initTint;
1151
1196
  for (const kf of this.spec.keyframes ?? []) {
1152
- const at = offset + resolveAt(kf.at, this.duration);
1197
+ const at = startTime + resolveAt(kf.at, this.duration);
1153
1198
  const duration = kf.duration ?? 0;
1154
1199
  const ease = kf.ease ?? "none";
1155
1200
  const fromTint = kf.from?.tint;
@@ -1170,11 +1215,12 @@ var ImageSequence = class extends Sequence {
1170
1215
  duration,
1171
1216
  ease,
1172
1217
  at,
1173
- colorSpace
1218
+ colorSpace,
1219
+ void 0,
1220
+ loopVars(kf)
1174
1221
  );
1175
1222
  }
1176
1223
  }
1177
- const startTime = offset + this.at;
1178
1224
  this.absoluteStart = startTime;
1179
1225
  const endTime = startTime + this.duration;
1180
1226
  this.target.renderable = startTime <= 0;
@@ -1205,7 +1251,25 @@ function stripKeysInKeyframe(kf, keys) {
1205
1251
  };
1206
1252
  }
1207
1253
  var STYLE_OPAQUE_KEYS = ["fontFamily", "fill", "align", "fontStyle", "fontWeight"];
1254
+ function formatValue(v, f) {
1255
+ const fixed = v.toFixed(Math.max(0, Math.floor(f?.decimals ?? 0)));
1256
+ if (!f?.grouping) return fixed;
1257
+ const [int, frac] = fixed.split(".");
1258
+ return int.replace(/\B(?=(\d{3})+(?!\d))/g, ",") + (frac !== void 0 ? "." + frac : "");
1259
+ }
1208
1260
  var TextSequence = class extends Sequence {
1261
+ /** Current number for the `{value}` placeholder (tweened by `value` keyframes). */
1262
+ _value = { value: 0 };
1263
+ _lastText = "";
1264
+ /** Rewrite the displayed string from the template and the current `value`. */
1265
+ _refreshText() {
1266
+ const template = this.spec.text ?? "";
1267
+ if (!template.includes("{value}")) return;
1268
+ const next = template.split("{value}").join(formatValue(this._value.value, this.spec.format));
1269
+ if (next === this._lastText) return;
1270
+ this._lastText = next;
1271
+ this.target.text = next;
1272
+ }
1209
1273
  async build() {
1210
1274
  const baseStyle = {
1211
1275
  fontFamily: "Arial",
@@ -1235,15 +1299,25 @@ var TextSequence = class extends Sequence {
1235
1299
  for (const k of Object.keys(resolved)) {
1236
1300
  text.style[k] = resolved[k];
1237
1301
  }
1302
+ this.intrinsicWidth = text.width;
1303
+ this.intrinsicHeight = text.height;
1304
+ }
1305
+ const initialValue = this.spec.initial?.value;
1306
+ if (initialValue !== void 0) this._value.value = resolveNumber(initialValue, this.scope());
1307
+ this._refreshText();
1308
+ if (this._lastText !== "") {
1309
+ this.intrinsicWidth = text.width;
1310
+ this.intrinsicHeight = text.height;
1238
1311
  }
1239
1312
  this.buildFilters();
1240
1313
  }
1241
1314
  bindTimeline(timeline, offset = 0) {
1242
1315
  if (!this.target) return;
1243
1316
  const colorSpace = this.spec.colorSpace ?? "rgb";
1244
- const stripped = stripFill(this.spec);
1317
+ const stripped = stripKeys2(this.spec, ["fill", "value"]);
1245
1318
  runSuperWithStrippedSpec(this, stripped, timeline, offset);
1246
- bindFillKeyframes(timeline, this.target, this.spec.keyframes ?? [], this.duration, offset, colorSpace);
1319
+ bindFillKeyframes(timeline, this.target, this.spec.keyframes ?? [], this.duration, offset + this.at, colorSpace);
1320
+ bindValueKeyframes(timeline, this._value, () => this._refreshText(), this.spec.keyframes ?? [], this.duration, offset + this.at, this.scope());
1247
1321
  }
1248
1322
  };
1249
1323
  function runSuperWithStrippedSpec(inst, strippedSpec, timeline, offset) {
@@ -1255,25 +1329,61 @@ function runSuperWithStrippedSpec(inst, strippedSpec, timeline, offset) {
1255
1329
  inst.spec = originalSpec;
1256
1330
  }
1257
1331
  }
1258
- function stripFill(spec) {
1332
+ function stripKeys2(spec, keys) {
1259
1333
  return {
1260
1334
  ...spec,
1261
- initial: stripKey(spec.initial, "fill"),
1335
+ initial: stripProps(spec.initial, keys),
1262
1336
  keyframes: spec.keyframes ? spec.keyframes.map((kf) => ({
1263
1337
  ...kf,
1264
- set: stripKey(kf.set, "fill"),
1265
- to: stripKey(kf.to, "fill"),
1266
- from: stripKey(kf.from, "fill")
1338
+ set: stripProps(kf.set, keys),
1339
+ to: stripProps(kf.to, keys),
1340
+ from: stripProps(kf.from, keys)
1267
1341
  })) : void 0
1268
1342
  };
1269
1343
  }
1270
- function stripKey(props, key) {
1344
+ function stripProps(props, keys) {
1271
1345
  if (!props) return props;
1272
- if (!(key in props)) return props;
1346
+ if (!keys.some((k) => k in props)) return props;
1273
1347
  const out = {};
1274
- for (const k of Object.keys(props)) if (k !== key) out[k] = props[k];
1348
+ for (const k of Object.keys(props)) if (!keys.includes(k)) out[k] = props[k];
1275
1349
  return out;
1276
1350
  }
1351
+ function resolveNumber(v, scope) {
1352
+ if (typeof v === "number") return v;
1353
+ const out = normalizeProps({ v }, scope).v;
1354
+ return typeof out === "number" ? out : Number(out) || 0;
1355
+ }
1356
+ function bindValueKeyframes(timeline, state, refresh, keyframes, parentDuration, origin, scope) {
1357
+ for (const kf of keyframes) {
1358
+ const setV = kf.set?.value;
1359
+ const fromV = kf.from?.value;
1360
+ const toV = kf.to?.value;
1361
+ if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
1362
+ const at = origin + resolveAt(kf.at, parentDuration);
1363
+ const duration = kf.duration ?? 0;
1364
+ const ease = kf.ease ?? "none";
1365
+ const loop = loopVars(kf);
1366
+ if (setV !== void 0) {
1367
+ const v = resolveNumber(setV, scope);
1368
+ timeline.call(() => {
1369
+ state.value = v;
1370
+ refresh();
1371
+ }, [], at);
1372
+ }
1373
+ if (toV !== void 0 && fromV !== void 0) {
1374
+ timeline.fromTo(
1375
+ state,
1376
+ { value: resolveNumber(fromV, scope) },
1377
+ { value: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
1378
+ at
1379
+ );
1380
+ } else if (toV !== void 0) {
1381
+ timeline.to(state, { value: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
1382
+ } else if (fromV !== void 0) {
1383
+ timeline.from(state, { value: resolveNumber(fromV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
1384
+ }
1385
+ }
1386
+ }
1277
1387
  function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, colorSpace) {
1278
1388
  const state = { fill: text.style.fill };
1279
1389
  const writeFill = () => {
@@ -1303,7 +1413,8 @@ function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, co
1303
1413
  ease,
1304
1414
  at,
1305
1415
  colorSpace,
1306
- writeFill
1416
+ writeFill,
1417
+ loopVars(kf)
1307
1418
  );
1308
1419
  }
1309
1420
  }
@@ -1322,6 +1433,11 @@ var AudioSequence = class extends Sequence {
1322
1433
  if (this.duration === void 0) {
1323
1434
  this.duration = this.spec.duration ?? data.duration ?? this.root.duration;
1324
1435
  }
1436
+ if (!this.spec.loop && data.duration !== void 0 && this.duration > data.duration + 0.05) {
1437
+ console.warn(
1438
+ `pixi-effects: ${describeLayer(this.spec)}: audio asset "${this.spec.asset}" is ${data.duration.toFixed(1)}s but the layer lasts ${this.duration.toFixed(1)}s, so it goes silent after ${data.duration.toFixed(1)}s. Add loop: true, or shorten the layer's duration.`
1439
+ );
1440
+ }
1325
1441
  this.target = null;
1326
1442
  }
1327
1443
  bindTimeline(_timeline) {
@@ -1491,14 +1607,15 @@ var VideoSequence = class extends Sequence {
1491
1607
  });
1492
1608
  this.buildFilters();
1493
1609
  }
1494
- bindTimeline(timeline) {
1495
- super.bindTimeline(timeline);
1610
+ bindTimeline(timeline, offset = 0) {
1611
+ super.bindTimeline(timeline, offset);
1496
1612
  const playDuration = this.spec.loop ? this.duration : Math.min(this.duration, this._sourceDuration);
1497
1613
  timeline.fromTo(
1498
1614
  this.target,
1499
1615
  { currentTime: 0 },
1500
1616
  { currentTime: playDuration, duration: playDuration, ease: "none" },
1501
- this.at
1617
+ offset + this.at
1618
+ // offset = start of the enclosing composition(s); dropping it misplaced videos inside nested compositions
1502
1619
  );
1503
1620
  }
1504
1621
  collectAudio(out, baseTime) {
@@ -1539,6 +1656,55 @@ var VideoSequence = class extends Sequence {
1539
1656
  super.destroy();
1540
1657
  }
1541
1658
  };
1659
+
1660
+ // src/sequences/gradient.ts
1661
+ function cssColor(c) {
1662
+ return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
1663
+ }
1664
+ function normalizedStops(g) {
1665
+ const stops = g.stops ?? [];
1666
+ if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
1667
+ return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
1668
+ }
1669
+ function gradientOptions(g) {
1670
+ const colorStops = normalizedStops(g);
1671
+ const a = (g.angle ?? 90) * Math.PI / 180;
1672
+ const dx = Math.cos(a), dy = Math.sin(a);
1673
+ const h = (Math.abs(dx) + Math.abs(dy)) / 2;
1674
+ return {
1675
+ type: "linear",
1676
+ start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
1677
+ end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
1678
+ colorStops,
1679
+ textureSpace: "local"
1680
+ };
1681
+ }
1682
+ function paintRadial(ctx, size, g) {
1683
+ const stops = normalizedStops(g);
1684
+ const [cx, cy] = g.center ?? [0.5, 0.5];
1685
+ const grad = ctx.createRadialGradient(cx * size, cy * size, (g.innerRadius ?? 0) * size, cx * size, cy * size, (g.radius ?? 0.5) * size);
1686
+ for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
1687
+ ctx.fillStyle = grad;
1688
+ ctx.fillRect(0, 0, size, size);
1689
+ }
1690
+
1691
+ // src/sequences/gradientFill.ts
1692
+ var RADIAL_SIZE = 256;
1693
+ function makeGradientFill(g) {
1694
+ if (g.type === "radial") {
1695
+ const canvas = document.createElement("canvas");
1696
+ canvas.width = RADIAL_SIZE;
1697
+ canvas.height = RADIAL_SIZE;
1698
+ const ctx = canvas.getContext("2d");
1699
+ if (ctx) {
1700
+ paintRadial(ctx, RADIAL_SIZE, g);
1701
+ return { texture: Texture.from(canvas), textureSpace: "local" };
1702
+ }
1703
+ }
1704
+ return new FillGradient(gradientOptions(g));
1705
+ }
1706
+
1707
+ // src/sequences/Shape.ts
1542
1708
  var STYLE_KEYS = ["fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth"];
1543
1709
  var COLOR_KEYS = /* @__PURE__ */ new Set(["fillColor", "strokeColor"]);
1544
1710
  var GEOMETRY_KEYS_BY_SHAPE = {
@@ -1576,6 +1742,8 @@ var ShapeSequence = class extends Sequence {
1576
1742
  for (const k of geomKeys) this._liveKeys.add(k);
1577
1743
  seedGeometry(this._state, this.spec, scope);
1578
1744
  seedStyle(this._state, this.spec.initial ?? {}, scope);
1745
+ const gradientSpec = this.spec.fillGradient ?? this.spec.initial?.fillGradient;
1746
+ if (gradientSpec) this._state.fillStyle = makeGradientFill(gradientSpec);
1579
1747
  this._drawGeometry = makeDrawGeometry(this.spec, scope, this._state);
1580
1748
  this._drawGeometry(graphics);
1581
1749
  applyState(graphics, this._state);
@@ -1583,7 +1751,12 @@ var ShapeSequence = class extends Sequence {
1583
1751
  this.intrinsicWidth = bounds.width;
1584
1752
  this.intrinsicHeight = bounds.height;
1585
1753
  if (!ANCHORED_SHAPES.has(this.spec.shape)) {
1586
- graphics.pivot.set(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
1754
+ const cx = bounds.x + bounds.width / 2;
1755
+ const cy = bounds.y + bounds.height / 2;
1756
+ graphics.pivot.set(cx, cy);
1757
+ const init = this.spec.initial ?? {};
1758
+ if (init.x === void 0) graphics.x = cx;
1759
+ if (init.y === void 0) graphics.y = cy;
1587
1760
  }
1588
1761
  graphics.onRender = () => {
1589
1762
  graphics.clear();
@@ -1595,13 +1768,13 @@ var ShapeSequence = class extends Sequence {
1595
1768
  bindTimeline(timeline, offset = 0) {
1596
1769
  if (!this.target) return;
1597
1770
  const scope = this.scope();
1598
- const initial = stripLive(this.spec.initial, this._liveKeys);
1771
+ const startTime = offset + this.at;
1772
+ const initial = stripLive(stripGradient(this.spec.initial), this._liveKeys);
1599
1773
  const keyframes = this.spec.keyframes ? this.spec.keyframes.map((kf) => stripLiveKeyframe(kf, this._liveKeys)) : void 0;
1600
1774
  applyInitial(this.target, initial, scope);
1601
- applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], offset);
1775
+ applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime);
1602
1776
  const colorSpace = this.spec.colorSpace ?? "rgb";
1603
- bindLiveKeyframes(timeline, this._state, this._liveKeys, this.spec.keyframes ?? [], this.duration, scope, offset, colorSpace);
1604
- const startTime = offset + this.at;
1777
+ bindLiveKeyframes(timeline, this._state, this._liveKeys, this.spec.keyframes ?? [], this.duration, scope, startTime, colorSpace);
1605
1778
  this.absoluteStart = startTime;
1606
1779
  const endTime = startTime + this.duration;
1607
1780
  this.target.renderable = startTime <= 0;
@@ -1611,8 +1784,9 @@ var ShapeSequence = class extends Sequence {
1611
1784
  };
1612
1785
  function seedGeometry(state, spec, scope) {
1613
1786
  const keys = GEOMETRY_KEYS_BY_SHAPE[spec.shape];
1787
+ const initial = spec.initial ?? {};
1614
1788
  for (const k of keys) {
1615
- const v = spec[k];
1789
+ const v = spec[k] ?? initial[k];
1616
1790
  if (v !== void 0) state[k] = num(v, scope);
1617
1791
  }
1618
1792
  }
@@ -1623,6 +1797,11 @@ function seedStyle(state, initial, scope) {
1623
1797
  state[k] = COLOR_KEYS.has(k) ? v : numOrZero(v, scope);
1624
1798
  }
1625
1799
  }
1800
+ function stripGradient(props) {
1801
+ if (!props || !("fillGradient" in props)) return props;
1802
+ const { fillGradient: _drop, ...rest } = props;
1803
+ return rest;
1804
+ }
1626
1805
  function stripLive(props, liveKeys) {
1627
1806
  if (!props) return props;
1628
1807
  const out = {};
@@ -1661,7 +1840,9 @@ function resolveLiveValue(key, value, scope) {
1661
1840
  return numOrZero(value, scope);
1662
1841
  }
1663
1842
  function applyState(g, s) {
1664
- if (s.fillColor !== void 0) {
1843
+ if (s.fillStyle) {
1844
+ g.fill(s.fillStyle);
1845
+ } else if (s.fillColor !== void 0) {
1665
1846
  g.fill({ color: s.fillColor, alpha: s.fillAlpha });
1666
1847
  }
1667
1848
  if (s.strokeColor !== void 0 && s.strokeWidth > 0) {
@@ -1741,6 +1922,7 @@ function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration,
1741
1922
  const at = offset + resolveAt(kf.at, parentDuration);
1742
1923
  const duration = kf.duration ?? 0;
1743
1924
  const ease = kf.ease ?? "none";
1925
+ const loop = loopVars(kf);
1744
1926
  if (kf.set) {
1745
1927
  const live = pickLive(kf.set, liveKeys);
1746
1928
  if (live) {
@@ -1764,11 +1946,11 @@ function bindLiveKeyframes(timeline, state, liveKeys, keyframes, parentDuration,
1764
1946
  const toRaw = toLive?.[key];
1765
1947
  const fromValue = fromRaw !== void 0 ? resolveLiveValue(key, fromRaw, scope) : void 0;
1766
1948
  const toValue = toRaw !== void 0 ? resolveLiveValue(key, toRaw, scope) : void 0;
1767
- tweenLiveKey(timeline, state, key, fromValue, toValue, duration, ease, at, colorSpace);
1949
+ tweenLiveKey(timeline, state, key, fromValue, toValue, duration, ease, at, colorSpace, loop);
1768
1950
  }
1769
1951
  }
1770
1952
  }
1771
- function tweenLiveKey(timeline, state, key, fromValue, toValue, duration, ease, at, colorSpace) {
1953
+ function tweenLiveKey(timeline, state, key, fromValue, toValue, duration, ease, at, colorSpace, loop) {
1772
1954
  if (COLOR_KEYS.has(key) && toValue !== void 0) {
1773
1955
  tweenColor(
1774
1956
  timeline,
@@ -1779,13 +1961,15 @@ function tweenLiveKey(timeline, state, key, fromValue, toValue, duration, ease,
1779
1961
  duration,
1780
1962
  ease,
1781
1963
  at,
1782
- colorSpace
1964
+ colorSpace,
1965
+ void 0,
1966
+ loop
1783
1967
  );
1784
1968
  } else if (toValue !== void 0) {
1785
1969
  if (fromValue !== void 0) {
1786
- timeline.fromTo(state, { [key]: fromValue }, { [key]: toValue, duration, ease }, at);
1970
+ timeline.fromTo(state, { [key]: fromValue }, { [key]: toValue, duration, ease, ...loop }, at);
1787
1971
  } else {
1788
- timeline.to(state, { [key]: toValue, duration, ease }, at);
1972
+ timeline.to(state, { [key]: toValue, duration, ease, ...loop }, at);
1789
1973
  }
1790
1974
  }
1791
1975
  }
@@ -1805,7 +1989,7 @@ function numOrZero(v, scope) {
1805
1989
  var _CompositionSequence = null;
1806
1990
  async function getCompositionSequence() {
1807
1991
  if (!_CompositionSequence) {
1808
- _CompositionSequence = (await import('./Composition-IO7ZN32J.js')).CompositionSequence;
1992
+ _CompositionSequence = (await import('./Composition-FEYAUMFI.js')).CompositionSequence;
1809
1993
  }
1810
1994
  return _CompositionSequence;
1811
1995
  }
@@ -1842,6 +2026,6 @@ async function buildSequenceTree(specs, parent, root) {
1842
2026
  return out;
1843
2027
  }
1844
2028
 
1845
- export { CameraSequence, DEG, Sequence, buildScope, buildSequenceTree, cameraBasis, collectPropKeys, evaluateExpr, homeCamera, projectLayer, registerSequenceType, resolveAt };
1846
- //# sourceMappingURL=chunk-VJCDG6YG.js.map
1847
- //# sourceMappingURL=chunk-VJCDG6YG.js.map
2029
+ export { CameraSequence, DEFAULT_FOV, DEG, Sequence, buildScope, buildSequenceTree, cameraBasis, collectPropKeys, describeLayer, evaluateExpr, homeCamera, homeDistance, lintText, lintTiming, projectLayer, registerSequenceType, resolveAt };
2030
+ //# sourceMappingURL=chunk-PN5A6QA7.js.map
2031
+ //# sourceMappingURL=chunk-PN5A6QA7.js.map