pixi-effects 0.30.1 → 0.32.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 (82) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +6 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/check.mjs +42 -2
  5. package/ai/tools/render.mjs +34 -4
  6. package/ai/tools/varArgs.mjs +75 -0
  7. package/dist/{Base-Ne74ztNl.d.ts → Base-CXsMdmJB.d.ts} +1 -1
  8. package/dist/{Base-CB1_FPZD.d.cts → Base-pNq6sVGl.d.cts} +1 -1
  9. package/dist/Composition-LNMKWXSL.cjs +16 -0
  10. package/dist/{Composition-MUKR3FYD.cjs.map → Composition-LNMKWXSL.cjs.map} +1 -1
  11. package/dist/Composition-RF7QOEXT.js +7 -0
  12. package/dist/{Composition-RJGA7KDU.js.map → Composition-RF7QOEXT.js.map} +1 -1
  13. package/dist/Controller.cjs +42 -1
  14. package/dist/Controller.cjs.map +1 -1
  15. package/dist/Controller.d.cts +10 -2
  16. package/dist/Controller.d.ts +10 -2
  17. package/dist/Controller.js +42 -1
  18. package/dist/Controller.js.map +1 -1
  19. package/dist/{Movie-DdnpLpw1.d.cts → Movie-CL6SsAWe.d.cts} +90 -3
  20. package/dist/{Movie-C2rZFkrO.d.ts → Movie-f0tQG9y5.d.ts} +90 -3
  21. package/dist/Spans-NQGKVMR5.cjs +55 -0
  22. package/dist/Spans-NQGKVMR5.cjs.map +1 -0
  23. package/dist/Spans-SDXABOV6.js +53 -0
  24. package/dist/Spans-SDXABOV6.js.map +1 -0
  25. package/dist/{blendModes-RIVZXL5A.js → blendModes-MVRLG3T7.js} +4 -4
  26. package/dist/{blendModes-RIVZXL5A.js.map → blendModes-MVRLG3T7.js.map} +1 -1
  27. package/dist/{blendModes-ETMYWATF.cjs → blendModes-VTKDXWEN.cjs} +5 -5
  28. package/dist/{blendModes-ETMYWATF.cjs.map → blendModes-VTKDXWEN.cjs.map} +1 -1
  29. package/dist/{chunk-ZAXYPXKW.js → chunk-AW5NPDE4.js} +1367 -1150
  30. package/dist/chunk-AW5NPDE4.js.map +1 -0
  31. package/dist/{chunk-RCVZX2JP.js → chunk-C5BNHVKK.js} +36 -3
  32. package/dist/chunk-C5BNHVKK.js.map +1 -0
  33. package/dist/{chunk-WHNADXXG.cjs → chunk-CRUDOKSD.cjs} +91 -91
  34. package/dist/chunk-CRUDOKSD.cjs.map +1 -0
  35. package/dist/{chunk-A3Q3SDSH.js → chunk-D5BFQVKL.js} +6 -6
  36. package/dist/chunk-D5BFQVKL.js.map +1 -0
  37. package/dist/{chunk-LYOKH7KE.cjs → chunk-FCGYJCVO.cjs} +36 -3
  38. package/dist/chunk-FCGYJCVO.cjs.map +1 -0
  39. package/dist/{chunk-CRQFDY7E.js → chunk-FCMOREIS.js} +467 -58
  40. package/dist/chunk-FCMOREIS.js.map +1 -0
  41. package/dist/{chunk-HREBQAY4.cjs → chunk-K3MAEKFM.cjs} +493 -84
  42. package/dist/chunk-K3MAEKFM.cjs.map +1 -0
  43. package/dist/{chunk-4KB3GUEK.js → chunk-QFNS6BFL.js} +4 -4
  44. package/dist/{chunk-4KB3GUEK.js.map → chunk-QFNS6BFL.js.map} +1 -1
  45. package/dist/{chunk-ZVUO325O.cjs → chunk-QHDN6TF5.cjs} +1382 -1160
  46. package/dist/chunk-QHDN6TF5.cjs.map +1 -0
  47. package/dist/{chunk-2DXPTKQZ.cjs → chunk-Z437UWI4.cjs} +5 -5
  48. package/dist/{chunk-2DXPTKQZ.cjs.map → chunk-Z437UWI4.cjs.map} +1 -1
  49. package/dist/index.cjs +33 -33
  50. package/dist/index.d.cts +5 -5
  51. package/dist/index.d.ts +5 -5
  52. package/dist/index.js +5 -5
  53. package/dist/loader.css +1 -1
  54. package/dist/present.cjs +5 -5
  55. package/dist/present.d.cts +2 -2
  56. package/dist/present.d.ts +2 -2
  57. package/dist/present.js +5 -5
  58. package/dist/three.cjs +6 -6
  59. package/dist/three.d.cts +2 -2
  60. package/dist/three.d.ts +2 -2
  61. package/dist/three.js +2 -2
  62. package/dist/{types-BzIB65Vk.d.ts → types-CavnUvd5.d.cts} +28 -1
  63. package/dist/{types-BzIB65Vk.d.cts → types-CavnUvd5.d.ts} +28 -1
  64. package/docs/api.md +28 -1
  65. package/docs/dsl.md +46 -5
  66. package/llms-full.txt +136 -10
  67. package/package.json +83 -20
  68. package/skills/pixi-effects/SKILL.md +1 -1
  69. package/skills/pixi-effects/reference/cheatsheet.md +5 -2
  70. package/skills/pixi-effects/reference/pitfalls.md +5 -1
  71. package/skills/pixi-effects/reference/recipes.md +52 -0
  72. package/skills/pixi-effects/template.html +6 -6
  73. package/dist/Composition-MUKR3FYD.cjs +0 -16
  74. package/dist/Composition-RJGA7KDU.js +0 -7
  75. package/dist/chunk-A3Q3SDSH.js.map +0 -1
  76. package/dist/chunk-CRQFDY7E.js.map +0 -1
  77. package/dist/chunk-HREBQAY4.cjs.map +0 -1
  78. package/dist/chunk-LYOKH7KE.cjs.map +0 -1
  79. package/dist/chunk-RCVZX2JP.js.map +0 -1
  80. package/dist/chunk-WHNADXXG.cjs.map +0 -1
  81. package/dist/chunk-ZAXYPXKW.js.map +0 -1
  82. package/dist/chunk-ZVUO325O.cjs.map +0 -1
@@ -1,7 +1,7 @@
1
- import { collectPropKeys, describeLayer } from './chunk-RCVZX2JP.js';
1
+ import { describeLayer, collectPropKeys } from './chunk-C5BNHVKK.js';
2
2
  import { suggestName, warnUnknownOptions } from './chunk-BXOCEZ4U.js';
3
3
  import * as PIXI from 'pixi.js';
4
- import { Rectangle, Container, Color, GraphicsPath, Sprite, Texture, Graphics, Assets, FillGradient, Text, Filter, GlProgram, defaultFilterVert, GpuProgram, UniformGroup } from 'pixi.js';
4
+ import { Rectangle, Container, FillGradient, Text, Color, GraphicsPath, Sprite, Texture, Graphics, Assets, Filter, GlProgram, defaultFilterVert, GpuProgram, UniformGroup, TextStyle, CanvasTextMetrics } from 'pixi.js';
5
5
 
6
6
  // src/core/spring.ts
7
7
  var SETTLE_TOL = 5e-3;
@@ -345,6 +345,35 @@ function pixiAccessors(target, key) {
345
345
  return null;
346
346
  }
347
347
  }
