pixi-effects 0.31.0 → 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 (74) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +5 -5
  3. package/ai/chat-template.html +3 -3
  4. package/ai/tools/render.mjs +19 -2
  5. package/dist/{Base-jRiMHcWT.d.ts → Base-CXsMdmJB.d.ts} +1 -1
  6. package/dist/{Base-BKagtWc0.d.cts → Base-pNq6sVGl.d.cts} +1 -1
  7. package/dist/Composition-LNMKWXSL.cjs +16 -0
  8. package/dist/{Composition-V2XYSW6P.cjs.map → Composition-LNMKWXSL.cjs.map} +1 -1
  9. package/dist/Composition-RF7QOEXT.js +7 -0
  10. package/dist/{Composition-DQCPJPFE.js.map → Composition-RF7QOEXT.js.map} +1 -1
  11. package/dist/Controller.d.cts +2 -2
  12. package/dist/Controller.d.ts +2 -2
  13. package/dist/{Movie-BH81stkb.d.cts → Movie-CL6SsAWe.d.cts} +1 -1
  14. package/dist/{Movie-CfFPItoL.d.ts → Movie-f0tQG9y5.d.ts} +1 -1
  15. package/dist/Spans-NQGKVMR5.cjs +55 -0
  16. package/dist/Spans-NQGKVMR5.cjs.map +1 -0
  17. package/dist/Spans-SDXABOV6.js +53 -0
  18. package/dist/Spans-SDXABOV6.js.map +1 -0
  19. package/dist/{blendModes-RIVZXL5A.js → blendModes-MVRLG3T7.js} +4 -4
  20. package/dist/{blendModes-RIVZXL5A.js.map → blendModes-MVRLG3T7.js.map} +1 -1
  21. package/dist/{blendModes-ETMYWATF.cjs → blendModes-VTKDXWEN.cjs} +5 -5
  22. package/dist/{blendModes-ETMYWATF.cjs.map → blendModes-VTKDXWEN.cjs.map} +1 -1
  23. package/dist/{chunk-MMTESMJ5.js → chunk-AW5NPDE4.js} +1363 -1151
  24. package/dist/chunk-AW5NPDE4.js.map +1 -0
  25. package/dist/{chunk-RCVZX2JP.js → chunk-C5BNHVKK.js} +36 -3
  26. package/dist/chunk-C5BNHVKK.js.map +1 -0
  27. package/dist/{chunk-L73KRFXI.cjs → chunk-CRUDOKSD.cjs} +91 -91
  28. package/dist/chunk-CRUDOKSD.cjs.map +1 -0
  29. package/dist/{chunk-BGMCZA36.js → chunk-D5BFQVKL.js} +6 -6
  30. package/dist/chunk-D5BFQVKL.js.map +1 -0
  31. package/dist/{chunk-LYOKH7KE.cjs → chunk-FCGYJCVO.cjs} +36 -3
  32. package/dist/chunk-FCGYJCVO.cjs.map +1 -0
  33. package/dist/{chunk-UTIBYMIO.js → chunk-FCMOREIS.js} +10 -6
  34. package/dist/chunk-FCMOREIS.js.map +1 -0
  35. package/dist/{chunk-X3WHETQ3.cjs → chunk-K3MAEKFM.cjs} +42 -38
  36. package/dist/chunk-K3MAEKFM.cjs.map +1 -0
  37. package/dist/{chunk-4KB3GUEK.js → chunk-QFNS6BFL.js} +4 -4
  38. package/dist/{chunk-4KB3GUEK.js.map → chunk-QFNS6BFL.js.map} +1 -1
  39. package/dist/{chunk-DJ5B7IKT.cjs → chunk-QHDN6TF5.cjs} +1377 -1161
  40. package/dist/chunk-QHDN6TF5.cjs.map +1 -0
  41. package/dist/{chunk-2DXPTKQZ.cjs → chunk-Z437UWI4.cjs} +5 -5
  42. package/dist/{chunk-2DXPTKQZ.cjs.map → chunk-Z437UWI4.cjs.map} +1 -1
  43. package/dist/index.cjs +33 -33
  44. package/dist/index.d.cts +5 -5
  45. package/dist/index.d.ts +5 -5
  46. package/dist/index.js +5 -5
  47. package/dist/loader.css +1 -1
  48. package/dist/present.cjs +5 -5
  49. package/dist/present.d.cts +2 -2
  50. package/dist/present.d.ts +2 -2
  51. package/dist/present.js +5 -5
  52. package/dist/three.cjs +6 -6
  53. package/dist/three.d.cts +2 -2
  54. package/dist/three.d.ts +2 -2
  55. package/dist/three.js +2 -2
  56. package/dist/{types-CK_crNAI.d.ts → types-CavnUvd5.d.cts} +26 -1
  57. package/dist/{types-CK_crNAI.d.cts → types-CavnUvd5.d.ts} +26 -1
  58. package/docs/dsl.md +14 -4
  59. package/llms-full.txt +19 -8
  60. package/package.json +1 -1
  61. package/skills/pixi-effects/SKILL.md +1 -1
  62. package/skills/pixi-effects/reference/cheatsheet.md +2 -2
  63. package/skills/pixi-effects/reference/pitfalls.md +2 -2
  64. package/skills/pixi-effects/template.html +6 -6
  65. package/dist/Composition-DQCPJPFE.js +0 -7
  66. package/dist/Composition-V2XYSW6P.cjs +0 -16
  67. package/dist/chunk-BGMCZA36.js.map +0 -1
  68. package/dist/chunk-DJ5B7IKT.cjs.map +0 -1
  69. package/dist/chunk-L73KRFXI.cjs.map +0 -1
  70. package/dist/chunk-LYOKH7KE.cjs.map +0 -1
  71. package/dist/chunk-MMTESMJ5.js.map +0 -1
  72. package/dist/chunk-RCVZX2JP.js.map +0 -1
  73. package/dist/chunk-UTIBYMIO.js.map +0 -1
  74. package/dist/chunk-X3WHETQ3.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
  }
@@ -2248,6 +2291,13 @@ var registry = {
2248
2291
  function looksLikePixiFilter(x) {
2249
2292
  return !!x && typeof x === "object" && typeof x.apply === "function";
2250
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
+ }
2251
2301
  function createFilter(spec) {
2252
2302
  if (spec.type === "custom") {
2253
2303
  const inst2 = spec.filter;
@@ -2258,7 +2308,8 @@ function createFilter(spec) {
2258
2308
  named._name = spec.name;
2259
2309
  return named;
2260
2310
  }
2261
- const { type, name, ...params } = spec;
2311
+ const { type, name, ...given } = spec;
2312
+ const params = copyPlain(given);
2262
2313
  const Cls = registry[type];
2263
2314
  const inst = Cls ? new Cls(params) : createNamedFilter(type, params);
2264
2315
  inst._name = name;
@@ -2383,694 +2434,345 @@ var NullSequence = class extends Sequence {
2383
2434
  }
2384
2435
  };
2385
2436
 
2386
- // src/core/remap.ts
2387
- var BAGS = ["set", "to", "from"];
2388
- function timeRemapOf(spec, prop = "time") {
2389
- const hasInitial = !!spec.initial && "time" in spec.initial;
2390
- const hasKf = (spec.keyframes ?? []).some((kf) => BAGS.some((b) => kf[b] && "time" in kf[b]));
2391
- const speed = typeof spec.speed === "number" && Number.isFinite(spec.speed) && spec.speed !== 0 ? spec.speed : void 0;
2392
- if (!hasInitial && !hasKf && speed === void 0) return null;
2393
- const timeKfs = [];
2394
- const restKfs = [];
2395
- for (const kf of spec.keyframes ?? []) {
2396
- const t = { ...kf }, r = { ...kf };
2397
- let touched = false, left = false;
2398
- for (const b of BAGS) {
2399
- const bag = kf[b];
2400
- if (!bag) {
2401
- delete t[b];
2402
- delete r[b];
2403
- continue;
2404
- }
2405
- const { time, ...others } = bag;
2406
- if ("time" in bag) {
2407
- touched = true;
2408
- t[b] = { [prop]: time };
2409
- } else delete t[b];
2410
- if (Object.keys(others).length) {
2411
- r[b] = others;
2412
- left = true;
2413
- } else delete r[b];
2414
- }
2415
- if (touched) timeKfs.push(t);
2416
- if (!touched || left) restKfs.push(r);
2417
- }
2418
- let restInitial = spec.initial;
2419
- let start;
2420
- if (hasInitial) {
2421
- const { time, ...others } = spec.initial;
2422
- start = typeof time === "number" && Number.isFinite(time) ? time : void 0;
2423
- restInitial = others;
2424
- }
2425
- 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;
2426
2440
  }
2427
- function resolveFromEnd(remap, length, prop) {
2428
- const fix = (v) => typeof v === "number" && v < 0 ? Math.max(0, length + v) : v;
2429
- const timeKfs = remap.timeKfs.map((kf) => {
2430
- const out = { ...kf };
2431
- for (const b of BAGS) {
2432
- const bag = kf[b];
2433
- if (bag && prop in bag) out[b] = { ...bag, [prop]: fix(bag[prop]) };
2434
- }
2435
- return out;
2436
- });
2437
- 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 });
2438
2445
  }
