@pixodesk/svg-animator-web 1.0.26 → 1.0.28

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.
package/dist/index.cjs CHANGED
@@ -172,114 +172,582 @@ var __objRest2 = (source, exclude) => {
172
172
  }
173
173
  return target;
174
174
  };
175
- function pathStr(path) {
176
- if (!path.length) return ".";
177
- let result = "";
178
- for (const seg of path) {
179
- if (seg.startsWith("[")) result += seg;
180
- else result += (result ? "." : "") + seg;
181
- }
182
- return result;
183
- }
184
- var Base = class {
185
- _canSanitize(raw) {
186
- return this.isValid(raw);
175
+ function bezierToSvgPath(path, forceCurves = false) {
176
+ var _a, _b, _c, _d;
177
+ const v = path.v;
178
+ const i = path.i;
179
+ const o = path.o;
180
+ const c = path.c;
181
+ if (!v.length) return "";
182
+ const d = [];
183
+ const len = v.length;
184
+ d.push("M" + v[0][0] + "," + v[0][1]);
185
+ for (let idx = 1; idx < len; idx++) {
186
+ const prevV = v[idx - 1];
187
+ const prevO = (_a = o == null ? void 0 : o[idx - 1]) != null ? _a : prevV;
188
+ const currI = (_b = i == null ? void 0 : i[idx]) != null ? _b : v[idx];
189
+ const currV = v[idx];
190
+ const isLine = !forceCurves && (prevO[0] === prevV[0] && prevO[1] === prevV[1]) && (currI[0] === currV[0] && currI[1] === currV[1]);
191
+ if (isLine) {
192
+ d.push("L" + currV[0] + "," + currV[1]);
193
+ } else {
194
+ d.push("C" + prevO[0] + "," + prevO[1] + "," + currI[0] + "," + currI[1] + "," + currV[0] + "," + currV[1]);
195
+ }
187
196
  }
188
- optional() {
189
- return new Optional(this);
197
+ if (c && len > 0) {
198
+ const lastV = v[len - 1];
199
+ const lastO = (_c = o == null ? void 0 : o[len - 1]) != null ? _c : lastV;
200
+ const firstI = (_d = i == null ? void 0 : i[0]) != null ? _d : v[0];
201
+ const firstV = v[0];
202
+ const isLine = !forceCurves && (lastO[0] === lastV[0] && lastO[1] === lastV[1]) && (firstI[0] === firstV[0] && firstI[1] === firstV[1]);
203
+ if (!isLine) {
204
+ d.push("C" + lastO[0] + "," + lastO[1] + "," + firstI[0] + "," + firstI[1] + "," + firstV[0] + "," + firstV[1]);
205
+ }
206
+ d.push("z");
190
207
  }
191
- };
192
- var Optional = class extends Base {
193
- constructor(inner) {
194
- super();
195
- this.inner = inner;
196
- this._default = void 0;
208
+ return d.join("");
209
+ }
210
+ function interpolateNum(a, b, t) {
211
+ return a + (b - a) * t;
212
+ }
213
+ function interpolateVec(a, b, t) {
214
+ const res = [];
215
+ const count = Math.max(a.length, b.length);
216
+ for (let i = 0; i < count; i++) {
217
+ res[i] = interpolateNum(a[i] || 0, b[i] || 0, t);
197
218
  }
198
- sanitize(raw) {
199
- if (raw === void 0 || raw === null) return void 0;
200
- return this.inner._canSanitize(raw) ? this.inner.sanitize(raw) : void 0;
219
+ return res;
220
+ }
221
+ function interpolateColor(a, b, t) {
222
+ return [
223
+ interpolateNum(a[0] || 0, b[0] || 0, t),
224
+ interpolateNum(a[1] || 0, b[1] || 0, t),
225
+ interpolateNum(a[2] || 0, b[2] || 0, t),
226
+ interpolateNum(a[3] === void 0 ? 1 : a[3], b[3] === void 0 ? 1 : b[3], t)
227
+ ];
228
+ }
229
+ function interpolateBeziers(paths1, paths2, progress) {
230
+ const count = Math.max(paths1.length, paths2.length);
231
+ const res = [];
232
+ for (let i = 0; i < count; i++) {
233
+ res.push(interpolateBezier(paths1[i], paths2[i], progress));
201
234
  }
202
- isValid(raw, ctx, path) {
203
- if (raw === void 0 || raw === null) return true;
204
- return this.inner.isValid(raw, ctx, path);
235
+ return res;
236
+ }
237
+ function interpolateBezier(path1, path2, progress) {
238
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
239
+ if (!path1 || !path2) return path1 || path2 || { v: [] };
240
+ const t = Math.min(Math.max(progress, 0), 1);
241
+ const len = Math.min(path1.v.length, path2.v.length);
242
+ const v = [];
243
+ const i = [];
244
+ const o = [];
245
+ for (let idx = 0; idx < len; idx++) {
246
+ const v1 = path1.v[idx];
247
+ const v2 = path2.v[idx];
248
+ v.push(interpolateVec(v1, v2, t));
249
+ const i1 = (_b = (_a = path1.i) == null ? void 0 : _a[idx]) != null ? _b : v1;
250
+ const i2 = (_d = (_c = path2.i) == null ? void 0 : _c[idx]) != null ? _d : v2;
251
+ i.push(interpolateVec(i1, i2, t));
252
+ const o1 = (_f = (_e = path1.o) == null ? void 0 : _e[idx]) != null ? _f : v1;
253
+ const o2 = (_h = (_g = path2.o) == null ? void 0 : _g[idx]) != null ? _h : v2;
254
+ o.push(interpolateVec(o1, o2, t));
205
255
  }
206
- _canSanitize(raw) {
207
- return raw === void 0 || raw === null || this.inner._canSanitize(raw);
256
+ return { v, i: i.length ? i : void 0, o: o.length ? o : void 0, c: (_i = path1.c) != null ? _i : path2.c };
257
+ }
258
+ function remap(value, inMin, inMax, outMin, outMax) {
259
+ if (inMax === inMin) return outMin;
260
+ const t = (value - inMin) / (inMax - inMin);
261
+ return outMin + t * (outMax - outMin);
262
+ }
263
+ function solveCubicBezierX(p1x, p2x, x) {
264
+ if (x <= 0) return 0;
265
+ if (x >= 1) return 1;
266
+ const cx = 3 * p1x;
267
+ const bx = 3 * (p2x - p1x) - cx;
268
+ const ax = 1 - cx - bx;
269
+ function sampleX(t) {
270
+ return ((ax * t + bx) * t + cx) * t;
208
271
  }
209
- };
210
- var Str = class extends Base {
211
- constructor(_default = "") {
212
- super();
213
- this._default = _default;
272
+ function sampleDX(t) {
273
+ return (3 * ax * t + 2 * bx) * t + cx;
214
274
  }
215
- sanitize(raw) {
216
- return typeof raw === "string" ? raw : this._default;
275
+ let t2 = x;
276
+ let t0 = 0;
277
+ let t1 = 1;
278
+ for (let i = 0; i < 8; i++) {
279
+ const x2 = sampleX(t2) - x;
280
+ if (Math.abs(x2) < 1e-6) return t2;
281
+ const d2 = sampleDX(t2);
282
+ if (Math.abs(d2) < 1e-6) break;
283
+ t2 -= x2 / d2;
217
284
  }
218
- isValid(raw, ctx, path) {
219
- if (typeof raw === "string") return true;
220
- ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected string, got " + typeof raw);
221
- return false;
285
+ t2 = x;
286
+ while (t0 < t1) {
287
+ const x2 = sampleX(t2);
288
+ if (Math.abs(x2 - x) < 1e-6) return t2;
289
+ if (x > x2) t0 = t2;
290
+ else t1 = t2;
291
+ t2 = (t1 + t0) / 2;
222
292
  }
223
- };
224
- var Num = class extends Base {
225
- constructor(_default = 0) {
226
- super();
227
- this._default = _default;
293
+ return t2;
294
+ }
295
+ function cubicBezier(easing) {
296
+ const [p1x, p1y, p2x, p2y] = easing;
297
+ const cy = 3 * p1y;
298
+ const by = 3 * (p2y - p1y) - cy;
299
+ const ay = 1 - cy - by;
300
+ function sampleCurveY(t) {
301
+ return ((ay * t + by) * t + cy) * t;
228
302
  }
229
- sanitize(raw) {
230
- return typeof raw === "number" && isFinite(raw) ? raw : this._default;
303
+ return function(x) {
304
+ return sampleCurveY(solveCubicBezierX(p1x, p2x, x));
305
+ };
306
+ }
307
+ function lerp2(a, b, t) {
308
+ return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
309
+ }
310
+ function subdivideCubicBezier(p0, p1, p2, p3, t) {
311
+ const q0 = lerp2(p0, p1, t);
312
+ const q1 = lerp2(p1, p2, t);
313
+ const q2 = lerp2(p2, p3, t);
314
+ const r0 = lerp2(q0, q1, t);
315
+ const r1 = lerp2(q1, q2, t);
316
+ const s = lerp2(r0, r1, t);
317
+ return {
318
+ left: [p0, q0, r0, s],
319
+ right: [s, r1, q2, p3]
320
+ };
321
+ }
322
+ function splitEasing(easing, xFraction) {
323
+ if (!easing) return { left: void 0, right: void 0 };
324
+ if (xFraction <= 0) return { left: void 0, right: easing };
325
+ if (xFraction >= 1) return { left: easing, right: void 0 };
326
+ const [x1, y1, x2, y2] = easing;
327
+ const t = solveCubicBezierX(x1, x2, xFraction);
328
+ const p0 = [0, 0];
329
+ const p1 = [x1, y1];
330
+ const p2 = [x2, y2];
331
+ const p3 = [1, 1];
332
+ const { left, right } = subdivideCubicBezier(p0, p1, p2, p3, t);
333
+ const sx = left[3][0];
334
+ const sy = left[3][1];
335
+ let leftEasing;
336
+ if (sx > 1e-9 && Math.abs(sy) > 1e-9) {
337
+ leftEasing = [
338
+ left[1][0] / sx,
339
+ left[1][1] / sy,
340
+ left[2][0] / sx,
341
+ left[2][1] / sy
342
+ ];
231
343
  }
232
- isValid(raw, ctx, path) {
233
- if (typeof raw === "number" && isFinite(raw)) return true;
234
- ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected finite number, got " + JSON.stringify(raw));
235
- return false;
344
+ let rightEasing;
345
+ const rx = 1 - sx;
346
+ const ry = 1 - sy;
347
+ if (rx > 1e-9 && Math.abs(ry) > 1e-9) {
348
+ rightEasing = [
349
+ (right[1][0] - sx) / rx,
350
+ (right[1][1] - sy) / ry,
351
+ (right[2][0] - sx) / rx,
352
+ (right[2][1] - sy) / ry
353
+ ];
236
354
  }
237
- };
238
- var Bool = class extends Base {
239
- constructor(_default = false) {
240
- super();
241
- this._default = _default;
355
+ return { left: leftEasing, right: rightEasing };
356
+ }
357
+ function reverseEasing(easing) {
358
+ if (!easing) return void 0;
359
+ return [1 - easing[2], 1 - easing[3], 1 - easing[0], 1 - easing[1]];
360
+ }
361
+ function toRGBA(color) {
362
+ const r = Math.round(color[0] * 255);
363
+ const g = Math.round(color[1] * 255);
364
+ const b = Math.round(color[2] * 255);
365
+ return color.length === 4 ? "rgba(" + r + "," + g + "," + b + "," + color[3] + ")" : "rgb(" + r + "," + g + "," + b + ")";
366
+ }
367
+ function parseRgba(s) {
368
+ var _a;
369
+ const inner = (_a = s.match(/rgba?\((.*)\)/)) == null ? void 0 : _a[1];
370
+ if (!inner) throw new Error("Invalid rgb/rgba format");
371
+ const parts = inner.split(",").map((v) => +v.trim());
372
+ return [parts[0] / 255, parts[1] / 255, parts[2] / 255, ...parts[3] !== void 0 ? [parts[3]] : []];
373
+ }
374
+ function parseHex(s) {
375
+ const hex = s.slice(1);
376
+ const isShort = hex.length <= 4;
377
+ const r = isShort ? hex[0] + hex[0] : hex.slice(0, 2);
378
+ const g = isShort ? hex[1] + hex[1] : hex.slice(2, 4);
379
+ const b = isShort ? hex[2] + hex[2] : hex.slice(4, 6);
380
+ const a = hex.length === 4 ? hex[3] + hex[3] : hex.length === 8 ? hex.slice(6, 8) : null;
381
+ const result = [
382
+ parseInt(r, 16) / 255,
383
+ parseInt(g, 16) / 255,
384
+ parseInt(b, 16) / 255
385
+ ];
386
+ if (a !== null) {
387
+ result.push(parseInt(a, 16) / 255);
242
388
  }
243
- sanitize(raw) {
244
- return typeof raw === "boolean" ? raw : this._default;
389
+ return result;
390
+ }
391
+ function parseColor(s) {
392
+ if (!s) return void 0;
393
+ if (Array.isArray(s)) return s;
394
+ if (typeof s !== "string") return void 0;
395
+ if (s.startsWith("#")) {
396
+ return parseHex(s);
397
+ } else if (s.startsWith("rgb")) {
398
+ return parseRgba(s);
399
+ } else {
400
+ console.warn("Unsupported color format: " + s);
245
401
  }
246
- isValid(raw, ctx, path) {
247
- if (typeof raw === "boolean") return true;
248
- ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected boolean, got " + typeof raw);
249
- return false;
402
+ return void 0;
403
+ }
404
+ var COLOUR_ATTR_NAMES = /* @__PURE__ */ new Set(["color", "fill", "flood-color", "lighting-color", "stop-color", "stroke"]);
405
+ var TRANSFORM_FN_NAMES = /* @__PURE__ */ new Set(["translate", "rotate", "scale", "skew"]);
406
+ var PCT_BASED_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
407
+ function composeTransformParts(parts, opts) {
408
+ var _a;
409
+ if (!parts) return "";
410
+ const withUnits = (_a = opts == null ? void 0 : opts.withUnits) != null ? _a : true;
411
+ const segs = [];
412
+ const t = parts.translate;
413
+ const o = parts.origin;
414
+ const r = parts.rotate;
415
+ const k = parts.skew;
416
+ const s = parts.scale;
417
+ const tu = withUnits ? "px" : "";
418
+ const ru = withUnits ? "deg" : "";
419
+ if (t) segs.push("translate(" + t[0] + tu + "," + t[1] + tu + ")");
420
+ if (o) segs.push("translate(" + o[0] + tu + "," + o[1] + tu + ")");
421
+ if (r !== void 0 && r !== null) segs.push("rotate(" + r + ru + ")");
422
+ if (k !== void 0 && k !== null) segs.push("skewX(" + k + ru + ")");
423
+ if (s) segs.push("scale(" + s[0] + "," + s[1] + ")");
424
+ if (o) segs.push("translate(" + -o[0] + tu + "," + -o[1] + tu + ")");
425
+ return segs.join("");
426
+ }
427
+ var STYLE_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
428
+ var DEFAULT_DURATION_MS = 1e3;
429
+ function kebabToCamelCaseWord(kebab) {
430
+ return kebab.includes("-") ? kebab.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()) : kebab;
431
+ }
432
+ function isCamelCaseWord(word) {
433
+ return !word.includes("-") && /[a-z][A-Z]/.test(word);
434
+ }
435
+ var SVG_CAMEL_CASE_ATTRS = /* @__PURE__ */ new Set([
436
+ // Transform/positioning
437
+ "viewBox",
438
+ "preserveAspectRatio",
439
+ // Gradient
440
+ "gradientUnits",
441
+ "gradientTransform",
442
+ "spreadMethod",
443
+ // Pattern
444
+ "patternUnits",
445
+ "patternContentUnits",
446
+ "patternTransform",
447
+ // Clipping/masking
448
+ "clipPathUnits",
449
+ "maskUnits",
450
+ "maskContentUnits",
451
+ // Marker (SVG spec keeps these camelCase, like viewBox)
452
+ "markerUnits",
453
+ "markerWidth",
454
+ "markerHeight",
455
+ "refX",
456
+ "refY",
457
+ // Text
458
+ "textLength",
459
+ "lengthAdjust",
460
+ "startOffset",
461
+ // Filter
462
+ "filterUnits",
463
+ "primitiveUnits",
464
+ "tableValues",
465
+ // feFuncR/G/B/A transfer table (type="table")
466
+ "stdDeviation",
467
+ "baseFrequency",
468
+ "numOctaves",
469
+ "surfaceScale",
470
+ "diffuseConstant",
471
+ "specularConstant",
472
+ "specularExponent",
473
+ "kernelMatrix",
474
+ "kernelUnitLength",
475
+ "edgeMode",
476
+ "preserveAlpha",
477
+ "targetX",
478
+ "targetY"
479
+ // // Animation
480
+ // 'attributeName',
481
+ // 'attributeType',
482
+ // 'calcMode',
483
+ // 'keyTimes',
484
+ // 'keySplines',
485
+ // 'repeatCount',
486
+ // 'repeatDur'
487
+ ]);
488
+ function camelCaseToKebabWordIfNeeded(camel) {
489
+ return SVG_CAMEL_CASE_ATTRS.has(camel) ? camel : camel.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
490
+ }
491
+ function clamp(value, min, max) {
492
+ return Math.max(min, Math.min(value, max));
493
+ }
494
+ function bezier2D_pointAt(P0, P1, P2, P3, t) {
495
+ if (t <= 0) return [P0[0], P0[1]];
496
+ if (t >= 1) return [P3[0], P3[1]];
497
+ const u = 1 - t;
498
+ const u2 = u * u;
499
+ const u3 = u2 * u;
500
+ const t2 = t * t;
501
+ const t3 = t2 * t;
502
+ const w0 = u3;
503
+ const w1 = 3 * t * u2;
504
+ const w2 = 3 * t2 * u;
505
+ const w3 = t3;
506
+ return [
507
+ w0 * P0[0] + w1 * P1[0] + w2 * P2[0] + w3 * P3[0],
508
+ w0 * P0[1] + w1 * P1[1] + w2 * P2[1] + w3 * P3[1]
509
+ ];
510
+ }
511
+ var BEZIER_T_NUDGE = 1e-4;
512
+ function bezier2D_derivativeAt(P0, P1, P2, P3, t) {
513
+ const result = _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t);
514
+ if (result[0] === 0 && result[1] === 0) {
515
+ const nudgedT = t < 0.5 ? t + BEZIER_T_NUDGE : t - BEZIER_T_NUDGE;
516
+ return _bezier2D_derivativeAtRaw(P0, P1, P2, P3, nudgedT);
517
+ }
518
+ return result;
519
+ }
520
+ function _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t) {
521
+ const u = 1 - t;
522
+ const a = 3 * u * u;
523
+ const b = 6 * t * u;
524
+ const c = 3 * t * t;
525
+ return [
526
+ a * (P1[0] - P0[0]) + b * (P2[0] - P1[0]) + c * (P3[0] - P2[0]),
527
+ a * (P1[1] - P0[1]) + b * (P2[1] - P1[1]) + c * (P3[1] - P2[1])
528
+ ];
529
+ }
530
+ function bezier2D_arcLengthLUT(P0, P1, P2, P3, steps = 100) {
531
+ const n = steps + 1;
532
+ const ts = new Float64Array(n);
533
+ const ds = new Float64Array(n);
534
+ let prev = bezier2D_pointAt(P0, P1, P2, P3, 0);
535
+ ts[0] = 0;
536
+ ds[0] = 0;
537
+ let cum = 0;
538
+ for (let i = 1; i < n; i++) {
539
+ const t = i / steps;
540
+ const cur = bezier2D_pointAt(P0, P1, P2, P3, t);
541
+ const dx = cur[0] - prev[0];
542
+ const dy = cur[1] - prev[1];
543
+ cum += Math.sqrt(dx * dx + dy * dy);
544
+ ts[i] = t;
545
+ ds[i] = cum;
546
+ prev = cur;
547
+ }
548
+ return { ts, ds };
549
+ }
550
+ function bezier2D_tForDistance(lut, distance) {
551
+ const { ts, ds } = lut;
552
+ const last = ds.length - 1;
553
+ if (distance <= 0) return ts[0];
554
+ if (distance >= ds[last]) return ts[last];
555
+ let lo = 1;
556
+ let hi = last;
557
+ while (lo < hi) {
558
+ const mid = lo + hi >>> 1;
559
+ if (ds[mid] < distance) lo = mid + 1;
560
+ else hi = mid;
561
+ }
562
+ const dPrev = ds[hi - 1];
563
+ const dCur = ds[hi];
564
+ const span = dCur - dPrev;
565
+ const frac = span > 0 ? (distance - dPrev) / span : 0;
566
+ return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);
567
+ }
568
+ function bezier2D_arcAtT(lut, t) {
569
+ const { ts, ds } = lut;
570
+ const last = ts.length - 1;
571
+ if (t <= ts[0]) return ds[0];
572
+ if (t >= ts[last]) return ds[last];
573
+ let lo = 1, hi = last;
574
+ while (lo < hi) {
575
+ const mid = lo + hi >>> 1;
576
+ if (ts[mid] < t) lo = mid + 1;
577
+ else hi = mid;
578
+ }
579
+ const tPrev = ts[hi - 1];
580
+ const span = ts[hi] - tPrev;
581
+ const frac = span > 0 ? (t - tPrev) / span : 0;
582
+ return ds[hi - 1] + frac * (ds[hi] - ds[hi - 1]);
583
+ }
584
+ function invertEasing(easing) {
585
+ if (!easing) return (y) => y;
586
+ const flipped = [easing[1], easing[0], easing[3], easing[2]];
587
+ return cubicBezier(flipped);
588
+ }
589
+ function isScrollTimeline(config) {
590
+ return (config == null ? void 0 : config.timelineSource) === "scroll";
591
+ }
592
+ function scrollTotalDurationMs(config) {
593
+ const duration = typeof (config == null ? void 0 : config.duration) === "number" && config.duration > 0 ? config.duration : DEFAULT_DURATION_MS;
594
+ const iterations = typeof (config == null ? void 0 : config.iterations) === "number" && config.iterations > 0 ? config.iterations : 1;
595
+ return duration * iterations;
596
+ }
597
+ function scrollPhaseInterval(phase, subjectSize, scrollportSize) {
598
+ const s = subjectSize, vp = scrollportSize;
599
+ switch (phase) {
600
+ case "cover":
601
+ return [0, s + vp];
602
+ case "entry":
603
+ return [0, Math.min(s, vp)];
604
+ case "contain":
605
+ return [Math.min(s, vp), Math.max(s, vp)];
606
+ case "exit":
607
+ return [Math.max(s, vp), s + vp];
608
+ case "entry-crossing":
609
+ return [0, s];
610
+ case "exit-crossing":
611
+ return [vp, s + vp];
612
+ }
613
+ }
614
+ var DEFAULT_PHASE = "cover";
615
+ function resolveRangePointU(point, defaultFraction, subjectSize, scrollportSize) {
616
+ var _a;
617
+ const [u0, u1] = scrollPhaseInterval((_a = point == null ? void 0 : point.phase) != null ? _a : DEFAULT_PHASE, subjectSize, scrollportSize);
618
+ const fraction = typeof (point == null ? void 0 : point.fraction) === "number" ? point.fraction : defaultFraction;
619
+ return u0 + fraction * (u1 - u0);
620
+ }
621
+ function scrollViewProgress(subjectStart, subjectSize, scrollportSize, range) {
622
+ const u = scrollportSize - subjectStart;
623
+ const uStart = resolveRangePointU(range == null ? void 0 : range.start, 0, subjectSize, scrollportSize);
624
+ const uEnd = resolveRangePointU(range == null ? void 0 : range.end, 1, subjectSize, scrollportSize);
625
+ if (uEnd <= uStart) return u >= uEnd ? 1 : 0;
626
+ return clamp((u - uStart) / (uEnd - uStart), 0, 1);
627
+ }
628
+ function scrollOffsetProgress(offset, maxOffset, range) {
629
+ var _a, _b;
630
+ const raw = maxOffset > 0 ? clamp(offset / maxOffset, 0, 1) : 1;
631
+ const start = typeof ((_a = range == null ? void 0 : range.start) == null ? void 0 : _a.fraction) === "number" ? range.start.fraction : 0;
632
+ const end = typeof ((_b = range == null ? void 0 : range.end) == null ? void 0 : _b.fraction) === "number" ? range.end.fraction : 1;
633
+ if (end <= start) return raw >= end ? 1 : 0;
634
+ return clamp((raw - start) / (end - start), 0, 1);
635
+ }
636
+ function scrollResolveAxis(axis, writingMode) {
637
+ const a = axis != null ? axis : "block";
638
+ if (a === "x" || a === "y") return a;
639
+ const vertical = !!writingMode && writingMode.startsWith("vertical");
640
+ if (a === "inline") return vertical ? "y" : "x";
641
+ return vertical ? "x" : "y";
642
+ }
643
+ function pathStr(path) {
644
+ if (!path.length) return ".";
645
+ let result = "";
646
+ for (const seg of path) {
647
+ if (seg.startsWith("[")) result += seg;
648
+ else result += (result ? "." : "") + seg;
649
+ }
650
+ return result;
651
+ }
652
+ var Base = class {
653
+ _canSanitize(raw) {
654
+ return this.isValid(raw);
655
+ }
656
+ optional() {
657
+ return new Optional(this);
250
658
  }
251
659
  };
252
- var Literal = class extends Base {
253
- constructor(value) {
660
+ var Optional = class extends Base {
661
+ constructor(inner) {
254
662
  super();
255
- this.value = value;
256
- this._default = value;
663
+ this.inner = inner;
664
+ this._default = void 0;
257
665
  }
258
666
  sanitize(raw) {
259
- return raw === this.value ? this.value : this._default;
667
+ if (raw === void 0 || raw === null) return void 0;
668
+ return this.inner._canSanitize(raw) ? this.inner.sanitize(raw) : void 0;
260
669
  }
261
670
  isValid(raw, ctx, path) {
262
- if (raw === this.value) return true;
263
- ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected " + JSON.stringify(this.value) + ", got " + JSON.stringify(raw));
264
- return false;
671
+ if (raw === void 0 || raw === null) return true;
672
+ return this.inner.isValid(raw, ctx, path);
673
+ }
674
+ _canSanitize(raw) {
675
+ return raw === void 0 || raw === null || this.inner._canSanitize(raw);
265
676
  }
266
677
  };
267
- var Enum = class extends Base {
268
- constructor(values, defaultVal) {
678
+ var Str = class extends Base {
679
+ constructor(_default = "") {
269
680
  super();
270
- this.values = values;
271
- this._default = defaultVal != null ? defaultVal : values[0];
681
+ this._default = _default;
272
682
  }
273
683
  sanitize(raw) {
274
- return this.values.includes(raw) ? raw : this._default;
684
+ return typeof raw === "string" ? raw : this._default;
275
685
  }
276
686
  isValid(raw, ctx, path) {
277
- if (this.values.includes(raw)) return true;
278
- ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected one of " + this.values.map((v) => JSON.stringify(v)).join(" | ") + ", got " + JSON.stringify(raw));
687
+ if (typeof raw === "string") return true;
688
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected string, got " + typeof raw);
279
689
  return false;
280
690
  }
281
691
  };
282
- var Union = class extends Base {
692
+ var Num = class extends Base {
693
+ constructor(_default = 0) {
694
+ super();
695
+ this._default = _default;
696
+ }
697
+ sanitize(raw) {
698
+ return typeof raw === "number" && isFinite(raw) ? raw : this._default;
699
+ }
700
+ isValid(raw, ctx, path) {
701
+ if (typeof raw === "number" && isFinite(raw)) return true;
702
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected finite number, got " + JSON.stringify(raw));
703
+ return false;
704
+ }
705
+ };
706
+ var Bool = class extends Base {
707
+ constructor(_default = false) {
708
+ super();
709
+ this._default = _default;
710
+ }
711
+ sanitize(raw) {
712
+ return typeof raw === "boolean" ? raw : this._default;
713
+ }
714
+ isValid(raw, ctx, path) {
715
+ if (typeof raw === "boolean") return true;
716
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected boolean, got " + typeof raw);
717
+ return false;
718
+ }
719
+ };
720
+ var Literal = class extends Base {
721
+ constructor(value) {
722
+ super();
723
+ this.value = value;
724
+ this._default = value;
725
+ }
726
+ sanitize(raw) {
727
+ return raw === this.value ? this.value : this._default;
728
+ }
729
+ isValid(raw, ctx, path) {
730
+ if (raw === this.value) return true;
731
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected " + JSON.stringify(this.value) + ", got " + JSON.stringify(raw));
732
+ return false;
733
+ }
734
+ };
735
+ var Enum = class extends Base {
736
+ constructor(values, defaultVal) {
737
+ super();
738
+ this.values = values;
739
+ this._default = defaultVal != null ? defaultVal : values[0];
740
+ }
741
+ sanitize(raw) {
742
+ return this.values.includes(raw) ? raw : this._default;
743
+ }
744
+ isValid(raw, ctx, path) {
745
+ if (this.values.includes(raw)) return true;
746
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected one of " + this.values.map((v) => JSON.stringify(v)).join(" | ") + ", got " + JSON.stringify(raw));
747
+ return false;
748
+ }
749
+ };
750
+ var Union = class extends Base {
283
751
  constructor(schemas, defaultVal) {
284
752
  super();
285
753
  this.schemas = schemas;
@@ -834,6 +1302,28 @@ var PxDefsSchema = implementsInterface()(px.object({
834
1302
  styles: px.record(px.any()).optional(),
835
1303
  glyphs: px.record(PxGlyphFontSchema).optional()
836
1304
  }));
1305
+ var PX_SCROLL_PHASES = ["cover", "contain", "entry", "exit", "entry-crossing", "exit-crossing"];
1306
+ var PxScrollRangePointSchema = implementsInterface()(px.object({
1307
+ phase: px.enum(PX_SCROLL_PHASES).optional(),
1308
+ fraction: px.number().optional()
1309
+ }));
1310
+ var PxScrollRangeSchema = px.object({
1311
+ start: PxScrollRangePointSchema.optional(),
1312
+ end: PxScrollRangePointSchema.optional()
1313
+ });
1314
+ var PxScrollSchema = implementsInterface()(px.object({
1315
+ driver: px.enum(["custom", "native"]).optional(),
1316
+ kind: px.enum(["view", "scroll"]).optional(),
1317
+ axis: px.enum(["block", "inline", "x", "y"]).optional(),
1318
+ source: px.enum(["nearest", "root"]).optional(),
1319
+ // Free-form: the two keywords `parent`/`scroller` plus any CSS selector.
1320
+ subject: px.string().optional(),
1321
+ smoothing: px.number().optional(),
1322
+ pin: px.boolean().optional(),
1323
+ pinTop: px.number().optional(),
1324
+ pinDistance: px.number().optional(),
1325
+ range: PxScrollRangeSchema.optional()
1326
+ }));
837
1327
  var PxAnimatorConfigSchema = implementsInterface()(px.object({
838
1328
  mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.waapi, PxAnimatorMode.frames]).optional(),
839
1329
  duration: px.number().optional(),
@@ -849,6 +1339,7 @@ var PxAnimatorConfigSchema = implementsInterface()(px.object({
849
1339
  definitions: PxDefsSchema.optional(),
850
1340
  animateById: px.record(PxElementAnimationSchema).optional(),
851
1341
  timelineSource: px.string().optional(),
1342
+ scroll: PxScrollSchema.optional(),
852
1343
  debugInstName: px.string().optional()
853
1344
  }));
854
1345
  var PxBindingSchema = implementsInterface()(px.object({
@@ -859,600 +1350,186 @@ var PxAttrValueSchema = px.union([
859
1350
  px.string(),
860
1351
  px.number(),
861
1352
  px.array(px.number()),
862
- // Structured static — `{value: …}` (read-accepted transitional spelling, S1).
863
- // `defined`, not `any`: the KEY's presence is what identifies this branch (V6).
864
- px.object({ value: px.defined() }),
865
- // Bare transform parts record — the canonical static `transform` on the wire (T2).
866
- PxTransformPartsSchema
867
- ]);
868
- var PxAnimatableNumberSchema = px.union([
869
- px.number(),
870
- px.object({ value: px.number() }),
871
- PxPropertyAnimationSchema
872
- ]);
873
- var PxAnimatableVec2Schema = px.union([
874
- px.tuple([px.number(), px.number()]),
875
- px.object({ value: px.tuple([px.number(), px.number()]) }),
876
- PxPropertyAnimationSchema
877
- ]);
878
- var PxAnimatableStringSchema = px.union([
879
- px.string(),
880
- px.object({ value: px.string() }),
881
- PxPropertyAnimationSchema
882
- ]);
883
- var PxTransformByEffectSchema = implementsInterface()(px.object({
884
- translate: PxAnimatableVec2Schema.optional(),
885
- rotate: PxAnimatableNumberSchema.optional(),
886
- scale: PxAnimatableVec2Schema.optional(),
887
- skew: PxAnimatableNumberSchema.optional(),
888
- origin: PxAnimatableVec2Schema.optional()
889
- }));
890
- var PxRepeaterEffectSchema = implementsInterface()(px.object({
891
- // STATIC config, not a channel (V2/SCHEMA-DESIGN R5): the copy COUNT is read
892
- // once at expansion time and never sampled — plain number, no `keyframes`.
893
- copies: px.number().optional(),
894
- translate: PxAnimatableVec2Schema.optional(),
895
- rotate: PxAnimatableNumberSchema.optional(),
896
- skew: PxAnimatableNumberSchema.optional(),
897
- scale: PxAnimatableVec2Schema.optional(),
898
- origin: PxAnimatableVec2Schema.optional()
899
- }));
900
- var PxMaskedByEffectSchema = implementsInterface()(px.object({
901
- sourceId: px.string().optional(),
902
- maskType: px.enum([PxMaskType.luminance, PxMaskType.alpha]).optional(),
903
- maskUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
904
- maskContentUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
905
- x: px.number().optional(),
906
- y: px.number().optional(),
907
- width: px.number().optional(),
908
- height: px.number().optional()
909
- }));
910
- var PxClipPathEffectSchema = implementsInterface()(px.object({
911
- d: PxAnimatableStringSchema.optional(),
912
- animate: PxPropertyAnimationSchema.optional()
913
- }));
914
- var PxTrimPathEffectSchema = implementsInterface()(px.object({
915
- offset: PxAnimatableNumberSchema.optional(),
916
- range: PxAnimatableVec2Schema.optional(),
917
- subPaths: px.enum([PxTrimSubPaths.separate, PxTrimSubPaths.combined]).optional()
918
- }));
919
- var PxRetimeEffectSchema = implementsInterface()(px.object({
920
- sourceId: px.string().optional(),
921
- start: px.number().optional(),
922
- stretch: px.number().optional(),
923
- timeCrop: px.tuple([px.number(), px.number()]).optional()
924
- }));
925
- var PxCloneEffectSchema = implementsInterface()(px.object({
926
- // Contextual kind — the `type` convention, see `PxNodeBase.type`.
927
- type: px.enum([PxCloneType.content]).optional(),
928
- sourceId: px.string().optional(),
929
- retime: PxRetimeEffectSchema.optional()
930
- }));
931
- var PxGradientStopSchema = implementsInterface()(px.object({
932
- offset: px.number(),
933
- color: px.string()
934
- }));
935
- var PxAnimatableGradientStopsSchema = px.union([
936
- px.array(PxGradientStopSchema),
937
- px.object({ value: px.array(PxGradientStopSchema) }),
938
- PxPropertyAnimationSchema
939
- ]);
940
- var PxFillGradientEffectSchema = implementsInterface()(px.object({
941
- // Contextual kind — the `type` convention, see `PxNodeBase.type`.
942
- type: px.enum([PxGradientType.linear, PxGradientType.radial]),
943
- p1: PxAnimatableVec2Schema.optional(),
944
- p2: PxAnimatableVec2Schema.optional(),
945
- c: PxAnimatableVec2Schema.optional(),
946
- r: PxAnimatableNumberSchema.optional(),
947
- fp: PxAnimatableVec2Schema.optional(),
948
- stops: PxAnimatableGradientStopsSchema.optional(),
949
- gradientUnits: px.enum([PxGradientUnits.userSpaceOnUse, PxGradientUnits.objectBoundingBox]).optional(),
950
- spreadMethod: px.enum([PxGradientSpreadMethod.pad, PxGradientSpreadMethod.reflect, PxGradientSpreadMethod.repeat]).optional(),
951
- gradientTransform: px.string().optional()
952
- }));
953
- var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
954
- var PxTextPathEffectSchema = implementsInterface()(px.object({
955
- path: px.string(),
956
- pathOverflow: px.enum([PxPathOverflow.clip, PxPathOverflow.extend]).optional(),
957
- lengthAdjust: px.enum([PxLengthAdjust.spacing, PxLengthAdjust.spacingAndGlyphs]).optional(),
958
- method: px.enum([PxTextPathMethod.align, PxTextPathMethod.stretch]).optional(),
959
- spacing: px.enum([PxTextPathSpacing.auto, PxTextPathSpacing.exact]).optional(),
960
- startOffset: PxAnimatableNumberSchema.optional(),
961
- textLength: PxAnimatableNumberSchema.optional()
962
- }));
963
- var PxTextEffectSchema = implementsInterface()(px.object({
964
- useGlyphs: px.boolean().optional()
965
- }));
966
- var PxEffectsSchema = implementsInterface()(px.object({
967
- transformBy: PxTransformByEffectSchema.optional(),
968
- repeater: PxRepeaterEffectSchema.optional(),
969
- maskedBy: PxMaskedByEffectSchema.optional(),
970
- clipPath: PxClipPathEffectSchema.optional(),
971
- trimPath: PxTrimPathEffectSchema.optional(),
972
- clone: PxCloneEffectSchema.optional(),
973
- fillGradient: PxFillGradientEffectSchema.optional(),
974
- strokeGradient: PxStrokeGradientEffectSchema.optional(),
975
- textPath: PxTextPathEffectSchema.optional(),
976
- text: PxTextEffectSchema.optional()
977
- }));
978
- function validateNodeEffects(root, opts) {
979
- const warnings = [];
980
- const walk = (node, path) => {
981
- if (node && node.effects) {
982
- const ctx = { errors: [], warnings: [], strict: !!(opts == null ? void 0 : opts.strict) };
983
- const ok = PxEffectsSchema.isValid(node.effects, ctx, [path + ".effects"]);
984
- if (!ok) {
985
- for (const err of ctx.errors) warnings.push(err);
986
- }
987
- }
988
- if (node && Array.isArray(node.children)) {
989
- node.children.forEach((c, i) => walk(c, path + ".children[" + i + "]"));
990
- }
991
- };
992
- walk(root, "root");
993
- return warnings;
994
- }
995
- var PxNodeBase = px.openObject({
996
- // CONVENTION (SCHEMA-DESIGN R1 / issues N4): `type` is the ONE word for "what
997
- // kind of thing is this", discriminated by its CARRIER — here the node TAG
998
- // (`rect`, `text`), and inside a sub-object that object's kind (`clone.type`,
999
- // `fillGradient.type`, editor `preset.type`). Each sits in its own object, so
1000
- // the carrier disambiguates completely; synonyms (`cloneKind`, `presetShape`)
1001
- // would add words that all mean "type" and still need the carrier to read.
1002
- // Guarding a `type` SLOT against a wrong VALUE is the job of strict enums
1003
- // (issues V3), never of distinct key names.
1004
- type: px.string(),
1005
- id: px.string().optional(),
1006
- meta: px.any().optional(),
1007
- // Player-effects bucket emitted by the Editor's lightweight design format.
1008
- // Consumed and removed by `applyPlayerEffects` before any other normalisation
1009
- // (see `createAnimatorImpl`), so downstream code never sees it.
1010
- effects: PxEffectsSchema.optional(),
1011
- // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts
1012
- // string ref / array of refs / inline definition / mixed array; mirrors
1013
- // `animator.animateById` map values and what `processNode` resolves at runtime.
1014
- animate: PxElementAnimationSchema.optional(),
1015
- style: px.union([px.string(), px.record(px.union([px.string(), px.number()]))]).optional()
1016
- }, PxAttrValueSchema);
1017
- var PxNodeSchema = px.openObject(__spreadProps2(__spreadValues2({}, PxNodeBase._shape), {
1018
- children: px.lazy(() => px.array(PxNodeSchema), []).optional()
1019
- }), PxAttrValueSchema);
1020
- var PxSvgNodeExtra = px.object({
1021
- // `"100%"` and other SVG length strings are legal here — a number-only slot rejected
1022
- // real documents (e.g. apple-store-look-14-main.json) at the root <svg>.
1023
- width: px.union([px.number(), px.string()]).optional(),
1024
- height: px.union([px.number(), px.string()]).optional(),
1025
- viewBox: px.string().optional(),
1026
- animator: PxAnimatorConfigSchema.optional()
1027
- });
1028
- var PxAnimatedSvgDocumentSchema = px.openObject(__spreadProps2(__spreadValues2(__spreadValues2({}, PxNodeBase._shape), PxSvgNodeExtra._shape), {
1029
- type: px.literal("svg"),
1030
- // override string → literal to require 'svg'
1031
- children: px.array(PxNodeSchema).optional()
1032
- }), PxAttrValueSchema);
1033
- var PxBezierPathSchema = implementsInterface()(px.object({
1034
- v: px.array(px.array(px.number())),
1035
- i: px.array(px.array(px.number())).optional(),
1036
- o: px.array(px.array(px.number())).optional(),
1037
- c: px.boolean().optional()
1038
- }));
1039
- function isPxElementFileFormatDeep(fileJson) {
1040
- const valid = PxAnimatedSvgDocumentSchema.isValid(fileJson);
1041
- return { valid, errors: valid ? [] : ["Document failed schema validation"] };
1042
- }
1043
- function bezierToSvgPath(path, forceCurves = false) {
1044
- var _a, _b, _c, _d;
1045
- const v = path.v;
1046
- const i = path.i;
1047
- const o = path.o;
1048
- const c = path.c;
1049
- if (!v.length) return "";
1050
- const d = [];
1051
- const len = v.length;
1052
- d.push("M" + v[0][0] + "," + v[0][1]);
1053
- for (let idx = 1; idx < len; idx++) {
1054
- const prevV = v[idx - 1];
1055
- const prevO = (_a = o == null ? void 0 : o[idx - 1]) != null ? _a : prevV;
1056
- const currI = (_b = i == null ? void 0 : i[idx]) != null ? _b : v[idx];
1057
- const currV = v[idx];
1058
- const isLine = !forceCurves && (prevO[0] === prevV[0] && prevO[1] === prevV[1]) && (currI[0] === currV[0] && currI[1] === currV[1]);
1059
- if (isLine) {
1060
- d.push("L" + currV[0] + "," + currV[1]);
1061
- } else {
1062
- d.push("C" + prevO[0] + "," + prevO[1] + "," + currI[0] + "," + currI[1] + "," + currV[0] + "," + currV[1]);
1063
- }
1064
- }
1065
- if (c && len > 0) {
1066
- const lastV = v[len - 1];
1067
- const lastO = (_c = o == null ? void 0 : o[len - 1]) != null ? _c : lastV;
1068
- const firstI = (_d = i == null ? void 0 : i[0]) != null ? _d : v[0];
1069
- const firstV = v[0];
1070
- const isLine = !forceCurves && (lastO[0] === lastV[0] && lastO[1] === lastV[1]) && (firstI[0] === firstV[0] && firstI[1] === firstV[1]);
1071
- if (!isLine) {
1072
- d.push("C" + lastO[0] + "," + lastO[1] + "," + firstI[0] + "," + firstI[1] + "," + firstV[0] + "," + firstV[1]);
1073
- }
1074
- d.push("z");
1075
- }
1076
- return d.join("");
1077
- }
1078
- function interpolateNum(a, b, t) {
1079
- return a + (b - a) * t;
1080
- }
1081
- function interpolateVec(a, b, t) {
1082
- const res = [];
1083
- const count = Math.max(a.length, b.length);
1084
- for (let i = 0; i < count; i++) {
1085
- res[i] = interpolateNum(a[i] || 0, b[i] || 0, t);
1086
- }
1087
- return res;
1088
- }
1089
- function interpolateColor(a, b, t) {
1090
- return [
1091
- interpolateNum(a[0] || 0, b[0] || 0, t),
1092
- interpolateNum(a[1] || 0, b[1] || 0, t),
1093
- interpolateNum(a[2] || 0, b[2] || 0, t),
1094
- interpolateNum(a[3] === void 0 ? 1 : a[3], b[3] === void 0 ? 1 : b[3], t)
1095
- ];
1096
- }
1097
- function interpolateBeziers(paths1, paths2, progress) {
1098
- const count = Math.max(paths1.length, paths2.length);
1099
- const res = [];
1100
- for (let i = 0; i < count; i++) {
1101
- res.push(interpolateBezier(paths1[i], paths2[i], progress));
1102
- }
1103
- return res;
1104
- }
1105
- function interpolateBezier(path1, path2, progress) {
1106
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1107
- if (!path1 || !path2) return path1 || path2 || { v: [] };
1108
- const t = Math.min(Math.max(progress, 0), 1);
1109
- const len = Math.min(path1.v.length, path2.v.length);
1110
- const v = [];
1111
- const i = [];
1112
- const o = [];
1113
- for (let idx = 0; idx < len; idx++) {
1114
- const v1 = path1.v[idx];
1115
- const v2 = path2.v[idx];
1116
- v.push(interpolateVec(v1, v2, t));
1117
- const i1 = (_b = (_a = path1.i) == null ? void 0 : _a[idx]) != null ? _b : v1;
1118
- const i2 = (_d = (_c = path2.i) == null ? void 0 : _c[idx]) != null ? _d : v2;
1119
- i.push(interpolateVec(i1, i2, t));
1120
- const o1 = (_f = (_e = path1.o) == null ? void 0 : _e[idx]) != null ? _f : v1;
1121
- const o2 = (_h = (_g = path2.o) == null ? void 0 : _g[idx]) != null ? _h : v2;
1122
- o.push(interpolateVec(o1, o2, t));
1123
- }
1124
- return { v, i: i.length ? i : void 0, o: o.length ? o : void 0, c: (_i = path1.c) != null ? _i : path2.c };
1125
- }
1126
- function remap(value, inMin, inMax, outMin, outMax) {
1127
- if (inMax === inMin) return outMin;
1128
- const t = (value - inMin) / (inMax - inMin);
1129
- return outMin + t * (outMax - outMin);
1130
- }
1131
- function solveCubicBezierX(p1x, p2x, x) {
1132
- if (x <= 0) return 0;
1133
- if (x >= 1) return 1;
1134
- const cx = 3 * p1x;
1135
- const bx = 3 * (p2x - p1x) - cx;
1136
- const ax = 1 - cx - bx;
1137
- function sampleX(t) {
1138
- return ((ax * t + bx) * t + cx) * t;
1139
- }
1140
- function sampleDX(t) {
1141
- return (3 * ax * t + 2 * bx) * t + cx;
1142
- }
1143
- let t2 = x;
1144
- let t0 = 0;
1145
- let t1 = 1;
1146
- for (let i = 0; i < 8; i++) {
1147
- const x2 = sampleX(t2) - x;
1148
- if (Math.abs(x2) < 1e-6) return t2;
1149
- const d2 = sampleDX(t2);
1150
- if (Math.abs(d2) < 1e-6) break;
1151
- t2 -= x2 / d2;
1152
- }
1153
- t2 = x;
1154
- while (t0 < t1) {
1155
- const x2 = sampleX(t2);
1156
- if (Math.abs(x2 - x) < 1e-6) return t2;
1157
- if (x > x2) t0 = t2;
1158
- else t1 = t2;
1159
- t2 = (t1 + t0) / 2;
1160
- }
1161
- return t2;
1162
- }
1163
- function cubicBezier(easing) {
1164
- const [p1x, p1y, p2x, p2y] = easing;
1165
- const cy = 3 * p1y;
1166
- const by = 3 * (p2y - p1y) - cy;
1167
- const ay = 1 - cy - by;
1168
- function sampleCurveY(t) {
1169
- return ((ay * t + by) * t + cy) * t;
1170
- }
1171
- return function(x) {
1172
- return sampleCurveY(solveCubicBezierX(p1x, p2x, x));
1173
- };
1174
- }
1175
- function lerp2(a, b, t) {
1176
- return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
1177
- }
1178
- function subdivideCubicBezier(p0, p1, p2, p3, t) {
1179
- const q0 = lerp2(p0, p1, t);
1180
- const q1 = lerp2(p1, p2, t);
1181
- const q2 = lerp2(p2, p3, t);
1182
- const r0 = lerp2(q0, q1, t);
1183
- const r1 = lerp2(q1, q2, t);
1184
- const s = lerp2(r0, r1, t);
1185
- return {
1186
- left: [p0, q0, r0, s],
1187
- right: [s, r1, q2, p3]
1188
- };
1189
- }
1190
- function splitEasing(easing, xFraction) {
1191
- if (!easing) return { left: void 0, right: void 0 };
1192
- if (xFraction <= 0) return { left: void 0, right: easing };
1193
- if (xFraction >= 1) return { left: easing, right: void 0 };
1194
- const [x1, y1, x2, y2] = easing;
1195
- const t = solveCubicBezierX(x1, x2, xFraction);
1196
- const p0 = [0, 0];
1197
- const p1 = [x1, y1];
1198
- const p2 = [x2, y2];
1199
- const p3 = [1, 1];
1200
- const { left, right } = subdivideCubicBezier(p0, p1, p2, p3, t);
1201
- const sx = left[3][0];
1202
- const sy = left[3][1];
1203
- let leftEasing;
1204
- if (sx > 1e-9 && Math.abs(sy) > 1e-9) {
1205
- leftEasing = [
1206
- left[1][0] / sx,
1207
- left[1][1] / sy,
1208
- left[2][0] / sx,
1209
- left[2][1] / sy
1210
- ];
1211
- }
1212
- let rightEasing;
1213
- const rx = 1 - sx;
1214
- const ry = 1 - sy;
1215
- if (rx > 1e-9 && Math.abs(ry) > 1e-9) {
1216
- rightEasing = [
1217
- (right[1][0] - sx) / rx,
1218
- (right[1][1] - sy) / ry,
1219
- (right[2][0] - sx) / rx,
1220
- (right[2][1] - sy) / ry
1221
- ];
1222
- }
1223
- return { left: leftEasing, right: rightEasing };
1224
- }
1225
- function reverseEasing(easing) {
1226
- if (!easing) return void 0;
1227
- return [1 - easing[2], 1 - easing[3], 1 - easing[0], 1 - easing[1]];
1228
- }
1229
- function toRGBA(color) {
1230
- const r = Math.round(color[0] * 255);
1231
- const g = Math.round(color[1] * 255);
1232
- const b = Math.round(color[2] * 255);
1233
- return color.length === 4 ? "rgba(" + r + "," + g + "," + b + "," + color[3] + ")" : "rgb(" + r + "," + g + "," + b + ")";
1234
- }
1235
- function parseRgba(s) {
1236
- var _a;
1237
- const inner = (_a = s.match(/rgba?\((.*)\)/)) == null ? void 0 : _a[1];
1238
- if (!inner) throw new Error("Invalid rgb/rgba format");
1239
- const parts = inner.split(",").map((v) => +v.trim());
1240
- return [parts[0] / 255, parts[1] / 255, parts[2] / 255, ...parts[3] !== void 0 ? [parts[3]] : []];
1241
- }
1242
- function parseHex(s) {
1243
- const hex = s.slice(1);
1244
- const isShort = hex.length <= 4;
1245
- const r = isShort ? hex[0] + hex[0] : hex.slice(0, 2);
1246
- const g = isShort ? hex[1] + hex[1] : hex.slice(2, 4);
1247
- const b = isShort ? hex[2] + hex[2] : hex.slice(4, 6);
1248
- const a = hex.length === 4 ? hex[3] + hex[3] : hex.length === 8 ? hex.slice(6, 8) : null;
1249
- const result = [
1250
- parseInt(r, 16) / 255,
1251
- parseInt(g, 16) / 255,
1252
- parseInt(b, 16) / 255
1253
- ];
1254
- if (a !== null) {
1255
- result.push(parseInt(a, 16) / 255);
1256
- }
1257
- return result;
1258
- }
1259
- function parseColor(s) {
1260
- if (!s) return void 0;
1261
- if (Array.isArray(s)) return s;
1262
- if (typeof s !== "string") return void 0;
1263
- if (s.startsWith("#")) {
1264
- return parseHex(s);
1265
- } else if (s.startsWith("rgb")) {
1266
- return parseRgba(s);
1267
- } else {
1268
- console.warn("Unsupported color format: " + s);
1269
- }
1270
- return void 0;
1271
- }
1272
- var COLOUR_ATTR_NAMES = /* @__PURE__ */ new Set(["color", "fill", "flood-color", "lighting-color", "stop-color", "stroke"]);
1273
- var TRANSFORM_FN_NAMES = /* @__PURE__ */ new Set(["translate", "rotate", "scale", "skew"]);
1274
- var PCT_BASED_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
1275
- function composeTransformParts(parts, opts) {
1276
- var _a;
1277
- if (!parts) return "";
1278
- const withUnits = (_a = opts == null ? void 0 : opts.withUnits) != null ? _a : true;
1279
- const segs = [];
1280
- const t = parts.translate;
1281
- const o = parts.origin;
1282
- const r = parts.rotate;
1283
- const k = parts.skew;
1284
- const s = parts.scale;
1285
- const tu = withUnits ? "px" : "";
1286
- const ru = withUnits ? "deg" : "";
1287
- if (t) segs.push("translate(" + t[0] + tu + "," + t[1] + tu + ")");
1288
- if (o) segs.push("translate(" + o[0] + tu + "," + o[1] + tu + ")");
1289
- if (r !== void 0 && r !== null) segs.push("rotate(" + r + ru + ")");
1290
- if (k !== void 0 && k !== null) segs.push("skewX(" + k + ru + ")");
1291
- if (s) segs.push("scale(" + s[0] + "," + s[1] + ")");
1292
- if (o) segs.push("translate(" + -o[0] + tu + "," + -o[1] + tu + ")");
1293
- return segs.join("");
1294
- }
1295
- var STYLE_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
1296
- var DEFAULT_DURATION_MS = 1e3;
1297
- function kebabToCamelCaseWord(kebab) {
1298
- return kebab.includes("-") ? kebab.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()) : kebab;
1299
- }
1300
- function isCamelCaseWord(word) {
1301
- return !word.includes("-") && /[a-z][A-Z]/.test(word);
1302
- }
1303
- var SVG_CAMEL_CASE_ATTRS = /* @__PURE__ */ new Set([
1304
- // Transform/positioning
1305
- "viewBox",
1306
- "preserveAspectRatio",
1307
- // Gradient
1308
- "gradientUnits",
1309
- "gradientTransform",
1310
- "spreadMethod",
1311
- // Pattern
1312
- "patternUnits",
1313
- "patternContentUnits",
1314
- "patternTransform",
1315
- // Clipping/masking
1316
- "clipPathUnits",
1317
- "maskUnits",
1318
- "maskContentUnits",
1319
- // Marker (SVG spec keeps these camelCase, like viewBox)
1320
- "markerUnits",
1321
- "markerWidth",
1322
- "markerHeight",
1323
- "refX",
1324
- "refY",
1325
- // Text
1326
- "textLength",
1327
- "lengthAdjust",
1328
- "startOffset",
1329
- // Filter
1330
- "filterUnits",
1331
- "primitiveUnits",
1332
- "tableValues",
1333
- // feFuncR/G/B/A transfer table (type="table")
1334
- "stdDeviation",
1335
- "baseFrequency",
1336
- "numOctaves",
1337
- "surfaceScale",
1338
- "diffuseConstant",
1339
- "specularConstant",
1340
- "specularExponent",
1341
- "kernelMatrix",
1342
- "kernelUnitLength",
1343
- "edgeMode",
1344
- "preserveAlpha",
1345
- "targetX",
1346
- "targetY"
1347
- // // Animation
1348
- // 'attributeName',
1349
- // 'attributeType',
1350
- // 'calcMode',
1351
- // 'keyTimes',
1352
- // 'keySplines',
1353
- // 'repeatCount',
1354
- // 'repeatDur'
1355
- ]);
1356
- function camelCaseToKebabWordIfNeeded(camel) {
1357
- return SVG_CAMEL_CASE_ATTRS.has(camel) ? camel : camel.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1358
- }
1359
- function clamp(value, min, max) {
1360
- return Math.max(min, Math.min(value, max));
1361
- }
1362
- function bezier2D_pointAt(P0, P1, P2, P3, t) {
1363
- if (t <= 0) return [P0[0], P0[1]];
1364
- if (t >= 1) return [P3[0], P3[1]];
1365
- const u = 1 - t;
1366
- const u2 = u * u;
1367
- const u3 = u2 * u;
1368
- const t2 = t * t;
1369
- const t3 = t2 * t;
1370
- const w0 = u3;
1371
- const w1 = 3 * t * u2;
1372
- const w2 = 3 * t2 * u;
1373
- const w3 = t3;
1374
- return [
1375
- w0 * P0[0] + w1 * P1[0] + w2 * P2[0] + w3 * P3[0],
1376
- w0 * P0[1] + w1 * P1[1] + w2 * P2[1] + w3 * P3[1]
1377
- ];
1378
- }
1379
- var BEZIER_T_NUDGE = 1e-4;
1380
- function bezier2D_derivativeAt(P0, P1, P2, P3, t) {
1381
- const result = _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t);
1382
- if (result[0] === 0 && result[1] === 0) {
1383
- const nudgedT = t < 0.5 ? t + BEZIER_T_NUDGE : t - BEZIER_T_NUDGE;
1384
- return _bezier2D_derivativeAtRaw(P0, P1, P2, P3, nudgedT);
1385
- }
1386
- return result;
1387
- }
1388
- function _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t) {
1389
- const u = 1 - t;
1390
- const a = 3 * u * u;
1391
- const b = 6 * t * u;
1392
- const c = 3 * t * t;
1393
- return [
1394
- a * (P1[0] - P0[0]) + b * (P2[0] - P1[0]) + c * (P3[0] - P2[0]),
1395
- a * (P1[1] - P0[1]) + b * (P2[1] - P1[1]) + c * (P3[1] - P2[1])
1396
- ];
1397
- }
1398
- function bezier2D_arcLengthLUT(P0, P1, P2, P3, steps = 100) {
1399
- const n = steps + 1;
1400
- const ts = new Float64Array(n);
1401
- const ds = new Float64Array(n);
1402
- let prev = bezier2D_pointAt(P0, P1, P2, P3, 0);
1403
- ts[0] = 0;
1404
- ds[0] = 0;
1405
- let cum = 0;
1406
- for (let i = 1; i < n; i++) {
1407
- const t = i / steps;
1408
- const cur = bezier2D_pointAt(P0, P1, P2, P3, t);
1409
- const dx = cur[0] - prev[0];
1410
- const dy = cur[1] - prev[1];
1411
- cum += Math.sqrt(dx * dx + dy * dy);
1412
- ts[i] = t;
1413
- ds[i] = cum;
1414
- prev = cur;
1415
- }
1416
- return { ts, ds };
1417
- }
1418
- function bezier2D_tForDistance(lut, distance) {
1419
- const { ts, ds } = lut;
1420
- const last = ds.length - 1;
1421
- if (distance <= 0) return ts[0];
1422
- if (distance >= ds[last]) return ts[last];
1423
- let lo = 1;
1424
- let hi = last;
1425
- while (lo < hi) {
1426
- const mid = lo + hi >>> 1;
1427
- if (ds[mid] < distance) lo = mid + 1;
1428
- else hi = mid;
1429
- }
1430
- const dPrev = ds[hi - 1];
1431
- const dCur = ds[hi];
1432
- const span = dCur - dPrev;
1433
- const frac = span > 0 ? (distance - dPrev) / span : 0;
1434
- return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);
1435
- }
1436
- function bezier2D_arcAtT(lut, t) {
1437
- const { ts, ds } = lut;
1438
- const last = ts.length - 1;
1439
- if (t <= ts[0]) return ds[0];
1440
- if (t >= ts[last]) return ds[last];
1441
- let lo = 1, hi = last;
1442
- while (lo < hi) {
1443
- const mid = lo + hi >>> 1;
1444
- if (ts[mid] < t) lo = mid + 1;
1445
- else hi = mid;
1446
- }
1447
- const tPrev = ts[hi - 1];
1448
- const span = ts[hi] - tPrev;
1449
- const frac = span > 0 ? (t - tPrev) / span : 0;
1450
- return ds[hi - 1] + frac * (ds[hi] - ds[hi - 1]);
1353
+ // Structured static — `{value: …}` (read-accepted transitional spelling, S1).
1354
+ // `defined`, not `any`: the KEY's presence is what identifies this branch (V6).
1355
+ px.object({ value: px.defined() }),
1356
+ // Bare transform parts record — the canonical static `transform` on the wire (T2).
1357
+ PxTransformPartsSchema
1358
+ ]);
1359
+ var PxAnimatableNumberSchema = px.union([
1360
+ px.number(),
1361
+ px.object({ value: px.number() }),
1362
+ PxPropertyAnimationSchema
1363
+ ]);
1364
+ var PxAnimatableVec2Schema = px.union([
1365
+ px.tuple([px.number(), px.number()]),
1366
+ px.object({ value: px.tuple([px.number(), px.number()]) }),
1367
+ PxPropertyAnimationSchema
1368
+ ]);
1369
+ var PxAnimatableStringSchema = px.union([
1370
+ px.string(),
1371
+ px.object({ value: px.string() }),
1372
+ PxPropertyAnimationSchema
1373
+ ]);
1374
+ var PxTransformByEffectSchema = implementsInterface()(px.object({
1375
+ translate: PxAnimatableVec2Schema.optional(),
1376
+ rotate: PxAnimatableNumberSchema.optional(),
1377
+ scale: PxAnimatableVec2Schema.optional(),
1378
+ skew: PxAnimatableNumberSchema.optional(),
1379
+ origin: PxAnimatableVec2Schema.optional()
1380
+ }));
1381
+ var PxRepeaterEffectSchema = implementsInterface()(px.object({
1382
+ // STATIC config, not a channel (V2/SCHEMA-DESIGN R5): the copy COUNT is read
1383
+ // once at expansion time and never sampled — plain number, no `keyframes`.
1384
+ copies: px.number().optional(),
1385
+ translate: PxAnimatableVec2Schema.optional(),
1386
+ rotate: PxAnimatableNumberSchema.optional(),
1387
+ skew: PxAnimatableNumberSchema.optional(),
1388
+ scale: PxAnimatableVec2Schema.optional(),
1389
+ origin: PxAnimatableVec2Schema.optional()
1390
+ }));
1391
+ var PxMaskedByEffectSchema = implementsInterface()(px.object({
1392
+ sourceId: px.string().optional(),
1393
+ maskType: px.enum([PxMaskType.luminance, PxMaskType.alpha]).optional(),
1394
+ maskUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
1395
+ maskContentUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
1396
+ x: px.number().optional(),
1397
+ y: px.number().optional(),
1398
+ width: px.number().optional(),
1399
+ height: px.number().optional()
1400
+ }));
1401
+ var PxClipPathEffectSchema = implementsInterface()(px.object({
1402
+ d: PxAnimatableStringSchema.optional(),
1403
+ animate: PxPropertyAnimationSchema.optional()
1404
+ }));
1405
+ var PxTrimPathEffectSchema = implementsInterface()(px.object({
1406
+ offset: PxAnimatableNumberSchema.optional(),
1407
+ range: PxAnimatableVec2Schema.optional(),
1408
+ subPaths: px.enum([PxTrimSubPaths.separate, PxTrimSubPaths.combined]).optional()
1409
+ }));
1410
+ var PxRetimeEffectSchema = implementsInterface()(px.object({
1411
+ sourceId: px.string().optional(),
1412
+ start: px.number().optional(),
1413
+ stretch: px.number().optional(),
1414
+ timeCrop: px.tuple([px.number(), px.number()]).optional()
1415
+ }));
1416
+ var PxCloneEffectSchema = implementsInterface()(px.object({
1417
+ // Contextual kind — the `type` convention, see `PxNodeBase.type`.
1418
+ type: px.enum([PxCloneType.content]).optional(),
1419
+ sourceId: px.string().optional(),
1420
+ retime: PxRetimeEffectSchema.optional()
1421
+ }));
1422
+ var PxGradientStopSchema = implementsInterface()(px.object({
1423
+ offset: px.number(),
1424
+ color: px.string()
1425
+ }));
1426
+ var PxAnimatableGradientStopsSchema = px.union([
1427
+ px.array(PxGradientStopSchema),
1428
+ px.object({ value: px.array(PxGradientStopSchema) }),
1429
+ PxPropertyAnimationSchema
1430
+ ]);
1431
+ var PxFillGradientEffectSchema = implementsInterface()(px.object({
1432
+ // Contextual kind — the `type` convention, see `PxNodeBase.type`.
1433
+ type: px.enum([PxGradientType.linear, PxGradientType.radial]),
1434
+ p1: PxAnimatableVec2Schema.optional(),
1435
+ p2: PxAnimatableVec2Schema.optional(),
1436
+ c: PxAnimatableVec2Schema.optional(),
1437
+ r: PxAnimatableNumberSchema.optional(),
1438
+ fp: PxAnimatableVec2Schema.optional(),
1439
+ stops: PxAnimatableGradientStopsSchema.optional(),
1440
+ gradientUnits: px.enum([PxGradientUnits.userSpaceOnUse, PxGradientUnits.objectBoundingBox]).optional(),
1441
+ spreadMethod: px.enum([PxGradientSpreadMethod.pad, PxGradientSpreadMethod.reflect, PxGradientSpreadMethod.repeat]).optional(),
1442
+ gradientTransform: px.string().optional()
1443
+ }));
1444
+ var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
1445
+ var PxTextPathEffectSchema = implementsInterface()(px.object({
1446
+ path: px.string(),
1447
+ pathOverflow: px.enum([PxPathOverflow.clip, PxPathOverflow.extend]).optional(),
1448
+ lengthAdjust: px.enum([PxLengthAdjust.spacing, PxLengthAdjust.spacingAndGlyphs]).optional(),
1449
+ method: px.enum([PxTextPathMethod.align, PxTextPathMethod.stretch]).optional(),
1450
+ spacing: px.enum([PxTextPathSpacing.auto, PxTextPathSpacing.exact]).optional(),
1451
+ startOffset: PxAnimatableNumberSchema.optional(),
1452
+ textLength: PxAnimatableNumberSchema.optional()
1453
+ }));
1454
+ var PxTextEffectSchema = implementsInterface()(px.object({
1455
+ useGlyphs: px.boolean().optional()
1456
+ }));
1457
+ var PxEffectsSchema = implementsInterface()(px.object({
1458
+ transformBy: PxTransformByEffectSchema.optional(),
1459
+ repeater: PxRepeaterEffectSchema.optional(),
1460
+ maskedBy: PxMaskedByEffectSchema.optional(),
1461
+ clipPath: PxClipPathEffectSchema.optional(),
1462
+ trimPath: PxTrimPathEffectSchema.optional(),
1463
+ clone: PxCloneEffectSchema.optional(),
1464
+ fillGradient: PxFillGradientEffectSchema.optional(),
1465
+ strokeGradient: PxStrokeGradientEffectSchema.optional(),
1466
+ textPath: PxTextPathEffectSchema.optional(),
1467
+ text: PxTextEffectSchema.optional()
1468
+ }));
1469
+ function validateNodeEffects(root, opts) {
1470
+ const warnings = [];
1471
+ const walk = (node, path) => {
1472
+ if (node && node.effects) {
1473
+ const ctx = { errors: [], warnings: [], strict: !!(opts == null ? void 0 : opts.strict) };
1474
+ const ok = PxEffectsSchema.isValid(node.effects, ctx, [path + ".effects"]);
1475
+ if (!ok) {
1476
+ for (const err of ctx.errors) warnings.push(err);
1477
+ }
1478
+ }
1479
+ if (node && Array.isArray(node.children)) {
1480
+ node.children.forEach((c, i) => walk(c, path + ".children[" + i + "]"));
1481
+ }
1482
+ };
1483
+ walk(root, "root");
1484
+ return warnings;
1451
1485
  }
1452
- function invertEasing(easing) {
1453
- if (!easing) return (y) => y;
1454
- const flipped = [easing[1], easing[0], easing[3], easing[2]];
1455
- return cubicBezier(flipped);
1486
+ var PxNodeBase = px.openObject({
1487
+ // CONVENTION (SCHEMA-DESIGN R1 / issues N4): `type` is the ONE word for "what
1488
+ // kind of thing is this", discriminated by its CARRIER — here the node TAG
1489
+ // (`rect`, `text`), and inside a sub-object that object's kind (`clone.type`,
1490
+ // `fillGradient.type`, editor `preset.type`). Each sits in its own object, so
1491
+ // the carrier disambiguates completely; synonyms (`cloneKind`, `presetShape`)
1492
+ // would add words that all mean "type" and still need the carrier to read.
1493
+ // Guarding a `type` SLOT against a wrong VALUE is the job of strict enums
1494
+ // (issues V3), never of distinct key names.
1495
+ type: px.string(),
1496
+ id: px.string().optional(),
1497
+ meta: px.any().optional(),
1498
+ // Player-effects bucket emitted by the Editor's lightweight design format.
1499
+ // Consumed and removed by `applyPlayerEffects` before any other normalisation
1500
+ // (see `createAnimatorImpl`), so downstream code never sees it.
1501
+ effects: PxEffectsSchema.optional(),
1502
+ // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts
1503
+ // string ref / array of refs / inline definition / mixed array; mirrors
1504
+ // `animator.animateById` map values and what `processNode` resolves at runtime.
1505
+ animate: PxElementAnimationSchema.optional(),
1506
+ style: px.union([px.string(), px.record(px.union([px.string(), px.number()]))]).optional()
1507
+ }, PxAttrValueSchema);
1508
+ var PxNodeSchema = px.openObject(__spreadProps2(__spreadValues2({}, PxNodeBase._shape), {
1509
+ children: px.lazy(() => px.array(PxNodeSchema), []).optional()
1510
+ }), PxAttrValueSchema);
1511
+ var PxSvgNodeExtra = px.object({
1512
+ // `"100%"` and other SVG length strings are legal here — a number-only slot rejected
1513
+ // real documents (e.g. apple-store-look-14-main.json) at the root <svg>.
1514
+ width: px.union([px.number(), px.string()]).optional(),
1515
+ height: px.union([px.number(), px.string()]).optional(),
1516
+ viewBox: px.string().optional(),
1517
+ animator: PxAnimatorConfigSchema.optional()
1518
+ });
1519
+ var PxAnimatedSvgDocumentSchema = px.openObject(__spreadProps2(__spreadValues2(__spreadValues2({}, PxNodeBase._shape), PxSvgNodeExtra._shape), {
1520
+ type: px.literal("svg"),
1521
+ // override string → literal to require 'svg'
1522
+ children: px.array(PxNodeSchema).optional()
1523
+ }), PxAttrValueSchema);
1524
+ var PxBezierPathSchema = implementsInterface()(px.object({
1525
+ v: px.array(px.array(px.number())),
1526
+ i: px.array(px.array(px.number())).optional(),
1527
+ o: px.array(px.array(px.number())).optional(),
1528
+ c: px.boolean().optional()
1529
+ }));
1530
+ function isPxElementFileFormatDeep(fileJson) {
1531
+ const valid = PxAnimatedSvgDocumentSchema.isValid(fileJson);
1532
+ return { valid, errors: valid ? [] : ["Document failed schema validation"] };
1456
1533
  }
1457
1534
  var _idCounter = 0;
1458
1535
  function generateUniqueId() {
@@ -6152,7 +6229,13 @@ function createFrameLoopAnimator(doc, adapter, callbacks, rootElement) {
6152
6229
  const api = __spreadProps(__spreadValues({}, basicApi), {
6153
6230
  "getRootElement": () => rootElement || null
6154
6231
  });
6155
- if (config.trigger) setupAnimationTriggers(api, config.trigger);
6232
+ if (config.trigger) {
6233
+ if (isScrollTimeline(config)) {
6234
+ console.warn("scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)");
6235
+ } else {
6236
+ setupAnimationTriggers(api, config.trigger);
6237
+ }
6238
+ }
6156
6239
  return api;
6157
6240
  }
6158
6241
  function createDomAdapter(rootElement) {
@@ -6280,7 +6363,7 @@ function convertToWebApiKeyframes(animDef, unsupportedSet, config) {
6280
6363
  }
6281
6364
  return result;
6282
6365
  }
6283
- function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsupportedAttrs) {
6366
+ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsupportedAttrs, scrollTimeline) {
6284
6367
  var _a;
6285
6368
  const config = getAnimatorConfig(doc) || {};
6286
6369
  if (!rootElement) {
@@ -6339,7 +6422,12 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
6339
6422
  if (keyframes.length > 0) {
6340
6423
  try {
6341
6424
  const effect = new KeyframeEffect(element, keyframes, effectOptions);
6342
- const anim = new Animation(effect, document.timeline);
6425
+ const anim = new Animation(effect, scrollTimeline ? scrollTimeline.timeline : document.timeline);
6426
+ if (scrollTimeline) {
6427
+ const a = anim;
6428
+ if (scrollTimeline.rangeStart) a.rangeStart = scrollTimeline.rangeStart;
6429
+ if (scrollTimeline.rangeEnd) a.rangeEnd = scrollTimeline.rangeEnd;
6430
+ }
6343
6431
  if (callbacks == null ? void 0 : callbacks.onFinish) anim.onfinish = () => {
6344
6432
  var _a2;
6345
6433
  if (finishNotified) return;
@@ -6433,11 +6521,242 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
6433
6521
  }
6434
6522
  };
6435
6523
  if (config.trigger) {
6436
- setupAnimationTriggers(api, config.trigger);
6524
+ if (config.timelineSource === "scroll") {
6525
+ console.warn("scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)");
6526
+ } else {
6527
+ setupAnimationTriggers(api, config.trigger);
6528
+ }
6529
+ }
6530
+ if (scrollTimeline) {
6531
+ animations.forEach((a) => a.play());
6437
6532
  }
6438
6533
  return api;
6439
6534
  }
6440
6535
 
6536
+ // src/PxScrollDriver.ts
6537
+ function nativeRangeOffset(point, defaultFraction, view) {
6538
+ var _a, _b, _c;
6539
+ const fraction = typeof (point == null ? void 0 : point.fraction) === "number" ? point.fraction : defaultFraction;
6540
+ const pct = (_b = (_a = globalThis.CSS) == null ? void 0 : _a.percent) == null ? void 0 : _b.call(_a, fraction * 100);
6541
+ if (pct === void 0) return void 0;
6542
+ return view ? { rangeName: (_c = point == null ? void 0 : point.phase) != null ? _c : "cover", offset: pct } : { offset: pct };
6543
+ }
6544
+ function createNativeScrollTimeline(subject, config) {
6545
+ var _a, _b, _c, _d;
6546
+ if (!config || !isScrollTimeline(config)) return null;
6547
+ const scroll = config.scroll || {};
6548
+ const kind = (_a = scroll.kind) != null ? _a : "view";
6549
+ if (scroll.smoothing) {
6550
+ console.warn('scroll timeline: `smoothing` needs the built-in driver \u2014 ignoring `driver: "native"`');
6551
+ return null;
6552
+ }
6553
+ const g = globalThis;
6554
+ const view = kind === "view";
6555
+ const Ctor = view ? g.ViewTimeline : g.ScrollTimeline;
6556
+ if (typeof Ctor !== "function") return null;
6557
+ const axis = (_b = scroll.axis) != null ? _b : "block";
6558
+ let timeline;
6559
+ try {
6560
+ if (view) {
6561
+ timeline = new Ctor({ subject: resolveScrollSubject(subject, scroll.subject), axis });
6562
+ } else {
6563
+ const source = scroll.source === "root" ? documentScroller() : findNearestScroller(subject, "y") || findNearestScroller(subject, "x") || documentScroller();
6564
+ timeline = new Ctor({ source, axis });
6565
+ }
6566
+ } catch (e) {
6567
+ console.warn("scroll timeline: native timeline construction failed \u2014 falling back to the custom driver", e);
6568
+ return null;
6569
+ }
6570
+ return {
6571
+ timeline,
6572
+ rangeStart: nativeRangeOffset((_c = scroll.range) == null ? void 0 : _c.start, 0, view),
6573
+ rangeEnd: nativeRangeOffset((_d = scroll.range) == null ? void 0 : _d.end, 1, view)
6574
+ };
6575
+ }
6576
+ function findNearestScroller(el, axis) {
6577
+ const body = document.body;
6578
+ const root = document.documentElement;
6579
+ for (let p = el.parentElement; p; p = p.parentElement) {
6580
+ if (p === body || p === root) return null;
6581
+ const style = getComputedStyle(p);
6582
+ const overflow = axis === "y" ? style.overflowY : style.overflowX;
6583
+ if (overflow === "auto" || overflow === "scroll" || overflow === "hidden" || overflow === "overlay") {
6584
+ return p;
6585
+ }
6586
+ }
6587
+ return null;
6588
+ }
6589
+ function documentScroller() {
6590
+ return document.scrollingElement || document.documentElement;
6591
+ }
6592
+ var SUBJECT_PARENT = "parent";
6593
+ var SUBJECT_SCROLLER = "scroller";
6594
+ function resolveScrollSubject(svgRoot, subject) {
6595
+ var _a, _b;
6596
+ const spec = subject == null ? void 0 : subject.trim();
6597
+ if (!spec) return svgRoot;
6598
+ if (spec === SUBJECT_PARENT) {
6599
+ let outermostPinned = null;
6600
+ for (let p = svgRoot.parentElement; p && p !== document.body; p = p.parentElement) {
6601
+ const position = getComputedStyle(p).position;
6602
+ if (position === "sticky" || position === "fixed") outermostPinned = p;
6603
+ }
6604
+ return (_b = (_a = outermostPinned == null ? void 0 : outermostPinned.parentElement) != null ? _a : svgRoot.parentElement) != null ? _b : svgRoot;
6605
+ }
6606
+ if (spec === SUBJECT_SCROLLER) {
6607
+ return findNearestScroller(svgRoot, "y") || findNearestScroller(svgRoot, "x") || documentScroller();
6608
+ }
6609
+ let found = null;
6610
+ try {
6611
+ found = document.querySelector(spec);
6612
+ } catch (e) {
6613
+ console.warn('scroll timeline: subject "' + spec + '" is not a valid selector \u2014 measuring the SVG itself');
6614
+ return svgRoot;
6615
+ }
6616
+ if (!found) {
6617
+ console.warn('scroll timeline: subject "' + spec + '" matched no element \u2014 measuring the SVG itself');
6618
+ return svgRoot;
6619
+ }
6620
+ return found;
6621
+ }
6622
+ function createScrollDriver(subject, config, onProgress) {
6623
+ var _a, _b;
6624
+ if (!config || !isScrollTimeline(config)) return null;
6625
+ const scroll = config.scroll || {};
6626
+ const kind = (_a = scroll.kind) != null ? _a : "view";
6627
+ const measured = resolveScrollSubject(subject, scroll.subject);
6628
+ const nearest = findNearestScroller(subject, "y") || findNearestScroller(subject, "x");
6629
+ const scroller = kind === "scroll" && scroll.source === "root" ? documentScroller() : nearest || documentScroller();
6630
+ const isRootScroller = scroller === documentScroller();
6631
+ const axis = scrollResolveAxis(scroll.axis, getComputedStyle(scroller).writingMode);
6632
+ const compute = () => {
6633
+ if (kind === "scroll") {
6634
+ const offset = axis === "y" ? scroller.scrollTop : scroller.scrollLeft;
6635
+ const maxOffset = axis === "y" ? scroller.scrollHeight - scroller.clientHeight : scroller.scrollWidth - scroller.clientWidth;
6636
+ return scrollOffsetProgress(offset, maxOffset, scroll.range);
6637
+ }
6638
+ const subjectRect = measured.getBoundingClientRect();
6639
+ let portStart, portSize;
6640
+ if (isRootScroller) {
6641
+ portStart = 0;
6642
+ portSize = axis === "y" ? document.documentElement.clientHeight : document.documentElement.clientWidth;
6643
+ } else {
6644
+ const portRect = scroller.getBoundingClientRect();
6645
+ portStart = axis === "y" ? portRect.top : portRect.left;
6646
+ portSize = axis === "y" ? scroller.clientHeight : scroller.clientWidth;
6647
+ }
6648
+ const subjectStart = (axis === "y" ? subjectRect.top : subjectRect.left) - portStart;
6649
+ const subjectSize = axis === "y" ? subjectRect.height : subjectRect.width;
6650
+ return scrollViewProgress(subjectStart, subjectSize, portSize, scroll.range);
6651
+ };
6652
+ const smoothingSec = Math.max(0, (_b = scroll.smoothing) != null ? _b : 0) / 1e3;
6653
+ const SETTLE_EPSILON = 1e-3;
6654
+ let destroyed = false;
6655
+ let smoothed = null;
6656
+ let smoothRaf = null;
6657
+ let lastFrameMs = 0;
6658
+ const emit = (target) => {
6659
+ if (!smoothingSec) {
6660
+ onProgress(target);
6661
+ return;
6662
+ }
6663
+ if (smoothed === null) {
6664
+ smoothed = target;
6665
+ onProgress(target);
6666
+ return;
6667
+ }
6668
+ if (smoothRaf !== null) return;
6669
+ lastFrameMs = 0;
6670
+ const step = (nowMs) => {
6671
+ smoothRaf = null;
6672
+ if (destroyed) return;
6673
+ const dtSec = lastFrameMs ? Math.min(0.1, (nowMs - lastFrameMs) / 1e3) : 1 / 60;
6674
+ lastFrameMs = nowMs;
6675
+ const goal = compute();
6676
+ const k = 1 - Math.exp(-dtSec / smoothingSec);
6677
+ smoothed = smoothed + (goal - smoothed) * k;
6678
+ if (Math.abs(goal - smoothed) < SETTLE_EPSILON) smoothed = goal;
6679
+ onProgress(smoothed);
6680
+ if (smoothed !== goal) smoothRaf = requestAnimationFrame(step);
6681
+ };
6682
+ smoothRaf = requestAnimationFrame(step);
6683
+ };
6684
+ let rafId = null;
6685
+ const tick = () => {
6686
+ rafId = null;
6687
+ if (destroyed) return;
6688
+ emit(compute());
6689
+ };
6690
+ const schedule = () => {
6691
+ if (destroyed || rafId !== null) return;
6692
+ rafId = requestAnimationFrame(tick);
6693
+ };
6694
+ const scrollTarget = isRootScroller ? window : scroller;
6695
+ scrollTarget.addEventListener("scroll", schedule, { passive: true });
6696
+ window.addEventListener("resize", schedule, { passive: true });
6697
+ let resizeObserver;
6698
+ if (typeof ResizeObserver !== "undefined") {
6699
+ resizeObserver = new ResizeObserver(schedule);
6700
+ resizeObserver.observe(measured);
6701
+ if (measured !== subject) resizeObserver.observe(subject);
6702
+ if (!isRootScroller) resizeObserver.observe(scroller);
6703
+ }
6704
+ const driver = {
6705
+ destroy: () => {
6706
+ if (destroyed) return;
6707
+ destroyed = true;
6708
+ scrollTarget.removeEventListener("scroll", schedule);
6709
+ window.removeEventListener("resize", schedule);
6710
+ resizeObserver == null ? void 0 : resizeObserver.disconnect();
6711
+ if (rafId !== null) {
6712
+ cancelAnimationFrame(rafId);
6713
+ rafId = null;
6714
+ }
6715
+ if (smoothRaf !== null) {
6716
+ cancelAnimationFrame(smoothRaf);
6717
+ smoothRaf = null;
6718
+ }
6719
+ },
6720
+ // `refresh` is a deliberate JUMP (attach, host relayout) — never eased.
6721
+ refresh: () => {
6722
+ if (!destroyed) {
6723
+ smoothed = compute();
6724
+ onProgress(smoothed);
6725
+ }
6726
+ }
6727
+ };
6728
+ driver.refresh();
6729
+ return driver;
6730
+ }
6731
+ function applyScrollPin(svgRoot, scroll) {
6732
+ var _a;
6733
+ const styled = svgRoot;
6734
+ if (!(scroll == null ? void 0 : scroll.pin) || !styled.style) return () => {
6735
+ };
6736
+ const style = styled.style;
6737
+ const prevPosition = style.position;
6738
+ const prevTop = style.top;
6739
+ style.position = "sticky";
6740
+ style.top = ((_a = scroll.pinTop) != null ? _a : 0) + "px";
6741
+ let wrapper = null;
6742
+ const parent = svgRoot.parentElement;
6743
+ if (scroll.pinDistance && scroll.pinDistance > 0 && parent) {
6744
+ wrapper = document.createElement("div");
6745
+ wrapper.setAttribute("data-px-pin", "");
6746
+ wrapper.style.height = scroll.pinDistance * 100 + "vh";
6747
+ parent.insertBefore(wrapper, svgRoot);
6748
+ wrapper.appendChild(svgRoot);
6749
+ }
6750
+ return () => {
6751
+ style.position = prevPosition;
6752
+ style.top = prevTop;
6753
+ if (wrapper == null ? void 0 : wrapper.parentElement) {
6754
+ wrapper.parentElement.insertBefore(svgRoot, wrapper);
6755
+ wrapper.remove();
6756
+ }
6757
+ };
6758
+ }
6759
+
6441
6760
  // src/PxAnimatorBind.ts
6442
6761
  function finaliseAnimator(animatorConfig, callbacks, make) {
6443
6762
  let apiRef;
@@ -6460,6 +6779,59 @@ function finaliseAnimator(animatorConfig, callbacks, make) {
6460
6779
  }
6461
6780
  function bindWithEngineChoice(doc, adapter, callbacks, rootElement) {
6462
6781
  const animatorConfig = getAnimatorConfig(doc) || {};
6782
+ if (isScrollTimeline(animatorConfig)) {
6783
+ return finaliseAnimator(animatorConfig, callbacks, (cb) => {
6784
+ var _a, _b;
6785
+ let unpin = () => {
6786
+ };
6787
+ if (animatorConfig.mode !== PxAnimatorMode.frames && ((_a = animatorConfig.scroll) == null ? void 0 : _a.driver) === "native" && rootElement) {
6788
+ unpin = applyScrollPin(rootElement, animatorConfig.scroll);
6789
+ const native = createNativeScrollTimeline(rootElement, animatorConfig);
6790
+ if (native) {
6791
+ const api2 = createWebApiAnimator(
6792
+ doc,
6793
+ cb,
6794
+ rootElement,
6795
+ animatorConfig.mode === PxAnimatorMode.waapi,
6796
+ native
6797
+ );
6798
+ if (api2) {
6799
+ const destroyNative = api2.destroy.bind(api2);
6800
+ api2.destroy = () => {
6801
+ unpin();
6802
+ destroyNative();
6803
+ };
6804
+ return api2;
6805
+ }
6806
+ }
6807
+ unpin();
6808
+ unpin = () => {
6809
+ };
6810
+ }
6811
+ const api = (animatorConfig.mode !== PxAnimatorMode.frames ? createWebApiAnimator(doc, cb, rootElement, animatorConfig.mode === PxAnimatorMode.waapi) : null) || createFrameLoopAnimator(doc, adapter, cb, rootElement);
6812
+ const subject = ((_b = api.getRootElement) == null ? void 0 : _b.call(api)) || rootElement;
6813
+ if (subject) {
6814
+ unpin = applyScrollPin(subject, animatorConfig.scroll);
6815
+ const totalMs = scrollTotalDurationMs(animatorConfig);
6816
+ const driver = createScrollDriver(
6817
+ subject,
6818
+ animatorConfig,
6819
+ (progress) => api.setCurrentTime(progress * totalMs)
6820
+ );
6821
+ if (driver) {
6822
+ const destroy = api.destroy.bind(api);
6823
+ api.destroy = () => {
6824
+ driver.destroy();
6825
+ unpin();
6826
+ destroy();
6827
+ };
6828
+ }
6829
+ } else {
6830
+ console.warn("scroll timeline: no root element to observe \u2014 animation will stay at frame 0");
6831
+ }
6832
+ return api;
6833
+ });
6834
+ }
6463
6835
  return finaliseAnimator(animatorConfig, callbacks, (cb) => {
6464
6836
  if (animatorConfig.mode === PxAnimatorMode.frames) {
6465
6837
  return createFrameLoopAnimator(doc, adapter, cb, rootElement);