@pixodesk/svg-animator-web 1.0.41 → 1.0.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +73 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +73 -41
- package/dist/index.js.map +1 -1
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.prerendered-waapi.umd.js +26 -25
- package/dist/index.prerendered-waapi.umd.js.map +1 -1
- package/dist/index.prerendered-waapi.umd.min.js +1 -1
- package/dist/index.prerendered.umd.js +35 -33
- package/dist/index.prerendered.umd.js.map +1 -1
- package/dist/index.prerendered.umd.min.js +1 -1
- package/dist/pixodesk-svg-animator.umd.js +42 -40
- package/dist/pixodesk-svg-animator.umd.js.map +1 -1
- package/dist/pixodesk-svg-animator.umd.min.js +1 -1
- package/mangle-reserved.json +2 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.prerendered-waapi.ts","../../svg-animator-core/src/schema/PxSchema.ts","../../svg-animator-core/src/format/PxAnimatorConstants.ts","../../svg-animator-core/src/format/PxAnimatorTypes.ts","../../svg-animator-core/src/util/PxAnimatorUtil.ts","../../svg-animator-core/src/materialize/PxMotionPath.ts","../../svg-animator-core/src/animation/PxDefinitions.ts","../../svg-animator-core/src/playback/PxPlaybackTime.ts","../../svg-animator-core/src/playback/PxDiagnostics.ts","../src/shared/PxAnimatorCallbacks.ts","../src/triggers/PxAnimatorTriggers.ts","../src/engines/PxAnimatorFrameLoop.ts","../src/engines/PxAnimatorWebApi.ts","../src/engines/PxAnimatorBind.ts","../src/shared/PxAnimatorKeys.ts"],"sourcesContent":["/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n// ============================================================================\n// UMD entry for PRE-RENDERED SVG — WAAPI only. The smallest build.\n//\n// Inlined by the Editor into SVG+JS exports whose `timeline.engine` is `native`. On top of\n// what `index.prerendered.ts` drops, this also excludes the frame-loop engine: the native\n// engine is forced, so there is no fallback path to link against (`createWebApiAnimator`\n// never returns null when forced — it only warns about unsupported attrs).\n//\n// Exported AS `createAnimator` so the emitted `<script>` is identical across bundles.\n// See dev-docs/plans/prerendered-player-builds.md.\n// ============================================================================\n\nexport { createPrerenderedWaapiAnimator as createAnimator } from './engines/PxAnimatorBind';\nexport { PX_ANIMATOR_DOC_KEY } from './shared/PxAnimatorKeys';\n\nexport { setupAnimationTriggers } from './triggers/PxAnimatorTriggers';\n\nexport type { PxPrerenderedAnimatorOptions } from './engines/PxAnimatorBind';\nexport type { PxAnimatorApi, PxPlaybackApi } from './shared/PxAnimatorWebTypes';\nexport type { PxAnimatedSvgDocument, PxEngineCallbacks } from '@pixodesk/svg-animator-core';\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n/**\n * Lightweight Zod-like schema system — type declaration + runtime sanitization.\n *\n * Two operations per schema:\n * isValid(raw) — true only when raw already conforms; no repair needed.\n * sanitize(raw) — always returns T; fixes or replaces invalid input with defaults.\n *\n * Field rules inside px.object():\n * required field → if absent/invalid, field's declared default is used.\n * optional field → if absent/invalid (or wrong JS type), field becomes undefined.\n *\n * Array: items that cannot even be attempted are filtered out.\n * Record: values that cannot even be attempted are dropped.\n *\n * The distinction between isValid and _canSanitize:\n * isValid — strict; ALL fields must be correct, no repairs accepted.\n * _canSanitize — permissive; \"is the JS type right enough to attempt repair?\"\n * For containers (object/array/record) this is a structural check\n * so that partially-valid nested objects are sanitized rather than dropped.\n * For primitives it equals isValid (a wrong primitive type is unrecoverable).\n *\n * Validation context (optional):\n * Pass a PxValidationContext as the second arg to isValid() to collect per-field\n * error and warning messages with their dot-paths.\n * e.g. schema.isValid(raw, ctx, []) where ctx = { errors: [], warnings: [] }\n * Path segments are pushed/popped during recursion; join with '.' only when reporting.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public interfaces\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The reason text a STRICT closed object reports for an undeclared key — `\"<dot.path>: <this>\"`.\n *\n * Exported because it is the one validation finding that carries a MEANING beyond \"invalid\":\n * a key this build does not declare is a key some other build wrote, which is the signal a\n * reader turns into \"written for a newer schema — update your player/editor\". Callers match on\n * this constant rather than on a copy of the sentence.\n * @internal\n */\nexport const PX_UNKNOWN_KEY_ERROR = 'unexpected extra key';\n\n/** Collects structured validation feedback. Pass to isValid() as the second argument. @public @advanced */\nexport interface PxValidationContext {\n /** Per-field errors — each entry is \"<dot.path>: <reason>\". */\n errors: Array<string>;\n /** Per-field warnings — same format as errors but non-fatal. */\n warnings: Array<string>;\n /**\n * When true, closed objects (`px.object` / `px.extendedObject`) report any\n * extra (undeclared) keys as errors. Default `false` (or omitted) — extras\n * are ignored, matching `sanitize`'s strip-extras behavior, which is what\n * production app code wants (forward-compat with unknown future fields).\n * Tests that want to lock the wire shape down to its declared schema\n * should pass `strict: true`.\n *\n * Open objects (`px.openObject`) are unaffected by `strict` — they accept\n * extras by design (used for SVG element nodes that carry arbitrary attrs).\n */\n strict?: boolean;\n}\n\n/** @public @advanced */\nexport interface PxSchema<T, IsOptional extends boolean = false> {\n /** Phantom discriminator — `false` for required schemas, `true` for optional. Used by InferShape. */\n readonly _optional: IsOptional;\n sanitize(raw: unknown): T;\n /**\n * Returns true when raw already conforms to this schema.\n * Pass an optional PxValidationContext to collect per-field error messages.\n * Pass a mutable Array<string> as `path` — segments are pushed/popped during\n * recursion so only a single array allocation is needed per validation call.\n */\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean;\n /** True if raw has the right structure to attempt sanitization (may still need repair). */\n _canSanitize(raw: unknown): boolean;\n readonly _default: T;\n /** Returns a new schema that marks this field as optional (?: in object shapes). */\n optional(): PxSchema<T | undefined, true>;\n}\n\n/** Extract the TypeScript type from a schema. @public @advanced */\nexport type PxInfer<S> = S extends PxSchema<infer T, any> ? T : never;\n\n/**\n * Removes string/number index signatures from T, leaving only explicitly named properties.\n * Useful for strict property-access checking on types that have an open `[key: string]: any`.\n *\n * @example\n * type Strict = PxRemoveIndex<PxAnimatedSvgDocument>;\n * Strict['animator'] // PxAnimatorConfig | undefined ✓\n * Strict['anything'] // compile error ✓\n * @public @advanced\n */\nexport type PxRemoveIndex<T> = {\n [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]\n};\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Internal path helper\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Joins path segments into a dot-path string for error messages.\n// Segments starting with '[' are appended without a leading dot.\n// e.g. ['obj', 'key'] -> 'obj.key' ['arr', '[0]'] -> 'arr[0]' [] -> '.'\nfunction pathStr(path: Array<string>): string {\n if (!path.length) return '.';\n let result = '';\n for (const seg of path) {\n if (seg.startsWith('[')) result += seg;\n else result += (result ? '.' : '') + seg;\n }\n return result;\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Base — default _canSanitize = isValid (correct for primitives)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Shared base; overrides _canSanitize only when the structural check must differ from isValid. */\nabstract class Base<T, IsOptional extends boolean = false> implements PxSchema<T, IsOptional> {\n // `declare` emits no runtime code; purely satisfies the interface's phantom _optional property.\n declare readonly _optional: IsOptional;\n abstract sanitize(raw: unknown): T;\n abstract isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean;\n abstract readonly _default: T;\n\n _canSanitize(raw: unknown): boolean { return this.isValid(raw); }\n\n optional(): PxSchema<T | undefined, true> { return new Optional(this); }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Optional wrapper\n// Uses _canSanitize (not isValid) so that partially-valid nested objects are\n// repaired rather than dropped entirely.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Wraps any schema to make its value optional.\n * sanitize uses _canSanitize (not isValid) so partially-valid objects are repaired, not dropped.\n */\nclass Optional<T> extends Base<T | undefined, true> {\n readonly _default = undefined as T | undefined;\n constructor(private readonly inner: PxSchema<T, any>) { super(); }\n\n sanitize(raw: unknown): T | undefined {\n if (raw === undefined || raw === null) return undefined;\n return this.inner._canSanitize(raw) ? this.inner.sanitize(raw) : undefined;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (raw === undefined || raw === null) return true;\n return this.inner.isValid(raw, ctx, path);\n }\n\n override _canSanitize(raw: unknown): boolean {\n return raw === undefined || raw === null || this.inner._canSanitize(raw);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Primitives — _canSanitize = isValid (a wrong primitive type is unrecoverable)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** String schema; wrong type is unrecoverable so _canSanitize = isValid (inherited default). */\nclass Str extends Base<string> {\n constructor(readonly _default: string = '') { super(); }\n sanitize(raw: unknown): string { return typeof raw === 'string' ? raw : this._default; }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (typeof raw === 'string') return true;\n ctx?.errors.push(pathStr(path ?? []) + ': expected string, got ' + typeof raw);\n return false;\n }\n}\n\n/** Finite-number schema; rejects NaN and ±Infinity as unrecoverable. */\nclass Num extends Base<number> {\n constructor(readonly _default: number = 0) { super(); }\n sanitize(raw: unknown): number {\n return typeof raw === 'number' && isFinite(raw) ? raw : this._default;\n }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (typeof raw === 'number' && isFinite(raw)) return true;\n ctx?.errors.push(pathStr(path ?? []) + ': expected finite number, got ' + JSON.stringify(raw));\n return false;\n }\n}\n\n/** Boolean schema. */\nclass Bool extends Base<boolean> {\n constructor(readonly _default: boolean = false) { super(); }\n sanitize(raw: unknown): boolean { return typeof raw === 'boolean' ? raw : this._default; }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (typeof raw === 'boolean') return true;\n ctx?.errors.push(pathStr(path ?? []) + ': expected boolean, got ' + typeof raw);\n return false;\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Literal — exact value match; default is the literal itself\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Matches one exact primitive value; its _default is the value itself. */\nclass Literal<T extends string | number | boolean> extends Base<T> {\n readonly _default: T;\n constructor(private readonly value: T) { super(); this._default = value; }\n sanitize(raw: unknown): T { return raw === this.value ? this.value : this._default; }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (raw === this.value) return true;\n ctx?.errors.push(pathStr(path ?? []) + ': expected ' + JSON.stringify(this.value) + ', got ' + JSON.stringify(raw));\n return false;\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Enum — union of string/number literals; default is first value\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Union of string/number literals; _default is the first value unless overridden. */\nclass Enum<T extends string | number> extends Base<T> {\n readonly _default: T;\n constructor(private readonly values: readonly T[], defaultVal?: T) {\n super();\n this._default = defaultVal ?? values[0];\n }\n sanitize(raw: unknown): T { return this.values.includes(raw as T) ? (raw as T) : this._default; }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (this.values.includes(raw as T)) return true;\n ctx?.errors.push(pathStr(path ?? []) + ': expected one of ' + this.values.map(v => JSON.stringify(v)).join(' | ') + ', got ' + JSON.stringify(raw));\n return false;\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Union — first matching schema wins\n// _canSanitize: true if any member can attempt sanitization\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** How many of the best-matching member's errors a failed union appends after its headline\n * (review §2.8). Enough to name the offending key and its neighbours, short of a wall. */\nconst UNION_MEMBER_ERROR_LIMIT = 4;\n\n/** Tries member schemas in order; first whose isValid passes wins. sanitize returns _default when none match. */\nclass Union<T> extends Base<T> {\n /** Structural tag read by {@link describeSchema} — `schemas` alone cannot tell Union from Tuple. */\n readonly _kind = 'union' as const;\n readonly _default: T;\n constructor(private readonly schemas: ReadonlyArray<PxSchema<T>>, defaultVal?: T) {\n super();\n this._default = defaultVal ?? schemas[0]._default;\n }\n sanitize(raw: unknown): T {\n for (const s of this.schemas) {\n if (s.isValid(raw)) return s.sanitize(raw);\n }\n return this._default;\n }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n // Members see the MODE but NOT the caller's error sink (V6). Two separate\n // things were bundled in `ctx`: passing it whole made every non-matching\n // branch push errors even when a later branch matched; passing nothing at\n // all left `strict` inert inside every union. A scratch ctx carries the\n // flags and swallows the per-branch noise.\n const probe: PxValidationContext | undefined = ctx && { errors: [], warnings: [], strict: ctx.strict };\n if (this.schemas.some(s => s.isValid(raw, probe, path ? [...path] : undefined))) return true;\n if (!ctx) return false;\n\n const base = pathStr(path ?? []);\n ctx.errors.push(base + ': no union member matched for value ' + (JSON.stringify(raw) ?? '').slice(0, 240));\n\n // …then say WHY (review §2.8). The headline alone reads the same for a\n // `keyframes`/`keyframe` typo, for short `t`/`v` keys and for a plain type\n // mismatch, so an entry diagnostic — or an LLM repair loop — got no pointer to\n // the fix. Re-run each member into its OWN sink and report the one that got\n // FURTHEST into the value: the likeliest intended shape.\n //\n // Not every member's errors: the real unions run to 11 alternatives\n // (`PxKeyframeValueSchema`), so a typo inside one object member would arrive\n // buried under ten \"expected string, got object\" lines.\n let best: Array<string> | undefined;\n let bestDepth = -1;\n const leafExpectations: Array<string> = [];\n\n for (const member of this.schemas) {\n const sink: PxValidationContext = { errors: [], warnings: [], strict: ctx.strict };\n member.isValid(raw, sink, path ? [...path] : undefined);\n if (!sink.errors.length) continue; // cannot happen (it failed), but keeps this total\n\n // How far in did it get? Every message is \"<path>: <reason>\", and a member that\n // descended reports at a LONGER path than the union's own.\n const depth = Math.max(...sink.errors.map(e => e.slice(0, e.indexOf(':')).length));\n if (depth > bestDepth || (depth === bestDepth && best && sink.errors.length < best.length)) {\n bestDepth = depth;\n best = sink.errors;\n }\n // A member that stayed at the union's own path is a shape mismatch, not a\n // near-miss: collect just its expectation for the folded line below.\n if (depth <= base.length) {\n for (const e of sink.errors) {\n const m = /: expected (.+?), got /.exec(e);\n if (m && !leafExpectations.includes(m[1])) leafExpectations.push(m[1]);\n }\n }\n }\n\n if (best && bestDepth > base.length) {\n // One member reached inside the value — its errors ARE the diagnosis.\n for (const e of best.slice(0, UNION_MEMBER_ERROR_LIMIT)) {\n if (!ctx.errors.includes(e)) ctx.errors.push(e);\n }\n } else if (leafExpectations.length) {\n // Nothing descended: one line naming every shape this slot accepts.\n ctx.errors.push(base + ': expected ' + leafExpectations.join(' | '));\n }\n return false;\n }\n override _canSanitize(raw: unknown): boolean { return this.schemas.some(s => s._canSanitize(raw)); }\n}\n\n// Infers the union of all member types from a tuple of schemas.\n// Mapped-then-indexed, NOT `T extends ReadonlyArray<PxSchema<infer U>>`: the latter\n// gives TS one inference site for the whole array, so it collapses a heterogeneous\n// union to a single member (in practice the object branch) and the bare statics\n// vanish from the inferred type — `px.union([px.number(), px.object(…)])` typed as\n// the object alone. Per-position inference unions them properly (V6/Q1).\ntype UnionMembers<T extends ReadonlyArray<PxSchema<any, any>>> = {\n [K in keyof T]: T[K] extends PxSchema<infer U, any> ? U : never\n}[number];\n// NOTE: this only works because `px.union` / `px.discriminatedUnion` declare their\n// schema list as `const T` (Q1). Without the modifier TS infers the argument as a\n// plain ARRAY and unifies the element type to one `PxSchema<…>`, so `keyof T` has a\n// single member to map and the union collapses to whichever branch won unification —\n// in practice the last object branch, silently dropping every bare-static member\n// (`px.union([tuple, …, propAnim])` typed as propAnim alone). Source-level checks and\n// the emitted .d.ts both went wrong, so the dist types rejected legal wire values.\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// DiscriminatedUnion — routes by a literal key field, not first-match order\n// ─────────────────────────────────────────────────────────────────────────────\n\n// `undefined` admitted: a member may declare its discriminant `.optional()` (see `_absentMember`).\ntype AnyDiscriminantShape<K extends string> = Record<K, PxSchema<string | number | boolean | undefined, any>>;\n\n/**\n * Reads `raw[key]`, finds the member schema whose literal matches that value,\n * then delegates sanitize/isValid to that member. Falls back to the first\n * member for sanitize when no match is found.\n */\nclass DiscriminatedUnion<T> extends Base<T> {\n /** Structural tag read by {@link describeSchema}. */\n readonly _kind = 'discriminatedUnion' as const;\n readonly _default: T;\n private readonly _map: Map<string | number | boolean, PxSchema<T>>;\n /** The member an ABSENT discriminant selects — the one whose discriminant slot is\n * `.optional()` (e.g. `timeline.type` omitted = the time-driven timeline). */\n private readonly _absentMember: PxSchema<T> | undefined;\n\n constructor(\n private readonly _key: string,\n private readonly _schemas: ReadonlyArray<PxSchema<T> & { readonly _shape: AnyDiscriminantShape<string> }>,\n defaultVal?: T\n ) {\n super();\n this._default = defaultVal ?? _schemas[0]._default;\n this._map = new Map();\n for (const s of _schemas) {\n const keySchema = s._shape[_key] as any;\n if (!keySchema) continue;\n // An optional discriminant wraps its literal: map the literal's value, and\n // remember this member as the one to use when the key is missing.\n const literal = keySchema.inner ?? keySchema;\n this._map.set(literal._default, s);\n if (keySchema.inner) this._absentMember = s;\n }\n }\n\n private _findSchema(raw: unknown): PxSchema<T> | undefined {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const val = (raw as Record<string, unknown>)[this._key];\n if (val === undefined || val === null) return this._absentMember;\n return this._map.get(val as string | number | boolean);\n }\n\n sanitize(raw: unknown): T {\n return (this._findSchema(raw) ?? this._schemas[0]).sanitize(raw);\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n const schema = this._findSchema(raw);\n if (!schema) {\n const val = (raw !== null && typeof raw === 'object' && !Array.isArray(raw))\n ? (raw as Record<string, unknown>)[this._key] : undefined;\n ctx?.errors.push(pathStr(path ?? []) + ': no discriminated union member matched '\n + this._key + '=' + JSON.stringify(val));\n return false;\n }\n return schema.isValid(raw, ctx, path);\n }\n\n override _canSanitize(raw: unknown): boolean {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return false;\n const schema = this._findSchema(raw);\n return schema ? schema._canSanitize(raw) : this._schemas[0]._canSanitize(raw);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Object — strips unknown keys; required fields use default, optional → undefined\n// _canSanitize: true when raw is a plain (non-array) object\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype AnyShape = Record<string, PxSchema<any, any>>;\n\n// Required keys: IsOptional=false → plain property.\n// Optional keys: IsOptional=true → ?:, with Exclude<T,undefined> (?: already adds undefined).\ntype InferShape<S extends AnyShape> =\n { [K in keyof S as S[K] extends PxSchema<any, true> ? never : K]: PxInfer<S[K]> } &\n { [K in keyof S as S[K] extends PxSchema<any, true> ? K : never]?: Exclude<PxInfer<S[K]>, undefined> };\n\n/**\n * Typed object schema; strips unknown keys, repairs required fields via their defaults.\n * _canSanitize checks structure only (non-array object) so partially-valid objects are repaired.\n */\nclass Obj<S extends AnyShape> extends Base<InferShape<S>> {\n readonly _default: InferShape<S>;\n\n constructor(readonly _shape: S) {\n super();\n const d: any = {};\n for (const key of Object.keys(_shape)) d[key] = _shape[key]._default;\n this._default = d;\n }\n\n sanitize(raw: unknown): InferShape<S> {\n const src = (raw && typeof raw === 'object' && !Array.isArray(raw)) ? raw as any : {};\n const out: any = {};\n for (const key of Object.keys(this._shape)) {\n const v = this._shape[key].sanitize(src[key]);\n // An OPTIONAL key that was absent sanitizes to `undefined`. Writing it anyway\n // produced a \"phantom\" own key — invisible to JSON, but NOT to `Object.keys`,\n // and consumers branch on that: `PxOffsetPathMaterializer` bails when a transform\n // carries keys beyond translate/origin, so phantoms silently disabled the CSS\n // Motion Path path, and `contentRefSplit` emitted `transform=\"\"`. Measured across\n // 135 real documents: 16,367 phantoms, changing the render of 10 of them.\n if (v !== undefined) out[key] = v;\n }\n return out;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n ctx?.errors.push(pathStr(path ?? []) + ': expected object, got ' + (Array.isArray(raw) ? 'array' : typeof raw));\n return false;\n }\n const obj = raw as any;\n const p = path ?? [];\n let ok = true;\n for (const key of Object.keys(this._shape)) {\n p.push(key);\n if (!this._shape[key].isValid(obj[key], ctx, p)) ok = false;\n p.pop();\n }\n // Strict mode: closed objects reject extra (undeclared) keys.\n // Default mode silently ignores them (matching `sanitize`'s strip-extras).\n if (ctx?.strict) {\n for (const key of Object.keys(obj)) {\n if (key in this._shape) continue;\n // An undefined-valued key is indistinguishable from an absent one for\n // every consumer, and JSON.stringify drops it — strict judges the\n // DOCUMENT, not the in-memory object that produced it (V6). Without\n // this, validating a freshly-built (pre-serialization) object flags\n // phantom keys that cannot exist on the wire.\n if (obj[key] === undefined) continue;\n p.push(key);\n ctx.errors.push(pathStr(p) + ': ' + PX_UNKNOWN_KEY_ERROR);\n p.pop();\n ok = false;\n }\n }\n return ok;\n }\n\n override _canSanitize(raw: unknown): boolean {\n return !!raw && typeof raw === 'object' && !Array.isArray(raw);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OpenObj — like Obj but passes unknown keys through unchanged (or validates/sanitizes\n// them against an optional open-value schema).\n// Known keys are validated/sanitized; unknown keys are passed through as-is when no\n// open schema is given, or validated/sanitized against the open schema when one is provided.\n// Inferred type: InferShape<S> & { [key: string]: V } where V defaults to any.\n// Note: TypeScript intersection semantics mean named-property access on the\n// derived type yields `any` rather than the specific declared type. For\n// type-precise access keep a hand-written interface alongside the schema.\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype InferOpenShape<S extends AnyShape, _V = any> = InferShape<S> & { [key: string]: any };\n\n/**\n * Open object schema; validates/repairs known keys, passes unknown keys through unchanged.\n * Use when the object may carry arbitrary extra properties (e.g. SVG element attributes).\n *\n * @param shape Known key schemas (validated and type-inferred).\n * @param openSchema Optional schema applied to every unknown key's value.\n * When omitted, unknown values are passed through as-is (`any`).\n */\nclass OpenObj<S extends AnyShape, V = any> extends Base<InferOpenShape<S, V>> {\n readonly _default: InferOpenShape<S, V>;\n\n constructor(readonly _shape: S, private readonly _openSchema?: PxSchema<V>) {\n super();\n const d: any = {};\n for (const key of Object.keys(_shape)) d[key] = _shape[key]._default;\n this._default = d;\n }\n\n sanitize(raw: unknown): InferOpenShape<S, V> {\n const src: Record<string, unknown> = (raw && typeof raw === 'object' && !Array.isArray(raw))\n ? raw as Record<string, unknown>\n : {};\n const out: Record<string, unknown> = { ...src };\n for (const key of Object.keys(this._shape)) {\n const v = this._shape[key].sanitize(src[key]);\n if (v !== undefined) out[key] = v; // no phantom keys — see Obj.sanitize\n }\n if (this._openSchema) {\n for (const key of Object.keys(src)) {\n if (!(key in this._shape)) out[key] = this._openSchema.sanitize(src[key]);\n }\n }\n return out as InferOpenShape<S, V>;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n ctx?.errors.push(pathStr(path ?? []) + ': expected object, got ' + (Array.isArray(raw) ? 'array' : typeof raw));\n return false;\n }\n const obj = raw as any;\n const p = path ?? [];\n let ok = true;\n for (const key of Object.keys(this._shape)) {\n p.push(key);\n if (!this._shape[key].isValid(obj[key], ctx, p)) ok = false;\n p.pop();\n }\n if (this._openSchema) {\n for (const key of Object.keys(obj)) {\n if (key in this._shape) continue;\n p.push(key);\n if (!this._openSchema.isValid(obj[key], ctx, p)) ok = false;\n p.pop();\n }\n }\n return ok;\n }\n\n override _canSanitize(raw: unknown): boolean {\n return !!raw && typeof raw === 'object' && !Array.isArray(raw);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Array — items that cannot be attempted are filtered out; default is []\n// Uses _canSanitize for filtering so partially-valid objects are repaired, not dropped.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Array schema; items failing _canSanitize are filtered out rather than blocking the whole array. */\nclass Arr<T> extends Base<Array<T>> {\n readonly _default: Array<T> = [];\n constructor(private readonly item: PxSchema<T>) { super(); }\n\n sanitize(raw: unknown): Array<T> {\n if (!Array.isArray(raw)) return [];\n const out: Array<T> = [];\n for (const el of raw) {\n if (this.item._canSanitize(el)) out.push(this.item.sanitize(el));\n }\n return out;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (!Array.isArray(raw)) {\n ctx?.errors.push(pathStr(path ?? []) + ': expected array, got ' + typeof raw);\n return false;\n }\n const p = path ?? [];\n let ok = true;\n for (let i = 0; i < raw.length; i++) {\n p.push('[' + i + ']');\n if (!this.item.isValid(raw[i], ctx, p)) ok = false;\n p.pop();\n }\n return ok;\n }\n\n override _canSanitize(raw: unknown): boolean { return Array.isArray(raw); }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Record — invalid values are dropped; default is {}\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** String-keyed record; values failing _canSanitize are dropped rather than blocking the whole record. */\nclass Rec<T> extends Base<Record<string, T>> {\n /** Structural tag read by {@link describeSchema}. */\n readonly _kind = 'record' as const;\n readonly _default: Record<string, T> = {};\n constructor(private readonly value: PxSchema<T>) { super(); }\n\n sanitize(raw: unknown): Record<string, T> {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};\n const out: Record<string, T> = {};\n for (const [k, v] of Object.entries(raw)) {\n if (this.value._canSanitize(v)) out[k] = this.value.sanitize(v);\n }\n return out;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n ctx?.errors.push(pathStr(path ?? []) + ': expected object/record, got ' + (Array.isArray(raw) ? 'array' : typeof raw));\n return false;\n }\n const p = path ?? [];\n let ok = true;\n for (const [k, v] of Object.entries(raw as object)) {\n p.push(k);\n if (!this.value.isValid(v, ctx, p)) ok = false;\n p.pop();\n }\n return ok;\n }\n\n override _canSanitize(raw: unknown): boolean {\n return !!raw && typeof raw === 'object' && !Array.isArray(raw);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Any — passes raw through unchanged, always valid\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Passes any value through unchanged; always valid. Useful for opaque blobs with no schema. */\nclass Any extends Base<any> {\n readonly _default: any = undefined;\n sanitize(raw: unknown): any { return raw; }\n isValid(_raw: unknown, _ctx?: PxValidationContext, _path?: Array<string>): boolean { return true; }\n override _canSanitize(_raw: unknown): boolean { return true; }\n}\n\n\n/**\n * Like {@link Any} but REQUIRES the value to be present: anything except `undefined`.\n *\n * Use it for a key whose type is open but whose PRESENCE is what identifies the\n * shape — e.g. the structured-static branch `{value: …}` (V6). With `px.any()`\n * there, the branch matched EVERY object, because `any` accepts `undefined` and\n * an absent key is indistinguishable from one holding `undefined`.\n */\nclass Defined extends Base<any> {\n readonly _default: any = undefined;\n sanitize(raw: unknown): any { return raw; }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (raw !== undefined) return true;\n ctx?.errors.push(pathStr(path ?? []) + ': required value is missing');\n return false;\n }\n override _canSanitize(raw: unknown): boolean { return raw !== undefined; }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Lazy — resolves schema on first use, required for recursive types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Defers schema resolution to first use; required to break circular references in recursive types. */\nclass Lazy<T> extends Base<T> {\n private resolved: PxSchema<T> | null = null;\n constructor(private readonly fn: () => PxSchema<T>, readonly _default: T) { super(); }\n\n private get schema(): PxSchema<T> {\n return this.resolved ?? (this.resolved = this.fn());\n }\n\n sanitize(raw: unknown): T { return this.schema.sanitize(raw); }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean { return this.schema.isValid(raw, ctx, path); }\n override _canSanitize(raw: unknown): boolean { return this.schema._canSanitize(raw); }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tuple — fixed-length array with per-position schemas; default is defaults of each position\n// _canSanitize: true only when raw is an array of the exact expected length\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Maps a tuple of schemas to a tuple of their inferred types.\ntype TupleItems<T extends ReadonlyArray<PxSchema<any, any>>> =\n { -readonly [K in keyof T]: T[K] extends PxSchema<infer U, any> ? U : never };\n\n/** Fixed-length array schema; validates element count and each position individually. */\nclass Tuple<T extends ReadonlyArray<PxSchema<any, any>>> extends Base<TupleItems<T>> {\n /** Structural tag read by {@link describeSchema} — `schemas` alone cannot tell Tuple from Union. */\n readonly _kind = 'tuple' as const;\n readonly _default: TupleItems<T>;\n\n constructor(private readonly schemas: T) {\n super();\n this._default = schemas.map(s => s._default) as unknown as TupleItems<T>;\n }\n\n sanitize(raw: unknown): TupleItems<T> {\n if (!Array.isArray(raw) || raw.length !== this.schemas.length) return this._default;\n return this.schemas.map((s, i) => s.sanitize((raw as unknown[])[i])) as unknown as TupleItems<T>;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (!Array.isArray(raw) || raw.length !== this.schemas.length) {\n ctx?.errors.push(pathStr(path ?? []) + ': expected tuple of length ' + this.schemas.length + ', got ' + (Array.isArray(raw) ? 'array[' + (raw as unknown[]).length + ']' : typeof raw));\n return false;\n }\n const p = path ?? [];\n let ok = true;\n for (let i = 0; i < this.schemas.length; i++) {\n p.push('[' + i + ']');\n if (!(this.schemas as ReadonlyArray<PxSchema<any, any>>)[i].isValid((raw as unknown[])[i], ctx, p)) ok = false;\n p.pop();\n }\n return ok;\n }\n\n // Require exact length so wrong-length arrays are dropped rather than repaired to default.\n override _canSanitize(raw: unknown): boolean {\n return Array.isArray(raw) && raw.length === this.schemas.length;\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// implementsInterface — compile-time lock between a schema and an interface\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Returns a pass-through wrapper that enforces at compile time that the schema's\n * inferred type is structurally assignable to `T`. Catches type mismatches on\n * required fields and value-type mismatches on optional fields.\n *\n * For key-level equality (catching optional field renames), pair with a\n * `KeysMatch` assertion after the inferred type alias:\n * ```ts\n * const _ck: KeysMatch<PxFoo, _PxFoo> = true;\n * ```\n *\n * @example\n * export const PxLoopSchema = implementsInterface<_PxLoop>()(px.object({ ... }));\n */\nexport function implementsInterface<T>() {\n return <S extends PxSchema<T>>(schema: S): S => schema;\n}\n\n/**\n * Evaluates to `true` when A and B have exactly the same set of keys; `false` otherwise.\n * Use with a `const` assertion to get a compile-time error on key renames:\n * ```ts\n * const _ck: KeysMatch<PxFoo, _PxFoo> = true;\n * ```\n * @public @advanced\n */\nexport type KeysMatch<A, B> =\n [Exclude<keyof A, keyof B>] extends [never]\n ? [Exclude<keyof B, keyof A>] extends [never] ? true : false\n : false;\n\n/**\n * Builds a `{ key: 'key', ... }` object from a schema's shape.\n * Each value is typed as the literal key name, so `schemaKeys(PxFooSchema).bar`\n * is typed as `'bar'` — use in @serializable calls to get a compile-time error\n * if the field is renamed in the schema.\n * @public @advanced\n */\nexport function schemaKeys<S extends { readonly _shape: Record<string, any> }>(schema: S) {\n return Object.fromEntries(\n Object.keys(schema['_shape']).map(k => [k, k])\n ) as { [K in keyof S['_shape']]: K };\n}\n\n/**\n * Describes the structural kind of a schema for traversal purposes.\n * Use with `getMissingCoveragePaths` or similar tree-walking utilities.\n *\n * Every composite kind is reported, so a walker can reach EVERY field a document\n * may legally carry — that is what makes schema-coverage checking possible\n * (see the app's `collectSchemaFieldUniverse`).\n *\n * - `shape` — object schema (Obj/OpenObj); `shape` maps key → sub-schema.\n * `openValue` is set for open objects: the schema unknown keys\n * are validated against (undefined ⇒ unknown keys pass through as-is)\n * - `array` — array schema; `item` is the element schema\n * - `optional` — optional wrapper; `inner` is the wrapped schema\n * - `lazy` — lazy/recursive schema; `resolved` is the resolved schema (cached)\n * - `union` — `px.union`; `members` are the alternatives, tried in order\n * - `discriminatedUnion`— `px.discriminatedUnion`; `key` is the discriminant field and\n * `members` the alternatives (each an object schema with a literal at `key`)\n * - `record` — `px.record`; `value` is the schema every entry's value must match\n * - `tuple` — `px.tuple`; `items` are the positional element schemas\n * - `leaf` — primitive, literal, enum, any — no traversable children\n * @public @advanced\n */\nexport type PxSchemaDesc =\n | { kind: 'shape'; shape: Record<string, PxSchema<any, any>>; openValue?: PxSchema<any, any> }\n | { kind: 'array'; item: PxSchema<any, any> }\n | { kind: 'optional'; inner: PxSchema<any, any> }\n | { kind: 'lazy'; resolved: PxSchema<any, any> }\n | { kind: 'union'; members: ReadonlyArray<PxSchema<any, any>> }\n | { kind: 'discriminatedUnion'; key: string; members: ReadonlyArray<PxSchema<any, any>> }\n | { kind: 'record'; value: PxSchema<any, any> }\n | { kind: 'tuple'; items: ReadonlyArray<PxSchema<any, any>> }\n | { kind: 'leaf' };\n\n/** @public @advanced */\nexport function describeSchema(schema: PxSchema<any, any>): PxSchemaDesc {\n const s = schema as any;\n // `_kind` is set only by the classes that are otherwise indistinguishable by\n // duck-typing (Union vs Tuple both carry `schemas`), so it is checked first.\n switch (s._kind) {\n case 'union': return { kind: 'union', members: s.schemas };\n case 'discriminatedUnion': return { kind: 'discriminatedUnion', key: s._key, members: s._schemas };\n case 'record': return { kind: 'record', value: s.value };\n case 'tuple': return { kind: 'tuple', items: s.schemas };\n }\n if ('_shape' in s) return { kind: 'shape', shape: s._shape, openValue: s._openSchema };\n if ('item' in s) return { kind: 'array', item: s.item };\n if ('inner' in s) return { kind: 'optional', inner: s.inner };\n // `??=` (not `??`): an unresolved lazy would otherwise build a FRESH schema on\n // every call, so two traversals could never agree on schema identity.\n if ('fn' in s) return { kind: 'lazy', resolved: (s.resolved ??= s.fn()) };\n return { kind: 'leaf' };\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public factory\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** @public @advanced */\nexport const px = {\n /** Matches a string. Default: '' or provided value. */\n string: (defaultVal = ''): PxSchema<string> => new Str(defaultVal),\n\n /** Matches a finite number. Default: 0 or provided value. */\n number: (defaultVal = 0): PxSchema<number> => new Num(defaultVal),\n\n /** Matches a boolean. Default: false or provided value. */\n boolean: (defaultVal = false): PxSchema<boolean> => new Bool(defaultVal),\n\n /** Matches one exact primitive value; its default is the value itself. */\n literal: <T extends string | number | boolean>(value: T): PxSchema<T> =>\n new Literal(value),\n\n /** Matches one of a fixed set of string/number values. Default: first value. */\n enum: <T extends string | number>(values: readonly T[], defaultVal?: T): PxSchema<T> =>\n new Enum(values, defaultVal),\n\n /**\n * Returns the first schema whose isValid passes.\n * TypeScript infers the union of all member types automatically.\n */\n union: <const T extends ReadonlyArray<PxSchema<any, any>>>(\n schemas: T,\n defaultVal?: UnionMembers<T>\n ): PxSchema<UnionMembers<T>> =>\n new Union(schemas as any, defaultVal) as any,\n\n /**\n * Discriminated union — reads `raw[key]`, finds the member schema whose\n * literal at `key` matches, then delegates sanitize/isValid to that member.\n * Each member must be an object schema with a `px.literal(...)` at `key`.\n * TypeScript infers the union of all member types automatically.\n */\n discriminatedUnion: <\n K extends string,\n const T extends ReadonlyArray<PxSchema<any, any> & { readonly _shape: AnyDiscriminantShape<K> }>\n >(key: K, schemas: T): PxSchema<UnionMembers<T>> =>\n new DiscriminatedUnion(key, schemas as any) as any,\n\n /** Typed object — unknown keys are stripped. Required fields fall back to their default. */\n object: <S extends AnyShape>(shape: S): PxSchema<InferShape<S>> & { readonly _shape: S } =>\n new Obj(shape),\n\n /**\n * Open object — validates known keys; passes unknown keys through as-is,\n * or validates/sanitizes them against `openSchema` when provided.\n */\n openObject: <S extends AnyShape, V = any>(\n shape: S,\n openSchema?: PxSchema<V>\n ): PxSchema<InferOpenShape<S, V>> & { readonly _shape: S } =>\n new OpenObj(shape, openSchema) as any,\n\n /**\n * Creates a new closed object schema by merging a base schema's shape with additional fields.\n * The base can be the result of px.object() or px.openObject() — anything with a _shape property.\n *\n * @example\n * const PxSvgNodeSchema = px.extendedObject(PxNodeBaseSchema, { width: px.number().optional() });\n */\n extendedObject: <B extends AnyShape, E extends AnyShape>(\n base: { readonly _shape: B },\n extra: E\n ): PxSchema<InferShape<B & E>> & { readonly _shape: B & E } =>\n new Obj({ ...base._shape, ...extra } as B & E),\n\n /** Array whose unrecoverable items are filtered out. Default: []. */\n array: <T>(item: PxSchema<T>): PxSchema<Array<T>> =>\n new Arr(item),\n\n /** String-keyed record whose unrecoverable values are dropped. Default: {}. */\n record: <T>(value: PxSchema<T>): PxSchema<Record<string, T>> =>\n new Rec(value),\n\n /** Passes anything through unchanged — always valid. */\n any: (): PxSchema<any> => new Any(),\n\n /** Anything EXCEPT `undefined` — an open type whose presence is required (V6). */\n defined: (): PxSchema<any> => new Defined(),\n\n /** Fixed-length tuple — validates element count and each position individually. */\n tuple: <T extends ReadonlyArray<PxSchema<any, any>>>(schemas: T): PxSchema<TupleItems<T>> =>\n new Tuple(schemas),\n\n /** Defers schema creation — required for recursive types. Must supply a default value. */\n lazy: <T>(fn: () => PxSchema<T>, defaultVal: T): PxSchema<T> =>\n new Lazy(fn, defaultVal),\n} as const;\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n// ============================================================================\n// Wire CONSTANTS and schema-free helpers.\n//\n// Split out of `PxAnimatorTypes` so that code needing only an enum does not drag the\n// SCHEMA ENGINE in with it. `PxAnimatorTypes` builds ~31 schema declarations at module\n// scope through curried `implementsInterface<T>()(px.object(...))` calls, which no\n// minifier can treat as side-effect-free — so a single value import from it pulls in\n// `PxSchema` plus every declaration.\n//\n// That is exactly what happened: `PxDefinitions` imported `PxLoopExtend` (one small const)\n// and the pre-rendered player builds ended up carrying 14 KB of validation code they never\n// call. See dev-docs/plans/prerendered-player-builds.md.\n//\n// RULE: nothing in this file may import a VALUE from `PxAnimatorTypes`. Type-only imports\n// are fine — they are erased at build time and cannot create a runtime edge.\n// ============================================================================\n\nimport type { PxAnimatedSvgDocument, PxAnimatorConfig, PxBinding, PxDefinitions, PxNode, PxScroll, PxTrigger } from './PxAnimatorTypes';\n\n// ── WIRE ENUMS (review §2.7) ────────────────────────────────────────────────\n// ONE shape for every enumerated wire value: an exported `Px*` const namespace plus the\n// string type derived from it, so a call site can write `PxFillMode.forwards` and the\n// schema can reference the same members instead of repeating bare literals.\n//\n// NOT a TypeScript `enum`: these are WIRE values, and a document is authored as plain\n// JSON — `{ fill: 'forwards' }`. A string `enum` is nominal, so that literal would not\n// typecheck without importing the enum; the derived union accepts both spellings. It is\n// also the only form that composes (`PxTimelineEngineSetting` spreads `PxTimelineEngine`)\n// and that survives erasable-syntax / type-stripping builds.\n\n/** WAAPI `fill` — which values apply outside the active period. @public */\nexport const PxFillMode = {\n forwards: 'forwards',\n backwards: 'backwards',\n both: 'both',\n none: 'none',\n} as const;\n\nexport type PxFillMode = typeof PxFillMode[keyof typeof PxFillMode];\n\n/** WAAPI `direction` — which way each iteration runs. @public */\nexport const PxPlaybackDirection = {\n normal: 'normal',\n reverse: 'reverse',\n alternate: 'alternate',\n alternateReverse: 'alternate-reverse',\n} as const;\n\nexport type PxPlaybackDirection = typeof PxPlaybackDirection[keyof typeof PxPlaybackDirection];\n\n\n/** @internal */\nexport const PX_ANIM_SRC_ATTR_NAME = 'data-px-animation-src';\n\n/** @internal */\nexport const PX_ANIM_ATTR_NAME = '_px_animator';\n\n/** `trigger.startOn` — what starts the animation. `programmatic` waits for `play()`. @public */\nexport const PxStartOn = {\n load: 'load',\n mouseOver: 'mouseOver',\n click: 'click',\n scrollIntoView: 'scrollIntoView',\n programmatic: 'programmatic',\n} as const;\n\nexport type PxStartOn = typeof PxStartOn[keyof typeof PxStartOn];\n\n/** `trigger.outAction` — what happens when the trigger condition stops holding. @public */\nexport const PxOutAction = {\n continue: 'continue',\n pause: 'pause',\n reset: 'reset',\n reverse: 'reverse',\n} as const;\n\nexport type PxOutAction = typeof PxOutAction[keyof typeof PxOutAction];\n\n/** `trigger.finishAction` — what happens after a NATURAL finish. @public */\nexport const PxFinishAction = {\n hold: 'hold',\n reset: 'reset',\n} as const;\n\nexport type PxFinishAction = typeof PxFinishAction[keyof typeof PxFinishAction];\n\n/** `scroll.kind` — which scroll-driven timeline member this is: the subject's journey\n * through the scrollport (`view`), or the scroll container's own offset (`scroll`). * @public\n */\nexport const PxScrollKind = {\n view: 'view',\n scroll: 'scroll',\n} as const;\n\nexport type PxScrollKind = typeof PxScrollKind[keyof typeof PxScrollKind];\n\n/** `timeline.axis` — which axis of the scroll container drives progress. @public */\nexport const PxScrollAxis = {\n block: 'block',\n inline: 'inline',\n x: 'x',\n y: 'y',\n} as const;\n\nexport type PxScrollAxis = typeof PxScrollAxis[keyof typeof PxScrollAxis];\n\n/** `timeline.source` (`scroll` kind) — which scroll container is measured. @public */\nexport const PxScrollSource = {\n nearest: 'nearest',\n root: 'root',\n} as const;\n\nexport type PxScrollSource = typeof PxScrollSource[keyof typeof PxScrollSource];\n\n/** `timeline.pin.align` — where the pinned canvas is held in the scrollport. @public */\nexport const PxPinAlign = {\n top: 'top',\n center: 'center',\n bottom: 'bottom',\n} as const;\n\nexport type PxPinAlign = typeof PxPinAlign[keyof typeof PxPinAlign];\n\n/** The subject's journey phases across the scrollport (see scroll-timeline.design.md §4\n * for the exact `u`-space intervals each phase maps to). * @public\n */\nexport const PxScrollPhase = {\n cover: 'cover',\n contain: 'contain',\n entry: 'entry',\n exit: 'exit',\n entryCrossing: 'entry-crossing',\n exitCrossing: 'exit-crossing',\n} as const;\n\nexport type PxScrollPhase = typeof PxScrollPhase[keyof typeof PxScrollPhase];\n\n/** `alongPathMode` — how a value is sampled along a motion path. @public */\nexport const PxAlongPathMode = {\n sampled: 'sampled',\n offsetPath: 'offsetPath',\n} as const;\n\nexport type PxAlongPathMode = typeof PxAlongPathMode[keyof typeof PxAlongPathMode];\n\n/** WIRE `timeline.engine` — who runs the animation. `auto` (default) prefers the\n * platform's animation API and falls back to JS when the document needs something it\n * cannot express; `native` DEMANDS that API (WAAPI — and, for scroll/view timelines,\n * the browser's ScrollTimeline) with no fallback; `js` pins the player's own frame loop\n * and its own progress measurement. Const-namespace + matching string type so call\n * sites use named members (`PxTimelineEngineSetting.js`), not bare literals.\n *\n * NAMED `engine`, not `mode`: it selects HOW the animated attributes get updated, not\n * WHAT you see — an implementation preference. (`native` is the one value that can also\n * change the outcome: being a demand, an attribute WAAPI declines simply does not\n * animate. See `isNativeForced`.) */\n/** Timeline keys that BOTH union members carry, so they survive a change of `type`. */\nexport const PX_TIMELINE_SHARED_KEYS = ['duration', 'iterations', 'engine', 'frameRate'] as const;\n\n/** Timeline keys that exist ONLY on the time-driven member — a scroll/view timeline is\n * scrubbed by position, so nothing starts it and nothing delays it. */\nexport const PX_TIME_ONLY_TIMELINE_KEYS = ['trigger', 'delay', 'fillMode', 'direction'] as const;\n\n/**\n * The flat RUNTIME-VIEW keys — `duration`, `trigger`, `scroll`, … at the animator ROOT.\n *\n * They are the internal view the engines consume, never a wire spelling: on the wire playback\n * lives inside `timeline`. `getAnimatorConfig` drops them from a document, so a stray one is an\n * unknown key the diagnostic reports rather than a second spelling that quietly plays.\n */\nexport const PX_FLAT_RUNTIME_VIEW_KEYS: ReadonlyArray<string> = [\n ...PX_TIMELINE_SHARED_KEYS, ...PX_TIME_ONLY_TIMELINE_KEYS,\n 'fill', 'resetOnFinish', 'timelineSource', 'scroll',\n];\n\n/**\n * THE ENGINES — the two things that can actually update an animated attribute: hand it to the\n * platform's animation API (`native`), or write it from the player's own frame loop (`js`).\n *\n * This is the CORE set. Code that always knows which engine is running takes this (e.g.\n * `normalizeBindings`'s `engine` arg gates motion-along-path materialization).\n * @public\n */\nexport const PxTimelineEngine = {\n native: 'native',\n js: 'js',\n} as const;\n\nexport type PxTimelineEngine = typeof PxTimelineEngine[keyof typeof PxTimelineEngine];\n\n/**\n * What `timeline.engine` ACCEPTS on the wire: the engines above plus `auto` — \"you pick\", which\n * prefers `native` and falls back to `js` per document when the platform API declines an\n * attribute.\n *\n * Built by ADDING to the core set rather than subtracting from a wider one, so the two cannot\n * drift: every engine is automatically an accepted value, and `auto` is visibly the one extra.\n * @public\n */\nexport const PxTimelineEngineSetting = {\n ...PxTimelineEngine,\n auto: 'auto',\n} as const;\n\nexport type PxTimelineEngineSetting = typeof PxTimelineEngineSetting[keyof typeof PxTimelineEngineSetting];\n\n/** What a requested engine resolves to BEFORE the runtime probes support: `js` pins the frame\n * loop, anything else starts at `native`. NOTE this is only the STARTING point — `auto` still\n * falls back to `js` per document when the platform API declines an attribute, which happens at\n * bind time (see `PxAnimatorBind`), not here. * @public @advanced\n */\nexport function resolveTimelineEngine(engine: PxTimelineEngineSetting | undefined): PxTimelineEngine {\n return engine === PxTimelineEngineSetting.js ? PxTimelineEngine.js : PxTimelineEngine.native;\n}\n\n/** `native` is a demand, not a preference: no JS fallback when the platform API declines an attribute. @public @advanced */\nexport function isNativeForced(engine: PxTimelineEngineSetting | undefined): boolean {\n return engine === PxTimelineEngineSetting.native;\n}\n\n/** May the browser's ScrollTimeline/ViewTimeline drive a scroll/view timeline?\n * `auto` tries it first (falling back to the player's own measurement), `native`\n * asks for it, `js` never uses it. * @public @advanced\n */\nexport function mayUseNativeScrollTimeline(engine: PxTimelineEngineSetting | undefined): boolean {\n return engine !== PxTimelineEngineSetting.js;\n}\n\n/**\n * THE TRIGGER DEFAULTS — what a missing `trigger` field means. One table, declared by\n * `PxTriggerSchema` and applied by {@link resolveTrigger}, which every player calls (the web's\n * `setupAnimationTriggers`, the React Native component) — so a file behaves the same everywhere:\n * - `startOn` 'load' — a document is designed to play\n * - `outAction` 'continue' — leaving the trigger does not interrupt playback\n * - `scrollIntoViewThreshold` 0 — any visible pixel counts\n * @public @advanced\n */\nexport const PX_TRIGGER_DEFAULTS = {\n startOn: 'load',\n outAction: 'continue',\n scrollIntoViewThreshold: 0,\n} as const;\n\n/** A trigger with every default filled in. @public @advanced */\nexport interface PxResolvedTrigger {\n readonly startOn: NonNullable<PxTrigger['startOn']>;\n readonly outAction: NonNullable<PxTrigger['outAction']>;\n readonly scrollIntoViewThreshold: number;\n}\n\n// ── CONTROL MODE (API review §1 / §7) ────────────────────────────────────────\n// Which set of props drives playback. Every component picked its own order, so\n// `autoplay` + `progress={0.5}` played on React and Vue but seeked on React Native, and\n// React let a REF choose the mode — `<PixodeskSvgAnimator autoplay apiRef={api} />` never\n// started, because the imperative branch forced `startOn: 'programmatic'`.\n//\n// One order, decided once, used by react / vue / rn. This module owns the LOGIC and the\n// WARNING TEXT only; each component keeps its own `console.warn` wiring.\n\n/** Which props drive playback. `apiRef` is deliberately NOT a mode: the handle is filled in\n * every mode, so passing it alone leaves the document's own trigger in charge. * @public\n */\nexport const PxControlMode = {\n /** No control props — the document's trigger decides, and nothing is taken over. */\n static: 'static',\n /** `progress` / `time` — the host scrubs; the component seeks and stays paused. */\n fixedTime: 'fixedTime',\n /** `play` / `pause` — the host drives playback with booleans. */\n play: 'play',\n /** `autoplay` — the document's own trigger starts it. */\n autoplay: 'autoplay',\n} as const;\n\nexport type PxControlMode = typeof PxControlMode[keyof typeof PxControlMode];\n\n/**\n * The control props every framework component takes. `resolveControlMode` reads only WHICH\n * are set; the components read the values. ONE definition (review §9) — React and React\n * Native extend it, so the hover text below is what their users see.\n * @public\n */\nexport interface PxControlProps {\n /**\n * Show the frame at this position in the whole timeline (duration × iterations): `0` the\n * first frame, `1` the last — of ONE iteration when `iterations` is `'infinite'`, since an\n * endless run has no whole to be a fraction of. Wins over every other control prop.\n */\n progress?: number;\n /** Show the frame at this time, ms from the start of the whole run. Wins like `progress`. */\n time?: number;\n /** `true` plays now, whatever the document's trigger says; `false` holds where it is. */\n play?: boolean;\n /** Hold the current frame; set it back to `false` to resume. */\n pause?: boolean;\n /** Start the way the document says — its own `startOn` / `outAction` trigger. */\n autoplay?: boolean;\n}\n\n/** The chosen mode plus any conflict warnings — ready-made sentences, so three components\n * cannot word the same conflict three ways. * @public\n */\nexport interface PxResolvedControlMode {\n readonly mode: PxControlMode;\n readonly warnings: ReadonlyArray<string>;\n}\n\n/**\n * Picks the control mode from the props a component was given.\n *\n * PRECEDENCE, most specific first:\n * 1. `progress` / `time` — an explicit position is the most precise instruction there is\n * 2. `play` / `pause` — explicit playback state\n * 3. `autoplay` — defer to the document's trigger\n * 4. otherwise `static` — the document's trigger, with nothing taken over\n *\n * `apiRef` is absent on purpose. A ref is a handle, not an instruction: it is populated in\n * every mode, so `autoplay` + `apiRef` autostarts AND gives you the handle.\n *\n * A warning is produced only when props from two different tiers are set together — the\n * lower tier is then ignored, and silence about that is what made this hard to debug.\n * @public\n */\nexport function resolveControlMode(props: PxControlProps): PxResolvedControlMode {\n const hasFixedTime = props.progress !== undefined || props.time !== undefined;\n const hasPlayPause = props.play !== undefined || props.pause !== undefined;\n const hasAutoplay = !!props.autoplay;\n\n const warnings: Array<string> = [];\n const named = (a: string, b: string, winner: string): string =>\n a + ' and ' + b + ' were both set — ' + winner + ' wins, ' + (winner === a ? b : a) + ' is ignored.';\n\n if (hasFixedTime) {\n if (hasPlayPause) warnings.push(named('progress/time', 'play/pause', 'progress/time'));\n if (hasAutoplay) warnings.push(named('progress/time', 'autoplay', 'progress/time'));\n return { mode: PxControlMode.fixedTime, warnings };\n }\n if (hasPlayPause) {\n if (hasAutoplay) warnings.push(named('play/pause', 'autoplay', 'play/pause'));\n return { mode: PxControlMode.play, warnings };\n }\n if (hasAutoplay) return { mode: PxControlMode.autoplay, warnings };\n return { mode: PxControlMode.static, warnings };\n}\n\n/**\n * True when the component must take the document's trigger over, by forcing\n * `startOn: 'programmatic'` into its config patch.\n *\n * Every mode except `autoplay` — INCLUDING `static`. A component given no control props at all\n * must not start on its own: `<PixodeskSvgAnimator doc={…} />` renders the first frame and waits.\n * `autoplay` is the one mode that says \"let the document's trigger decide\".\n * @public\n */\nexport function controlModeTakesOverTrigger(mode: PxControlMode): boolean {\n return mode !== PxControlMode.autoplay;\n}\n\n/** A document's trigger with the defaults filled in. (`finishAction` is not a start/stop decision:\n * it reaches the engines as the runtime view's `resetOnFinish`.) * @public @advanced\n */\nexport function resolveTrigger(trigger: PxTrigger | undefined): PxResolvedTrigger {\n return {\n startOn: trigger?.startOn ?? PX_TRIGGER_DEFAULTS.startOn,\n outAction: trigger?.outAction ?? PX_TRIGGER_DEFAULTS.outAction,\n scrollIntoViewThreshold: trigger?.scrollIntoViewThreshold ?? PX_TRIGGER_DEFAULTS.scrollIntoViewThreshold,\n };\n}\n\n// V3 — every closed value list is a NAMED const + a strict `px.enum` slot, so a\n// typo is a schema ERROR instead of silently shipping. Plain `px.string()` stays\n// ONLY where SVG itself is open-ended (`gradientTransform`, `viewBox`, `path` d,\n// ids/refs, `debugGlobalName`).\n\n/** `loop.repeatAt` — WHICH END of the keyframe sequence the repeated segment is taken\n * from, and therefore which side of the timeline the repetition fills. A named\n * two-way selector (not a boolean) so a third value stays possible. * @public\n */\nexport const PxLoopRepeatAt = {\n /** Segment from the START; the repetition runs BEFORE the first keyframe\n * (intro loops that play until the main timeline begins). */\n start: 'start',\n /** DEFAULT — segment from the END; the repetition runs AFTER the last keyframe\n * (idle/outro loops that continue once the main timeline has finished). */\n end: 'end',\n} as const;\n\nexport type PxLoopRepeatAt = typeof PxLoopRepeatAt[keyof typeof PxLoopRepeatAt];\n\n/** `loop.direction` — how successive repetitions play, spelled like the timeline's\n * own `direction` so the two read as one idea. * @public\n */\nexport const PxLoopDirection = {\n /** DEFAULT — cycle: every repetition replays the segment the same way round. */\n normal: 'normal',\n /** Ping-pong: repetitions alternate forward / backward. */\n alternate: 'alternate',\n} as const;\n\nexport type PxLoopDirection = typeof PxLoopDirection[keyof typeof PxLoopDirection];\n\n/** SVG `mask-type` — how the mask source's pixels become alpha. @public */\nexport const PxMaskType = {\n luminance: 'luminance',\n alpha: 'alpha',\n} as const;\n\nexport type PxMaskType = typeof PxMaskType[keyof typeof PxMaskType];\n\n/** SVG coordinate system for `maskUnits` / `maskContentUnits` (and the gradient twin below). @public */\nexport const PxUnits = {\n userSpaceOnUse: 'userSpaceOnUse',\n objectBoundingBox: 'objectBoundingBox',\n} as const;\n\nexport type PxUnits = typeof PxUnits[keyof typeof PxUnits];\n\n/** `clone.without` — which part of the SOURCE'S OWN transform a `<use>` clone leaves\n * out. Absent = the whole element, as SVG `<use>` (a direct link, moves with the source);\n * `translate` = the source's placement is dropped, so the clone stays where the `<use>` put\n * it but still rotates/scales with the source. A future value `transform` may drop the\n * whole transform (content only) — not implemented yet.\n * (Was `clone.type: 'content'`; the wire is subtractive because the mechanism is a\n * ladder — the `<use>` can only point at one wrapper layer of the source.)\n * Old doc line:\n * `content` excludes the target's own translate (see `contentRefSplit`). * @public\n */\nexport const PxCloneWithout = {\n translate: 'translate',\n // transform: 'transform', // future: drop rotate/scale too (content only)\n} as const;\n\nexport type PxCloneWithout = typeof PxCloneWithout[keyof typeof PxCloneWithout];\n\n/** `textPath.pathOverflow` — glyphs past the path end: hide them, or keep laying\n * them along the tangent extension. * @public\n */\nexport const PxPathOverflow = {\n clip: 'clip',\n extend: 'extend',\n} as const;\n\nexport type PxPathOverflow = typeof PxPathOverflow[keyof typeof PxPathOverflow];\n\n/** SVG `lengthAdjust` — what `textLength` stretches. @public */\nexport const PxLengthAdjust = {\n spacing: 'spacing',\n spacingAndGlyphs: 'spacingAndGlyphs',\n} as const;\n\nexport type PxLengthAdjust = typeof PxLengthAdjust[keyof typeof PxLengthAdjust];\n\n/** SVG `<textPath method>` — how glyphs follow curvature. @public */\nexport const PxTextPathMethod = {\n align: 'align',\n stretch: 'stretch',\n} as const;\n\nexport type PxTextPathMethod = typeof PxTextPathMethod[keyof typeof PxTextPathMethod];\n\n/** SVG `<textPath spacing>` — whether the renderer may adjust spacing. @public */\nexport const PxTextPathSpacing = {\n auto: 'auto',\n exact: 'exact',\n} as const;\n\nexport type PxTextPathSpacing = typeof PxTextPathSpacing[keyof typeof PxTextPathSpacing];\n\n/** `strokeTrim.subPaths` — what the 0..1 `range`/`offset` window is measured over.\n * `separate` (default): each sub-path against its OWN length, all trimmed alike.\n * `combined`: every descendant sub-path chained end-to-end into one virtual path,\n * so the window slides across siblings (AE \"Trim All As One\"). * @public\n */\nexport const PxStrokeTrimSubPaths = {\n separate: 'separate',\n combined: 'combined',\n} as const;\n\nexport type PxStrokeTrimSubPaths = typeof PxStrokeTrimSubPaths[keyof typeof PxStrokeTrimSubPaths];\n\n\n// S8: `textContent` is the ONE text-content key (the DOM property name). `text` is not a wire\n// key: it was triply overloaded (the `text` tag, the `effects.text` group, and a content alias)\n// and no reader accepts it.\n/** @internal */\nexport const PX_TEXT_CONTENT_ATTR = 'textContent';\n\n/** The DOM `class` attribute. A name we EMIT but do not own, so it is written through this\n * constant rather than as an identifier — every other emitted attribute name reaches the\n * DOM as a string, and `class` was the one exception, which is why the minifier renamed it\n * to `ct` in the shipped bundles (dev-docs/plans/minification-boundary.md §1.1). */\nexport const CLASS_ATTR = 'class';\n\n/** The DOM `transform` attribute, and the key the animation record uses for it. Both are\n * DATA names — a dictionary key, not a field of one of our typed structures — so they are\n * written as constants rather than as identifiers. */\nexport const TRANSFORM_ATTR = 'transform';\n\n/** `animate.offsetDistance` — the CSS Motion Path channel the offset-path materializer writes. */\nexport const OFFSET_DISTANCE_ATTR = 'offsetDistance';\n\n// Wire keys that are NEVER DOM attributes (internal use only).\n//\n// `effects` is here for safety rather than necessity: `materializeNodeEffects` deletes it at\n// load, so today nothing reaches the renderer with it still attached. That is a property\n// of the pipeline, though, not of the contract — an effect path that returns early, or a\n// document carrying an effect key the pipeline does not recognize, would otherwise leave\n// the object behind and the renderer would write `effects=\"[object Object]\"` with no error\n// anywhere. Listing it makes the invariant structural (J4).\n/** @internal */\nexport const INTERNAL_ATTRS = new Set([\n 'type', 'children', 'animator', 'meta', 'animate', 'effects', PX_TEXT_CONTENT_ATTR\n]);\n\n// ============================================================================\n// TRANSFORM\n// ============================================================================\n\n/**\n * Names of the transform parts that can appear inside a transform value record.\n * The unified `transform` slot replaces the earlier per-part top-level keys\n * (`translate`, `rotate`, `scale`, `origin`) — those names now live as keys\n * inside a `PxTransformParts` record.\n */\n/** The transform-part names, as a named record — they are keys of a DATA record (the\n * transform value), so code reaches them through this rather than as bare literals. */\nexport const TRANSFORM_PART = {\n translate: 'translate',\n rotate: 'rotate',\n scale: 'scale',\n origin: 'origin',\n} as const;\n\n/** @public */\nexport const PX_TRANSFORM_PART_KEYS = [\n TRANSFORM_PART.translate, TRANSFORM_PART.rotate, TRANSFORM_PART.scale, TRANSFORM_PART.origin,\n] as const;\n\n/** One of the transform-part key strings. @public */\nexport type PxTransformPartKey = typeof PX_TRANSFORM_PART_KEYS[number];\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Gradient paint effect — `fillGradient` / `strokeGradient`.\n//\n// Materializer pattern mirrors `maskedByEffect`: at apply time the gradient\n// effect generates a `<linearGradient>` / `<radialGradient>` def into `ctx.defs`,\n// then sets the host element's `fill` / `stroke` to `url(#auto-id)`. The wire\n// gradient is geometry parts (`p1`/`p2` linear, `c`/`r`/`fp` radial — standard\n// animatable slots) + a stop sequence that is either static (bare array) or\n// animated (a single `{keyframes}` block whose each kf's `value` is the FULL\n// `Array<{offset, color}>` snapshot at that time). Per-stop independent\n// timelines are intentionally NOT modelled — the source is a single\n// stop-color keyframe group. Animated geometry is frames-engine only\n// (CSS/WAAPI cannot animate gradient endpoints; `mode: 'auto'` handles it).\n//\n// Stop count is constant across kfs. `gradientTransform` is captured as static\n// only (animated transform is vanishingly rare).\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Loose enums for `spreadMethod` / gradient `type` — kept on the wire as\n * plain strings (matches the rest of the schema's loose-enum stance) but\n * collected here so call sites use named constants instead of bare literals.\n *\n * `gradientUnits` has NO enum of its own: it takes the same two values as every other\n * units slot, so it reuses {@link PxUnits} (review §2.7 — one name per value set). * @public\n */\nexport const PxGradientSpreadMethod = {\n pad: 'pad',\n reflect: 'reflect',\n repeat: 'repeat',\n} as const;\n\nexport type PxGradientSpreadMethod = typeof PxGradientSpreadMethod[keyof typeof PxGradientSpreadMethod];\n\n/** @public */\nexport const PxGradientType = {\n linear: 'linear',\n radial: 'radial',\n} as const;\n\nexport type PxGradientType = typeof PxGradientType[keyof typeof PxGradientType];\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/** @public @advanced */\nexport function isPxDocument(doc: any): doc is PxAnimatedSvgDocument {\n if (!(\n doc &&\n typeof doc === 'object' &&\n !Array.isArray(doc)\n )) {\n return false;\n }\n\n // `type` is the tag, and the ONLY discriminator — it is what the schema requires\n // (`px.literal('svg')`). A `tagName` alternative was accepted here until 2026-08,\n // which meant a tagName-only document passed this gate and then failed\n // `isValidPxDocument`; nothing ever wrote it.\n return doc.type === 'svg';\n}\n\n/**\n * The animator config, at either of its TWO canonical addresses (S4).\n *\n * `animator` is the only name. It has two addresses because the SVG form has no other\n * slot: a `.svga`/JSON document carries it at the top level, while a pre-rendered\n * `.svg` carries it inside the root element's `data-px-meta` blob — i.e. under `meta`.\n * The editor lifts/un-lifts between the two on write/read.\n *\n * The `animation` / `meta.animation` spellings were removed 2026-08: nothing wrote\n * them and they were never in the schema.\n * @public @advanced\n */\nexport function getAnimatorConfig(doc: PxAnimatedSvgDocument): PxAnimatorConfig | undefined {\n const cfg = doc?.animator || doc?.meta?.animator;\n if (!cfg) return undefined;\n const memoised = wireViewMemo.get(cfg as object);\n if (memoised) return memoised;\n\n // A document states playback ONLY inside `timeline`. A flat key at the animator root is not a\n // second spelling to honor — it is an unknown key (`validateDocument` and the entry diagnostic\n // both report it), so it is dropped here and never reaches an engine.\n const wire = cfg as Record<string, unknown>;\n const stray = PX_FLAT_RUNTIME_VIEW_KEYS.filter(k => wire[k] !== undefined);\n const source = stray.length ? { ...wire } : cfg;\n for (const k of stray) delete (source as Record<string, unknown>)[k];\n\n // Every internal consumer sees the FLAT view — the nested `timeline` spelling is\n // folded down here, once, so the engines/effects/drivers never branch on it.\n const view = flattenAnimatorTimeline(source as PxAnimatorConfig);\n // Memoised on the DOCUMENT's config: repeated calls must return the same object (callers\n // compare identity and cache off it), and `source` is a fresh object when keys were dropped.\n wireViewMemo.set(cfg as object, view);\n return view;\n}\n\n/** Memo for {@link getAnimatorConfig} — see the identity note inside it. */\nconst wireViewMemo = new WeakMap<object, PxAnimatorConfig>();\n\n\n// ============================================================================\n// TIMELINE SPELLING (review §2.1)\n//\n// The wire spelling is `animator.timeline: { type?: 'time'|'scroll'|'view', … }` (absent = 'time');\n// the flat form (`timelineSource` + `scroll` + loose clock knobs) is the INTERNAL\n// runtime view only — not a wire format. These two functions convert between them:\n// • flattenAnimatorTimeline — wire → runtime view; applied by `getAnimatorConfig`,\n// so ALL runtime code keeps consuming the flat form it always has.\n// • nestAnimatorTimeline — runtime view → wire; applied by writers (the editor) so\n// files carry only the nested spelling and its mode-dead keys are structurally absent.\n// ============================================================================\n\n/** Memo: flatten allocates a new config; repeated `getAnimatorConfig` calls must keep\n * returning the SAME object (some callers compare identity / cache off it). */\nconst flattenMemo = new WeakMap<object, PxAnimatorConfig>();\n\n/**\n * Folds `cfg.timeline` (the wire spelling) into the flat runtime-view fields the engines\n * consume. Returns `cfg` unchanged when there is nothing to fold. Never mutates input.\n * @public @advanced\n */\nexport function flattenAnimatorTimeline(cfg: PxAnimatorConfig): PxAnimatorConfig {\n const timeline: any = (cfg as any).timeline;\n if (timeline === undefined || timeline === null || typeof timeline !== 'object') return cfg;\n\n const memoised = flattenMemo.get(cfg as object);\n if (memoised) return memoised;\n\n const { timeline: _dropped, ...flat } = cfg as any;\n\n // `engine` and `frameRate` are shared by every timeline type: how the attributes get\n // updated, and at what rate when that is the player's own frame loop.\n if (timeline.engine !== undefined) flat.engine = timeline.engine;\n if (timeline.frameRate !== undefined) flat.frameRate = timeline.frameRate;\n\n if (timeline.type === 'scroll' || timeline.type === 'view') {\n flat.timelineSource = 'scroll';\n if (timeline.duration !== undefined) flat.duration = timeline.duration; // §2.8\n if (timeline.iterations !== undefined) flat.iterations = timeline.iterations;\n const scroll: PxScroll = { ...(flat.scroll || {}) };\n scroll.kind = timeline.type;\n if (timeline.axis !== undefined) scroll.axis = timeline.axis;\n if (timeline.source !== undefined) scroll.source = timeline.source;\n if (timeline.subject !== undefined) scroll.subject = timeline.subject;\n if (timeline.smoothing !== undefined) scroll.smoothing = timeline.smoothing;\n if (timeline.range !== undefined) scroll.range = timeline.range;\n const pin = timeline.pin;\n if (typeof pin === 'boolean') scroll.pin = pin;\n else if (pin && typeof pin === 'object') {\n scroll.pin = true;\n if (pin.align !== undefined) scroll.pinAlign = pin.align;\n if (pin.offset !== undefined) scroll.pinOffset = pin.offset;\n if (pin.distance !== undefined) scroll.pinDistance = pin.distance;\n }\n flat.scroll = scroll;\n } else { // 'time', absent, or unknown — the time-driven timeline is the default\n if (timeline.duration !== undefined) flat.duration = timeline.duration; // §2.8\n if (timeline.trigger !== undefined) {\n const { finishAction, ...restTrigger } = timeline.trigger;\n if (Object.keys(restTrigger).length) flat.trigger = restTrigger;\n if (finishAction !== undefined) flat.resetOnFinish = finishAction === 'reset';\n }\n if (timeline.delay !== undefined) flat.delay = timeline.delay;\n if (timeline.iterations !== undefined) flat.iterations = timeline.iterations;\n if (timeline.direction !== undefined) flat.direction = timeline.direction;\n if (timeline.fillMode !== undefined) flat.fill = timeline.fillMode; // wire `fillMode` → runtime `fill`\n }\n\n flattenMemo.set(cfg as object, flat);\n return flat;\n}\n\n/**\n * Which scroll-driven member an absent `scroll.kind` selects — `'view'`, per `_PxScroll.kind`.\n *\n * The flat view says WHICH FAMILY drives progress (`timelineSource: 'scroll'`) separately from\n * WHICH MEMBER of it (`scroll.kind`), and only the family is named \"scroll\". Defaulting the\n * member to its family's name reads natural and is wrong: it silently rewrote every document\n * that left the kind at its default — which, being the default, is most of them.\n */\nfunction scrollKindOrDefault(kind: unknown): 'view' | 'scroll' {\n return kind === 'scroll' ? 'scroll' : 'view';\n}\n\n/**\n * Converts a FLAT animator config into the written spelling: mode-specific keys fold into\n * one discriminated `timeline` object; the legacy flat keys are removed from the output.\n * Returns a new object (input untouched); a config already carrying `timeline` passes\n * through unchanged; a pure-shared config (duration/mode/… only) gets no `timeline` at all.\n * @public @advanced\n */\nexport function nestAnimatorTimeline(cfg: PxAnimatorConfig): PxAnimatorConfig {\n if (!cfg || (cfg as any).timeline !== undefined) return cfg;\n\n const { timelineSource, scroll, trigger, delay, iterations, direction, fill, resetOnFinish,\n duration, engine, frameRate, ...shared } = cfg as any;\n\n if (timelineSource === 'scroll') {\n const timeline: any = { type: scrollKindOrDefault(scroll?.kind) };\n if (engine !== undefined) timeline.engine = engine;\n if (frameRate !== undefined) timeline.frameRate = frameRate;\n if (duration !== undefined) timeline.duration = duration; // §2.8\n // Finite iterations survive scrubbing (D4); 'infinite' cannot map to a range.\n if (typeof iterations === 'number') timeline.iterations = iterations;\n if (scroll) {\n if (scroll.axis !== undefined) timeline.axis = scroll.axis;\n if (scroll.source !== undefined) timeline.source = scroll.source;\n if (scroll.subject !== undefined) timeline.subject = scroll.subject;\n if (scroll.smoothing !== undefined) timeline.smoothing = scroll.smoothing;\n if (scroll.range !== undefined) timeline.range = scroll.range;\n const hasPinParams = scroll.pinAlign !== undefined || scroll.pinOffset !== undefined || scroll.pinDistance !== undefined;\n if (hasPinParams) {\n timeline.pin = {\n ...(scroll.pinAlign !== undefined ? { align: scroll.pinAlign } : {}),\n ...(scroll.pinOffset !== undefined ? { offset: scroll.pinOffset } : {}),\n ...(scroll.pinDistance !== undefined ? { distance: scroll.pinDistance } : {}),\n };\n } else if (scroll.pin !== undefined) {\n timeline.pin = scroll.pin;\n }\n }\n return { ...shared, timeline };\n }\n\n // Time-driven: `type` is optional on the wire and 'time' is the default, so the\n // writer omits it — the common case declares nothing.\n const timeline: any = {};\n if (engine !== undefined) timeline.engine = engine;\n if (frameRate !== undefined) timeline.frameRate = frameRate;\n if (duration !== undefined) timeline.duration = duration; // §2.8\n if (trigger !== undefined || resetOnFinish) {\n const t: any = { ...(trigger || {}) };\n if (resetOnFinish) t.finishAction = 'reset';\n timeline.trigger = t;\n }\n if (delay !== undefined) timeline.delay = delay;\n if (iterations !== undefined) timeline.iterations = iterations;\n if (direction !== undefined) timeline.direction = direction;\n if (fill !== undefined) timeline.fillMode = fill; // runtime `fill` → wire `fillMode`\n\n // An empty time timeline says nothing — omit the block entirely.\n return Object.keys(timeline).length > 0 ? { ...shared, timeline } : shared;\n}\n\n\n/** @public @advanced */\nexport function getDefinitions(doc: PxAnimatedSvgDocument): PxDefinitions | undefined {\n if (!doc) return undefined;\n return getAnimatorConfig(doc)?.definitions;\n}\n\n/** The bind-by-id document's `animator.bindings`, as written — `target` keeps its `#`. @public @advanced */\nexport function getBindings(doc: PxAnimatedSvgDocument): PxBinding[] | undefined {\n if (!doc) return undefined;\n return getAnimatorConfig(doc)?.bindings;\n}\n\n\n/** @public @advanced */\nexport function getChildren(doc: PxAnimatedSvgDocument): PxNode[] | undefined {\n return doc?.children;\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport type { KeysMatch, PxInfer, PxSchema, PxValidationContext, PxRemoveIndex } from '../schema/PxSchema';\nimport { implementsInterface, px } from '../schema/PxSchema';\n// Constants live in their own module so importing one does not pull the schema engine\n// in; re-exported here so this module's public surface is unchanged. See there.\nexport * from './PxAnimatorConstants';\nimport { getAnimatorConfig, INTERNAL_ATTRS, isPxDocument, PX_TRANSFORM_PART_KEYS, PX_TRIGGER_DEFAULTS, PxTimelineEngineSetting, PxCloneWithout, PxPathOverflow, PxGradientSpreadMethod, PxGradientType, PxLengthAdjust, PxLoopRepeatAt, PxLoopDirection, PxMaskType, PxTextPathMethod, PxTextPathSpacing, PxStrokeTrimSubPaths, PxUnits,\n // Wire enums named in review §2.7 — used as VALUES by the schemas below.\n PxAlongPathMode, PxFillMode, PxFinishAction, PxOutAction, PxPinAlign, PxPlaybackDirection,\n PxScrollAxis, PxScrollKind, PxScrollPhase, PxScrollSource, PxStartOn } from './PxAnimatorConstants';\nimport type { PxTimelineEngine, PxTransformPartKey } from './PxAnimatorConstants';\n// Version stamps are parsed by the reader's own parser (review §2.10) so the validator and the\n// reader can never disagree on what counts as a stamp. `PxWireVersion` imports only\n// `PxSchemaVersion`, so this edge creates no cycle.\nimport { parseWireVersion } from '../version/PxWireVersion';\n// The diagnostics channel's payload, named by `PxEngineCallbacks` below (review §5).\n// `PxDiagnostics` imports nothing, so this edge creates no cycle either.\nimport type { PxDiagnosticKind, PxDiagnosticsConfig } from '../playback/PxDiagnostics';\n\n// ============================================================================\n// EASING\n// ============================================================================\n\n/**\n * Easing function definition.\n * Can be a named reference to a predefined easing or a cubic-bezier array [x1, y1, x2, y2].\n *\n * @example \"ease-in\" | [0.68, -0.55, 0.265, 1.55]\n *\n * `string | [x1, y1, x2, y2]`\n * @public @advanced\n */\nexport const PxEasingOrRefSchema = px.union([\n px.string(),\n px.tuple([px.number(), px.number(), px.number(), px.number()] as const),\n]);\n\n/**\n * Easing function definition.\n * Can be a named reference to a predefined easing or a cubic-bezier array [x1, y1, x2, y2].\n *\n * @example \"ease-in\" | \"easeOut\" | [0.68, -0.55, 0.265, 1.55]\n */\nexport type PxEasingOrRef = PxInfer<typeof PxEasingOrRefSchema>;\n\n\n// ============================================================================\n// KEYFRAME\n// ============================================================================\n\n/**\n * A single animation keyframe defining the state at a specific point in time.\n *\n * THE WIRE FORM, and only that: `time` / `value` / `easing` / `tangentIn` / `tangentOut`.\n * Locked to `PxKeyframeSchema` by the `KeysMatch` assertion below, so this interface and the\n * validator cannot drift apart.\n *\n * The engines consume {@link _PxNormalizedKeyframe} instead — the short-field form\n * `normalizeKeyframes` produces, with easing refs resolved and values parsed. The two used to\n * be ONE interface carrying both spellings, which meant no key-set lock was possible here and\n * nothing in the types said which form a given function expected.\n */\nexport interface _PxKeyframe {\n\n /** Timestamp in milliseconds from animation start */\n time?: number;\n\n /** The value of the animated property at this keyframe */\n value?: any;\n\n /** Easing function applied to the interval from this keyframe to the next */\n easing?: PxEasingOrRef;\n\n /**\n * Outgoing spatial tangent `[dx, dy]` for motion-along-path interpolation\n * (translate animations only).\n *\n * Stored as a *delta relative to* this keyframe's translate position —\n * the cubic Bezier segment between this kf and the next is built from\n * `(P0=value, P1=value+tangentOut, P2=next.value+next.tangentIn, P3=next.value)`.\n *\n * Defined when the segment leaving this keyframe is curved.\n */\n tangentOut?: [number, number];\n\n /**\n * Incoming spatial tangent `[dx, dy]` for motion-along-path interpolation\n * (translate animations only). Delta relative to this keyframe's translate\n * position. See `tangentOut` for the segment construction.\n *\n * Defined when the segment arriving at this keyframe is curved.\n */\n tangentIn?: [number, number];\n\n}\n\n/**\n * Allowed shapes for a single keyframe `value` across the wire schema:\n * - `number` — scalar properties (rotate-degree, opacity, offset-distance, …)\n * - `Array<number>` — vector properties (translate `[x,y]`, scale `[sx,sy]`, stroke-dasharray, RGBA …)\n * - `string` — color (hex / `url(#…)` / named) and other string-valued props\n * - `PxTransformParts` — unified body `transform` parts record\n * `{translate, rotate, scale, origin}`\n * - `{ paths: Array<PxBezierPath> }` — animated SVG path `d` value\n * - `Array<PxGradientStop>` — gradient `stops` timeline (each kf value is\n * the FULL `[{offset, color}, …]` snapshot)\n *\n * Plugged into `PxKeyframeSchema.value` / `.v` — every keyframe value on the\n * wire is validated against this union. The inferred TS type of `PxKeyframe`\n * stays permissive (`value?: any` via the generic default) so the duck-typed\n * interpolator code in `PxDefinitions.ts` (`prevV?.paths`,\n * `Array.isArray(prevV)`, …) keeps working without per-shape narrowing.\n */\nexport type _PxKeyframeValue =\n | string\n | number\n | Array<number>\n | PxTransformParts\n | { pathData: string }\n | Array<_PxGradientStop>;\n\n// `string | number | Array<number> | PxTransformParts | { pathData: string }`\n//\n// `{ pathData: \"M…\" }` is the ONE form for animated path geometry: a single `d` string\n// (a compound shape is one string with several `M…` sub-paths). The Lottie-style\n// `{ paths: [{v,i,o,c}] }` array was retired 2026-09-11 — it lives on only as the\n// interpolator's INTERNAL shape (`normalizePathValue` in `PxDefinitions.ts`).\n//\n// `PxTransformPartsSchema` is declared later in this file — `px.lazy` defers the lookup\n// until validation time so the declarations stay in narrative order without a TDZ at load.\n/** @public @advanced */\nexport const PxKeyframeValueSchema = implementsInterface<_PxKeyframeValue>()(px.union([\n px.string(), // e.g. for colors\n px.number(),\n px.array(px.number()),\n // ORDER LAW: the key-discriminated object shape (`{pathData}`) comes BEFORE the\n // all-optional transform-parts record. In default (non-strict) mode that record accepts\n // ANY object (every key optional, unknown keys ignored), so listing it earlier made\n // Union.sanitize route `{pathData}` values into it and strip them to `{}` —\n // silent morph-data loss (repro: the editor's keyframeValueSanitize spec). Validity is\n // order-independent (`some()`); only sanitize routing depends on this order.\n px.object({ pathData: px.string() }),\n // Gradient `stops` timeline — each kf value is the full stops-array snapshot.\n px.lazy<Array<_PxGradientStop>>(() => px.array(PxGradientStopSchema), []),\n px.lazy<PxTransformParts>(() => PxTransformPartsSchema, {}),\n]));\n\n/** A single keyframe `value` — union of all wire-allowed shapes. */\nexport type PxKeyframeValue = PxInfer<typeof PxKeyframeValueSchema>;\n\n// `{ time?:number, t?:number, value?:PxKeyframeValue, v?:PxKeyframeValue,\n// easing?:Easing, e?:Easing, tangentOut?:[dx,dy], tangentIn?:[dx,dy] }`\n//\n// `value` / `v` validate against {@link PxKeyframeValueSchema} — malformed\n// keyframe values are now schema errors instead of passing as `px.any()`\n// (SCHEMA-DESIGN I-5). The `_PxKeyframe` interface keeps `value?: any` (and\n// `PxKeyframe<T = any>` its generic default) so the duck-typed interpolator\n// access in `PxDefinitions.ts` stays untyped-permissive at compile time.\n// LONG SPELLINGS ONLY (review §1.2/§6.1): the short aliases (`t`/`v`/`e`/`to`/`ti`)\n// were removed from the wire outright — one clear spelling, no mixing ambiguity.\n// They survive only as the internal normalized runtime view (see `_PxKeyframe`).\n/** @public @advanced */\nexport const PxKeyframeSchema = implementsInterface<_PxKeyframe>()(px.object({\n time: px.number().optional(),\n value: PxKeyframeValueSchema.optional(),\n easing: PxEasingOrRefSchema.optional(),\n tangentOut: px.tuple([px.number(), px.number()] as const).optional(),\n tangentIn: px.tuple([px.number(), px.number()] as const).optional(),\n // (`selected` — editor timeline-selection UI state — was REMOVED from the wire\n // (review §1.3): editor data lives under `meta`. The editor still carries it on\n // its internal COPY-PASTE payload, which never validates against this schema.)\n}));\n\n/**\n * THE RUNTIME FORM — what `normalizeKeyframes` hands the engines, and what the tree-level\n * materializers (`materializeInternalLoopsInTree` and everything after it in\n * `materializeAllInTree`) write back into the document.\n *\n * Short-named on purpose: these are read once per property per frame. `e` is a RESOLVED easing\n * (named refs already looked up in `definitions.easings`) and `v` is a PARSED value (colors as\n * RGBA arrays, path `d` normalized) — which is the substantive difference from the wire form,\n * not just the spelling. Deliberately NOT a wire shape: `validateDocument` rejects it, and a\n * materialized document is a runtime artefact that is never written to disk.\n */\nexport interface _PxNormalizedKeyframe {\n /** Time in ms from the animation start (the wire spells it `time`). */\n t?: number;\n /** The parsed value at this keyframe (the wire spells it `value`). */\n v?: any;\n /** The RESOLVED easing — never a name (the wire spells it `easing`). */\n e?: PxEasingOrRef;\n /** Incoming spatial tangent, same meaning as the wire's. */\n tangentIn?: [number, number];\n /** Outgoing spatial tangent, same meaning as the wire's. */\n tangentOut?: [number, number];\n}\n\n/** @internal */\nexport type PxNormalizedKeyframe = _PxNormalizedKeyframe;\n\n/**\n * Either spelling. For the handful of helpers that genuinely run on BOTH sides of\n * normalization — read them through the `kf*` accessors below rather than branching inline.\n * @internal\n */\nexport type PxAnyKeyframe = _PxKeyframe | _PxNormalizedKeyframe;\n\nconst anyKf = (kf: PxAnyKeyframe) => kf as _PxKeyframe & _PxNormalizedKeyframe;\n\n/** Time in ms, whichever spelling the keyframe is in. @internal */\nexport const keyframeTime = (kf: PxAnyKeyframe): number => anyKf(kf).time ?? anyKf(kf).t ?? 0;\n/** Value, whichever spelling. @internal */\nexport const keyframeValue = (kf: PxAnyKeyframe): any => anyKf(kf).value ?? anyKf(kf).v;\n/** Easing — resolved on a normalized keyframe, possibly a NAME on a wire one. @internal */\nexport const keyframeEasing = (kf: PxAnyKeyframe): PxEasingOrRef | undefined => anyKf(kf).easing ?? anyKf(kf).e;\n/** Incoming spatial tangent, whichever spelling. @internal */\nexport const keyframeTangentIn = (kf: PxAnyKeyframe): [number, number] | undefined => anyKf(kf).tangentIn;\n/** Outgoing spatial tangent, whichever spelling. @internal */\nexport const keyframeTangentOut = (kf: PxAnyKeyframe): [number, number] | undefined => anyKf(kf).tangentOut;\n\n/**\n * A single animation keyframe defining the state at a specific point in time.\n *\n * Generic over the keyframe `value` type for callers that know the per-property\n * value shape (e.g. `PxKeyframe<PxVec2>` in the effect appliers). Defaults to\n * `any`, matching the schema (`value` is stored as `px.any()` on the wire).\n * @public\n */\n// The WIRE type, generic over the value type. The engines use `PxNormalizedKeyframe`.\nexport type PxKeyframe<T = any> = Omit<_PxKeyframe, 'value'> & { value?: T };\n// Locks the interface to the schema at the default instantiation.\nconst _ck_PxKeyframe: KeysMatch<PxInfer<typeof PxKeyframeSchema>, _PxKeyframe> = true;\n\n/** {@link PxNormalizedKeyframe}, generic over the value type — the runtime counterpart. */\nexport type PxNormalizedKeyframeOf<T = any> = Omit<_PxNormalizedKeyframe, 'v'> & { v?: T };\n\n/**\n * A property animation whose keyframes are in the RUNTIME form.\n *\n * What `normalizeKeyframes` produces, what the engines consume — and what the EDITOR's in-memory\n * model is: its keyframe objects carry the short field names and serialize to the long wire ones\n * through `@serializable`, so the model implements this rather than the wire shape.\n * @internal\n */\nexport type PxNormalizedPropertyAnimation =\n Omit<_PxPropertyAnimation, 'keyframes'> & { keyframes?: Array<_PxNormalizedKeyframe> };\n\n\n// ============================================================================\n// LOOP\n// ============================================================================\n\n/**\n * Defines how a property's keyframe animation is extended beyond its defined keyframe range\n * by continuously repeating a chosen segment of the sequence.\n *\n * The repeated segment is a contiguous run of keyframe *intervals* (gaps between consecutive\n * keyframes). Which end of the sequence is repeated is controlled by `before`, and whether\n * each repetition plays in the same direction or alternates is controlled by `alternate`.\n *\n * **Relationship to `animator.iterations`**\n *\n * `PxLoop` and `animator.iterations` are independent mechanisms operating at different levels:\n *\n * - `PxLoop` is a **pre-processing step**: it expands the property's keyframe list to fill the\n * full `animator.duration` before any playback begins. The runtime sees a single, fully\n * expanded keyframe sequence — it has no knowledge of the loop.\n *\n * - `animator.iterations` repeats the **entire document timeline** (all properties, all\n * elements) as a unit, after the expanded keyframes are already in place.\n *\n * The two compose independently: a property with `loop: true` inside a document with\n * `iterations: \"infinite\"` will cycle its own segment within each document iteration, and\n * that iteration will itself repeat forever — loop-within-loop.\n */\nexport interface _PxLoop {\n\n /**\n * Number of keyframe intervals (gaps between consecutive keyframes) that form the repeating\n * segment.\n *\n * - `undefined` → the entire keyframe sequence is used as the loop segment.\n * - `N` → only the first `N` intervals (when `repeatAt: 'start'`) or the last `N`\n * intervals (when `repeatAt: 'end'`) are looped. Clamped to `[1, keyframes.length - 1]`.\n */\n segmentCount?: number;\n\n /**\n * Which end of the keyframe sequence the repetition fills — see {@link PxLoopRepeatAt}.\n * `'start'` repeats ahead of the first keyframe (intro loop); `'end'` (default)\n * repeats past the last (idle/outro loop).\n */\n repeatAt?: PxLoopRepeatAt;\n\n /**\n * How successive repetitions play — see {@link PxLoopDirection}.\n *\n * - `'normal'` (default) → **cycle**: every repetition replays the segment the same way round.\n * - `'alternate'` → **ping-pong**: repetitions alternate forward and backward\n * (even repetitions play forward, odd ones in reverse).\n */\n direction?: PxLoopDirection;\n}\n\n// `{ segmentCount?:number, repeatAt?:'start'|'end', direction?:'normal'|'alternate' }`\n/** @public @advanced */\nexport const PxLoopSchema = implementsInterface<_PxLoop>()(px.object({\n segmentCount: px.number().optional(),\n repeatAt: px.enum([PxLoopRepeatAt.start, PxLoopRepeatAt.end] as const).optional(),\n direction: px.enum([PxLoopDirection.normal, PxLoopDirection.alternate] as const).optional(),\n}));\n\n/**\n * Defines how a property's keyframe animation is extended beyond its defined keyframe range\n * by continuously repeating a chosen segment of the sequence.\n * @public\n */\nexport type PxLoop = PxInfer<typeof PxLoopSchema>;\nconst _ck_PxLoop: KeysMatch<PxLoop, _PxLoop> = true; // the key sets are identical\n\n\n// ============================================================================\n// PROPERTY ANIMATION\n// ============================================================================\n\n/**\n * Animation definition for a single CSS/SVG property.\n * Contains an array of keyframes that define how the property changes over time.\n */\nexport interface _PxPropertyAnimation {\n\n /**\n * Optional static / base value for the animated property.\n *\n * Two uses:\n * - structured static: `{value}` with no keyframes is the static form of\n * the universal animatable pattern (`PxAnimatable<T>`);\n * - base + keyframes: when both are present, `value` is the baseline the\n * animation starts from. For most properties keyframe values are\n * complete and `value` is just the pre-tick DOM baseline; slots with\n * patch semantics (the editor's extended-d `shape` effect) merge each\n * keyframe's partial value over this base.\n */\n value?: any;\n\n /** Array of keyframes defining the animation timeline */\n keyframes?: PxKeyframe[];\n\n /**\n * Optional loop configuration. When set, the keyframe sequence is expanded at pre-processing\n * time to fill the gap between the keyframe range and `animator.duration` by repeating a\n * chosen segment. `true` is shorthand for the default {@link PxLoop} (loop the last segment\n * after the final keyframe, cycling forward). See {@link PxLoop} for details.\n *\n * Note: this operates independently of `animator.iterations` — see {@link _PxLoop} for the\n * interaction between the two.\n */\n loop?: PxLoop | boolean;\n\n /**\n * Motion-along-path \"auto-orient\" flag. Only meaningful for translate\n * animations whose keyframes carry spatial tangents (`tangentIn` /\n * `tangentOut`): when true, the element rotates so its local X axis aligns\n * with the path tangent at the current position. The rotation is computed\n * from the cubic-Bezier derivative at the eased progress along the\n * arc-length-parametrised segment.\n */\n autoOrient?: boolean;\n\n /**\n * How a motion-along-path `transform` animation is RENDERED: `'sampled'` (default,\n * absent) — the path is pre-sampled into plain transform keyframes;\n * `'offsetPath'` — the browser drives it as a CSS Motion Path (`offset-path` /\n * `offset-distance`). Written by the editor, consumed by `materializeAllInTree`.\n */\n alongPathMode?: 'sampled' | 'offsetPath';\n}\n\n// `{ value?:KeyframeValue, keyframes?:Keyframe[], loop?:Loop|boolean, autoOrient?:bool, alongPathMode?:'sampled'|'offsetPath' }`\n// (the `kfs` alias was removed outright — review §1.2/§6.1: one spelling only)\n/** @public @advanced */\nexport const PxPropertyAnimationSchema = implementsInterface<_PxPropertyAnimation>()(px.object({\n value: PxKeyframeValueSchema.optional(),\n keyframes: px.array(PxKeyframeSchema).optional(),\n loop: px.union([PxLoopSchema, px.boolean()]).optional(),\n autoOrient: px.boolean().optional(),\n alongPathMode: px.enum([PxAlongPathMode.sampled, PxAlongPathMode.offsetPath] as const).optional(),\n}));\n\n/** Animation definition for a single CSS/SVG property. @public */\n// The runtime-VIEW type: its `keyframes` items are runtime-view PxKeyframes (they may\n// carry the internal normalized short fields), which the schema-inferred type cannot.\nexport type PxPropertyAnimation = _PxPropertyAnimation;\n// KeysMatch compares only the KEY SETS, so it still locks the schema to the interface even\n// though the two disagree about the VALUE type of `keyframes` (above). Worth having here in\n// particular: this is the object that carried the `kfs` alias until it was deleted.\nconst _ck_PxPropertyAnimation: KeysMatch<PxInfer<typeof PxPropertyAnimationSchema>, _PxPropertyAnimation> = true;\n\n\n/**\n * Record of transform parts forming a single transform `value`. Each present\n * key contributes one segment of the composed CSS transform string at render /\n * interpolation time, in the canonical order\n * `translate, translate(+origin), rotate, scale, translate(-origin)`.\n *\n * `origin` is meaningful only when `rotate` or `scale` is also present in the\n * same record — see \"When does origin belong inside a keyframe value?\" in\n * `file-format-remaining-design-issues2.md`.\n */\nexport interface _PxTransformParts {\n\n /** Translation offset `[x, y]` in user units. */\n translate?: [number, number];\n\n /** Rotation in degrees. */\n rotate?: number;\n\n /** Skew (skewX) in degrees, pivoting at `origin` — composed between `rotate`\n * and `scale` (matches Lottie's transform order). */\n skew?: number;\n\n /** Scale factor `[sx, sy]`. */\n scale?: [number, number];\n\n /**\n * Pivot for rotate / scale `[x, y]`. Only meaningful alongside `rotate` or\n * `scale` in the same record.\n */\n origin?: [number, number];\n}\n\n// `{ translate?:[x,y], rotate?:deg, skew?:deg, scale?:[sx,sy], origin?:[x,y] }`\n/** @public @advanced */\nexport const PxTransformPartsSchema = implementsInterface<_PxTransformParts>()(px.object({\n translate: px.tuple([px.number(), px.number()] as const).optional(),\n rotate: px.number().optional(),\n skew: px.number().optional(),\n scale: px.tuple([px.number(), px.number()] as const).optional(),\n origin: px.tuple([px.number(), px.number()] as const).optional(),\n}));\n\n/** Record of transform parts forming a single transform `value`. @public */\nexport type PxTransformParts = PxInfer<typeof PxTransformPartsSchema>;\nconst _ck_PxTransformParts: KeysMatch<PxTransformParts, _PxTransformParts> = true; // the key sets are identical\n\n/**\n * Unified `transform` slot value. Valid shapes:\n *\n * - **Bare parts record** — `{translate:[100,100], rotate:45, scale:[1.5,1.5],\n * origin:[25,25]}` — THE canonical lightweight static (SCHEMA-DESIGN §2/S1):\n * the authored parts verbatim, the exact record grammar animated keyframe\n * values use. Unambiguous because a body attr never carries animation — that\n * lives in the parallel `animate` channel (R2); the parts keys are disjoint\n * from the animation-wrapper keys.\n * - **SVG transform string** — `\"translate(125,125)rotate(45)…translate(-25,-25)\"`.\n * The pre-rendered/browser form (origin baked into a pivot sandwich) and the\n * foreign-SVG import path. Read forever.\n * - **Structured static** — `{value: PxTransformParts}`. Read-accepted legacy\n * spelling of the record.\n * - **Animated** — `{keyframes: [{time, value: PxTransformParts, …}, …]}`.\n * Legacy inline form (the editor writes the `animate` channel instead).\n *\n * Replaces the earlier convention of putting each animated transform part\n * under its own top-level attribute name (`translate`, `rotate`, `scale`,\n * `origin`).\n * @public @advanced\n */\nexport const PxTransformValueSchema = px.union([\n px.string(),\n PxTransformPartsSchema,\n px.object({ value: PxTransformPartsSchema }),\n PxPropertyAnimationSchema,\n]);\n\n/** Unified `transform` slot value: string | structured static | animated. @public */\nexport type PxTransformValue = PxInfer<typeof PxTransformValueSchema>;\n\n\n// ============================================================================\n// ANIMATION DEFINITION\n// ============================================================================\n\n/**\n * Complete animation definition containing one or more property animations.\n * Each key is a CSS/SVG property name (e.g., \"opacity\", \"translate\", \"fill\").\n *\n * @example\n * { \"opacity\": { keyframes: [...] }, \"translate\": { keyframes: [...] } }\n */\nexport interface _PxAnimationDefinition {\n [property: string]: PxPropertyAnimation;\n}\n\n// `Record<propName, PropertyAnimation>`\n/** @public @advanced */\nexport const PxAnimationDefinitionSchema = implementsInterface<_PxAnimationDefinition>()(\n px.record(PxPropertyAnimationSchema)\n);\n\n/**\n * Complete animation definition containing one or more property animations.\n * Each key is a CSS/SVG property name (e.g., \"opacity\", \"scale\", \"rotate\").\n * @public\n */\nexport type PxAnimationDefinition = PxInfer<typeof PxAnimationDefinitionSchema>;\n\n\n// ============================================================================\n// ELEMENT ANIMATION\n// ============================================================================\n\n/**\n * Element animation specification. Can be:\n * - A string referencing a named animation from `definitions.animations`\n * - An array of named references\n * - An inline `AnimationDefinition` object\n * - A mixed array of references and inline definitions\n *\n * @example\n * \"fadeIn\"\n * [\"fadeIn\", \"spin\"]\n * { opacity: { keyframes: [...] } }\n * [\"fadeIn\", { scale: { keyframes: [...] } }]\n */\nexport type _PxElementAnimation =\n | string\n | string[]\n | PxAnimationDefinition\n | (string | PxAnimationDefinition)[];\n\n// `string | Array<string|AnimationDefinition> | AnimationDefinition`\n/** @public @advanced */\nexport const PxElementAnimationSchema = implementsInterface<_PxElementAnimation>()(px.union([\n px.string(),\n px.array(px.union([px.string(), PxAnimationDefinitionSchema])),\n PxAnimationDefinitionSchema,\n]));\n\n/**\n * Element animation specification.\n * Can be a string reference, array of references, inline definition, or a mixed array.\n * @public\n */\nexport type PxElementAnimation = PxInfer<typeof PxElementAnimationSchema>;\n\n\n// ============================================================================\n// TRIGGER\n// ============================================================================\n\n/**\n * Defines when and how an animation should be triggered.\n */\nexport interface _PxTrigger {\n\n /** Event that starts the animation. Default `'load'` — a document is designed to play;\n * `'programmatic'` waits for `play()`. */\n startOn?: PxStartOn;\n\n /** Action to take when the trigger condition is no longer met (e.g., mouse leaves).\n * Default `'continue'`. */\n outAction?: PxOutAction;\n\n /** Percentage of element visibility required to trigger (0–1, default 0 = any pixel).\n * Only applies to scrollIntoView. */\n scrollIntoViewThreshold?: number;\n\n /** After a NATURAL finish: `'hold'` (default — keep the end state per `fill`) or `'reset'`\n * (snap back to the start). Named to pair with its sibling `outAction`, and NOT `onFinish`,\n * which is the CALLBACK on `PxEngineCallbacks` — a value key and a function key with\n * one name read badly side by side in a document literal or in JSX. */\n finishAction?: PxFinishAction;\n}\n\n// `{ startOn?:'load'|'mouseOver'|'click'|'scrollIntoView'|'programmatic', outAction?:..., scrollIntoViewThreshold?:number }`\n// An absent field means its PX_TRIGGER_DEFAULTS entry — the table every player resolves through.\n/** @public @advanced */\nexport const PxTriggerSchema = implementsInterface<_PxTrigger>()(px.object({\n startOn: px.enum([PxStartOn.load, PxStartOn.mouseOver, PxStartOn.click, PxStartOn.scrollIntoView, PxStartOn.programmatic] as const, PX_TRIGGER_DEFAULTS.startOn).optional(),\n outAction: px.enum([PxOutAction.continue, PxOutAction.pause, PxOutAction.reset, PxOutAction.reverse] as const, PX_TRIGGER_DEFAULTS.outAction).optional(),\n // What happens after a NATURAL finish — `'hold'` (default: keep the end state per\n // `fill`) or `'reset'` (snap back to the start state). Pairs with `outAction` (\"what\n // happens when the trigger condition ends\"); both end-of-life knobs now read alike.\n finishAction: px.enum([PxFinishAction.hold, PxFinishAction.reset] as const).optional(),\n scrollIntoViewThreshold: px.number().optional(),\n}));\n\n/** Defines when and how an animation should be triggered. @public */\nexport type PxTrigger = PxInfer<typeof PxTriggerSchema>;\nconst _ck_PxTrigger: KeysMatch<PxTrigger, _PxTrigger> = true; // the key sets are identical\n\n\n// ============================================================================\n// DEFS\n// ============================================================================\n\n/**\n * A single character's embedded outline (glyph-mode text). Coordinates and\n * advance are in the owning {@link _PxGlyphFont}'s `unitsPerEm` units, so the\n * player can render text without the original font. See svga.text.design.md.\n */\nexport interface _PxGlyph {\n /** Advance width, in the font's `unitsPerEm`. */\n width: number;\n /** Outline path data, in the font's `unitsPerEm` (empty for whitespace). Named\n * `pathData`, never `d`: `d` is SVG's name for the node ATTRIBUTE only, and every\n * structure of ours spells the concept out (review §2.4). */\n pathData: string;\n}\n\nexport const PxGlyphSchema = implementsInterface<_PxGlyph>()(px.object({\n width: px.number(),\n pathData: px.string(),\n}));\n\n/** @public */\nexport type PxGlyph = PxInfer<typeof PxGlyphSchema>;\nconst _ck_PxGlyph: KeysMatch<PxGlyph, _PxGlyph> = true; // the key sets are identical\n\n/**\n * The used glyphs of one font, keyed by character. Referenced by a text's\n * `font-family` (the key in {@link _PxDefs.fonts}).\n */\nexport interface _PxGlyphFont {\n /** CSS family name, e.g. \"Roboto\". */\n fontFamily: string;\n /** Font-style notation, e.g. \"\" | \"italic\" (review §5.2 — `style` would collide with the node's CSS `style`). */\n fontStyle: string;\n /** Ascent, in `unitsPerEm` units (baseline placement). */\n ascent: number;\n /** Units per em the glyph `width`/`pathData` are expressed in, e.g. 1000. */\n unitsPerEm: number;\n /** Outlines of the used characters, keyed by the character itself. */\n glyphs: { [char: string]: PxGlyph; };\n}\n\nexport const PxGlyphFontSchema = implementsInterface<_PxGlyphFont>()(px.object({\n fontFamily: px.string(),\n fontStyle: px.string(),\n ascent: px.number(),\n unitsPerEm: px.number(),\n glyphs: px.record(PxGlyphSchema),\n}));\n\n/** @public */\nexport type PxGlyphFont = PxInfer<typeof PxGlyphFontSchema>;\nconst _ck_PxGlyphFont: KeysMatch<PxGlyphFont, _PxGlyphFont> = true; // the key sets are identical\n\n/**\n * Reusable definitions library for easings, animations and fonts.\n * Defined once here, referenced by name on elements (or, for animations, from bindings).\n * (`styles` — named style presets — was removed on 2026-09-13, review 2.13: nothing wrote it.)\n */\nexport interface _PxDefs {\n\n /** Named cubic-bezier easing functions */\n easings?: { [name: string]: [number, number, number, number]; };\n\n /** Named animation definitions that can be referenced by elements */\n animations?: { [name: string]: PxAnimationDefinition; };\n\n /** Embedded fonts — per-font glyph outlines, keyed by the text's `font-family`.\n * Lets glyph-mode `<text>` render without an external font. */\n fonts?: { [fontName: string]: PxGlyphFont; };\n}\n\n// `{ easings?:Record<name,[x1,y1,x2,y2]>, animations?:Record<name,AnimationDefinition>, fonts?:Record<fontName,PxGlyphFont> }`\n/** @public @advanced */\nexport const PxDefinitionsSchema = implementsInterface<_PxDefs>()(px.object({\n easings: px.record(px.tuple([px.number(), px.number(), px.number(), px.number()] as const)).optional(),\n animations: px.record(PxAnimationDefinitionSchema).optional(),\n fonts: px.record(PxGlyphFontSchema).optional(),\n}));\n\n/** Reusable definitions library for easings, animations and fonts. @public */\nexport type PxDefinitions = PxInfer<typeof PxDefinitionsSchema>;\nconst _ck_PxDefs: KeysMatch<PxDefinitions, _PxDefs> = true; // the key sets are identical\n\n\n// ============================================================================\n// SCROLL TIMELINE\n// ============================================================================\n\n/**\n * A point on a scroll timeline's range, anchoring where the animation's 0%/100% sit.\n *\n * For `kind: 'view'`, `phase` names WHICH slice of the subject's journey across the\n * scrollport the point is measured in (CSS \"timeline range name\"), and `fraction` is the\n * 0..1 position within that slice. For `kind: 'scroll'` there are no phases — `phase` is\n * ignored and `fraction` is a fraction of the scroller's total scroll range.\n *\n * Structured on purpose (never a CSS string like `\"cover 0%\"`): atomic values, mechanical\n * translation to CSS `animation-range` / WAAPI `rangeStart/rangeEnd` where needed.\n */\nexport interface _PxScrollRangePoint {\n /** `view` kind only: the journey phase this point is anchored in. Default `'cover'`. */\n phase?: PxScrollPhase;\n /** 0..1 within the phase (or of the total scroll range for `kind: 'scroll'`). */\n fraction?: number;\n}\n\n/** @public @advanced */\nexport const PxScrollRangePointSchema = implementsInterface<_PxScrollRangePoint>()(px.object({\n phase: px.enum([PxScrollPhase.cover, PxScrollPhase.contain, PxScrollPhase.entry,\n PxScrollPhase.exit, PxScrollPhase.entryCrossing, PxScrollPhase.exitCrossing] as const).optional(),\n fraction: px.number().optional(),\n}));\n/** @public */\nexport type PxScrollRangePoint = PxInfer<typeof PxScrollRangePointSchema>;\nconst _ck_PxScrollRangePoint: KeysMatch<PxScrollRangePoint, _PxScrollRangePoint> = true; // the key sets are identical\n\n/**\n * Scroll-timeline configuration — consulted only when `timelineSource: 'scroll'`.\n * All fields optional; the defaults give \"scrub the whole animation as the SVG crosses\n * the viewport\" (`view` / `block` / full `cover` range).\n */\nexport interface _PxScroll {\n /** What progress measures. `'view'` (default): the SVG's own journey across the\n * scrollport (enter → leave). `'scroll'`: the scroll container's offset ratio,\n * regardless of where the SVG sits. */\n kind?: 'view' | 'scroll';\n\n /** Scroll axis. `block`/`inline` are writing-mode relative (block = vertical in\n * horizontal writing); `x`/`y` are physical. Default `'block'`. */\n axis?: 'block' | 'inline' | 'x' | 'y';\n\n /** `kind: 'scroll'` only: which scroller. `'nearest'` (default) = nearest scrollable\n * ancestor of the SVG; `'root'` = the document. (`view` always tracks the nearest\n * scrollport.) */\n source?: 'nearest' | 'root';\n\n /**\n * `kind: 'view'` only — WHICH ELEMENT'S journey is measured. Unset (default) = the\n * animation's own `<svg>`. The same indirection CSS gives via `view-timeline-name` +\n * `timeline-scope`, GSAP via `trigger`, Framer via \"Section in view\".\n *\n * - `'parent'` — the nearest ancestor that actually scrolls past: any `sticky`/`fixed`\n * ancestors are skipped and their container is used instead. This is what makes a\n * PINNED section work (a stuck element's rect stops moving, so measuring the graphic\n * itself would freeze); its `contain` phase is exactly the pinned stretch.\n * - `'scroller'` — the scroll container itself.\n * - anything else — a CSS selector, resolved against the host document.\n *\n * An unresolvable selector warns and falls back to the `<svg>` (never a silent freeze).\n */\n subject?: string;\n\n /**\n * MILLISECONDS of catch-up lag — the same idea as GSAP's `scrub: <seconds>`, in the unit\n * the rest of this schema uses (`duration`, `delay`). Unset/0 = the playhead is locked to\n * the scrollbar; above 0 the progress eases toward the scroll position instead of snapping\n * to it, which reads far smoother under momentum scrolling and trackpads.\n * Custom driver only — a browser-native `ScrollTimeline` has no equivalent, so setting\n * this forces the player's own measurement (the browser timeline is skipped).\n */\n smoothing?: number;\n\n /**\n * Hold the canvas still on screen while scrolling scrubs it — GSAP's `pin: true`, done with\n * `position: sticky` (which keeps the element's space in normal flow, so unlike GSAP's\n * `position: fixed` no spacer padding has to be injected into the host's layout).\n *\n * The player owns the DOM inside its own container, so this needs NO host CSS. Pair it with\n * `subject: 'parent'` for the complete scrollytelling pattern.\n */\n pin?: boolean;\n\n /**\n * `pin` only: WHERE in the scrollport the canvas is held — the alignment the sticky\n * offset is computed from. `top` (default) holds it against the top edge; `center`\n * and `bottom` need the canvas's own height, so the player measures it and keeps the\n * offset in sync on resize. `pinOffset` is added on top of whichever alignment is chosen.\n */\n pinAlign?: 'top' | 'center' | 'bottom';\n\n /** `pin` only: offset from the alignment position (see `pinAlign`), in px. Default 0.\n * The runtime-view twin of the wire `timeline.pin.offset` (review §2.6). */\n pinOffset?: number;\n\n /**\n * `pin` only: how much scroll travel the pin should last, in VIEWPORT HEIGHTS — the player\n * injects a wrapper of that height around the canvas to create it. Omit to pin inside\n * whatever tall section the host page already provides.\n */\n pinDistance?: number;\n\n /** The timeline slice mapped onto animation progress 0..1.\n * Default `{ start: {phase:'cover', fraction:0}, end: {phase:'cover', fraction:1} }`. */\n range?: {\n start?: _PxScrollRangePoint;\n end?: _PxScrollRangePoint;\n };\n}\n\n/** The `range` sub-object — named so consumers can derive its keys. @public @advanced */\nexport const PxScrollRangeSchema = px.object({\n start: PxScrollRangePointSchema.optional(),\n end: PxScrollRangePointSchema.optional(),\n});\n\n/** @public @advanced */\nexport const PxScrollSchema = implementsInterface<_PxScroll>()(px.object({\n kind: px.enum([PxScrollKind.view, PxScrollKind.scroll] as const).optional(),\n axis: px.enum([PxScrollAxis.block, PxScrollAxis.inline, PxScrollAxis.x, PxScrollAxis.y] as const).optional(),\n source: px.enum([PxScrollSource.nearest, PxScrollSource.root] as const).optional(),\n // Free-form: the two keywords `parent`/`scroller` plus any CSS selector.\n subject: px.string().optional(),\n smoothing: px.number().optional(),\n pin: px.boolean().optional(),\n pinAlign: px.enum([PxPinAlign.top, PxPinAlign.center, PxPinAlign.bottom] as const).optional(),\n pinOffset: px.number().optional(),\n pinDistance: px.number().optional(),\n range: PxScrollRangeSchema.optional(),\n}));\n/** @public */\nexport type PxScroll = PxInfer<typeof PxScrollSchema>;\nconst _ck_PxScroll: KeysMatch<PxScroll, _PxScroll> = true; // the key sets are identical\n\n\n// ============================================================================\n// TIMELINE — what advances the animation's progress (review §2.1)\n// ============================================================================\n//\n// `animator.timeline` is a discriminated object: `type?: 'time' | 'scroll' | 'view'`,\n// deliberately mirroring WAAPI's three timeline classes (DocumentTimeline /\n// ScrollTimeline / ViewTimeline) and CSS `animation-timeline: auto | scroll() | view()`.\n// Each mode carries ONLY its own parameters, so a key that is dead in the other mode is\n// structurally unwritable — the old flat spelling (`timelineSource` + sibling `scroll` +\n// clock knobs loose at the animator root) required design rules (D3/D4) to keep dead keys\n// out; readers accept BOTH spellings (see `flattenAnimatorTimeline`), writers emit only\n// this one.\n\n/** Pin parameters as one object — presence enables pinning (review §2.2; the flat runtime-view\n * spelling is `scroll.pin/pinAlign/pinOffset/pinDistance`). */\nexport interface _PxTimelinePin {\n /** Where the pinned canvas sits in the viewport. Default `'top'`. */\n align?: 'top' | 'center' | 'bottom';\n /** Offset from the alignment position, in px. Default 0. Named `offset`, not `top`: it is a\n * delta from whichever edge `align` picked, so `{ align: 'bottom', top: 20 }` read as a\n * contradiction (review §2.6). */\n offset?: number;\n /** How much scroll travel the pin lasts, in VIEWPORT HEIGHTS. Omit to pin inside\n * whatever tall section the host page already provides. */\n distance?: number;\n}\n\n/** @public @advanced */\nexport const PxTimelinePinSchema = implementsInterface<_PxTimelinePin>()(px.object({\n align: px.enum([PxPinAlign.top, PxPinAlign.center, PxPinAlign.bottom] as const).optional(),\n offset: px.number().optional(),\n distance: px.number().optional(),\n}));\n/** @public */\nexport type PxTimelinePin = PxInfer<typeof PxTimelinePinSchema>;\nconst _ck_PxTimelinePin: KeysMatch<PxTimelinePin, _PxTimelinePin> = true; // the key sets are identical\n\n// Time-driven — wall-clock playback: something STARTS it (trigger) and it has the\n// WAAPI playback dynamics. `type` is OPTIONAL: an absent `type` (or an absent\n// `timeline` altogether) means this one — the common case declares nothing.\n// `resetOnFinish` has no slot here: its successor is `trigger.finishAction: 'reset'`.\n/** `timeline.engine` — HOW the animated attributes get updated (every timeline type;\n * default `auto`). Not `mode`: an implementation preference, not a behavior switch. */\nconst PxTimelineEngineSchema = px.enum([PxTimelineEngineSetting.auto, PxTimelineEngineSetting.native, PxTimelineEngineSetting.js] as const).optional();\n\n/**\n * The time-driven timeline. Declared as an interface so a rename inside the schema below is a\n * COMPILE error rather than a silent wire-format change — `flattenAnimatorTimeline` reads the\n * timeline through `any`, so without this lock nothing else in the repo would notice.\n */\nexport interface _PxTimeTimeline {\n /** Optional: an absent `type` (or an absent `timeline`) already means this member. */\n type?: 'time';\n /** How the animated attributes get updated. Default `auto`. */\n engine?: PxTimelineEngineSetting;\n /** Target fps for the player's frame loop — a parameter of the `engine` chosen above, so it\n * sits beside it. Uncapped when absent; ignored by every engine except the frame loop. */\n frameRate?: number;\n /** §2.8: how long one pass takes, ms. */\n duration?: number;\n /** What starts it, and what happens when that condition ends. */\n trigger?: _PxTrigger;\n /** Wait before the first iteration, ms. Negative skips ahead. */\n delay?: number;\n /** Repeat count, or `'infinite'`. */\n iterations?: number | 'infinite';\n /** CSS `animation-fill-mode` — what shows outside the active time. NEVER spelled `fill`,\n * which is paint everywhere else in the format; the runtime view calls it `fill`. */\n fillMode?: 'forwards' | 'backwards' | 'both' | 'none';\n /** Forward, backward, or turning around each iteration. */\n direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';\n}\n\nconst PxTimeTimelineSchema = implementsInterface<_PxTimeTimeline>()(px.object({\n type: px.literal('time').optional(),\n engine: PxTimelineEngineSchema,\n frameRate: px.number().optional(),\n // §2.8: duration is a property of the TIMELINE — how long one pass takes.\n duration: px.number().optional(),\n trigger: PxTriggerSchema.optional(),\n delay: px.number().optional(),\n iterations: px.union([px.number(), px.literal('infinite')]).optional(),\n // `fillMode` on the wire (CSS `animation-fill-mode`; the runtime view calls it `fill`)\n // — never `fill`, which is paint everywhere else in the format.\n fillMode: px.enum([PxFillMode.forwards, PxFillMode.backwards, PxFillMode.both, PxFillMode.none] as const).optional(),\n direction: px.enum([PxPlaybackDirection.normal, PxPlaybackDirection.reverse, PxPlaybackDirection.alternate, PxPlaybackDirection.alternateReverse] as const).optional(),\n}));\nconst _ck_PxTimeTimeline: KeysMatch<PxInfer<typeof PxTimeTimelineSchema>, _PxTimeTimeline> = true;\n\n// Scroll-driven modes — progress scrubbed from scroll position; nothing starts or\n// finishes it, so none of the clock knobs exist here. `'scroll'` tracks a scroller's\n// scroll offset, `'view'` tracks the subject's visibility through the viewport —\n// exactly WAAPI ScrollTimeline vs ViewTimeline (the old nested `scroll.kind` dissolved\n// into this discriminant). `mode` (shared by every timeline type) says who runs the\n// animation; `pin` is boolean-or-object (§2.2).\nconst scrollishTimelineShape = {\n // §2.8: duration is a property of the TIMELINE — under scrubbing it is the keyframe\n // span the scroll range maps onto.\n duration: px.number().optional(),\n // Finite repeat count IS meaningful when scrubbing — the scroll range maps onto\n // duration × iterations (rule D4; `'infinite'` cannot map to a range, so no literal here).\n iterations: px.number().optional(),\n engine: PxTimelineEngineSchema,\n frameRate: px.number().optional(),\n axis: px.enum([PxScrollAxis.block, PxScrollAxis.inline, PxScrollAxis.x, PxScrollAxis.y] as const).optional(),\n source: px.enum([PxScrollSource.nearest, PxScrollSource.root] as const).optional(),\n subject: px.string().optional(), // 'parent' | 'scroller' | any CSS selector\n smoothing: px.number().optional(), // ms\n pin: px.union([px.boolean(), PxTimelinePinSchema]).optional(),\n range: PxScrollRangeSchema.optional(),\n};\n/** The keys both scroll-driven members carry. Locked the same way as the time member. */\nexport interface _PxScrollishTimelineShape {\n /** §2.8: under scrubbing, the keyframe span the scroll range maps onto. */\n duration?: number;\n /** Finite only — `'infinite'` cannot map onto a range (rule D4). */\n iterations?: number;\n /** How the animated attributes get updated. Default `auto`. */\n engine?: PxTimelineEngineSetting;\n /** Target fps for the player's frame loop (shared with the time member). */\n frameRate?: number;\n /** `block`/`inline` are writing-mode relative; `x`/`y` are physical. Default `'block'`. */\n axis?: 'block' | 'inline' | 'x' | 'y';\n /** `'nearest'` (default) scrollable ancestor, or `'root'`, the document. */\n source?: 'nearest' | 'root';\n /** `'parent'` · `'scroller'` · any CSS selector. */\n subject?: string;\n /** How long the playhead takes to catch up with the scrollbar, ms. */\n smoothing?: number;\n /** `true` to pin with defaults, or the parameters (review §2.2). */\n pin?: boolean | _PxTimelinePin;\n /** The slice of the timeline mapped onto progress 0..1. */\n range?: { start?: _PxScrollRangePoint; end?: _PxScrollRangePoint };\n}\n\n/** `interface X extends Y { type: 'scroll' }` — spelled as an intersection so the key-set\n * check below compares exactly the discriminant plus the shared shape. */\nexport type _PxScrollTimeline = _PxScrollishTimelineShape & { type: 'scroll' };\nexport type _PxViewTimeline = _PxScrollishTimelineShape & { type: 'view' };\n\nconst PxScrollTimelineSchema = implementsInterface<_PxScrollTimeline>()(\n px.object({ type: px.literal('scroll'), ...scrollishTimelineShape }));\nconst PxViewTimelineSchema = implementsInterface<_PxViewTimeline>()(\n px.object({ type: px.literal('view'), ...scrollishTimelineShape }));\nconst _ck_PxScrollTimeline: KeysMatch<PxInfer<typeof PxScrollTimelineSchema>, _PxScrollTimeline> = true;\nconst _ck_PxViewTimeline: KeysMatch<PxInfer<typeof PxViewTimelineSchema>, _PxViewTimeline> = true;\n\n/** @public @advanced */\nexport const PxTimelineSchema = px.discriminatedUnion('type', [\n PxTimeTimelineSchema, // first = the member an absent `type` selects\n PxScrollTimelineSchema,\n PxViewTimelineSchema,\n]);\n/** @public */\nexport type PxTimeline = PxInfer<typeof PxTimelineSchema>;\n\n\n// ============================================================================\n// ANIMATOR CONFIG\n// ============================================================================\n\n/**\n * Global animation configuration that applies to all animations in the document.\n * Defines timing, playback behavior, and rendering strategy.\n */\nexport interface _PxAnimatorConfig {\n\n /** RUNTIME VIEW ONLY (not wire — the wire spells it `timeline.engine`, on every\n * timeline type; same word both sides). How the animated attributes get updated;\n * see {@link PxTimelineEngineSetting}. */\n engine?: PxTimelineEngineSetting;\n\n /** RUNTIME VIEW ONLY (not wire — §2.8: the wire spells it `timeline.duration`).\n * Total animation duration in milliseconds. */\n duration?: number;\n\n /** Delay before animation starts in milliseconds */\n delay?: number;\n\n /**\n * Number of times to repeat the entire document timeline. Use `\"infinite\"` for endless loop.\n *\n * This repeats **all properties across all elements** as a unit. It is independent of\n * per-property `loop` configuration: if a property uses `loop`, its keyframes are already\n * expanded to fill `duration` before `iterations` takes effect — the two do not interfere,\n * but they do compose (a looping property inside an infinitely iterating document loops\n * within each iteration).\n */\n iterations?: number | \"infinite\";\n\n /** After a natural finish, snap the document back to its start state (same\n * mechanics as the trigger `reset` out-action). Off by default — the animation\n * holds its end state per `fill`. */\n resetOnFinish?: boolean;\n\n /**\n * Defines which values are applied before/after the active animation period\n * (maps directly to the Web Animations API `fill` option).\n * Defaults to `'forwards'` when not set so that elements hold their final\n * state after the animation ends — consistent with Lottie and other animation\n * runtimes. Without this default, seeking to the last frame would cause\n * elements to revert to their pre-animation state.\n */\n fill?: PxFillMode;\n\n /** Direction of animation playback */\n direction?: PxPlaybackDirection;\n\n /** RUNTIME VIEW ONLY — the wire spells it `timeline.frameRate`. Target frame rate for the\n * player's frame loop; ignored by WAAPI, React Native and the pre-rendered CSS export. */\n frameRate?: number;\n\n /** Trigger configuration for when animation should start */\n trigger?: PxTrigger;\n\n /** Named easings, animations and embedded fonts — referenced by elements and bindings */\n definitions?: PxDefinitions;\n\n /**\n * The bind-by-id document (a pre-rendered SVG + JS export, no `children`): the elements\n * already exist as markup, so instead of carrying them again the document lists WHICH\n * element plays WHICH named animations — see {@link _PxBinding}. Written by the editor's\n * exporter only; a self-contained document keeps its keyframes on the nodes (`node.animate`).\n *\n * @example [{ target: \"#_px_3cnuvau3\", animateWith: [\"a0\"] }]\n */\n bindings?: Array<PxBinding>;\n\n /**\n * RUNTIME VIEW ONLY (not wire) — what ADVANCES the animation. `'time'` (default)\n * is the wall clock; `'scroll'` is scroll-linked playback (\"scrubbing\"), matching\n * CSS scroll-driven animations.\n *\n * The wire spells this as `timeline.type` ('time' — or absent — vs 'scroll'/'view');\n * `flattenAnimatorTimeline` folds it into this field for the engines.\n * Distinct from `trigger.startOn`, which says what STARTS the animation: one\n * names the beginning, this one names what moves the playhead afterwards.\n * Design: app `svgeditor/animation/scroll-timeline.design.md`.\n */\n timelineSource?: string;\n\n /** RUNTIME VIEW ONLY (not wire) — scroll-timeline parameters, only consulted when\n * `timelineSource: 'scroll'`. The wire spells them inside `timeline`. */\n scroll?: PxScroll;\n\n /** What advances the animation — THE wire spelling (review §2.1): a discriminated\n * `{ type?: 'time' | 'scroll' | 'view', … }` object mirroring WAAPI's timeline\n * classes. Carries the playback dynamics (`trigger`/`delay`/`iterations`/`fillMode`/\n * `direction` for time; scroll geometry for scroll/view); the flat fields above\n * are the internal runtime view `flattenAnimatorTimeline` produces from it. */\n timeline?: PxTimeline;\n\n /** Debug helper: exposes the animator instance as `window[debugGlobalName]`. */\n debugGlobalName?: string;\n\n /**\n * WIRE FORMAT VERSION, `\"a.b.c\"` — the layering, not a build number:\n * `a.b` the PLAYER schema. A reader at `a.b` reads any file at `a.[b' <= b]`.\n * `c` the EDITOR's extension on top of that player schema (`meta.*`). The player\n * ignores it entirely; it is scoped to `(a,b)` and restarts when `b` moves.\n *\n * A mismatch on its own means NOTHING and must never warn: a bump says the schema gained\n * something, not that this document uses it. The number is consulted only when a\n * conversion needs it, or when unknown content was actually met — where it turns\n * \"something is wrong\" into \"written for 1.5, this build reads 1.1; update the player\".\n *\n * ABSENT means unknown, never \"oldest\": no version is assumed and no migration is guessed.\n */\n version?: string;\n}\n\n// ============================================================================\n// BINDINGS — the bind-by-id document (a pre-rendered SVG + JS export)\n// ============================================================================\n\n/**\n * One binding: WHICH element (`target`, `#id`-spelled like every element reference) plays\n * WHICH named animations (`animateWith` — names into `definitions.animations`, applied in\n * order, always an array).\n *\n * `…With` is the format's naming convention for \"by name, from `definitions`\" (review 2.12):\n * a bare key holds the thing itself (`node.animate` holds keyframes); `<key>With` holds an\n * ARRAY of names of the same concept (`animateWith` → `definitions.animations`). Two kinds of\n * reference, two spellings — `source` / `target` point at ELEMENTS (`#id`), `…With` points at\n * DEFINITIONS. Nothing else uses the convention yet; it is written down here so the next\n * key that refers to a definition by name spells it the same way.\n */\nexport interface _PxBinding {\n /** The element to animate — `'#id'`. */\n target: string;\n /** Names into `definitions.animations`, applied in order. */\n animateWith: Array<string>;\n}\n\n/** @public @advanced */\nexport const PxBindingSchema = implementsInterface<_PxBinding>()(px.object({\n target: px.string(),\n animateWith: px.array(px.string()),\n}));\n\n/** @public */\nexport type PxBinding = PxInfer<typeof PxBindingSchema>;\nconst _ck_PxBinding: KeysMatch<PxBinding, _PxBinding> = true; // the key sets are identical\n\n/**\n * RUNTIME VIEW ONLY (not wire) — a binding once `normalizeBindings` has resolved it: the\n * bare DOM id and the merged, normalized animation. A self-contained document yields the same\n * shape from every animated node, so the engines never see which kind of document they play.\n * @internal\n */\nexport interface PxNormalizedBinding {\n id: string;\n animate: PxAnimationDefinition;\n}\n\n// The WIRE format (review §2.1): playback dynamics live only inside `timeline` —\n// the flat spelling (`trigger`/`delay`/`iterations`/`fill`/`direction`/`resetOnFinish`/\n// `timelineSource`/`scroll`) is NOT part of the format. It exists only as the internal\n// runtime VIEW (`_PxAnimatorConfig`) that `flattenAnimatorTimeline` produces for the engines.\n/** @public @advanced */\nexport const PxAnimatorConfigSchema = implementsInterface<_PxAnimatorConfig>()(px.object({\n // (`mode`, `duration` and `frameRate` live INSIDE `timeline` on the wire — §2.8; they exist\n // at this level only on the runtime view, like the rest of the playback dynamics.)\n // THE spelling of \"what advances progress\" — clock / scroll / view (review §2.1).\n timeline: PxTimelineSchema.optional(),\n definitions: PxDefinitionsSchema.optional(),\n bindings: px.array(PxBindingSchema).optional(),\n debugGlobalName: px.string().optional(),\n // Declared HERE because this is a closed object: an undeclared key would be stripped by\n // `sanitize` and flagged by strict validation on our own files.\n version: px.string().optional(),\n}));\n\n/**\n * Global animation configuration that applies to all animations in the document.\n * Defines timing, playback behavior, and rendering strategy.\n *\n * This is the runtime VIEW type: the wire carries the playback dynamics nested in\n * `timeline` (see `PxAnimatorConfigSchema`), and `flattenAnimatorTimeline` folds them\n * into the flat fields the engines consume — so the type is a superset of the wire.\n * @public\n */\nexport type PxAnimatorConfig = _PxAnimatorConfig;\n\n\n\n// ============================================================================\n// NODE\n// ============================================================================\n\n/**\n * Per-attribute value shape on the element body. A property key carries either:\n * - a primitive (string/number) — static SVG attribute\n * - a number array — static number-LIST attribute (`strokeDasharray: [16, 16]`);\n * the canonical static form for list attrs (the \"5,5\" string form is also\n * accepted). Raw arrays are unambiguous — only plain OBJECTS need the\n * `{value}` wrapper.\n * - a `{value: …}` object — structured static parametric source (record-shaped\n * static value, used by attributes whose static representation is itself a\n * record — notably `transform: {value: PxTransformParts}`)\n * - a `{keyframes}` object — inline property animation\n *\n * The unified rule (primitive/array | `{value}` | `{keyframes}`) applies across\n * the format. For most attributes the `{value}` form is rarely used on the body\n * (a primitive suffices for static); for `transform` it is the canonical\n * structured-static shape. See `PxTransformValueSchema`.\n */\n/**\n * Value of an open (undeclared) attribute key on a node — i.e. a BODY attribute.\n *\n * STATIC ONLY (R2/J3): a body attr never carries its own animation. Animation goes\n * in the parallel `animate` channel, keyed by attribute name — that is what keeps a\n * document degradable to valid static SVG. `PxPropertyAnimationSchema` used to be a\n * member here, which made an inline `\"opacity\": {keyframes:[…]}` schema-LEGAL even\n * though nothing writes it and nothing reads it; worse, being an all-optional object\n * schema it was ALSO what (accidentally) validated the transform parts record. The\n * parts record is now declared explicitly, so the two are no longer conflated.\n * @public @advanced\n */\nexport const PxAttrValueSchema = px.union([\n px.string(),\n px.number(),\n px.array(px.number()),\n // Structured static — `{value: …}` (read-accepted transitional spelling, S1).\n // `defined`, not `any`: the KEY's presence is what identifies this branch (V6).\n px.object({ value: px.defined() }),\n // Bare transform parts record — the canonical static `transform` on the wire (T2).\n PxTransformPartsSchema,\n]);\n\n/** Per-attribute value: primitive/number-array for static, `{value}` for structured\n * static, or a bare transform parts record. NEVER an animation — see `animate` (R2). * @public\n */\nexport type PxAttrValue = string | number | Array<number> | { value: any } | PxTransformParts;\n\n\n/**\n * Base interface for all SVG elements.\n * Named properties take precedence over the index signature when accessed.\n */\nexport interface _PxNode {\n\n /** SVG element type (e.g., \"circle\", \"rect\", \"path\", \"g\") */\n type: string;\n\n /** A REAL `type` attribute, for the elements that have one (`<feTurbulence\n * type=\"fractalNoise\">`, `<feFuncR type=\"table\">`) — `type` itself is the tag name.\n * The renderer turns this back into the attribute. */\n domType?: string;\n\n /** Text content of a `<text>` / `<tspan>` — the one text-content key (the DOM property name). */\n textContent?: string;\n\n /** Child elements (for container elements like <g>) */\n children?: PxNode[];\n\n /** Meta informaion about this element */\n meta?: any;\n\n /**\n * Player-effects bucket (transformation/repeater/maskedBy/strokeTrim/retime/ref)\n * emitted by the Editor's lightweight design format. `materializeNodeEffects`\n * materializes and removes these before any other normalization, so the\n * Player never observes a non-empty `effects` after entry-point processing.\n *\n * Typed against `PxEffectsSchema` (closed object — strict-mode validation\n * flags unknown effect keys). Adding a new effect requires extending the\n * `_PxEffects` interface AND the schema in lockstep.\n */\n effects?: PxEffects;\n\n /**\n * In-place property animations for this element. Same shape as the\n * `node.animate` values: string ref, array of refs, inline\n * definition (`{propName: PxPropertyAnimation}`), or mixed array.\n * The static initial value of an animated property is still carried as a\n * plain attribute on the body.\n */\n animate?: PxElementAnimation;\n\n /**\n * Inline style declarations for this element — camelCase CSS property names, exactly like\n * React's `style` prop (`whiteSpace`, `pointerEvents`, `mixBlendMode`) → value. The web\n * player writes them to `element.style`, React Native applies them as props; an explicit\n * attribute on the node wins. An OBJECT only: no CSS text, no preset names\n * (`definitions.styles` was removed — review 2.13).\n */\n style?: Record<string, string | number>;\n\n /**\n * Every other key is an SVG attribute under its camelCase DOM name — the spelling React\n * uses (`strokeWidth`, `fontSize`, `viewBox`, `clipPath`), which is what the editor writes.\n * The player renders it to the standard attribute (`stroke-width`) and accepts that kebab\n * spelling on the way in. A value is either a primitive (static) or a PxPropertyAnimation\n * (in-place animation `{ keyframes: [...] }`).\n */\n [camelCaseDomKey: string]: any;\n}\n\n// ============================================================================\n// PLAYER-EFFECTS BUCKET — INTERFACES + SCHEMAS (linked via `implementsInterface`)\n// ============================================================================\n//\n// Schemas for the `node.effects` payload emitted by the Editor's lightweight\n// design format. `materializeNodeEffects` (in `effects/PlayerEffectsUtil.ts`)\n// materializes and removes these before any other normalization, so the Player\n// never observes a non-empty `effects` after entry-point processing.\n//\n// Each effect is declared as a `_Px*` interface, paired with a `Px*Schema`\n// wrapped in `implementsInterface<_Px*>()(…)`. The runtime schema and the\n// compile-time interface drift together: a new field added to either without\n// matching the other is a TS error. `KeysMatch` asserts key-set equality so\n// renames are caught too. This is the same pattern used by `PxKeyframe`,\n// `PxLoop`, etc. earlier in this file.\n//\n// `effects/types.ts` re-exports these types so the applier internals\n// (`effects/*.ts`) can still `import from './types'` unchanged.\n\n/** Fixed-length 2-number tuple. `[x, y]` for positions, `[sx, sy]` for scale, …. @public */\nexport type PxVec2 = [number, number];\n\n/**\n * Animatable wire value — the ONE grammar for every animatable slot:\n *\n * T — raw static (non-object T)\n * { value: T } — structured static\n * PxPropertyAnimation — animated: `{value?, keyframes, loop?, autoOrient?}`\n *\n * The animated form IS `PxPropertyAnimation` — the exact object `node.animate`\n * channels use — so effect slots and node attributes share one schema, one\n * reader (`effects/transformParts.readAnimatable`) and one loop-materialization\n * path. `value` inside the animated form is the optional static baseline (see\n * `_PxPropertyAnimation.value`).\n *\n * Generic over the per-kf value type `T` for compile-time narrowing of the\n * static / `{value}` forms. The animated form uses the lib's non-generic\n * `PxKeyframe` (whose `value` is `any`) — kf values are read with care in the\n * applier (the visualModel walker / `interpParts` know per-property shapes).\n * @internal\n */\nexport type PxAnimatable<T> = T | { value: T } | _PxPropertyAnimation;\n\n// ORDER LAW (same medicine as PxKeyframeValueSchema): in every animatable union the\n// PropertyAnimation member comes BEFORE the bare `{value}` wrapper. Union.sanitize takes\n// the first member that validates, and default-mode object validation tolerates unknown\n// keys — wrapper-first would route `{value, keyframes}` to the wrapper and silently strip\n// the keyframes. A `{value}`-only static hitting PropertyAnimation first loses nothing\n// (it declares `value` too). Validity is order-independent; only repair cares.\n\n// PxAnimatable<number> — static number OR `{value}` static OR PxPropertyAnimation.\nconst PxAnimatableNumberSchema = px.union([\n px.number(),\n PxPropertyAnimationSchema,\n px.object({ value: px.number() }),\n]);\n\n// PxAnimatable<PxVec2> — static `[x,y]` OR `{value:[x,y]}` OR PxPropertyAnimation.\n// `as const` on the tuples is REQUIRED for TS to infer `[number, number]` (a\n// fixed-length tuple = `PxVec2`) instead of the looser `number[]`.\nconst PxAnimatableVec2Schema = px.union([\n px.tuple([px.number(), px.number()] as const),\n PxPropertyAnimationSchema,\n px.object({ value: px.tuple([px.number(), px.number()] as const) }),\n]);\n\n// PxAnimatable<string> — static `\"M…\"` OR `{value:\"M…\"}` OR PxPropertyAnimation.\nconst PxAnimatableStringSchema = px.union([\n px.string(),\n PxPropertyAnimationSchema,\n px.object({ value: px.string() }),\n]);\n\n// ── CHANNEL vs CONFIG (V2) — the split is declared in the SOURCE, twice over ──\n// An effect slot is one of exactly two kinds, and both declarations must agree:\n// channel (samplable per frame) → interface `PxAnimatable<T>` + a named\n// `PxAnimatable*Schema` in the schema\n// static config (read once) → the bare type + a bare `px.*()` slot\n// Never hand-inline the `[T, {value:T}, PxPropertyAnimation]` union at a slot:\n// the NAME is what makes the split machine-readable. Editor side mirrors this\n// with `isAnimatable: true` on the value's config.\n\n\n/** Per-part editor transform (`transformBy` effect). All parts optional and animatable. */\nexport interface _PxTransformByEffect {\n translate?: PxAnimatable<PxVec2>;\n rotate?: PxAnimatable<number>;\n scale?: PxAnimatable<PxVec2>;\n /** Skew (skewX) in degrees — a NUMBER (matches the editor's scalar skew part). */\n skew?: PxAnimatable<number>;\n origin?: PxAnimatable<PxVec2>;\n}\n/** @public @advanced */\nexport const PxTransformByEffectSchema = implementsInterface<_PxTransformByEffect>()(px.object({\n translate: PxAnimatableVec2Schema.optional(),\n rotate: PxAnimatableNumberSchema.optional(),\n scale: PxAnimatableVec2Schema.optional(),\n skew: PxAnimatableNumberSchema.optional(),\n origin: PxAnimatableVec2Schema.optional(),\n}));\n/** @public */\nexport type PxTransformByEffect = PxInfer<typeof PxTransformByEffectSchema>;\nconst _ck_PxTransformByEffect: KeysMatch<PxTransformByEffect, _PxTransformByEffect> = true;\n\n\n/** Per-copy repeater offsets. Each part is animatable; per-copy values scale\n * with the copy index `i` (translate/rotate/skew × i; scale per-axis `v^i`).\n * Static repeater values pass through as a structured `transform: {value:…}` on\n * the per-copy wrapper; animated values are emitted as `animate.transform.keyframes`\n * with each kf value scaled by `i`. See `effects/repeaterEffect.ts`.\n *\n * NAMING — why `repeater`, NOT `repeat` (SCHEMA-DESIGN R5 / issues N6): this\n * effect repeats in SPACE (N copies, each with a compounding per-copy delta), but\n * in an ANIMATION format a bare `repeat` reads as TIME — and this format has real\n * time-repetition concepts for it to be confused with: `animator.iterations`,\n * per-property `loop {segmentCount, alternate}`, and SVG/SMIL's own\n * `repeatCount`/`repeatDur`. The agent noun keeps it unambiguously spatial, and\n * matches the term the audience already knows (After Effects \"Repeater\",\n * Lottie shape item `rp`). Same principle as `maskedBy` over `mask`: prefer the\n * form that preserves the right MEANING over the grammatically uniform one. */\nexport interface _PxRepeaterEffect {\n copies?: number;\n translate?: PxAnimatable<PxVec2>;\n rotate?: PxAnimatable<number>;\n /** Per-copy skew (skewX) increment in degrees — copy `i` is skewed by `skew × i`. */\n skew?: PxAnimatable<number>;\n scale?: PxAnimatable<PxVec2>; // per-copy FACTOR (0.85 = 85% per copy), like every other scale\n origin?: PxAnimatable<PxVec2>;\n}\n/** @public @advanced */\nexport const PxRepeaterEffectSchema = implementsInterface<_PxRepeaterEffect>()(px.object({\n // STATIC config, not a channel (V2/SCHEMA-DESIGN R5): the copy COUNT is read\n // once at expansion time and never sampled — plain number, no `keyframes`.\n copies: px.number().optional(),\n translate: PxAnimatableVec2Schema.optional(),\n rotate: PxAnimatableNumberSchema.optional(),\n skew: PxAnimatableNumberSchema.optional(),\n scale: PxAnimatableVec2Schema.optional(),\n origin: PxAnimatableVec2Schema.optional(),\n}));\n/** @public */\nexport type PxRepeaterEffect = PxInfer<typeof PxRepeaterEffectSchema>;\nconst _ck_PxRepeaterEffect: KeysMatch<PxRepeaterEffect, _PxRepeaterEffect> = true;\n\n\n/** Mask source ref + standard `<mask>` attributes.\n * `source` is `#id` (canonical ref spelling, SCHEMA-DESIGN §4 E-5); bare `id` is legacy, read-only.\n * `start`/`size` are the `<mask>` viewport — its `x`/`y` and `width`/`height` in\n * `maskUnits` space. Absent = SVG's implicit mask region (−10% … 120% of the\n * bounding box), which is also the editor's default — so they only appear when a\n * document (typically an imported SVG) carries explicit mask bounds. */\nexport interface _PxMaskedByEffect {\n source?: string;\n maskType?: string;\n maskUnits?: string;\n maskContentUnits?: string;\n // Mask viewport in `maskUnits` space — the SVG `<mask>` attrs verbatim (B5).\n // NOT `start`/`size` pairs: those were the EDITOR's model FIELD names, never a\n // wire spelling — the editor has always written these four scalars, so the old\n // pair declaration meant the player silently dropped every non-default viewport.\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n}\n/** @public @advanced */\nexport const PxMaskedByEffectSchema = implementsInterface<_PxMaskedByEffect>()(px.object({\n source: px.string().optional(),\n maskType: px.enum([PxMaskType.luminance, PxMaskType.alpha] as const).optional(),\n maskUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox] as const).optional(),\n maskContentUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox] as const).optional(),\n x: px.number().optional(),\n y: px.number().optional(),\n width: px.number().optional(),\n height: px.number().optional(),\n}));\n/** @public */\nexport type PxMaskedByEffect = PxInfer<typeof PxMaskedByEffectSchema>;\nconst _ck_PxMaskedByEffect: KeysMatch<PxMaskedByEffect, _PxMaskedByEffect> = true;\n\n\n/**\n * Clip-path effect — clips the host element to a vector path. `pathData` is a standard\n * animatable slot (same grammar as the body `d` ATTRIBUTE, which keeps SVG's own name):\n * static = plain SVG path-data string (one or more subpaths); animated = `{keyframes}`\n * whose values are `{pathData:\"M…\"}`.\n * At apply time an animated slot lands on the generated `<path>`'s `animate.d`, so the\n * player's frame loop rewrites the clip path's `d` attribute per frame. `clip-path`\n * is a live reference, so the browser re-clips each frame (unlike `<marker>` —\n * verified across SMIL/CSS/JS/WAAPI).\n *\n * At apply time the effect generates a `<clipPath><path d/></clipPath>` def and sets\n * `clip-path=\"url(#auto-id)\"` on the host (materializer pattern, like `maskedBy` /\n * gradient). See `effects/clipPathEffect.ts`.\n */\nexport interface _PxClipPathEffect {\n pathData?: PxAnimatable<string>;\n}\n/** @public @advanced */\nexport const PxClipPathEffectSchema = implementsInterface<_PxClipPathEffect>()(px.object({\n pathData: PxAnimatableStringSchema.optional(),\n}));\nexport type PxClipPathEffect = PxInfer<typeof PxClipPathEffectSchema>;\nconst _ck_PxClipPathEffect: KeysMatch<PxClipPathEffect, _PxClipPathEffect> = true;\n\n\n/**\n * Stroke-trim effect. `range[0..1]` is the visible fraction of the STROKE; `offset`\n * shifts the visible window along the path (also a fraction). Both are animatable.\n * `subPaths` says what that fraction is measured over: `separate` (default) trims\n * each sub-path against its own length; `combined` chains all descendant sub-path\n * lengths into one virtual path (\"Trim All As One\") so the window slides across\n * siblings — see `effects/strokeTrimEffect.ts`.\n *\n * NAME — renamed from `trimPath` (2026-08, hard rename, no legacy alias): this\n * trims the STROKE only. It emits `stroke-dasharray` / `stroke-dashoffset` (plus\n * `stroke-opacity` for the empty-range hide) and NEVER rewrites `d`, so the fill\n * is untouched. Lottie's same-named `ty:'tm'` is a path OPERATOR that rewrites\n * geometry (and therefore does change the fill) — the old name imported that\n * wrong mental model from the format most authors convert from.\n */\nexport interface _PxStrokeTrimEffect {\n offset?: PxAnimatable<number>;\n range?: PxAnimatable<PxVec2>;\n subPaths?: PxStrokeTrimSubPaths;\n}\n/** @public @advanced */\nexport const PxStrokeTrimEffectSchema = implementsInterface<_PxStrokeTrimEffect>()(px.object({\n offset: PxAnimatableNumberSchema.optional(),\n range: PxAnimatableVec2Schema.optional(),\n subPaths: px.enum([PxStrokeTrimSubPaths.separate, PxStrokeTrimSubPaths.combined] as const).optional(),\n}));\n/** @public */\nexport type PxStrokeTrimEffect = PxInfer<typeof PxStrokeTrimEffectSchema>;\nconst _ck_PxStrokeTrimEffect: KeysMatch<PxStrokeTrimEffect, _PxStrokeTrimEffect> = true;\n\n\n/** Ref-attr naming rule (see editor dev-docs/schema-design.md): `source` = ref to an EXTERNAL element\n * (clone/maskedBy/retime); `coreId` = a unit's own survivor; `partOf` = a derived node's host.\n *\n * `<use>` retime: pure timing — the source ref lives ONCE, on the parent `clone.source`\n * (review §4.3; retime's own duplicate `source` was removed outright — no consumer ever\n * read it: the materializer follows `href`). `start`/`timeCrop` in ms.\n * `timeCrop: [inMs, outMs]` is a VISIBILITY WINDOW on the document timeline — implemented\n * (2026-08) as an opacity gate on a player-side wrapper `<g>`, independent of the\n * `start`/`stretch` remap (see `effects/retimeEffect.ts`). */\nexport interface _PxRetimeEffect {\n start?: number;\n stretch?: number;\n timeCrop?: [number, number];\n}\n/** @public @advanced */\nexport const PxRetimeEffectSchema = implementsInterface<_PxRetimeEffect>()(px.object({\n start: px.number().optional(),\n stretch: px.number().optional(),\n timeCrop: px.tuple([px.number(), px.number()] as const).optional(),\n}));\n/** @public */\nexport type PxRetimeEffect = PxInfer<typeof PxRetimeEffectSchema>;\nconst _ck_PxRetimeEffect: KeysMatch<PxRetimeEffect, _PxRetimeEffect> = true;\n\n\n/**\n * `<use>` CLONE — merges the former `ref` + `retime` effects. A `<use>` is a clone\n * of something: `type`/`source` say WHAT it clones, `retime` says WHEN.\n * - `without: 'translate'` → content-ref: the source's own translate is left out (the\n * clone stays where the `<use>` put it, still rotates/scales with the source);\n * absent → direct / whole-element link (keeps translate). A future `'transform'`\n * value may leave out the whole transform.\n * - `source` = the source element ref, `#id` (canonical spelling, SCHEMA-DESIGN §4 E-5;\n * bare `id` is legacy, read-only). Lives once here; the player follows `href`.\n * - `retime` = optional time-shift (nested).\n * Omitted entirely when all-default (a bare `<use href>` carries no `clone` bucket).\n */\nexport interface _PxCloneEffect {\n without?: string;\n source?: string;\n retime?: _PxRetimeEffect;\n}\n/** @public @advanced */\nexport const PxCloneEffectSchema = implementsInterface<_PxCloneEffect>()(px.object({\n // Subtractive on purpose: the `<use>` can only point at one wrapper layer of the\n // source, so the choices form a ladder — 'translate' now, maybe 'transform' later.\n without: px.enum([PxCloneWithout.translate] as const).optional(),\n source: px.string().optional(),\n retime: PxRetimeEffectSchema.optional(),\n}));\n/** @public */\nexport type PxCloneEffect = PxInfer<typeof PxCloneEffectSchema>;\nconst _ck_PxCloneEffect: KeysMatch<PxCloneEffect, _PxCloneEffect> = true;\n\n\n/** A single color stop. `offset` is in `[0, 1]`; `color` is a CSS color\n * string (`#rrggbb`, `rgb(…)`, `rgba(…)`, or named). */\nexport interface _PxGradientStop {\n offset: number;\n color: string;\n}\n/** @public @advanced */\nexport const PxGradientStopSchema = implementsInterface<_PxGradientStop>()(px.object({\n offset: px.number(),\n color: px.string(),\n}));\n/** @public */\nexport type PxGradientStop = PxInfer<typeof PxGradientStopSchema>;\nconst _ck_PxGradientStop: KeysMatch<PxGradientStop, _PxGradientStop> = true;\n\n/** `PxAnimatable<Array<PxGradientStop>>` schema. Static is the bare array;\n * `{value: […]}` wraps the same; the animated form is `PxPropertyAnimation`\n * (one timeline whose each kf's `value` is the FULL stops array at that time).\n * LAW (SCHEMA-DESIGN R5, S9): stops are ONE animatable value — whole-array\n * snapshots on a single timeline, deliberately NO per-stop keyframes/easing\n * (gradient GEOMETRY animates per-slot with independent timelines). */\nconst PxAnimatableGradientStopsSchema = px.union([\n px.array(PxGradientStopSchema),\n px.object({ value: px.array(PxGradientStopSchema) }),\n PxPropertyAnimationSchema,\n]);\n\n/** Gradient paint effect — used by both `fillGradient` and `strokeGradient`\n * (same shape, different host attribute). Linear: `start`/`end`. Radial:\n * `center`/`radius`/`focal`. Stops animate as one timeline; geometry stays static. */\n// (The old `_PxGradientGeometryAnimation` per-scalar channel record —\n// `animate: {gradientX1: …}` — was REMOVED outright, read included: geometry\n// animates on the `start`/`end`/`center`/`radius`/`focal` slots. Backward compat dropped\n// deliberately. Frames-engine-only note still applies to animated geometry:\n// CSS/WAAPI cannot animate gradient endpoints; `mode: 'auto'` handles it.)\n\nexport interface _PxFillGradientEffect {\n type: PxGradientType; // 'linear' | 'radial'\n start?: PxAnimatable<PxVec2>; // linear start ([x1,y1]; review §4.2 — plain words, no abbreviations)\n end?: PxAnimatable<PxVec2>; // linear end ([x2,y2])\n center?: PxAnimatable<PxVec2>; // radial center ([cx,cy])\n radius?: PxAnimatable<number>; // radial radius (r)\n focal?: PxAnimatable<PxVec2>; // radial focal point ([fx,fy])\n stops?: PxAnimatable<Array<_PxGradientStop>>; // single animation timeline\n gradientUnits?: string; // PxUnits values\n spreadMethod?: string; // PxGradientSpreadMethod values\n gradientTransform?: string; // static only in v1\n}\n/** @public @advanced */\nexport const PxFillGradientEffectSchema = implementsInterface<_PxFillGradientEffect>()(px.object({\n // Contextual kind — the `type` convention, see `PxNodeBaseSchema.type`.\n type: px.enum([PxGradientType.linear, PxGradientType.radial] as const),\n start: PxAnimatableVec2Schema.optional(),\n end: PxAnimatableVec2Schema.optional(),\n center: PxAnimatableVec2Schema.optional(),\n radius: PxAnimatableNumberSchema.optional(),\n focal: PxAnimatableVec2Schema.optional(),\n stops: PxAnimatableGradientStopsSchema.optional(),\n gradientUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox] as const).optional(),\n spreadMethod: px.enum([PxGradientSpreadMethod.pad, PxGradientSpreadMethod.reflect, PxGradientSpreadMethod.repeat] as const).optional(),\n gradientTransform: px.string().optional(),\n}));\n/** @public */\nexport type PxFillGradientEffect = PxInfer<typeof PxFillGradientEffectSchema>;\nconst _ck_PxFillGradientEffect: KeysMatch<PxFillGradientEffect, _PxFillGradientEffect> = true;\n\n/** Stroke gradient is the same shape as fill gradient; the difference is\n * only which host attribute (`fill` vs `stroke`) the applier rewrites. */\nexport type _PxStrokeGradientEffect = _PxFillGradientEffect;\n/** @public @advanced */\nexport const PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;\n/** @public */\nexport type PxStrokeGradientEffect = PxFillGradientEffect;\n\n/** Text-path effect on a `<text>` host. The path geometry is carried INLINE as\n * `pathData` (an SVG `d`; static for now, keyframed animation is a later step) — the\n * applier generates a `<path>` def from it and wraps the text's children in a native\n * `<textPath href=\"#…\">` at apply time. All SVG-native textPath attrs\n * (`lengthAdjust`, `method`, `spacing`, `startOffset`, `textLength`) ride on this\n * effect; `startOffset`/`textLength` accept the full `PxAnimatable<number>` shape.\n *\n * `pathOverflow` controls what happens to glyphs past the end of an OPEN path:\n * - `'extend'` (default): glyphs continue straight along the endpoint tangent\n * (Lottie / native-glyph behavior).\n * - `'clip'`: glyphs past the end disappear (native `<textPath>` behavior). */\nexport interface _PxTextPathEffect {\n pathData: string; // inline SVG `d`\n pathOverflow?: string; // 'clip' | 'extend' (default 'extend')\n lengthAdjust?: string; // 'spacing' | 'spacingAndGlyphs'\n method?: string; // 'align' | 'stretch'\n spacing?: string; // 'auto' | 'exact'\n startOffset?: PxAnimatable<number>;\n textLength?: PxAnimatable<number>;\n}\n/** @public @advanced */\nexport const PxTextPathEffectSchema = implementsInterface<_PxTextPathEffect>()(px.object({\n pathData: px.string(),\n pathOverflow: px.enum([PxPathOverflow.clip, PxPathOverflow.extend] as const).optional(),\n lengthAdjust: px.enum([PxLengthAdjust.spacing, PxLengthAdjust.spacingAndGlyphs] as const).optional(),\n method: px.enum([PxTextPathMethod.align, PxTextPathMethod.stretch] as const).optional(),\n spacing: px.enum([PxTextPathSpacing.auto, PxTextPathSpacing.exact] as const).optional(),\n startOffset: PxAnimatableNumberSchema.optional(),\n textLength: PxAnimatableNumberSchema.optional(),\n}));\n/** @public */\nexport type PxTextPathEffect = PxInfer<typeof PxTextPathEffectSchema>;\nconst _ck_PxTextPathEffect: KeysMatch<PxTextPathEffect, _PxTextPathEffect> = true;\n\n\n/**\n * `effects.text` — text-rendering options for a `<text>` node.\n *\n * `useGlyphs: true` tells the player to render this text from the embedded\n * per-glyph outlines in `definitions.fonts` (self-contained, no external\n * font) instead of a native `<text>`. See svga.text.design.md.\n */\nexport interface _PxTextEffect {\n useGlyphs?: boolean;\n}\n/** @public @advanced */\nexport const PxTextEffectSchema = implementsInterface<_PxTextEffect>()(px.object({\n useGlyphs: px.boolean().optional(),\n}));\nexport type PxTextEffect = PxInfer<typeof PxTextEffectSchema>;\nconst _ck_PxTextEffect: KeysMatch<PxTextEffect, _PxTextEffect> = true;\n\n\n/**\n * The full `node.effects` bucket. Closed — each known effect is declared\n * (strict-mode validation flags an unknown effect key as a wire-format drift).\n *\n * DESIGN LAW — attribute vs effect:\n * - An ATTRIBUTE is a value the browser consumes as-is on that element\n * (`fill=\"#f00\"`, `opacity`, `d`); animating it is \"this value over time\" —\n * one channel, zero structure. The test is STRUCTURE, not value-encoding\n * complexity: `transform` has a parts-record wire value but lands in one\n * attribute on the same element, so it stays an attribute.\n * - An EFFECT is anything whose realization requires structure — generating defs\n * (gradient, clipPath, maskedBy, textPath), wrapper nodes (transformation),\n * clones (repeater, clone), or geometry-derived multi-attr rewrites (strokeTrim).\n * - The same attribute name can sit on both sides, split by value: flat `fill`\n * is an attribute; gradient fill is an effect (no value of `fill` IS a\n * gradient — it needs a def + stops + a `url(#id)` indirection).\n * New features follow the same test: pattern fills / filters need defs → effects.\n *\n * COMPOSITION ORDER (SCHEMA-DESIGN §R5): one bag per element — JSON key order\n * carries NO meaning and is never read. The applier composes in one hard-coded\n * order, innermost → outermost:\n * glyphs/textPath → fill/strokeGradient → strokeTrim → repeater → maskedBy\n * → clipPath → clone-href+transformBy (retime = pass 2, time-remap only)\n * \"Other\" orders are expressed by STRUCTURE (nest elements), never by key order.\n * If authorable order is ever demanded: an explicit `effects.order: [names]`\n * extension — never key-order significance (JSON tooling silently reorders).\n */\nexport interface _PxEffects {\n transformBy?: _PxTransformByEffect;\n repeater?: _PxRepeaterEffect;\n maskedBy?: _PxMaskedByEffect;\n clipPath?: _PxClipPathEffect;\n strokeTrim?: _PxStrokeTrimEffect;\n clone?: _PxCloneEffect;\n fillGradient?: _PxFillGradientEffect;\n strokeGradient?: _PxStrokeGradientEffect;\n textPath?: _PxTextPathEffect;\n text?: _PxTextEffect;\n}\n/** @public @advanced */\nexport const PxEffectsSchema = implementsInterface<_PxEffects>()(px.object({\n transformBy: PxTransformByEffectSchema.optional(),\n repeater: PxRepeaterEffectSchema.optional(),\n maskedBy: PxMaskedByEffectSchema.optional(),\n clipPath: PxClipPathEffectSchema.optional(),\n strokeTrim: PxStrokeTrimEffectSchema.optional(),\n clone: PxCloneEffectSchema.optional(),\n fillGradient: PxFillGradientEffectSchema.optional(),\n strokeGradient: PxStrokeGradientEffectSchema.optional(),\n textPath: PxTextPathEffectSchema.optional(),\n text: PxTextEffectSchema.optional(),\n}));\n/** @public */\nexport type PxEffects = PxInfer<typeof PxEffectsSchema>;\nconst _ck_PxEffects: KeysMatch<PxEffects, _PxEffects> = true;\n\n/**\n * Walks `root` and validates every `node.effects` bucket against `PxEffectsSchema`.\n * Returns an array of human-readable warning strings (empty when all good).\n * Doesn't mutate the tree. Called by `createAnimatorImpl` before applying effects.\n *\n * Pass `strict: true` to also flag undeclared keys (useful in dev / tests).\n * @public @advanced\n */\nexport function validateNodeEffects(root: PxNode, options?: { strict?: boolean }): Array<string> {\n const warnings: Array<string> = [];\n // `path` is a human-readable breadcrumb prepended to each warning so the\n // reader can locate the offending node in the tree (e.g.\n // `root.children[0].children[2].effects.transformBy.translate: …`).\n const walk = (node: PxNode, path: string): void => {\n if (node && node.effects) {\n const ctx: PxValidationContext = { errors: [], warnings: [], strict: !!options?.strict };\n const ok = PxEffectsSchema.isValid(node.effects, ctx, [path + '.effects']);\n if (!ok) {\n for (const err of ctx.errors) warnings.push(err);\n }\n }\n if (node && Array.isArray(node.children)) {\n node.children.forEach((c, i) => walk(c, path + '.children[' + i + ']'));\n }\n };\n walk(root, 'root');\n return warnings;\n}\n\n/**\n * Cross-checks glyph-mode text against the embedded faces (review §2.5).\n *\n * A `definitions.fonts` key IS the face name, matched against the node's `font-family`\n * verbatim — so a name with no entry renders a row of □ placeholder boxes behind nothing but\n * a console warning. Nothing else catches that: the schema validates each side's SHAPE, never\n * that the two agree.\n *\n * Deliberately silent in two legal cases:\n * - the document embeds NO faces — browser-font text, a different situation entirely;\n * - a node carries no `font-family` — with exactly one face embedded the player resolves it\n * (`soleFont`), and with none there is nothing to name.\n *\n * The reverse (a face nothing references) is NOT reported: keeping the outlines of a text\n * whose glyph mode is currently off is legal and deliberate, so that toggling it back on\n * needs no font reload.\n */\nexport function validateGlyphFontRefs(root: PxNode, fonts: { [face: string]: unknown; } | undefined): Array<string> {\n if (!fonts || !Object.keys(fonts).length) return [];\n\n const problems: Array<string> = [];\n const walk = (node: PxNode, path: string, inherited: string | undefined, inGlyphText: boolean): void => {\n if (!node) return;\n // `font-family` inherits down the text tree, exactly as the renderer resolves it.\n const own = typeof node.fontFamily === 'string' ? node.fontFamily : undefined;\n const family = own ?? inherited;\n const isGlyphText = inGlyphText || (node.type === 'text' && !!node.effects?.text?.useGlyphs);\n\n // Report at the node that DECLARES the family — one problem per mistake, not one per\n // descendant that merely inherits it.\n if (isGlyphText && own && !Object.prototype.hasOwnProperty.call(fonts, own)) {\n const problem = path + ': glyph-mode text uses font-family \"' + own\n + '\", which has no entry in animator.definitions.fonts';\n if (!problems.includes(problem)) problems.push(problem);\n }\n if (Array.isArray(node.children)) {\n node.children.forEach((c, i) => walk(c, path + '.children[' + i + ']', family, isGlyphText));\n }\n };\n walk(root, 'root', undefined, false);\n return problems;\n}\n\n/**\n * Cross-checks named easing references against `definitions.easings` (review §2.9).\n *\n * A keyframe's `easing` is either a cubic-bezier array or the NAME of an entry in\n * `definitions.easings` — CSS keywords are deliberately not built in (player weight), so\n * `easing: \"ease-in-out\"` validates as a string, resolves to nothing, and plays LINEAR behind\n * one `console.warn`. That makes it the likeliest silent mistake in a generated document, and\n * the schema cannot catch it: it checks the shape of each side, never that the two agree.\n *\n * Only the wire spelling `easing` is read. The runtime view's `e` is an already-RESOLVED curve,\n * never a name, so it has nothing to cross-check.\n */\nexport function validateEasingRefs(root: PxNode, easings: { [name: string]: unknown; } | undefined): Array<string> {\n const problems: Array<string> = [];\n const walk = (node: unknown, path: string): void => {\n if (Array.isArray(node)) {\n node.forEach((item, i) => walk(item, path + '[' + i + ']'));\n return;\n }\n if (!node || typeof node !== 'object') return;\n\n const easing = (node as { easing?: unknown }).easing;\n if (typeof easing === 'string' && !(easings && Object.prototype.hasOwnProperty.call(easings, easing))) {\n const problem = path + '.easing: \"' + easing\n + '\" names no entry in animator.definitions.easings — it will play linear';\n if (!problems.includes(problem)) problems.push(problem);\n }\n for (const [key, value] of Object.entries(node as Record<string, unknown>)) {\n if (key === 'easing') continue; // already handled; a string has nothing to walk\n if (value && typeof value === 'object') walk(value, path + '.' + key);\n }\n };\n walk(root, 'root');\n return problems;\n}\n\n/**\n * Checks the `animator.version` stamp parses (review §2.10).\n *\n * The slot is `px.string()`, so `\"v1\"` validates and is then read as UNSTAMPED — the document\n * silently loses the one diagnostic that says which schema wrote it. Parsing is delegated to\n * {@link parseWireVersion} so this can never disagree with the reader.\n *\n * An ABSENT stamp is legal and silent: only a present-but-unparseable one is reported.\n */\nexport function validateVersionStamp(doc: PxAnimatedSvgDocument): Array<string> {\n const version = getAnimatorConfig(doc)?.version;\n if (version === undefined) return [];\n if (parseWireVersion(version) !== undefined) return [];\n return ['root.animator.version: ' + JSON.stringify(version)\n + ' is not a version stamp (\"a.b\" or \"a.b.c\") — it reads as unstamped'];\n}\n\n/**\n * Validates a WHOLE document against the wire schema — strictly, so undeclared keys are\n * reported too — plus every node's `effects` bucket and the glyph-font references. Returns\n * human-readable problems (`path: what is wrong`), empty when the document is sound; never\n * throws. The player itself only warns and skips what it cannot read; this is the one call\n * for tooling, CI and agents that want a yes/no answer before shipping a document.\n * @public\n */\nexport function validateDocument(doc: unknown, options?: { strict?: boolean }): Array<string> {\n // Strict (the default) rejects keys the schema does not declare — the right answer for a\n // document you are about to ship. `strict: false` tolerates them, which is what a READER\n // wants: an unknown key usually means a newer writer — worth a warning, never a refusal.\n const strict = options?.strict !== false;\n const ctx: PxValidationContext = { errors: [], warnings: [], strict };\n const problems: Array<string> = PxAnimatedSvgDocumentSchema.isValid(doc, ctx, ['root']) ? [] : [...ctx.errors];\n if (doc && typeof doc === 'object') {\n for (const w of validateNodeEffects(doc as PxNode, { strict })) {\n if (!problems.includes(w)) problems.push(w);\n }\n const defs = getAnimatorConfig(doc as PxAnimatedSvgDocument)?.definitions;\n for (const w of validateGlyphFontRefs(doc as PxNode, defs?.fonts)) {\n if (!problems.includes(w)) problems.push(w);\n }\n for (const w of validateEasingRefs(doc as PxNode, defs?.easings)) {\n if (!problems.includes(w)) problems.push(w);\n }\n for (const w of validateVersionStamp(doc as PxAnimatedSvgDocument)) {\n if (!problems.includes(w)) problems.push(w);\n }\n }\n return problems;\n}\n\n\n// ============================================================================\n// NODE\n// ============================================================================\n\n/**\n * Base shape for all SVG element nodes.\n * Open object: validated known keys + arbitrary SVG attributes whose values are\n * either primitives (static) or PxPropertyAnimation objects (in-place animation).\n * Non-recursive — excludes `children` (circular reference). Used for type extraction via PxInfer.\n *\n * `{ type:string, style?:…, [key:string]: string|number|PxPropertyAnimation }`\n * @public @advanced\n */\nexport const PxNodeBaseSchema = px.openObject({\n // CONVENTION (SCHEMA-DESIGN R1 / issues N4): `type` is the ONE word for \"what\n // kind of thing is this\", discriminated by its CARRIER — here the node TAG\n // (`rect`, `text`), and inside a sub-object that object's kind (`fillGradient.type`,\n // `fillGradient.type`, editor `preset.type`). Each sits in its own object, so\n // the carrier disambiguates completely; synonyms (`cloneKind`, `presetShape`)\n // would add words that all mean \"type\" and still need the carrier to read.\n // Guarding a `type` SLOT against a wrong VALUE is the job of strict enums\n // (issues V3), never of distinct key names.\n type: px.string(),\n // The escape hatch for elements that carry a REAL `type` attribute — `<feTurbulence\n // type=\"fractalNoise\">`, `<feFuncR type=\"table\">`, `<feColorMatrix type=\"saturate\">`.\n // `type` is taken by the tag name, so the attribute travels here and the renderer puts\n // it back (`PxAnimatorDOM.renderNode`, `PxRnRender`). Declared here — not merely\n // documented — because a wire key that is not in a schema is invisible to the\n // minifier's reserve list and gets renamed (dev-docs/plans/minification-boundary.md §1.1).\n domType: px.string().optional(),\n // Text content of a `<text>` / `<tspan>`. Declared, so a non-string value is a schema error\n // and the minifier reserves the key; `text` is NOT an alias for it and is not read anywhere.\n textContent: px.string().optional(),\n id: px.string().optional(),\n meta: px.any().optional(),\n // Player-effects bucket emitted by the Editor's lightweight design format.\n // Consumed and removed by `materializeNodeEffects` before any other normalization\n // (see `createAnimatorImpl`), so downstream code never sees it.\n effects: PxEffectsSchema.optional(),\n // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts\n // string ref / array of refs / inline definition / mixed array; mirrors\n // `node.animate` values and what `processNode` resolves at runtime.\n animate: PxElementAnimationSchema.optional(),\n style: px.record(px.union([px.string(), px.number()])).optional(),\n}, PxAttrValueSchema);\n\n// `let` so the lazy closure can capture the variable reference after assignment.\n// By the time the lazy resolves (first isValid/sanitize call), PxNodeSchema is assigned.\n// `PxNodeBaseSchema & { children?:PxNode[] }`\n/** @public @advanced */\nlet PxNodeSchema: PxSchema<any> = px.openObject({\n ...PxNodeBaseSchema._shape,\n children: px.lazy(() => px.array(PxNodeSchema), []).optional(),\n}, PxAttrValueSchema);\nexport { PxNodeSchema };\n\n/**\n * Base interface for all SVG elements.\n * Extends schema-derived typed fields; adds recursive children and the open\n * index signature for arbitrary SVG attributes under their camelCase DOM names\n * (cx, cy, r, fill, strokeWidth, …) — see `_PxNodeBase`.\n * Named properties take precedence over the index signature when accessed.\n * @public\n */\nexport interface PxNode extends PxInfer<typeof PxNodeBaseSchema> {\n children?: PxNode[];\n [camelCaseDomKey: string]: any;\n}\n\n\n// ============================================================================\n// SVG NODE (ROOT)\n// ============================================================================\n\n/**\n * Root SVG element containing the entire animated graphic.\n * Extends PxNode with SVG-specific properties and global configuration.\n */\nexport interface _PxSvgNode extends PxNode {\n\n /** SVG viewport width. `number` OR an SVG length string (`\"100%\"`, `\"12em\"`) —\n * percentages are legal SVG and appear in real documents. */\n width?: number | string;\n\n /** SVG viewport height — `number` or SVG length string, see `width`. */\n height?: number | string;\n\n /** FIXME - do we need it? SVG viewBox attribute defining coordinate system */\n viewBox?: string;\n\n /** Global animation configuration */\n animator?: PxAnimatorConfig;\n}\n\n/**\n * Extra fields present on the root SVG node, on top of PxNode.\n * Used for type extraction via PxInfer.\n *\n * `{ width?:number, height?:number, viewBox?:string, animator?:AnimatorConfig }`\n * @public @advanced\n */\nexport const PxSvgNodeRootSchema = px.object({\n // `\"100%\"` and other SVG length strings are legal here — a number-only slot rejected\n // real documents (e.g. apple-store-look-14-main.json) at the root <svg>.\n width: px.union([px.number(), px.string()]).optional(),\n height: px.union([px.number(), px.string()]).optional(),\n viewBox: px.string().optional(),\n animator: PxAnimatorConfigSchema.optional(),\n});\n\n/**\n * Root SVG element containing the entire animated graphic.\n * Extends PxNode (inheriting the open index signature) plus schema-derived\n * SVG-root fields.\n * @public\n */\nexport interface PxSvgNode extends PxNode, Omit<PxInfer<typeof PxSvgNodeRootSchema>, 'animator'> {\n /** The RUNTIME-VIEW type, not the wire shape: in-memory documents may carry the\n * flat playback fields (`flattenAnimatorTimeline` output, prop overrides in the\n * RN/React wrappers), while `PxAnimatorConfigSchema` validates only the nested\n * `timeline` spelling on the wire (review §2.1). */\n animator?: PxAnimatorConfig;\n}\n\n\n// ============================================================================\n// DOCUMENT\n// ============================================================================\n\n/**\n * Root SVG document schema. Enforces `type === 'svg'` to distinguish from child nodes.\n * This is the root type for the entire file format.\n *\n * `{ type:'svg', style?:…, width?:number, height?:number,\n * viewBox?:string, animator?:AnimatorConfig, children?:PxNode[],\n * [svgAttr]: string|number|PxPropertyAnimation }`\n * @public @advanced\n */\nexport const PxAnimatedSvgDocumentSchema = px.openObject({\n ...PxNodeBaseSchema._shape,\n ...PxSvgNodeRootSchema._shape,\n type: px.literal('svg'), // override string → literal to require 'svg'\n children: px.array(PxNodeSchema).optional()\n}, PxAttrValueSchema);\n\n/**\n * The complete animated SVG document.\n * This is the root type for the entire file format.\n * @public\n */\nexport interface PxAnimatedSvgDocument extends PxSvgNode {\n}\n\n\n// ============================================================================\n// API INTERFACES\n// ============================================================================\n\n// -- Callbacks: one chain, three levels (API review §9, §5; dev-docs/reviews/api-surface-review.md §26.1) ----------------------------\n//\n// PxDiagnosticsConfig onWarn / onError / muteWarn / muteError — what `createDiagnostics` reads\n// PxEngineCallbacks + onPlay / onPause / onCancel / onFinish / onRemove — what an ENGINE takes\n// PxAnimatorCallbacks + onStop — what every PUBLIC surface takes\n//\n// Each level extends the one above, so a field is spelled once and the surfaces cannot drift.\n// The diagnostics rule (`onError` = this instance will not play; `onWarn` = it plays, but\n// something was ignored, degraded or misspelled) is written on `PxDiagnosticsConfig`.\n\n/**\n * The callbacks an ENGINE takes — the frame loop, WAAPI, React Native's sampler: the playback\n * lifecycle plus the diagnostics channel. Public surfaces take `PxAnimatorCallbacks`.\n * @public\n */\nexport interface PxEngineCallbacks extends PxDiagnosticsConfig {\n\n /** Callback executed when the animation starts or resumes. */\n onPlay?: () => void;\n\n /** Callback executed when the animation is paused. */\n onPause?: () => void;\n\n /** Callback executed when the animation is canceled. */\n onCancel?: () => void;\n\n /**\n * Callback executed when the animation reaches its end — it played every iteration, or\n * `finish()` was called. Not fired when playback is stopped early (pause / cancel / remove).\n */\n onFinish?: () => void;\n\n /** Callback executed when the animation is removed. */\n onRemove?: () => void;\n}\n\n/**\n * What every PUBLIC surface takes, inline and under the same names — `createAnimator({ onFinish })`,\n * `loadTagAnimators`, the pre-rendered entries, and `<PixodeskSvgAnimator onFinish />` on React,\n * Vue and React Native (review §9, §24): the engine's callbacks plus `onStop`, which fires after\n * any of `onPause` / `onCancel` / `onFinish` / `onRemove` — for callers who only care that\n * playback is no longer running, whatever the reason.\n *\n * ONE definition: the components derive their props from it instead of each spelling the same\n * names, which is how their comments had already started to drift.\n * @public\n */\nexport interface PxAnimatorCallbacks extends PxEngineCallbacks {\n onStop?: () => void;\n}\n\n\nexport type PxPoint2D = Array<number>;\n\n\n// ============================================================================\n// BEZIER PATH\n// ============================================================================\n\n/** Represents a vector path for SVG shape animations. */\nexport interface _PxBezierPath {\n\n /** An array of vertex points [[x, y], ...]. */\n v: Array<PxPoint2D>;\n\n /** An array of 'in' tangent handles for each vertex [[x, y], ...]. */\n i?: Array<PxPoint2D>;\n\n /** An array of 'out' tangent handles for each vertex [[x, y], ...]. */\n o?: Array<PxPoint2D>;\n\n /** A boolean indicating if the path is closed. */\n c?: boolean;\n}\n\n// `{ v:number[][], i?:number[][], o?:number[][], c?:boolean }`\n/** @public @advanced */\nexport const PxBezierPathSchema = implementsInterface<_PxBezierPath>()(px.object({\n v: px.array(px.array(px.number())),\n i: px.array(px.array(px.number())).optional(),\n o: px.array(px.array(px.number())).optional(),\n c: px.boolean().optional(),\n}));\n\n/** Represents a vector path for SVG shape animations. @public */\nexport type PxBezierPath = PxInfer<typeof PxBezierPathSchema>;\nconst _ck_PxBezierPath: KeysMatch<PxBezierPath, _PxBezierPath> = true; // the key sets are identical\n\n\n// ============================================================================\n// ANIMATOR API\n// ============================================================================\n\n/**\n * Basic animation controls common to all animator types.\n *\n * Generic over the platform's root-element type (`TRoot`) so this package stays\n * platform-neutral: the web player specializes it to the DOM `Element`, a\n * React Native player to its own view handle. Defaults to `unknown`.\n * @public\n */\nexport interface PxPlaybackApi<TRoot = unknown> {\n\n isReady(): boolean;\n\n /** Returns the root element for the animation (platform-specific type). */\n getRootElement(): TRoot | null;\n\n /** Returns true if the animation is currently running. */\n isPlaying(): boolean;\n\n /** Starts or resumes the animation. */\n play(): void;\n\n /** Pauses the animation at its current state. */\n pause(): void;\n\n /** Stops the animation and resets it to its initial state. */\n cancel(): void;\n\n}\n\n/**\n * The full programmatic control interface for an animation.\n *\n * ### The time contract (API review §3)\n *\n * Every engine — the browser's WAAPI, the frame loop, React Native — answers these the same way:\n *\n * - **Time is ms from the start of the WHOLE run**, iterations included; never ms within the\n * current iteration. A time slider therefore reads the same on every player instead of\n * jumping back each time the animation repeats.\n * - **A seek clamps to `[0, duration × iterations]`**, with no upper bound when `iterations`\n * is `'infinite'`.\n * - **A rate of 0 is rejected** with a warning, everywhere. Use `pause()`.\n *\n * The maths behind it lives in `playback/PxPlaybackTime.ts`, so there is one implementation\n * rather than one per engine.\n * @public\n */\nexport interface PxAnimatorApi<TRoot = unknown> extends PxPlaybackApi<TRoot> {\n\n /** Jumps to the end of the animation and holds the final state. */\n finish(): void;\n\n /**\n * Changes the speed of the animation. 1 is normal, 2 is double, -1 is reverse.\n * A rate of 0 — or a non-finite one — is rejected with a warning; use `pause()`.\n */\n setPlaybackRate(rate: number): void;\n\n /** Current playback time, ms from the start of the whole run. `null` before ready. */\n getCurrentTime(): number | null;\n\n /** Seeks, ms from the start of the whole run; clamped to `[0, duration × iterations]`. */\n setCurrentTime(time: number): void;\n\n /**\n * Current position as 0–1 of the whole run. `null` before ready.\n *\n * The span is `duration × iterations`, or ONE iteration when `iterations` is `'infinite'`\n * (where the value wraps) — the same rule the components' `progress` prop already uses.\n */\n getCurrentProgress(): number | null;\n\n /** Seeks to 0–1 of the whole run, clamped to `[0, 1]`. The twin of `getCurrentProgress`. */\n setCurrentProgress(progress: number): void;\n\n /** Stops the animation and cleans up all associated resources. */\n destroy(): void;\n}\n\n/**\n * The imperative handle a framework component exposes through its ref: the player API minus\n * what the component itself owns — `isReady` (the document is inline, so it is always ready),\n * `getRootElement` (the framework renders it) and `destroy` (unmounting does it).\n *\n * ONE definition (review §9). `ReactAnimatorApi`, `VueAnimatorApi` and `RnAnimatorApi` are\n * aliases of this, so the three can no longer drift — they had: React Native's\n * `setPlaybackRate` comment had already lost \"negative plays backwards\".\n * @public\n */\nexport type PxAnimatorHandle = Omit<PxAnimatorApi, 'isReady' | 'getRootElement' | 'destroy'>;\n\n\n// ============================================================================\n// DEEP VALIDATION\n// ============================================================================\n\n/** @public @advanced */\nexport interface PxValidationResult {\n valid: boolean;\n errors: Array<string>;\n}\n\n/**\n * The pass/fail form of {@link validateDocument}, NON-strict — unknown keys are tolerated —\n * for readers that want a flag plus messages rather than a list.\n *\n * One implementation (review §10). This used to run the schema on its own and answer\n * `'Document failed schema validation'` without ever saying what failed; now every problem\n * `validateDocument` can name comes back with its path. Non-strict on purpose: the editor calls\n * this on OPEN, where a key from a newer version is worth a warning, never a refusal.\n * @public @advanced\n */\nexport function isValidPxDocument(doc: unknown): PxValidationResult {\n const errors = validateDocument(doc, { strict: false });\n return { valid: errors.length === 0, errors };\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport type { PxBezierPath, PxTransformParts } from '../format/PxAnimatorTypes';\n\n\n/**\n * Converts a PxBezierPath to an SVG path string.\n * Control points (i, o) are treated as ABSOLUTE coordinates.\n * @param {PxBezierPath} path\n * @returns {string}\n */\n/**\n * @param forceCurves Emit EVERY segment (incl. the closing one) as a cubic `C`, even when\n * its control points are degenerate (a straight line). Needed for keyframe values the\n * BROWSER interpolates (WAAPI / CSS `path()`): CSS only interpolates paths with\n * IDENTICAL command sequences, so an opportunistic `L` in one keyframe vs a `C` in the\n * next (e.g. a round-corner radius animating from 0) turns the whole animation\n * DISCRETE — it flips at 50% instead of morphing.\n * @internal\n */\nexport function bezierToSvgPath(path: PxBezierPath, forceCurves = false): string {\n const v = path.v;\n const i = path.i;\n const o = path.o;\n const c = path.c;\n\n if (!v.length) return \"\";\n\n const d: Array<string> = [];\n const len = v.length;\n d.push(\"M\" + v[0][0] + \",\" + v[0][1]);\n\n for (let idx = 1; idx < len; idx++) {\n const prevV = v[idx - 1];\n const prevO = o?.[idx - 1] ?? prevV;\n const currI = i?.[idx] ?? v[idx];\n const currV = v[idx];\n\n // Check if it's a straight line (control points coincide with vertices)\n const isLine = !forceCurves && (prevO[0] === prevV[0] && prevO[1] === prevV[1]) &&\n (currI[0] === currV[0] && currI[1] === currV[1]);\n\n if (isLine) {\n d.push(\"L\" + currV[0] + \",\" + currV[1]);\n } else {\n // Control points are absolute coordinates\n d.push(\"C\" + prevO[0] + \",\" + prevO[1] + \",\" + currI[0] + \",\" + currI[1] + \",\" + currV[0] + \",\" + currV[1]);\n }\n }\n\n if (c && len > 0) {\n const lastV = v[len - 1];\n const lastO = o?.[len - 1] ?? lastV;\n const firstI = i?.[0] ?? v[0];\n const firstV = v[0];\n\n // Check if closing segment is a straight line\n const isLine = !forceCurves && (lastO[0] === lastV[0] && lastO[1] === lastV[1]) &&\n (firstI[0] === firstV[0] && firstI[1] === firstV[1]);\n\n if (!isLine) {\n d.push(\"C\" + lastO[0] + \",\" + lastO[1] + \",\" + firstI[0] + \",\" + firstI[1] + \",\" + firstV[0] + \",\" + firstV[1]);\n }\n\n d.push(\"z\");\n }\n\n return d.join(\"\");\n}\n\n/**\n * @param {number} a \n * @param {number} b \n * @param {number} t \n * @returns {number}\n */\nexport function interpolateNum(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\n\n/**\n * @param {Array<number>} a \n * @param {Array<number>} b \n * @param {number} t \n * @returns {Array<number>}\n */\nexport function interpolateVec(a: Array<number>, b: Array<number>, t: number): Array<number> {\n const res: Array<number> = [];\n const count = Math.max(a.length, b.length);\n for (let i = 0; i < count; i++) {\n res[i] = interpolateNum(a[i] || 0, b[i] || 0, t);\n }\n return res;\n}\n\n/**\n * Interpolates between two color arrays [r, g, b] or [r, g, b, a].\n * Normalizes both colors to 4 elements, defaulting alpha to 1 if missing.\n */\nexport function interpolateColor(a: Array<number>, b: Array<number>, t: number): Array<number> {\n return [\n interpolateNum(a[0] || 0, b[0] || 0, t),\n interpolateNum(a[1] || 0, b[1] || 0, t),\n interpolateNum(a[2] || 0, b[2] || 0, t),\n interpolateNum(a[3] === undefined ? 1 : a[3], b[3] === undefined ? 1 : b[3], t)\n ];\n}\n\n/**\n * Interpolates between two arrays of bezier paths.\n * @param paths1 The starting array of paths.\n * @param paths2 The ending array of paths.\n * @param progress The interpolation progress from 0.0 to 1.0.\n * @internal\n */\nexport function interpolateBeziers(\n paths1: Array<PxBezierPath>,\n paths2: Array<PxBezierPath>,\n progress: number\n): Array<PxBezierPath> {\n const count = Math.max(paths1.length, paths2.length);\n const res: Array<PxBezierPath> = [];\n for (let i = 0; i < count; i++) {\n res.push(interpolateBezier(paths1[i], paths2[i], progress));\n }\n return res;\n}\n\n/**\n * Interpolates between two bezier paths.\n * Control points (i, o) are treated as ABSOLUTE coordinates.\n * When control points are missing, they default to the vertex position.\n * @param {PxBezierPath} path1\n * @param {PxBezierPath} path2\n * @param {number} progress\n * @returns {PxBezierPath}\n */\nexport function interpolateBezier(\n path1: PxBezierPath | undefined,\n path2: PxBezierPath | undefined,\n progress: number\n): PxBezierPath {\n if (!path1 || !path2) return path1 || path2 || { v: [] };\n\n const t = Math.min(Math.max(progress, 0), 1);\n const len = Math.min(path1.v.length, path2.v.length);\n\n const v: Array<Array<number>> = [];\n const i: Array<Array<number>> = [];\n const o: Array<Array<number>> = [];\n\n for (let idx = 0; idx < len; idx++) {\n const v1 = path1.v[idx];\n const v2 = path2.v[idx];\n v.push(interpolateVec(v1, v2, t));\n\n // For absolute control points, default to vertex position (straight line)\n const i1 = path1.i?.[idx] ?? v1;\n const i2 = path2.i?.[idx] ?? v2;\n i.push(interpolateVec(i1, i2, t));\n\n const o1 = path1.o?.[idx] ?? v1;\n const o2 = path2.o?.[idx] ?? v2;\n o.push(interpolateVec(o1, o2, t));\n }\n\n return { v, i: i.length ? i : undefined, o: o.length ? o : undefined, c: path1.c ?? path2.c };\n}\n\n\n/**\n * Remap a number from one range to another.\n *\n * @param {number} value - The input value.\n * @param {number} inMin - Lower bound of the input range.\n * @param {number} inMax - Upper bound of the input range.\n * @param {number} outMin - Lower bound of the output range.\n * @param {number} outMax - Upper bound of the output range.\n * @returns The remapped value.\n */\nexport function remap(\n value: number,\n inMin: number,\n inMax: number,\n outMin: number,\n outMax: number\n): number {\n if (inMax === inMin) return outMin; // avoid divide-by-zero\n const t = (value - inMin) / (inMax - inMin);\n return outMin + t * (outMax - outMin);\n}\n\n/**\n * Solves for the parameter t such that the cubic bezier X(t) = x,\n * where the bezier has control point x-coordinates p1x and p2x\n * (endpoints are fixed at x=0 and x=1).\n * Uses Newton-Raphson with bisection fallback.\n */\nexport function solveCubicBezierX(p1x: number, p2x: number, x: number): number {\n if (x <= 0) return 0;\n if (x >= 1) return 1;\n\n const cx = 3 * p1x;\n const bx = 3 * (p2x - p1x) - cx;\n const ax = 1 - cx - bx;\n\n function sampleX(t: number) { return ((ax * t + bx) * t + cx) * t; }\n function sampleDX(t: number) { return (3 * ax * t + 2 * bx) * t + cx; }\n\n let t2 = x;\n let t0 = 0;\n let t1 = 1;\n\n for (let i = 0; i < 8; i++) {\n const x2 = sampleX(t2) - x;\n if (Math.abs(x2) < 1e-6) return t2;\n const d2 = sampleDX(t2);\n if (Math.abs(d2) < 1e-6) break;\n t2 -= x2 / d2;\n }\n\n t2 = x;\n while (t0 < t1) {\n const x2 = sampleX(t2);\n if (Math.abs(x2 - x) < 1e-6) return t2;\n if (x > x2) t0 = t2;\n else t1 = t2;\n t2 = (t1 + t0) / 2;\n }\n\n return t2;\n}\n\n/**\n * Creates a cubic-bezier easing function.\n * @param easing An array of four numbers [x1, y1, x2, y2] defining the bezier curve.\n * @returns A function that takes a progress value (0-1) and returns an eased value.\n * @internal\n */\nexport function cubicBezier(easing: [number, number, number, number]) {\n const [p1x, p1y, p2x, p2y] = easing;\n\n const cy = 3 * p1y;\n const by = 3 * (p2y - p1y) - cy;\n const ay = 1 - cy - by;\n\n function sampleCurveY(t: number) { return ((ay * t + by) * t + cy) * t; }\n\n return function (x: number) {\n return sampleCurveY(solveCubicBezierX(p1x, p2x, x));\n };\n}\n\ntype Point2 = [number, number];\n\nfunction lerp2(a: Point2, b: Point2, t: number): Point2 {\n return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];\n}\n\n/**\n * Splits a cubic bezier curve at parameter t using De Casteljau's algorithm.\n * Returns the left and right sub-curves as 4-point tuples.\n * @internal\n */\nexport function subdivideCubicBezier(\n p0: Point2, p1: Point2, p2: Point2, p3: Point2, t: number\n): { left: [Point2, Point2, Point2, Point2], right: [Point2, Point2, Point2, Point2] } {\n const q0 = lerp2(p0, p1, t);\n const q1 = lerp2(p1, p2, t);\n const q2 = lerp2(p2, p3, t);\n const r0 = lerp2(q0, q1, t);\n const r1 = lerp2(q1, q2, t);\n const s = lerp2(r0, r1, t);\n return {\n left: [p0, q0, r0, s],\n right: [s, r1, q2, p3]\n };\n}\n\ntype Easing = [number, number, number, number];\n\n/**\n * Splits a CSS cubic-bezier easing [x1,y1,x2,y2] at a given x-axis fraction.\n * Each half is re-normalized to map [0,0]→[1,1].\n * Returns undefined for either half if the input is undefined (linear) or the split is degenerate.\n * @internal\n */\nexport function splitEasing(\n easing: Easing | undefined,\n xFraction: number\n): { left: Easing | undefined, right: Easing | undefined } {\n if (!easing) return { left: undefined, right: undefined };\n if (xFraction <= 0) return { left: undefined, right: easing };\n if (xFraction >= 1) return { left: easing, right: undefined };\n\n const [x1, y1, x2, y2] = easing;\n const t = solveCubicBezierX(x1, x2, xFraction);\n\n const p0: Point2 = [0, 0];\n const p1: Point2 = [x1, y1];\n const p2: Point2 = [x2, y2];\n const p3: Point2 = [1, 1];\n\n const { left, right } = subdivideCubicBezier(p0, p1, p2, p3, t);\n\n // Split point coordinates\n const sx = left[3][0];\n const sy = left[3][1];\n\n let leftEasing: Easing | undefined;\n if (sx > 1e-9 && Math.abs(sy) > 1e-9) {\n leftEasing = [\n left[1][0] / sx, left[1][1] / sy,\n left[2][0] / sx, left[2][1] / sy\n ];\n }\n\n let rightEasing: Easing | undefined;\n const rx = 1 - sx;\n const ry = 1 - sy;\n if (rx > 1e-9 && Math.abs(ry) > 1e-9) {\n rightEasing = [\n (right[1][0] - sx) / rx, (right[1][1] - sy) / ry,\n (right[2][0] - sx) / rx, (right[2][1] - sy) / ry\n ];\n }\n\n return { left: leftEasing, right: rightEasing };\n}\n\n/**\n * Reverses a cubic-bezier easing for backward playback.\n * [x1,y1,x2,y2] → [1-x2, 1-y2, 1-x1, 1-y1].\n * @internal\n */\nexport function reverseEasing(easing: Easing | undefined): Easing | undefined {\n if (!easing) return undefined;\n return [1 - easing[2], 1 - easing[3], 1 - easing[0], 1 - easing[1]];\n}\n\n/**\n * Converts a color from a [r, g, b, a] array (where values are 0-1) to an rgba() or rgb() CSS string.\n * @param color The color array.\n * @internal\n */\nexport function toRGBA(color: Array<number>): string {\n const r = Math.round(color[0] * 255);\n const g = Math.round(color[1] * 255);\n const b = Math.round(color[2] * 255);\n return color.length === 4 ?\n 'rgba(' + r + ',' + g + ',' + b + ',' + color[3] + ')' :\n 'rgb(' + r + ',' + g + ',' + b + ')';\n}\n\n/** Parse rgb/rgba string to normalized array */\nexport function parseRgba(s: string): number[] {\n const inner = s.match(/rgba?\\((.*)\\)/)?.[1];\n if (!inner) throw new Error('Invalid rgb/rgba format');\n const parts = inner.split(',').map(v => +v.trim());\n return [parts[0] / 255, parts[1] / 255, parts[2] / 255, ...(parts[3] !== undefined ? [parts[3]] : [])];\n}\n\n/** Parse hex string to normalized array (#RGB, #RGBA, #RRGGBB, #RRGGBBAA) */\nfunction parseHex(s: string): number[] {\n const hex = s.slice(1); // Remove '#'\n const isShort = hex.length <= 4; // #RGB or #RGBA vs #RRGGBB or #RRGGBBAA\n\n const r = isShort ? hex[0] + hex[0] : hex.slice(0, 2);\n const g = isShort ? hex[1] + hex[1] : hex.slice(2, 4);\n const b = isShort ? hex[2] + hex[2] : hex.slice(4, 6);\n const a = hex.length === 4 ? hex[3] + hex[3] : hex.length === 8 ? hex.slice(6, 8) : null;\n\n const result = [\n parseInt(r, 16) / 255,\n parseInt(g, 16) / 255,\n parseInt(b, 16) / 255\n ];\n\n if (a !== null) {\n result.push(parseInt(a, 16) / 255);\n }\n\n return result;\n}\n\n//// FIXME - support normalization of config from different formats\n/** Parse color string (hex or rgb/rgba) to normalized [r, g, b] or [r, g, b, a] array (0-1 range) */\nexport function parseColor(s: any): number[] | undefined {\n if (!s) return undefined;\n if (Array.isArray(s)) return s;\n if (typeof s !== 'string') return undefined;\n if (s.startsWith('#')) {\n return parseHex(s);\n } else if (s.startsWith('rgb')) {\n return parseRgba(s);\n } else {\n // FIXME - come up with some solution how to report errors...\n console.warn('Unsupported color format: ' + s);\n }\n return undefined;\n}\n\n/** @internal */\nexport const PX_COLOR_ATTR_NAMES = new Set([\"color\", \"fill\", \"flood-color\", \"lighting-color\", \"stop-color\", \"stroke\"]);\n/** @internal */\nexport const PX_TRANSFORM_FN_NAMES = new Set([\"translate\", \"rotate\", \"scale\", \"skew\"]);\n/** @internal */\nexport const PX_PCT_BASED_ATTR_NAMES = new Set([\"offset-distance\", \"offsetDistance\"]);\n\n/**\n * Compose a `PxTransformParts` record into a single SVG/CSS transform string in\n * the canonical order:\n *\n * translate, translate(+origin), rotate, scale, translate(-origin)\n *\n * Each part is omitted when not present. `origin` becomes a `translate(+o)` /\n * `translate(-o)` pair surrounding the rotate/scale segment — the SVG-native\n * way to render a transform-origin pivot.\n *\n * @param parts the parts record (translate / rotate / scale / origin)\n * @param opts.withUnits when true (default), translates use `px` and rotate\n * uses `deg` — required for CSS / WebAnimations keyframes. When false, no\n * units are emitted — required for the SVG `transform` attribute.\n * @internal\n */\nexport function composeTransformParts(\n parts: PxTransformParts | null | undefined,\n opts?: { withUnits?: boolean }\n): string {\n if (!parts) return '';\n const withUnits = opts?.withUnits ?? true;\n const segs: Array<string> = [];\n const t = parts.translate;\n const o = parts.origin;\n const r = parts.rotate;\n const k = parts.skew;\n const s = parts.scale;\n const tu = withUnits ? 'px' : '';\n const ru = withUnits ? 'deg' : '';\n if (t) segs.push('translate(' + t[0] + tu + ',' + t[1] + tu + ')');\n if (o) segs.push('translate(' + o[0] + tu + ',' + o[1] + tu + ')');\n if (r !== undefined && r !== null) segs.push('rotate(' + r + ru + ')');\n // Canonical slot: between rotate and scale (Lottie-compatible; pivots at origin).\n if (k !== undefined && k !== null) segs.push('skewX(' + k + ru + ')');\n if (s) segs.push('scale(' + s[0] + ',' + s[1] + ')');\n if (o) segs.push('translate(' + (-o[0]) + tu + ',' + (-o[1]) + tu + ')');\n return segs.join('');\n}\n/**\n * Parses an SVG `transform` attribute string back into a `PxTransformParts` record —\n * the inverse of {@link composeTransformParts} for its canonical shapes.\n *\n * Deliberately CONSERVATIVE (review §0.4 — used to merge a static transform under an\n * animation, where a wrong guess is worse than no merge): accepts only a linear\n * sequence with at most one `translate`, `rotate`, `skewX`, `scale` in the canonical\n * order. Anything else — `matrix(…)`, repeated functions, the ±origin translate\n * sandwich, three-arg `rotate(a cx cy)` — returns `undefined` (caller skips the merge).\n * @internal\n */\nexport function parseTransformParts(str: string | null | undefined): PxTransformParts | undefined {\n if (!str || typeof str !== 'string') return undefined;\n const out: PxTransformParts = {};\n const re = /([a-zA-Z]+)\\s*\\(([^)]*)\\)/g;\n const order = ['translate', 'rotate', 'skewX', 'scale'];\n let lastIdx = -1;\n let m: RegExpExecArray | null;\n while ((m = re.exec(str)) !== null) {\n const fn = m[1];\n const idx = order.indexOf(fn);\n if (idx < 0 || idx <= lastIdx) return undefined; // unknown fn, repeat, or out of order\n lastIdx = idx;\n const nums = m[2].split(/[\\s,]+/).filter(Boolean).map(Number);\n if (nums.some(n => Number.isNaN(n))) return undefined;\n if (fn === 'translate') {\n if (nums.length < 1 || nums.length > 2) return undefined;\n out.translate = [nums[0], nums[1] ?? 0];\n } else if (fn === 'rotate') {\n if (nums.length !== 1) return undefined; // rotate(a cx cy) has no parts spelling\n out.rotate = nums[0];\n } else if (fn === 'skewX') {\n if (nums.length !== 1) return undefined;\n out.skew = nums[0];\n } else { // scale\n if (nums.length < 1 || nums.length > 2) return undefined;\n out.scale = [nums[0], nums[1] ?? nums[0]];\n }\n }\n // Reject when anything but whitespace remains outside the parsed functions.\n if (str.replace(/([a-zA-Z]+)\\s*\\(([^)]*)\\)/g, '').replace(/[\\s,]/g, '').length) return undefined;\n return Object.keys(out).length ? out : undefined;\n}\n\n/** @internal */\nexport const PX_STYLE_ATTR_NAMES = new Set([\"offset-distance\", \"offsetDistance\"]); // Props that need to go to style\n/** @internal */\nexport const PX_DEFAULT_DURATION_MS = 1000;\n\n/**\n * Converts a kebab-case string to camelCase.\n * @param kebab The kebab-case string.\n * @internal\n */\nexport function kebabToCamelCaseWord(kebab: string): string {\n return kebab.includes('-') ? kebab.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()) : kebab;\n}\n\n/**\n * Checks if a string is in camelCase.\n * @param word The string to check.\n */\nexport function isCamelCaseWord(word: string): boolean {\n return !word.includes('-') && /[a-z][A-Z]/.test(word);\n}\n\n// FIXME - docs, rename?\nconst SVG_CAMEL_CASE_ATTRS = new Set([\n // Transform/positioning\n 'viewBox',\n 'preserveAspectRatio',\n\n // Gradient\n 'gradientUnits',\n 'gradientTransform',\n 'spreadMethod',\n\n // Pattern\n 'patternUnits',\n 'patternContentUnits',\n 'patternTransform',\n\n // Clipping/masking\n 'clipPathUnits',\n 'maskUnits',\n 'maskContentUnits',\n\n // Marker (SVG spec keeps these camelCase, like viewBox)\n 'markerUnits',\n 'markerWidth',\n 'markerHeight',\n 'refX',\n 'refY',\n\n // Text\n 'textLength',\n 'lengthAdjust',\n 'startOffset',\n\n // Filter\n 'filterUnits',\n 'primitiveUnits',\n 'tableValues', // feFuncR/G/B/A transfer table (type=\"table\")\n 'stdDeviation',\n 'baseFrequency',\n 'numOctaves',\n 'surfaceScale',\n 'diffuseConstant',\n 'specularConstant',\n 'specularExponent',\n 'kernelMatrix',\n 'kernelUnitLength',\n 'edgeMode',\n 'preserveAlpha',\n 'targetX',\n 'targetY',\n\n // // Animation\n // 'attributeName',\n // 'attributeType',\n // 'calcMode',\n // 'keyTimes',\n // 'keySplines',\n // 'repeatCount',\n // 'repeatDur' \n]);\n\n/**\n * Converts a camelCase string to kebab-case.\n * @param camel The camelCase string.\n * @internal\n */\nexport function camelCaseToKebabWordIfNeeded(camel: string): string { // FIXME - docs, rename function?\n return SVG_CAMEL_CASE_ATTRS.has(camel) ?\n camel :\n camel.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\n/**\n * Checks if a CSS property is set inline on an element's style.\n *\n * Typed structurally (not as the DOM `CSSStyleDeclaration`) so this package\n * stays platform-neutral; a real `element.style` satisfies the shape.\n *\n * @param style - element.style (CSSStyleDeclaration-shaped)\n * @param propName - property name (camelCase or kebab-case)\n */\nexport function hasStyleProp(\n style: { getPropertyValue(propName: string): string },\n propName: string\n): boolean {\n return style.getPropertyValue(propName) !== '';\n}\n\n/**\n * Clamps a number between a minimum and maximum value.\n * @param value The number to clamp.\n * @param min The minimum value.\n * @param max The maximum value.\n * @internal\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, max));\n}\n\n\n// ============================================================================\n// 2D CUBIC BÉZIER PRIMITIVES — motion-along-path support\n// ============================================================================\n//\n// Hand-written, dependency-free 2D Bézier maths used by the motion-along-path\n// playback paths (both WAAPI normalization and frames-mode direct compute).\n// See `fix-motion-along-path--fix-plan.md` for the wider plan.\n//\n// Conventions:\n// - Points are `[x, y]` tuples (`Point2`) — matches the wire format.\n// - A cubic segment is `(P0, P1, P2, P3)` where `P0` and `P3` are the\n// endpoints and `P1`, `P2` are the control points in ABSOLUTE coordinates.\n// - The Lottie-style tangent storage convention is `kf.to` =\n// outgoing-from-kf-as-a-delta, `kf.ti` = incoming-at-the-next-kf-as-a-delta.\n// The wire format re-attaches them to the natural endpoints as\n// `tangentOut` on the FROM keyframe and `tangentIn` on the TO keyframe.\n// Caller is responsible for the position-to-control-point lift, i.e.\n// `P1 = fromKf.value + fromKf.tangentOut`, `P2 = toKf.value + toKf.tangentIn`.\n\n\n/**\n * Evaluate a 2D cubic Bézier at parameter `t ∈ [0, 1]` via Bernstein form:\n *\n * B(t) = (1-t)³ P0 + 3t(1-t)² P1 + 3t²(1-t) P2 + t³ P3\n *\n * `t` is the curve PARAMETER, not arc-length. For arc-length (CSS Motion Path\n * / `offset-distance`) semantics, use `bezier2D_tForDistance` first to convert\n * a distance to its parameter, then call this. Out-of-range `t` is clamped.\n */\nexport function bezier2D_pointAt(\n P0: Point2, P1: Point2, P2: Point2, P3: Point2,\n t: number\n): Point2 {\n if (t <= 0) return [P0[0], P0[1]];\n if (t >= 1) return [P3[0], P3[1]];\n const u = 1 - t;\n const u2 = u * u;\n const u3 = u2 * u;\n const t2 = t * t;\n const t3 = t2 * t;\n const w0 = u3;\n const w1 = 3 * t * u2;\n const w2 = 3 * t2 * u;\n const w3 = t3;\n return [\n w0 * P0[0] + w1 * P1[0] + w2 * P2[0] + w3 * P3[0],\n w0 * P0[1] + w1 * P1[1] + w2 * P2[1] + w3 * P3[1],\n ];\n}\n\n\n/**\n * Evaluate the derivative `B'(t)` of a 2D cubic Bézier at parameter `t`.\n *\n * B'(t) = 3(1-t)² (P1-P0) + 6t(1-t) (P2-P1) + 3t² (P3-P2)\n *\n * Returns the tangent VECTOR (not a unit vector). For auto-orient rotation,\n * caller computes `Math.atan2(d.y, d.x)`.\n *\n * Epsilon-nudge for degenerate endpoints: when a handle coincides with its\n * endpoint (e.g. `P1 === P0` and `t === 0`, common for the start/end of a\n * Lottie spatial-tangent path), the derivative collapses to zero. The\n * exact-endpoint value is then meaningless; we nudge `t` inward by `1e-4`\n * and retry.\n */\nconst BEZIER_T_NUDGE = 1e-4;\nexport function bezier2D_derivativeAt(\n P0: Point2, P1: Point2, P2: Point2, P3: Point2,\n t: number\n): Point2 {\n const result = _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t);\n if (result[0] === 0 && result[1] === 0) {\n // Degenerate (handle = endpoint). Nudge inward and retry.\n const nudgedT = t < 0.5 ? t + BEZIER_T_NUDGE : t - BEZIER_T_NUDGE;\n return _bezier2D_derivativeAtRaw(P0, P1, P2, P3, nudgedT);\n }\n return result;\n}\n\nfunction _bezier2D_derivativeAtRaw(\n P0: Point2, P1: Point2, P2: Point2, P3: Point2,\n t: number\n): Point2 {\n const u = 1 - t;\n const a = 3 * u * u;\n const b = 6 * t * u;\n const c = 3 * t * t;\n return [\n a * (P1[0] - P0[0]) + b * (P2[0] - P1[0]) + c * (P3[0] - P2[0]),\n a * (P1[1] - P0[1]) + b * (P2[1] - P1[1]) + c * (P3[1] - P2[1]),\n ];\n}\n\n\n/**\n * Arc-length lookup table for a 2D cubic Bézier.\n *\n * Samples the curve at `steps + 1` evenly-spaced parameter values\n * (`t = 0, 1/steps, 2/steps, …, 1`), computes the cumulative Euclidean\n * distance between consecutive samples, and returns parallel\n * `Float64Array`s for parameter (`ts`) and arc length (`ds`). `ds[steps]`\n * is the total arc length of the curve.\n *\n * The LUT shape is `{ts, ds}` with parallel Float64Arrays rather than\n * `Array<{t, d}>` because:\n * - One contiguous allocation per array instead of `steps+1` objects.\n * - Binary search in `bezier2D_tForDistance` reads `Float64Array` directly.\n * - The structure is read-only after construction — no need for per-sample\n * field access.\n *\n * Approximation error is roughly `O((1/steps)²)` for smooth curves; the\n * default 100 samples gives <1% error vs analytic arc length on typical\n * motion paths.\n */\nexport interface ArcLengthLUT {\n readonly ts: Float64Array;\n readonly ds: Float64Array;\n}\n\nexport function bezier2D_arcLengthLUT(\n P0: Point2, P1: Point2, P2: Point2, P3: Point2,\n steps: number = 100\n): ArcLengthLUT {\n const n = steps + 1;\n const ts = new Float64Array(n);\n const ds = new Float64Array(n);\n\n let prev = bezier2D_pointAt(P0, P1, P2, P3, 0);\n ts[0] = 0;\n ds[0] = 0;\n\n let cum = 0;\n for (let i = 1; i < n; i++) {\n const t = i / steps;\n const cur = bezier2D_pointAt(P0, P1, P2, P3, t);\n const dx = cur[0] - prev[0];\n const dy = cur[1] - prev[1];\n cum += Math.sqrt(dx * dx + dy * dy);\n ts[i] = t;\n ds[i] = cum;\n prev = cur;\n }\n return { ts, ds };\n}\n\n\n/**\n * Inverse of `bezier2D_arcLengthLUT` lookup: given a distance along the\n * curve, return the curve parameter `t` reached at that distance via\n * binary search on `lut.ds` + linear interpolation between adjacent\n * samples.\n *\n * Distance is clamped to `[0, lut.ds[last]]`. The returned `t` is in\n * `[0, 1]`. Pair with `bezier2D_pointAt` to get the point at that\n * arc-length distance:\n *\n * const t = bezier2D_tForDistance(lut, distance);\n * const point = bezier2D_pointAt(P0, P1, P2, P3, t);\n */\nexport function bezier2D_tForDistance(lut: ArcLengthLUT, distance: number): number {\n const { ts, ds } = lut;\n const last = ds.length - 1;\n if (distance <= 0) return ts[0];\n if (distance >= ds[last]) return ts[last];\n\n // Binary search for the upper-bound index `hi` such that ds[hi-1] <= distance < ds[hi].\n let lo = 1;\n let hi = last;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (ds[mid] < distance) lo = mid + 1;\n else hi = mid;\n }\n // Linear interpolate between the bracketing samples.\n const dPrev = ds[hi - 1];\n const dCur = ds[hi];\n const span = dCur - dPrev;\n const frac = span > 0 ? (distance - dPrev) / span : 0;\n return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);\n}\n\n\n/**\n * `bezier2D_tForDistance` convenience taking a fraction of the total arc\n * length instead of an absolute distance. `pct` is in `[0, 1]` (CSS Motion\n * Path `offset-distance` semantics — `offset-distance: 50%` ≡\n * `bezier2D_tForDistancePct(lut, 0.5)`). Out-of-range values clamp.\n */\nexport function bezier2D_tForDistancePct(lut: ArcLengthLUT, pct: number): number {\n const total = lut.ds[lut.ds.length - 1];\n return bezier2D_tForDistance(lut, pct * total);\n}\n\n\n/**\n * Inverse of {@link bezier2D_tForDistance}: given a curve parameter `t`,\n * returns the arc length from the start. Binary searches the LUT's `ts`\n * (uniformly spaced) and linearly interpolates `ds` between adjacent samples.\n */\nexport function bezier2D_arcAtT(lut: ArcLengthLUT, t: number): number {\n const { ts, ds } = lut;\n const last = ts.length - 1;\n if (t <= ts[0]) return ds[0];\n if (t >= ts[last]) return ds[last];\n let lo = 1, hi = last;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (ts[mid] < t) lo = mid + 1;\n else hi = mid;\n }\n const tPrev = ts[hi - 1];\n const span = ts[hi] - tPrev;\n const frac = span > 0 ? (t - tPrev) / span : 0;\n return ds[hi - 1] + frac * (ds[hi] - ds[hi - 1]);\n}\n\n\n/**\n * Inverse of {@link cubicBezier}: for a CSS easing `[x1,y1,x2,y2]` and a\n * target output `y`, returns the input `x` such that\n * `cubicBezier(easing)(x) ≈ y`. Works for monotonic easings (the CSS\n * default) by swapping the easing's x/y axes — the inverse easing has\n * controls `[y1, x1, y2, x2]`, which `cubicBezier` evaluates directly.\n *\n * `undefined` easing (linear) → identity function.\n */\nexport function invertEasing(easing: Easing | undefined): (y: number) => number {\n if (!easing) return (y) => y;\n const flipped: Easing = [easing[1], easing[0], easing[3], easing[2]];\n return cubicBezier(flipped);\n}","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n/**\n * Motion-along-path → plain transform keyframes.\n *\n * Editor wire format for a curved-translation (and/or `autoOrient`) animation\n * carries per-kf `tangentIn` / `tangentOut` plus an animation-level `autoOrient`\n * flag — a parametric representation that any consumer must evaluate per frame.\n *\n * This module DESUGARS that shape into a regular unified-transform animation:\n * extra `{ translate, rotate? }` keyframes are inserted at curve extrema and\n * adaptive bisection points so the linear chord between adjacent samples stays\n * within `flatnessTolerance` of the true Bezier, and the original easing is\n * SPLIT (via De Casteljau, `splitEasing` in `PxAnimatorUtil`) across the\n * sub-segments so the combined timing reproduces the input easing exactly.\n *\n * Output kfs have no tangents and no `autoOrient` — both engines (`frames` and\n * `waapi`) and any future renderer (e.g. react-native-svg) consume them via\n * their normal unified-transform code path.\n *\n * The plan + rationale (extremes-aware sampling, easing-split, full pipeline\n * order) is in `motion-along-path-waapi-rework.md`.\n */\n\n\nimport { bezier2D_arcAtT, bezier2D_arcLengthLUT, bezier2D_derivativeAt, bezier2D_pointAt, clamp, invertEasing, splitEasing } from '../util/PxAnimatorUtil';\nimport type { ArcLengthLUT } from '../util/PxAnimatorUtil';\nimport type { PxAnyKeyframe, PxKeyframe, PxNormalizedKeyframe, PxNode, PxPropertyAnimation, PxTransformParts } from '../format/PxAnimatorTypes';\nimport { keyframeTime, keyframeValue, keyframeEasing, keyframeTangentIn, keyframeTangentOut } from '../format/PxAnimatorTypes';\n\n\ntype Point2 = [number, number];\ntype Easing = [number, number, number, number];\n\n\nfunction getKfTranslate(kf: PxAnyKeyframe): Point2 | undefined {\n const v = keyframeValue(kf);\n if (!v) return undefined;\n if (Array.isArray(v) && v.length >= 2 && typeof v[0] === 'number' && typeof v[1] === 'number') {\n // Composite per-part shape: `value: [x, y]` directly.\n return [v[0], v[1]];\n }\n const tr = (v as PxTransformParts).translate;\n if (Array.isArray(tr) && tr.length >= 2) return [tr[0], tr[1]];\n return undefined;\n}\n\nfunction getKfTime(kf: PxAnyKeyframe): number {\n return keyframeTime(kf) as number;\n}\n\nfunction getKfEasing(kf: PxAnyKeyframe): Easing | undefined {\n return keyframeEasing(kf) as Easing | undefined;\n}\n\n\n/**\n * True when `anim` is a motion-along-path animation — at least one keyframe\n * carries spatial tangents (`tangentIn` / `tangentOut`) and/or the animation\n * has `autoOrient` set. Animation-level helper; works for either the body\n * `transform` slot or a composite per-part `translate` slot.\n * @internal\n */\nexport function propAnimIsMotionPath(anim: PxPropertyAnimation): boolean {\n const kfs: Array<PxAnyKeyframe> | undefined = anim.keyframes;\n if (!Array.isArray(kfs)) return false;\n if (anim.autoOrient) return true;\n for (const kf of kfs) {\n if (keyframeTangentIn(kf) || keyframeTangentOut(kf)) return true;\n }\n return false;\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Segment cache — shared by `evaluateMotionPathSegment` (frames-mode kernel)\n// and the materializer. Keyed by FROM-keyframe identity (WeakMap), so cache\n// entries vanish automatically when keyframes are replaced.\n// ─────────────────────────────────────────────────────────────────────────────\n\n\ninterface MotionPathSegmentCache {\n readonly P0: Point2;\n readonly P1: Point2;\n readonly P2: Point2;\n readonly P3: Point2;\n readonly lut: ArcLengthLUT;\n readonly totalArc: number;\n}\n\n// Keyed on the PAIR, not on `prevKf` alone: one keyframe can start different segments\n// across evaluations (a lookup that falls outside the keyframe range answers with a\n// first→last pair), and a `prevKf`-only key let that bogus segment's Bezier be served\n// for every later evaluation of the real `prevKf`→next segment.\nconst _segmentCache = new WeakMap<PxAnyKeyframe, WeakMap<PxAnyKeyframe, MotionPathSegmentCache>>();\n\nfunction getSegmentCache(\n prevKf: PxAnyKeyframe,\n nextKf: PxAnyKeyframe,\n prevPos: Point2,\n nextPos: Point2,\n): MotionPathSegmentCache {\n let byNext = _segmentCache.get(prevKf);\n const existing = byNext?.get(nextKf);\n if (existing) return existing;\n const to = keyframeTangentOut(prevKf);\n const ti = keyframeTangentIn(nextKf);\n const P1: Point2 = [prevPos[0] + (to ? to[0] : 0), prevPos[1] + (to ? to[1] : 0)];\n const P2: Point2 = [nextPos[0] + (ti ? ti[0] : 0), nextPos[1] + (ti ? ti[1] : 0)];\n const lut = bezier2D_arcLengthLUT(prevPos, P1, P2, nextPos);\n const entry: MotionPathSegmentCache = {\n P0: prevPos, P1, P2, P3: nextPos,\n lut,\n totalArc: lut.ds[lut.ds.length - 1],\n };\n if (!byNext) { byNext = new WeakMap<PxAnyKeyframe, MotionPathSegmentCache>(); _segmentCache.set(prevKf, byNext); }\n byNext.set(nextKf, entry);\n return entry;\n}\n\n/** Test helper. No-op in production (WeakMap; entries self-evict). Tests\n * should use fresh keyframe objects to force cache misses. */\nexport function _resetMotionPathSegmentCache(): void {\n // Intentionally empty — kept for backwards-compat with any callers.\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Frames-mode kernel — preserved for any caller that still wants parametric\n// evaluation. The binding pipeline no longer needs it (motion-path is\n// materialized at `normalizeBindings` time), but it's a useful primitive\n// on its own.\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n/** @internal */\nexport interface MotionPathSample {\n /** Translate at the current time (motion-path arc-length-parametrised). */\n readonly translate: Point2;\n /** Auto-orient rotation in degrees (only when `autoOrient` is set). */\n readonly rotateDeg?: number;\n}\n\n/**\n * Evaluates the motion-path position (and optional auto-orient rotation) for a\n * single segment kf[i] → kf[i+1], given local progress already remapped to\n * `[0, 1]` and eased. Builds (or reuses cached) Bezier control points\n * `P1 = P0 + tangentOut`, `P2 = P3 + tangentIn`, maps `localProgress` to arc\n * length, then to curve parameter `t` via the arc-length LUT.\n * @internal\n */\nexport function evaluateMotionPathSegment(\n prevKf: PxKeyframe,\n nextKf: PxKeyframe,\n prevPos: Point2,\n nextPos: Point2,\n localProgress: number,\n autoOrient: boolean,\n): MotionPathSample {\n const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);\n const t = seg.totalArc === 0\n ? localProgress\n : tFromArcFraction(seg.lut, localProgress);\n const point = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, t);\n if (!autoOrient) return { translate: point };\n const tan = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, t);\n const rotateDeg = Math.atan2(tan[1], tan[0]) * 180 / Math.PI;\n return { translate: point, rotateDeg };\n}\n\nfunction tFromArcFraction(lut: ArcLengthLUT, arcFrac: number): number {\n const total = lut.ds[lut.ds.length - 1];\n // Binary search on `ds` for `arcFrac * total` (mirrors bezier2D_tForDistance).\n const target = arcFrac * total;\n const { ts, ds } = lut;\n const last = ds.length - 1;\n if (target <= 0) return ts[0];\n if (target >= ds[last]) return ts[last];\n let lo = 1, hi = last;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (ds[mid] < target) lo = mid + 1;\n else hi = mid;\n }\n const dPrev = ds[hi - 1];\n const span = ds[hi] - dPrev;\n const frac = span > 0 ? (target - dPrev) / span : 0;\n return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public API — materialize parametric motion-path into plain transform kfs\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n/** Sampling configuration shared by `materializeMotionPathInPropAnim` + `materializeMotionPathsInTree`. @internal */\nexport interface MotionPathMaterializationOptions {\n /** Max chord-to-curve deviation per sub-interval, in user units (default 0.5). */\n flatnessTolerance?: number;\n /** Max rotation delta (in degrees) between adjacent samples when `autoOrient`\n * is set (default 5). */\n rotationTolerance?: number;\n /** Hard cap on samples per original segment; prevents runaway recursion on\n * pathological inputs (default 32). */\n maxSamplesPerSegment?: number;\n}\n\nconst DEFAULT_FLATNESS_TOL = 0.5;\nconst DEFAULT_ROTATION_TOL = 5;\nconst DEFAULT_MAX_SAMPLES = 32;\n\n\n/**\n * Converts ONE animated property to sampled transform kfs.\n *\n * Returns a NEW `PxPropertyAnimation` whose `kfs` are flat\n * `{ translate, rotate? }` records placed at curve extrema and adaptive\n * bisection points. Positions are arc-length-parametrised; easing is split via\n * De Casteljau so per-sub-segment easings reproduce the input easing exactly.\n * Original `loop` is carried across; `autoOrient` and per-kf `tangentIn` /\n * `tangentOut` are consumed.\n *\n * Returns the input unchanged (by reference) when it's not a motion-path\n * animation — callers can blindly run it through every propAnim.\n * @internal\n */\nexport function materializeMotionPathInPropAnim(\n anim: PxPropertyAnimation,\n opts?: MotionPathMaterializationOptions,\n): PxPropertyAnimation {\n if (!propAnimIsMotionPath(anim)) return anim;\n const kfs = anim.keyframes as Array<PxAnyKeyframe> | undefined;\n if (!Array.isArray(kfs) || kfs.length < 2) return anim;\n\n const autoOrient = !!anim.autoOrient;\n const flatnessTol = opts?.flatnessTolerance ?? DEFAULT_FLATNESS_TOL;\n const rotationTol = opts?.rotationTolerance ?? DEFAULT_ROTATION_TOL;\n const maxSamples = opts?.maxSamplesPerSegment ?? DEFAULT_MAX_SAMPLES;\n\n const out: Array<PxNormalizedKeyframe> = [];\n\n // First output kf — translate from input; rotate from segment-0 derivative\n // at t=0 if autoOrient. All other transform parts (`origin`, `scale`, an\n // explicit `rotate` when `autoOrient` is false, …) come from kfs[0].value.\n const firstPos = getKfTranslate(kfs[0]);\n if (!firstPos) return anim;\n const firstRotate = autoOrient ? derivAngleForFirstKf(kfs[0], kfs[1]) : undefined;\n out.push(makeOutKf(\n getKfTime(kfs[0]),\n buildOutKfValue(getKfValueParts(kfs[0]), getKfValueParts(kfs[0]), 0, firstPos, firstRotate, autoOrient),\n ));\n\n for (let i = 0; i < kfs.length - 1; i++) {\n const prevKf = kfs[i];\n const nextKf = kfs[i + 1];\n const prevPos = getKfTranslate(prevKf);\n const nextPos = getKfTranslate(nextKf);\n if (!prevPos || !nextPos) {\n // Skip undefined translate kfs — just push next as-is.\n out.push(makeOutKf(\n getKfTime(nextKf),\n buildOutKfValue(getKfValueParts(nextKf), getKfValueParts(nextKf), 1, nextPos ?? [0, 0], undefined, autoOrient),\n ));\n continue;\n }\n\n // Sharp-corner step: between adjacent segments, the prev-segment's\n // exit tangent angle (already on out[last]) can differ from this\n // segment's entry tangent angle (deriv at t=0 of the segment about to\n // start) by a lot — e.g. a rectangular path has 90° steps at each\n // corner. Linear interp from prev-exit to the FAR-END kf of this\n // segment would slide rotation through the whole segment instead of\n // stepping at the boundary. Fix: insert a duplicate kf at the\n // boundary time carrying this segment's entry angle. With `e=undef`\n // on the prior kf, the engines render an instant step boundary then\n // constant rotation through the segment.\n if (autoOrient && i > 0) {\n insertSharpCornerStepKfIfNeeded(out, prevKf, nextKf, prevPos, nextPos, rotationTol);\n }\n\n materializeSegment(out, prevKf, nextKf, prevPos, nextPos, autoOrient, flatnessTol, rotationTol, maxSamples);\n }\n\n // The very last out-kf inherits the original last kf's `easing` (which\n // applies to the NEXT segment after this animation, or nothing — but the\n // wire format preserves it regardless).\n const lastInE = getKfEasing(kfs[kfs.length - 1]);\n if (lastInE) out[out.length - 1].e = lastInE;\n\n // Unwrap autoOrient rotations so linear interp between samples doesn't\n // take the long way around the circle. atan2 returns in (-180°, 180°];\n // a tangent rotating slowly across the +X axis (e.g. -179° → +179°)\n // would otherwise be lerp'd as a ~358° spin instead of a ~2° step.\n if (autoOrient) unwrapAutoOrientRotations(out);\n\n // Output converges on `keyframes` — the `kfs` alias is gone (review §1.2/§6.1).\n const result: PxPropertyAnimation = { keyframes: out };\n if (anim.loop !== undefined) (result as { loop?: unknown }).loop = anim.loop;\n return result;\n}\n\n\n/** Walks `kfs` in order; for each kf with a `rotate` value, shifts it by ±360°\n * multiples so the delta from the previous kf's rotate stays within ±180°.\n * Linear interpolation between consecutive samples then always takes the\n * shorter arc. The accumulated shift means a continuously-rotating element\n * may end up with rotate values well outside [-180°, 180°], which is fine —\n * CSS / SVG rotation accepts any range. Exported — the glyph along-path baker\n * (`buildAnimatedAlongPath`) needs the identical seam fix for its sampled\n * per-glyph tangent rotations. */\nexport function unwrapAutoOrientRotations(kfs: Array<PxAnyKeyframe>): void {\n let prev: number | undefined;\n for (const kf of kfs) {\n const v = keyframeValue(kf) as { rotate?: number } | undefined;\n if (!v || typeof v.rotate !== 'number') continue;\n if (prev === undefined) { prev = v.rotate; continue; }\n let r = v.rotate;\n while (r - prev > 180) r -= 360;\n while (r - prev < -180) r += 360;\n v.rotate = r;\n prev = r;\n }\n}\n\n\nfunction makeOutKf(time: number, value: PxTransformParts): PxNormalizedKeyframe {\n return { t: time, v: value };\n}\n\n/** Reads the kf's value-as-parts (object form). Returns `undefined` if the kf\n * has no value or it's an array form (single-part composite). */\nfunction getKfValueParts(kf: PxAnyKeyframe): PxTransformParts | undefined {\n const v = keyframeValue(kf);\n if (!v || typeof v !== 'object' || Array.isArray(v)) return undefined;\n return v as PxTransformParts;\n}\n\n/** Linearly interpolates one transform-part value (number / PxVec2). Returns the\n * non-undefined input when only one side is present, falls back to `prev`\n * for unsupported types. */\nfunction interpolatePart(prev: unknown, next: unknown, p: number): unknown {\n if (prev === undefined) return next;\n if (next === undefined) return prev;\n if (typeof prev === 'number' && typeof next === 'number') {\n return prev + (next - prev) * p;\n }\n if (Array.isArray(prev) && Array.isArray(next) && prev.length === next.length) {\n const out: Array<number> = new Array(prev.length);\n for (let i = 0; i < prev.length; i++) {\n const a = typeof prev[i] === 'number' ? prev[i] : 0;\n const b = typeof next[i] === 'number' ? next[i] : 0;\n out[i] = a + (b - a) * p;\n }\n return out;\n }\n return p < 0.5 ? prev : next;\n}\n\n/** Builds the value-record for one sampled output kf. `translate` overrides\n * whatever the per-part interpolation would have produced (motion path is the\n * source of truth for position); `rotateDegFromAutoOrient`, when defined, is\n * ADDED on top of the (interpolated) explicit animated `rotate` — auto-orient\n * and a user-set rotation compose, matching After Effects / Lottie semantics.\n * All OTHER parts present on `prevV` / `nextV` (origin, scale, etc.) are\n * interpolated at the eased arc-progress `p` — mirroring the frames-mode\n * `calcPropertyValue` loop. */\nfunction buildOutKfValue(\n prevV: PxTransformParts | undefined,\n nextV: PxTransformParts | undefined,\n p: number,\n translate: Point2,\n rotateDegFromAutoOrient: number | undefined,\n autoOrient: boolean,\n): PxTransformParts {\n const value: { [k: string]: unknown } = { translate };\n const keys = new Set<string>();\n if (prevV) for (const k of Object.keys(prevV)) keys.add(k);\n if (nextV) for (const k of Object.keys(nextV)) keys.add(k);\n for (const k of keys) {\n if (k === 'translate') continue; // overridden by motion path\n if (k === 'rotate' && autoOrient) continue; // composed below with autoOrient\n const pv = (prevV as Record<string, unknown> | undefined)?.[k];\n const nv = (nextV as Record<string, unknown> | undefined)?.[k];\n if (pv === undefined && nv === undefined) continue;\n value[k] = interpolatePart(pv, nv, p);\n }\n if (rotateDegFromAutoOrient !== undefined) {\n // Sum the auto-orient tangent angle with the element's own animated\n // rotation (interpolated at `p`, 0 when absent) rather than discarding it.\n value.rotate = rotateDegFromAutoOrient + explicitRotateAt(prevV, nextV, p);\n }\n return value as PxTransformParts;\n}\n\n/** Interpolated explicit `rotate` from a transform-parts pair at arc-progress\n * `p`; 0 when neither side carries a numeric rotate. */\nfunction explicitRotateAt(\n prevV: PxTransformParts | undefined,\n nextV: PxTransformParts | undefined,\n p: number,\n): number {\n const pv = typeof prevV?.rotate === 'number' ? prevV.rotate : undefined;\n const nv = typeof nextV?.rotate === 'number' ? nextV.rotate : undefined;\n if (pv === undefined && nv === undefined) return 0;\n const r = interpolatePart(pv, nv, p);\n return typeof r === 'number' ? r : 0;\n}\n\n\n/** Derivative angle at t=0 of segment kf[0] → kf[1]. Used to seed the first\n * output kf's rotation; without this the very first frame would render with\n * no rotation while every subsequent sample has one. */\nfunction derivAngleForFirstKf(kf0: PxAnyKeyframe, kf1: PxAnyKeyframe): number {\n const p0 = getKfTranslate(kf0);\n const p1 = getKfTranslate(kf1);\n if (!p0 || !p1) return 0;\n const seg = getSegmentCache(kf0, kf1, p0, p1);\n const tan = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, 0);\n return Math.atan2(tan[1], tan[0]) * 180 / Math.PI;\n}\n\n\n/** Shortest signed difference of `a − b` wrapped into (-180°, 180°]. */\nfunction wrappedAngleDelta(a: number, b: number): number {\n let d = a - b;\n while (d > 180) d -= 360;\n while (d < -180) d += 360;\n return d;\n}\n\n\n/**\n * Appends a \"step\" kf to `out` at the boundary time iff the next segment's\n * entry tangent direction differs from the last emitted kf's rotation by more\n * than `rotationTol`. The new kf carries the SAME translate / origin / scale\n * as the boundary kf already in `out` (continuous in space), but a different\n * `rotate` — making engines render an instant rotation snap at the boundary\n * rather than linearly sliding rotation across the whole next segment.\n *\n * Called between `materializeSegment` calls. The boundary kf already in `out`\n * keeps its outgoing easing as undefined (no easing between the two duplicates\n * — the step is instant); the next `materializeSegment` will then attach its\n * sequential-split easing to the newly-inserted duplicate, so the per-sample\n * easing on segment `i+1` works exactly as before.\n */\nfunction insertSharpCornerStepKfIfNeeded(\n out: Array<PxNormalizedKeyframe>,\n prevKf: PxAnyKeyframe, nextKf: PxAnyKeyframe,\n prevPos: Point2, nextPos: Point2,\n rotationTol: number,\n): void {\n const lastKf = out[out.length - 1];\n const lastV = keyframeValue(lastKf) as { rotate?: number } | undefined;\n const prevExit = lastV?.rotate;\n if (typeof prevExit !== 'number') return;\n\n // `prevExit` is the SUMMED rotation (tangent + explicit). Compare pure\n // tangent-to-tangent by subtracting the boundary kf's explicit rotate,\n // which is shared by both adjoining segments at this shared keyframe.\n const boundaryV = getKfValueParts(prevKf);\n const prevExitTangent = prevExit - explicitRotateAt(boundaryV, boundaryV, 0);\n\n const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);\n const tanAtStart = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, 0);\n const nextEntry = Math.atan2(tanAtStart[1], tanAtStart[0]) * 180 / Math.PI;\n const delta = wrappedAngleDelta(nextEntry, prevExitTangent);\n if (Math.abs(delta) <= rotationTol) return; // continuous within tolerance\n\n // Step kf carrying the new entry angle, a hair AFTER the boundary — the boundary\n // time itself must render the PREVIOUS segment's exit pose. A duplicate at the exact\n // boundary time made CSS/WAAPI resolve the LATER keyframe there, so scrubbing to\n // exactly the corner showed the next segment's angle while the editor (and the\n // parametric frames engine, whose segment lookup treats a boundary as the END of the\n // segment before it) showed the previous one. The offset is far below a frame, so\n // playback still reads as an instant step at the corner.\n const prevTime = getKfTime(prevKf);\n const stepTime = Math.min(prevTime + CORNER_STEP_AFTER_BOUNDARY_MS, (prevTime + getKfTime(nextKf)) / 2);\n const dupValue = buildOutKfValue(\n getKfValueParts(prevKf), getKfValueParts(prevKf), 0,\n prevPos, nextEntry, true,\n );\n out.push(makeOutKf(stepTime, dupValue));\n}\n\n/** How far past a sharp corner the entry-angle step kf sits (ms). Small enough to be\n * invisible in playback (a frame is ~16 ms) — and strictly under the 0.1 ms step-width\n * budget the corner-step CSS spec asserts — while still a distinct WAAPI offset. */\nconst CORNER_STEP_AFTER_BOUNDARY_MS = 0.05;\n\n\n/** Materializes a single segment into `out`. Appends one kf per sample (interior\n * critical/adaptive points + the next-kf endpoint), with positions, optional\n * rotations, and split easings. */\nfunction materializeSegment(\n out: Array<PxNormalizedKeyframe>,\n prevKf: PxKeyframe, nextKf: PxKeyframe,\n prevPos: Point2, nextPos: Point2,\n autoOrient: boolean,\n flatnessTol: number, rotationTol: number, maxSamples: number,\n): void {\n const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);\n const prevTime = getKfTime(prevKf);\n const nextTime = getKfTime(nextKf);\n const prevEasing = getKfEasing(prevKf);\n const invertFn = invertEasing(prevEasing);\n const prevV = getKfValueParts(prevKf);\n const nextV = getKfValueParts(nextKf);\n\n // 1. Critical t-values: axis extrema + endpoints.\n const interiorTs = computeSampleTs(seg, autoOrient, flatnessTol, rotationTol, maxSamples);\n // `interiorTs` is the list of t in (0, 1] in ascending order, ending with t=1.\n\n // 2. For each sample t, compute position, rotation, the eased arc-fraction\n // `p` (= the value frames-mode would use for non-translate part interp),\n // and the linear-time fraction `u` (via invertEasing of `p`).\n interface Sample { u: number; p: number; pos: Point2; rotateDeg?: number; }\n const samples: Array<Sample> = [];\n for (const t of interiorTs) {\n const arc = bezier2D_arcAtT(seg.lut, t);\n const p = clamp(seg.totalArc > 0 ? arc / seg.totalArc : t, 0, 1);\n const u = clamp(invertFn(p), 0, 1);\n const pos = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, t);\n const sample: Sample = { u, p, pos };\n if (autoOrient) {\n const tan = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, t);\n sample.rotateDeg = Math.atan2(tan[1], tan[0]) * 180 / Math.PI;\n }\n samples.push(sample);\n }\n\n // 3. Sequential easing split. The easing for sub-segment k (out[L+k-1] → out[L+k])\n // is the `left` half of splitting the still-unconsumed easing at\n // `(u_k - u_{k-1}) / (1 - u_{k-1})`.\n let remaining = prevEasing;\n let prevU = 0;\n const startIdx = out.length - 1; // out[startIdx] is the prev kf — it owns the FIRST sub-easing.\n for (let i = 0; i < samples.length; i++) {\n const s = samples[i];\n const xFrac = prevU < 1 ? clamp((s.u - prevU) / (1 - prevU), 0, 1) : 1;\n const { left, right } = splitEasing(remaining, xFrac);\n\n // Assign `left` as the outgoing easing of the kf at the end of `out`\n // (which is the kf that PRECEDES this sub-kf — the prev kf for i=0,\n // or the previously-emitted sub-kf for i>0).\n const ownerIdx = i === 0 ? startIdx : out.length - 1;\n if (left) out[ownerIdx].e = left;\n else delete out[ownerIdx].e;\n\n const tGlobal = prevTime + s.u * (nextTime - prevTime);\n const value = buildOutKfValue(prevV, nextV, s.p, s.pos, s.rotateDeg, autoOrient);\n out.push(makeOutKf(tGlobal, value));\n\n remaining = right;\n prevU = s.u;\n }\n}\n\n\n/** Returns t-values in (0, 1], sorted ascending, ending with t=1. The list\n * always includes the input segment's axis extrema interior to (0, 1), plus\n * adaptive bisection samples wherever the chord deviates from the curve by\n * more than `flatnessTol` (or the rotation delta exceeds `rotationTol` for\n * autoOrient). Capped at `maxSamples`. */\nfunction computeSampleTs(\n seg: MotionPathSegmentCache, autoOrient: boolean,\n flatnessTol: number, rotationTol: number, maxSamples: number,\n): Array<number> {\n // Axis extrema (interior to (0, 1)).\n const extremes: Array<number> = [];\n addAxisExtremes(seg.P0[0], seg.P1[0], seg.P2[0], seg.P3[0], extremes);\n addAxisExtremes(seg.P0[1], seg.P1[1], seg.P2[1], seg.P3[1], extremes);\n extremes.sort((a, b) => a - b);\n\n const critical: Array<number> = [0];\n for (const t of extremes) {\n if (t > critical[critical.length - 1] + 1e-6 && t < 1 - 1e-6) {\n critical.push(t);\n }\n }\n critical.push(1);\n\n const out: Array<number> = [];\n const budget = { remaining: maxSamples - critical.length }; // already-committed critical points count against the budget\n for (let i = 0; i < critical.length - 1; i++) {\n bisect(critical[i], critical[i + 1], out, seg, autoOrient, flatnessTol, rotationTol, budget);\n }\n return out;\n}\n\n\n/** Solves `P'(t).axis = 0` for one axis (a quadratic in t). Pushes any real\n * roots in `(0, 1)` into `out`. Coefficients via standard cubic Bezier\n * derivative: with `a = p1 − p0, b = p2 − p1, c = p3 − p2`, the equation is\n * `(a − 2b + c)·t² + 2(b − a)·t + a = 0`. */\nfunction addAxisExtremes(p0: number, p1: number, p2: number, p3: number, out: Array<number>): void {\n const a = p1 - p0;\n const b = p2 - p1;\n const c = p3 - p2;\n const A = a - 2 * b + c;\n const B = 2 * (b - a);\n const C = a;\n if (Math.abs(A) < 1e-10) {\n if (Math.abs(B) > 1e-10) {\n const t = -C / B;\n if (t > 1e-6 && t < 1 - 1e-6) out.push(t);\n }\n return;\n }\n const disc = B * B - 4 * A * C;\n if (disc < 0) return;\n const sq = Math.sqrt(disc);\n const t1 = (-B - sq) / (2 * A);\n const t2 = (-B + sq) / (2 * A);\n if (t1 > 1e-6 && t1 < 1 - 1e-6) out.push(t1);\n if (t2 > 1e-6 && t2 < 1 - 1e-6) out.push(t2);\n}\n\n\n/** Adaptive bisection between `tA` and `tB`. Always appends `tB` exactly once\n * (either directly when the chord is flat enough, or via recursion).\n *\n * Flatness is tested at THREE interior points (t = 0.25, 0.5, 0.75 of the\n * sub-interval), not just the midpoint. Symmetric Bezier segments often have\n * the curve crossing the chord exactly at t=0.5 — a single-midpoint check\n * would mistake that for flatness and skip subdivision, leaving visible\n * chord deviation between samples. */\nfunction bisect(\n tA: number, tB: number,\n out: Array<number>,\n seg: MotionPathSegmentCache,\n autoOrient: boolean,\n flatnessTol: number, rotationTol: number,\n budget: { remaining: number },\n): void {\n const tMid = (tA + tB) / 2;\n const pA = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tA);\n const pB = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tB);\n const span = tB - tA;\n const p25 = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tA + span * 0.25);\n const p50 = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tMid);\n const p75 = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tA + span * 0.75);\n\n const dev = Math.max(\n perpDist(p25, pA, pB),\n perpDist(p50, pA, pB),\n perpDist(p75, pA, pB),\n );\n let rotOk = true;\n if (autoOrient) {\n const tanA = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, tA);\n const tanB = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, tB);\n const angA = Math.atan2(tanA[1], tanA[0]) * 180 / Math.PI;\n const angB = Math.atan2(tanB[1], tanB[0]) * 180 / Math.PI;\n let delta = Math.abs(angA - angB);\n if (delta > 180) delta = 360 - delta;\n if (delta > rotationTol) rotOk = false;\n }\n\n if ((dev <= flatnessTol && rotOk) || budget.remaining <= 0 || span < 1e-6) {\n out.push(tB);\n return;\n }\n\n budget.remaining -= 1;\n bisect(tA, tMid, out, seg, autoOrient, flatnessTol, rotationTol, budget);\n bisect(tMid, tB, out, seg, autoOrient, flatnessTol, rotationTol, budget);\n}\n\n\nfunction perpDist(q: Point2, pA: Point2, pB: Point2): number {\n const dx = pB[0] - pA[0];\n const dy = pB[1] - pA[1];\n const len2 = dx * dx + dy * dy;\n if (len2 < 1e-20) {\n const qdx = q[0] - pA[0];\n const qdy = q[1] - pA[1];\n return Math.sqrt(qdx * qdx + qdy * qdy);\n }\n const cross = (q[0] - pA[0]) * dy - (q[1] - pA[1]) * dx;\n return Math.abs(cross) / Math.sqrt(len2);\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tree walker — applies `materializeMotionPathInPropAnim` to every `node.animate.transform`\n// whose propAnim is a motion-path. Immutable: returns the input by reference\n// when no changes were needed; otherwise clones along the path to each\n// converted node and shares all other sub-trees.\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n/** @internal */\nexport function materializeMotionPathsInTree(\n root: PxNode,\n opts?: MotionPathMaterializationOptions,\n): PxNode {\n const out = walkAndMaterialize(root, opts);\n return out ?? root;\n}\n\nfunction walkAndMaterialize(node: PxNode, opts?: MotionPathMaterializationOptions): PxNode | null {\n let newChildren: Array<PxNode> | undefined;\n if (node.children) {\n for (let i = 0; i < node.children.length; i++) {\n const ch = node.children[i];\n const ret = walkAndMaterialize(ch, opts);\n if (ret !== null) {\n if (!newChildren) newChildren = node.children.slice();\n newChildren[i] = ret;\n }\n }\n }\n\n let newAnimate: Record<string, PxPropertyAnimation> | undefined;\n const animBucket = node.animate;\n if (animBucket && typeof animBucket === 'object' && !Array.isArray(animBucket)) {\n const animDef = animBucket as Record<string, PxPropertyAnimation>;\n const transformAnim = animDef.transform;\n if (transformAnim && typeof transformAnim === 'object' && propAnimIsMotionPath(transformAnim)) {\n const materialized = materializeMotionPathInPropAnim(transformAnim, opts);\n if (materialized !== transformAnim) {\n newAnimate = { ...animDef, transform: materialized };\n }\n }\n }\n\n if (!newChildren && !newAnimate) return null;\n const cloned: PxNode = { ...node };\n if (newChildren) cloned.children = newChildren;\n if (newAnimate) cloned.animate = newAnimate as PxNode['animate'];\n return cloned;\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { type PxAnimatedSvgDocument, type PxAnimationDefinition, type PxBezierPath, type PxNormalizedBinding, type PxBinding, type PxDefinitions, type PxElementAnimation, type PxKeyframe, type PxNormalizedKeyframe, type PxLoop, type PxNode, type PxPropertyAnimation, type PxTransformParts, keyframeTime, keyframeValue, keyframeEasing, keyframeTangentIn, keyframeTangentOut } from '../format/PxAnimatorTypes';\nimport { getBindings, getDefinitions, TRANSFORM_ATTR } from '../format/PxAnimatorConstants';\nimport { getAnimatorConfig, PxTimelineEngine, PxLoopDirection, PxLoopRepeatAt } from '../format/PxAnimatorConstants';\nimport { bezierToSvgPath, camelCaseToKebabWordIfNeeded, clamp, PX_COLOR_ATTR_NAMES, composeTransformParts, cubicBezier, interpolateBeziers, interpolateColor, interpolateNum, interpolateVec, isCamelCaseWord, parseColor, parseTransformParts, PX_PCT_BASED_ATTR_NAMES, remap, reverseEasing, splitEasing, toRGBA, PX_TRANSFORM_FN_NAMES } from '../util/PxAnimatorUtil';\nimport { evaluateMotionPathSegment, materializeMotionPathInPropAnim, propAnimIsMotionPath } from '../materialize/PxMotionPath';\n\n/**\n * Time separation between a cycle's snap-back keyframe and the previous repetition's\n * end, in ms.\n *\n * SINGLE SOURCE OF TRUTH — the editor imports this and converts to its own frame unit\n * (`TLoop.smallFrameShift = PX_LOOP_JUMP_SHIFT_MS / FRAME_DURATION_MS`), so the two sides\n * cannot drift apart and materialize different keyframes (B7).\n *\n * 1ms, not one 10ms editor frame: a 10ms snap-back is long enough to read as a visible\n * jump in a looping animation (confirmed visually). The gap only has to be non-zero — it\n * exists so the snap-back is a discontinuity rather than an interpolated tween.\n *\n * ⚠️ The editor works in FRAMES (`FRAME_DURATION_MS = 10`), so this is a sub-frame shift\n * there. `TKeyframeGroup` rounds keyframe times to integer frames in some paths; the\n * editor side keeps the fractional value and must not be re-clamped to a whole frame.\n * @internal\n */\nexport const PX_LOOP_JUMP_SHIFT_MS = 1;\n\n/** Structural equality for keyframe values (numbers, arrays, transform-part records). */\nfunction deepEqualValue(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b || a === null || b === null || typeof a !== 'object') return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n return ka.every(k => deepEqualValue((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]));\n}\n\n\n\n// ============================================================================\n// PATH PARSING: Convert \"path(...)\" strings to PxBezierPath format\n// ============================================================================\n\ninterface PathCommand {\n type: string;\n values: Array<number>;\n}\n\n/**\n * Parses an SVG path string into command tokens.\n * Supports M, L, C, Z commands (case-insensitive).\n */\nfunction parsePathCommands(d: string): Array<PathCommand> {\n const tokens = d.split(/([MLCZmlcz]|[\\s,]+)/).map(t => t.trim()).filter(t => t && t !== ',');\n\n const commands: Array<PathCommand> = [];\n let currentCommand: PathCommand | null = null;\n\n for (const token of tokens) {\n if (/[MLCZmlcz]/.test(token)) {\n currentCommand = { type: token, values: [] };\n commands.push(currentCommand);\n } else if (currentCommand) {\n const value = +token;\n currentCommand.values.push(Number.isNaN(value) ? 0 : value);\n }\n }\n\n return commands;\n}\n\n/**\n * Parses an internal SVG path string into PxBezierPath array.\n * Handles M (moveto), L (lineto), C (curveto), Z (close) commands.\n */\nexport function parseSvgPathToBezier(d: string): Array<PxBezierPath> {\n const res: Array<PxBezierPath> = [];\n let currentPath: PxBezierPath | undefined;\n\n const commands = parsePathCommands(d);\n\n for (const command of commands) {\n const type = command.type;\n const values = command.values;\n\n if (type === 'M' || type === 'm') {\n const x = values[0] || 0;\n const y = values[1] || 0;\n currentPath = {\n v: [[x, y]],\n i: [[x, y]],\n o: [[x, y]],\n c: false\n };\n res.push(currentPath);\n continue;\n }\n\n // Ensure we have a current path\n if (!currentPath) {\n currentPath = {\n v: [[0, 0]],\n i: [[0, 0]],\n o: [[0, 0]],\n c: false\n };\n res.push(currentPath);\n }\n\n if (type === 'L') {\n const x = values[0] || 0;\n const y = values[1] || 0;\n currentPath.v.push([x, y]);\n currentPath.i!.push([x, y]);\n currentPath.o!.push([x, y]);\n\n } else if (type === 'C') {\n const outX = values[0] || 0;\n const outY = values[1] || 0;\n const inX2 = values[2] || 0;\n const inY2 = values[3] || 0;\n const x2 = values[4] || 0;\n const y2 = values[5] || 0;\n\n // Update out-point of previous vertex\n currentPath.o![currentPath.o!.length - 1] = [outX, outY];\n\n // Add new vertex with its in-point\n currentPath.v.push([x2, y2]);\n currentPath.i!.push([inX2, inY2]);\n currentPath.o!.push([x2, y2]);\n\n } else if (type === 'Z' || type === 'z') {\n currentPath.c = true;\n\n } else {\n console.warn('Unsupported path command \"' + type + '\"');\n }\n }\n\n return res;\n}\n\n/**\n * Extracts SVG path data from a string.\n * Handles both \"path(M...)\" wrapper format and raw \"M...\" format.\n * @returns The path data string, or undefined if not a valid path string\n */\nfunction extractPathData(str: string): string | undefined {\n if (str.startsWith('path(') && str.endsWith(')')) {\n return str.slice(5, -1); // Remove \"path(\" and \")\"\n }\n // Raw path string starting with a path command (M, m, or other commands)\n if (/^[MmZzLlHhVvCcSsQqTtAa]/.test(str)) {\n return str;\n }\n return undefined;\n}\n\n/**\n * Checks if the value is a path string (either \"path(...)\" or raw \"M...\").\n */\nfunction isPathString(value: any): value is string {\n return typeof value === 'string' && extractPathData(value) !== undefined;\n}\n\n/**\n * Normalizes a 'd' attribute value to { paths: PxBezierPath[] } format.\n * Handles:\n * - { pathData: \"M...\" } / { pathData: \"path(...)\" } -> { paths: [PxBezierPath] } (THE wire form)\n * - { paths: [\"path(...)\"] } -> { paths: [PxBezierPath] }\n * - { paths: [\"M...\"] } -> { paths: [PxBezierPath] }\n * - [\"path(...)\"] -> { paths: [PxBezierPath] }\n * - [\"M...\"] -> { paths: [PxBezierPath] }\n * - \"path(...)\" -> { paths: [PxBezierPath] }\n * - \"M...\" -> { paths: [PxBezierPath] }\n * - { paths: [PxBezierPath] } -> as-is\n */\nfunction normalizePathValue(value: any): { paths: Array<PxBezierPath> } | any {\n // THE wire form: { pathData: \"M...\" } (compound = multiple `M…` sub-paths in one string).\n // The `{ paths: [...] }` shapes below are this function's OWN output, not wire spellings.\n if (value && typeof value === 'object' && typeof value.pathData === 'string') {\n const d = extractPathData(value.pathData);\n return d ? { paths: parseSvgPathToBezier(d) } : value;\n }\n\n // If already in { paths: [...] } format\n if (value && typeof value === 'object' && 'paths' in value) {\n const pathsArray = value.paths;\n if (Array.isArray(pathsArray) && pathsArray.length > 0) {\n // Check if paths contain path strings that need parsing\n if (isPathString(pathsArray[0])) {\n const paths: Array<PxBezierPath> = [];\n for (const pathStr of pathsArray) {\n const d = extractPathData(pathStr);\n if (d) {\n paths.push(...parseSvgPathToBezier(d));\n }\n }\n return { paths };\n }\n }\n // Already in correct format\n return value;\n }\n\n // If it's an array of path strings\n if (Array.isArray(value)) {\n if (value.length > 0 && isPathString(value[0])) {\n const paths: Array<PxBezierPath> = [];\n for (const pathStr of value) {\n const d = extractPathData(pathStr);\n if (d) {\n paths.push(...parseSvgPathToBezier(d));\n }\n }\n return { paths };\n }\n // Already an array of PxBezierPath - wrap in { paths: }\n return { paths: value };\n }\n\n // If it's a single path string\n if (isPathString(value)) {\n const d = extractPathData(value)!;\n return { paths: parseSvgPathToBezier(d) };\n }\n\n return value;\n}\n\n\n// ============================================================================\n// NORMALIZATION: Convert new API format to internal normalized format\n// ============================================================================\n\n/**\n * Resolves an easing reference to a cubic-bezier array.\n * @param easing The easing reference (string name or bezier array)\n * @param defs The definitions containing named easings\n * @returns The resolved cubic-bezier array or undefined\n */\nfunction resolveEasing(\n easing: string | [number, number, number, number] | undefined,\n defs?: PxDefinitions\n): [number, number, number, number] | undefined {\n if (!easing) return undefined;\n\n if (Array.isArray(easing)) {\n return easing;\n }\n\n // Look up named easing in defs\n if (defs?.easings?.[easing]) {\n return defs.easings[easing];\n }\n\n // Unknown easing name - return undefined\n console.warn('Unknown easing name: ' + easing);\n return undefined;\n}\n\n/**\n * Resolves an animation reference to an AnimationDefinition.\n * @param animRef The animation reference (string name or inline definition)\n * @param defs The definitions containing named animations\n * @returns The resolved animation definition\n */\nfunction resolveAnimation(\n animRef: string | PxAnimationDefinition,\n defs?: PxDefinitions\n): PxAnimationDefinition | undefined {\n if (typeof animRef === 'string') {\n // Look up named animation in defs\n const resolved = defs?.animations?.[animRef];\n if (!resolved) {\n console.warn('Unknown animation name: ' + animRef);\n }\n return resolved;\n }\n\n // It's an inline definition\n return animRef;\n}\n\n/**\n * Resolves an element animation (which can be string, array, or inline) to an array of AnimationDefinitions.\n * @param animate The element animation specification\n * @param defs The definitions containing named animations\n * @returns Array of resolved animation definitions\n */\nfunction resolveElementAnimation(\n animate: PxElementAnimation | undefined,\n defs?: PxDefinitions\n): PxAnimationDefinition[] {\n if (!animate) return [];\n\n const results: PxAnimationDefinition[] = [];\n\n if (typeof animate === 'string') {\n const resolved = resolveAnimation(animate, defs);\n if (resolved) results.push(resolved);\n } else if (Array.isArray(animate)) {\n for (const item of animate) {\n const resolved = resolveAnimation(item, defs);\n if (resolved) results.push(resolved);\n }\n } else {\n // It's an inline AnimationDefinition\n results.push(animate);\n }\n\n return results;\n}\n\n// ============================================================================\n// LOOP EXPANSION: Duplicate keyframe segments to fill gaps in the timeline\n// ============================================================================\n\n/**\n * Interpolates between two keyframe values based on property type.\n * Returns the raw interpolated value (not a CSS string).\n *\n * Dispatch order matters — the unified-transform-record branch must run\n * BEFORE the standalone-`rotate` branch, otherwise a `transform`-named\n * animation whose kfs are number-typed (rare but valid) would be routed\n * through the vec path.\n * @internal\n */\nexport function interpolateValue(propName: string, a: any, b: any, t: number): any {\n if (propName === 'd') {\n const aPaths = a?.paths ?? (Array.isArray(a) ? a : []);\n const bPaths = b?.paths ?? (Array.isArray(b) ? b : []);\n return { paths: interpolateBeziers(aPaths, bPaths, t) };\n }\n if (PX_COLOR_ATTR_NAMES.has(propName)) {\n return interpolateColor(a || [0, 0, 0, 1], b || [0, 0, 0, 1], t);\n }\n // Unified-transform record (`{translate, rotate, scale, origin}`) — the\n // most common animated `propName === 'transform'` shape. Per-part interp\n // so `+({}) = NaN` (the old fall-through) doesn't poison the boundary kf\n // at a loop seam.\n if (propName === 'transform'\n && typeof a === 'object' && a !== null && !Array.isArray(a)\n && typeof b === 'object' && b !== null && !Array.isArray(b)\n ) {\n return interpolateTransformParts(a as PxTransformParts, b as PxTransformParts, t);\n }\n // Standalone `rotate` as a propAnim — value is a NUMBER, not a vector.\n // The general PX_TRANSFORM_FN_NAMES branch below would route it through\n // `interpolateVec`, which on a scalar returns `[]` (length NaN → no loop)\n // — wrong CSS output.\n if (propName === 'rotate' && typeof a === 'number' && typeof b === 'number') {\n return interpolateNum(a, b, t);\n }\n if (PX_TRANSFORM_FN_NAMES.has(propName) || propName === 'stroke-dasharray' || propName === 'strokeDasharray') {\n return interpolateVec(a || [], b || [], t);\n }\n return interpolateNum(+(a || 0), +(b || 0), t);\n}\n\n/** Per-part interpolation for a unified-transform parts record. Mirrors the\n * inline logic in `calcPropertyValue`'s transform branch. */\nfunction interpolateTransformParts(a: PxTransformParts, b: PxTransformParts, t: number): PxTransformParts {\n const keys = new Set<string>([...Object.keys(a ?? {}), ...Object.keys(b ?? {})]);\n const out: { [k: string]: unknown } = {};\n for (const k of keys) {\n const av = (a as { [k: string]: unknown })?.[k];\n const bv = (b as { [k: string]: unknown })?.[k];\n if (k === 'rotate' || k === 'skew') {\n out[k] = interpolateNum(+(av ?? 0), +(bv ?? 0), t);\n } else if (k === 'translate' || k === 'scale' || k === 'origin') {\n const fallback: Array<number> = k === 'scale' ? [1, 1] : [0, 0];\n out[k] = interpolateVec((av as Array<number>) || fallback, (bv as Array<number>) || fallback, t);\n } else {\n // Unknown part — prefer next if present, otherwise prev.\n out[k] = bv ?? av;\n }\n }\n return out as PxTransformParts;\n}\n\ninterface LoopTemplateEntry {\n relT: number; // 0..1 relative position within segment\n v: any;\n e: [number, number, number, number] | undefined;\n // Per-vertex spatial tangents (motion-along-path). Carried so repeated\n // segments keep their curvature; reversed reps swap in<->out (see appendRep).\n tangentIn?: [number, number];\n tangentOut?: [number, number];\n}\n\n/**\n * Expands keyframes by repeating a segment to fill the gap between the keyframe\n * range and the global animation duration, implementing PxLoop \"local loop\" behavior.\n */\nfunction expandLoopKeyframes(\n propName: string,\n keyframes: PxNormalizedKeyframe[],\n loop: PxLoop,\n duration: number\n): PxNormalizedKeyframe[] {\n const totalIntervals = keyframes.length - 1;\n const segCount = clamp(loop.segmentCount ?? totalIntervals, 1, totalIntervals);\n\n // Extract segment keyframes\n let segKfs: PxNormalizedKeyframe[];\n if (loop.repeatAt === PxLoopRepeatAt.start) {\n segKfs = keyframes.slice(0, segCount + 1);\n } else {\n segKfs = keyframes.slice(totalIntervals - segCount);\n }\n\n // Determine fill region\n const firstT = keyframes[0].t ?? 0;\n const lastT = keyframes[keyframes.length - 1].t ?? 0;\n\n let fillStart: number, fillEnd: number;\n if (loop.repeatAt === PxLoopRepeatAt.start) {\n fillStart = 0;\n fillEnd = firstT;\n } else {\n fillStart = lastT;\n fillEnd = duration;\n }\n\n const fillDuration = fillEnd - fillStart;\n if (fillDuration <= 0) return keyframes;\n\n // Segment timing\n const segStartT = segKfs[0].t ?? 0;\n const segEndT = segKfs[segKfs.length - 1].t ?? 0;\n const segDuration = segEndT - segStartT;\n if (segDuration <= 0) return keyframes;\n\n // Build template with relative offsets (0..1)\n const template: LoopTemplateEntry[] = segKfs.map(kf => ({\n relT: (kf.t! - segStartT) / segDuration,\n v: kf.v,\n e: kf.e as [number, number, number, number] | undefined,\n tangentIn: keyframeTangentIn(kf) as [number, number] | undefined,\n tangentOut: keyframeTangentOut(kf) as [number, number] | undefined\n }));\n\n const fullReps = Math.floor(fillDuration / segDuration);\n const remainder = fillDuration - fullReps * segDuration;\n const partialFraction = remainder / segDuration;\n\n const looped: PxNormalizedKeyframe[] = [];\n\n // A cycle's first keyframe lands at the SAME time as the previous repetition's\n // last one. Emitting both at that time makes the value at that instant depend on\n // the sampler's tie-break, and left the player disagreeing with the editor (B7).\n // Mirror `TLoop.toKeyframes` exactly:\n // - values EQUAL (pingpong turn, closed loop) → the duplicate says nothing, skip it;\n // - values DIFFER (a real cycle snap) → separate them by PX_LOOP_JUMP_SHIFT_MS.\n // The editor's shift is one 10ms frame (`smallFrameShift`), so the two sides\n // materialize identical keyframes. Anything smaller is blocked editor-side: a\n // fractional-frame shift was tried there and reverted (TKeyframeGroup mishandles it).\n // SCOPE: loopOut (`after`) only — see the note above. For loopIn the pair sits in\n // the opposite order in the array, so separating it means moving the EARLIER\n // keyframe earlier; done naively it inverts keyframe order and re-breaks the\n // loopIn `f0` regression (`appendRepTail`). Left as-is until it has its own\n // editor-CSS evidence; the two sides may still differ at a loopIn boundary.\n const separateBoundary = loop.repeatAt !== PxLoopRepeatAt.start;\n // The keyframe the FIRST repetition butts up against: loopOut tiles forward from\n // the last original keyframe (the originals are concatenated only at assembly).\n const originalTerminalKf: PxNormalizedKeyframe | undefined = keyframes[keyframes.length - 1];\n\n // Easing that a skipped boundary keyframe hands to the ORIGINAL terminal keyframe.\n // Easing describes the interval that FOLLOWS a keyframe, so when a pingpong turn's\n // duplicate is dropped (same value, nothing to say about position) its easing is NOT\n // redundant — it owns the return leg. Without this hand-off the return leg renders\n // LINEAR while the outbound is eased. The originals belong to the caller, so it is\n // applied by replacing the terminal with a copy at assembly, never by mutation.\n let terminalEasingOverride: PxNormalizedKeyframe['e'] | undefined;\n let hasTerminalEasingOverride = false;\n\n // Helper: append one full or partial repetition\n function appendRep(repStart: number, isReversed: boolean, partial?: number) {\n let entries: LoopTemplateEntry[];\n if (isReversed) {\n // Reverse keyframe order and reverse easings\n entries = [];\n for (let i = template.length - 1; i >= 0; i--) {\n entries.push({\n relT: 1 - template[i].relT,\n v: template[i].v,\n // Easing for reversed transition: use reversed easing from the forward \"from\" keyframe\n e: i > 0 ? reverseEasing(template[i - 1].e) : undefined,\n // Reversed traversal swaps each vertex's in/out spatial tangents\n // (geometry is identical, walked backwards), so curvature and\n // auto-orientation survive the reversed rep.\n tangentIn: template[i].tangentOut,\n tangentOut: template[i].tangentIn\n });\n }\n } else {\n entries = template;\n }\n\n const cutRelT = partial !== undefined ? partial : 1;\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n if (entry.relT > cutRelT + 1e-9) {\n // Past the cut point — insert interpolated keyframe\n const prev = entries[i - 1];\n const intervalSpan = entry.relT - prev.relT;\n const localFrac = (cutRelT - prev.relT) / intervalSpan;\n\n // Apply easing to get the eased progress for value interpolation\n const easedFrac = prev.e ? cubicBezier(prev.e)(localFrac) : localFrac;\n const cutValue = interpolateValue(propName, prev.v, entry.v, easedFrac);\n\n // Split easing — use left portion for the truncated interval\n const { left: leftEasing } = splitEasing(prev.e, localFrac);\n\n // Update previous keyframe's easing to the left portion\n if (looped.length > 0 && prev.relT <= cutRelT) {\n looped[looped.length - 1].e = leftEasing;\n }\n\n looped.push({ t: repStart + cutRelT * segDuration, v: cutValue, e: undefined });\n return;\n }\n\n // Repetition boundary: this entry coincides in time with whatever precedes\n // it. For the FIRST rep that neighbour is the last ORIGINAL keyframe (the\n // originals are concatenated only at assembly time), not a `looped` entry.\n const prevKf = looped.length > 0 ? looped[looped.length - 1] : originalTerminalKf;\n const isBoundary = separateBoundary && i === 0 && prevKf !== undefined\n && Math.abs((prevKf.t ?? 0) - (repStart + entry.relT * segDuration)) < 1e-9;\n if (isBoundary) {\n if (deepEqualValue(prevKf.v, entry.v)) {\n // Pingpong turn — the duplicate says nothing about VALUE, but it carries\n // the easing (and out-tangent) for the interval that follows it. Hand\n // those to the surviving neighbour instead of discarding them.\n if (looped.length > 0) {\n prevKf.e = entry.e;\n prevKf.tangentOut = entry.tangentOut;\n } else {\n terminalEasingOverride = entry.e;\n hasTerminalEasingOverride = true;\n }\n continue;\n }\n // Real snap: the jump segment must not carry motion-path tangents.\n // Only ours are safe to mutate — never the caller's originals.\n if (looped.length > 0) { delete prevKf.tangentIn; delete prevKf.tangentOut; }\n }\n\n const pushed: PxNormalizedKeyframe = {\n t: repStart + entry.relT * segDuration + (isBoundary ? PX_LOOP_JUMP_SHIFT_MS : 0),\n v: entry.v,\n e: i < entries.length - 1 ? entry.e : undefined\n };\n // Carry per-vertex spatial tangents so the repeated segment keeps its\n // motion-path curvature (reversed reps already have in/out swapped above).\n if (entry.tangentIn) pushed.tangentIn = entry.tangentIn;\n if (entry.tangentOut) pushed.tangentOut = entry.tangentOut;\n looped.push(pushed);\n }\n }\n\n // Like {@link appendRep} but emits only the TAIL of the segment — relT ∈\n // [1-tailFraction, 1] — over [repStart, repStart + tailFraction*segDuration].\n // Used for the leftover (partial) rep of a `before` loop: it sits at fillStart\n // and runs UP TO the segment-end value, so the full reps after it align to end\n // exactly at the first original keyframe (firstT). A head-truncated partial here\n // (as appendRep does) would land the leftover ADJACENT to firstT and desync the\n // whole backward fill — the loopIn `f0` bug.\n function appendRepTail(repStart: number, isReversed: boolean, tailFraction: number) {\n let entries: LoopTemplateEntry[];\n if (isReversed) {\n entries = [];\n for (let i = template.length - 1; i >= 0; i--) {\n entries.push({\n relT: 1 - template[i].relT,\n v: template[i].v,\n e: i > 0 ? reverseEasing(template[i - 1].e) : undefined,\n tangentIn: template[i].tangentOut,\n tangentOut: template[i].tangentIn\n });\n }\n } else {\n entries = template;\n }\n\n const startRelT = 1 - tailFraction;\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n if (entry.relT < startRelT - 1e-9) continue; // wholly before the tail window\n const prev = entries[i - 1];\n\n // If startRelT falls strictly inside (prev, entry), the tail begins\n // mid-interval — emit an interpolated keyframe at repStart carrying the\n // RIGHT split of prev's easing.\n if (prev && prev.relT < startRelT - 1e-9 && entry.relT > startRelT + 1e-9) {\n const intervalSpan = entry.relT - prev.relT;\n const localFrac = (startRelT - prev.relT) / intervalSpan;\n const easedFrac = prev.e ? cubicBezier(prev.e)(localFrac) : localFrac;\n const startValue = interpolateValue(propName, prev.v, entry.v, easedFrac);\n const { right: rightEasing } = splitEasing(prev.e, localFrac);\n looped.push({ t: repStart, v: startValue, e: rightEasing });\n }\n\n const pushed: PxNormalizedKeyframe = {\n t: repStart + (entry.relT - startRelT) * segDuration,\n v: entry.v,\n e: i < entries.length - 1 ? entry.e : undefined\n };\n if (entry.tangentIn) pushed.tangentIn = entry.tangentIn;\n if (entry.tangentOut) pushed.tangentOut = entry.tangentOut;\n looped.push(pushed);\n }\n }\n\n // Generate repetitions.\n // The rep closest to the original keyframes boundary must be reversed first\n // in pingpong mode (the animation just finished going forward, so the next\n // iteration goes backward).\n if (loop.repeatAt === PxLoopRepeatAt.start) {\n // loopIn — the fill must END exactly at the first keyframe (firstT), so the\n // reps tile BACKWARD from that boundary: the leftover (partial) rep sits at\n // fillStart showing the segment's tail, then `fullReps` full reps run up to\n // firstT. (Forward-tiling — as loopOut does — would push the partial next to\n // firstT and desync the fill: the loopIn `f0` regression.)\n if (partialFraction > 1e-9) {\n const isReversed = loop.direction === PxLoopDirection.alternate && (fullReps % 2 === 0);\n appendRepTail(fillStart, isReversed, partialFraction);\n }\n for (let rep = 0; rep < fullReps; rep++) {\n const distFromBoundary = fullReps - 1 - rep;\n const isReversed = loop.direction === PxLoopDirection.alternate && (distFromBoundary % 2 === 0);\n const repStart = fillStart + remainder + rep * segDuration;\n appendRep(repStart, isReversed);\n }\n } else {\n // loopOut — boundary is at fillStart (lastT); tile forward, partial at the end.\n for (let rep = 0; rep < fullReps; rep++) {\n const isReversed = loop.direction === PxLoopDirection.alternate && (rep % 2 === 0);\n const repStart = fillStart + rep * segDuration;\n appendRep(repStart, isReversed);\n }\n if (partialFraction > 1e-9) {\n const isReversed = loop.direction === PxLoopDirection.alternate && (fullReps % 2 === 0);\n const repStart = fillStart + fullReps * segDuration;\n appendRep(repStart, isReversed, partialFraction);\n }\n }\n\n // Assemble: looped keyframes go before or after the original keyframes.\n // No junction deduplication — cycle mode relies on value jumps at boundaries.\n if (loop.repeatAt === PxLoopRepeatAt.start) {\n return [...looped, ...keyframes];\n } else {\n if (hasTerminalEasingOverride && keyframes.length > 0) {\n const head = keyframes.slice(0, -1);\n const tail = { ...keyframes[keyframes.length - 1], e: terminalEasingOverride };\n return [...head, tail, ...looped];\n }\n return [...keyframes, ...looped];\n }\n}\n\n\n// ============================================================================\n// KEYFRAME NORMALIZATION\n// ============================================================================\n\n/**\n * Normalizes keyframes from the new API format (time in ms) to internal format (time as 0-1 fraction).\n * Resolves easing references, normalizes times, and converts path strings for 'd' attribute.\n * If a loop configuration is present, expands the keyframes to fill the global duration.\n * @param propName The property name (e.g., 'd' for path)\n * @param propAnim The property animation with keyframes\n * @param duration The total animation duration in ms\n * @param defs The definitions containing named easings\n * @returns Array of normalized keyframes (same structure, resolved refs, normalized times)\n */\nfunction normalizeKeyframes(\n propName: string,\n propAnim: PxPropertyAnimation,\n duration: number,\n defs?: PxDefinitions\n): PxNormalizedKeyframe[] {\n const keyframes = propAnim.keyframes || [];\n\n const normalized: PxNormalizedKeyframe[] = [];\n\n for (const kf of keyframes) {\n const timePct = keyframeTime(kf);\n let value = keyframeValue(kf);\n const easing = keyframeEasing(kf);\n\n // Normalize path values for 'd' attribute\n if (propName === 'd') {\n value = normalizePathValue(value);\n }\n\n // Normalize color values (hex/rgb/rgba strings to [0-1] vectors).\n // `PX_COLOR_ATTR_NAMES` is keyed in kebab-case (`stop-color`, `flood-color`,\n // `lighting-color`); the wire format uses both kebab AND camelCase\n // (`stopColor`) for these props. Without converting, frames-mode would\n // call `interpolateColor` on the raw strings and produce `NaN` channels\n // — the visible \"rgba(NaN,NaN,NaN,…)\" bug on stop-color animations.\n const propNameKebab = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;\n if (PX_COLOR_ATTR_NAMES.has(propNameKebab)) {\n value = parseColor(value) ?? value;\n }\n\n const normKf: PxNormalizedKeyframe = {\n t: timePct,\n v: value,\n e: resolveEasing(easing, defs)\n };\n // Motion-along-path: keep the spatial tangents so the downstream\n // `materializeMotionPathInPropAnim` (called in `normalizeAnimationDefinition`) can\n // sample them into transform kfs. Short aliases `ti` / `to` collapse\n // into their canonical names.\n const tIn = keyframeTangentIn(kf);\n const tOut = keyframeTangentOut(kf);\n if (tIn) normKf.tangentIn = tIn;\n if (tOut) normKf.tangentOut = tOut;\n\n normalized.push(normKf);\n }\n\n // Sort by time\n normalized.sort((a, b) => (a.t ?? 0) - (b.t ?? 0));\n\n // Expand loop if configured (loop:true is shorthand for default PxLoop)\n const loopRaw = propAnim.loop;\n const loop: PxLoop | undefined = loopRaw === true ? {} : loopRaw || undefined;\n if (loop && normalized.length >= 2) {\n return expandLoopKeyframes(propName, normalized, loop, duration);\n }\n\n return normalized;\n}\n\n/**\n * Merges multiple animation definitions into a single combined definition.\n * Later definitions override earlier ones for the same property.\n */\nfunction mergeAnimationDefinitions(\n animations: PxAnimationDefinition[]\n): PxAnimationDefinition {\n const merged: PxAnimationDefinition = {};\n\n for (const anim of animations) {\n for (const [prop, propAnim] of Object.entries(anim)) {\n merged[prop] = propAnim;\n }\n }\n\n return merged;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public loop-materialization API\n//\n// Used internally by `normalizeKeyframes` (where `expandLoopKeyframes` is\n// already called at the tail of normalization). Exposed here at the propAnim\n// and tree levels so the Editor (or any external caller) can compose:\n// root = materializeNodeEffects(root).root;\n// root = materializeInternalLoopsInTree(root, duration);\n// root = materializeMotionPathsInTree(root);\n// to produce a fully-flat document with no `loop`, no `effects`, no tangents.\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n/**\n * Replaces `propAnim.loop` with explicit repeated keyframes via\n * `expandLoopKeyframes`. Returns the input by reference when no loop is\n * configured (no-op). Output drops the `loop` field (consumed).\n * @internal\n */\nexport function materializeInternalLoopsInPropAnim(\n propName: string,\n propAnim: PxPropertyAnimation,\n duration: number,\n): PxPropertyAnimation {\n const loopRaw = propAnim.loop;\n if (loopRaw === undefined || loopRaw === null || loopRaw === false) return propAnim;\n const loop: PxLoop = loopRaw === true ? {} : (loopRaw as PxLoop);\n const rawKfs = propAnim.keyframes as PxKeyframe[] | undefined;\n if (!Array.isArray(rawKfs) || rawKfs.length < 2) return propAnim;\n\n // `expandLoopKeyframes` reads kf.t / kf.v / kf.e (short form). When this\n // function is called from the materialization pipeline OUTSIDE the\n // binding-normalization path (e.g. by `materializeAllInTree`), the input\n // kfs may still be in long form (`time` / `value` / `easing`) AND the\n // values may be unparsed (hex color strings, raw path-`d`). Normalize\n // both here so `interpolateValue` (called by `expandLoopKeyframes` at the\n // loop seam) sees structured data — without this, a color boundary kf\n // ends up `[NaN,NaN,NaN,NaN]` and the bug stays visible until the\n // animation cycle restarts.\n const propNameKebab = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;\n const isColor = PX_COLOR_ATTR_NAMES.has(propNameKebab);\n const kfs: PxNormalizedKeyframe[] = rawKfs.map(kf => {\n const t = keyframeTime(kf);\n let v: unknown = keyframeValue(kf);\n if (propName === 'd') v = normalizePathValue(v);\n if (isColor) v = parseColor(v) ?? v;\n const e = keyframeEasing(kf);\n const out: PxNormalizedKeyframe = { t, v, e };\n const tIn = keyframeTangentIn(kf);\n const tOut = keyframeTangentOut(kf);\n if (tIn) out.tangentIn = tIn;\n if (tOut) out.tangentOut = tOut;\n return out;\n });\n\n const expanded = expandLoopKeyframes(propName, kfs, loop, duration);\n const out: PxPropertyAnimation = { keyframes: expanded };\n if (propAnim.autoOrient !== undefined) (out as { autoOrient?: unknown }).autoOrient = propAnim.autoOrient;\n return out;\n}\n\n\n/**\n * Walks `root` and materializes every animated property's `loop` via\n * `materializeInternalLoopsInPropAnim`. Immutable — returns the input by\n * reference when no loop was found anywhere; otherwise clones along the path\n * to each affected node, sharing untouched sub-trees.\n * @internal\n */\nexport function materializeInternalLoopsInTree(\n root: PxNode,\n duration: number,\n): PxNode {\n const ret = walkAndMaterializeLoops(root, duration);\n return ret ?? root;\n}\n\nfunction walkAndMaterializeLoops(node: PxNode, duration: number): PxNode | null {\n let newChildren: Array<PxNode> | undefined;\n if (node.children) {\n for (let i = 0; i < node.children.length; i++) {\n const ret = walkAndMaterializeLoops(node.children[i], duration);\n if (ret !== null) {\n if (!newChildren) newChildren = node.children.slice();\n newChildren[i] = ret;\n }\n }\n }\n let newAnimate: Record<string, PxPropertyAnimation> | undefined;\n const animBucket = node.animate;\n if (animBucket && typeof animBucket === 'object' && !Array.isArray(animBucket)) {\n const animDef = animBucket as Record<string, PxPropertyAnimation>;\n for (const propName of Object.keys(animDef)) {\n const propAnim = animDef[propName];\n const materialized = materializeInternalLoopsInPropAnim(propName, propAnim, duration);\n if (materialized !== propAnim) {\n if (!newAnimate) newAnimate = { ...animDef };\n newAnimate[propName] = materialized;\n }\n }\n }\n if (!newChildren && !newAnimate) return null;\n const cloned: PxNode = { ...node };\n if (newChildren) cloned.children = newChildren;\n if (newAnimate) cloned.animate = newAnimate as PxNode['animate'];\n return cloned;\n}\n\n\n/**\n * Generates a unique element ID for internal tracking during DOM rendering.\n */\nlet _elementIdCounter = 0;\nexport function generateElementId(): string {\n return '_px_el_' + (++_elementIdCounter);\n}\n\n/**\n * Resets the element ID counter (useful for testing).\n */\nexport function resetElementIdCounter(): void {\n _elementIdCounter = 0;\n}\n\n/**\n * TRANSFORM PRECEDENCE (review §0.4/§1.6) — CSS's own composition rule, applied at READ:\n * a static `transform` on the element composes UNDER the animated transform, instead of\n * being silently clobbered by it. Implemented as a keyframe-value MERGE during\n * normalization, so both engines (and every consumer downstream) see complete parts:\n *\n * 1. `animate.transform` with PARTIAL parts records — every keyframe value (and the\n * base `value`) inherits the static parts it does not set:\n * static `{rotate: 45}` + kf `{translate: [80, 0]}` → kf `{rotate: 45, translate: [80, 0]}`.\n * 2. ONE individual channel (`translate` / `rotate` / `scale` / `skew`) and no\n * `transform` channel — the channel is REWRITTEN as a unified `transform` channel\n * whose values carry the static parts: the rect stays rotated 45° AND slides.\n *\n * The static transform may be a parts record or an attribute string (parsed by the\n * conservative {@link parseTransformParts} — unparseable strings skip the merge).\n * NOT merged (documented limitations): several individual channels animated at once\n * (they still last-write-wins against each other), and an individual channel next to an\n * animated `transform` (the `transform` channel wins, as before).\n * @internal\n */\nexport function mergeStaticTransformIntoAnimDef(\n animDef: PxAnimationDefinition,\n staticTransform: unknown,\n): PxAnimationDefinition {\n if (!animDef) return animDef;\n const staticParts: PxTransformParts | undefined =\n staticTransform && typeof staticTransform === 'object' && !Array.isArray(staticTransform)\n ? staticTransform as PxTransformParts\n : parseTransformParts(staticTransform as string);\n if (!staticParts || !Object.keys(staticParts).length) return animDef;\n\n const mergeKfValue = (v: unknown): unknown =>\n v && typeof v === 'object' && !Array.isArray(v) ? { ...staticParts, ...(v as PxTransformParts) } : v;\n\n const transformAnim = animDef[TRANSFORM_ATTR];\n if (transformAnim && typeof transformAnim === 'object') {\n const anim = transformAnim as PxPropertyAnimation;\n if (Array.isArray(anim.keyframes)) {\n const out: PxPropertyAnimation = {\n ...anim,\n keyframes: anim.keyframes.map(kf => ({ ...kf, value: mergeKfValue(kf.value) })),\n };\n if (out.value !== undefined) out.value = mergeKfValue(out.value) as PxPropertyAnimation['value'];\n return { ...animDef, transform: out };\n }\n return animDef;\n }\n\n const channels = Object.keys(animDef).filter(k => PX_TRANSFORM_FN_NAMES.has(k));\n if (channels.length !== 1) return animDef; // several channels: unchanged (documented)\n const ch = channels[0];\n const chAnim = animDef[ch] as PxPropertyAnimation;\n if (!chAnim || typeof chAnim !== 'object' || !Array.isArray(chAnim.keyframes)) return animDef;\n const lifted: PxPropertyAnimation = {\n ...chAnim,\n keyframes: chAnim.keyframes.map(kf => ({ ...kf, value: { ...staticParts, [ch]: kf.value } })),\n };\n if (lifted.value !== undefined) lifted.value = { ...staticParts, [ch]: lifted.value };\n const rest: PxAnimationDefinition = { ...animDef };\n delete rest[ch];\n return { ...rest, transform: lifted };\n}\n\n/**\n * Normalizes an animation definition by resolving easing references and normalizing keyframe times.\n * Keeps the key/value mapping structure. `engine` controls motion-along-path\n * handling — see {@link PxTimelineEngine}.\n */\nfunction normalizeAnimationDefinition(\n animDef: PxAnimationDefinition,\n duration: number,\n defs?: PxDefinitions,\n engine: PxTimelineEngine = PxTimelineEngine.native,\n): PxAnimationDefinition {\n const normalized: PxAnimationDefinition = {};\n\n for (const [propName, propAnim] of Object.entries(animDef)) {\n // `alongPathMode: 'offsetPath'` — this transform's motion is rendered by CSS\n // Motion Path (`offset-path` style on the element + an `offsetDistance` track in\n // the same dict). The tangented keyframes stay on the wire as the DESIGN source\n // (the editor round-trip reads them back), but the player must not ALSO drive\n // them: doing both moved the element to the path position and then translated it\n // again (double position). Skip the binding; `offsetDistance` owns the motion.\n if (propName === 'transform'\n && (propAnim as { alongPathMode?: string }).alongPathMode === 'offsetPath'\n // …but ONLY when the offset infrastructure actually exists (an `offsetDistance`\n // track in the same definition — the pre-rendered dict shape, or a lightweight\n // doc the offset materializer rewrote). A marked transform the materializer\n // BAILED on (e.g. rotate animated in the same keyframes — inexpressible as\n // offset-path) must fall through to the ordinary sampled pipeline; skipping it\n // unconditionally froze those elements at their base pose.\n && (animDef as Record<string, unknown>)['offsetDistance'] !== undefined) {\n continue;\n }\n const normalizedKfs = normalizeKeyframes(propName, propAnim, duration, defs);\n if (normalizedKfs.length > 0) {\n // Internal normalized form converges on `keyframes` too — the `kfs` alias\n // is gone from the format AND the runtime (review §1.2/§6.1).\n const out: PxPropertyAnimation = { keyframes: normalizedKfs };\n // Carry top-level animation flags through normalization — `materializeMotionPathInPropAnim`\n // and the runtime evaluators need `autoOrient` / `loop` to be present\n // alongside the kfs.\n if (propAnim.autoOrient !== undefined) out.autoOrient = propAnim.autoOrient;\n if (propAnim.loop !== undefined) out.loop = propAnim.loop;\n\n // Pipeline: loops are already expanded by `normalizeKeyframes` above.\n // For the `waapi` engine ONLY, materialize motion-along-path\n // (tangented `transform` kfs + autoOrient) into plain sampled\n // `{ translate, rotate? }` kfs — CSS WAAPI can't evaluate parametric\n // tangents at runtime. Frames-mode keeps the parametric form so the\n // frame-loop kernel `evaluateMotionPathSegment` can sample per frame\n // (better spatial fidelity than any finite sampling).\n // `materializeMotionPathInPropAnim` is a no-op for non-motion-path\n // animations, so non-transform props pay zero cost.\n normalized[propName] = (engine === PxTimelineEngine.native && propName === 'transform')\n ? materializeMotionPathInPropAnim(out)\n : out;\n }\n }\n\n return normalized;\n}\n\n/**\n * Normalizes a PxAnimatedSvgDocument to a PxAnimatorConfig for the animation engines.\n * This is the main entry point for converting the new API format to internal format.\n * Resolves animation/easing references. `engine` controls motion-along-path\n * handling — see {@link PxTimelineEngine}.\n * @public @advanced\n */\nexport function normalizeBindings(\n doc: PxAnimatedSvgDocument,\n engine: PxTimelineEngine = PxTimelineEngine.native,\n): PxNormalizedBinding[] {\n const animatorConfig = getAnimatorConfig(doc) || {};\n const defs = getDefinitions(doc);\n const duration = animatorConfig.duration || 1000; // FIXME - get rid of 1000 here\n\n const bindings: PxNormalizedBinding[] = [];\n\n // Helper to merge and normalize the resolved animation definitions of one element\n const processAnimation = (\n id: string,\n animDefs: PxAnimationDefinition[],\n staticTransform?: unknown,\n ): PxNormalizedBinding | null => {\n if (animDefs.length === 0) return null;\n\n // CSS transform precedence (review §0.4/§1.6): the node's static transform\n // composes under the animated one — merged BEFORE normalization so both\n // engines see complete parts records.\n const merged = mergeStaticTransformIntoAnimDef(mergeAnimationDefinitions(animDefs), staticTransform);\n const normalizedAnim = normalizeAnimationDefinition(merged, duration, defs, engine);\n\n if (Object.keys(normalizedAnim).length === 0) return null;\n\n return {\n id,\n animate: normalizedAnim\n };\n };\n\n // Process bindings (for pre-rendered DOM): `target` is `#id`-spelled on the wire (every\n // element reference carries the hash — review §3.2); the engines get the bare DOM id.\n const docBindings = getBindings(doc);\n if (docBindings) {\n for (const binding of docBindings) {\n const id = binding.target.startsWith('#') ? binding.target.slice(1) : binding.target;\n const animDefs = binding.animateWith\n .map(name => resolveAnimation(name, defs))\n .filter((d): d is PxAnimationDefinition => !!d);\n const normalized = processAnimation(id, animDefs);\n if (normalized) bindings.push(normalized);\n }\n }\n\n // Process children (for rendered DOM).\n //\n // Per-element animations live under the node's `animate` bucket, keyed by\n // SVG/CSS property name — a PxAnimationDefinition (`{ transform: {keyframes},\n // fill: {keyframes}, … }`). The static initial value of each animated\n // property is carried separately as a plain attribute on the element body.\n // On-disk locations: top-level `node.animate` (JSON form).\n const processNode = (node: PxNode) => {\n const inlineAnim = node.animate;\n if (inlineAnim && Object.keys(inlineAnim).length > 0) {\n const nodeId = node.id || generateElementId();\n node.id = nodeId; // Ensure the node has an ID\n const normalized = processAnimation(nodeId, resolveElementAnimation(inlineAnim, defs), node.transform);\n if (normalized) bindings.push(normalized);\n }\n\n // Process children\n if (node.children) {\n for (let i = 0; i < node.children.length; i++) {\n processNode(node.children[i]);\n }\n }\n };\n\n // Process children of the root\n if (doc.children) {\n for (let i = 0; i < doc.children.length; i++) {\n processNode(doc.children[i]);\n }\n }\n\n return bindings;\n}\n\n\n// ============================================================================\n// ATTRIBUTE VALUE CALCULATION (used by frame loop animator)\n// ============================================================================\n\n/**\n * Finds prev/next keyframes for a given progress.\n */\nfunction getKeyframesPair(keyframes: PxNormalizedKeyframe[], progress: number) {\n // Outside the keyframe range, clamp to the NEAREST REAL segment (the caller clamps\n // `localProgress` to 0/1, so that holds the boundary pose). Spanning first→last\n // instead would invent a segment that exists nowhere in the animation: for keyframes\n // that start part-way into the timeline, the pre-start frames used to interpolate\n // straight from the first to the LAST keyframe — skipping everything between, and\n // handing motion-path evaluation a chord it then cached (see `getSegmentCache`).\n const last = keyframes.length - 1;\n let prevKf = keyframes[0];\n let nextKf = keyframes[last > 0 ? 1 : 0];\n\n for (let j = 0; j < last; j++) {\n const aOff = (keyframes[j].t ?? 0);\n const bOff = (keyframes[j + 1].t ?? 0);\n if (aOff <= progress && progress <= bOff) {\n prevKf = keyframes[j];\n nextKf = keyframes[j + 1];\n break;\n }\n // Past this segment and not bracketed by any later one → hold the final segment.\n if (progress > bOff && j === last - 1) {\n prevKf = keyframes[last > 0 ? last - 1 : 0];\n nextKf = keyframes[last];\n }\n }\n return { prevKf, nextKf };\n}\n\n/**\n * Calculates interpolated value for a single property animation.\n */\nfunction calcPropertyValue(\n propName: string,\n propAnim: PxPropertyAnimation,\n progress: number\n): { k: string, v: string } | null {\n const keyframes = propAnim.keyframes || [];\n if (keyframes.length === 0) return null;\n\n const { prevKf, nextKf } = getKeyframesPair(keyframes, progress);\n\n // remap to local 0..1 within prevKf..nextKf\n let localProgress = (prevKf === nextKf) ? 0 : remap(progress, prevKf.t ?? 0, nextKf.t ?? 0, 0, 1);\n localProgress = clamp(localProgress, 0, 1);\n const easing = keyframeEasing(prevKf); // e is on the source keyframe: applied from this KF to the next\n if (easing && Array.isArray(easing)) {\n try {\n localProgress = cubicBezier(easing as [number, number, number, number])(localProgress);\n } catch (e) {\n // fallback: ignore easing if parsing fails\n }\n }\n\n let cssAttrName = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;\n let cssValue: string | number | null = null;\n\n const prevV = prevKf?.v;\n const nextV = nextKf?.v;\n\n if (cssAttrName === 'd') {\n // Extract paths from { paths: [...] } format\n const prevPaths = prevV?.paths ?? (Array.isArray(prevV) ? prevV : []);\n const nextPaths = nextV?.paths ?? (Array.isArray(nextV) ? nextV : []);\n cssValue = interpolateBeziers(\n prevPaths,\n nextPaths,\n localProgress\n ).map(bz => bezierToSvgPath(bz)).join('');\n } else if (PX_COLOR_ATTR_NAMES.has(cssAttrName)) {\n cssValue = toRGBA(interpolateColor(\n prevV || [0, 0, 0, 1],\n nextV || [0, 0, 0, 1],\n localProgress\n ));\n cssAttrName = propName;\n } else if (cssAttrName === 'stroke-dasharray') {\n cssValue = interpolateVec(\n prevV || [],\n nextV || [],\n localProgress\n ).join(' ');\n cssAttrName = propName;\n } else if (\n cssAttrName === 'transform' &&\n prevV !== null && typeof prevV === 'object' && !Array.isArray(prevV)\n ) {\n // Unified transform: keyframe values are PxTransformParts records.\n // Interpolate each present part separately, then compose into a transform\n // string for the SVG `transform` attribute (no units).\n const partKeys = new Set<string>([\n ...(prevV ? Object.keys(prevV) : []),\n ...(nextV ? Object.keys(nextV) : []),\n ]);\n const partsResult: PxTransformParts = {};\n for (const partKey of partKeys) {\n const prevPart = prevV?.[partKey];\n const nextPart = nextV?.[partKey];\n if (partKey === 'rotate' || partKey === 'skew') {\n partsResult[partKey] = interpolateNum(+(prevPart ?? 0), +(nextPart ?? 0), localProgress);\n } else if (partKey === 'translate' || partKey === 'scale' || partKey === 'origin') {\n const fallback = partKey === 'scale' ? [1, 1] : [0, 0];\n const interp = interpolateVec(prevPart || fallback, nextPart || fallback, localProgress);\n (partsResult as any)[partKey] = interp;\n }\n }\n // Motion-along-path override (frames-mode only). The WAAPI binding\n // pipeline materializes tangents + autoOrient into sampled\n // `{translate, rotate}` kfs upstream (in `normalizeAnimationDefinition`),\n // so for WAAPI this branch is a no-op (`propAnimIsMotionPath` returns\n // false on already-materialized kfs). Frames-mode preserves the\n // parametric form and we evaluate the Bezier per frame here — gives\n // exact spatial fidelity vs. any finite sampling.\n if (propAnimIsMotionPath(propAnim)) {\n const prevTr = (prevV as { translate?: [number, number] }).translate;\n const nextTr = (nextV as { translate?: [number, number] }).translate;\n if (Array.isArray(prevTr) && Array.isArray(nextTr)) {\n const sample = evaluateMotionPathSegment(\n prevKf, nextKf,\n [+prevTr[0], +prevTr[1]],\n [+nextTr[0], +nextTr[1]],\n localProgress,\n !!propAnim.autoOrient,\n );\n partsResult.translate = [sample.translate[0], sample.translate[1]];\n if (sample.rotateDeg !== undefined) partsResult.rotate = sample.rotateDeg;\n }\n }\n cssValue = composeTransformParts(partsResult, { withUnits: false });\n cssAttrName = 'transform';\n } else if (cssAttrName === 'translate') {\n const v = interpolateVec(\n prevV || [0, 0],\n nextV || [0, 0],\n localProgress\n );\n cssValue = 'translate(' + v.join(',') + ')';\n cssAttrName = 'transform';\n } else if (cssAttrName === 'rotate') {\n const v = interpolateNum(\n +(prevV || 0),\n +(nextV || 0),\n localProgress\n );\n cssValue = 'rotate(' + v + ')';\n cssAttrName = 'transform';\n } else if (cssAttrName === 'scale') {\n const v = interpolateVec(\n prevV || [1, 1],\n nextV || [1, 1],\n localProgress\n );\n cssValue = 'scale(' + v.join(',') + ')';\n cssAttrName = 'transform';\n } else {\n // numeric attr\n const num = interpolateNum(\n +(prevV || 0),\n +(nextV || 0),\n localProgress\n );\n cssValue = num;\n }\n\n if (PX_PCT_BASED_ATTR_NAMES.has(cssAttrName) && typeof cssValue === 'number') {\n cssValue = (cssValue * 100) + '%';\n }\n\n return { k: cssAttrName, v: cssValue === null ? '' : '' + cssValue };\n}\n\n/**\n * Calculates interpolated attribute values for an animation definition.\n * @param animDef The animation definition (with resolved refs and normalized times)\n * @param progress The current animation progress (0-1)\n * @returns Object with computed attribute name/value pairs\n * @public @advanced\n */\nexport function calcAnimationValues(\n animDef: PxAnimationDefinition,\n progress: number\n): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const [propName, propAnim] of Object.entries(animDef)) { \n const computed = calcPropertyValue(propName, propAnim, progress);\n if (computed) {\n result[computed.k] = computed.v;\n }\n }\n\n return result;\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n// ONE time contract for every engine (API review §3). The same three calls used to mean three\n// different things:\n//\n// | engine | getCurrentTime() | setCurrentTime(t) | setPlaybackRate(0) |\n// |-----------------|-------------------------|-------------------|--------------------|\n// | web, WAAPI | ms across the whole run | NOT clamped | accepted |\n// | web, frame loop | ms across the whole run | clamped | rejected + warn |\n// | React Native | ms within ONE iteration | wrapped | rejected + warn |\n//\n// So a slider built on `getCurrentTime()` jumped back every iteration on React Native, and\n// `timeline.engine: 'auto'` meant two documents on one page could answer the same call\n// differently. The contract every player now implements:\n//\n// - time is ms from the start of the WHOLE run, iterations included — never per-iteration;\n// - a seek clamps to [0, seekCeilingMs];\n// - a rate of 0 is rejected everywhere, with this one message.\n\n/** The one message every engine prints for a rejected rate. @public @advanced */\nexport const PX_RATE_REJECTED = 'setPlaybackRate: rate must be finite and non-zero';\n\n/**\n * A playback rate is usable when it is finite and non-zero.\n *\n * 0 is rejected rather than accepted: it freezes the animation in a state indistinguishable\n * from a stuck player, and `pause()` already says that properly. Two of the three engines\n * rejected it already — this makes the third agree.\n * @public @advanced\n */\nexport function isValidPlaybackRate(rate: number): boolean {\n return Number.isFinite(rate) && rate !== 0;\n}\n\n/**\n * Highest seekable time, ms — `duration × iterations`.\n *\n * `Infinity` for an endless timeline, so callers must test `Number.isFinite` before using it\n * as an upper bound. This is the SEEK ceiling, which is deliberately not the same thing as the\n * span `progress` covers — see `progressSpanMs`.\n * @public @advanced\n */\nexport function seekCeilingMs(durationMs: number, iterations: number): number {\n if (!(durationMs > 0)) return 0;\n if (iterations === Infinity) return Infinity;\n return durationMs * (iterations > 0 ? iterations : 1);\n}\n\n/**\n * The span `progress` 0→1 covers, ms.\n *\n * An endless timeline maps progress onto ONE iteration — the rule the components already\n * document for the `progress` prop (\"0–1 of duration × iterations, one iteration when\n * iterations is 'infinite'\"). Always finite, so it is safe as a divisor.\n * @public @advanced\n */\nexport function progressSpanMs(durationMs: number, iterations: number): number {\n if (!(durationMs > 0)) return 0;\n if (iterations === Infinity) return durationMs;\n return durationMs * (iterations > 0 ? iterations : 1);\n}\n\n/**\n * Clamps a seek into [0, ceiling].\n *\n * `NaN` and anything below 0 land on 0. `Infinity` means \"the end\", so it clamps to the ceiling\n * like any other overshoot — except on an endless timeline, where there is no end to land on and\n * a non-finite playhead would poison every later read, so that reads as 0.\n * @public @advanced\n */\nexport function clampSeekMs(timeMs: number, ceilingMs: number): number {\n if (Number.isNaN(timeMs) || timeMs < 0) return 0;\n if (Number.isFinite(ceilingMs)) return timeMs > ceilingMs ? ceilingMs : timeMs;\n return Number.isFinite(timeMs) ? timeMs : 0;\n}\n\n/**\n * Whole-run ms → 0–1.\n *\n * A finite timeline clamps at both ends. An endless one wraps within the current iteration,\n * so the value stays meaningful however long it has been running. A zero-length span reads as\n * 0 rather than NaN.\n * @public @advanced\n */\nexport function timeToProgress(timeMs: number, durationMs: number, iterations: number): number {\n const span = progressSpanMs(durationMs, iterations);\n if (!(span > 0) || !Number.isFinite(timeMs)) return 0;\n if (timeMs <= 0) return 0;\n if (iterations === Infinity) return (timeMs % span) / span;\n return timeMs >= span ? 1 : timeMs / span;\n}\n\n/** 0–1 → whole-run ms, clamped into the span. A non-finite progress reads as 0. @public @advanced */\nexport function progressToTimeMs(progress: number, durationMs: number, iterations: number): number {\n const span = progressSpanMs(durationMs, iterations);\n if (!(span > 0) || !Number.isFinite(progress)) return 0;\n if (progress <= 0) return 0;\n return progress >= 1 ? span : progress * span;\n}\n\n/** A playhead that keeps whole-run time across iterations. See `createRunClock`. @public @advanced */\nexport interface PxRunClock {\n /** Whole-run position right now, ms, clamped to the ceiling. */\n now(): number;\n /** True while time is advancing. */\n isRunning(): boolean;\n /** Start or resume at `rate`, optionally from a given whole-run time. */\n start(rate: number, atMs?: number): void;\n /** Stop advancing, freezing the current position. */\n stop(): void;\n /** Jump to a whole-run time; keeps running if it already was. */\n seek(ms: number): void;\n}\n\n/**\n * A whole-run playhead that survives repetition.\n *\n * React Native drives playback with Reanimated's `withRepeat`, whose shared value only ever\n * holds the position WITHIN one iteration and which never reports how many iterations have\n * elapsed. Whole-run time therefore cannot be recovered from it: under `alternate` the value\n * runs backwards rather than wrapping, which is indistinguishable from a negative rate. So the\n * time is kept on a clock of its own — the same thing the frame-loop engine does inline.\n *\n * `nowFn` is injectable so this is testable without real time passing.\n * @public @advanced\n */\nexport function createRunClock(ceilingMs: number, nowFn: () => number = Date.now): PxRunClock {\n let baseMs = 0; // whole-run ms as of the last start/seek/stop\n let startedAt = 0; // nowFn() when running began\n let running = false; // a FLAG, not `startedAt !== 0`: a time source may legitimately\n let rate = 1; // read 0, and `Date.now()` never does — so the bug would hide.\n\n const value = (): number => running\n ? clampSeekMs(baseMs + (nowFn() - startedAt) * rate, ceilingMs)\n : clampSeekMs(baseMs, ceilingMs);\n\n return {\n now: value,\n isRunning: () => running,\n start: (r: number, atMs?: number) => {\n baseMs = clampSeekMs(atMs ?? value(), ceilingMs);\n rate = isValidPlaybackRate(r) ? r : 1;\n startedAt = nowFn();\n running = true;\n },\n stop: () => {\n baseMs = value();\n startedAt = 0;\n running = false;\n },\n seek: (ms: number) => {\n baseMs = clampSeekMs(ms, ceilingMs);\n if (running) startedAt = nowFn();\n },\n };\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n// ONE diagnostics channel for every player (API review §5, §25.1).\n//\n// Before this, only React Native had an error channel (`onError` + `fallback`). The web player\n// sent a failed fetch or an invalid document to `console.error` and then answered\n// `isReady() === false` for ever, and React and Vue could not offer anything at all because\n// the engine callbacks had no slot for it. Everything else — validation warnings, config\n// overrides, unsupported attributes — went straight to `console.warn`, where an embedding app\n// could neither see it nor quiet it.\n//\n// THE RULE — two severities, one meaning each, on every player:\n//\n// - `onError`: THIS INSTANCE WILL NOT PLAY. Nothing is rendered, `isReady()` stays false,\n// the component shows its `fallback`. The player reports it and stays inert rather than\n// throwing at the caller — a throw would land in a fetch callback or a render, where no\n// one can catch it. (A wrong CALL — `createAnimator()` with neither `src` nor `doc` — still\n// throws: that is a bug at the call site, found the moment the line runs.)\n// - `onWarn`: IT PLAYS, but something was ignored, degraded or misspelled — an unknown\n// easing, an override that could not apply, an attribute the platform will not animate.\n//\n// - a handler takes over from the console: give `onWarn` / `onError` and the console stays\n// out of it; give none and the console is the fallback, so nothing is lost by default;\n// - `muteWarn` / `muteError` switch that console fallback off — for a host that knows\n// about the warnings and is prepared to tolerate them. A handler you passed still fires.\n//\n// Severity (warn vs error) and SOURCE (`kind`) are separate axes on purpose: `invalid animation\n// document format` is a document problem AND fatal, while an effects-shape warning is a document\n// problem that still plays. Splitting the callbacks by source would have produced four handlers\n// and forced anyone who just wants everything to wire all of them.\n\n/**\n * Who can do something about a diagnostic.\n *\n * A const object rather than a TypeScript `enum`: consumers compare against these values, so\n * they must accept plain string literals too (the same pattern as `PxControlMode`).\n * @public\n */\nexport const PxDiagnosticKind = {\n /** The document is wrong — regenerate or repair the file. */\n document: 'document',\n /** The page or app cannot provide what the document asks for — fix the mount. */\n host: 'host',\n /** The platform cannot do it and the player degraded — usually nothing to fix. */\n platform: 'platform',\n /** The call is wrong or self-contradictory — fix the options or props you passed. */\n usage: 'usage',\n /** The player failed where it did not expect to — report it to us. */\n internal: 'internal',\n} as const;\nexport type PxDiagnosticKind = typeof PxDiagnosticKind[keyof typeof PxDiagnosticKind];\n\n/** One thing a player has to say. @public */\nexport interface PxDiagnostic {\n /** Who can act on it — see {@link PxDiagnosticKind}. */\n readonly kind: PxDiagnosticKind;\n /** Human-readable, and never carries the console prefix. */\n readonly message: string;\n /**\n * Whatever the site had to hand: the offending binding, the element map, the raw error —\n * or, for a React Native render failure, `{ componentStack }`.\n */\n readonly detail?: unknown;\n /** Present on errors: the Error that stopped the player. Its message is `message`. */\n readonly error?: Error;\n}\n\n/**\n * Where a player sends what it wants to say. Every field is optional.\n *\n * The SHARED base of every callbacks object (dev-docs/reviews/api-surface-review.md §26.1): `createDiagnostics` reads it directly,\n * `PxEngineCallbacks` extends it with the playback lifecycle, `PxAnimatorCallbacks` adds `onStop`\n * on top — so the four diagnostics fields are spelled once, here.\n * @public\n */\nexport interface PxDiagnosticsConfig {\n\n /**\n * IT PLAYS, but something was ignored, degraded or misspelled — an unknown easing, an\n * override that could not apply, an attribute the platform will not animate. Each\n * diagnostic says WHO can act on it via `kind` (`document` / `host` / `platform` / `usage` /\n * `internal`). Without this: `console.warn`.\n */\n onWarn?: (diagnostic: PxDiagnostic) => void;\n\n /**\n * THIS INSTANCE WILL NOT PLAY — the document failed to load, parse or build, or the render\n * threw: nothing rendered, `isReady()` false, the component's `fallback` shown. The player\n * stays inert rather than throwing at the caller. `diagnostic.error` is the Error; on React\n * Native `diagnostic.detail` carries `{ componentStack }` when the error boundary caught it.\n * Without this: `console.error`.\n */\n onError?: (diagnostic: PxDiagnostic) => void;\n\n /**\n * Switch the `console.warn` fallback off. For a host that knows the player has something\n * to say about this document and is prepared to tolerate it — a chatty player is not what\n * an end user's console is for. A handler you passed (`onWarn`) still fires: mute is\n * about the console, not about you.\n */\n muteWarn?: boolean;\n\n /** The same switch for the `console.error` fallback. `onError` still fires. */\n muteError?: boolean;\n}\n\n/** The reporting channel a player writes to. @public */\nexport interface PxDiagnostics {\n /** Report something survivable — it plays. */\n warn(kind: PxDiagnosticKind, message: string, detail?: unknown): void;\n /** Report a failure that stopped this instance — it will not play. */\n error(kind: PxDiagnosticKind, error: Error | string, detail?: unknown): void;\n}\n\n/** Anything not already an Error becomes one, so handlers get a single shape. */\nfunction asError(error: Error | string): Error {\n return typeof error === 'string' ? new Error(error) : error;\n}\n\n/**\n * Builds the channel a player reports through.\n *\n * `prefix` labels the console fallback (e.g. `'[PixodeskSvgAnimator]'`) and is NOT added to the\n * diagnostic handed to a handler — a caller that wants to prefix its own log can, and one\n * feeding a UI should not have to strip ours.\n * @internal\n */\nexport function createDiagnostics(config?: PxDiagnosticsConfig, prefix?: string): PxDiagnostics {\n const tag = prefix ? prefix + ' ' : '';\n return {\n warn: (kind: PxDiagnosticKind, message: string, detail?: unknown): void => {\n if (config?.onWarn) { config.onWarn({ kind, message, detail }); return; }\n if (config?.muteWarn) return;\n const line = tag + kind + ': ' + message;\n if (detail === undefined) console.warn(line);\n else console.warn(line, detail);\n },\n error: (kind: PxDiagnosticKind, error: Error | string, detail?: unknown): void => {\n const err = asError(error);\n if (config?.onError) { config.onError({ kind, message: err.message, error: err, detail }); return; }\n if (config?.muteError) return;\n const line = tag + kind + ': ' + err.message;\n if (detail === undefined) console.error(line);\n else console.error(line, detail);\n },\n };\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport type { PxEngineCallbacks, PxAnimatorCallbacks } from '@pixodesk/svg-animator-core';\nimport type { PxAnimatorApi } from './PxAnimatorWebTypes';\n\n/**\n * The engines take ONE callbacks object; every public surface takes the callbacks INLINE —\n * `createAnimator({ onFinish })`, `loadTagAnimators({ onFinish })`, `<PixodeskSvgAnimator\n * onFinish />`. This builds the one from the other, and adds `onStop`: it fires after any of\n * pause / cancel / finish / remove, for callers who only care that playback is no longer\n * running. Defined HERE, once, so the web player and the components cannot disagree on when.\n *\n * A wrapper is only made when there is something to call — an engine tests\n * `callbacks?.onFinish` for presence, so an always-present function would change its behaviour.\n */\nexport function toEngineCallbacks(inline: PxAnimatorCallbacks | undefined): PxEngineCallbacks {\n const { onPlay, onPause, onCancel, onFinish, onRemove, onStop, onWarn, onError, muteWarn, muteError } = inline ?? {};\n const withStop = (own: (() => void) | undefined): (() => void) | undefined =>\n own || onStop ? () => { own?.(); onStop?.(); } : undefined;\n return {\n onPlay,\n onPause: withStop(onPause),\n onCancel: withStop(onCancel),\n onFinish: withStop(onFinish),\n onRemove: withStop(onRemove),\n onWarn, onError, muteWarn, muteError,\n };\n}\n\n/**\n * The API of a player that could not be built (the rule in core's `PxDiagnostics`): every\n * call is a no-op, every getter answers \"not ready\". Returned instead of throwing, after the\n * failure has been reported through `onError`.\n */\nexport function createInertAnimator(): PxAnimatorApi {\n return {\n isReady: () => false,\n getRootElement: () => null,\n isPlaying: () => false,\n play: () => {},\n pause: () => {},\n cancel: () => {},\n finish: () => {},\n setPlaybackRate: () => {},\n getCurrentTime: () => null,\n setCurrentTime: () => {},\n getCurrentProgress: () => null,\n setCurrentProgress: () => {},\n destroy: () => {},\n };\n}\n\n/** Anything thrown becomes an Error, so a diagnostic always carries one shape. */\nexport function asThrownError(e: unknown): Error {\n return e instanceof Error ? e : new Error(String(e));\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { PxDiagnosticKind, resolveTrigger, type PxDiagnostics, type PxTrigger } from '@pixodesk/svg-animator-core';\nimport { createDiagnostics } from '@pixodesk/svg-animator-core/internal';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\n\n\n/**\n * Sets up event-based triggers for an animation.\n *\n * This function attaches event listeners to the animation's root element based on the\n * provided configuration, allowing animations to be started by user interactions\n * or visibility changes.\n *\n * ### Trigger Options (startOn):\n * - 'load' (default): Starts after the page loads.\n * - 'mouseOver': Starts on mouse enter.\n * - 'click': Toggles play/end action on click.\n * - 'scrollIntoView': Starts when the element scrolls into the viewport.\n * - 'programmatic': No automatic start. Must be controlled via the API.\n *\n * ### End Action Options (outAction):\n * Defines behavior when the trigger condition ends (e.g., mouse leave).\n * - 'continue' (default): Animation continues playing.\n * - 'pause': Pauses the animation.\n * - 'reset': Cancels the animation, resetting it to the start.\n * - 'reverse': Reverses the animation playback.\n *\n * @param {!PxAnimatorApi} api The animator API instance to control.\n * @param {!PxTrigger} trigger The trigger configuration object. Only `startOn`, `outAction` and\n * `scrollIntoViewThreshold` are read here; `finishAction` belongs to the PLAYER (what happens\n * after a natural end), not to the trigger wiring.\n * @returns A disposer that detaches every listener and observer this call attached (review §14).\n * `createAnimator` ties it to `destroy()`. Call it yourself before re-arming an element you\n * wired by hand — otherwise the old listeners stay live next to the new ones.\n * @public\n */\nexport function setupAnimationTriggers(\n api: PxAnimatorApi,\n trigger: PxTrigger,\n diag?: PxDiagnostics\n): () => void {\n // Public export, so the channel is optional and falls back to the console (review §5).\n const report = diag ?? createDiagnostics(undefined, '[PxAnimator]');\n\n // Everything attached below registers its own undo here, so one call detaches it all.\n const cleanups: Array<() => void> = [];\n const dispose = (): void => { for (const undo of cleanups.splice(0)) undo(); };\n // The defaults come from core's one table, shared with every player (`PX_TRIGGER_DEFAULTS`):\n // no `startOn` = 'load', no `outAction` = 'continue', no threshold = 0 (\"any pixel visible\").\n // The threshold default must match the editor model's (TSvgSvgAnimationAttr\n // .scrollIntoViewThreshold), which OMITS the value on the wire when it equals it.\n const { startOn, outAction, scrollIntoViewThreshold } = resolveTrigger(trigger);\n\n const root = api.getRootElement();\n\n if (!root) {\n report.warn(PxDiagnosticKind.host, 'setupAnimationTriggers: No root element found for animation.');\n return dispose;\n }\n\n // Tracks whether the LAST out-action put the animation into reverse, so the\n // next trigger start can restore forward playback without clobbering a\n // custom playback rate the user may have set through the API.\n let reversed = false;\n\n /** Ensures forward playback and starts or resumes the animation. */\n const start = () => {\n if (reversed) {\n reversed = false;\n api.setPlaybackRate(1);\n }\n api.play();\n };\n\n /** Handles what to do when the element leaves the active trigger condition. */\n const handleEndAction = () => {\n switch (outAction) {\n case 'pause':\n api.pause();\n break;\n case 'reset':\n api.cancel();\n break;\n case 'reverse':\n // Play the animation backwards from its current position.\n reversed = true;\n api.setPlaybackRate(-1);\n api.play();\n break;\n case 'continue':\n default:\n // Do nothing\n break;\n }\n };\n\n // ---- Setup event-based start logic ----\n switch (startOn) {\n case 'load': {\n const startHandler = () => start();\n if (document.readyState === 'complete') {\n startHandler();\n } else {\n window.addEventListener('load', startHandler, { once: true });\n cleanups.push(() => window.removeEventListener('load', startHandler));\n }\n break;\n }\n\n case 'mouseOver': {\n // An OUT may only follow an IN. A `mouseleave` with no preceding `mouseenter` happens\n // when the pointer is already over the element at load and then moves away — and for\n // `outAction: 'reverse'` the out action PLAYS (`setPlaybackRate(-1); play()`), so an\n // untriggered leave would start the animation running backwards. Same class of bug as\n // the scrollIntoView initial-intersection case handled below.\n let enteredOnce = false;\n const mouseOverHandler = () => { enteredOnce = true; start(); };\n const mouseOutHandler = () => { if (enteredOnce) handleEndAction(); };\n\n root.addEventListener('mouseenter', mouseOverHandler);\n root.addEventListener('mouseleave', mouseOutHandler);\n cleanups.push(() => {\n root.removeEventListener('mouseenter', mouseOverHandler);\n root.removeEventListener('mouseleave', mouseOutHandler);\n });\n break;\n }\n\n case 'click': {\n const clickHandler = () => {\n if (api.isPlaying()) {\n handleEndAction();\n } else {\n start();\n }\n };\n root.addEventListener('click', clickHandler);\n cleanups.push(() => root.removeEventListener('click', clickHandler));\n break;\n }\n\n case 'scrollIntoView': {\n // `observe()` delivers an INITIAL entry describing the CURRENT state, which is how an\n // element that is already on screen starts without any scrolling. But that same initial\n // entry also reports \"not intersecting\" for an element merely below the fold — and\n // treating that as an out-action ran it before anything had ever played. For `reverse`\n // that meant `setPlaybackRate(-1)` + `play()`, i.e. the animation started running\n // BACKWARDS on page load. An OUT is only meaningful after an IN, so require one.\n // A target TALLER than the viewport can never reach a high ratio (ratio is measured\n // against the TARGET's own size), so a 0.5/0.9 threshold would be unsatisfiable and the\n // animation would never play. Normalize by what could possibly be visible, and register\n // a granular threshold list — registering the raw threshold would mean the callback\n // never fires at all for such a target.\n const effectiveRatio = (entry: IntersectionObserverEntry): number => {\n const target = entry.boundingClientRect;\n const visible = entry.intersectionRect;\n // Simplified entries (tests, older engines) may omit the rects — fall back to the\n // browser's own ratio rather than inventing one.\n if (!target?.height || !visible) return entry.intersectionRatio;\n // Use the SMALLER of `rootBounds` and the live viewport. `rootBounds` can be null\n // (implicit root in some embeddings) and can also report a box LARGER than the\n // actual viewport — trusting it then reinstates the very cap this normalization\n // exists to remove. `intersectionRect` is already clipped to the real viewport, so\n // the denominator must be too.\n const live = typeof window !== 'undefined' && window.innerHeight ? window.innerHeight : Infinity;\n const declared = entry.rootBounds?.height ?? Infinity;\n const viewport = Math.min(live, declared);\n const denom = Math.min(target.height, Number.isFinite(viewport) ? viewport : target.height);\n return denom > 0 ? visible.height / denom : entry.intersectionRatio;\n };\n const thresholdSteps = Array.from({ length: 21 }, (_, i) => i / 20);\n let wasIntersecting = false;\n const observer = new IntersectionObserver(\n entries => {\n entries.forEach(entry => {\n // If the element is at least partially visible\n if (entry.isIntersecting && effectiveRatio(entry) >= scrollIntoViewThreshold) {\n wasIntersecting = true;\n start();\n } else if (wasIntersecting) {\n // Element scrolled OFF screen after having been on it -> out action\n wasIntersecting = false;\n handleEndAction();\n }\n });\n },\n { threshold: thresholdSteps }\n );\n observer.observe(root);\n cleanups.push(() => observer.disconnect());\n break;\n }\n\n case 'programmatic':\n // No auto-start; external code must call play()\n break;\n }\n\n return dispose;\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { createAdapterAnimator, getAnimatorConfig, PxDiagnosticKind, type PxAnimatedSvgDocument, type PxEngineCallbacks, type PxDiagnostics, type PxPlatformAdapter } from '@pixodesk/svg-animator-core';\nimport { camelCaseToKebabWordIfNeeded, createDiagnostics, isScrollTimeline, PX_STYLE_ATTR_NAMES } from '@pixodesk/svg-animator-core/internal';\nimport { setupAnimationTriggers } from '../triggers/PxAnimatorTriggers';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\n\n// Re-export the platform-neutral pieces from their historical home so the\n// package surface is unchanged by the core extraction.\nexport { createAdapterAnimator };\nexport type { PxPlatformAdapter };\n\nexport function getSelector(id: string) {\n // return `[data-px-id=\"${id}\"]`; FIXME\n return '#' + id;\n}\n\n\n////////////////////////////////////////////////////////////////\n// Browser DOM implementation\n////////////////////////////////////////////////////////////////\n\n\n\n/**\n * Creates an animator instance that uses a requestAnimationFrame loop for animations.\n * This is the browser DOM-specific version.\n *\n * @param {PxEngineCallbacks=} callbacks Optional lifecycle callbacks.\n * @param {Element=} rootElement Optional pre-rendered root element.\n * @returns {PxAnimatorApi} A PxAnimatorApi instance.\n * @internal\n */\nexport function createFrameLoopAnimator(\n doc: PxAnimatedSvgDocument,\n adapter?: PxPlatformAdapter,\n callbacks?: PxEngineCallbacks,\n rootElement?: Element | null\n): PxAnimatorApi {\n\n const config = getAnimatorConfig(doc) || {};\n\n // One channel for everything this engine has to say (API review §5).\n const diag = createDiagnostics(callbacks, '[PxAnimator]');\n\n // Use provided root element or try to find by selector\n if (!rootElement) {\n if (doc.id) {\n const rootSelector = getSelector(doc.id);\n rootElement = document.querySelector(rootSelector);\n if (!rootElement) diag.warn(PxDiagnosticKind.host, 'createFrameLoopAnimator: No root element found for selector: ' + rootSelector);\n } else {\n diag.warn(PxDiagnosticKind.host, 'createFrameLoopAnimator: No root element provided');\n }\n }\n\n const basicApi = createAdapterAnimator(\n doc,\n adapter || createDomAdapter(rootElement, diag),\n callbacks\n );\n\n // Specialize the platform-neutral API to the DOM: the root is an Element.\n const api: PxAnimatorApi = {\n ...basicApi,\n \"getRootElement\": () => rootElement || null\n };\n // D3 (scroll-timeline.design.md): triggers are meaningless when the playhead is\n // scroll-driven — writers must not emit them, and a document that carries them\n // anyway gets a warning, not behavior.\n // Every time-driven document IS wired: no `trigger` means the defaults (`startOn` 'load').\n if (isScrollTimeline(config)) {\n if (config.trigger) diag.warn(PxDiagnosticKind.usage, 'scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)');\n } else {\n // The disposer rides on destroy(), so the listeners go when the animator does (§14).\n const detachTriggers = setupAnimationTriggers(api, config.trigger ?? {}, diag);\n const destroyEngine = api.destroy.bind(api);\n api.destroy = () => { detachTriggers(); destroyEngine(); };\n }\n return api;\n}\n\nexport function createDomAdapter(rootElement?: Element | null, diag?: PxDiagnostics) {\n // Track warnings to avoid spamming console\n const warnedSelectors = new Set<string>();\n // Called directly by consumers too, so the channel is optional and defaults to the console.\n const report = diag ?? createDiagnostics(undefined, '[PxAnimator]');\n\n const adapter: PxPlatformAdapter = {\n isConnected: () => {\n if (!rootElement) return true; // No root element means we're always \"connected\"\n return rootElement.isConnected;\n },\n setAttribute: (id, attrName, value) => {\n\n attrName = camelCaseToKebabWordIfNeeded(attrName);\n\n const selector = getSelector(id);\n\n // Query elements by selector within root (or document if no root)\n const elements = rootElement?.querySelectorAll(selector) || document.querySelectorAll(selector);\n\n if (elements.length === 0 && !warnedSelectors.has(selector)) {\n warnedSelectors.add(selector);\n report.warn(PxDiagnosticKind.host, 'setAttribute: No elements found for selector \"' + selector + '\"');\n }\n\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n // `<pattern>` ignores the plain `transform` ATTRIBUTE (it transforms via\n // `patternTransform`). The WAAPI engine drives the same animation through\n // CSS `transform`, which browsers do apply to patterns — remap here so the\n // frames engine animates everything WAAPI animates.\n const effectiveAttrName = attrName === 'transform' && element.tagName === 'pattern'\n ? 'patternTransform'\n : attrName;\n element.setAttribute(effectiveAttrName, value);\n if (PX_STYLE_ATTR_NAMES.has(attrName)) {\n (element as HTMLElement).style[attrName as any] = value;\n }\n }\n },\n };\n return adapter;\n}","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { getAnimatorConfig, normalizeBindings, PxTimelineEngine, type PxAnimatedSvgDocument, type PxAnimationDefinition, type PxEngineCallbacks, type PxAnimatorConfig, type PxBezierPath, clampSeekMs, isValidPlaybackRate, progressToTimeMs, PX_RATE_REJECTED, PxDiagnosticKind, seekCeilingMs, timeToProgress } from '@pixodesk/svg-animator-core';\nimport { PX_PCT_BASED_ATTR_NAMES, bezierToSvgPath, camelCaseToKebabWordIfNeeded, clamp, PX_COLOR_ATTR_NAMES, composeTransformParts, cubicBezier, interpolateValue, kebabToCamelCaseWord, splitEasing, toRGBA, PX_TRANSFORM_FN_NAMES, type PxAnyKeyframe, type PxNormalizedKeyframe, keyframeEasing, keyframeValue, createDiagnostics } from '@pixodesk/svg-animator-core/internal';\nimport { getSelector } from './PxAnimatorFrameLoop';\nimport { setupAnimationTriggers } from '../triggers/PxAnimatorTriggers';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\n\n\n/**\n * Converts a single normalized keyframe into a Web Animations API Keyframe object.\n *\n * Handles three categories of CSS property:\n * - **Color attributes** (e.g. fill, stroke): array values are converted to an rgba() string.\n * - **Transform functions** (e.g. translate, rotate, scale): values are formatted as a\n * CSS transform function string and mapped to the transform property.\n * - **All other properties**: the value is coerced to a string as-is.\n *\n * If the resulting (cssKey, cssValue) pair is not supported by the browser (CSS.supports returns\n * false), cssKey is added to unsupportedSet so the caller can decide whether to fall back to the\n * frame-loop animator.\n */\nfunction createCssKf(kf: PxAnyKeyframe, t: number, propName: string, unsupportedSet: Set<string>) {\n let value = keyframeValue(kf);\n // The easing is on the SOURCE keyframe: it applies from this kf to the next, matching the\n // WAAPI convention. Resolved already when the keyframe came through `normalizeKeyframes`.\n const e = keyframeEasing(kf);\n\n const cssKf: Keyframe = {\n offset: t,\n easing: e && Array.isArray(e) ? \"cubic-bezier(\" + e.join(',') + \")\" : undefined\n };\n\n let cssValue: any;\n let cssKey = propName;\n\n if (PX_COLOR_ATTR_NAMES.has(propName) && Array.isArray(value)) {\n cssValue = toRGBA(value);\n } else if (propName === 'transform' && value !== null && typeof value === 'object' && !Array.isArray(value)) {\n // Unified transform: keyframe value is a parts record (PxTransformParts).\n // Compose all present parts into one CSS transform string.\n cssValue = composeTransformParts(value, { withUnits: true });\n cssKey = 'transform';\n } else if (PX_TRANSFORM_FN_NAMES.has(propName)) {\n if (Array.isArray(value)) {\n if (propName === 'translate') value = value.map(v => v + 'px');\n value = value.join(',');\n }\n if (propName === 'rotate') value = value + 'deg';\n cssValue = propName + '(' + value + ')';\n cssKey = 'transform';\n } else if (propName === 'd') {\n // Animated path. The value is a { paths: PxBezierPath[] } record (same shape the CSS/frame\n // builder consumes). Without this branch it stringified to \"[object Object]\", an invalid\n // `d`, which empties the <path> — the shape vanished in forced WAAPI while svg-css (which\n // emits `d: path(...)`) rendered it. Emit the CSS `d` presentation-attr syntax `path(\"…\")`\n // via the same bezierToSvgPath used by the CSS path, so WAAPI animates it identically.\n const paths: Array<PxBezierPath> = (value && typeof value === 'object' && Array.isArray(value.paths))\n ? value.paths : [];\n // forceCurves: CSS/WAAPI only interpolates `path()` values with IDENTICAL command\n // sequences — uniform all-`C` output keeps every keyframe structurally equal\n // (mixed L/C, e.g. round-corner radius 0 vs >0, would go DISCRETE → 50% flip).\n cssValue = 'path(\"' + paths.map(bz => bezierToSvgPath(bz, true)).join('') + '\")';\n } else if (PX_PCT_BASED_ATTR_NAMES.has(propName) && typeof value === 'number') {\n // Percent-based CSS properties (offset-distance): the wire carries 0..1 numbers,\n // but the property needs a <length-percentage>. The frames engine already converts\n // (`calcPropertyValue`); without this twin branch WAAPI got a bare \"0.25\", which\n // `CSS.supports('offset-distance', '0.25')` rejects — so the attr was flagged\n // unsupported and the WHOLE document silently fell back to frames. Same maths,\n // same units, both engines.\n cssValue = (value * 100) + '%';\n } else {\n cssValue = '' + value;\n }\n\n // `CSS.supports` takes a CSS PROPERTY name, so it must be asked in kebab-case —\n // `CSS.supports('strokeDasharray', …)` is always false. Prop names reach us in\n // either form (the app materializes trim paths as camelCase `strokeDasharray` /\n // `strokeDashoffset` / `strokeOpacity`), and an unsupported entry makes the caller\n // discard EVERY animation on the document, so a false negative here is costly.\n if (!CSS.supports(camelCaseToKebabWordIfNeeded(cssKey), cssValue)) unsupportedSet.add(cssKey);\n\n cssKey = kebabToCamelCaseWord(cssKey);\n cssKf[cssKey] = cssValue;\n return cssKf;\n}\n\n/**\n * Clips keyframes to the [0, duration] range.\n * If a keyframe pair straddles a boundary (t=0 or t=duration), inserts an interpolated\n * keyframe at the boundary with the correct value and split easing, so WAAPI sees\n * exact start/end values rather than out-of-range ones.\n */\nfunction clipKeyframesToDuration(\n propName: string,\n keyframes: PxNormalizedKeyframe[],\n duration: number\n): PxNormalizedKeyframe[] {\n const result: PxNormalizedKeyframe[] = [];\n\n for (let i = 0; i < keyframes.length; i++) {\n const kf = keyframes[i];\n const t = kf.t ?? 0;\n\n if (t < 0) {\n // If the next keyframe is in range, interpolate value at t=0\n const next = keyframes[i + 1];\n if (next && (next.t ?? 0) >= 0) {\n const nextT = next.t ?? 0;\n const localFrac = (0 - t) / (nextT - t);\n const easedFrac = kf.e ? cubicBezier(kf.e as [number, number, number, number])(localFrac) : localFrac;\n const { right: rightEasing } = splitEasing(kf.e as any, localFrac);\n result.push({ t: 0, v: interpolateValue(propName, kf.v, next.v, easedFrac), e: rightEasing });\n }\n continue;\n }\n\n if (t > duration) {\n // If the previous keyframe was in range, interpolate value at t=duration\n const prev = keyframes[i - 1];\n if (prev && (prev.t ?? 0) <= duration) {\n const prevT = prev.t ?? 0;\n const localFrac = (duration - prevT) / (t - prevT);\n const easedFrac = prev.e ? cubicBezier(prev.e as [number, number, number, number])(localFrac) : localFrac;\n const { left: leftEasing } = splitEasing(prev.e as any, localFrac);\n if (result.length > 0) result[result.length - 1] = { ...result[result.length - 1], e: leftEasing };\n result.push({ t: duration, v: interpolateValue(propName, prev.v, kf.v, easedFrac), e: undefined });\n }\n break;\n }\n\n result.push(kf);\n }\n\n return result;\n}\n\n/**\n * Converts a PxAnimationDefinition into a map of Web Animations API Keyframe arrays, one per\n * animated property.\n *\n * For each property in the definition the function:\n * 1. Clips keyframes to [0, duration], interpolating boundary values when a pair straddles an edge.\n * 2. Normalizes keyframe time values to the [0, 1] offset range (time / duration).\n * 3. Delegates CSS value conversion to createCssKf.\n * 4. Ensures the keyframe sequence always starts at offset: 0 and ends at offset: 1 — a\n * requirement of the Web Animations API for correct looping behavior. If the first keyframe\n * starts after 0 or the last keyframe ends before 1, a copy of that keyframe is inserted at the\n * boundary with the adjusted offset.\n */\nexport function convertToWebApiKeyframes(\n animDef: PxAnimationDefinition,\n unsupportedSet: Set<string>,\n config: PxAnimatorConfig\n): Map<string, Keyframe[]> {\n const result = new Map<string, Keyframe[]>();\n\n for (const [propName, propAnim] of Object.entries(animDef)) {\n const duration = config.duration || 1;\n const clippedKeyframes = clipKeyframesToDuration(propName, propAnim.keyframes || [], duration);\n const cssKeyframes: Keyframe[] = [];\n\n for (let i = 0; i < clippedKeyframes.length; i++) {\n const kf = clippedKeyframes[i];\n\n const t = clamp((kf.t ?? 0) / duration, 0, 1);\n const cssKf: Keyframe = createCssKf(kf, t, propName, unsupportedSet);\n\n // Keyframes need to start with offset:0 to work correctly with loops\n if (i === 0 && (cssKf.offset || 0) > 0) {\n cssKeyframes.push({ ...cssKf, offset: 0 });\n }\n\n cssKeyframes.push(cssKf);\n }\n\n // Keyframes need to end with offset:1 to work correctly with loops\n if (cssKeyframes.length > 0 && (cssKeyframes[cssKeyframes.length - 1].offset || 0) < 1) {\n cssKeyframes.push({\n ...cssKeyframes[cssKeyframes.length - 1],\n offset: 1\n });\n }\n\n if (cssKeyframes.length > 0) {\n result.set(propName, cssKeyframes);\n }\n }\n\n return result;\n}\n\n/**\n * Creates an animator instance that uses the native Web Animations API.\n *\n * This is the preferred, more performant animator. It will return null if the\n * animation configuration contains properties not supported by the browser's\n * Web Animations API implementation, unless forceEvenIfHasUnsupportedAttrs is true.\n *\n * @param callbacks Optional lifecycle callbacks.\n * @param rootElement Root element.\n * @param forceEvenIfHasUnsupportedAttrs If true, an animator will be created even if some CSS properties are not supported.\n * @returns An PxAnimatorApi instance, or null if unsupported features are used and not forced.\n */\n/** Native scroll-timeline payload (`timeline.engine: 'native'` / `auto`; see PxScrollDriver.createNativeScrollTimeline): the\n * browser-native timeline every Animation attaches to, plus optional range offsets. */\nexport interface PxWebApiScrollTimeline {\n timeline: AnimationTimeline;\n rangeStart?: Record<string, unknown>;\n rangeEnd?: Record<string, unknown>;\n}\n\n/** @internal */\nexport function createWebApiAnimator(\n doc: PxAnimatedSvgDocument,\n callbacks?: PxEngineCallbacks,\n rootElement?: Element | null,\n forceEvenIfHasUnsupportedAttrs?: boolean,\n scrollTimeline?: PxWebApiScrollTimeline\n): PxAnimatorApi | null {\n\n const config = getAnimatorConfig(doc) || {};\n\n // One channel for everything this engine has to say (API review §5).\n const diag = createDiagnostics(callbacks, '[PxAnimator]');\n\n // Use provided root element or try to find by selector\n if (!rootElement) {\n if (doc.id) {\n const rootSelector = getSelector(doc.id);\n rootElement = document.querySelector(rootSelector);\n if (!rootElement) diag.warn(PxDiagnosticKind.host, 'createWebApiAnimator: No root element found for selector: ' + rootSelector);\n } else {\n diag.warn(PxDiagnosticKind.host, 'createWebApiAnimator: No root element provided');\n }\n }\n\n // WAAPI bindings: motion-along-path is materialized into plain `{ translate,\n // rotate }` transform kfs inside `normalizeAnimationDefinition` (gated on\n // `engine === 'waapi'`). The WAAPI keyframe builder then sees a vanilla\n // unified-transform animation — no DOM-style mutation, no offset-path.\n const bindings = normalizeBindings(doc, PxTimelineEngine.native);\n\n const animations: Array<Animation> = [];\n\n const _iterations = config.iterations;\n let iterations: number | undefined;\n if (typeof _iterations === 'number') iterations = _iterations;\n if (_iterations === 'infinite') iterations = Infinity;\n\n const unsupportedSet = new Set<string>();\n\n // A document commonly produces many Animation objects (one per element ×\n // animated property), but the public callbacks describe the document\n // timeline as a whole — guard so each fires once per finish/remove episode.\n // `finishNotified` re-arms on play/cancel/seek.\n let finishNotified = false;\n let removeCalled = false;\n\n ////////////////////////////////////////////////////////////////\n\n // Warn if no bindings defined\n if (!bindings?.length) {\n diag.warn(PxDiagnosticKind.document, 'createWebApiAnimator: No animation bindings defined');\n }\n\n for (const binding of bindings || []) {\n const animDef = binding.animate;\n if (!animDef || typeof animDef !== 'object' || Array.isArray(animDef)) {\n diag.warn(PxDiagnosticKind.document, 'createWebApiAnimator: Empty or unresolved binding', binding);\n continue;\n }\n\n const selector = getSelector(binding.id);\n\n // Use CSS selector to find elements\n const elements = rootElement?.querySelectorAll(selector) || document.querySelectorAll(selector);\n\n if (elements.length === 0) {\n diag.warn(PxDiagnosticKind.host, 'createWebApiAnimator: No elements found for selector \"' + selector + '\"');\n }\n\n // Convert animation definition to Web API keyframes\n const keyframesMap = convertToWebApiKeyframes(animDef, unsupportedSet, config);\n\n\n // Delay handling:\n // - Positive delay (e.g., 500): Wait before starting → use delay option\n // - Negative delay (e.g., -500): Start mid-animation → use currentTime to seek\n // (Web Animations API doesn't reliably support negative delay values)\n // WAAPI `currentTime` spans ALL iterations, so a finite timeline clamps\n // the seek to duration × iterations (seeking to exactly the end must\n // land on the final frame, not wrap to 0); an infinite timeline wraps\n // within one iteration instead.\n const positiveDelay = config.delay && config.delay > 0 ? config.delay : undefined;\n let seekPosition: number | undefined;\n if (config.delay && config.delay < 0 && config.duration) {\n const rawSeek = -config.delay;\n seekPosition = iterations === Infinity\n ? rawSeek % config.duration\n : Math.min(rawSeek, config.duration * (iterations ?? 1));\n }\n\n const effectOptions: KeyframeEffectOptions = {\n duration: config.duration,\n delay: positiveDelay,\n // Default to 'forwards' so elements hold their final state after the\n // animation ends — consistent with Lottie and other animation runtimes.\n // Without this, seeking to the last frame reverts elements to their\n // pre-animation state (the Web Animations API \"after\" phase with fill:'none').\n fill: config.fill ?? 'forwards',\n direction: config.direction,\n iterations: iterations\n };\n\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n\n for (const [, keyframes] of keyframesMap) {\n if (keyframes.length > 0) {\n try {\n const effect = new KeyframeEffect(element, keyframes, effectOptions);\n // Native scroll timeline (`mode: 'native'` / `auto`): the browser computes\n // progress AND applies values (compositor-driven). Range offsets are\n // per-Animation in WAAPI.\n const anim = new Animation(effect, scrollTimeline ? scrollTimeline.timeline : document.timeline);\n if (scrollTimeline) {\n const a = anim as unknown as { rangeStart?: unknown; rangeEnd?: unknown };\n if (scrollTimeline.rangeStart) a.rangeStart = scrollTimeline.rangeStart;\n if (scrollTimeline.rangeEnd) a.rangeEnd = scrollTimeline.rangeEnd;\n }\n\n if (callbacks?.onFinish) anim.onfinish = () => {\n if (finishNotified) return;\n finishNotified = true;\n callbacks.onFinish?.();\n };\n if (callbacks?.onRemove) anim.onremove = () => {\n if (removeCalled) return;\n removeCalled = true;\n callbacks.onRemove?.();\n };\n\n // Seek forward for negative delay (e.g., delay=-500 → seek to 500ms).\n // `!== undefined` — a seek of exactly 0 is still a seek.\n if (seekPosition !== undefined) {\n anim.currentTime = seekPosition;\n }\n\n animations.push(anim);\n } catch (e) {\n // Was a bare dump of the error object; the channel carries it as the\n // DETAIL so a handler gets something it can act on (review §5).\n diag.warn(PxDiagnosticKind.internal, 'createWebApiAnimator: could not build the animation', e);\n }\n }\n }\n }\n }\n\n ////////////////////////////////////////////////////////////////\n\n if (!forceEvenIfHasUnsupportedAttrs && unsupportedSet.size) {\n diag.warn(PxDiagnosticKind.platform, 'Unsupported CSS attrs: ' + [...unsupportedSet].join(', '));\n return null;\n }\n\n ////////////////////////////////////////////////////////////////\n\n const api: PxAnimatorApi = {\n\n \"isReady\": () => true,\n\n \"getRootElement\": () => rootElement || null,\n\n \"isPlaying\": (): boolean => { return animations[0]?.playState === 'running'; },\n\n \"play\": () => {\n finishNotified = false; // re-arm finish notification\n animations.forEach(a => a.play());\n callbacks?.onPlay?.();\n },\n \"pause\": () => {\n animations.forEach(a => a.pause());\n callbacks?.onPause?.();\n },\n \"cancel\": () => {\n finishNotified = false; // re-arm finish notification\n animations.forEach(a => a.cancel());\n callbacks?.onCancel?.();\n },\n \"finish\": () => {\n for (const a of animations) {\n try {\n if (a.effect?.getTiming().iterations === Infinity) {\n a.effect.updateTiming({ iterations: 1 });\n a.finish();\n a.effect.updateTiming({ iterations: Infinity });\n } else {\n a.finish();\n }\n } catch (e) {\n a.cancel();\n }\n }\n // Natural finish also reaches onFinish via the native `anim.onfinish`\n // handler wired at construction time.\n },\n\n \"setPlaybackRate\": (rate: number) => {\n // WAAPI itself accepts 0 and silently freezes; the other two engines reject it.\n // One answer everywhere (review §3) — `pause()` is how you stop.\n if (!isValidPlaybackRate(rate)) {\n diag.warn(PxDiagnosticKind.usage, PX_RATE_REJECTED);\n return api;\n }\n animations.forEach(a => (a.playbackRate = rate));\n return api;\n },\n \"getCurrentTime\": (): number | null => {\n const res = animations[0]?.currentTime ?? null;\n return res !== null ? +res : null;\n },\n \"setCurrentTime\": (time: number) => {\n // Clamp like every other engine (review §3). A duration is not always declared,\n // and a ceiling of 0 would pin every seek to the first frame — so clamp only when\n // the timeline length is actually known, and otherwise just floor at 0.\n const ceiling = seekCeilingMs(config.duration ?? 0, iterations ?? 1);\n const seek = ceiling > 0 ? clampSeekMs(time, ceiling) : Math.max(0, time);\n finishNotified = false; // re-arm finish notification\n animations.forEach(a => {\n a.currentTime = seek;\n });\n },\n\n \"getCurrentProgress\": (): number | null => {\n const t = api.getCurrentTime();\n return t === null ? null : timeToProgress(t, config.duration ?? 0, iterations ?? 1);\n },\n\n \"setCurrentProgress\": (progress: number) => {\n api.setCurrentTime(progressToTimeMs(progress, config.duration ?? 0, iterations ?? 1));\n },\n\n \"destroy\": () => {\n api.cancel();\n animations.splice(0, animations.length);\n if (!removeCalled) {\n removeCalled = true;\n callbacks?.onRemove?.();\n }\n }\n };\n\n ////////////////////////////////////////////////////////////////\n\n // D3 (scroll-timeline.design.md): triggers are inert on a scroll-driven document —\n // writers must not emit them; a doc that carries them anyway gets a warning.\n // Every time-driven document IS wired: no `trigger` means the defaults (`startOn` 'load').\n if (config.timelineSource === 'scroll') {\n if (config.trigger) diag.warn(PxDiagnosticKind.usage, 'scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)');\n } else {\n // The disposer rides on destroy(), so the listeners go when the animator does (§14).\n const detachTriggers = setupAnimationTriggers(api, config.trigger ?? {}, diag);\n const destroyEngine = api.destroy.bind(api);\n api.destroy = () => { detachTriggers(); destroyEngine(); };\n }\n\n // A progress-based timeline only tracks while the animation is PLAYING — start it\n // (there is no wall clock involved; \"playing\" here means \"bound to the timeline\").\n if (scrollTimeline) {\n animations.forEach(a => a.play());\n }\n\n return api;\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { getAnimatorConfig, isNativeForced, mayUseNativeScrollTimeline, PxDiagnosticKind, PxTimelineEngineSetting, type PxAnimatedSvgDocument, type PxEngineCallbacks, type PxAnimatorConfig, type PxAnimatorCallbacks, type PxPlatformAdapter } from '@pixodesk/svg-animator-core';\nimport { createDiagnostics, isScrollTimeline, scrollTotalDurationMs } from '@pixodesk/svg-animator-core/internal';\nimport { asThrownError, createInertAnimator, toEngineCallbacks } from '../shared/PxAnimatorCallbacks';\nimport { createFrameLoopAnimator } from './PxAnimatorFrameLoop';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\nimport { createWebApiAnimator } from './PxAnimatorWebApi';\nimport { applyScrollPin, createNativeScrollTimeline, createScrollDriver } from '../scroll/PxScrollDriver';\n\n/**\n * Engine construction, shared by the full player and the pre-rendered builds.\n *\n * Everything here operates on a document that is ALREADY in its final shape — no\n * validation, no materialization, no rendering. `createAnimatorImpl` calls in after it\n * has done those stages; the pre-rendered entries call in directly, because the Editor\n * did them at export time. See dev-docs/plans/prerendered-player-builds.md.\n */\n\n/**\n * Applies the two `animator` config behaviors that are engine-independent, then hands\n * off to `make` for the actual engine.\n *\n * `resetOnFinish` is composed here so BOTH engines get it: after a NATURAL finish the\n * document snaps back to its start state (same mechanics as the trigger `reset`\n * out-action). The caller's own `onFinish` still fires first. `apiRef` is assigned right\n * after creation — finish always happens asynchronously later.\n */\nexport function finaliseAnimator(\n animatorConfig: PxAnimatorConfig,\n callbacks: PxEngineCallbacks | undefined,\n make: (effectiveCallbacks?: PxEngineCallbacks) => PxAnimatorApi\n): PxAnimatorApi {\n\n let apiRef: PxAnimatorApi | undefined;\n let effectiveCallbacks = callbacks;\n if (animatorConfig.resetOnFinish) {\n effectiveCallbacks = {\n ...callbacks,\n onFinish: () => {\n callbacks?.onFinish?.();\n apiRef?.cancel();\n },\n };\n }\n\n const res = make(effectiveCallbacks);\n apiRef = res;\n\n if (animatorConfig.debugGlobalName) {\n (window as any)[animatorConfig.debugGlobalName] = res; // Exposing as global variable for debug\n }\n\n return res;\n}\n\n/**\n * Picks the engine the way the full player does, from `timeline.engine`: `player` pins the\n * frame loop; otherwise waapi, with a frames fallback for unsupported attrs unless\n * `native` demands waapi.\n */\nexport function bindWithEngineChoice(\n doc: PxAnimatedSvgDocument,\n adapter?: PxPlatformAdapter,\n callbacks?: PxEngineCallbacks,\n rootElement?: Element | null\n): PxAnimatorApi {\n const animatorConfig = getAnimatorConfig(doc) || {};\n // One channel for everything this binder and the scroll driver have to say (review §5).\n const diag = createDiagnostics(callbacks, '[PxAnimator]');\n\n // Scroll-driven document: the playhead follows scroll position, never the wall\n // clock. One knob — `timeline.engine` — picks the row (scroll-timeline.design.md §4.0):\n // player → the player measures progress, frames applies values (`setCurrentTime`)\n // native → browser ScrollTimeline/ViewTimeline drives waapi (compositor thread)\n // auto → native first; when unsupported, the player measures and waapi (or\n // frames, if waapi declines the doc) applies — the fallback cascade (D8)\n // Triggers are inert either way (D3 — both engines warn + skip\n // `setupAnimationTriggers` for scroll docs). The animator is never `play()`ed by us\n // for custom driving; scrubbing via `setCurrentTime` holds the pose.\n if (isScrollTimeline(animatorConfig)) {\n return finaliseAnimator(animatorConfig, callbacks, cb => {\n\n // `pin` — hold the canvas on screen for the scrubbed stretch (`position: sticky`,\n // + an optional tall wrapper the player injects). MUST be applied before anything\n // measures: it changes the very geometry the timeline reads, and\n // `subject: 'parent'` is meant to resolve THROUGH the injected wrapper.\n let unpin = () => { /* nothing pinned */ };\n\n // `auto` / `native`: try the browser's own timeline first.\n if (mayUseNativeScrollTimeline(animatorConfig.engine) && rootElement) {\n unpin = applyScrollPin(rootElement, animatorConfig.scroll);\n const native = createNativeScrollTimeline(rootElement, animatorConfig, diag);\n if (native) {\n const api = createWebApiAnimator(doc, cb, rootElement,\n isNativeForced(animatorConfig.engine), native);\n if (api) {\n const destroyNative = api.destroy.bind(api);\n api.destroy = () => { unpin(); destroyNative(); };\n return api;\n }\n // unsupported attrs → fall through to frames + custom below\n }\n unpin(); // …and undo the pin so the fallback re-applies it\n unpin = () => { /* re-pinned below */ };\n }\n\n // The player measures progress (the reference implementation). Engine per\n // `mode`: waapi unless `player` pins frames or waapi declines the doc.\n const api = (\n animatorConfig.engine !== PxTimelineEngineSetting.js\n ? createWebApiAnimator(doc, cb, rootElement, isNativeForced(animatorConfig.engine))\n : null\n ) || createFrameLoopAnimator(doc, adapter, cb, rootElement);\n\n // The animator knows its real root — for an SVG+JS export the runtime binds to the\n // `<svg>` already in the document, which is NOT the container passed in. Pin and\n // measure the same element, or the pin lands on nothing (it silently did).\n const subject = api.getRootElement?.() || rootElement;\n if (subject) {\n unpin = applyScrollPin(subject, animatorConfig.scroll);\n const totalMs = scrollTotalDurationMs(animatorConfig);\n const driver = createScrollDriver(subject, animatorConfig,\n progress => api.setCurrentTime(progress * totalMs), diag);\n if (driver) {\n // Tie the driver's (and the pin's) lifetime to the animator's.\n const destroy = api.destroy.bind(api);\n api.destroy = () => { driver.destroy(); unpin(); destroy(); };\n }\n } else {\n diag.warn(PxDiagnosticKind.host, 'scroll timeline: no root element to observe — animation will stay at frame 0');\n }\n return api;\n });\n }\n\n return finaliseAnimator(animatorConfig, callbacks, cb => {\n if (animatorConfig.engine === PxTimelineEngineSetting.js) {\n // `player` pins the frame loop, even if waapi could be used.\n return createFrameLoopAnimator(doc, adapter, cb, rootElement);\n }\n // Try waapi first; fall back to frames if it returns null (unsupported\n // attrs) unless `native` demands waapi.\n return (\n createWebApiAnimator(doc, cb, rootElement,\n isNativeForced(animatorConfig.engine)\n ) ||\n createFrameLoopAnimator(doc, adapter, cb, rootElement)\n );\n });\n}\n\n/**\n * Options accepted by the pre-rendered entry points — a subset of `PxAnimatorOptions`: the\n * document and the callbacks INLINE under the same names every surface uses (review §9). No\n * `adapter`: a pre-rendered SVG is by definition already in the DOM (review §25.14).\n * @public\n */\nexport interface PxPrerenderedAnimatorOptions extends PxAnimatorCallbacks {\n /**\n * The animation document. For a pre-rendered SVG this carries `animator.definitions`\n * and `animator.bindings` only — no `children`, because the elements are already\n * in the DOM.\n */\n doc: PxAnimatedSvgDocument;\n}\n\nfunction requireDoc(options: PxPrerenderedAnimatorOptions): PxAnimatedSvgDocument {\n // A wrong CALL throws (a bug at the call site); a document that cannot play is reported\n // through `onError` below — the rule in core's `PxDiagnostics` (review §25.1).\n if (!options?.doc) throw new Error('createAnimator: `doc` is required');\n return options.doc;\n}\n\n/**\n * Builds the player; when that throws, reports \"this instance will not play\" through the\n * diagnostics channel and returns an inert API instead of throwing at the caller.\n */\nfunction buildOrReport(options: PxPrerenderedAnimatorOptions, build: () => PxAnimatorApi): PxAnimatorApi {\n try {\n return build();\n } catch (e) {\n const err = asThrownError(e);\n createDiagnostics(options, '[PxAnimator]')\n .error(PxDiagnosticKind.internal, 'createAnimator: could not build the player — ' + err.message, err);\n return createInertAnimator();\n }\n}\n\n/**\n * Pre-rendered entry, both engines (`auto` / `player` / `native` all honored).\n *\n * Deliberately skips `validateNodeEffects`, `materializeAllInTree`, `generateNewIds` and\n * `renderNode`. Safe because the payload has no `children`, so all four are provably\n * no-ops for this document shape — and none of them reads `animator.bindings`.\n * @public\n */\nexport function createPrerenderedAnimator(options: PxPrerenderedAnimatorOptions): PxAnimatorApi {\n const doc = requireDoc(options);\n return buildOrReport(options, () => bindWithEngineChoice(doc, undefined, toEngineCallbacks(options), null));\n}\n\n/**\n * Pre-rendered entry, WAAPI only — the smallest build. Forces waapi so there is no\n * frames fallback to link against (`createWebApiAnimator` never returns null when\n * forced; it only warns about unsupported attrs).\n * @public\n */\nexport function createPrerenderedWaapiAnimator(options: PxPrerenderedAnimatorOptions): PxAnimatorApi {\n const doc = requireDoc(options);\n return buildOrReport(options, () => {\n const animatorConfig = getAnimatorConfig(doc) || {};\n return finaliseAnimator(animatorConfig, toEngineCallbacks(options),\n cb => createWebApiAnimator(doc, cb, null, true)!);\n });\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n/**\n * Wire keys shared by every entry point.\n *\n * These live here rather than in `PxAnimator.ts` on purpose: that module used to end with a\n * top-level `if (typeof window !== 'undefined')` block publishing `createAnimator` /\n * `loadTagAnimators` as globals. A module-level side effect cannot be tree-shaken, so\n * importing ANY symbol from `PxAnimator.ts` pulled the entire full player in with it —\n * which silently made the pre-rendered builds the same size as the full one until this\n * constant was moved out. See dev-docs/plans/prerendered-player-builds.md.\n *\n * That block is gone (API review §4) and the package now declares `\"sideEffects\": false`, but\n * keeping these here costs nothing and removes the trap for good.\n */\n\n/**\n * Key under which `createAnimator` options carry the inline animation document. The editor\n * writes it into every exported SVG+JS — `createAnimator({\"doc\": …})` — so it is part of the\n * export format, which is why it is a named constant and not a literal.\n * @internal\n */\nexport const PX_ANIMATOR_DOC_KEY = 'doc';\n\n/** Key under which `createAnimator` options carry the per-instance `timeline` override. */\nexport const PX_ANIMATOR_TIMELINE_KEY = 'timeline';\n\n/** Key that makes the override start from the player's default timeline instead of the document's. */\nexport const PX_ANIMATOR_RESET_KEY = 'resetTimeline';\n"],"mappings":"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","names":["pathStr","_a","_a","_b"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.prerendered-waapi.ts","../../svg-animator-core/src/schema/PxSchema.ts","../../svg-animator-core/src/format/PxAnimatorConstants.ts","../../svg-animator-core/src/format/PxAnimatorTypes.ts","../../svg-animator-core/src/util/PxAnimatorUtil.ts","../../svg-animator-core/src/materialize/PxMotionPath.ts","../../svg-animator-core/src/animation/PxDefinitions.ts","../../svg-animator-core/src/playback/PxPlaybackTime.ts","../../svg-animator-core/src/playback/PxDiagnostics.ts","../src/shared/PxAnimatorCallbacks.ts","../src/triggers/PxAnimatorTriggers.ts","../src/engines/PxAnimatorFrameLoop.ts","../src/engines/PxAnimatorWebApi.ts","../src/engines/PxAnimatorBind.ts","../src/shared/PxAnimatorKeys.ts"],"sourcesContent":["/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n// ============================================================================\n// UMD entry for PRE-RENDERED SVG — WAAPI only. The smallest build.\n//\n// Inlined by the Editor into SVG+JS exports whose `timeline.engine` is `native`. On top of\n// what `index.prerendered.ts` drops, this also excludes the frame-loop engine: the native\n// engine is forced, so there is no fallback path to link against (`createWebApiAnimator`\n// never returns null when forced — it only warns about unsupported attrs).\n//\n// Exported AS `createAnimator` so the emitted `<script>` is identical across bundles.\n// See dev-docs/plans/prerendered-player-builds.md.\n// ============================================================================\n\nexport { createPrerenderedWaapiAnimator as createAnimator } from './engines/PxAnimatorBind';\nexport { PX_ANIMATOR_DOC_KEY } from './shared/PxAnimatorKeys';\n\nexport { setupAnimationTriggers } from './triggers/PxAnimatorTriggers';\n\nexport type { PxPrerenderedAnimatorOptions } from './engines/PxAnimatorBind';\nexport type { PxAnimatorApi, PxPlaybackApi } from './shared/PxAnimatorWebTypes';\nexport type { PxAnimatedSvgDocument, PxEngineCallbacks } from '@pixodesk/svg-animator-core';\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n/**\n * Lightweight Zod-like schema system — type declaration + runtime sanitization.\n *\n * Two operations per schema:\n * isValid(raw) — true only when raw already conforms; no repair needed.\n * sanitize(raw) — always returns T; fixes or replaces invalid input with defaults.\n *\n * Field rules inside px.object():\n * required field → if absent/invalid, field's declared default is used.\n * optional field → if absent/invalid (or wrong JS type), field becomes undefined.\n *\n * Array: items that cannot even be attempted are filtered out.\n * Record: values that cannot even be attempted are dropped.\n *\n * The distinction between isValid and _canSanitize:\n * isValid — strict; ALL fields must be correct, no repairs accepted.\n * _canSanitize — permissive; \"is the JS type right enough to attempt repair?\"\n * For containers (object/array/record) this is a structural check\n * so that partially-valid nested objects are sanitized rather than dropped.\n * For primitives it equals isValid (a wrong primitive type is unrecoverable).\n *\n * Validation context (optional):\n * Pass a PxValidationContext as the second arg to isValid() to collect per-field\n * error and warning messages with their dot-paths.\n * e.g. schema.isValid(raw, ctx, []) where ctx = { errors: [], warnings: [] }\n * Path segments are pushed/popped during recursion; join with '.' only when reporting.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public interfaces\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The reason text a STRICT closed object reports for an undeclared key — `\"<dot.path>: <this>\"`.\n *\n * Exported because it is the one validation finding that carries a MEANING beyond \"invalid\":\n * a key this build does not declare is a key some other build wrote, which is the signal a\n * reader turns into \"written for a newer schema — update your player/editor\". Callers match on\n * this constant rather than on a copy of the sentence.\n * @internal\n */\nexport const PX_UNKNOWN_KEY_ERROR = 'unexpected extra key';\n\n/** Collects structured validation feedback. Pass to isValid() as the second argument. @public @advanced */\nexport interface PxValidationContext {\n /** Per-field errors — each entry is \"<dot.path>: <reason>\". */\n errors: Array<string>;\n /** Per-field warnings — same format as errors but non-fatal. */\n warnings: Array<string>;\n /**\n * When true, closed objects (`px.object` / `px.extendedObject`) report any\n * extra (undeclared) keys as errors. Default `false` (or omitted) — extras\n * are ignored, matching `sanitize`'s strip-extras behavior, which is what\n * production app code wants (forward-compat with unknown future fields).\n * Tests that want to lock the wire shape down to its declared schema\n * should pass `strict: true`.\n *\n * Open objects (`px.openObject`) are unaffected by `strict` — they accept\n * extras by design (used for SVG element nodes that carry arbitrary attrs).\n */\n strict?: boolean;\n}\n\n/** @public @advanced */\nexport interface PxSchema<T, IsOptional extends boolean = false> {\n /** Phantom discriminator — `false` for required schemas, `true` for optional. Used by InferShape. */\n readonly _optional: IsOptional;\n sanitize(raw: unknown): T;\n /**\n * Returns true when raw already conforms to this schema.\n * Pass an optional PxValidationContext to collect per-field error messages.\n * Pass a mutable Array<string> as `path` — segments are pushed/popped during\n * recursion so only a single array allocation is needed per validation call.\n */\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean;\n /** True if raw has the right structure to attempt sanitization (may still need repair). */\n _canSanitize(raw: unknown): boolean;\n readonly _default: T;\n /** Returns a new schema that marks this field as optional (?: in object shapes). */\n optional(): PxSchema<T | undefined, true>;\n}\n\n/** Extract the TypeScript type from a schema. @public @advanced */\nexport type PxInfer<S> = S extends PxSchema<infer T, any> ? T : never;\n\n/**\n * Removes string/number index signatures from T, leaving only explicitly named properties.\n * Useful for strict property-access checking on types that have an open `[key: string]: any`.\n *\n * @example\n * type Strict = PxRemoveIndex<PxAnimatedSvgDocument>;\n * Strict['animator'] // PxAnimatorConfig | undefined ✓\n * Strict['anything'] // compile error ✓\n * @public @advanced\n */\nexport type PxRemoveIndex<T> = {\n [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K]\n};\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Internal path helper\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Joins path segments into a dot-path string for error messages.\n// Segments starting with '[' are appended without a leading dot.\n// e.g. ['obj', 'key'] -> 'obj.key' ['arr', '[0]'] -> 'arr[0]' [] -> '.'\nfunction pathStr(path: Array<string>): string {\n if (!path.length) return '.';\n let result = '';\n for (const seg of path) {\n if (seg.startsWith('[')) result += seg;\n else result += (result ? '.' : '') + seg;\n }\n return result;\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Base — default _canSanitize = isValid (correct for primitives)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Shared base; overrides _canSanitize only when the structural check must differ from isValid. */\nabstract class Base<T, IsOptional extends boolean = false> implements PxSchema<T, IsOptional> {\n // `declare` emits no runtime code; purely satisfies the interface's phantom _optional property.\n declare readonly _optional: IsOptional;\n abstract sanitize(raw: unknown): T;\n abstract isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean;\n abstract readonly _default: T;\n\n _canSanitize(raw: unknown): boolean { return this.isValid(raw); }\n\n optional(): PxSchema<T | undefined, true> { return new Optional(this); }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Optional wrapper\n// Uses _canSanitize (not isValid) so that partially-valid nested objects are\n// repaired rather than dropped entirely.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Wraps any schema to make its value optional.\n * sanitize uses _canSanitize (not isValid) so partially-valid objects are repaired, not dropped.\n */\nclass Optional<T> extends Base<T | undefined, true> {\n readonly _default = undefined as T | undefined;\n constructor(private readonly inner: PxSchema<T, any>) { super(); }\n\n sanitize(raw: unknown): T | undefined {\n if (raw === undefined || raw === null) return undefined;\n return this.inner._canSanitize(raw) ? this.inner.sanitize(raw) : undefined;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (raw === undefined || raw === null) return true;\n return this.inner.isValid(raw, ctx, path);\n }\n\n override _canSanitize(raw: unknown): boolean {\n return raw === undefined || raw === null || this.inner._canSanitize(raw);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Primitives — _canSanitize = isValid (a wrong primitive type is unrecoverable)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** String schema; wrong type is unrecoverable so _canSanitize = isValid (inherited default). */\nclass Str extends Base<string> {\n constructor(readonly _default: string = '') { super(); }\n sanitize(raw: unknown): string { return typeof raw === 'string' ? raw : this._default; }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (typeof raw === 'string') return true;\n ctx?.errors.push(pathStr(path ?? []) + ': expected string, got ' + typeof raw);\n return false;\n }\n}\n\n/** Finite-number schema; rejects NaN and ±Infinity as unrecoverable. */\nclass Num extends Base<number> {\n constructor(readonly _default: number = 0) { super(); }\n sanitize(raw: unknown): number {\n return typeof raw === 'number' && isFinite(raw) ? raw : this._default;\n }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (typeof raw === 'number' && isFinite(raw)) return true;\n ctx?.errors.push(pathStr(path ?? []) + ': expected finite number, got ' + JSON.stringify(raw));\n return false;\n }\n}\n\n/** Boolean schema. */\nclass Bool extends Base<boolean> {\n constructor(readonly _default: boolean = false) { super(); }\n sanitize(raw: unknown): boolean { return typeof raw === 'boolean' ? raw : this._default; }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (typeof raw === 'boolean') return true;\n ctx?.errors.push(pathStr(path ?? []) + ': expected boolean, got ' + typeof raw);\n return false;\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Literal — exact value match; default is the literal itself\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Matches one exact primitive value; its _default is the value itself. */\nclass Literal<T extends string | number | boolean> extends Base<T> {\n readonly _default: T;\n constructor(private readonly value: T) { super(); this._default = value; }\n sanitize(raw: unknown): T { return raw === this.value ? this.value : this._default; }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (raw === this.value) return true;\n ctx?.errors.push(pathStr(path ?? []) + ': expected ' + JSON.stringify(this.value) + ', got ' + JSON.stringify(raw));\n return false;\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Enum — union of string/number literals; default is first value\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Union of string/number literals; _default is the first value unless overridden. */\nclass Enum<T extends string | number> extends Base<T> {\n readonly _default: T;\n constructor(private readonly values: readonly T[], defaultVal?: T) {\n super();\n this._default = defaultVal ?? values[0];\n }\n sanitize(raw: unknown): T { return this.values.includes(raw as T) ? (raw as T) : this._default; }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (this.values.includes(raw as T)) return true;\n ctx?.errors.push(pathStr(path ?? []) + ': expected one of ' + this.values.map(v => JSON.stringify(v)).join(' | ') + ', got ' + JSON.stringify(raw));\n return false;\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Union — first matching schema wins\n// _canSanitize: true if any member can attempt sanitization\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** How many of the best-matching member's errors a failed union appends after its headline\n * (review §2.8). Enough to name the offending key and its neighbours, short of a wall. */\nconst UNION_MEMBER_ERROR_LIMIT = 4;\n\n/** Tries member schemas in order; first whose isValid passes wins. sanitize returns _default when none match. */\nclass Union<T> extends Base<T> {\n /** Structural tag read by {@link describeSchema} — `schemas` alone cannot tell Union from Tuple. */\n readonly _kind = 'union' as const;\n readonly _default: T;\n constructor(private readonly schemas: ReadonlyArray<PxSchema<T>>, defaultVal?: T) {\n super();\n this._default = defaultVal ?? schemas[0]._default;\n }\n sanitize(raw: unknown): T {\n for (const s of this.schemas) {\n if (s.isValid(raw)) return s.sanitize(raw);\n }\n return this._default;\n }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n // Members see the MODE but NOT the caller's error sink (V6). Two separate\n // things were bundled in `ctx`: passing it whole made every non-matching\n // branch push errors even when a later branch matched; passing nothing at\n // all left `strict` inert inside every union. A scratch ctx carries the\n // flags and swallows the per-branch noise.\n const probe: PxValidationContext | undefined = ctx && { errors: [], warnings: [], strict: ctx.strict };\n if (this.schemas.some(s => s.isValid(raw, probe, path ? [...path] : undefined))) return true;\n if (!ctx) return false;\n\n const base = pathStr(path ?? []);\n ctx.errors.push(base + ': no union member matched for value ' + (JSON.stringify(raw) ?? '').slice(0, 240));\n\n // …then say WHY (review §2.8). The headline alone reads the same for a\n // `keyframes`/`keyframe` typo, for short `t`/`v` keys and for a plain type\n // mismatch, so an entry diagnostic — or an LLM repair loop — got no pointer to\n // the fix. Re-run each member into its OWN sink and report the one that got\n // FURTHEST into the value: the likeliest intended shape.\n //\n // Not every member's errors: the real unions run to 11 alternatives\n // (`PxKeyframeValueSchema`), so a typo inside one object member would arrive\n // buried under ten \"expected string, got object\" lines.\n let best: Array<string> | undefined;\n let bestDepth = -1;\n const leafExpectations: Array<string> = [];\n\n for (const member of this.schemas) {\n const sink: PxValidationContext = { errors: [], warnings: [], strict: ctx.strict };\n member.isValid(raw, sink, path ? [...path] : undefined);\n if (!sink.errors.length) continue; // cannot happen (it failed), but keeps this total\n\n // How far in did it get? Every message is \"<path>: <reason>\", and a member that\n // descended reports at a LONGER path than the union's own.\n const depth = Math.max(...sink.errors.map(e => e.slice(0, e.indexOf(':')).length));\n if (depth > bestDepth || (depth === bestDepth && best && sink.errors.length < best.length)) {\n bestDepth = depth;\n best = sink.errors;\n }\n // A member that stayed at the union's own path is a shape mismatch, not a\n // near-miss: collect just its expectation for the folded line below.\n if (depth <= base.length) {\n for (const e of sink.errors) {\n const m = /: expected (.+?), got /.exec(e);\n if (m && !leafExpectations.includes(m[1])) leafExpectations.push(m[1]);\n }\n }\n }\n\n if (best && bestDepth > base.length) {\n // One member reached inside the value — its errors ARE the diagnosis.\n for (const e of best.slice(0, UNION_MEMBER_ERROR_LIMIT)) {\n if (!ctx.errors.includes(e)) ctx.errors.push(e);\n }\n } else if (leafExpectations.length) {\n // Nothing descended: one line naming every shape this slot accepts.\n ctx.errors.push(base + ': expected ' + leafExpectations.join(' | '));\n }\n return false;\n }\n override _canSanitize(raw: unknown): boolean { return this.schemas.some(s => s._canSanitize(raw)); }\n}\n\n// Infers the union of all member types from a tuple of schemas.\n// Mapped-then-indexed, NOT `T extends ReadonlyArray<PxSchema<infer U>>`: the latter\n// gives TS one inference site for the whole array, so it collapses a heterogeneous\n// union to a single member (in practice the object branch) and the bare statics\n// vanish from the inferred type — `px.union([px.number(), px.object(…)])` typed as\n// the object alone. Per-position inference unions them properly (V6/Q1).\ntype UnionMembers<T extends ReadonlyArray<PxSchema<any, any>>> = {\n [K in keyof T]: T[K] extends PxSchema<infer U, any> ? U : never\n}[number];\n// NOTE: this only works because `px.union` / `px.discriminatedUnion` declare their\n// schema list as `const T` (Q1). Without the modifier TS infers the argument as a\n// plain ARRAY and unifies the element type to one `PxSchema<…>`, so `keyof T` has a\n// single member to map and the union collapses to whichever branch won unification —\n// in practice the last object branch, silently dropping every bare-static member\n// (`px.union([tuple, …, propAnim])` typed as propAnim alone). Source-level checks and\n// the emitted .d.ts both went wrong, so the dist types rejected legal wire values.\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// DiscriminatedUnion — routes by a literal key field, not first-match order\n// ─────────────────────────────────────────────────────────────────────────────\n\n// `undefined` admitted: a member may declare its discriminant `.optional()` (see `_absentMember`).\ntype AnyDiscriminantShape<K extends string> = Record<K, PxSchema<string | number | boolean | undefined, any>>;\n\n/**\n * Reads `raw[key]`, finds the member schema whose literal matches that value,\n * then delegates sanitize/isValid to that member. Falls back to the first\n * member for sanitize when no match is found.\n */\nclass DiscriminatedUnion<T> extends Base<T> {\n /** Structural tag read by {@link describeSchema}. */\n readonly _kind = 'discriminatedUnion' as const;\n readonly _default: T;\n private readonly _map: Map<string | number | boolean, PxSchema<T>>;\n /** The member an ABSENT discriminant selects — the one whose discriminant slot is\n * `.optional()` (e.g. `timeline.type` omitted = the time-driven timeline). */\n private readonly _absentMember: PxSchema<T> | undefined;\n\n constructor(\n private readonly _key: string,\n private readonly _schemas: ReadonlyArray<PxSchema<T> & { readonly _shape: AnyDiscriminantShape<string> }>,\n defaultVal?: T\n ) {\n super();\n this._default = defaultVal ?? _schemas[0]._default;\n this._map = new Map();\n for (const s of _schemas) {\n const keySchema = s._shape[_key] as any;\n if (!keySchema) continue;\n // An optional discriminant wraps its literal: map the literal's value, and\n // remember this member as the one to use when the key is missing.\n const literal = keySchema.inner ?? keySchema;\n this._map.set(literal._default, s);\n if (keySchema.inner) this._absentMember = s;\n }\n }\n\n private _findSchema(raw: unknown): PxSchema<T> | undefined {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return undefined;\n const val = (raw as Record<string, unknown>)[this._key];\n if (val === undefined || val === null) return this._absentMember;\n return this._map.get(val as string | number | boolean);\n }\n\n sanitize(raw: unknown): T {\n return (this._findSchema(raw) ?? this._schemas[0]).sanitize(raw);\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n const schema = this._findSchema(raw);\n if (!schema) {\n const val = (raw !== null && typeof raw === 'object' && !Array.isArray(raw))\n ? (raw as Record<string, unknown>)[this._key] : undefined;\n ctx?.errors.push(pathStr(path ?? []) + ': no discriminated union member matched '\n + this._key + '=' + JSON.stringify(val));\n return false;\n }\n return schema.isValid(raw, ctx, path);\n }\n\n override _canSanitize(raw: unknown): boolean {\n if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) return false;\n const schema = this._findSchema(raw);\n return schema ? schema._canSanitize(raw) : this._schemas[0]._canSanitize(raw);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Object — strips unknown keys; required fields use default, optional → undefined\n// _canSanitize: true when raw is a plain (non-array) object\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype AnyShape = Record<string, PxSchema<any, any>>;\n\n// Required keys: IsOptional=false → plain property.\n// Optional keys: IsOptional=true → ?:, with Exclude<T,undefined> (?: already adds undefined).\ntype InferShape<S extends AnyShape> =\n { [K in keyof S as S[K] extends PxSchema<any, true> ? never : K]: PxInfer<S[K]> } &\n { [K in keyof S as S[K] extends PxSchema<any, true> ? K : never]?: Exclude<PxInfer<S[K]>, undefined> };\n\n/**\n * Typed object schema; strips unknown keys, repairs required fields via their defaults.\n * _canSanitize checks structure only (non-array object) so partially-valid objects are repaired.\n */\nclass Obj<S extends AnyShape> extends Base<InferShape<S>> {\n readonly _default: InferShape<S>;\n\n constructor(readonly _shape: S) {\n super();\n const d: any = {};\n for (const key of Object.keys(_shape)) d[key] = _shape[key]._default;\n this._default = d;\n }\n\n sanitize(raw: unknown): InferShape<S> {\n const src = (raw && typeof raw === 'object' && !Array.isArray(raw)) ? raw as any : {};\n const out: any = {};\n for (const key of Object.keys(this._shape)) {\n const v = this._shape[key].sanitize(src[key]);\n // An OPTIONAL key that was absent sanitizes to `undefined`. Writing it anyway\n // produced a \"phantom\" own key — invisible to JSON, but NOT to `Object.keys`,\n // and consumers branch on that: `PxOffsetPathMaterializer` bails when a transform\n // carries keys beyond translate/origin, so phantoms silently disabled the CSS\n // Motion Path path, and `contentRefSplit` emitted `transform=\"\"`. Measured across\n // 135 real documents: 16,367 phantoms, changing the render of 10 of them.\n if (v !== undefined) out[key] = v;\n }\n return out;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n ctx?.errors.push(pathStr(path ?? []) + ': expected object, got ' + (Array.isArray(raw) ? 'array' : typeof raw));\n return false;\n }\n const obj = raw as any;\n const p = path ?? [];\n let ok = true;\n for (const key of Object.keys(this._shape)) {\n p.push(key);\n if (!this._shape[key].isValid(obj[key], ctx, p)) ok = false;\n p.pop();\n }\n // Strict mode: closed objects reject extra (undeclared) keys.\n // Default mode silently ignores them (matching `sanitize`'s strip-extras).\n if (ctx?.strict) {\n for (const key of Object.keys(obj)) {\n if (key in this._shape) continue;\n // An undefined-valued key is indistinguishable from an absent one for\n // every consumer, and JSON.stringify drops it — strict judges the\n // DOCUMENT, not the in-memory object that produced it (V6). Without\n // this, validating a freshly-built (pre-serialization) object flags\n // phantom keys that cannot exist on the wire.\n if (obj[key] === undefined) continue;\n p.push(key);\n ctx.errors.push(pathStr(p) + ': ' + PX_UNKNOWN_KEY_ERROR);\n p.pop();\n ok = false;\n }\n }\n return ok;\n }\n\n override _canSanitize(raw: unknown): boolean {\n return !!raw && typeof raw === 'object' && !Array.isArray(raw);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OpenObj — like Obj but passes unknown keys through unchanged (or validates/sanitizes\n// them against an optional open-value schema).\n// Known keys are validated/sanitized; unknown keys are passed through as-is when no\n// open schema is given, or validated/sanitized against the open schema when one is provided.\n// Inferred type: InferShape<S> & { [key: string]: V } where V defaults to any.\n// Note: TypeScript intersection semantics mean named-property access on the\n// derived type yields `any` rather than the specific declared type. For\n// type-precise access keep a hand-written interface alongside the schema.\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype InferOpenShape<S extends AnyShape, _V = any> = InferShape<S> & { [key: string]: any };\n\n/**\n * Open object schema; validates/repairs known keys, passes unknown keys through unchanged.\n * Use when the object may carry arbitrary extra properties (e.g. SVG element attributes).\n *\n * @param shape Known key schemas (validated and type-inferred).\n * @param openSchema Optional schema applied to every unknown key's value.\n * When omitted, unknown values are passed through as-is (`any`).\n */\nclass OpenObj<S extends AnyShape, V = any> extends Base<InferOpenShape<S, V>> {\n readonly _default: InferOpenShape<S, V>;\n\n constructor(readonly _shape: S, private readonly _openSchema?: PxSchema<V>) {\n super();\n const d: any = {};\n for (const key of Object.keys(_shape)) d[key] = _shape[key]._default;\n this._default = d;\n }\n\n sanitize(raw: unknown): InferOpenShape<S, V> {\n const src: Record<string, unknown> = (raw && typeof raw === 'object' && !Array.isArray(raw))\n ? raw as Record<string, unknown>\n : {};\n const out: Record<string, unknown> = { ...src };\n for (const key of Object.keys(this._shape)) {\n const v = this._shape[key].sanitize(src[key]);\n if (v !== undefined) out[key] = v; // no phantom keys — see Obj.sanitize\n }\n if (this._openSchema) {\n for (const key of Object.keys(src)) {\n if (!(key in this._shape)) out[key] = this._openSchema.sanitize(src[key]);\n }\n }\n return out as InferOpenShape<S, V>;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n ctx?.errors.push(pathStr(path ?? []) + ': expected object, got ' + (Array.isArray(raw) ? 'array' : typeof raw));\n return false;\n }\n const obj = raw as any;\n const p = path ?? [];\n let ok = true;\n for (const key of Object.keys(this._shape)) {\n p.push(key);\n if (!this._shape[key].isValid(obj[key], ctx, p)) ok = false;\n p.pop();\n }\n if (this._openSchema) {\n for (const key of Object.keys(obj)) {\n if (key in this._shape) continue;\n p.push(key);\n if (!this._openSchema.isValid(obj[key], ctx, p)) ok = false;\n p.pop();\n }\n }\n return ok;\n }\n\n override _canSanitize(raw: unknown): boolean {\n return !!raw && typeof raw === 'object' && !Array.isArray(raw);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Array — items that cannot be attempted are filtered out; default is []\n// Uses _canSanitize for filtering so partially-valid objects are repaired, not dropped.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Array schema; items failing _canSanitize are filtered out rather than blocking the whole array. */\nclass Arr<T> extends Base<Array<T>> {\n readonly _default: Array<T> = [];\n constructor(private readonly item: PxSchema<T>) { super(); }\n\n sanitize(raw: unknown): Array<T> {\n if (!Array.isArray(raw)) return [];\n const out: Array<T> = [];\n for (const el of raw) {\n if (this.item._canSanitize(el)) out.push(this.item.sanitize(el));\n }\n return out;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (!Array.isArray(raw)) {\n ctx?.errors.push(pathStr(path ?? []) + ': expected array, got ' + typeof raw);\n return false;\n }\n const p = path ?? [];\n let ok = true;\n for (let i = 0; i < raw.length; i++) {\n p.push('[' + i + ']');\n if (!this.item.isValid(raw[i], ctx, p)) ok = false;\n p.pop();\n }\n return ok;\n }\n\n override _canSanitize(raw: unknown): boolean { return Array.isArray(raw); }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Record — invalid values are dropped; default is {}\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** String-keyed record; values failing _canSanitize are dropped rather than blocking the whole record. */\nclass Rec<T> extends Base<Record<string, T>> {\n /** Structural tag read by {@link describeSchema}. */\n readonly _kind = 'record' as const;\n readonly _default: Record<string, T> = {};\n constructor(private readonly value: PxSchema<T>) { super(); }\n\n sanitize(raw: unknown): Record<string, T> {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return {};\n const out: Record<string, T> = {};\n for (const [k, v] of Object.entries(raw)) {\n if (this.value._canSanitize(v)) out[k] = this.value.sanitize(v);\n }\n return out;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n ctx?.errors.push(pathStr(path ?? []) + ': expected object/record, got ' + (Array.isArray(raw) ? 'array' : typeof raw));\n return false;\n }\n const p = path ?? [];\n let ok = true;\n for (const [k, v] of Object.entries(raw as object)) {\n p.push(k);\n if (!this.value.isValid(v, ctx, p)) ok = false;\n p.pop();\n }\n return ok;\n }\n\n override _canSanitize(raw: unknown): boolean {\n return !!raw && typeof raw === 'object' && !Array.isArray(raw);\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Any — passes raw through unchanged, always valid\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Passes any value through unchanged; always valid. Useful for opaque blobs with no schema. */\nclass Any extends Base<any> {\n readonly _default: any = undefined;\n sanitize(raw: unknown): any { return raw; }\n isValid(_raw: unknown, _ctx?: PxValidationContext, _path?: Array<string>): boolean { return true; }\n override _canSanitize(_raw: unknown): boolean { return true; }\n}\n\n\n/**\n * Like {@link Any} but REQUIRES the value to be present: anything except `undefined`.\n *\n * Use it for a key whose type is open but whose PRESENCE is what identifies the\n * shape — e.g. the structured-static branch `{value: …}` (V6). With `px.any()`\n * there, the branch matched EVERY object, because `any` accepts `undefined` and\n * an absent key is indistinguishable from one holding `undefined`.\n */\nclass Defined extends Base<any> {\n readonly _default: any = undefined;\n sanitize(raw: unknown): any { return raw; }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (raw !== undefined) return true;\n ctx?.errors.push(pathStr(path ?? []) + ': required value is missing');\n return false;\n }\n override _canSanitize(raw: unknown): boolean { return raw !== undefined; }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Lazy — resolves schema on first use, required for recursive types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Defers schema resolution to first use; required to break circular references in recursive types. */\nclass Lazy<T> extends Base<T> {\n private resolved: PxSchema<T> | null = null;\n constructor(private readonly fn: () => PxSchema<T>, readonly _default: T) { super(); }\n\n private get schema(): PxSchema<T> {\n return this.resolved ?? (this.resolved = this.fn());\n }\n\n sanitize(raw: unknown): T { return this.schema.sanitize(raw); }\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean { return this.schema.isValid(raw, ctx, path); }\n override _canSanitize(raw: unknown): boolean { return this.schema._canSanitize(raw); }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tuple — fixed-length array with per-position schemas; default is defaults of each position\n// _canSanitize: true only when raw is an array of the exact expected length\n// ─────────────────────────────────────────────────────────────────────────────\n\n// Maps a tuple of schemas to a tuple of their inferred types.\ntype TupleItems<T extends ReadonlyArray<PxSchema<any, any>>> =\n { -readonly [K in keyof T]: T[K] extends PxSchema<infer U, any> ? U : never };\n\n/** Fixed-length array schema; validates element count and each position individually. */\nclass Tuple<T extends ReadonlyArray<PxSchema<any, any>>> extends Base<TupleItems<T>> {\n /** Structural tag read by {@link describeSchema} — `schemas` alone cannot tell Tuple from Union. */\n readonly _kind = 'tuple' as const;\n readonly _default: TupleItems<T>;\n\n constructor(private readonly schemas: T) {\n super();\n this._default = schemas.map(s => s._default) as unknown as TupleItems<T>;\n }\n\n sanitize(raw: unknown): TupleItems<T> {\n if (!Array.isArray(raw) || raw.length !== this.schemas.length) return this._default;\n return this.schemas.map((s, i) => s.sanitize((raw as unknown[])[i])) as unknown as TupleItems<T>;\n }\n\n isValid(raw: unknown, ctx?: PxValidationContext, path?: Array<string>): boolean {\n if (!Array.isArray(raw) || raw.length !== this.schemas.length) {\n ctx?.errors.push(pathStr(path ?? []) + ': expected tuple of length ' + this.schemas.length + ', got ' + (Array.isArray(raw) ? 'array[' + (raw as unknown[]).length + ']' : typeof raw));\n return false;\n }\n const p = path ?? [];\n let ok = true;\n for (let i = 0; i < this.schemas.length; i++) {\n p.push('[' + i + ']');\n if (!(this.schemas as ReadonlyArray<PxSchema<any, any>>)[i].isValid((raw as unknown[])[i], ctx, p)) ok = false;\n p.pop();\n }\n return ok;\n }\n\n // Require exact length so wrong-length arrays are dropped rather than repaired to default.\n override _canSanitize(raw: unknown): boolean {\n return Array.isArray(raw) && raw.length === this.schemas.length;\n }\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// implementsInterface — compile-time lock between a schema and an interface\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Returns a pass-through wrapper that enforces at compile time that the schema's\n * inferred type is structurally assignable to `T`. Catches type mismatches on\n * required fields and value-type mismatches on optional fields.\n *\n * For key-level equality (catching optional field renames), pair with a\n * `KeysMatch` assertion after the inferred type alias:\n * ```ts\n * const _ck: KeysMatch<PxFoo, _PxFoo> = true;\n * ```\n *\n * @example\n * export const PxLoopSchema = implementsInterface<_PxLoop>()(px.object({ ... }));\n */\nexport function implementsInterface<T>() {\n return <S extends PxSchema<T>>(schema: S): S => schema;\n}\n\n/**\n * Evaluates to `true` when A and B have exactly the same set of keys; `false` otherwise.\n * Use with a `const` assertion to get a compile-time error on key renames:\n * ```ts\n * const _ck: KeysMatch<PxFoo, _PxFoo> = true;\n * ```\n * @public @advanced\n */\nexport type KeysMatch<A, B> =\n [Exclude<keyof A, keyof B>] extends [never]\n ? [Exclude<keyof B, keyof A>] extends [never] ? true : false\n : false;\n\n/**\n * Builds a `{ key: 'key', ... }` object from a schema's shape.\n * Each value is typed as the literal key name, so `schemaKeys(PxFooSchema).bar`\n * is typed as `'bar'` — use in @serializable calls to get a compile-time error\n * if the field is renamed in the schema.\n * @public @advanced\n */\nexport function schemaKeys<S extends { readonly _shape: Record<string, any> }>(schema: S) {\n return Object.fromEntries(\n Object.keys(schema['_shape']).map(k => [k, k])\n ) as { [K in keyof S['_shape']]: K };\n}\n\n/**\n * Describes the structural kind of a schema for traversal purposes.\n * Use with `getMissingCoveragePaths` or similar tree-walking utilities.\n *\n * Every composite kind is reported, so a walker can reach EVERY field a document\n * may legally carry — that is what makes schema-coverage checking possible\n * (see the app's `collectSchemaFieldUniverse`).\n *\n * - `shape` — object schema (Obj/OpenObj); `shape` maps key → sub-schema.\n * `openValue` is set for open objects: the schema unknown keys\n * are validated against (undefined ⇒ unknown keys pass through as-is)\n * - `array` — array schema; `item` is the element schema\n * - `optional` — optional wrapper; `inner` is the wrapped schema\n * - `lazy` — lazy/recursive schema; `resolved` is the resolved schema (cached)\n * - `union` — `px.union`; `members` are the alternatives, tried in order\n * - `discriminatedUnion`— `px.discriminatedUnion`; `key` is the discriminant field and\n * `members` the alternatives (each an object schema with a literal at `key`)\n * - `record` — `px.record`; `value` is the schema every entry's value must match\n * - `tuple` — `px.tuple`; `items` are the positional element schemas\n * - `leaf` — primitive, literal, enum, any — no traversable children\n * @public @advanced\n */\nexport type PxSchemaDesc =\n | { kind: 'shape'; shape: Record<string, PxSchema<any, any>>; openValue?: PxSchema<any, any> }\n | { kind: 'array'; item: PxSchema<any, any> }\n | { kind: 'optional'; inner: PxSchema<any, any> }\n | { kind: 'lazy'; resolved: PxSchema<any, any> }\n | { kind: 'union'; members: ReadonlyArray<PxSchema<any, any>> }\n | { kind: 'discriminatedUnion'; key: string; members: ReadonlyArray<PxSchema<any, any>> }\n | { kind: 'record'; value: PxSchema<any, any> }\n | { kind: 'tuple'; items: ReadonlyArray<PxSchema<any, any>> }\n | { kind: 'leaf' };\n\n/** @public @advanced */\nexport function describeSchema(schema: PxSchema<any, any>): PxSchemaDesc {\n const s = schema as any;\n // `_kind` is set only by the classes that are otherwise indistinguishable by\n // duck-typing (Union vs Tuple both carry `schemas`), so it is checked first.\n switch (s._kind) {\n case 'union': return { kind: 'union', members: s.schemas };\n case 'discriminatedUnion': return { kind: 'discriminatedUnion', key: s._key, members: s._schemas };\n case 'record': return { kind: 'record', value: s.value };\n case 'tuple': return { kind: 'tuple', items: s.schemas };\n }\n if ('_shape' in s) return { kind: 'shape', shape: s._shape, openValue: s._openSchema };\n if ('item' in s) return { kind: 'array', item: s.item };\n if ('inner' in s) return { kind: 'optional', inner: s.inner };\n // `??=` (not `??`): an unresolved lazy would otherwise build a FRESH schema on\n // every call, so two traversals could never agree on schema identity.\n if ('fn' in s) return { kind: 'lazy', resolved: (s.resolved ??= s.fn()) };\n return { kind: 'leaf' };\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public factory\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** @public @advanced */\nexport const px = {\n /** Matches a string. Default: '' or provided value. */\n string: (defaultVal = ''): PxSchema<string> => new Str(defaultVal),\n\n /** Matches a finite number. Default: 0 or provided value. */\n number: (defaultVal = 0): PxSchema<number> => new Num(defaultVal),\n\n /** Matches a boolean. Default: false or provided value. */\n boolean: (defaultVal = false): PxSchema<boolean> => new Bool(defaultVal),\n\n /** Matches one exact primitive value; its default is the value itself. */\n literal: <T extends string | number | boolean>(value: T): PxSchema<T> =>\n new Literal(value),\n\n /** Matches one of a fixed set of string/number values. Default: first value. */\n enum: <T extends string | number>(values: readonly T[], defaultVal?: T): PxSchema<T> =>\n new Enum(values, defaultVal),\n\n /**\n * Returns the first schema whose isValid passes.\n * TypeScript infers the union of all member types automatically.\n */\n union: <const T extends ReadonlyArray<PxSchema<any, any>>>(\n schemas: T,\n defaultVal?: UnionMembers<T>\n ): PxSchema<UnionMembers<T>> =>\n new Union(schemas as any, defaultVal) as any,\n\n /**\n * Discriminated union — reads `raw[key]`, finds the member schema whose\n * literal at `key` matches, then delegates sanitize/isValid to that member.\n * Each member must be an object schema with a `px.literal(...)` at `key`.\n * TypeScript infers the union of all member types automatically.\n */\n discriminatedUnion: <\n K extends string,\n const T extends ReadonlyArray<PxSchema<any, any> & { readonly _shape: AnyDiscriminantShape<K> }>\n >(key: K, schemas: T): PxSchema<UnionMembers<T>> =>\n new DiscriminatedUnion(key, schemas as any) as any,\n\n /** Typed object — unknown keys are stripped. Required fields fall back to their default. */\n object: <S extends AnyShape>(shape: S): PxSchema<InferShape<S>> & { readonly _shape: S } =>\n new Obj(shape),\n\n /**\n * Open object — validates known keys; passes unknown keys through as-is,\n * or validates/sanitizes them against `openSchema` when provided.\n */\n openObject: <S extends AnyShape, V = any>(\n shape: S,\n openSchema?: PxSchema<V>\n ): PxSchema<InferOpenShape<S, V>> & { readonly _shape: S } =>\n new OpenObj(shape, openSchema) as any,\n\n /**\n * Creates a new closed object schema by merging a base schema's shape with additional fields.\n * The base can be the result of px.object() or px.openObject() — anything with a _shape property.\n *\n * @example\n * const PxSvgNodeSchema = px.extendedObject(PxNodeBaseSchema, { width: px.number().optional() });\n */\n extendedObject: <B extends AnyShape, E extends AnyShape>(\n base: { readonly _shape: B },\n extra: E\n ): PxSchema<InferShape<B & E>> & { readonly _shape: B & E } =>\n new Obj({ ...base._shape, ...extra } as B & E),\n\n /** Array whose unrecoverable items are filtered out. Default: []. */\n array: <T>(item: PxSchema<T>): PxSchema<Array<T>> =>\n new Arr(item),\n\n /** String-keyed record whose unrecoverable values are dropped. Default: {}. */\n record: <T>(value: PxSchema<T>): PxSchema<Record<string, T>> =>\n new Rec(value),\n\n /** Passes anything through unchanged — always valid. */\n any: (): PxSchema<any> => new Any(),\n\n /** Anything EXCEPT `undefined` — an open type whose presence is required (V6). */\n defined: (): PxSchema<any> => new Defined(),\n\n /** Fixed-length tuple — validates element count and each position individually. */\n tuple: <T extends ReadonlyArray<PxSchema<any, any>>>(schemas: T): PxSchema<TupleItems<T>> =>\n new Tuple(schemas),\n\n /** Defers schema creation — required for recursive types. Must supply a default value. */\n lazy: <T>(fn: () => PxSchema<T>, defaultVal: T): PxSchema<T> =>\n new Lazy(fn, defaultVal),\n} as const;\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n// ============================================================================\n// Wire CONSTANTS and schema-free helpers.\n//\n// Split out of `PxAnimatorTypes` so that code needing only an enum does not drag the\n// SCHEMA ENGINE in with it. `PxAnimatorTypes` builds ~31 schema declarations at module\n// scope through curried `implementsInterface<T>()(px.object(...))` calls, which no\n// minifier can treat as side-effect-free — so a single value import from it pulls in\n// `PxSchema` plus every declaration.\n//\n// That is exactly what happened: `PxDefinitions` imported `PxLoopExtend` (one small const)\n// and the pre-rendered player builds ended up carrying 14 KB of validation code they never\n// call. See dev-docs/plans/prerendered-player-builds.md.\n//\n// RULE: nothing in this file may import a VALUE from `PxAnimatorTypes`. Type-only imports\n// are fine — they are erased at build time and cannot create a runtime edge.\n// ============================================================================\n\nimport type { PxAnimatedSvgDocument, PxAnimatorConfig, PxBinding, PxDefinitions, PxNode, PxScroll, PxTrigger } from './PxAnimatorTypes';\n\n// ── WIRE ENUMS (review §2.7) ────────────────────────────────────────────────\n// ONE shape for every enumerated wire value: an exported `Px*` const namespace plus the\n// string type derived from it, so a call site can write `PxFillMode.forwards` and the\n// schema can reference the same members instead of repeating bare literals.\n//\n// NOT a TypeScript `enum`: these are WIRE values, and a document is authored as plain\n// JSON — `{ fill: 'forwards' }`. A string `enum` is nominal, so that literal would not\n// typecheck without importing the enum; the derived union accepts both spellings. It is\n// also the only form that composes (`PxTimelineEngineSetting` spreads `PxTimelineEngine`)\n// and that survives erasable-syntax / type-stripping builds.\n\n/** WAAPI `fill` — which values apply outside the active period. @public */\nexport const PxFillMode = {\n forwards: 'forwards',\n backwards: 'backwards',\n both: 'both',\n none: 'none',\n} as const;\n\nexport type PxFillMode = typeof PxFillMode[keyof typeof PxFillMode];\n\n/** WAAPI `direction` — which way each iteration runs. @public */\nexport const PxPlaybackDirection = {\n normal: 'normal',\n reverse: 'reverse',\n alternate: 'alternate',\n alternateReverse: 'alternate-reverse',\n} as const;\n\nexport type PxPlaybackDirection = typeof PxPlaybackDirection[keyof typeof PxPlaybackDirection];\n\n\n/** @internal */\nexport const PX_ANIM_SRC_ATTR_NAME = 'data-px-animation-src';\n\n/** @internal */\nexport const PX_ANIM_ATTR_NAME = '_px_animator';\n\n/** `trigger.startOn` — what starts the animation. `programmatic` waits for `play()`. @public */\nexport const PxStartOn = {\n load: 'load',\n mouseOver: 'mouseOver',\n click: 'click',\n scrollIntoView: 'scrollIntoView',\n programmatic: 'programmatic',\n} as const;\n\nexport type PxStartOn = typeof PxStartOn[keyof typeof PxStartOn];\n\n/** `trigger.outAction` — what happens when the trigger condition stops holding. @public */\nexport const PxOutAction = {\n continue: 'continue',\n pause: 'pause',\n reset: 'reset',\n reverse: 'reverse',\n} as const;\n\nexport type PxOutAction = typeof PxOutAction[keyof typeof PxOutAction];\n\n/** `trigger.finishAction` — what happens after a NATURAL finish. @public */\nexport const PxFinishAction = {\n hold: 'hold',\n reset: 'reset',\n} as const;\n\nexport type PxFinishAction = typeof PxFinishAction[keyof typeof PxFinishAction];\n\n/** `scroll.kind` — which scroll-driven timeline member this is: the subject's journey\n * through the scrollport (`view`), or the scroll container's own offset (`scroll`). * @public\n */\nexport const PxScrollKind = {\n view: 'view',\n scroll: 'scroll',\n} as const;\n\nexport type PxScrollKind = typeof PxScrollKind[keyof typeof PxScrollKind];\n\n/** `timeline.axis` — which axis of the scroll container drives progress. @public */\nexport const PxScrollAxis = {\n block: 'block',\n inline: 'inline',\n x: 'x',\n y: 'y',\n} as const;\n\nexport type PxScrollAxis = typeof PxScrollAxis[keyof typeof PxScrollAxis];\n\n/** `timeline.source` (`scroll` kind) — which scroll container is measured. @public */\nexport const PxScrollSource = {\n nearest: 'nearest',\n root: 'root',\n} as const;\n\nexport type PxScrollSource = typeof PxScrollSource[keyof typeof PxScrollSource];\n\n/** `timeline.pin.align` — where the pinned canvas is held in the scrollport. @public */\nexport const PxPinAlign = {\n top: 'top',\n center: 'center',\n bottom: 'bottom',\n} as const;\n\nexport type PxPinAlign = typeof PxPinAlign[keyof typeof PxPinAlign];\n\n/** The subject's journey phases across the scrollport (see scroll-timeline.design.md §4\n * for the exact `u`-space intervals each phase maps to). * @public\n */\nexport const PxScrollPhase = {\n cover: 'cover',\n contain: 'contain',\n entry: 'entry',\n exit: 'exit',\n entryCrossing: 'entry-crossing',\n exitCrossing: 'exit-crossing',\n} as const;\n\nexport type PxScrollPhase = typeof PxScrollPhase[keyof typeof PxScrollPhase];\n\n/** `alongPathMode` — how a value is sampled along a motion path. @public */\nexport const PxAlongPathMode = {\n sampled: 'sampled',\n offsetPath: 'offsetPath',\n} as const;\n\nexport type PxAlongPathMode = typeof PxAlongPathMode[keyof typeof PxAlongPathMode];\n\n/** WIRE `timeline.engine` — who runs the animation. `auto` (default) prefers the\n * platform's animation API and falls back to JS when the document needs something it\n * cannot express; `native` DEMANDS that API (WAAPI — and, for scroll/view timelines,\n * the browser's ScrollTimeline) with no fallback; `js` pins the player's own frame loop\n * and its own progress measurement. Const-namespace + matching string type so call\n * sites use named members (`PxTimelineEngineSetting.js`), not bare literals.\n *\n * NAMED `engine`, not `mode`: it selects HOW the animated attributes get updated, not\n * WHAT you see — an implementation preference. (`native` is the one value that can also\n * change the outcome: being a demand, an attribute WAAPI declines simply does not\n * animate. See `isNativeForced`.) */\n/** Timeline keys that BOTH union members carry, so they survive a change of `type`. */\nexport const PX_TIMELINE_SHARED_KEYS = ['duration', 'iterations', 'engine', 'frameRate'] as const;\n\n/** Timeline keys that exist ONLY on the time-driven member — a scroll/view timeline is\n * scrubbed by position, so nothing starts it and nothing delays it. */\nexport const PX_TIME_ONLY_TIMELINE_KEYS = ['trigger', 'delay', 'fillMode', 'direction'] as const;\n\n/**\n * The flat RUNTIME-VIEW keys — `duration`, `trigger`, `scroll`, … at the animator ROOT.\n *\n * They are the internal view the engines consume, never a wire spelling: on the wire playback\n * lives inside `timeline`. `getAnimatorConfig` drops them from a document, so a stray one is an\n * unknown key the diagnostic reports rather than a second spelling that quietly plays.\n */\nexport const PX_FLAT_RUNTIME_VIEW_KEYS: ReadonlyArray<string> = [\n ...PX_TIMELINE_SHARED_KEYS, ...PX_TIME_ONLY_TIMELINE_KEYS,\n 'fill', 'resetOnFinish', 'timelineSource', 'scroll',\n];\n\n/**\n * THE ENGINES — the two things that can actually update an animated attribute: hand it to the\n * platform's animation API (`native`), or write it from the player's own frame loop (`js`).\n *\n * This is the CORE set. Code that always knows which engine is running takes this (e.g.\n * `normalizeBindings`'s `engine` arg gates motion-along-path materialization).\n * @public\n */\nexport const PxTimelineEngine = {\n native: 'native',\n js: 'js',\n} as const;\n\nexport type PxTimelineEngine = typeof PxTimelineEngine[keyof typeof PxTimelineEngine];\n\n/**\n * What `timeline.engine` ACCEPTS on the wire: the engines above plus `auto` — \"you pick\", which\n * prefers `native` and falls back to `js` per document when the platform API declines an\n * attribute.\n *\n * Built by ADDING to the core set rather than subtracting from a wider one, so the two cannot\n * drift: every engine is automatically an accepted value, and `auto` is visibly the one extra.\n * @public\n */\nexport const PxTimelineEngineSetting = {\n ...PxTimelineEngine,\n auto: 'auto',\n} as const;\n\nexport type PxTimelineEngineSetting = typeof PxTimelineEngineSetting[keyof typeof PxTimelineEngineSetting];\n\n/** What a requested engine resolves to BEFORE the runtime probes support: `js` pins the frame\n * loop, anything else starts at `native`. NOTE this is only the STARTING point — `auto` still\n * falls back to `js` per document when the platform API declines an attribute, which happens at\n * bind time (see `PxAnimatorBind`), not here. * @public @advanced\n */\nexport function resolveTimelineEngine(engine: PxTimelineEngineSetting | undefined): PxTimelineEngine {\n return engine === PxTimelineEngineSetting.js ? PxTimelineEngine.js : PxTimelineEngine.native;\n}\n\n/** `native` is a demand, not a preference: no JS fallback when the platform API declines an attribute. @public @advanced */\nexport function isNativeForced(engine: PxTimelineEngineSetting | undefined): boolean {\n return engine === PxTimelineEngineSetting.native;\n}\n\n/** May the browser's ScrollTimeline/ViewTimeline drive a scroll/view timeline?\n * `auto` tries it first (falling back to the player's own measurement), `native`\n * asks for it, `js` never uses it. * @public @advanced\n */\nexport function mayUseNativeScrollTimeline(engine: PxTimelineEngineSetting | undefined): boolean {\n return engine !== PxTimelineEngineSetting.js;\n}\n\n/**\n * THE TRIGGER DEFAULTS — what a missing `trigger` field means. One table, declared by\n * `PxTriggerSchema` and applied by {@link resolveTrigger}, which every player calls (the web's\n * `setupAnimationTriggers`, the React Native component) — so a file behaves the same everywhere:\n * - `startOn` 'load' — a document is designed to play\n * - `outAction` 'continue' — leaving the trigger does not interrupt playback\n * - `scrollIntoViewThreshold` 0 — any visible pixel counts\n * @public @advanced\n */\nexport const PX_TRIGGER_DEFAULTS = {\n startOn: 'load',\n outAction: 'continue',\n scrollIntoViewThreshold: 0,\n} as const;\n\n/** A trigger with every default filled in. @public @advanced */\nexport interface PxResolvedTrigger {\n readonly startOn: NonNullable<PxTrigger['startOn']>;\n readonly outAction: NonNullable<PxTrigger['outAction']>;\n readonly scrollIntoViewThreshold: number;\n}\n\n// ── CONTROL MODE (API review §1 / §7) ────────────────────────────────────────\n// Which set of props drives playback. Every component picked its own order, so\n// `autoplay` + `progress={0.5}` played on React and Vue but seeked on React Native, and\n// React let a REF choose the mode — `<PixodeskSvgAnimator autoplay apiRef={api} />` never\n// started, because the imperative branch forced `startOn: 'programmatic'`.\n//\n// One order, decided once, used by react / vue / rn. This module owns the LOGIC and the\n// WARNING TEXT only; each component keeps its own `console.warn` wiring.\n\n/** Which props drive playback. `apiRef` is deliberately NOT a mode: the handle is filled in\n * every mode, so passing it alone leaves the document's own trigger in charge. * @public\n */\nexport const PxControlMode = {\n /** No control props — the document's trigger decides, and nothing is taken over. */\n static: 'static',\n /** `progress` / `time` — the host scrubs; the component seeks and stays paused. */\n fixedTime: 'fixedTime',\n /** `play` / `pause` — the host drives playback with booleans. */\n play: 'play',\n /** `autoplay` — the document's own trigger starts it. */\n autoplay: 'autoplay',\n} as const;\n\nexport type PxControlMode = typeof PxControlMode[keyof typeof PxControlMode];\n\n/**\n * The control props every framework component takes. `resolveControlMode` reads only WHICH\n * are set; the components read the values. ONE definition (review §9) — React and React\n * Native extend it, so the hover text below is what their users see.\n * @public\n */\nexport interface PxControlProps {\n /**\n * Show the frame at this position in the whole timeline (duration × iterations): `0` the\n * first frame, `1` the last — of ONE iteration when `iterations` is `'infinite'`, since an\n * endless run has no whole to be a fraction of. Wins over every other control prop.\n */\n progress?: number;\n /** Show the frame at this time, ms from the start of the whole run. Wins like `progress`. */\n time?: number;\n /** `true` plays now, whatever the document's trigger says; `false` holds where it is. */\n play?: boolean;\n /** Hold the current frame; set it back to `false` to resume. */\n pause?: boolean;\n /** Start the way the document says — its own `startOn` / `outAction` trigger. */\n autoplay?: boolean;\n}\n\n/** The chosen mode plus any conflict warnings — ready-made sentences, so three components\n * cannot word the same conflict three ways. * @public\n */\nexport interface PxResolvedControlMode {\n readonly mode: PxControlMode;\n readonly warnings: ReadonlyArray<string>;\n}\n\n/**\n * Picks the control mode from the props a component was given.\n *\n * PRECEDENCE, most specific first:\n * 1. `progress` / `time` — an explicit position is the most precise instruction there is\n * 2. `play` / `pause` — explicit playback state\n * 3. `autoplay` — defer to the document's trigger\n * 4. otherwise `static` — the document's trigger, with nothing taken over\n *\n * `apiRef` is absent on purpose. A ref is a handle, not an instruction: it is populated in\n * every mode, so `autoplay` + `apiRef` autostarts AND gives you the handle.\n *\n * A warning is produced only when props from two different tiers are set together — the\n * lower tier is then ignored, and silence about that is what made this hard to debug.\n * @public\n */\nexport function resolveControlMode(props: PxControlProps): PxResolvedControlMode {\n const hasFixedTime = props.progress !== undefined || props.time !== undefined;\n const hasPlayPause = props.play !== undefined || props.pause !== undefined;\n const hasAutoplay = !!props.autoplay;\n\n const warnings: Array<string> = [];\n const named = (a: string, b: string, winner: string): string =>\n a + ' and ' + b + ' were both set — ' + winner + ' wins, ' + (winner === a ? b : a) + ' is ignored.';\n\n if (hasFixedTime) {\n if (hasPlayPause) warnings.push(named('progress/time', 'play/pause', 'progress/time'));\n if (hasAutoplay) warnings.push(named('progress/time', 'autoplay', 'progress/time'));\n return { mode: PxControlMode.fixedTime, warnings };\n }\n if (hasPlayPause) {\n if (hasAutoplay) warnings.push(named('play/pause', 'autoplay', 'play/pause'));\n return { mode: PxControlMode.play, warnings };\n }\n if (hasAutoplay) return { mode: PxControlMode.autoplay, warnings };\n return { mode: PxControlMode.static, warnings };\n}\n\n/**\n * True when the component must take the document's trigger over, by forcing\n * `startOn: 'programmatic'` into its config patch.\n *\n * Every mode except `autoplay` — INCLUDING `static`. A component given no control props at all\n * must not start on its own: `<PixodeskSvgAnimator doc={…} />` renders the first frame and waits.\n * `autoplay` is the one mode that says \"let the document's trigger decide\".\n * @public\n */\nexport function controlModeTakesOverTrigger(mode: PxControlMode): boolean {\n return mode !== PxControlMode.autoplay;\n}\n\n/** A document's trigger with the defaults filled in. (`finishAction` is not a start/stop decision:\n * it reaches the engines as the runtime view's `resetOnFinish`.) * @public @advanced\n */\nexport function resolveTrigger(trigger: PxTrigger | undefined): PxResolvedTrigger {\n return {\n startOn: trigger?.startOn ?? PX_TRIGGER_DEFAULTS.startOn,\n outAction: trigger?.outAction ?? PX_TRIGGER_DEFAULTS.outAction,\n scrollIntoViewThreshold: trigger?.scrollIntoViewThreshold ?? PX_TRIGGER_DEFAULTS.scrollIntoViewThreshold,\n };\n}\n\n// V3 — every closed value list is a NAMED const + a strict `px.enum` slot, so a\n// typo is a schema ERROR instead of silently shipping. Plain `px.string()` stays\n// ONLY where SVG itself is open-ended (`gradientTransform`, `viewBox`, `path` d,\n// ids/refs, `debugGlobalName`).\n\n/** `loop.repeatAt` — WHICH END of the keyframe sequence the repeated segment is taken\n * from, and therefore which side of the timeline the repetition fills. A named\n * two-way selector (not a boolean) so a third value stays possible. * @public\n */\nexport const PxLoopRepeatAt = {\n /** Segment from the START; the repetition runs BEFORE the first keyframe\n * (intro loops that play until the main timeline begins). */\n start: 'start',\n /** DEFAULT — segment from the END; the repetition runs AFTER the last keyframe\n * (idle/outro loops that continue once the main timeline has finished). */\n end: 'end',\n} as const;\n\nexport type PxLoopRepeatAt = typeof PxLoopRepeatAt[keyof typeof PxLoopRepeatAt];\n\n/** `loop.direction` — how successive repetitions play, spelled like the timeline's\n * own `direction` so the two read as one idea. * @public\n */\nexport const PxLoopDirection = {\n /** DEFAULT — cycle: every repetition replays the segment the same way round. */\n normal: 'normal',\n /** Ping-pong: repetitions alternate forward / backward. */\n alternate: 'alternate',\n} as const;\n\nexport type PxLoopDirection = typeof PxLoopDirection[keyof typeof PxLoopDirection];\n\n/** SVG `mask-type` — how the mask source's pixels become alpha. @public */\nexport const PxMaskType = {\n luminance: 'luminance',\n alpha: 'alpha',\n} as const;\n\nexport type PxMaskType = typeof PxMaskType[keyof typeof PxMaskType];\n\n/** SVG coordinate system for `maskUnits` / `maskContentUnits` (and the gradient twin below). @public */\nexport const PxUnits = {\n userSpaceOnUse: 'userSpaceOnUse',\n objectBoundingBox: 'objectBoundingBox',\n} as const;\n\nexport type PxUnits = typeof PxUnits[keyof typeof PxUnits];\n\n/** `clone.without` — which part of the SOURCE'S OWN transform a `<use>` clone leaves\n * out. Absent = the whole element, as SVG `<use>` (a direct link, moves with the source);\n * `translate` = the source's placement is dropped, so the clone stays where the `<use>` put\n * it but still rotates/scales with the source. A future value `transform` may drop the\n * whole transform (content only) — not implemented yet.\n * (Was `clone.type: 'content'`; the wire is subtractive because the mechanism is a\n * ladder — the `<use>` can only point at one wrapper layer of the source.)\n * Old doc line:\n * `content` excludes the target's own translate (see `contentRefSplit`). * @public\n */\nexport const PxCloneWithout = {\n translate: 'translate',\n // transform: 'transform', // future: drop rotate/scale too (content only)\n} as const;\n\nexport type PxCloneWithout = typeof PxCloneWithout[keyof typeof PxCloneWithout];\n\n/** `textPath.pathOverflow` — glyphs past the path end: hide them, or keep laying\n * them along the tangent extension. * @public\n */\nexport const PxPathOverflow = {\n clip: 'clip',\n extend: 'extend',\n} as const;\n\nexport type PxPathOverflow = typeof PxPathOverflow[keyof typeof PxPathOverflow];\n\n/** SVG `lengthAdjust` — what `textLength` stretches. @public */\nexport const PxLengthAdjust = {\n spacing: 'spacing',\n spacingAndGlyphs: 'spacingAndGlyphs',\n} as const;\n\nexport type PxLengthAdjust = typeof PxLengthAdjust[keyof typeof PxLengthAdjust];\n\n/** SVG `<textPath method>` — how glyphs follow curvature. @public */\nexport const PxTextPathMethod = {\n align: 'align',\n stretch: 'stretch',\n} as const;\n\nexport type PxTextPathMethod = typeof PxTextPathMethod[keyof typeof PxTextPathMethod];\n\n/** SVG `<textPath spacing>` — whether the renderer may adjust spacing. @public */\nexport const PxTextPathSpacing = {\n auto: 'auto',\n exact: 'exact',\n} as const;\n\nexport type PxTextPathSpacing = typeof PxTextPathSpacing[keyof typeof PxTextPathSpacing];\n\n/** `strokeTrim.subPaths` — what the 0..1 `range`/`offset` window is measured over.\n * `separate` (default): each sub-path against its OWN length, all trimmed alike.\n * `combined`: every descendant sub-path chained end-to-end into one virtual path,\n * so the window slides across siblings (AE \"Trim All As One\"). * @public\n */\nexport const PxStrokeTrimSubPaths = {\n separate: 'separate',\n combined: 'combined',\n} as const;\n\nexport type PxStrokeTrimSubPaths = typeof PxStrokeTrimSubPaths[keyof typeof PxStrokeTrimSubPaths];\n\n\n// S8: `textContent` is the ONE text-content key (the DOM property name). `text` is not a wire\n// key: it was triply overloaded (the `text` tag, the `effects.text` group, and a content alias)\n// and no reader accepts it.\n/** @internal */\nexport const PX_TEXT_CONTENT_ATTR = 'textContent';\n\n/** The DOM `class` attribute. A name we EMIT but do not own, so it is written through this\n * constant rather than as an identifier — every other emitted attribute name reaches the\n * DOM as a string, and `class` was the one exception, which is why the minifier renamed it\n * to `ct` in the shipped bundles (dev-docs/plans/minification-boundary.md §1.1). */\nexport const CLASS_ATTR = 'class';\n\n/** The DOM `transform` attribute, and the key the animation record uses for it. Both are\n * DATA names — a dictionary key, not a field of one of our typed structures — so they are\n * written as constants rather than as identifiers. */\nexport const TRANSFORM_ATTR = 'transform';\n\n/** `animate.offsetDistance` — the CSS Motion Path channel the offset-path materializer writes. */\nexport const OFFSET_DISTANCE_ATTR = 'offsetDistance';\n\n// Wire keys that are NEVER DOM attributes (internal use only).\n//\n// `effects` is here for safety rather than necessity: `materializeNodeEffects` deletes it at\n// load, so today nothing reaches the renderer with it still attached. That is a property\n// of the pipeline, though, not of the contract — an effect path that returns early, or a\n// document carrying an effect key the pipeline does not recognize, would otherwise leave\n// the object behind and the renderer would write `effects=\"[object Object]\"` with no error\n// anywhere. Listing it makes the invariant structural (J4).\n/** @internal */\nexport const INTERNAL_ATTRS = new Set([\n 'type', 'children', 'animator', 'meta', 'animate', 'effects', PX_TEXT_CONTENT_ATTR\n]);\n\n// ============================================================================\n// TRANSFORM\n// ============================================================================\n\n/**\n * Names of the transform parts that can appear inside a transform value record.\n * The unified `transform` slot replaces the earlier per-part top-level keys\n * (`translate`, `rotate`, `scale`, `origin`) — those names now live as keys\n * inside a `PxTransformParts` record.\n */\n/** The transform-part names, as a named record — they are keys of a DATA record (the\n * transform value), so code reaches them through this rather than as bare literals. */\nexport const TRANSFORM_PART = {\n translate: 'translate',\n rotate: 'rotate',\n scale: 'scale',\n origin: 'origin',\n} as const;\n\n/** @public */\nexport const PX_TRANSFORM_PART_KEYS = [\n TRANSFORM_PART.translate, TRANSFORM_PART.rotate, TRANSFORM_PART.scale, TRANSFORM_PART.origin,\n] as const;\n\n/** One of the transform-part key strings. @public */\nexport type PxTransformPartKey = typeof PX_TRANSFORM_PART_KEYS[number];\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Gradient paint effect — `fillGradient` / `strokeGradient`.\n//\n// Materializer pattern mirrors `maskedByEffect`: at apply time the gradient\n// effect generates a `<linearGradient>` / `<radialGradient>` def into `ctx.defs`,\n// then sets the host element's `fill` / `stroke` to `url(#auto-id)`. The wire\n// gradient is geometry parts (`p1`/`p2` linear, `c`/`r`/`fp` radial — standard\n// animatable slots) + a stop sequence that is either static (bare array) or\n// animated (a single `{keyframes}` block whose each kf's `value` is the FULL\n// `Array<{offset, color}>` snapshot at that time). Per-stop independent\n// timelines are intentionally NOT modelled — the source is a single\n// stop-color keyframe group. Animated geometry is frames-engine only\n// (CSS/WAAPI cannot animate gradient endpoints; `mode: 'auto'` handles it).\n//\n// Stop count is constant across kfs. `gradientTransform` is captured as static\n// only (animated transform is vanishingly rare).\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Loose enums for `spreadMethod` / gradient `type` — kept on the wire as\n * plain strings (matches the rest of the schema's loose-enum stance) but\n * collected here so call sites use named constants instead of bare literals.\n *\n * `gradientUnits` has NO enum of its own: it takes the same two values as every other\n * units slot, so it reuses {@link PxUnits} (review §2.7 — one name per value set). * @public\n */\nexport const PxGradientSpreadMethod = {\n pad: 'pad',\n reflect: 'reflect',\n repeat: 'repeat',\n} as const;\n\nexport type PxGradientSpreadMethod = typeof PxGradientSpreadMethod[keyof typeof PxGradientSpreadMethod];\n\n/** @public */\nexport const PxGradientType = {\n linear: 'linear',\n radial: 'radial',\n} as const;\n\nexport type PxGradientType = typeof PxGradientType[keyof typeof PxGradientType];\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/** @public @advanced */\nexport function isPxDocument(doc: any): doc is PxAnimatedSvgDocument {\n if (!(\n doc &&\n typeof doc === 'object' &&\n !Array.isArray(doc)\n )) {\n return false;\n }\n\n // `type` is the tag, and the ONLY discriminator — it is what the schema requires\n // (`px.literal('svg')`). A `tagName` alternative was accepted here until 2026-08,\n // which meant a tagName-only document passed this gate and then failed\n // `isValidPxDocument`; nothing ever wrote it.\n return doc.type === 'svg';\n}\n\n/**\n * The animator config, at either of its TWO canonical addresses (S4).\n *\n * `animator` is the only name. It has two addresses because the SVG form has no other\n * slot: a `.svga`/JSON document carries it at the top level, while a pre-rendered\n * `.svg` carries it inside the root element's `data-px-meta` blob — i.e. under `meta`.\n * The editor lifts/un-lifts between the two on write/read.\n *\n * The `animation` / `meta.animation` spellings were removed 2026-08: nothing wrote\n * them and they were never in the schema.\n * @public @advanced\n */\nexport function getAnimatorConfig(doc: PxAnimatedSvgDocument): PxAnimatorConfig | undefined {\n const cfg = doc?.animator || doc?.meta?.animator;\n if (!cfg) return undefined;\n const memoised = wireViewMemo.get(cfg as object);\n if (memoised) return memoised;\n\n // A document states playback ONLY inside `timeline`. A flat key at the animator root is not a\n // second spelling to honor — it is an unknown key (`validateDocument` and the entry diagnostic\n // both report it), so it is dropped here and never reaches an engine.\n const wire = cfg as Record<string, unknown>;\n const stray = PX_FLAT_RUNTIME_VIEW_KEYS.filter(k => wire[k] !== undefined);\n const source = stray.length ? { ...wire } : cfg;\n for (const k of stray) delete (source as Record<string, unknown>)[k];\n\n // Every internal consumer sees the FLAT view — the nested `timeline` spelling is\n // folded down here, once, so the engines/effects/drivers never branch on it.\n const view = flattenAnimatorTimeline(source as PxAnimatorConfig);\n // Memoised on the DOCUMENT's config: repeated calls must return the same object (callers\n // compare identity and cache off it), and `source` is a fresh object when keys were dropped.\n wireViewMemo.set(cfg as object, view);\n return view;\n}\n\n/** Memo for {@link getAnimatorConfig} — see the identity note inside it. */\nconst wireViewMemo = new WeakMap<object, PxAnimatorConfig>();\n\n\n// ============================================================================\n// TIMELINE SPELLING (review §2.1)\n//\n// The wire spelling is `animator.timeline: { type?: 'time'|'scroll'|'view', … }` (absent = 'time');\n// the flat form (`timelineSource` + `scroll` + loose clock knobs) is the INTERNAL\n// runtime view only — not a wire format. These two functions convert between them:\n// • flattenAnimatorTimeline — wire → runtime view; applied by `getAnimatorConfig`,\n// so ALL runtime code keeps consuming the flat form it always has.\n// • nestAnimatorTimeline — runtime view → wire; applied by writers (the editor) so\n// files carry only the nested spelling and its mode-dead keys are structurally absent.\n// ============================================================================\n\n/** Memo: flatten allocates a new config; repeated `getAnimatorConfig` calls must keep\n * returning the SAME object (some callers compare identity / cache off it). */\nconst flattenMemo = new WeakMap<object, PxAnimatorConfig>();\n\n/**\n * Folds `cfg.timeline` (the wire spelling) into the flat runtime-view fields the engines\n * consume. Returns `cfg` unchanged when there is nothing to fold. Never mutates input.\n * @public @advanced\n */\nexport function flattenAnimatorTimeline(cfg: PxAnimatorConfig): PxAnimatorConfig {\n const timeline: any = (cfg as any).timeline;\n if (timeline === undefined || timeline === null || typeof timeline !== 'object') return cfg;\n\n const memoised = flattenMemo.get(cfg as object);\n if (memoised) return memoised;\n\n const { timeline: _dropped, ...flat } = cfg as any;\n\n // `engine` and `frameRate` are shared by every timeline type: how the attributes get\n // updated, and at what rate when that is the player's own frame loop.\n if (timeline.engine !== undefined) flat.engine = timeline.engine;\n if (timeline.frameRate !== undefined) flat.frameRate = timeline.frameRate;\n\n if (timeline.type === 'scroll' || timeline.type === 'view') {\n flat.timelineSource = 'scroll';\n if (timeline.duration !== undefined) flat.duration = timeline.duration; // §2.8\n if (timeline.iterations !== undefined) flat.iterations = timeline.iterations;\n const scroll: PxScroll = { ...(flat.scroll || {}) };\n scroll.kind = timeline.type;\n if (timeline.axis !== undefined) scroll.axis = timeline.axis;\n if (timeline.source !== undefined) scroll.source = timeline.source;\n if (timeline.subject !== undefined) scroll.subject = timeline.subject;\n if (timeline.smoothing !== undefined) scroll.smoothing = timeline.smoothing;\n if (timeline.range !== undefined) scroll.range = timeline.range;\n const pin = timeline.pin;\n if (typeof pin === 'boolean') scroll.pin = pin;\n else if (pin && typeof pin === 'object') {\n scroll.pin = true;\n if (pin.align !== undefined) scroll.pinAlign = pin.align;\n if (pin.offset !== undefined) scroll.pinOffset = pin.offset;\n if (pin.distance !== undefined) scroll.pinDistance = pin.distance;\n }\n flat.scroll = scroll;\n } else { // 'time', absent, or unknown — the time-driven timeline is the default\n if (timeline.duration !== undefined) flat.duration = timeline.duration; // §2.8\n if (timeline.trigger !== undefined) {\n const { finishAction, ...restTrigger } = timeline.trigger;\n if (Object.keys(restTrigger).length) flat.trigger = restTrigger;\n if (finishAction !== undefined) flat.resetOnFinish = finishAction === 'reset';\n }\n if (timeline.delay !== undefined) flat.delay = timeline.delay;\n if (timeline.iterations !== undefined) flat.iterations = timeline.iterations;\n if (timeline.direction !== undefined) flat.direction = timeline.direction;\n if (timeline.fillMode !== undefined) flat.fill = timeline.fillMode; // wire `fillMode` → runtime `fill`\n }\n\n flattenMemo.set(cfg as object, flat);\n return flat;\n}\n\n/**\n * Which scroll-driven member an absent `scroll.kind` selects — `'view'`, per `_PxScroll.kind`.\n *\n * The flat view says WHICH FAMILY drives progress (`timelineSource: 'scroll'`) separately from\n * WHICH MEMBER of it (`scroll.kind`), and only the family is named \"scroll\". Defaulting the\n * member to its family's name reads natural and is wrong: it silently rewrote every document\n * that left the kind at its default — which, being the default, is most of them.\n */\nfunction scrollKindOrDefault(kind: unknown): 'view' | 'scroll' {\n return kind === 'scroll' ? 'scroll' : 'view';\n}\n\n/**\n * Converts a FLAT animator config into the written spelling: mode-specific keys fold into\n * one discriminated `timeline` object; the legacy flat keys are removed from the output.\n * Returns a new object (input untouched); a config already carrying `timeline` passes\n * through unchanged; a pure-shared config (duration/mode/… only) gets no `timeline` at all.\n * @public @advanced\n */\nexport function nestAnimatorTimeline(cfg: PxAnimatorConfig): PxAnimatorConfig {\n if (!cfg || (cfg as any).timeline !== undefined) return cfg;\n\n const { timelineSource, scroll, trigger, delay, iterations, direction, fill, resetOnFinish,\n duration, engine, frameRate, ...shared } = cfg as any;\n\n if (timelineSource === 'scroll') {\n const timeline: any = { type: scrollKindOrDefault(scroll?.kind) };\n if (engine !== undefined) timeline.engine = engine;\n if (frameRate !== undefined) timeline.frameRate = frameRate;\n if (duration !== undefined) timeline.duration = duration; // §2.8\n // Finite iterations survive scrubbing (D4); 'infinite' cannot map to a range.\n if (typeof iterations === 'number') timeline.iterations = iterations;\n if (scroll) {\n if (scroll.axis !== undefined) timeline.axis = scroll.axis;\n if (scroll.source !== undefined) timeline.source = scroll.source;\n if (scroll.subject !== undefined) timeline.subject = scroll.subject;\n if (scroll.smoothing !== undefined) timeline.smoothing = scroll.smoothing;\n if (scroll.range !== undefined) timeline.range = scroll.range;\n const hasPinParams = scroll.pinAlign !== undefined || scroll.pinOffset !== undefined || scroll.pinDistance !== undefined;\n if (hasPinParams) {\n timeline.pin = {\n ...(scroll.pinAlign !== undefined ? { align: scroll.pinAlign } : {}),\n ...(scroll.pinOffset !== undefined ? { offset: scroll.pinOffset } : {}),\n ...(scroll.pinDistance !== undefined ? { distance: scroll.pinDistance } : {}),\n };\n } else if (scroll.pin !== undefined) {\n timeline.pin = scroll.pin;\n }\n }\n return { ...shared, timeline };\n }\n\n // Time-driven: `type` is optional on the wire and 'time' is the default, so the\n // writer omits it — the common case declares nothing.\n const timeline: any = {};\n if (engine !== undefined) timeline.engine = engine;\n if (frameRate !== undefined) timeline.frameRate = frameRate;\n if (duration !== undefined) timeline.duration = duration; // §2.8\n if (trigger !== undefined || resetOnFinish) {\n const t: any = { ...(trigger || {}) };\n if (resetOnFinish) t.finishAction = 'reset';\n timeline.trigger = t;\n }\n if (delay !== undefined) timeline.delay = delay;\n if (iterations !== undefined) timeline.iterations = iterations;\n if (direction !== undefined) timeline.direction = direction;\n if (fill !== undefined) timeline.fillMode = fill; // runtime `fill` → wire `fillMode`\n\n // An empty time timeline says nothing — omit the block entirely.\n return Object.keys(timeline).length > 0 ? { ...shared, timeline } : shared;\n}\n\n\n/** @public @advanced */\nexport function getDefinitions(doc: PxAnimatedSvgDocument): PxDefinitions | undefined {\n if (!doc) return undefined;\n return getAnimatorConfig(doc)?.definitions;\n}\n\n/** The bind-by-id document's `animator.bindings`, as written — `target` keeps its `#`. @public @advanced */\nexport function getBindings(doc: PxAnimatedSvgDocument): PxBinding[] | undefined {\n if (!doc) return undefined;\n return getAnimatorConfig(doc)?.bindings;\n}\n\n\n/** @public @advanced */\nexport function getChildren(doc: PxAnimatedSvgDocument): PxNode[] | undefined {\n return doc?.children;\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport type { KeysMatch, PxInfer, PxSchema, PxValidationContext, PxRemoveIndex } from '../schema/PxSchema';\nimport { implementsInterface, px } from '../schema/PxSchema';\n// Constants live in their own module so importing one does not pull the schema engine\n// in; re-exported here so this module's public surface is unchanged. See there.\nexport * from './PxAnimatorConstants';\nimport { getAnimatorConfig, INTERNAL_ATTRS, isPxDocument, PX_TRANSFORM_PART_KEYS, PX_TRIGGER_DEFAULTS, PxTimelineEngineSetting, PxCloneWithout, PxPathOverflow, PxGradientSpreadMethod, PxGradientType, PxLengthAdjust, PxLoopRepeatAt, PxLoopDirection, PxMaskType, PxTextPathMethod, PxTextPathSpacing, PxStrokeTrimSubPaths, PxUnits,\n // Wire enums named in review §2.7 — used as VALUES by the schemas below.\n PxAlongPathMode, PxFillMode, PxFinishAction, PxOutAction, PxPinAlign, PxPlaybackDirection,\n PxScrollAxis, PxScrollKind, PxScrollPhase, PxScrollSource, PxStartOn } from './PxAnimatorConstants';\nimport type { PxTimelineEngine, PxTransformPartKey } from './PxAnimatorConstants';\n// Version stamps are parsed by the reader's own parser (review §2.10) so the validator and the\n// reader can never disagree on what counts as a stamp. `PxWireVersion` imports only\n// `PxSchemaVersion`, so this edge creates no cycle.\nimport { parseWireVersion } from '../version/PxWireVersion';\n// The diagnostics channel's payload, named by `PxEngineCallbacks` below (review §5).\n// `PxDiagnostics` imports nothing, so this edge creates no cycle either.\nimport type { PxDiagnosticKind, PxDiagnosticsConfig } from '../playback/PxDiagnostics';\n\n// ============================================================================\n// EASING\n// ============================================================================\n\n/**\n * Easing function definition.\n * Can be a named reference to a predefined easing or a cubic-bezier array [x1, y1, x2, y2].\n *\n * @example \"ease-in\" | [0.68, -0.55, 0.265, 1.55]\n *\n * `string | [x1, y1, x2, y2]`\n * @public @advanced\n */\nexport const PxEasingOrRefSchema = px.union([\n px.string(),\n px.tuple([px.number(), px.number(), px.number(), px.number()] as const),\n]);\n\n/**\n * Easing function definition.\n * Can be a named reference to a predefined easing or a cubic-bezier array [x1, y1, x2, y2].\n *\n * @example \"ease-in\" | \"easeOut\" | [0.68, -0.55, 0.265, 1.55]\n */\nexport type PxEasingOrRef = PxInfer<typeof PxEasingOrRefSchema>;\n\n\n// ============================================================================\n// KEYFRAME\n// ============================================================================\n\n/**\n * A single animation keyframe defining the state at a specific point in time.\n *\n * THE WIRE FORM, and only that: `time` / `value` / `easing` / `tangentIn` / `tangentOut`.\n * Locked to `PxKeyframeSchema` by the `KeysMatch` assertion below, so this interface and the\n * validator cannot drift apart.\n *\n * The engines consume {@link _PxNormalizedKeyframe} instead — the short-field form\n * `normalizeKeyframes` produces, with easing refs resolved and values parsed. The two used to\n * be ONE interface carrying both spellings, which meant no key-set lock was possible here and\n * nothing in the types said which form a given function expected.\n */\nexport interface _PxKeyframe {\n\n /** Timestamp in milliseconds from animation start */\n time?: number;\n\n /** The value of the animated property at this keyframe */\n value?: any;\n\n /** Easing function applied to the interval from this keyframe to the next */\n easing?: PxEasingOrRef;\n\n /**\n * Outgoing spatial tangent `[dx, dy]` for motion-along-path interpolation\n * (translate animations only).\n *\n * Stored as a *delta relative to* this keyframe's translate position —\n * the cubic Bezier segment between this kf and the next is built from\n * `(P0=value, P1=value+tangentOut, P2=next.value+next.tangentIn, P3=next.value)`.\n *\n * Defined when the segment leaving this keyframe is curved.\n */\n tangentOut?: [number, number];\n\n /**\n * Incoming spatial tangent `[dx, dy]` for motion-along-path interpolation\n * (translate animations only). Delta relative to this keyframe's translate\n * position. See `tangentOut` for the segment construction.\n *\n * Defined when the segment arriving at this keyframe is curved.\n */\n tangentIn?: [number, number];\n\n}\n\n/**\n * Allowed shapes for a single keyframe `value` across the wire schema:\n * - `number` — scalar properties (rotate-degree, opacity, offset-distance, …)\n * - `Array<number>` — vector properties (translate `[x,y]`, scale `[sx,sy]`, stroke-dasharray, RGBA …)\n * - `string` — color (hex / `url(#…)` / named) and other string-valued props\n * - `PxTransformParts` — unified body `transform` parts record\n * `{translate, rotate, scale, origin}`\n * - `{ paths: Array<PxBezierPath> }` — animated SVG path `d` value\n * - `Array<PxGradientStop>` — gradient `stops` timeline (each kf value is\n * the FULL `[{offset, color}, …]` snapshot)\n *\n * Plugged into `PxKeyframeSchema.value` / `.v` — every keyframe value on the\n * wire is validated against this union. The inferred TS type of `PxKeyframe`\n * stays permissive (`value?: any` via the generic default) so the duck-typed\n * interpolator code in `PxDefinitions.ts` (`prevV?.paths`,\n * `Array.isArray(prevV)`, …) keeps working without per-shape narrowing.\n */\nexport type _PxKeyframeValue =\n | string\n | number\n | Array<number>\n | PxTransformParts\n | { pathData: string }\n | Array<_PxGradientStop>;\n\n// `string | number | Array<number> | PxTransformParts | { pathData: string }`\n//\n// `{ pathData: \"M…\" }` is the ONE form for animated path geometry: a single `d` string\n// (a compound shape is one string with several `M…` sub-paths). The Lottie-style\n// `{ paths: [{v,i,o,c}] }` array was retired 2026-09-11 — it lives on only as the\n// interpolator's INTERNAL shape (`normalizePathValue` in `PxDefinitions.ts`).\n//\n// `PxTransformPartsSchema` is declared later in this file — `px.lazy` defers the lookup\n// until validation time so the declarations stay in narrative order without a TDZ at load.\n/** @public @advanced */\nexport const PxKeyframeValueSchema = implementsInterface<_PxKeyframeValue>()(px.union([\n px.string(), // e.g. for colors\n px.number(),\n px.array(px.number()),\n // ORDER LAW: the key-discriminated object shape (`{pathData}`) comes BEFORE the\n // all-optional transform-parts record. In default (non-strict) mode that record accepts\n // ANY object (every key optional, unknown keys ignored), so listing it earlier made\n // Union.sanitize route `{pathData}` values into it and strip them to `{}` —\n // silent morph-data loss (repro: the editor's keyframeValueSanitize spec). Validity is\n // order-independent (`some()`); only sanitize routing depends on this order.\n px.object({ pathData: px.string() }),\n // Gradient `stops` timeline — each kf value is the full stops-array snapshot.\n px.lazy<Array<_PxGradientStop>>(() => px.array(PxGradientStopSchema), []),\n px.lazy<PxTransformParts>(() => PxTransformPartsSchema, {}),\n]));\n\n/** A single keyframe `value` — union of all wire-allowed shapes. */\nexport type PxKeyframeValue = PxInfer<typeof PxKeyframeValueSchema>;\n\n// `{ time?:number, t?:number, value?:PxKeyframeValue, v?:PxKeyframeValue,\n// easing?:Easing, e?:Easing, tangentOut?:[dx,dy], tangentIn?:[dx,dy] }`\n//\n// `value` / `v` validate against {@link PxKeyframeValueSchema} — malformed\n// keyframe values are now schema errors instead of passing as `px.any()`\n// (SCHEMA-DESIGN I-5). The `_PxKeyframe` interface keeps `value?: any` (and\n// `PxKeyframe<T = any>` its generic default) so the duck-typed interpolator\n// access in `PxDefinitions.ts` stays untyped-permissive at compile time.\n// LONG SPELLINGS ONLY (review §1.2/§6.1): the short aliases (`t`/`v`/`e`/`to`/`ti`)\n// were removed from the wire outright — one clear spelling, no mixing ambiguity.\n// They survive only as the internal normalized runtime view (see `_PxKeyframe`).\n/** @public @advanced */\nexport const PxKeyframeSchema = implementsInterface<_PxKeyframe>()(px.object({\n time: px.number().optional(),\n value: PxKeyframeValueSchema.optional(),\n easing: PxEasingOrRefSchema.optional(),\n tangentOut: px.tuple([px.number(), px.number()] as const).optional(),\n tangentIn: px.tuple([px.number(), px.number()] as const).optional(),\n // (`selected` — editor timeline-selection UI state — was REMOVED from the wire\n // (review §1.3): editor data lives under `meta`. The editor still carries it on\n // its internal COPY-PASTE payload, which never validates against this schema.)\n}));\n\n/**\n * THE RUNTIME FORM — what `normalizeKeyframes` hands the engines, and what the tree-level\n * materializers (`materializeInternalLoopsInTree` and everything after it in\n * `materializeAllInTree`) write back into the document.\n *\n * Short-named on purpose: these are read once per property per frame. `e` is a RESOLVED easing\n * (named refs already looked up in `definitions.easings`) and `v` is a PARSED value (colors as\n * RGBA arrays, path `d` normalized) — which is the substantive difference from the wire form,\n * not just the spelling. Deliberately NOT a wire shape: `validateDocument` rejects it, and a\n * materialized document is a runtime artefact that is never written to disk.\n */\nexport interface _PxNormalizedKeyframe {\n /** Time in ms from the animation start (the wire spells it `time`). */\n t?: number;\n /** The parsed value at this keyframe (the wire spells it `value`). */\n v?: any;\n /** The RESOLVED easing — never a name (the wire spells it `easing`). */\n e?: PxEasingOrRef;\n /** Incoming spatial tangent, same meaning as the wire's. */\n tangentIn?: [number, number];\n /** Outgoing spatial tangent, same meaning as the wire's. */\n tangentOut?: [number, number];\n}\n\n/** @internal */\nexport type PxNormalizedKeyframe = _PxNormalizedKeyframe;\n\n/**\n * Either spelling. For the handful of helpers that genuinely run on BOTH sides of\n * normalization — read them through the `kf*` accessors below rather than branching inline.\n * @internal\n */\nexport type PxAnyKeyframe = _PxKeyframe | _PxNormalizedKeyframe;\n\nconst anyKf = (kf: PxAnyKeyframe) => kf as _PxKeyframe & _PxNormalizedKeyframe;\n\n/** Time in ms, whichever spelling the keyframe is in. @internal */\nexport const keyframeTime = (kf: PxAnyKeyframe): number => anyKf(kf).time ?? anyKf(kf).t ?? 0;\n/** Value, whichever spelling. @internal */\nexport const keyframeValue = (kf: PxAnyKeyframe): any => anyKf(kf).value ?? anyKf(kf).v;\n/** Easing — resolved on a normalized keyframe, possibly a NAME on a wire one. @internal */\nexport const keyframeEasing = (kf: PxAnyKeyframe): PxEasingOrRef | undefined => anyKf(kf).easing ?? anyKf(kf).e;\n/** Incoming spatial tangent, whichever spelling. @internal */\nexport const keyframeTangentIn = (kf: PxAnyKeyframe): [number, number] | undefined => anyKf(kf).tangentIn;\n/** Outgoing spatial tangent, whichever spelling. @internal */\nexport const keyframeTangentOut = (kf: PxAnyKeyframe): [number, number] | undefined => anyKf(kf).tangentOut;\n\n/**\n * A single animation keyframe defining the state at a specific point in time.\n *\n * Generic over the keyframe `value` type for callers that know the per-property\n * value shape (e.g. `PxKeyframe<PxVec2>` in the effect appliers). Defaults to\n * `any`, matching the schema (`value` is stored as `px.any()` on the wire).\n * @public\n */\n// The WIRE type, generic over the value type. The engines use `PxNormalizedKeyframe`.\nexport type PxKeyframe<T = any> = Omit<_PxKeyframe, 'value'> & { value?: T };\n// Locks the interface to the schema at the default instantiation.\nconst _ck_PxKeyframe: KeysMatch<PxInfer<typeof PxKeyframeSchema>, _PxKeyframe> = true;\n\n/** {@link PxNormalizedKeyframe}, generic over the value type — the runtime counterpart. */\nexport type PxNormalizedKeyframeOf<T = any> = Omit<_PxNormalizedKeyframe, 'v'> & { v?: T };\n\n/**\n * A property animation whose keyframes are in the RUNTIME form.\n *\n * What `normalizeKeyframes` produces, what the engines consume — and what the EDITOR's in-memory\n * model is: its keyframe objects carry the short field names and serialize to the long wire ones\n * through `@serializable`, so the model implements this rather than the wire shape.\n * @internal\n */\nexport type PxNormalizedPropertyAnimation =\n Omit<_PxPropertyAnimation, 'keyframes'> & { keyframes?: Array<_PxNormalizedKeyframe> };\n\n\n// ============================================================================\n// LOOP\n// ============================================================================\n\n/**\n * Defines how a property's keyframe animation is extended beyond its defined keyframe range\n * by continuously repeating a chosen segment of the sequence.\n *\n * The repeated segment is a contiguous run of keyframe *intervals* (gaps between consecutive\n * keyframes). Which end of the sequence is repeated is controlled by `before`, and whether\n * each repetition plays in the same direction or alternates is controlled by `alternate`.\n *\n * **Relationship to `animator.iterations`**\n *\n * `PxLoop` and `animator.iterations` are independent mechanisms operating at different levels:\n *\n * - `PxLoop` is a **pre-processing step**: it expands the property's keyframe list to fill the\n * full `animator.duration` before any playback begins. The runtime sees a single, fully\n * expanded keyframe sequence — it has no knowledge of the loop.\n *\n * - `animator.iterations` repeats the **entire document timeline** (all properties, all\n * elements) as a unit, after the expanded keyframes are already in place.\n *\n * The two compose independently: a property with `loop: true` inside a document with\n * `iterations: \"infinite\"` will cycle its own segment within each document iteration, and\n * that iteration will itself repeat forever — loop-within-loop.\n */\nexport interface _PxLoop {\n\n /**\n * Number of keyframe intervals (gaps between consecutive keyframes) that form the repeating\n * segment.\n *\n * - `undefined` → the entire keyframe sequence is used as the loop segment.\n * - `N` → only the first `N` intervals (when `repeatAt: 'start'`) or the last `N`\n * intervals (when `repeatAt: 'end'`) are looped. Clamped to `[1, keyframes.length - 1]`.\n */\n segmentCount?: number;\n\n /**\n * Which end of the keyframe sequence the repetition fills — see {@link PxLoopRepeatAt}.\n * `'start'` repeats ahead of the first keyframe (intro loop); `'end'` (default)\n * repeats past the last (idle/outro loop).\n */\n repeatAt?: PxLoopRepeatAt;\n\n /**\n * How successive repetitions play — see {@link PxLoopDirection}.\n *\n * - `'normal'` (default) → **cycle**: every repetition replays the segment the same way round.\n * - `'alternate'` → **ping-pong**: repetitions alternate forward and backward\n * (even repetitions play forward, odd ones in reverse).\n */\n direction?: PxLoopDirection;\n}\n\n// `{ segmentCount?:number, repeatAt?:'start'|'end', direction?:'normal'|'alternate' }`\n/** @public @advanced */\nexport const PxLoopSchema = implementsInterface<_PxLoop>()(px.object({\n segmentCount: px.number().optional(),\n repeatAt: px.enum([PxLoopRepeatAt.start, PxLoopRepeatAt.end] as const).optional(),\n direction: px.enum([PxLoopDirection.normal, PxLoopDirection.alternate] as const).optional(),\n}));\n\n/**\n * Defines how a property's keyframe animation is extended beyond its defined keyframe range\n * by continuously repeating a chosen segment of the sequence.\n * @public\n */\nexport type PxLoop = PxInfer<typeof PxLoopSchema>;\nconst _ck_PxLoop: KeysMatch<PxLoop, _PxLoop> = true; // the key sets are identical\n\n\n// ============================================================================\n// PROPERTY ANIMATION\n// ============================================================================\n\n/**\n * Animation definition for a single CSS/SVG property.\n * Contains an array of keyframes that define how the property changes over time.\n */\nexport interface _PxPropertyAnimation {\n\n /**\n * Optional static / base value for the animated property.\n *\n * Two uses:\n * - structured static: `{value}` with no keyframes is the static form of\n * the universal animatable pattern (`PxAnimatable<T>`);\n * - base + keyframes: when both are present, `value` is the baseline the\n * animation starts from. For most properties keyframe values are\n * complete and `value` is just the pre-tick DOM baseline; slots with\n * patch semantics (the editor's extended-d `shape` effect) merge each\n * keyframe's partial value over this base.\n */\n value?: any;\n\n /** Array of keyframes defining the animation timeline */\n keyframes?: PxKeyframe[];\n\n /**\n * Optional loop configuration. When set, the keyframe sequence is expanded at pre-processing\n * time to fill the gap between the keyframe range and `animator.duration` by repeating a\n * chosen segment. `true` is shorthand for the default {@link PxLoop} (loop the last segment\n * after the final keyframe, cycling forward). See {@link PxLoop} for details.\n *\n * Note: this operates independently of `animator.iterations` — see {@link _PxLoop} for the\n * interaction between the two.\n */\n loop?: PxLoop | boolean;\n\n /**\n * Motion-along-path \"auto-orient\" flag. Only meaningful for translate\n * animations whose keyframes carry spatial tangents (`tangentIn` /\n * `tangentOut`): when true, the element rotates so its local X axis aligns\n * with the path tangent at the current position. The rotation is computed\n * from the cubic-Bezier derivative at the eased progress along the\n * arc-length-parametrised segment.\n */\n autoOrient?: boolean;\n\n /**\n * How a motion-along-path `transform` animation is RENDERED: `'sampled'` (default,\n * absent) — the path is pre-sampled into plain transform keyframes;\n * `'offsetPath'` — the browser drives it as a CSS Motion Path (`offset-path` /\n * `offset-distance`). Written by the editor, consumed by `materializeAllInTree`.\n */\n alongPathMode?: 'sampled' | 'offsetPath';\n}\n\n// `{ value?:KeyframeValue, keyframes?:Keyframe[], loop?:Loop|boolean, autoOrient?:bool, alongPathMode?:'sampled'|'offsetPath' }`\n// (the `kfs` alias was removed outright — review §1.2/§6.1: one spelling only)\n/** @public @advanced */\nexport const PxPropertyAnimationSchema = implementsInterface<_PxPropertyAnimation>()(px.object({\n value: PxKeyframeValueSchema.optional(),\n keyframes: px.array(PxKeyframeSchema).optional(),\n loop: px.union([PxLoopSchema, px.boolean()]).optional(),\n autoOrient: px.boolean().optional(),\n alongPathMode: px.enum([PxAlongPathMode.sampled, PxAlongPathMode.offsetPath] as const).optional(),\n}));\n\n/** Animation definition for a single CSS/SVG property. @public */\n// The runtime-VIEW type: its `keyframes` items are runtime-view PxKeyframes (they may\n// carry the internal normalized short fields), which the schema-inferred type cannot.\nexport type PxPropertyAnimation = _PxPropertyAnimation;\n// KeysMatch compares only the KEY SETS, so it still locks the schema to the interface even\n// though the two disagree about the VALUE type of `keyframes` (above). Worth having here in\n// particular: this is the object that carried the `kfs` alias until it was deleted.\nconst _ck_PxPropertyAnimation: KeysMatch<PxInfer<typeof PxPropertyAnimationSchema>, _PxPropertyAnimation> = true;\n\n\n/**\n * Record of transform parts forming a single transform `value`. Each present\n * key contributes one segment of the composed CSS transform string at render /\n * interpolation time, in the canonical order\n * `translate, translate(+origin), rotate, scale, translate(-origin)`.\n *\n * `origin` is meaningful only when `rotate` or `scale` is also present in the\n * same record — see \"When does origin belong inside a keyframe value?\" in\n * `file-format-remaining-design-issues2.md`.\n */\nexport interface _PxTransformParts {\n\n /** Translation offset `[x, y]` in user units. */\n translate?: [number, number];\n\n /** Rotation in degrees. */\n rotate?: number;\n\n /** Skew (skewX) in degrees, pivoting at `origin` — composed between `rotate`\n * and `scale` (matches Lottie's transform order). */\n skew?: number;\n\n /** Scale factor `[sx, sy]`. */\n scale?: [number, number];\n\n /**\n * Pivot for rotate / scale `[x, y]`. Only meaningful alongside `rotate` or\n * `scale` in the same record.\n */\n origin?: [number, number];\n}\n\n// `{ translate?:[x,y], rotate?:deg, skew?:deg, scale?:[sx,sy], origin?:[x,y] }`\n/** @public @advanced */\nexport const PxTransformPartsSchema = implementsInterface<_PxTransformParts>()(px.object({\n translate: px.tuple([px.number(), px.number()] as const).optional(),\n rotate: px.number().optional(),\n skew: px.number().optional(),\n scale: px.tuple([px.number(), px.number()] as const).optional(),\n origin: px.tuple([px.number(), px.number()] as const).optional(),\n}));\n\n/** Record of transform parts forming a single transform `value`. @public */\nexport type PxTransformParts = PxInfer<typeof PxTransformPartsSchema>;\nconst _ck_PxTransformParts: KeysMatch<PxTransformParts, _PxTransformParts> = true; // the key sets are identical\n\n/**\n * Unified `transform` slot value. Valid shapes:\n *\n * - **Bare parts record** — `{translate:[100,100], rotate:45, scale:[1.5,1.5],\n * origin:[25,25]}` — THE canonical lightweight static (SCHEMA-DESIGN §2/S1):\n * the authored parts verbatim, the exact record grammar animated keyframe\n * values use. Unambiguous because a body attr never carries animation — that\n * lives in the parallel `animate` channel (R2); the parts keys are disjoint\n * from the animation-wrapper keys.\n * - **SVG transform string** — `\"translate(125,125)rotate(45)…translate(-25,-25)\"`.\n * The pre-rendered/browser form (origin baked into a pivot sandwich) and the\n * foreign-SVG import path. Read forever.\n * - **Structured static** — `{value: PxTransformParts}`. Read-accepted legacy\n * spelling of the record.\n * - **Animated** — `{keyframes: [{time, value: PxTransformParts, …}, …]}`.\n * Legacy inline form (the editor writes the `animate` channel instead).\n *\n * Replaces the earlier convention of putting each animated transform part\n * under its own top-level attribute name (`translate`, `rotate`, `scale`,\n * `origin`).\n * @public @advanced\n */\nexport const PxTransformValueSchema = px.union([\n px.string(),\n PxTransformPartsSchema,\n px.object({ value: PxTransformPartsSchema }),\n PxPropertyAnimationSchema,\n]);\n\n/** Unified `transform` slot value: string | structured static | animated. @public */\nexport type PxTransformValue = PxInfer<typeof PxTransformValueSchema>;\n\n\n// ============================================================================\n// ANIMATION DEFINITION\n// ============================================================================\n\n/**\n * Complete animation definition containing one or more property animations.\n * Each key is a CSS/SVG property name (e.g., \"opacity\", \"translate\", \"fill\").\n *\n * @example\n * { \"opacity\": { keyframes: [...] }, \"translate\": { keyframes: [...] } }\n */\nexport interface _PxAnimationDefinition {\n [property: string]: PxPropertyAnimation;\n}\n\n// `Record<propName, PropertyAnimation>`\n/** @public @advanced */\nexport const PxAnimationDefinitionSchema = implementsInterface<_PxAnimationDefinition>()(\n px.record(PxPropertyAnimationSchema)\n);\n\n/**\n * Complete animation definition containing one or more property animations.\n * Each key is a CSS/SVG property name (e.g., \"opacity\", \"scale\", \"rotate\").\n * @public\n */\nexport type PxAnimationDefinition = PxInfer<typeof PxAnimationDefinitionSchema>;\n\n\n// ============================================================================\n// ELEMENT ANIMATION\n// ============================================================================\n\n/**\n * Element animation specification. Can be:\n * - A string referencing a named animation from `definitions.animations`\n * - An array of named references\n * - An inline `AnimationDefinition` object\n * - A mixed array of references and inline definitions\n *\n * @example\n * \"fadeIn\"\n * [\"fadeIn\", \"spin\"]\n * { opacity: { keyframes: [...] } }\n * [\"fadeIn\", { scale: { keyframes: [...] } }]\n */\nexport type _PxElementAnimation =\n | string\n | string[]\n | PxAnimationDefinition\n | (string | PxAnimationDefinition)[];\n\n// `string | Array<string|AnimationDefinition> | AnimationDefinition`\n/** @public @advanced */\nexport const PxElementAnimationSchema = implementsInterface<_PxElementAnimation>()(px.union([\n px.string(),\n px.array(px.union([px.string(), PxAnimationDefinitionSchema])),\n PxAnimationDefinitionSchema,\n]));\n\n/**\n * Element animation specification.\n * Can be a string reference, array of references, inline definition, or a mixed array.\n * @public\n */\nexport type PxElementAnimation = PxInfer<typeof PxElementAnimationSchema>;\n\n\n// ============================================================================\n// TRIGGER\n// ============================================================================\n\n/**\n * Defines when and how an animation should be triggered.\n */\nexport interface _PxTrigger {\n\n /** Event that starts the animation. Default `'load'` — a document is designed to play;\n * `'programmatic'` waits for `play()`. */\n startOn?: PxStartOn;\n\n /** Action to take when the trigger condition is no longer met (e.g., mouse leaves).\n * Default `'continue'`. */\n outAction?: PxOutAction;\n\n /** Percentage of element visibility required to trigger (0–1, default 0 = any pixel).\n * Only applies to scrollIntoView. */\n scrollIntoViewThreshold?: number;\n\n /** After a NATURAL finish: `'hold'` (default — keep the end state per `fill`) or `'reset'`\n * (snap back to the start). Named to pair with its sibling `outAction`, and NOT `onFinish`,\n * which is the CALLBACK on `PxEngineCallbacks` — a value key and a function key with\n * one name read badly side by side in a document literal or in JSX. */\n finishAction?: PxFinishAction;\n}\n\n// `{ startOn?:'load'|'mouseOver'|'click'|'scrollIntoView'|'programmatic', outAction?:..., scrollIntoViewThreshold?:number }`\n// An absent field means its PX_TRIGGER_DEFAULTS entry — the table every player resolves through.\n/** @public @advanced */\nexport const PxTriggerSchema = implementsInterface<_PxTrigger>()(px.object({\n startOn: px.enum([PxStartOn.load, PxStartOn.mouseOver, PxStartOn.click, PxStartOn.scrollIntoView, PxStartOn.programmatic] as const, PX_TRIGGER_DEFAULTS.startOn).optional(),\n outAction: px.enum([PxOutAction.continue, PxOutAction.pause, PxOutAction.reset, PxOutAction.reverse] as const, PX_TRIGGER_DEFAULTS.outAction).optional(),\n // What happens after a NATURAL finish — `'hold'` (default: keep the end state per\n // `fill`) or `'reset'` (snap back to the start state). Pairs with `outAction` (\"what\n // happens when the trigger condition ends\"); both end-of-life knobs now read alike.\n finishAction: px.enum([PxFinishAction.hold, PxFinishAction.reset] as const).optional(),\n scrollIntoViewThreshold: px.number().optional(),\n}));\n\n/** Defines when and how an animation should be triggered. @public */\nexport type PxTrigger = PxInfer<typeof PxTriggerSchema>;\nconst _ck_PxTrigger: KeysMatch<PxTrigger, _PxTrigger> = true; // the key sets are identical\n\n\n// ============================================================================\n// DEFS\n// ============================================================================\n\n/**\n * A single character's embedded outline (glyph-mode text). Coordinates and\n * advance are in the owning {@link _PxGlyphFont}'s `unitsPerEm` units, so the\n * player can render text without the original font. See svga.text.design.md.\n */\nexport interface _PxGlyph {\n /** Advance width, in the font's `unitsPerEm`. */\n width: number;\n /** Outline path data, in the font's `unitsPerEm` (empty for whitespace). Named\n * `pathData`, never `d`: `d` is SVG's name for the node ATTRIBUTE only, and every\n * structure of ours spells the concept out (review §2.4). */\n pathData: string;\n}\n\nexport const PxGlyphSchema = implementsInterface<_PxGlyph>()(px.object({\n width: px.number(),\n pathData: px.string(),\n}));\n\n/** @public */\nexport type PxGlyph = PxInfer<typeof PxGlyphSchema>;\nconst _ck_PxGlyph: KeysMatch<PxGlyph, _PxGlyph> = true; // the key sets are identical\n\n/**\n * The used glyphs of one font, keyed by character. Referenced by a text's\n * `font-family` (the key in {@link _PxDefs.fonts}).\n */\nexport interface _PxGlyphFont {\n /** CSS family name, e.g. \"Roboto\". */\n fontFamily: string;\n /** Font-style notation, e.g. \"\" | \"italic\" (review §5.2 — `style` would collide with the node's CSS `style`). */\n fontStyle: string;\n /** Ascent, in `unitsPerEm` units (baseline placement). */\n ascent: number;\n /** Units per em the glyph `width`/`pathData` are expressed in, e.g. 1000. */\n unitsPerEm: number;\n /** Outlines of the used characters, keyed by the character itself. */\n glyphs: { [char: string]: PxGlyph; };\n}\n\nexport const PxGlyphFontSchema = implementsInterface<_PxGlyphFont>()(px.object({\n fontFamily: px.string(),\n fontStyle: px.string(),\n ascent: px.number(),\n unitsPerEm: px.number(),\n glyphs: px.record(PxGlyphSchema),\n}));\n\n/** @public */\nexport type PxGlyphFont = PxInfer<typeof PxGlyphFontSchema>;\nconst _ck_PxGlyphFont: KeysMatch<PxGlyphFont, _PxGlyphFont> = true; // the key sets are identical\n\n/**\n * Reusable definitions library for easings, animations and fonts.\n * Defined once here, referenced by name on elements (or, for animations, from bindings).\n * (`styles` — named style presets — was removed on 2026-09-13, review 2.13: nothing wrote it.)\n */\nexport interface _PxDefs {\n\n /** Named cubic-bezier easing functions */\n easings?: { [name: string]: [number, number, number, number]; };\n\n /** Named animation definitions that can be referenced by elements */\n animations?: { [name: string]: PxAnimationDefinition; };\n\n /** Embedded fonts — per-font glyph outlines, keyed by the text's `font-family`.\n * Lets glyph-mode `<text>` render without an external font. */\n fonts?: { [fontName: string]: PxGlyphFont; };\n}\n\n// `{ easings?:Record<name,[x1,y1,x2,y2]>, animations?:Record<name,AnimationDefinition>, fonts?:Record<fontName,PxGlyphFont> }`\n/** @public @advanced */\nexport const PxDefinitionsSchema = implementsInterface<_PxDefs>()(px.object({\n easings: px.record(px.tuple([px.number(), px.number(), px.number(), px.number()] as const)).optional(),\n animations: px.record(PxAnimationDefinitionSchema).optional(),\n fonts: px.record(PxGlyphFontSchema).optional(),\n}));\n\n/** Reusable definitions library for easings, animations and fonts. @public */\nexport type PxDefinitions = PxInfer<typeof PxDefinitionsSchema>;\nconst _ck_PxDefs: KeysMatch<PxDefinitions, _PxDefs> = true; // the key sets are identical\n\n\n// ============================================================================\n// SCROLL TIMELINE\n// ============================================================================\n\n/**\n * A point on a scroll timeline's range, anchoring where the animation's 0%/100% sit.\n *\n * For `kind: 'view'`, `phase` names WHICH slice of the subject's journey across the\n * scrollport the point is measured in (CSS \"timeline range name\"), and `fraction` is the\n * 0..1 position within that slice. For `kind: 'scroll'` there are no phases — `phase` is\n * ignored and `fraction` is a fraction of the scroller's total scroll range.\n *\n * Structured on purpose (never a CSS string like `\"cover 0%\"`): atomic values, mechanical\n * translation to CSS `animation-range` / WAAPI `rangeStart/rangeEnd` where needed.\n */\nexport interface _PxScrollRangePoint {\n /** `view` kind only: the journey phase this point is anchored in. Default `'cover'`. */\n phase?: PxScrollPhase;\n /** 0..1 within the phase (or of the total scroll range for `kind: 'scroll'`). */\n fraction?: number;\n}\n\n/** @public @advanced */\nexport const PxScrollRangePointSchema = implementsInterface<_PxScrollRangePoint>()(px.object({\n phase: px.enum([PxScrollPhase.cover, PxScrollPhase.contain, PxScrollPhase.entry,\n PxScrollPhase.exit, PxScrollPhase.entryCrossing, PxScrollPhase.exitCrossing] as const).optional(),\n fraction: px.number().optional(),\n}));\n/** @public */\nexport type PxScrollRangePoint = PxInfer<typeof PxScrollRangePointSchema>;\nconst _ck_PxScrollRangePoint: KeysMatch<PxScrollRangePoint, _PxScrollRangePoint> = true; // the key sets are identical\n\n/**\n * Scroll-timeline configuration — consulted only when `timelineSource: 'scroll'`.\n * All fields optional; the defaults give \"scrub the whole animation as the SVG crosses\n * the viewport\" (`view` / `block` / full `cover` range).\n */\nexport interface _PxScroll {\n /** What progress measures. `'view'` (default): the SVG's own journey across the\n * scrollport (enter → leave). `'scroll'`: the scroll container's offset ratio,\n * regardless of where the SVG sits. */\n kind?: 'view' | 'scroll';\n\n /** Scroll axis. `block`/`inline` are writing-mode relative (block = vertical in\n * horizontal writing); `x`/`y` are physical. Default `'block'`. */\n axis?: 'block' | 'inline' | 'x' | 'y';\n\n /** `kind: 'scroll'` only: which scroller. `'nearest'` (default) = nearest scrollable\n * ancestor of the SVG; `'root'` = the document. (`view` always tracks the nearest\n * scrollport.) */\n source?: 'nearest' | 'root';\n\n /**\n * `kind: 'view'` only — WHICH ELEMENT'S journey is measured. Unset (default) = the\n * animation's own `<svg>`. The same indirection CSS gives via `view-timeline-name` +\n * `timeline-scope`, GSAP via `trigger`, Framer via \"Section in view\".\n *\n * - `'parent'` — the nearest ancestor that actually scrolls past: any `sticky`/`fixed`\n * ancestors are skipped and their container is used instead. This is what makes a\n * PINNED section work (a stuck element's rect stops moving, so measuring the graphic\n * itself would freeze); its `contain` phase is exactly the pinned stretch.\n * - `'scroller'` — the scroll container itself.\n * - anything else — a CSS selector, resolved against the host document.\n *\n * An unresolvable selector warns and falls back to the `<svg>` (never a silent freeze).\n */\n subject?: string;\n\n /**\n * MILLISECONDS of catch-up lag — the same idea as GSAP's `scrub: <seconds>`, in the unit\n * the rest of this schema uses (`duration`, `delay`). Unset/0 = the playhead is locked to\n * the scrollbar; above 0 the progress eases toward the scroll position instead of snapping\n * to it, which reads far smoother under momentum scrolling and trackpads.\n * Custom driver only — a browser-native `ScrollTimeline` has no equivalent, so setting\n * this forces the player's own measurement (the browser timeline is skipped).\n */\n smoothing?: number;\n\n /**\n * Hold the canvas still on screen while scrolling scrubs it — GSAP's `pin: true`, done with\n * `position: sticky` (which keeps the element's space in normal flow, so unlike GSAP's\n * `position: fixed` no spacer padding has to be injected into the host's layout).\n *\n * The player owns the DOM inside its own container, so this needs NO host CSS. Pair it with\n * `subject: 'parent'` for the complete scrollytelling pattern.\n */\n pin?: boolean;\n\n /**\n * `pin` only: WHERE in the scrollport the canvas is held — the alignment the sticky\n * offset is computed from. `top` (default) holds it against the top edge; `center`\n * and `bottom` need the canvas's own height, so the player measures it and keeps the\n * offset in sync on resize. `pinOffset` is added on top of whichever alignment is chosen.\n */\n pinAlign?: 'top' | 'center' | 'bottom';\n\n /** `pin` only: offset from the alignment position (see `pinAlign`), in px. Default 0.\n * The runtime-view twin of the wire `timeline.pin.offset` (review §2.6). */\n pinOffset?: number;\n\n /**\n * `pin` only: how much scroll travel the pin should last, in VIEWPORT HEIGHTS — the player\n * injects a wrapper of that height around the canvas to create it. Omit to pin inside\n * whatever tall section the host page already provides.\n */\n pinDistance?: number;\n\n /** The timeline slice mapped onto animation progress 0..1.\n * Default `{ start: {phase:'cover', fraction:0}, end: {phase:'cover', fraction:1} }`. */\n range?: {\n start?: _PxScrollRangePoint;\n end?: _PxScrollRangePoint;\n };\n}\n\n/** The `range` sub-object — named so consumers can derive its keys. @public @advanced */\nexport const PxScrollRangeSchema = px.object({\n start: PxScrollRangePointSchema.optional(),\n end: PxScrollRangePointSchema.optional(),\n});\n\n/** @public @advanced */\nexport const PxScrollSchema = implementsInterface<_PxScroll>()(px.object({\n kind: px.enum([PxScrollKind.view, PxScrollKind.scroll] as const).optional(),\n axis: px.enum([PxScrollAxis.block, PxScrollAxis.inline, PxScrollAxis.x, PxScrollAxis.y] as const).optional(),\n source: px.enum([PxScrollSource.nearest, PxScrollSource.root] as const).optional(),\n // Free-form: the two keywords `parent`/`scroller` plus any CSS selector.\n subject: px.string().optional(),\n smoothing: px.number().optional(),\n pin: px.boolean().optional(),\n pinAlign: px.enum([PxPinAlign.top, PxPinAlign.center, PxPinAlign.bottom] as const).optional(),\n pinOffset: px.number().optional(),\n pinDistance: px.number().optional(),\n range: PxScrollRangeSchema.optional(),\n}));\n/** @public */\nexport type PxScroll = PxInfer<typeof PxScrollSchema>;\nconst _ck_PxScroll: KeysMatch<PxScroll, _PxScroll> = true; // the key sets are identical\n\n\n// ============================================================================\n// TIMELINE — what advances the animation's progress (review §2.1)\n// ============================================================================\n//\n// `animator.timeline` is a discriminated object: `type?: 'time' | 'scroll' | 'view'`,\n// deliberately mirroring WAAPI's three timeline classes (DocumentTimeline /\n// ScrollTimeline / ViewTimeline) and CSS `animation-timeline: auto | scroll() | view()`.\n// Each mode carries ONLY its own parameters, so a key that is dead in the other mode is\n// structurally unwritable — the old flat spelling (`timelineSource` + sibling `scroll` +\n// clock knobs loose at the animator root) required design rules (D3/D4) to keep dead keys\n// out; readers accept BOTH spellings (see `flattenAnimatorTimeline`), writers emit only\n// this one.\n\n/** Pin parameters as one object — presence enables pinning (review §2.2; the flat runtime-view\n * spelling is `scroll.pin/pinAlign/pinOffset/pinDistance`). */\nexport interface _PxTimelinePin {\n /** Where the pinned canvas sits in the viewport. Default `'top'`. */\n align?: 'top' | 'center' | 'bottom';\n /** Offset from the alignment position, in px. Default 0. Named `offset`, not `top`: it is a\n * delta from whichever edge `align` picked, so `{ align: 'bottom', top: 20 }` read as a\n * contradiction (review §2.6). */\n offset?: number;\n /** How much scroll travel the pin lasts, in VIEWPORT HEIGHTS. Omit to pin inside\n * whatever tall section the host page already provides. */\n distance?: number;\n}\n\n/** @public @advanced */\nexport const PxTimelinePinSchema = implementsInterface<_PxTimelinePin>()(px.object({\n align: px.enum([PxPinAlign.top, PxPinAlign.center, PxPinAlign.bottom] as const).optional(),\n offset: px.number().optional(),\n distance: px.number().optional(),\n}));\n/** @public */\nexport type PxTimelinePin = PxInfer<typeof PxTimelinePinSchema>;\nconst _ck_PxTimelinePin: KeysMatch<PxTimelinePin, _PxTimelinePin> = true; // the key sets are identical\n\n// Time-driven — wall-clock playback: something STARTS it (trigger) and it has the\n// WAAPI playback dynamics. `type` is OPTIONAL: an absent `type` (or an absent\n// `timeline` altogether) means this one — the common case declares nothing.\n// `resetOnFinish` has no slot here: its successor is `trigger.finishAction: 'reset'`.\n/** `timeline.engine` — HOW the animated attributes get updated (every timeline type;\n * default `auto`). Not `mode`: an implementation preference, not a behavior switch. */\nconst PxTimelineEngineSchema = px.enum([PxTimelineEngineSetting.auto, PxTimelineEngineSetting.native, PxTimelineEngineSetting.js] as const).optional();\n\n/**\n * The time-driven timeline. Declared as an interface so a rename inside the schema below is a\n * COMPILE error rather than a silent wire-format change — `flattenAnimatorTimeline` reads the\n * timeline through `any`, so without this lock nothing else in the repo would notice.\n */\nexport interface _PxTimeTimeline {\n /** Optional: an absent `type` (or an absent `timeline`) already means this member. */\n type?: 'time';\n /** How the animated attributes get updated. Default `auto`. */\n engine?: PxTimelineEngineSetting;\n /** Target fps for the player's frame loop — a parameter of the `engine` chosen above, so it\n * sits beside it. Uncapped when absent; ignored by every engine except the frame loop. */\n frameRate?: number;\n /** §2.8: how long one pass takes, ms. */\n duration?: number;\n /** What starts it, and what happens when that condition ends. */\n trigger?: _PxTrigger;\n /** Wait before the first iteration, ms. Negative skips ahead. */\n delay?: number;\n /** Repeat count, or `'infinite'`. */\n iterations?: number | 'infinite';\n /** CSS `animation-fill-mode` — what shows outside the active time. NEVER spelled `fill`,\n * which is paint everywhere else in the format; the runtime view calls it `fill`. */\n fillMode?: 'forwards' | 'backwards' | 'both' | 'none';\n /** Forward, backward, or turning around each iteration. */\n direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';\n}\n\nconst PxTimeTimelineSchema = implementsInterface<_PxTimeTimeline>()(px.object({\n type: px.literal('time').optional(),\n engine: PxTimelineEngineSchema,\n frameRate: px.number().optional(),\n // §2.8: duration is a property of the TIMELINE — how long one pass takes.\n duration: px.number().optional(),\n trigger: PxTriggerSchema.optional(),\n delay: px.number().optional(),\n iterations: px.union([px.number(), px.literal('infinite')]).optional(),\n // `fillMode` on the wire (CSS `animation-fill-mode`; the runtime view calls it `fill`)\n // — never `fill`, which is paint everywhere else in the format.\n fillMode: px.enum([PxFillMode.forwards, PxFillMode.backwards, PxFillMode.both, PxFillMode.none] as const).optional(),\n direction: px.enum([PxPlaybackDirection.normal, PxPlaybackDirection.reverse, PxPlaybackDirection.alternate, PxPlaybackDirection.alternateReverse] as const).optional(),\n}));\nconst _ck_PxTimeTimeline: KeysMatch<PxInfer<typeof PxTimeTimelineSchema>, _PxTimeTimeline> = true;\n\n// Scroll-driven modes — progress scrubbed from scroll position; nothing starts or\n// finishes it, so none of the clock knobs exist here. `'scroll'` tracks a scroller's\n// scroll offset, `'view'` tracks the subject's visibility through the viewport —\n// exactly WAAPI ScrollTimeline vs ViewTimeline (the old nested `scroll.kind` dissolved\n// into this discriminant). `mode` (shared by every timeline type) says who runs the\n// animation; `pin` is boolean-or-object (§2.2).\nconst scrollishTimelineShape = {\n // §2.8: duration is a property of the TIMELINE — under scrubbing it is the keyframe\n // span the scroll range maps onto.\n duration: px.number().optional(),\n // Finite repeat count IS meaningful when scrubbing — the scroll range maps onto\n // duration × iterations (rule D4; `'infinite'` cannot map to a range, so no literal here).\n iterations: px.number().optional(),\n engine: PxTimelineEngineSchema,\n frameRate: px.number().optional(),\n axis: px.enum([PxScrollAxis.block, PxScrollAxis.inline, PxScrollAxis.x, PxScrollAxis.y] as const).optional(),\n source: px.enum([PxScrollSource.nearest, PxScrollSource.root] as const).optional(),\n subject: px.string().optional(), // 'parent' | 'scroller' | any CSS selector\n smoothing: px.number().optional(), // ms\n pin: px.union([px.boolean(), PxTimelinePinSchema]).optional(),\n range: PxScrollRangeSchema.optional(),\n};\n/** The keys both scroll-driven members carry. Locked the same way as the time member. */\nexport interface _PxScrollishTimelineShape {\n /** §2.8: under scrubbing, the keyframe span the scroll range maps onto. */\n duration?: number;\n /** Finite only — `'infinite'` cannot map onto a range (rule D4). */\n iterations?: number;\n /** How the animated attributes get updated. Default `auto`. */\n engine?: PxTimelineEngineSetting;\n /** Target fps for the player's frame loop (shared with the time member). */\n frameRate?: number;\n /** `block`/`inline` are writing-mode relative; `x`/`y` are physical. Default `'block'`. */\n axis?: 'block' | 'inline' | 'x' | 'y';\n /** `'nearest'` (default) scrollable ancestor, or `'root'`, the document. */\n source?: 'nearest' | 'root';\n /** `'parent'` · `'scroller'` · any CSS selector. */\n subject?: string;\n /** How long the playhead takes to catch up with the scrollbar, ms. */\n smoothing?: number;\n /** `true` to pin with defaults, or the parameters (review §2.2). */\n pin?: boolean | _PxTimelinePin;\n /** The slice of the timeline mapped onto progress 0..1. */\n range?: { start?: _PxScrollRangePoint; end?: _PxScrollRangePoint };\n}\n\n/** `interface X extends Y { type: 'scroll' }` — spelled as an intersection so the key-set\n * check below compares exactly the discriminant plus the shared shape. */\nexport type _PxScrollTimeline = _PxScrollishTimelineShape & { type: 'scroll' };\nexport type _PxViewTimeline = _PxScrollishTimelineShape & { type: 'view' };\n\nconst PxScrollTimelineSchema = implementsInterface<_PxScrollTimeline>()(\n px.object({ type: px.literal('scroll'), ...scrollishTimelineShape }));\nconst PxViewTimelineSchema = implementsInterface<_PxViewTimeline>()(\n px.object({ type: px.literal('view'), ...scrollishTimelineShape }));\nconst _ck_PxScrollTimeline: KeysMatch<PxInfer<typeof PxScrollTimelineSchema>, _PxScrollTimeline> = true;\nconst _ck_PxViewTimeline: KeysMatch<PxInfer<typeof PxViewTimelineSchema>, _PxViewTimeline> = true;\n\n/** @public @advanced */\nexport const PxTimelineSchema = px.discriminatedUnion('type', [\n PxTimeTimelineSchema, // first = the member an absent `type` selects\n PxScrollTimelineSchema,\n PxViewTimelineSchema,\n]);\n/** @public */\nexport type PxTimeline = PxInfer<typeof PxTimelineSchema>;\n\n\n// ============================================================================\n// ANIMATOR CONFIG\n// ============================================================================\n\n/**\n * Global animation configuration that applies to all animations in the document.\n * Defines timing, playback behavior, and rendering strategy.\n */\nexport interface _PxAnimatorConfig {\n\n /** RUNTIME VIEW ONLY (not wire — the wire spells it `timeline.engine`, on every\n * timeline type; same word both sides). How the animated attributes get updated;\n * see {@link PxTimelineEngineSetting}. */\n engine?: PxTimelineEngineSetting;\n\n /** RUNTIME VIEW ONLY (not wire — §2.8: the wire spells it `timeline.duration`).\n * Total animation duration in milliseconds. */\n duration?: number;\n\n /** Delay before animation starts in milliseconds */\n delay?: number;\n\n /**\n * Number of times to repeat the entire document timeline. Use `\"infinite\"` for endless loop.\n *\n * This repeats **all properties across all elements** as a unit. It is independent of\n * per-property `loop` configuration: if a property uses `loop`, its keyframes are already\n * expanded to fill `duration` before `iterations` takes effect — the two do not interfere,\n * but they do compose (a looping property inside an infinitely iterating document loops\n * within each iteration).\n */\n iterations?: number | \"infinite\";\n\n /** After a natural finish, snap the document back to its start state (same\n * mechanics as the trigger `reset` out-action). Off by default — the animation\n * holds its end state per `fill`. */\n resetOnFinish?: boolean;\n\n /**\n * Defines which values are applied before/after the active animation period\n * (maps directly to the Web Animations API `fill` option).\n * Defaults to `'forwards'` when not set so that elements hold their final\n * state after the animation ends — consistent with Lottie and other animation\n * runtimes. Without this default, seeking to the last frame would cause\n * elements to revert to their pre-animation state.\n */\n fill?: PxFillMode;\n\n /** Direction of animation playback */\n direction?: PxPlaybackDirection;\n\n /** RUNTIME VIEW ONLY — the wire spells it `timeline.frameRate`. Target frame rate for the\n * player's frame loop; ignored by WAAPI, React Native and the pre-rendered CSS export. */\n frameRate?: number;\n\n /** Trigger configuration for when animation should start */\n trigger?: PxTrigger;\n\n /** Named easings, animations and embedded fonts — referenced by elements and bindings */\n definitions?: PxDefinitions;\n\n /**\n * The bind-by-id document (a pre-rendered SVG + JS export, no `children`): the elements\n * already exist as markup, so instead of carrying them again the document lists WHICH\n * element plays WHICH named animations — see {@link _PxBinding}. Written by the editor's\n * exporter only; a self-contained document keeps its keyframes on the nodes (`node.animate`).\n *\n * @example [{ target: \"#_px_3cnuvau3\", animateWith: [\"a0\"] }]\n */\n bindings?: Array<PxBinding>;\n\n /**\n * RUNTIME VIEW ONLY (not wire) — what ADVANCES the animation. `'time'` (default)\n * is the wall clock; `'scroll'` is scroll-linked playback (\"scrubbing\"), matching\n * CSS scroll-driven animations.\n *\n * The wire spells this as `timeline.type` ('time' — or absent — vs 'scroll'/'view');\n * `flattenAnimatorTimeline` folds it into this field for the engines.\n * Distinct from `trigger.startOn`, which says what STARTS the animation: one\n * names the beginning, this one names what moves the playhead afterwards.\n * Design: app `svgeditor/animation/scroll-timeline.design.md`.\n */\n timelineSource?: string;\n\n /** RUNTIME VIEW ONLY (not wire) — scroll-timeline parameters, only consulted when\n * `timelineSource: 'scroll'`. The wire spells them inside `timeline`. */\n scroll?: PxScroll;\n\n /** What advances the animation — THE wire spelling (review §2.1): a discriminated\n * `{ type?: 'time' | 'scroll' | 'view', … }` object mirroring WAAPI's timeline\n * classes. Carries the playback dynamics (`trigger`/`delay`/`iterations`/`fillMode`/\n * `direction` for time; scroll geometry for scroll/view); the flat fields above\n * are the internal runtime view `flattenAnimatorTimeline` produces from it. */\n timeline?: PxTimeline;\n\n /** Debug helper: exposes the animator instance as `window[debugGlobalName]`. */\n debugGlobalName?: string;\n\n /**\n * WIRE FORMAT VERSION, `\"a.b.c\"` — the layering, not a build number:\n * `a.b` the PLAYER schema. A reader at `a.b` reads any file at `a.[b' <= b]`.\n * `c` the EDITOR's extension on top of that player schema (`meta.*`). The player\n * ignores it entirely; it is scoped to `(a,b)` and restarts when `b` moves.\n *\n * A mismatch on its own means NOTHING and must never warn: a bump says the schema gained\n * something, not that this document uses it. The number is consulted only when a\n * conversion needs it, or when unknown content was actually met — where it turns\n * \"something is wrong\" into \"written for 1.5, this build reads 1.1; update the player\".\n *\n * ABSENT means unknown, never \"oldest\": no version is assumed and no migration is guessed.\n */\n version?: string;\n}\n\n// ============================================================================\n// BINDINGS — the bind-by-id document (a pre-rendered SVG + JS export)\n// ============================================================================\n\n/**\n * One binding: WHICH element (`target`, `#id`-spelled like every element reference) plays\n * WHICH named animations (`animateWith` — names into `definitions.animations`, applied in\n * order, always an array).\n *\n * `…With` is the format's naming convention for \"by name, from `definitions`\" (review 2.12):\n * a bare key holds the thing itself (`node.animate` holds keyframes); `<key>With` holds an\n * ARRAY of names of the same concept (`animateWith` → `definitions.animations`). Two kinds of\n * reference, two spellings — `source` / `target` point at ELEMENTS (`#id`), `…With` points at\n * DEFINITIONS. Nothing else uses the convention yet; it is written down here so the next\n * key that refers to a definition by name spells it the same way.\n */\nexport interface _PxBinding {\n /** The element to animate — `'#id'`. */\n target: string;\n /** Names into `definitions.animations`, applied in order. */\n animateWith: Array<string>;\n}\n\n/** @public @advanced */\nexport const PxBindingSchema = implementsInterface<_PxBinding>()(px.object({\n target: px.string(),\n animateWith: px.array(px.string()),\n}));\n\n/** @public */\nexport type PxBinding = PxInfer<typeof PxBindingSchema>;\nconst _ck_PxBinding: KeysMatch<PxBinding, _PxBinding> = true; // the key sets are identical\n\n/**\n * RUNTIME VIEW ONLY (not wire) — a binding once `normalizeBindings` has resolved it: the\n * bare DOM id and the merged, normalized animation. A self-contained document yields the same\n * shape from every animated node, so the engines never see which kind of document they play.\n * @internal\n */\nexport interface PxNormalizedBinding {\n id: string;\n animate: PxAnimationDefinition;\n}\n\n// The WIRE format (review §2.1): playback dynamics live only inside `timeline` —\n// the flat spelling (`trigger`/`delay`/`iterations`/`fill`/`direction`/`resetOnFinish`/\n// `timelineSource`/`scroll`) is NOT part of the format. It exists only as the internal\n// runtime VIEW (`_PxAnimatorConfig`) that `flattenAnimatorTimeline` produces for the engines.\n/** @public @advanced */\nexport const PxAnimatorConfigSchema = implementsInterface<_PxAnimatorConfig>()(px.object({\n // (`mode`, `duration` and `frameRate` live INSIDE `timeline` on the wire — §2.8; they exist\n // at this level only on the runtime view, like the rest of the playback dynamics.)\n // THE spelling of \"what advances progress\" — clock / scroll / view (review §2.1).\n timeline: PxTimelineSchema.optional(),\n definitions: PxDefinitionsSchema.optional(),\n bindings: px.array(PxBindingSchema).optional(),\n debugGlobalName: px.string().optional(),\n // Declared HERE because this is a closed object: an undeclared key would be stripped by\n // `sanitize` and flagged by strict validation on our own files.\n version: px.string().optional(),\n}));\n\n/**\n * Global animation configuration that applies to all animations in the document.\n * Defines timing, playback behavior, and rendering strategy.\n *\n * This is the runtime VIEW type: the wire carries the playback dynamics nested in\n * `timeline` (see `PxAnimatorConfigSchema`), and `flattenAnimatorTimeline` folds them\n * into the flat fields the engines consume — so the type is a superset of the wire.\n * @public\n */\nexport type PxAnimatorConfig = _PxAnimatorConfig;\n\n\n\n// ============================================================================\n// NODE\n// ============================================================================\n\n/**\n * Per-attribute value shape on the element body. A property key carries either:\n * - a primitive (string/number) — static SVG attribute\n * - a number array — static number-LIST attribute (`strokeDasharray: [16, 16]`);\n * the canonical static form for list attrs (the \"5,5\" string form is also\n * accepted). Raw arrays are unambiguous — only plain OBJECTS need the\n * `{value}` wrapper.\n * - a `{value: …}` object — structured static parametric source (record-shaped\n * static value, used by attributes whose static representation is itself a\n * record — notably `transform: {value: PxTransformParts}`)\n * - a `{keyframes}` object — inline property animation\n *\n * The unified rule (primitive/array | `{value}` | `{keyframes}`) applies across\n * the format. For most attributes the `{value}` form is rarely used on the body\n * (a primitive suffices for static); for `transform` it is the canonical\n * structured-static shape. See `PxTransformValueSchema`.\n */\n/**\n * Value of an open (undeclared) attribute key on a node — i.e. a BODY attribute.\n *\n * STATIC ONLY (R2/J3): a body attr never carries its own animation. Animation goes\n * in the parallel `animate` channel, keyed by attribute name — that is what keeps a\n * document degradable to valid static SVG. `PxPropertyAnimationSchema` used to be a\n * member here, which made an inline `\"opacity\": {keyframes:[…]}` schema-LEGAL even\n * though nothing writes it and nothing reads it; worse, being an all-optional object\n * schema it was ALSO what (accidentally) validated the transform parts record. The\n * parts record is now declared explicitly, so the two are no longer conflated.\n * @public @advanced\n */\nexport const PxAttrValueSchema = px.union([\n px.string(),\n px.number(),\n px.array(px.number()),\n // Structured static — `{value: …}` (read-accepted transitional spelling, S1).\n // `defined`, not `any`: the KEY's presence is what identifies this branch (V6).\n px.object({ value: px.defined() }),\n // Bare transform parts record — the canonical static `transform` on the wire (T2).\n PxTransformPartsSchema,\n]);\n\n/** Per-attribute value: primitive/number-array for static, `{value}` for structured\n * static, or a bare transform parts record. NEVER an animation — see `animate` (R2). * @public\n */\nexport type PxAttrValue = string | number | Array<number> | { value: any } | PxTransformParts;\n\n\n/**\n * Base interface for all SVG elements.\n * Named properties take precedence over the index signature when accessed.\n */\nexport interface _PxNode {\n\n /** SVG element type (e.g., \"circle\", \"rect\", \"path\", \"g\") */\n type: string;\n\n /** A REAL `type` attribute, for the elements that have one (`<feTurbulence\n * type=\"fractalNoise\">`, `<feFuncR type=\"table\">`) — `type` itself is the tag name.\n * The renderer turns this back into the attribute. */\n domType?: string;\n\n /** Text content of a `<text>` / `<tspan>` — the one text-content key (the DOM property name). */\n textContent?: string;\n\n /** Child elements (for container elements like <g>) */\n children?: PxNode[];\n\n /** Meta informaion about this element */\n meta?: any;\n\n /**\n * Player-effects bucket (transformation/repeater/maskedBy/strokeTrim/retime/ref)\n * emitted by the Editor's lightweight design format. `materializeNodeEffects`\n * materializes and removes these before any other normalization, so the\n * Player never observes a non-empty `effects` after entry-point processing.\n *\n * Typed against `PxEffectsSchema` (closed object — strict-mode validation\n * flags unknown effect keys). Adding a new effect requires extending the\n * `_PxEffects` interface AND the schema in lockstep.\n */\n effects?: PxEffects;\n\n /**\n * In-place property animations for this element. Same shape as the\n * `node.animate` values: string ref, array of refs, inline\n * definition (`{propName: PxPropertyAnimation}`), or mixed array.\n * The static initial value of an animated property is still carried as a\n * plain attribute on the body.\n */\n animate?: PxElementAnimation;\n\n /**\n * Inline style declarations for this element — camelCase CSS property names, exactly like\n * React's `style` prop (`whiteSpace`, `pointerEvents`, `mixBlendMode`) → value. The web\n * player writes them to `element.style`, React Native applies them as props; an explicit\n * attribute on the node wins. An OBJECT only: no CSS text, no preset names\n * (`definitions.styles` was removed — review 2.13).\n */\n style?: Record<string, string | number>;\n\n /**\n * Every other key is an SVG attribute under its camelCase DOM name — the spelling React\n * uses (`strokeWidth`, `fontSize`, `viewBox`, `clipPath`), which is what the editor writes.\n * The player renders it to the standard attribute (`stroke-width`) and accepts that kebab\n * spelling on the way in. A value is either a primitive (static) or a PxPropertyAnimation\n * (in-place animation `{ keyframes: [...] }`).\n */\n [camelCaseDomKey: string]: any;\n}\n\n// ============================================================================\n// PLAYER-EFFECTS BUCKET — INTERFACES + SCHEMAS (linked via `implementsInterface`)\n// ============================================================================\n//\n// Schemas for the `node.effects` payload emitted by the Editor's lightweight\n// design format. `materializeNodeEffects` (in `effects/PlayerEffectsUtil.ts`)\n// materializes and removes these before any other normalization, so the Player\n// never observes a non-empty `effects` after entry-point processing.\n//\n// Each effect is declared as a `_Px*` interface, paired with a `Px*Schema`\n// wrapped in `implementsInterface<_Px*>()(…)`. The runtime schema and the\n// compile-time interface drift together: a new field added to either without\n// matching the other is a TS error. `KeysMatch` asserts key-set equality so\n// renames are caught too. This is the same pattern used by `PxKeyframe`,\n// `PxLoop`, etc. earlier in this file.\n//\n// `effects/types.ts` re-exports these types so the applier internals\n// (`effects/*.ts`) can still `import from './types'` unchanged.\n\n/** Fixed-length 2-number tuple. `[x, y]` for positions, `[sx, sy]` for scale, …. @public */\nexport type PxVec2 = [number, number];\n\n/**\n * Animatable wire value — the ONE grammar for every animatable slot:\n *\n * T — raw static (non-object T)\n * { value: T } — structured static\n * PxPropertyAnimation — animated: `{value?, keyframes, loop?, autoOrient?}`\n *\n * The animated form IS `PxPropertyAnimation` — the exact object `node.animate`\n * channels use — so effect slots and node attributes share one schema, one\n * reader (`effects/transformParts.readAnimatable`) and one loop-materialization\n * path. `value` inside the animated form is the optional static baseline (see\n * `_PxPropertyAnimation.value`).\n *\n * Generic over the per-kf value type `T` for compile-time narrowing of the\n * static / `{value}` forms. The animated form uses the lib's non-generic\n * `PxKeyframe` (whose `value` is `any`) — kf values are read with care in the\n * applier (the visualModel walker / `interpParts` know per-property shapes).\n * @internal\n */\nexport type PxAnimatable<T> = T | { value: T } | _PxPropertyAnimation;\n\n// ORDER LAW (same medicine as PxKeyframeValueSchema): in every animatable union the\n// PropertyAnimation member comes BEFORE the bare `{value}` wrapper. Union.sanitize takes\n// the first member that validates, and default-mode object validation tolerates unknown\n// keys — wrapper-first would route `{value, keyframes}` to the wrapper and silently strip\n// the keyframes. A `{value}`-only static hitting PropertyAnimation first loses nothing\n// (it declares `value` too). Validity is order-independent; only repair cares.\n\n// PxAnimatable<number> — static number OR `{value}` static OR PxPropertyAnimation.\nconst PxAnimatableNumberSchema = px.union([\n px.number(),\n PxPropertyAnimationSchema,\n px.object({ value: px.number() }),\n]);\n\n// PxAnimatable<PxVec2> — static `[x,y]` OR `{value:[x,y]}` OR PxPropertyAnimation.\n// `as const` on the tuples is REQUIRED for TS to infer `[number, number]` (a\n// fixed-length tuple = `PxVec2`) instead of the looser `number[]`.\nconst PxAnimatableVec2Schema = px.union([\n px.tuple([px.number(), px.number()] as const),\n PxPropertyAnimationSchema,\n px.object({ value: px.tuple([px.number(), px.number()] as const) }),\n]);\n\n// PxAnimatable<string> — static `\"M…\"` OR `{value:\"M…\"}` OR PxPropertyAnimation.\nconst PxAnimatableStringSchema = px.union([\n px.string(),\n PxPropertyAnimationSchema,\n px.object({ value: px.string() }),\n]);\n\n// ── CHANNEL vs CONFIG (V2) — the split is declared in the SOURCE, twice over ──\n// An effect slot is one of exactly two kinds, and both declarations must agree:\n// channel (samplable per frame) → interface `PxAnimatable<T>` + a named\n// `PxAnimatable*Schema` in the schema\n// static config (read once) → the bare type + a bare `px.*()` slot\n// Never hand-inline the `[T, {value:T}, PxPropertyAnimation]` union at a slot:\n// the NAME is what makes the split machine-readable. Editor side mirrors this\n// with `isAnimatable: true` on the value's config.\n\n\n/** Per-part editor transform (`transformBy` effect). All parts optional and animatable. */\nexport interface _PxTransformByEffect {\n translate?: PxAnimatable<PxVec2>;\n rotate?: PxAnimatable<number>;\n scale?: PxAnimatable<PxVec2>;\n /** Skew (skewX) in degrees — a NUMBER (matches the editor's scalar skew part). */\n skew?: PxAnimatable<number>;\n origin?: PxAnimatable<PxVec2>;\n}\n/** @public @advanced */\nexport const PxTransformByEffectSchema = implementsInterface<_PxTransformByEffect>()(px.object({\n translate: PxAnimatableVec2Schema.optional(),\n rotate: PxAnimatableNumberSchema.optional(),\n scale: PxAnimatableVec2Schema.optional(),\n skew: PxAnimatableNumberSchema.optional(),\n origin: PxAnimatableVec2Schema.optional(),\n}));\n/** @public */\nexport type PxTransformByEffect = PxInfer<typeof PxTransformByEffectSchema>;\nconst _ck_PxTransformByEffect: KeysMatch<PxTransformByEffect, _PxTransformByEffect> = true;\n\n\n/** Per-copy repeater offsets. Each part is animatable; per-copy values scale\n * with the copy index `i` (translate/rotate/skew × i; scale per-axis `v^i`).\n * Static repeater values pass through as a structured `transform: {value:…}` on\n * the per-copy wrapper; animated values are emitted as `animate.transform.keyframes`\n * with each kf value scaled by `i`. See `effects/repeaterEffect.ts`.\n *\n * NAMING — why `repeater`, NOT `repeat` (SCHEMA-DESIGN R5 / issues N6): this\n * effect repeats in SPACE (N copies, each with a compounding per-copy delta), but\n * in an ANIMATION format a bare `repeat` reads as TIME — and this format has real\n * time-repetition concepts for it to be confused with: `animator.iterations`,\n * per-property `loop {segmentCount, alternate}`, and SVG/SMIL's own\n * `repeatCount`/`repeatDur`. The agent noun keeps it unambiguously spatial, and\n * matches the term the audience already knows (After Effects \"Repeater\",\n * Lottie shape item `rp`). Same principle as `maskedBy` over `mask`: prefer the\n * form that preserves the right MEANING over the grammatically uniform one. */\nexport interface _PxRepeaterEffect {\n copies?: number;\n translate?: PxAnimatable<PxVec2>;\n rotate?: PxAnimatable<number>;\n /** Per-copy skew (skewX) increment in degrees — copy `i` is skewed by `skew × i`. */\n skew?: PxAnimatable<number>;\n scale?: PxAnimatable<PxVec2>; // per-copy FACTOR (0.85 = 85% per copy), like every other scale\n origin?: PxAnimatable<PxVec2>;\n}\n/** @public @advanced */\nexport const PxRepeaterEffectSchema = implementsInterface<_PxRepeaterEffect>()(px.object({\n // STATIC config, not a channel (V2/SCHEMA-DESIGN R5): the copy COUNT is read\n // once at expansion time and never sampled — plain number, no `keyframes`.\n copies: px.number().optional(),\n translate: PxAnimatableVec2Schema.optional(),\n rotate: PxAnimatableNumberSchema.optional(),\n skew: PxAnimatableNumberSchema.optional(),\n scale: PxAnimatableVec2Schema.optional(),\n origin: PxAnimatableVec2Schema.optional(),\n}));\n/** @public */\nexport type PxRepeaterEffect = PxInfer<typeof PxRepeaterEffectSchema>;\nconst _ck_PxRepeaterEffect: KeysMatch<PxRepeaterEffect, _PxRepeaterEffect> = true;\n\n\n/** Mask source ref + standard `<mask>` attributes.\n * `source` is `#id` (canonical ref spelling, SCHEMA-DESIGN §4 E-5); bare `id` is legacy, read-only.\n * `start`/`size` are the `<mask>` viewport — its `x`/`y` and `width`/`height` in\n * `maskUnits` space. Absent = SVG's implicit mask region (−10% … 120% of the\n * bounding box), which is also the editor's default — so they only appear when a\n * document (typically an imported SVG) carries explicit mask bounds. */\nexport interface _PxMaskedByEffect {\n source?: string;\n maskType?: string;\n maskUnits?: string;\n maskContentUnits?: string;\n // Mask viewport in `maskUnits` space — the SVG `<mask>` attrs verbatim (B5).\n // NOT `start`/`size` pairs: those were the EDITOR's model FIELD names, never a\n // wire spelling — the editor has always written these four scalars, so the old\n // pair declaration meant the player silently dropped every non-default viewport.\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n}\n/** @public @advanced */\nexport const PxMaskedByEffectSchema = implementsInterface<_PxMaskedByEffect>()(px.object({\n source: px.string().optional(),\n maskType: px.enum([PxMaskType.luminance, PxMaskType.alpha] as const).optional(),\n maskUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox] as const).optional(),\n maskContentUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox] as const).optional(),\n x: px.number().optional(),\n y: px.number().optional(),\n width: px.number().optional(),\n height: px.number().optional(),\n}));\n/** @public */\nexport type PxMaskedByEffect = PxInfer<typeof PxMaskedByEffectSchema>;\nconst _ck_PxMaskedByEffect: KeysMatch<PxMaskedByEffect, _PxMaskedByEffect> = true;\n\n\n/**\n * Clip-path effect — clips the host element to a vector path. `pathData` is a standard\n * animatable slot (same grammar as the body `d` ATTRIBUTE, which keeps SVG's own name):\n * static = plain SVG path-data string (one or more subpaths); animated = `{keyframes}`\n * whose values are `{pathData:\"M…\"}`.\n * At apply time an animated slot lands on the generated `<path>`'s `animate.d`, so the\n * player's frame loop rewrites the clip path's `d` attribute per frame. `clip-path`\n * is a live reference, so the browser re-clips each frame (unlike `<marker>` —\n * verified across SMIL/CSS/JS/WAAPI).\n *\n * At apply time the effect generates a `<clipPath><path d/></clipPath>` def and sets\n * `clip-path=\"url(#auto-id)\"` on the host (materializer pattern, like `maskedBy` /\n * gradient). See `effects/clipPathEffect.ts`.\n */\nexport interface _PxClipPathEffect {\n pathData?: PxAnimatable<string>;\n}\n/** @public @advanced */\nexport const PxClipPathEffectSchema = implementsInterface<_PxClipPathEffect>()(px.object({\n pathData: PxAnimatableStringSchema.optional(),\n}));\nexport type PxClipPathEffect = PxInfer<typeof PxClipPathEffectSchema>;\nconst _ck_PxClipPathEffect: KeysMatch<PxClipPathEffect, _PxClipPathEffect> = true;\n\n\n/**\n * Stroke-trim effect. `range[0..1]` is the visible fraction of the STROKE; `offset`\n * shifts the visible window along the path (also a fraction). Both are animatable.\n * `subPaths` says what that fraction is measured over: `separate` (default) trims\n * each sub-path against its own length; `combined` chains all descendant sub-path\n * lengths into one virtual path (\"Trim All As One\") so the window slides across\n * siblings — see `effects/strokeTrimEffect.ts`.\n *\n * NAME — renamed from `trimPath` (2026-08, hard rename, no legacy alias): this\n * trims the STROKE only. It emits `stroke-dasharray` / `stroke-dashoffset` (plus\n * `stroke-opacity` for the empty-range hide) and NEVER rewrites `d`, so the fill\n * is untouched. Lottie's same-named `ty:'tm'` is a path OPERATOR that rewrites\n * geometry (and therefore does change the fill) — the old name imported that\n * wrong mental model from the format most authors convert from.\n */\nexport interface _PxStrokeTrimEffect {\n offset?: PxAnimatable<number>;\n range?: PxAnimatable<PxVec2>;\n subPaths?: PxStrokeTrimSubPaths;\n}\n/** @public @advanced */\nexport const PxStrokeTrimEffectSchema = implementsInterface<_PxStrokeTrimEffect>()(px.object({\n offset: PxAnimatableNumberSchema.optional(),\n range: PxAnimatableVec2Schema.optional(),\n subPaths: px.enum([PxStrokeTrimSubPaths.separate, PxStrokeTrimSubPaths.combined] as const).optional(),\n}));\n/** @public */\nexport type PxStrokeTrimEffect = PxInfer<typeof PxStrokeTrimEffectSchema>;\nconst _ck_PxStrokeTrimEffect: KeysMatch<PxStrokeTrimEffect, _PxStrokeTrimEffect> = true;\n\n\n/** Ref-attr naming rule (see editor dev-docs/schema-design.md): `source` = ref to an EXTERNAL element\n * (clone/maskedBy/retime); `coreId` = a unit's own survivor; `partOf` = a derived node's host.\n *\n * `<use>` retime: pure timing — the source ref lives ONCE, on the parent `clone.source`\n * (review §4.3; retime's own duplicate `source` was removed outright — no consumer ever\n * read it: the materializer follows `href`). `start`/`timeCrop` in ms.\n * `timeCrop: [inMs, outMs]` is a VISIBILITY WINDOW on the document timeline — implemented\n * (2026-08) as an opacity gate on a player-side wrapper `<g>`, independent of the\n * `start`/`stretch` remap (see `effects/retimeEffect.ts`). */\nexport interface _PxRetimeEffect {\n start?: number;\n stretch?: number;\n timeCrop?: [number, number];\n}\n/** @public @advanced */\nexport const PxRetimeEffectSchema = implementsInterface<_PxRetimeEffect>()(px.object({\n start: px.number().optional(),\n stretch: px.number().optional(),\n timeCrop: px.tuple([px.number(), px.number()] as const).optional(),\n}));\n/** @public */\nexport type PxRetimeEffect = PxInfer<typeof PxRetimeEffectSchema>;\nconst _ck_PxRetimeEffect: KeysMatch<PxRetimeEffect, _PxRetimeEffect> = true;\n\n\n/**\n * `<use>` CLONE — merges the former `ref` + `retime` effects. A `<use>` is a clone\n * of something: `type`/`source` say WHAT it clones, `retime` says WHEN.\n * - `without: 'translate'` → content-ref: the source's own translate is left out (the\n * clone stays where the `<use>` put it, still rotates/scales with the source);\n * absent → direct / whole-element link (keeps translate). A future `'transform'`\n * value may leave out the whole transform.\n * - `source` = the source element ref, `#id` (canonical spelling, SCHEMA-DESIGN §4 E-5;\n * bare `id` is legacy, read-only). Lives once here; the player follows `href`.\n * - `retime` = optional time-shift (nested).\n * Omitted entirely when all-default (a bare `<use href>` carries no `clone` bucket).\n */\nexport interface _PxCloneEffect {\n without?: string;\n source?: string;\n retime?: _PxRetimeEffect;\n}\n/** @public @advanced */\nexport const PxCloneEffectSchema = implementsInterface<_PxCloneEffect>()(px.object({\n // Subtractive on purpose: the `<use>` can only point at one wrapper layer of the\n // source, so the choices form a ladder — 'translate' now, maybe 'transform' later.\n without: px.enum([PxCloneWithout.translate] as const).optional(),\n source: px.string().optional(),\n retime: PxRetimeEffectSchema.optional(),\n}));\n/** @public */\nexport type PxCloneEffect = PxInfer<typeof PxCloneEffectSchema>;\nconst _ck_PxCloneEffect: KeysMatch<PxCloneEffect, _PxCloneEffect> = true;\n\n\n/** A single color stop. `offset` is in `[0, 1]`; `color` is a CSS color\n * string (`#rrggbb`, `rgb(…)`, `rgba(…)`, or named). */\nexport interface _PxGradientStop {\n offset: number;\n color: string;\n}\n/** @public @advanced */\nexport const PxGradientStopSchema = implementsInterface<_PxGradientStop>()(px.object({\n offset: px.number(),\n color: px.string(),\n}));\n/** @public */\nexport type PxGradientStop = PxInfer<typeof PxGradientStopSchema>;\nconst _ck_PxGradientStop: KeysMatch<PxGradientStop, _PxGradientStop> = true;\n\n/** `PxAnimatable<Array<PxGradientStop>>` schema. Static is the bare array;\n * `{value: […]}` wraps the same; the animated form is `PxPropertyAnimation`\n * (one timeline whose each kf's `value` is the FULL stops array at that time).\n * LAW (SCHEMA-DESIGN R5, S9): stops are ONE animatable value — whole-array\n * snapshots on a single timeline, deliberately NO per-stop keyframes/easing\n * (gradient GEOMETRY animates per-slot with independent timelines). */\nconst PxAnimatableGradientStopsSchema = px.union([\n px.array(PxGradientStopSchema),\n px.object({ value: px.array(PxGradientStopSchema) }),\n PxPropertyAnimationSchema,\n]);\n\n/** Gradient paint effect — used by both `fillGradient` and `strokeGradient`\n * (same shape, different host attribute). Linear: `start`/`end`. Radial:\n * `center`/`radius`/`focal`. Stops animate as one timeline; geometry stays static. */\n// (The old `_PxGradientGeometryAnimation` per-scalar channel record —\n// `animate: {gradientX1: …}` — was REMOVED outright, read included: geometry\n// animates on the `start`/`end`/`center`/`radius`/`focal` slots. Backward compat dropped\n// deliberately. Frames-engine-only note still applies to animated geometry:\n// CSS/WAAPI cannot animate gradient endpoints; `mode: 'auto'` handles it.)\n\nexport interface _PxFillGradientEffect {\n type: PxGradientType; // 'linear' | 'radial'\n start?: PxAnimatable<PxVec2>; // linear start ([x1,y1]; review §4.2 — plain words, no abbreviations)\n end?: PxAnimatable<PxVec2>; // linear end ([x2,y2])\n center?: PxAnimatable<PxVec2>; // radial center ([cx,cy])\n radius?: PxAnimatable<number>; // radial radius (r)\n focal?: PxAnimatable<PxVec2>; // radial focal point ([fx,fy])\n stops?: PxAnimatable<Array<_PxGradientStop>>; // single animation timeline\n gradientUnits?: string; // PxUnits values\n spreadMethod?: string; // PxGradientSpreadMethod values\n gradientTransform?: string; // static only in v1\n}\n/** @public @advanced */\nexport const PxFillGradientEffectSchema = implementsInterface<_PxFillGradientEffect>()(px.object({\n // Contextual kind — the `type` convention, see `PxNodeBaseSchema.type`.\n type: px.enum([PxGradientType.linear, PxGradientType.radial] as const),\n start: PxAnimatableVec2Schema.optional(),\n end: PxAnimatableVec2Schema.optional(),\n center: PxAnimatableVec2Schema.optional(),\n radius: PxAnimatableNumberSchema.optional(),\n focal: PxAnimatableVec2Schema.optional(),\n stops: PxAnimatableGradientStopsSchema.optional(),\n gradientUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox] as const).optional(),\n spreadMethod: px.enum([PxGradientSpreadMethod.pad, PxGradientSpreadMethod.reflect, PxGradientSpreadMethod.repeat] as const).optional(),\n gradientTransform: px.string().optional(),\n}));\n/** @public */\nexport type PxFillGradientEffect = PxInfer<typeof PxFillGradientEffectSchema>;\nconst _ck_PxFillGradientEffect: KeysMatch<PxFillGradientEffect, _PxFillGradientEffect> = true;\n\n/** Stroke gradient is the same shape as fill gradient; the difference is\n * only which host attribute (`fill` vs `stroke`) the applier rewrites. */\nexport type _PxStrokeGradientEffect = _PxFillGradientEffect;\n/** @public @advanced */\nexport const PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;\n/** @public */\nexport type PxStrokeGradientEffect = PxFillGradientEffect;\n\n/** Text-path effect on a `<text>` host. The path geometry is carried INLINE as\n * `pathData` (an SVG `d`; static for now, keyframed animation is a later step) — the\n * applier generates a `<path>` def from it and wraps the text's children in a native\n * `<textPath href=\"#…\">` at apply time. All SVG-native textPath attrs\n * (`lengthAdjust`, `method`, `spacing`, `startOffset`, `textLength`) ride on this\n * effect; `startOffset`/`textLength` accept the full `PxAnimatable<number>` shape.\n *\n * `pathOverflow` controls what happens to glyphs past the end of an OPEN path:\n * - `'extend'` (default): glyphs continue straight along the endpoint tangent\n * (Lottie / native-glyph behavior).\n * - `'clip'`: glyphs past the end disappear (native `<textPath>` behavior). */\nexport interface _PxTextPathEffect {\n pathData: string; // inline SVG `d`\n pathOverflow?: string; // 'clip' | 'extend' (default 'extend')\n lengthAdjust?: string; // 'spacing' | 'spacingAndGlyphs'\n method?: string; // 'align' | 'stretch'\n spacing?: string; // 'auto' | 'exact'\n startOffset?: PxAnimatable<number>;\n textLength?: PxAnimatable<number>;\n}\n/** @public @advanced */\nexport const PxTextPathEffectSchema = implementsInterface<_PxTextPathEffect>()(px.object({\n pathData: px.string(),\n pathOverflow: px.enum([PxPathOverflow.clip, PxPathOverflow.extend] as const).optional(),\n lengthAdjust: px.enum([PxLengthAdjust.spacing, PxLengthAdjust.spacingAndGlyphs] as const).optional(),\n method: px.enum([PxTextPathMethod.align, PxTextPathMethod.stretch] as const).optional(),\n spacing: px.enum([PxTextPathSpacing.auto, PxTextPathSpacing.exact] as const).optional(),\n startOffset: PxAnimatableNumberSchema.optional(),\n textLength: PxAnimatableNumberSchema.optional(),\n}));\n/** @public */\nexport type PxTextPathEffect = PxInfer<typeof PxTextPathEffectSchema>;\nconst _ck_PxTextPathEffect: KeysMatch<PxTextPathEffect, _PxTextPathEffect> = true;\n\n\n/**\n * `effects.text` — text-rendering options for a `<text>` node.\n *\n * `useGlyphs: true` tells the player to render this text from the embedded\n * per-glyph outlines in `definitions.fonts` (self-contained, no external\n * font) instead of a native `<text>`. See svga.text.design.md.\n */\nexport interface _PxTextEffect {\n useGlyphs?: boolean;\n}\n/** @public @advanced */\nexport const PxTextEffectSchema = implementsInterface<_PxTextEffect>()(px.object({\n useGlyphs: px.boolean().optional(),\n}));\nexport type PxTextEffect = PxInfer<typeof PxTextEffectSchema>;\nconst _ck_PxTextEffect: KeysMatch<PxTextEffect, _PxTextEffect> = true;\n\n\n/**\n * The full `node.effects` bucket. Closed — each known effect is declared\n * (strict-mode validation flags an unknown effect key as a wire-format drift).\n *\n * DESIGN LAW — attribute vs effect:\n * - An ATTRIBUTE is a value the browser consumes as-is on that element\n * (`fill=\"#f00\"`, `opacity`, `d`); animating it is \"this value over time\" —\n * one channel, zero structure. The test is STRUCTURE, not value-encoding\n * complexity: `transform` has a parts-record wire value but lands in one\n * attribute on the same element, so it stays an attribute.\n * - An EFFECT is anything whose realization requires structure — generating defs\n * (gradient, clipPath, maskedBy, textPath), wrapper nodes (transformation),\n * clones (repeater, clone), or geometry-derived multi-attr rewrites (strokeTrim).\n * - The same attribute name can sit on both sides, split by value: flat `fill`\n * is an attribute; gradient fill is an effect (no value of `fill` IS a\n * gradient — it needs a def + stops + a `url(#id)` indirection).\n * New features follow the same test: pattern fills / filters need defs → effects.\n *\n * COMPOSITION ORDER (SCHEMA-DESIGN §R5): one bag per element — JSON key order\n * carries NO meaning and is never read. The applier composes in one hard-coded\n * order, innermost → outermost:\n * glyphs/textPath → fill/strokeGradient → strokeTrim → repeater → maskedBy\n * → clipPath → clone-href+transformBy (retime = pass 2, time-remap only)\n * \"Other\" orders are expressed by STRUCTURE (nest elements), never by key order.\n * If authorable order is ever demanded: an explicit `effects.order: [names]`\n * extension — never key-order significance (JSON tooling silently reorders).\n */\nexport interface _PxEffects {\n transformBy?: _PxTransformByEffect;\n repeater?: _PxRepeaterEffect;\n maskedBy?: _PxMaskedByEffect;\n clipPath?: _PxClipPathEffect;\n strokeTrim?: _PxStrokeTrimEffect;\n clone?: _PxCloneEffect;\n fillGradient?: _PxFillGradientEffect;\n strokeGradient?: _PxStrokeGradientEffect;\n textPath?: _PxTextPathEffect;\n text?: _PxTextEffect;\n}\n/** @public @advanced */\nexport const PxEffectsSchema = implementsInterface<_PxEffects>()(px.object({\n transformBy: PxTransformByEffectSchema.optional(),\n repeater: PxRepeaterEffectSchema.optional(),\n maskedBy: PxMaskedByEffectSchema.optional(),\n clipPath: PxClipPathEffectSchema.optional(),\n strokeTrim: PxStrokeTrimEffectSchema.optional(),\n clone: PxCloneEffectSchema.optional(),\n fillGradient: PxFillGradientEffectSchema.optional(),\n strokeGradient: PxStrokeGradientEffectSchema.optional(),\n textPath: PxTextPathEffectSchema.optional(),\n text: PxTextEffectSchema.optional(),\n}));\n/** @public */\nexport type PxEffects = PxInfer<typeof PxEffectsSchema>;\nconst _ck_PxEffects: KeysMatch<PxEffects, _PxEffects> = true;\n\n/**\n * Walks `root` and validates every `node.effects` bucket against `PxEffectsSchema`.\n * Returns an array of human-readable warning strings (empty when all good).\n * Doesn't mutate the tree. Called by `createAnimatorImpl` before applying effects.\n *\n * Pass `strict: true` to also flag undeclared keys (useful in dev / tests).\n * @public @advanced\n */\nexport function validateNodeEffects(root: PxNode, options?: { strict?: boolean }): Array<string> {\n const warnings: Array<string> = [];\n // `path` is a human-readable breadcrumb prepended to each warning so the\n // reader can locate the offending node in the tree (e.g.\n // `root.children[0].children[2].effects.transformBy.translate: …`).\n const walk = (node: PxNode, path: string): void => {\n if (node && node.effects) {\n const ctx: PxValidationContext = { errors: [], warnings: [], strict: !!options?.strict };\n const ok = PxEffectsSchema.isValid(node.effects, ctx, [path + '.effects']);\n if (!ok) {\n for (const err of ctx.errors) warnings.push(err);\n }\n }\n if (node && Array.isArray(node.children)) {\n node.children.forEach((c, i) => walk(c, path + '.children[' + i + ']'));\n }\n };\n walk(root, 'root');\n return warnings;\n}\n\n/**\n * Cross-checks glyph-mode text against the embedded faces (review §2.5).\n *\n * A `definitions.fonts` key IS the face name, matched against the node's `font-family`\n * verbatim — so a name with no entry renders a row of □ placeholder boxes behind nothing but\n * a console warning. Nothing else catches that: the schema validates each side's SHAPE, never\n * that the two agree.\n *\n * Deliberately silent in two legal cases:\n * - the document embeds NO faces — browser-font text, a different situation entirely;\n * - a node carries no `font-family` — with exactly one face embedded the player resolves it\n * (`soleFont`), and with none there is nothing to name.\n *\n * The reverse (a face nothing references) is NOT reported: keeping the outlines of a text\n * whose glyph mode is currently off is legal and deliberate, so that toggling it back on\n * needs no font reload.\n */\nexport function validateGlyphFontRefs(root: PxNode, fonts: { [face: string]: unknown; } | undefined): Array<string> {\n if (!fonts || !Object.keys(fonts).length) return [];\n\n const problems: Array<string> = [];\n const walk = (node: PxNode, path: string, inherited: string | undefined, inGlyphText: boolean): void => {\n if (!node) return;\n // `font-family` inherits down the text tree, exactly as the renderer resolves it.\n const own = typeof node.fontFamily === 'string' ? node.fontFamily : undefined;\n const family = own ?? inherited;\n const isGlyphText = inGlyphText || (node.type === 'text' && !!node.effects?.text?.useGlyphs);\n\n // Report at the node that DECLARES the family — one problem per mistake, not one per\n // descendant that merely inherits it.\n if (isGlyphText && own && !Object.prototype.hasOwnProperty.call(fonts, own)) {\n const problem = path + ': glyph-mode text uses font-family \"' + own\n + '\", which has no entry in animator.definitions.fonts';\n if (!problems.includes(problem)) problems.push(problem);\n }\n if (Array.isArray(node.children)) {\n node.children.forEach((c, i) => walk(c, path + '.children[' + i + ']', family, isGlyphText));\n }\n };\n walk(root, 'root', undefined, false);\n return problems;\n}\n\n/**\n * Cross-checks named easing references against `definitions.easings` (review §2.9).\n *\n * A keyframe's `easing` is either a cubic-bezier array or the NAME of an entry in\n * `definitions.easings` — CSS keywords are deliberately not built in (player weight), so\n * `easing: \"ease-in-out\"` validates as a string, resolves to nothing, and plays LINEAR behind\n * one `console.warn`. That makes it the likeliest silent mistake in a generated document, and\n * the schema cannot catch it: it checks the shape of each side, never that the two agree.\n *\n * Only the wire spelling `easing` is read. The runtime view's `e` is an already-RESOLVED curve,\n * never a name, so it has nothing to cross-check.\n */\nexport function validateEasingRefs(root: PxNode, easings: { [name: string]: unknown; } | undefined): Array<string> {\n const problems: Array<string> = [];\n const walk = (node: unknown, path: string): void => {\n if (Array.isArray(node)) {\n node.forEach((item, i) => walk(item, path + '[' + i + ']'));\n return;\n }\n if (!node || typeof node !== 'object') return;\n\n const easing = (node as { easing?: unknown }).easing;\n if (typeof easing === 'string' && !(easings && Object.prototype.hasOwnProperty.call(easings, easing))) {\n const problem = path + '.easing: \"' + easing\n + '\" names no entry in animator.definitions.easings — it will play linear';\n if (!problems.includes(problem)) problems.push(problem);\n }\n for (const [key, value] of Object.entries(node as Record<string, unknown>)) {\n if (key === 'easing') continue; // already handled; a string has nothing to walk\n if (value && typeof value === 'object') walk(value, path + '.' + key);\n }\n };\n walk(root, 'root');\n return problems;\n}\n\n/**\n * Checks the `animator.version` stamp parses (review §2.10).\n *\n * The slot is `px.string()`, so `\"v1\"` validates and is then read as UNSTAMPED — the document\n * silently loses the one diagnostic that says which schema wrote it. Parsing is delegated to\n * {@link parseWireVersion} so this can never disagree with the reader.\n *\n * An ABSENT stamp is legal and silent: only a present-but-unparseable one is reported.\n */\nexport function validateVersionStamp(doc: PxAnimatedSvgDocument): Array<string> {\n const version = getAnimatorConfig(doc)?.version;\n if (version === undefined) return [];\n if (parseWireVersion(version) !== undefined) return [];\n return ['root.animator.version: ' + JSON.stringify(version)\n + ' is not a version stamp (\"a.b\" or \"a.b.c\") — it reads as unstamped'];\n}\n\n/**\n * Validates a WHOLE document against the wire schema — strictly, so undeclared keys are\n * reported too — plus every node's `effects` bucket and the glyph-font references. Returns\n * human-readable problems (`path: what is wrong`), empty when the document is sound; never\n * throws. The player itself only warns and skips what it cannot read; this is the one call\n * for tooling, CI and agents that want a yes/no answer before shipping a document.\n * @public\n */\nexport function validateDocument(doc: unknown, options?: { strict?: boolean }): Array<string> {\n // Strict (the default) rejects keys the schema does not declare — the right answer for a\n // document you are about to ship. `strict: false` tolerates them, which is what a READER\n // wants: an unknown key usually means a newer writer — worth a warning, never a refusal.\n const strict = options?.strict !== false;\n const ctx: PxValidationContext = { errors: [], warnings: [], strict };\n const problems: Array<string> = PxAnimatedSvgDocumentSchema.isValid(doc, ctx, ['root']) ? [] : [...ctx.errors];\n if (doc && typeof doc === 'object') {\n for (const w of validateNodeEffects(doc as PxNode, { strict })) {\n if (!problems.includes(w)) problems.push(w);\n }\n const defs = getAnimatorConfig(doc as PxAnimatedSvgDocument)?.definitions;\n for (const w of validateGlyphFontRefs(doc as PxNode, defs?.fonts)) {\n if (!problems.includes(w)) problems.push(w);\n }\n for (const w of validateEasingRefs(doc as PxNode, defs?.easings)) {\n if (!problems.includes(w)) problems.push(w);\n }\n for (const w of validateVersionStamp(doc as PxAnimatedSvgDocument)) {\n if (!problems.includes(w)) problems.push(w);\n }\n }\n return problems;\n}\n\n\n// ============================================================================\n// NODE\n// ============================================================================\n\n/**\n * Base shape for all SVG element nodes.\n * Open object: validated known keys + arbitrary SVG attributes whose values are\n * either primitives (static) or PxPropertyAnimation objects (in-place animation).\n * Non-recursive — excludes `children` (circular reference). Used for type extraction via PxInfer.\n *\n * `{ type:string, style?:…, [key:string]: string|number|PxPropertyAnimation }`\n * @public @advanced\n */\nexport const PxNodeBaseSchema = px.openObject({\n // CONVENTION (SCHEMA-DESIGN R1 / issues N4): `type` is the ONE word for \"what\n // kind of thing is this\", discriminated by its CARRIER — here the node TAG\n // (`rect`, `text`), and inside a sub-object that object's kind (`fillGradient.type`,\n // `fillGradient.type`, editor `preset.type`). Each sits in its own object, so\n // the carrier disambiguates completely; synonyms (`cloneKind`, `presetShape`)\n // would add words that all mean \"type\" and still need the carrier to read.\n // Guarding a `type` SLOT against a wrong VALUE is the job of strict enums\n // (issues V3), never of distinct key names.\n type: px.string(),\n // The escape hatch for elements that carry a REAL `type` attribute — `<feTurbulence\n // type=\"fractalNoise\">`, `<feFuncR type=\"table\">`, `<feColorMatrix type=\"saturate\">`.\n // `type` is taken by the tag name, so the attribute travels here and the renderer puts\n // it back (`PxAnimatorDOM.renderNode`, `PxRnRender`). Declared here — not merely\n // documented — because a wire key that is not in a schema is invisible to the\n // minifier's reserve list and gets renamed (dev-docs/plans/minification-boundary.md §1.1).\n domType: px.string().optional(),\n // Text content of a `<text>` / `<tspan>`. Declared, so a non-string value is a schema error\n // and the minifier reserves the key; `text` is NOT an alias for it and is not read anywhere.\n textContent: px.string().optional(),\n id: px.string().optional(),\n meta: px.any().optional(),\n // Player-effects bucket emitted by the Editor's lightweight design format.\n // Consumed and removed by `materializeNodeEffects` before any other normalization\n // (see `createAnimatorImpl`), so downstream code never sees it.\n effects: PxEffectsSchema.optional(),\n // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts\n // string ref / array of refs / inline definition / mixed array; mirrors\n // `node.animate` values and what `processNode` resolves at runtime.\n animate: PxElementAnimationSchema.optional(),\n style: px.record(px.union([px.string(), px.number()])).optional(),\n}, PxAttrValueSchema);\n\n// `let` so the lazy closure can capture the variable reference after assignment.\n// By the time the lazy resolves (first isValid/sanitize call), PxNodeSchema is assigned.\n// `PxNodeBaseSchema & { children?:PxNode[] }`\n/** @public @advanced */\nlet PxNodeSchema: PxSchema<any> = px.openObject({\n ...PxNodeBaseSchema._shape,\n children: px.lazy(() => px.array(PxNodeSchema), []).optional(),\n}, PxAttrValueSchema);\nexport { PxNodeSchema };\n\n/**\n * Base interface for all SVG elements.\n * Extends schema-derived typed fields; adds recursive children and the open\n * index signature for arbitrary SVG attributes under their camelCase DOM names\n * (cx, cy, r, fill, strokeWidth, …) — see `_PxNodeBase`.\n * Named properties take precedence over the index signature when accessed.\n * @public\n */\nexport interface PxNode extends PxInfer<typeof PxNodeBaseSchema> {\n children?: PxNode[];\n [camelCaseDomKey: string]: any;\n}\n\n\n// ============================================================================\n// SVG NODE (ROOT)\n// ============================================================================\n\n/**\n * Root SVG element containing the entire animated graphic.\n * Extends PxNode with SVG-specific properties and global configuration.\n */\nexport interface _PxSvgNode extends PxNode {\n\n /** SVG viewport width. `number` OR an SVG length string (`\"100%\"`, `\"12em\"`) —\n * percentages are legal SVG and appear in real documents. */\n width?: number | string;\n\n /** SVG viewport height — `number` or SVG length string, see `width`. */\n height?: number | string;\n\n /** FIXME - do we need it? SVG viewBox attribute defining coordinate system */\n viewBox?: string;\n\n /** Global animation configuration */\n animator?: PxAnimatorConfig;\n}\n\n/**\n * Extra fields present on the root SVG node, on top of PxNode.\n * Used for type extraction via PxInfer.\n *\n * `{ width?:number, height?:number, viewBox?:string, animator?:AnimatorConfig }`\n * @public @advanced\n */\nexport const PxSvgNodeRootSchema = px.object({\n // `\"100%\"` and other SVG length strings are legal here — a number-only slot rejected\n // real documents (e.g. apple-store-look-14-main.json) at the root <svg>.\n width: px.union([px.number(), px.string()]).optional(),\n height: px.union([px.number(), px.string()]).optional(),\n viewBox: px.string().optional(),\n animator: PxAnimatorConfigSchema.optional(),\n});\n\n/**\n * Root SVG element containing the entire animated graphic.\n * Extends PxNode (inheriting the open index signature) plus schema-derived\n * SVG-root fields.\n * @public\n */\nexport interface PxSvgNode extends PxNode, Omit<PxInfer<typeof PxSvgNodeRootSchema>, 'animator'> {\n /** The RUNTIME-VIEW type, not the wire shape: in-memory documents may carry the\n * flat playback fields (`flattenAnimatorTimeline` output, prop overrides in the\n * RN/React wrappers), while `PxAnimatorConfigSchema` validates only the nested\n * `timeline` spelling on the wire (review §2.1). */\n animator?: PxAnimatorConfig;\n}\n\n\n// ============================================================================\n// DOCUMENT\n// ============================================================================\n\n/**\n * Root SVG document schema. Enforces `type === 'svg'` to distinguish from child nodes.\n * This is the root type for the entire file format.\n *\n * `{ type:'svg', style?:…, width?:number, height?:number,\n * viewBox?:string, animator?:AnimatorConfig, children?:PxNode[],\n * [svgAttr]: string|number|PxPropertyAnimation }`\n * @public @advanced\n */\nexport const PxAnimatedSvgDocumentSchema = px.openObject({\n ...PxNodeBaseSchema._shape,\n ...PxSvgNodeRootSchema._shape,\n type: px.literal('svg'), // override string → literal to require 'svg'\n children: px.array(PxNodeSchema).optional()\n}, PxAttrValueSchema);\n\n/**\n * The complete animated SVG document.\n * This is the root type for the entire file format.\n * @public\n */\nexport interface PxAnimatedSvgDocument extends PxSvgNode {\n}\n\n\n// ============================================================================\n// API INTERFACES\n// ============================================================================\n\n// -- Callbacks: one chain, three levels (API review §9, §5; dev-docs/reviews/api-surface-review.md §26.1) ----------------------------\n//\n// PxDiagnosticsConfig onWarn / onError / muteWarn / muteError — what `createDiagnostics` reads\n// PxEngineCallbacks + onPlay / onPause / onCancel / onFinish / onRemove — what an ENGINE takes\n// PxAnimatorCallbacks + onStop — what every PUBLIC surface takes\n//\n// Each level extends the one above, so a field is spelled once and the surfaces cannot drift.\n// The diagnostics rule (`onError` = this instance will not play; `onWarn` = it plays, but\n// something was ignored, degraded or misspelled) is written on `PxDiagnosticsConfig`.\n\n/**\n * The callbacks an ENGINE takes — the frame loop, WAAPI, React Native's sampler: the playback\n * lifecycle plus the diagnostics channel. Public surfaces take `PxAnimatorCallbacks`.\n * @public\n */\nexport interface PxEngineCallbacks extends PxDiagnosticsConfig {\n\n /** Callback executed when the animation starts or resumes. */\n onPlay?: () => void;\n\n /** Callback executed when the animation is paused. */\n onPause?: () => void;\n\n /** Callback executed when the animation is canceled. */\n onCancel?: () => void;\n\n /**\n * Callback executed when the animation reaches its end — it played every iteration, or\n * `finish()` was called. Not fired when playback is stopped early (pause / cancel / remove).\n */\n onFinish?: () => void;\n\n /** Callback executed when the animation is removed. */\n onRemove?: () => void;\n}\n\n/**\n * What every PUBLIC surface takes, inline and under the same names — `createAnimator({ onFinish })`,\n * `loadTagAnimators`, the pre-rendered entries, and `<PixodeskSvgAnimator onFinish />` on React,\n * Vue and React Native (review §9, §24): the engine's callbacks plus `onStop`, which fires after\n * any of `onPause` / `onCancel` / `onFinish` / `onRemove` — for callers who only care that\n * playback is no longer running, whatever the reason.\n *\n * ONE definition: the components derive their props from it instead of each spelling the same\n * names, which is how their comments had already started to drift.\n * @public\n */\nexport interface PxAnimatorCallbacks extends PxEngineCallbacks {\n onStop?: () => void;\n}\n\n\nexport type PxPoint2D = Array<number>;\n\n\n// ============================================================================\n// BEZIER PATH\n// ============================================================================\n\n/** Represents a vector path for SVG shape animations. */\nexport interface _PxBezierPath {\n\n /** An array of vertex points [[x, y], ...]. */\n v: Array<PxPoint2D>;\n\n /** An array of 'in' tangent handles for each vertex [[x, y], ...]. */\n i?: Array<PxPoint2D>;\n\n /** An array of 'out' tangent handles for each vertex [[x, y], ...]. */\n o?: Array<PxPoint2D>;\n\n /** A boolean indicating if the path is closed. */\n c?: boolean;\n}\n\n// `{ v:number[][], i?:number[][], o?:number[][], c?:boolean }`\n/** @public @advanced */\nexport const PxBezierPathSchema = implementsInterface<_PxBezierPath>()(px.object({\n v: px.array(px.array(px.number())),\n i: px.array(px.array(px.number())).optional(),\n o: px.array(px.array(px.number())).optional(),\n c: px.boolean().optional(),\n}));\n\n/** Represents a vector path for SVG shape animations. @public */\nexport type PxBezierPath = PxInfer<typeof PxBezierPathSchema>;\nconst _ck_PxBezierPath: KeysMatch<PxBezierPath, _PxBezierPath> = true; // the key sets are identical\n\n\n// ============================================================================\n// ANIMATOR API\n// ============================================================================\n\n/**\n * Basic animation controls common to all animator types.\n *\n * Generic over the platform's root-element type (`TRoot`) so this package stays\n * platform-neutral: the web player specializes it to the DOM `Element`, a\n * React Native player to its own view handle. Defaults to `unknown`.\n * @public\n */\nexport interface PxPlaybackApi<TRoot = unknown> {\n\n isReady(): boolean;\n\n /** Returns the root element for the animation (platform-specific type). */\n getRootElement(): TRoot | null;\n\n /** Returns true if the animation is currently running. */\n isPlaying(): boolean;\n\n /** Starts or resumes the animation. */\n play(): void;\n\n /** Pauses the animation at its current state. */\n pause(): void;\n\n /** Stops the animation and resets it to its initial state. */\n cancel(): void;\n\n}\n\n/**\n * The full programmatic control interface for an animation.\n *\n * ### The time contract (API review §3)\n *\n * Every engine — the browser's WAAPI, the frame loop, React Native — answers these the same way:\n *\n * - **Time is ms from the start of the WHOLE run**, iterations included; never ms within the\n * current iteration. A time slider therefore reads the same on every player instead of\n * jumping back each time the animation repeats.\n * - **A seek clamps to `[0, duration × iterations]`**, with no upper bound when `iterations`\n * is `'infinite'`.\n * - **A rate of 0 is rejected** with a warning, everywhere. Use `pause()`.\n *\n * The maths behind it lives in `playback/PxPlaybackTime.ts`, so there is one implementation\n * rather than one per engine.\n * @public\n */\nexport interface PxAnimatorApi<TRoot = unknown> extends PxPlaybackApi<TRoot> {\n\n /** Jumps to the end of the animation and holds the final state. */\n finish(): void;\n\n /**\n * Changes the speed of the animation. 1 is normal, 2 is double, -1 is reverse.\n * A rate of 0 — or a non-finite one — is rejected with a warning; use `pause()`.\n */\n setPlaybackRate(rate: number): void;\n\n /** Current playback time, ms from the start of the whole run. `null` before ready. */\n getCurrentTime(): number | null;\n\n /** Seeks, ms from the start of the whole run; clamped to `[0, duration × iterations]`. */\n setCurrentTime(time: number): void;\n\n /**\n * Current position as 0–1 of the whole run. `null` before ready.\n *\n * The span is `duration × iterations`, or ONE iteration when `iterations` is `'infinite'`\n * (where the value wraps) — the same rule the components' `progress` prop already uses.\n */\n getCurrentProgress(): number | null;\n\n /** Seeks to 0–1 of the whole run, clamped to `[0, 1]`. The twin of `getCurrentProgress`. */\n setCurrentProgress(progress: number): void;\n\n /** Stops the animation and cleans up all associated resources. */\n destroy(): void;\n}\n\n/**\n * The imperative handle a framework component exposes through its ref: the player API minus\n * what the component itself owns — `isReady` (the document is inline, so it is always ready),\n * `getRootElement` (the framework renders it) and `destroy` (unmounting does it).\n *\n * ONE definition (review §9). `ReactAnimatorApi`, `VueAnimatorApi` and `RnAnimatorApi` are\n * aliases of this, so the three can no longer drift — they had: React Native's\n * `setPlaybackRate` comment had already lost \"negative plays backwards\".\n * @public\n */\nexport type PxAnimatorHandle = Omit<PxAnimatorApi, 'isReady' | 'getRootElement' | 'destroy'>;\n\n\n// ============================================================================\n// DEEP VALIDATION\n// ============================================================================\n\n/** @public @advanced */\nexport interface PxValidationResult {\n valid: boolean;\n errors: Array<string>;\n}\n\n/**\n * The pass/fail form of {@link validateDocument}, NON-strict — unknown keys are tolerated —\n * for readers that want a flag plus messages rather than a list.\n *\n * One implementation (review §10). This used to run the schema on its own and answer\n * `'Document failed schema validation'` without ever saying what failed; now every problem\n * `validateDocument` can name comes back with its path. Non-strict on purpose: the editor calls\n * this on OPEN, where a key from a newer version is worth a warning, never a refusal.\n * @public @advanced\n */\nexport function isValidPxDocument(doc: unknown): PxValidationResult {\n const errors = validateDocument(doc, { strict: false });\n return { valid: errors.length === 0, errors };\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport type { PxBezierPath, PxTransformParts } from '../format/PxAnimatorTypes';\n\n\n/**\n * Converts a PxBezierPath to an SVG path string.\n * Control points (i, o) are treated as ABSOLUTE coordinates.\n * @param {PxBezierPath} path\n * @returns {string}\n */\n/**\n * @param forceCurves Emit EVERY segment (incl. the closing one) as a cubic `C`, even when\n * its control points are degenerate (a straight line). Needed for keyframe values the\n * BROWSER interpolates (WAAPI / CSS `path()`): CSS only interpolates paths with\n * IDENTICAL command sequences, so an opportunistic `L` in one keyframe vs a `C` in the\n * next (e.g. a round-corner radius animating from 0) turns the whole animation\n * DISCRETE — it flips at 50% instead of morphing.\n * @internal\n */\nexport function bezierToSvgPath(path: PxBezierPath, forceCurves = false): string {\n const v = path.v;\n const i = path.i;\n const o = path.o;\n const c = path.c;\n\n if (!v.length) return \"\";\n\n const d: Array<string> = [];\n const len = v.length;\n d.push(\"M\" + v[0][0] + \",\" + v[0][1]);\n\n for (let idx = 1; idx < len; idx++) {\n const prevV = v[idx - 1];\n const prevO = o?.[idx - 1] ?? prevV;\n const currI = i?.[idx] ?? v[idx];\n const currV = v[idx];\n\n // Check if it's a straight line (control points coincide with vertices)\n const isLine = !forceCurves && (prevO[0] === prevV[0] && prevO[1] === prevV[1]) &&\n (currI[0] === currV[0] && currI[1] === currV[1]);\n\n if (isLine) {\n d.push(\"L\" + currV[0] + \",\" + currV[1]);\n } else {\n // Control points are absolute coordinates\n d.push(\"C\" + prevO[0] + \",\" + prevO[1] + \",\" + currI[0] + \",\" + currI[1] + \",\" + currV[0] + \",\" + currV[1]);\n }\n }\n\n if (c && len > 0) {\n const lastV = v[len - 1];\n const lastO = o?.[len - 1] ?? lastV;\n const firstI = i?.[0] ?? v[0];\n const firstV = v[0];\n\n // Check if closing segment is a straight line\n const isLine = !forceCurves && (lastO[0] === lastV[0] && lastO[1] === lastV[1]) &&\n (firstI[0] === firstV[0] && firstI[1] === firstV[1]);\n\n if (!isLine) {\n d.push(\"C\" + lastO[0] + \",\" + lastO[1] + \",\" + firstI[0] + \",\" + firstI[1] + \",\" + firstV[0] + \",\" + firstV[1]);\n }\n\n d.push(\"z\");\n }\n\n return d.join(\"\");\n}\n\n/**\n * @param {number} a \n * @param {number} b \n * @param {number} t \n * @returns {number}\n */\nexport function interpolateNum(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\n\n/**\n * @param {Array<number>} a \n * @param {Array<number>} b \n * @param {number} t \n * @returns {Array<number>}\n */\nexport function interpolateVec(a: Array<number>, b: Array<number>, t: number): Array<number> {\n const res: Array<number> = [];\n const count = Math.max(a.length, b.length);\n for (let i = 0; i < count; i++) {\n res[i] = interpolateNum(a[i] || 0, b[i] || 0, t);\n }\n return res;\n}\n\n/**\n * Interpolates between two color arrays [r, g, b] or [r, g, b, a].\n * Normalizes both colors to 4 elements, defaulting alpha to 1 if missing.\n */\nexport function interpolateColor(a: Array<number>, b: Array<number>, t: number): Array<number> {\n return [\n interpolateNum(a[0] || 0, b[0] || 0, t),\n interpolateNum(a[1] || 0, b[1] || 0, t),\n interpolateNum(a[2] || 0, b[2] || 0, t),\n interpolateNum(a[3] === undefined ? 1 : a[3], b[3] === undefined ? 1 : b[3], t)\n ];\n}\n\n/**\n * Interpolates between two arrays of bezier paths.\n * @param paths1 The starting array of paths.\n * @param paths2 The ending array of paths.\n * @param progress The interpolation progress from 0.0 to 1.0.\n * @internal\n */\nexport function interpolateBeziers(\n paths1: Array<PxBezierPath>,\n paths2: Array<PxBezierPath>,\n progress: number\n): Array<PxBezierPath> {\n const count = Math.max(paths1.length, paths2.length);\n const res: Array<PxBezierPath> = [];\n for (let i = 0; i < count; i++) {\n res.push(interpolateBezier(paths1[i], paths2[i], progress));\n }\n return res;\n}\n\n/**\n * Interpolates between two bezier paths.\n * Control points (i, o) are treated as ABSOLUTE coordinates.\n * When control points are missing, they default to the vertex position.\n * @param {PxBezierPath} path1\n * @param {PxBezierPath} path2\n * @param {number} progress\n * @returns {PxBezierPath}\n */\nexport function interpolateBezier(\n path1: PxBezierPath | undefined,\n path2: PxBezierPath | undefined,\n progress: number\n): PxBezierPath {\n if (!path1 || !path2) return path1 || path2 || { v: [] };\n\n const t = Math.min(Math.max(progress, 0), 1);\n const len = Math.min(path1.v.length, path2.v.length);\n\n const v: Array<Array<number>> = [];\n const i: Array<Array<number>> = [];\n const o: Array<Array<number>> = [];\n\n for (let idx = 0; idx < len; idx++) {\n const v1 = path1.v[idx];\n const v2 = path2.v[idx];\n v.push(interpolateVec(v1, v2, t));\n\n // For absolute control points, default to vertex position (straight line)\n const i1 = path1.i?.[idx] ?? v1;\n const i2 = path2.i?.[idx] ?? v2;\n i.push(interpolateVec(i1, i2, t));\n\n const o1 = path1.o?.[idx] ?? v1;\n const o2 = path2.o?.[idx] ?? v2;\n o.push(interpolateVec(o1, o2, t));\n }\n\n return { v, i: i.length ? i : undefined, o: o.length ? o : undefined, c: path1.c ?? path2.c };\n}\n\n\n/**\n * Remap a number from one range to another.\n *\n * @param {number} value - The input value.\n * @param {number} inMin - Lower bound of the input range.\n * @param {number} inMax - Upper bound of the input range.\n * @param {number} outMin - Lower bound of the output range.\n * @param {number} outMax - Upper bound of the output range.\n * @returns The remapped value.\n */\nexport function remap(\n value: number,\n inMin: number,\n inMax: number,\n outMin: number,\n outMax: number\n): number {\n if (inMax === inMin) return outMin; // avoid divide-by-zero\n const t = (value - inMin) / (inMax - inMin);\n return outMin + t * (outMax - outMin);\n}\n\n/**\n * Solves for the parameter t such that the cubic bezier X(t) = x,\n * where the bezier has control point x-coordinates p1x and p2x\n * (endpoints are fixed at x=0 and x=1).\n * Uses Newton-Raphson with bisection fallback.\n */\nexport function solveCubicBezierX(p1x: number, p2x: number, x: number): number {\n if (x <= 0) return 0;\n if (x >= 1) return 1;\n\n const cx = 3 * p1x;\n const bx = 3 * (p2x - p1x) - cx;\n const ax = 1 - cx - bx;\n\n function sampleX(t: number) { return ((ax * t + bx) * t + cx) * t; }\n function sampleDX(t: number) { return (3 * ax * t + 2 * bx) * t + cx; }\n\n let t2 = x;\n let t0 = 0;\n let t1 = 1;\n\n for (let i = 0; i < 8; i++) {\n const x2 = sampleX(t2) - x;\n if (Math.abs(x2) < 1e-6) return t2;\n const d2 = sampleDX(t2);\n if (Math.abs(d2) < 1e-6) break;\n t2 -= x2 / d2;\n }\n\n t2 = x;\n while (t0 < t1) {\n const x2 = sampleX(t2);\n if (Math.abs(x2 - x) < 1e-6) return t2;\n if (x > x2) t0 = t2;\n else t1 = t2;\n t2 = (t1 + t0) / 2;\n }\n\n return t2;\n}\n\n/**\n * Creates a cubic-bezier easing function.\n * @param easing An array of four numbers [x1, y1, x2, y2] defining the bezier curve.\n * @returns A function that takes a progress value (0-1) and returns an eased value.\n * @internal\n */\nexport function cubicBezier(easing: [number, number, number, number]) {\n const [p1x, p1y, p2x, p2y] = easing;\n\n const cy = 3 * p1y;\n const by = 3 * (p2y - p1y) - cy;\n const ay = 1 - cy - by;\n\n function sampleCurveY(t: number) { return ((ay * t + by) * t + cy) * t; }\n\n return function (x: number) {\n return sampleCurveY(solveCubicBezierX(p1x, p2x, x));\n };\n}\n\ntype Point2 = [number, number];\n\nfunction lerp2(a: Point2, b: Point2, t: number): Point2 {\n return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];\n}\n\n/**\n * Splits a cubic bezier curve at parameter t using De Casteljau's algorithm.\n * Returns the left and right sub-curves as 4-point tuples.\n * @internal\n */\nexport function subdivideCubicBezier(\n p0: Point2, p1: Point2, p2: Point2, p3: Point2, t: number\n): { left: [Point2, Point2, Point2, Point2], right: [Point2, Point2, Point2, Point2] } {\n const q0 = lerp2(p0, p1, t);\n const q1 = lerp2(p1, p2, t);\n const q2 = lerp2(p2, p3, t);\n const r0 = lerp2(q0, q1, t);\n const r1 = lerp2(q1, q2, t);\n const s = lerp2(r0, r1, t);\n return {\n left: [p0, q0, r0, s],\n right: [s, r1, q2, p3]\n };\n}\n\ntype Easing = [number, number, number, number];\n\n/**\n * Splits a CSS cubic-bezier easing [x1,y1,x2,y2] at a given x-axis fraction.\n * Each half is re-normalized to map [0,0]→[1,1].\n * Returns undefined for either half if the input is undefined (linear) or the split is degenerate.\n * @internal\n */\nexport function splitEasing(\n easing: Easing | undefined,\n xFraction: number\n): { left: Easing | undefined, right: Easing | undefined } {\n if (!easing) return { left: undefined, right: undefined };\n if (xFraction <= 0) return { left: undefined, right: easing };\n if (xFraction >= 1) return { left: easing, right: undefined };\n\n const [x1, y1, x2, y2] = easing;\n const t = solveCubicBezierX(x1, x2, xFraction);\n\n const p0: Point2 = [0, 0];\n const p1: Point2 = [x1, y1];\n const p2: Point2 = [x2, y2];\n const p3: Point2 = [1, 1];\n\n const { left, right } = subdivideCubicBezier(p0, p1, p2, p3, t);\n\n // Split point coordinates\n const sx = left[3][0];\n const sy = left[3][1];\n\n let leftEasing: Easing | undefined;\n if (sx > 1e-9 && Math.abs(sy) > 1e-9) {\n leftEasing = [\n left[1][0] / sx, left[1][1] / sy,\n left[2][0] / sx, left[2][1] / sy\n ];\n }\n\n let rightEasing: Easing | undefined;\n const rx = 1 - sx;\n const ry = 1 - sy;\n if (rx > 1e-9 && Math.abs(ry) > 1e-9) {\n rightEasing = [\n (right[1][0] - sx) / rx, (right[1][1] - sy) / ry,\n (right[2][0] - sx) / rx, (right[2][1] - sy) / ry\n ];\n }\n\n return { left: leftEasing, right: rightEasing };\n}\n\n/**\n * Reverses a cubic-bezier easing for backward playback.\n * [x1,y1,x2,y2] → [1-x2, 1-y2, 1-x1, 1-y1].\n * @internal\n */\nexport function reverseEasing(easing: Easing | undefined): Easing | undefined {\n if (!easing) return undefined;\n return [1 - easing[2], 1 - easing[3], 1 - easing[0], 1 - easing[1]];\n}\n\n/**\n * Converts a color from a [r, g, b, a] array (where values are 0-1) to an rgba() or rgb() CSS string.\n * @param color The color array.\n * @internal\n */\nexport function toRGBA(color: Array<number>): string {\n const r = Math.round(color[0] * 255);\n const g = Math.round(color[1] * 255);\n const b = Math.round(color[2] * 255);\n return color.length === 4 ?\n 'rgba(' + r + ',' + g + ',' + b + ',' + color[3] + ')' :\n 'rgb(' + r + ',' + g + ',' + b + ')';\n}\n\n/** Parse rgb/rgba string to normalized array */\nexport function parseRgba(s: string): number[] {\n const inner = s.match(/rgba?\\((.*)\\)/)?.[1];\n if (!inner) throw new Error('Invalid rgb/rgba format');\n const parts = inner.split(',').map(v => +v.trim());\n return [parts[0] / 255, parts[1] / 255, parts[2] / 255, ...(parts[3] !== undefined ? [parts[3]] : [])];\n}\n\n/** Parse hex string to normalized array (#RGB, #RGBA, #RRGGBB, #RRGGBBAA) */\nfunction parseHex(s: string): number[] {\n const hex = s.slice(1); // Remove '#'\n const isShort = hex.length <= 4; // #RGB or #RGBA vs #RRGGBB or #RRGGBBAA\n\n const r = isShort ? hex[0] + hex[0] : hex.slice(0, 2);\n const g = isShort ? hex[1] + hex[1] : hex.slice(2, 4);\n const b = isShort ? hex[2] + hex[2] : hex.slice(4, 6);\n const a = hex.length === 4 ? hex[3] + hex[3] : hex.length === 8 ? hex.slice(6, 8) : null;\n\n const result = [\n parseInt(r, 16) / 255,\n parseInt(g, 16) / 255,\n parseInt(b, 16) / 255\n ];\n\n if (a !== null) {\n result.push(parseInt(a, 16) / 255);\n }\n\n return result;\n}\n\n//// FIXME - support normalization of config from different formats\n/** Parse color string (hex or rgb/rgba) to normalized [r, g, b] or [r, g, b, a] array (0-1 range) */\nexport function parseColor(s: any): number[] | undefined {\n if (!s) return undefined;\n if (Array.isArray(s)) return s;\n if (typeof s !== 'string') return undefined;\n if (s.startsWith('#')) {\n return parseHex(s);\n } else if (s.startsWith('rgb')) {\n return parseRgba(s);\n } else {\n // FIXME - come up with some solution how to report errors...\n console.warn('Unsupported color format: ' + s);\n }\n return undefined;\n}\n\n/** @internal */\nexport const PX_COLOR_ATTR_NAMES = new Set([\"color\", \"fill\", \"flood-color\", \"lighting-color\", \"stop-color\", \"stroke\"]);\n/** @internal */\nexport const PX_TRANSFORM_FN_NAMES = new Set([\"translate\", \"rotate\", \"scale\", \"skew\"]);\n/** @internal */\nexport const PX_PCT_BASED_ATTR_NAMES = new Set([\"offset-distance\", \"offsetDistance\"]);\n\n/**\n * Compose a `PxTransformParts` record into a single SVG/CSS transform string in\n * the canonical order:\n *\n * translate, translate(+origin), rotate, scale, translate(-origin)\n *\n * Each part is omitted when not present. `origin` becomes a `translate(+o)` /\n * `translate(-o)` pair surrounding the rotate/scale segment — the SVG-native\n * way to render a transform-origin pivot.\n *\n * @param parts the parts record (translate / rotate / scale / origin)\n * @param opts.withUnits when true (default), translates use `px` and rotate\n * uses `deg` — required for CSS / WebAnimations keyframes. When false, no\n * units are emitted — required for the SVG `transform` attribute.\n * @internal\n */\nexport function composeTransformParts(\n parts: PxTransformParts | null | undefined,\n opts?: { withUnits?: boolean }\n): string {\n if (!parts) return '';\n const withUnits = opts?.withUnits ?? true;\n const segs: Array<string> = [];\n const t = parts.translate;\n const o = parts.origin;\n const r = parts.rotate;\n const k = parts.skew;\n const s = parts.scale;\n const tu = withUnits ? 'px' : '';\n const ru = withUnits ? 'deg' : '';\n if (t) segs.push('translate(' + t[0] + tu + ',' + t[1] + tu + ')');\n if (o) segs.push('translate(' + o[0] + tu + ',' + o[1] + tu + ')');\n if (r !== undefined && r !== null) segs.push('rotate(' + r + ru + ')');\n // Canonical slot: between rotate and scale (Lottie-compatible; pivots at origin).\n if (k !== undefined && k !== null) segs.push('skewX(' + k + ru + ')');\n if (s) segs.push('scale(' + s[0] + ',' + s[1] + ')');\n if (o) segs.push('translate(' + (-o[0]) + tu + ',' + (-o[1]) + tu + ')');\n return segs.join('');\n}\n/**\n * Parses an SVG `transform` attribute string back into a `PxTransformParts` record —\n * the inverse of {@link composeTransformParts} for its canonical shapes.\n *\n * Deliberately CONSERVATIVE (review §0.4 — used to merge a static transform under an\n * animation, where a wrong guess is worse than no merge): accepts only a linear\n * sequence with at most one `translate`, `rotate`, `skewX`, `scale` in the canonical\n * order. Anything else — `matrix(…)`, repeated functions, the ±origin translate\n * sandwich, three-arg `rotate(a cx cy)` — returns `undefined` (caller skips the merge).\n * @internal\n */\nexport function parseTransformParts(str: string | null | undefined): PxTransformParts | undefined {\n if (!str || typeof str !== 'string') return undefined;\n const out: PxTransformParts = {};\n const re = /([a-zA-Z]+)\\s*\\(([^)]*)\\)/g;\n const order = ['translate', 'rotate', 'skewX', 'scale'];\n let lastIdx = -1;\n let m: RegExpExecArray | null;\n while ((m = re.exec(str)) !== null) {\n const fn = m[1];\n const idx = order.indexOf(fn);\n if (idx < 0 || idx <= lastIdx) return undefined; // unknown fn, repeat, or out of order\n lastIdx = idx;\n const nums = m[2].split(/[\\s,]+/).filter(Boolean).map(Number);\n if (nums.some(n => Number.isNaN(n))) return undefined;\n if (fn === 'translate') {\n if (nums.length < 1 || nums.length > 2) return undefined;\n out.translate = [nums[0], nums[1] ?? 0];\n } else if (fn === 'rotate') {\n if (nums.length !== 1) return undefined; // rotate(a cx cy) has no parts spelling\n out.rotate = nums[0];\n } else if (fn === 'skewX') {\n if (nums.length !== 1) return undefined;\n out.skew = nums[0];\n } else { // scale\n if (nums.length < 1 || nums.length > 2) return undefined;\n out.scale = [nums[0], nums[1] ?? nums[0]];\n }\n }\n // Reject when anything but whitespace remains outside the parsed functions.\n if (str.replace(/([a-zA-Z]+)\\s*\\(([^)]*)\\)/g, '').replace(/[\\s,]/g, '').length) return undefined;\n return Object.keys(out).length ? out : undefined;\n}\n\n/** @internal */\nexport const PX_STYLE_ATTR_NAMES = new Set([\"offset-distance\", \"offsetDistance\"]); // Props that need to go to style\n/** @internal */\nexport const PX_DEFAULT_DURATION_MS = 1000;\n\n/**\n * Converts a kebab-case string to camelCase.\n * @param kebab The kebab-case string.\n * @internal\n */\nexport function kebabToCamelCaseWord(kebab: string): string {\n return kebab.includes('-') ? kebab.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()) : kebab;\n}\n\n/**\n * Checks if a string is in camelCase.\n * @param word The string to check.\n */\nexport function isCamelCaseWord(word: string): boolean {\n return !word.includes('-') && /[a-z][A-Z]/.test(word);\n}\n\n// FIXME - docs, rename?\nconst SVG_CAMEL_CASE_ATTRS = new Set([\n // Transform/positioning\n 'viewBox',\n 'preserveAspectRatio',\n\n // Gradient\n 'gradientUnits',\n 'gradientTransform',\n 'spreadMethod',\n\n // Pattern\n 'patternUnits',\n 'patternContentUnits',\n 'patternTransform',\n\n // Clipping/masking\n 'clipPathUnits',\n 'maskUnits',\n 'maskContentUnits',\n\n // Marker (SVG spec keeps these camelCase, like viewBox)\n 'markerUnits',\n 'markerWidth',\n 'markerHeight',\n 'refX',\n 'refY',\n\n // Text\n 'textLength',\n 'lengthAdjust',\n 'startOffset',\n\n // Filter\n 'filterUnits',\n 'primitiveUnits',\n 'tableValues', // feFuncR/G/B/A transfer table (type=\"table\")\n 'stdDeviation',\n 'baseFrequency',\n 'numOctaves',\n 'surfaceScale',\n 'diffuseConstant',\n 'specularConstant',\n 'specularExponent',\n 'kernelMatrix',\n 'kernelUnitLength',\n 'edgeMode',\n 'preserveAlpha',\n 'targetX',\n 'targetY',\n\n // // Animation\n // 'attributeName',\n // 'attributeType',\n // 'calcMode',\n // 'keyTimes',\n // 'keySplines',\n // 'repeatCount',\n // 'repeatDur' \n]);\n\n/**\n * Converts a camelCase string to kebab-case.\n * @param camel The camelCase string.\n * @internal\n */\nexport function camelCaseToKebabWordIfNeeded(camel: string): string { // FIXME - docs, rename function?\n return SVG_CAMEL_CASE_ATTRS.has(camel) ?\n camel :\n camel.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();\n}\n\n/**\n * Checks if a CSS property is set inline on an element's style.\n *\n * Typed structurally (not as the DOM `CSSStyleDeclaration`) so this package\n * stays platform-neutral; a real `element.style` satisfies the shape.\n *\n * @param style - element.style (CSSStyleDeclaration-shaped)\n * @param propName - property name (camelCase or kebab-case)\n */\nexport function hasStyleProp(\n style: { getPropertyValue(propName: string): string },\n propName: string\n): boolean {\n return style.getPropertyValue(propName) !== '';\n}\n\n/**\n * Clamps a number between a minimum and maximum value.\n * @param value The number to clamp.\n * @param min The minimum value.\n * @param max The maximum value.\n * @internal\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, max));\n}\n\n\n// ============================================================================\n// 2D CUBIC BÉZIER PRIMITIVES — motion-along-path support\n// ============================================================================\n//\n// Hand-written, dependency-free 2D Bézier maths used by the motion-along-path\n// playback paths (both WAAPI normalization and frames-mode direct compute).\n// See `fix-motion-along-path--fix-plan.md` for the wider plan.\n//\n// Conventions:\n// - Points are `[x, y]` tuples (`Point2`) — matches the wire format.\n// - A cubic segment is `(P0, P1, P2, P3)` where `P0` and `P3` are the\n// endpoints and `P1`, `P2` are the control points in ABSOLUTE coordinates.\n// - The Lottie-style tangent storage convention is `kf.to` =\n// outgoing-from-kf-as-a-delta, `kf.ti` = incoming-at-the-next-kf-as-a-delta.\n// The wire format re-attaches them to the natural endpoints as\n// `tangentOut` on the FROM keyframe and `tangentIn` on the TO keyframe.\n// Caller is responsible for the position-to-control-point lift, i.e.\n// `P1 = fromKf.value + fromKf.tangentOut`, `P2 = toKf.value + toKf.tangentIn`.\n\n\n/**\n * Evaluate a 2D cubic Bézier at parameter `t ∈ [0, 1]` via Bernstein form:\n *\n * B(t) = (1-t)³ P0 + 3t(1-t)² P1 + 3t²(1-t) P2 + t³ P3\n *\n * `t` is the curve PARAMETER, not arc-length. For arc-length (CSS Motion Path\n * / `offset-distance`) semantics, use `bezier2D_tForDistance` first to convert\n * a distance to its parameter, then call this. Out-of-range `t` is clamped.\n */\nexport function bezier2D_pointAt(\n P0: Point2, P1: Point2, P2: Point2, P3: Point2,\n t: number\n): Point2 {\n if (t <= 0) return [P0[0], P0[1]];\n if (t >= 1) return [P3[0], P3[1]];\n const u = 1 - t;\n const u2 = u * u;\n const u3 = u2 * u;\n const t2 = t * t;\n const t3 = t2 * t;\n const w0 = u3;\n const w1 = 3 * t * u2;\n const w2 = 3 * t2 * u;\n const w3 = t3;\n return [\n w0 * P0[0] + w1 * P1[0] + w2 * P2[0] + w3 * P3[0],\n w0 * P0[1] + w1 * P1[1] + w2 * P2[1] + w3 * P3[1],\n ];\n}\n\n\n/**\n * Evaluate the derivative `B'(t)` of a 2D cubic Bézier at parameter `t`.\n *\n * B'(t) = 3(1-t)² (P1-P0) + 6t(1-t) (P2-P1) + 3t² (P3-P2)\n *\n * Returns the tangent VECTOR (not a unit vector). For auto-orient rotation,\n * caller computes `Math.atan2(d.y, d.x)`.\n *\n * Epsilon-nudge for degenerate endpoints: when a handle coincides with its\n * endpoint (e.g. `P1 === P0` and `t === 0`, common for the start/end of a\n * Lottie spatial-tangent path), the derivative collapses to zero. The\n * exact-endpoint value is then meaningless; we nudge `t` inward by `1e-4`\n * and retry.\n */\nconst BEZIER_T_NUDGE = 1e-4;\nexport function bezier2D_derivativeAt(\n P0: Point2, P1: Point2, P2: Point2, P3: Point2,\n t: number\n): Point2 {\n const result = _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t);\n if (result[0] === 0 && result[1] === 0) {\n // Degenerate (handle = endpoint). Nudge inward and retry.\n const nudgedT = t < 0.5 ? t + BEZIER_T_NUDGE : t - BEZIER_T_NUDGE;\n return _bezier2D_derivativeAtRaw(P0, P1, P2, P3, nudgedT);\n }\n return result;\n}\n\nfunction _bezier2D_derivativeAtRaw(\n P0: Point2, P1: Point2, P2: Point2, P3: Point2,\n t: number\n): Point2 {\n const u = 1 - t;\n const a = 3 * u * u;\n const b = 6 * t * u;\n const c = 3 * t * t;\n return [\n a * (P1[0] - P0[0]) + b * (P2[0] - P1[0]) + c * (P3[0] - P2[0]),\n a * (P1[1] - P0[1]) + b * (P2[1] - P1[1]) + c * (P3[1] - P2[1]),\n ];\n}\n\n\n/**\n * Arc-length lookup table for a 2D cubic Bézier.\n *\n * Samples the curve at `steps + 1` evenly-spaced parameter values\n * (`t = 0, 1/steps, 2/steps, …, 1`), computes the cumulative Euclidean\n * distance between consecutive samples, and returns parallel\n * `Float64Array`s for parameter (`ts`) and arc length (`ds`). `ds[steps]`\n * is the total arc length of the curve.\n *\n * The LUT shape is `{ts, ds}` with parallel Float64Arrays rather than\n * `Array<{t, d}>` because:\n * - One contiguous allocation per array instead of `steps+1` objects.\n * - Binary search in `bezier2D_tForDistance` reads `Float64Array` directly.\n * - The structure is read-only after construction — no need for per-sample\n * field access.\n *\n * Approximation error is roughly `O((1/steps)²)` for smooth curves; the\n * default 100 samples gives <1% error vs analytic arc length on typical\n * motion paths.\n */\nexport interface ArcLengthLUT {\n readonly ts: Float64Array;\n readonly ds: Float64Array;\n}\n\nexport function bezier2D_arcLengthLUT(\n P0: Point2, P1: Point2, P2: Point2, P3: Point2,\n steps: number = 100\n): ArcLengthLUT {\n const n = steps + 1;\n const ts = new Float64Array(n);\n const ds = new Float64Array(n);\n\n let prev = bezier2D_pointAt(P0, P1, P2, P3, 0);\n ts[0] = 0;\n ds[0] = 0;\n\n let cum = 0;\n for (let i = 1; i < n; i++) {\n const t = i / steps;\n const cur = bezier2D_pointAt(P0, P1, P2, P3, t);\n const dx = cur[0] - prev[0];\n const dy = cur[1] - prev[1];\n cum += Math.sqrt(dx * dx + dy * dy);\n ts[i] = t;\n ds[i] = cum;\n prev = cur;\n }\n return { ts, ds };\n}\n\n\n/**\n * Inverse of `bezier2D_arcLengthLUT` lookup: given a distance along the\n * curve, return the curve parameter `t` reached at that distance via\n * binary search on `lut.ds` + linear interpolation between adjacent\n * samples.\n *\n * Distance is clamped to `[0, lut.ds[last]]`. The returned `t` is in\n * `[0, 1]`. Pair with `bezier2D_pointAt` to get the point at that\n * arc-length distance:\n *\n * const t = bezier2D_tForDistance(lut, distance);\n * const point = bezier2D_pointAt(P0, P1, P2, P3, t);\n */\nexport function bezier2D_tForDistance(lut: ArcLengthLUT, distance: number): number {\n const { ts, ds } = lut;\n const last = ds.length - 1;\n if (distance <= 0) return ts[0];\n if (distance >= ds[last]) return ts[last];\n\n // Binary search for the upper-bound index `hi` such that ds[hi-1] <= distance < ds[hi].\n let lo = 1;\n let hi = last;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (ds[mid] < distance) lo = mid + 1;\n else hi = mid;\n }\n // Linear interpolate between the bracketing samples.\n const dPrev = ds[hi - 1];\n const dCur = ds[hi];\n const span = dCur - dPrev;\n const frac = span > 0 ? (distance - dPrev) / span : 0;\n return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);\n}\n\n\n/**\n * `bezier2D_tForDistance` convenience taking a fraction of the total arc\n * length instead of an absolute distance. `pct` is in `[0, 1]` (CSS Motion\n * Path `offset-distance` semantics — `offset-distance: 50%` ≡\n * `bezier2D_tForDistancePct(lut, 0.5)`). Out-of-range values clamp.\n */\nexport function bezier2D_tForDistancePct(lut: ArcLengthLUT, pct: number): number {\n const total = lut.ds[lut.ds.length - 1];\n return bezier2D_tForDistance(lut, pct * total);\n}\n\n\n/**\n * Inverse of {@link bezier2D_tForDistance}: given a curve parameter `t`,\n * returns the arc length from the start. Binary searches the LUT's `ts`\n * (uniformly spaced) and linearly interpolates `ds` between adjacent samples.\n */\nexport function bezier2D_arcAtT(lut: ArcLengthLUT, t: number): number {\n const { ts, ds } = lut;\n const last = ts.length - 1;\n if (t <= ts[0]) return ds[0];\n if (t >= ts[last]) return ds[last];\n let lo = 1, hi = last;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (ts[mid] < t) lo = mid + 1;\n else hi = mid;\n }\n const tPrev = ts[hi - 1];\n const span = ts[hi] - tPrev;\n const frac = span > 0 ? (t - tPrev) / span : 0;\n return ds[hi - 1] + frac * (ds[hi] - ds[hi - 1]);\n}\n\n\n/**\n * Inverse of {@link cubicBezier}: for a CSS easing `[x1,y1,x2,y2]` and a\n * target output `y`, returns the input `x` such that\n * `cubicBezier(easing)(x) ≈ y`. Works for monotonic easings (the CSS\n * default) by swapping the easing's x/y axes — the inverse easing has\n * controls `[y1, x1, y2, x2]`, which `cubicBezier` evaluates directly.\n *\n * `undefined` easing (linear) → identity function.\n */\nexport function invertEasing(easing: Easing | undefined): (y: number) => number {\n if (!easing) return (y) => y;\n const flipped: Easing = [easing[1], easing[0], easing[3], easing[2]];\n return cubicBezier(flipped);\n}","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n/**\n * Motion-along-path → plain transform keyframes.\n *\n * Editor wire format for a curved-translation (and/or `autoOrient`) animation\n * carries per-kf `tangentIn` / `tangentOut` plus an animation-level `autoOrient`\n * flag — a parametric representation that any consumer must evaluate per frame.\n *\n * This module DESUGARS that shape into a regular unified-transform animation:\n * extra `{ translate, rotate? }` keyframes are inserted at curve extrema and\n * adaptive bisection points so the linear chord between adjacent samples stays\n * within `flatnessTolerance` of the true Bezier, and the original easing is\n * SPLIT (via De Casteljau, `splitEasing` in `PxAnimatorUtil`) across the\n * sub-segments so the combined timing reproduces the input easing exactly.\n *\n * Output kfs have no tangents and no `autoOrient` — both engines (`frames` and\n * `waapi`) and any future renderer (e.g. react-native-svg) consume them via\n * their normal unified-transform code path.\n *\n * The plan + rationale (extremes-aware sampling, easing-split, full pipeline\n * order) is in `motion-along-path-waapi-rework.md`.\n */\n\n\nimport { bezier2D_arcAtT, bezier2D_arcLengthLUT, bezier2D_derivativeAt, bezier2D_pointAt, clamp, invertEasing, splitEasing } from '../util/PxAnimatorUtil';\nimport type { ArcLengthLUT } from '../util/PxAnimatorUtil';\nimport type { PxAnyKeyframe, PxKeyframe, PxNormalizedKeyframe, PxNode, PxPropertyAnimation, PxTransformParts } from '../format/PxAnimatorTypes';\nimport { keyframeTime, keyframeValue, keyframeEasing, keyframeTangentIn, keyframeTangentOut } from '../format/PxAnimatorTypes';\n\n\ntype Point2 = [number, number];\ntype Easing = [number, number, number, number];\n\n\nfunction getKfTranslate(kf: PxAnyKeyframe): Point2 | undefined {\n const v = keyframeValue(kf);\n if (!v) return undefined;\n if (Array.isArray(v) && v.length >= 2 && typeof v[0] === 'number' && typeof v[1] === 'number') {\n // Composite per-part shape: `value: [x, y]` directly.\n return [v[0], v[1]];\n }\n const tr = (v as PxTransformParts).translate;\n if (Array.isArray(tr) && tr.length >= 2) return [tr[0], tr[1]];\n return undefined;\n}\n\nfunction getKfTime(kf: PxAnyKeyframe): number {\n return keyframeTime(kf) as number;\n}\n\nfunction getKfEasing(kf: PxAnyKeyframe): Easing | undefined {\n return keyframeEasing(kf) as Easing | undefined;\n}\n\n\n/**\n * True when `anim` is a motion-along-path animation — at least one keyframe\n * carries spatial tangents (`tangentIn` / `tangentOut`) and/or the animation\n * has `autoOrient` set. Animation-level helper; works for either the body\n * `transform` slot or a composite per-part `translate` slot.\n * @internal\n */\nexport function propAnimIsMotionPath(anim: PxPropertyAnimation): boolean {\n const kfs: Array<PxAnyKeyframe> | undefined = anim.keyframes;\n if (!Array.isArray(kfs)) return false;\n if (anim.autoOrient) return true;\n for (const kf of kfs) {\n if (keyframeTangentIn(kf) || keyframeTangentOut(kf)) return true;\n }\n return false;\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Segment cache — shared by `evaluateMotionPathSegment` (frames-mode kernel)\n// and the materializer. Keyed by FROM-keyframe identity (WeakMap), so cache\n// entries vanish automatically when keyframes are replaced.\n// ─────────────────────────────────────────────────────────────────────────────\n\n\ninterface MotionPathSegmentCache {\n readonly P0: Point2;\n readonly P1: Point2;\n readonly P2: Point2;\n readonly P3: Point2;\n readonly lut: ArcLengthLUT;\n readonly totalArc: number;\n}\n\n// Keyed on the PAIR, not on `prevKf` alone: one keyframe can start different segments\n// across evaluations (a lookup that falls outside the keyframe range answers with a\n// first→last pair), and a `prevKf`-only key let that bogus segment's Bezier be served\n// for every later evaluation of the real `prevKf`→next segment.\nconst _segmentCache = new WeakMap<PxAnyKeyframe, WeakMap<PxAnyKeyframe, MotionPathSegmentCache>>();\n\nfunction getSegmentCache(\n prevKf: PxAnyKeyframe,\n nextKf: PxAnyKeyframe,\n prevPos: Point2,\n nextPos: Point2,\n): MotionPathSegmentCache {\n let byNext = _segmentCache.get(prevKf);\n const existing = byNext?.get(nextKf);\n if (existing) return existing;\n const to = keyframeTangentOut(prevKf);\n const ti = keyframeTangentIn(nextKf);\n const P1: Point2 = [prevPos[0] + (to ? to[0] : 0), prevPos[1] + (to ? to[1] : 0)];\n const P2: Point2 = [nextPos[0] + (ti ? ti[0] : 0), nextPos[1] + (ti ? ti[1] : 0)];\n const lut = bezier2D_arcLengthLUT(prevPos, P1, P2, nextPos);\n const entry: MotionPathSegmentCache = {\n P0: prevPos, P1, P2, P3: nextPos,\n lut,\n totalArc: lut.ds[lut.ds.length - 1],\n };\n if (!byNext) { byNext = new WeakMap<PxAnyKeyframe, MotionPathSegmentCache>(); _segmentCache.set(prevKf, byNext); }\n byNext.set(nextKf, entry);\n return entry;\n}\n\n/** Test helper. No-op in production (WeakMap; entries self-evict). Tests\n * should use fresh keyframe objects to force cache misses. */\nexport function _resetMotionPathSegmentCache(): void {\n // Intentionally empty — kept for backwards-compat with any callers.\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Frames-mode kernel — preserved for any caller that still wants parametric\n// evaluation. The binding pipeline no longer needs it (motion-path is\n// materialized at `normalizeBindings` time), but it's a useful primitive\n// on its own.\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n/** @internal */\nexport interface MotionPathSample {\n /** Translate at the current time (motion-path arc-length-parametrised). */\n readonly translate: Point2;\n /** Auto-orient rotation in degrees (only when `autoOrient` is set). */\n readonly rotateDeg?: number;\n}\n\n/**\n * Evaluates the motion-path position (and optional auto-orient rotation) for a\n * single segment kf[i] → kf[i+1], given local progress already remapped to\n * `[0, 1]` and eased. Builds (or reuses cached) Bezier control points\n * `P1 = P0 + tangentOut`, `P2 = P3 + tangentIn`, maps `localProgress` to arc\n * length, then to curve parameter `t` via the arc-length LUT.\n * @internal\n */\nexport function evaluateMotionPathSegment(\n prevKf: PxKeyframe,\n nextKf: PxKeyframe,\n prevPos: Point2,\n nextPos: Point2,\n localProgress: number,\n autoOrient: boolean,\n): MotionPathSample {\n const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);\n const t = seg.totalArc === 0\n ? localProgress\n : tFromArcFraction(seg.lut, localProgress);\n const point = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, t);\n if (!autoOrient) return { translate: point };\n const tan = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, t);\n const rotateDeg = Math.atan2(tan[1], tan[0]) * 180 / Math.PI;\n return { translate: point, rotateDeg };\n}\n\nfunction tFromArcFraction(lut: ArcLengthLUT, arcFrac: number): number {\n const total = lut.ds[lut.ds.length - 1];\n // Binary search on `ds` for `arcFrac * total` (mirrors bezier2D_tForDistance).\n const target = arcFrac * total;\n const { ts, ds } = lut;\n const last = ds.length - 1;\n if (target <= 0) return ts[0];\n if (target >= ds[last]) return ts[last];\n let lo = 1, hi = last;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (ds[mid] < target) lo = mid + 1;\n else hi = mid;\n }\n const dPrev = ds[hi - 1];\n const span = ds[hi] - dPrev;\n const frac = span > 0 ? (target - dPrev) / span : 0;\n return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public API — materialize parametric motion-path into plain transform kfs\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n/** Sampling configuration shared by `materializeMotionPathInPropAnim` + `materializeMotionPathsInTree`. @internal */\nexport interface MotionPathMaterializationOptions {\n /** Max chord-to-curve deviation per sub-interval, in user units (default 0.5). */\n flatnessTolerance?: number;\n /** Max rotation delta (in degrees) between adjacent samples when `autoOrient`\n * is set (default 5). */\n rotationTolerance?: number;\n /** Hard cap on samples per original segment; prevents runaway recursion on\n * pathological inputs (default 32). */\n maxSamplesPerSegment?: number;\n}\n\nconst DEFAULT_FLATNESS_TOL = 0.5;\nconst DEFAULT_ROTATION_TOL = 5;\nconst DEFAULT_MAX_SAMPLES = 32;\n\n\n/**\n * Converts ONE animated property to sampled transform kfs.\n *\n * Returns a NEW `PxPropertyAnimation` whose `kfs` are flat\n * `{ translate, rotate? }` records placed at curve extrema and adaptive\n * bisection points. Positions are arc-length-parametrised; easing is split via\n * De Casteljau so per-sub-segment easings reproduce the input easing exactly.\n * Original `loop` is carried across; `autoOrient` and per-kf `tangentIn` /\n * `tangentOut` are consumed.\n *\n * Returns the input unchanged (by reference) when it's not a motion-path\n * animation — callers can blindly run it through every propAnim.\n * @internal\n */\nexport function materializeMotionPathInPropAnim(\n anim: PxPropertyAnimation,\n opts?: MotionPathMaterializationOptions,\n): PxPropertyAnimation {\n if (!propAnimIsMotionPath(anim)) return anim;\n const kfs = anim.keyframes as Array<PxAnyKeyframe> | undefined;\n if (!Array.isArray(kfs) || kfs.length < 2) return anim;\n\n const autoOrient = !!anim.autoOrient;\n const flatnessTol = opts?.flatnessTolerance ?? DEFAULT_FLATNESS_TOL;\n const rotationTol = opts?.rotationTolerance ?? DEFAULT_ROTATION_TOL;\n const maxSamples = opts?.maxSamplesPerSegment ?? DEFAULT_MAX_SAMPLES;\n\n const out: Array<PxNormalizedKeyframe> = [];\n\n // First output kf — translate from input; rotate from segment-0 derivative\n // at t=0 if autoOrient. All other transform parts (`origin`, `scale`, an\n // explicit `rotate` when `autoOrient` is false, …) come from kfs[0].value.\n const firstPos = getKfTranslate(kfs[0]);\n if (!firstPos) return anim;\n const firstRotate = autoOrient ? derivAngleForFirstKf(kfs[0], kfs[1]) : undefined;\n out.push(makeOutKf(\n getKfTime(kfs[0]),\n buildOutKfValue(getKfValueParts(kfs[0]), getKfValueParts(kfs[0]), 0, firstPos, firstRotate, autoOrient),\n ));\n\n for (let i = 0; i < kfs.length - 1; i++) {\n const prevKf = kfs[i];\n const nextKf = kfs[i + 1];\n const prevPos = getKfTranslate(prevKf);\n const nextPos = getKfTranslate(nextKf);\n if (!prevPos || !nextPos) {\n // Skip undefined translate kfs — just push next as-is.\n out.push(makeOutKf(\n getKfTime(nextKf),\n buildOutKfValue(getKfValueParts(nextKf), getKfValueParts(nextKf), 1, nextPos ?? [0, 0], undefined, autoOrient),\n ));\n continue;\n }\n\n // Sharp-corner step: between adjacent segments, the prev-segment's\n // exit tangent angle (already on out[last]) can differ from this\n // segment's entry tangent angle (deriv at t=0 of the segment about to\n // start) by a lot — e.g. a rectangular path has 90° steps at each\n // corner. Linear interp from prev-exit to the FAR-END kf of this\n // segment would slide rotation through the whole segment instead of\n // stepping at the boundary. Fix: insert a duplicate kf at the\n // boundary time carrying this segment's entry angle. With `e=undef`\n // on the prior kf, the engines render an instant step boundary then\n // constant rotation through the segment.\n if (autoOrient && i > 0) {\n insertSharpCornerStepKfIfNeeded(out, prevKf, nextKf, prevPos, nextPos, rotationTol);\n }\n\n materializeSegment(out, prevKf, nextKf, prevPos, nextPos, autoOrient, flatnessTol, rotationTol, maxSamples);\n }\n\n // The very last out-kf inherits the original last kf's `easing` (which\n // applies to the NEXT segment after this animation, or nothing — but the\n // wire format preserves it regardless).\n const lastInE = getKfEasing(kfs[kfs.length - 1]);\n if (lastInE) out[out.length - 1].e = lastInE;\n\n // Unwrap autoOrient rotations so linear interp between samples doesn't\n // take the long way around the circle. atan2 returns in (-180°, 180°];\n // a tangent rotating slowly across the +X axis (e.g. -179° → +179°)\n // would otherwise be lerp'd as a ~358° spin instead of a ~2° step.\n if (autoOrient) unwrapAutoOrientRotations(out);\n\n // Output converges on `keyframes` — the `kfs` alias is gone (review §1.2/§6.1).\n const result: PxPropertyAnimation = { keyframes: out };\n if (anim.loop !== undefined) (result as { loop?: unknown }).loop = anim.loop;\n return result;\n}\n\n\n/** Walks `kfs` in order; for each kf with a `rotate` value, shifts it by ±360°\n * multiples so the delta from the previous kf's rotate stays within ±180°.\n * Linear interpolation between consecutive samples then always takes the\n * shorter arc. The accumulated shift means a continuously-rotating element\n * may end up with rotate values well outside [-180°, 180°], which is fine —\n * CSS / SVG rotation accepts any range. Exported — the glyph along-path baker\n * (`buildAnimatedAlongPath`) needs the identical seam fix for its sampled\n * per-glyph tangent rotations. */\nexport function unwrapAutoOrientRotations(kfs: Array<PxAnyKeyframe>): void {\n let prev: number | undefined;\n for (const kf of kfs) {\n const v = keyframeValue(kf) as { rotate?: number } | undefined;\n if (!v || typeof v.rotate !== 'number') continue;\n if (prev === undefined) { prev = v.rotate; continue; }\n let r = v.rotate;\n while (r - prev > 180) r -= 360;\n while (r - prev < -180) r += 360;\n v.rotate = r;\n prev = r;\n }\n}\n\n\nfunction makeOutKf(time: number, value: PxTransformParts): PxNormalizedKeyframe {\n return { t: time, v: value };\n}\n\n/** Reads the kf's value-as-parts (object form). Returns `undefined` if the kf\n * has no value or it's an array form (single-part composite). */\nfunction getKfValueParts(kf: PxAnyKeyframe): PxTransformParts | undefined {\n const v = keyframeValue(kf);\n if (!v || typeof v !== 'object' || Array.isArray(v)) return undefined;\n return v as PxTransformParts;\n}\n\n/** Linearly interpolates one transform-part value (number / PxVec2). Returns the\n * non-undefined input when only one side is present, falls back to `prev`\n * for unsupported types. */\nfunction interpolatePart(prev: unknown, next: unknown, p: number): unknown {\n if (prev === undefined) return next;\n if (next === undefined) return prev;\n if (typeof prev === 'number' && typeof next === 'number') {\n return prev + (next - prev) * p;\n }\n if (Array.isArray(prev) && Array.isArray(next) && prev.length === next.length) {\n const out: Array<number> = new Array(prev.length);\n for (let i = 0; i < prev.length; i++) {\n const a = typeof prev[i] === 'number' ? prev[i] : 0;\n const b = typeof next[i] === 'number' ? next[i] : 0;\n out[i] = a + (b - a) * p;\n }\n return out;\n }\n return p < 0.5 ? prev : next;\n}\n\n/** Builds the value-record for one sampled output kf. `translate` overrides\n * whatever the per-part interpolation would have produced (motion path is the\n * source of truth for position); `rotateDegFromAutoOrient`, when defined, is\n * ADDED on top of the (interpolated) explicit animated `rotate` — auto-orient\n * and a user-set rotation compose, matching After Effects / Lottie semantics.\n * All OTHER parts present on `prevV` / `nextV` (origin, scale, etc.) are\n * interpolated at the eased arc-progress `p` — mirroring the frames-mode\n * `calcPropertyValue` loop. */\nfunction buildOutKfValue(\n prevV: PxTransformParts | undefined,\n nextV: PxTransformParts | undefined,\n p: number,\n translate: Point2,\n rotateDegFromAutoOrient: number | undefined,\n autoOrient: boolean,\n): PxTransformParts {\n const value: { [k: string]: unknown } = { translate };\n const keys = new Set<string>();\n if (prevV) for (const k of Object.keys(prevV)) keys.add(k);\n if (nextV) for (const k of Object.keys(nextV)) keys.add(k);\n for (const k of keys) {\n if (k === 'translate') continue; // overridden by motion path\n if (k === 'rotate' && autoOrient) continue; // composed below with autoOrient\n const pv = (prevV as Record<string, unknown> | undefined)?.[k];\n const nv = (nextV as Record<string, unknown> | undefined)?.[k];\n if (pv === undefined && nv === undefined) continue;\n value[k] = interpolatePart(pv, nv, p);\n }\n if (rotateDegFromAutoOrient !== undefined) {\n // Sum the auto-orient tangent angle with the element's own animated\n // rotation (interpolated at `p`, 0 when absent) rather than discarding it.\n value.rotate = rotateDegFromAutoOrient + explicitRotateAt(prevV, nextV, p);\n }\n return value as PxTransformParts;\n}\n\n/** Interpolated explicit `rotate` from a transform-parts pair at arc-progress\n * `p`; 0 when neither side carries a numeric rotate. */\nfunction explicitRotateAt(\n prevV: PxTransformParts | undefined,\n nextV: PxTransformParts | undefined,\n p: number,\n): number {\n const pv = typeof prevV?.rotate === 'number' ? prevV.rotate : undefined;\n const nv = typeof nextV?.rotate === 'number' ? nextV.rotate : undefined;\n if (pv === undefined && nv === undefined) return 0;\n const r = interpolatePart(pv, nv, p);\n return typeof r === 'number' ? r : 0;\n}\n\n\n/** Derivative angle at t=0 of segment kf[0] → kf[1]. Used to seed the first\n * output kf's rotation; without this the very first frame would render with\n * no rotation while every subsequent sample has one. */\nfunction derivAngleForFirstKf(kf0: PxAnyKeyframe, kf1: PxAnyKeyframe): number {\n const p0 = getKfTranslate(kf0);\n const p1 = getKfTranslate(kf1);\n if (!p0 || !p1) return 0;\n const seg = getSegmentCache(kf0, kf1, p0, p1);\n const tan = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, 0);\n return Math.atan2(tan[1], tan[0]) * 180 / Math.PI;\n}\n\n\n/** Shortest signed difference of `a − b` wrapped into (-180°, 180°]. */\nfunction wrappedAngleDelta(a: number, b: number): number {\n let d = a - b;\n while (d > 180) d -= 360;\n while (d < -180) d += 360;\n return d;\n}\n\n\n/**\n * Appends a \"step\" kf to `out` at the boundary time iff the next segment's\n * entry tangent direction differs from the last emitted kf's rotation by more\n * than `rotationTol`. The new kf carries the SAME translate / origin / scale\n * as the boundary kf already in `out` (continuous in space), but a different\n * `rotate` — making engines render an instant rotation snap at the boundary\n * rather than linearly sliding rotation across the whole next segment.\n *\n * Called between `materializeSegment` calls. The boundary kf already in `out`\n * keeps its outgoing easing as undefined (no easing between the two duplicates\n * — the step is instant); the next `materializeSegment` will then attach its\n * sequential-split easing to the newly-inserted duplicate, so the per-sample\n * easing on segment `i+1` works exactly as before.\n */\nfunction insertSharpCornerStepKfIfNeeded(\n out: Array<PxNormalizedKeyframe>,\n prevKf: PxAnyKeyframe, nextKf: PxAnyKeyframe,\n prevPos: Point2, nextPos: Point2,\n rotationTol: number,\n): void {\n const lastKf = out[out.length - 1];\n const lastV = keyframeValue(lastKf) as { rotate?: number } | undefined;\n const prevExit = lastV?.rotate;\n if (typeof prevExit !== 'number') return;\n\n // `prevExit` is the SUMMED rotation (tangent + explicit). Compare pure\n // tangent-to-tangent by subtracting the boundary kf's explicit rotate,\n // which is shared by both adjoining segments at this shared keyframe.\n const boundaryV = getKfValueParts(prevKf);\n const prevExitTangent = prevExit - explicitRotateAt(boundaryV, boundaryV, 0);\n\n const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);\n const tanAtStart = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, 0);\n const nextEntry = Math.atan2(tanAtStart[1], tanAtStart[0]) * 180 / Math.PI;\n const delta = wrappedAngleDelta(nextEntry, prevExitTangent);\n if (Math.abs(delta) <= rotationTol) return; // continuous within tolerance\n\n // Step kf carrying the new entry angle, a hair AFTER the boundary — the boundary\n // time itself must render the PREVIOUS segment's exit pose. A duplicate at the exact\n // boundary time made CSS/WAAPI resolve the LATER keyframe there, so scrubbing to\n // exactly the corner showed the next segment's angle while the editor (and the\n // parametric frames engine, whose segment lookup treats a boundary as the END of the\n // segment before it) showed the previous one. The offset is far below a frame, so\n // playback still reads as an instant step at the corner.\n const prevTime = getKfTime(prevKf);\n const stepTime = Math.min(prevTime + CORNER_STEP_AFTER_BOUNDARY_MS, (prevTime + getKfTime(nextKf)) / 2);\n const dupValue = buildOutKfValue(\n getKfValueParts(prevKf), getKfValueParts(prevKf), 0,\n prevPos, nextEntry, true,\n );\n out.push(makeOutKf(stepTime, dupValue));\n}\n\n/** How far past a sharp corner the entry-angle step kf sits (ms). Small enough to be\n * invisible in playback (a frame is ~16 ms) — and strictly under the 0.1 ms step-width\n * budget the corner-step CSS spec asserts — while still a distinct WAAPI offset. */\nconst CORNER_STEP_AFTER_BOUNDARY_MS = 0.05;\n\n\n/** Materializes a single segment into `out`. Appends one kf per sample (interior\n * critical/adaptive points + the next-kf endpoint), with positions, optional\n * rotations, and split easings. */\nfunction materializeSegment(\n out: Array<PxNormalizedKeyframe>,\n prevKf: PxKeyframe, nextKf: PxKeyframe,\n prevPos: Point2, nextPos: Point2,\n autoOrient: boolean,\n flatnessTol: number, rotationTol: number, maxSamples: number,\n): void {\n const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);\n const prevTime = getKfTime(prevKf);\n const nextTime = getKfTime(nextKf);\n const prevEasing = getKfEasing(prevKf);\n const invertFn = invertEasing(prevEasing);\n const prevV = getKfValueParts(prevKf);\n const nextV = getKfValueParts(nextKf);\n\n // 1. Critical t-values: axis extrema + endpoints.\n const interiorTs = computeSampleTs(seg, autoOrient, flatnessTol, rotationTol, maxSamples);\n // `interiorTs` is the list of t in (0, 1] in ascending order, ending with t=1.\n\n // 2. For each sample t, compute position, rotation, the eased arc-fraction\n // `p` (= the value frames-mode would use for non-translate part interp),\n // and the linear-time fraction `u` (via invertEasing of `p`).\n interface Sample { u: number; p: number; pos: Point2; rotateDeg?: number; }\n const samples: Array<Sample> = [];\n for (const t of interiorTs) {\n const arc = bezier2D_arcAtT(seg.lut, t);\n const p = clamp(seg.totalArc > 0 ? arc / seg.totalArc : t, 0, 1);\n const u = clamp(invertFn(p), 0, 1);\n const pos = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, t);\n const sample: Sample = { u, p, pos };\n if (autoOrient) {\n const tan = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, t);\n sample.rotateDeg = Math.atan2(tan[1], tan[0]) * 180 / Math.PI;\n }\n samples.push(sample);\n }\n\n // 3. Sequential easing split. The easing for sub-segment k (out[L+k-1] → out[L+k])\n // is the `left` half of splitting the still-unconsumed easing at\n // `(u_k - u_{k-1}) / (1 - u_{k-1})`.\n let remaining = prevEasing;\n let prevU = 0;\n const startIdx = out.length - 1; // out[startIdx] is the prev kf — it owns the FIRST sub-easing.\n for (let i = 0; i < samples.length; i++) {\n const s = samples[i];\n const xFrac = prevU < 1 ? clamp((s.u - prevU) / (1 - prevU), 0, 1) : 1;\n const { left, right } = splitEasing(remaining, xFrac);\n\n // Assign `left` as the outgoing easing of the kf at the end of `out`\n // (which is the kf that PRECEDES this sub-kf — the prev kf for i=0,\n // or the previously-emitted sub-kf for i>0).\n const ownerIdx = i === 0 ? startIdx : out.length - 1;\n if (left) out[ownerIdx].e = left;\n else delete out[ownerIdx].e;\n\n const tGlobal = prevTime + s.u * (nextTime - prevTime);\n const value = buildOutKfValue(prevV, nextV, s.p, s.pos, s.rotateDeg, autoOrient);\n out.push(makeOutKf(tGlobal, value));\n\n remaining = right;\n prevU = s.u;\n }\n}\n\n\n/** Returns t-values in (0, 1], sorted ascending, ending with t=1. The list\n * always includes the input segment's axis extrema interior to (0, 1), plus\n * adaptive bisection samples wherever the chord deviates from the curve by\n * more than `flatnessTol` (or the rotation delta exceeds `rotationTol` for\n * autoOrient). Capped at `maxSamples`. */\nfunction computeSampleTs(\n seg: MotionPathSegmentCache, autoOrient: boolean,\n flatnessTol: number, rotationTol: number, maxSamples: number,\n): Array<number> {\n // Axis extrema (interior to (0, 1)).\n const extremes: Array<number> = [];\n addAxisExtremes(seg.P0[0], seg.P1[0], seg.P2[0], seg.P3[0], extremes);\n addAxisExtremes(seg.P0[1], seg.P1[1], seg.P2[1], seg.P3[1], extremes);\n extremes.sort((a, b) => a - b);\n\n const critical: Array<number> = [0];\n for (const t of extremes) {\n if (t > critical[critical.length - 1] + 1e-6 && t < 1 - 1e-6) {\n critical.push(t);\n }\n }\n critical.push(1);\n\n const out: Array<number> = [];\n const budget = { remaining: maxSamples - critical.length }; // already-committed critical points count against the budget\n for (let i = 0; i < critical.length - 1; i++) {\n bisect(critical[i], critical[i + 1], out, seg, autoOrient, flatnessTol, rotationTol, budget);\n }\n return out;\n}\n\n\n/** Solves `P'(t).axis = 0` for one axis (a quadratic in t). Pushes any real\n * roots in `(0, 1)` into `out`. Coefficients via standard cubic Bezier\n * derivative: with `a = p1 − p0, b = p2 − p1, c = p3 − p2`, the equation is\n * `(a − 2b + c)·t² + 2(b − a)·t + a = 0`. */\nfunction addAxisExtremes(p0: number, p1: number, p2: number, p3: number, out: Array<number>): void {\n const a = p1 - p0;\n const b = p2 - p1;\n const c = p3 - p2;\n const A = a - 2 * b + c;\n const B = 2 * (b - a);\n const C = a;\n if (Math.abs(A) < 1e-10) {\n if (Math.abs(B) > 1e-10) {\n const t = -C / B;\n if (t > 1e-6 && t < 1 - 1e-6) out.push(t);\n }\n return;\n }\n const disc = B * B - 4 * A * C;\n if (disc < 0) return;\n const sq = Math.sqrt(disc);\n const t1 = (-B - sq) / (2 * A);\n const t2 = (-B + sq) / (2 * A);\n if (t1 > 1e-6 && t1 < 1 - 1e-6) out.push(t1);\n if (t2 > 1e-6 && t2 < 1 - 1e-6) out.push(t2);\n}\n\n\n/** Adaptive bisection between `tA` and `tB`. Always appends `tB` exactly once\n * (either directly when the chord is flat enough, or via recursion).\n *\n * Flatness is tested at THREE interior points (t = 0.25, 0.5, 0.75 of the\n * sub-interval), not just the midpoint. Symmetric Bezier segments often have\n * the curve crossing the chord exactly at t=0.5 — a single-midpoint check\n * would mistake that for flatness and skip subdivision, leaving visible\n * chord deviation between samples. */\nfunction bisect(\n tA: number, tB: number,\n out: Array<number>,\n seg: MotionPathSegmentCache,\n autoOrient: boolean,\n flatnessTol: number, rotationTol: number,\n budget: { remaining: number },\n): void {\n const tMid = (tA + tB) / 2;\n const pA = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tA);\n const pB = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tB);\n const span = tB - tA;\n const p25 = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tA + span * 0.25);\n const p50 = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tMid);\n const p75 = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tA + span * 0.75);\n\n const dev = Math.max(\n perpDist(p25, pA, pB),\n perpDist(p50, pA, pB),\n perpDist(p75, pA, pB),\n );\n let rotOk = true;\n if (autoOrient) {\n const tanA = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, tA);\n const tanB = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, tB);\n const angA = Math.atan2(tanA[1], tanA[0]) * 180 / Math.PI;\n const angB = Math.atan2(tanB[1], tanB[0]) * 180 / Math.PI;\n let delta = Math.abs(angA - angB);\n if (delta > 180) delta = 360 - delta;\n if (delta > rotationTol) rotOk = false;\n }\n\n if ((dev <= flatnessTol && rotOk) || budget.remaining <= 0 || span < 1e-6) {\n out.push(tB);\n return;\n }\n\n budget.remaining -= 1;\n bisect(tA, tMid, out, seg, autoOrient, flatnessTol, rotationTol, budget);\n bisect(tMid, tB, out, seg, autoOrient, flatnessTol, rotationTol, budget);\n}\n\n\nfunction perpDist(q: Point2, pA: Point2, pB: Point2): number {\n const dx = pB[0] - pA[0];\n const dy = pB[1] - pA[1];\n const len2 = dx * dx + dy * dy;\n if (len2 < 1e-20) {\n const qdx = q[0] - pA[0];\n const qdy = q[1] - pA[1];\n return Math.sqrt(qdx * qdx + qdy * qdy);\n }\n const cross = (q[0] - pA[0]) * dy - (q[1] - pA[1]) * dx;\n return Math.abs(cross) / Math.sqrt(len2);\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Tree walker — applies `materializeMotionPathInPropAnim` to every `node.animate.transform`\n// whose propAnim is a motion-path. Immutable: returns the input by reference\n// when no changes were needed; otherwise clones along the path to each\n// converted node and shares all other sub-trees.\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n/** @internal */\nexport function materializeMotionPathsInTree(\n root: PxNode,\n opts?: MotionPathMaterializationOptions,\n): PxNode {\n const out = walkAndMaterialize(root, opts);\n return out ?? root;\n}\n\nfunction walkAndMaterialize(node: PxNode, opts?: MotionPathMaterializationOptions): PxNode | null {\n let newChildren: Array<PxNode> | undefined;\n if (node.children) {\n for (let i = 0; i < node.children.length; i++) {\n const ch = node.children[i];\n const ret = walkAndMaterialize(ch, opts);\n if (ret !== null) {\n if (!newChildren) newChildren = node.children.slice();\n newChildren[i] = ret;\n }\n }\n }\n\n let newAnimate: Record<string, PxPropertyAnimation> | undefined;\n const animBucket = node.animate;\n if (animBucket && typeof animBucket === 'object' && !Array.isArray(animBucket)) {\n const animDef = animBucket as Record<string, PxPropertyAnimation>;\n const transformAnim = animDef.transform;\n if (transformAnim && typeof transformAnim === 'object' && propAnimIsMotionPath(transformAnim)) {\n const materialized = materializeMotionPathInPropAnim(transformAnim, opts);\n if (materialized !== transformAnim) {\n newAnimate = { ...animDef, transform: materialized };\n }\n }\n }\n\n if (!newChildren && !newAnimate) return null;\n const cloned: PxNode = { ...node };\n if (newChildren) cloned.children = newChildren;\n if (newAnimate) cloned.animate = newAnimate as PxNode['animate'];\n return cloned;\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { type PxAnimatedSvgDocument, type PxAnimationDefinition, type PxBezierPath, type PxNormalizedBinding, type PxBinding, type PxDefinitions, type PxElementAnimation, type PxKeyframe, type PxNormalizedKeyframe, type PxLoop, type PxNode, type PxPropertyAnimation, type PxTransformParts, keyframeTime, keyframeValue, keyframeEasing, keyframeTangentIn, keyframeTangentOut } from '../format/PxAnimatorTypes';\nimport { getBindings, getDefinitions, TRANSFORM_ATTR } from '../format/PxAnimatorConstants';\nimport { getAnimatorConfig, PxTimelineEngine, PxLoopDirection, PxLoopRepeatAt } from '../format/PxAnimatorConstants';\nimport { bezierToSvgPath, camelCaseToKebabWordIfNeeded, clamp, PX_COLOR_ATTR_NAMES, composeTransformParts, cubicBezier, interpolateBeziers, interpolateColor, interpolateNum, interpolateVec, isCamelCaseWord, parseColor, parseTransformParts, PX_PCT_BASED_ATTR_NAMES, remap, reverseEasing, splitEasing, toRGBA, PX_TRANSFORM_FN_NAMES } from '../util/PxAnimatorUtil';\nimport { evaluateMotionPathSegment, materializeMotionPathInPropAnim, propAnimIsMotionPath } from '../materialize/PxMotionPath';\n\n/**\n * Time separation between a cycle's snap-back keyframe and the previous repetition's\n * end, in ms.\n *\n * SINGLE SOURCE OF TRUTH — the editor imports this and converts to its own frame unit\n * (`TLoop.smallFrameShift = PX_LOOP_JUMP_SHIFT_MS / FRAME_DURATION_MS`), so the two sides\n * cannot drift apart and materialize different keyframes (B7).\n *\n * 1ms, not one 10ms editor frame: a 10ms snap-back is long enough to read as a visible\n * jump in a looping animation (confirmed visually). The gap only has to be non-zero — it\n * exists so the snap-back is a discontinuity rather than an interpolated tween.\n *\n * ⚠️ The editor works in FRAMES (`FRAME_DURATION_MS = 10`), so this is a sub-frame shift\n * there. `TKeyframeGroup` rounds keyframe times to integer frames in some paths; the\n * editor side keeps the fractional value and must not be re-clamped to a whole frame.\n * @internal\n */\nexport const PX_LOOP_JUMP_SHIFT_MS = 1;\n\n/** Structural equality for keyframe values (numbers, arrays, transform-part records). */\nfunction deepEqualValue(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b || a === null || b === null || typeof a !== 'object') return false;\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const ka = Object.keys(a as object);\n const kb = Object.keys(b as object);\n if (ka.length !== kb.length) return false;\n return ka.every(k => deepEqualValue((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k]));\n}\n\n\n\n// ============================================================================\n// PATH PARSING: Convert \"path(...)\" strings to PxBezierPath format\n// ============================================================================\n\ninterface PathCommand {\n type: string;\n values: Array<number>;\n}\n\n/**\n * Parses an SVG path string into command tokens.\n * Supports M, L, C, Z commands (case-insensitive).\n */\nfunction parsePathCommands(d: string): Array<PathCommand> {\n const tokens = d.split(/([MLCZmlcz]|[\\s,]+)/).map(t => t.trim()).filter(t => t && t !== ',');\n\n const commands: Array<PathCommand> = [];\n let currentCommand: PathCommand | null = null;\n\n for (const token of tokens) {\n if (/[MLCZmlcz]/.test(token)) {\n currentCommand = { type: token, values: [] };\n commands.push(currentCommand);\n } else if (currentCommand) {\n const value = +token;\n currentCommand.values.push(Number.isNaN(value) ? 0 : value);\n }\n }\n\n return commands;\n}\n\n/**\n * Parses an internal SVG path string into PxBezierPath array.\n * Handles M (moveto), L (lineto), C (curveto), Z (close) commands.\n */\nexport function parseSvgPathToBezier(d: string): Array<PxBezierPath> {\n const res: Array<PxBezierPath> = [];\n let currentPath: PxBezierPath | undefined;\n\n const commands = parsePathCommands(d);\n\n for (const command of commands) {\n const type = command.type;\n const values = command.values;\n\n if (type === 'M' || type === 'm') {\n const x = values[0] || 0;\n const y = values[1] || 0;\n currentPath = {\n v: [[x, y]],\n i: [[x, y]],\n o: [[x, y]],\n c: false\n };\n res.push(currentPath);\n continue;\n }\n\n // Ensure we have a current path\n if (!currentPath) {\n currentPath = {\n v: [[0, 0]],\n i: [[0, 0]],\n o: [[0, 0]],\n c: false\n };\n res.push(currentPath);\n }\n\n if (type === 'L') {\n const x = values[0] || 0;\n const y = values[1] || 0;\n currentPath.v.push([x, y]);\n currentPath.i!.push([x, y]);\n currentPath.o!.push([x, y]);\n\n } else if (type === 'C') {\n const outX = values[0] || 0;\n const outY = values[1] || 0;\n const inX2 = values[2] || 0;\n const inY2 = values[3] || 0;\n const x2 = values[4] || 0;\n const y2 = values[5] || 0;\n\n // Update out-point of previous vertex\n currentPath.o![currentPath.o!.length - 1] = [outX, outY];\n\n // Add new vertex with its in-point\n currentPath.v.push([x2, y2]);\n currentPath.i!.push([inX2, inY2]);\n currentPath.o!.push([x2, y2]);\n\n } else if (type === 'Z' || type === 'z') {\n currentPath.c = true;\n\n } else {\n console.warn('Unsupported path command \"' + type + '\"');\n }\n }\n\n return res;\n}\n\n/**\n * Extracts SVG path data from a string.\n * Handles both \"path(M...)\" wrapper format and raw \"M...\" format.\n * @returns The path data string, or undefined if not a valid path string\n */\nfunction extractPathData(str: string): string | undefined {\n if (str.startsWith('path(') && str.endsWith(')')) {\n return str.slice(5, -1); // Remove \"path(\" and \")\"\n }\n // Raw path string starting with a path command (M, m, or other commands)\n if (/^[MmZzLlHhVvCcSsQqTtAa]/.test(str)) {\n return str;\n }\n return undefined;\n}\n\n/**\n * Checks if the value is a path string (either \"path(...)\" or raw \"M...\").\n */\nfunction isPathString(value: any): value is string {\n return typeof value === 'string' && extractPathData(value) !== undefined;\n}\n\n/**\n * Normalizes a 'd' attribute value to { paths: PxBezierPath[] } format.\n * Handles:\n * - { pathData: \"M...\" } / { pathData: \"path(...)\" } -> { paths: [PxBezierPath] } (THE wire form)\n * - { paths: [\"path(...)\"] } -> { paths: [PxBezierPath] }\n * - { paths: [\"M...\"] } -> { paths: [PxBezierPath] }\n * - [\"path(...)\"] -> { paths: [PxBezierPath] }\n * - [\"M...\"] -> { paths: [PxBezierPath] }\n * - \"path(...)\" -> { paths: [PxBezierPath] }\n * - \"M...\" -> { paths: [PxBezierPath] }\n * - { paths: [PxBezierPath] } -> as-is\n */\nfunction normalizePathValue(value: any): { paths: Array<PxBezierPath> } | any {\n // THE wire form: { pathData: \"M...\" } (compound = multiple `M…` sub-paths in one string).\n // The `{ paths: [...] }` shapes below are this function's OWN output, not wire spellings.\n if (value && typeof value === 'object' && typeof value.pathData === 'string') {\n const d = extractPathData(value.pathData);\n return d ? { paths: parseSvgPathToBezier(d) } : value;\n }\n\n // If already in { paths: [...] } format\n if (value && typeof value === 'object' && 'paths' in value) {\n const pathsArray = value.paths;\n if (Array.isArray(pathsArray) && pathsArray.length > 0) {\n // Check if paths contain path strings that need parsing\n if (isPathString(pathsArray[0])) {\n const paths: Array<PxBezierPath> = [];\n for (const pathStr of pathsArray) {\n const d = extractPathData(pathStr);\n if (d) {\n paths.push(...parseSvgPathToBezier(d));\n }\n }\n return { paths };\n }\n }\n // Already in correct format\n return value;\n }\n\n // If it's an array of path strings\n if (Array.isArray(value)) {\n if (value.length > 0 && isPathString(value[0])) {\n const paths: Array<PxBezierPath> = [];\n for (const pathStr of value) {\n const d = extractPathData(pathStr);\n if (d) {\n paths.push(...parseSvgPathToBezier(d));\n }\n }\n return { paths };\n }\n // Already an array of PxBezierPath - wrap in { paths: }\n return { paths: value };\n }\n\n // If it's a single path string\n if (isPathString(value)) {\n const d = extractPathData(value)!;\n return { paths: parseSvgPathToBezier(d) };\n }\n\n return value;\n}\n\n\n// ============================================================================\n// NORMALIZATION: Convert new API format to internal normalized format\n// ============================================================================\n\n/**\n * Resolves an easing reference to a cubic-bezier array.\n * @param easing The easing reference (string name or bezier array)\n * @param defs The definitions containing named easings\n * @returns The resolved cubic-bezier array or undefined\n */\nfunction resolveEasing(\n easing: string | [number, number, number, number] | undefined,\n defs?: PxDefinitions\n): [number, number, number, number] | undefined {\n if (!easing) return undefined;\n\n if (Array.isArray(easing)) {\n return easing;\n }\n\n // Look up named easing in defs\n if (defs?.easings?.[easing]) {\n return defs.easings[easing];\n }\n\n // Unknown easing name - return undefined\n console.warn('Unknown easing name: ' + easing);\n return undefined;\n}\n\n/**\n * Resolves an animation reference to an AnimationDefinition.\n * @param animRef The animation reference (string name or inline definition)\n * @param defs The definitions containing named animations\n * @returns The resolved animation definition\n */\nfunction resolveAnimation(\n animRef: string | PxAnimationDefinition,\n defs?: PxDefinitions\n): PxAnimationDefinition | undefined {\n if (typeof animRef === 'string') {\n // Look up named animation in defs\n const resolved = defs?.animations?.[animRef];\n if (!resolved) {\n console.warn('Unknown animation name: ' + animRef);\n }\n return resolved;\n }\n\n // It's an inline definition\n return animRef;\n}\n\n/**\n * Resolves an element animation (which can be string, array, or inline) to an array of AnimationDefinitions.\n * @param animate The element animation specification\n * @param defs The definitions containing named animations\n * @returns Array of resolved animation definitions\n */\nfunction resolveElementAnimation(\n animate: PxElementAnimation | undefined,\n defs?: PxDefinitions\n): PxAnimationDefinition[] {\n if (!animate) return [];\n\n const results: PxAnimationDefinition[] = [];\n\n if (typeof animate === 'string') {\n const resolved = resolveAnimation(animate, defs);\n if (resolved) results.push(resolved);\n } else if (Array.isArray(animate)) {\n for (const item of animate) {\n const resolved = resolveAnimation(item, defs);\n if (resolved) results.push(resolved);\n }\n } else {\n // It's an inline AnimationDefinition\n results.push(animate);\n }\n\n return results;\n}\n\n// ============================================================================\n// LOOP EXPANSION: Duplicate keyframe segments to fill gaps in the timeline\n// ============================================================================\n\n/**\n * Interpolates between two keyframe values based on property type.\n * Returns the raw interpolated value (not a CSS string).\n *\n * Dispatch order matters — the unified-transform-record branch must run\n * BEFORE the standalone-`rotate` branch, otherwise a `transform`-named\n * animation whose kfs are number-typed (rare but valid) would be routed\n * through the vec path.\n * @internal\n */\nexport function interpolateValue(propName: string, a: any, b: any, t: number): any {\n if (propName === 'd') {\n const aPaths = a?.paths ?? (Array.isArray(a) ? a : []);\n const bPaths = b?.paths ?? (Array.isArray(b) ? b : []);\n return { paths: interpolateBeziers(aPaths, bPaths, t) };\n }\n if (PX_COLOR_ATTR_NAMES.has(propName)) {\n return interpolateColor(a || [0, 0, 0, 1], b || [0, 0, 0, 1], t);\n }\n // Unified-transform record (`{translate, rotate, scale, origin}`) — the\n // most common animated `propName === 'transform'` shape. Per-part interp\n // so `+({}) = NaN` (the old fall-through) doesn't poison the boundary kf\n // at a loop seam.\n if (propName === 'transform'\n && typeof a === 'object' && a !== null && !Array.isArray(a)\n && typeof b === 'object' && b !== null && !Array.isArray(b)\n ) {\n return interpolateTransformParts(a as PxTransformParts, b as PxTransformParts, t);\n }\n // Standalone `rotate` as a propAnim — value is a NUMBER, not a vector.\n // The general PX_TRANSFORM_FN_NAMES branch below would route it through\n // `interpolateVec`, which on a scalar returns `[]` (length NaN → no loop)\n // — wrong CSS output.\n if (propName === 'rotate' && typeof a === 'number' && typeof b === 'number') {\n return interpolateNum(a, b, t);\n }\n if (PX_TRANSFORM_FN_NAMES.has(propName) || propName === 'stroke-dasharray' || propName === 'strokeDasharray') {\n return interpolateVec(a || [], b || [], t);\n }\n return interpolateNum(+(a || 0), +(b || 0), t);\n}\n\n/** Per-part interpolation for a unified-transform parts record. Mirrors the\n * inline logic in `calcPropertyValue`'s transform branch. */\nfunction interpolateTransformParts(a: PxTransformParts, b: PxTransformParts, t: number): PxTransformParts {\n const keys = new Set<string>([...Object.keys(a ?? {}), ...Object.keys(b ?? {})]);\n const out: { [k: string]: unknown } = {};\n for (const k of keys) {\n const av = (a as { [k: string]: unknown })?.[k];\n const bv = (b as { [k: string]: unknown })?.[k];\n if (k === 'rotate' || k === 'skew') {\n out[k] = interpolateNum(+(av ?? 0), +(bv ?? 0), t);\n } else if (k === 'translate' || k === 'scale' || k === 'origin') {\n const fallback: Array<number> = k === 'scale' ? [1, 1] : [0, 0];\n out[k] = interpolateVec((av as Array<number>) || fallback, (bv as Array<number>) || fallback, t);\n } else {\n // Unknown part — prefer next if present, otherwise prev.\n out[k] = bv ?? av;\n }\n }\n return out as PxTransformParts;\n}\n\ninterface LoopTemplateEntry {\n relT: number; // 0..1 relative position within segment\n v: any;\n e: [number, number, number, number] | undefined;\n // Per-vertex spatial tangents (motion-along-path). Carried so repeated\n // segments keep their curvature; reversed reps swap in<->out (see appendRep).\n tangentIn?: [number, number];\n tangentOut?: [number, number];\n}\n\n/**\n * Expands keyframes by repeating a segment to fill the gap between the keyframe\n * range and the global animation duration, implementing PxLoop \"local loop\" behavior.\n */\nfunction expandLoopKeyframes(\n propName: string,\n keyframes: PxNormalizedKeyframe[],\n loop: PxLoop,\n duration: number\n): PxNormalizedKeyframe[] {\n const totalIntervals = keyframes.length - 1;\n const segCount = clamp(loop.segmentCount ?? totalIntervals, 1, totalIntervals);\n\n // Extract segment keyframes\n let segKfs: PxNormalizedKeyframe[];\n if (loop.repeatAt === PxLoopRepeatAt.start) {\n segKfs = keyframes.slice(0, segCount + 1);\n } else {\n segKfs = keyframes.slice(totalIntervals - segCount);\n }\n\n // Determine fill region\n const firstT = keyframes[0].t ?? 0;\n const lastT = keyframes[keyframes.length - 1].t ?? 0;\n\n let fillStart: number, fillEnd: number;\n if (loop.repeatAt === PxLoopRepeatAt.start) {\n fillStart = 0;\n fillEnd = firstT;\n } else {\n fillStart = lastT;\n fillEnd = duration;\n }\n\n const fillDuration = fillEnd - fillStart;\n if (fillDuration <= 0) return keyframes;\n\n // Segment timing\n const segStartT = segKfs[0].t ?? 0;\n const segEndT = segKfs[segKfs.length - 1].t ?? 0;\n const segDuration = segEndT - segStartT;\n if (segDuration <= 0) return keyframes;\n\n // Build template with relative offsets (0..1)\n const template: LoopTemplateEntry[] = segKfs.map(kf => ({\n relT: (kf.t! - segStartT) / segDuration,\n v: kf.v,\n e: kf.e as [number, number, number, number] | undefined,\n tangentIn: keyframeTangentIn(kf) as [number, number] | undefined,\n tangentOut: keyframeTangentOut(kf) as [number, number] | undefined\n }));\n\n const fullReps = Math.floor(fillDuration / segDuration);\n const remainder = fillDuration - fullReps * segDuration;\n const partialFraction = remainder / segDuration;\n\n const looped: PxNormalizedKeyframe[] = [];\n\n // A cycle's first keyframe lands at the SAME time as the previous repetition's\n // last one. Emitting both at that time makes the value at that instant depend on\n // the sampler's tie-break, and left the player disagreeing with the editor (B7).\n // Mirror `TLoop.toKeyframes` exactly:\n // - values EQUAL (pingpong turn, closed loop) → the duplicate says nothing, skip it;\n // - values DIFFER (a real cycle snap) → separate them by PX_LOOP_JUMP_SHIFT_MS.\n // The editor's shift is one 10ms frame (`smallFrameShift`), so the two sides\n // materialize identical keyframes. Anything smaller is blocked editor-side: a\n // fractional-frame shift was tried there and reverted (TKeyframeGroup mishandles it).\n // SCOPE: loopOut (`after`) only — see the note above. For loopIn the pair sits in\n // the opposite order in the array, so separating it means moving the EARLIER\n // keyframe earlier; done naively it inverts keyframe order and re-breaks the\n // loopIn `f0` regression (`appendRepTail`). Left as-is until it has its own\n // editor-CSS evidence; the two sides may still differ at a loopIn boundary.\n const separateBoundary = loop.repeatAt !== PxLoopRepeatAt.start;\n // The keyframe the FIRST repetition butts up against: loopOut tiles forward from\n // the last original keyframe (the originals are concatenated only at assembly).\n const originalTerminalKf: PxNormalizedKeyframe | undefined = keyframes[keyframes.length - 1];\n\n // Easing that a skipped boundary keyframe hands to the ORIGINAL terminal keyframe.\n // Easing describes the interval that FOLLOWS a keyframe, so when a pingpong turn's\n // duplicate is dropped (same value, nothing to say about position) its easing is NOT\n // redundant — it owns the return leg. Without this hand-off the return leg renders\n // LINEAR while the outbound is eased. The originals belong to the caller, so it is\n // applied by replacing the terminal with a copy at assembly, never by mutation.\n let terminalEasingOverride: PxNormalizedKeyframe['e'] | undefined;\n let hasTerminalEasingOverride = false;\n\n // Helper: append one full or partial repetition\n function appendRep(repStart: number, isReversed: boolean, partial?: number) {\n let entries: LoopTemplateEntry[];\n if (isReversed) {\n // Reverse keyframe order and reverse easings\n entries = [];\n for (let i = template.length - 1; i >= 0; i--) {\n entries.push({\n relT: 1 - template[i].relT,\n v: template[i].v,\n // Easing for reversed transition: use reversed easing from the forward \"from\" keyframe\n e: i > 0 ? reverseEasing(template[i - 1].e) : undefined,\n // Reversed traversal swaps each vertex's in/out spatial tangents\n // (geometry is identical, walked backwards), so curvature and\n // auto-orientation survive the reversed rep.\n tangentIn: template[i].tangentOut,\n tangentOut: template[i].tangentIn\n });\n }\n } else {\n entries = template;\n }\n\n const cutRelT = partial !== undefined ? partial : 1;\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n if (entry.relT > cutRelT + 1e-9) {\n // Past the cut point — insert interpolated keyframe\n const prev = entries[i - 1];\n const intervalSpan = entry.relT - prev.relT;\n const localFrac = (cutRelT - prev.relT) / intervalSpan;\n\n // Apply easing to get the eased progress for value interpolation\n const easedFrac = prev.e ? cubicBezier(prev.e)(localFrac) : localFrac;\n const cutValue = interpolateValue(propName, prev.v, entry.v, easedFrac);\n\n // Split easing — use left portion for the truncated interval\n const { left: leftEasing } = splitEasing(prev.e, localFrac);\n\n // Update previous keyframe's easing to the left portion\n if (looped.length > 0 && prev.relT <= cutRelT) {\n looped[looped.length - 1].e = leftEasing;\n }\n\n looped.push({ t: repStart + cutRelT * segDuration, v: cutValue, e: undefined });\n return;\n }\n\n // Repetition boundary: this entry coincides in time with whatever precedes\n // it. For the FIRST rep that neighbour is the last ORIGINAL keyframe (the\n // originals are concatenated only at assembly time), not a `looped` entry.\n const prevKf = looped.length > 0 ? looped[looped.length - 1] : originalTerminalKf;\n const isBoundary = separateBoundary && i === 0 && prevKf !== undefined\n && Math.abs((prevKf.t ?? 0) - (repStart + entry.relT * segDuration)) < 1e-9;\n if (isBoundary) {\n if (deepEqualValue(prevKf.v, entry.v)) {\n // Pingpong turn — the duplicate says nothing about VALUE, but it carries\n // the easing (and out-tangent) for the interval that follows it. Hand\n // those to the surviving neighbour instead of discarding them.\n if (looped.length > 0) {\n prevKf.e = entry.e;\n prevKf.tangentOut = entry.tangentOut;\n } else {\n terminalEasingOverride = entry.e;\n hasTerminalEasingOverride = true;\n }\n continue;\n }\n // Real snap: the jump segment must not carry motion-path tangents.\n // Only ours are safe to mutate — never the caller's originals.\n if (looped.length > 0) { delete prevKf.tangentIn; delete prevKf.tangentOut; }\n }\n\n const pushed: PxNormalizedKeyframe = {\n t: repStart + entry.relT * segDuration + (isBoundary ? PX_LOOP_JUMP_SHIFT_MS : 0),\n v: entry.v,\n e: i < entries.length - 1 ? entry.e : undefined\n };\n // Carry per-vertex spatial tangents so the repeated segment keeps its\n // motion-path curvature (reversed reps already have in/out swapped above).\n if (entry.tangentIn) pushed.tangentIn = entry.tangentIn;\n if (entry.tangentOut) pushed.tangentOut = entry.tangentOut;\n looped.push(pushed);\n }\n }\n\n // Like {@link appendRep} but emits only the TAIL of the segment — relT ∈\n // [1-tailFraction, 1] — over [repStart, repStart + tailFraction*segDuration].\n // Used for the leftover (partial) rep of a `before` loop: it sits at fillStart\n // and runs UP TO the segment-end value, so the full reps after it align to end\n // exactly at the first original keyframe (firstT). A head-truncated partial here\n // (as appendRep does) would land the leftover ADJACENT to firstT and desync the\n // whole backward fill — the loopIn `f0` bug.\n function appendRepTail(repStart: number, isReversed: boolean, tailFraction: number) {\n let entries: LoopTemplateEntry[];\n if (isReversed) {\n entries = [];\n for (let i = template.length - 1; i >= 0; i--) {\n entries.push({\n relT: 1 - template[i].relT,\n v: template[i].v,\n e: i > 0 ? reverseEasing(template[i - 1].e) : undefined,\n tangentIn: template[i].tangentOut,\n tangentOut: template[i].tangentIn\n });\n }\n } else {\n entries = template;\n }\n\n const startRelT = 1 - tailFraction;\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i];\n if (entry.relT < startRelT - 1e-9) continue; // wholly before the tail window\n const prev = entries[i - 1];\n\n // If startRelT falls strictly inside (prev, entry), the tail begins\n // mid-interval — emit an interpolated keyframe at repStart carrying the\n // RIGHT split of prev's easing.\n if (prev && prev.relT < startRelT - 1e-9 && entry.relT > startRelT + 1e-9) {\n const intervalSpan = entry.relT - prev.relT;\n const localFrac = (startRelT - prev.relT) / intervalSpan;\n const easedFrac = prev.e ? cubicBezier(prev.e)(localFrac) : localFrac;\n const startValue = interpolateValue(propName, prev.v, entry.v, easedFrac);\n const { right: rightEasing } = splitEasing(prev.e, localFrac);\n looped.push({ t: repStart, v: startValue, e: rightEasing });\n }\n\n const pushed: PxNormalizedKeyframe = {\n t: repStart + (entry.relT - startRelT) * segDuration,\n v: entry.v,\n e: i < entries.length - 1 ? entry.e : undefined\n };\n if (entry.tangentIn) pushed.tangentIn = entry.tangentIn;\n if (entry.tangentOut) pushed.tangentOut = entry.tangentOut;\n looped.push(pushed);\n }\n }\n\n // Generate repetitions.\n // The rep closest to the original keyframes boundary must be reversed first\n // in pingpong mode (the animation just finished going forward, so the next\n // iteration goes backward).\n if (loop.repeatAt === PxLoopRepeatAt.start) {\n // loopIn — the fill must END exactly at the first keyframe (firstT), so the\n // reps tile BACKWARD from that boundary: the leftover (partial) rep sits at\n // fillStart showing the segment's tail, then `fullReps` full reps run up to\n // firstT. (Forward-tiling — as loopOut does — would push the partial next to\n // firstT and desync the fill: the loopIn `f0` regression.)\n if (partialFraction > 1e-9) {\n const isReversed = loop.direction === PxLoopDirection.alternate && (fullReps % 2 === 0);\n appendRepTail(fillStart, isReversed, partialFraction);\n }\n for (let rep = 0; rep < fullReps; rep++) {\n const distFromBoundary = fullReps - 1 - rep;\n const isReversed = loop.direction === PxLoopDirection.alternate && (distFromBoundary % 2 === 0);\n const repStart = fillStart + remainder + rep * segDuration;\n appendRep(repStart, isReversed);\n }\n } else {\n // loopOut — boundary is at fillStart (lastT); tile forward, partial at the end.\n for (let rep = 0; rep < fullReps; rep++) {\n const isReversed = loop.direction === PxLoopDirection.alternate && (rep % 2 === 0);\n const repStart = fillStart + rep * segDuration;\n appendRep(repStart, isReversed);\n }\n if (partialFraction > 1e-9) {\n const isReversed = loop.direction === PxLoopDirection.alternate && (fullReps % 2 === 0);\n const repStart = fillStart + fullReps * segDuration;\n appendRep(repStart, isReversed, partialFraction);\n }\n }\n\n // Assemble: looped keyframes go before or after the original keyframes.\n // No junction deduplication — cycle mode relies on value jumps at boundaries.\n if (loop.repeatAt === PxLoopRepeatAt.start) {\n return [...looped, ...keyframes];\n } else {\n if (hasTerminalEasingOverride && keyframes.length > 0) {\n const head = keyframes.slice(0, -1);\n const tail = { ...keyframes[keyframes.length - 1], e: terminalEasingOverride };\n return [...head, tail, ...looped];\n }\n return [...keyframes, ...looped];\n }\n}\n\n\n// ============================================================================\n// KEYFRAME NORMALIZATION\n// ============================================================================\n\n/**\n * Normalizes keyframes from the new API format (time in ms) to internal format (time as 0-1 fraction).\n * Resolves easing references, normalizes times, and converts path strings for 'd' attribute.\n * If a loop configuration is present, expands the keyframes to fill the global duration.\n * @param propName The property name (e.g., 'd' for path)\n * @param propAnim The property animation with keyframes\n * @param duration The total animation duration in ms\n * @param defs The definitions containing named easings\n * @returns Array of normalized keyframes (same structure, resolved refs, normalized times)\n */\nfunction normalizeKeyframes(\n propName: string,\n propAnim: PxPropertyAnimation,\n duration: number,\n defs?: PxDefinitions\n): PxNormalizedKeyframe[] {\n const keyframes = propAnim.keyframes || [];\n\n const normalized: PxNormalizedKeyframe[] = [];\n\n for (const kf of keyframes) {\n const timePct = keyframeTime(kf);\n let value = keyframeValue(kf);\n const easing = keyframeEasing(kf);\n\n // Normalize path values for 'd' attribute\n if (propName === 'd') {\n value = normalizePathValue(value);\n }\n\n // Normalize color values (hex/rgb/rgba strings to [0-1] vectors).\n // `PX_COLOR_ATTR_NAMES` is keyed in kebab-case (`stop-color`, `flood-color`,\n // `lighting-color`); the wire format uses both kebab AND camelCase\n // (`stopColor`) for these props. Without converting, frames-mode would\n // call `interpolateColor` on the raw strings and produce `NaN` channels\n // — the visible \"rgba(NaN,NaN,NaN,…)\" bug on stop-color animations.\n const propNameKebab = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;\n if (PX_COLOR_ATTR_NAMES.has(propNameKebab)) {\n value = parseColor(value) ?? value;\n }\n\n const normKf: PxNormalizedKeyframe = {\n t: timePct,\n v: value,\n e: resolveEasing(easing, defs)\n };\n // Motion-along-path: keep the spatial tangents so the downstream\n // `materializeMotionPathInPropAnim` (called in `normalizeAnimationDefinition`) can\n // sample them into transform kfs. Short aliases `ti` / `to` collapse\n // into their canonical names.\n const tIn = keyframeTangentIn(kf);\n const tOut = keyframeTangentOut(kf);\n if (tIn) normKf.tangentIn = tIn;\n if (tOut) normKf.tangentOut = tOut;\n\n normalized.push(normKf);\n }\n\n // Sort by time\n normalized.sort((a, b) => (a.t ?? 0) - (b.t ?? 0));\n\n // Expand loop if configured (loop:true is shorthand for default PxLoop)\n const loopRaw = propAnim.loop;\n const loop: PxLoop | undefined = loopRaw === true ? {} : loopRaw || undefined;\n if (loop && normalized.length >= 2) {\n return expandLoopKeyframes(propName, normalized, loop, duration);\n }\n\n return normalized;\n}\n\n/**\n * Merges multiple animation definitions into a single combined definition.\n * Later definitions override earlier ones for the same property.\n */\nfunction mergeAnimationDefinitions(\n animations: PxAnimationDefinition[]\n): PxAnimationDefinition {\n const merged: PxAnimationDefinition = {};\n\n for (const anim of animations) {\n for (const [prop, propAnim] of Object.entries(anim)) {\n merged[prop] = propAnim;\n }\n }\n\n return merged;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public loop-materialization API\n//\n// Used internally by `normalizeKeyframes` (where `expandLoopKeyframes` is\n// already called at the tail of normalization). Exposed here at the propAnim\n// and tree levels so the Editor (or any external caller) can compose:\n// root = materializeNodeEffects(root).root;\n// root = materializeInternalLoopsInTree(root, duration);\n// root = materializeMotionPathsInTree(root);\n// to produce a fully-flat document with no `loop`, no `effects`, no tangents.\n// ─────────────────────────────────────────────────────────────────────────────\n\n\n/**\n * Replaces `propAnim.loop` with explicit repeated keyframes via\n * `expandLoopKeyframes`. Returns the input by reference when no loop is\n * configured (no-op). Output drops the `loop` field (consumed).\n * @internal\n */\nexport function materializeInternalLoopsInPropAnim(\n propName: string,\n propAnim: PxPropertyAnimation,\n duration: number,\n): PxPropertyAnimation {\n const loopRaw = propAnim.loop;\n if (loopRaw === undefined || loopRaw === null || loopRaw === false) return propAnim;\n const loop: PxLoop = loopRaw === true ? {} : (loopRaw as PxLoop);\n const rawKfs = propAnim.keyframes as PxKeyframe[] | undefined;\n if (!Array.isArray(rawKfs) || rawKfs.length < 2) return propAnim;\n\n // `expandLoopKeyframes` reads kf.t / kf.v / kf.e (short form). When this\n // function is called from the materialization pipeline OUTSIDE the\n // binding-normalization path (e.g. by `materializeAllInTree`), the input\n // kfs may still be in long form (`time` / `value` / `easing`) AND the\n // values may be unparsed (hex color strings, raw path-`d`). Normalize\n // both here so `interpolateValue` (called by `expandLoopKeyframes` at the\n // loop seam) sees structured data — without this, a color boundary kf\n // ends up `[NaN,NaN,NaN,NaN]` and the bug stays visible until the\n // animation cycle restarts.\n const propNameKebab = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;\n const isColor = PX_COLOR_ATTR_NAMES.has(propNameKebab);\n const kfs: PxNormalizedKeyframe[] = rawKfs.map(kf => {\n const t = keyframeTime(kf);\n let v: unknown = keyframeValue(kf);\n if (propName === 'd') v = normalizePathValue(v);\n if (isColor) v = parseColor(v) ?? v;\n const e = keyframeEasing(kf);\n const out: PxNormalizedKeyframe = { t, v, e };\n const tIn = keyframeTangentIn(kf);\n const tOut = keyframeTangentOut(kf);\n if (tIn) out.tangentIn = tIn;\n if (tOut) out.tangentOut = tOut;\n return out;\n });\n\n const expanded = expandLoopKeyframes(propName, kfs, loop, duration);\n const out: PxPropertyAnimation = { keyframes: expanded };\n if (propAnim.autoOrient !== undefined) (out as { autoOrient?: unknown }).autoOrient = propAnim.autoOrient;\n return out;\n}\n\n\n/**\n * Walks `root` and materializes every animated property's `loop` via\n * `materializeInternalLoopsInPropAnim`. Immutable — returns the input by\n * reference when no loop was found anywhere; otherwise clones along the path\n * to each affected node, sharing untouched sub-trees.\n * @internal\n */\nexport function materializeInternalLoopsInTree(\n root: PxNode,\n duration: number,\n): PxNode {\n const ret = walkAndMaterializeLoops(root, duration);\n return ret ?? root;\n}\n\nfunction walkAndMaterializeLoops(node: PxNode, duration: number): PxNode | null {\n let newChildren: Array<PxNode> | undefined;\n if (node.children) {\n for (let i = 0; i < node.children.length; i++) {\n const ret = walkAndMaterializeLoops(node.children[i], duration);\n if (ret !== null) {\n if (!newChildren) newChildren = node.children.slice();\n newChildren[i] = ret;\n }\n }\n }\n let newAnimate: Record<string, PxPropertyAnimation> | undefined;\n const animBucket = node.animate;\n if (animBucket && typeof animBucket === 'object' && !Array.isArray(animBucket)) {\n const animDef = animBucket as Record<string, PxPropertyAnimation>;\n for (const propName of Object.keys(animDef)) {\n const propAnim = animDef[propName];\n const materialized = materializeInternalLoopsInPropAnim(propName, propAnim, duration);\n if (materialized !== propAnim) {\n if (!newAnimate) newAnimate = { ...animDef };\n newAnimate[propName] = materialized;\n }\n }\n }\n if (!newChildren && !newAnimate) return null;\n const cloned: PxNode = { ...node };\n if (newChildren) cloned.children = newChildren;\n if (newAnimate) cloned.animate = newAnimate as PxNode['animate'];\n return cloned;\n}\n\n\n/**\n * Generates a unique element ID for internal tracking during DOM rendering.\n */\nlet _elementIdCounter = 0;\nexport function generateElementId(): string {\n return '_px_el_' + (++_elementIdCounter);\n}\n\n/**\n * Resets the element ID counter (useful for testing).\n */\nexport function resetElementIdCounter(): void {\n _elementIdCounter = 0;\n}\n\n/**\n * TRANSFORM PRECEDENCE (review §0.4/§1.6) — CSS's own composition rule, applied at READ:\n * a static `transform` on the element composes UNDER the animated transform, instead of\n * being silently clobbered by it. Implemented as a keyframe-value MERGE during\n * normalization, so both engines (and every consumer downstream) see complete parts:\n *\n * 1. `animate.transform` with PARTIAL parts records — every keyframe value (and the\n * base `value`) inherits the static parts it does not set:\n * static `{rotate: 45}` + kf `{translate: [80, 0]}` → kf `{rotate: 45, translate: [80, 0]}`.\n * 2. ONE individual channel (`translate` / `rotate` / `scale` / `skew`) and no\n * `transform` channel — the channel is REWRITTEN as a unified `transform` channel\n * whose values carry the static parts: the rect stays rotated 45° AND slides.\n *\n * The static transform may be a parts record or an attribute string (parsed by the\n * conservative {@link parseTransformParts} — unparseable strings skip the merge).\n * NOT merged (documented limitations): several individual channels animated at once\n * (they still last-write-wins against each other), and an individual channel next to an\n * animated `transform` (the `transform` channel wins, as before).\n * @internal\n */\nexport function mergeStaticTransformIntoAnimDef(\n animDef: PxAnimationDefinition,\n staticTransform: unknown,\n): PxAnimationDefinition {\n if (!animDef) return animDef;\n const staticParts: PxTransformParts | undefined =\n staticTransform && typeof staticTransform === 'object' && !Array.isArray(staticTransform)\n ? staticTransform as PxTransformParts\n : parseTransformParts(staticTransform as string);\n if (!staticParts || !Object.keys(staticParts).length) return animDef;\n\n const mergeKfValue = (v: unknown): unknown =>\n v && typeof v === 'object' && !Array.isArray(v) ? { ...staticParts, ...(v as PxTransformParts) } : v;\n\n const transformAnim = animDef[TRANSFORM_ATTR];\n if (transformAnim && typeof transformAnim === 'object') {\n const anim = transformAnim as PxPropertyAnimation;\n if (Array.isArray(anim.keyframes)) {\n const out: PxPropertyAnimation = {\n ...anim,\n keyframes: anim.keyframes.map(kf => ({ ...kf, value: mergeKfValue(kf.value) })),\n };\n if (out.value !== undefined) out.value = mergeKfValue(out.value) as PxPropertyAnimation['value'];\n return { ...animDef, transform: out };\n }\n return animDef;\n }\n\n const channels = Object.keys(animDef).filter(k => PX_TRANSFORM_FN_NAMES.has(k));\n if (channels.length !== 1) return animDef; // several channels: unchanged (documented)\n const ch = channels[0];\n const chAnim = animDef[ch] as PxPropertyAnimation;\n if (!chAnim || typeof chAnim !== 'object' || !Array.isArray(chAnim.keyframes)) return animDef;\n const lifted: PxPropertyAnimation = {\n ...chAnim,\n keyframes: chAnim.keyframes.map(kf => ({ ...kf, value: { ...staticParts, [ch]: kf.value } })),\n };\n if (lifted.value !== undefined) lifted.value = { ...staticParts, [ch]: lifted.value };\n const rest: PxAnimationDefinition = { ...animDef };\n delete rest[ch];\n return { ...rest, transform: lifted };\n}\n\n/**\n * Normalizes an animation definition by resolving easing references and normalizing keyframe times.\n * Keeps the key/value mapping structure. `engine` controls motion-along-path\n * handling — see {@link PxTimelineEngine}.\n */\nfunction normalizeAnimationDefinition(\n animDef: PxAnimationDefinition,\n duration: number,\n defs?: PxDefinitions,\n engine: PxTimelineEngine = PxTimelineEngine.native,\n): PxAnimationDefinition {\n const normalized: PxAnimationDefinition = {};\n\n for (const [propName, propAnim] of Object.entries(animDef)) {\n // `alongPathMode: 'offsetPath'` — this transform's motion is rendered by CSS\n // Motion Path (`offset-path` style on the element + an `offsetDistance` track in\n // the same dict). The tangented keyframes stay on the wire as the DESIGN source\n // (the editor round-trip reads them back), but the player must not ALSO drive\n // them: doing both moved the element to the path position and then translated it\n // again (double position). Skip the binding; `offsetDistance` owns the motion.\n if (propName === 'transform'\n && (propAnim as { alongPathMode?: string }).alongPathMode === 'offsetPath'\n // …but ONLY when the offset infrastructure actually exists (an `offsetDistance`\n // track in the same definition — the pre-rendered dict shape, or a lightweight\n // doc the offset materializer rewrote). A marked transform the materializer\n // BAILED on (e.g. rotate animated in the same keyframes — inexpressible as\n // offset-path) must fall through to the ordinary sampled pipeline; skipping it\n // unconditionally froze those elements at their base pose.\n && (animDef as Record<string, unknown>)['offsetDistance'] !== undefined) {\n continue;\n }\n const normalizedKfs = normalizeKeyframes(propName, propAnim, duration, defs);\n if (normalizedKfs.length > 0) {\n // Internal normalized form converges on `keyframes` too — the `kfs` alias\n // is gone from the format AND the runtime (review §1.2/§6.1).\n const out: PxPropertyAnimation = { keyframes: normalizedKfs };\n // Carry top-level animation flags through normalization — `materializeMotionPathInPropAnim`\n // and the runtime evaluators need `autoOrient` / `loop` to be present\n // alongside the kfs.\n if (propAnim.autoOrient !== undefined) out.autoOrient = propAnim.autoOrient;\n if (propAnim.loop !== undefined) out.loop = propAnim.loop;\n\n // Pipeline: loops are already expanded by `normalizeKeyframes` above.\n // For the `waapi` engine ONLY, materialize motion-along-path\n // (tangented `transform` kfs + autoOrient) into plain sampled\n // `{ translate, rotate? }` kfs — CSS WAAPI can't evaluate parametric\n // tangents at runtime. Frames-mode keeps the parametric form so the\n // frame-loop kernel `evaluateMotionPathSegment` can sample per frame\n // (better spatial fidelity than any finite sampling).\n // `materializeMotionPathInPropAnim` is a no-op for non-motion-path\n // animations, so non-transform props pay zero cost.\n normalized[propName] = (engine === PxTimelineEngine.native && propName === 'transform')\n ? materializeMotionPathInPropAnim(out)\n : out;\n }\n }\n\n return normalized;\n}\n\n/**\n * Normalizes a PxAnimatedSvgDocument to a PxAnimatorConfig for the animation engines.\n * This is the main entry point for converting the new API format to internal format.\n * Resolves animation/easing references. `engine` controls motion-along-path\n * handling — see {@link PxTimelineEngine}.\n * @public @advanced\n */\nexport function normalizeBindings(\n doc: PxAnimatedSvgDocument,\n engine: PxTimelineEngine = PxTimelineEngine.native,\n): PxNormalizedBinding[] {\n const animatorConfig = getAnimatorConfig(doc) || {};\n const defs = getDefinitions(doc);\n const duration = animatorConfig.duration || 1000; // FIXME - get rid of 1000 here\n\n const bindings: PxNormalizedBinding[] = [];\n\n // Helper to merge and normalize the resolved animation definitions of one element\n const processAnimation = (\n id: string,\n animDefs: PxAnimationDefinition[],\n staticTransform?: unknown,\n ): PxNormalizedBinding | null => {\n if (animDefs.length === 0) return null;\n\n // CSS transform precedence (review §0.4/§1.6): the node's static transform\n // composes under the animated one — merged BEFORE normalization so both\n // engines see complete parts records.\n const merged = mergeStaticTransformIntoAnimDef(mergeAnimationDefinitions(animDefs), staticTransform);\n const normalizedAnim = normalizeAnimationDefinition(merged, duration, defs, engine);\n\n if (Object.keys(normalizedAnim).length === 0) return null;\n\n return {\n id,\n animate: normalizedAnim\n };\n };\n\n // Process bindings (for pre-rendered DOM): `target` is `#id`-spelled on the wire (every\n // element reference carries the hash — review §3.2); the engines get the bare DOM id.\n const docBindings = getBindings(doc);\n if (docBindings) {\n for (const binding of docBindings) {\n const id = binding.target.startsWith('#') ? binding.target.slice(1) : binding.target;\n const animDefs = binding.animateWith\n .map(name => resolveAnimation(name, defs))\n .filter((d): d is PxAnimationDefinition => !!d);\n const normalized = processAnimation(id, animDefs);\n if (normalized) bindings.push(normalized);\n }\n }\n\n // Process children (for rendered DOM).\n //\n // Per-element animations live under the node's `animate` bucket, keyed by\n // SVG/CSS property name — a PxAnimationDefinition (`{ transform: {keyframes},\n // fill: {keyframes}, … }`). The static initial value of each animated\n // property is carried separately as a plain attribute on the element body.\n // On-disk locations: top-level `node.animate` (JSON form).\n const processNode = (node: PxNode) => {\n const inlineAnim = node.animate;\n if (inlineAnim && Object.keys(inlineAnim).length > 0) {\n const nodeId = node.id || generateElementId();\n node.id = nodeId; // Ensure the node has an ID\n const normalized = processAnimation(nodeId, resolveElementAnimation(inlineAnim, defs), node.transform);\n if (normalized) bindings.push(normalized);\n }\n\n // Process children\n if (node.children) {\n for (let i = 0; i < node.children.length; i++) {\n processNode(node.children[i]);\n }\n }\n };\n\n // Process children of the root\n if (doc.children) {\n for (let i = 0; i < doc.children.length; i++) {\n processNode(doc.children[i]);\n }\n }\n\n return bindings;\n}\n\n\n// ============================================================================\n// ATTRIBUTE VALUE CALCULATION (used by frame loop animator)\n// ============================================================================\n\n/**\n * Finds prev/next keyframes for a given progress.\n */\nfunction getKeyframesPair(keyframes: PxNormalizedKeyframe[], progress: number) {\n // Outside the keyframe range, clamp to the NEAREST REAL segment (the caller clamps\n // `localProgress` to 0/1, so that holds the boundary pose). Spanning first→last\n // instead would invent a segment that exists nowhere in the animation: for keyframes\n // that start part-way into the timeline, the pre-start frames used to interpolate\n // straight from the first to the LAST keyframe — skipping everything between, and\n // handing motion-path evaluation a chord it then cached (see `getSegmentCache`).\n const last = keyframes.length - 1;\n let prevKf = keyframes[0];\n let nextKf = keyframes[last > 0 ? 1 : 0];\n\n for (let j = 0; j < last; j++) {\n const aOff = (keyframes[j].t ?? 0);\n const bOff = (keyframes[j + 1].t ?? 0);\n if (aOff <= progress && progress <= bOff) {\n prevKf = keyframes[j];\n nextKf = keyframes[j + 1];\n break;\n }\n // Past this segment and not bracketed by any later one → hold the final segment.\n if (progress > bOff && j === last - 1) {\n prevKf = keyframes[last > 0 ? last - 1 : 0];\n nextKf = keyframes[last];\n }\n }\n return { prevKf, nextKf };\n}\n\n/**\n * Calculates interpolated value for a single property animation.\n */\nfunction calcPropertyValue(\n propName: string,\n propAnim: PxPropertyAnimation,\n progress: number\n): { k: string, v: string } | null {\n const keyframes = propAnim.keyframes || [];\n if (keyframes.length === 0) return null;\n\n const { prevKf, nextKf } = getKeyframesPair(keyframes, progress);\n\n // remap to local 0..1 within prevKf..nextKf\n let localProgress = (prevKf === nextKf) ? 0 : remap(progress, prevKf.t ?? 0, nextKf.t ?? 0, 0, 1);\n localProgress = clamp(localProgress, 0, 1);\n const easing = keyframeEasing(prevKf); // e is on the source keyframe: applied from this KF to the next\n if (easing && Array.isArray(easing)) {\n try {\n localProgress = cubicBezier(easing as [number, number, number, number])(localProgress);\n } catch (e) {\n // fallback: ignore easing if parsing fails\n }\n }\n\n let cssAttrName = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;\n let cssValue: string | number | null = null;\n\n const prevV = prevKf?.v;\n const nextV = nextKf?.v;\n\n if (cssAttrName === 'd') {\n // Extract paths from { paths: [...] } format\n const prevPaths = prevV?.paths ?? (Array.isArray(prevV) ? prevV : []);\n const nextPaths = nextV?.paths ?? (Array.isArray(nextV) ? nextV : []);\n cssValue = interpolateBeziers(\n prevPaths,\n nextPaths,\n localProgress\n ).map(bz => bezierToSvgPath(bz)).join('');\n } else if (PX_COLOR_ATTR_NAMES.has(cssAttrName)) {\n cssValue = toRGBA(interpolateColor(\n prevV || [0, 0, 0, 1],\n nextV || [0, 0, 0, 1],\n localProgress\n ));\n cssAttrName = propName;\n } else if (cssAttrName === 'stroke-dasharray') {\n cssValue = interpolateVec(\n prevV || [],\n nextV || [],\n localProgress\n ).join(' ');\n cssAttrName = propName;\n } else if (\n cssAttrName === 'transform' &&\n prevV !== null && typeof prevV === 'object' && !Array.isArray(prevV)\n ) {\n // Unified transform: keyframe values are PxTransformParts records.\n // Interpolate each present part separately, then compose into a transform\n // string for the SVG `transform` attribute (no units).\n const partKeys = new Set<string>([\n ...(prevV ? Object.keys(prevV) : []),\n ...(nextV ? Object.keys(nextV) : []),\n ]);\n const partsResult: PxTransformParts = {};\n for (const partKey of partKeys) {\n const prevPart = prevV?.[partKey];\n const nextPart = nextV?.[partKey];\n if (partKey === 'rotate' || partKey === 'skew') {\n partsResult[partKey] = interpolateNum(+(prevPart ?? 0), +(nextPart ?? 0), localProgress);\n } else if (partKey === 'translate' || partKey === 'scale' || partKey === 'origin') {\n const fallback = partKey === 'scale' ? [1, 1] : [0, 0];\n const interp = interpolateVec(prevPart || fallback, nextPart || fallback, localProgress);\n (partsResult as any)[partKey] = interp;\n }\n }\n // Motion-along-path override (frames-mode only). The WAAPI binding\n // pipeline materializes tangents + autoOrient into sampled\n // `{translate, rotate}` kfs upstream (in `normalizeAnimationDefinition`),\n // so for WAAPI this branch is a no-op (`propAnimIsMotionPath` returns\n // false on already-materialized kfs). Frames-mode preserves the\n // parametric form and we evaluate the Bezier per frame here — gives\n // exact spatial fidelity vs. any finite sampling.\n if (propAnimIsMotionPath(propAnim)) {\n const prevTr = (prevV as { translate?: [number, number] }).translate;\n const nextTr = (nextV as { translate?: [number, number] }).translate;\n if (Array.isArray(prevTr) && Array.isArray(nextTr)) {\n const sample = evaluateMotionPathSegment(\n prevKf, nextKf,\n [+prevTr[0], +prevTr[1]],\n [+nextTr[0], +nextTr[1]],\n localProgress,\n !!propAnim.autoOrient,\n );\n partsResult.translate = [sample.translate[0], sample.translate[1]];\n if (sample.rotateDeg !== undefined) partsResult.rotate = sample.rotateDeg;\n }\n }\n cssValue = composeTransformParts(partsResult, { withUnits: false });\n cssAttrName = 'transform';\n } else if (cssAttrName === 'translate') {\n const v = interpolateVec(\n prevV || [0, 0],\n nextV || [0, 0],\n localProgress\n );\n cssValue = 'translate(' + v.join(',') + ')';\n cssAttrName = 'transform';\n } else if (cssAttrName === 'rotate') {\n const v = interpolateNum(\n +(prevV || 0),\n +(nextV || 0),\n localProgress\n );\n cssValue = 'rotate(' + v + ')';\n cssAttrName = 'transform';\n } else if (cssAttrName === 'scale') {\n const v = interpolateVec(\n prevV || [1, 1],\n nextV || [1, 1],\n localProgress\n );\n cssValue = 'scale(' + v.join(',') + ')';\n cssAttrName = 'transform';\n } else {\n // numeric attr\n const num = interpolateNum(\n +(prevV || 0),\n +(nextV || 0),\n localProgress\n );\n cssValue = num;\n }\n\n if (PX_PCT_BASED_ATTR_NAMES.has(cssAttrName) && typeof cssValue === 'number') {\n cssValue = (cssValue * 100) + '%';\n }\n\n return { k: cssAttrName, v: cssValue === null ? '' : '' + cssValue };\n}\n\n/**\n * Calculates interpolated attribute values for an animation definition.\n * @param animDef The animation definition (with resolved refs and normalized times)\n * @param progress The current animation progress (0-1)\n * @returns Object with computed attribute name/value pairs\n * @public @advanced\n */\nexport function calcAnimationValues(\n animDef: PxAnimationDefinition,\n progress: number\n): Record<string, string> {\n const result: Record<string, string> = {};\n\n for (const [propName, propAnim] of Object.entries(animDef)) { \n const computed = calcPropertyValue(propName, propAnim, progress);\n if (computed) {\n result[computed.k] = computed.v;\n }\n }\n\n return result;\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n// ONE time contract for every engine (API review §3). The same three calls used to mean three\n// different things:\n//\n// | engine | getCurrentTime() | setCurrentTime(t) | setPlaybackRate(0) |\n// |-----------------|-------------------------|-------------------|--------------------|\n// | web, WAAPI | ms across the whole run | NOT clamped | accepted |\n// | web, frame loop | ms across the whole run | clamped | rejected + warn |\n// | React Native | ms within ONE iteration | wrapped | rejected + warn |\n//\n// So a slider built on `getCurrentTime()` jumped back every iteration on React Native, and\n// `timeline.engine: 'auto'` meant two documents on one page could answer the same call\n// differently. The contract every player now implements:\n//\n// - time is ms from the start of the WHOLE run, iterations included — never per-iteration;\n// - a seek clamps to [0, seekCeilingMs];\n// - a rate of 0 is rejected everywhere, with this one message.\n\n/** The one message every engine prints for a rejected rate. @public @advanced */\nexport const PX_RATE_REJECTED = 'setPlaybackRate: rate must be finite and non-zero';\n\n/**\n * A playback rate is usable when it is finite and non-zero.\n *\n * 0 is rejected rather than accepted: it freezes the animation in a state indistinguishable\n * from a stuck player, and `pause()` already says that properly. Two of the three engines\n * rejected it already — this makes the third agree.\n * @public @advanced\n */\nexport function isValidPlaybackRate(rate: number): boolean {\n return Number.isFinite(rate) && rate !== 0;\n}\n\n/**\n * Highest seekable time, ms — `duration × iterations`.\n *\n * `Infinity` for an endless timeline, so callers must test `Number.isFinite` before using it\n * as an upper bound. This is the SEEK ceiling, which is deliberately not the same thing as the\n * span `progress` covers — see `progressSpanMs`.\n * @public @advanced\n */\nexport function seekCeilingMs(durationMs: number, iterations: number): number {\n if (!(durationMs > 0)) return 0;\n if (iterations === Infinity) return Infinity;\n return durationMs * (iterations > 0 ? iterations : 1);\n}\n\n/**\n * The span `progress` 0→1 covers, ms.\n *\n * An endless timeline maps progress onto ONE iteration — the rule the components already\n * document for the `progress` prop (\"0–1 of duration × iterations, one iteration when\n * iterations is 'infinite'\"). Always finite, so it is safe as a divisor.\n * @public @advanced\n */\nexport function progressSpanMs(durationMs: number, iterations: number): number {\n if (!(durationMs > 0)) return 0;\n if (iterations === Infinity) return durationMs;\n return durationMs * (iterations > 0 ? iterations : 1);\n}\n\n/**\n * Clamps a seek into [0, ceiling].\n *\n * `NaN` and anything below 0 land on 0. `Infinity` means \"the end\", so it clamps to the ceiling\n * like any other overshoot — except on an endless timeline, where there is no end to land on and\n * a non-finite playhead would poison every later read, so that reads as 0.\n * @public @advanced\n */\nexport function clampSeekMs(timeMs: number, ceilingMs: number): number {\n if (Number.isNaN(timeMs) || timeMs < 0) return 0;\n if (Number.isFinite(ceilingMs)) return timeMs > ceilingMs ? ceilingMs : timeMs;\n return Number.isFinite(timeMs) ? timeMs : 0;\n}\n\n/**\n * Whole-run ms → 0–1.\n *\n * A finite timeline clamps at both ends. An endless one wraps within the current iteration,\n * so the value stays meaningful however long it has been running. A zero-length span reads as\n * 0 rather than NaN.\n * @public @advanced\n */\nexport function timeToProgress(timeMs: number, durationMs: number, iterations: number): number {\n const span = progressSpanMs(durationMs, iterations);\n if (!(span > 0) || !Number.isFinite(timeMs)) return 0;\n if (timeMs <= 0) return 0;\n if (iterations === Infinity) return (timeMs % span) / span;\n return timeMs >= span ? 1 : timeMs / span;\n}\n\n/** 0–1 → whole-run ms, clamped into the span. A non-finite progress reads as 0. @public @advanced */\nexport function progressToTimeMs(progress: number, durationMs: number, iterations: number): number {\n const span = progressSpanMs(durationMs, iterations);\n if (!(span > 0) || !Number.isFinite(progress)) return 0;\n if (progress <= 0) return 0;\n return progress >= 1 ? span : progress * span;\n}\n\n/** A playhead that keeps whole-run time across iterations. See `createRunClock`. @public @advanced */\nexport interface PxRunClock {\n /** Whole-run position right now, ms, clamped to the ceiling. */\n now(): number;\n /** True while time is advancing. */\n isRunning(): boolean;\n /** Start or resume at `rate`, optionally from a given whole-run time. */\n start(rate: number, atMs?: number): void;\n /** Stop advancing, freezing the current position. */\n stop(): void;\n /** Jump to a whole-run time; keeps running if it already was. */\n seek(ms: number): void;\n}\n\n/**\n * A whole-run playhead that survives repetition.\n *\n * React Native drives playback with Reanimated's `withRepeat`, whose shared value only ever\n * holds the position WITHIN one iteration and which never reports how many iterations have\n * elapsed. Whole-run time therefore cannot be recovered from it: under `alternate` the value\n * runs backwards rather than wrapping, which is indistinguishable from a negative rate. So the\n * time is kept on a clock of its own — the same thing the frame-loop engine does inline.\n *\n * `nowFn` is injectable so this is testable without real time passing.\n * @public @advanced\n */\nexport function createRunClock(ceilingMs: number, nowFn: () => number = Date.now): PxRunClock {\n let baseMs = 0; // whole-run ms as of the last start/seek/stop\n let startedAt = 0; // nowFn() when running began\n let running = false; // a FLAG, not `startedAt !== 0`: a time source may legitimately\n let rate = 1; // read 0, and `Date.now()` never does — so the bug would hide.\n\n const value = (): number => running\n ? clampSeekMs(baseMs + (nowFn() - startedAt) * rate, ceilingMs)\n : clampSeekMs(baseMs, ceilingMs);\n\n return {\n now: value,\n isRunning: () => running,\n start: (r: number, atMs?: number) => {\n baseMs = clampSeekMs(atMs ?? value(), ceilingMs);\n rate = isValidPlaybackRate(r) ? r : 1;\n startedAt = nowFn();\n running = true;\n },\n stop: () => {\n baseMs = value();\n startedAt = 0;\n running = false;\n },\n seek: (ms: number) => {\n baseMs = clampSeekMs(ms, ceilingMs);\n if (running) startedAt = nowFn();\n },\n };\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n// ONE diagnostics channel for every player (API review §5, §25.1).\n//\n// Before this, only React Native had an error channel (`onError` + `fallback`). The web player\n// sent a failed fetch or an invalid document to `console.error` and then answered\n// `isReady() === false` for ever, and React and Vue could not offer anything at all because\n// the engine callbacks had no slot for it. Everything else — validation warnings, config\n// overrides, unsupported attributes — went straight to `console.warn`, where an embedding app\n// could neither see it nor quiet it.\n//\n// THE RULE — two severities, one meaning each, on every player:\n//\n// - `onError`: THIS INSTANCE WILL NOT PLAY. Nothing is rendered, `isReady()` stays false,\n// the component shows its `fallback`. The player reports it and stays inert rather than\n// throwing at the caller — a throw would land in a fetch callback or a render, where no\n// one can catch it. (A wrong CALL — `createAnimator()` with neither `src` nor `doc` — still\n// throws: that is a bug at the call site, found the moment the line runs.)\n// - `onWarn`: IT PLAYS, but something was ignored, degraded or misspelled — an unknown\n// easing, an override that could not apply, an attribute the platform will not animate.\n//\n// - a handler takes over from the console: give `onWarn` / `onError` and the console stays\n// out of it; give none and the console is the fallback, so nothing is lost by default;\n// - `muteWarn` / `muteError` switch that console fallback off — for a host that knows\n// about the warnings and is prepared to tolerate them. A handler you passed still fires.\n//\n// Severity (warn vs error) and SOURCE (`kind`) are separate axes on purpose: an invalid document\n// is a document problem AND fatal, while an effects-shape warning is a document problem that\n// still plays. Splitting the callbacks by source would have produced four handlers and forced\n// anyone who just wants everything to wire all of them.\n//\n// THE TEXT IS NOT SHIPPED. A diagnostic carries a NUMBER (`PxDiagnosticCode`) and the values the\n// site had (`data`); the words live in docs/diagnostics.md, generated from the enum's comments,\n// and every diagnostic links to it. See PxDiagnosticCode.ts for why, and for the rules on\n// adding one.\n\nimport type { PxDiagnosticCode } from './PxDiagnosticCode';\n\n/**\n * Who can do something about a diagnostic.\n *\n * A const object rather than a TypeScript `enum`: consumers compare against these values, so\n * they must accept plain string literals too (the same pattern as `PxControlMode`).\n * @public\n */\nexport const PxDiagnosticKind = {\n /** The document is wrong — regenerate or repair the file. */\n document: 'document',\n /** The page or app cannot provide what the document asks for — fix the mount. */\n host: 'host',\n /** The platform cannot do it and the player degraded — usually nothing to fix. */\n platform: 'platform',\n /** The call is wrong or self-contradictory — fix the options or props you passed. */\n usage: 'usage',\n /** The player failed where it did not expect to — report it to us. */\n internal: 'internal',\n} as const;\nexport type PxDiagnosticKind = typeof PxDiagnosticKind[keyof typeof PxDiagnosticKind];\n\n/** Where the words behind a code live. One string, shared by every diagnostic. */\nconst DOCS_URL = 'https://github.com/pixodesk/pixodesk-svg-animator/blob/main/docs/diagnostics.md';\n\n/** `PX1204 https://…/diagnostics.md#px1204` — the code, and where to read what it means. */\nfunction codeLine(code: PxDiagnosticCode): string {\n return 'PX' + code + ' ' + DOCS_URL + '#px' + code;\n}\n\n/**\n * One thing a player has to say.\n *\n * The TEXT is not here, and is not in the bundle: `code` identifies the diagnostic and\n * {@link https://github.com/pixodesk/pixodesk-svg-animator/blob/main/docs/diagnostics.md the\n * codes page} carries the description. That is the trade this library makes — a smaller\n * download, and a stable number you can switch on instead of matching a sentence that may be\n * reworded. The specifics are in `data`.\n * @public\n */\nexport interface PxDiagnostic {\n /** WHICH diagnostic this is — a permanent number; look it up on the codes page. */\n readonly code: PxDiagnosticCode;\n /** Who can act on it — see {@link PxDiagnosticKind}. */\n readonly kind: PxDiagnosticKind;\n /**\n * What the site had to hand, in the order the code's `@data` lists: a selector, a URL, the\n * offending binding, an inner problem. Everything a sentence would have interpolated.\n */\n readonly data?: ReadonlyArray<unknown>;\n /** The code and a link to its description — NOT the description itself, which is not shipped. */\n readonly message: string;\n /** Present on errors: the Error that stopped the player. */\n readonly error?: Error;\n}\n\n/**\n * Where a player sends what it wants to say. Every field is optional.\n *\n * The SHARED base of every callbacks object (dev-docs/reviews/api-surface-review.md §26.1): `createDiagnostics` reads it directly,\n * `PxEngineCallbacks` extends it with the playback lifecycle, `PxAnimatorCallbacks` adds `onStop`\n * on top — so the four diagnostics fields are spelled once, here.\n * @public\n */\nexport interface PxDiagnosticsConfig {\n\n /**\n * IT PLAYS, but something was ignored, degraded or misspelled — an unknown easing, an\n * override that could not apply, an attribute the platform will not animate. Each\n * diagnostic says WHAT happened via `code` (a number — look it up on the codes page) and\n * WHO can act on it via `kind` (`document` / `host` / `platform` / `usage` / `internal`);\n * `data` carries the values the site had. Without this: `console.warn`.\n */\n onWarn?: (diagnostic: PxDiagnostic) => void;\n\n /**\n * THIS INSTANCE WILL NOT PLAY — the document failed to load, parse or build, or the render\n * threw: nothing rendered, `isReady()` false, the component's `fallback` shown. The player\n * stays inert rather than throwing at the caller. `diagnostic.error` is the Error; on React\n * Native `diagnostic.data` carries the component stack when the error boundary caught it.\n * Without this: `console.error`.\n */\n onError?: (diagnostic: PxDiagnostic) => void;\n\n /**\n * Switch the `console.warn` fallback off. For a host that knows the player has something\n * to say about this document and is prepared to tolerate it — a chatty player is not what\n * an end user's console is for. A handler you passed (`onWarn`) still fires: mute is\n * about the console, not about you.\n */\n muteWarn?: boolean;\n\n /** The same switch for the `console.error` fallback. `onError` still fires. */\n muteError?: boolean;\n}\n\n/** The reporting channel a player writes to. @public */\nexport interface PxDiagnostics {\n /** Report something survivable — it plays. `data` is whatever the code's `@data` names. */\n warn(kind: PxDiagnosticKind, code: PxDiagnosticCode, ...data: Array<unknown>): void;\n /**\n * Report a failure that stopped this instance — it will not play. An `Error` anywhere in\n * `data` becomes the diagnostic's `error`, so a site can pass it wherever it reads best.\n */\n error(kind: PxDiagnosticKind, code: PxDiagnosticCode, ...data: Array<unknown>): void;\n}\n\n/** The Error a site passed, if it passed one — handlers get it on `error` as well as in `data`. */\nfunction errorIn(data: ReadonlyArray<unknown>): Error | undefined {\n return data.find((d): d is Error => d instanceof Error);\n}\n\n/**\n * Builds the channel a player reports through.\n *\n * `prefix` labels the console fallback (e.g. `'[PixodeskSvgAnimator]'`) and is NOT added to the\n * diagnostic handed to a handler — a caller that wants to prefix its own log can, and one\n * feeding a UI should not have to strip ours.\n * @internal\n */\nexport function createDiagnostics(config?: PxDiagnosticsConfig, prefix?: string): PxDiagnostics {\n const tag = prefix ? prefix + ' ' : '';\n return {\n warn: (kind: PxDiagnosticKind, code: PxDiagnosticCode, ...data: Array<unknown>): void => {\n const message = codeLine(code);\n if (config?.onWarn) { config.onWarn({ code, kind, data, message }); return; }\n if (config?.muteWarn) return;\n console.warn(tag + kind + ' ' + message, ...data);\n },\n error: (kind: PxDiagnosticKind, code: PxDiagnosticCode, ...data: Array<unknown>): void => {\n const message = codeLine(code);\n const error = errorIn(data);\n if (config?.onError) { config.onError({ code, kind, data, message, error }); return; }\n if (config?.muteError) return;\n console.error(tag + kind + ' ' + message, ...data);\n },\n };\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport type { PxEngineCallbacks, PxAnimatorCallbacks } from '@pixodesk/svg-animator-core';\nimport type { PxAnimatorApi } from './PxAnimatorWebTypes';\n\n/**\n * The engines take ONE callbacks object; every public surface takes the callbacks INLINE —\n * `createAnimator({ onFinish })`, `loadTagAnimators({ onFinish })`, `<PixodeskSvgAnimator\n * onFinish />`. This builds the one from the other, and adds `onStop`: it fires after any of\n * pause / cancel / finish / remove, for callers who only care that playback is no longer\n * running. Defined HERE, once, so the web player and the components cannot disagree on when.\n *\n * A wrapper is only made when there is something to call — an engine tests\n * `callbacks?.onFinish` for presence, so an always-present function would change its behaviour.\n */\nexport function toEngineCallbacks(inline: PxAnimatorCallbacks | undefined): PxEngineCallbacks {\n const { onPlay, onPause, onCancel, onFinish, onRemove, onStop, onWarn, onError, muteWarn, muteError } = inline ?? {};\n const withStop = (own: (() => void) | undefined): (() => void) | undefined =>\n own || onStop ? () => { own?.(); onStop?.(); } : undefined;\n return {\n onPlay,\n onPause: withStop(onPause),\n onCancel: withStop(onCancel),\n onFinish: withStop(onFinish),\n onRemove: withStop(onRemove),\n onWarn, onError, muteWarn, muteError,\n };\n}\n\n/**\n * The API of a player that could not be built (the rule in core's `PxDiagnostics`): every\n * call is a no-op, every getter answers \"not ready\". Returned instead of throwing, after the\n * failure has been reported through `onError`.\n */\nexport function createInertAnimator(): PxAnimatorApi {\n return {\n isReady: () => false,\n getRootElement: () => null,\n isPlaying: () => false,\n play: () => {},\n pause: () => {},\n cancel: () => {},\n finish: () => {},\n setPlaybackRate: () => {},\n getCurrentTime: () => null,\n setCurrentTime: () => {},\n getCurrentProgress: () => null,\n setCurrentProgress: () => {},\n destroy: () => {},\n };\n}\n\n/** Anything thrown becomes an Error, so a diagnostic always carries one shape. */\nexport function asThrownError(e: unknown): Error {\n return e instanceof Error ? e : new Error(String(e));\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { PxDiagnosticCode, PxDiagnosticKind, resolveTrigger, type PxDiagnostics, type PxTrigger } from '@pixodesk/svg-animator-core';\nimport { createDiagnostics } from '@pixodesk/svg-animator-core/internal';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\n\n\n/**\n * Sets up event-based triggers for an animation.\n *\n * This function attaches event listeners to the animation's root element based on the\n * provided configuration, allowing animations to be started by user interactions\n * or visibility changes.\n *\n * ### Trigger Options (startOn):\n * - 'load' (default): Starts after the page loads.\n * - 'mouseOver': Starts on mouse enter.\n * - 'click': Toggles play/end action on click.\n * - 'scrollIntoView': Starts when the element scrolls into the viewport.\n * - 'programmatic': No automatic start. Must be controlled via the API.\n *\n * ### End Action Options (outAction):\n * Defines behavior when the trigger condition ends (e.g., mouse leave).\n * - 'continue' (default): Animation continues playing.\n * - 'pause': Pauses the animation.\n * - 'reset': Cancels the animation, resetting it to the start.\n * - 'reverse': Reverses the animation playback.\n *\n * @param {!PxAnimatorApi} api The animator API instance to control.\n * @param {!PxTrigger} trigger The trigger configuration object. Only `startOn`, `outAction` and\n * `scrollIntoViewThreshold` are read here; `finishAction` belongs to the PLAYER (what happens\n * after a natural end), not to the trigger wiring.\n * @returns A disposer that detaches every listener and observer this call attached (review §14).\n * `createAnimator` ties it to `destroy()`. Call it yourself before re-arming an element you\n * wired by hand — otherwise the old listeners stay live next to the new ones.\n * @public\n */\nexport function setupAnimationTriggers(\n api: PxAnimatorApi,\n trigger: PxTrigger,\n diag?: PxDiagnostics\n): () => void {\n // Public export, so the channel is optional and falls back to the console (review §5).\n const report = diag ?? createDiagnostics(undefined, '[PxAnimator]');\n\n // Everything attached below registers its own undo here, so one call detaches it all.\n const cleanups: Array<() => void> = [];\n const dispose = (): void => { for (const undo of cleanups.splice(0)) undo(); };\n // The defaults come from core's one table, shared with every player (`PX_TRIGGER_DEFAULTS`):\n // no `startOn` = 'load', no `outAction` = 'continue', no threshold = 0 (\"any pixel visible\").\n // The threshold default must match the editor model's (TSvgSvgAnimationAttr\n // .scrollIntoViewThreshold), which OMITS the value on the wire when it equals it.\n const { startOn, outAction, scrollIntoViewThreshold } = resolveTrigger(trigger);\n\n const root = api.getRootElement();\n\n if (!root) {\n report.warn(PxDiagnosticKind.host, PxDiagnosticCode.triggersNoRoot);\n return dispose;\n }\n\n // Tracks whether the LAST out-action put the animation into reverse, so the\n // next trigger start can restore forward playback without clobbering a\n // custom playback rate the user may have set through the API.\n let reversed = false;\n\n /** Ensures forward playback and starts or resumes the animation. */\n const start = () => {\n if (reversed) {\n reversed = false;\n api.setPlaybackRate(1);\n }\n api.play();\n };\n\n /** Handles what to do when the element leaves the active trigger condition. */\n const handleEndAction = () => {\n switch (outAction) {\n case 'pause':\n api.pause();\n break;\n case 'reset':\n api.cancel();\n break;\n case 'reverse':\n // Play the animation backwards from its current position.\n reversed = true;\n api.setPlaybackRate(-1);\n api.play();\n break;\n case 'continue':\n default:\n // Do nothing\n break;\n }\n };\n\n // ---- Setup event-based start logic ----\n switch (startOn) {\n case 'load': {\n const startHandler = () => start();\n if (document.readyState === 'complete') {\n startHandler();\n } else {\n window.addEventListener('load', startHandler, { once: true });\n cleanups.push(() => window.removeEventListener('load', startHandler));\n }\n break;\n }\n\n case 'mouseOver': {\n // An OUT may only follow an IN. A `mouseleave` with no preceding `mouseenter` happens\n // when the pointer is already over the element at load and then moves away — and for\n // `outAction: 'reverse'` the out action PLAYS (`setPlaybackRate(-1); play()`), so an\n // untriggered leave would start the animation running backwards. Same class of bug as\n // the scrollIntoView initial-intersection case handled below.\n let enteredOnce = false;\n const mouseOverHandler = () => { enteredOnce = true; start(); };\n const mouseOutHandler = () => { if (enteredOnce) handleEndAction(); };\n\n root.addEventListener('mouseenter', mouseOverHandler);\n root.addEventListener('mouseleave', mouseOutHandler);\n cleanups.push(() => {\n root.removeEventListener('mouseenter', mouseOverHandler);\n root.removeEventListener('mouseleave', mouseOutHandler);\n });\n break;\n }\n\n case 'click': {\n const clickHandler = () => {\n if (api.isPlaying()) {\n handleEndAction();\n } else {\n start();\n }\n };\n root.addEventListener('click', clickHandler);\n cleanups.push(() => root.removeEventListener('click', clickHandler));\n break;\n }\n\n case 'scrollIntoView': {\n // `observe()` delivers an INITIAL entry describing the CURRENT state, which is how an\n // element that is already on screen starts without any scrolling. But that same initial\n // entry also reports \"not intersecting\" for an element merely below the fold — and\n // treating that as an out-action ran it before anything had ever played. For `reverse`\n // that meant `setPlaybackRate(-1)` + `play()`, i.e. the animation started running\n // BACKWARDS on page load. An OUT is only meaningful after an IN, so require one.\n // A target TALLER than the viewport can never reach a high ratio (ratio is measured\n // against the TARGET's own size), so a 0.5/0.9 threshold would be unsatisfiable and the\n // animation would never play. Normalize by what could possibly be visible, and register\n // a granular threshold list — registering the raw threshold would mean the callback\n // never fires at all for such a target.\n const effectiveRatio = (entry: IntersectionObserverEntry): number => {\n const target = entry.boundingClientRect;\n const visible = entry.intersectionRect;\n // Simplified entries (tests, older engines) may omit the rects — fall back to the\n // browser's own ratio rather than inventing one.\n if (!target?.height || !visible) return entry.intersectionRatio;\n // Use the SMALLER of `rootBounds` and the live viewport. `rootBounds` can be null\n // (implicit root in some embeddings) and can also report a box LARGER than the\n // actual viewport — trusting it then reinstates the very cap this normalization\n // exists to remove. `intersectionRect` is already clipped to the real viewport, so\n // the denominator must be too.\n const live = typeof window !== 'undefined' && window.innerHeight ? window.innerHeight : Infinity;\n const declared = entry.rootBounds?.height ?? Infinity;\n const viewport = Math.min(live, declared);\n const denom = Math.min(target.height, Number.isFinite(viewport) ? viewport : target.height);\n return denom > 0 ? visible.height / denom : entry.intersectionRatio;\n };\n const thresholdSteps = Array.from({ length: 21 }, (_, i) => i / 20);\n let wasIntersecting = false;\n const observer = new IntersectionObserver(\n entries => {\n entries.forEach(entry => {\n // If the element is at least partially visible\n if (entry.isIntersecting && effectiveRatio(entry) >= scrollIntoViewThreshold) {\n wasIntersecting = true;\n start();\n } else if (wasIntersecting) {\n // Element scrolled OFF screen after having been on it -> out action\n wasIntersecting = false;\n handleEndAction();\n }\n });\n },\n { threshold: thresholdSteps }\n );\n observer.observe(root);\n cleanups.push(() => observer.disconnect());\n break;\n }\n\n case 'programmatic':\n // No auto-start; external code must call play()\n break;\n }\n\n return dispose;\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { createAdapterAnimator, getAnimatorConfig, PxDiagnosticCode, PxDiagnosticKind, type PxAnimatedSvgDocument, type PxEngineCallbacks, type PxDiagnostics, type PxPlatformAdapter } from '@pixodesk/svg-animator-core';\nimport { camelCaseToKebabWordIfNeeded, createDiagnostics, isScrollTimeline, PX_STYLE_ATTR_NAMES } from '@pixodesk/svg-animator-core/internal';\nimport { setupAnimationTriggers } from '../triggers/PxAnimatorTriggers';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\n\n// Re-export the platform-neutral pieces from their historical home so the\n// package surface is unchanged by the core extraction.\nexport { createAdapterAnimator };\nexport type { PxPlatformAdapter };\n\nexport function getSelector(id: string) {\n // return `[data-px-id=\"${id}\"]`; FIXME\n return '#' + id;\n}\n\n\n////////////////////////////////////////////////////////////////\n// Browser DOM implementation\n////////////////////////////////////////////////////////////////\n\n\n\n/**\n * Creates an animator instance that uses a requestAnimationFrame loop for animations.\n * This is the browser DOM-specific version.\n *\n * @param {PxEngineCallbacks=} callbacks Optional lifecycle callbacks.\n * @param {Element=} rootElement Optional pre-rendered root element.\n * @returns {PxAnimatorApi} A PxAnimatorApi instance.\n * @internal\n */\nexport function createFrameLoopAnimator(\n doc: PxAnimatedSvgDocument,\n adapter?: PxPlatformAdapter,\n callbacks?: PxEngineCallbacks,\n rootElement?: Element | null\n): PxAnimatorApi {\n\n const config = getAnimatorConfig(doc) || {};\n\n // One channel for everything this engine has to say (API review §5).\n const diag = createDiagnostics(callbacks, '[PxAnimator]');\n\n // Use provided root element or try to find by selector\n if (!rootElement) {\n if (doc.id) {\n const rootSelector = getSelector(doc.id);\n rootElement = document.querySelector(rootSelector);\n if (!rootElement) diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.noRootForSelector, rootSelector);\n } else {\n diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.noRootElement);\n }\n }\n\n const basicApi = createAdapterAnimator(\n doc,\n adapter || createDomAdapter(rootElement, diag),\n callbacks\n );\n\n // Specialize the platform-neutral API to the DOM: the root is an Element.\n const api: PxAnimatorApi = {\n ...basicApi,\n \"getRootElement\": () => rootElement || null\n };\n // D3 (scroll-timeline.design.md): triggers are meaningless when the playhead is\n // scroll-driven — writers must not emit them, and a document that carries them\n // anyway gets a warning, not behavior.\n // Every time-driven document IS wired: no `trigger` means the defaults (`startOn` 'load').\n if (isScrollTimeline(config)) {\n if (config.trigger) diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.scrollTriggerIgnored);\n } else {\n // The disposer rides on destroy(), so the listeners go when the animator does (§14).\n const detachTriggers = setupAnimationTriggers(api, config.trigger ?? {}, diag);\n const destroyEngine = api.destroy.bind(api);\n api.destroy = () => { detachTriggers(); destroyEngine(); };\n }\n return api;\n}\n\nexport function createDomAdapter(rootElement?: Element | null, diag?: PxDiagnostics) {\n // Track warnings to avoid spamming console\n const warnedSelectors = new Set<string>();\n // Called directly by consumers too, so the channel is optional and defaults to the console.\n const report = diag ?? createDiagnostics(undefined, '[PxAnimator]');\n\n const adapter: PxPlatformAdapter = {\n isConnected: () => {\n if (!rootElement) return true; // No root element means we're always \"connected\"\n return rootElement.isConnected;\n },\n setAttribute: (id, attrName, value) => {\n\n attrName = camelCaseToKebabWordIfNeeded(attrName);\n\n const selector = getSelector(id);\n\n // Query elements by selector within root (or document if no root)\n const elements = rootElement?.querySelectorAll(selector) || document.querySelectorAll(selector);\n\n if (elements.length === 0 && !warnedSelectors.has(selector)) {\n warnedSelectors.add(selector);\n report.warn(PxDiagnosticKind.host, PxDiagnosticCode.setAttributeNoElement, selector);\n }\n\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n // `<pattern>` ignores the plain `transform` ATTRIBUTE (it transforms via\n // `patternTransform`). The WAAPI engine drives the same animation through\n // CSS `transform`, which browsers do apply to patterns — remap here so the\n // frames engine animates everything WAAPI animates.\n const effectiveAttrName = attrName === 'transform' && element.tagName === 'pattern'\n ? 'patternTransform'\n : attrName;\n element.setAttribute(effectiveAttrName, value);\n if (PX_STYLE_ATTR_NAMES.has(attrName)) {\n (element as HTMLElement).style[attrName as any] = value;\n }\n }\n },\n };\n return adapter;\n}","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { getAnimatorConfig, normalizeBindings, PxTimelineEngine, type PxAnimatedSvgDocument, type PxAnimationDefinition, type PxEngineCallbacks, type PxAnimatorConfig, type PxBezierPath, clampSeekMs, isValidPlaybackRate, progressToTimeMs, PxDiagnosticCode, PxDiagnosticKind, seekCeilingMs, timeToProgress } from '@pixodesk/svg-animator-core';\nimport { PX_PCT_BASED_ATTR_NAMES, bezierToSvgPath, camelCaseToKebabWordIfNeeded, clamp, PX_COLOR_ATTR_NAMES, composeTransformParts, cubicBezier, interpolateValue, kebabToCamelCaseWord, splitEasing, toRGBA, PX_TRANSFORM_FN_NAMES, type PxAnyKeyframe, type PxNormalizedKeyframe, keyframeEasing, keyframeValue, createDiagnostics } from '@pixodesk/svg-animator-core/internal';\nimport { getSelector } from './PxAnimatorFrameLoop';\nimport { setupAnimationTriggers } from '../triggers/PxAnimatorTriggers';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\n\n\n/**\n * Converts a single normalized keyframe into a Web Animations API Keyframe object.\n *\n * Handles three categories of CSS property:\n * - **Color attributes** (e.g. fill, stroke): array values are converted to an rgba() string.\n * - **Transform functions** (e.g. translate, rotate, scale): values are formatted as a\n * CSS transform function string and mapped to the transform property.\n * - **All other properties**: the value is coerced to a string as-is.\n *\n * If the resulting (cssKey, cssValue) pair is not supported by the browser (CSS.supports returns\n * false), cssKey is added to unsupportedSet so the caller can decide whether to fall back to the\n * frame-loop animator.\n */\nfunction createCssKf(kf: PxAnyKeyframe, t: number, propName: string, unsupportedSet: Set<string>) {\n let value = keyframeValue(kf);\n // The easing is on the SOURCE keyframe: it applies from this kf to the next, matching the\n // WAAPI convention. Resolved already when the keyframe came through `normalizeKeyframes`.\n const e = keyframeEasing(kf);\n\n const cssKf: Keyframe = {\n offset: t,\n easing: e && Array.isArray(e) ? \"cubic-bezier(\" + e.join(',') + \")\" : undefined\n };\n\n let cssValue: any;\n let cssKey = propName;\n\n if (PX_COLOR_ATTR_NAMES.has(propName) && Array.isArray(value)) {\n cssValue = toRGBA(value);\n } else if (propName === 'transform' && value !== null && typeof value === 'object' && !Array.isArray(value)) {\n // Unified transform: keyframe value is a parts record (PxTransformParts).\n // Compose all present parts into one CSS transform string.\n cssValue = composeTransformParts(value, { withUnits: true });\n cssKey = 'transform';\n } else if (PX_TRANSFORM_FN_NAMES.has(propName)) {\n if (Array.isArray(value)) {\n if (propName === 'translate') value = value.map(v => v + 'px');\n value = value.join(',');\n }\n if (propName === 'rotate') value = value + 'deg';\n cssValue = propName + '(' + value + ')';\n cssKey = 'transform';\n } else if (propName === 'd') {\n // Animated path. The value is a { paths: PxBezierPath[] } record (same shape the CSS/frame\n // builder consumes). Without this branch it stringified to \"[object Object]\", an invalid\n // `d`, which empties the <path> — the shape vanished in forced WAAPI while svg-css (which\n // emits `d: path(...)`) rendered it. Emit the CSS `d` presentation-attr syntax `path(\"…\")`\n // via the same bezierToSvgPath used by the CSS path, so WAAPI animates it identically.\n const paths: Array<PxBezierPath> = (value && typeof value === 'object' && Array.isArray(value.paths))\n ? value.paths : [];\n // forceCurves: CSS/WAAPI only interpolates `path()` values with IDENTICAL command\n // sequences — uniform all-`C` output keeps every keyframe structurally equal\n // (mixed L/C, e.g. round-corner radius 0 vs >0, would go DISCRETE → 50% flip).\n cssValue = 'path(\"' + paths.map(bz => bezierToSvgPath(bz, true)).join('') + '\")';\n } else if (PX_PCT_BASED_ATTR_NAMES.has(propName) && typeof value === 'number') {\n // Percent-based CSS properties (offset-distance): the wire carries 0..1 numbers,\n // but the property needs a <length-percentage>. The frames engine already converts\n // (`calcPropertyValue`); without this twin branch WAAPI got a bare \"0.25\", which\n // `CSS.supports('offset-distance', '0.25')` rejects — so the attr was flagged\n // unsupported and the WHOLE document silently fell back to frames. Same maths,\n // same units, both engines.\n cssValue = (value * 100) + '%';\n } else {\n cssValue = '' + value;\n }\n\n // `CSS.supports` takes a CSS PROPERTY name, so it must be asked in kebab-case —\n // `CSS.supports('strokeDasharray', …)` is always false. Prop names reach us in\n // either form (the app materializes trim paths as camelCase `strokeDasharray` /\n // `strokeDashoffset` / `strokeOpacity`), and an unsupported entry makes the caller\n // discard EVERY animation on the document, so a false negative here is costly.\n if (!CSS.supports(camelCaseToKebabWordIfNeeded(cssKey), cssValue)) unsupportedSet.add(cssKey);\n\n cssKey = kebabToCamelCaseWord(cssKey);\n cssKf[cssKey] = cssValue;\n return cssKf;\n}\n\n/**\n * Clips keyframes to the [0, duration] range.\n * If a keyframe pair straddles a boundary (t=0 or t=duration), inserts an interpolated\n * keyframe at the boundary with the correct value and split easing, so WAAPI sees\n * exact start/end values rather than out-of-range ones.\n */\nfunction clipKeyframesToDuration(\n propName: string,\n keyframes: PxNormalizedKeyframe[],\n duration: number\n): PxNormalizedKeyframe[] {\n const result: PxNormalizedKeyframe[] = [];\n\n for (let i = 0; i < keyframes.length; i++) {\n const kf = keyframes[i];\n const t = kf.t ?? 0;\n\n if (t < 0) {\n // If the next keyframe is in range, interpolate value at t=0\n const next = keyframes[i + 1];\n if (next && (next.t ?? 0) >= 0) {\n const nextT = next.t ?? 0;\n const localFrac = (0 - t) / (nextT - t);\n const easedFrac = kf.e ? cubicBezier(kf.e as [number, number, number, number])(localFrac) : localFrac;\n const { right: rightEasing } = splitEasing(kf.e as any, localFrac);\n result.push({ t: 0, v: interpolateValue(propName, kf.v, next.v, easedFrac), e: rightEasing });\n }\n continue;\n }\n\n if (t > duration) {\n // If the previous keyframe was in range, interpolate value at t=duration\n const prev = keyframes[i - 1];\n if (prev && (prev.t ?? 0) <= duration) {\n const prevT = prev.t ?? 0;\n const localFrac = (duration - prevT) / (t - prevT);\n const easedFrac = prev.e ? cubicBezier(prev.e as [number, number, number, number])(localFrac) : localFrac;\n const { left: leftEasing } = splitEasing(prev.e as any, localFrac);\n if (result.length > 0) result[result.length - 1] = { ...result[result.length - 1], e: leftEasing };\n result.push({ t: duration, v: interpolateValue(propName, prev.v, kf.v, easedFrac), e: undefined });\n }\n break;\n }\n\n result.push(kf);\n }\n\n return result;\n}\n\n/**\n * Converts a PxAnimationDefinition into a map of Web Animations API Keyframe arrays, one per\n * animated property.\n *\n * For each property in the definition the function:\n * 1. Clips keyframes to [0, duration], interpolating boundary values when a pair straddles an edge.\n * 2. Normalizes keyframe time values to the [0, 1] offset range (time / duration).\n * 3. Delegates CSS value conversion to createCssKf.\n * 4. Ensures the keyframe sequence always starts at offset: 0 and ends at offset: 1 — a\n * requirement of the Web Animations API for correct looping behavior. If the first keyframe\n * starts after 0 or the last keyframe ends before 1, a copy of that keyframe is inserted at the\n * boundary with the adjusted offset.\n */\nexport function convertToWebApiKeyframes(\n animDef: PxAnimationDefinition,\n unsupportedSet: Set<string>,\n config: PxAnimatorConfig\n): Map<string, Keyframe[]> {\n const result = new Map<string, Keyframe[]>();\n\n for (const [propName, propAnim] of Object.entries(animDef)) {\n const duration = config.duration || 1;\n const clippedKeyframes = clipKeyframesToDuration(propName, propAnim.keyframes || [], duration);\n const cssKeyframes: Keyframe[] = [];\n\n for (let i = 0; i < clippedKeyframes.length; i++) {\n const kf = clippedKeyframes[i];\n\n const t = clamp((kf.t ?? 0) / duration, 0, 1);\n const cssKf: Keyframe = createCssKf(kf, t, propName, unsupportedSet);\n\n // Keyframes need to start with offset:0 to work correctly with loops\n if (i === 0 && (cssKf.offset || 0) > 0) {\n cssKeyframes.push({ ...cssKf, offset: 0 });\n }\n\n cssKeyframes.push(cssKf);\n }\n\n // Keyframes need to end with offset:1 to work correctly with loops\n if (cssKeyframes.length > 0 && (cssKeyframes[cssKeyframes.length - 1].offset || 0) < 1) {\n cssKeyframes.push({\n ...cssKeyframes[cssKeyframes.length - 1],\n offset: 1\n });\n }\n\n if (cssKeyframes.length > 0) {\n result.set(propName, cssKeyframes);\n }\n }\n\n return result;\n}\n\n/**\n * Creates an animator instance that uses the native Web Animations API.\n *\n * This is the preferred, more performant animator. It will return null if the\n * animation configuration contains properties not supported by the browser's\n * Web Animations API implementation, unless forceEvenIfHasUnsupportedAttrs is true.\n *\n * @param callbacks Optional lifecycle callbacks.\n * @param rootElement Root element.\n * @param forceEvenIfHasUnsupportedAttrs If true, an animator will be created even if some CSS properties are not supported.\n * @returns An PxAnimatorApi instance, or null if unsupported features are used and not forced.\n */\n/** Native scroll-timeline payload (`timeline.engine: 'native'` / `auto`; see PxScrollDriver.createNativeScrollTimeline): the\n * browser-native timeline every Animation attaches to, plus optional range offsets. */\nexport interface PxWebApiScrollTimeline {\n timeline: AnimationTimeline;\n rangeStart?: Record<string, unknown>;\n rangeEnd?: Record<string, unknown>;\n}\n\n/** @internal */\nexport function createWebApiAnimator(\n doc: PxAnimatedSvgDocument,\n callbacks?: PxEngineCallbacks,\n rootElement?: Element | null,\n forceEvenIfHasUnsupportedAttrs?: boolean,\n scrollTimeline?: PxWebApiScrollTimeline\n): PxAnimatorApi | null {\n\n const config = getAnimatorConfig(doc) || {};\n\n // One channel for everything this engine has to say (API review §5).\n const diag = createDiagnostics(callbacks, '[PxAnimator]');\n\n // Use provided root element or try to find by selector\n if (!rootElement) {\n if (doc.id) {\n const rootSelector = getSelector(doc.id);\n rootElement = document.querySelector(rootSelector);\n if (!rootElement) diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.noRootForSelector, rootSelector);\n } else {\n diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.noRootElement);\n }\n }\n\n // WAAPI bindings: motion-along-path is materialized into plain `{ translate,\n // rotate }` transform kfs inside `normalizeAnimationDefinition` (gated on\n // `engine === 'waapi'`). The WAAPI keyframe builder then sees a vanilla\n // unified-transform animation — no DOM-style mutation, no offset-path.\n const bindings = normalizeBindings(doc, PxTimelineEngine.native);\n\n const animations: Array<Animation> = [];\n\n const _iterations = config.iterations;\n let iterations: number | undefined;\n if (typeof _iterations === 'number') iterations = _iterations;\n if (_iterations === 'infinite') iterations = Infinity;\n\n const unsupportedSet = new Set<string>();\n\n // A document commonly produces many Animation objects (one per element ×\n // animated property), but the public callbacks describe the document\n // timeline as a whole — guard so each fires once per finish/remove episode.\n // `finishNotified` re-arms on play/cancel/seek.\n let finishNotified = false;\n let removeCalled = false;\n\n ////////////////////////////////////////////////////////////////\n\n // Warn if no bindings defined\n if (!bindings?.length) {\n diag.warn(PxDiagnosticKind.document, PxDiagnosticCode.noBindings);\n }\n\n for (const binding of bindings || []) {\n const animDef = binding.animate;\n if (!animDef || typeof animDef !== 'object' || Array.isArray(animDef)) {\n diag.warn(PxDiagnosticKind.document, PxDiagnosticCode.unresolvedBinding, binding);\n continue;\n }\n\n const selector = getSelector(binding.id);\n\n // Use CSS selector to find elements\n const elements = rootElement?.querySelectorAll(selector) || document.querySelectorAll(selector);\n\n if (elements.length === 0) {\n diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.noElementsForSelector, selector);\n }\n\n // Convert animation definition to Web API keyframes\n const keyframesMap = convertToWebApiKeyframes(animDef, unsupportedSet, config);\n\n\n // Delay handling:\n // - Positive delay (e.g., 500): Wait before starting → use delay option\n // - Negative delay (e.g., -500): Start mid-animation → use currentTime to seek\n // (Web Animations API doesn't reliably support negative delay values)\n // WAAPI `currentTime` spans ALL iterations, so a finite timeline clamps\n // the seek to duration × iterations (seeking to exactly the end must\n // land on the final frame, not wrap to 0); an infinite timeline wraps\n // within one iteration instead.\n const positiveDelay = config.delay && config.delay > 0 ? config.delay : undefined;\n let seekPosition: number | undefined;\n if (config.delay && config.delay < 0 && config.duration) {\n const rawSeek = -config.delay;\n seekPosition = iterations === Infinity\n ? rawSeek % config.duration\n : Math.min(rawSeek, config.duration * (iterations ?? 1));\n }\n\n const effectOptions: KeyframeEffectOptions = {\n duration: config.duration,\n delay: positiveDelay,\n // Default to 'forwards' so elements hold their final state after the\n // animation ends — consistent with Lottie and other animation runtimes.\n // Without this, seeking to the last frame reverts elements to their\n // pre-animation state (the Web Animations API \"after\" phase with fill:'none').\n fill: config.fill ?? 'forwards',\n direction: config.direction,\n iterations: iterations\n };\n\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n\n for (const [, keyframes] of keyframesMap) {\n if (keyframes.length > 0) {\n try {\n const effect = new KeyframeEffect(element, keyframes, effectOptions);\n // Native scroll timeline (`mode: 'native'` / `auto`): the browser computes\n // progress AND applies values (compositor-driven). Range offsets are\n // per-Animation in WAAPI.\n const anim = new Animation(effect, scrollTimeline ? scrollTimeline.timeline : document.timeline);\n if (scrollTimeline) {\n const a = anim as unknown as { rangeStart?: unknown; rangeEnd?: unknown };\n if (scrollTimeline.rangeStart) a.rangeStart = scrollTimeline.rangeStart;\n if (scrollTimeline.rangeEnd) a.rangeEnd = scrollTimeline.rangeEnd;\n }\n\n if (callbacks?.onFinish) anim.onfinish = () => {\n if (finishNotified) return;\n finishNotified = true;\n callbacks.onFinish?.();\n };\n if (callbacks?.onRemove) anim.onremove = () => {\n if (removeCalled) return;\n removeCalled = true;\n callbacks.onRemove?.();\n };\n\n // Seek forward for negative delay (e.g., delay=-500 → seek to 500ms).\n // `!== undefined` — a seek of exactly 0 is still a seek.\n if (seekPosition !== undefined) {\n anim.currentTime = seekPosition;\n }\n\n animations.push(anim);\n } catch (e) {\n // Was a bare dump of the error object; the channel carries it as the\n // DETAIL so a handler gets something it can act on (review §5).\n diag.warn(PxDiagnosticKind.internal, PxDiagnosticCode.animationBuildFailed, e);\n }\n }\n }\n }\n }\n\n ////////////////////////////////////////////////////////////////\n\n if (!forceEvenIfHasUnsupportedAttrs && unsupportedSet.size) {\n diag.warn(PxDiagnosticKind.platform, PxDiagnosticCode.unsupportedAnimatedAttrs, [...unsupportedSet].join(', '));\n return null;\n }\n\n ////////////////////////////////////////////////////////////////\n\n const api: PxAnimatorApi = {\n\n \"isReady\": () => true,\n\n \"getRootElement\": () => rootElement || null,\n\n \"isPlaying\": (): boolean => { return animations[0]?.playState === 'running'; },\n\n \"play\": () => {\n finishNotified = false; // re-arm finish notification\n animations.forEach(a => a.play());\n callbacks?.onPlay?.();\n },\n \"pause\": () => {\n animations.forEach(a => a.pause());\n callbacks?.onPause?.();\n },\n \"cancel\": () => {\n finishNotified = false; // re-arm finish notification\n animations.forEach(a => a.cancel());\n callbacks?.onCancel?.();\n },\n \"finish\": () => {\n for (const a of animations) {\n try {\n if (a.effect?.getTiming().iterations === Infinity) {\n a.effect.updateTiming({ iterations: 1 });\n a.finish();\n a.effect.updateTiming({ iterations: Infinity });\n } else {\n a.finish();\n }\n } catch (e) {\n a.cancel();\n }\n }\n // Natural finish also reaches onFinish via the native `anim.onfinish`\n // handler wired at construction time.\n },\n\n \"setPlaybackRate\": (rate: number) => {\n // WAAPI itself accepts 0 and silently freezes; the other two engines reject it.\n // One answer everywhere (review §3) — `pause()` is how you stop.\n if (!isValidPlaybackRate(rate)) {\n diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.rateRejected);\n return api;\n }\n animations.forEach(a => (a.playbackRate = rate));\n return api;\n },\n \"getCurrentTime\": (): number | null => {\n const res = animations[0]?.currentTime ?? null;\n return res !== null ? +res : null;\n },\n \"setCurrentTime\": (time: number) => {\n // Clamp like every other engine (review §3). A duration is not always declared,\n // and a ceiling of 0 would pin every seek to the first frame — so clamp only when\n // the timeline length is actually known, and otherwise just floor at 0.\n const ceiling = seekCeilingMs(config.duration ?? 0, iterations ?? 1);\n const seek = ceiling > 0 ? clampSeekMs(time, ceiling) : Math.max(0, time);\n finishNotified = false; // re-arm finish notification\n animations.forEach(a => {\n a.currentTime = seek;\n });\n },\n\n \"getCurrentProgress\": (): number | null => {\n const t = api.getCurrentTime();\n return t === null ? null : timeToProgress(t, config.duration ?? 0, iterations ?? 1);\n },\n\n \"setCurrentProgress\": (progress: number) => {\n api.setCurrentTime(progressToTimeMs(progress, config.duration ?? 0, iterations ?? 1));\n },\n\n \"destroy\": () => {\n api.cancel();\n animations.splice(0, animations.length);\n if (!removeCalled) {\n removeCalled = true;\n callbacks?.onRemove?.();\n }\n }\n };\n\n ////////////////////////////////////////////////////////////////\n\n // D3 (scroll-timeline.design.md): triggers are inert on a scroll-driven document —\n // writers must not emit them; a doc that carries them anyway gets a warning.\n // Every time-driven document IS wired: no `trigger` means the defaults (`startOn` 'load').\n if (config.timelineSource === 'scroll') {\n if (config.trigger) diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.scrollTriggerIgnored);\n } else {\n // The disposer rides on destroy(), so the listeners go when the animator does (§14).\n const detachTriggers = setupAnimationTriggers(api, config.trigger ?? {}, diag);\n const destroyEngine = api.destroy.bind(api);\n api.destroy = () => { detachTriggers(); destroyEngine(); };\n }\n\n // A progress-based timeline only tracks while the animation is PLAYING — start it\n // (there is no wall clock involved; \"playing\" here means \"bound to the timeline\").\n if (scrollTimeline) {\n animations.forEach(a => a.play());\n }\n\n return api;\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\nimport { getAnimatorConfig, isNativeForced, mayUseNativeScrollTimeline, PxDiagnosticCode, PxDiagnosticKind, PxTimelineEngineSetting, type PxAnimatedSvgDocument, type PxEngineCallbacks, type PxAnimatorConfig, type PxAnimatorCallbacks, type PxPlatformAdapter } from '@pixodesk/svg-animator-core';\nimport { createDiagnostics, isScrollTimeline, scrollTotalDurationMs } from '@pixodesk/svg-animator-core/internal';\nimport { asThrownError, createInertAnimator, toEngineCallbacks } from '../shared/PxAnimatorCallbacks';\nimport { createFrameLoopAnimator } from './PxAnimatorFrameLoop';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\nimport { createWebApiAnimator } from './PxAnimatorWebApi';\nimport { applyScrollPin, createNativeScrollTimeline, createScrollDriver } from '../scroll/PxScrollDriver';\n\n/**\n * Engine construction, shared by the full player and the pre-rendered builds.\n *\n * Everything here operates on a document that is ALREADY in its final shape — no\n * validation, no materialization, no rendering. `createAnimatorImpl` calls in after it\n * has done those stages; the pre-rendered entries call in directly, because the Editor\n * did them at export time. See dev-docs/plans/prerendered-player-builds.md.\n */\n\n/**\n * Applies the two `animator` config behaviors that are engine-independent, then hands\n * off to `make` for the actual engine.\n *\n * `resetOnFinish` is composed here so BOTH engines get it: after a NATURAL finish the\n * document snaps back to its start state (same mechanics as the trigger `reset`\n * out-action). The caller's own `onFinish` still fires first. `apiRef` is assigned right\n * after creation — finish always happens asynchronously later.\n */\nexport function finaliseAnimator(\n animatorConfig: PxAnimatorConfig,\n callbacks: PxEngineCallbacks | undefined,\n make: (effectiveCallbacks?: PxEngineCallbacks) => PxAnimatorApi\n): PxAnimatorApi {\n\n let apiRef: PxAnimatorApi | undefined;\n let effectiveCallbacks = callbacks;\n if (animatorConfig.resetOnFinish) {\n effectiveCallbacks = {\n ...callbacks,\n onFinish: () => {\n callbacks?.onFinish?.();\n apiRef?.cancel();\n },\n };\n }\n\n const res = make(effectiveCallbacks);\n apiRef = res;\n\n if (animatorConfig.debugGlobalName) {\n (window as any)[animatorConfig.debugGlobalName] = res; // Exposing as global variable for debug\n }\n\n return res;\n}\n\n/**\n * Picks the engine the way the full player does, from `timeline.engine`: `player` pins the\n * frame loop; otherwise waapi, with a frames fallback for unsupported attrs unless\n * `native` demands waapi.\n */\nexport function bindWithEngineChoice(\n doc: PxAnimatedSvgDocument,\n adapter?: PxPlatformAdapter,\n callbacks?: PxEngineCallbacks,\n rootElement?: Element | null\n): PxAnimatorApi {\n const animatorConfig = getAnimatorConfig(doc) || {};\n // One channel for everything this binder and the scroll driver have to say (review §5).\n const diag = createDiagnostics(callbacks, '[PxAnimator]');\n\n // Scroll-driven document: the playhead follows scroll position, never the wall\n // clock. One knob — `timeline.engine` — picks the row (scroll-timeline.design.md §4.0):\n // player → the player measures progress, frames applies values (`setCurrentTime`)\n // native → browser ScrollTimeline/ViewTimeline drives waapi (compositor thread)\n // auto → native first; when unsupported, the player measures and waapi (or\n // frames, if waapi declines the doc) applies — the fallback cascade (D8)\n // Triggers are inert either way (D3 — both engines warn + skip\n // `setupAnimationTriggers` for scroll docs). The animator is never `play()`ed by us\n // for custom driving; scrubbing via `setCurrentTime` holds the pose.\n if (isScrollTimeline(animatorConfig)) {\n return finaliseAnimator(animatorConfig, callbacks, cb => {\n\n // `pin` — hold the canvas on screen for the scrubbed stretch (`position: sticky`,\n // + an optional tall wrapper the player injects). MUST be applied before anything\n // measures: it changes the very geometry the timeline reads, and\n // `subject: 'parent'` is meant to resolve THROUGH the injected wrapper.\n let unpin = () => { /* nothing pinned */ };\n\n // `auto` / `native`: try the browser's own timeline first.\n if (mayUseNativeScrollTimeline(animatorConfig.engine) && rootElement) {\n unpin = applyScrollPin(rootElement, animatorConfig.scroll);\n const native = createNativeScrollTimeline(rootElement, animatorConfig, diag);\n if (native) {\n const api = createWebApiAnimator(doc, cb, rootElement,\n isNativeForced(animatorConfig.engine), native);\n if (api) {\n const destroyNative = api.destroy.bind(api);\n api.destroy = () => { unpin(); destroyNative(); };\n return api;\n }\n // unsupported attrs → fall through to frames + custom below\n }\n unpin(); // …and undo the pin so the fallback re-applies it\n unpin = () => { /* re-pinned below */ };\n }\n\n // The player measures progress (the reference implementation). Engine per\n // `mode`: waapi unless `player` pins frames or waapi declines the doc.\n const api = (\n animatorConfig.engine !== PxTimelineEngineSetting.js\n ? createWebApiAnimator(doc, cb, rootElement, isNativeForced(animatorConfig.engine))\n : null\n ) || createFrameLoopAnimator(doc, adapter, cb, rootElement);\n\n // The animator knows its real root — for an SVG+JS export the runtime binds to the\n // `<svg>` already in the document, which is NOT the container passed in. Pin and\n // measure the same element, or the pin lands on nothing (it silently did).\n const subject = api.getRootElement?.() || rootElement;\n if (subject) {\n unpin = applyScrollPin(subject, animatorConfig.scroll);\n const totalMs = scrollTotalDurationMs(animatorConfig);\n const driver = createScrollDriver(subject, animatorConfig,\n progress => api.setCurrentTime(progress * totalMs), diag);\n if (driver) {\n // Tie the driver's (and the pin's) lifetime to the animator's.\n const destroy = api.destroy.bind(api);\n api.destroy = () => { driver.destroy(); unpin(); destroy(); };\n }\n } else {\n diag.warn(PxDiagnosticKind.host, PxDiagnosticCode.scrollNoRootToObserve);\n }\n return api;\n });\n }\n\n return finaliseAnimator(animatorConfig, callbacks, cb => {\n if (animatorConfig.engine === PxTimelineEngineSetting.js) {\n // `player` pins the frame loop, even if waapi could be used.\n return createFrameLoopAnimator(doc, adapter, cb, rootElement);\n }\n // Try waapi first; fall back to frames if it returns null (unsupported\n // attrs) unless `native` demands waapi.\n return (\n createWebApiAnimator(doc, cb, rootElement,\n isNativeForced(animatorConfig.engine)\n ) ||\n createFrameLoopAnimator(doc, adapter, cb, rootElement)\n );\n });\n}\n\n/**\n * Options accepted by the pre-rendered entry points — a subset of `PxAnimatorOptions`: the\n * document and the callbacks INLINE under the same names every surface uses (review §9). No\n * `adapter`: a pre-rendered SVG is by definition already in the DOM (review §25.14).\n * @public\n */\nexport interface PxPrerenderedAnimatorOptions extends PxAnimatorCallbacks {\n /**\n * The animation document. For a pre-rendered SVG this carries `animator.definitions`\n * and `animator.bindings` only — no `children`, because the elements are already\n * in the DOM.\n */\n doc: PxAnimatedSvgDocument;\n}\n\nfunction requireDoc(options: PxPrerenderedAnimatorOptions): PxAnimatedSvgDocument {\n // A wrong CALL throws (a bug at the call site); a document that cannot play is reported\n // through `onError` below — the rule in core's `PxDiagnostics` (review §25.1).\n if (!options?.doc) throw new Error('createAnimator: `doc` is required');\n return options.doc;\n}\n\n/**\n * Builds the player; when that throws, reports \"this instance will not play\" through the\n * diagnostics channel and returns an inert API instead of throwing at the caller.\n */\nfunction buildOrReport(options: PxPrerenderedAnimatorOptions, build: () => PxAnimatorApi): PxAnimatorApi {\n try {\n return build();\n } catch (e) {\n const err = asThrownError(e);\n createDiagnostics(options, '[PxAnimator]')\n .error(PxDiagnosticKind.internal, PxDiagnosticCode.buildFailed, err);\n return createInertAnimator();\n }\n}\n\n/**\n * Pre-rendered entry, both engines (`auto` / `player` / `native` all honored).\n *\n * Deliberately skips `validateNodeEffects`, `materializeAllInTree`, `generateNewIds` and\n * `renderNode`. Safe because the payload has no `children`, so all four are provably\n * no-ops for this document shape — and none of them reads `animator.bindings`.\n * @public\n */\nexport function createPrerenderedAnimator(options: PxPrerenderedAnimatorOptions): PxAnimatorApi {\n const doc = requireDoc(options);\n return buildOrReport(options, () => bindWithEngineChoice(doc, undefined, toEngineCallbacks(options), null));\n}\n\n/**\n * Pre-rendered entry, WAAPI only — the smallest build. Forces waapi so there is no\n * frames fallback to link against (`createWebApiAnimator` never returns null when\n * forced; it only warns about unsupported attrs).\n * @public\n */\nexport function createPrerenderedWaapiAnimator(options: PxPrerenderedAnimatorOptions): PxAnimatorApi {\n const doc = requireDoc(options);\n return buildOrReport(options, () => {\n const animatorConfig = getAnimatorConfig(doc) || {};\n return finaliseAnimator(animatorConfig, toEngineCallbacks(options),\n cb => createWebApiAnimator(doc, cb, null, true)!);\n });\n}\n","/*---------------------------------------------------------------------------------------\n * Copyright (c) Pixodesk LTD.\n * Licensed under the MIT License. See the LICENSE file in the project root for details.\n *---------------------------------------------------------------------------------------*/\n\n/**\n * Wire keys shared by every entry point.\n *\n * These live here rather than in `PxAnimator.ts` on purpose: that module used to end with a\n * top-level `if (typeof window !== 'undefined')` block publishing `createAnimator` /\n * `loadTagAnimators` as globals. A module-level side effect cannot be tree-shaken, so\n * importing ANY symbol from `PxAnimator.ts` pulled the entire full player in with it —\n * which silently made the pre-rendered builds the same size as the full one until this\n * constant was moved out. See dev-docs/plans/prerendered-player-builds.md.\n *\n * That block is gone (API review §4) and the package now declares `\"sideEffects\": false`, but\n * keeping these here costs nothing and removes the trap for good.\n */\n\n/**\n * Key under which `createAnimator` options carry the inline animation document. The editor\n * writes it into every exported SVG+JS — `createAnimator({\"doc\": …})` — so it is part of the\n * export format, which is why it is a named constant and not a literal.\n * @internal\n */\nexport const PX_ANIMATOR_DOC_KEY = 'doc';\n\n/** Key under which `createAnimator` options carry the per-instance `timeline` override. */\nexport const PX_ANIMATOR_TIMELINE_KEY = 'timeline';\n\n/** Key that makes the override start from the player's default timeline instead of the document's. */\nexport const PX_ANIMATOR_RESET_KEY = 'resetTimeline';\n"],"mappings":"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","names":["pathStr","_a","_a","_b"]}
|