2439
- function remapOf(spec, prop, length) {
2440
- const r = timeRemapOf(spec, prop);
2441
- 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
+ };
2442
2458
  }
2443
- function contentLength(remap, duration) {
2444
- let m = duration;
2445
- if (remap.speed !== void 0) m = Math.max(m, Math.abs(remap.speed) * duration);
2446
- if (remap.start !== void 0) m = Math.max(m, remap.start + (remap.speed ?? 1) * duration);
2447
- for (const kf of remap.timeKfs) {
2448
- for (const b of BAGS) {
2449
- const bag = kf[b];
2450
- const v = bag && Object.values(bag)[0];
2451
- if (typeof v === "number") m = Math.max(m, v);
2452
- }
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
+ };
2453
2484
  }
2454
- 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
+ };
2455
2499
  }
2456
- function bindClock(timeline, clock, prop, remap, duration, at, scope) {
2457
- if (remap.timeKfs.length) {
2458
- if (remap.start !== void 0) timeline.set(clock, { [prop]: remap.start }, at);
2459
- applyKeyframes(timeline, clock, remap.timeKfs, duration, scope, [], at);
2460
- 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 };
2461
2508
  }
2462
- const s = remap.speed ?? 1;
2463
- const from = remap.start ?? (s < 0 ? -s * duration : 0);
2464
- 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 };
2465
2513
  }
2466
- function clockTable(remap, prop, duration, at, scope, to) {
2467
- const probe = createTimeline({ paused: true });
2468
- const clock = { [prop]: remap.start ?? 0 };
2469
- bindClock(probe, clock, prop, remap, duration, at, scope);
2470
- probe.time(probe.duration());
2471
- probe.time(0);
2472
- const h = 1 / 1e3;
2473
- const n = Math.max(2, Math.ceil(to / h) + 2);
2474
- const tab = new Float64Array(n);
2475
- let min = Infinity, max = -Infinity;
2476
- for (let k = 0; k < n; k++) {
2477
- probe.time(k * h);
2478
- const v = clock[prop];
2479
- tab[k] = v;
2480
- const t = k * h;
2481
- if (t >= at && t <= at + duration) {
2482
- if (v < min) min = v;
2483
- if (v > max) max = v;
2484
- }
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;
2485
2527
  }
2486
- 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);
2487
2533
  return {
2488
- at(t) {
2489
- const x = Math.min(Math.max(t / h, 0), n - 1.000001);
2490
- const k = Math.floor(x), f = x - k;
2491
- return tab[k] * (1 - f) + tab[k + 1] * f;
2492
- },
2493
- min: Number.isFinite(min) ? min : 0,
2494
- 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
2495
2538
  };
2496
2539
  }
2497
-
2498
- // src/space/math.ts
2499
- var DEG3 = Math.PI / 180;
2500
- var DEFAULT_FOV = 40;
2501
- var MIN_FOV = 1;
2502
- var MAX_FOV = 179;
2503
- var NEAR = 1;
2504
- function clampFov(fov) {
2505
- if (!Number.isFinite(fov)) return DEFAULT_FOV;
2506
- 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})`;
2507
2545
  }
2508
- function homeDistance(compHeight, fovDeg) {
2509
- 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);
2510
2548
  }
2511
- 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);
2512
2562
  return {
2513
- x: compW / 2,
2514
- y: compH / 2,
2515
- z: homeDistance(compH, fov),
2516
- lookAtX: compW / 2,
2517
- lookAtY: compH / 2,
2518
- lookAtZ: 0,
2519
- 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_
2520
2566
  };
2521
2567
  }
2522
- function cameraBasis(cam, compW, compH) {
2523
- let fx = cam.lookAtX - cam.x;
2524
- let fy = cam.lookAtY - cam.y;
2525
- let fz = cam.lookAtZ - cam.z;
2526
- let len = Math.hypot(fx, fy, fz);
2527
- if (len < 1e-9) {
2528
- fx = 0;
2529
- fy = 0;
2530
- fz = -1;
2531
- len = 1;
2532
- }
2533
- fx /= len;
2534
- fy /= len;
2535
- fz /= len;
2536
- let rx = -fz;
2537
- const ry = 0;
2538
- let rz = fx;
2539
- const rlen = Math.hypot(rx, rz);
2540
- if (rlen < 1e-9) {
2541
- rx = 1;
2542
- rz = 0;
2543
- } else {
2544
- rx /= rlen;
2545
- rz /= rlen;
2546
- }
2547
- const dx = ry * fz - rz * fy;
2548
- const dy = rz * fx - rx * fz;
2549
- 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_;
2550
2575
  return {
2551
- cx: cam.x,
2552
- cy: cam.y,
2553
- cz: cam.z,
2554
- rx,
2555
- ry,
2556
- rz,
2557
- dx,
2558
- dy,
2559
- dz,
2560
- fx,
2561
- fy,
2562
- fz,
2563
- focal: compH / 2 / Math.tan(clampFov(cam.fov) * DEG3 / 2),
2564
- halfW: compW / 2,
2565
- 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)
2566
2579
  };
2567
2580
  }
2568
- function layerToWorld(t, px, py) {
2569
- let x = (px - t.pivotX) * t.scaleX;
2570
- let y = (py - t.pivotY) * t.scaleY;
2571
- let z = 0;
2572
- let c = Math.cos(t.rotationX);
2573
- let s = Math.sin(t.rotationX);
2574
- [y, z] = [y * c - z * s, y * s + z * c];
2575
- c = Math.cos(t.rotationY);
2576
- s = Math.sin(t.rotationY);
2577
- [x, z] = [x * c + z * s, -x * s + z * c];
2578
- c = Math.cos(t.rotationZ);
2579
- s = Math.sin(t.rotationZ);
2580
- [x, y] = [x * c - y * s, x * s + y * c];
2581
- return { x: x + t.x, y: y + t.y, z: z + t.z };
2582
- }
2583
- function projectPoint(b, p) {
2584
- const vx = p.x - b.cx;
2585
- const vy = p.y - b.cy;
2586
- const vz = p.z - b.cz;
2587
- const xc = vx * b.rx + vy * b.ry + vz * b.rz;
2588
- const yc = vx * b.dx + vy * b.dy + vz * b.dz;
2589
- const zc = vx * b.fx + vy * b.fy + vz * b.fz;
2590
- 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) };
2591
2583
  }
2592
- function projectLayer(t, frame, b) {
2593
- const pts = [
2594
- [frame.x, frame.y],
2595
- [frame.x + frame.width, frame.y],
2596
- [frame.x + frame.width, frame.y + frame.height],
2597
- [frame.x, frame.y + frame.height]
2598
- ];
2599
- let visible = true;
2600
- const corners = [];
2601
- for (const [px, py] of pts) {
2602
- const p = projectPoint(b, layerToWorld(t, px, py));
2603
- if (!(p.depth > NEAR)) visible = false;
2604
- corners.push(p.x, p.y);
2605
- }
2606
- const origin = projectPoint(b, { x: t.x, y: t.y, z: t.z });
2607
- 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) };
2608
2586
  }
2609
-
2610
- // src/space/focus.ts
2611
- var DEFAULT_APERTURE = 30;
2612
- var MIN_BLUR = 0.05;
2613
- var MAX_BLUR = 32;
2614
- var MANY_BLURRED = 20;
2615
- function blurRadius(aperture, focal, depth, focusDepth) {
2616
- if (!(aperture > 0) || !(focal > 0) || !(depth > 0) || !(focusDepth > 0)) return 0;
2617
- const r = aperture * focal * Math.abs(1 / depth - 1 / focusDepth) / 2;
2618
- return Math.min(r, MAX_BLUR);
2587
+ function lerp(a, b, t) {
2588
+ return a + (b - a) * t;
2619
2589
  }
2620
- var SCOPE_NAMES = /* @__PURE__ */ new Set(["w", "h", "W", "H", "GW", "GH", "contain", "cover", "t", "d", "T"]);
2621
- function looksLikeLayerName(s) {
2622
- return /^[A-Za-z_][\w-]*$/.test(s) && !SCOPE_NAMES.has(s);
2590
+ function clamp01(v) {
2591
+ return v < 0 ? 0 : v > 1 ? 1 : v;
2623
2592
  }
2624
- var isName = (v, known) => typeof v === "string" && (known.has(v) || looksLikeLayerName(v));
2625
- function focusNames(props, known) {
2626
- const out = [];
2627
- const read2 = (bag) => {
2628
- const v = bag?.focus;
2629
- if (isName(v, known)) out.push(v);
2630
- };
2631
- read2(props.initial);
2632
- for (const kf of props.keyframes ?? []) for (const bag of [kf.set, kf.to, kf.from]) read2(bag);
2633
- 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;
2634
2599
  }
2635
- function withFocusResolved(props, zOf, known = /* @__PURE__ */ new Set()) {
2636
- if (focusNames(props, known).length === 0) return props;
2637
- const fix = (bag) => {
2638
- const v = bag?.focus;
2639
- if (!bag || !isName(v, known)) return bag;
2640
- return { ...bag, focus: zOf(v) ?? 0 };
2641
- };
2600
+
2601
+ // src/sequences/gradientAnim.ts
2602
+ var PATCH_KEYS = ["type", "angle", "center", "innerRadius", "radius", "stops"];
2603
+ function gradStateFrom(g) {
2642
2604
  return {
2643
- ...props,
2644
- initial: fix(props.initial),
2645
- 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) })
2646
2611
  };
2647
2612
  }
2648
- function layerInitialZ(spec, scope) {
2649
- const z = spec.initial?.z;
2650
- if (typeof z === "number") return z;
2651
- if (typeof z === "string") return evaluateExpr(z, scope);
2652
- return 0;
2653
- }
2654
- var movesZ = (spec) => (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((bag) => bag && "z" in bag));
2655
- function focusProblems(camera, siblings) {
2656
- const out = [];
2657
- const said = /* @__PURE__ */ new Set();
2658
- const threeD = siblings.filter((s) => s.threeD && s.name).map((s) => s.name);
2659
- const known = new Set(siblings.map((s) => s.name).filter((n) => !!n));
2660
- for (const name of focusNames(camera, known)) {
2661
- if (said.has(name)) continue;
2662
- said.add(name);
2663
- const same = siblings.filter((s) => s.name === name);
2664
- const hit = same.find((s) => s.threeD) ?? same[0];
2665
- if (!hit) out.push({ kind: "missing", name, hint: suggestName(name, threeD) });
2666
- else if (!hit.threeD) out.push({ kind: "not-threeD", name });
2667
- else {
2668
- if (same.length > 1) out.push({ kind: "duplicate", name, count: same.length });
2669
- if (movesZ(hit)) out.push({ kind: "moving", name });
2670
- }
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;
2671
2624
  }
2672
2625
  return out;
2673
2626
  }
2674
- var CameraSequence = class extends Sequence {
2675
- /** True when `z` is never specified anywhere: z then follows `fov`. */
2676
- autoZ = true;
2677
- compH = 0;
2678
- warnedFov = false;
2679
- warnedLookAt = false;
2680
- warnedAperture = false;
2681
- /** True when the camera writes `focus` or `aperture` anywhere: depth of field is on. */
2682
- dof = false;
2683
- /** True when the camera writes any fog key anywhere: depth fog is on (nothing is made for a camera without fog). */
2684
- fog = false;
2685
- fogC = null;
2686
- zOfName = null;
2687
- layerNames = /* @__PURE__ */ new Set();
2688
- async build() {
2689
- const compW = this.parent?.width ?? this.root.width;
2690
- this.compH = this.parent?.height ?? this.root.height;
2691
- if (this.duration === void 0) {
2692
- this.duration = this.parent?.duration ?? this.root.duration;
2693
- }
2694
- const home = homeCamera(compW, this.compH);
2695
- const carrier = new Container();
2696
- carrier.x = home.x;
2697
- carrier.y = home.y;
2698
- carrier.z = home.z;
2699
- carrier.lookAtX = home.lookAtX;
2700
- carrier.lookAtY = home.lookAtY;
2701
- carrier.lookAtZ = home.lookAtZ;
2702
- carrier.fov = home.fov;
2703
- carrier.focus = 0;
2704
- carrier.aperture = DEFAULT_APERTURE;
2705
- carrier.offsetX = carrier.offsetY = carrier.offsetZ = 0;
2706
- carrier.lookOffsetX = carrier.lookOffsetY = carrier.lookOffsetZ = 0;
2707
- carrier.fogNear = home.z;
2708
- carrier.fogFar = home.z * 3;
2709
- carrier.fogColor = "#000000";
2710
- carrier.fogAmount = 1;
2711
- this.target = carrier;
2712
- const keys = collectPropKeys(this.spec);
2713
- this.autoZ = !keys.has("z");
2714
- this.dof = keys.has("focus") || keys.has("aperture");
2715
- this.fog = keys.has("fogNear") || keys.has("fogFar") || keys.has("fogColor") || keys.has("fogAmount");
2716
- }
2717
- /** 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). */
2718
- apertureNow(a) {
2719
- if (!this.dof) return 0;
2720
- if (!(a >= 0) && !this.warnedAperture) {
2721
- this.warnedAperture = true;
2722
- const who = this.spec.name ? ` "${this.spec.name}"` : "";
2723
- 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}`);
2724
2634
  }
