@pixodesk/svg-animator-web 1.0.34 → 1.0.39

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.
@@ -50,12 +50,12 @@ var PixodeskAnimator = (() => {
50
50
  // src/index.prerendered.ts
51
51
  var index_prerendered_exports = {};
52
52
  __export(index_prerendered_exports, {
53
- PX_ANIMATOR_DATA_KEY: () => PX_ANIMATOR_DATA_KEY,
53
+ PX_ANIMATOR_DOC_KEY: () => PX_ANIMATOR_DOC_KEY,
54
54
  createAnimator: () => createPrerenderedAnimator,
55
55
  setupAnimationTriggers: () => setupAnimationTriggers
56
56
  });
57
57
 
58
- // ../svg-animator-core/src/PxAnimatorUtil.ts
58
+ // ../svg-animator-core/src/util/PxAnimatorUtil.ts
59
59
  function bezierToSvgPath(path, forceCurves = false) {
60
60
  var _a, _b, _c, _d;
61
61
  const v = path.v;
@@ -285,7 +285,7 @@ var PixodeskAnimator = (() => {
285
285
  }
286
286
  return void 0;
287
287
  }
288
- var COLOUR_ATTR_NAMES = /* @__PURE__ */ new Set(["color", "fill", "flood-color", "lighting-color", "stop-color", "stroke"]);
288
+ var COLOR_ATTR_NAMES = /* @__PURE__ */ new Set(["color", "fill", "flood-color", "lighting-color", "stop-color", "stroke"]);
289
289
  var TRANSFORM_FN_NAMES = /* @__PURE__ */ new Set(["translate", "rotate", "scale", "skew"]);
290
290
  var PCT_BASED_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
291
291
  function composeTransformParts(parts, opts) {
@@ -485,7 +485,7 @@ var PixodeskAnimator = (() => {
485
485
  return cubicBezier(flipped);
486
486
  }
487
487
 
488
- // ../svg-animator-core/src/PxScrollMath.ts
488
+ // ../svg-animator-core/src/playback/PxScrollMath.ts
489
489
  function isScrollTimeline(config) {
490
490
  return (config == null ? void 0 : config.timelineSource) === "scroll";
491
491
  }
@@ -541,29 +541,693 @@ var PixodeskAnimator = (() => {
541
541
  return vertical ? "x" : "y";
542
542
  }
543
543
 
544
- // ../svg-animator-core/src/PxAnimatorConstants.ts
545
- var PxAnimatorMode = {
544
+ // ../svg-animator-core/src/schema/PxSchema.ts
545
+ var PX_UNKNOWN_KEY_ERROR = "unexpected extra key";
546
+ function pathStr(path) {
547
+ if (!path.length) return ".";
548
+ let result = "";
549
+ for (const seg of path) {
550
+ if (seg.startsWith("[")) result += seg;
551
+ else result += (result ? "." : "") + seg;
552
+ }
553
+ return result;
554
+ }
555
+ var Base = class {
556
+ _canSanitize(raw) {
557
+ return this.isValid(raw);
558
+ }
559
+ optional() {
560
+ return new Optional(this);
561
+ }
562
+ };
563
+ var Optional = class extends Base {
564
+ constructor(inner) {
565
+ super();
566
+ this.inner = inner;
567
+ this._default = void 0;
568
+ }
569
+ sanitize(raw) {
570
+ if (raw === void 0 || raw === null) return void 0;
571
+ return this.inner._canSanitize(raw) ? this.inner.sanitize(raw) : void 0;
572
+ }
573
+ isValid(raw, ctx, path) {
574
+ if (raw === void 0 || raw === null) return true;
575
+ return this.inner.isValid(raw, ctx, path);
576
+ }
577
+ _canSanitize(raw) {
578
+ return raw === void 0 || raw === null || this.inner._canSanitize(raw);
579
+ }
580
+ };
581
+ var Str = class extends Base {
582
+ constructor(_default = "") {
583
+ super();
584
+ this._default = _default;
585
+ }
586
+ sanitize(raw) {
587
+ return typeof raw === "string" ? raw : this._default;
588
+ }
589
+ isValid(raw, ctx, path) {
590
+ if (typeof raw === "string") return true;
591
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected string, got " + typeof raw);
592
+ return false;
593
+ }
594
+ };
595
+ var Num = class extends Base {
596
+ constructor(_default = 0) {
597
+ super();
598
+ this._default = _default;
599
+ }
600
+ sanitize(raw) {
601
+ return typeof raw === "number" && isFinite(raw) ? raw : this._default;
602
+ }
603
+ isValid(raw, ctx, path) {
604
+ if (typeof raw === "number" && isFinite(raw)) return true;
605
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected finite number, got " + JSON.stringify(raw));
606
+ return false;
607
+ }
608
+ };
609
+ var Bool = class extends Base {
610
+ constructor(_default = false) {
611
+ super();
612
+ this._default = _default;
613
+ }
614
+ sanitize(raw) {
615
+ return typeof raw === "boolean" ? raw : this._default;
616
+ }
617
+ isValid(raw, ctx, path) {
618
+ if (typeof raw === "boolean") return true;
619
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected boolean, got " + typeof raw);
620
+ return false;
621
+ }
622
+ };
623
+ var Literal = class extends Base {
624
+ constructor(value) {
625
+ super();
626
+ this.value = value;
627
+ this._default = value;
628
+ }
629
+ sanitize(raw) {
630
+ return raw === this.value ? this.value : this._default;
631
+ }
632
+ isValid(raw, ctx, path) {
633
+ if (raw === this.value) return true;
634
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected " + JSON.stringify(this.value) + ", got " + JSON.stringify(raw));
635
+ return false;
636
+ }
637
+ };
638
+ var Enum = class extends Base {
639
+ constructor(values, defaultVal) {
640
+ super();
641
+ this.values = values;
642
+ this._default = defaultVal != null ? defaultVal : values[0];
643
+ }
644
+ sanitize(raw) {
645
+ return this.values.includes(raw) ? raw : this._default;
646
+ }
647
+ isValid(raw, ctx, path) {
648
+ if (this.values.includes(raw)) return true;
649
+ 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));
650
+ return false;
651
+ }
652
+ };
653
+ var UNION_MEMBER_ERROR_LIMIT = 4;
654
+ var Union = class extends Base {
655
+ constructor(schemas, defaultVal) {
656
+ super();
657
+ this.schemas = schemas;
658
+ /** Structural tag read by {@link describeSchema} — `schemas` alone cannot tell Union from Tuple. */
659
+ this._kind = "union";
660
+ this._default = defaultVal != null ? defaultVal : schemas[0]._default;
661
+ }
662
+ sanitize(raw) {
663
+ for (const s of this.schemas) {
664
+ if (s.isValid(raw)) return s.sanitize(raw);
665
+ }
666
+ return this._default;
667
+ }
668
+ isValid(raw, ctx, path) {
669
+ var _a;
670
+ const probe = ctx && { errors: [], warnings: [], strict: ctx.strict };
671
+ if (this.schemas.some((s) => s.isValid(raw, probe, path ? [...path] : void 0))) return true;
672
+ if (!ctx) return false;
673
+ const base = pathStr(path != null ? path : []);
674
+ ctx.errors.push(base + ": no union member matched for value " + ((_a = JSON.stringify(raw)) != null ? _a : "").slice(0, 240));
675
+ let best;
676
+ let bestDepth = -1;
677
+ const leafExpectations = [];
678
+ for (const member of this.schemas) {
679
+ const sink = { errors: [], warnings: [], strict: ctx.strict };
680
+ member.isValid(raw, sink, path ? [...path] : void 0);
681
+ if (!sink.errors.length) continue;
682
+ const depth = Math.max(...sink.errors.map((e) => e.slice(0, e.indexOf(":")).length));
683
+ if (depth > bestDepth || depth === bestDepth && best && sink.errors.length < best.length) {
684
+ bestDepth = depth;
685
+ best = sink.errors;
686
+ }
687
+ if (depth <= base.length) {
688
+ for (const e of sink.errors) {
689
+ const m = /: expected (.+?), got /.exec(e);
690
+ if (m && !leafExpectations.includes(m[1])) leafExpectations.push(m[1]);
691
+ }
692
+ }
693
+ }
694
+ if (best && bestDepth > base.length) {
695
+ for (const e of best.slice(0, UNION_MEMBER_ERROR_LIMIT)) {
696
+ if (!ctx.errors.includes(e)) ctx.errors.push(e);
697
+ }
698
+ } else if (leafExpectations.length) {
699
+ ctx.errors.push(base + ": expected " + leafExpectations.join(" | "));
700
+ }
701
+ return false;
702
+ }
703
+ _canSanitize(raw) {
704
+ return this.schemas.some((s) => s._canSanitize(raw));
705
+ }
706
+ };
707
+ var DiscriminatedUnion = class extends Base {
708
+ constructor(_key, _schemas, defaultVal) {
709
+ var _a;
710
+ super();
711
+ this._key = _key;
712
+ this._schemas = _schemas;
713
+ /** Structural tag read by {@link describeSchema}. */
714
+ this._kind = "discriminatedUnion";
715
+ this._default = defaultVal != null ? defaultVal : _schemas[0]._default;
716
+ this._map = /* @__PURE__ */ new Map();
717
+ for (const s of _schemas) {
718
+ const keySchema = s._shape[_key];
719
+ if (!keySchema) continue;
720
+ const literal = (_a = keySchema.inner) != null ? _a : keySchema;
721
+ this._map.set(literal._default, s);
722
+ if (keySchema.inner) this._absentMember = s;
723
+ }
724
+ }
725
+ _findSchema(raw) {
726
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
727
+ const val = raw[this._key];
728
+ if (val === void 0 || val === null) return this._absentMember;
729
+ return this._map.get(val);
730
+ }
731
+ sanitize(raw) {
732
+ var _a;
733
+ return ((_a = this._findSchema(raw)) != null ? _a : this._schemas[0]).sanitize(raw);
734
+ }
735
+ isValid(raw, ctx, path) {
736
+ const schema = this._findSchema(raw);
737
+ if (!schema) {
738
+ const val = raw !== null && typeof raw === "object" && !Array.isArray(raw) ? raw[this._key] : void 0;
739
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": no discriminated union member matched " + this._key + "=" + JSON.stringify(val));
740
+ return false;
741
+ }
742
+ return schema.isValid(raw, ctx, path);
743
+ }
744
+ _canSanitize(raw) {
745
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return false;
746
+ const schema = this._findSchema(raw);
747
+ return schema ? schema._canSanitize(raw) : this._schemas[0]._canSanitize(raw);
748
+ }
749
+ };
750
+ var Obj = class extends Base {
751
+ constructor(_shape) {
752
+ super();
753
+ this._shape = _shape;
754
+ const d = {};
755
+ for (const key of Object.keys(_shape)) d[key] = _shape[key]._default;
756
+ this._default = d;
757
+ }
758
+ sanitize(raw) {
759
+ const src = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
760
+ const out = {};
761
+ for (const key of Object.keys(this._shape)) {
762
+ const v = this._shape[key].sanitize(src[key]);
763
+ if (v !== void 0) out[key] = v;
764
+ }
765
+ return out;
766
+ }
767
+ isValid(raw, ctx, path) {
768
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
769
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected object, got " + (Array.isArray(raw) ? "array" : typeof raw));
770
+ return false;
771
+ }
772
+ const obj = raw;
773
+ const p = path != null ? path : [];
774
+ let ok = true;
775
+ for (const key of Object.keys(this._shape)) {
776
+ p.push(key);
777
+ if (!this._shape[key].isValid(obj[key], ctx, p)) ok = false;
778
+ p.pop();
779
+ }
780
+ if (ctx == null ? void 0 : ctx.strict) {
781
+ for (const key of Object.keys(obj)) {
782
+ if (key in this._shape) continue;
783
+ if (obj[key] === void 0) continue;
784
+ p.push(key);
785
+ ctx.errors.push(pathStr(p) + ": " + PX_UNKNOWN_KEY_ERROR);
786
+ p.pop();
787
+ ok = false;
788
+ }
789
+ }
790
+ return ok;
791
+ }
792
+ _canSanitize(raw) {
793
+ return !!raw && typeof raw === "object" && !Array.isArray(raw);
794
+ }
795
+ };
796
+ var OpenObj = class extends Base {
797
+ constructor(_shape, _openSchema) {
798
+ super();
799
+ this._shape = _shape;
800
+ this._openSchema = _openSchema;
801
+ const d = {};
802
+ for (const key of Object.keys(_shape)) d[key] = _shape[key]._default;
803
+ this._default = d;
804
+ }
805
+ sanitize(raw) {
806
+ const src = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
807
+ const out = __spreadValues({}, src);
808
+ for (const key of Object.keys(this._shape)) {
809
+ const v = this._shape[key].sanitize(src[key]);
810
+ if (v !== void 0) out[key] = v;
811
+ }
812
+ if (this._openSchema) {
813
+ for (const key of Object.keys(src)) {
814
+ if (!(key in this._shape)) out[key] = this._openSchema.sanitize(src[key]);
815
+ }
816
+ }
817
+ return out;
818
+ }
819
+ isValid(raw, ctx, path) {
820
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
821
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected object, got " + (Array.isArray(raw) ? "array" : typeof raw));
822
+ return false;
823
+ }
824
+ const obj = raw;
825
+ const p = path != null ? path : [];
826
+ let ok = true;
827
+ for (const key of Object.keys(this._shape)) {
828
+ p.push(key);
829
+ if (!this._shape[key].isValid(obj[key], ctx, p)) ok = false;
830
+ p.pop();
831
+ }
832
+ if (this._openSchema) {
833
+ for (const key of Object.keys(obj)) {
834
+ if (key in this._shape) continue;
835
+ p.push(key);
836
+ if (!this._openSchema.isValid(obj[key], ctx, p)) ok = false;
837
+ p.pop();
838
+ }
839
+ }
840
+ return ok;
841
+ }
842
+ _canSanitize(raw) {
843
+ return !!raw && typeof raw === "object" && !Array.isArray(raw);
844
+ }
845
+ };
846
+ var Arr = class extends Base {
847
+ constructor(item) {
848
+ super();
849
+ this.item = item;
850
+ this._default = [];
851
+ }
852
+ sanitize(raw) {
853
+ if (!Array.isArray(raw)) return [];
854
+ const out = [];
855
+ for (const el of raw) {
856
+ if (this.item._canSanitize(el)) out.push(this.item.sanitize(el));
857
+ }
858
+ return out;
859
+ }
860
+ isValid(raw, ctx, path) {
861
+ if (!Array.isArray(raw)) {
862
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected array, got " + typeof raw);
863
+ return false;
864
+ }
865
+ const p = path != null ? path : [];
866
+ let ok = true;
867
+ for (let i = 0; i < raw.length; i++) {
868
+ p.push("[" + i + "]");
869
+ if (!this.item.isValid(raw[i], ctx, p)) ok = false;
870
+ p.pop();
871
+ }
872
+ return ok;
873
+ }
874
+ _canSanitize(raw) {
875
+ return Array.isArray(raw);
876
+ }
877
+ };
878
+ var Rec = class extends Base {
879
+ constructor(value) {
880
+ super();
881
+ this.value = value;
882
+ /** Structural tag read by {@link describeSchema}. */
883
+ this._kind = "record";
884
+ this._default = {};
885
+ }
886
+ sanitize(raw) {
887
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
888
+ const out = {};
889
+ for (const [k, v] of Object.entries(raw)) {
890
+ if (this.value._canSanitize(v)) out[k] = this.value.sanitize(v);
891
+ }
892
+ return out;
893
+ }
894
+ isValid(raw, ctx, path) {
895
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
896
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected object/record, got " + (Array.isArray(raw) ? "array" : typeof raw));
897
+ return false;
898
+ }
899
+ const p = path != null ? path : [];
900
+ let ok = true;
901
+ for (const [k, v] of Object.entries(raw)) {
902
+ p.push(k);
903
+ if (!this.value.isValid(v, ctx, p)) ok = false;
904
+ p.pop();
905
+ }
906
+ return ok;
907
+ }
908
+ _canSanitize(raw) {
909
+ return !!raw && typeof raw === "object" && !Array.isArray(raw);
910
+ }
911
+ };
912
+ var Any = class extends Base {
913
+ constructor() {
914
+ super(...arguments);
915
+ this._default = void 0;
916
+ }
917
+ sanitize(raw) {
918
+ return raw;
919
+ }
920
+ isValid(_raw, _ctx, _path) {
921
+ return true;
922
+ }
923
+ _canSanitize(_raw) {
924
+ return true;
925
+ }
926
+ };
927
+ var Defined = class extends Base {
928
+ constructor() {
929
+ super(...arguments);
930
+ this._default = void 0;
931
+ }
932
+ sanitize(raw) {
933
+ return raw;
934
+ }
935
+ isValid(raw, ctx, path) {
936
+ if (raw !== void 0) return true;
937
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": required value is missing");
938
+ return false;
939
+ }
940
+ _canSanitize(raw) {
941
+ return raw !== void 0;
942
+ }
943
+ };
944
+ var Lazy = class extends Base {
945
+ constructor(fn, _default) {
946
+ super();
947
+ this.fn = fn;
948
+ this._default = _default;
949
+ this.resolved = null;
950
+ }
951
+ get schema() {
952
+ var _a;
953
+ return (_a = this.resolved) != null ? _a : this.resolved = this.fn();
954
+ }
955
+ sanitize(raw) {
956
+ return this.schema.sanitize(raw);
957
+ }
958
+ isValid(raw, ctx, path) {
959
+ return this.schema.isValid(raw, ctx, path);
960
+ }
961
+ _canSanitize(raw) {
962
+ return this.schema._canSanitize(raw);
963
+ }
964
+ };
965
+ var Tuple = class extends Base {
966
+ constructor(schemas) {
967
+ super();
968
+ this.schemas = schemas;
969
+ /** Structural tag read by {@link describeSchema} — `schemas` alone cannot tell Tuple from Union. */
970
+ this._kind = "tuple";
971
+ this._default = schemas.map((s) => s._default);
972
+ }
973
+ sanitize(raw) {
974
+ if (!Array.isArray(raw) || raw.length !== this.schemas.length) return this._default;
975
+ return this.schemas.map((s, i) => s.sanitize(raw[i]));
976
+ }
977
+ isValid(raw, ctx, path) {
978
+ if (!Array.isArray(raw) || raw.length !== this.schemas.length) {
979
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected tuple of length " + this.schemas.length + ", got " + (Array.isArray(raw) ? "array[" + raw.length + "]" : typeof raw));
980
+ return false;
981
+ }
982
+ const p = path != null ? path : [];
983
+ let ok = true;
984
+ for (let i = 0; i < this.schemas.length; i++) {
985
+ p.push("[" + i + "]");
986
+ if (!this.schemas[i].isValid(raw[i], ctx, p)) ok = false;
987
+ p.pop();
988
+ }
989
+ return ok;
990
+ }
991
+ // Require exact length so wrong-length arrays are dropped rather than repaired to default.
992
+ _canSanitize(raw) {
993
+ return Array.isArray(raw) && raw.length === this.schemas.length;
994
+ }
995
+ };
996
+ function implementsInterface() {
997
+ return (schema) => schema;
998
+ }
999
+ var px = {
1000
+ /** Matches a string. Default: '' or provided value. */
1001
+ string: (defaultVal = "") => new Str(defaultVal),
1002
+ /** Matches a finite number. Default: 0 or provided value. */
1003
+ number: (defaultVal = 0) => new Num(defaultVal),
1004
+ /** Matches a boolean. Default: false or provided value. */
1005
+ boolean: (defaultVal = false) => new Bool(defaultVal),
1006
+ /** Matches one exact primitive value; its default is the value itself. */
1007
+ literal: (value) => new Literal(value),
1008
+ /** Matches one of a fixed set of string/number values. Default: first value. */
1009
+ enum: (values, defaultVal) => new Enum(values, defaultVal),
1010
+ /**
1011
+ * Returns the first schema whose isValid passes.
1012
+ * TypeScript infers the union of all member types automatically.
1013
+ */
1014
+ union: (schemas, defaultVal) => new Union(schemas, defaultVal),
1015
+ /**
1016
+ * Discriminated union — reads `raw[key]`, finds the member schema whose
1017
+ * literal at `key` matches, then delegates sanitize/isValid to that member.
1018
+ * Each member must be an object schema with a `px.literal(...)` at `key`.
1019
+ * TypeScript infers the union of all member types automatically.
1020
+ */
1021
+ discriminatedUnion: (key, schemas) => new DiscriminatedUnion(key, schemas),
1022
+ /** Typed object — unknown keys are stripped. Required fields fall back to their default. */
1023
+ object: (shape) => new Obj(shape),
1024
+ /**
1025
+ * Open object — validates known keys; passes unknown keys through as-is,
1026
+ * or validates/sanitizes them against `openSchema` when provided.
1027
+ */
1028
+ openObject: (shape, openSchema) => new OpenObj(shape, openSchema),
1029
+ /**
1030
+ * Creates a new closed object schema by merging a base schema's shape with additional fields.
1031
+ * The base can be the result of px.object() or px.openObject() — anything with a _shape property.
1032
+ *
1033
+ * @example
1034
+ * const PxSvgNodeSchema = px.extendedObject(PxNodeBase, { width: px.number().optional() });
1035
+ */
1036
+ extendedObject: (base, extra) => new Obj(__spreadValues(__spreadValues({}, base._shape), extra)),
1037
+ /** Array whose unrecoverable items are filtered out. Default: []. */
1038
+ array: (item) => new Arr(item),
1039
+ /** String-keyed record whose unrecoverable values are dropped. Default: {}. */
1040
+ record: (value) => new Rec(value),
1041
+ /** Passes anything through unchanged — always valid. */
1042
+ any: () => new Any(),
1043
+ /** Anything EXCEPT `undefined` — an open type whose presence is required (V6). */
1044
+ defined: () => new Defined(),
1045
+ /** Fixed-length tuple — validates element count and each position individually. */
1046
+ tuple: (schemas) => new Tuple(schemas),
1047
+ /** Defers schema creation — required for recursive types. Must supply a default value. */
1048
+ lazy: (fn, defaultVal) => new Lazy(fn, defaultVal)
1049
+ };
1050
+
1051
+ // ../svg-animator-core/src/format/PxAnimatorConstants.ts
1052
+ var PxFillMode = {
1053
+ forwards: "forwards",
1054
+ backwards: "backwards",
1055
+ both: "both",
1056
+ none: "none"
1057
+ };
1058
+ var PxPlaybackDirection = {
1059
+ normal: "normal",
1060
+ reverse: "reverse",
1061
+ alternate: "alternate",
1062
+ alternateReverse: "alternate-reverse"
1063
+ };
1064
+ var PxStartOn = {
1065
+ load: "load",
1066
+ mouseOver: "mouseOver",
1067
+ click: "click",
1068
+ scrollIntoView: "scrollIntoView",
1069
+ programmatic: "programmatic"
1070
+ };
1071
+ var PxOutAction = {
1072
+ continue: "continue",
1073
+ pause: "pause",
1074
+ reset: "reset",
1075
+ reverse: "reverse"
1076
+ };
1077
+ var PxFinishAction = {
1078
+ hold: "hold",
1079
+ reset: "reset"
1080
+ };
1081
+ var PxScrollKind = {
1082
+ view: "view",
1083
+ scroll: "scroll"
1084
+ };
1085
+ var PxScrollAxis = {
1086
+ block: "block",
1087
+ inline: "inline",
1088
+ x: "x",
1089
+ y: "y"
1090
+ };
1091
+ var PxScrollSource = {
1092
+ nearest: "nearest",
1093
+ root: "root"
1094
+ };
1095
+ var PxPinAlign = {
1096
+ top: "top",
1097
+ center: "center",
1098
+ bottom: "bottom"
1099
+ };
1100
+ var PxScrollPhase = {
1101
+ cover: "cover",
1102
+ contain: "contain",
1103
+ entry: "entry",
1104
+ exit: "exit",
1105
+ entryCrossing: "entry-crossing",
1106
+ exitCrossing: "exit-crossing"
1107
+ };
1108
+ var PxAlongPathMode = {
1109
+ sampled: "sampled",
1110
+ offsetPath: "offsetPath"
1111
+ };
1112
+ var PX_TIMELINE_SHARED_KEYS = ["duration", "iterations", "engine", "frameRate"];
1113
+ var PX_TIME_ONLY_TIMELINE_KEYS = ["trigger", "delay", "fillMode", "direction"];
1114
+ var PX_FLAT_RUNTIME_VIEW_KEYS = [
1115
+ ...PX_TIMELINE_SHARED_KEYS,
1116
+ ...PX_TIME_ONLY_TIMELINE_KEYS,
1117
+ "fill",
1118
+ "resetOnFinish",
1119
+ "timelineSource",
1120
+ "scroll"
1121
+ ];
1122
+ var PxTimelineEngine = {
1123
+ native: "native",
1124
+ js: "js"
1125
+ };
1126
+ var PxTimelineEngineExtra = __spreadProps(__spreadValues({}, PxTimelineEngine), {
1127
+ auto: "auto"
1128
+ });
1129
+ function isNativeForced(engine) {
1130
+ return engine === PxTimelineEngineExtra.native;
1131
+ }
1132
+ function mayUseNativeScrollTimeline(engine) {
1133
+ return engine !== PxTimelineEngineExtra.js;
1134
+ }
1135
+ var PX_TRIGGER_DEFAULTS = {
1136
+ startOn: "load",
1137
+ outAction: "continue",
1138
+ scrollIntoViewThreshold: 0
1139
+ };
1140
+ function resolveTrigger(trigger) {
1141
+ var _a, _b, _c;
1142
+ return {
1143
+ startOn: (_a = trigger == null ? void 0 : trigger.startOn) != null ? _a : PX_TRIGGER_DEFAULTS.startOn,
1144
+ outAction: (_b = trigger == null ? void 0 : trigger.outAction) != null ? _b : PX_TRIGGER_DEFAULTS.outAction,
1145
+ scrollIntoViewThreshold: (_c = trigger == null ? void 0 : trigger.scrollIntoViewThreshold) != null ? _c : PX_TRIGGER_DEFAULTS.scrollIntoViewThreshold
1146
+ };
1147
+ }
1148
+ var PxLoopRepeatAt = {
1149
+ /** Segment from the START; the repetition runs BEFORE the first keyframe
1150
+ * (intro loops that play until the main timeline begins). */
1151
+ start: "start",
1152
+ /** DEFAULT — segment from the END; the repetition runs AFTER the last keyframe
1153
+ * (idle/outro loops that continue once the main timeline has finished). */
1154
+ end: "end"
1155
+ };
1156
+ var PxLoopDirection = {
1157
+ /** DEFAULT — cycle: every repetition replays the segment the same way round. */
1158
+ normal: "normal",
1159
+ /** Ping-pong: repetitions alternate forward / backward. */
1160
+ alternate: "alternate"
1161
+ };
1162
+ var PxMaskType = {
1163
+ luminance: "luminance",
1164
+ alpha: "alpha"
1165
+ };
1166
+ var PxUnits = {
1167
+ userSpaceOnUse: "userSpaceOnUse",
1168
+ objectBoundingBox: "objectBoundingBox"
1169
+ };
1170
+ var PxCloneWithout = {
1171
+ translate: "translate"
1172
+ // transform: 'transform', // future: drop rotate/scale too (content only)
1173
+ };
1174
+ var PxPathOverflow = {
1175
+ clip: "clip",
1176
+ extend: "extend"
1177
+ };
1178
+ var PxLengthAdjust = {
1179
+ spacing: "spacing",
1180
+ spacingAndGlyphs: "spacingAndGlyphs"
1181
+ };
1182
+ var PxTextPathMethod = {
1183
+ align: "align",
1184
+ stretch: "stretch"
1185
+ };
1186
+ var PxTextPathSpacing = {
546
1187
  auto: "auto",
547
- waapi: "waapi",
548
- frames: "frames"
1188
+ exact: "exact"
549
1189
  };