348
+ function pathView(target, key) {
349
+ const parts = key.split(".");
350
+ const leaf = parts.pop();
351
+ const parentOf = () => {
352
+ let o = target;
353
+ for (const p of parts) {
354
+ o = o?.[p];
355
+ if (o === null || typeof o !== "object") return null;
356
+ }
357
+ return o;
358
+ };
359
+ const first = parentOf();
360
+ if (!first || !(leaf in first)) return null;
361
+ const view = {};
362
+ Object.defineProperty(view, leaf, {
363
+ get: () => parentOf()?.[leaf],
364
+ set: (v) => {
365
+ const parent = parentOf();
366
+ if (parent) parent[leaf] = v;
367
+ }
368
+ });
369
+ return { view, leaf };
370
+ }
371
+ var saidPaths = /* @__PURE__ */ new Set();
372
+ function warnLeadsNowhere(key) {
373
+ if (saidPaths.has(key)) return;
374
+ saidPaths.add(key);
375
+ console.warn(`pixi-effects: "${key}" leads nowhere on its target, so it is ignored (a point option of a filter must exist to be animated: give it in the filter's options, e.g. red: { x: 0, y: 0 })`);
376
+ }
348
377
  function rawAccessor(target, key) {
349
378
  return { id: key, kind: "raw", unit: 1, round: 1, get: () => target[key], set: (v) => {
350
379
  target[key] = v;
@@ -462,7 +491,14 @@ var PureTimeline = class _PureTimeline {
462
491
  for (const key of Object.keys(v)) {
463
492
  if (RESERVED.has(key) || key === "pixi" || CALLBACKS.includes(key)) continue;
464
493
  const value = v[key];
465
- const acc = parseRel(value) ? plainAccessor(t, key) : typeof value === "string" && parseCssColor(value) ? cssAccessor(t, key) : rawAccessor(t, key);
494
+ const path = key.includes(".") ? pathView(t, key) : null;
495
+ if (key.includes(".") && !path) {
496
+ warnLeadsNowhere(key);
497
+ continue;
498
+ }
499
+ const at2 = path?.view ?? t, name = path?.leaf ?? key;
500
+ const acc = parseRel(value) ? plainAccessor(at2, name) : typeof value === "string" && parseCssColor(value) ? cssAccessor(at2, name) : rawAccessor(at2, name);
501
+ acc.id = key;
466
502
  out.set(acc.id, { acc, value });
467
503
  }
468
504
  if (v.pixi) for (const key of Object.keys(v.pixi)) {
@@ -870,7 +906,14 @@ function setNow(target, vars) {
870
906
  }
871
907
  } else {
872
908
  const v = vars[key];
873
- plainAccessor(t, key).set(typeof v === "number" ? Math.round(v * 1e6) / 1e6 : v);
909
+ const value = typeof v === "number" ? Math.round(v * 1e6) / 1e6 : v;
910
+ if (key.includes(".")) {
911
+ const path = pathView(t, key);
912
+ if (path) path.view[path.leaf] = value;
913
+ else warnLeadsNowhere(key);
914
+ continue;
915
+ }
916
+ plainAccessor(t, key).set(value);
874
917
  }
875
918
  }
876
919
  }
@@ -954,6 +997,7 @@ var FUNCS = {
954
997
  step: (edge, x) => x >= edge ? 1 : 0
955
998
  };
956
999
  var CONSTS = { PI: Math.PI };
1000
+ var EXPRESSION_NAMES = [...Object.keys(FUNCS), ...Object.keys(CONSTS)];
957
1001
  function isDigit(c) {
958
1002
  return c >= "0" && c <= "9";
959
1003
  }
@@ -1460,6 +1504,8 @@ function buildScope(sequence, parent, root) {
1460
1504
  const contain = w === 0 || h === 0 ? 1 : Math.min(W / w, H / h);
1461
1505
  const cover = w === 0 || h === 0 ? 1 : Math.max(W / w, H / h);
1462
1506
  return {
1507
+ ...root.vars ?? {},
1508
+ // the declared numbers first: the built-in words below win
1463
1509
  w,
1464
1510
  h,
1465
1511
  W,
@@ -2245,6 +2291,13 @@ var registry = {
2245
2291
  function looksLikePixiFilter(x) {
2246
2292
  return !!x && typeof x === "object" && typeof x.apply === "function";
2247
2293
  }
2294
+ function copyPlain(v) {
2295
+ if (Array.isArray(v)) return v.map(copyPlain);
2296
+ if (v && typeof v === "object" && Object.getPrototypeOf(v) === Object.prototype) {
2297
+ return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, copyPlain(x)]));
2298
+ }
2299
+ return v;
2300
+ }
2248
2301
  function createFilter(spec) {
2249
2302
  if (spec.type === "custom") {
2250
2303
  const inst2 = spec.filter;
@@ -2255,7 +2308,8 @@ function createFilter(spec) {
2255
2308
  named._name = spec.name;
2256
2309
  return named;
2257
2310
  }
2258
- const { type, name, ...params } = spec;
2311
+ const { type, name, ...given } = spec;
2312
+ const params = copyPlain(given);
2259
2313
  const Cls = registry[type];
2260
2314
  const inst = Cls ? new Cls(params) : createNamedFilter(type, params);
2261
2315
  inst._name = name;
@@ -2380,694 +2434,345 @@ var NullSequence = class extends Sequence {
2380
2434
  }
2381
2435
  };
2382
2436
 
2383
- // src/core/remap.ts
2384
- var BAGS = ["set", "to", "from"];
2385
- function timeRemapOf(spec, prop = "time") {
2386
- const hasInitial = !!spec.initial && "time" in spec.initial;
2387
- const hasKf = (spec.keyframes ?? []).some((kf) => BAGS.some((b) => kf[b] && "time" in kf[b]));
2388
- const speed = typeof spec.speed === "number" && Number.isFinite(spec.speed) && spec.speed !== 0 ? spec.speed : void 0;
2389
- if (!hasInitial && !hasKf && speed === void 0) return null;
2390
- const timeKfs = [];
2391
- const restKfs = [];
2392
- for (const kf of spec.keyframes ?? []) {
2393
- const t = { ...kf }, r = { ...kf };
2394
- let touched = false, left = false;
2395
- for (const b of BAGS) {
2396
- const bag = kf[b];
2397
- if (!bag) {
2398
- delete t[b];
2399
- delete r[b];
2400
- continue;
2401
- }
2402
- const { time, ...others } = bag;
2403
- if ("time" in bag) {
2404
- touched = true;
2405
- t[b] = { [prop]: time };
2406
- } else delete t[b];
2407
- if (Object.keys(others).length) {
2408
- r[b] = others;
2409
- left = true;
2410
- } else delete r[b];
2411
- }
2412
- if (touched) timeKfs.push(t);
2413
- if (!touched || left) restKfs.push(r);
2414
- }
2415
- let restInitial = spec.initial;
2416
- let start;
2417
- if (hasInitial) {
2418
- const { time, ...others } = spec.initial;
2419
- start = typeof time === "number" && Number.isFinite(time) ? time : void 0;
2420
- restInitial = others;
2421
- }
2422
- return { timeKfs, restKfs: restKfs.length ? restKfs : void 0, restInitial, start, speed };
2437
+ // src/sequences/gradient.ts
2438
+ function cssColor(c) {
2439
+ return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
2423
2440
  }
2424
- function resolveFromEnd(remap, length, prop) {
2425
- const fix = (v) => typeof v === "number" && v < 0 ? Math.max(0, length + v) : v;
2426
- const timeKfs = remap.timeKfs.map((kf) => {
2427
- const out = { ...kf };
2428
- for (const b of BAGS) {
2429
- const bag = kf[b];
2430
- if (bag && prop in bag) out[b] = { ...bag, [prop]: fix(bag[prop]) };
2431
- }
2432
- return out;
2433
- });
2434
- return { ...remap, timeKfs, start: remap.start === void 0 ? void 0 : fix(remap.start) };
2441
+ function normalizedStops(g) {
2442
+ const stops = g.stops ?? [];
2443
+ if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
2444
+ return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
2435
2445
  }
2436
- function remapOf(spec, prop, length) {
2437
- const r = timeRemapOf(spec, prop);
2438
- return r && length !== null ? resolveFromEnd(r, length, prop) : r;
2446
+ function gradientOptions(g) {
2447
+ const colorStops = normalizedStops(g);
2448
+ const a = (g.angle ?? 90) * Math.PI / 180;
2449
+ const dx = Math.cos(a), dy = Math.sin(a);
2450
+ const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2451
+ return {
2452
+ type: "linear",
2453
+ start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
2454
+ end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
2455
+ colorStops,
2456
+ textureSpace: "local"
2457
+ };
2439
2458
  }
2440
- function contentLength(remap, duration) {
2441
- let m = duration;
2442
- if (remap.speed !== void 0) m = Math.max(m, Math.abs(remap.speed) * duration);
2443
- if (remap.start !== void 0) m = Math.max(m, remap.start + (remap.speed ?? 1) * duration);
2444
- for (const kf of remap.timeKfs) {
2445
- for (const b of BAGS) {
2446
- const bag = kf[b];
2447
- const v = bag && Object.values(bag)[0];
2448
- if (typeof v === "number") m = Math.max(m, v);
2449
- }
2459
+ function paintRadial(ctx, size, g) {
2460
+ const stops = normalizedStops(g);
2461
+ const [cx, cy] = g.center ?? [0.5, 0.5];
2462
+ const grad = ctx.createRadialGradient(cx * size, cy * size, (g.innerRadius ?? 0) * size, cx * size, cy * size, (g.radius ?? 0.5) * size);
2463
+ for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
2464
+ ctx.fillStyle = grad;
2465
+ ctx.fillRect(0, 0, size, size);
2466
+ }
2467
+
2468
+ // src/expr/colorInterp.ts
2469
+ var CHROMA_FLOOR = 0.03;
2470
+ var CHROMA_FULL = 0.08;
2471
+ function buildColorInterp(from, to, space) {
2472
+ const fromRGBA = parseToRGBA(from);
2473
+ const toRGBA = parseToRGBA(to);
2474
+ const fromOk = rgbToOklab(fromRGBA);
2475
+ const toOk = rgbToOklab(toRGBA);
2476
+ if (space === "oklab") {
2477
+ return (t) => {
2478
+ const L = lerp(fromOk.L, toOk.L, t);
2479
+ const a = lerp(fromOk.a, toOk.a, t);
2480
+ const b = lerp(fromOk.b, toOk.b, t);
2481
+ const alpha = lerp(fromRGBA.a, toRGBA.a, t);
2482
+ return rgbaString(oklabToRgb({ L, a, b }), alpha);
2483
+ };
2450
2484
  }
2451
- return m;
2485
+ const fromLCH = oklabToOklch(fromOk);
2486
+ const toLCH = oklabToOklch(toOk);
2487
+ const fromHue = fromLCH.h, toHue = toLCH.h;
2488
+ fromLCH.h = borrowHue(fromHue, fromLCH.C, toHue);
2489
+ toLCH.h = borrowHue(toHue, toLCH.C, fromHue);
2490
+ const hueDelta = shortestHueDelta(fromLCH.h, toLCH.h);
2491
+ return (t) => {
2492
+ const L = lerp(fromLCH.L, toLCH.L, t);
2493
+ const C = lerp(fromLCH.C, toLCH.C, t);
2494
+ const h = fromLCH.h + hueDelta * t;
2495
+ const alpha = lerp(fromRGBA.a, toRGBA.a, t);
2496
+ const ab = oklchToOklab({ L, C, h });
2497
+ return rgbaString(oklabToRgb(ab), alpha);
2498
+ };
2452
2499
  }
2453
- function bindClock(timeline, clock, prop, remap, duration, at, scope) {
2454
- if (remap.timeKfs.length) {
2455
- if (remap.start !== void 0) timeline.set(clock, { [prop]: remap.start }, at);
2456
- applyKeyframes(timeline, clock, remap.timeKfs, duration, scope, [], at);
2457
- return;
2500
+ function borrowHue(own, chroma, other) {
2501
+ const u = Math.min(1, Math.max(0, (chroma - CHROMA_FLOOR) / (CHROMA_FULL - CHROMA_FLOOR)));
2502
+ const keep = u * u * (3 - 2 * u);
2503
+ return own + shortestHueDelta(own, other) * (1 - keep);
2504
+ }
2505
+ function parseToRGBA(input) {
2506
+ if (typeof input === "number") {
2507
+ return { r: (input >> 16 & 255) / 255, g: (input >> 8 & 255) / 255, b: (input & 255) / 255, a: 1 };
2458
2508
  }
2459
- const s = remap.speed ?? 1;
2460
- const from = remap.start ?? (s < 0 ? -s * duration : 0);
2461
- timeline.fromTo(clock, { [prop]: from }, { [prop]: from + s * duration, duration, ease: "none" }, at);
2509
+ const s = input.trim();
2510
+ if (s.startsWith("#")) return parseHex(s);
2511
+ if (s.startsWith("rgb")) return parseRgbFunc(s);
2512
+ return { r: 0, g: 0, b: 0, a: 1 };
2462
2513
  }
2463
- function clockTable(remap, prop, duration, at, scope, to) {
2464
- const probe = createTimeline({ paused: true });
2465
- const clock = { [prop]: remap.start ?? 0 };
2466
- bindClock(probe, clock, prop, remap, duration, at, scope);
2467
- probe.time(probe.duration());
2468
- probe.time(0);
2469
- const h = 1 / 1e3;
2470
- const n = Math.max(2, Math.ceil(to / h) + 2);
2471
- const tab = new Float64Array(n);
2472
- let min = Infinity, max = -Infinity;
2473
- for (let k = 0; k < n; k++) {
2474
- probe.time(k * h);
2475
- const v = clock[prop];
2476
- tab[k] = v;
2477
- const t = k * h;
2478
- if (t >= at && t <= at + duration) {
2479
- if (v < min) min = v;
2480
- if (v > max) max = v;
2481
- }
2514
+ function parseHex(s) {
2515
+ const hex = s.slice(1);
2516
+ let r = 0, g = 0, b = 0, a = 1;
2517
+ if (hex.length === 3 || hex.length === 4) {
2518
+ r = parseInt(hex[0] + hex[0], 16) / 255;
2519
+ g = parseInt(hex[1] + hex[1], 16) / 255;
2520
+ b = parseInt(hex[2] + hex[2], 16) / 255;
2521
+ if (hex.length === 4) a = parseInt(hex[3] + hex[3], 16) / 255;
2522
+ } else if (hex.length === 6 || hex.length === 8) {
2523
+ r = parseInt(hex.slice(0, 2), 16) / 255;
2524
+ g = parseInt(hex.slice(2, 4), 16) / 255;
2525
+ b = parseInt(hex.slice(4, 6), 16) / 255;
2526
+ if (hex.length === 8) a = parseInt(hex.slice(6, 8), 16) / 255;
2482
2527
  }
2483
- probe.kill();
2528
+ return { r, g, b, a };
2529
+ }
2530
+ function parseRgbFunc(s) {
2531
+ const inner = s.replace(/^rgba?\(/, "").replace(/\)$/, "");
2532
+ const parts = inner.split(/[,\s/]+/).filter(Boolean).map(parseFloat);
2484
2533
  return {
2485
- at(t) {
2486
- const x = Math.min(Math.max(t / h, 0), n - 1.000001);
2487
- const k = Math.floor(x), f = x - k;
2488
- return tab[k] * (1 - f) + tab[k + 1] * f;
2489
- },
2490
- min: Number.isFinite(min) ? min : 0,
2491
- max: Number.isFinite(max) ? max : 0
2534
+ r: (parts[0] ?? 0) / 255,
2535
+ g: (parts[1] ?? 0) / 255,
2536
+ b: (parts[2] ?? 0) / 255,
2537
+ a: parts.length > 3 ? parts[3] ?? 1 : 1
2492
2538
  };
2493
2539
  }
2494
-
2495
- // src/space/math.ts
2496
- var DEG3 = Math.PI / 180;
2497
- var DEFAULT_FOV = 40;
2498
- var MIN_FOV = 1;
2499
- var MAX_FOV = 179;
2500
- var NEAR = 1;
2501
- function clampFov(fov) {
2502
- if (!Number.isFinite(fov)) return DEFAULT_FOV;
2503
- return Math.min(MAX_FOV, Math.max(MIN_FOV, fov));
2540
+ function rgbaString(rgb, alpha) {
2541
+ const r = Math.round(clamp01(rgb.r) * 255);
2542
+ const g = Math.round(clamp01(rgb.g) * 255);
2543
+ const b = Math.round(clamp01(rgb.b) * 255);
2544
+ return `rgba(${r},${g},${b},${alpha})`;
2504
2545
  }
2505
- function homeDistance(compHeight, fovDeg) {
2506
- return compHeight / 2 / Math.tan(clampFov(fovDeg) * DEG3 / 2);
2546
+ function srgbToLinear(c) {
2547
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
2507
2548
  }
2508
- function homeCamera(compW, compH, fov = DEFAULT_FOV) {
2549
+ function linearToSrgb(c) {
2550
+ return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
2551
+ }
2552
+ function rgbToOklab(rgba) {
2553
+ const r = srgbToLinear(rgba.r);
2554
+ const g = srgbToLinear(rgba.g);
2555
+ const b = srgbToLinear(rgba.b);
2556
+ const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
2557
+ const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
2558
+ const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
2559
+ const l_ = Math.cbrt(l);
2560
+ const m_ = Math.cbrt(m);
2561
+ const s_ = Math.cbrt(s);
2509
2562
  return {
2510
- x: compW / 2,
2511
- y: compH / 2,
2512
- z: homeDistance(compH, fov),
2513
- lookAtX: compW / 2,
2514
- lookAtY: compH / 2,
2515
- lookAtZ: 0,
2516
- fov
2563
+ L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
2564
+ a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
2565
+ b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_
2517
2566
  };
2518
2567
  }
2519
- function cameraBasis(cam, compW, compH) {
2520
- let fx = cam.lookAtX - cam.x;
2521
- let fy = cam.lookAtY - cam.y;
2522
- let fz = cam.lookAtZ - cam.z;
2523
- let len = Math.hypot(fx, fy, fz);
2524
- if (len < 1e-9) {
2525
- fx = 0;
2526
- fy = 0;
2527
- fz = -1;
2528
- len = 1;
2529
- }
2530
- fx /= len;
2531
- fy /= len;
2532
- fz /= len;
2533
- let rx = -fz;
2534
- const ry = 0;
2535
- let rz = fx;
2536
- const rlen = Math.hypot(rx, rz);
2537
- if (rlen < 1e-9) {
2538
- rx = 1;
2539
- rz = 0;
2540
- } else {
2541
- rx /= rlen;
2542
- rz /= rlen;
2543
- }
2544
- const dx = ry * fz - rz * fy;
2545
- const dy = rz * fx - rx * fz;
2546
- const dz = rx * fy - ry * fx;
2568
+ function oklabToRgb(lab) {
2569
+ const l_ = lab.L + 0.3963377774 * lab.a + 0.2158037573 * lab.b;
2570
+ const m_ = lab.L - 0.1055613458 * lab.a - 0.0638541728 * lab.b;
2571
+ const s_ = lab.L - 0.0894841775 * lab.a - 1.291485548 * lab.b;
2572
+ const l = l_ * l_ * l_;
2573
+ const m = m_ * m_ * m_;
2574
+ const s = s_ * s_ * s_;
2547
2575
  return {
2548
- cx: cam.x,
2549
- cy: cam.y,
2550
- cz: cam.z,
2551
- rx,
2552
- ry,
2553
- rz,
2554
- dx,
2555
- dy,
2556
- dz,
2557
- fx,
2558
- fy,
2559
- fz,
2560
- focal: compH / 2 / Math.tan(clampFov(cam.fov) * DEG3 / 2),
2561
- halfW: compW / 2,
2562
- halfH: compH / 2
2576
+ r: linearToSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
2577
+ g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
2578
+ b: linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)
2563
2579
  };
2564
2580
  }
2565
- function layerToWorld(t, px, py) {
2566
- let x = (px - t.pivotX) * t.scaleX;
2567
- let y = (py - t.pivotY) * t.scaleY;
2568
- let z = 0;
2569
- let c = Math.cos(t.rotationX);
2570
- let s = Math.sin(t.rotationX);
2571
- [y, z] = [y * c - z * s, y * s + z * c];
2572
- c = Math.cos(t.rotationY);
2573
- s = Math.sin(t.rotationY);
2574
- [x, z] = [x * c + z * s, -x * s + z * c];
2575
- c = Math.cos(t.rotationZ);
2576
- s = Math.sin(t.rotationZ);
2577
- [x, y] = [x * c - y * s, x * s + y * c];
2578
- return { x: x + t.x, y: y + t.y, z: z + t.z };
2579
- }
2580
- function projectPoint(b, p) {
2581
- const vx = p.x - b.cx;
2582
- const vy = p.y - b.cy;
2583
- const vz = p.z - b.cz;
2584
- const xc = vx * b.rx + vy * b.ry + vz * b.rz;
2585
- const yc = vx * b.dx + vy * b.dy + vz * b.dz;
2586
- const zc = vx * b.fx + vy * b.fy + vz * b.fz;
2587
- return { x: b.halfW + b.focal * xc / zc, y: b.halfH + b.focal * yc / zc, depth: zc };
2581
+ function oklabToOklch(lab) {
2582
+ return { L: lab.L, C: Math.hypot(lab.a, lab.b), h: Math.atan2(lab.b, lab.a) };
2588
2583
  }
2589
- function projectLayer(t, frame, b) {
2590
- const pts = [
2591
- [frame.x, frame.y],
2592
- [frame.x + frame.width, frame.y],
2593
- [frame.x + frame.width, frame.y + frame.height],
2594
- [frame.x, frame.y + frame.height]
2595
- ];
2596
- let visible = true;
2597
- const corners = [];
2598
- for (const [px, py] of pts) {
2599
- const p = projectPoint(b, layerToWorld(t, px, py));
2600
- if (!(p.depth > NEAR)) visible = false;
2601
- corners.push(p.x, p.y);
2602
- }
2603
- const origin = projectPoint(b, { x: t.x, y: t.y, z: t.z });
2604
- return { corners, depth: origin.depth, visible };
2584
+ function oklchToOklab(lch) {
2585
+ return { L: lch.L, a: lch.C * Math.cos(lch.h), b: lch.C * Math.sin(lch.h) };
2605
2586
  }
2606
-
2607
- // src/space/focus.ts
2608
- var DEFAULT_APERTURE = 30;
2609
- var MIN_BLUR = 0.05;
2610
- var MAX_BLUR = 32;
2611
- var MANY_BLURRED = 20;
2612
- function blurRadius(aperture, focal, depth, focusDepth) {
2613
- if (!(aperture > 0) || !(focal > 0) || !(depth > 0) || !(focusDepth > 0)) return 0;
2614
- const r = aperture * focal * Math.abs(1 / depth - 1 / focusDepth) / 2;
2615
- return Math.min(r, MAX_BLUR);
2587
+ function lerp(a, b, t) {
2588
+ return a + (b - a) * t;
2616
2589
  }
2617
- var SCOPE_NAMES = /* @__PURE__ */ new Set(["w", "h", "W", "H", "GW", "GH", "contain", "cover", "t", "d", "T"]);
2618
- function looksLikeLayerName(s) {
2619
- return /^[A-Za-z_][\w-]*$/.test(s) && !SCOPE_NAMES.has(s);
2590
+ function clamp01(v) {
2591
+ return v < 0 ? 0 : v > 1 ? 1 : v;
2620
2592
  }
2621
- var isName = (v, known) => typeof v === "string" && (known.has(v) || looksLikeLayerName(v));
2622
- function focusNames(props, known) {
2623
- const out = [];
2624
- const read2 = (bag) => {
2625
- const v = bag?.focus;
2626
- if (isName(v, known)) out.push(v);
2627
- };
2628
- read2(props.initial);
2629
- for (const kf of props.keyframes ?? []) for (const bag of [kf.set, kf.to, kf.from]) read2(bag);
2630
- return out;
2593
+ function shortestHueDelta(from, to) {
2594
+ const TWO_PI = Math.PI * 2;
2595
+ let d = (to - from) % TWO_PI;
2596
+ if (d > Math.PI) d -= TWO_PI;
2597
+ if (d < -Math.PI) d += TWO_PI;
2598
+ return d;
2631
2599
  }
2632
- function withFocusResolved(props, zOf, known = /* @__PURE__ */ new Set()) {
2633
- if (focusNames(props, known).length === 0) return props;
2634
- const fix = (bag) => {
2635
- const v = bag?.focus;
2636
- if (!bag || !isName(v, known)) return bag;
2637
- return { ...bag, focus: zOf(v) ?? 0 };
2638
- };
2600
+
2601
+ // src/sequences/gradientAnim.ts
2602
+ var PATCH_KEYS = ["type", "angle", "center", "innerRadius", "radius", "stops"];
2603
+ function gradStateFrom(g) {
2639
2604
  return {
2640
- ...props,
2641
- initial: fix(props.initial),
2642
- keyframes: props.keyframes?.map((kf) => ({ ...kf, set: fix(kf.set), to: fix(kf.to), from: fix(kf.from) }))
2605
+ type: g.type ?? "linear",
2606
+ angle: g.angle ?? 90,
2607
+ center: [...g.center ?? [0.5, 0.5]],
2608
+ innerRadius: g.innerRadius ?? 0,
2609
+ radius: g.radius ?? 0.5,
2610
+ stops: (g.stops ?? []).map((s) => Array.isArray(s) ? { offset: s[0], color: cssColor(s[1]) } : { offset: s.offset, color: cssColor(s.color) })
2643
2611
  };
2644
2612
  }
2645
- function layerInitialZ(spec, scope) {
2646
- const z = spec.initial?.z;
2647
- if (typeof z === "number") return z;
2648
- if (typeof z === "string") return evaluateExpr(z, scope);
2649
- return 0;
2650
- }
2651
- var movesZ = (spec) => (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((bag) => bag && "z" in bag));
2652
- function focusProblems(camera, siblings) {
2653
- const out = [];
2654
- const said = /* @__PURE__ */ new Set();
2655
- const threeD = siblings.filter((s) => s.threeD && s.name).map((s) => s.name);
2656
- const known = new Set(siblings.map((s) => s.name).filter((n) => !!n));
2657
- for (const name of focusNames(camera, known)) {
2658
- if (said.has(name)) continue;
2659
- said.add(name);
2660
- const same = siblings.filter((s) => s.name === name);
2661
- const hit = same.find((s) => s.threeD) ?? same[0];
2662
- if (!hit) out.push({ kind: "missing", name, hint: suggestName(name, threeD) });
2663
- else if (!hit.threeD) out.push({ kind: "not-threeD", name });
2664
- else {
2665
- if (same.length > 1) out.push({ kind: "duplicate", name, count: same.length });
2666
- if (movesZ(hit)) out.push({ kind: "moving", name });
2667
- }
2613
+ function mergeGrad(base, patch) {
2614
+ const out = { ...base, center: [...base.center], stops: base.stops.map((s) => ({ ...s })) };
2615
+ if (patch === null || typeof patch !== "object" || Array.isArray(patch)) return out;
2616
+ const p = patch;
2617
+ if (typeof p.angle === "number") out.angle = p.angle;
2618
+ if (Array.isArray(p.center)) out.center = [Number(p.center[0]), Number(p.center[1])];
2619
+ if (typeof p.innerRadius === "number") out.innerRadius = p.innerRadius;
2620
+ if (typeof p.radius === "number") out.radius = p.radius;
2621
+ if (p.stops !== void 0) {
2622
+ const next = gradStateFrom({ stops: p.stops }).stops;
2623
+ if (next.length === base.stops.length) out.stops = next;
2668
2624
  }
2669
2625
  return out;
2670
2626
  }
2671
- var CameraSequence = class extends Sequence {
2672
- /** True when `z` is never specified anywhere: z then follows `fov`. */
2673
- autoZ = true;
2674
- compH = 0;
2675
- warnedFov = false;
2676
- warnedLookAt = false;
2677
- warnedAperture = false;
2678
- /** True when the camera writes `focus` or `aperture` anywhere: depth of field is on. */
2679
- dof = false;
2680
- /** True when the camera writes any fog key anywhere: depth fog is on (nothing is made for a camera without fog). */
2681
- fog = false;
2682
- fogC = null;
2683
- zOfName = null;
2684
- layerNames = /* @__PURE__ */ new Set();
2685
- async build() {
2686
- const compW = this.parent?.width ?? this.root.width;
2687
- this.compH = this.parent?.height ?? this.root.height;
2688
- if (this.duration === void 0) {
2689
- this.duration = this.parent?.duration ?? this.root.duration;
2690
- }
2691
- const home = homeCamera(compW, this.compH);
2692
- const carrier = new Container();
2693
- carrier.x = home.x;
2694
- carrier.y = home.y;
2695
- carrier.z = home.z;
2696
- carrier.lookAtX = home.lookAtX;
2697
- carrier.lookAtY = home.lookAtY;
2698
- carrier.lookAtZ = home.lookAtZ;
2699
- carrier.fov = home.fov;
2700
- carrier.focus = 0;
2701
- carrier.aperture = DEFAULT_APERTURE;
2702
- carrier.offsetX = carrier.offsetY = carrier.offsetZ = 0;
2703
- carrier.lookOffsetX = carrier.lookOffsetY = carrier.lookOffsetZ = 0;
2704
- carrier.fogNear = home.z;
2705
- carrier.fogFar = home.z * 3;
2706
- carrier.fogColor = "#000000";
2707
- carrier.fogAmount = 1;
2708
- this.target = carrier;
2709
- const keys = collectPropKeys(this.spec);
2710
- this.autoZ = !keys.has("z");
2711
- this.dof = keys.has("focus") || keys.has("aperture");
2712
- this.fog = keys.has("fogNear") || keys.has("fogFar") || keys.has("fogColor") || keys.has("fogAmount");
2713
- }
2714
- /** The aperture to use now: off unless depth of field is on and the value is a number of 0 or more (said once if it is not). */
2715
- apertureNow(a) {
2716
- if (!this.dof) return 0;
2717
- if (!(a >= 0) && !this.warnedAperture) {
2718
- this.warnedAperture = true;
2719
- const who = this.spec.name ? ` "${this.spec.name}"` : "";
2720
- console.warn(`pixi-effects: camera${who}: aperture ${a} is not a number of 0 or more; depth of field is off (it is the lens diameter in px: 30 is the default, 0 turns it off)`);
2627
+ var objectHint = "a keyframe's fillGradient must be an object like { angle: 200, stops: [[0, '#f00'], [1, '#00f']] }";
2628
+ function validateGradientKeyframes(spec, who, warn = (m) => console.warn(m), kind = "shape") {
2629
+ const said = /* @__PURE__ */ new Set();
2630
+ const say = (m) => {
2631
+ if (!said.has(m)) {
2632
+ said.add(m);
2633
+ warn(`pixi-effects: ${who}: ${m}`);
2721
2634
  }
2722
- return a > 0 ? a : 0;
2723
- }
2724
- /** Called by the composition once its children exist: how a layer name in `focus` becomes the z of that layer. */
2725
- resolveFocusNames(zOf, names = /* @__PURE__ */ new Set()) {
2726
- this.zOfName = zOf;
2727
- this.layerNames = names;
2728
- }
2729
- displayProps() {
2730
- const base = super.displayProps();
2731
- return this.zOfName ? withFocusResolved(base, this.zOfName, this.layerNames) : base;
2635
+ };
2636
+ const startSpec = spec.fillGradient ?? spec.initial?.fillGradient;
2637
+ const start = startSpec && typeof startSpec === "object" && Array.isArray(startSpec.stops) ? gradStateFrom(startSpec) : null;
2638
+ let animated = false;
2639
+ if (kind === "text") {
2640
+ const radial = (g) => !!g && typeof g === "object" && ["type", "center", "innerRadius", "radius"].some((k) => g[k] !== void 0 && !(k === "type" && g.type === "linear"));
2641
+ const asked = radial(startSpec) || (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => radial(b?.fillGradient)));
2642
+ 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");
2732
2643
  }
2733
- /** The camera's state at the current timeline position. */
2734
- state() {
2735
- const c = this.target;
2736
- if (!this.warnedFov && !(c.fov >= MIN_FOV && c.fov <= MAX_FOV)) {
2737
- this.warnedFov = true;
2738
- const who = this.spec.name ? ` "${this.spec.name}"` : "";
2739
- console.warn(`pixi-effects: camera${who}: fov ${c.fov} is outside [${MIN_FOV}, ${MAX_FOV}]; clamped`);
2740
- }
2741
- const fov = clampFov(c.fov);
2742
- const z = this.autoZ ? homeDistance(this.compH, fov) : c.z;
2743
- if (!this.warnedLookAt && c.lookAtX === c.x && c.lookAtY === c.y && c.lookAtZ === z) {
2744
- this.warnedLookAt = true;
2745
- const who = this.spec.name ? ` "${this.spec.name}"` : "";
2746
- console.warn(`pixi-effects: camera${who}: lookAt equals the camera position; looking along -z instead`);
2747
- }
2748
- return {
2749
- x: c.x + c.offsetX,
2750
- y: c.y + c.offsetY,
2751
- z: z + c.offsetZ,
2752
- lookAtX: c.lookAtX + c.lookOffsetX,
2753
- lookAtY: c.lookAtY + c.lookOffsetY,
2754
- lookAtZ: c.lookAtZ + c.lookOffsetZ,
2755
- fov,
2756
- focus: Number.isFinite(c.focus) ? c.focus : 0,
2757
- aperture: this.apertureNow(c.aperture)
2758
- };
2759
- }
2760
- /** The fog now (colour as 0..1 channels, distances, 0..1 amount), or null when the camera writes no fog key. */
2761
- fogState() {
2762
- if (!this.fog) return null;
2763
- const c = this.target;
2764
- const fc = this.fogC ??= new Color();
2765
- try {
2766
- fc.setValue(c.fogColor);
2767
- } catch {
2768
- fc.setValue(0);
2644
+ for (const kf of spec.keyframes ?? []) {
2645
+ for (const bag of [kf.set, kf.to, kf.from]) {
2646
+ if (!bag) continue;
2647
+ for (const [key, value] of Object.entries(bag)) {
2648
+ if (key === "fillGradient") {
2649
+ animated = true;
2650
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2651
+ say(objectHint);
2652
+ continue;
2653
+ }
2654
+ const p = value;
2655
+ for (const k of Object.keys(p)) {
2656
+ if (PATCH_KEYS.includes(k)) continue;
2657
+ const guess = suggestName(k, PATCH_KEYS.filter((x) => x !== "type"));
2658
+ say(`fillGradient.${k} is not a gradient property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${PATCH_KEYS.join(", ")}); it is ignored`);
2659
+ }
2660
+ if (p.type !== void 0 && start && p.type !== start.type) say("fillGradient.type cannot change over time (linear <-> radial); it is ignored");
2661
+ if (p.stops !== void 0 && start) {
2662
+ const n = Array.isArray(p.stops) ? p.stops.length : 0;
2663
+ 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`);
2664
+ }
2665
+ } else if (/^fillGradient\./i.test(key) || /^gradient/i.test(key)) {
2666
+ const tail = key.replace(/^fillGradient\./i, "").replace(/^gradient/i, "");
2667
+ const sub2 = tail ? tail[0].toLowerCase() + tail.slice(1) : "angle";
2668
+ say(`"${key}" is not an animatable property: write fillGradient: { ${PATCH_KEYS.includes(sub2) ? sub2 : "angle"} } (the whole gradient is one object) in the keyframe's to / from / set`);
2669
+ }
2670
+ }
2769
2671
  }
2770
- const [r, g, b] = fc.toArray();
2771
- const amount = Number.isFinite(c.fogAmount) ? Math.min(1, Math.max(0, c.fogAmount)) : 1;
2772
- return { r, g, b, near: c.fogNear, far: c.fogFar, amount };
2773
- }
2774
- /** Lifespan on the global timeline (valid after bindTimeline). */
2775
- window() {
2776
- const start = this.absoluteStart ?? this.at;
2777
- return { start, end: start + (this.duration ?? 0) };
2778
2672
  }
2779
- };
2780
-
2781
- // src/space/lighting.ts
2782
- var LIGHT_KINDS = ["ambient", "point", "spot", "parallel"];
2783
- var FALLOFFS = ["none", "smooth", "inverseSquare"];
2784
- var LIGHT_PROPS = ["x", "y", "z", "lookAtX", "lookAtY", "lookAtZ", "intensity", "color", "coneAngle", "coneFeather", "radius", "falloffDistance", "shadowDarkness", "shadowDiffusion"];
2785
- var MAX_LIGHTS = 8;
2786
- var MAX_CASTERS = 4;
2787
- var SHADOW_TAPS = 16;
2788
- var FALLOFF_ID = { none: 0, smooth: 1, inverseSquare: 2 };
2789
- var KIND_ID = { point: 1, spot: 2, parallel: 3 };
2790
- function smoothstep(e0, e1, x) {
2791
- const t = Math.min(1, Math.max(0, (x - e0) / (e1 - e0)));
2792
- return t * t * (3 - 2 * t);
2793
- }
2794
- var DEG4 = Math.PI / 180;
2795
- function norm(v) {
2796
- const l = Math.hypot(v.x, v.y, v.z) || 1;
2797
- return { x: v.x / l, y: v.y / l, z: v.z / l };
2673
+ 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)");
2798
2674
  }
2799
- var dot = (a, b) => a.x * b.x + a.y * b.y + a.z * b.z;
2800
- var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y, z: a.z - b.z });
2801
- function coneCos(l) {
2802
- const half = Math.min(179, Math.max(0, l.coneAngle)) * DEG4 / 2;
2803
- const inner = half * (1 - Math.min(1, Math.max(0, l.coneFeather)));
2804
- const cosOuter = Math.cos(half);
2805
- const cosInner = Math.max(Math.cos(inner), cosOuter + 1e-4);
2806
- return { cosOuter, cosInner };
2675
+ function tweenGradient(timeline, holder, fromPatch, toPatch, duration, ease, at, colorSpace, onChange, loop = {}) {
2676
+ timeline.tweenValue({
2677
+ holder,
2678
+ id: "grad",
2679
+ get: () => holder.grad,
2680
+ set: (g) => {
2681
+ holder.grad = g;
2682
+ onChange();
2683
+ },
2684
+ from: fromPatch !== void 0 ? (prev) => mergeGrad(prev, fromPatch) : void 0,
2685
+ to: (start, prev) => toPatch !== void 0 ? mergeGrad(start, toPatch) : prev,
2686
+ make: (a, b) => {
2687
+ const colors = a.stops.map((s, i) => colorSpace === "rgb" ? interpolateColors(s.color, b.stops[i].color) : buildColorInterp(s.color, b.stops[i].color, colorSpace));
2688
+ return (t) => {
2689
+ const l = (x, y) => x + (y - x) * t;
2690
+ return {
2691
+ type: a.type,
2692
+ angle: l(a.angle, b.angle),
2693
+ center: [l(a.center[0], b.center[0]), l(a.center[1], b.center[1])],
2694
+ innerRadius: l(a.innerRadius, b.innerRadius),
2695
+ radius: l(a.radius, b.radius),
2696
+ stops: a.stops.map((s, i) => ({ offset: l(s.offset, b.stops[i].offset), color: colors[i](t) }))
2697
+ };
2698
+ };
2699
+ },
2700
+ duration,
2701
+ ease,
2702
+ at,
2703
+ repeat: loop.repeat,
2704
+ yoyo: loop.yoyo,
2705
+ repeatDelay: loop.repeatDelay
2706
+ });
2807
2707
  }
2808
- function packLights(lights) {
2809
- const pos = new Float32Array(4 * MAX_LIGHTS), dir = new Float32Array(4 * MAX_LIGHTS), col = new Float32Array(4 * MAX_LIGHTS), fall = new Float32Array(4 * MAX_LIGHTS), shadow = new Float32Array(4 * MAX_LIGHTS);
2810
- const ambient = [0, 0, 0];
2811
- let n = 0;
2812
- for (const l of lights) {
2813
- if (l.kind === "ambient") {
2814
- ambient[0] += l.r * l.intensity;
2815
- ambient[1] += l.g * l.intensity;
2816
- ambient[2] += l.b * l.intensity;
2817
- continue;
2818
- }
2819
- if (n >= MAX_LIGHTS) continue;
2820
- const d = norm({ x: l.lookAtX - l.x, y: l.lookAtY - l.y, z: l.lookAtZ - l.z });
2821
- const { cosOuter, cosInner } = coneCos(l);
2822
- pos.set([l.x, l.y, l.z, KIND_ID[l.kind]], 4 * n);
2823
- dir.set([d.x, d.y, d.z, cosOuter], 4 * n);
2824
- col.set([l.r * l.intensity, l.g * l.intensity, l.b * l.intensity, cosInner], 4 * n);
2825
- fall.set([FALLOFF_ID[l.falloff], l.radius, l.falloffDistance, l.castsShadows ? 1 : 0], 4 * n);
2826
- shadow.set([l.castsShadows ? l.shadowDarkness : 0, l.shadowDiffusion, 0, 0], 4 * n);
2827
- n++;
2708
+ var PAINT_SIZE = 512;
2709
+ var GradientPainter = class {
2710
+ constructor(size = PAINT_SIZE) {
2711
+ this.size = size;
2712
+ this.canvas.width = size;
2713
+ this.canvas.height = size;
2714
+ this.tex = Texture.from(this.canvas);
2715
+ globalThis.__gradientPainters = (globalThis.__gradientPainters ?? 0) + 1;
2828
2716
  }
2829
- return { count: n, ambient, pos, dir, col, fall, shadow };
2830
- }
2831
- function shadeReference(p, n, cam, lights) {
2832
- let N = norm(n);
2833
- if (dot(N, sub(cam, p)) < 0) N = { x: -N.x, y: -N.y, z: -N.z };
2834
- const out = [0, 0, 0];
2835
- for (const l of lights) {
2836
- if (l.kind === "ambient") {
2837
- out[0] += l.r * l.intensity;
2838
- out[1] += l.g * l.intensity;
2839
- out[2] += l.b * l.intensity;
2840
- continue;
2841
- }
2842
- const dirLA = norm({ x: l.lookAtX - l.x, y: l.lookAtY - l.y, z: l.lookAtZ - l.z });
2843
- let L;
2844
- let dist = 0;
2845
- if (l.kind === "parallel") L = { x: -dirLA.x, y: -dirLA.y, z: -dirLA.z };
2846
- else {
2847
- const d = sub({ x: l.x, y: l.y, z: l.z }, p);
2848
- dist = Math.hypot(d.x, d.y, d.z);
2849
- L = { x: d.x / dist, y: d.y / dist, z: d.z / dist };
2717
+ size;
2718
+ canvas = document.createElement("canvas");
2719
+ tex;
2720
+ /** Paint `g` and return what `Graphics.fill(...)` takes. */
2721
+ paint(g) {
2722
+ const ctx = this.canvas.getContext("2d"), s = this.size;
2723
+ if (!ctx) return new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((x) => [x.offset, x.color]) }));
2724
+ ctx.clearRect(0, 0, s, s);
2725
+ if (g.type === "radial") {
2726
+ paintRadial(ctx, s, { stops: g.stops.map((x) => [x.offset, x.color]), center: g.center, innerRadius: g.innerRadius, radius: g.radius });
2727
+ } else {
2728
+ const a = g.angle * Math.PI / 180, dx = Math.cos(a), dy = Math.sin(a);
2729
+ const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2730
+ const grad = ctx.createLinearGradient((0.5 - dx * h) * s, (0.5 - dy * h) * s, (0.5 + dx * h) * s, (0.5 + dy * h) * s);
2731
+ for (const st of g.stops) grad.addColorStop(Math.min(1, Math.max(0, st.offset)), st.color);
2732
+ ctx.fillStyle = grad;
2733
+ ctx.fillRect(0, 0, s, s);
2850
2734
  }
2851
- const ndl = Math.max(0, dot(N, L));
2852
- let att = 1;
2853
- if (l.kind !== "parallel") {
2854
- if (l.falloff === "smooth") att = 1 - smoothstep(l.radius, l.radius + Math.max(l.falloffDistance, 1e-3), dist);
2855
- else if (l.falloff === "inverseSquare") att = Math.min(1, l.radius * l.radius / Math.max(dist * dist, 1e-6));
2735
+ this.tex.source.update();
2736
+ return { texture: this.tex, textureSpace: "local" };
2737
+ }
2738
+ /** Give the texture and the canvas back (a layer that is destroyed must not leave them). */
2739
+ destroy() {
2740
+ this.tex.destroy(true);
2741
+ this.canvas.width = this.canvas.height = 0;
2742
+ globalThis.__gradientPainters = Math.max(0, (globalThis.__gradientPainters ?? 1) - 1);
2743
+ }
2744
+ };
2745
+ function bindGradientKeyframes(timeline, holder, keyframes, duration, offset, colorSpace, onChange) {
2746
+ const bag = (b) => b?.fillGradient;
2747
+ for (const kf of keyframes) {
2748
+ const at = offset + resolveAt(kf.at, duration);
2749
+ const setV = bag(kf.set), fromV = bag(kf.from), toV = bag(kf.to);
2750
+ if (setV !== void 0) {
2751
+ timeline.tweenValue({
2752
+ holder,
2753
+ id: "grad",
2754
+ get: () => holder.grad,
2755
+ set: (g) => {
2756
+ holder.grad = g;
2757
+ onChange();
2758
+ },
2759
+ to: (start) => mergeGrad(start, setV),
2760
+ make: (_a, b) => () => b,
2761
+ duration: 0,
2762
+ ease: "none",
2763
+ at
2764
+ });
2856
2765
  }
2857
- let cone = 1;
2858
- if (l.kind === "spot") {
2859
- const { cosOuter, cosInner } = coneCos(l);
2860
- cone = smoothstep(cosOuter, cosInner, -dot(L, dirLA));
2766
+ if (fromV !== void 0 || toV !== void 0) {
2767
+ tweenGradient(timeline, holder, fromV, toV, kfDuration(kf), kf.ease ?? "none", at, colorSpace, onChange, loopVars(kf));
2861
2768
  }
2862
- const k = l.intensity * ndl * att * cone;
2863
- out[0] += l.r * k;
2864
- out[1] += l.g * k;
2865
- out[2] += l.b * k;
2866
2769
  }
2867
- return out;
2868
2770
  }
2869
- function lightLevel(frame, cam, lights) {
2870
- const mid = { x: frame.o.x + 0.5 * frame.u.x + 0.5 * frame.v.x, y: frame.o.y + 0.5 * frame.u.y + 0.5 * frame.v.y, z: frame.o.z + 0.5 * frame.u.z + 0.5 * frame.v.z };
2871
- const n = { x: frame.u.y * frame.v.z - frame.u.z * frame.v.y, y: frame.u.z * frame.v.x - frame.u.x * frame.v.z, z: frame.u.x * frame.v.y - frame.u.y * frame.v.x };
2872
- const m = shadeReference(mid, n, cam, lights);
2873
- return 0.2126 * m[0] + 0.7152 * m[1] + 0.0722 * m[2];
2874
- }
2875
- var LightSequence = class extends Sequence {
2876
- color = new Color();
2877
- async build() {
2878
- const w = this.parent?.width ?? this.root.width;
2879
- const h = this.parent?.height ?? this.root.height;
2880
- if (this.duration === void 0) this.duration = this.parent?.duration ?? this.root.duration;
2881
- const c = new Container();
2882
- c.x = w * 0.3;
2883
- c.y = h * 0.2;
2884
- c.z = homeDistance(h, 40) * 0.6;
2885
- c.lookAtX = w / 2;
2886
- c.lookAtY = h / 2;
2887
- c.lookAtZ = 0;
2888
- c.intensity = 1;
2889
- c.color = "#ffffff";
2890
- c.coneAngle = 90;
2891
- c.coneFeather = 0.5;
2892
- c.radius = 500;
2893
- c.falloffDistance = 500;
2894
- c.shadowDarkness = 1;
2895
- c.shadowDiffusion = 0;
2896
- this.target = c;
2897
- }
2898
- state() {
2899
- const c = this.target;
2900
- try {
2901
- this.color.setValue(c.color);
2902
- } catch {
2903
- this.color.setValue(16777215);
2904
- }
2905
- const [r, g, b] = this.color.toArray();
2906
- return {
2907
- // a kind or falloff nobody knows is the default the warning promises (the shader must never see an unknown id)
2908
- kind: LIGHT_KINDS.includes(this.spec.kind) ? this.spec.kind : "point",
2909
- x: c.x,
2910
- y: c.y,
2911
- z: c.z,
2912
- lookAtX: c.lookAtX,
2913
- lookAtY: c.lookAtY,
2914
- lookAtZ: c.lookAtZ,
2915
- r,
2916
- g,
2917
- b,
2918
- intensity: Number.isFinite(c.intensity) ? Math.max(0, c.intensity) : 1,
2919
- coneAngle: c.coneAngle,
2920
- coneFeather: c.coneFeather,
2921
- falloff: FALLOFFS.includes(this.spec.falloff) ? this.spec.falloff : "none",
2922
- radius: c.radius,
2923
- falloffDistance: c.falloffDistance,
2924
- castsShadows: !!this.spec.castsShadows,
2925
- shadowDarkness: Math.min(1, Math.max(0, c.shadowDarkness)),
2926
- shadowDiffusion: Math.max(0, c.shadowDiffusion)
2927
- };
2928
- }
2929
- window() {
2930
- const start = this.absoluteStart ?? this.at;
2931
- return { start, end: start + (this.duration ?? 0) };
2932
- }
2933
- };
2934
-
2935
- // src/core/revertibleSet.ts
2936
- function revertibleSet(timeline, at, read2, write2, value, property) {
2937
- timeline.setValue(property?.[0] ?? read2, property?.[1] ?? "set", at, read2, write2, value);
2938
- }
2939
-
2940
- // src/expr/colorInterp.ts
2941
- var CHROMA_FLOOR = 0.03;
2942
- var CHROMA_FULL = 0.08;
2943
- function buildColorInterp(from, to, space) {
2944
- const fromRGBA = parseToRGBA(from);
2945
- const toRGBA = parseToRGBA(to);
2946
- const fromOk = rgbToOklab(fromRGBA);
2947
- const toOk = rgbToOklab(toRGBA);
2948
- if (space === "oklab") {
2949
- return (t) => {
2950
- const L = lerp(fromOk.L, toOk.L, t);
2951
- const a = lerp(fromOk.a, toOk.a, t);
2952
- const b = lerp(fromOk.b, toOk.b, t);
2953
- const alpha = lerp(fromRGBA.a, toRGBA.a, t);
2954
- return rgbaString(oklabToRgb({ L, a, b }), alpha);
2955
- };
2956
- }
2957
- const fromLCH = oklabToOklch(fromOk);
2958
- const toLCH = oklabToOklch(toOk);
2959
- const fromHue = fromLCH.h, toHue = toLCH.h;
2960
- fromLCH.h = borrowHue(fromHue, fromLCH.C, toHue);
2961
- toLCH.h = borrowHue(toHue, toLCH.C, fromHue);
2962
- const hueDelta = shortestHueDelta(fromLCH.h, toLCH.h);
2963
- return (t) => {
2964
- const L = lerp(fromLCH.L, toLCH.L, t);
2965
- const C = lerp(fromLCH.C, toLCH.C, t);
2966
- const h = fromLCH.h + hueDelta * t;
2967
- const alpha = lerp(fromRGBA.a, toRGBA.a, t);
2968
- const ab = oklchToOklab({ L, C, h });
2969
- return rgbaString(oklabToRgb(ab), alpha);
2970
- };
2971
- }
2972
- function borrowHue(own, chroma, other) {
2973
- const u = Math.min(1, Math.max(0, (chroma - CHROMA_FLOOR) / (CHROMA_FULL - CHROMA_FLOOR)));
2974
- const keep = u * u * (3 - 2 * u);
2975
- return own + shortestHueDelta(own, other) * (1 - keep);
2976
- }
2977
- function parseToRGBA(input) {
2978
- if (typeof input === "number") {
2979
- return { r: (input >> 16 & 255) / 255, g: (input >> 8 & 255) / 255, b: (input & 255) / 255, a: 1 };
2980
- }
2981
- const s = input.trim();
2982
- if (s.startsWith("#")) return parseHex(s);
2983
- if (s.startsWith("rgb")) return parseRgbFunc(s);
2984
- return { r: 0, g: 0, b: 0, a: 1 };
2985
- }
2986
- function parseHex(s) {
2987
- const hex = s.slice(1);
2988
- let r = 0, g = 0, b = 0, a = 1;
2989
- if (hex.length === 3 || hex.length === 4) {
2990
- r = parseInt(hex[0] + hex[0], 16) / 255;
2991
- g = parseInt(hex[1] + hex[1], 16) / 255;
2992
- b = parseInt(hex[2] + hex[2], 16) / 255;
2993
- if (hex.length === 4) a = parseInt(hex[3] + hex[3], 16) / 255;
2994
- } else if (hex.length === 6 || hex.length === 8) {
2995
- r = parseInt(hex.slice(0, 2), 16) / 255;
2996
- g = parseInt(hex.slice(2, 4), 16) / 255;
2997
- b = parseInt(hex.slice(4, 6), 16) / 255;
2998
- if (hex.length === 8) a = parseInt(hex.slice(6, 8), 16) / 255;
2999
- }
3000
- return { r, g, b, a };
3001
- }
3002
- function parseRgbFunc(s) {
3003
- const inner = s.replace(/^rgba?\(/, "").replace(/\)$/, "");
3004
- const parts = inner.split(/[,\s/]+/).filter(Boolean).map(parseFloat);
3005
- return {
3006
- r: (parts[0] ?? 0) / 255,
3007
- g: (parts[1] ?? 0) / 255,
3008
- b: (parts[2] ?? 0) / 255,
3009
- a: parts.length > 3 ? parts[3] ?? 1 : 1
3010
- };
3011
- }
3012
- function rgbaString(rgb, alpha) {
3013
- const r = Math.round(clamp01(rgb.r) * 255);
3014
- const g = Math.round(clamp01(rgb.g) * 255);
3015
- const b = Math.round(clamp01(rgb.b) * 255);
3016
- return `rgba(${r},${g},${b},${alpha})`;
3017
- }
3018
- function srgbToLinear(c) {
3019
- return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
3020
- }
3021
- function linearToSrgb(c) {
3022
- return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
3023
- }
3024
- function rgbToOklab(rgba) {
3025
- const r = srgbToLinear(rgba.r);
3026
- const g = srgbToLinear(rgba.g);
3027
- const b = srgbToLinear(rgba.b);
3028
- const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
3029
- const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
3030
- const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
3031
- const l_ = Math.cbrt(l);
3032
- const m_ = Math.cbrt(m);
3033
- const s_ = Math.cbrt(s);
3034
- return {
3035
- L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
3036
- a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
3037
- b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_
3038
- };
3039
- }
3040
- function oklabToRgb(lab) {
3041
- const l_ = lab.L + 0.3963377774 * lab.a + 0.2158037573 * lab.b;
3042
- const m_ = lab.L - 0.1055613458 * lab.a - 0.0638541728 * lab.b;
3043
- const s_ = lab.L - 0.0894841775 * lab.a - 1.291485548 * lab.b;
3044
- const l = l_ * l_ * l_;
3045
- const m = m_ * m_ * m_;
3046
- const s = s_ * s_ * s_;
3047
- return {
3048
- r: linearToSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
3049
- g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
3050
- b: linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)
3051
- };
3052
- }
3053
- function oklabToOklch(lab) {
3054
- return { L: lab.L, C: Math.hypot(lab.a, lab.b), h: Math.atan2(lab.b, lab.a) };
3055
- }
3056
- function oklchToOklab(lch) {
3057
- return { L: lch.L, a: lch.C * Math.cos(lch.h), b: lch.C * Math.sin(lch.h) };
3058
- }
3059
- function lerp(a, b, t) {
3060
- return a + (b - a) * t;
3061
- }
3062
- function clamp01(v) {
3063
- return v < 0 ? 0 : v > 1 ? 1 : v;
3064
- }
3065
- function shortestHueDelta(from, to) {
3066
- const TWO_PI = Math.PI * 2;
3067
- let d = (to - from) % TWO_PI;
3068
- if (d > Math.PI) d -= TWO_PI;
3069
- if (d < -Math.PI) d += TWO_PI;
3070
- return d;
2771
+ var hasGradientKeys = (keyframes) => (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => b && b.fillGradient !== void 0));
2772
+
2773
+ // src/core/revertibleSet.ts
2774
+ function revertibleSet(timeline, at, read2, write2, value, property) {
2775
+ timeline.setValue(property?.[0] ?? read2, property?.[1] ?? "set", at, read2, write2, value);
3071
2776
  }
3072
2777
 
3073
2778
  // src/expr/colorTween.ts
@@ -3095,555 +2800,1058 @@ function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, a
3095
2800
  });
3096
2801
  }
3097
2802
 
3098
- // src/sequences/Image.ts
3099
- var ImageSequence = class extends Sequence {
3100
- async build() {
3101
- const texture = await Assets.get(this.spec.asset);
3102
- const sprite = new Sprite({ texture, label: this.spec.name });
3103
- sprite.cullable = true;
3104
- this.target = sprite;
3105
- this.intrinsicWidth = texture.width;
3106
- this.intrinsicHeight = texture.height;
3107
- if (this.duration === void 0) {
3108
- this.duration = this.parent?.duration ?? this.root.duration;
3109
- }
3110
- this.buildFilters();
2803
+ // src/text/styleKeys.ts
2804
+ var STYLE_OPAQUE_KEYS = [
2805
+ "fontFamily",
2806
+ "fill",
2807
+ "align",
2808
+ "fontStyle",
2809
+ "fontWeight",
2810
+ "fontVariant",
2811
+ "textBaseline",
2812
+ "whiteSpace",
2813
+ "lineJoin",
2814
+ "join",
2815
+ "cap",
2816
+ "color",
2817
+ "stroke"
2818
+ ];
2819
+ var TEXT_STYLE_DEFAULTS = {
2820
+ fontFamily: "Arial",
2821
+ fontSize: 36,
2822
+ fill: "#ffffff",
2823
+ align: "center"
2824
+ };
2825
+
2826
+ // src/sequences/Text.ts
2827
+ function formatValue(v, f) {
2828
+ const fixed = v.toFixed(Math.max(0, Math.floor(f?.decimals ?? 0)));
2829
+ const pad = Math.max(0, Math.floor(f?.pad ?? 0));
2830
+ if (!f?.grouping && !pad) return fixed;
2831
+ const [signed, frac] = fixed.split(".");
2832
+ const sign = signed.startsWith("-") ? "-" : "";
2833
+ let int = sign ? signed.slice(1) : signed;
2834
+ if (pad) int = int.padStart(pad, "0");
2835
+ if (f?.grouping) int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
2836
+ return sign + int + (frac !== void 0 ? "." + frac : "");
2837
+ }
2838
+ var TextSequence = class extends Sequence {
2839
+ /** `value`: the number for the `{value}` placeholder; `visibleChars`: how many characters show (typewriter). Both tweened by keyframes. */
2840
+ _counters = { value: 0, visibleChars: 0 };
2841
+ /** The string now on the layer (before `{value}` / `visibleChars`): `text`, or the last `set: { text }`. */
2842
+ _template = "";
2843
+ /** `visibleChars` is in play (initial or animated): otherwise the whole text shows. */
2844
+ _typed = false;
2845
+ _lastText = "";
2846
+ /** A gradient fill for the text (`fillGradient`, static or animated): rebuilt as a FillGradient on each change (the text is re-rasterised). */
2847
+ _grad = null;
2848
+ _fillGradient = null;
2849
+ _applyGradient() {
2850
+ const g = this._grad.grad;
2851
+ const next = new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((s) => [s.offset, s.color]) }));
2852
+ this.target.style.fill = next;
2853
+ this._fillGradient?.destroy();
2854
+ this._fillGradient = next;
3111
2855
  }
3112
- bindTimeline(timeline, offset = 0) {
3113
- if (!this.target) return;
3114
- const colorSpace = this.spec.colorSpace;
3115
- if (!colorSpace || colorSpace === "rgb") {
3116
- super.bindTimeline(timeline, offset);
3117
- return;
3118
- }
3119
- const scope = this.scope();
3120
- const startTime = offset + this.at;
3121
- const initial = stripKeys(this.spec.initial, ["tint"]);
3122
- const keyframes = this.spec.keyframes ? this.spec.keyframes.map((kf) => stripKeysInKeyframe(kf, ["tint"])) : void 0;
3123
- applyInitial(this.target, initial, scope);
3124
- applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime);
3125
- const initTint = this.spec.initial?.tint;
3126
- if (initTint !== void 0) this.target.tint = initTint;
3127
- for (const kf of this.spec.keyframes ?? []) {
3128
- const at = startTime + resolveAt(kf.at, this.duration);
3129
- const duration = kfDuration(kf);
3130
- const ease = kf.ease ?? "none";
3131
- const fromTint = kf.from?.tint;
3132
- const toTint = kf.to?.tint;
3133
- const setTint = kf.set?.tint;
3134
- if (setTint !== void 0) {
3135
- const sprite = this.target;
3136
- revertibleSet(timeline, at, () => sprite.tint, (c) => {
3137
- sprite.tint = c;
3138
- }, setTint, [sprite, "tint"]);
3139
- }
3140
- if (toTint !== void 0) {
3141
- tweenColor(
3142
- timeline,
3143
- this.target,
3144
- "tint",
3145
- fromTint,
3146
- toTint,
3147
- duration,
3148
- ease,
3149
- at,
3150
- colorSpace,
3151
- void 0,
3152
- loopVars(kf)
3153
- );
2856
+ destroy() {
2857
+ super.destroy();
2858
+ this._fillGradient?.destroy();
2859
+ this._fillGradient = null;
2860
+ }
2861
+ /** @internal A `spans` group that types over several pieces: this piece shows its first `n` characters (the piece was built typed). */
2862
+ setVisibleChars(n) {
2863
+ this._counters.visibleChars = n;
2864
+ this._refreshText();
2865
+ }
2866
+ /** A typewriter that has typed nothing yet: the layer is empty on purpose (inspect must not call that "no size"). */
2867
+ get showsNothingYet() {
2868
+ return this._typed && this._lastText === "" && this._template !== "";
2869
+ }
2870
+ /** Rewrite the displayed string from the template, the current `value` and `visibleChars`. */
2871
+ _refreshText() {
2872
+ let next = this._template.includes("{value}") ? this._template.split("{value}").join(formatValue(this._counters.value, this.spec.format)) : this._template;
2873
+ if (this._typed) next = Array.from(next).slice(0, Math.max(0, Math.floor(this._counters.visibleChars))).join("");
2874
+ if (next === this._lastText) return;
2875
+ this._lastText = next;
2876
+ this.target.text = next;
2877
+ }
2878
+ async build() {
2879
+ const baseStyle = { ...TEXT_STYLE_DEFAULTS };
2880
+ const initialProps = this.spec.initial ?? {};
2881
+ this._template = String(initialProps.text ?? this.spec.text ?? "");
2882
+ const text = new Text({
2883
+ text: this._template,
2884
+ style: baseStyle,
2885
+ label: this.spec.name
2886
+ });
2887
+ text.cullable = true;
2888
+ this.target = text;
2889
+ this.intrinsicWidth = text.width;
2890
+ this.intrinsicHeight = text.height;
2891
+ if (this.duration === void 0) {
2892
+ this.duration = this.parent?.duration ?? this.root.duration;
2893
+ }
2894
+ if (this.spec.style) {
2895
+ const scope2 = this.scope();
2896
+ const resolved = normalizeProps(
2897
+ this.spec.style,
2898
+ scope2,
2899
+ { skipKeys: STYLE_OPAQUE_KEYS }
2900
+ );
2901
+ const unset = Object.keys(resolved).filter((k) => resolved[k] === void 0);
2902
+ if (unset.length) console.warn(`pixi-effects: ${describeLayer(this.spec)}: ${unset.map((k) => `style.${k}`).join(", ")} ${unset.length > 1 ? "are" : "is"} undefined (a variable that was never set?), so ${unset.length > 1 ? "they are" : "it is"} ignored and the default is used: omit the key instead`);
2903
+ const outline = resolved.stroke;
2904
+ if (outline && typeof outline === "object" && !("join" in outline) && !("lineJoin" in outline)) resolved.stroke = { ...outline, join: "round" };
2905
+ for (const k of Object.keys(resolved)) {
2906
+ if (unset.includes(k)) continue;
2907
+ text.style[k] = resolved[k];
3154
2908
  }
2909
+ this.intrinsicWidth = text.width;
2910
+ this.intrinsicHeight = text.height;
3155
2911
  }
3156
- this.absoluteStart = startTime;
3157
- const endTime = startTime + this.duration;
3158
- if (this.keepHidden) return;
3159
- this.target.renderable = startTime <= 0;
3160
- timeline.set(this.target, { renderable: true }, startTime);
3161
- timeline.set(this.target, { renderable: false }, endTime);
2912
+ validateGradientKeyframes(this.spec, describeLayer(this.spec), void 0, "text");
2913
+ const gradientSpec = this.spec.fillGradient ?? initialProps.fillGradient;
2914
+ if (gradientSpec) {
2915
+ this._grad = { grad: gradStateFrom(gradientSpec) };
2916
+ this._applyGradient();
2917
+ }
2918
+ const scope = this.scope();
2919
+ if (initialProps.value !== void 0) this._counters.value = resolveNumber(initialProps.value, scope);
2920
+ this._typed = initialProps.visibleChars !== void 0 || animates(this.spec.keyframes, "visibleChars");
2921
+ if (initialProps.visibleChars !== void 0) this._counters.visibleChars = resolveNumber(initialProps.visibleChars, scope);
2922
+ const typed = this._typed;
2923
+ this._typed = false;
2924
+ this._refreshText();
2925
+ if (this._lastText !== "") {
2926
+ this.intrinsicWidth = text.width;
2927
+ this.intrinsicHeight = text.height;
2928
+ }
2929
+ this._typed = typed;
2930
+ this._refreshText();
2931
+ this.buildFilters();
2932
+ }
2933
+ bindTimeline(timeline, offset = 0) {
2934
+ if (!this.target) return;
2935
+ const colorSpace = this.spec.colorSpace ?? "rgb";
2936
+ const stripped = stripKeys(this.spec, ["fill", "value", "text", "visibleChars", "fillGradient"]);
2937
+ runSuperWithStrippedSpec(this, stripped, timeline, offset);
2938
+ const keyframes = this.spec.keyframes ?? [];
2939
+ const origin = offset + this.at;
2940
+ bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
2941
+ if (this._grad) bindGradientKeyframes(timeline, this._grad, keyframes, this.duration, origin, colorSpace, () => this._applyGradient());
2942
+ const refresh = () => this._refreshText();
2943
+ bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
2944
+ bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
2945
+ bindTextKeyframes(timeline, keyframes, this.duration, origin, () => this._template, (t) => {
2946
+ this._template = t;
2947
+ refresh();
2948
+ }, describeLayer(this.spec));
3162
2949
  }
3163
2950
  };
3164
- function stripKeys(props, keys) {
3165
- if (!props) return props;
3166
- const drop = new Set(keys);
3167
- const out = {};
3168
- let changed = false;
3169
- for (const k of Object.keys(props)) {
3170
- if (drop.has(k)) {
3171
- changed = true;
3172
- continue;
3173
- }
3174
- out[k] = props[k];
2951
+ function runSuperWithStrippedSpec(inst, strippedSpec, timeline, offset) {
2952
+ const originalSpec = inst.spec;
2953
+ inst.spec = strippedSpec;
2954
+ try {
2955
+ Sequence.prototype.bindTimeline.call(inst, timeline, offset);
2956
+ } finally {
2957
+ inst.spec = originalSpec;
3175
2958
  }
3176
- return changed ? out : props;
3177
- }
3178
- function stripKeysInKeyframe(kf, keys) {
3179
- return {
3180
- ...kf,
3181
- set: stripKeys(kf.set, keys),
3182
- to: stripKeys(kf.to, keys),
3183
- from: stripKeys(kf.from, keys)
3184
- };
3185
2959
  }
3186
-
3187
- // src/sequences/gradient.ts
3188
- function cssColor(c) {
3189
- return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
3190
- }
3191
- function normalizedStops(g) {
3192
- const stops = g.stops ?? [];
3193
- if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
3194
- return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
3195
- }
3196
- function gradientOptions(g) {
3197
- const colorStops = normalizedStops(g);
3198
- const a = (g.angle ?? 90) * Math.PI / 180;
3199
- const dx = Math.cos(a), dy = Math.sin(a);
3200
- const h = (Math.abs(dx) + Math.abs(dy)) / 2;
2960
+ function stripKeys(spec, keys) {
3201
2961
  return {
3202
- type: "linear",
3203
- start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
3204
- end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
3205
- colorStops,
3206
- textureSpace: "local"
2962
+ ...spec,
2963
+ initial: stripProps(spec.initial, keys),
2964
+ keyframes: spec.keyframes ? spec.keyframes.map((kf) => ({
2965
+ ...kf,
2966
+ set: stripProps(kf.set, keys),
2967
+ to: stripProps(kf.to, keys),
2968
+ from: stripProps(kf.from, keys)
2969
+ })) : void 0
3207
2970
  };
3208
2971
  }
3209
- function paintRadial(ctx, size, g) {
3210
- const stops = normalizedStops(g);
3211
- const [cx, cy] = g.center ?? [0.5, 0.5];
3212
- const grad = ctx.createRadialGradient(cx * size, cy * size, (g.innerRadius ?? 0) * size, cx * size, cy * size, (g.radius ?? 0.5) * size);
3213
- for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
3214
- ctx.fillStyle = grad;
3215
- ctx.fillRect(0, 0, size, size);
2972
+ function stripProps(props, keys) {
2973
+ if (!props) return props;
2974
+ if (!keys.some((k) => k in props)) return props;
2975
+ const out = {};
2976
+ for (const k of Object.keys(props)) if (!keys.includes(k)) out[k] = props[k];
2977
+ return out;
3216
2978
  }
3217
-
3218
- // src/sequences/gradientAnim.ts
3219
- var PATCH_KEYS = ["type", "angle", "center", "innerRadius", "radius", "stops"];
3220
- function gradStateFrom(g) {
3221
- return {
3222
- type: g.type ?? "linear",
3223
- angle: g.angle ?? 90,
3224
- center: [...g.center ?? [0.5, 0.5]],
3225
- innerRadius: g.innerRadius ?? 0,
3226
- radius: g.radius ?? 0.5,
3227
- stops: (g.stops ?? []).map((s) => Array.isArray(s) ? { offset: s[0], color: cssColor(s[1]) } : { offset: s.offset, color: cssColor(s.color) })
3228
- };
2979
+ function resolveNumber(v, scope) {
2980
+ if (typeof v === "number") return v;
2981
+ const out = normalizeProps({ v }, scope).v;
2982
+ return typeof out === "number" ? out : Number(out) || 0;
3229
2983
  }
3230
- function mergeGrad(base, patch) {
3231
- const out = { ...base, center: [...base.center], stops: base.stops.map((s) => ({ ...s })) };
3232
- if (patch === null || typeof patch !== "object" || Array.isArray(patch)) return out;
3233
- const p = patch;
3234
- if (typeof p.angle === "number") out.angle = p.angle;
3235
- if (Array.isArray(p.center)) out.center = [Number(p.center[0]), Number(p.center[1])];
3236
- if (typeof p.innerRadius === "number") out.innerRadius = p.innerRadius;
3237
- if (typeof p.radius === "number") out.radius = p.radius;
3238
- if (p.stops !== void 0) {
3239
- const next = gradStateFrom({ stops: p.stops }).stops;
3240
- if (next.length === base.stops.length) out.stops = next;
2984
+ function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDuration, origin, scope) {
2985
+ for (const kf of keyframes) {
2986
+ const setV = kf.set?.[key];
2987
+ const fromV = kf.from?.[key];
2988
+ const toV = kf.to?.[key];
2989
+ if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
2990
+ const at = origin + resolveAt(kf.at, parentDuration);
2991
+ const duration = kfDuration(kf);
2992
+ const ease = kf.ease ?? "none";
2993
+ const loop = loopVars(kf);
2994
+ if (setV !== void 0) {
2995
+ const v = resolveNumber(setV, scope);
2996
+ revertibleSet(timeline, at, () => state[key], (n) => {
2997
+ state[key] = n;
2998
+ refresh();
2999
+ }, v, [state, key]);
3000
+ }
3001
+ if (toV !== void 0 && fromV !== void 0) {
3002
+ timeline.fromTo(
3003
+ state,
3004
+ { [key]: resolveNumber(fromV, scope) },
3005
+ { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
3006
+ at
3007
+ );
3008
+ } else if (toV !== void 0) {
3009
+ timeline.to(state, { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
3010
+ } else if (fromV !== void 0) {
3011
+ timeline.from(state, { [key]: resolveNumber(fromV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
3012
+ }
3241
3013
  }
3242
- return out;
3243
3014
  }
3244
- var objectHint = "a keyframe's fillGradient must be an object like { angle: 200, stops: [[0, '#f00'], [1, '#00f']] }";
3245
- function validateGradientKeyframes(spec, who, warn = (m) => console.warn(m), kind = "shape") {
3246
- const said = /* @__PURE__ */ new Set();
3247
- const say = (m) => {
3248
- if (!said.has(m)) {
3249
- said.add(m);
3250
- warn(`pixi-effects: ${who}: ${m}`);
3015
+ function bindTextKeyframes(timeline, keyframes, parentDuration, origin, read2, write2, who) {
3016
+ for (const kf of keyframes) {
3017
+ for (const [what, props] of [["to", kf.to], ["from", kf.from]]) {
3018
+ if (props && props.text !== void 0) {
3019
+ console.warn(`pixi-effects: ${who}: a keyframe's ${what}: { text } cannot tween a string: use set: { text: '\u2026' } at the time it should change`);
3020
+ }
3251
3021
  }
3252
- };
3253
- const startSpec = spec.fillGradient ?? spec.initial?.fillGradient;
3254
- const start = startSpec && typeof startSpec === "object" && Array.isArray(startSpec.stops) ? gradStateFrom(startSpec) : null;
3255
- let animated = false;
3256
- if (kind === "text") {
3257
- const radial = (g) => !!g && typeof g === "object" && ["type", "center", "innerRadius", "radius"].some((k) => g[k] !== void 0 && !(k === "type" && g.type === "linear"));
3258
- const asked = radial(startSpec) || (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => radial(b?.fillGradient)));
3259
- 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");
3022
+ const t = kf.set?.text;
3023
+ if (t === void 0) continue;
3024
+ revertibleSet(timeline, origin + resolveAt(kf.at, parentDuration), read2, write2, String(t));
3260
3025
  }
3261
- for (const kf of spec.keyframes ?? []) {
3262
- for (const bag of [kf.set, kf.to, kf.from]) {
3263
- if (!bag) continue;
3264
- for (const [key, value] of Object.entries(bag)) {
3265
- if (key === "fillGradient") {
3266
- animated = true;
3267
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
3268
- say(objectHint);
3269
- continue;
3270
- }
3271
- const p = value;
3272
- for (const k of Object.keys(p)) {
3273
- if (PATCH_KEYS.includes(k)) continue;
3274
- const guess = suggestName(k, PATCH_KEYS.filter((x) => x !== "type"));
3275
- say(`fillGradient.${k} is not a gradient property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${PATCH_KEYS.join(", ")}); it is ignored`);
3276
- }
3277
- if (p.type !== void 0 && start && p.type !== start.type) say("fillGradient.type cannot change over time (linear <-> radial); it is ignored");
3278
- if (p.stops !== void 0 && start) {
3279
- const n = Array.isArray(p.stops) ? p.stops.length : 0;
3280
- 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`);
3281
- }
3282
- } else if (/^fillGradient\./i.test(key) || /^gradient/i.test(key)) {
3283
- const tail = key.replace(/^fillGradient\./i, "").replace(/^gradient/i, "");
3284
- const sub2 = tail ? tail[0].toLowerCase() + tail.slice(1) : "angle";
3285
- say(`"${key}" is not an animatable property: write fillGradient: { ${PATCH_KEYS.includes(sub2) ? sub2 : "angle"} } (the whole gradient is one object) in the keyframe's to / from / set`);
3286
- }
3287
- }
3026
+ }
3027
+ function animates(keyframes, key) {
3028
+ return (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((p) => p && p[key] !== void 0));
3029
+ }
3030
+ function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, colorSpace) {
3031
+ const state = { fill: text.style.fill };
3032
+ const writeFill = () => {
3033
+ text.style.fill = state.fill;
3034
+ };
3035
+ for (const kf of keyframes) {
3036
+ const at = offset + resolveAt(kf.at, parentDuration);
3037
+ const duration = kfDuration(kf);
3038
+ const ease = kf.ease ?? "none";
3039
+ const setFill = pickFill(kf.set);
3040
+ const fromFill = pickFill(kf.from);
3041
+ const toFill = pickFill(kf.to);
3042
+ if (setFill !== void 0) {
3043
+ revertibleSet(timeline, at, () => state.fill, (c) => {
3044
+ state.fill = c;
3045
+ writeFill();
3046
+ }, setFill, [state, "fill"]);
3047
+ }
3048
+ if (toFill !== void 0) {
3049
+ tweenColor(
3050
+ timeline,
3051
+ state,
3052
+ "fill",
3053
+ fromFill,
3054
+ toFill,
3055
+ duration,
3056
+ ease,
3057
+ at,
3058
+ colorSpace,
3059
+ writeFill,
3060
+ loopVars(kf)
3061
+ );
3288
3062
  }
3289
3063
  }
3290
- 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)");
3291
3064
  }
3292
- function tweenGradient(timeline, holder, fromPatch, toPatch, duration, ease, at, colorSpace, onChange, loop = {}) {
3293
- timeline.tweenValue({
3294
- holder,
3295
- id: "grad",
3296
- get: () => holder.grad,
3297
- set: (g) => {
3298
- holder.grad = g;
3299
- onChange();
3300
- },
3301
- from: fromPatch !== void 0 ? (prev) => mergeGrad(prev, fromPatch) : void 0,
3302
- to: (start, prev) => toPatch !== void 0 ? mergeGrad(start, toPatch) : prev,
3303
- make: (a, b) => {
3304
- const colors = a.stops.map((s, i) => colorSpace === "rgb" ? interpolateColors(s.color, b.stops[i].color) : buildColorInterp(s.color, b.stops[i].color, colorSpace));
3305
- return (t) => {
3306
- const l = (x, y) => x + (y - x) * t;
3307
- return {
3308
- type: a.type,
3309
- angle: l(a.angle, b.angle),
3310
- center: [l(a.center[0], b.center[0]), l(a.center[1], b.center[1])],
3311
- innerRadius: l(a.innerRadius, b.innerRadius),
3312
- radius: l(a.radius, b.radius),
3313
- stops: a.stops.map((s, i) => ({ offset: l(s.offset, b.stops[i].offset), color: colors[i](t) }))
3314
- };
3315
- };
3316
- },
3317
- duration,
3318
- ease,
3319
- at,
3320
- repeat: loop.repeat,
3321
- yoyo: loop.yoyo,
3322
- repeatDelay: loop.repeatDelay
3323
- });
3065
+ function pickFill(props) {
3066
+ if (!props) return void 0;
3067
+ const v = props.fill;
3068
+ if (typeof v === "string" || typeof v === "number") return v;
3069
+ return void 0;
3324
3070
  }
3325
- var PAINT_SIZE = 512;
3326
- var GradientPainter = class {
3327
- constructor(size = PAINT_SIZE) {
3328
- this.size = size;
3329
- this.canvas.width = size;
3330
- this.canvas.height = size;
3331
- this.tex = Texture.from(this.canvas);
3332
- globalThis.__gradientPainters = (globalThis.__gradientPainters ?? 0) + 1;
3333
- }
3334
- size;
3335
- canvas = document.createElement("canvas");
3336
- tex;
3337
- /** Paint `g` and return what `Graphics.fill(...)` takes. */
3338
- paint(g) {
3339
- const ctx = this.canvas.getContext("2d"), s = this.size;
3340
- if (!ctx) return new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((x) => [x.offset, x.color]) }));
3341
- ctx.clearRect(0, 0, s, s);
3342
- if (g.type === "radial") {
3343
- paintRadial(ctx, s, { stops: g.stops.map((x) => [x.offset, x.color]), center: g.center, innerRadius: g.innerRadius, radius: g.radius });
3344
- } else {
3345
- const a = g.angle * Math.PI / 180, dx = Math.cos(a), dy = Math.sin(a);
3346
- const h = (Math.abs(dx) + Math.abs(dy)) / 2;
3347
- const grad = ctx.createLinearGradient((0.5 - dx * h) * s, (0.5 - dy * h) * s, (0.5 + dx * h) * s, (0.5 + dy * h) * s);
3348
- for (const st of g.stops) grad.addColorStop(Math.min(1, Math.max(0, st.offset)), st.color);
3349
- ctx.fillStyle = grad;
3350
- ctx.fillRect(0, 0, s, s);
3071
+
3072
+ // src/core/remap.ts
3073
+ var BAGS = ["set", "to", "from"];
3074
+ function timeRemapOf(spec, prop = "time") {
3075
+ const hasInitial = !!spec.initial && "time" in spec.initial;
3076
+ const hasKf = (spec.keyframes ?? []).some((kf) => BAGS.some((b) => kf[b] && "time" in kf[b]));
3077
+ const speed = typeof spec.speed === "number" && Number.isFinite(spec.speed) && spec.speed !== 0 ? spec.speed : void 0;
3078
+ if (!hasInitial && !hasKf && speed === void 0) return null;
3079
+ const timeKfs = [];
3080
+ const restKfs = [];
3081
+ for (const kf of spec.keyframes ?? []) {
3082
+ const t = { ...kf }, r = { ...kf };
3083
+ let touched = false, left = false;
3084
+ for (const b of BAGS) {
3085
+ const bag = kf[b];
3086
+ if (!bag) {
3087
+ delete t[b];
3088
+ delete r[b];
3089
+ continue;
3090
+ }
3091
+ const { time, ...others } = bag;
3092
+ if ("time" in bag) {
3093
+ touched = true;
3094
+ t[b] = { [prop]: time };
3095
+ } else delete t[b];
3096
+ if (Object.keys(others).length) {
3097
+ r[b] = others;
3098
+ left = true;
3099
+ } else delete r[b];
3351
3100
  }
3352
- this.tex.source.update();
3353
- return { texture: this.tex, textureSpace: "local" };
3101
+ if (touched) timeKfs.push(t);
3102
+ if (!touched || left) restKfs.push(r);
3354
3103
  }
3355
- /** Give the texture and the canvas back (a layer that is destroyed must not leave them). */
3356
- destroy() {
3357
- this.tex.destroy(true);
3358
- this.canvas.width = this.canvas.height = 0;
3359
- globalThis.__gradientPainters = Math.max(0, (globalThis.__gradientPainters ?? 1) - 1);
3104
+ let restInitial = spec.initial;
3105
+ let start;
3106
+ if (hasInitial) {
3107
+ const { time, ...others } = spec.initial;
3108
+ start = typeof time === "number" && Number.isFinite(time) ? time : void 0;
3109
+ restInitial = others;
3360
3110
  }
3361
- };
3362
- function bindGradientKeyframes(timeline, holder, keyframes, duration, offset, colorSpace, onChange) {
3363
- const bag = (b) => b?.fillGradient;
3364
- for (const kf of keyframes) {
3365
- const at = offset + resolveAt(kf.at, duration);
3366
- const setV = bag(kf.set), fromV = bag(kf.from), toV = bag(kf.to);
3367
- if (setV !== void 0) {
3368
- timeline.tweenValue({
3369
- holder,
3370
- id: "grad",
3371
- get: () => holder.grad,
3372
- set: (g) => {
3373
- holder.grad = g;
3374
- onChange();
3375
- },
3376
- to: (start) => mergeGrad(start, setV),
3377
- make: (_a, b) => () => b,
3378
- duration: 0,
3379
- ease: "none",
3380
- at
3381
- });
3111
+ return { timeKfs, restKfs: restKfs.length ? restKfs : void 0, restInitial, start, speed };
3112
+ }
3113
+ function resolveFromEnd(remap, length, prop) {
3114
+ const fix = (v) => typeof v === "number" && v < 0 ? Math.max(0, length + v) : v;
3115
+ const timeKfs = remap.timeKfs.map((kf) => {
3116
+ const out = { ...kf };
3117
+ for (const b of BAGS) {
3118
+ const bag = kf[b];
3119
+ if (bag && prop in bag) out[b] = { ...bag, [prop]: fix(bag[prop]) };
3382
3120
  }
3383
- if (fromV !== void 0 || toV !== void 0) {
3384
- tweenGradient(timeline, holder, fromV, toV, kfDuration(kf), kf.ease ?? "none", at, colorSpace, onChange, loopVars(kf));
3121
+ return out;
3122
+ });
3123
+ return { ...remap, timeKfs, start: remap.start === void 0 ? void 0 : fix(remap.start) };
3124
+ }
3125
+ function remapOf(spec, prop, length) {
3126
+ const r = timeRemapOf(spec, prop);
3127
+ return r && length !== null ? resolveFromEnd(r, length, prop) : r;
3128
+ }
3129
+ function contentLength(remap, duration) {
3130
+ let m = duration;
3131
+ if (remap.speed !== void 0) m = Math.max(m, Math.abs(remap.speed) * duration);
3132
+ if (remap.start !== void 0) m = Math.max(m, remap.start + (remap.speed ?? 1) * duration);
3133
+ for (const kf of remap.timeKfs) {
3134
+ for (const b of BAGS) {
3135
+ const bag = kf[b];
3136
+ const v = bag && Object.values(bag)[0];
3137
+ if (typeof v === "number") m = Math.max(m, v);
3385
3138
  }
3386
3139
  }
3140
+ return m;
3387
3141
  }
3388
- var hasGradientKeys = (keyframes) => (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => b && b.fillGradient !== void 0));
3389
-
3390
- // src/sequences/Text.ts
3391
- var STYLE_OPAQUE_KEYS = [
3392
- "fontFamily",
3393
- "fill",
3394
- "align",
3395
- "fontStyle",
3396
- "fontWeight",
3397
- "fontVariant",
3398
- "textBaseline",
3399
- "whiteSpace",
3400
- "lineJoin",
3401
- "join",
3402
- "cap",
3403
- "color",
3404
- "stroke"
3405
- ];
3406
- function formatValue(v, f) {
3407
- const fixed = v.toFixed(Math.max(0, Math.floor(f?.decimals ?? 0)));
3408
- const pad = Math.max(0, Math.floor(f?.pad ?? 0));
3409
- if (!f?.grouping && !pad) return fixed;
3410
- const [signed, frac] = fixed.split(".");
3411
- const sign = signed.startsWith("-") ? "-" : "";
3412
- let int = sign ? signed.slice(1) : signed;
3413
- if (pad) int = int.padStart(pad, "0");
3414
- if (f?.grouping) int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
3415
- return sign + int + (frac !== void 0 ? "." + frac : "");
3142
+ function bindClock(timeline, clock, prop, remap, duration, at, scope) {
3143
+ if (remap.timeKfs.length) {
3144
+ if (remap.start !== void 0) timeline.set(clock, { [prop]: remap.start }, at);
3145
+ applyKeyframes(timeline, clock, remap.timeKfs, duration, scope, [], at);
3146
+ return;
3147
+ }
3148
+ const s = remap.speed ?? 1;
3149
+ const from = remap.start ?? (s < 0 ? -s * duration : 0);
3150
+ timeline.fromTo(clock, { [prop]: from }, { [prop]: from + s * duration, duration, ease: "none" }, at);
3416
3151
  }
3417
- var TextSequence = class extends Sequence {
3418
- /** `value`: the number for the `{value}` placeholder; `visibleChars`: how many characters show (typewriter). Both tweened by keyframes. */
3419
- _counters = { value: 0, visibleChars: 0 };
3420
- /** The string now on the layer (before `{value}` / `visibleChars`): `text`, or the last `set: { text }`. */
3421
- _template = "";
3422
- /** `visibleChars` is in play (initial or animated): otherwise the whole text shows. */
3423
- _typed = false;
3424
- _lastText = "";
3425
- /** A gradient fill for the text (`fillGradient`, static or animated): rebuilt as a FillGradient on each change (the text is re-rasterised). */
3426
- _grad = null;
3427
- _fillGradient = null;
3428
- _applyGradient() {
3429
- const g = this._grad.grad;
3430
- const next = new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((s) => [s.offset, s.color]) }));
3431
- this.target.style.fill = next;
3432
- this._fillGradient?.destroy();
3433
- this._fillGradient = next;
3152
+ function clockTable(remap, prop, duration, at, scope, to) {
3153
+ const probe = createTimeline({ paused: true });
3154
+ const clock = { [prop]: remap.start ?? 0 };
3155
+ bindClock(probe, clock, prop, remap, duration, at, scope);
3156
+ probe.time(probe.duration());
3157
+ probe.time(0);
3158
+ const h = 1 / 1e3;
3159
+ const n = Math.max(2, Math.ceil(to / h) + 2);
3160
+ const tab = new Float64Array(n);
3161
+ let min = Infinity, max = -Infinity;
3162
+ for (let k = 0; k < n; k++) {
3163
+ probe.time(k * h);
3164
+ const v = clock[prop];
3165
+ tab[k] = v;
3166
+ const t = k * h;
3167
+ if (t >= at && t <= at + duration) {
3168
+ if (v < min) min = v;
3169
+ if (v > max) max = v;
3170
+ }
3434
3171
  }
3435
- destroy() {
3436
- super.destroy();
3437
- this._fillGradient?.destroy();
3438
- this._fillGradient = null;
3172
+ probe.kill();
3173
+ return {
3174
+ at(t) {
3175
+ const x = Math.min(Math.max(t / h, 0), n - 1.000001);
3176
+ const k = Math.floor(x), f = x - k;
3177
+ return tab[k] * (1 - f) + tab[k + 1] * f;
3178
+ },
3179
+ min: Number.isFinite(min) ? min : 0,
3180
+ max: Number.isFinite(max) ? max : 0
3181
+ };
3182
+ }
3183
+
3184
+ // src/space/math.ts
3185
+ var DEG3 = Math.PI / 180;
3186
+ var DEFAULT_FOV = 40;
3187
+ var MIN_FOV = 1;
3188
+ var MAX_FOV = 179;
3189
+ var NEAR = 1;
3190
+ function clampFov(fov) {
3191
+ if (!Number.isFinite(fov)) return DEFAULT_FOV;
3192
+ return Math.min(MAX_FOV, Math.max(MIN_FOV, fov));
3193
+ }
3194
+ function homeDistance(compHeight, fovDeg) {
3195
+ return compHeight / 2 / Math.tan(clampFov(fovDeg) * DEG3 / 2);
3196
+ }
3197
+ function homeCamera(compW, compH, fov = DEFAULT_FOV) {
3198
+ return {
3199
+ x: compW / 2,
3200
+ y: compH / 2,
3201
+ z: homeDistance(compH, fov),
3202
+ lookAtX: compW / 2,
3203
+ lookAtY: compH / 2,
3204
+ lookAtZ: 0,
3205
+ fov
3206
+ };
3207
+ }
3208
+ function cameraBasis(cam, compW, compH) {
3209
+ let fx = cam.lookAtX - cam.x;
3210
+ let fy = cam.lookAtY - cam.y;
3211
+ let fz = cam.lookAtZ - cam.z;
3212
+ let len = Math.hypot(fx, fy, fz);
3213
+ if (len < 1e-9) {
3214
+ fx = 0;
3215
+ fy = 0;
3216
+ fz = -1;
3217
+ len = 1;
3439
3218
  }
3440
- /** A typewriter that has typed nothing yet: the layer is empty on purpose (inspect must not call that "no size"). */
3441
- get showsNothingYet() {
3442
- return this._typed && this._lastText === "" && this._template !== "";
3219
+ fx /= len;
3220
+ fy /= len;
3221
+ fz /= len;
3222
+ let rx = -fz;
3223
+ const ry = 0;
3224
+ let rz = fx;
3225
+ const rlen = Math.hypot(rx, rz);
3226
+ if (rlen < 1e-9) {
3227
+ rx = 1;
3228
+ rz = 0;
3229
+ } else {
3230
+ rx /= rlen;
3231
+ rz /= rlen;
3443
3232
  }
3444
- /** Rewrite the displayed string from the template, the current `value` and `visibleChars`. */
3445
- _refreshText() {
3446
- let next = this._template.includes("{value}") ? this._template.split("{value}").join(formatValue(this._counters.value, this.spec.format)) : this._template;
3447
- if (this._typed) next = Array.from(next).slice(0, Math.max(0, Math.floor(this._counters.visibleChars))).join("");
3448
- if (next === this._lastText) return;
3449
- this._lastText = next;
3450
- this.target.text = next;
3233
+ const dx = ry * fz - rz * fy;
3234
+ const dy = rz * fx - rx * fz;
3235
+ const dz = rx * fy - ry * fx;
3236
+ return {
3237
+ cx: cam.x,
3238
+ cy: cam.y,
3239
+ cz: cam.z,
3240
+ rx,
3241
+ ry,
3242
+ rz,
3243
+ dx,
3244
+ dy,
3245
+ dz,
3246
+ fx,
3247
+ fy,
3248
+ fz,
3249
+ focal: compH / 2 / Math.tan(clampFov(cam.fov) * DEG3 / 2),
3250
+ halfW: compW / 2,
3251
+ halfH: compH / 2
3252
+ };
3253
+ }
3254
+ function layerToWorld(t, px, py) {
3255
+ let x = (px - t.pivotX) * t.scaleX;
3256
+ let y = (py - t.pivotY) * t.scaleY;
3257
+ let z = 0;
3258
+ let c = Math.cos(t.rotationX);
3259
+ let s = Math.sin(t.rotationX);
3260
+ [y, z] = [y * c - z * s, y * s + z * c];
3261
+ c = Math.cos(t.rotationY);
3262
+ s = Math.sin(t.rotationY);
3263
+ [x, z] = [x * c + z * s, -x * s + z * c];
3264
+ c = Math.cos(t.rotationZ);
3265
+ s = Math.sin(t.rotationZ);
3266
+ [x, y] = [x * c - y * s, x * s + y * c];
3267
+ return { x: x + t.x, y: y + t.y, z: z + t.z };
3268
+ }
3269
+ function projectPoint(b, p) {
3270
+ const vx = p.x - b.cx;
3271
+ const vy = p.y - b.cy;
3272
+ const vz = p.z - b.cz;
3273
+ const xc = vx * b.rx + vy * b.ry + vz * b.rz;
3274
+ const yc = vx * b.dx + vy * b.dy + vz * b.dz;
3275
+ const zc = vx * b.fx + vy * b.fy + vz * b.fz;
3276
+ return { x: b.halfW + b.focal * xc / zc, y: b.halfH + b.focal * yc / zc, depth: zc };
3277
+ }
3278
+ function projectLayer(t, frame, b) {
3279
+ const pts = [
3280
+ [frame.x, frame.y],
3281
+ [frame.x + frame.width, frame.y],
3282
+ [frame.x + frame.width, frame.y + frame.height],
3283
+ [frame.x, frame.y + frame.height]
3284
+ ];
3285
+ let visible = true;
3286
+ const corners = [];
3287
+ for (const [px, py] of pts) {
3288
+ const p = projectPoint(b, layerToWorld(t, px, py));
3289
+ if (!(p.depth > NEAR)) visible = false;
3290
+ corners.push(p.x, p.y);
3451
3291
  }
3292
+ const origin = projectPoint(b, { x: t.x, y: t.y, z: t.z });
3293
+ return { corners, depth: origin.depth, visible };
3294
+ }
3295
+
3296
+ // src/space/focus.ts
3297
+ var DEFAULT_APERTURE = 30;
3298
+ var MIN_BLUR = 0.05;
3299
+ var MAX_BLUR = 32;
3300
+ var MANY_BLURRED = 20;
3301
+ function blurRadius(aperture, focal, depth, focusDepth) {
3302
+ if (!(aperture > 0) || !(focal > 0) || !(depth > 0) || !(focusDepth > 0)) return 0;
3303
+ const r = aperture * focal * Math.abs(1 / depth - 1 / focusDepth) / 2;
3304
+ return Math.min(r, MAX_BLUR);
3305
+ }
3306
+ var SCOPE_NAMES = /* @__PURE__ */ new Set(["w", "h", "W", "H", "GW", "GH", "contain", "cover", "t", "d", "T"]);
3307
+ function looksLikeLayerName(s) {
3308
+ return /^[A-Za-z_][\w-]*$/.test(s) && !SCOPE_NAMES.has(s);
3309
+ }
3310
+ var isName = (v, known) => typeof v === "string" && (known.has(v) || looksLikeLayerName(v));
3311
+ function focusNames(props, known) {
3312
+ const out = [];
3313
+ const read2 = (bag) => {
3314
+ const v = bag?.focus;
3315
+ if (isName(v, known)) out.push(v);
3316
+ };
3317
+ read2(props.initial);
3318
+ for (const kf of props.keyframes ?? []) for (const bag of [kf.set, kf.to, kf.from]) read2(bag);
3319
+ return out;
3320
+ }
3321
+ function withFocusResolved(props, zOf, known = /* @__PURE__ */ new Set()) {
3322
+ if (focusNames(props, known).length === 0) return props;
3323
+ const fix = (bag) => {
3324
+ const v = bag?.focus;
3325
+ if (!bag || !isName(v, known)) return bag;
3326
+ return { ...bag, focus: zOf(v) ?? 0 };
3327
+ };
3328
+ return {
3329
+ ...props,
3330
+ initial: fix(props.initial),
3331
+ keyframes: props.keyframes?.map((kf) => ({ ...kf, set: fix(kf.set), to: fix(kf.to), from: fix(kf.from) }))
3332
+ };
3333
+ }
3334
+ function layerInitialZ(spec, scope) {
3335
+ const z = spec.initial?.z;
3336
+ if (typeof z === "number") return z;
3337
+ if (typeof z === "string") return evaluateExpr(z, scope);
3338
+ return 0;
3339
+ }
3340
+ var movesZ = (spec) => (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((bag) => bag && "z" in bag));
3341
+ function focusProblems(camera, siblings) {
3342
+ const out = [];
3343
+ const said = /* @__PURE__ */ new Set();
3344
+ const threeD = siblings.filter((s) => s.threeD && s.name).map((s) => s.name);
3345
+ const known = new Set(siblings.map((s) => s.name).filter((n) => !!n));
3346
+ for (const name of focusNames(camera, known)) {
3347
+ if (said.has(name)) continue;
3348
+ said.add(name);
3349
+ const same = siblings.filter((s) => s.name === name);
3350
+ const hit = same.find((s) => s.threeD) ?? same[0];
3351
+ if (!hit) out.push({ kind: "missing", name, hint: suggestName(name, threeD) });
3352
+ else if (!hit.threeD) out.push({ kind: "not-threeD", name });
3353
+ else {
3354
+ if (same.length > 1) out.push({ kind: "duplicate", name, count: same.length });
3355
+ if (movesZ(hit)) out.push({ kind: "moving", name });
3356
+ }
3357
+ }
3358
+ return out;
3359
+ }
3360
+ var CameraSequence = class extends Sequence {
3361
+ /** True when `z` is never specified anywhere: z then follows `fov`. */
3362
+ autoZ = true;
3363
+ compH = 0;
3364
+ warnedFov = false;
3365
+ warnedLookAt = false;
3366
+ warnedAperture = false;
3367
+ /** True when the camera writes `focus` or `aperture` anywhere: depth of field is on. */
3368
+ dof = false;
3369
+ /** True when the camera writes any fog key anywhere: depth fog is on (nothing is made for a camera without fog). */
3370
+ fog = false;
3371
+ fogC = null;
3372
+ zOfName = null;
3373
+ layerNames = /* @__PURE__ */ new Set();
3452
3374
  async build() {
3453
- const baseStyle = {
3454
- fontFamily: "Arial",
3455
- fontSize: 36,
3456
- fill: "#ffffff",
3457
- align: "center"
3458
- };
3459
- const initialProps = this.spec.initial ?? {};
3460
- this._template = String(initialProps.text ?? this.spec.text ?? "");
3461
- const text = new Text({
3462
- text: this._template,
3463
- style: baseStyle,
3464
- label: this.spec.name
3465
- });
3466
- text.cullable = true;
3467
- this.target = text;
3468
- this.intrinsicWidth = text.width;
3469
- this.intrinsicHeight = text.height;
3375
+ const compW = this.parent?.width ?? this.root.width;
3376
+ this.compH = this.parent?.height ?? this.root.height;
3470
3377
  if (this.duration === void 0) {
3471
3378
  this.duration = this.parent?.duration ?? this.root.duration;
3472
3379
  }
3473
- if (this.spec.style) {
3474
- const scope2 = this.scope();
3475
- const resolved = normalizeProps(
3476
- this.spec.style,
3477
- scope2,
3478
- { skipKeys: STYLE_OPAQUE_KEYS }
3479
- );
3480
- const unset = Object.keys(resolved).filter((k) => resolved[k] === void 0);
3481
- if (unset.length) console.warn(`pixi-effects: ${describeLayer(this.spec)}: ${unset.map((k) => `style.${k}`).join(", ")} ${unset.length > 1 ? "are" : "is"} undefined (a variable that was never set?), so ${unset.length > 1 ? "they are" : "it is"} ignored and the default is used: omit the key instead`);
3482
- for (const k of Object.keys(resolved)) {
3483
- if (unset.includes(k)) continue;
3484
- text.style[k] = resolved[k];
3485
- }
3486
- this.intrinsicWidth = text.width;
3487
- this.intrinsicHeight = text.height;
3380
+ const home = homeCamera(compW, this.compH);
3381
+ const carrier = new Container();
3382
+ carrier.x = home.x;
3383
+ carrier.y = home.y;
3384
+ carrier.z = home.z;
3385
+ carrier.lookAtX = home.lookAtX;
3386
+ carrier.lookAtY = home.lookAtY;
3387
+ carrier.lookAtZ = home.lookAtZ;
3388
+ carrier.fov = home.fov;
3389
+ carrier.focus = 0;
3390
+ carrier.aperture = DEFAULT_APERTURE;
3391
+ carrier.offsetX = carrier.offsetY = carrier.offsetZ = 0;
3392
+ carrier.lookOffsetX = carrier.lookOffsetY = carrier.lookOffsetZ = 0;
3393
+ carrier.fogNear = home.z;
3394
+ carrier.fogFar = home.z * 3;
3395
+ carrier.fogColor = "#000000";
3396
+ carrier.fogAmount = 1;
3397
+ this.target = carrier;
3398
+ const keys = collectPropKeys(this.spec);
3399
+ this.autoZ = !keys.has("z");
3400
+ this.dof = keys.has("focus") || keys.has("aperture");
3401
+ this.fog = keys.has("fogNear") || keys.has("fogFar") || keys.has("fogColor") || keys.has("fogAmount");
3402
+ }
3403
+ /** The aperture to use now: off unless depth of field is on and the value is a number of 0 or more (said once if it is not). */
3404
+ apertureNow(a) {
3405
+ if (!this.dof) return 0;
3406
+ if (!(a >= 0) && !this.warnedAperture) {
3407
+ this.warnedAperture = true;
3408
+ const who = this.spec.name ? ` "${this.spec.name}"` : "";
3409
+ console.warn(`pixi-effects: camera${who}: aperture ${a} is not a number of 0 or more; depth of field is off (it is the lens diameter in px: 30 is the default, 0 turns it off)`);
3488
3410
  }
3489
- validateGradientKeyframes(this.spec, describeLayer(this.spec), void 0, "text");
3490
- const gradientSpec = this.spec.fillGradient ?? initialProps.fillGradient;
3491
- if (gradientSpec) {
3492
- this._grad = { grad: gradStateFrom(gradientSpec) };
3493
- this._applyGradient();
3411
+ return a > 0 ? a : 0;
3412
+ }
3413
+ /** Called by the composition once its children exist: how a layer name in `focus` becomes the z of that layer. */
3414
+ resolveFocusNames(zOf, names = /* @__PURE__ */ new Set()) {
3415
+ this.zOfName = zOf;
3416
+ this.layerNames = names;
3417
+ }
3418
+ displayProps() {
3419
+ const base = super.displayProps();
3420
+ return this.zOfName ? withFocusResolved(base, this.zOfName, this.layerNames) : base;
3421
+ }
3422
+ /** The camera's state at the current timeline position. */
3423
+ state() {
3424
+ const c = this.target;
3425
+ if (!this.warnedFov && !(c.fov >= MIN_FOV && c.fov <= MAX_FOV)) {
3426
+ this.warnedFov = true;
3427
+ const who = this.spec.name ? ` "${this.spec.name}"` : "";
3428
+ console.warn(`pixi-effects: camera${who}: fov ${c.fov} is outside [${MIN_FOV}, ${MAX_FOV}]; clamped`);
3494
3429
  }
3495
- const scope = this.scope();
3496
- if (initialProps.value !== void 0) this._counters.value = resolveNumber(initialProps.value, scope);
3497
- this._typed = initialProps.visibleChars !== void 0 || animates(this.spec.keyframes, "visibleChars");
3498
- if (initialProps.visibleChars !== void 0) this._counters.visibleChars = resolveNumber(initialProps.visibleChars, scope);
3499
- const typed = this._typed;
3500
- this._typed = false;
3501
- this._refreshText();
3502
- if (this._lastText !== "") {
3503
- this.intrinsicWidth = text.width;
3504
- this.intrinsicHeight = text.height;
3430
+ const fov = clampFov(c.fov);
3431
+ const z = this.autoZ ? homeDistance(this.compH, fov) : c.z;
3432
+ if (!this.warnedLookAt && c.lookAtX === c.x && c.lookAtY === c.y && c.lookAtZ === z) {
3433
+ this.warnedLookAt = true;
3434
+ const who = this.spec.name ? ` "${this.spec.name}"` : "";
3435
+ console.warn(`pixi-effects: camera${who}: lookAt equals the camera position; looking along -z instead`);
3436
+ }
3437
+ return {
3438
+ x: c.x + c.offsetX,
3439
+ y: c.y + c.offsetY,
3440
+ z: z + c.offsetZ,
3441
+ lookAtX: c.lookAtX + c.lookOffsetX,
3442
+ lookAtY: c.lookAtY + c.lookOffsetY,
3443
+ lookAtZ: c.lookAtZ + c.lookOffsetZ,
3444
+ fov,
3445
+ focus: Number.isFinite(c.focus) ? c.focus : 0,
3446
+ aperture: this.apertureNow(c.aperture)
3447
+ };
3448
+ }
3449
+ /** The fog now (colour as 0..1 channels, distances, 0..1 amount), or null when the camera writes no fog key. */
3450
+ fogState() {
3451
+ if (!this.fog) return null;
3452
+ const c = this.target;
3453
+ const fc = this.fogC ??= new Color();
3454
+ try {
3455
+ fc.setValue(c.fogColor);
3456
+ } catch {
3457
+ fc.setValue(0);
3458
+ }
3459
+ const [r, g, b] = fc.toArray();
3460
+ const amount = Number.isFinite(c.fogAmount) ? Math.min(1, Math.max(0, c.fogAmount)) : 1;
3461
+ return { r, g, b, near: c.fogNear, far: c.fogFar, amount };
3462
+ }
3463
+ /** Lifespan on the global timeline (valid after bindTimeline). */
3464
+ window() {
3465
+ const start = this.absoluteStart ?? this.at;
3466
+ return { start, end: start + (this.duration ?? 0) };
3467
+ }
3468
+ };
3469
+
3470
+ // src/space/lighting.ts
3471
+ var LIGHT_KINDS = ["ambient", "point", "spot", "parallel"];
3472
+ var FALLOFFS = ["none", "smooth", "inverseSquare"];
3473
+ var LIGHT_PROPS = ["x", "y", "z", "lookAtX", "lookAtY", "lookAtZ", "intensity", "color", "coneAngle", "coneFeather", "radius", "falloffDistance", "shadowDarkness", "shadowDiffusion"];
3474
+ var MAX_LIGHTS = 8;
3475
+ var MAX_CASTERS = 4;
3476
+ var SHADOW_TAPS = 16;
3477
+ var FALLOFF_ID = { none: 0, smooth: 1, inverseSquare: 2 };
3478
+ var KIND_ID = { point: 1, spot: 2, parallel: 3 };
3479
+ function smoothstep(e0, e1, x) {
3480
+ const t = Math.min(1, Math.max(0, (x - e0) / (e1 - e0)));
3481
+ return t * t * (3 - 2 * t);
3482
+ }
3483
+ var DEG4 = Math.PI / 180;
3484
+ function norm(v) {
3485
+ const l = Math.hypot(v.x, v.y, v.z) || 1;
3486
+ return { x: v.x / l, y: v.y / l, z: v.z / l };
3487
+ }
3488
+ var dot = (a, b) => a.x * b.x + a.y * b.y + a.z * b.z;
3489
+ var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y, z: a.z - b.z });
3490
+ function coneCos(l) {
3491
+ const half = Math.min(179, Math.max(0, l.coneAngle)) * DEG4 / 2;
3492
+ const inner = half * (1 - Math.min(1, Math.max(0, l.coneFeather)));
3493
+ const cosOuter = Math.cos(half);
3494
+ const cosInner = Math.max(Math.cos(inner), cosOuter + 1e-4);
3495
+ return { cosOuter, cosInner };
3496
+ }
3497
+ function packLights(lights) {
3498
+ const pos = new Float32Array(4 * MAX_LIGHTS), dir = new Float32Array(4 * MAX_LIGHTS), col = new Float32Array(4 * MAX_LIGHTS), fall = new Float32Array(4 * MAX_LIGHTS), shadow = new Float32Array(4 * MAX_LIGHTS);
3499
+ const ambient = [0, 0, 0];
3500
+ let n = 0;
3501
+ for (const l of lights) {
3502
+ if (l.kind === "ambient") {
3503
+ ambient[0] += l.r * l.intensity;
3504
+ ambient[1] += l.g * l.intensity;
3505
+ ambient[2] += l.b * l.intensity;
3506
+ continue;
3507
+ }
3508
+ if (n >= MAX_LIGHTS) continue;
3509
+ const d = norm({ x: l.lookAtX - l.x, y: l.lookAtY - l.y, z: l.lookAtZ - l.z });
3510
+ const { cosOuter, cosInner } = coneCos(l);
3511
+ pos.set([l.x, l.y, l.z, KIND_ID[l.kind]], 4 * n);
3512
+ dir.set([d.x, d.y, d.z, cosOuter], 4 * n);
3513
+ col.set([l.r * l.intensity, l.g * l.intensity, l.b * l.intensity, cosInner], 4 * n);
3514
+ fall.set([FALLOFF_ID[l.falloff], l.radius, l.falloffDistance, l.castsShadows ? 1 : 0], 4 * n);
3515
+ shadow.set([l.castsShadows ? l.shadowDarkness : 0, l.shadowDiffusion, 0, 0], 4 * n);
3516
+ n++;
3517
+ }
3518
+ return { count: n, ambient, pos, dir, col, fall, shadow };
3519
+ }
3520
+ function shadeReference(p, n, cam, lights) {
3521
+ let N = norm(n);
3522
+ if (dot(N, sub(cam, p)) < 0) N = { x: -N.x, y: -N.y, z: -N.z };
3523
+ const out = [0, 0, 0];
3524
+ for (const l of lights) {
3525
+ if (l.kind === "ambient") {
3526
+ out[0] += l.r * l.intensity;
3527
+ out[1] += l.g * l.intensity;
3528
+ out[2] += l.b * l.intensity;
3529
+ continue;
3530
+ }
3531
+ const dirLA = norm({ x: l.lookAtX - l.x, y: l.lookAtY - l.y, z: l.lookAtZ - l.z });
3532
+ let L;
3533
+ let dist = 0;
3534
+ if (l.kind === "parallel") L = { x: -dirLA.x, y: -dirLA.y, z: -dirLA.z };
3535
+ else {
3536
+ const d = sub({ x: l.x, y: l.y, z: l.z }, p);
3537
+ dist = Math.hypot(d.x, d.y, d.z);
3538
+ L = { x: d.x / dist, y: d.y / dist, z: d.z / dist };
3539
+ }
3540
+ const ndl = Math.max(0, dot(N, L));
3541
+ let att = 1;
3542
+ if (l.kind !== "parallel") {
3543
+ if (l.falloff === "smooth") att = 1 - smoothstep(l.radius, l.radius + Math.max(l.falloffDistance, 1e-3), dist);
3544
+ else if (l.falloff === "inverseSquare") att = Math.min(1, l.radius * l.radius / Math.max(dist * dist, 1e-6));
3545
+ }
3546
+ let cone = 1;
3547
+ if (l.kind === "spot") {
3548
+ const { cosOuter, cosInner } = coneCos(l);
3549
+ cone = smoothstep(cosOuter, cosInner, -dot(L, dirLA));
3550
+ }
3551
+ const k = l.intensity * ndl * att * cone;
3552
+ out[0] += l.r * k;
3553
+ out[1] += l.g * k;
3554
+ out[2] += l.b * k;
3555
+ }
3556
+ return out;
3557
+ }
3558
+ function lightLevel(frame, cam, lights) {
3559
+ const mid = { x: frame.o.x + 0.5 * frame.u.x + 0.5 * frame.v.x, y: frame.o.y + 0.5 * frame.u.y + 0.5 * frame.v.y, z: frame.o.z + 0.5 * frame.u.z + 0.5 * frame.v.z };
3560
+ const n = { x: frame.u.y * frame.v.z - frame.u.z * frame.v.y, y: frame.u.z * frame.v.x - frame.u.x * frame.v.z, z: frame.u.x * frame.v.y - frame.u.y * frame.v.x };
3561
+ const m = shadeReference(mid, n, cam, lights);
3562
+ return 0.2126 * m[0] + 0.7152 * m[1] + 0.0722 * m[2];
3563
+ }
3564
+ var LightSequence = class extends Sequence {
3565
+ color = new Color();
3566
+ async build() {
3567
+ const w = this.parent?.width ?? this.root.width;
3568
+ const h = this.parent?.height ?? this.root.height;
3569
+ if (this.duration === void 0) this.duration = this.parent?.duration ?? this.root.duration;
3570
+ const c = new Container();
3571
+ c.x = w * 0.3;
3572
+ c.y = h * 0.2;
3573
+ c.z = homeDistance(h, 40) * 0.6;
3574
+ c.lookAtX = w / 2;
3575
+ c.lookAtY = h / 2;
3576
+ c.lookAtZ = 0;
3577
+ c.intensity = 1;
3578
+ c.color = "#ffffff";
3579
+ c.coneAngle = 90;
3580
+ c.coneFeather = 0.5;
3581
+ c.radius = 500;
3582
+ c.falloffDistance = 500;
3583
+ c.shadowDarkness = 1;
3584
+ c.shadowDiffusion = 0;
3585
+ this.target = c;
3586
+ }
3587
+ state() {
3588
+ const c = this.target;
3589
+ try {
3590
+ this.color.setValue(c.color);
3591
+ } catch {
3592
+ this.color.setValue(16777215);
3505
3593
  }
3506
- this._typed = typed;
3507
- this._refreshText();
3508
- this.buildFilters();
3594
+ const [r, g, b] = this.color.toArray();
3595
+ return {
3596
+ // a kind or falloff nobody knows is the default the warning promises (the shader must never see an unknown id)
3597
+ kind: LIGHT_KINDS.includes(this.spec.kind) ? this.spec.kind : "point",
3598
+ x: c.x,
3599
+ y: c.y,
3600
+ z: c.z,
3601
+ lookAtX: c.lookAtX,
3602
+ lookAtY: c.lookAtY,
3603
+ lookAtZ: c.lookAtZ,
3604
+ r,
3605
+ g,
3606
+ b,
3607
+ intensity: Number.isFinite(c.intensity) ? Math.max(0, c.intensity) : 1,
3608
+ coneAngle: c.coneAngle,
3609
+ coneFeather: c.coneFeather,
3610
+ falloff: FALLOFFS.includes(this.spec.falloff) ? this.spec.falloff : "none",
3611
+ radius: c.radius,
3612
+ falloffDistance: c.falloffDistance,
3613
+ castsShadows: !!this.spec.castsShadows,
3614
+ shadowDarkness: Math.min(1, Math.max(0, c.shadowDarkness)),
3615
+ shadowDiffusion: Math.max(0, c.shadowDiffusion)
3616
+ };
3509
3617
  }
3510
- bindTimeline(timeline, offset = 0) {
3511
- if (!this.target) return;
3512
- const colorSpace = this.spec.colorSpace ?? "rgb";
3513
- const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars", "fillGradient"]);
3514
- runSuperWithStrippedSpec(this, stripped, timeline, offset);
3515
- const keyframes = this.spec.keyframes ?? [];
3516
- const origin = offset + this.at;
3517
- bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
3518
- if (this._grad) bindGradientKeyframes(timeline, this._grad, keyframes, this.duration, origin, colorSpace, () => this._applyGradient());
3519
- const refresh = () => this._refreshText();
3520
- bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
3521
- bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
3522
- bindTextKeyframes(timeline, keyframes, this.duration, origin, () => this._template, (t) => {
3523
- this._template = t;
3524
- refresh();
3525
- }, describeLayer(this.spec));
3618
+ window() {
3619
+ const start = this.absoluteStart ?? this.at;
3620
+ return { start, end: start + (this.duration ?? 0) };
3526
3621
  }
3527
3622
  };
3528
- function runSuperWithStrippedSpec(inst, strippedSpec, timeline, offset) {
3529
- const originalSpec = inst.spec;
3530
- inst.spec = strippedSpec;
3531
- try {
3532
- Sequence.prototype.bindTimeline.call(inst, timeline, offset);
3533
- } finally {
3534
- inst.spec = originalSpec;
3623
+ var defaultWarn = (m) => console.warn(m);
3624
+ var SPAN_STYLE_KEYS = ["fill", "fontWeight", "fontStyle", "fontVariant", "letterSpacing", "dropShadow"];
3625
+ var LINE_KEYS = ["fontSize", "fontFamily", "lineHeight", "wordWrap", "wordWrapWidth", "align", "leading", "textBaseline", "whiteSpace", "stroke", "padding"];
3626
+ var spansMeta = /* @__PURE__ */ new WeakMap();
3627
+ var widthOf = (text, style) => CanvasTextMetrics.measureText(text, style).width;
3628
+ function normalize(spans, who, warn) {
3629
+ if (!Array.isArray(spans) || spans.length === 0) {
3630
+ warn(`pixi-effects: ${who}: spans is empty: write an array of strings and { text, fill \u2026 } objects (nothing is drawn)`);
3631
+ return [];
3535
3632
  }
3536
- }
3537
- function stripKeys2(spec, keys) {
3538
- return {
3539
- ...spec,
3540
- initial: stripProps(spec.initial, keys),
3541
- keyframes: spec.keyframes ? spec.keyframes.map((kf) => ({
3542
- ...kf,
3543
- set: stripProps(kf.set, keys),
3544
- to: stripProps(kf.to, keys),
3545
- from: stripProps(kf.from, keys)
3546
- })) : void 0
3547
- };
3548
- }
3549
- function stripProps(props, keys) {
3550
- if (!props) return props;
3551
- if (!keys.some((k) => k in props)) return props;
3552
- const out = {};
3553
- for (const k of Object.keys(props)) if (!keys.includes(k)) out[k] = props[k];
3554
- return out;
3555
- }
3556
- function resolveNumber(v, scope) {
3557
- if (typeof v === "number") return v;
3558
- const out = normalizeProps({ v }, scope).v;
3559
- return typeof out === "number" ? out : Number(out) || 0;
3560
- }
3561
- function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDuration, origin, scope) {
3562
- for (const kf of keyframes) {
3563
- const setV = kf.set?.[key];
3564
- const fromV = kf.from?.[key];
3565
- const toV = kf.to?.[key];
3566
- if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
3567
- const at = origin + resolveAt(kf.at, parentDuration);
3568
- const duration = kfDuration(kf);
3569
- const ease = kf.ease ?? "none";
3570
- const loop = loopVars(kf);
3571
- if (setV !== void 0) {
3572
- const v = resolveNumber(setV, scope);
3573
- revertibleSet(timeline, at, () => state[key], (n) => {
3574
- state[key] = n;
3575
- refresh();
3576
- }, v, [state, key]);
3633
+ const out = [];
3634
+ const lineKeys = /* @__PURE__ */ new Set();
3635
+ spans.forEach((sp, i) => {
3636
+ if (typeof sp === "string") {
3637
+ out.push({ text: sp, overrides: {} });
3638
+ return;
3577
3639
  }
3578
- if (toV !== void 0 && fromV !== void 0) {
3579
- timeline.fromTo(
3580
- state,
3581
- { [key]: resolveNumber(fromV, scope) },
3582
- { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
3583
- at
3584
- );
3585
- } else if (toV !== void 0) {
3586
- timeline.to(state, { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
3587
- } else if (fromV !== void 0) {
3588
- timeline.from(state, { [key]: resolveNumber(fromV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
3640
+ if (!sp || typeof sp !== "object" || typeof sp.text !== "string") {
3641
+ warn(`pixi-effects: ${who}: spans[${i}] must be a string or { text: '\u2026', fill: '#\u2026' }, got ${JSON.stringify(sp)}; skipped`);
3642
+ return;
3589
3643
  }
3590
- }
3644
+ const { text, ...rest } = sp;
3645
+ const overrides = {};
3646
+ for (const [k, v] of Object.entries(rest)) {
3647
+ if (v === void 0) continue;
3648
+ if (SPAN_STYLE_KEYS.includes(k)) overrides[k] = v;
3649
+ else if (LINE_KEYS.includes(k)) lineKeys.add(k);
3650
+ else {
3651
+ const guess = suggestName(k, [...SPAN_STYLE_KEYS]);
3652
+ warn(`pixi-effects: ${who}: spans[${i}]: "${k}" is not a span key${guess ? ` \u2014 did you mean "${guess}"?` : ""} (it is ignored). A span takes text and ${SPAN_STYLE_KEYS.join(", ")}`);
3653
+ }
3654
+ }
3655
+ out.push({ text, overrides });
3656
+ });
3657
+ if (lineKeys.size) warn(`pixi-effects: ${who}: a span cannot set ${[...lineKeys].join(", ")}: they move the baseline, the line or the pieces' places (a stroke shifts a piece by half its width); put them in the style of the layer (one size, one font and one outline per layer). They are ignored`);
3658
+ return out;
3591
3659
  }
3592
- function bindTextKeyframes(timeline, keyframes, parentDuration, origin, read2, write2, who) {
3593
- for (const kf of keyframes) {
3594
- for (const [what, props] of [["to", kf.to], ["from", kf.from]]) {
3595
- if (props && props.text !== void 0) {
3596
- console.warn(`pixi-effects: ${who}: a keyframe's ${what}: { text } cannot tween a string: use set: { text: '\u2026' } at the time it should change`);
3660
+ function layoutSpans(spans, style, align2, who, warn = defaultWarn) {
3661
+ const clean = normalize(spans, who, warn);
3662
+ const base = new TextStyle(style);
3663
+ const m = CanvasTextMetrics.measureText("M", base);
3664
+ const lineHeight = m.lineHeight ?? m.height;
3665
+ const sentinel = (ts) => widthOf("|", ts);
3666
+ const measure = (text, ts) => widthOf(text + "|", ts) - sentinel(ts);
3667
+ const pieces = [];
3668
+ let line = 0, chars = 0, x = 0;
3669
+ clean.forEach((sp, spanIdx) => {
3670
+ const ts = Object.keys(sp.overrides).length ? new TextStyle({ ...style, ...sp.overrides }) : base;
3671
+ sp.text.split("\n").forEach((part, k) => {
3672
+ if (k > 0) {
3673
+ line++;
3674
+ x = 0;
3675
+ chars += 1;
3676
+ }
3677
+ if (part === "") return;
3678
+ const width2 = measure(part, ts);
3679
+ const length = Array.from(part).length;
3680
+ pieces.push({ text: part, x, y: line * lineHeight, width: width2, line, span: spanIdx, start: chars, length, style: sp.overrides, blank: part.trim() === "" });
3681
+ x += width2;
3682
+ chars += length;
3683
+ });
3684
+ });
3685
+ const lineWidth = /* @__PURE__ */ new Map();
3686
+ for (const p of pieces) lineWidth.set(p.line, Math.max(lineWidth.get(p.line) ?? 0, p.x + p.width));
3687
+ const width = Math.max(0, ...lineWidth.values());
3688
+ for (const p of pieces) {
3689
+ const room = width - (lineWidth.get(p.line) ?? 0);
3690
+ p.x += align2 === "center" ? room / 2 : align2 === "right" ? room : 0;
3691
+ }
3692
+ return { pieces, width, height: pieces.length ? (line + 1) * lineHeight : 0, lineHeight, chars };
3693
+ }
3694
+ function resolveSpansStyle(style, scope, who, warn = defaultWarn) {
3695
+ const out = { ...TEXT_STYLE_DEFAULTS };
3696
+ for (const [k, raw] of Object.entries(style)) {
3697
+ if (raw === void 0) continue;
3698
+ let v = raw;
3699
+ if (scope) {
3700
+ try {
3701
+ v = normalizeProps({ [k]: raw }, scope, { skipKeys: STYLE_OPAQUE_KEYS })[k];
3702
+ } catch (e) {
3703
+ v = NaN;
3704
+ if (!(k in TEXT_STYLE_DEFAULTS) || k === "fontSize") warn(`pixi-effects: ${who}: style.${k} ${JSON.stringify(raw)} could not be resolved (${e.message}); ${k === "fontSize" ? `the default, ${TEXT_STYLE_DEFAULTS.fontSize}, is used` : "it is left out"}`);
3705
+ continue;
3597
3706
  }
3598
3707
  }
3599
- const t = kf.set?.text;
3600
- if (t === void 0) continue;
3601
- revertibleSet(timeline, origin + resolveAt(kf.at, parentDuration), read2, write2, String(t));
3708
+ if (["fontSize", "letterSpacing", "lineHeight"].includes(k) && (typeof v !== "number" || !Number.isFinite(v) || k !== "letterSpacing" && v <= 0)) {
3709
+ warn(`pixi-effects: ${who}: style.${k} ${JSON.stringify(raw)} is not a usable number here (a name that is not declared counts as 0; spans measure the text when the layer is built); ${k === "fontSize" ? `the default, ${TEXT_STYLE_DEFAULTS.fontSize}, is used` : "it is left out"}`);
3710
+ continue;
3711
+ }
3712
+ out[k] = v;
3602
3713
  }
3714
+ return out;
3603
3715
  }
3604
- function animates(keyframes, key) {
3605
- return (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((p) => p && p[key] !== void 0));
3606
- }
3607
- function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, colorSpace) {
3608
- const state = { fill: text.style.fill };
3609
- const writeFill = () => {
3610
- text.style.fill = state.fill;
3716
+ var TEXT_ONLY = ["type", "spans", "text", "style", "format", "colorSpace", "fillGradient", "initial", "keyframes"];
3717
+ var NOT_FOR_A_GROUP = ["fill", "text", "value", "fillGradient"];
3718
+ function expandSpans(spec, warn = defaultWarn, scope) {
3719
+ const who = describeLayer(spec);
3720
+ const s = spec;
3721
+ if (s.text !== void 0) warn(`pixi-effects: ${who}: "text" is ignored when the layer has spans: write the text as spans`);
3722
+ const style = resolveSpansStyle(s.style ?? {}, scope, who, warn);
3723
+ const noLayout = ["wordWrap", "wordWrapWidth", "leading"].filter((k) => style[k] !== void 0 && style[k] !== false && style[k] !== 0);
3724
+ for (const k of ["wordWrap", "wordWrapWidth", "leading"]) delete style[k];
3725
+ if (noLayout.length) warn(`pixi-effects: ${who}: style.${noLayout.join(", style.")} ${noLayout.length > 1 ? "do" : "does"} not apply to a spans layer (the lines are the \\n of the spans); break the lines with \\n. ${noLayout.length > 1 ? "They are" : "It is"} ignored`);
3726
+ const layout = layoutSpans(s.spans, style, style.align, who, warn);
3727
+ const dropped = new Set(["fillGradient", "format", "colorSpace"].filter((k) => s[k] !== void 0));
3728
+ const clean = (bag) => {
3729
+ if (!bag) return bag;
3730
+ const out = {};
3731
+ for (const [k, v] of Object.entries(bag)) {
3732
+ if (NOT_FOR_A_GROUP.includes(k)) dropped.add(k);
3733
+ else out[k] = v;
3734
+ }
3735
+ return out;
3611
3736
  };
3612
- for (const kf of keyframes) {
3613
- const at = offset + resolveAt(kf.at, parentDuration);
3614
- const duration = kfDuration(kf);
3615
- const ease = kf.ease ?? "none";
3616
- const setFill = pickFill(kf.set);
3617
- const fromFill = pickFill(kf.from);
3618
- const toFill = pickFill(kf.to);
3619
- if (setFill !== void 0) {
3620
- revertibleSet(timeline, at, () => state.fill, (c) => {
3621
- state.fill = c;
3622
- writeFill();
3623
- }, setFill, [state, "fill"]);
3737
+ const initial = clean(s.initial) ?? {};
3738
+ const keyframes = (s.keyframes ?? []).map((kf) => ({ ...kf, set: clean(kf.set), to: clean(kf.to), from: clean(kf.from) }));
3739
+ if (dropped.size) warn(`pixi-effects: ${who}: ${[...dropped].join(", ")} do not apply to a spans layer (it is a group of text layers): use a separate text layer, or put the colour in a span`);
3740
+ const typed = initial.visibleChars !== void 0 || keyframes.some((kf) => [kf.set, kf.to, kf.from].some((p) => p && p.visibleChars !== void 0));
3741
+ const width = Math.max(1, layout.width), height = Math.max(1, layout.height);
3742
+ for (const [a, pivot, size] of [["anchorX", "pivotX", width], ["anchorY", "pivotY", height]]) {
3743
+ if (initial[a] === void 0) continue;
3744
+ if (typeof initial[a] === "number") {
3745
+ initial[pivot] = initial[a] * size;
3746
+ delete initial[a];
3747
+ } else {
3748
+ warn(`pixi-effects: ${who}: initial.${a} ${JSON.stringify(initial[a])} must be a number on a spans layer (it becomes the pivot of the block); it is ignored`);
3749
+ delete initial[a];
3750
+ }
3751
+ }
3752
+ const animatedAnchors = ["anchorX", "anchorY"].filter((a) => keyframes.some((kf) => [kf.set, kf.to, kf.from].some((p) => p && p[a] !== void 0)));
3753
+ if (animatedAnchors.length) warn(`pixi-effects: ${who}: ${animatedAnchors.join(", ")} cannot be keyframed on a spans layer (the anchor is the pivot of the block, set once in initial); the keyframes do nothing`);
3754
+ const baseName = typeof s.name === "string" && s.name ? s.name : "spans";
3755
+ const drawn = layout.pieces.filter((p) => !p.blank);
3756
+ const sequences = drawn.map((p, i) => ({
3757
+ type: "text",
3758
+ name: `${baseName}.${i}`,
3759
+ text: p.text,
3760
+ style: { ...style, ...p.style },
3761
+ initial: { x: p.x, y: p.y, ...typed ? { visibleChars: p.length } : {} }
3762
+ }));
3763
+ const rest = {};
3764
+ for (const [k, v] of Object.entries(s)) if (!TEXT_ONLY.includes(k)) rest[k] = v;
3765
+ const group = { type: "composition", ...rest, width, height, initial, keyframes, sequences };
3766
+ spansMeta.set(group, { typed, pieces: drawn.map((p) => ({ start: p.start, length: p.length })), preview: layout.pieces.map((p, i) => (i > 0 && p.line !== layout.pieces[i - 1].line ? " " : "") + p.text).join("") });
3767
+ return group;
3768
+ }
3769
+ var ImageSequence = class extends Sequence {
3770
+ async build() {
3771
+ const texture = await Assets.get(this.spec.asset);
3772
+ const sprite = new Sprite({ texture, label: this.spec.name });
3773
+ sprite.cullable = true;
3774
+ this.target = sprite;
3775
+ this.intrinsicWidth = texture.width;
3776
+ this.intrinsicHeight = texture.height;
3777
+ if (this.duration === void 0) {
3778
+ this.duration = this.parent?.duration ?? this.root.duration;
3624
3779
  }
3625
- if (toFill !== void 0) {
3626
- tweenColor(
3627
- timeline,
3628
- state,
3629
- "fill",
3630
- fromFill,
3631
- toFill,
3632
- duration,
3633
- ease,
3634
- at,
3635
- colorSpace,
3636
- writeFill,
3637
- loopVars(kf)
3638
- );
3780
+ this.buildFilters();
3781
+ }
3782
+ bindTimeline(timeline, offset = 0) {
3783
+ if (!this.target) return;
3784
+ const colorSpace = this.spec.colorSpace;
3785
+ if (!colorSpace || colorSpace === "rgb") {
3786
+ super.bindTimeline(timeline, offset);
3787
+ return;
3788
+ }
3789
+ const scope = this.scope();
3790
+ const startTime = offset + this.at;
3791
+ const initial = stripKeys2(this.spec.initial, ["tint"]);
3792
+ const keyframes = this.spec.keyframes ? this.spec.keyframes.map((kf) => stripKeysInKeyframe(kf, ["tint"])) : void 0;
3793
+ applyInitial(this.target, initial, scope);
3794
+ applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime);
3795
+ const initTint = this.spec.initial?.tint;
3796
+ if (initTint !== void 0) this.target.tint = initTint;
3797
+ for (const kf of this.spec.keyframes ?? []) {
3798
+ const at = startTime + resolveAt(kf.at, this.duration);
3799
+ const duration = kfDuration(kf);
3800
+ const ease = kf.ease ?? "none";
3801
+ const fromTint = kf.from?.tint;
3802
+ const toTint = kf.to?.tint;
3803
+ const setTint = kf.set?.tint;
3804
+ if (setTint !== void 0) {
3805
+ const sprite = this.target;
3806
+ revertibleSet(timeline, at, () => sprite.tint, (c) => {
3807
+ sprite.tint = c;
3808
+ }, setTint, [sprite, "tint"]);
3809
+ }
3810
+ if (toTint !== void 0) {
3811
+ tweenColor(
3812
+ timeline,
3813
+ this.target,
3814
+ "tint",
3815
+ fromTint,
3816
+ toTint,
3817
+ duration,
3818
+ ease,
3819
+ at,
3820
+ colorSpace,
3821
+ void 0,
3822
+ loopVars(kf)
3823
+ );
3824
+ }
3825
+ }
3826
+ this.absoluteStart = startTime;
3827
+ const endTime = startTime + this.duration;
3828
+ if (this.keepHidden) return;
3829
+ this.target.renderable = startTime <= 0;
3830
+ timeline.set(this.target, { renderable: true }, startTime);
3831
+ timeline.set(this.target, { renderable: false }, endTime);
3832
+ }
3833
+ };
3834
+ function stripKeys2(props, keys) {
3835
+ if (!props) return props;
3836
+ const drop = new Set(keys);
3837
+ const out = {};
3838
+ let changed = false;
3839
+ for (const k of Object.keys(props)) {
3840
+ if (drop.has(k)) {
3841
+ changed = true;
3842
+ continue;
3639
3843
  }
3844
+ out[k] = props[k];
3640
3845
  }
3846
+ return changed ? out : props;
3641
3847
  }
3642
- function pickFill(props) {
3643
- if (!props) return void 0;
3644
- const v = props.fill;
3645
- if (typeof v === "string" || typeof v === "number") return v;
3646
- return void 0;
3848
+ function stripKeysInKeyframe(kf, keys) {
3849
+ return {
3850
+ ...kf,
3851
+ set: stripKeys2(kf.set, keys),
3852
+ to: stripKeys2(kf.to, keys),
3853
+ from: stripKeys2(kf.from, keys)
3854
+ };
3647
3855
  }
3648
3856
  var AudioSequence = class extends Sequence {
3649
3857
  _audioBuffer = null;
@@ -5398,10 +5606,15 @@ function resolveDim(v, scope) {
5398
5606
  var _CompositionSequence = null;
5399
5607
  async function getCompositionSequence() {
5400
5608
  if (!_CompositionSequence) {
5401
- _CompositionSequence = (await import('./Composition-RJGA7KDU.js')).CompositionSequence;
5609
+ _CompositionSequence = (await import('./Composition-RF7QOEXT.js')).CompositionSequence;
5402
5610
  }
5403
5611
  return _CompositionSequence;
5404
5612
  }
5613
+ var _SpansSequence = null;
5614
+ async function getSpansSequence() {
5615
+ if (!_SpansSequence) _SpansSequence = (await import('./Spans-SDXABOV6.js')).SpansSequence;
5616
+ return _SpansSequence;
5617
+ }
5405
5618
  var staticTypes = {
5406
5619
  image: ImageSequence,
5407
5620
  text: TextSequence,
@@ -5419,8 +5632,12 @@ function registerSequenceType(type, ctor) {
5419
5632
  }
5420
5633
  async function buildSequenceTree(specs, parent, root) {
5421
5634
  const out = [];
5422
- for (const spec of specs) {
5635
+ for (let spec of specs) {
5423
5636
  let Cls = staticTypes[spec.type] ?? registry2[spec.type];
5637
+ if (spec.type === "text" && spec.spans !== void 0) {
5638
+ spec = expandSpans(spec, void 0, buildScope({ intrinsicWidth: 0, intrinsicHeight: 0, at: spec.at, duration: spec.duration }, parent, root));
5639
+ Cls = await getSpansSequence();
5640
+ }
5424
5641
  if (!Cls && spec.type === "composition") {
5425
5642
  Cls = await getCompositionSequence();
5426
5643
  }
@@ -5440,6 +5657,6 @@ async function buildSequenceTree(specs, parent, root) {
5440
5657
  return out;
5441
5658
  }
5442
5659
 
5443
- export { CameraSequence, DEFAULT_FOV, DEG3 as DEG, FALLOFFS, LIGHT_KINDS, LIGHT_PROPS, LightSequence, MANY_BLURRED, MAX_BLUR, MAX_CASTERS, MAX_LIGHTS, MIN_BLUR, NEAR, NullSequence, SHADOW_TAPS, Sequence, bindClock, blurRadius, buildScope, buildSequenceTree, cameraBasis, checkEase, clockTable, contentLength, createTimeline, ensureFilterLibrary, evaluateExpr, flattenSvgPath, focusProblems, homeCamera, homeDistance, layerInitialZ, layerToWorld, lengthen, lightLevel, noise, packLights, parseEase, projectLayer, projectPoint, rand, random, registerSequenceType, remapOf, resolveAt, timeRemapOf };
5444
- //# sourceMappingURL=chunk-ZAXYPXKW.js.map
5445
- //# sourceMappingURL=chunk-ZAXYPXKW.js.map
5660
+ export { CameraSequence, DEFAULT_FOV, DEG3 as DEG, EXPRESSION_NAMES, FALLOFFS, LIGHT_KINDS, LIGHT_PROPS, LightSequence, MANY_BLURRED, MAX_BLUR, MAX_CASTERS, MAX_LIGHTS, MIN_BLUR, NEAR, NullSequence, SHADOW_TAPS, Sequence, TextSequence, bindClock, bindCounterKeyframes, blurRadius, buildScope, buildSequenceTree, cameraBasis, checkEase, clockTable, contentLength, createTimeline, ensureFilterLibrary, evaluateExpr, flattenSvgPath, focusProblems, homeCamera, homeDistance, layerInitialZ, layerToWorld, lengthen, lightLevel, noise, packLights, parseEase, projectLayer, projectPoint, rand, random, registerSequenceType, remapOf, resolveAt, resolveNumber, spansMeta, timeRemapOf };
5661
+ //# sourceMappingURL=chunk-AW5NPDE4.js.map
5662
+ //# sourceMappingURL=chunk-AW5NPDE4.js.map