2725
- return a > 0 ? a : 0;
2726
- }
2727
- /** Called by the composition once its children exist: how a layer name in `focus` becomes the z of that layer. */
2728
- resolveFocusNames(zOf, names = /* @__PURE__ */ new Set()) {
2729
- this.zOfName = zOf;
2730
- this.layerNames = names;
2731
- }
2732
- displayProps() {
2733
- const base = super.displayProps();
2734
- 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");
2735
2643
  }
2736
- /** The camera's state at the current timeline position. */
2737
- state() {
2738
- const c = this.target;
2739
- if (!this.warnedFov && !(c.fov >= MIN_FOV && c.fov <= MAX_FOV)) {
2740
- this.warnedFov = true;
2741
- const who = this.spec.name ? ` "${this.spec.name}"` : "";
2742
- console.warn(`pixi-effects: camera${who}: fov ${c.fov} is outside [${MIN_FOV}, ${MAX_FOV}]; clamped`);
2743
- }
2744
- const fov = clampFov(c.fov);
2745
- const z = this.autoZ ? homeDistance(this.compH, fov) : c.z;
2746
- if (!this.warnedLookAt && c.lookAtX === c.x && c.lookAtY === c.y && c.lookAtZ === z) {
2747
- this.warnedLookAt = true;
2748
- const who = this.spec.name ? ` "${this.spec.name}"` : "";
2749
- console.warn(`pixi-effects: camera${who}: lookAt equals the camera position; looking along -z instead`);
2750
- }
2751
- return {
2752
- x: c.x + c.offsetX,
2753
- y: c.y + c.offsetY,
2754
- z: z + c.offsetZ,
2755
- lookAtX: c.lookAtX + c.lookOffsetX,
2756
- lookAtY: c.lookAtY + c.lookOffsetY,
2757
- lookAtZ: c.lookAtZ + c.lookOffsetZ,
2758
- fov,
2759
- focus: Number.isFinite(c.focus) ? c.focus : 0,
2760
- aperture: this.apertureNow(c.aperture)
2761
- };
2762
- }
2763
- /** The fog now (colour as 0..1 channels, distances, 0..1 amount), or null when the camera writes no fog key. */
2764
- fogState() {
2765
- if (!this.fog) return null;
2766
- const c = this.target;
2767
- const fc = this.fogC ??= new Color();
2768
- try {
2769
- fc.setValue(c.fogColor);
2770
- } catch {
2771
- 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
+ }
2772
2671
  }
2773
- const [r, g, b] = fc.toArray();
2774
- const amount = Number.isFinite(c.fogAmount) ? Math.min(1, Math.max(0, c.fogAmount)) : 1;
2775
- return { r, g, b, near: c.fogNear, far: c.fogFar, amount };
2776
- }
2777
- /** Lifespan on the global timeline (valid after bindTimeline). */
2778
- window() {
2779
- const start = this.absoluteStart ?? this.at;
2780
- return { start, end: start + (this.duration ?? 0) };
2781
2672
  }
2782
- };
2783
-
2784
- // src/space/lighting.ts
2785
- var LIGHT_KINDS = ["ambient", "point", "spot", "parallel"];
2786
- var FALLOFFS = ["none", "smooth", "inverseSquare"];
2787
- var LIGHT_PROPS = ["x", "y", "z", "lookAtX", "lookAtY", "lookAtZ", "intensity", "color", "coneAngle", "coneFeather", "radius", "falloffDistance", "shadowDarkness", "shadowDiffusion"];
2788
- var MAX_LIGHTS = 8;
2789
- var MAX_CASTERS = 4;
2790
- var SHADOW_TAPS = 16;
2791
- var FALLOFF_ID = { none: 0, smooth: 1, inverseSquare: 2 };
2792
- var KIND_ID = { point: 1, spot: 2, parallel: 3 };
2793
- function smoothstep(e0, e1, x) {
2794
- const t = Math.min(1, Math.max(0, (x - e0) / (e1 - e0)));
2795
- return t * t * (3 - 2 * t);
2796
- }
2797
- var DEG4 = Math.PI / 180;
2798
- function norm(v) {
2799
- const l = Math.hypot(v.x, v.y, v.z) || 1;
2800
- 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)");
2801
2674
  }