550
- var PxAnimatorEngine = {
551
- waapi: PxAnimatorMode.waapi,
552
- frames: PxAnimatorMode.frames
1190
+ var PxStrokeTrimSubPaths = {
1191
+ separate: "separate",
1192
+ combined: "combined"
553
1193
  };
554
- var PxLoopExtend = {
555
- /** Segment from the START; the animation is extended BEFORE the first keyframe
556
- * (intro loops that run before the main timeline begins). */
557
- before: "before",
558
- /** DEFAULT — segment from the END; extended AFTER the last keyframe (idle/outro
559
- * loops that continue once the main timeline has finished). */
560
- after: "after"
1194
+ var TRANSFORM_ATTR = "transform";
1195
+ var TRANSFORM_PART = {
1196
+ translate: "translate",
1197
+ rotate: "rotate",
1198
+ scale: "scale",
1199
+ origin: "origin"
1200
+ };
1201
+ var PX_TRANSFORM_PART_KEYS = [
1202
+ TRANSFORM_PART.translate,
1203
+ TRANSFORM_PART.rotate,
1204
+ TRANSFORM_PART.scale,
1205
+ TRANSFORM_PART.origin
1206
+ ];
1207
+ var PxGradientSpreadMethod = {
1208
+ pad: "pad",
1209
+ reflect: "reflect",
1210
+ repeat: "repeat"
1211
+ };
1212
+ var PxGradientType = {
1213
+ linear: "linear",
1214
+ radial: "radial"
561
1215
  };
562
1216
  function getAnimatorConfig(doc) {
563
1217
  var _a;
564
1218
  const cfg = (doc == null ? void 0 : doc.animator) || ((_a = doc == null ? void 0 : doc.meta) == null ? void 0 : _a.animator);
565
- return cfg ? flattenAnimatorTimeline(cfg) : void 0;
566
- }
1219
+ if (!cfg) return void 0;
1220
+ const memoised = wireViewMemo.get(cfg);
1221
+ if (memoised) return memoised;
1222
+ const wire = cfg;
1223
+ const stray = PX_FLAT_RUNTIME_VIEW_KEYS.filter((k) => wire[k] !== void 0);
1224
+ const source = stray.length ? __spreadValues({}, wire) : cfg;
1225
+ for (const k of stray) delete source[k];
1226
+ const view = flattenAnimatorTimeline(source);
1227
+ wireViewMemo.set(cfg, view);
1228
+ return view;
1229
+ }
1230
+ var wireViewMemo = /* @__PURE__ */ new WeakMap();
567
1231
  var flattenMemo = /* @__PURE__ */ new WeakMap();
568
1232
  function flattenAnimatorTimeline(cfg) {
569
1233
  const timeline = cfg.timeline;
@@ -571,13 +1235,14 @@ var PixodeskAnimator = (() => {
571
1235
  const memoised = flattenMemo.get(cfg);
572
1236
  if (memoised) return memoised;
573
1237
  const _a = cfg, { timeline: _dropped } = _a, flat = __objRest(_a, ["timeline"]);
1238
+ if (timeline.engine !== void 0) flat.engine = timeline.engine;
1239
+ if (timeline.frameRate !== void 0) flat.frameRate = timeline.frameRate;
574
1240
  if (timeline.type === "scroll" || timeline.type === "view") {
575
1241
  flat.timelineSource = "scroll";
576
1242
  if (timeline.duration !== void 0) flat.duration = timeline.duration;
577
1243
  if (timeline.iterations !== void 0) flat.iterations = timeline.iterations;
578
1244
  const scroll = __spreadValues({}, flat.scroll || {});
579
1245
  scroll.kind = timeline.type;
580
- if (timeline.engine !== void 0) scroll.driver = timeline.engine;
581
1246
  if (timeline.axis !== void 0) scroll.axis = timeline.axis;
582
1247
  if (timeline.source !== void 0) scroll.source = timeline.source;
583
1248
  if (timeline.subject !== void 0) scroll.subject = timeline.subject;
@@ -588,21 +1253,21 @@ var PixodeskAnimator = (() => {
588
1253
  else if (pin && typeof pin === "object") {
589
1254
  scroll.pin = true;
590
1255
  if (pin.align !== void 0) scroll.pinAlign = pin.align;
591
- if (pin.top !== void 0) scroll.pinTop = pin.top;
1256
+ if (pin.offset !== void 0) scroll.pinOffset = pin.offset;
592
1257
  if (pin.distance !== void 0) scroll.pinDistance = pin.distance;
593
1258
  }
594
1259
  flat.scroll = scroll;
595
1260
  } else {
596
1261
  if (timeline.duration !== void 0) flat.duration = timeline.duration;
597
1262
  if (timeline.trigger !== void 0) {
598
- const _b = timeline.trigger, { onFinish } = _b, restTrigger = __objRest(_b, ["onFinish"]);
1263
+ const _b = timeline.trigger, { finishAction } = _b, restTrigger = __objRest(_b, ["finishAction"]);
599
1264
  if (Object.keys(restTrigger).length) flat.trigger = restTrigger;
600
- if (onFinish !== void 0) flat.resetOnFinish = onFinish === "reset";
1265
+ if (finishAction !== void 0) flat.resetOnFinish = finishAction === "reset";
601
1266
  }
602
1267
  if (timeline.delay !== void 0) flat.delay = timeline.delay;
603
1268
  if (timeline.iterations !== void 0) flat.iterations = timeline.iterations;
604
1269
  if (timeline.direction !== void 0) flat.direction = timeline.direction;
605
- if (timeline.fill !== void 0) flat.fill = timeline.fill;
1270
+ if (timeline.fillMode !== void 0) flat.fill = timeline.fillMode;
606
1271
  }
607
1272
  flattenMemo.set(cfg, flat);
608
1273
  return flat;
@@ -620,10 +1285,384 @@ var PixodeskAnimator = (() => {
620
1285
  return Object.entries(animateById).map(([id, anim]) => ({ id: id.startsWith("#") ? id.slice(1) : id, animate: anim }));
621
1286
  }
622
1287
 
623
- // ../svg-animator-core/src/PxMotionPath.ts
624
- function getKfTranslate(kf) {
1288
+ // ../svg-animator-core/src/format/PxAnimatorTypes.ts
1289
+ var PxEasingOrRefSchema = px.union([
1290
+ px.string(),
1291
+ px.tuple([px.number(), px.number(), px.number(), px.number()])
1292
+ ]);
1293
+ var PxKeyframeValueSchema = implementsInterface()(px.union([
1294
+ px.string(),
1295
+ // e.g. for colors
1296
+ px.number(),
1297
+ px.array(px.number()),
1298
+ // ORDER LAW: the key-discriminated object shape (`{pathData}`) comes BEFORE the
1299
+ // all-optional transform-parts record. In default (non-strict) mode that record accepts
1300
+ // ANY object (every key optional, unknown keys ignored), so listing it earlier made
1301
+ // Union.sanitize route `{pathData}` values into it and strip them to `{}` —
1302
+ // silent morph-data loss (repro: the editor's keyframeValueSanitize spec). Validity is
1303
+ // order-independent (`some()`); only sanitize routing depends on this order.
1304
+ px.object({ pathData: px.string() }),
1305
+ // Gradient `stops` timeline — each kf value is the full stops-array snapshot.
1306
+ px.lazy(() => px.array(PxGradientStopSchema), []),
1307
+ px.lazy(() => PxTransformPartsSchema, {})
1308
+ ]));
1309
+ var PxKeyframeSchema = implementsInterface()(px.object({
1310
+ time: px.number().optional(),
1311
+ value: PxKeyframeValueSchema.optional(),
1312
+ easing: PxEasingOrRefSchema.optional(),
1313
+ tangentOut: px.tuple([px.number(), px.number()]).optional(),
1314
+ tangentIn: px.tuple([px.number(), px.number()]).optional()
1315
+ // (`selected` — editor timeline-selection UI state — was REMOVED from the wire
1316
+ // (review §1.3): editor data lives under `meta`. The editor still carries it on
1317
+ // its internal COPY-PASTE payload, which never validates against this schema.)
1318
+ }));
1319
+ var anyKf = (kf) => kf;
1320
+ var kfTime = (kf) => {
1321
+ var _a, _b;
1322
+ return (_b = (_a = anyKf(kf).time) != null ? _a : anyKf(kf).t) != null ? _b : 0;
1323
+ };
1324
+ var kfValue = (kf) => {
1325
+ var _a;
1326
+ return (_a = anyKf(kf).value) != null ? _a : anyKf(kf).v;
1327
+ };
1328
+ var kfEasing = (kf) => {
625
1329
  var _a;
626
- const v = (_a = kf.value) != null ? _a : kf.v;
1330
+ return (_a = anyKf(kf).easing) != null ? _a : anyKf(kf).e;
1331
+ };
1332
+ var kfTangentIn = (kf) => anyKf(kf).tangentIn;
1333
+ var kfTangentOut = (kf) => anyKf(kf).tangentOut;
1334
+ var PxLoopSchema = implementsInterface()(px.object({
1335
+ segmentCount: px.number().optional(),
1336
+ repeatAt: px.enum([PxLoopRepeatAt.start, PxLoopRepeatAt.end]).optional(),
1337
+ direction: px.enum([PxLoopDirection.normal, PxLoopDirection.alternate]).optional()
1338
+ }));
1339
+ var PxPropertyAnimationSchema = implementsInterface()(px.object({
1340
+ value: PxKeyframeValueSchema.optional(),
1341
+ keyframes: px.array(PxKeyframeSchema).optional(),
1342
+ loop: px.union([PxLoopSchema, px.boolean()]).optional(),
1343
+ autoOrient: px.boolean().optional(),
1344
+ alongPathMode: px.enum([PxAlongPathMode.sampled, PxAlongPathMode.offsetPath]).optional()
1345
+ }));
1346
+ var PxTransformPartsSchema = implementsInterface()(px.object({
1347
+ translate: px.tuple([px.number(), px.number()]).optional(),
1348
+ rotate: px.number().optional(),
1349
+ skew: px.number().optional(),
1350
+ scale: px.tuple([px.number(), px.number()]).optional(),
1351
+ origin: px.tuple([px.number(), px.number()]).optional()
1352
+ }));
1353
+ var PxTransformValueSchema = px.union([
1354
+ px.string(),
1355
+ PxTransformPartsSchema,
1356
+ px.object({ value: PxTransformPartsSchema }),
1357
+ PxPropertyAnimationSchema
1358
+ ]);
1359
+ var PxAnimationDefinitionSchema = implementsInterface()(
1360
+ px.record(PxPropertyAnimationSchema)
1361
+ );
1362
+ var PxElementAnimationSchema = implementsInterface()(px.union([
1363
+ px.string(),
1364
+ px.array(px.union([px.string(), PxAnimationDefinitionSchema])),
1365
+ PxAnimationDefinitionSchema
1366
+ ]));
1367
+ var PxTriggerSchema = implementsInterface()(px.object({
1368
+ startOn: px.enum([PxStartOn.load, PxStartOn.mouseOver, PxStartOn.click, PxStartOn.scrollIntoView, PxStartOn.programmatic], PX_TRIGGER_DEFAULTS.startOn).optional(),
1369
+ outAction: px.enum([PxOutAction.continue, PxOutAction.pause, PxOutAction.reset, PxOutAction.reverse], PX_TRIGGER_DEFAULTS.outAction).optional(),
1370
+ // What happens after a NATURAL finish — `'hold'` (default: keep the end state per
1371
+ // `fill`) or `'reset'` (snap back to the start state). Pairs with `outAction` ("what
1372
+ // happens when the trigger condition ends"); both end-of-life knobs now read alike.
1373
+ finishAction: px.enum([PxFinishAction.hold, PxFinishAction.reset]).optional(),
1374
+ scrollIntoViewThreshold: px.number().optional()
1375
+ }));
1376
+ var PxGlyphSchema = implementsInterface()(px.object({
1377
+ width: px.number(),
1378
+ pathData: px.string()
1379
+ }));
1380
+ var PxGlyphFontSchema = implementsInterface()(px.object({
1381
+ fontFamily: px.string(),
1382
+ fontStyle: px.string(),
1383
+ ascent: px.number(),
1384
+ unitsPerEm: px.number(),
1385
+ glyphs: px.record(PxGlyphSchema)
1386
+ }));
1387
+ var PxDefsSchema = implementsInterface()(px.object({
1388
+ easings: px.record(px.tuple([px.number(), px.number(), px.number(), px.number()])).optional(),
1389
+ animations: px.record(PxAnimationDefinitionSchema).optional(),
1390
+ // Review §2.6: the schema now matches the declared type — a style preset is a flat
1391
+ // record of string|number attribute values, nothing nested.
1392
+ styles: px.record(px.record(px.union([px.string(), px.number()]))).optional(),
1393
+ fonts: px.record(PxGlyphFontSchema).optional()
1394
+ }));
1395
+ var PxScrollRangePointSchema = implementsInterface()(px.object({
1396
+ phase: px.enum([
1397
+ PxScrollPhase.cover,
1398
+ PxScrollPhase.contain,
1399
+ PxScrollPhase.entry,
1400
+ PxScrollPhase.exit,
1401
+ PxScrollPhase.entryCrossing,
1402
+ PxScrollPhase.exitCrossing
1403
+ ]).optional(),
1404
+ fraction: px.number().optional()
1405
+ }));
1406
+ var PxScrollRangeSchema = px.object({
1407
+ start: PxScrollRangePointSchema.optional(),
1408
+ end: PxScrollRangePointSchema.optional()
1409
+ });
1410
+ var PxScrollSchema = implementsInterface()(px.object({
1411
+ kind: px.enum([PxScrollKind.view, PxScrollKind.scroll]).optional(),
1412
+ axis: px.enum([PxScrollAxis.block, PxScrollAxis.inline, PxScrollAxis.x, PxScrollAxis.y]).optional(),
1413
+ source: px.enum([PxScrollSource.nearest, PxScrollSource.root]).optional(),
1414
+ // Free-form: the two keywords `parent`/`scroller` plus any CSS selector.
1415
+ subject: px.string().optional(),
1416
+ smoothing: px.number().optional(),
1417
+ pin: px.boolean().optional(),
1418
+ pinAlign: px.enum([PxPinAlign.top, PxPinAlign.center, PxPinAlign.bottom]).optional(),
1419
+ pinOffset: px.number().optional(),
1420
+ pinDistance: px.number().optional(),
1421
+ range: PxScrollRangeSchema.optional()
1422
+ }));
1423
+ var PxTimelinePinSchema = implementsInterface()(px.object({
1424
+ align: px.enum([PxPinAlign.top, PxPinAlign.center, PxPinAlign.bottom]).optional(),
1425
+ offset: px.number().optional(),
1426
+ distance: px.number().optional()
1427
+ }));
1428
+ var PxTimelineEngineSchema = px.enum([PxTimelineEngineExtra.auto, PxTimelineEngineExtra.native, PxTimelineEngineExtra.js]).optional();
1429
+ var PxTimeTimelineSchema = implementsInterface()(px.object({
1430
+ type: px.literal("time").optional(),
1431
+ engine: PxTimelineEngineSchema,
1432
+ frameRate: px.number().optional(),
1433
+ // §2.8: duration is a property of the TIMELINE — how long one pass takes.
1434
+ duration: px.number().optional(),
1435
+ trigger: PxTriggerSchema.optional(),
1436
+ delay: px.number().optional(),
1437
+ iterations: px.union([px.number(), px.literal("infinite")]).optional(),
1438
+ // `fillMode` on the wire (CSS `animation-fill-mode`; the runtime view calls it `fill`)
1439
+ // — never `fill`, which is paint everywhere else in the format.
1440
+ fillMode: px.enum([PxFillMode.forwards, PxFillMode.backwards, PxFillMode.both, PxFillMode.none]).optional(),
1441
+ direction: px.enum([PxPlaybackDirection.normal, PxPlaybackDirection.reverse, PxPlaybackDirection.alternate, PxPlaybackDirection.alternateReverse]).optional()
1442
+ }));
1443
+ var scrollishTimelineShape = {
1444
+ // §2.8: duration is a property of the TIMELINE — under scrubbing it is the keyframe
1445
+ // span the scroll range maps onto.
1446
+ duration: px.number().optional(),
1447
+ // Finite repeat count IS meaningful when scrubbing — the scroll range maps onto
1448
+ // duration × iterations (rule D4; `'infinite'` cannot map to a range, so no literal here).
1449
+ iterations: px.number().optional(),
1450
+ engine: PxTimelineEngineSchema,
1451
+ frameRate: px.number().optional(),
1452
+ axis: px.enum([PxScrollAxis.block, PxScrollAxis.inline, PxScrollAxis.x, PxScrollAxis.y]).optional(),
1453
+ source: px.enum([PxScrollSource.nearest, PxScrollSource.root]).optional(),
1454
+ subject: px.string().optional(),
1455
+ // 'parent' | 'scroller' | any CSS selector
1456
+ smoothing: px.number().optional(),
1457
+ // ms
1458
+ pin: px.union([px.boolean(), PxTimelinePinSchema]).optional(),
1459
+ range: PxScrollRangeSchema.optional()
1460
+ };
1461
+ var PxScrollTimelineSchema = implementsInterface()(
1462
+ px.object(__spreadValues({ type: px.literal("scroll") }, scrollishTimelineShape))
1463
+ );
1464
+ var PxViewTimelineSchema = implementsInterface()(
1465
+ px.object(__spreadValues({ type: px.literal("view") }, scrollishTimelineShape))
1466
+ );
1467
+ var PxTimelineSchema = px.discriminatedUnion("type", [
1468
+ PxTimeTimelineSchema,
1469
+ // first = the member an absent `type` selects
1470
+ PxScrollTimelineSchema,
1471
+ PxViewTimelineSchema
1472
+ ]);
1473
+ var PxAnimatorConfigSchema = implementsInterface()(px.object({
1474
+ // (`mode`, `duration` and `frameRate` live INSIDE `timeline` on the wire — §2.8; they exist
1475
+ // at this level only on the runtime view, like the rest of the playback dynamics.)
1476
+ // THE spelling of "what advances progress" — clock / scroll / view (review §2.1).
1477
+ timeline: PxTimelineSchema.optional(),
1478
+ definitions: PxDefsSchema.optional(),
1479
+ animateById: px.record(PxElementAnimationSchema).optional(),
1480
+ debugGlobalName: px.string().optional(),
1481
+ // Declared HERE because this is a closed object: an undeclared key would be stripped by
1482
+ // `sanitize` and flagged by strict validation on our own files.
1483
+ version: px.string().optional()
1484
+ }));
1485
+ var PxBindingSchema = implementsInterface()(px.object({
1486
+ id: px.string(),
1487
+ animate: PxElementAnimationSchema
1488
+ }));
1489
+ var PxAttrValueSchema = px.union([
1490
+ px.string(),
1491
+ px.number(),
1492
+ px.array(px.number()),
1493
+ // Structured static — `{value: …}` (read-accepted transitional spelling, S1).
1494
+ // `defined`, not `any`: the KEY's presence is what identifies this branch (V6).
1495
+ px.object({ value: px.defined() }),
1496
+ // Bare transform parts record — the canonical static `transform` on the wire (T2).
1497
+ PxTransformPartsSchema
1498
+ ]);
1499
+ var PxAnimatableNumberSchema = px.union([
1500
+ px.number(),
1501
+ PxPropertyAnimationSchema,
1502
+ px.object({ value: px.number() })
1503
+ ]);
1504
+ var PxAnimatableVec2Schema = px.union([
1505
+ px.tuple([px.number(), px.number()]),
1506
+ PxPropertyAnimationSchema,
1507
+ px.object({ value: px.tuple([px.number(), px.number()]) })
1508
+ ]);
1509
+ var PxAnimatableStringSchema = px.union([
1510
+ px.string(),
1511
+ PxPropertyAnimationSchema,
1512
+ px.object({ value: px.string() })
1513
+ ]);
1514
+ var PxTransformByEffectSchema = implementsInterface()(px.object({
1515
+ translate: PxAnimatableVec2Schema.optional(),
1516
+ rotate: PxAnimatableNumberSchema.optional(),
1517
+ scale: PxAnimatableVec2Schema.optional(),
1518
+ skew: PxAnimatableNumberSchema.optional(),
1519
+ origin: PxAnimatableVec2Schema.optional()
1520
+ }));
1521
+ var PxRepeaterEffectSchema = implementsInterface()(px.object({
1522
+ // STATIC config, not a channel (V2/SCHEMA-DESIGN R5): the copy COUNT is read
1523
+ // once at expansion time and never sampled — plain number, no `keyframes`.
1524
+ copies: px.number().optional(),
1525
+ translate: PxAnimatableVec2Schema.optional(),
1526
+ rotate: PxAnimatableNumberSchema.optional(),
1527
+ skew: PxAnimatableNumberSchema.optional(),
1528
+ scale: PxAnimatableVec2Schema.optional(),
1529
+ origin: PxAnimatableVec2Schema.optional()
1530
+ }));
1531
+ var PxMaskedByEffectSchema = implementsInterface()(px.object({
1532
+ source: px.string().optional(),
1533
+ maskType: px.enum([PxMaskType.luminance, PxMaskType.alpha]).optional(),
1534
+ maskUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
1535
+ maskContentUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
1536
+ x: px.number().optional(),
1537
+ y: px.number().optional(),
1538
+ width: px.number().optional(),
1539
+ height: px.number().optional()
1540
+ }));
1541
+ var PxClipPathEffectSchema = implementsInterface()(px.object({
1542
+ pathData: PxAnimatableStringSchema.optional()
1543
+ }));
1544
+ var PxStrokeTrimEffectSchema = implementsInterface()(px.object({
1545
+ offset: PxAnimatableNumberSchema.optional(),
1546
+ range: PxAnimatableVec2Schema.optional(),
1547
+ subPaths: px.enum([PxStrokeTrimSubPaths.separate, PxStrokeTrimSubPaths.combined]).optional()
1548
+ }));
1549
+ var PxRetimeEffectSchema = implementsInterface()(px.object({
1550
+ start: px.number().optional(),
1551
+ stretch: px.number().optional(),
1552
+ timeCrop: px.tuple([px.number(), px.number()]).optional()
1553
+ }));
1554
+ var PxCloneEffectSchema = implementsInterface()(px.object({
1555
+ // Subtractive on purpose: the `<use>` can only point at one wrapper layer of the
1556
+ // source, so the choices form a ladder — 'translate' now, maybe 'transform' later.
1557
+ without: px.enum([PxCloneWithout.translate]).optional(),
1558
+ source: px.string().optional(),
1559
+ retime: PxRetimeEffectSchema.optional()
1560
+ }));
1561
+ var PxGradientStopSchema = implementsInterface()(px.object({
1562
+ offset: px.number(),
1563
+ color: px.string()
1564
+ }));
1565
+ var PxAnimatableGradientStopsSchema = px.union([
1566
+ px.array(PxGradientStopSchema),
1567
+ px.object({ value: px.array(PxGradientStopSchema) }),
1568
+ PxPropertyAnimationSchema
1569
+ ]);
1570
+ var PxFillGradientEffectSchema = implementsInterface()(px.object({
1571
+ // Contextual kind — the `type` convention, see `PxNodeBase.type`.
1572
+ type: px.enum([PxGradientType.linear, PxGradientType.radial]),
1573
+ start: PxAnimatableVec2Schema.optional(),
1574
+ end: PxAnimatableVec2Schema.optional(),
1575
+ center: PxAnimatableVec2Schema.optional(),
1576
+ radius: PxAnimatableNumberSchema.optional(),
1577
+ focal: PxAnimatableVec2Schema.optional(),
1578
+ stops: PxAnimatableGradientStopsSchema.optional(),
1579
+ gradientUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
1580
+ spreadMethod: px.enum([PxGradientSpreadMethod.pad, PxGradientSpreadMethod.reflect, PxGradientSpreadMethod.repeat]).optional(),
1581
+ gradientTransform: px.string().optional()
1582
+ }));
1583
+ var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
1584
+ var PxTextPathEffectSchema = implementsInterface()(px.object({
1585
+ pathData: px.string(),
1586
+ pathOverflow: px.enum([PxPathOverflow.clip, PxPathOverflow.extend]).optional(),
1587
+ lengthAdjust: px.enum([PxLengthAdjust.spacing, PxLengthAdjust.spacingAndGlyphs]).optional(),
1588
+ method: px.enum([PxTextPathMethod.align, PxTextPathMethod.stretch]).optional(),
1589
+ spacing: px.enum([PxTextPathSpacing.auto, PxTextPathSpacing.exact]).optional(),
1590
+ startOffset: PxAnimatableNumberSchema.optional(),
1591
+ textLength: PxAnimatableNumberSchema.optional()
1592
+ }));
1593
+ var PxTextEffectSchema = implementsInterface()(px.object({
1594
+ useGlyphs: px.boolean().optional()
1595
+ }));
1596
+ var PxEffectsSchema = implementsInterface()(px.object({
1597
+ transformBy: PxTransformByEffectSchema.optional(),
1598
+ repeater: PxRepeaterEffectSchema.optional(),
1599
+ maskedBy: PxMaskedByEffectSchema.optional(),
1600
+ clipPath: PxClipPathEffectSchema.optional(),
1601
+ strokeTrim: PxStrokeTrimEffectSchema.optional(),
1602
+ clone: PxCloneEffectSchema.optional(),
1603
+ fillGradient: PxFillGradientEffectSchema.optional(),
1604
+ strokeGradient: PxStrokeGradientEffectSchema.optional(),
1605
+ textPath: PxTextPathEffectSchema.optional(),
1606
+ text: PxTextEffectSchema.optional()
1607
+ }));
1608
+ var PxNodeBase = px.openObject({
1609
+ // CONVENTION (SCHEMA-DESIGN R1 / issues N4): `type` is the ONE word for "what
1610
+ // kind of thing is this", discriminated by its CARRIER — here the node TAG
1611
+ // (`rect`, `text`), and inside a sub-object that object's kind (`fillGradient.type`,
1612
+ // `fillGradient.type`, editor `preset.type`). Each sits in its own object, so
1613
+ // the carrier disambiguates completely; synonyms (`cloneKind`, `presetShape`)
1614
+ // would add words that all mean "type" and still need the carrier to read.
1615
+ // Guarding a `type` SLOT against a wrong VALUE is the job of strict enums
1616
+ // (issues V3), never of distinct key names.
1617
+ type: px.string(),
1618
+ // The escape hatch for elements that carry a REAL `type` attribute — `<feTurbulence
1619
+ // type="fractalNoise">`, `<feFuncR type="table">`, `<feColorMatrix type="saturate">`.
1620
+ // `type` is taken by the tag name, so the attribute travels here and the renderer puts
1621
+ // it back (`PxAnimatorDOM.renderNode`, `PxRnRender`). Declared here — not merely
1622
+ // documented — because a wire key that is not in a schema is invisible to the
1623
+ // minifier's reserve list and gets renamed (MINIFICATION-BOUNDARY-PLAN.md §1.1).
1624
+ domType: px.string().optional(),
1625
+ // Text content of a `<text>` / `<tspan>`. Declared, so a non-string value is a schema error
1626
+ // and the minifier reserves the key; `text` is NOT an alias for it and is not read anywhere.
1627
+ textContent: px.string().optional(),
1628
+ id: px.string().optional(),
1629
+ meta: px.any().optional(),
1630
+ // Player-effects bucket emitted by the Editor's lightweight design format.
1631
+ // Consumed and removed by `applyPlayerEffects` before any other normalization
1632
+ // (see `createAnimatorImpl`), so downstream code never sees it.
1633
+ effects: PxEffectsSchema.optional(),
1634
+ // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts
1635
+ // string ref / array of refs / inline definition / mixed array; mirrors
1636
+ // `animator.animateById` map values and what `processNode` resolves at runtime.
1637
+ animate: PxElementAnimationSchema.optional(),
1638
+ style: px.union([px.string(), px.record(px.union([px.string(), px.number()]))]).optional()
1639
+ }, PxAttrValueSchema);
1640
+ var PxNodeSchema = px.openObject(__spreadProps(__spreadValues({}, PxNodeBase._shape), {
1641
+ children: px.lazy(() => px.array(PxNodeSchema), []).optional()
1642
+ }), PxAttrValueSchema);
1643
+ var PxSvgNodeExtra = px.object({
1644
+ // `"100%"` and other SVG length strings are legal here — a number-only slot rejected
1645
+ // real documents (e.g. apple-store-look-14-main.json) at the root <svg>.
1646
+ width: px.union([px.number(), px.string()]).optional(),
1647
+ height: px.union([px.number(), px.string()]).optional(),
1648
+ viewBox: px.string().optional(),
1649
+ animator: PxAnimatorConfigSchema.optional()
1650
+ });
1651
+ var PxAnimatedSvgDocumentSchema = px.openObject(__spreadProps(__spreadValues(__spreadValues({}, PxNodeBase._shape), PxSvgNodeExtra._shape), {
1652
+ type: px.literal("svg"),
1653
+ // override string → literal to require 'svg'
1654
+ children: px.array(PxNodeSchema).optional()
1655
+ }), PxAttrValueSchema);
1656
+ var PxBezierPathSchema = implementsInterface()(px.object({
1657
+ v: px.array(px.array(px.number())),
1658
+ i: px.array(px.array(px.number())).optional(),
1659
+ o: px.array(px.array(px.number())).optional(),
1660
+ c: px.boolean().optional()
1661
+ }));
1662
+
1663
+ // ../svg-animator-core/src/materialize/PxMotionPath.ts
1664
+ function getKfTranslate(kf) {
1665
+ const v = kfValue(kf);
627
1666
  if (!v) return void 0;
628
1667
  if (Array.isArray(v) && v.length >= 2 && typeof v[0] === "number" && typeof v[1] === "number") {
629
1668
  return [v[0], v[1]];
@@ -633,31 +1672,27 @@ var PixodeskAnimator = (() => {
633
1672
  return void 0;
634
1673
  }
635
1674
  function getKfTime(kf) {
636
- var _a, _b;
637
- return (_b = (_a = kf.time) != null ? _a : kf.t) != null ? _b : 0;
1675
+ return kfTime(kf);
638
1676
  }
639
1677
  function getKfEasing(kf) {
640
- var _a;
641
- return (_a = kf.easing) != null ? _a : kf.e;
1678
+ return kfEasing(kf);
642
1679
  }
643
1680
  function propAnimIsMotionPath(anim) {
644
- var _a, _b;
645
1681
  const kfs = anim.keyframes;
646
1682
  if (!Array.isArray(kfs)) return false;
647
1683
  if (anim.autoOrient) return true;
648
1684
  for (const kf of kfs) {
649
- if (((_a = kf.tangentIn) != null ? _a : kf.ti) || ((_b = kf.tangentOut) != null ? _b : kf.to)) return true;
1685
+ if (kfTangentIn(kf) || kfTangentOut(kf)) return true;
650
1686
  }
651
1687
  return false;
652
1688
  }
653
1689
  var _segmentCache = /* @__PURE__ */ new WeakMap();
654
1690
  function getSegmentCache(prevKf, nextKf, prevPos, nextPos) {
655
- var _a, _b;
656
1691
  let byNext = _segmentCache.get(prevKf);
657
1692
  const existing = byNext == null ? void 0 : byNext.get(nextKf);
658
1693
  if (existing) return existing;
659
- const to = (_a = prevKf.tangentOut) != null ? _a : prevKf.to;
660
- const ti = (_b = nextKf.tangentIn) != null ? _b : nextKf.ti;
1694
+ const to = kfTangentOut(prevKf);
1695
+ const ti = kfTangentIn(nextKf);
661
1696
  const P1 = [prevPos[0] + (to ? to[0] : 0), prevPos[1] + (to ? to[1] : 0)];
662
1697
  const P2 = [nextPos[0] + (ti ? ti[0] : 0), nextPos[1] + (ti ? ti[1] : 0)];
663
1698
  const lut = bezier2D_arcLengthLUT(prevPos, P1, P2, nextPos);
@@ -706,7 +1741,7 @@ var PixodeskAnimator = (() => {
706
1741
  var DEFAULT_FLATNESS_TOL = 0.5;
707
1742
  var DEFAULT_ROTATION_TOL = 5;
708
1743
  var DEFAULT_MAX_SAMPLES = 32;
709
- function materialiseMotionPathInPropAnim(anim, opts) {
1744
+ function materializeMotionPathInPropAnim(anim, opts) {
710
1745
  var _a, _b, _c;
711
1746
  if (!propAnimIsMotionPath(anim)) return anim;
712
1747
  const kfs = anim.keyframes;
@@ -738,7 +1773,7 @@ var PixodeskAnimator = (() => {
738
1773
  if (autoOrient && i > 0) {
739
1774
  insertSharpCornerStepKfIfNeeded(out, prevKf, nextKf, prevPos, nextPos, rotationTol);
740
1775
  }
741
- materialiseSegment(out, prevKf, nextKf, prevPos, nextPos, autoOrient, flatnessTol, rotationTol, maxSamples);
1776
+ materializeSegment(out, prevKf, nextKf, prevPos, nextPos, autoOrient, flatnessTol, rotationTol, maxSamples);
742
1777
  }
743
1778
  const lastInE = getKfEasing(kfs[kfs.length - 1]);
744
1779
  if (lastInE) out[out.length - 1].e = lastInE;
@@ -748,10 +1783,9 @@ var PixodeskAnimator = (() => {
748
1783
  return result;
749
1784
  }
750
1785
  function unwrapAutoOrientRotations(kfs) {
751
- var _a;
752
1786
  let prev;
753
1787
  for (const kf of kfs) {
754
- const v = (_a = kf.v) != null ? _a : kf.value;
1788
+ const v = kfValue(kf);
755
1789
  if (!v || typeof v.rotate !== "number") continue;
756
1790
  if (prev === void 0) {
757
1791
  prev = v.rotate;
@@ -768,8 +1802,7 @@ var PixodeskAnimator = (() => {
768
1802
  return { t: time, v: value };
769
1803
  }
770
1804
  function getKfValueParts(kf) {
771
- var _a;
772
- const v = (_a = kf.value) != null ? _a : kf.v;
1805
+ const v = kfValue(kf);
773
1806
  if (!v || typeof v !== "object" || Array.isArray(v)) return void 0;
774
1807
  return v;
775
1808
  }
@@ -830,9 +1863,8 @@ var PixodeskAnimator = (() => {
830
1863
  return d;
831
1864
  }
832
1865
  function insertSharpCornerStepKfIfNeeded(out, prevKf, nextKf, prevPos, nextPos, rotationTol) {
833
- var _a;
834
1866
  const lastKf = out[out.length - 1];
835
- const lastV = (_a = lastKf.v) != null ? _a : lastKf.value;
1867
+ const lastV = kfValue(lastKf);
836
1868
  const prevExit = lastV == null ? void 0 : lastV.rotate;
837
1869
  if (typeof prevExit !== "number") return;
838
1870
  const boundaryV = getKfValueParts(prevKf);
@@ -855,7 +1887,7 @@ var PixodeskAnimator = (() => {
855
1887
  out.push(makeOutKf(stepTime, dupValue));
856
1888
  }
857
1889
  var CORNER_STEP_AFTER_BOUNDARY_MS = 0.05;
858
- function materialiseSegment(out, prevKf, nextKf, prevPos, nextPos, autoOrient, flatnessTol, rotationTol, maxSamples) {
1890
+ function materializeSegment(out, prevKf, nextKf, prevPos, nextPos, autoOrient, flatnessTol, rotationTol, maxSamples) {
859
1891
  const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);
860
1892
  const prevTime = getKfTime(prevKf);
861
1893
  const nextTime = getKfTime(nextKf);
@@ -979,7 +2011,7 @@ var PixodeskAnimator = (() => {
979
2011
  return Math.abs(cross) / Math.sqrt(len2);
980
2012
  }
981
2013
 
982
- // ../svg-animator-core/src/PxDefinitions.ts
2014
+ // ../svg-animator-core/src/animation/PxDefinitions.ts
983
2015
  var LOOP_JUMP_SHIFT_MS = 1;
984
2016
  function deepEqualValue(a, b) {
985
2017
  if (a === b) return true;
@@ -1071,8 +2103,8 @@ var PixodeskAnimator = (() => {
1071
2103
  return typeof value === "string" && extractPathData(value) !== void 0;
1072
2104
  }
1073
2105
  function normalizePathValue(value) {
1074
- if (value && typeof value === "object" && typeof value.path === "string") {
1075
- const d = extractPathData(value.path);
2106
+ if (value && typeof value === "object" && typeof value.pathData === "string") {
2107
+ const d = extractPathData(value.pathData);
1076
2108
  return d ? { paths: parseSvgPathToBezier(d) } : value;
1077
2109
  }
1078
2110
  if (value && typeof value === "object" && "paths" in value) {
@@ -1080,8 +2112,8 @@ var PixodeskAnimator = (() => {
1080
2112
  if (Array.isArray(pathsArray) && pathsArray.length > 0) {
1081
2113
  if (isPathString(pathsArray[0])) {
1082
2114
  const paths = [];
1083
- for (const pathStr of pathsArray) {
1084
- const d = extractPathData(pathStr);
2115
+ for (const pathStr2 of pathsArray) {
2116
+ const d = extractPathData(pathStr2);
1085
2117
  if (d) {
1086
2118
  paths.push(...parseSvgPathToBezier(d));
1087
2119
  }
@@ -1094,8 +2126,8 @@ var PixodeskAnimator = (() => {
1094
2126
  if (Array.isArray(value)) {
1095
2127
  if (value.length > 0 && isPathString(value[0])) {
1096
2128
  const paths = [];
1097
- for (const pathStr of value) {
1098
- const d = extractPathData(pathStr);
2129
+ for (const pathStr2 of value) {
2130
+ const d = extractPathData(pathStr2);
1099
2131
  if (d) {
1100
2132
  paths.push(...parseSvgPathToBezier(d));
1101
2133
  }
@@ -1156,7 +2188,7 @@ var PixodeskAnimator = (() => {
1156
2188
  const bPaths = (_b = b == null ? void 0 : b.paths) != null ? _b : Array.isArray(b) ? b : [];
1157
2189
  return { paths: interpolateBeziers(aPaths, bPaths, t) };
1158
2190
  }
1159
- if (COLOUR_ATTR_NAMES.has(propName)) {
2191
+ if (COLOR_ATTR_NAMES.has(propName)) {
1160
2192
  return interpolateColor(a || [0, 0, 0, 1], b || [0, 0, 0, 1], t);
1161
2193
  }
1162
2194
  if (propName === "transform" && typeof a === "object" && a !== null && !Array.isArray(a) && typeof b === "object" && b !== null && !Array.isArray(b)) {
@@ -1192,7 +2224,7 @@ var PixodeskAnimator = (() => {
1192
2224
  const totalIntervals = keyframes.length - 1;
1193
2225
  const segCount = clamp((_a = loop.segmentCount) != null ? _a : totalIntervals, 1, totalIntervals);
1194
2226
  let segKfs;
1195
- if (loop.extend === PxLoopExtend.before) {
2227
+ if (loop.repeatAt === PxLoopRepeatAt.start) {
1196
2228
  segKfs = keyframes.slice(0, segCount + 1);
1197
2229
  } else {
1198
2230
  segKfs = keyframes.slice(totalIntervals - segCount);
@@ -1200,7 +2232,7 @@ var PixodeskAnimator = (() => {
1200
2232
  const firstT = (_b = keyframes[0].t) != null ? _b : 0;
1201
2233
  const lastT = (_c = keyframes[keyframes.length - 1].t) != null ? _c : 0;
1202
2234
  let fillStart, fillEnd;
1203
- if (loop.extend === PxLoopExtend.before) {
2235
+ if (loop.repeatAt === PxLoopRepeatAt.start) {
1204
2236
  fillStart = 0;
1205
2237
  fillEnd = firstT;
1206
2238
  } else {
@@ -1213,21 +2245,18 @@ var PixodeskAnimator = (() => {
1213
2245
  const segEndT = (_e = segKfs[segKfs.length - 1].t) != null ? _e : 0;
1214
2246
  const segDuration = segEndT - segStartT;
1215
2247
  if (segDuration <= 0) return keyframes;
1216
- const template = segKfs.map((kf) => {
1217
- var _a2, _b2;
1218
- return {
1219
- relT: (kf.t - segStartT) / segDuration,
1220
- v: kf.v,
1221
- e: kf.e,
1222
- tangentIn: (_a2 = kf.tangentIn) != null ? _a2 : kf.ti,
1223
- tangentOut: (_b2 = kf.tangentOut) != null ? _b2 : kf.to
1224
- };
1225
- });
2248
+ const template = segKfs.map((kf) => ({
2249
+ relT: (kf.t - segStartT) / segDuration,
2250
+ v: kf.v,
2251
+ e: kf.e,
2252
+ tangentIn: kfTangentIn(kf),
2253
+ tangentOut: kfTangentOut(kf)
2254
+ }));
1226
2255
  const fullReps = Math.floor(fillDuration / segDuration);
1227
2256
  const remainder = fillDuration - fullReps * segDuration;
1228
2257
  const partialFraction = remainder / segDuration;
1229
2258
  const looped = [];
1230
- const separateBoundary = loop.extend !== PxLoopExtend.before;
2259
+ const separateBoundary = loop.repeatAt !== PxLoopRepeatAt.start;
1231
2260
  const originalTerminalKf = keyframes[keyframes.length - 1];
1232
2261
  let terminalEasingOverride;
1233
2262
  let hasTerminalEasingOverride = false;
@@ -1335,30 +2364,30 @@ var PixodeskAnimator = (() => {
1335
2364
  looped.push(pushed);
1336
2365
  }
1337
2366
  }
1338
- if (loop.extend === PxLoopExtend.before) {
2367
+ if (loop.repeatAt === PxLoopRepeatAt.start) {
1339
2368
  if (partialFraction > 1e-9) {
1340
- const isReversed = !!loop.alternate && fullReps % 2 === 0;
2369
+ const isReversed = loop.direction === PxLoopDirection.alternate && fullReps % 2 === 0;
1341
2370
  appendRepTail(fillStart, isReversed, partialFraction);
1342
2371
  }
1343
2372
  for (let rep = 0; rep < fullReps; rep++) {
1344
2373
  const distFromBoundary = fullReps - 1 - rep;
1345
- const isReversed = !!loop.alternate && distFromBoundary % 2 === 0;
2374
+ const isReversed = loop.direction === PxLoopDirection.alternate && distFromBoundary % 2 === 0;
1346
2375
  const repStart = fillStart + remainder + rep * segDuration;
1347
2376
  appendRep(repStart, isReversed);
1348
2377
  }
1349
2378
  } else {
1350
2379
  for (let rep = 0; rep < fullReps; rep++) {
1351
- const isReversed = !!loop.alternate && rep % 2 === 0;
2380
+ const isReversed = loop.direction === PxLoopDirection.alternate && rep % 2 === 0;
1352
2381
  const repStart = fillStart + rep * segDuration;
1353
2382
  appendRep(repStart, isReversed);
1354
2383
  }
1355
2384
  if (partialFraction > 1e-9) {
1356
- const isReversed = !!loop.alternate && fullReps % 2 === 0;
2385
+ const isReversed = loop.direction === PxLoopDirection.alternate && fullReps % 2 === 0;
1357
2386
  const repStart = fillStart + fullReps * segDuration;
1358
2387
  appendRep(repStart, isReversed, partialFraction);
1359
2388
  }
1360
2389
  }
1361
- if (loop.extend === PxLoopExtend.before) {
2390
+ if (loop.repeatAt === PxLoopRepeatAt.start) {
1362
2391
  return [...looped, ...keyframes];
1363
2392
  } else {
1364
2393
  if (hasTerminalEasingOverride && keyframes.length > 0) {
@@ -1370,34 +2399,34 @@ var PixodeskAnimator = (() => {
1370
2399
  }
1371
2400
  }
1372
2401
  function normalizeKeyframes(propName, propAnim, duration, defs) {
1373
- var _a, _b, _c, _d, _e, _f, _g;
2402
+ var _a;
1374
2403
  const keyframes = propAnim.keyframes || [];
1375
2404
  const normalized = [];
1376
2405
  for (const kf of keyframes) {
1377
- const timePct = (_b = (_a = kf.time) != null ? _a : kf.t) != null ? _b : 0;
1378
- let value = (_c = kf.value) != null ? _c : kf.v;
1379
- const easing = (_d = kf.easing) != null ? _d : kf.e;
2406
+ const timePct = kfTime(kf);
2407
+ let value = kfValue(kf);
2408
+ const easing = kfEasing(kf);
1380
2409
  if (propName === "d") {
1381
2410
  value = normalizePathValue(value);
1382
2411
  }
1383
2412
  const propNameKebab = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;
1384
- if (COLOUR_ATTR_NAMES.has(propNameKebab)) {
1385
- value = (_e = parseColor(value)) != null ? _e : value;
2413
+ if (COLOR_ATTR_NAMES.has(propNameKebab)) {
2414
+ value = (_a = parseColor(value)) != null ? _a : value;
1386
2415
  }
1387
2416
  const normKf = {
1388
2417
  t: timePct,
1389
2418
  v: value,
1390
2419
  e: resolveEasing(easing, defs)
1391
2420
  };
1392
- const tIn = (_f = kf.tangentIn) != null ? _f : kf.ti;
1393
- const tOut = (_g = kf.tangentOut) != null ? _g : kf.to;
2421
+ const tIn = kfTangentIn(kf);
2422
+ const tOut = kfTangentOut(kf);
1394
2423
  if (tIn) normKf.tangentIn = tIn;
1395
2424
  if (tOut) normKf.tangentOut = tOut;
1396
2425
  normalized.push(normKf);
1397
2426
  }
1398
2427
  normalized.sort((a, b) => {
1399
- var _a2, _b2;
1400
- return ((_a2 = a.t) != null ? _a2 : 0) - ((_b2 = b.t) != null ? _b2 : 0);
2428
+ var _a2, _b;
2429
+ return ((_a2 = a.t) != null ? _a2 : 0) - ((_b = b.t) != null ? _b : 0);
1401
2430
  });
1402
2431
  const loopRaw = propAnim.loop;
1403
2432
  const loop = loopRaw === true ? {} : loopRaw || void 0;
@@ -1424,7 +2453,7 @@ var PixodeskAnimator = (() => {
1424
2453
  const staticParts = staticTransform && typeof staticTransform === "object" && !Array.isArray(staticTransform) ? staticTransform : parseTransformParts(staticTransform);
1425
2454
  if (!staticParts || !Object.keys(staticParts).length) return animDef;
1426
2455
  const mergeKfValue = (v) => v && typeof v === "object" && !Array.isArray(v) ? __spreadValues(__spreadValues({}, staticParts), v) : v;
1427
- const transformAnim = animDef["transform"];
2456
+ const transformAnim = animDef[TRANSFORM_ATTR];
1428
2457
  if (transformAnim && typeof transformAnim === "object") {
1429
2458
  const anim = transformAnim;
1430
2459
  if (Array.isArray(anim.keyframes)) {
@@ -1449,7 +2478,7 @@ var PixodeskAnimator = (() => {
1449
2478
  delete rest[ch];
1450
2479
  return __spreadProps(__spreadValues({}, rest), { transform: lifted });
1451
2480
  }
1452
- function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimatorEngine.waapi) {
2481
+ function normalizeAnimationDefinition(animDef, duration, defs, engine = PxTimelineEngine.native) {
1453
2482
  const normalized = {};
1454
2483
  for (const [propName, propAnim] of Object.entries(animDef)) {
1455
2484
  if (propName === "transform" && propAnim.alongPathMode === "offsetPath" && animDef["offsetDistance"] !== void 0) {
@@ -1460,12 +2489,12 @@ var PixodeskAnimator = (() => {
1460
2489
  const out = { keyframes: normalizedKfs };
1461
2490
  if (propAnim.autoOrient !== void 0) out.autoOrient = propAnim.autoOrient;
1462
2491
  if (propAnim.loop !== void 0) out.loop = propAnim.loop;
1463
- normalized[propName] = engine === PxAnimatorEngine.waapi && propName === "transform" ? materialiseMotionPathInPropAnim(out) : out;
2492
+ normalized[propName] = engine === PxTimelineEngine.native && propName === "transform" ? materializeMotionPathInPropAnim(out) : out;
1464
2493
  }
1465
2494
  }
1466
2495
  return normalized;
1467
2496
  }
1468
- function getNormalisedBindings(doc, engine = PxAnimatorEngine.waapi) {
2497
+ function getNormalizedBindings(doc, engine = PxTimelineEngine.native) {
1469
2498
  const animatorConfig = getAnimatorConfig(doc) || {};
1470
2499
  const defs = getDefs(doc);
1471
2500
  const duration = animatorConfig.duration || 1e3;
@@ -1531,13 +2560,13 @@ var PixodeskAnimator = (() => {
1531
2560
  return { prevKf, nextKf };
1532
2561
  }
1533
2562
  function calcPropertyValue(propName, propAnim, progress) {
1534
- var _a, _b, _c, _d, _e, _f, _g;
2563
+ var _a, _b, _c, _d;
1535
2564
  const keyframes = propAnim.keyframes || [];
1536
2565
  if (keyframes.length === 0) return null;
1537
2566
  const { prevKf, nextKf } = getKeyframesPair(keyframes, progress);
1538
2567
  let localProgress = prevKf === nextKf ? 0 : remap(progress, (_a = prevKf.t) != null ? _a : 0, (_b = nextKf.t) != null ? _b : 0, 0, 1);
1539
2568
  localProgress = clamp(localProgress, 0, 1);
1540
- const easing = (_c = prevKf.e) != null ? _c : prevKf.easing;
2569
+ const easing = kfEasing(prevKf);
1541
2570
  if (easing && Array.isArray(easing)) {
1542
2571
  try {
1543
2572
  localProgress = cubicBezier(easing)(localProgress);
@@ -1546,17 +2575,17 @@ var PixodeskAnimator = (() => {
1546
2575
  }
1547
2576
  let cssAttrName = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;
1548
2577
  let cssValue = null;
1549
- const prevV = (_d = prevKf == null ? void 0 : prevKf.v) != null ? _d : prevKf == null ? void 0 : prevKf.value;
1550
- const nextV = (_e = nextKf == null ? void 0 : nextKf.v) != null ? _e : nextKf == null ? void 0 : nextKf.value;
2578
+ const prevV = prevKf == null ? void 0 : prevKf.v;
2579
+ const nextV = nextKf == null ? void 0 : nextKf.v;
1551
2580
  if (cssAttrName === "d") {
1552
- const prevPaths = (_f = prevV == null ? void 0 : prevV.paths) != null ? _f : Array.isArray(prevV) ? prevV : [];
1553
- const nextPaths = (_g = nextV == null ? void 0 : nextV.paths) != null ? _g : Array.isArray(nextV) ? nextV : [];
2581
+ const prevPaths = (_c = prevV == null ? void 0 : prevV.paths) != null ? _c : Array.isArray(prevV) ? prevV : [];
2582
+ const nextPaths = (_d = nextV == null ? void 0 : nextV.paths) != null ? _d : Array.isArray(nextV) ? nextV : [];
1554
2583
  cssValue = interpolateBeziers(
1555
2584
  prevPaths,
1556
2585
  nextPaths,
1557
2586
  localProgress
1558
2587
  ).map((bz) => bezierToSvgPath(bz)).join("");
1559
- } else if (COLOUR_ATTR_NAMES.has(cssAttrName)) {
2588
+ } else if (COLOR_ATTR_NAMES.has(cssAttrName)) {
1560
2589
  cssValue = toRGBA(interpolateColor(
1561
2590
  prevV || [0, 0, 0, 1],
1562
2591
  nextV || [0, 0, 0, 1],
@@ -1653,7 +2682,41 @@ var PixodeskAnimator = (() => {
1653
2682
  return result;
1654
2683
  }
1655
2684
 
1656
- // ../svg-animator-core/src/PxFrameLoop.ts
2685
+ // ../svg-animator-core/src/playback/PxPlaybackTime.ts
2686
+ var PX_RATE_REJECTED = "setPlaybackRate: rate must be finite and non-zero";
2687
+ function isValidPlaybackRate(rate) {
2688
+ return Number.isFinite(rate) && rate !== 0;
2689
+ }
2690
+ function seekCeilingMs(durationMs, iterations) {
2691
+ if (!(durationMs > 0)) return 0;
2692
+ if (iterations === Infinity) return Infinity;
2693
+ return durationMs * (iterations > 0 ? iterations : 1);
2694
+ }
2695
+ function progressSpanMs(durationMs, iterations) {
2696
+ if (!(durationMs > 0)) return 0;
2697
+ if (iterations === Infinity) return durationMs;
2698
+ return durationMs * (iterations > 0 ? iterations : 1);
2699
+ }
2700
+ function clampSeekMs(timeMs, ceilingMs) {
2701
+ if (Number.isNaN(timeMs) || timeMs < 0) return 0;
2702
+ if (Number.isFinite(ceilingMs)) return timeMs > ceilingMs ? ceilingMs : timeMs;
2703
+ return Number.isFinite(timeMs) ? timeMs : 0;
2704
+ }
2705
+ function timeToProgress(timeMs, durationMs, iterations) {
2706
+ const span = progressSpanMs(durationMs, iterations);
2707
+ if (!(span > 0) || !Number.isFinite(timeMs)) return 0;
2708
+ if (timeMs <= 0) return 0;
2709
+ if (iterations === Infinity) return timeMs % span / span;
2710
+ return timeMs >= span ? 1 : timeMs / span;
2711
+ }
2712
+ function progressToTimeMs(progress, durationMs, iterations) {
2713
+ const span = progressSpanMs(durationMs, iterations);
2714
+ if (!(span > 0) || !Number.isFinite(progress)) return 0;
2715
+ if (progress <= 0) return 0;
2716
+ return progress >= 1 ? span : progress * span;
2717
+ }
2718
+
2719
+ // ../svg-animator-core/src/playback/PxFrameLoop.ts
1657
2720
  function requestFrame(cb) {
1658
2721
  const g = globalThis;
1659
2722
  if (typeof g.requestAnimationFrame === "function") return g.requestAnimationFrame(cb);
@@ -1670,7 +2733,7 @@ var PixodeskAnimator = (() => {
1670
2733
  function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
1671
2734
  var _a;
1672
2735
  const config = getAnimatorConfig(doc) || {};
1673
- const bindings = getNormalisedBindings(doc, PxAnimatorEngine.frames);
2736
+ const bindings = getNormalizedBindings(doc, PxTimelineEngine.js);
1674
2737
  const _iterations = config.iterations;
1675
2738
  let iterations = 1;
1676
2739
  if (typeof _iterations === "number") iterations = _iterations || 1;
@@ -1900,8 +2963,8 @@ var PixodeskAnimator = (() => {
1900
2963
  finishAnim(true);
1901
2964
  },
1902
2965
  "setPlaybackRate": (rate) => {
1903
- if (!isFinite(rate) || rate === 0) {
1904
- console.warn("setPlaybackRate: rate must be finite and non-zero");
2966
+ if (!isValidPlaybackRate(rate)) {
2967
+ console.warn(PX_RATE_REJECTED);
1905
2968
  return;
1906
2969
  }
1907
2970
  let current = getRawAnimTime();
@@ -1915,13 +2978,16 @@ var PixodeskAnimator = (() => {
1915
2978
  return getAnimCurrentTime();
1916
2979
  },
1917
2980
  "setCurrentTime": (newTime) => {
1918
- if (newTime < 0) newTime = 0;
1919
- if (Number.isFinite(totalDuration) && newTime > totalDuration) newTime = totalDuration;
2981
+ newTime = clampSeekMs(newTime, totalDuration);
1920
2982
  timeBeforeLastStartMs = newTime;
1921
2983
  if (playing) lastStartedTs = Date.now();
1922
2984
  if (!Number.isFinite(totalDuration) || newTime < totalDuration) finishCalled = false;
1923
2985
  renderFrame(getAnimCurrentTime());
1924
2986
  },
2987
+ "getCurrentProgress": () => timeToProgress(getAnimCurrentTime(), duration, iterations),
2988
+ "setCurrentProgress": (progress) => {
2989
+ api.setCurrentTime(progressToTimeMs(progress, duration, iterations));
2990
+ },
1925
2991
  "destroy": () => {
1926
2992
  var _a2;
1927
2993
  api.cancel();
@@ -1931,13 +2997,82 @@ var PixodeskAnimator = (() => {
1931
2997
  return api;
1932
2998
  }
1933
2999
 
1934
- // src/PxAnimatorTriggers.ts
1935
- function setupAnimationTriggers(api, config) {
1936
- const { startOn, outAction = "continue", scrollIntoViewThreshold = 0 } = config;
3000
+ // ../svg-animator-core/src/playback/PxDiagnostics.ts
3001
+ var PxDiagnosticKind = {
3002
+ /** The document is wrong — regenerate or repair the file. */
3003
+ document: "document",
3004
+ /** The page or app cannot provide what the document asks for — fix the mount. */
3005
+ host: "host",
3006
+ /** The platform cannot do it and the player degraded — usually nothing to fix. */
3007
+ platform: "platform",
3008
+ /** The call is wrong or self-contradictory — fix the options or props you passed. */
3009
+ usage: "usage",
3010
+ /** The player failed where it did not expect to — report it to us. */
3011
+ internal: "internal"
3012
+ };
3013
+ function asError(error) {
3014
+ return typeof error === "string" ? new Error(error) : error;
3015
+ }
3016
+ function isSilenced(silent, kind) {
3017
+ if (!silent) return false;
3018
+ return silent === true || silent.includes(kind);
3019
+ }
3020
+ function createDiagnostics(config, prefix) {
3021
+ const tag = prefix ? prefix + " " : "";
3022
+ return {
3023
+ warn: (kind, message, detail) => {
3024
+ if (config == null ? void 0 : config.onWarn) {
3025
+ config.onWarn({ kind, message, detail });
3026
+ return;
3027
+ }
3028
+ if (isSilenced(config == null ? void 0 : config.silent, kind)) return;
3029
+ const line = tag + kind + ": " + message;
3030
+ if (detail === void 0) console.warn(line);
3031
+ else console.warn(line, detail);
3032
+ },
3033
+ error: (kind, error) => {
3034
+ const err = asError(error);
3035
+ if (config == null ? void 0 : config.onError) {
3036
+ config.onError({ kind, message: err.message, error: err });
3037
+ return;
3038
+ }
3039
+ if (isSilenced(config == null ? void 0 : config.silent, kind)) return;
3040
+ console.error(tag + kind + ": " + err.message);
3041
+ }
3042
+ };
3043
+ }
3044
+
3045
+ // src/shared/PxAnimatorCallbacks.ts
3046
+ function toEngineCallbacks(inline) {
3047
+ const { onPlay, onPause, onCancel, onFinish, onRemove, onStop, onWarn, onError, silent } = inline != null ? inline : {};
3048
+ const withStop = (own) => own || onStop ? () => {
3049
+ own == null ? void 0 : own();
3050
+ onStop == null ? void 0 : onStop();
3051
+ } : void 0;
3052
+ return {
3053
+ onPlay,
3054
+ onPause: withStop(onPause),
3055
+ onCancel: withStop(onCancel),
3056
+ onFinish: withStop(onFinish),
3057
+ onRemove: withStop(onRemove),
3058
+ onWarn,
3059
+ onError,
3060
+ silent
3061
+ };
3062
+ }
3063
+
3064
+ // src/triggers/PxAnimatorTriggers.ts
3065
+ function setupAnimationTriggers(api, config, diag) {
3066
+ const report = diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]");
3067
+ const cleanups = [];
3068
+ const dispose = () => {
3069
+ for (const undo of cleanups.splice(0)) undo();
3070
+ };
3071
+ const { startOn, outAction, scrollIntoViewThreshold } = resolveTrigger(config);
1937
3072
  const root = api.getRootElement();
1938
3073
  if (!root) {
1939
- console.warn("setupAnimationTriggers: No root element found for animation.");
1940
- return api;
3074
+ report.warn(PxDiagnosticKind.host, "setupAnimationTriggers: No root element found for animation.");
3075
+ return dispose;
1941
3076
  }
1942
3077
  let reversed = false;
1943
3078
  const start = () => {
@@ -1972,6 +3107,7 @@ var PixodeskAnimator = (() => {
1972
3107
  startHandler();
1973
3108
  } else {
1974
3109
  window.addEventListener("load", startHandler, { once: true });
3110
+ cleanups.push(() => window.removeEventListener("load", startHandler));
1975
3111
  }
1976
3112
  break;
1977
3113
  }
@@ -1986,6 +3122,10 @@ var PixodeskAnimator = (() => {
1986
3122
  };
1987
3123
  root.addEventListener("mouseenter", mouseOverHandler);
1988
3124
  root.addEventListener("mouseleave", mouseOutHandler);
3125
+ cleanups.push(() => {
3126
+ root.removeEventListener("mouseenter", mouseOverHandler);
3127
+ root.removeEventListener("mouseleave", mouseOutHandler);
3128
+ });
1989
3129
  break;
1990
3130
  }
1991
3131
  case "click": {
@@ -1997,6 +3137,7 @@ var PixodeskAnimator = (() => {
1997
3137
  }
1998
3138
  };
1999
3139
  root.addEventListener("click", clickHandler);
3140
+ cleanups.push(() => root.removeEventListener("click", clickHandler));
2000
3141
  break;
2001
3142
  }
2002
3143
  case "scrollIntoView": {
@@ -2028,48 +3169,55 @@ var PixodeskAnimator = (() => {
2028
3169
  { threshold: thresholdSteps }
2029
3170
  );
2030
3171
  observer.observe(root);
3172
+ cleanups.push(() => observer.disconnect());
2031
3173
  break;
2032
3174
  }
2033
3175
  case "programmatic":
2034
3176
  break;
2035
3177
  }
2036
- return api;
3178
+ return dispose;
2037
3179
  }
2038
3180
 
2039
- // src/PxAnimatorFrameLoop.ts
3181
+ // src/engines/PxAnimatorFrameLoop.ts
2040
3182
  function getSelector(id) {
2041
3183
  return "#" + id;
2042
3184
  }
2043
3185
  function createFrameLoopAnimator(doc, adapter, callbacks, rootElement) {
3186
+ var _a;
2044
3187
  const config = getAnimatorConfig(doc) || {};
3188
+ const diag = createDiagnostics(callbacks, "[PxAnimator]");
2045
3189
  if (!rootElement) {
2046
3190
  if (doc.id) {
2047
3191
  const rootSelector = getSelector(doc.id);
2048
3192
  rootElement = document.querySelector(rootSelector);
2049
- if (!rootElement) console.warn("createFrameLoopAnimator: No root element found for selector: ", rootSelector);
3193
+ if (!rootElement) diag.warn(PxDiagnosticKind.host, "createFrameLoopAnimator: No root element found for selector: " + rootSelector);
2050
3194
  } else {
2051
- console.warn("createFrameLoopAnimator: No root element provided");
3195
+ diag.warn(PxDiagnosticKind.host, "createFrameLoopAnimator: No root element provided");
2052
3196
  }
2053
3197
  }
2054
3198
  const basicApi = createBasicFrameLoopAnimator(
2055
3199
  doc,
2056
- adapter || createDomAdapter(rootElement),
3200
+ adapter || createDomAdapter(rootElement, diag),
2057
3201
  callbacks
2058
3202
  );
2059
3203
  const api = __spreadProps(__spreadValues({}, basicApi), {
2060
3204
  "getRootElement": () => rootElement || null
2061
3205
  });
2062
- if (config.trigger) {
2063
- if (isScrollTimeline(config)) {
2064
- console.warn("scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)");
2065
- } else {
2066
- setupAnimationTriggers(api, config.trigger);
2067
- }
3206
+ if (isScrollTimeline(config)) {
3207
+ if (config.trigger) diag.warn(PxDiagnosticKind.usage, "scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)");
3208
+ } else {
3209
+ const detachTriggers = setupAnimationTriggers(api, (_a = config.trigger) != null ? _a : {}, diag);
3210
+ const destroyEngine = api.destroy.bind(api);
3211
+ api.destroy = () => {
3212
+ detachTriggers();
3213
+ destroyEngine();
3214
+ };
2068
3215
  }
2069
3216
  return api;
2070
3217
  }
2071
- function createDomAdapter(rootElement) {
3218
+ function createDomAdapter(rootElement, diag) {
2072
3219
  const warnedSelectors = /* @__PURE__ */ new Set();
3220
+ const report = diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]");
2073
3221
  const adapter = {
2074
3222
  isConnected: () => {
2075
3223
  if (!rootElement) return true;
@@ -2081,7 +3229,7 @@ var PixodeskAnimator = (() => {
2081
3229
  const elements = (rootElement == null ? void 0 : rootElement.querySelectorAll(selector)) || document.querySelectorAll(selector);
2082
3230
  if (elements.length === 0 && !warnedSelectors.has(selector)) {
2083
3231
  warnedSelectors.add(selector);
2084
- console.warn('setAttribute: No elements found for selector "' + selector + '"');
3232
+ report.warn(PxDiagnosticKind.host, 'setAttribute: No elements found for selector "' + selector + '"');
2085
3233
  }
2086
3234
  for (let i = 0; i < elements.length; i++) {
2087
3235
  const element = elements[i];
@@ -2096,18 +3244,17 @@ var PixodeskAnimator = (() => {
2096
3244
  return adapter;
2097
3245
  }
2098
3246
 
2099
- // src/PxAnimatorWebApi.ts
3247
+ // src/engines/PxAnimatorWebApi.ts
2100
3248
  function createCssKf(kf, t, propName, unsupportedSet) {
2101
- var _a, _b;
2102
- let value = (_a = kf.v) != null ? _a : kf.value;
2103
- const e = (_b = kf.e) != null ? _b : kf.easing;
3249
+ let value = kfValue(kf);
3250
+ const e = kfEasing(kf);
2104
3251
  const cssKf = {
2105
3252
  offset: t,
2106
3253
  easing: e && Array.isArray(e) ? "cubic-bezier(" + e.join(",") + ")" : void 0
2107
3254
  };
2108
3255
  let cssValue;
2109
3256
  let cssKey = propName;
2110
- if (COLOUR_ATTR_NAMES.has(propName) && Array.isArray(value)) {
3257
+ if (COLOR_ATTR_NAMES.has(propName) && Array.isArray(value)) {
2111
3258
  cssValue = toRGBA(value);
2112
3259
  } else if (propName === "transform" && value !== null && typeof value === "object" && !Array.isArray(value)) {
2113
3260
  cssValue = composeTransformParts(value, { withUnits: true });
@@ -2194,18 +3341,19 @@ var PixodeskAnimator = (() => {
2194
3341
  return result;
2195
3342
  }
2196
3343
  function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsupportedAttrs, scrollTimeline) {
2197
- var _a;
3344
+ var _a, _b;
2198
3345
  const config = getAnimatorConfig(doc) || {};
3346
+ const diag = createDiagnostics(callbacks, "[PxAnimator]");
2199
3347
  if (!rootElement) {
2200
3348
  if (doc.id) {
2201
3349
  const rootSelector = getSelector(doc.id);
2202
3350
  rootElement = document.querySelector(rootSelector);
2203
- if (!rootElement) console.warn("createFrameLoopAnimator: No root element found for selector: ", rootSelector);
3351
+ if (!rootElement) diag.warn(PxDiagnosticKind.host, "createWebApiAnimator: No root element found for selector: " + rootSelector);
2204
3352
  } else {
2205
- console.warn("createFrameLoopAnimator: No root element provided");
3353
+ diag.warn(PxDiagnosticKind.host, "createWebApiAnimator: No root element provided");
2206
3354
  }
2207
3355
  }
2208
- const bindings = getNormalisedBindings(doc, PxAnimatorEngine.waapi);
3356
+ const bindings = getNormalizedBindings(doc, PxTimelineEngine.native);
2209
3357
  const animations = [];
2210
3358
  const _iterations = config.iterations;
2211
3359
  let iterations;
@@ -2215,18 +3363,18 @@ var PixodeskAnimator = (() => {
2215
3363
  let finishNotified = false;
2216
3364
  let removeCalled = false;
2217
3365
  if (!(bindings == null ? void 0 : bindings.length)) {
2218
- console.warn("createWebApiAnimator: No animation bindings defined");
3366
+ diag.warn(PxDiagnosticKind.document, "createWebApiAnimator: No animation bindings defined");
2219
3367
  }
2220
3368
  for (const binding of bindings || []) {
2221
3369
  const animDef = binding.animate;
2222
3370
  if (!animDef || typeof animDef !== "object" || Array.isArray(animDef)) {
2223
- console.warn("createWebApiAnimator: Empty or unresolved binding", binding);
3371
+ diag.warn(PxDiagnosticKind.document, "createWebApiAnimator: Empty or unresolved binding", binding);
2224
3372
  continue;
2225
3373
  }
2226
3374
  const selector = getSelector(binding.id);
2227
3375
  const elements = (rootElement == null ? void 0 : rootElement.querySelectorAll(selector)) || document.querySelectorAll(selector);
2228
3376
  if (elements.length === 0) {
2229
- console.warn('createWebApiAnimator: No elements found for selector "' + selector + '"');
3377
+ diag.warn(PxDiagnosticKind.host, 'createWebApiAnimator: No elements found for selector "' + selector + '"');
2230
3378
  }
2231
3379
  const keyframesMap = convertToWebApiKeyframes(animDef, unsupportedSet, config);
2232
3380
  const positiveDelay = config.delay && config.delay > 0 ? config.delay : void 0;
@@ -2275,14 +3423,14 @@ var PixodeskAnimator = (() => {
2275
3423
  }
2276
3424
  animations.push(anim);
2277
3425
  } catch (e) {
2278
- console.warn(e);
3426
+ diag.warn(PxDiagnosticKind.internal, "createWebApiAnimator: could not build the animation", e);
2279
3427
  }
2280
3428
  }
2281
3429
  }
2282
3430
  }
2283
3431
  }
2284
3432
  if (!forceEvenIfHasUnsupportedAttrs && unsupportedSet.size) {
2285
- console.warn("Unsupported CSS attrs: " + [...unsupportedSet].join(", "));
3433
+ diag.warn(PxDiagnosticKind.platform, "Unsupported CSS attrs: " + [...unsupportedSet].join(", "));
2286
3434
  return null;
2287
3435
  }
2288
3436
  const api = {
@@ -2326,20 +3474,36 @@ var PixodeskAnimator = (() => {
2326
3474
  }
2327
3475
  },
2328
3476
  "setPlaybackRate": (rate) => {
3477
+ if (!isValidPlaybackRate(rate)) {
3478
+ diag.warn(PxDiagnosticKind.usage, PX_RATE_REJECTED);
3479
+ return api;
3480
+ }
2329
3481
  animations.forEach((a) => a.playbackRate = rate);
2330
3482
  return api;
2331
3483
  },
2332
3484
  "getCurrentTime": () => {
2333
- var _a2, _b;
2334
- const res = (_b = (_a2 = animations[0]) == null ? void 0 : _a2.currentTime) != null ? _b : null;
3485
+ var _a2, _b2;
3486
+ const res = (_b2 = (_a2 = animations[0]) == null ? void 0 : _a2.currentTime) != null ? _b2 : null;
2335
3487
  return res !== null ? +res : null;
2336
3488
  },
2337
3489
  "setCurrentTime": (time) => {
3490
+ var _a2;
3491
+ const ceiling = seekCeilingMs((_a2 = config.duration) != null ? _a2 : 0, iterations != null ? iterations : 1);
3492
+ const seek = ceiling > 0 ? clampSeekMs(time, ceiling) : Math.max(0, time);
2338
3493
  finishNotified = false;
2339
3494
  animations.forEach((a) => {
2340
- a.currentTime = time;
3495
+ a.currentTime = seek;
2341
3496
  });
2342
3497
  },
3498
+ "getCurrentProgress": () => {
3499
+ var _a2;
3500
+ const t = api.getCurrentTime();
3501
+ return t === null ? null : timeToProgress(t, (_a2 = config.duration) != null ? _a2 : 0, iterations != null ? iterations : 1);
3502
+ },
3503
+ "setCurrentProgress": (progress) => {
3504
+ var _a2;
3505
+ api.setCurrentTime(progressToTimeMs(progress, (_a2 = config.duration) != null ? _a2 : 0, iterations != null ? iterations : 1));
3506
+ },
2343
3507
  "destroy": () => {
2344
3508
  var _a2;
2345
3509
  api.cancel();
@@ -2350,12 +3514,15 @@ var PixodeskAnimator = (() => {
2350
3514
  }
2351
3515
  }
2352
3516
  };
2353
- if (config.trigger) {
2354
- if (config.timelineSource === "scroll") {
2355
- console.warn("scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)");
2356
- } else {
2357
- setupAnimationTriggers(api, config.trigger);
2358
- }
3517
+ if (config.timelineSource === "scroll") {
3518
+ if (config.trigger) diag.warn(PxDiagnosticKind.usage, "scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)");
3519
+ } else {
3520
+ const detachTriggers = setupAnimationTriggers(api, (_b = config.trigger) != null ? _b : {}, diag);
3521
+ const destroyEngine = api.destroy.bind(api);
3522
+ api.destroy = () => {
3523
+ detachTriggers();
3524
+ destroyEngine();
3525
+ };
2359
3526
  }
2360
3527
  if (scrollTimeline) {
2361
3528
  animations.forEach((a) => a.play());
@@ -2363,7 +3530,7 @@ var PixodeskAnimator = (() => {
2363
3530
  return api;
2364
3531
  }
2365
3532
 
2366
- // src/PxScrollDriver.ts
3533
+ // src/scroll/PxScrollDriver.ts
2367
3534
  function nativeRangeOffset(point, defaultFraction, view) {
2368
3535
  var _a, _b, _c;
2369
3536
  const fraction = typeof (point == null ? void 0 : point.fraction) === "number" ? point.fraction : defaultFraction;
@@ -2371,13 +3538,14 @@ var PixodeskAnimator = (() => {
2371
3538
  if (pct === void 0) return void 0;
2372
3539
  return view ? { rangeName: (_c = point == null ? void 0 : point.phase) != null ? _c : "cover", offset: pct } : { offset: pct };
2373
3540
  }
2374
- function createNativeScrollTimeline(subject, config) {
3541
+ function createNativeScrollTimeline(subject, config, diag) {
2375
3542
  var _a, _b, _c, _d;
3543
+ const report = diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]");
2376
3544
  if (!config || !isScrollTimeline(config)) return null;
2377
3545
  const scroll = config.scroll || {};
2378
3546
  const kind = (_a = scroll.kind) != null ? _a : "view";
2379
3547
  if (scroll.smoothing) {
2380
- console.warn('scroll timeline: `smoothing` needs the built-in driver \u2014 ignoring `driver: "native"`');
3548
+ report.warn(PxDiagnosticKind.platform, "scroll timeline: `smoothing` needs the built-in driver \u2014 using the built-in driver instead of the browser timeline");
2381
3549
  return null;
2382
3550
  }
2383
3551
  const g = globalThis;
@@ -2388,13 +3556,13 @@ var PixodeskAnimator = (() => {
2388
3556
  let timeline;
2389
3557
  try {
2390
3558
  if (view) {
2391
- timeline = new Ctor({ subject: resolveScrollSubject(subject, scroll.subject), axis });
3559
+ timeline = new Ctor({ subject: resolveScrollSubject(subject, scroll.subject, report), axis });
2392
3560
  } else {
2393
3561
  const source = scroll.source === "root" ? documentScroller() : findNearestScroller(subject, "y") || findNearestScroller(subject, "x") || documentScroller();
2394
3562
  timeline = new Ctor({ source, axis });
2395
3563
  }
2396
3564
  } catch (e) {
2397
- console.warn("scroll timeline: native timeline construction failed \u2014 falling back to the custom driver", e);
3565
+ report.warn(PxDiagnosticKind.platform, "scroll timeline: native timeline construction failed \u2014 falling back to the player measuring progress itself", e);
2398
3566
  return null;
2399
3567
  }
2400
3568
  return {
@@ -2421,8 +3589,9 @@ var PixodeskAnimator = (() => {
2421
3589
  }
2422
3590
  var SUBJECT_PARENT = "parent";
2423
3591
  var SUBJECT_SCROLLER = "scroller";
2424
- function resolveScrollSubject(svgRoot, subject) {
3592
+ function resolveScrollSubject(svgRoot, subject, diag) {
2425
3593
  var _a, _b;
3594
+ const report = diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]");
2426
3595
  const spec = subject == null ? void 0 : subject.trim();
2427
3596
  if (!spec) return svgRoot;
2428
3597
  if (spec === SUBJECT_PARENT) {
@@ -2440,21 +3609,22 @@ var PixodeskAnimator = (() => {
2440
3609
  try {
2441
3610
  found = document.querySelector(spec);
2442
3611
  } catch (e) {
2443
- console.warn('scroll timeline: subject "' + spec + '" is not a valid selector \u2014 measuring the SVG itself');
3612
+ report.warn(PxDiagnosticKind.document, 'scroll timeline: subject "' + spec + '" is not a valid selector \u2014 measuring the SVG itself');
2444
3613
  return svgRoot;
2445
3614
  }
2446
3615
  if (!found) {
2447
- console.warn('scroll timeline: subject "' + spec + '" matched no element \u2014 measuring the SVG itself');
3616
+ report.warn(PxDiagnosticKind.host, 'scroll timeline: subject "' + spec + '" matched no element \u2014 measuring the SVG itself');
2448
3617
  return svgRoot;
2449
3618
  }
2450
3619
  return found;
2451
3620
  }
2452
- function createScrollDriver(subject, config, onProgress) {
3621
+ function createScrollDriver(subject, config, onProgress, diag) {
2453
3622
  var _a, _b;
2454
3623
  if (!config || !isScrollTimeline(config)) return null;
3624
+ const report = diag != null ? diag : createDiagnostics(void 0, "[PxAnimator]");
2455
3625
  const scroll = config.scroll || {};
2456
3626
  const kind = (_a = scroll.kind) != null ? _a : "view";
2457
- const measured = resolveScrollSubject(subject, scroll.subject);
3627
+ const measured = resolveScrollSubject(subject, scroll.subject, report);
2458
3628
  const nearest = findNearestScroller(subject, "y") || findNearestScroller(subject, "x");
2459
3629
  const scroller = kind === "scroll" && scroll.source === "root" ? documentScroller() : nearest || documentScroller();
2460
3630
  const isRootScroller = scroller === documentScroller();
@@ -2573,7 +3743,7 @@ var PixodeskAnimator = (() => {
2573
3743
  const alignFactor = scroll.pinAlign === "center" ? 0.5 : scroll.pinAlign === "bottom" ? 1 : 0;
2574
3744
  const applyTop = () => {
2575
3745
  var _a;
2576
- const extra = (_a = scroll.pinTop) != null ? _a : 0;
3746
+ const extra = (_a = scroll.pinOffset) != null ? _a : 0;
2577
3747
  if (!alignFactor) {
2578
3748
  style.top = extra + "px";
2579
3749
  return;
@@ -2613,7 +3783,7 @@ var PixodeskAnimator = (() => {
2613
3783
  };
2614
3784
  }
2615
3785
 
2616
- // src/PxAnimatorBind.ts
3786
+ // src/engines/PxAnimatorBind.ts
2617
3787
  function finaliseAnimator(animatorConfig, callbacks, make) {
2618
3788
  let apiRef;
2619
3789
  let effectiveCallbacks = callbacks;
@@ -2628,27 +3798,28 @@ var PixodeskAnimator = (() => {
2628
3798
  }
2629
3799
  const res = make(effectiveCallbacks);
2630
3800
  apiRef = res;
2631
- if (animatorConfig.debugInstName) {
2632
- window[animatorConfig.debugInstName] = res;
3801
+ if (animatorConfig.debugGlobalName) {
3802
+ window[animatorConfig.debugGlobalName] = res;
2633
3803
  }
2634
3804
  return res;
2635
3805
  }
2636
3806
  function bindWithEngineChoice(doc, adapter, callbacks, rootElement) {
2637
3807
  const animatorConfig = getAnimatorConfig(doc) || {};
3808
+ const diag = createDiagnostics(callbacks, "[PxAnimator]");
2638
3809
  if (isScrollTimeline(animatorConfig)) {
2639
3810
  return finaliseAnimator(animatorConfig, callbacks, (cb) => {
2640
- var _a, _b;
3811
+ var _a;
2641
3812
  let unpin = () => {
2642
3813
  };
2643
- if (animatorConfig.mode !== PxAnimatorMode.frames && ((_a = animatorConfig.scroll) == null ? void 0 : _a.driver) === "native" && rootElement) {
3814
+ if (mayUseNativeScrollTimeline(animatorConfig.engine) && rootElement) {
2644
3815
  unpin = applyScrollPin(rootElement, animatorConfig.scroll);
2645
- const native = createNativeScrollTimeline(rootElement, animatorConfig);
3816
+ const native = createNativeScrollTimeline(rootElement, animatorConfig, diag);
2646
3817
  if (native) {
2647
3818
  const api2 = createWebApiAnimator(
2648
3819
  doc,
2649
3820
  cb,
2650
3821
  rootElement,
2651
- animatorConfig.mode === PxAnimatorMode.waapi,
3822
+ isNativeForced(animatorConfig.engine),
2652
3823
  native
2653
3824
  );
2654
3825
  if (api2) {
@@ -2664,15 +3835,16 @@ var PixodeskAnimator = (() => {
2664
3835
  unpin = () => {
2665
3836
  };
2666
3837
  }
2667
- const api = (animatorConfig.mode !== PxAnimatorMode.frames ? createWebApiAnimator(doc, cb, rootElement, animatorConfig.mode === PxAnimatorMode.waapi) : null) || createFrameLoopAnimator(doc, adapter, cb, rootElement);
2668
- const subject = ((_b = api.getRootElement) == null ? void 0 : _b.call(api)) || rootElement;
3838
+ const api = (animatorConfig.engine !== PxTimelineEngineExtra.js ? createWebApiAnimator(doc, cb, rootElement, isNativeForced(animatorConfig.engine)) : null) || createFrameLoopAnimator(doc, adapter, cb, rootElement);
3839
+ const subject = ((_a = api.getRootElement) == null ? void 0 : _a.call(api)) || rootElement;
2669
3840
  if (subject) {
2670
3841
  unpin = applyScrollPin(subject, animatorConfig.scroll);
2671
3842
  const totalMs = scrollTotalDurationMs(animatorConfig);
2672
3843
  const driver = createScrollDriver(
2673
3844
  subject,
2674
3845
  animatorConfig,
2675
- (progress) => api.setCurrentTime(progress * totalMs)
3846
+ (progress) => api.setCurrentTime(progress * totalMs),
3847
+ diag
2676
3848
  );
2677
3849
  if (driver) {
2678
3850
  const destroy = api.destroy.bind(api);
@@ -2683,34 +3855,33 @@ var PixodeskAnimator = (() => {
2683
3855
  };
2684
3856
  }
2685
3857
  } else {
2686
- console.warn("scroll timeline: no root element to observe \u2014 animation will stay at frame 0");
3858
+ diag.warn(PxDiagnosticKind.host, "scroll timeline: no root element to observe \u2014 animation will stay at frame 0");
2687
3859
  }
2688
3860
  return api;
2689
3861
  });
2690
3862
  }
2691
3863
  return finaliseAnimator(animatorConfig, callbacks, (cb) => {
2692
- if (animatorConfig.mode === PxAnimatorMode.frames) {
3864
+ if (animatorConfig.engine === PxTimelineEngineExtra.js) {
2693
3865
  return createFrameLoopAnimator(doc, adapter, cb, rootElement);
2694
3866
  }
2695
3867
  return createWebApiAnimator(
2696
3868
  doc,
2697
3869
  cb,
2698
3870
  rootElement,
2699
- animatorConfig.mode === PxAnimatorMode.waapi
2700
- // forcing waapi
3871
+ isNativeForced(animatorConfig.engine)
2701
3872
  ) || createFrameLoopAnimator(doc, adapter, cb, rootElement);
2702
3873
  });
2703
3874
  }
2704
- function requireData(options) {
2705
- if (!(options == null ? void 0 : options.data)) throw new Error("createAnimator: `data` is required");
2706
- return options.data;
3875
+ function requireDoc(options) {
3876
+ if (!(options == null ? void 0 : options.doc)) throw new Error("createAnimator: `doc` is required");
3877
+ return options.doc;
2707
3878
  }
2708
3879
  function createPrerenderedAnimator(options) {
2709
- return bindWithEngineChoice(requireData(options), options.adapter, options.callbacks, null);
3880
+ return bindWithEngineChoice(requireDoc(options), options.adapter, toEngineCallbacks(options), null);
2710
3881
  }
2711
3882
 
2712
- // src/PxAnimatorKeys.ts
2713
- var PX_ANIMATOR_DATA_KEY = "data";
3883
+ // src/shared/PxAnimatorKeys.ts
3884
+ var PX_ANIMATOR_DOC_KEY = "doc";
2714
3885
  return __toCommonJS(index_prerendered_exports);
2715
3886
  })();
2716
3887
  //# sourceMappingURL=index.prerendered.umd.js.map