2802
- var dot = (a, b) => a.x * b.x + a.y * b.y + a.z * b.z;
2803
- var sub = (a, b) => ({ x: a.x - b.x, y: a.y - b.y, z: a.z - b.z });
2804
- function coneCos(l) {
2805
- const half = Math.min(179, Math.max(0, l.coneAngle)) * DEG4 / 2;
2806
- const inner = half * (1 - Math.min(1, Math.max(0, l.coneFeather)));
2807
- const cosOuter = Math.cos(half);
2808
- const cosInner = Math.max(Math.cos(inner), cosOuter + 1e-4);
2809
- 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
+ });
2810
2707
  }
2811
- function packLights(lights) {
2812
- 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);
2813
- const ambient = [0, 0, 0];
2814
- let n = 0;
2815
- for (const l of lights) {
2816
- if (l.kind === "ambient") {
2817
- ambient[0] += l.r * l.intensity;
2818
- ambient[1] += l.g * l.intensity;
2819
- ambient[2] += l.b * l.intensity;
2820
- continue;
2821
- }
2822
- if (n >= MAX_LIGHTS) continue;
2823
- const d = norm({ x: l.lookAtX - l.x, y: l.lookAtY - l.y, z: l.lookAtZ - l.z });
2824
- const { cosOuter, cosInner } = coneCos(l);
2825
- pos.set([l.x, l.y, l.z, KIND_ID[l.kind]], 4 * n);
2826
- dir.set([d.x, d.y, d.z, cosOuter], 4 * n);
2827
- col.set([l.r * l.intensity, l.g * l.intensity, l.b * l.intensity, cosInner], 4 * n);
2828
- fall.set([FALLOFF_ID[l.falloff], l.radius, l.falloffDistance, l.castsShadows ? 1 : 0], 4 * n);
2829
- shadow.set([l.castsShadows ? l.shadowDarkness : 0, l.shadowDiffusion, 0, 0], 4 * n);
2830
- 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;
2831
2716
  }
2832
- return { count: n, ambient, pos, dir, col, fall, shadow };
2833
- }
2834
- function shadeReference(p, n, cam, lights) {
2835
- let N = norm(n);
2836
- if (dot(N, sub(cam, p)) < 0) N = { x: -N.x, y: -N.y, z: -N.z };
2837
- const out = [0, 0, 0];
2838
- for (const l of lights) {
2839
- if (l.kind === "ambient") {
2840
- out[0] += l.r * l.intensity;
2841
- out[1] += l.g * l.intensity;
2842
- out[2] += l.b * l.intensity;
2843
- continue;
2844
- }
2845
- const dirLA = norm({ x: l.lookAtX - l.x, y: l.lookAtY - l.y, z: l.lookAtZ - l.z });
2846
- let L;
2847
- let dist = 0;
2848
- if (l.kind === "parallel") L = { x: -dirLA.x, y: -dirLA.y, z: -dirLA.z };
2849
- else {
2850
- const d = sub({ x: l.x, y: l.y, z: l.z }, p);
2851
- dist = Math.hypot(d.x, d.y, d.z);
2852
- 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);
2853
2734
  }
2854
- const ndl = Math.max(0, dot(N, L));
2855
- let att = 1;
2856
- if (l.kind !== "parallel") {
2857
- if (l.falloff === "smooth") att = 1 - smoothstep(l.radius, l.radius + Math.max(l.falloffDistance, 1e-3), dist);
2858
- 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
+ });
2859
2765
  }
2860
- let cone = 1;
2861
- if (l.kind === "spot") {
2862
- const { cosOuter, cosInner } = coneCos(l);
2863
- 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));
2864
2768
  }
2865
- const k = l.intensity * ndl * att * cone;
2866
- out[0] += l.r * k;
2867
- out[1] += l.g * k;
2868
- out[2] += l.b * k;
2869
2769
  }
2870
- return out;
2871
2770
  }
2872
- function lightLevel(frame, cam, lights) {
2873
- 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 };
2874
- 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 };
2875
- const m = shadeReference(mid, n, cam, lights);
2876
- return 0.2126 * m[0] + 0.7152 * m[1] + 0.0722 * m[2];
2877
- }
2878
- var LightSequence = class extends Sequence {
2879
- color = new Color();
2880
- async build() {
2881
- const w = this.parent?.width ?? this.root.width;
2882
- const h = this.parent?.height ?? this.root.height;
2883
- if (this.duration === void 0) this.duration = this.parent?.duration ?? this.root.duration;
2884
- const c = new Container();
2885
- c.x = w * 0.3;
2886
- c.y = h * 0.2;
2887
- c.z = homeDistance(h, 40) * 0.6;
2888
- c.lookAtX = w / 2;
2889
- c.lookAtY = h / 2;
2890
- c.lookAtZ = 0;
2891
- c.intensity = 1;
2892
- c.color = "#ffffff";
2893
- c.coneAngle = 90;
2894
- c.coneFeather = 0.5;
2895
- c.radius = 500;
2896
- c.falloffDistance = 500;
2897
- c.shadowDarkness = 1;
2898
- c.shadowDiffusion = 0;
2899
- this.target = c;
2900
- }
2901
- state() {
2902
- const c = this.target;
2903
- try {
2904
- this.color.setValue(c.color);
2905
- } catch {
2906
- this.color.setValue(16777215);
2907
- }
2908
- const [r, g, b] = this.color.toArray();
2909
- return {
2910
- // a kind or falloff nobody knows is the default the warning promises (the shader must never see an unknown id)
2911
- kind: LIGHT_KINDS.includes(this.spec.kind) ? this.spec.kind : "point",
2912
- x: c.x,
2913
- y: c.y,
2914
- z: c.z,
2915
- lookAtX: c.lookAtX,
2916
- lookAtY: c.lookAtY,
2917
- lookAtZ: c.lookAtZ,
2918
- r,
2919
- g,
2920
- b,
2921
- intensity: Number.isFinite(c.intensity) ? Math.max(0, c.intensity) : 1,
2922
- coneAngle: c.coneAngle,
2923
- coneFeather: c.coneFeather,
2924
- falloff: FALLOFFS.includes(this.spec.falloff) ? this.spec.falloff : "none",
2925
- radius: c.radius,
2926
- falloffDistance: c.falloffDistance,
2927
- castsShadows: !!this.spec.castsShadows,
2928
- shadowDarkness: Math.min(1, Math.max(0, c.shadowDarkness)),
2929
- shadowDiffusion: Math.max(0, c.shadowDiffusion)
2930
- };
2931
- }
2932
- window() {
2933
- const start = this.absoluteStart ?? this.at;
2934
- return { start, end: start + (this.duration ?? 0) };
2935
- }
2936
- };
2937
-
2938
- // src/core/revertibleSet.ts
2939
- function revertibleSet(timeline, at, read2, write2, value, property) {
2940
- timeline.setValue(property?.[0] ?? read2, property?.[1] ?? "set", at, read2, write2, value);
2941
- }
2942
-
2943
- // src/expr/colorInterp.ts
2944
- var CHROMA_FLOOR = 0.03;
2945
- var CHROMA_FULL = 0.08;
2946
- function buildColorInterp(from, to, space) {
2947
- const fromRGBA = parseToRGBA(from);
2948
- const toRGBA = parseToRGBA(to);
2949
- const fromOk = rgbToOklab(fromRGBA);
2950
- const toOk = rgbToOklab(toRGBA);
2951
- if (space === "oklab") {
2952
- return (t) => {
2953
- const L = lerp(fromOk.L, toOk.L, t);
2954
- const a = lerp(fromOk.a, toOk.a, t);
2955
- const b = lerp(fromOk.b, toOk.b, t);
2956
- const alpha = lerp(fromRGBA.a, toRGBA.a, t);
2957
- return rgbaString(oklabToRgb({ L, a, b }), alpha);
2958
- };
2959
- }
2960
- const fromLCH = oklabToOklch(fromOk);
2961
- const toLCH = oklabToOklch(toOk);
2962
- const fromHue = fromLCH.h, toHue = toLCH.h;
2963
- fromLCH.h = borrowHue(fromHue, fromLCH.C, toHue);
2964
- toLCH.h = borrowHue(toHue, toLCH.C, fromHue);
2965
- const hueDelta = shortestHueDelta(fromLCH.h, toLCH.h);
2966
- return (t) => {
2967
- const L = lerp(fromLCH.L, toLCH.L, t);
2968
- const C = lerp(fromLCH.C, toLCH.C, t);
2969
- const h = fromLCH.h + hueDelta * t;
2970
- const alpha = lerp(fromRGBA.a, toRGBA.a, t);
2971
- const ab = oklchToOklab({ L, C, h });
2972
- return rgbaString(oklabToRgb(ab), alpha);
2973
- };
2974
- }
2975
- function borrowHue(own, chroma, other) {
2976
- const u = Math.min(1, Math.max(0, (chroma - CHROMA_FLOOR) / (CHROMA_FULL - CHROMA_FLOOR)));
2977
- const keep = u * u * (3 - 2 * u);
2978
- return own + shortestHueDelta(own, other) * (1 - keep);
2979
- }
2980
- function parseToRGBA(input) {
2981
- if (typeof input === "number") {
2982
- return { r: (input >> 16 & 255) / 255, g: (input >> 8 & 255) / 255, b: (input & 255) / 255, a: 1 };
2983
- }
2984
- const s = input.trim();
2985
- if (s.startsWith("#")) return parseHex(s);
2986
- if (s.startsWith("rgb")) return parseRgbFunc(s);
2987
- return { r: 0, g: 0, b: 0, a: 1 };
2988
- }
2989
- function parseHex(s) {
2990
- const hex = s.slice(1);
2991
- let r = 0, g = 0, b = 0, a = 1;
2992
- if (hex.length === 3 || hex.length === 4) {
2993
- r = parseInt(hex[0] + hex[0], 16) / 255;
2994
- g = parseInt(hex[1] + hex[1], 16) / 255;
2995
- b = parseInt(hex[2] + hex[2], 16) / 255;
2996
- if (hex.length === 4) a = parseInt(hex[3] + hex[3], 16) / 255;
2997
- } else if (hex.length === 6 || hex.length === 8) {
2998
- r = parseInt(hex.slice(0, 2), 16) / 255;
2999
- g = parseInt(hex.slice(2, 4), 16) / 255;
3000
- b = parseInt(hex.slice(4, 6), 16) / 255;
3001
- if (hex.length === 8) a = parseInt(hex.slice(6, 8), 16) / 255;
3002
- }
3003
- return { r, g, b, a };
3004
- }
3005
- function parseRgbFunc(s) {
3006
- const inner = s.replace(/^rgba?\(/, "").replace(/\)$/, "");
3007
- const parts = inner.split(/[,\s/]+/).filter(Boolean).map(parseFloat);
3008
- return {
3009
- r: (parts[0] ?? 0) / 255,
3010
- g: (parts[1] ?? 0) / 255,
3011
- b: (parts[2] ?? 0) / 255,
3012
- a: parts.length > 3 ? parts[3] ?? 1 : 1
3013
- };
3014
- }
3015
- function rgbaString(rgb, alpha) {
3016
- const r = Math.round(clamp01(rgb.r) * 255);
3017
- const g = Math.round(clamp01(rgb.g) * 255);
3018
- const b = Math.round(clamp01(rgb.b) * 255);
3019
- return `rgba(${r},${g},${b},${alpha})`;
3020
- }
3021
- function srgbToLinear(c) {
3022
- return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
3023
- }
3024
- function linearToSrgb(c) {
3025
- return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
3026
- }
3027
- function rgbToOklab(rgba) {
3028
- const r = srgbToLinear(rgba.r);
3029
- const g = srgbToLinear(rgba.g);
3030
- const b = srgbToLinear(rgba.b);
3031
- const l = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
3032
- const m = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
3033
- const s = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
3034
- const l_ = Math.cbrt(l);
3035
- const m_ = Math.cbrt(m);
3036
- const s_ = Math.cbrt(s);
3037
- return {
3038
- L: 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_,
3039
- a: 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_,
3040
- b: 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_
3041
- };
3042
- }
3043
- function oklabToRgb(lab) {
3044
- const l_ = lab.L + 0.3963377774 * lab.a + 0.2158037573 * lab.b;
3045
- const m_ = lab.L - 0.1055613458 * lab.a - 0.0638541728 * lab.b;
3046
- const s_ = lab.L - 0.0894841775 * lab.a - 1.291485548 * lab.b;
3047
- const l = l_ * l_ * l_;
3048
- const m = m_ * m_ * m_;
3049
- const s = s_ * s_ * s_;
3050
- return {
3051
- r: linearToSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
3052
- g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
3053
- b: linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)
3054
- };
3055
- }
3056
- function oklabToOklch(lab) {
3057
- return { L: lab.L, C: Math.hypot(lab.a, lab.b), h: Math.atan2(lab.b, lab.a) };
3058
- }
3059
- function oklchToOklab(lch) {
3060
- return { L: lch.L, a: lch.C * Math.cos(lch.h), b: lch.C * Math.sin(lch.h) };
3061
- }
3062
- function lerp(a, b, t) {
3063
- return a + (b - a) * t;
3064
- }
3065
- function clamp01(v) {
3066
- return v < 0 ? 0 : v > 1 ? 1 : v;
3067
- }
3068
- function shortestHueDelta(from, to) {
3069
- const TWO_PI = Math.PI * 2;
3070
- let d = (to - from) % TWO_PI;
3071
- if (d > Math.PI) d -= TWO_PI;
3072
- if (d < -Math.PI) d += TWO_PI;
3073
- 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);
3074
2776
  }
3075
2777
 
3076
2778
  // src/expr/colorTween.ts
@@ -3098,557 +2800,1058 @@ function tweenColor(timeline, target, key, fromValue, toValue, duration, ease, a
3098
2800
  });
3099
2801
  }
3100
2802
 
3101
- // src/sequences/Image.ts
3102
- var ImageSequence = class extends Sequence {
3103
- async build() {
3104
- const texture = await Assets.get(this.spec.asset);
3105
- const sprite = new Sprite({ texture, label: this.spec.name });
3106
- sprite.cullable = true;
3107
- this.target = sprite;
3108
- this.intrinsicWidth = texture.width;
3109
- this.intrinsicHeight = texture.height;
3110
- if (this.duration === void 0) {
3111
- this.duration = this.parent?.duration ?? this.root.duration;
3112
- }
3113
- 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;
3114
2855
  }
3115
- bindTimeline(timeline, offset = 0) {
3116
- if (!this.target) return;
3117
- const colorSpace = this.spec.colorSpace;
3118
- if (!colorSpace || colorSpace === "rgb") {
3119
- super.bindTimeline(timeline, offset);
3120
- return;
3121
- }
3122
- const scope = this.scope();
3123
- const startTime = offset + this.at;
3124
- const initial = stripKeys(this.spec.initial, ["tint"]);
3125
- const keyframes = this.spec.keyframes ? this.spec.keyframes.map((kf) => stripKeysInKeyframe(kf, ["tint"])) : void 0;
3126
- applyInitial(this.target, initial, scope);
3127
- applyKeyframes(timeline, this.target, keyframes, this.duration, scope, [], startTime);
3128
- const initTint = this.spec.initial?.tint;
3129
- if (initTint !== void 0) this.target.tint = initTint;
3130
- for (const kf of this.spec.keyframes ?? []) {
3131
- const at = startTime + resolveAt(kf.at, this.duration);
3132
- const duration = kfDuration(kf);
3133
- const ease = kf.ease ?? "none";
3134
- const fromTint = kf.from?.tint;
3135
- const toTint = kf.to?.tint;
3136
- const setTint = kf.set?.tint;
3137
- if (setTint !== void 0) {
3138
- const sprite = this.target;
3139
- revertibleSet(timeline, at, () => sprite.tint, (c) => {
3140
- sprite.tint = c;
3141
- }, setTint, [sprite, "tint"]);
3142
- }
3143
- if (toTint !== void 0) {
3144
- tweenColor(
3145
- timeline,
3146
- this.target,
3147
- "tint",
3148
- fromTint,
3149
- toTint,
3150
- duration,
3151
- ease,
3152
- at,
3153
- colorSpace,
3154
- void 0,
3155
- loopVars(kf)
3156
- );
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];
3157
2908
  }
2909
+ this.intrinsicWidth = text.width;
2910
+ this.intrinsicHeight = text.height;
3158
2911
  }
3159
- this.absoluteStart = startTime;
3160
- const endTime = startTime + this.duration;
3161
- if (this.keepHidden) return;
3162
- this.target.renderable = startTime <= 0;
3163
- timeline.set(this.target, { renderable: true }, startTime);
3164
- 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));
3165
2949
  }
3166
2950
  };
3167
- function stripKeys(props, keys) {
3168
- if (!props) return props;
3169
- const drop = new Set(keys);
3170
- const out = {};
3171
- let changed = false;
3172
- for (const k of Object.keys(props)) {
3173
- if (drop.has(k)) {
3174
- changed = true;
3175
- continue;
3176
- }
3177
- 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;
3178
2958
  }
3179
- return changed ? out : props;
3180
2959
  }
3181
- function stripKeysInKeyframe(kf, keys) {
2960
+ function stripKeys(spec, keys) {
3182
2961
  return {
3183
- ...kf,
3184
- set: stripKeys(kf.set, keys),
3185
- to: stripKeys(kf.to, keys),
3186
- from: stripKeys(kf.from, keys)
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
3187
2970
  };
3188
2971
  }
3189
-
3190
- // src/sequences/gradient.ts
3191
- function cssColor(c) {
3192
- return typeof c === "number" ? "#" + c.toString(16).padStart(6, "0") : c;
3193
- }
3194
- function normalizedStops(g) {
3195
- const stops = g.stops ?? [];
3196
- if (stops.length < 2) throw new Error('pixi-effects: fillGradient needs at least two colour stops, e.g. stops: [[0, "#000"], [1, "#fff"]]');
3197
- return stops.map((s) => Array.isArray(s) ? { offset: s[0], color: s[1] } : { offset: s.offset, color: s.color });
3198
- }
3199
- function gradientOptions(g) {
3200
- const colorStops = normalizedStops(g);
3201
- const a = (g.angle ?? 90) * Math.PI / 180;
3202
- const dx = Math.cos(a), dy = Math.sin(a);
3203
- const h = (Math.abs(dx) + Math.abs(dy)) / 2;
3204
- return {
3205
- type: "linear",
3206
- start: { x: 0.5 - dx * h, y: 0.5 - dy * h },
3207
- end: { x: 0.5 + dx * h, y: 0.5 + dy * h },
3208
- colorStops,
3209
- textureSpace: "local"
3210
- };
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;
3211
2978
  }
3212
- function paintRadial(ctx, size, g) {
3213
- const stops = normalizedStops(g);
3214
- const [cx, cy] = g.center ?? [0.5, 0.5];
3215
- const grad = ctx.createRadialGradient(cx * size, cy * size, (g.innerRadius ?? 0) * size, cx * size, cy * size, (g.radius ?? 0.5) * size);
3216
- for (const s of stops) grad.addColorStop(s.offset, cssColor(s.color));
3217
- ctx.fillStyle = grad;
3218
- ctx.fillRect(0, 0, size, size);
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;
3219
2983
  }
3220
-
3221
- // src/sequences/gradientAnim.ts
3222
- var PATCH_KEYS = ["type", "angle", "center", "innerRadius", "radius", "stops"];
3223
- function gradStateFrom(g) {
3224
- return {
3225
- type: g.type ?? "linear",
3226
- angle: g.angle ?? 90,
3227
- center: [...g.center ?? [0.5, 0.5]],
3228
- innerRadius: g.innerRadius ?? 0,
3229
- radius: g.radius ?? 0.5,
3230
- stops: (g.stops ?? []).map((s) => Array.isArray(s) ? { offset: s[0], color: cssColor(s[1]) } : { offset: s.offset, color: cssColor(s.color) })
3231
- };
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
+ }
3013
+ }
3232
3014
  }
3233
- function mergeGrad(base, patch) {
3234
- const out = { ...base, center: [...base.center], stops: base.stops.map((s) => ({ ...s })) };
3235
- if (patch === null || typeof patch !== "object" || Array.isArray(patch)) return out;
3236
- const p = patch;
3237
- if (typeof p.angle === "number") out.angle = p.angle;
3238
- if (Array.isArray(p.center)) out.center = [Number(p.center[0]), Number(p.center[1])];
3239
- if (typeof p.innerRadius === "number") out.innerRadius = p.innerRadius;
3240
- if (typeof p.radius === "number") out.radius = p.radius;
3241
- if (p.stops !== void 0) {
3242
- const next = gradStateFrom({ stops: p.stops }).stops;
3243
- if (next.length === base.stops.length) out.stops = next;
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
+ }
3021
+ }
3022
+ const t = kf.set?.text;
3023
+ if (t === void 0) continue;
3024
+ revertibleSet(timeline, origin + resolveAt(kf.at, parentDuration), read2, write2, String(t));
3244
3025
  }
3245
- return out;
3246
3026
  }
3247
- var objectHint = "a keyframe's fillGradient must be an object like { angle: 200, stops: [[0, '#f00'], [1, '#00f']] }";
3248
- function validateGradientKeyframes(spec, who, warn = (m) => console.warn(m), kind = "shape") {
3249
- const said = /* @__PURE__ */ new Set();
3250
- const say = (m) => {
3251
- if (!said.has(m)) {
3252
- said.add(m);
3253
- warn(`pixi-effects: ${who}: ${m}`);
3254
- }
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;
3255
3034
  };
3256
- const startSpec = spec.fillGradient ?? spec.initial?.fillGradient;
3257
- const start = startSpec && typeof startSpec === "object" && Array.isArray(startSpec.stops) ? gradStateFrom(startSpec) : null;
3258
- let animated = false;
3259
- if (kind === "text") {
3260
- const radial = (g) => !!g && typeof g === "object" && ["type", "center", "innerRadius", "radius"].some((k) => g[k] !== void 0 && !(k === "type" && g.type === "linear"));
3261
- const asked = radial(startSpec) || (spec.keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => radial(b?.fillGradient)));
3262
- 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");
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
+ );
3062
+ }
3263
3063
  }
3064
+ }
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;
3070
+ }
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 = [];
3264
3081
  for (const kf of spec.keyframes ?? []) {
3265
- for (const bag of [kf.set, kf.to, kf.from]) {
3266
- if (!bag) continue;
3267
- for (const [key, value] of Object.entries(bag)) {
3268
- if (key === "fillGradient") {
3269
- animated = true;
3270
- if (value === null || typeof value !== "object" || Array.isArray(value)) {
3271
- say(objectHint);
3272
- continue;
3273
- }
3274
- const p = value;
3275
- for (const k of Object.keys(p)) {
3276
- if (PATCH_KEYS.includes(k)) continue;
3277
- const guess = suggestName(k, PATCH_KEYS.filter((x) => x !== "type"));
3278
- say(`fillGradient.${k} is not a gradient property${guess ? ` \u2014 did you mean "${guess}"?` : ""} (valid: ${PATCH_KEYS.join(", ")}); it is ignored`);
3279
- }
3280
- if (p.type !== void 0 && start && p.type !== start.type) say("fillGradient.type cannot change over time (linear <-> radial); it is ignored");
3281
- if (p.stops !== void 0 && start) {
3282
- const n = Array.isArray(p.stops) ? p.stops.length : 0;
3283
- 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`);
3284
- }
3285
- } else if (/^fillGradient\./i.test(key) || /^gradient/i.test(key)) {
3286
- const tail = key.replace(/^fillGradient\./i, "").replace(/^gradient/i, "");
3287
- const sub2 = tail ? tail[0].toLowerCase() + tail.slice(1) : "angle";
3288
- 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`);
3289
- }
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;
3290
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];
3291
3100
  }
3101
+ if (touched) timeKfs.push(t);
3102
+ if (!touched || left) restKfs.push(r);
3292
3103
  }
3293
- 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)");
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;
3110
+ }
3111
+ return { timeKfs, restKfs: restKfs.length ? restKfs : void 0, restInitial, start, speed };
3294
3112
  }
3295
- function tweenGradient(timeline, holder, fromPatch, toPatch, duration, ease, at, colorSpace, onChange, loop = {}) {
3296
- timeline.tweenValue({
3297
- holder,
3298
- id: "grad",
3299
- get: () => holder.grad,
3300
- set: (g) => {
3301
- holder.grad = g;
3302
- onChange();
3303
- },
3304
- from: fromPatch !== void 0 ? (prev) => mergeGrad(prev, fromPatch) : void 0,
3305
- to: (start, prev) => toPatch !== void 0 ? mergeGrad(start, toPatch) : prev,
3306
- make: (a, b) => {
3307
- const colors = a.stops.map((s, i) => colorSpace === "rgb" ? interpolateColors(s.color, b.stops[i].color) : buildColorInterp(s.color, b.stops[i].color, colorSpace));
3308
- return (t) => {
3309
- const l = (x, y) => x + (y - x) * t;
3310
- return {
3311
- type: a.type,
3312
- angle: l(a.angle, b.angle),
3313
- center: [l(a.center[0], b.center[0]), l(a.center[1], b.center[1])],
3314
- innerRadius: l(a.innerRadius, b.innerRadius),
3315
- radius: l(a.radius, b.radius),
3316
- stops: a.stops.map((s, i) => ({ offset: l(s.offset, b.stops[i].offset), color: colors[i](t) }))
3317
- };
3318
- };
3319
- },
3320
- duration,
3321
- ease,
3322
- at,
3323
- repeat: loop.repeat,
3324
- yoyo: loop.yoyo,
3325
- repeatDelay: loop.repeatDelay
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]) };
3120
+ }
3121
+ return out;
3326
3122
  });
3123
+ return { ...remap, timeKfs, start: remap.start === void 0 ? void 0 : fix(remap.start) };
3327
3124
  }
3328
- var PAINT_SIZE = 512;
3329
- var GradientPainter = class {
3330
- constructor(size = PAINT_SIZE) {
3331
- this.size = size;
3332
- this.canvas.width = size;
3333
- this.canvas.height = size;
3334
- this.tex = Texture.from(this.canvas);
3335
- globalThis.__gradientPainters = (globalThis.__gradientPainters ?? 0) + 1;
3336
- }
3337
- size;
3338
- canvas = document.createElement("canvas");
3339
- tex;
3340
- /** Paint `g` and return what `Graphics.fill(...)` takes. */
3341
- paint(g) {
3342
- const ctx = this.canvas.getContext("2d"), s = this.size;
3343
- if (!ctx) return new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((x) => [x.offset, x.color]) }));
3344
- ctx.clearRect(0, 0, s, s);
3345
- if (g.type === "radial") {
3346
- paintRadial(ctx, s, { stops: g.stops.map((x) => [x.offset, x.color]), center: g.center, innerRadius: g.innerRadius, radius: g.radius });
3347
- } else {
3348
- const a = g.angle * Math.PI / 180, dx = Math.cos(a), dy = Math.sin(a);
3349
- const h = (Math.abs(dx) + Math.abs(dy)) / 2;
3350
- const grad = ctx.createLinearGradient((0.5 - dx * h) * s, (0.5 - dy * h) * s, (0.5 + dx * h) * s, (0.5 + dy * h) * s);
3351
- for (const st of g.stops) grad.addColorStop(Math.min(1, Math.max(0, st.offset)), st.color);
3352
- ctx.fillStyle = grad;
3353
- ctx.fillRect(0, 0, s, s);
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);
3354
3138
  }
3355
- this.tex.source.update();
3356
- return { texture: this.tex, textureSpace: "local" };
3357
3139
  }
3358
- /** Give the texture and the canvas back (a layer that is destroyed must not leave them). */
3359
- destroy() {
3360
- this.tex.destroy(true);
3361
- this.canvas.width = this.canvas.height = 0;
3362
- globalThis.__gradientPainters = Math.max(0, (globalThis.__gradientPainters ?? 1) - 1);
3363
- }
3364
- };
3365
- function bindGradientKeyframes(timeline, holder, keyframes, duration, offset, colorSpace, onChange) {
3366
- const bag = (b) => b?.fillGradient;
3367
- for (const kf of keyframes) {
3368
- const at = offset + resolveAt(kf.at, duration);
3369
- const setV = bag(kf.set), fromV = bag(kf.from), toV = bag(kf.to);
3370
- if (setV !== void 0) {
3371
- timeline.tweenValue({
3372
- holder,
3373
- id: "grad",
3374
- get: () => holder.grad,
3375
- set: (g) => {
3376
- holder.grad = g;
3377
- onChange();
3378
- },
3379
- to: (start) => mergeGrad(start, setV),
3380
- make: (_a, b) => () => b,
3381
- duration: 0,
3382
- ease: "none",
3383
- at
3384
- });
3385
- }
3386
- if (fromV !== void 0 || toV !== void 0) {
3387
- tweenGradient(timeline, holder, fromV, toV, kfDuration(kf), kf.ease ?? "none", at, colorSpace, onChange, loopVars(kf));
3388
- }
3140
+ return m;
3141
+ }
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;
3389
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);
3390
3151
  }
3391
- var hasGradientKeys = (keyframes) => (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((b) => b && b.fillGradient !== void 0));
3392
-
3393
- // src/sequences/Text.ts
3394
- var STYLE_OPAQUE_KEYS = [
3395
- "fontFamily",
3396
- "fill",
3397
- "align",
3398
- "fontStyle",
3399
- "fontWeight",
3400
- "fontVariant",
3401
- "textBaseline",
3402
- "whiteSpace",
3403
- "lineJoin",
3404
- "join",
3405
- "cap",
3406
- "color",
3407
- "stroke"
3408
- ];
3409
- function formatValue(v, f) {
3410
- const fixed = v.toFixed(Math.max(0, Math.floor(f?.decimals ?? 0)));
3411
- const pad = Math.max(0, Math.floor(f?.pad ?? 0));
3412
- if (!f?.grouping && !pad) return fixed;
3413
- const [signed, frac] = fixed.split(".");
3414
- const sign = signed.startsWith("-") ? "-" : "";
3415
- let int = sign ? signed.slice(1) : signed;
3416
- if (pad) int = int.padStart(pad, "0");
3417
- if (f?.grouping) int = int.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
3418
- return sign + int + (frac !== void 0 ? "." + frac : "");
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
+ }
3171
+ }
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
+ };
3419
3182
  }
3420
- var TextSequence = class extends Sequence {
3421
- /** `value`: the number for the `{value}` placeholder; `visibleChars`: how many characters show (typewriter). Both tweened by keyframes. */
3422
- _counters = { value: 0, visibleChars: 0 };
3423
- /** The string now on the layer (before `{value}` / `visibleChars`): `text`, or the last `set: { text }`. */
3424
- _template = "";
3425
- /** `visibleChars` is in play (initial or animated): otherwise the whole text shows. */
3426
- _typed = false;
3427
- _lastText = "";
3428
- /** A gradient fill for the text (`fillGradient`, static or animated): rebuilt as a FillGradient on each change (the text is re-rasterised). */
3429
- _grad = null;
3430
- _fillGradient = null;
3431
- _applyGradient() {
3432
- const g = this._grad.grad;
3433
- const next = new FillGradient(gradientOptions({ angle: g.angle, stops: g.stops.map((s) => [s.offset, s.color]) }));
3434
- this.target.style.fill = next;
3435
- this._fillGradient?.destroy();
3436
- this._fillGradient = next;
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;
3437
3218
  }
3438
- destroy() {
3439
- super.destroy();
3440
- this._fillGradient?.destroy();
3441
- this._fillGradient = null;
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;
3442
3232
  }
3443
- /** A typewriter that has typed nothing yet: the layer is empty on purpose (inspect must not call that "no size"). */
3444
- get showsNothingYet() {
3445
- return this._typed && this._lastText === "" && this._template !== "";
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);
3446
3291
  }
3447
- /** Rewrite the displayed string from the template, the current `value` and `visibleChars`. */
3448
- _refreshText() {
3449
- let next = this._template.includes("{value}") ? this._template.split("{value}").join(formatValue(this._counters.value, this.spec.format)) : this._template;
3450
- if (this._typed) next = Array.from(next).slice(0, Math.max(0, Math.floor(this._counters.visibleChars))).join("");
3451
- if (next === this._lastText) return;
3452
- this._lastText = next;
3453
- this.target.text = next;
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
+ }
3454
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();
3455
3374
  async build() {
3456
- const baseStyle = {
3457
- fontFamily: "Arial",
3458
- fontSize: 36,
3459
- fill: "#ffffff",
3460
- align: "center"
3461
- };
3462
- const initialProps = this.spec.initial ?? {};
3463
- this._template = String(initialProps.text ?? this.spec.text ?? "");
3464
- const text = new Text({
3465
- text: this._template,
3466
- style: baseStyle,
3467
- label: this.spec.name
3468
- });
3469
- text.cullable = true;
3470
- this.target = text;
3471
- this.intrinsicWidth = text.width;
3472
- this.intrinsicHeight = text.height;
3375
+ const compW = this.parent?.width ?? this.root.width;
3376
+ this.compH = this.parent?.height ?? this.root.height;
3473
3377
  if (this.duration === void 0) {
3474
3378
  this.duration = this.parent?.duration ?? this.root.duration;
3475
3379
  }
3476
- if (this.spec.style) {
3477
- const scope2 = this.scope();
3478
- const resolved = normalizeProps(
3479
- this.spec.style,
3480
- scope2,
3481
- { skipKeys: STYLE_OPAQUE_KEYS }
3482
- );
3483
- const unset = Object.keys(resolved).filter((k) => resolved[k] === void 0);
3484
- 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`);
3485
- const outline = resolved.stroke;
3486
- if (outline && typeof outline === "object" && !("join" in outline) && !("lineJoin" in outline)) resolved.stroke = { ...outline, join: "round" };
3487
- for (const k of Object.keys(resolved)) {
3488
- if (unset.includes(k)) continue;
3489
- text.style[k] = resolved[k];
3490
- }
3491
- this.intrinsicWidth = text.width;
3492
- 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)`);
3493
3410
  }
3494
- validateGradientKeyframes(this.spec, describeLayer(this.spec), void 0, "text");
3495
- const gradientSpec = this.spec.fillGradient ?? initialProps.fillGradient;
3496
- if (gradientSpec) {
3497
- this._grad = { grad: gradStateFrom(gradientSpec) };
3498
- 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`);
3499
3429
  }
3500
- const scope = this.scope();
3501
- if (initialProps.value !== void 0) this._counters.value = resolveNumber(initialProps.value, scope);
3502
- this._typed = initialProps.visibleChars !== void 0 || animates(this.spec.keyframes, "visibleChars");
3503
- if (initialProps.visibleChars !== void 0) this._counters.visibleChars = resolveNumber(initialProps.visibleChars, scope);
3504
- const typed = this._typed;
3505
- this._typed = false;
3506
- this._refreshText();
3507
- if (this._lastText !== "") {
3508
- this.intrinsicWidth = text.width;
3509
- 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);
3510
3593
  }
3511
- this._typed = typed;
3512
- this._refreshText();
3513
- 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
+ };
3514
3617
  }
3515
- bindTimeline(timeline, offset = 0) {
3516
- if (!this.target) return;
3517
- const colorSpace = this.spec.colorSpace ?? "rgb";
3518
- const stripped = stripKeys2(this.spec, ["fill", "value", "text", "visibleChars", "fillGradient"]);
3519
- runSuperWithStrippedSpec(this, stripped, timeline, offset);
3520
- const keyframes = this.spec.keyframes ?? [];
3521
- const origin = offset + this.at;
3522
- bindFillKeyframes(timeline, this.target, keyframes, this.duration, origin, colorSpace);
3523
- if (this._grad) bindGradientKeyframes(timeline, this._grad, keyframes, this.duration, origin, colorSpace, () => this._applyGradient());
3524
- const refresh = () => this._refreshText();
3525
- bindCounterKeyframes(timeline, this._counters, "value", refresh, keyframes, this.duration, origin, this.scope());
3526
- bindCounterKeyframes(timeline, this._counters, "visibleChars", refresh, keyframes, this.duration, origin, this.scope());
3527
- bindTextKeyframes(timeline, keyframes, this.duration, origin, () => this._template, (t) => {
3528
- this._template = t;
3529
- refresh();
3530
- }, describeLayer(this.spec));
3618
+ window() {
3619
+ const start = this.absoluteStart ?? this.at;
3620
+ return { start, end: start + (this.duration ?? 0) };
3531
3621
  }
3532
3622
  };
3533
- function runSuperWithStrippedSpec(inst, strippedSpec, timeline, offset) {
3534
- const originalSpec = inst.spec;
3535
- inst.spec = strippedSpec;
3536
- try {
3537
- Sequence.prototype.bindTimeline.call(inst, timeline, offset);
3538
- } finally {
3539
- 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 [];
3540
3632
  }
3541
- }
3542
- function stripKeys2(spec, keys) {
3543
- return {
3544
- ...spec,
3545
- initial: stripProps(spec.initial, keys),
3546
- keyframes: spec.keyframes ? spec.keyframes.map((kf) => ({
3547
- ...kf,
3548
- set: stripProps(kf.set, keys),
3549
- to: stripProps(kf.to, keys),
3550
- from: stripProps(kf.from, keys)
3551
- })) : void 0
3552
- };
3553
- }
3554
- function stripProps(props, keys) {
3555
- if (!props) return props;
3556
- if (!keys.some((k) => k in props)) return props;
3557
- const out = {};
3558
- for (const k of Object.keys(props)) if (!keys.includes(k)) out[k] = props[k];
3559
- return out;
3560
- }
3561
- function resolveNumber(v, scope) {
3562
- if (typeof v === "number") return v;
3563
- const out = normalizeProps({ v }, scope).v;
3564
- return typeof out === "number" ? out : Number(out) || 0;
3565
- }
3566
- function bindCounterKeyframes(timeline, state, key, refresh, keyframes, parentDuration, origin, scope) {
3567
- for (const kf of keyframes) {
3568
- const setV = kf.set?.[key];
3569
- const fromV = kf.from?.[key];
3570
- const toV = kf.to?.[key];
3571
- if (setV === void 0 && fromV === void 0 && toV === void 0) continue;
3572
- const at = origin + resolveAt(kf.at, parentDuration);
3573
- const duration = kfDuration(kf);
3574
- const ease = kf.ease ?? "none";
3575
- const loop = loopVars(kf);
3576
- if (setV !== void 0) {
3577
- const v = resolveNumber(setV, scope);
3578
- revertibleSet(timeline, at, () => state[key], (n) => {
3579
- state[key] = n;
3580
- refresh();
3581
- }, 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;
3582
3639
  }
3583
- if (toV !== void 0 && fromV !== void 0) {
3584
- timeline.fromTo(
3585
- state,
3586
- { [key]: resolveNumber(fromV, scope) },
3587
- { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh },
3588
- at
3589
- );
3590
- } else if (toV !== void 0) {
3591
- timeline.to(state, { [key]: resolveNumber(toV, scope), duration, ease, ...loop, onUpdate: refresh }, at);
3592
- } else if (fromV !== void 0) {
3593
- 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;
3594
3643
  }
3595
- }
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;
3596
3659
  }
3597
- function bindTextKeyframes(timeline, keyframes, parentDuration, origin, read2, write2, who) {
3598
- for (const kf of keyframes) {
3599
- for (const [what, props] of [["to", kf.to], ["from", kf.from]]) {
3600
- if (props && props.text !== void 0) {
3601
- 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;
3602
3706
  }
3603
3707
  }
3604
- const t = kf.set?.text;
3605
- if (t === void 0) continue;
3606
- 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;
3607
3713
  }
3714
+ return out;
3608
3715
  }
3609
- function animates(keyframes, key) {
3610
- return (keyframes ?? []).some((kf) => [kf.set, kf.to, kf.from].some((p) => p && p[key] !== void 0));
3611
- }
3612
- function bindFillKeyframes(timeline, text, keyframes, parentDuration, offset, colorSpace) {
3613
- const state = { fill: text.style.fill };
3614
- const writeFill = () => {
3615
- 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;
3616
3736
  };
3617
- for (const kf of keyframes) {
3618
- const at = offset + resolveAt(kf.at, parentDuration);
3619
- const duration = kfDuration(kf);
3620
- const ease = kf.ease ?? "none";
3621
- const setFill = pickFill(kf.set);
3622
- const fromFill = pickFill(kf.from);
3623
- const toFill = pickFill(kf.to);
3624
- if (setFill !== void 0) {
3625
- revertibleSet(timeline, at, () => state.fill, (c) => {
3626
- state.fill = c;
3627
- writeFill();
3628
- }, 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;
3629
3779
  }
3630
- if (toFill !== void 0) {
3631
- tweenColor(
3632
- timeline,
3633
- state,
3634
- "fill",
3635
- fromFill,
3636
- toFill,
3637
- duration,
3638
- ease,
3639
- at,
3640
- colorSpace,
3641
- writeFill,
3642
- loopVars(kf)
3643
- );
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;
3644
3843
  }
3844
+ out[k] = props[k];
3645
3845
  }
3846
+ return changed ? out : props;
3646
3847
  }
3647
- function pickFill(props) {
3648
- if (!props) return void 0;
3649
- const v = props.fill;
3650
- if (typeof v === "string" || typeof v === "number") return v;
3651
- 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
+ };
3652
3855
  }
3653
3856
  var AudioSequence = class extends Sequence {
3654
3857
  _audioBuffer = null;
@@ -5403,10 +5606,15 @@ function resolveDim(v, scope) {
5403
5606
  var _CompositionSequence = null;
5404
5607
  async function getCompositionSequence() {
5405
5608
  if (!_CompositionSequence) {
5406
- _CompositionSequence = (await import('./Composition-DQCPJPFE.js')).CompositionSequence;
5609
+ _CompositionSequence = (await import('./Composition-RF7QOEXT.js')).CompositionSequence;
5407
5610
  }
5408
5611
  return _CompositionSequence;
5409
5612
  }
5613
+ var _SpansSequence = null;
5614
+ async function getSpansSequence() {
5615
+ if (!_SpansSequence) _SpansSequence = (await import('./Spans-SDXABOV6.js')).SpansSequence;
5616
+ return _SpansSequence;
5617
+ }
5410
5618
  var staticTypes = {
5411
5619
  image: ImageSequence,
5412
5620
  text: TextSequence,
@@ -5424,8 +5632,12 @@ function registerSequenceType(type, ctor) {
5424
5632
  }
5425
5633
  async function buildSequenceTree(specs, parent, root) {
5426
5634
  const out = [];
5427
- for (const spec of specs) {
5635
+ for (let spec of specs) {
5428
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
+ }
5429
5641
  if (!Cls && spec.type === "composition") {
5430
5642
  Cls = await getCompositionSequence();
5431
5643
  }
@@ -5445,6 +5657,6 @@ async function buildSequenceTree(specs, parent, root) {
5445
5657
  return out;
5446
5658
  }
5447
5659
 
5448
- 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, 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 };
5449
- //# sourceMappingURL=chunk-MMTESMJ5.js.map
5450
- //# sourceMappingURL=chunk-MMTESMJ5.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