@pixodesk/svg-animator-web 1.0.45 → 1.0.46
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/chunk-2AFALI5O.min.js +1 -0
- package/dist/{chunk-QE7KJWAN.js → chunk-ZBGGXE5W.js} +143 -83
- package/dist/chunk-ZBGGXE5W.js.map +1 -0
- package/dist/index.cjs +144 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -5
- 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 +107 -72
- 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 +111 -77
- package/dist/index.prerendered.umd.js.map +1 -1
- package/dist/index.prerendered.umd.min.js +1 -1
- package/dist/internal.cjs +104 -69
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.min.cjs +1 -1
- package/dist/internal.min.js +1 -1
- package/dist/pixodesk-svg-animator.umd.js +143 -84
- 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 -0
- package/package.json +2 -2
- package/dist/chunk-QE7KJWAN.js.map +0 -1
- package/dist/chunk-RQ5KZXDT.min.js +0 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../svg-animator-core/src/effects/PlayerEffectsUtil.visualModel.ts","../../svg-animator-core/src/render/PxRenderTree.ts","../../svg-animator-core/src/playback/PxScrollMath.ts","../../svg-animator-core/src/version/PxSchemaFieldUniverse.ts","../../svg-animator-core/src/version/PxSchemaRelease.ts","../src/shared/PxAnimatorCallbacks.ts","../src/registry/PxAnimatorRegistry.ts","../src/triggers/PxAnimatorTriggers.ts","../src/engines/PxAnimatorFrameLoop.ts","../src/engines/PxAnimatorWebApi.ts","../src/scroll/PxScrollDriver.ts","../src/engines/PxAnimatorBind.ts","../src/dom/PxAnimatorDOM.ts","../src/animator/PxAnimator.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/**\n * \"Equal-in-effect\" comparator for SVGA node trees.\n *\n * Two trees can render identically while differing structurally — extra `<g>`\n * wrappers, different ids, `<use>` clones vs inline copies, a transform expressed\n * as a baked string vs a parts record. This module reduces a tree to what is\n * actually painted: every drawable leaf, with its CUMULATIVE transform matrix\n * (resolved at a given time) and key visual attributes. Comparing those leaf\n * sets ignores representation and catches genuine visual differences.\n *\n * It is a deterministic function applied identically to both trees, so equal\n * inputs (in effect) yield equal output regardless of how each was encoded.\n *\n * Dependency-free on purpose (mirrors the applier's isolation). Scope: the node\n * types and transform forms the player-effects / heavy serializers emit. Color\n * animation is not interpolated — leaves are compared at keyframe instants where\n * sampled values are exact.\n */\n\ntype Mat = [number, number, number, number, number, number];\n\ninterface VmNode {\n type?: string;\n children?: Array<VmNode>;\n [attr: string]: any;\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// MATRIX MATH (2x3 affine, SVG order: [a b c d e f])\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst IDENTITY: Mat = [1, 0, 0, 1, 0, 0];\n\nfunction mul(m: Mat, n: Mat): Mat {\n return [\n m[0] * n[0] + m[2] * n[1],\n m[1] * n[0] + m[3] * n[1],\n m[0] * n[2] + m[2] * n[3],\n m[1] * n[2] + m[3] * n[3],\n m[0] * n[4] + m[2] * n[5] + m[4],\n m[1] * n[4] + m[3] * n[5] + m[5],\n ];\n}\n\nconst translateM = (x: number, y: number): Mat => [1, 0, 0, 1, x, y];\nconst scaleM = (sx: number, sy: number): Mat => [sx, 0, 0, sy, 0, 0];\nfunction rotateM(deg: number): Mat {\n const r = (deg * Math.PI) / 180;\n return [Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0];\n}\nconst skewXM = (deg: number): Mat => [1, 0, Math.tan((deg * Math.PI) / 180), 1, 0, 0];\nconst skewYM = (deg: number): Mat => [1, Math.tan((deg * Math.PI) / 180), 0, 1, 0, 0];\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TRANSFORM EVALUATION\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Parses an SVG transform string (\"translate(..)rotate(..)…\") into a matrix. */\nfunction parseTransformString(s: string): Mat {\n let m = IDENTITY;\n const re = /(translate|rotate|scale|matrix|skewX|skewY)\\(([^)]*)\\)/g;\n let hit: RegExpExecArray | null;\n while ((hit = re.exec(s))) {\n const fn = hit[1];\n const a = hit[2].split(/[\\s,]+/).filter(Boolean).map(Number);\n if (fn === 'translate') m = mul(m, translateM(a[0] || 0, a[1] || 0));\n else if (fn === 'scale') m = mul(m, scaleM(a[0], a.length > 1 ? a[1] : a[0]));\n else if (fn === 'rotate') m = mul(m, rotateM(a[0]));\n else if (fn === 'skewX') m = mul(m, skewXM(a[0]));\n else if (fn === 'skewY') m = mul(m, skewYM(a[0]));\n else if (fn === 'matrix') m = mul(m, [a[0], a[1], a[2], a[3], a[4], a[5]]);\n }\n return m;\n}\n\ninterface Parts { translate?: [number, number]; rotate?: number; scale?: [number, number]; origin?: [number, number]; }\n\n/** Composes a `PxTransformParts` record (player canonical order, origin-pivoted). */\nfunction partsToMatrix(p: Parts): Mat {\n let m = IDENTITY;\n if (p.translate) m = mul(m, translateM(p.translate[0], p.translate[1]));\n const pivot = p.origin && (p.rotate !== undefined || p.scale);\n if (pivot) m = mul(m, translateM(p.origin![0], p.origin![1]));\n if (p.rotate !== undefined) m = mul(m, rotateM(p.rotate));\n if (p.scale) m = mul(m, scaleM(p.scale[0], p.scale[1]));\n if (pivot) m = mul(m, translateM(-p.origin![0], -p.origin![1]));\n return m;\n}\n\nfunction lerp(a: number, b: number, f: number): number { return a + (b - a) * f; }\n\n/** Linear-interpolates a parts record between keyframes at time `t` (ms). */\nfunction interpParts(kfs: Array<any>, t: number): Parts {\n if (!kfs.length) return {};\n if (t <= (kfs[0].time ?? 0)) return kfs[0].value || {};\n if (t >= (kfs[kfs.length - 1].time ?? 0)) return kfs[kfs.length - 1].value || {};\n\n let i = 0;\n while (i < kfs.length - 1 && (kfs[i + 1].time ?? 0) < t) i++;\n const a = kfs[i], b = kfs[i + 1];\n const f = (t - (a.time ?? 0)) / ((b.time ?? 0) - (a.time ?? 0) || 1);\n const va: Parts = a.value || {}, vb: Parts = b.value || {};\n\n const out: Parts = {};\n if (va.translate && vb.translate) out.translate = [lerp(va.translate[0], vb.translate[0], f), lerp(va.translate[1], vb.translate[1], f)];\n else out.translate = va.translate || vb.translate;\n if (va.rotate !== undefined && vb.rotate !== undefined) out.rotate = lerp(va.rotate, vb.rotate, f);\n else out.rotate = va.rotate ?? vb.rotate;\n if (va.scale && vb.scale) out.scale = [lerp(va.scale[0], vb.scale[0], f), lerp(va.scale[1], vb.scale[1], f)];\n else out.scale = va.scale || vb.scale;\n out.origin = va.origin || vb.origin;\n return out;\n}\n\n/** Resolves any transform-slot form (string | bare record | {value} | {keyframes}) to a matrix at `t`. */\nfunction evalTransformValue(v: any, t: number): Mat {\n if (v === undefined || v === null) return IDENTITY;\n if (typeof v === 'string') return parseTransformString(v);\n if (v.keyframes) return partsToMatrix(interpParts(v.keyframes, t));\n if (v.value) return partsToMatrix(v.value);\n if (typeof v === 'object' && !Array.isArray(v)) return partsToMatrix(v); // bare parts record\n return IDENTITY;\n}\n\n/** The node's own transform at `t`: animated slot wins over the static baseline. */\nfunction nodeMatrix(node: VmNode, t: number): Mat {\n if (node.animate && node.animate.transform) return evalTransformValue(node.animate.transform, t);\n if (node.transform !== undefined) return evalTransformValue(node.transform, t);\n return IDENTITY;\n}\n\n/** Linear-interpolates a scalar animation (e.g. opacity); falls back to static / default. */\nfunction evalScalar(animated: any, staticVal: any, fallback: number, t: number): number {\n if (animated && animated.keyframes && animated.keyframes.length) {\n const kfs = animated.keyframes;\n if (t <= (kfs[0].time ?? 0)) return kfs[0].value;\n if (t >= (kfs[kfs.length - 1].time ?? 0)) return kfs[kfs.length - 1].value;\n let i = 0;\n while (i < kfs.length - 1 && (kfs[i + 1].time ?? 0) < t) i++;\n const a = kfs[i], b = kfs[i + 1];\n const f = (t - (a.time ?? 0)) / ((b.time ?? 0) - (a.time ?? 0) || 1);\n return lerp(a.value, b.value, f);\n }\n return staticVal !== undefined ? Number(staticVal) : fallback;\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FLATTEN → multiset of painted primitives\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst CONTAINER_TYPES = new Set(['svg', 'g', 'symbol']);\nconst SKIP_TYPES = new Set(['defs', 'mask', 'clipPath', 'title']);\n\nfunction buildIdMap(node: VmNode, map: Map<string, VmNode>): void {\n if (typeof node.id === 'string') map.set(node.id, node);\n node.children?.forEach(c => buildIdMap(c, map));\n}\n\nfunction num(v: any): number { return v === undefined || v === null ? 0 : Number(v); }\n\n// 2-decimal rounding (sub-pixel). The heavy path bakes matrices into strings\n// rounded to ~4 decimals; comparing at 2 decimals avoids straddling a rounding\n// boundary against the applier's full-precision values, while staying visually exact.\nfunction round(n: number): number { return Math.round(n * 100) / 100 + 0; }\n\nfunction geomKey(node: VmNode): string {\n switch (node.type) {\n case 'rect': return num(node.width) + ',' + num(node.height) + ',' + num(node.x) + ',' + num(node.y);\n case 'ellipse': return num(node.rx) + ',' + num(node.ry) + ',' + num(node.cx) + ',' + num(node.cy);\n case 'circle': return num(node.r) + ',' + num(node.cx) + ',' + num(node.cy);\n case 'path': return String(node.d ?? '');\n default: return '';\n }\n}\n\nfunction describePrimitive(node: VmNode, m: Mat, t: number): string {\n const fill = node.fill ?? '';\n const stroke = node.stroke ?? '';\n const sw = node['stroke-width'] ?? node.strokeWidth ?? '';\n const opacity = round(evalScalar(node.animate?.opacity, node.opacity, 1, t));\n const masked = node.mask ? 1 : 0;\n const mat = m.map(round).join(',');\n return node.type + '|' + geomKey(node) + '|[' + mat + ']|f:' + fill + '|s:' + stroke + '|sw:' + (num(sw) || '') + '|o:' + opacity + '|m:' + masked;\n}\n\nfunction flatten(node: VmNode, parent: Mat, t: number, idMap: Map<string, VmNode>, out: Array<string>): void {\n const type = node.type || '';\n if (SKIP_TYPES.has(type)) return;\n\n const m = mul(parent, nodeMatrix(node, t));\n\n if (type === 'use') {\n const targetId = typeof node.href === 'string' ? node.href.replace(/^#/, '') : '';\n const target = idMap.get(targetId);\n const useM = mul(m, translateM(num(node.x), num(node.y)));\n if (target) flatten(target, useM, t, idMap, out);\n else out.push('UNRESOLVED_USE:#' + targetId);\n return;\n }\n\n if (CONTAINER_TYPES.has(type)) {\n node.children?.forEach(c => flatten(c, m, t, idMap, out));\n return;\n }\n\n out.push(describePrimitive(node, m, t));\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PUBLIC: sample times + visual model + comparison\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Every keyframe `time` found anywhere in the tree, plus 0. @internal */\nexport function collectSampleTimes(node: VmNode, into: Set<number>): void {\n into.add(0);\n const scanAnim = (anim: any) => {\n if (!anim || typeof anim !== 'object') return;\n for (const key of Object.keys(anim)) {\n const kfs = anim[key]?.keyframes;\n if (Array.isArray(kfs)) kfs.forEach(kf => into.add(kf.time ?? 0));\n }\n };\n scanAnim(node.animate);\n if (node.transform && typeof node.transform === 'object' && (node.transform as any).keyframes) {\n (node.transform as any).keyframes.forEach((kf: any) => into.add(kf.time ?? 0));\n }\n node.children?.forEach(c => collectSampleTimes(c, into));\n}\n\n/** Sorted, painted-primitive multiset for the tree at time `t`. @internal */\nexport function visualModelAt(root: VmNode, t: number): Array<string> {\n const idMap = new Map<string, VmNode>();\n buildIdMap(root, idMap);\n const out: Array<string> = [];\n flatten(root, IDENTITY, t, idMap, out);\n return out.sort();\n}\n\nexport interface EffectDiff {\n time: number;\n onlyInA: Array<string>;\n onlyInB: Array<string>;\n}\n\n/**\n * Compares two trees \"in effect\" across all keyframe instants found in either.\n * Returns one entry per time where the painted-primitive multisets differ.\n * @internal\n */\nexport function diffInEffect(a: VmNode, b: VmNode): Array<EffectDiff> {\n const times = new Set<number>();\n collectSampleTimes(a, times);\n collectSampleTimes(b, times);\n\n const diffs: Array<EffectDiff> = [];\n for (const t of Array.from(times).sort((x, y) => x - y)) {\n const ma = visualModelAt(a, t);\n const mb = visualModelAt(b, t);\n const onlyInA = subtractMultiset(ma, mb);\n const onlyInB = subtractMultiset(mb, ma);\n if (onlyInA.length || onlyInB.length) diffs.push({ time: t, onlyInA, onlyInB });\n }\n return diffs;\n}\n\n/** Items in `a` not matched one-for-one in `b` (multiset difference). */\nfunction subtractMultiset(a: Array<string>, b: Array<string>): Array<string> {\n const counts = new Map<string, number>();\n for (const x of b) counts.set(x, (counts.get(x) || 0) + 1);\n const extra: Array<string> = [];\n for (const x of a) {\n const c = counts.get(x) || 0;\n if (c > 0) counts.set(x, c - 1);\n else extra.push(x);\n }\n return extra;\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// THE renderer — the one place a `PxNode` tree becomes elements, for EVERY DOM player.\n//\n// The web player, the React component and the Vue component used to carry a renderer each.\n// Three copies drifted: React and Vue skipped sanitization, wrote camelCase names SVG ignores\n// (`maskType`, and in Vue every presentation attribute — gradients lost their colours), wrote\n// the `domType` escape key as a literal attribute (so `<feColorMatrix>` lost its `type` and the\n// filter painted its target black), and put CSS-only properties out as dead attributes.\n//\n// So every DECISION lives here, once:\n// - which tag (`type`, default `g`) and whether it is allowed at all;\n// - the `domType` escape key → the real `type` attribute;\n// - which keys are attributes (`toDomProps`) and what their values may be\n// (`sanitizeAttributeValue`);\n// - the NAME each attribute has in SVG (`camelCaseToKebabWordIfNeeded`, `className` → `class`);\n// - which properties are CSS-only and so go to `style`, plus the node's own `style` record;\n// - children, or the node's `textContent` when it has none.\n//\n// A framework supplies exactly one thing — `PxElementFactory`: given the finished spec, make\n// an element (DOM node / React element / Vue vnode). It decides nothing about the document.\n// Platform-neutral on purpose: no DOM here, so it also runs on a server.\n\nimport { PX_TEXT_CONTENT_ATTR } from '../format/PxAnimatorConstants';\nimport type { PxNode } from '../format/PxAnimatorTypes';\nimport { PxDiagnosticCode } from '../playback/PxDiagnosticCode';\nimport { createDiagnostics, PxDiagnosticKind, type PxDiagnostics } from '../playback/PxDiagnostics';\nimport { camelCaseToKebabWordIfNeeded } from '../util/PxAnimatorUtil';\nimport { PX_CSS_ONLY_STYLE_PROPS, PX_DISALLOWED_SVG_TAGS_LOWER, sanitizeAttributeValue, toDomProps } from '../util/PxNodeProps';\n\n\n/**\n * One element, fully decided. Everything a factory needs and nothing it has to think about.\n * @internal\n */\nexport interface PxElementSpec<T> {\n /** The SVG tag name. */\n tag: string;\n /** Attributes under the names SVG reads (`stroke-width`, `viewBox`, `class`), sanitized. */\n attrs: Record<string, string>;\n /** Inline style, camelCase property → value; `undefined` when there is none. */\n style: Record<string, string> | undefined;\n /** Rendered children, blocked ones already dropped. Empty when the node has none. */\n children: Array<T>;\n /** The node's text — set ONLY when `children` is empty (a line `<tspan>` can carry both,\n * and then the styled child spans are what renders). */\n text: string | undefined;\n /** The source node — for the node's `id` (refs) and nothing about rendering. */\n node: PxNode;\n /** True for the tree's root `<svg>`. */\n isRoot: boolean;\n /** Position among its siblings — a stable list key for frameworks that want one. */\n index: number;\n}\n\n/**\n * The ONE thing a framework supplies: make an element from a finished spec.\n * @internal\n */\nexport type PxElementFactory<T> = (spec: PxElementSpec<T>) => T;\n\n/** The attribute SVG calls `class`; the wire and the DOM property call it `className`. */\nconst CLASS_NAME_KEY = 'className';\nconst CLASS_ATTR = 'class';\n\n/** The escape key for an element whose own `type` ATTRIBUTE collides with the node-tag key —\n * `feColorMatrix`, `feTurbulence`, `feFunc*` (written by the editor at one choke point). */\nconst DOM_TYPE_KEY = 'domType';\nconst TYPE_ATTR = 'type';\n\nconst DEFAULT_TAG = 'g';\n\n\n/**\n * Renders a `PxNode` tree through `factory`. Returns `null` for a missing node or a blocked\n * root tag; blocked descendants are dropped and reported.\n * @internal\n */\nexport function renderPxTree<T>(node: PxNode | undefined, factory: PxElementFactory<T>, diag?: PxDiagnostics): T | null {\n return node ? renderOne(node, factory, diag, true, 0) : null;\n}\n\nfunction renderOne<T>(node: PxNode, factory: PxElementFactory<T>, diag: PxDiagnostics | undefined, isRoot: boolean, index: number): T | null {\n const { type, children, style, ...props } = node;\n const tag = type || DEFAULT_TAG;\n\n if (PX_DISALLOWED_SVG_TAGS_LOWER.has(tag.toLowerCase())) {\n // `document`: a blocked tag is content the FILE asked for, so the file is what changes.\n (diag ?? createDiagnostics(undefined, '[PxAnimator]')).warn(PxDiagnosticKind.document, PxDiagnosticCode.blockedTag, tag);\n return null;\n }\n\n // `type` is reserved for the tag and `toDomProps` drops it, so the escaped value is lifted\n // out first and written back as the real attribute below. Without it the primitive is lost\n // and an EMPTY `<filter>` paints its target transparent black.\n const domType = props[DOM_TYPE_KEY];\n if (domType !== undefined) delete props[DOM_TYPE_KEY];\n\n const attrs: Record<string, string> = {};\n let inlineStyle: Record<string, string> | undefined;\n\n const domProps = toDomProps(props);\n for (const propName of Object.keys(domProps)) {\n // `undefined` means \"do not emit\" (whitelist miss, blocked dangerous value, …) — a\n // writer would otherwise coerce it to the literal string \"undefined\".\n const sanitized = sanitizeAttributeValue(propName, domProps[propName]);\n if (sanitized === undefined) continue;\n\n // CSS-only properties (mix-blend-mode, isolation) are not SVG presentation attributes;\n // as attributes the browser ignores them, so they go through `style`.\n if (PX_CSS_ONLY_STYLE_PROPS.has(propName)) {\n (inlineStyle ??= {})[propName] = String(sanitized);\n continue;\n }\n attrs[propName === CLASS_NAME_KEY ? CLASS_ATTR : camelCaseToKebabWordIfNeeded(propName)] = sanitized;\n }\n if (domType !== undefined) attrs[TYPE_ATTR] = String(domType);\n\n // The node's own `style` record — declarations, not attributes. Written after the CSS-only\n // properties so an explicit declaration wins over the same property given as an attribute.\n if (style) {\n for (const styleProp of Object.keys(style)) (inlineStyle ??= {})[styleProp] = String(style[styleProp]);\n }\n\n const rendered: Array<T> = [];\n if (children) {\n children.forEach((child, i) => {\n const el = renderOne(child, factory, diag, false, i);\n if (el !== null) rendered.push(el);\n });\n }\n\n const ownText = props[PX_TEXT_CONTENT_ATTR];\n const text = !rendered.length && typeof ownText === 'string' && ownText ? ownText : undefined;\n\n return factory({ tag, attrs, style: inlineStyle, children: rendered, text, node, isRoot, index });\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// Scroll-timeline PROGRESS MATH — pure functions, no DOM. This is the reference\n// implementation for `animator.timelineSource: 'scroll'` (the player-measured path — `timeline.engine: 'js'`, and the fallback of `auto`,\n// both engines): the DOM driver in svg-animator-web measures rects/offsets and calls in\n// here; the editor's design doc (`app/src/svgeditor/animation/scroll-timeline.design.md`\n// §4) documents the same formulas — keep them in sync.\n\nimport { clamp, PX_DEFAULT_DURATION_MS } from '../util/PxAnimatorUtil';\nimport type { PxAnimatorConfig, PxScroll, PxScrollPhase, PxScrollRangePoint } from '../format/PxAnimatorTypes';\n\n\n/** Is this document scroll-driven? (`animator.timelineSource === 'scroll'`) @internal */\nexport function isScrollTimeline(config: PxAnimatorConfig | undefined): boolean {\n return config?.timelineSource === 'scroll';\n}\n\n/**\n * The seek-space length (ms) a scroll progress of 1 maps to: duration × finite\n * iterations. `'infinite'` is meaningless on a finite progress timeline (see design doc\n * D4) — treated as 1 with the read-side warning left to the consumer.\n * @internal\n */\nexport function scrollTotalDurationMs(config: PxAnimatorConfig | undefined): number {\n const duration = (typeof config?.duration === 'number' && config.duration > 0)\n ? config.duration : PX_DEFAULT_DURATION_MS;\n const iterations = (typeof config?.iterations === 'number' && config.iterations > 0)\n ? config.iterations : 1;\n return duration * iterations;\n}\n\n/**\n * A named phase's interval in `u`-space.\n *\n * `u` is the subject's \"journey distance\": with `sTop` = subject's leading edge in\n * scrollport coordinates, `u = vpSize − sTop` — 0 exactly when the subject is about to\n * enter (leading edge at the scrollport's trailing edge), growing as the user scrolls.\n * The `min`/`max` pairs make every formula valid BOTH for a subject smaller than the\n * scrollport and one larger than it (where \"fully visible\" flips to \"covers the\n * scrollport\") — the same case split CSS specifies for its named timeline ranges.\n * @internal\n */\nexport function scrollPhaseInterval(\n phase: PxScrollPhase, subjectSize: number, scrollportSize: number\n): [number, number] {\n const s = subjectSize, vp = scrollportSize;\n switch (phase) {\n case 'cover': return [0, s + vp];\n case 'entry': return [0, Math.min(s, vp)];\n case 'contain': return [Math.min(s, vp), Math.max(s, vp)];\n case 'exit': return [Math.max(s, vp), s + vp];\n case 'entry-crossing': return [0, s];\n case 'exit-crossing': return [vp, s + vp];\n }\n}\n\nconst DEFAULT_PHASE: PxScrollPhase = 'cover';\n\n/** One range endpoint resolved to a `u`-space value. */\nfunction resolveRangePointU(\n point: PxScrollRangePoint | undefined, defaultFraction: number,\n subjectSize: number, scrollportSize: number\n): number {\n const [u0, u1] = scrollPhaseInterval(point?.phase ?? DEFAULT_PHASE, subjectSize, scrollportSize);\n const fraction = typeof point?.fraction === 'number' ? point.fraction : defaultFraction;\n return u0 + fraction * (u1 - u0);\n}\n\n/**\n * `kind: 'view'` progress ∈ [0, 1]: where the subject's journey sits within the\n * configured range.\n *\n * @param subjectStart subject's leading edge in scrollport coordinates\n * (`subjectRect.top − scrollportRect.top` on the resolved axis)\n * @param subjectSize subject size on the axis\n * @param scrollportSize scrollport size on the axis\n *\n * A degenerate/inverted range (uStart ≥ uEnd — e.g. zero-size subject with an `entry`\n * range) reports 1 once the point is passed, 0 before — never NaN.\n * @internal\n */\nexport function scrollViewProgress(\n subjectStart: number, subjectSize: number, scrollportSize: number,\n range: PxScroll['range'] | undefined\n): number {\n const u = scrollportSize - subjectStart;\n const uStart = resolveRangePointU(range?.start, 0, subjectSize, scrollportSize);\n const uEnd = resolveRangePointU(range?.end, 1, subjectSize, scrollportSize);\n if (uEnd <= uStart) return u >= uEnd ? 1 : 0;\n return clamp((u - uStart) / (uEnd - uStart), 0, 1);\n}\n\n/**\n * `kind: 'scroll'` progress ∈ [0, 1]: the scroller's offset ratio mapped through the\n * range (phases don't exist here — `fraction` is of the total scroll range).\n *\n * `maxOffset === 0` (nothing to scroll) reports 1, matching the CSS spec's rule that a\n * zero-length timeline is at 100%.\n * @internal\n */\nexport function scrollOffsetProgress(\n offset: number, maxOffset: number,\n range: PxScroll['range'] | undefined\n): number {\n const raw = maxOffset > 0 ? clamp(offset / maxOffset, 0, 1) : 1;\n const start = typeof range?.start?.fraction === 'number' ? range.start.fraction : 0;\n const end = typeof range?.end?.fraction === 'number' ? range.end.fraction : 1;\n if (end <= start) return raw >= end ? 1 : 0;\n return clamp((raw - start) / (end - start), 0, 1);\n}\n\n/**\n * Resolve a logical axis to a physical one. `block`/`inline` are writing-mode relative:\n * in horizontal writing (`horizontal-tb`, the default) block flows vertically; in\n * vertical writing modes it flows horizontally.\n * @internal\n */\nexport function scrollResolveAxis(\n axis: PxScroll['axis'] | undefined, writingMode: string | undefined\n): 'x' | 'y' {\n const a = axis ?? 'block';\n if (a === 'x' || a === 'y') return a;\n const vertical = !!writingMode && writingMode.startsWith('vertical');\n if (a === 'inline') return vertical ? 'y' : 'x';\n return vertical ? 'x' : 'y'; // block\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// THE PLAYER'S WIRE-KEY INVENTORY — every field the player schema can carry, as a canonical\n// path, sorted. The library half of the editor's ship-guard (task 5.1).\n//\n// Why a committed list at all: A RENAME IS LENGTH-NEUTRAL. One name out, one in, same count,\n// green suite — which is how three renames shipped with no compat read, the last one wiping\n// the geometry of every shape-preset path on open. Diffing the list names the key that left.\n//\n// It lives HERE, not only in the editor, because the library ships on its own: a player\n// released with a key quietly gone breaks every document using it, editor or not.\n//\n// CANONICAL PATHS — the same rule as the editor's `collectSchemaFields`, so the two lists speak\n// one dialect: each object schema is enumerated ONCE, at the first path it is reached by\n// (breadth-first); `[]` marks an array item, `{*}` a record value or open-object value, `|i` /\n// `|tag` a union member. Without the once-only rule every recursive `children` re-lists the\n// whole node schema and the inventory explodes into thousands of paths that say nothing new.\n// ============================================================================\n\nimport { describeSchema, type PxSchema } from '../schema/PxSchema';\n\n/** Every field identity `root` can carry, as canonical paths, sorted. @internal */\nexport function schemaFieldUniverse(root: PxSchema<any, any>): Array<string> {\n const fields = new Set<string>();\n const enumerated = new Set<unknown>();\n const queue: Array<{ schema: PxSchema<any, any>; path: string }> = [{ schema: root, path: '' }];\n\n while (queue.length) {\n const { schema, path } = queue.shift()!;\n const d = describeSchema(schema);\n switch (d.kind) {\n case 'shape': {\n if (enumerated.has(schema)) break; // already listed under its canonical path\n enumerated.add(schema);\n for (const key of Object.keys(d.shape)) {\n const id = path ? path + '.' + key : key;\n fields.add(id);\n queue.push({ schema: d.shape[key], path: id });\n }\n if (d.openValue) queue.push({ schema: d.openValue, path: path + '{*}' });\n break;\n }\n case 'optional': queue.push({ schema: d.inner, path }); break;\n case 'array': queue.push({ schema: d.item, path: path + '[]' }); break;\n case 'lazy': queue.push({ schema: d.resolved, path }); break;\n case 'record': queue.push({ schema: d.value, path: path + '{*}' }); break;\n case 'union':\n d.members.forEach((member, i) => queue.push({ schema: member, path: path + '|' + i }));\n break;\n case 'discriminatedUnion':\n for (const member of d.members) queue.push({ schema: member, path: path + '|' + discriminantOf(member, d.key) });\n break;\n default: break; // tuple / leaf: no named fields\n }\n }\n return [...fields].sort();\n}\n\n/** The literal a discriminated-union member carries at its discriminant, unwrapping an\n * OPTIONAL discriminant (`type?: 'time'`). `?` when it carries none. */\nfunction discriminantOf(member: PxSchema<any, any>, key: string): string {\n const d = describeSchema(member);\n if (d.kind !== 'shape') return '?';\n const keySchema = d.shape[key];\n if (keySchema === undefined) return '?';\n const kd = describeSchema(keySchema);\n return String((kd.kind === 'optional' ? kd.inner : keySchema)._default);\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// SCHEMA RELEASES (tasks 5.3 / 5.4) — the decision \"does this release move the schema\n// version, and which part?\" made by the DIFF, not by a person, plus the dated log of every\n// release kept beside the step table (`schema-releases.player.json`).\n//\n// Pure functions, so the release CLI (`scripts/schema-release.mjs`) and the guard test run the\n// SAME rule — a CLI and a test that each re-implement it are two rules that drift apart.\n//\n// The diff is over the canonical field inventory (`schemaFieldUniverse`): key paths only, so\n// descriptions and comments can never make two identical schemas look different.\n// ============================================================================\n\nimport { parseWireVersion, PxWireStepKind, type PxWireVersionStep } from './PxWireVersion';\n\n/** One entry of the release log — what shipped, when, and what it changed. @internal */\nexport interface SchemaReleaseRecord {\n readonly version: string;\n /** ISO date, `YYYY-MM-DD`. */\n readonly date: string;\n /** The first release: nothing to compare it against. */\n readonly baseline?: boolean;\n readonly added: ReadonlyArray<string>;\n readonly removed: ReadonlyArray<string>;\n readonly note?: string;\n}\n\n/** Keys that appeared and keys that left between two inventories, each sorted. @internal */\nexport function diffFieldUniverse(\n previous: ReadonlyArray<string>, current: ReadonlyArray<string>,\n): { added: Array<string>; removed: Array<string> } {\n const prev = new Set(previous);\n const cur = new Set(current);\n return {\n added: current.filter(k => !prev.has(k)).sort(),\n removed: previous.filter(k => !cur.has(k)).sort(),\n };\n}\n\n/** What a release must do about the version. `refuse` set means: do not release as-is. @internal */\nexport interface SchemaReleasePlan {\n /** Did any key appear or leave since the last release? */\n readonly changed: boolean;\n /** Which kind of step the change requires — a removal is never additive. */\n readonly requiredKind?: PxWireStepKind;\n /** The version this release must carry. */\n readonly requiredVersion?: string;\n readonly refuse?: string;\n}\n\n/**\n * THE BUMP RULE. A key change requires `b + 1` and a step that explains it; no key change\n * requires nothing. The rule never picks the number by taste — the inventory diff does.\n * @internal\n */\nexport function planSchemaRelease(p: {\n readonly added: ReadonlyArray<string>;\n readonly removed: ReadonlyArray<string>;\n /** `PX_WIRE_SCHEMA_VERSION` — what the source says now. */\n readonly declared: string;\n /** The version of the last release record. */\n readonly lastReleased: string;\n readonly steps: ReadonlyArray<PxWireVersionStep>;\n}): SchemaReleasePlan {\n const changed = p.added.length > 0 || p.removed.length > 0;\n const last = parseWireVersion(p.lastReleased);\n const declared = parseWireVersion(p.declared);\n if (!last || !declared) return { changed, refuse: 'Unparseable version: ' + p.lastReleased + ' / ' + p.declared + '.' };\n\n const requiredVersion = last.a + '.' + (last.b + 1);\n if (!changed) {\n // Same keys: nothing to bump. A bump anyway is a SEMANTIC change and still needs its step.\n if (declared.b === last.b && declared.a === last.a) return { changed };\n const step = p.steps.find(s => s.to === p.declared);\n return step ? { changed, requiredVersion: p.declared }\n : { changed, refuse: 'The version moved to ' + p.declared + ' with no key change and no step explaining it.' };\n }\n\n const requiredKind = p.removed.length ? PxWireStepKind.converted : PxWireStepKind.additive;\n const summary = p.added.length + ' key(s) added, ' + p.removed.length + ' removed';\n if (declared.a !== last.a || declared.b !== last.b + 1) {\n return {\n changed, requiredKind, requiredVersion,\n refuse: 'The player schema changed (' + summary + ') but PX_WIRE_SCHEMA_VERSION is '\n + p.declared + '. Set it to ' + requiredVersion + ' and add the PX_WIRE_STEPS entry.',\n };\n }\n const step = p.steps.find(s => s.to === p.declared);\n if (!step) {\n return { changed, requiredKind, requiredVersion, refuse: 'No PX_WIRE_STEPS entry reaches ' + p.declared + '.' };\n }\n if (requiredKind === PxWireStepKind.converted && step.kind !== PxWireStepKind.converted) {\n return {\n changed, requiredKind, requiredVersion,\n refuse: 'Keys were REMOVED (' + p.removed.join(', ') + '), which is never additive — the '\n + p.declared + ' step must be `converted`, with an up().',\n };\n }\n return { changed, requiredKind, requiredVersion };\n}\n\n/**\n * Everything wrong with the release log, as sentences — empty when it is consistent. The log\n * must start at the baseline, move strictly forward, END at the version the source declares,\n * and every release after the baseline must have the step that explains it.\n * @internal\n */\nexport function releaseLogProblems(\n releases: ReadonlyArray<SchemaReleaseRecord>, steps: ReadonlyArray<PxWireVersionStep>,\n declared: string, baseline: string,\n): Array<string> {\n const problems: Array<string> = [];\n if (!releases.length) return ['The release log is empty.'];\n if (!releases[0].baseline || releases[0].version !== baseline) {\n problems.push('The first release must be the baseline ' + baseline + '.');\n }\n for (let i = 1; i < releases.length; i++) {\n const prev = parseWireVersion(releases[i - 1].version);\n const cur = parseWireVersion(releases[i].version);\n if (!prev || !cur || cur.a !== prev.a || cur.b !== prev.b + 1) {\n problems.push('Release ' + releases[i].version + ' does not follow ' + releases[i - 1].version + ' by one `b` step.');\n }\n if (releases[i].date < releases[i - 1].date) problems.push('Release ' + releases[i].version + ' is dated before its predecessor.');\n const step = steps.find(s => s.to === releases[i].version);\n if (!step) problems.push('Release ' + releases[i].version + ' has no PX_WIRE_STEPS entry.');\n else if (releases[i].removed.length && step.kind !== PxWireStepKind.converted) {\n problems.push('Release ' + releases[i].version + ' removed keys, but its step is not `converted`.');\n }\n }\n const latest = releases[releases.length - 1].version;\n if (latest !== declared) {\n problems.push('PX_WIRE_SCHEMA_VERSION is ' + declared + ' but the last release record is ' + latest\n + ' — a bump needs its changelog entry (run scripts/schema-release.mjs --apply).');\n }\n return problems;\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 type { PxEngineCallbacks } from '@pixodesk/svg-animator-core';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\n\n/**\n * THE PAGE-WIDE REGISTRY — every live animator in the window, whoever created it.\n *\n * Like lottie-web's `getRegisteredAnimations()`: a dev tool, a test, or a \"pause everything\n * while the tab is hidden\" can reach every player without holding on to each `createAnimator`\n * result. An animator joins when it is built and leaves on `destroy()`; play / pause / cancel /\n * finish are announced to subscribers.\n *\n * ONE registry per window, whatever the bundling: the store hangs off `globalThis` under a\n * `Symbol.for` key, so the ESM build in one script and the UMD build in another share it. It\n * is also reachable by NAME — `globalThis.__pixodeskAnimators` — for tooling that does not\n * import the library (a devtools panel reading a page it did not build). Web only: React\n * Native players never touch it.\n *\n * EXPERIMENTAL (2026-09): not settled — the event names, what `getAll()` includes and the\n * global's name may change without a major version. Documented as such in\n * docs/library/web-player.md (\"Every animator on the page\").\n */\n\n/** What changed for an animator in the page-wide registry. Experimental — may change. @public */\nexport type PxAnimatorsEvent = 'add' | 'remove' | 'play' | 'pause' | 'cancel' | 'finish';\n\n/** Called for every {@link PxAnimatorsEvent} of every animator in the window. Experimental — may change. @public */\nexport type PxAnimatorsListener = (event: PxAnimatorsEvent, animator: PxAnimatorApi) => void;\n\n/** The store itself — what `globalThis.__pixodeskAnimators` holds. Experimental — may change. @public */\nexport interface PxAnimatorRegistry {\n /** Every live animator, in creation order. */\n getAll(): ReadonlyArray<PxAnimatorApi>;\n /** Announcements of add / remove / play / pause / cancel / finish; returns the unsubscribe. */\n subscribe(listener: PxAnimatorsListener): () => void;\n}\n\ninterface RegistryStore extends PxAnimatorRegistry {\n readonly animators: Set<PxAnimatorApi>;\n readonly listeners: Set<PxAnimatorsListener>;\n}\n\nconst STORE_KEY = Symbol.for('@pixodesk/svg-animator-web:animators');\n/** The by-name handle for tooling that cannot import the library. */\nconst GLOBAL_NAME = '__pixodeskAnimators';\n\n/** The one store of this window, created on first use. `globalThis` carries it under a\n * symbol (shared across bundle copies) and, for discoverability, under {@link GLOBAL_NAME}. */\nfunction store(): RegistryStore {\n // `globalThis` has no declared slot for either key — this is exactly the ad-hoc global\n // a cross-bundle registry needs, so it is typed as the record it is used as.\n const g = globalThis as unknown as Record<string | symbol, RegistryStore | undefined>;\n let s = g[STORE_KEY];\n if (!s) {\n const animators = new Set<PxAnimatorApi>();\n const listeners = new Set<PxAnimatorsListener>();\n s = {\n animators,\n listeners,\n getAll: () => Array.from(animators),\n subscribe: (listener) => {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n };\n g[STORE_KEY] = s;\n if (g[GLOBAL_NAME] === undefined) g[GLOBAL_NAME] = s;\n }\n return s;\n}\n\n/** Every live animator in this window, in creation order. Experimental — may change. @public */\nexport function getAllAnimators(): ReadonlyArray<PxAnimatorApi> {\n return store().getAll();\n}\n\n/** Hear every animator in this window start, pause, cancel, finish, appear or go — returns\n * the unsubscribe. Experimental — may change. @public */\nexport function onAnimatorsChange(listener: PxAnimatorsListener): () => void {\n return store().subscribe(listener);\n}\n\n/** A listener must never break playback: its error surfaces on its own, asynchronously. */\nfunction notify(event: PxAnimatorsEvent, animator: PxAnimatorApi): void {\n for (const listener of store().listeners) {\n try { listener(event, animator); } catch (e) { setTimeout(() => { throw e; }, 0); }\n }\n}\n\n/** @internal Adds a freshly built animator; `destroy()` on the returned API removes it again. */\nexport function registerAnimator(animator: PxAnimatorApi): void {\n const s = store();\n if (s.animators.has(animator)) return;\n s.animators.add(animator);\n notify('add', animator);\n const destroy = animator.destroy.bind(animator);\n animator.destroy = () => {\n destroy();\n if (s.animators.delete(animator)) notify('remove', animator);\n };\n}\n\n/**\n * @internal The engine callbacks with the registry listening in: each of the caller's own\n * handlers still runs first. `api()` resolves the animator lazily — the callbacks are built\n * before it exists.\n */\nexport function withRegistryEvents(callbacks: PxEngineCallbacks | undefined, api: () => PxAnimatorApi | undefined): PxEngineCallbacks {\n const fire = (event: PxAnimatorsEvent): void => { const a = api(); if (a) notify(event, a); };\n return {\n ...callbacks,\n onPlay: () => { callbacks?.onPlay?.(); fire('play'); },\n onPause: () => { callbacks?.onPause?.(); fire('pause'); },\n onCancel: () => { callbacks?.onCancel?.(); fire('cancel'); },\n onFinish: () => { callbacks?.onFinish?.(); fire('finish'); },\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 { PxDiagnosticCode, PxDiagnosticKind, PxMouseOutAction, PxTriggerStart, resolveTrigger,\n type PxDiagnostics, type PxTrigger } from '@pixodesk/svg-animator-core';\nimport { createDiagnostics } from '@pixodesk/svg-animator-core/internal';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\nimport { createVisibilityGate } from './PxVisibilityGate';\n\n\n/**\n * Wires a document's trigger block to its root element.\n *\n * TWO INDEPENDENT AXES, which is why there is no `scrollIntoView` here:\n * - `start` — what STARTS the animation: 'load' (default), 'mouseOver', 'click', or 'none'\n * (nothing but the API does).\n * - `offScreen` + `visibilityThreshold` + `visibilityDebounce` — whether it may RUN, whatever\n * started it. Owned by {@link createVisibilityGate}, wired for every document.\n *\n * `start: 'load'` behind the default gate is what `startOn: 'scrollIntoView'` used to mean: hold\n * at frame 0 until enough is on screen, play, pause when it leaves, resume when it returns. The\n * difference is that the same gate now also applies to a document started by a click or a hover.\n *\n * `mouseOut` is what happens when the pointer LEAVES ('continue' by default, or pause / reset /\n * reverse); it is read only for `start: 'mouseOver'`. A `click` document is a plain play/pause\n * toggle with nothing to configure.\n *\n * @param api The animator API instance to control.\n * @param trigger The trigger configuration. `finish` belongs to the PLAYER (what happens after a\n * natural end), not to the trigger wiring, and is not read here.\n * @returns A disposer that detaches every listener, observer and timer this call attached\n * (review §14). `createAnimator` ties it to `destroy()`. Call it yourself before re-arming an\n * element you 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 `start` = 'load', no `offScreen` = 'pause', no `mouseOut` = 'continue', no threshold\n // = 0.5, no debounce = 150ms. The threshold default must match the editor model's\n // (TSvgSvgAnimationAttr.visibilityThreshold), which OMITS the value on the wire when it equals it.\n const resolved = 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 mouse-out action put the animation into reverse, so the next start\n // can restore forward playback without clobbering a custom playback rate set through the API.\n let reversed = false;\n\n /** Ensures forward playback and starts or resumes the animation. */\n const start = (): void => {\n if (reversed) {\n reversed = false;\n api.setPlaybackRate(1);\n }\n api.play();\n };\n\n // Permission to run, for every document and whatever starts it.\n const gate = createVisibilityGate(root, resolved, {\n isPlaying: () => api.isPlaying(),\n play: start,\n pause: () => api.pause(),\n cancel: () => api.cancel(),\n });\n cleanups.push(() => gate.dispose());\n\n /** What to do when the pointer leaves — `start: 'mouseOver'` only. */\n const handleMouseOut = (): void => {\n switch (resolved.mouseOut) {\n case PxMouseOutAction.pause:\n api.pause();\n break;\n case PxMouseOutAction.reset:\n api.cancel();\n break;\n case PxMouseOutAction.reverse:\n // Play the animation backwards from its current position.\n reversed = true;\n api.setPlaybackRate(-1);\n api.play();\n break;\n case PxMouseOutAction.continue:\n default:\n // Do nothing\n break;\n }\n };\n\n // ---- What starts it ----\n switch (resolved.start) {\n case PxTriggerStart.load: {\n // The only start that the gate may hold: nobody interacted, so there is nothing to\n // honour immediately. `requestStart(false)` plays now if enough is already on screen\n // (after the debounce), and otherwise waits for it to be.\n const startHandler = () => gate.requestStart(false);\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 PxTriggerStart.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 // `mouseOut: 'reverse'` the out action PLAYS (`setPlaybackRate(-1); play()`), so an\n // untriggered leave would start the animation running backwards.\n let enteredOnce = false;\n const mouseOverHandler = () => { enteredOnce = true; gate.requestStart(true); };\n const mouseOutHandler = () => { if (enteredOnce) handleMouseOut(); };\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 PxTriggerStart.click: {\n // A plain toggle. The reader is pointing at it, so a start never waits for the gate.\n const clickHandler = () => {\n if (api.isPlaying()) api.pause();\n else gate.requestStart(true);\n };\n root.addEventListener('click', clickHandler);\n cleanups.push(() => root.removeEventListener('click', clickHandler));\n break;\n }\n\n case PxTriggerStart.none:\n // No auto-start; external code must call play(). The gate still applies afterwards,\n // so an API-started animation pauses when it scrolls out of view.\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\n/**\n * An id as a CSS id-selector, ESCAPED.\n *\n * `'#' + id` is not valid CSS whenever the id starts with a digit — and editor ids often do\n * (`2kjrlj4l`), because SVG and HTML both allow it where CSS does not. `querySelector` then\n * THROWS a SyntaxError rather than returning null, so the animation never starts and the\n * document looks broken for a reason nothing in it explains. Punctuation has the same problem.\n */\nexport function getSelector(id: string) {\n // return `[data-px-id=\"${id}\"]`; FIXME\n return '#' + escapeCssId(id);\n}\n\n/** `CSS.escape` where the engine has it; otherwise the two rules that actually bite. */\nfunction escapeCssId(id: string): string {\n const css = (globalThis as { CSS?: { escape?: (value: string) => string } }).CSS;\n if (typeof css?.escape === 'function') return css.escape(id);\n return id\n // A leading digit is spelled as its hex code point plus a separating space.\n .replace(/^([0-9])/, (_all, digit: string) => '\\\\3' + digit + ' ')\n // Anything outside the CSS identifier set is backslash-escaped.\n .replace(/([^\\w\\-\\\\ ])/g, '\\\\$1');\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 (`start` 'load',\n // `offScreen` 'pause' — so an unseen animation does not run).\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 (`start` 'load',\n // `offScreen` 'pause' — so an unseen animation does not run).\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\n// The DOM half of `animator.timelineSource: 'scroll'` (`timeline.engine: 'js'` — and the fallback of `auto`\n// and the reference implementation): measures the scroller/subject and turns scroll\n// position into animation progress via the pure math in core `PxScrollMath`. The\n// consumer decides what a progress value does (frames: seek `setCurrentTime`; waapi:\n// same, via the engine-agnostic API).\n//\n// Event model: `scroll` (+`resize`) listeners, passive, coalesced into ONE\n// requestAnimationFrame tick — at most one measurement + one seek per frame. A\n// `ResizeObserver` (where available) catches subject/scroller size changes that happen\n// without a scroll. TODO (optimization, deliberate v1 omission): an IntersectionObserver\n// gate to park the listeners entirely while the subject is far outside its range.\n\nimport { PxDiagnosticCode, PxDiagnosticKind, type PxAnimatorConfig, type PxDiagnostics, type PxScroll } from '@pixodesk/svg-animator-core';\nimport { createDiagnostics, isScrollTimeline, scrollOffsetProgress, scrollResolveAxis, scrollViewProgress } from '@pixodesk/svg-animator-core/internal';\n\n\n// ── Native timeline support (`timeline.engine: 'native'`, tried first by `auto`) ────────────────────────────────────────\n// `ScrollTimeline`/`ViewTimeline` aren't in TS's dom lib yet — minimal local declarations,\n// resolved from globalThis so absence is an ordinary feature-detect, never a crash.\n\ninterface NativeTimelineCtor { new(options: Record<string, unknown>): AnimationTimeline }\n\ninterface PxNativeScrollTimeline {\n timeline: AnimationTimeline;\n /** WAAPI `Animation.rangeStart/rangeEnd` values (TimelineRangeOffset-shaped). */\n rangeStart?: Record<string, unknown>;\n rangeEnd?: Record<string, unknown>;\n}\n\nfunction nativeRangeOffset(point: { phase?: string; fraction?: number } | undefined, defaultFraction: number, view: boolean): Record<string, unknown> | undefined {\n const fraction = typeof point?.fraction === 'number' ? point.fraction : defaultFraction;\n const pct = (globalThis as { CSS?: { percent?: (n: number) => unknown } }).CSS?.percent?.(fraction * 100);\n if (pct === undefined) return undefined;\n // Named phases exist only on VIEW timelines; a scroll timeline takes a bare offset.\n return view ? { rangeName: point?.phase ?? 'cover', offset: pct } : { offset: pct };\n}\n\n/**\n * Build the browser-native timeline (`mode: 'native'` / `auto`), or `null` when the platform\n * doesn't support scroll-driven WAAPI timelines — the caller then falls back to the\n * custom driver (D8: the option is a preference, never a requirement).\n */\nexport function createNativeScrollTimeline(\n subject: Element,\n config: PxAnimatorConfig | undefined,\n diag?: PxDiagnostics,\n): PxNativeScrollTimeline | null {\n // Exported, so the channel is optional and falls back to the console (review §5).\n const report = diag ?? createDiagnostics(undefined, '[PxAnimator]');\n if (!config || !isScrollTimeline(config)) return null;\n const scroll: PxScroll = config.scroll || {};\n const kind = scroll.kind ?? 'view';\n\n // Smoothing is a per-frame easing of progress, which a browser-native timeline (a direct\n // scroll→time mapping) cannot express. Honor the authored LOOK over the perf hint — D8\n // already makes `native` a preference rather than a requirement.\n if (scroll.smoothing) {\n report.warn(PxDiagnosticKind.platform, PxDiagnosticCode.scrollSmoothingNeedsOwnDriver);\n return null;\n }\n\n const g = globalThis as unknown as { ScrollTimeline?: NativeTimelineCtor; ViewTimeline?: NativeTimelineCtor };\n const view = kind === 'view';\n const Ctor = view ? g.ViewTimeline : g.ScrollTimeline;\n if (typeof Ctor !== 'function') return null;\n\n const axis = scroll.axis ?? 'block';\n let timeline: AnimationTimeline;\n try {\n if (view) {\n // `scroll.subject` is the same indirection the platform's own ViewTimeline takes.\n timeline = new Ctor({ subject: resolveScrollSubject(subject, scroll.subject, report), axis });\n } else {\n const source = scroll.source === 'root'\n ? documentScroller()\n : (findNearestScroller(subject, 'y') || findNearestScroller(subject, 'x') || documentScroller());\n timeline = new Ctor({ source, axis });\n }\n } catch (e) {\n report.warn(PxDiagnosticKind.platform, PxDiagnosticCode.scrollNativeUnavailable, e);\n return null;\n }\n\n return {\n timeline,\n rangeStart: nativeRangeOffset(scroll.range?.start, 0, view),\n rangeEnd: nativeRangeOffset(scroll.range?.end, 1, view),\n };\n}\n\n\nexport interface PxScrollDriver {\n /** Detach every listener/observer. Idempotent. */\n destroy(): void;\n /** Re-measure and emit now (also called once on attach). */\n refresh(): void;\n}\n\n/**\n * The nearest ancestor that can scroll on the given physical axis — the CSS \"scroll\n * container\" definition: computed overflow other than `visible`/`clip` counts\n * (`hidden` scrolls programmatically). Returns null when there is none, so callers fall\n * back to the document scroller.\n *\n * `<html>` and `<body>` are NEVER returned. CSS propagates the root element's overflow to\n * the VIEWPORT (and, when the root's is `visible`, the body's instead) — so with the very\n * common `body { overflow-y: auto }` the body computes as `auto` yet is not a scroll\n * container at all: the viewport scrolls, `body.scrollTop` stays 0, and `body`'s rect is\n * the full content box rather than the 100vh scrollport. Returning it produced a frozen,\n * badly-scaled progress (a doc that never advanced, or sat at a fixed mid-value). The\n * document scroller — which the caller measures with viewport semantics — is the right\n * answer for both elements.\n */\nexport function findNearestScroller(el: Element, axis: 'x' | 'y'): Element | null {\n const body = document.body;\n const root = document.documentElement;\n for (let p = el.parentElement; p; p = p.parentElement) {\n if (p === body || p === root) return null; // the viewport scrolls for these — see above\n const style = getComputedStyle(p);\n const overflow = axis === 'y' ? style.overflowY : style.overflowX;\n if (overflow === 'auto' || overflow === 'scroll' || overflow === 'hidden' || overflow === 'overlay') {\n return p;\n }\n }\n return null;\n}\n\nfunction documentScroller(): Element {\n return document.scrollingElement || document.documentElement;\n}\n\n/** `scroll.subject` keywords (anything else is treated as a CSS selector). */\nconst SUBJECT_PARENT = 'parent';\nconst SUBJECT_SCROLLER = 'scroller';\n\n/**\n * WHICH element's journey `kind: 'view'` measures — `scroll.subject`. Unset = the animation's\n * own `<svg>` (the original behavior).\n *\n * `'parent'` is the pinned-section answer and needs no knowledge of the host's markup: a\n * `position: sticky` element STOPS MOVING once stuck, so measuring the graphic itself freezes\n * progress for exactly the stretch that should be animating. Walking out to the outermost\n * sticky/fixed ancestor's container gives the element that really does scroll past — and its\n * `contain` phase is precisely the pinned stretch.\n *\n * Never throws and never returns null: an unresolvable selector warns and falls back to the\n * `<svg>`, because a silent freeze is indistinguishable from a broken animation.\n */\nexport function resolveScrollSubject(svgRoot: Element, subject: string | undefined, diag?: PxDiagnostics): Element {\n const report = diag ?? createDiagnostics(undefined, '[PxAnimator]');\n const spec = subject?.trim();\n if (!spec) return svgRoot;\n\n if (spec === SUBJECT_PARENT) {\n // Outermost sticky/fixed ancestor wins — nested sticky wrappers are common.\n let outermostPinned: Element | null = null;\n for (let p = svgRoot.parentElement; p && p !== document.body; p = p.parentElement) {\n const position = getComputedStyle(p).position;\n if (position === 'sticky' || position === 'fixed') outermostPinned = p;\n }\n return outermostPinned?.parentElement ?? svgRoot.parentElement ?? svgRoot;\n }\n\n if (spec === SUBJECT_SCROLLER) {\n return findNearestScroller(svgRoot, 'y') || findNearestScroller(svgRoot, 'x') || documentScroller();\n }\n\n let found: Element | null = null;\n try {\n found = document.querySelector(spec);\n } catch {\n // `document`: the bad selector is a VALUE IN THE FILE, so the fix is to the document...\n report.warn(PxDiagnosticKind.document, PxDiagnosticCode.scrollSubjectInvalid, spec);\n return svgRoot;\n }\n if (!found) {\n // ...whereas a valid selector that matches nothing is the page's business.\n report.warn(PxDiagnosticKind.host, PxDiagnosticCode.scrollSubjectNoMatch, spec);\n return svgRoot;\n }\n return found;\n}\n\n/**\n * Attach a scroll driver for `subject` (the animation's root `<svg>`).\n * Emits `onProgress(0..1)` — clamped, range-mapped — on attach and on every\n * scroll/resize tick. Returns `null` when the document isn't scroll-driven.\n */\nexport function createScrollDriver(\n subject: Element,\n config: PxAnimatorConfig | undefined,\n onProgress: (progress: number) => void,\n diag?: PxDiagnostics,\n): PxScrollDriver | null {\n if (!config || !isScrollTimeline(config)) return null;\n\n const report = diag ?? createDiagnostics(undefined, '[PxAnimator]');\n const scroll: PxScroll = config.scroll || {};\n const kind = scroll.kind ?? 'view';\n\n // WHAT is measured (`view` only): the SVG itself by default, or whatever `scroll.subject`\n // names — the pinned-section case measures the wrapper that actually scrolls past.\n const measured = resolveScrollSubject(subject, scroll.subject, report);\n\n // Scroller resolution (once, at attach): `view` always tracks the nearest scrollport;\n // `scroll` honors `source`. Axis resolves against the SCROLLER's writing mode.\n const nearest = findNearestScroller(subject, 'y') || findNearestScroller(subject, 'x');\n const scroller: Element = (kind === 'scroll' && scroll.source === 'root')\n ? documentScroller()\n : (nearest || documentScroller());\n const isRootScroller = scroller === documentScroller();\n const axis = scrollResolveAxis(scroll.axis, getComputedStyle(scroller).writingMode);\n\n const compute = (): number => {\n if (kind === 'scroll') {\n const offset = axis === 'y' ? scroller.scrollTop : scroller.scrollLeft;\n const maxOffset = axis === 'y'\n ? scroller.scrollHeight - scroller.clientHeight\n : scroller.scrollWidth - scroller.clientWidth;\n return scrollOffsetProgress(offset, maxOffset, scroll.range);\n }\n\n // view: the MEASURED element's journey across the scrollport, in scrollport coordinates.\n const subjectRect = measured.getBoundingClientRect();\n let portStart: number, portSize: number;\n if (isRootScroller) {\n portStart = 0;\n // documentElement.client* excludes scrollbars (window.inner* does not).\n portSize = axis === 'y' ? document.documentElement.clientHeight : document.documentElement.clientWidth;\n } else {\n const portRect = scroller.getBoundingClientRect();\n portStart = axis === 'y' ? portRect.top : portRect.left;\n portSize = axis === 'y' ? scroller.clientHeight : scroller.clientWidth;\n }\n const subjectStart = (axis === 'y' ? subjectRect.top : subjectRect.left) - portStart;\n const subjectSize = axis === 'y' ? subjectRect.height : subjectRect.width;\n return scrollViewProgress(subjectStart, subjectSize, portSize, scroll.range);\n };\n\n // ── smoothing (`scroll.smoothing`, GSAP's `scrub: <seconds>`) ───────────────────────\n // Without it, emitted progress IS the measurement. With it, the emitted value chases the\n // measurement with an exponential ease, so momentum scrolling and trackpad jitter read as\n // a glide instead of a snap. Frame-rate independent (`1 - exp(-dt/tau)`), and it SETTLES\n // exactly on the target rather than asymptotically near it.\n const smoothingSec = Math.max(0, scroll.smoothing ?? 0) / 1000; // schema is in ms\n // An exponential ease only approaches its target, so snap the last sliver and stop the rAF\n // loop rather than idling for another second. 0.1% of progress = 1ms of a 1s animation —\n // far below anything visible, and it keeps the tail off the battery.\n const SETTLE_EPSILON = 0.001;\n let destroyed = false;\n let smoothed: number | null = null; // null until the first emit (which is instant)\n let smoothRaf: number | null = null;\n let lastFrameMs = 0;\n\n const emit = (target: number) => {\n if (!smoothingSec) { onProgress(target); return; }\n if (smoothed === null) { smoothed = target; onProgress(target); return; } // no lag on load\n if (smoothRaf !== null) return; // already chasing\n lastFrameMs = 0;\n const step = (nowMs: number) => {\n smoothRaf = null;\n if (destroyed) return;\n const dtSec = lastFrameMs ? Math.min(0.1, (nowMs - lastFrameMs) / 1000) : 1 / 60;\n lastFrameMs = nowMs;\n const goal = compute(); // re-measure: the user may still be scrolling\n const k = 1 - Math.exp(-dtSec / smoothingSec);\n smoothed = smoothed! + (goal - smoothed!) * k;\n if (Math.abs(goal - smoothed!) < SETTLE_EPSILON) smoothed = goal; // land exactly\n onProgress(smoothed!);\n if (smoothed !== goal) smoothRaf = requestAnimationFrame(step);\n };\n smoothRaf = requestAnimationFrame(step);\n };\n\n // rAF coalescing: any number of scroll/resize events in a frame → one measurement.\n let rafId: number | null = null;\n const tick = () => {\n rafId = null;\n if (destroyed) return;\n emit(compute());\n };\n const schedule = () => {\n if (destroyed || rafId !== null) return;\n rafId = requestAnimationFrame(tick);\n };\n\n // Root scrolling fires on window; container scrolling on the container.\n const scrollTarget: EventTarget = isRootScroller ? window : scroller;\n scrollTarget.addEventListener('scroll', schedule, { passive: true });\n window.addEventListener('resize', schedule, { passive: true });\n\n let resizeObserver: ResizeObserver | undefined;\n if (typeof ResizeObserver !== 'undefined') {\n resizeObserver = new ResizeObserver(schedule);\n resizeObserver.observe(measured);\n if (measured !== subject) resizeObserver.observe(subject);\n if (!isRootScroller) resizeObserver.observe(scroller);\n }\n\n const driver: PxScrollDriver = {\n destroy: () => {\n if (destroyed) return;\n destroyed = true;\n scrollTarget.removeEventListener('scroll', schedule);\n window.removeEventListener('resize', schedule);\n resizeObserver?.disconnect();\n if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; }\n if (smoothRaf !== null) { cancelAnimationFrame(smoothRaf); smoothRaf = null; }\n },\n // `refresh` is a deliberate JUMP (attach, host relayout) — never eased.\n refresh: () => { if (!destroyed) { smoothed = compute(); onProgress(smoothed); } },\n };\n\n // Initial pose: reflect the CURRENT scroll position immediately (a page loaded\n // mid-scroll must not flash frame 0).\n driver.refresh();\n\n return driver;\n}\n\n\n/**\n * Pin the canvas on screen for the scrubbed stretch — GSAP's `pin: true`, expressed as\n * `position: sticky`. Sticky keeps the element's space in normal flow, so (unlike GSAP's\n * `position: fixed`) no spacer has to be injected into the host's layout to stop the page\n * collapsing. Optionally wraps the canvas in a tall block so the PLAYER creates the scroll\n * travel and the host page needs no CSS at all.\n *\n * Returns a cleanup that restores the DOM exactly. No-op (and no cleanup cost) when `pin` is off.\n */\n/** Height of the scrollport the canvas is held inside — the nearest y-scroller's, else the\n * document's. `clientHeight` (not `innerHeight`) so scrollbars are excluded, matching the\n * progress math above. Pinning is a `top` offset, so it is always the VERTICAL size. */\nfunction pinScrollportHeight(svgRoot: Element): number {\n const scroller = findNearestScroller(svgRoot, 'y');\n return scroller ? scroller.clientHeight : document.documentElement.clientHeight;\n}\n\nexport function applyScrollPin(svgRoot: Element, scroll: PxScroll | undefined): () => void {\n const styled = svgRoot as Element & Partial<ElementCSSInlineStyle>;\n if (!scroll?.pin || !styled.style) return () => { /* nothing pinned */ };\n\n const style = styled.style;\n const prevPosition = style.position;\n const prevTop = style.top;\n style.position = 'sticky';\n\n // WHERE it is held: `top` is the alignment position plus the `pinOffset` fine-tune.\n // `center`/`bottom` depend on the canvas's own height AND the scrollport height, neither\n // of which CSS can express for a sticky offset (a `top` percentage resolves against the\n // CONTAINING BLOCK, not the element), so they are measured and re-applied on resize.\n const alignFactor = scroll.pinAlign === 'center' ? 0.5 : scroll.pinAlign === 'bottom' ? 1 : 0;\n const applyTop = (): void => {\n const extra = scroll.pinOffset ?? 0;\n if (!alignFactor) {\n style.top = extra + 'px';\n return;\n }\n const portSize = pinScrollportHeight(svgRoot);\n const ownSize = svgRoot.getBoundingClientRect().height;\n style.top = Math.round((portSize - ownSize) * alignFactor + extra) + 'px';\n };\n applyTop();\n\n // Keep a measured offset correct as the viewport or the canvas resizes.\n let resizeObserver: ResizeObserver | null = null;\n const onWindowResize = alignFactor ? applyTop : null;\n if (alignFactor) {\n if (onWindowResize) window.addEventListener('resize', onWindowResize);\n if (typeof ResizeObserver !== 'undefined') {\n resizeObserver = new ResizeObserver(applyTop);\n resizeObserver.observe(svgRoot);\n }\n }\n\n // `pinDistance` (in viewport heights) — the travel the pin should last for.\n let wrapper: HTMLElement | null = null;\n const parent = svgRoot.parentElement;\n if (scroll.pinDistance && scroll.pinDistance > 0 && parent) {\n wrapper = document.createElement('div');\n wrapper.setAttribute('data-px-pin', '');\n wrapper.style.height = (scroll.pinDistance * 100) + 'vh';\n parent.insertBefore(wrapper, svgRoot);\n wrapper.appendChild(svgRoot);\n }\n\n return () => {\n if (onWindowResize) window.removeEventListener('resize', onWindowResize);\n resizeObserver?.disconnect();\n style.position = prevPosition;\n style.top = prevTop;\n if (wrapper?.parentElement) {\n wrapper.parentElement.insertBefore(svgRoot, wrapper);\n wrapper.remove();\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 { 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 { registerAnimator, withRegistryEvents } from '../registry/PxAnimatorRegistry';\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 registered(options, callbacks => buildOrReport(options, () => bindWithEngineChoice(doc, undefined, callbacks, null)));\n}\n\n/**\n * The page-wide registry (`getAllAnimators()`) for the pre-rendered builds: the returned\n * API is enrolled — a build that failed (the inert API, `isReady()` false) is not — and hears\n * play / pause / cancel / finish through the callbacks the engine is given.\n */\nfunction registered(options: PxPrerenderedAnimatorOptions, build: (callbacks: PxEngineCallbacks) => PxAnimatorApi): PxAnimatorApi {\n let apiRef: PxAnimatorApi | undefined;\n const api = build(withRegistryEvents(toEngineCallbacks(options), () => apiRef));\n if (api.isReady()) { apiRef = api; registerAnimator(api); }\n return api;\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 registered(options, callbacks => buildOrReport(options, () => {\n const animatorConfig = getAnimatorConfig(doc) || {};\n return finaliseAnimator(animatorConfig, callbacks, 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\nimport { toDomProps, type PxDefinitions, type PxDiagnostics, type PxNode } from '@pixodesk/svg-animator-core';\nimport { renderPxTree, type PxElementFactory } from '@pixodesk/svg-animator-core/internal';\n\n// Re-export from the historical home so the package surface is unchanged.\nexport { toDomProps };\n\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\n/**\n * The web player's element factory — the ONLY web-specific part of rendering. Every decision\n * about the document (tags, attribute names and values, sanitization, styles, text) is made by\n * core's `renderPxTree`, which the React and Vue components call with their own factories. So\n * the three cannot disagree about a document: they differ only in how an element is created.\n */\nconst createDomElement: PxElementFactory<Element> = ({ tag, attrs, style, children, text }) => {\n const element = document.createElementNS(SVG_NS, tag);\n\n for (const name of Object.keys(attrs)) element.setAttribute(name, attrs[name]);\n\n if (style) {\n const target = element.style as unknown as Record<string, string>;\n for (const prop of Object.keys(style)) target[prop] = style[prop];\n }\n\n // Children, or the node's own text — never both (assigning `textContent` would REPLACE\n // children already appended). `renderPxTree` has already decided which.\n for (const child of children) element.appendChild(child);\n if (text !== undefined) element.textContent = text;\n\n return element;\n};\n\n\n/**\n * Renders a PxNode tree to DOM elements.\n * @public @advanced\n */\nexport function renderNode(node: PxNode, _defs?: PxDefinitions, diag?: PxDiagnostics): Element | null {\n return renderPxTree(node, createDomElement, diag);\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 { applyAnimatorConfig, foldTimelineOverride, generateNewIds, getAnimatorConfig, isPxDocument, materializeAllInTree, PxDiagnosticCode, PxDiagnosticKind, resolveTimelineEngine, type PxTimelineEngine, validateNodeEffects, type PxAnimatedSvgDocument, type PxEngineCallbacks, type PxAnimatorConfigPatch, type PxAnimatorCallbacks, type PxPlaybackOverride, type PxPlatformAdapter } from '@pixodesk/svg-animator-core';\nimport { reportDocumentDiagnostics, createDiagnostics, PX_ANIM_ATTR_NAME, PX_ANIM_SRC_ATTR_NAME } from '@pixodesk/svg-animator-core/internal';\nimport { asThrownError, toEngineCallbacks } from '../shared/PxAnimatorCallbacks';\nimport { registerAnimator, withRegistryEvents } from '../registry/PxAnimatorRegistry';\nimport { bindWithEngineChoice } from '../engines/PxAnimatorBind';\nimport { renderNode } from '../dom/PxAnimatorDOM';\nimport { setupAnimationTriggers } from '../triggers/PxAnimatorTriggers';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\n\n// Re-export so the package surface keeps `generateNewIds` at its historical home.\nexport { generateNewIds };\n\n\n/**\n * Creates an animator instance from a normalized player config.\n * This is the internal implementation that both engines use.\n *\n * The engine choice plus the `resetOnFinish` / `debugGlobalName` handling live in\n * `PxAnimatorBind` so the pre-rendered builds share them verbatim — one code path, no\n * parallel pipeline. See dev-docs/plans/prerendered-player-builds.md.\n */\nfunction createAnimatorFromConfig(\n doc: PxAnimatedSvgDocument,\n adapter?: PxPlatformAdapter,\n callbacks?: PxEngineCallbacks,\n rootElement?: Element | null\n): PxAnimatorApi {\n return bindWithEngineChoice(doc, adapter, callbacks, rootElement);\n}\n\n\n/**\n * Creates an animator instance from an AnimatedSvgDocument.\n *\n * This function serves as the main entry point for the animation library. The engine comes from\n * `timeline.engine`: `auto` tries the browser's Web Animations API and falls back to the frame\n * loop, `native` and `js` force one — see `resolveTimelineEngine`.\n *\n * @param doc The animated SVG document.\n * @param callbacks Optional object with callback functions for animation lifecycle events (play, pause, finish, etc.).\n * @param containerElement Optional selector or element to render the SVG into.\n * @param providedRoot The root `<svg>` a caller rendered itself — used when there is no container.\n * @returns An PxAnimatorApi instance to programmatically control the animation.\n */\nfunction createAnimatorImpl(\n doc: PxAnimatedSvgDocument,\n adapter?: PxPlatformAdapter,\n callbacks?: PxEngineCallbacks,\n containerElement?: string | Element,\n patch?: PxAnimatorConfigPatch,\n resetTimeline?: boolean,\n providedRoot?: Element\n): PxAnimatorApi {\n\n // Validate every `node.effects` bucket against `PxEffectsSchema` and warn\n // about any shape drift. Doesn't mutate or block — the materializer tries\n // its best even when shapes are off, but a warning helps spot wire-format\n // regressions early.\n // Everything this player has to say goes through one channel (API review §5): the caller's\n // `onWarn` / `onError` if given, the console otherwise — unless `muteWarn` / `muteError` switch it off.\n const diag = createDiagnostics(callbacks, '[PxAnimator]');\n\n const effectsWarnings = validateNodeEffects(doc as any);\n for (const w of effectsWarnings) diag.warn(PxDiagnosticKind.document, PxDiagnosticCode.effectsShape, w);\n\n // …and the WHOLE-document check beside it. This is the boundary diagnostic: if a consumer's\n // build mangled property names, the keys reaching us are unrecognizable and this says so,\n // instead of the animation silently rendering nothing (dev-docs/plans/minification-boundary.md §3).\n reportDocumentDiagnostics(doc, '[PxAnimator] createAnimator');\n\n // The per-instance override, applied BEFORE anything reads the config. Everything below\n // depends on the final values: `timeline.engine` picks the engine, `duration` drives loop\n // expansion and motion-path sampling in `materializeAllInTree`, and `generateNewIds`\n // rewrites binding targets — a late patch would be read by none of them.\n if (patch !== undefined || resetTimeline) {\n const patched = applyAnimatorConfig(doc, patch ?? {}, { resetTimeline: !!resetTimeline });\n for (const w of patched.warnings) diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.timelineOverrideIgnored, w);\n doc = patched.doc;\n }\n\n // Decide the engine upfront so the materialization pipeline knows which\n // stages to run. `auto` and `native` resolve to the native (WAAPI) materialization; if the\n // native engine later declines the document at construction, the frame loop (`js`) is used\n // as fallback — slight over-materialization for that doc, but no correctness issue.\n const animatorConfig = getAnimatorConfig(doc) || {};\n const engine: PxTimelineEngine = resolveTimelineEngine(animatorConfig.engine);\n\n // Run the full document materialization pipeline:\n // effects → loops → motion-path (native engine only) → animated-use (native engine only)\n // → rest poses (so the frame shown BEFORE anything plays is the first frame)\n // The exact same function is exported for the Editor — no parallel pipeline.\n doc = materializeAllInTree(doc, engine);\n\n let rootElement: Element | null = null;\n\n // Render whenever there's a container. A document with no children is still a\n // document — it carries the viewBox/size that make it a viewport — so it renders as an\n // EMPTY `<svg>`. Gating on `doc.children` left `getRootElement()` answering `null`,\n // which a consumer cannot tell apart from \"the render failed\".\n if (containerElement) {\n\n doc = generateNewIds(doc); // Regenerate IDs so repeated calls to createAnimator(...) produce different ids in elements\n\n const containerEl = typeof containerElement === 'string' ?\n document.querySelector(containerElement) : containerElement;\n\n if (containerEl) {\n rootElement = renderNode(doc, undefined, diag);\n if (rootElement) {\n containerEl.replaceChildren(rootElement);\n }\n }\n }\n\n // A caller that rendered the document itself hands over the root it rendered — see\n // `PxInternalAnimatorOptions.rootElement`. A container render above always wins.\n if (!rootElement && providedRoot) rootElement = providedRoot;\n\n const api = createAnimatorFromConfig(doc, adapter, callbacks, rootElement);\n\n // The player put the SVG into the container, so destroy() takes it out again —\n // otherwise a frozen last frame lingers after the animator is gone. Scoped to\n // the container path on purpose: a root the caller rendered (the React / Vue\n // adapters, Mode B binding to an existing SVG) is theirs to remove.\n if (containerElement && rootElement) {\n const rendered = rootElement;\n const destroyNative = api.destroy.bind(api);\n api.destroy = () => {\n destroyNative();\n rendered.remove();\n };\n }\n\n return api;\n}\n\n// Re-exported so this module's public surface is unchanged; declared in\n// `PxAnimatorKeys` so entries can use it without importing this module. See there.\nexport { PX_ANIMATOR_DOC_KEY } from '../shared/PxAnimatorKeys';\n\n/**\n * Everything `createAnimator` takes. The playback override (`timeline`, `resetTimeline` and the\n * four shortcuts) and the callbacks are core's shared shapes — the SAME names, inline, as the\n * React, Vue and React Native components take (review §9) — so only what is web-specific is\n * declared here.\n * @public\n */\nexport interface PxAnimatorOptions extends PxPlaybackOverride, PxAnimatorCallbacks {\n /** URL to fetch the animation document from. Provide either this or `doc`, not both. */\n src?: string;\n /** The animation document, inline (see docs/format/README.md). Provide either this or `src`, not both. */\n doc?: PxAnimatedSvgDocument;\n /** CSS selector or element to render the SVG into. */\n container?: string | Element;\n}\n\n/**\n * What the framework COMPONENTS build the player with: the public options plus the `adapter`\n * that routes the frame loop's attribute writes to the elements they rendered themselves.\n *\n * NOT part of the public API (review §25.14). The React and Vue packages are its only callers;\n * `createAnimator`'s signature says `PxAnimatorOptions` on purpose — a page has a DOM to write\n * to, so for anyone else the option would only be a way to hold the player wrong. Exported as a\n * type so those packages can name it; never documented as an option.\n * @internal\n */\nexport interface PxInternalAnimatorOptions extends PxAnimatorOptions {\n /** A custom render target for the frame-loop engine (`PxPlatformAdapter`). */\n adapter?: PxPlatformAdapter;\n\n /**\n * The root `<svg>` the CALLER rendered, when it renders the document itself (the React and\n * Vue components). Triggers attach to it — pointer listeners, the visibility gate — and\n * without one they are not wired at all, so even a `load` trigger never fires.\n *\n * Otherwise the engines look the root up as `#` + the document's root id, which finds\n * nothing for the common document whose root `<svg>` has no id. The component already holds\n * the element, so it hands it over rather than have it guessed.\n */\n rootElement?: Element;\n}\n\n/** The one place `createAnimator` reads past its public signature — see `PxInternalAnimatorOptions`. */\nfunction isInternalOptions(options: PxAnimatorOptions): options is PxInternalAnimatorOptions {\n // EITHER internal key. Testing `adapter` alone meant a caller passing only `rootElement` —\n // what the React and Vue components do — had it silently ignored.\n return 'adapter' in options || 'rootElement' in options;\n}\n\n/**\n * The `createAnimator` spelling of the shared timeline fold (core owns the logic so the three\n * component packages and the plain-JS entry cannot drift).\n */\nexport function resolveTimelineOption(options: PxAnimatorOptions): PxAnimatorConfigPatch | undefined {\n const { timeline, duration, delay, iterations, start } = options;\n return foldTimelineOverride(timeline, { duration, delay, iterations, start });\n}\n\n/**\n * Creates an animator instance to control SVG animations.\n *\n * @param options.src URL to fetch the animation document from.\n * @param options.doc The animation document, inline.\n * @param options.container CSS selector or element to render the SVG into.\n * @returns A PxAnimatorApi instance to programmatically control the animation.\n * @public\n */\nexport function createAnimator(options: PxAnimatorOptions): PxAnimatorApi {\n\n const { src, doc, container, resetTimeline } = options;\n const adapter = isInternalOptions(options) ? options.adapter : undefined;\n const providedRoot = isInternalOptions(options) ? options.rootElement : undefined;\n const patch = resolveTimelineOption(options);\n // The registry hears play / pause / … through the engine callbacks, and names the PROXY\n // below — the object the caller holds — never the engine API behind it.\n let proxy: PxAnimatorApi | undefined;\n const callbacks = withRegistryEvents(toEngineCallbacks(options), () => proxy);\n\n // A wrong CALL throws — a bug at the call site, found the moment the line runs. A document\n // or environment that cannot play is reported through `onError` instead (the rule in core's\n // `PxDiagnostics`, review §25.1): the returned API stays inert and `isReady()` false.\n if (doc !== undefined && src !== undefined) {\n throw new Error('createAnimator: provide either `src` or `doc`, not both');\n }\n if (doc === undefined && src === undefined) {\n throw new Error('createAnimator: either `src` or `doc` is required');\n }\n\n let animator: PxAnimatorApi | null = null;\n\n // Control calls made before the player exists are queued and replayed (in order) once it\n // is ready, so e.g. `createAnimator({src}).play()` works as expected. Getters are not\n // queued — they return their \"not ready yet\" value until then. With `doc` the player is\n // ready before this returns; the same proxy then simply forwards.\n let pending: Array<(api: PxAnimatorApi) => void> | null = [];\n let destroyed = false;\n\n const enqueue = (call: (api: PxAnimatorApi) => void) => {\n if (animator) {\n call(animator);\n } else if (pending) {\n pending.push(call);\n }\n };\n\n const diag = createDiagnostics(callbacks, '[PxAnimator]');\n\n const ready = (api: PxAnimatorApi): void => {\n animator = api;\n const queued = pending;\n pending = null;\n queued?.forEach(call => call(api));\n };\n // The instance will not play: report once, drop the queue, stay inert.\n const failed = (kind: PxDiagnosticKind, code: PxDiagnosticCode, ...data: Array<unknown>): void => {\n pending = null;\n diag.error(kind, code, ...data);\n };\n // Building the player threw: a broken document past validation, or a player bug — never a\n // throw at the caller, which would land inside a fetch callback where no one can catch it.\n const build = (document: PxAnimatedSvgDocument): void => {\n try {\n ready(createAnimatorImpl(document, adapter, callbacks, container, patch, resetTimeline, providedRoot));\n } catch (e) {\n const err = asThrownError(e);\n failed(PxDiagnosticKind.internal, PxDiagnosticCode.buildFailed, err);\n }\n };\n\n // The proxy: forwards once the player exists, queues control calls until then. Built and\n // listed BEFORE the build, so a `load` trigger's first `play` during construction is\n // announced for it like every later one.\n proxy = {\n \"isReady\": () => !!animator,\n \"getRootElement\": () => animator ? animator.getRootElement() : null,\n \"isPlaying\": () => animator?.isPlaying() || false,\n \"play\": () => { enqueue(api => api.play()); },\n \"pause\": () => { enqueue(api => api.pause()); },\n \"cancel\": () => { enqueue(api => api.cancel()); },\n \"finish\": () => { enqueue(api => api.finish()); },\n \"setPlaybackRate\": (rate: number) => { enqueue(api => api.setPlaybackRate(rate)); },\n \"getCurrentTime\": () => animator ? animator.getCurrentTime() : null,\n \"setCurrentTime\": (time: number) => { enqueue(api => api.setCurrentTime(time)); },\n \"getCurrentProgress\": () => animator ? animator.getCurrentProgress() : null,\n \"setCurrentProgress\": (progress: number) => { enqueue(api => api.setCurrentProgress(progress)); },\n \"destroy\": () => {\n destroyed = true;\n pending = null; // drop any queued calls\n animator?.destroy();\n }\n };\n // Listed from this moment (`add`) until `destroy()` — a `src` still loading included.\n registerAnimator(proxy);\n\n if (doc !== undefined) {\n build(doc);\n } else {\n fetch(src!).then(res => res.json()).then(json => {\n if (destroyed) return; // destroy() was called before the document loaded\n if (isPxDocument(json)) build(json);\n else failed(PxDiagnosticKind.document, PxDiagnosticCode.invalidDocumentAtSrc, src);\n }).catch(err => {\n // `host`, not `document`: the file may be perfect — the page could not fetch it.\n failed(PxDiagnosticKind.host, PxDiagnosticCode.loadFailed, src, err?.message ?? String(err));\n });\n }\n\n return proxy;\n}\n\n/**\n * Scan and load for tags, e.g.\n * <div data-px-animation-src=\"animation.json\"></div>\n */\n/**\n * Everything `createAnimator` takes except the three the tag supplies (`src`, `container`) or\n * forbids (`data`): callbacks, the diagnostics channel, a playback override and its shortcuts.\n * @public\n */\nexport type PxTagAnimatorOptions = Omit<PxAnimatorOptions, 'src' | 'doc' | 'container'>;\n\n/**\n * Scan the page for `<div data-px-animation-src=\"animation.json\">` and create one player per\n * match, rendered into that element and stored on it. Safe to call repeatedly: elements that\n * already carry a player are skipped.\n *\n * `options` applies to EVERY player this call creates (review §15) — the same callbacks, the\n * same override. Omit it for the zero-config path.\n * @public\n */\nexport function loadTagAnimators(options?: PxTagAnimatorOptions) {\n const elements = document.querySelectorAll('[' + PX_ANIM_SRC_ATTR_NAME + ']');\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n if (!(element as any)[PX_ANIM_ATTR_NAME]) {\n const src = element.getAttribute(PX_ANIM_SRC_ATTR_NAME);\n if (src) {\n (element as any)[PX_ANIM_ATTR_NAME] = createAnimator({ ...options, src, container: element });\n }\n }\n }\n}\n\n// No module-level globals (API review §4). This file used to end by assigning\n// `window.createAnimator` / `loadTagAnimators` / `setupAnimationTriggers` whenever it loaded —\n// for every ESM and CJS consumer too, not just `<script>` pages. That could overwrite a page's\n// own `createAnimator`, and the side effect made the whole module untree-shakable.\n//\n// `<script>` users reach all three through `PixodeskAnimator.*` on the UMD build, which is what\n// the editor's exported SVG+JS calls. Older exported files are unaffected: they inline their own\n// player and assign these names themselves."],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACiEA,IAAM,iBAAiB;AACvB,IAAM,aAAa;AAInB,IAAM,eAAe;AACrB,IAAM,YAAY;AAElB,IAAM,cAAc;AAQb,SAAS,aAAgB,MAA0B,SAA8B,MAAgC;AACpH,SAAO,OAAO,UAAU,MAAM,SAAS,MAAM,MAAM,CAAC,IAAI;AAC5D;AAEA,SAAS,UAAa,MAAc,SAA8B,MAAiC,QAAiB,OAAyB;AACzI,QAA4C,KAAA,MAApC,EAAA,MAAM,UAAU,MAtF5B,IAsFgD,IAAV,QAAA,UAAU,IAAV,CAA1B,QAAM,YAAU,OAAA,CAAA;AACxB,QAAM,MAAM,QAAQ;AAEpB,MAAI,6BAA6B,IAAI,IAAI,YAAY,CAAC,GAAG;AAErD,KAAC,QAAA,OAAA,OAAQ,kBAAkB,QAAW,cAAc,GAAG,KAAK,iBAAiB,UAAA,MAAuC,GAAG;AACvH,WAAO;EACX;AAKA,QAAM,UAAU,MAAM,YAAY;AAClC,MAAI,YAAY,OAAW,QAAO,MAAM,YAAY;AAEpD,QAAM,QAAgC,CAAC;AACvC,MAAI;AAEJ,QAAM,WAAW,WAAW,KAAK;AACjC,aAAW,YAAY,OAAO,KAAK,QAAQ,GAAG;AAG1C,UAAM,YAAY,uBAAuB,UAAU,SAAS,QAAQ,CAAC;AACrE,QAAI,cAAc,OAAW;AAI7B,QAAI,wBAAwB,IAAI,QAAQ,GAAG;AACvC,OAAC,eAAA,OAAA,cAAA,cAAgB,CAAC,GAAG,QAAQ,IAAI,OAAO,SAAS;AACjD;IACJ;AACA,UAAM,aAAa,iBAAiB,aAAa,6BAA6B,QAAQ,CAAC,IAAI;EAC/F;AACA,MAAI,YAAY,OAAW,OAAM,SAAS,IAAI,OAAO,OAAO;AAI5D,MAAI,OAAO;AACP,eAAW,aAAa,OAAO,KAAK,KAAK,EAAG,EAAC,eAAA,OAAA,cAAA,cAAgB,CAAC,GAAG,SAAS,IAAI,OAAO,MAAM,SAAS,CAAC;EACzG;AAEA,QAAM,WAAqB,CAAC;AAC5B,MAAI,UAAU;AACV,aAAS,QAAQ,CAAC,OAAO,MAAM;AAC3B,YAAM,KAAK,UAAU,OAAO,SAAS,MAAM,OAAO,CAAC;AACnD,UAAI,OAAO,KAAM,UAAS,KAAK,EAAE;IACrC,CAAC;EACL;AAEA,QAAM,UAAU,MAAM,oBAAoB;AAC1C,QAAM,OAAO,CAAC,SAAS,UAAU,OAAO,YAAY,YAAY,UAAU,UAAU;AAEpF,SAAO,QAAQ,EAAE,KAAK,OAAO,OAAO,aAAa,UAAU,UAAU,MAAM,MAAM,QAAQ,MAAM,CAAC;AACpG;AC3HO,SAAS,iBAAiB,QAA+C;AAC5E,UAAO,UAAA,OAAA,SAAA,OAAQ,oBAAmB;AACtC;AAQO,SAAS,sBAAsB,QAA8C;AAChF,QAAM,WAAY,QAAO,UAAA,OAAA,SAAA,OAAQ,cAAa,YAAY,OAAO,WAAW,IACtE,OAAO,WAAW;AACxB,QAAM,aAAc,QAAO,UAAA,OAAA,SAAA,OAAQ,gBAAe,YAAY,OAAO,aAAa,IAC5E,OAAO,aAAa;AAC1B,SAAO,WAAW;AACtB;AAaO,SAAS,oBACZ,OAAsB,aAAqB,gBAC3B;AAChB,QAAM,IAAI,aAAa,KAAK;AAC5B,UAAQ,OAAO;IACX,KAAK;AAAS,aAAO,CAAC,GAAG,IAAI,EAAE;IAC/B,KAAK;AAAS,aAAO,CAAC,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC;IACxC,KAAK;AAAW,aAAO,CAAC,KAAK,IAAI,GAAG,EAAE,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC;IACxD,KAAK;AAAQ,aAAO,CAAC,KAAK,IAAI,GAAG,EAAE,GAAG,IAAI,EAAE;IAC5C,KAAK;AAAkB,aAAO,CAAC,GAAG,CAAC;IACnC,KAAK;AAAiB,aAAO,CAAC,IAAI,IAAI,EAAE;EAC5C;AACJ;AAEA,IAAM,gBAA+B;AAGrC,SAAS,mBACL,OAAuC,iBACvC,aAAqB,gBACf;AAjEV,MAAA;AAkEI,QAAM,CAAC,IAAI,EAAE,IAAI,qBAAoB,KAAA,SAAA,OAAA,SAAA,MAAO,UAAP,OAAA,KAAgB,eAAe,aAAa,cAAc;AAC/F,QAAM,WAAW,QAAO,SAAA,OAAA,SAAA,MAAO,cAAa,WAAW,MAAM,WAAW;AACxE,SAAO,KAAK,YAAY,KAAK;AACjC;AAeO,SAAS,mBACZ,cAAsB,aAAqB,gBAC3C,OACM;AACN,QAAM,IAAI,iBAAiB;AAC3B,QAAM,SAAS,mBAAmB,SAAA,OAAA,SAAA,MAAO,OAAO,GAAG,aAAa,cAAc;AAC9E,QAAM,OAAO,mBAAmB,SAAA,OAAA,SAAA,MAAO,KAAK,GAAG,aAAa,cAAc;AAC1E,MAAI,QAAQ,OAAQ,QAAO,KAAK,OAAO,IAAI;AAC3C,SAAO,OAAO,IAAI,WAAW,OAAO,SAAS,GAAG,CAAC;AACrD;AAUO,SAAS,qBACZ,QAAgB,WAChB,OACM;AA1GV,MAAA,IAAA;AA2GI,QAAM,MAAM,YAAY,IAAI,MAAM,SAAS,WAAW,GAAG,CAAC,IAAI;AAC9D,QAAM,QAAQ,SAAO,KAAA,SAAA,OAAA,SAAA,MAAO,UAAP,OAAA,SAAA,GAAc,cAAa,WAAW,MAAM,MAAM,WAAW;AAClF,QAAM,MAAM,SAAO,KAAA,SAAA,OAAA,SAAA,MAAO,QAAP,OAAA,SAAA,GAAY,cAAa,WAAW,MAAM,IAAI,WAAW;AAC5E,MAAI,OAAO,MAAO,QAAO,OAAO,MAAM,IAAI;AAC1C,SAAO,OAAO,MAAM,UAAU,MAAM,QAAQ,GAAG,CAAC;AACpD;AAQO,SAAS,kBACZ,MAAoC,aAC3B;AACT,QAAM,IAAI,QAAA,OAAA,OAAQ;AAClB,MAAI,MAAM,OAAO,MAAM,IAAK,QAAO;AACnC,QAAM,WAAW,CAAC,CAAC,eAAe,YAAY,WAAW,UAAU;AACnE,MAAI,MAAM,SAAU,QAAO,WAAW,MAAM;AAC5C,SAAO,WAAW,MAAM;AAC5B;;;AG9GO,SAAS,kBAAkB,QAA4D;AAC1F,QAAM,EAAE,QAAQ,SAAS,UAAU,UAAU,UAAU,QAAQ,QAAQ,SAAS,UAAU,UAAU,IAAI,0BAAU,CAAC;AACnH,QAAM,WAAW,CAAC,QACd,OAAO,SAAS,MAAM;AAAE;AAAS;AAAA,EAAY,IAAI;AACrD,SAAO;AAAA,IACH;AAAA,IACA,SAAU,SAAS,OAAO;AAAA,IAC1B,UAAU,SAAS,QAAQ;AAAA,IAC3B,UAAU,SAAS,QAAQ;AAAA,IAC3B,UAAU,SAAS,QAAQ;AAAA,IAC3B;AAAA,IAAQ;AAAA,IAAS;AAAA,IAAU;AAAA,EAC/B;AACJ;AA0BO,SAAS,cAAc,GAAmB;AAC7C,SAAO,aAAa,QAAQ,IAAI,IAAI,MAAM,OAAO,CAAC,CAAC;AACvD;;;ACZA,IAAM,YAAY,uBAAO,IAAI,sCAAsC;AAEnE,IAAM,cAAc;AAIpB,SAAS,QAAuB;AAG5B,QAAM,IAAI;AACV,MAAI,IAAI,EAAE,SAAS;AACnB,MAAI,CAAC,GAAG;AACJ,UAAM,YAAY,oBAAI,IAAmB;AACzC,UAAM,YAAY,oBAAI,IAAyB;AAC/C,QAAI;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ,MAAM,MAAM,KAAK,SAAS;AAAA,MAClC,WAAW,CAAC,aAAa;AACrB,kBAAU,IAAI,QAAQ;AACtB,eAAO,MAAM;AAAE,oBAAU,OAAO,QAAQ;AAAA,QAAG;AAAA,MAC/C;AAAA,IACJ;AACA,MAAE,SAAS,IAAI;AACf,QAAI,EAAE,WAAW,MAAM,OAAW,GAAE,WAAW,IAAI;AAAA,EACvD;AACA,SAAO;AACX;AAGO,SAAS,kBAAgD;AAC5D,SAAO,MAAM,EAAE,OAAO;AAC1B;AAIO,SAAS,kBAAkB,UAA2C;AACzE,SAAO,MAAM,EAAE,UAAU,QAAQ;AACrC;AAGA,SAAS,OAAO,OAAyB,UAA+B;AACpE,aAAW,YAAY,MAAM,EAAE,WAAW;AACtC,QAAI;AAAE,eAAS,OAAO,QAAQ;AAAA,IAAG,SAAS,GAAG;AAAE,iBAAW,MAAM;AAAE,cAAM;AAAA,MAAG,GAAG,CAAC;AAAA,IAAG;AAAA,EACtF;AACJ;AAGO,SAAS,iBAAiB,UAA+B;AAC5D,QAAM,IAAI,MAAM;AAChB,MAAI,EAAE,UAAU,IAAI,QAAQ,EAAG;AAC/B,IAAE,UAAU,IAAI,QAAQ;AACxB,SAAO,OAAO,QAAQ;AACtB,QAAM,UAAU,SAAS,QAAQ,KAAK,QAAQ;AAC9C,WAAS,UAAU,MAAM;AACrB,YAAQ;AACR,QAAI,EAAE,UAAU,OAAO,QAAQ,EAAG,QAAO,UAAU,QAAQ;AAAA,EAC/D;AACJ;AAOO,SAAS,mBAAmB,WAA0C,KAAyD;AAClI,QAAM,OAAO,CAAC,UAAkC;AAAE,UAAM,IAAI,IAAI;AAAG,QAAI,EAAG,QAAO,OAAO,CAAC;AAAA,EAAG;AAC5F,SAAO,iCACA,YADA;AAAA,IAEH,QAAQ,MAAM;AAnHtB;AAmHwB,mDAAW,WAAX;AAAuB,WAAK,MAAM;AAAA,IAAG;AAAA,IACrD,SAAS,MAAM;AApHvB;AAoHyB,mDAAW,YAAX;AAAwB,WAAK,OAAO;AAAA,IAAG;AAAA,IACxD,UAAU,MAAM;AArHxB;AAqH0B,mDAAW,aAAX;AAAyB,WAAK,QAAQ;AAAA,IAAG;AAAA,IAC3D,UAAU,MAAM;AAtHxB;AAsH0B,mDAAW,aAAX;AAAyB,WAAK,QAAQ;AAAA,IAAG;AAAA,EAC/D;AACJ;;;ACnFO,SAAS,uBACZ,KACA,SACA,MACU;AAEV,QAAM,SAAS,sBAAQ,kBAAkB,QAAW,cAAc;AAGlE,QAAM,WAA8B,CAAC;AACrC,QAAM,UAAU,MAAY;AAAE,eAAW,QAAQ,SAAS,OAAO,CAAC,EAAG,MAAK;AAAA,EAAG;AAK7E,QAAM,WAAW,eAAe,OAAO;AAEvC,QAAM,OAAO,IAAI,eAAe;AAEhC,MAAI,CAAC,MAAM;AACP,WAAO,KAAK,iBAAiB,MAAM,iBAAiB,cAAc;AAClE,WAAO;AAAA,EACX;AAIA,MAAI,WAAW;AAGf,QAAM,QAAQ,MAAY;AACtB,QAAI,UAAU;AACV,iBAAW;AACX,UAAI,gBAAgB,CAAC;AAAA,IACzB;AACA,QAAI,KAAK;AAAA,EACb;AAGA,QAAM,OAAO,qBAAqB,MAAM,UAAU;AAAA,IAC9C,WAAW,MAAM,IAAI,UAAU;AAAA,IAC/B,MAAM;AAAA,IACN,OAAO,MAAM,IAAI,MAAM;AAAA,IACvB,QAAQ,MAAM,IAAI,OAAO;AAAA,EAC7B,CAAC;AACD,WAAS,KAAK,MAAM,KAAK,QAAQ,CAAC;AAGlC,QAAM,iBAAiB,MAAY;AAC/B,YAAQ,SAAS,UAAU;AAAA,MACvB,KAAK,iBAAiB;AAClB,YAAI,MAAM;AACV;AAAA,MACJ,KAAK,iBAAiB;AAClB,YAAI,OAAO;AACX;AAAA,MACJ,KAAK,iBAAiB;AAElB,mBAAW;AACX,YAAI,gBAAgB,EAAE;AACtB,YAAI,KAAK;AACT;AAAA,MACJ,KAAK,iBAAiB;AAAA,MACtB;AAEI;AAAA,IACR;AAAA,EACJ;AAGA,UAAQ,SAAS,OAAO;AAAA,IACpB,KAAK,eAAe,MAAM;AAItB,YAAM,eAAe,MAAM,KAAK,aAAa,KAAK;AAClD,UAAI,SAAS,eAAe,YAAY;AACpC,qBAAa;AAAA,MACjB,OAAO;AACH,eAAO,iBAAiB,QAAQ,cAAc,EAAE,MAAM,KAAK,CAAC;AAC5D,iBAAS,KAAK,MAAM,OAAO,oBAAoB,QAAQ,YAAY,CAAC;AAAA,MACxE;AACA;AAAA,IACJ;AAAA,IAEA,KAAK,eAAe,WAAW;AAK3B,UAAI,cAAc;AAClB,YAAM,mBAAmB,MAAM;AAAE,sBAAc;AAAM,aAAK,aAAa,IAAI;AAAA,MAAG;AAC9E,YAAM,kBAAkB,MAAM;AAAE,YAAI,YAAa,gBAAe;AAAA,MAAG;AAEnE,WAAK,iBAAiB,cAAc,gBAAgB;AACpD,WAAK,iBAAiB,cAAc,eAAe;AACnD,eAAS,KAAK,MAAM;AAChB,aAAK,oBAAoB,cAAc,gBAAgB;AACvD,aAAK,oBAAoB,cAAc,eAAe;AAAA,MAC1D,CAAC;AACD;AAAA,IACJ;AAAA,IAEA,KAAK,eAAe,OAAO;AAEvB,YAAM,eAAe,MAAM;AACvB,YAAI,IAAI,UAAU,EAAG,KAAI,MAAM;AAAA,YAC1B,MAAK,aAAa,IAAI;AAAA,MAC/B;AACA,WAAK,iBAAiB,SAAS,YAAY;AAC3C,eAAS,KAAK,MAAM,KAAK,oBAAoB,SAAS,YAAY,CAAC;AACnE;AAAA,IACJ;AAAA,IAEA,KAAK,eAAe;AAGhB;AAAA,EACR;AAEA,SAAO;AACX;;;ACtIO,SAAS,YAAY,IAAY;AAEpC,SAAO,MAAM,YAAY,EAAE;AAC/B;AAGA,SAAS,YAAY,IAAoB;AACrC,QAAM,MAAO,WAAgE;AAC7E,MAAI,QAAO,2BAAK,YAAW,WAAY,QAAO,IAAI,OAAO,EAAE;AAC3D,SAAO,GAEF,QAAQ,YAAY,CAAC,MAAM,UAAkB,QAAQ,QAAQ,GAAG,EAEhE,QAAQ,iBAAiB,MAAM;AACxC;AAkBO,SAAS,wBACZ,KACA,SACA,WACA,aACa;AA5DjB;AA8DI,QAAM,SAAS,kBAAkB,GAAG,KAAK,CAAC;AAG1C,QAAM,OAAO,kBAAkB,WAAW,cAAc;AAGxD,MAAI,CAAC,aAAa;AACd,QAAI,IAAI,IAAI;AACR,YAAM,eAAe,YAAY,IAAI,EAAE;AACvC,oBAAc,SAAS,cAAc,YAAY;AACjD,UAAI,CAAC,YAAa,MAAK,KAAK,iBAAiB,MAAM,iBAAiB,mBAAmB,YAAY;AAAA,IACvG,OAAO;AACH,WAAK,KAAK,iBAAiB,MAAM,iBAAiB,aAAa;AAAA,IACnE;AAAA,EACJ;AAEA,QAAM,WAAW;AAAA,IACb;AAAA,IACA,WAAW,iBAAiB,aAAa,IAAI;AAAA,IAC7C;AAAA,EACJ;AAGA,QAAM,MAAqB,iCACpB,WADoB;AAAA,IAEvB,kBAAkB,MAAM,eAAe;AAAA,EAC3C;AAMA,MAAI,iBAAiB,MAAM,GAAG;AAC1B,QAAI,OAAO,QAAS,MAAK,KAAK,iBAAiB,OAAO,iBAAiB,oBAAoB;AAAA,EAC/F,OAAO;AAEH,UAAM,iBAAiB,uBAAuB,MAAK,YAAO,YAAP,YAAkB,CAAC,GAAG,IAAI;AAC7E,UAAM,gBAAgB,IAAI,QAAQ,KAAK,GAAG;AAC1C,QAAI,UAAU,MAAM;AAAE,qBAAe;AAAG,oBAAc;AAAA,IAAG;AAAA,EAC7D;AACA,SAAO;AACX;AAEO,SAAS,iBAAiB,aAA8B,MAAsB;AAEjF,QAAM,kBAAkB,oBAAI,IAAY;AAExC,QAAM,SAAS,sBAAQ,kBAAkB,QAAW,cAAc;AAElE,QAAM,UAA6B;AAAA,IAC/B,aAAa,MAAM;AACf,UAAI,CAAC,YAAa,QAAO;AACzB,aAAO,YAAY;AAAA,IACvB;AAAA,IACA,cAAc,CAAC,IAAI,UAAU,UAAU;AAEnC,iBAAW,6BAA6B,QAAQ;AAEhD,YAAM,WAAW,YAAY,EAAE;AAG/B,YAAM,YAAW,2CAAa,iBAAiB,cAAa,SAAS,iBAAiB,QAAQ;AAE9F,UAAI,SAAS,WAAW,KAAK,CAAC,gBAAgB,IAAI,QAAQ,GAAG;AACzD,wBAAgB,IAAI,QAAQ;AAC5B,eAAO,KAAK,iBAAiB,MAAM,iBAAiB,uBAAuB,QAAQ;AAAA,MACvF;AAEA,eAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACtC,cAAM,UAAU,SAAS,CAAC;AAK1B,cAAM,oBAAoB,aAAa,eAAe,QAAQ,YAAY,YACpE,qBACA;AACN,gBAAQ,aAAa,mBAAmB,KAAK;AAC7C,YAAI,oBAAoB,IAAI,QAAQ,GAAG;AACnC,UAAC,QAAwB,MAAM,QAAe,IAAI;AAAA,QACtD;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACA,SAAO;AACX;;;AC1HA,SAAS,YAAY,IAAmB,GAAW,UAAkB,gBAA6B;AAC9F,MAAI,QAAQ,cAAc,EAAE;AAG5B,QAAM,IAAI,eAAe,EAAE;AAE3B,QAAM,QAAkB;AAAA,IACpB,QAAQ;AAAA,IACR,QAAQ,KAAK,MAAM,QAAQ,CAAC,IAAI,kBAAkB,EAAE,KAAK,GAAG,IAAI,MAAM;AAAA,EAC1E;AAEA,MAAI;AACJ,MAAI,SAAS;AAEb,MAAI,oBAAoB,IAAI,QAAQ,KAAK,MAAM,QAAQ,KAAK,GAAG;AAC3D,eAAW,OAAO,KAAK;AAAA,EAC3B,WAAW,aAAa,eAAe,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAGzG,eAAW,sBAAsB,OAAO,EAAE,WAAW,KAAK,CAAC;AAC3D,aAAS;AAAA,EACb,WAAW,sBAAsB,IAAI,QAAQ,GAAG;AAC5C,QAAI,MAAM,QAAQ,KAAK,GAAG;AACtB,UAAI,aAAa,YAAa,SAAQ,MAAM,IAAI,OAAK,IAAI,IAAI;AAC7D,cAAQ,MAAM,KAAK,GAAG;AAAA,IAC1B;AACA,QAAI,aAAa,SAAU,SAAQ,QAAQ;AAC3C,eAAW,WAAW,MAAM,QAAQ;AACpC,aAAS;AAAA,EACb,WAAW,aAAa,KAAK;AAMzB,UAAM,QAA8B,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,KAAK,IAC7F,MAAM,QAAQ,CAAC;AAIrB,eAAW,WAAW,MAAM,IAAI,QAAM,gBAAgB,IAAI,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI;AAAA,EAChF,WAAW,wBAAwB,IAAI,QAAQ,KAAK,OAAO,UAAU,UAAU;AAO3E,eAAY,QAAQ,MAAO;AAAA,EAC/B,OAAO;AACH,eAAW,KAAK;AAAA,EACpB;AAOA,MAAI,CAAC,IAAI,SAAS,6BAA6B,MAAM,GAAG,QAAQ,EAAG,gBAAe,IAAI,MAAM;AAE5F,WAAS,qBAAqB,MAAM;AACpC,QAAM,MAAM,IAAI;AAChB,SAAO;AACX;AAQA,SAAS,wBACL,UACA,WACA,UACsB;AApG1B;AAqGI,QAAM,SAAiC,CAAC;AAExC,WAAS,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;AACvC,UAAM,KAAK,UAAU,CAAC;AACtB,UAAM,KAAI,QAAG,MAAH,YAAQ;AAElB,QAAI,IAAI,GAAG;AAEP,YAAM,OAAO,UAAU,IAAI,CAAC;AAC5B,UAAI,UAAS,UAAK,MAAL,YAAU,MAAM,GAAG;AAC5B,cAAM,SAAQ,UAAK,MAAL,YAAU;AACxB,cAAM,aAAa,IAAI,MAAM,QAAQ;AACrC,cAAM,YAAY,GAAG,IAAI,YAAY,GAAG,CAAqC,EAAE,SAAS,IAAI;AAC5F,cAAM,EAAE,OAAO,YAAY,IAAI,YAAY,GAAG,GAAU,SAAS;AACjE,eAAO,KAAK,EAAE,GAAG,GAAG,GAAG,iBAAiB,UAAU,GAAG,GAAG,KAAK,GAAG,SAAS,GAAG,GAAG,YAAY,CAAC;AAAA,MAChG;AACA;AAAA,IACJ;AAEA,QAAI,IAAI,UAAU;AAEd,YAAM,OAAO,UAAU,IAAI,CAAC;AAC5B,UAAI,UAAS,UAAK,MAAL,YAAU,MAAM,UAAU;AACnC,cAAM,SAAQ,UAAK,MAAL,YAAU;AACxB,cAAM,aAAa,WAAW,UAAU,IAAI;AAC5C,cAAM,YAAY,KAAK,IAAI,YAAY,KAAK,CAAqC,EAAE,SAAS,IAAI;AAChG,cAAM,EAAE,MAAM,WAAW,IAAI,YAAY,KAAK,GAAU,SAAS;AACjE,YAAI,OAAO,SAAS,EAAG,QAAO,OAAO,SAAS,CAAC,IAAI,iCAAK,OAAO,OAAO,SAAS,CAAC,IAA7B,EAAgC,GAAG,WAAW;AACjG,eAAO,KAAK,EAAE,GAAG,UAAU,GAAG,iBAAiB,UAAU,KAAK,GAAG,GAAG,GAAG,SAAS,GAAG,GAAG,OAAU,CAAC;AAAA,MACrG;AACA;AAAA,IACJ;AAEA,WAAO,KAAK,EAAE;AAAA,EAClB;AAEA,SAAO;AACX;AAeO,SAAS,yBACZ,SACA,gBACA,QACuB;AA7J3B;AA8JI,QAAM,SAAS,oBAAI,IAAwB;AAE3C,aAAW,CAAC,UAAU,QAAQ,KAAK,OAAO,QAAQ,OAAO,GAAG;AACxD,UAAM,WAAW,OAAO,YAAY;AACpC,UAAM,mBAAmB,wBAAwB,UAAU,SAAS,aAAa,CAAC,GAAG,QAAQ;AAC7F,UAAM,eAA2B,CAAC;AAElC,aAAS,IAAI,GAAG,IAAI,iBAAiB,QAAQ,KAAK;AAC9C,YAAM,KAAK,iBAAiB,CAAC;AAE7B,YAAM,IAAI,QAAO,QAAG,MAAH,YAAQ,KAAK,UAAU,GAAG,CAAC;AAC5C,YAAM,QAAkB,YAAY,IAAI,GAAG,UAAU,cAAc;AAGnE,UAAI,MAAM,MAAM,MAAM,UAAU,KAAK,GAAG;AACpC,qBAAa,KAAK,iCAAK,QAAL,EAAY,QAAQ,EAAE,EAAC;AAAA,MAC7C;AAEA,mBAAa,KAAK,KAAK;AAAA,IAC3B;AAGA,QAAI,aAAa,SAAS,MAAM,aAAa,aAAa,SAAS,CAAC,EAAE,UAAU,KAAK,GAAG;AACpF,mBAAa,KAAK,iCACX,aAAa,aAAa,SAAS,CAAC,IADzB;AAAA,QAEd,QAAQ;AAAA,MACZ,EAAC;AAAA,IACL;AAEA,QAAI,aAAa,SAAS,GAAG;AACzB,aAAO,IAAI,UAAU,YAAY;AAAA,IACrC;AAAA,EACJ;AAEA,SAAO;AACX;AAuBO,SAAS,qBACZ,KACA,WACA,aACA,gCACA,gBACoB;AA9NxB;AAgOI,QAAM,SAAS,kBAAkB,GAAG,KAAK,CAAC;AAG1C,QAAM,OAAO,kBAAkB,WAAW,cAAc;AAGxD,MAAI,CAAC,aAAa;AACd,QAAI,IAAI,IAAI;AACR,YAAM,eAAe,YAAY,IAAI,EAAE;AACvC,oBAAc,SAAS,cAAc,YAAY;AACjD,UAAI,CAAC,YAAa,MAAK,KAAK,iBAAiB,MAAM,iBAAiB,mBAAmB,YAAY;AAAA,IACvG,OAAO;AACH,WAAK,KAAK,iBAAiB,MAAM,iBAAiB,aAAa;AAAA,IACnE;AAAA,EACJ;AAMA,QAAM,WAAW,kBAAkB,KAAK,iBAAiB,MAAM;AAE/D,QAAM,aAA+B,CAAC;AAEtC,QAAM,cAAc,OAAO;AAC3B,MAAI;AACJ,MAAI,OAAO,gBAAgB,SAAU,cAAa;AAClD,MAAI,gBAAgB,WAAY,cAAa;AAE7C,QAAM,iBAAiB,oBAAI,IAAY;AAMvC,MAAI,iBAAiB;AACrB,MAAI,eAAe;AAKnB,MAAI,EAAC,qCAAU,SAAQ;AACnB,SAAK,KAAK,iBAAiB,UAAU,iBAAiB,UAAU;AAAA,EACpE;AAEA,aAAW,WAAW,YAAY,CAAC,GAAG;AAClC,UAAM,UAAU,QAAQ;AACxB,QAAI,CAAC,WAAW,OAAO,YAAY,YAAY,MAAM,QAAQ,OAAO,GAAG;AACnE,WAAK,KAAK,iBAAiB,UAAU,iBAAiB,mBAAmB,OAAO;AAChF;AAAA,IACJ;AAEA,UAAM,WAAW,YAAY,QAAQ,EAAE;AAGvC,UAAM,YAAW,2CAAa,iBAAiB,cAAa,SAAS,iBAAiB,QAAQ;AAE9F,QAAI,SAAS,WAAW,GAAG;AACvB,WAAK,KAAK,iBAAiB,MAAM,iBAAiB,uBAAuB,QAAQ;AAAA,IACrF;AAGA,UAAM,eAAe,yBAAyB,SAAS,gBAAgB,MAAM;AAW7E,UAAM,gBAAgB,OAAO,SAAS,OAAO,QAAQ,IAAI,OAAO,QAAQ;AACxE,QAAI;AACJ,QAAI,OAAO,SAAS,OAAO,QAAQ,KAAK,OAAO,UAAU;AACrD,YAAM,UAAU,CAAC,OAAO;AACxB,qBAAe,eAAe,WACxB,UAAU,OAAO,WACjB,KAAK,IAAI,SAAS,OAAO,YAAY,kCAAc,EAAE;AAAA,IAC/D;AAEA,UAAM,gBAAuC;AAAA,MACzC,UAAU,OAAO;AAAA,MACjB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,MAKP,OAAM,YAAO,SAAP,YAAe;AAAA,MACrB,WAAW,OAAO;AAAA,MAClB;AAAA,IACJ;AAEA,aAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACtC,YAAM,UAAU,SAAS,CAAC;AAE1B,iBAAW,CAAC,EAAE,SAAS,KAAK,cAAc;AACtC,YAAI,UAAU,SAAS,GAAG;AACtB,cAAI;AACA,kBAAM,SAAS,IAAI,eAAe,SAAS,WAAW,aAAa;AAInE,kBAAM,OAAO,IAAI,UAAU,QAAQ,iBAAiB,eAAe,WAAW,SAAS,QAAQ;AAC/F,gBAAI,gBAAgB;AAChB,oBAAM,IAAI;AACV,kBAAI,eAAe,WAAY,GAAE,aAAa,eAAe;AAC7D,kBAAI,eAAe,SAAU,GAAE,WAAW,eAAe;AAAA,YAC7D;AAEA,gBAAI,uCAAW,SAAU,MAAK,WAAW,MAAM;AA/UvE,kBAAAA;AAgV4B,kBAAI,eAAgB;AACpB,+BAAiB;AACjB,eAAAA,MAAA,UAAU,aAAV,gBAAAA,IAAA;AAAA,YACJ;AACA,gBAAI,uCAAW,SAAU,MAAK,WAAW,MAAM;AApVvE,kBAAAA;AAqV4B,kBAAI,aAAc;AAClB,6BAAe;AACf,eAAAA,MAAA,UAAU,aAAV,gBAAAA,IAAA;AAAA,YACJ;AAIA,gBAAI,iBAAiB,QAAW;AAC5B,mBAAK,cAAc;AAAA,YACvB;AAEA,uBAAW,KAAK,IAAI;AAAA,UACxB,SAAS,GAAG;AAGR,iBAAK,KAAK,iBAAiB,UAAU,iBAAiB,sBAAsB,CAAC;AAAA,UACjF;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAIA,MAAI,CAAC,kCAAkC,eAAe,MAAM;AACxD,SAAK,KAAK,iBAAiB,UAAU,iBAAiB,0BAA0B,CAAC,GAAG,cAAc,EAAE,KAAK,IAAI,CAAC;AAC9G,WAAO;AAAA,EACX;AAIA,QAAM,MAAqB;AAAA,IAEvB,WAAW,MAAM;AAAA,IAEjB,kBAAkB,MAAM,eAAe;AAAA,IAEvC,aAAa,MAAe;AA1XpC,UAAAA;AA0XsC,eAAOA,MAAA,WAAW,CAAC,MAAZ,gBAAAA,IAAe,eAAc;AAAA,IAAW;AAAA,IAE7E,QAAQ,MAAM;AA5XtB,UAAAA;AA6XY,uBAAiB;AACjB,iBAAW,QAAQ,OAAK,EAAE,KAAK,CAAC;AAChC,OAAAA,MAAA,uCAAW,WAAX,gBAAAA,IAAA;AAAA,IACJ;AAAA,IACA,SAAS,MAAM;AAjYvB,UAAAA;AAkYY,iBAAW,QAAQ,OAAK,EAAE,MAAM,CAAC;AACjC,OAAAA,MAAA,uCAAW,YAAX,gBAAAA,IAAA;AAAA,IACJ;AAAA,IACA,UAAU,MAAM;AArYxB,UAAAA;AAsYY,uBAAiB;AACjB,iBAAW,QAAQ,OAAK,EAAE,OAAO,CAAC;AAClC,OAAAA,MAAA,uCAAW,aAAX,gBAAAA,IAAA;AAAA,IACJ;AAAA,IACA,UAAU,MAAM;AA1YxB,UAAAA;AA2YY,iBAAW,KAAK,YAAY;AACxB,YAAI;AACA,gBAAIA,MAAA,EAAE,WAAF,gBAAAA,IAAU,YAAY,gBAAe,UAAU;AAC/C,cAAE,OAAO,aAAa,EAAE,YAAY,EAAE,CAAC;AACvC,cAAE,OAAO;AACT,cAAE,OAAO,aAAa,EAAE,YAAY,SAAS,CAAC;AAAA,UAClD,OAAO;AACH,cAAE,OAAO;AAAA,UACb;AAAA,QACJ,SAAS,GAAG;AACR,YAAE,OAAO;AAAA,QACb;AAAA,MACJ;AAAA,IAGJ;AAAA,IAEA,mBAAmB,CAAC,SAAiB;AAGjC,UAAI,CAAC,oBAAoB,IAAI,GAAG;AAC5B,aAAK,KAAK,iBAAiB,OAAO,iBAAiB,YAAY;AAC/D,eAAO;AAAA,MACX;AACA,iBAAW,QAAQ,OAAM,EAAE,eAAe,IAAK;AAC/C,aAAO;AAAA,IACX;AAAA,IACA,kBAAkB,MAAqB;AAta/C,UAAAA,KAAAC;AAuaY,YAAM,OAAMA,OAAAD,MAAA,WAAW,CAAC,MAAZ,gBAAAA,IAAe,gBAAf,OAAAC,MAA8B;AAC1C,aAAO,QAAQ,OAAO,CAAC,MAAM;AAAA,IACjC;AAAA,IACA,kBAAkB,CAAC,SAAiB;AA1a5C,UAAAD;AA8aY,YAAM,UAAU,eAAcA,MAAA,OAAO,aAAP,OAAAA,MAAmB,GAAG,kCAAc,CAAC;AACnE,YAAM,OAAO,UAAU,IAAI,YAAY,MAAM,OAAO,IAAI,KAAK,IAAI,GAAG,IAAI;AACxE,uBAAiB;AACjB,iBAAW,QAAQ,OAAK;AACpB,UAAE,cAAc;AAAA,MACpB,CAAC;AAAA,IACL;AAAA,IAEA,sBAAsB,MAAqB;AAtbnD,UAAAA;AAubY,YAAM,IAAI,IAAI,eAAe;AAC7B,aAAO,MAAM,OAAO,OAAO,eAAe,IAAGA,MAAA,OAAO,aAAP,OAAAA,MAAmB,GAAG,kCAAc,CAAC;AAAA,IACtF;AAAA,IAEA,sBAAsB,CAAC,aAAqB;AA3bpD,UAAAA;AA4bY,UAAI,eAAe,iBAAiB,WAAUA,MAAA,OAAO,aAAP,OAAAA,MAAmB,GAAG,kCAAc,CAAC,CAAC;AAAA,IACxF;AAAA,IAEA,WAAW,MAAM;AA/bzB,UAAAA;AAgcY,UAAI,OAAO;AACX,iBAAW,OAAO,GAAG,WAAW,MAAM;AACtC,UAAI,CAAC,cAAc;AACf,uBAAe;AACf,SAAAA,MAAA,uCAAW,aAAX,gBAAAA,IAAA;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AAQA,MAAI,OAAO,mBAAmB,UAAU;AACpC,QAAI,OAAO,QAAS,MAAK,KAAK,iBAAiB,OAAO,iBAAiB,oBAAoB;AAAA,EAC/F,OAAO;AAEH,UAAM,iBAAiB,uBAAuB,MAAK,YAAO,YAAP,YAAkB,CAAC,GAAG,IAAI;AAC7E,UAAM,gBAAgB,IAAI,QAAQ,KAAK,GAAG;AAC1C,QAAI,UAAU,MAAM;AAAE,qBAAe;AAAG,oBAAc;AAAA,IAAG;AAAA,EAC7D;AAIA,MAAI,gBAAgB;AAChB,eAAW,QAAQ,OAAK,EAAE,KAAK,CAAC;AAAA,EACpC;AAEA,SAAO;AACX;;;AC7bA,SAAS,kBAAkB,OAA0D,iBAAyB,MAAoD;AAlClK;AAmCI,QAAM,WAAW,QAAO,+BAAO,cAAa,WAAW,MAAM,WAAW;AACxE,QAAM,OAAO,sBAA8D,QAA9D,mBAAmE,YAAnE,4BAA6E,WAAW;AACrG,MAAI,QAAQ,OAAW,QAAO;AAE9B,SAAO,OAAO,EAAE,YAAW,oCAAO,UAAP,YAAgB,SAAS,QAAQ,IAAI,IAAI,EAAE,QAAQ,IAAI;AACtF;AAOO,SAAS,2BACZ,SACA,QACA,MAC6B;AAnDjC;AAqDI,QAAM,SAAS,sBAAQ,kBAAkB,QAAW,cAAc;AAClE,MAAI,CAAC,UAAU,CAAC,iBAAiB,MAAM,EAAG,QAAO;AACjD,QAAM,SAAmB,OAAO,UAAU,CAAC;AAC3C,QAAM,QAAO,YAAO,SAAP,YAAe;AAK5B,MAAI,OAAO,WAAW;AAClB,WAAO,KAAK,iBAAiB,UAAU,iBAAiB,6BAA6B;AACrF,WAAO;AAAA,EACX;AAEA,QAAM,IAAI;AACV,QAAM,OAAO,SAAS;AACtB,QAAM,OAAO,OAAO,EAAE,eAAe,EAAE;AACvC,MAAI,OAAO,SAAS,WAAY,QAAO;AAEvC,QAAM,QAAO,YAAO,SAAP,YAAe;AAC5B,MAAI;AACJ,MAAI;AACA,QAAI,MAAM;AAEN,iBAAW,IAAI,KAAK,EAAE,SAAS,qBAAqB,SAAS,OAAO,SAAS,MAAM,GAAG,KAAK,CAAC;AAAA,IAChG,OAAO;AACH,YAAM,SAAS,OAAO,WAAW,SAC3B,iBAAiB,IAChB,oBAAoB,SAAS,GAAG,KAAK,oBAAoB,SAAS,GAAG,KAAK,iBAAiB;AAClG,iBAAW,IAAI,KAAK,EAAE,QAAQ,KAAK,CAAC;AAAA,IACxC;AAAA,EACJ,SAAS,GAAG;AACR,WAAO,KAAK,iBAAiB,UAAU,iBAAiB,yBAAyB,CAAC;AAClF,WAAO;AAAA,EACX;AAEA,SAAO;AAAA,IACH;AAAA,IACA,YAAY,mBAAkB,YAAO,UAAP,mBAAc,OAAO,GAAG,IAAI;AAAA,IAC1D,UAAU,mBAAkB,YAAO,UAAP,mBAAc,KAAK,GAAG,IAAI;AAAA,EAC1D;AACJ;AAyBO,SAAS,oBAAoB,IAAa,MAAiC;AAC9E,QAAM,OAAO,SAAS;AACtB,QAAM,OAAO,SAAS;AACtB,WAAS,IAAI,GAAG,eAAe,GAAG,IAAI,EAAE,eAAe;AACnD,QAAI,MAAM,QAAQ,MAAM,KAAM,QAAO;AACrC,UAAM,QAAQ,iBAAiB,CAAC;AAChC,UAAM,WAAW,SAAS,MAAM,MAAM,YAAY,MAAM;AACxD,QAAI,aAAa,UAAU,aAAa,YAAY,aAAa,YAAY,aAAa,WAAW;AACjG,aAAO;AAAA,IACX;AAAA,EACJ;AACA,SAAO;AACX;AAEA,SAAS,mBAA4B;AACjC,SAAO,SAAS,oBAAoB,SAAS;AACjD;AAGA,IAAM,iBAAiB;AACvB,IAAM,mBAAmB;AAelB,SAAS,qBAAqB,SAAkB,SAA6B,MAA+B;AAzJnH;AA0JI,QAAM,SAAS,sBAAQ,kBAAkB,QAAW,cAAc;AAClE,QAAM,OAAO,mCAAS;AACtB,MAAI,CAAC,KAAM,QAAO;AAElB,MAAI,SAAS,gBAAgB;AAEzB,QAAI,kBAAkC;AACtC,aAAS,IAAI,QAAQ,eAAe,KAAK,MAAM,SAAS,MAAM,IAAI,EAAE,eAAe;AAC/E,YAAM,WAAW,iBAAiB,CAAC,EAAE;AACrC,UAAI,aAAa,YAAY,aAAa,QAAS,mBAAkB;AAAA,IACzE;AACA,YAAO,8DAAiB,kBAAjB,YAAkC,QAAQ,kBAA1C,YAA2D;AAAA,EACtE;AAEA,MAAI,SAAS,kBAAkB;AAC3B,WAAO,oBAAoB,SAAS,GAAG,KAAK,oBAAoB,SAAS,GAAG,KAAK,iBAAiB;AAAA,EACtG;AAEA,MAAI,QAAwB;AAC5B,MAAI;AACA,YAAQ,SAAS,cAAc,IAAI;AAAA,EACvC,SAAQ;AAEJ,WAAO,KAAK,iBAAiB,UAAU,iBAAiB,sBAAsB,IAAI;AAClF,WAAO;AAAA,EACX;AACA,MAAI,CAAC,OAAO;AAER,WAAO,KAAK,iBAAiB,MAAM,iBAAiB,sBAAsB,IAAI;AAC9E,WAAO;AAAA,EACX;AACA,SAAO;AACX;AAOO,SAAS,mBACZ,SACA,QACA,YACA,MACqB;AAtMzB;AAuMI,MAAI,CAAC,UAAU,CAAC,iBAAiB,MAAM,EAAG,QAAO;AAEjD,QAAM,SAAS,sBAAQ,kBAAkB,QAAW,cAAc;AAClE,QAAM,SAAmB,OAAO,UAAU,CAAC;AAC3C,QAAM,QAAO,YAAO,SAAP,YAAe;AAI5B,QAAM,WAAW,qBAAqB,SAAS,OAAO,SAAS,MAAM;AAIrE,QAAM,UAAU,oBAAoB,SAAS,GAAG,KAAK,oBAAoB,SAAS,GAAG;AACrF,QAAM,WAAqB,SAAS,YAAY,OAAO,WAAW,SAC5D,iBAAiB,IAChB,WAAW,iBAAiB;AACnC,QAAM,iBAAiB,aAAa,iBAAiB;AACrD,QAAM,OAAO,kBAAkB,OAAO,MAAM,iBAAiB,QAAQ,EAAE,WAAW;AAElF,QAAM,UAAU,MAAc;AAC1B,QAAI,SAAS,UAAU;AACnB,YAAM,SAAS,SAAS,MAAM,SAAS,YAAY,SAAS;AAC5D,YAAM,YAAY,SAAS,MACrB,SAAS,eAAe,SAAS,eACjC,SAAS,cAAc,SAAS;AACtC,aAAO,qBAAqB,QAAQ,WAAW,OAAO,KAAK;AAAA,IAC/D;AAGA,UAAM,cAAc,SAAS,sBAAsB;AACnD,QAAI,WAAmB;AACvB,QAAI,gBAAgB;AAChB,kBAAY;AAEZ,iBAAW,SAAS,MAAM,SAAS,gBAAgB,eAAe,SAAS,gBAAgB;AAAA,IAC/F,OAAO;AACH,YAAM,WAAW,SAAS,sBAAsB;AAChD,kBAAY,SAAS,MAAM,SAAS,MAAM,SAAS;AACnD,iBAAW,SAAS,MAAM,SAAS,eAAe,SAAS;AAAA,IAC/D;AACA,UAAM,gBAAgB,SAAS,MAAM,YAAY,MAAM,YAAY,QAAQ;AAC3E,UAAM,cAAc,SAAS,MAAM,YAAY,SAAS,YAAY;AACpE,WAAO,mBAAmB,cAAc,aAAa,UAAU,OAAO,KAAK;AAAA,EAC/E;AAOA,QAAM,eAAe,KAAK,IAAI,IAAG,YAAO,cAAP,YAAoB,CAAC,IAAI;AAI1D,QAAM,iBAAiB;AACvB,MAAI,YAAY;AAChB,MAAI,WAA0B;AAC9B,MAAI,YAA2B;AAC/B,MAAI,cAAc;AAElB,QAAM,OAAO,CAAC,WAAmB;AAC7B,QAAI,CAAC,cAAc;AAAE,iBAAW,MAAM;AAAG;AAAA,IAAQ;AACjD,QAAI,aAAa,MAAM;AAAE,iBAAW;AAAQ,iBAAW,MAAM;AAAG;AAAA,IAAQ;AACxE,QAAI,cAAc,KAAM;AACxB,kBAAc;AACd,UAAM,OAAO,CAAC,UAAkB;AAC5B,kBAAY;AACZ,UAAI,UAAW;AACf,YAAM,QAAQ,cAAc,KAAK,IAAI,MAAM,QAAQ,eAAe,GAAI,IAAI,IAAI;AAC9E,oBAAc;AACd,YAAM,OAAO,QAAQ;AACrB,YAAM,IAAI,IAAI,KAAK,IAAI,CAAC,QAAQ,YAAY;AAC5C,iBAAW,YAAa,OAAO,YAAa;AAC5C,UAAI,KAAK,IAAI,OAAO,QAAS,IAAI,eAAgB,YAAW;AAC5D,iBAAW,QAAS;AACpB,UAAI,aAAa,KAAM,aAAY,sBAAsB,IAAI;AAAA,IACjE;AACA,gBAAY,sBAAsB,IAAI;AAAA,EAC1C;AAGA,MAAI,QAAuB;AAC3B,QAAM,OAAO,MAAM;AACf,YAAQ;AACR,QAAI,UAAW;AACf,SAAK,QAAQ,CAAC;AAAA,EAClB;AACA,QAAM,WAAW,MAAM;AACnB,QAAI,aAAa,UAAU,KAAM;AACjC,YAAQ,sBAAsB,IAAI;AAAA,EACtC;AAGA,QAAM,eAA4B,iBAAiB,SAAS;AAC5D,eAAa,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;AACnE,SAAO,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;AAE7D,MAAI;AACJ,MAAI,OAAO,mBAAmB,aAAa;AACvC,qBAAiB,IAAI,eAAe,QAAQ;AAC5C,mBAAe,QAAQ,QAAQ;AAC/B,QAAI,aAAa,QAAS,gBAAe,QAAQ,OAAO;AACxD,QAAI,CAAC,eAAgB,gBAAe,QAAQ,QAAQ;AAAA,EACxD;AAEA,QAAM,SAAyB;AAAA,IAC3B,SAAS,MAAM;AACX,UAAI,UAAW;AACf,kBAAY;AACZ,mBAAa,oBAAoB,UAAU,QAAQ;AACnD,aAAO,oBAAoB,UAAU,QAAQ;AAC7C,uDAAgB;AAChB,UAAI,UAAU,MAAM;AAAE,6BAAqB,KAAK;AAAG,gBAAQ;AAAA,MAAM;AACjE,UAAI,cAAc,MAAM;AAAE,6BAAqB,SAAS;AAAG,oBAAY;AAAA,MAAM;AAAA,IACjF;AAAA;AAAA,IAEA,SAAS,MAAM;AAAE,UAAI,CAAC,WAAW;AAAE,mBAAW,QAAQ;AAAG,mBAAW,QAAQ;AAAA,MAAG;AAAA,IAAE;AAAA,EACrF;AAIA,SAAO,QAAQ;AAEf,SAAO;AACX;AAeA,SAAS,oBAAoB,SAA0B;AACnD,QAAM,WAAW,oBAAoB,SAAS,GAAG;AACjD,SAAO,WAAW,SAAS,eAAe,SAAS,gBAAgB;AACvE;AAEO,SAAS,eAAe,SAAkB,QAA0C;AACvF,QAAM,SAAS;AACf,MAAI,EAAC,iCAAQ,QAAO,CAAC,OAAO,MAAO,QAAO,MAAM;AAAA,EAAuB;AAEvE,QAAM,QAAQ,OAAO;AACrB,QAAM,eAAe,MAAM;AAC3B,QAAM,UAAU,MAAM;AACtB,QAAM,WAAW;AAMjB,QAAM,cAAc,OAAO,aAAa,WAAW,MAAM,OAAO,aAAa,WAAW,IAAI;AAC5F,QAAM,WAAW,MAAY;AArWjC;AAsWQ,UAAM,SAAQ,YAAO,cAAP,YAAoB;AAClC,QAAI,CAAC,aAAa;AACd,YAAM,MAAM,QAAQ;AACpB;AAAA,IACJ;AACA,UAAM,WAAW,oBAAoB,OAAO;AAC5C,UAAM,UAAU,QAAQ,sBAAsB,EAAE;AAChD,UAAM,MAAM,KAAK,OAAO,WAAW,WAAW,cAAc,KAAK,IAAI;AAAA,EACzE;AACA,WAAS;AAGT,MAAI,iBAAwC;AAC5C,QAAM,iBAAiB,cAAc,WAAW;AAChD,MAAI,aAAa;AACb,QAAI,eAAgB,QAAO,iBAAiB,UAAU,cAAc;AACpE,QAAI,OAAO,mBAAmB,aAAa;AACvC,uBAAiB,IAAI,eAAe,QAAQ;AAC5C,qBAAe,QAAQ,OAAO;AAAA,IAClC;AAAA,EACJ;AAGA,MAAI,UAA8B;AAClC,QAAM,SAAS,QAAQ;AACvB,MAAI,OAAO,eAAe,OAAO,cAAc,KAAK,QAAQ;AACxD,cAAU,SAAS,cAAc,KAAK;AACtC,YAAQ,aAAa,eAAe,EAAE;AACtC,YAAQ,MAAM,SAAU,OAAO,cAAc,MAAO;AACpD,WAAO,aAAa,SAAS,OAAO;AACpC,YAAQ,YAAY,OAAO;AAAA,EAC/B;AAEA,SAAO,MAAM;AACT,QAAI,eAAgB,QAAO,oBAAoB,UAAU,cAAc;AACvE,qDAAgB;AAChB,UAAM,WAAW;AACjB,UAAM,MAAM;AACZ,QAAI,mCAAS,eAAe;AACxB,cAAQ,cAAc,aAAa,SAAS,OAAO;AACnD,cAAQ,OAAO;AAAA,IACnB;AAAA,EACJ;AACJ;;;ACjXO,SAAS,iBACZ,gBACA,WACA,MACa;AAEb,MAAI;AACJ,MAAI,qBAAqB;AACzB,MAAI,eAAe,eAAe;AAC9B,yBAAqB,iCACd,YADc;AAAA,MAEjB,UAAU,MAAM;AA3C5B;AA4CgB,qDAAW,aAAX;AACA,yCAAQ;AAAA,MACZ;AAAA,IACJ;AAAA,EACJ;AAEA,QAAM,MAAM,KAAK,kBAAkB;AACnC,WAAS;AAET,MAAI,eAAe,iBAAiB;AAChC,IAAC,OAAe,eAAe,eAAe,IAAI;AAAA,EACtD;AAEA,SAAO;AACX;AAOO,SAAS,qBACZ,KACA,SACA,WACA,aACa;AACb,QAAM,iBAAiB,kBAAkB,GAAG,KAAK,CAAC;AAElD,QAAM,OAAO,kBAAkB,WAAW,cAAc;AAWxD,MAAI,iBAAiB,cAAc,GAAG;AAClC,WAAO,iBAAiB,gBAAgB,WAAW,QAAM;AArFjE;AA2FY,UAAI,QAAQ,MAAM;AAAA,MAAuB;AAGzC,UAAI,2BAA2B,eAAe,MAAM,KAAK,aAAa;AAClE,gBAAQ,eAAe,aAAa,eAAe,MAAM;AACzD,cAAM,SAAS,2BAA2B,aAAa,gBAAgB,IAAI;AAC3E,YAAI,QAAQ;AACR,gBAAME,OAAM;AAAA,YAAqB;AAAA,YAAK;AAAA,YAAI;AAAA,YACtC,eAAe,eAAe,MAAM;AAAA,YAAG;AAAA,UAAM;AACjD,cAAIA,MAAK;AACL,kBAAM,gBAAgBA,KAAI,QAAQ,KAAKA,IAAG;AAC1C,YAAAA,KAAI,UAAU,MAAM;AAAE,oBAAM;AAAG,4BAAc;AAAA,YAAG;AAChD,mBAAOA;AAAA,UACX;AAAA,QAEJ;AACA,cAAM;AACN,gBAAQ,MAAM;AAAA,QAAwB;AAAA,MAC1C;AAIA,YAAM,OACF,eAAe,WAAW,wBAAwB,KAC5C,qBAAqB,KAAK,IAAI,aAAa,eAAe,eAAe,MAAM,CAAC,IAChF,SACL,wBAAwB,KAAK,SAAS,IAAI,WAAW;AAK1D,YAAM,YAAU,SAAI,mBAAJ,iCAA0B;AAC1C,UAAI,SAAS;AACT,gBAAQ,eAAe,SAAS,eAAe,MAAM;AACrD,cAAM,UAAU,sBAAsB,cAAc;AACpD,cAAM,SAAS;AAAA,UAAmB;AAAA,UAAS;AAAA,UACvC,cAAY,IAAI,eAAe,WAAW,OAAO;AAAA,UAAG;AAAA,QAAI;AAC5D,YAAI,QAAQ;AAER,gBAAM,UAAU,IAAI,QAAQ,KAAK,GAAG;AACpC,cAAI,UAAU,MAAM;AAAE,mBAAO,QAAQ;AAAG,kBAAM;AAAG,oBAAQ;AAAA,UAAG;AAAA,QAChE;AAAA,MACJ,OAAO;AACH,aAAK,KAAK,iBAAiB,MAAM,iBAAiB,qBAAqB;AAAA,MAC3E;AACA,aAAO;AAAA,IACX,CAAC;AAAA,EACL;AAEA,SAAO,iBAAiB,gBAAgB,WAAW,QAAM;AACrD,QAAI,eAAe,WAAW,wBAAwB,IAAI;AAEtD,aAAO,wBAAwB,KAAK,SAAS,IAAI,WAAW;AAAA,IAChE;AAGA,WACI;AAAA,MAAqB;AAAA,MAAK;AAAA,MAAI;AAAA,MAC1B,eAAe,eAAe,MAAM;AAAA,IACxC,KACA,wBAAwB,KAAK,SAAS,IAAI,WAAW;AAAA,EAE7D,CAAC;AACL;;;AC9IA,IAAM,SAAS;AAQf,IAAM,mBAA8C,CAAC,EAAE,KAAK,OAAO,OAAO,UAAU,KAAK,MAAM;AAC3F,QAAM,UAAU,SAAS,gBAAgB,QAAQ,GAAG;AAEpD,aAAW,QAAQ,OAAO,KAAK,KAAK,EAAG,SAAQ,aAAa,MAAM,MAAM,IAAI,CAAC;AAE7E,MAAI,OAAO;AACP,UAAM,SAAS,QAAQ;AACvB,eAAW,QAAQ,OAAO,KAAK,KAAK,EAAG,QAAO,IAAI,IAAI,MAAM,IAAI;AAAA,EACpE;AAIA,aAAW,SAAS,SAAU,SAAQ,YAAY,KAAK;AACvD,MAAI,SAAS,OAAW,SAAQ,cAAc;AAE9C,SAAO;AACX;AAOO,SAAS,WAAW,MAAc,OAAuB,MAAsC;AAClG,SAAO,aAAa,MAAM,kBAAkB,IAAI;AACpD;;;ACnBA,SAAS,yBACL,KACA,SACA,WACA,aACa;AACb,SAAO,qBAAqB,KAAK,SAAS,WAAW,WAAW;AACpE;AAgBA,SAAS,mBACL,KACA,SACA,WACA,kBACA,OACA,eACA,cACa;AAQb,QAAM,OAAO,kBAAkB,WAAW,cAAc;AAExD,QAAM,kBAAkB,oBAAoB,GAAU;AACtD,aAAW,KAAK,gBAAiB,MAAK,KAAK,iBAAiB,UAAU,iBAAiB,cAAc,CAAC;AAKtG,4BAA0B,KAAK,6BAA6B;AAM5D,MAAI,UAAU,UAAa,eAAe;AACtC,UAAM,UAAU,oBAAoB,KAAK,wBAAS,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC,cAAc,CAAC;AACxF,eAAW,KAAK,QAAQ,SAAU,MAAK,KAAK,iBAAiB,OAAO,iBAAiB,yBAAyB,CAAC;AAC/G,UAAM,QAAQ;AAAA,EAClB;AAMA,QAAM,iBAAiB,kBAAkB,GAAG,KAAK,CAAC;AAClD,QAAM,SAA2B,sBAAsB,eAAe,MAAM;AAM5E,QAAM,qBAAqB,KAAK,MAAM;AAEtC,MAAI,cAA8B;AAMlC,MAAI,kBAAkB;AAElB,UAAM,eAAe,GAAG;AAExB,UAAM,cAAc,OAAO,qBAAqB,WAC5C,SAAS,cAAc,gBAAgB,IAAI;AAE/C,QAAI,aAAa;AACb,oBAAc,WAAW,KAAK,QAAW,IAAI;AAC7C,UAAI,aAAa;AACb,oBAAY,gBAAgB,WAAW;AAAA,MAC3C;AAAA,IACJ;AAAA,EACJ;AAIA,MAAI,CAAC,eAAe,aAAc,eAAc;AAEhD,QAAM,MAAM,yBAAyB,KAAK,SAAS,WAAW,WAAW;AAMzE,MAAI,oBAAoB,aAAa;AACjC,UAAM,WAAW;AACjB,UAAM,gBAAgB,IAAI,QAAQ,KAAK,GAAG;AAC1C,QAAI,UAAU,MAAM;AAChB,oBAAc;AACd,eAAS,OAAO;AAAA,IACpB;AAAA,EACJ;AAEA,SAAO;AACX;AAiDA,SAAS,kBAAkB,SAAkE;AAGzF,SAAO,aAAa,WAAW,iBAAiB;AACpD;AAMO,SAAS,sBAAsB,SAA+D;AACjG,QAAM,EAAE,UAAU,UAAU,OAAO,YAAY,MAAM,IAAI;AACzD,SAAO,qBAAqB,UAAU,EAAE,UAAU,OAAO,YAAY,MAAM,CAAC;AAChF;AAWO,SAAS,eAAe,SAA2C;AAEtE,QAAM,EAAE,KAAK,KAAK,WAAW,cAAc,IAAI;AAC/C,QAAM,UAAU,kBAAkB,OAAO,IAAI,QAAQ,UAAU;AAC/D,QAAM,eAAe,kBAAkB,OAAO,IAAI,QAAQ,cAAc;AACxE,QAAM,QAAQ,sBAAsB,OAAO;AAG3C,MAAI;AACJ,QAAM,YAAY,mBAAmB,kBAAkB,OAAO,GAAG,MAAM,KAAK;AAK5E,MAAI,QAAQ,UAAa,QAAQ,QAAW;AACxC,UAAM,IAAI,MAAM,yDAAyD;AAAA,EAC7E;AACA,MAAI,QAAQ,UAAa,QAAQ,QAAW;AACxC,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACvE;AAEA,MAAI,WAAiC;AAMrC,MAAI,UAAsD,CAAC;AAC3D,MAAI,YAAY;AAEhB,QAAM,UAAU,CAAC,SAAuC;AACpD,QAAI,UAAU;AACV,WAAK,QAAQ;AAAA,IACjB,WAAW,SAAS;AAChB,cAAQ,KAAK,IAAI;AAAA,IACrB;AAAA,EACJ;AAEA,QAAM,OAAO,kBAAkB,WAAW,cAAc;AAExD,QAAM,QAAQ,CAAC,QAA6B;AACxC,eAAW;AACX,UAAM,SAAS;AACf,cAAU;AACV,qCAAQ,QAAQ,UAAQ,KAAK,GAAG;AAAA,EACpC;AAEA,QAAM,SAAS,CAAC,MAAwB,SAA2B,SAA+B;AAC9F,cAAU;AACV,SAAK,MAAM,MAAM,MAAM,GAAG,IAAI;AAAA,EAClC;AAGA,QAAM,QAAQ,CAACC,cAA0C;AACrD,QAAI;AACA,YAAM,mBAAmBA,WAAU,SAAS,WAAW,WAAW,OAAO,eAAe,YAAY,CAAC;AAAA,IACzG,SAAS,GAAG;AACR,YAAM,MAAM,cAAc,CAAC;AAC3B,aAAO,iBAAiB,UAAU,iBAAiB,aAAa,GAAG;AAAA,IACvE;AAAA,EACJ;AAKA,UAAQ;AAAA,IACJ,WAAW,MAAM,CAAC,CAAC;AAAA,IACnB,kBAAkB,MAAM,WAAW,SAAS,eAAe,IAAI;AAAA,IAC/D,aAAa,OAAM,qCAAU,gBAAe;AAAA,IAC5C,QAAQ,MAAM;AAAE,cAAQ,SAAO,IAAI,KAAK,CAAC;AAAA,IAAG;AAAA,IAC5C,SAAS,MAAM;AAAE,cAAQ,SAAO,IAAI,MAAM,CAAC;AAAA,IAAG;AAAA,IAC9C,UAAU,MAAM;AAAE,cAAQ,SAAO,IAAI,OAAO,CAAC;AAAA,IAAG;AAAA,IAChD,UAAU,MAAM;AAAE,cAAQ,SAAO,IAAI,OAAO,CAAC;AAAA,IAAG;AAAA,IAChD,mBAAmB,CAAC,SAAiB;AAAE,cAAQ,SAAO,IAAI,gBAAgB,IAAI,CAAC;AAAA,IAAG;AAAA,IAClF,kBAAkB,MAAM,WAAW,SAAS,eAAe,IAAI;AAAA,IAC/D,kBAAkB,CAAC,SAAiB;AAAE,cAAQ,SAAO,IAAI,eAAe,IAAI,CAAC;AAAA,IAAG;AAAA,IAChF,sBAAsB,MAAM,WAAW,SAAS,mBAAmB,IAAI;AAAA,IACvE,sBAAsB,CAAC,aAAqB;AAAE,cAAQ,SAAO,IAAI,mBAAmB,QAAQ,CAAC;AAAA,IAAG;AAAA,IAChG,WAAW,MAAM;AACb,kBAAY;AACZ,gBAAU;AACV,2CAAU;AAAA,IACd;AAAA,EACJ;AAEA,mBAAiB,KAAK;AAEtB,MAAI,QAAQ,QAAW;AACnB,UAAM,GAAG;AAAA,EACb,OAAO;AACH,UAAM,GAAI,EAAE,KAAK,SAAO,IAAI,KAAK,CAAC,EAAE,KAAK,UAAQ;AAC7C,UAAI,UAAW;AACf,UAAI,aAAa,IAAI,EAAG,OAAM,IAAI;AAAA,UAC7B,QAAO,iBAAiB,UAAU,iBAAiB,sBAAsB,GAAG;AAAA,IACrF,CAAC,EAAE,MAAM,SAAO;AAlTxB;AAoTY,aAAO,iBAAiB,MAAM,iBAAiB,YAAY,MAAK,gCAAK,YAAL,YAAgB,OAAO,GAAG,CAAC;AAAA,IAC/F,CAAC;AAAA,EACL;AAEA,SAAO;AACX;AAsBO,SAAS,iBAAiB,SAAgC;AAC7D,QAAM,WAAW,SAAS,iBAAiB,MAAM,wBAAwB,GAAG;AAC5E,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACtC,UAAM,UAAU,SAAS,CAAC;AAC1B,QAAI,CAAE,QAAgB,iBAAiB,GAAG;AACtC,YAAM,MAAM,QAAQ,aAAa,qBAAqB;AACtD,UAAI,KAAK;AACL,QAAC,QAAgB,iBAAiB,IAAI,eAAe,iCAAK,UAAL,EAAc,KAAK,WAAW,QAAQ,EAAC;AAAA,MAChG;AAAA,IACJ;AAAA,EACJ;AACJ;","names":["_a","_b","api","document"]}
|
|
1
|
+
{"version":3,"sources":["../../svg-animator-core/src/effects/PlayerEffectsUtil.visualModel.ts","../../svg-animator-core/src/render/PxRenderTree.ts","../../svg-animator-core/src/playback/PxScrollMath.ts","../../svg-animator-core/src/version/PxSchemaFieldUniverse.ts","../../svg-animator-core/src/version/PxSchemaRelease.ts","../src/shared/PxAnimatorCallbacks.ts","../src/registry/PxAnimatorRegistry.ts","../src/triggers/PxAnimatorTriggers.ts","../src/engines/PxAnimatorFrameLoop.ts","../src/engines/PxAnimatorWebApi.ts","../src/scroll/PxScrollDriver.ts","../src/engines/PxAnimatorBind.ts","../src/dom/PxAnimatorDOM.ts","../src/animator/PxAnimator.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/**\n * \"Equal-in-effect\" comparator for SVGA node trees.\n *\n * Two trees can render identically while differing structurally — extra `<g>`\n * wrappers, different ids, `<use>` clones vs inline copies, a transform expressed\n * as a baked string vs a parts record. This module reduces a tree to what is\n * actually painted: every drawable leaf, with its CUMULATIVE transform matrix\n * (resolved at a given time) and key visual attributes. Comparing those leaf\n * sets ignores representation and catches genuine visual differences.\n *\n * It is a deterministic function applied identically to both trees, so equal\n * inputs (in effect) yield equal output regardless of how each was encoded.\n *\n * Dependency-free on purpose (mirrors the applier's isolation). Scope: the node\n * types and transform forms the player-effects / heavy serializers emit. Color\n * animation is not interpolated — leaves are compared at keyframe instants where\n * sampled values are exact.\n */\n\ntype Mat = [number, number, number, number, number, number];\n\ninterface VmNode {\n type?: string;\n children?: Array<VmNode>;\n [attr: string]: any;\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// MATRIX MATH (2x3 affine, SVG order: [a b c d e f])\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst IDENTITY: Mat = [1, 0, 0, 1, 0, 0];\n\nfunction mul(m: Mat, n: Mat): Mat {\n return [\n m[0] * n[0] + m[2] * n[1],\n m[1] * n[0] + m[3] * n[1],\n m[0] * n[2] + m[2] * n[3],\n m[1] * n[2] + m[3] * n[3],\n m[0] * n[4] + m[2] * n[5] + m[4],\n m[1] * n[4] + m[3] * n[5] + m[5],\n ];\n}\n\nconst translateM = (x: number, y: number): Mat => [1, 0, 0, 1, x, y];\nconst scaleM = (sx: number, sy: number): Mat => [sx, 0, 0, sy, 0, 0];\nfunction rotateM(deg: number): Mat {\n const r = (deg * Math.PI) / 180;\n return [Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0];\n}\nconst skewXM = (deg: number): Mat => [1, 0, Math.tan((deg * Math.PI) / 180), 1, 0, 0];\nconst skewYM = (deg: number): Mat => [1, Math.tan((deg * Math.PI) / 180), 0, 1, 0, 0];\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TRANSFORM EVALUATION\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Parses an SVG transform string (\"translate(..)rotate(..)…\") into a matrix. */\nfunction parseTransformString(s: string): Mat {\n let m = IDENTITY;\n const re = /(translate|rotate|scale|matrix|skewX|skewY)\\(([^)]*)\\)/g;\n let hit: RegExpExecArray | null;\n while ((hit = re.exec(s))) {\n const fn = hit[1];\n const a = hit[2].split(/[\\s,]+/).filter(Boolean).map(Number);\n if (fn === 'translate') m = mul(m, translateM(a[0] || 0, a[1] || 0));\n else if (fn === 'scale') m = mul(m, scaleM(a[0], a.length > 1 ? a[1] : a[0]));\n else if (fn === 'rotate') m = mul(m, rotateM(a[0]));\n else if (fn === 'skewX') m = mul(m, skewXM(a[0]));\n else if (fn === 'skewY') m = mul(m, skewYM(a[0]));\n else if (fn === 'matrix') m = mul(m, [a[0], a[1], a[2], a[3], a[4], a[5]]);\n }\n return m;\n}\n\ninterface Parts { translate?: [number, number]; rotate?: number; scale?: [number, number]; origin?: [number, number]; }\n\n/** Composes a `PxTransformParts` record (player canonical order, origin-pivoted). */\nfunction partsToMatrix(p: Parts): Mat {\n let m = IDENTITY;\n if (p.translate) m = mul(m, translateM(p.translate[0], p.translate[1]));\n const pivot = p.origin && (p.rotate !== undefined || p.scale);\n if (pivot) m = mul(m, translateM(p.origin![0], p.origin![1]));\n if (p.rotate !== undefined) m = mul(m, rotateM(p.rotate));\n if (p.scale) m = mul(m, scaleM(p.scale[0], p.scale[1]));\n if (pivot) m = mul(m, translateM(-p.origin![0], -p.origin![1]));\n return m;\n}\n\nfunction lerp(a: number, b: number, f: number): number { return a + (b - a) * f; }\n\n/** Linear-interpolates a parts record between keyframes at time `t` (ms). */\nfunction interpParts(kfs: Array<any>, t: number): Parts {\n if (!kfs.length) return {};\n if (t <= (kfs[0].time ?? 0)) return kfs[0].value || {};\n if (t >= (kfs[kfs.length - 1].time ?? 0)) return kfs[kfs.length - 1].value || {};\n\n let i = 0;\n while (i < kfs.length - 1 && (kfs[i + 1].time ?? 0) < t) i++;\n const a = kfs[i], b = kfs[i + 1];\n const f = (t - (a.time ?? 0)) / ((b.time ?? 0) - (a.time ?? 0) || 1);\n const va: Parts = a.value || {}, vb: Parts = b.value || {};\n\n const out: Parts = {};\n if (va.translate && vb.translate) out.translate = [lerp(va.translate[0], vb.translate[0], f), lerp(va.translate[1], vb.translate[1], f)];\n else out.translate = va.translate || vb.translate;\n if (va.rotate !== undefined && vb.rotate !== undefined) out.rotate = lerp(va.rotate, vb.rotate, f);\n else out.rotate = va.rotate ?? vb.rotate;\n if (va.scale && vb.scale) out.scale = [lerp(va.scale[0], vb.scale[0], f), lerp(va.scale[1], vb.scale[1], f)];\n else out.scale = va.scale || vb.scale;\n out.origin = va.origin || vb.origin;\n return out;\n}\n\n/** Resolves any transform-slot form (string | bare record | {value} | {keyframes}) to a matrix at `t`. */\nfunction evalTransformValue(v: any, t: number): Mat {\n if (v === undefined || v === null) return IDENTITY;\n if (typeof v === 'string') return parseTransformString(v);\n if (v.keyframes) return partsToMatrix(interpParts(v.keyframes, t));\n if (v.value) return partsToMatrix(v.value);\n if (typeof v === 'object' && !Array.isArray(v)) return partsToMatrix(v); // bare parts record\n return IDENTITY;\n}\n\n/** The node's own transform at `t`: animated slot wins over the static baseline. */\nfunction nodeMatrix(node: VmNode, t: number): Mat {\n if (node.animate && node.animate.transform) return evalTransformValue(node.animate.transform, t);\n if (node.transform !== undefined) return evalTransformValue(node.transform, t);\n return IDENTITY;\n}\n\n/** Linear-interpolates a scalar animation (e.g. opacity); falls back to static / default. */\nfunction evalScalar(animated: any, staticVal: any, fallback: number, t: number): number {\n if (animated && animated.keyframes && animated.keyframes.length) {\n const kfs = animated.keyframes;\n if (t <= (kfs[0].time ?? 0)) return kfs[0].value;\n if (t >= (kfs[kfs.length - 1].time ?? 0)) return kfs[kfs.length - 1].value;\n let i = 0;\n while (i < kfs.length - 1 && (kfs[i + 1].time ?? 0) < t) i++;\n const a = kfs[i], b = kfs[i + 1];\n const f = (t - (a.time ?? 0)) / ((b.time ?? 0) - (a.time ?? 0) || 1);\n return lerp(a.value, b.value, f);\n }\n return staticVal !== undefined ? Number(staticVal) : fallback;\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FLATTEN → multiset of painted primitives\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst CONTAINER_TYPES = new Set(['svg', 'g', 'symbol']);\nconst SKIP_TYPES = new Set(['defs', 'mask', 'clipPath', 'title']);\n\nfunction buildIdMap(node: VmNode, map: Map<string, VmNode>): void {\n if (typeof node.id === 'string') map.set(node.id, node);\n node.children?.forEach(c => buildIdMap(c, map));\n}\n\nfunction num(v: any): number { return v === undefined || v === null ? 0 : Number(v); }\n\n// 2-decimal rounding (sub-pixel). The heavy path bakes matrices into strings\n// rounded to ~4 decimals; comparing at 2 decimals avoids straddling a rounding\n// boundary against the applier's full-precision values, while staying visually exact.\nfunction round(n: number): number { return Math.round(n * 100) / 100 + 0; }\n\nfunction geomKey(node: VmNode): string {\n switch (node.type) {\n case 'rect': return num(node.width) + ',' + num(node.height) + ',' + num(node.x) + ',' + num(node.y);\n case 'ellipse': return num(node.rx) + ',' + num(node.ry) + ',' + num(node.cx) + ',' + num(node.cy);\n case 'circle': return num(node.r) + ',' + num(node.cx) + ',' + num(node.cy);\n case 'path': return String(node.d ?? '');\n default: return '';\n }\n}\n\nfunction describePrimitive(node: VmNode, m: Mat, t: number): string {\n const fill = node.fill ?? '';\n const stroke = node.stroke ?? '';\n const sw = node['stroke-width'] ?? node.strokeWidth ?? '';\n const opacity = round(evalScalar(node.animate?.opacity, node.opacity, 1, t));\n const masked = node.mask ? 1 : 0;\n const mat = m.map(round).join(',');\n return node.type + '|' + geomKey(node) + '|[' + mat + ']|f:' + fill + '|s:' + stroke + '|sw:' + (num(sw) || '') + '|o:' + opacity + '|m:' + masked;\n}\n\nfunction flatten(node: VmNode, parent: Mat, t: number, idMap: Map<string, VmNode>, out: Array<string>): void {\n const type = node.type || '';\n if (SKIP_TYPES.has(type)) return;\n\n const m = mul(parent, nodeMatrix(node, t));\n\n if (type === 'use') {\n const targetId = typeof node.href === 'string' ? node.href.replace(/^#/, '') : '';\n const target = idMap.get(targetId);\n const useM = mul(m, translateM(num(node.x), num(node.y)));\n if (target) flatten(target, useM, t, idMap, out);\n else out.push('UNRESOLVED_USE:#' + targetId);\n return;\n }\n\n if (CONTAINER_TYPES.has(type)) {\n node.children?.forEach(c => flatten(c, m, t, idMap, out));\n return;\n }\n\n out.push(describePrimitive(node, m, t));\n}\n\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PUBLIC: sample times + visual model + comparison\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Every keyframe `time` found anywhere in the tree, plus 0. @internal */\nexport function collectSampleTimes(node: VmNode, into: Set<number>): void {\n into.add(0);\n const scanAnim = (anim: any) => {\n if (!anim || typeof anim !== 'object') return;\n for (const key of Object.keys(anim)) {\n const kfs = anim[key]?.keyframes;\n if (Array.isArray(kfs)) kfs.forEach(kf => into.add(kf.time ?? 0));\n }\n };\n scanAnim(node.animate);\n if (node.transform && typeof node.transform === 'object' && (node.transform as any).keyframes) {\n (node.transform as any).keyframes.forEach((kf: any) => into.add(kf.time ?? 0));\n }\n node.children?.forEach(c => collectSampleTimes(c, into));\n}\n\n/** Sorted, painted-primitive multiset for the tree at time `t`. @internal */\nexport function visualModelAt(root: VmNode, t: number): Array<string> {\n const idMap = new Map<string, VmNode>();\n buildIdMap(root, idMap);\n const out: Array<string> = [];\n flatten(root, IDENTITY, t, idMap, out);\n return out.sort();\n}\n\nexport interface EffectDiff {\n time: number;\n onlyInA: Array<string>;\n onlyInB: Array<string>;\n}\n\n/**\n * Compares two trees \"in effect\" across all keyframe instants found in either.\n * Returns one entry per time where the painted-primitive multisets differ.\n * @internal\n */\nexport function diffInEffect(a: VmNode, b: VmNode): Array<EffectDiff> {\n const times = new Set<number>();\n collectSampleTimes(a, times);\n collectSampleTimes(b, times);\n\n const diffs: Array<EffectDiff> = [];\n for (const t of Array.from(times).sort((x, y) => x - y)) {\n const ma = visualModelAt(a, t);\n const mb = visualModelAt(b, t);\n const onlyInA = subtractMultiset(ma, mb);\n const onlyInB = subtractMultiset(mb, ma);\n if (onlyInA.length || onlyInB.length) diffs.push({ time: t, onlyInA, onlyInB });\n }\n return diffs;\n}\n\n/** Items in `a` not matched one-for-one in `b` (multiset difference). */\nfunction subtractMultiset(a: Array<string>, b: Array<string>): Array<string> {\n const counts = new Map<string, number>();\n for (const x of b) counts.set(x, (counts.get(x) || 0) + 1);\n const extra: Array<string> = [];\n for (const x of a) {\n const c = counts.get(x) || 0;\n if (c > 0) counts.set(x, c - 1);\n else extra.push(x);\n }\n return extra;\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// THE renderer — the one place a `PxNode` tree becomes elements, for EVERY DOM player.\n//\n// The web player, the React component and the Vue component used to carry a renderer each.\n// Three copies drifted: React and Vue skipped sanitization, wrote camelCase names SVG ignores\n// (`maskType`, and in Vue every presentation attribute — gradients lost their colours), wrote\n// the `domType` escape key as a literal attribute (so `<feColorMatrix>` lost its `type` and the\n// filter painted its target black), and put CSS-only properties out as dead attributes.\n//\n// So every DECISION lives here, once:\n// - which tag (`type`, default `g`) and whether it is allowed at all;\n// - the `domType` escape key → the real `type` attribute;\n// - which keys are attributes (`toDomProps`) and what their values may be\n// (`sanitizeAttributeValue`);\n// - the NAME each attribute has in SVG (`camelCaseToKebabWordIfNeeded`, `className` → `class`);\n// - which properties are CSS-only and so go to `style`, plus the node's own `style` record;\n// - children, or the node's `textContent` when it has none.\n//\n// A framework supplies exactly one thing — `PxElementFactory`: given the finished spec, make\n// an element (DOM node / React element / Vue vnode). It decides nothing about the document.\n// Platform-neutral on purpose: no DOM here, so it also runs on a server.\n\nimport { PX_TEXT_CONTENT_ATTR } from '../format/PxAnimatorConstants';\nimport type { PxNode } from '../format/PxAnimatorTypes';\nimport { PxDiagnosticCode } from '../playback/PxDiagnosticCode';\nimport { createDiagnostics, PxDiagnosticKind, type PxDiagnostics } from '../playback/PxDiagnostics';\nimport { camelCaseToKebabWordIfNeeded } from '../util/PxAnimatorUtil';\nimport { PX_CSS_ONLY_STYLE_PROPS, PX_DISALLOWED_SVG_TAGS_LOWER, sanitizeAttributeValue, toDomProps } from '../util/PxNodeProps';\n\n\n/**\n * One element, fully decided. Everything a factory needs and nothing it has to think about.\n * @internal\n */\nexport interface PxElementSpec<T> {\n /** The SVG tag name. */\n tag: string;\n /** Attributes under the names SVG reads (`stroke-width`, `viewBox`, `class`), sanitized. */\n attrs: Record<string, string>;\n /** Inline style, camelCase property → value; `undefined` when there is none. */\n style: Record<string, string> | undefined;\n /** Rendered children, blocked ones already dropped. Empty when the node has none. */\n children: Array<T>;\n /** The node's text — set ONLY when `children` is empty (a line `<tspan>` can carry both,\n * and then the styled child spans are what renders). */\n text: string | undefined;\n /** The source node — for the node's `id` (refs) and nothing about rendering. */\n node: PxNode;\n /** True for the tree's root `<svg>`. */\n isRoot: boolean;\n /** Position among its siblings — a stable list key for frameworks that want one. */\n index: number;\n}\n\n/**\n * The ONE thing a framework supplies: make an element from a finished spec.\n * @internal\n */\nexport type PxElementFactory<T> = (spec: PxElementSpec<T>) => T;\n\n/** The attribute SVG calls `class`; the wire and the DOM property call it `className`. */\nconst CLASS_NAME_KEY = 'className';\nconst CLASS_ATTR = 'class';\n\n/** The escape key for an element whose own `type` ATTRIBUTE collides with the node-tag key —\n * `feColorMatrix`, `feTurbulence`, `feFunc*` (written by the editor at one choke point). */\nconst DOM_TYPE_KEY = 'domType';\nconst TYPE_ATTR = 'type';\n\nconst DEFAULT_TAG = 'g';\n\n\n/**\n * Renders a `PxNode` tree through `factory`. Returns `null` for a missing node or a blocked\n * root tag; blocked descendants are dropped and reported.\n * @internal\n */\nexport function renderPxTree<T>(node: PxNode | undefined, factory: PxElementFactory<T>, diag?: PxDiagnostics): T | null {\n return node ? renderOne(node, factory, diag, true, 0) : null;\n}\n\nfunction renderOne<T>(node: PxNode, factory: PxElementFactory<T>, diag: PxDiagnostics | undefined, isRoot: boolean, index: number): T | null {\n const { type, children, style, ...props } = node;\n const tag = type || DEFAULT_TAG;\n\n if (PX_DISALLOWED_SVG_TAGS_LOWER.has(tag.toLowerCase())) {\n // `document`: a blocked tag is content the FILE asked for, so the file is what changes.\n (diag ?? createDiagnostics(undefined, '[PxAnimator]')).warn(PxDiagnosticKind.document, PxDiagnosticCode.blockedTag, tag);\n return null;\n }\n\n // `type` is reserved for the tag and `toDomProps` drops it, so the escaped value is lifted\n // out first and written back as the real attribute below. Without it the primitive is lost\n // and an EMPTY `<filter>` paints its target transparent black.\n const domType = props[DOM_TYPE_KEY];\n if (domType !== undefined) delete props[DOM_TYPE_KEY];\n\n const attrs: Record<string, string> = {};\n let inlineStyle: Record<string, string> | undefined;\n\n const domProps = toDomProps(props);\n for (const propName of Object.keys(domProps)) {\n // `undefined` means \"do not emit\" (whitelist miss, blocked dangerous value, …) — a\n // writer would otherwise coerce it to the literal string \"undefined\".\n const sanitized = sanitizeAttributeValue(propName, domProps[propName]);\n if (sanitized === undefined) continue;\n\n // CSS-only properties (mix-blend-mode, isolation) are not SVG presentation attributes;\n // as attributes the browser ignores them, so they go through `style`.\n if (PX_CSS_ONLY_STYLE_PROPS.has(propName)) {\n (inlineStyle ??= {})[propName] = String(sanitized);\n continue;\n }\n attrs[propName === CLASS_NAME_KEY ? CLASS_ATTR : camelCaseToKebabWordIfNeeded(propName)] = sanitized;\n }\n if (domType !== undefined) attrs[TYPE_ATTR] = String(domType);\n\n // The node's own `style` record — declarations, not attributes. Written after the CSS-only\n // properties so an explicit declaration wins over the same property given as an attribute.\n if (style) {\n for (const styleProp of Object.keys(style)) (inlineStyle ??= {})[styleProp] = String(style[styleProp]);\n }\n\n const rendered: Array<T> = [];\n if (children) {\n children.forEach((child, i) => {\n const el = renderOne(child, factory, diag, false, i);\n if (el !== null) rendered.push(el);\n });\n }\n\n const ownText = props[PX_TEXT_CONTENT_ATTR];\n const text = !rendered.length && typeof ownText === 'string' && ownText ? ownText : undefined;\n\n return factory({ tag, attrs, style: inlineStyle, children: rendered, text, node, isRoot, index });\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// Scroll-timeline PROGRESS MATH — pure functions, no DOM. This is the reference\n// implementation for `animator.timelineSource: 'scroll'` (the player-measured path — `timeline.engine: 'js'`, and the fallback of `auto`,\n// both engines): the DOM driver in svg-animator-web measures rects/offsets and calls in\n// here; the editor's design doc (`app/src/svgeditor/animation/scroll-timeline.design.md`\n// §4) documents the same formulas — keep them in sync.\n\nimport { clamp, PX_DEFAULT_DURATION_MS } from '../util/PxAnimatorUtil';\nimport type { PxAnimatorConfig, PxScroll, PxScrollPhase, PxScrollRangePoint } from '../format/PxAnimatorTypes';\n\n\n/** Is this document scroll-driven? (`animator.timelineSource === 'scroll'`) @internal */\nexport function isScrollTimeline(config: PxAnimatorConfig | undefined): boolean {\n return config?.timelineSource === 'scroll';\n}\n\n/**\n * The seek-space length (ms) a scroll progress of 1 maps to: duration × finite\n * iterations. `'infinite'` is meaningless on a finite progress timeline (see design doc\n * D4) — treated as 1 with the read-side warning left to the consumer.\n * @internal\n */\nexport function scrollTotalDurationMs(config: PxAnimatorConfig | undefined): number {\n const duration = (typeof config?.duration === 'number' && config.duration > 0)\n ? config.duration : PX_DEFAULT_DURATION_MS;\n const iterations = (typeof config?.iterations === 'number' && config.iterations > 0)\n ? config.iterations : 1;\n return duration * iterations;\n}\n\n/**\n * A named phase's interval in `u`-space.\n *\n * `u` is the subject's \"journey distance\": with `sTop` = subject's leading edge in\n * scrollport coordinates, `u = vpSize − sTop` — 0 exactly when the subject is about to\n * enter (leading edge at the scrollport's trailing edge), growing as the user scrolls.\n * The `min`/`max` pairs make every formula valid BOTH for a subject smaller than the\n * scrollport and one larger than it (where \"fully visible\" flips to \"covers the\n * scrollport\") — the same case split CSS specifies for its named timeline ranges.\n * @internal\n */\nexport function scrollPhaseInterval(\n phase: PxScrollPhase, subjectSize: number, scrollportSize: number\n): [number, number] {\n const s = subjectSize, vp = scrollportSize;\n switch (phase) {\n case 'cover': return [0, s + vp];\n case 'entry': return [0, Math.min(s, vp)];\n case 'contain': return [Math.min(s, vp), Math.max(s, vp)];\n case 'exit': return [Math.max(s, vp), s + vp];\n case 'entry-crossing': return [0, s];\n case 'exit-crossing': return [vp, s + vp];\n }\n}\n\nconst DEFAULT_PHASE: PxScrollPhase = 'cover';\n\n/** One range endpoint resolved to a `u`-space value. */\nfunction resolveRangePointU(\n point: PxScrollRangePoint | undefined, defaultFraction: number,\n subjectSize: number, scrollportSize: number\n): number {\n const [u0, u1] = scrollPhaseInterval(point?.phase ?? DEFAULT_PHASE, subjectSize, scrollportSize);\n const fraction = typeof point?.fraction === 'number' ? point.fraction : defaultFraction;\n return u0 + fraction * (u1 - u0);\n}\n\n/**\n * `kind: 'view'` progress ∈ [0, 1]: where the subject's journey sits within the\n * configured range.\n *\n * @param subjectStart subject's leading edge in scrollport coordinates\n * (`subjectRect.top − scrollportRect.top` on the resolved axis)\n * @param subjectSize subject size on the axis\n * @param scrollportSize scrollport size on the axis\n *\n * A degenerate/inverted range (uStart ≥ uEnd — e.g. zero-size subject with an `entry`\n * range) reports 1 once the point is passed, 0 before — never NaN.\n * @internal\n */\nexport function scrollViewProgress(\n subjectStart: number, subjectSize: number, scrollportSize: number,\n range: PxScroll['range'] | undefined\n): number {\n const u = scrollportSize - subjectStart;\n const uStart = resolveRangePointU(range?.start, 0, subjectSize, scrollportSize);\n const uEnd = resolveRangePointU(range?.end, 1, subjectSize, scrollportSize);\n if (uEnd <= uStart) return u >= uEnd ? 1 : 0;\n return clamp((u - uStart) / (uEnd - uStart), 0, 1);\n}\n\n/**\n * `kind: 'scroll'` progress ∈ [0, 1]: the scroller's offset ratio mapped through the\n * range (phases don't exist here — `fraction` is of the total scroll range).\n *\n * `maxOffset === 0` (nothing to scroll) reports 1, matching the CSS spec's rule that a\n * zero-length timeline is at 100%.\n * @internal\n */\nexport function scrollOffsetProgress(\n offset: number, maxOffset: number,\n range: PxScroll['range'] | undefined\n): number {\n const raw = maxOffset > 0 ? clamp(offset / maxOffset, 0, 1) : 1;\n const start = typeof range?.start?.fraction === 'number' ? range.start.fraction : 0;\n const end = typeof range?.end?.fraction === 'number' ? range.end.fraction : 1;\n if (end <= start) return raw >= end ? 1 : 0;\n return clamp((raw - start) / (end - start), 0, 1);\n}\n\n/**\n * Resolve a logical axis to a physical one. `block`/`inline` are writing-mode relative:\n * in horizontal writing (`horizontal-tb`, the default) block flows vertically; in\n * vertical writing modes it flows horizontally.\n * @internal\n */\nexport function scrollResolveAxis(\n axis: PxScroll['axis'] | undefined, writingMode: string | undefined\n): 'x' | 'y' {\n const a = axis ?? 'block';\n if (a === 'x' || a === 'y') return a;\n const vertical = !!writingMode && writingMode.startsWith('vertical');\n if (a === 'inline') return vertical ? 'y' : 'x';\n return vertical ? 'x' : 'y'; // block\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// THE PLAYER'S WIRE-KEY INVENTORY — every field the player schema can carry, as a canonical\n// path, sorted. The library half of the editor's ship-guard (task 5.1).\n//\n// Why a committed list at all: A RENAME IS LENGTH-NEUTRAL. One name out, one in, same count,\n// green suite — which is how three renames shipped with no compat read, the last one wiping\n// the geometry of every shape-preset path on open. Diffing the list names the key that left.\n//\n// It lives HERE, not only in the editor, because the library ships on its own: a player\n// released with a key quietly gone breaks every document using it, editor or not.\n//\n// CANONICAL PATHS — the same rule as the editor's `collectSchemaFields`, so the two lists speak\n// one dialect: each object schema is enumerated ONCE, at the first path it is reached by\n// (breadth-first); `[]` marks an array item, `{*}` a record value or open-object value, `|i` /\n// `|tag` a union member. Without the once-only rule every recursive `children` re-lists the\n// whole node schema and the inventory explodes into thousands of paths that say nothing new.\n// ============================================================================\n\nimport { describeSchema, type PxSchema } from '../schema/PxSchema';\n\n/** Every field identity `root` can carry, as canonical paths, sorted. @internal */\nexport function schemaFieldUniverse(root: PxSchema<any, any>): Array<string> {\n const fields = new Set<string>();\n const enumerated = new Set<unknown>();\n const queue: Array<{ schema: PxSchema<any, any>; path: string }> = [{ schema: root, path: '' }];\n\n while (queue.length) {\n const { schema, path } = queue.shift()!;\n const d = describeSchema(schema);\n switch (d.kind) {\n case 'shape': {\n if (enumerated.has(schema)) break; // already listed under its canonical path\n enumerated.add(schema);\n for (const key of Object.keys(d.shape)) {\n const id = path ? path + '.' + key : key;\n fields.add(id);\n queue.push({ schema: d.shape[key], path: id });\n }\n if (d.openValue) queue.push({ schema: d.openValue, path: path + '{*}' });\n break;\n }\n case 'optional': queue.push({ schema: d.inner, path }); break;\n case 'array': queue.push({ schema: d.item, path: path + '[]' }); break;\n case 'lazy': queue.push({ schema: d.resolved, path }); break;\n case 'record': queue.push({ schema: d.value, path: path + '{*}' }); break;\n case 'union':\n d.members.forEach((member, i) => queue.push({ schema: member, path: path + '|' + i }));\n break;\n case 'discriminatedUnion':\n for (const member of d.members) queue.push({ schema: member, path: path + '|' + discriminantOf(member, d.key) });\n break;\n default: break; // tuple / leaf: no named fields\n }\n }\n return [...fields].sort();\n}\n\n/** The literal a discriminated-union member carries at its discriminant, unwrapping an\n * OPTIONAL discriminant (`type?: 'time'`). `?` when it carries none. */\nfunction discriminantOf(member: PxSchema<any, any>, key: string): string {\n const d = describeSchema(member);\n if (d.kind !== 'shape') return '?';\n const keySchema = d.shape[key];\n if (keySchema === undefined) return '?';\n const kd = describeSchema(keySchema);\n return String((kd.kind === 'optional' ? kd.inner : keySchema)._default);\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// SCHEMA RELEASES (tasks 5.3 / 5.4) — the decision \"does this release move the schema\n// version, and which part?\" made by the DIFF, not by a person, plus the dated log of every\n// release kept beside the step table (`schema-releases.player.json`).\n//\n// Pure functions, so the release CLI (`scripts/schema-release.mjs`) and the guard test run the\n// SAME rule — a CLI and a test that each re-implement it are two rules that drift apart.\n//\n// The diff is over the canonical field inventory (`schemaFieldUniverse`): key paths only, so\n// descriptions and comments can never make two identical schemas look different.\n// ============================================================================\n\nimport { parseWireVersion, PxWireStepKind, type PxWireVersionStep } from './PxWireVersion';\n\n/** One entry of the release log — what shipped, when, and what it changed. @internal */\nexport interface SchemaReleaseRecord {\n readonly version: string;\n /** ISO date, `YYYY-MM-DD`. */\n readonly date: string;\n /** The first release: nothing to compare it against. */\n readonly baseline?: boolean;\n readonly added: ReadonlyArray<string>;\n readonly removed: ReadonlyArray<string>;\n readonly note?: string;\n}\n\n/** Keys that appeared and keys that left between two inventories, each sorted. @internal */\nexport function diffFieldUniverse(\n previous: ReadonlyArray<string>, current: ReadonlyArray<string>,\n): { added: Array<string>; removed: Array<string> } {\n const prev = new Set(previous);\n const cur = new Set(current);\n return {\n added: current.filter(k => !prev.has(k)).sort(),\n removed: previous.filter(k => !cur.has(k)).sort(),\n };\n}\n\n/** What a release must do about the version. `refuse` set means: do not release as-is. @internal */\nexport interface SchemaReleasePlan {\n /** Did any key appear or leave since the last release? */\n readonly changed: boolean;\n /** Which kind of step the change requires — a removal is never additive. */\n readonly requiredKind?: PxWireStepKind;\n /** The version this release must carry. */\n readonly requiredVersion?: string;\n readonly refuse?: string;\n}\n\n/**\n * THE BUMP RULE. A key change requires `b + 1` and a step that explains it; no key change\n * requires nothing. The rule never picks the number by taste — the inventory diff does.\n * @internal\n */\nexport function planSchemaRelease(p: {\n readonly added: ReadonlyArray<string>;\n readonly removed: ReadonlyArray<string>;\n /** `PX_WIRE_SCHEMA_VERSION` — what the source says now. */\n readonly declared: string;\n /** The version of the last release record. */\n readonly lastReleased: string;\n readonly steps: ReadonlyArray<PxWireVersionStep>;\n}): SchemaReleasePlan {\n const changed = p.added.length > 0 || p.removed.length > 0;\n const last = parseWireVersion(p.lastReleased);\n const declared = parseWireVersion(p.declared);\n if (!last || !declared) return { changed, refuse: 'Unparseable version: ' + p.lastReleased + ' / ' + p.declared + '.' };\n\n const requiredVersion = last.a + '.' + (last.b + 1);\n if (!changed) {\n // Same keys: nothing to bump. A bump anyway is a SEMANTIC change and still needs its step.\n if (declared.b === last.b && declared.a === last.a) return { changed };\n const step = p.steps.find(s => s.to === p.declared);\n return step ? { changed, requiredVersion: p.declared }\n : { changed, refuse: 'The version moved to ' + p.declared + ' with no key change and no step explaining it.' };\n }\n\n const requiredKind = p.removed.length ? PxWireStepKind.converted : PxWireStepKind.additive;\n const summary = p.added.length + ' key(s) added, ' + p.removed.length + ' removed';\n if (declared.a !== last.a || declared.b !== last.b + 1) {\n return {\n changed, requiredKind, requiredVersion,\n refuse: 'The player schema changed (' + summary + ') but PX_WIRE_SCHEMA_VERSION is '\n + p.declared + '. Set it to ' + requiredVersion + ' and add the PX_WIRE_STEPS entry.',\n };\n }\n const step = p.steps.find(s => s.to === p.declared);\n if (!step) {\n return { changed, requiredKind, requiredVersion, refuse: 'No PX_WIRE_STEPS entry reaches ' + p.declared + '.' };\n }\n if (requiredKind === PxWireStepKind.converted && step.kind !== PxWireStepKind.converted) {\n return {\n changed, requiredKind, requiredVersion,\n refuse: 'Keys were REMOVED (' + p.removed.join(', ') + '), which is never additive — the '\n + p.declared + ' step must be `converted`, with an up().',\n };\n }\n return { changed, requiredKind, requiredVersion };\n}\n\n/**\n * Everything wrong with the release log, as sentences — empty when it is consistent. The log\n * must start at the baseline, move strictly forward, END at the version the source declares,\n * and every release after the baseline must have the step that explains it.\n * @internal\n */\nexport function releaseLogProblems(\n releases: ReadonlyArray<SchemaReleaseRecord>, steps: ReadonlyArray<PxWireVersionStep>,\n declared: string, baseline: string,\n): Array<string> {\n const problems: Array<string> = [];\n if (!releases.length) return ['The release log is empty.'];\n if (!releases[0].baseline || releases[0].version !== baseline) {\n problems.push('The first release must be the baseline ' + baseline + '.');\n }\n for (let i = 1; i < releases.length; i++) {\n const prev = parseWireVersion(releases[i - 1].version);\n const cur = parseWireVersion(releases[i].version);\n if (!prev || !cur || cur.a !== prev.a || cur.b !== prev.b + 1) {\n problems.push('Release ' + releases[i].version + ' does not follow ' + releases[i - 1].version + ' by one `b` step.');\n }\n if (releases[i].date < releases[i - 1].date) problems.push('Release ' + releases[i].version + ' is dated before its predecessor.');\n const step = steps.find(s => s.to === releases[i].version);\n if (!step) problems.push('Release ' + releases[i].version + ' has no PX_WIRE_STEPS entry.');\n else if (releases[i].removed.length && step.kind !== PxWireStepKind.converted) {\n problems.push('Release ' + releases[i].version + ' removed keys, but its step is not `converted`.');\n }\n }\n const latest = releases[releases.length - 1].version;\n if (latest !== declared) {\n problems.push('PX_WIRE_SCHEMA_VERSION is ' + declared + ' but the last release record is ' + latest\n + ' — a bump needs its changelog entry (run scripts/schema-release.mjs --apply).');\n }\n return problems;\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 type { PxEngineCallbacks } from '@pixodesk/svg-animator-core';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\n\n/**\n * THE PAGE-WIDE REGISTRY — every live animator in the window, whoever created it.\n *\n * Like lottie-web's `getRegisteredAnimations()`: a dev tool, a test, or a \"pause everything\n * while the tab is hidden\" can reach every player without holding on to each `createAnimator`\n * result. An animator joins when it is built and leaves on `destroy()`; play / pause / cancel /\n * finish are announced to subscribers.\n *\n * ONE registry per window, whatever the bundling: the store hangs off `globalThis` under a\n * `Symbol.for` key, so the ESM build in one script and the UMD build in another share it. It\n * is also reachable by NAME — `globalThis.__pixodeskAnimators` — for tooling that does not\n * import the library (a devtools panel reading a page it did not build). Web only: React\n * Native players never touch it.\n *\n * EXPERIMENTAL (2026-09): not settled — the event names, what `getAll()` includes and the\n * global's name may change without a major version. Documented as such in\n * docs/library/web-player.md (\"Every animator on the page\").\n */\n\n/** What changed for an animator in the page-wide registry. Experimental — may change. @public */\nexport type PxAnimatorsEvent = 'add' | 'remove' | 'play' | 'pause' | 'cancel' | 'finish';\n\n/** Called for every {@link PxAnimatorsEvent} of every animator in the window. Experimental — may change. @public */\nexport type PxAnimatorsListener = (event: PxAnimatorsEvent, animator: PxAnimatorApi) => void;\n\n/** The store itself — what `globalThis.__pixodeskAnimators` holds. Experimental — may change. @public */\nexport interface PxAnimatorRegistry {\n /** Every live animator, in creation order. */\n getAll(): ReadonlyArray<PxAnimatorApi>;\n /** Announcements of add / remove / play / pause / cancel / finish; returns the unsubscribe. */\n subscribe(listener: PxAnimatorsListener): () => void;\n}\n\ninterface RegistryStore extends PxAnimatorRegistry {\n readonly animators: Set<PxAnimatorApi>;\n readonly listeners: Set<PxAnimatorsListener>;\n}\n\nconst STORE_KEY = Symbol.for('@pixodesk/svg-animator-web:animators');\n/** The by-name handle for tooling that cannot import the library. */\nconst GLOBAL_NAME = '__pixodeskAnimators';\n\n/** The one store of this window, created on first use. `globalThis` carries it under a\n * symbol (shared across bundle copies) and, for discoverability, under {@link GLOBAL_NAME}. */\nfunction store(): RegistryStore {\n // `globalThis` has no declared slot for either key — this is exactly the ad-hoc global\n // a cross-bundle registry needs, so it is typed as the record it is used as.\n const g = globalThis as unknown as Record<string | symbol, RegistryStore | undefined>;\n let s = g[STORE_KEY];\n if (!s) {\n const animators = new Set<PxAnimatorApi>();\n const listeners = new Set<PxAnimatorsListener>();\n s = {\n animators,\n listeners,\n getAll: () => Array.from(animators),\n subscribe: (listener) => {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n };\n g[STORE_KEY] = s;\n if (g[GLOBAL_NAME] === undefined) g[GLOBAL_NAME] = s;\n }\n return s;\n}\n\n/** Every live animator in this window, in creation order. Experimental — may change. @public */\nexport function getAllAnimators(): ReadonlyArray<PxAnimatorApi> {\n return store().getAll();\n}\n\n/** Hear every animator in this window start, pause, cancel, finish, appear or go — returns\n * the unsubscribe. Experimental — may change. @public */\nexport function onAnimatorsChange(listener: PxAnimatorsListener): () => void {\n return store().subscribe(listener);\n}\n\n/** A listener must never break playback: its error surfaces on its own, asynchronously. */\nfunction notify(event: PxAnimatorsEvent, animator: PxAnimatorApi): void {\n for (const listener of store().listeners) {\n try { listener(event, animator); } catch (e) { setTimeout(() => { throw e; }, 0); }\n }\n}\n\n/** @internal Adds a freshly built animator; `destroy()` on the returned API removes it again. */\nexport function registerAnimator(animator: PxAnimatorApi): void {\n const s = store();\n if (s.animators.has(animator)) return;\n s.animators.add(animator);\n notify('add', animator);\n const destroy = animator.destroy.bind(animator);\n animator.destroy = () => {\n destroy();\n if (s.animators.delete(animator)) notify('remove', animator);\n };\n}\n\n/**\n * @internal The engine callbacks with the registry listening in: each of the caller's own\n * handlers still runs first. `api()` resolves the animator lazily — the callbacks are built\n * before it exists.\n */\nexport function withRegistryEvents(callbacks: PxEngineCallbacks | undefined, api: () => PxAnimatorApi | undefined): PxEngineCallbacks {\n const fire = (event: PxAnimatorsEvent): void => { const a = api(); if (a) notify(event, a); };\n return {\n ...callbacks,\n onPlay: () => { callbacks?.onPlay?.(); fire('play'); },\n onPause: () => { callbacks?.onPause?.(); fire('pause'); },\n onCancel: () => { callbacks?.onCancel?.(); fire('cancel'); },\n onFinish: () => { callbacks?.onFinish?.(); fire('finish'); },\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 { PxDiagnosticCode, PxDiagnosticKind, PxMouseOutAction, PxTriggerStart, resolveTrigger,\n type PxDiagnostics, type PxTrigger } from '@pixodesk/svg-animator-core';\nimport { createDiagnostics } from '@pixodesk/svg-animator-core/internal';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\nimport { createVisibilityGate } from './PxVisibilityGate';\n\n\n/**\n * Wires a document's trigger block to its root element.\n *\n * TWO INDEPENDENT AXES, which is why there is no `scrollIntoView` here:\n * - `start` — what STARTS the animation: 'load' (default), 'mouseOver', 'click', or 'none'\n * (nothing but the API does).\n * - `offScreen` + `visibilityThreshold` + `visibilityDebounce` — whether it may RUN, whatever\n * started it. Owned by {@link createVisibilityGate}, wired for every document.\n *\n * `start: 'load'` behind the default gate is what `startOn: 'scrollIntoView'` used to mean: hold\n * at frame 0 until enough is on screen, play, pause when it leaves, resume when it returns. The\n * difference is that the same gate now also applies to a document started by a click or a hover.\n *\n * `mouseOut` is what happens when the pointer LEAVES ('continue' by default, or pause / reset /\n * reverse); it is read only for `start: 'mouseOver'`. A `click` document is a plain play/pause\n * toggle with nothing to configure.\n *\n * @param api The animator API instance to control.\n * @param trigger The trigger configuration. `finish` belongs to the PLAYER (what happens after a\n * natural end), not to the trigger wiring, and is not read here.\n * @returns A disposer that detaches every listener, observer and timer this call attached\n * (review §14). `createAnimator` ties it to `destroy()`. Call it yourself before re-arming an\n * element you 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 `start` = 'load', no `offScreen` = 'pause', no `mouseOut` = 'continue', no threshold\n // = 0.5, no debounce = 150ms. The threshold default must match the editor model's\n // (TSvgSvgAnimationAttr.visibilityThreshold), which OMITS the value on the wire when it equals it.\n const resolved = 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 mouse-out action put the animation into reverse, so the next start\n // can restore forward playback without clobbering a custom playback rate set through the API.\n let reversed = false;\n\n /** Ensures forward playback and starts or resumes the animation. */\n const start = (): void => {\n if (reversed) {\n reversed = false;\n api.setPlaybackRate(1);\n }\n api.play();\n };\n\n // Permission to run, for every document and whatever starts it.\n const gate = createVisibilityGate(root, resolved, {\n isPlaying: () => api.isPlaying(),\n play: start,\n pause: () => api.pause(),\n cancel: () => api.cancel(),\n });\n cleanups.push(() => gate.dispose());\n\n /** What to do when the pointer leaves — `start: 'mouseOver'` only. */\n const handleMouseOut = (): void => {\n switch (resolved.mouseOut) {\n case PxMouseOutAction.pause:\n api.pause();\n break;\n case PxMouseOutAction.reset:\n api.cancel();\n break;\n case PxMouseOutAction.reverse:\n // Play the animation backwards from its current position.\n reversed = true;\n api.setPlaybackRate(-1);\n api.play();\n break;\n case PxMouseOutAction.continue:\n default:\n // Do nothing\n break;\n }\n };\n\n // ---- What starts it ----\n switch (resolved.start) {\n case PxTriggerStart.load: {\n // The only start that the gate may hold: nobody interacted, so there is nothing to\n // honour immediately. `requestStart(false)` plays now if enough is already on screen\n // (after the debounce), and otherwise waits for it to be.\n const startHandler = () => gate.requestStart(false);\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 PxTriggerStart.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 // `mouseOut: 'reverse'` the out action PLAYS (`setPlaybackRate(-1); play()`), so an\n // untriggered leave would start the animation running backwards.\n let enteredOnce = false;\n const mouseOverHandler = () => { enteredOnce = true; gate.requestStart(true); };\n const mouseOutHandler = () => { if (enteredOnce) handleMouseOut(); };\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 PxTriggerStart.click: {\n // A plain toggle. The reader is pointing at it, so a start never waits for the gate.\n const clickHandler = () => {\n if (api.isPlaying()) api.pause();\n else gate.requestStart(true);\n };\n root.addEventListener('click', clickHandler);\n cleanups.push(() => root.removeEventListener('click', clickHandler));\n break;\n }\n\n case PxTriggerStart.none:\n // No auto-start; external code must call play(). The gate still applies afterwards,\n // so an API-started animation pauses when it scrolls out of view.\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\n/**\n * An id as a CSS id-selector, ESCAPED.\n *\n * `'#' + id` is not valid CSS whenever the id starts with a digit — and editor ids often do\n * (`2kjrlj4l`), because SVG and HTML both allow it where CSS does not. `querySelector` then\n * THROWS a SyntaxError rather than returning null, so the animation never starts and the\n * document looks broken for a reason nothing in it explains. Punctuation has the same problem.\n */\nexport function getSelector(id: string) {\n // return `[data-px-id=\"${id}\"]`; FIXME\n return '#' + escapeCssId(id);\n}\n\n/** `CSS.escape` where the engine has it; otherwise the two rules that actually bite. */\nfunction escapeCssId(id: string): string {\n const css = (globalThis as { CSS?: { escape?: (value: string) => string } }).CSS;\n if (typeof css?.escape === 'function') return css.escape(id);\n return id\n // A leading digit is spelled as its hex code point plus a separating space.\n .replace(/^([0-9])/, (_all, digit: string) => '\\\\3' + digit + ' ')\n // Anything outside the CSS identifier set is backslash-escaped.\n .replace(/([^\\w\\-\\\\ ])/g, '\\\\$1');\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 (`start` 'load',\n // `offScreen` 'pause' — so an unseen animation does not run).\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_DEFAULT_ITERATIONS } from '@pixodesk/svg-animator-core/internal';\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 ?? PX_DEFAULT_ITERATIONS));\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 ?? PX_DEFAULT_ITERATIONS);\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 ?? PX_DEFAULT_ITERATIONS);\n },\n\n \"setCurrentProgress\": (progress: number) => {\n api.setCurrentTime(progressToTimeMs(progress, config.duration ?? 0, iterations ?? PX_DEFAULT_ITERATIONS));\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 (`start` 'load',\n // `offScreen` 'pause' — so an unseen animation does not run).\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\n// The DOM half of `animator.timelineSource: 'scroll'` (`timeline.engine: 'js'` — and the fallback of `auto`\n// and the reference implementation): measures the scroller/subject and turns scroll\n// position into animation progress via the pure math in core `PxScrollMath`. The\n// consumer decides what a progress value does (frames: seek `setCurrentTime`; waapi:\n// same, via the engine-agnostic API).\n//\n// Event model: `scroll` (+`resize`) listeners, passive, coalesced into ONE\n// requestAnimationFrame tick — at most one measurement + one seek per frame. A\n// `ResizeObserver` (where available) catches subject/scroller size changes that happen\n// without a scroll. TODO (optimization, deliberate v1 omission): an IntersectionObserver\n// gate to park the listeners entirely while the subject is far outside its range.\n\nimport { PxDiagnosticCode, PxDiagnosticKind, type PxAnimatorConfig, type PxDiagnostics, type PxScroll } from '@pixodesk/svg-animator-core';\nimport { createDiagnostics, isScrollTimeline, scrollOffsetProgress, scrollResolveAxis, scrollViewProgress } from '@pixodesk/svg-animator-core/internal';\n\n\n// ── Native timeline support (`timeline.engine: 'native'`, tried first by `auto`) ────────────────────────────────────────\n// `ScrollTimeline`/`ViewTimeline` aren't in TS's dom lib yet — minimal local declarations,\n// resolved from globalThis so absence is an ordinary feature-detect, never a crash.\n\ninterface NativeTimelineCtor { new(options: Record<string, unknown>): AnimationTimeline }\n\ninterface PxNativeScrollTimeline {\n timeline: AnimationTimeline;\n /** WAAPI `Animation.rangeStart/rangeEnd` values (TimelineRangeOffset-shaped). */\n rangeStart?: Record<string, unknown>;\n rangeEnd?: Record<string, unknown>;\n}\n\nfunction nativeRangeOffset(point: { phase?: string; fraction?: number } | undefined, defaultFraction: number, view: boolean): Record<string, unknown> | undefined {\n const fraction = typeof point?.fraction === 'number' ? point.fraction : defaultFraction;\n const pct = (globalThis as { CSS?: { percent?: (n: number) => unknown } }).CSS?.percent?.(fraction * 100);\n if (pct === undefined) return undefined;\n // Named phases exist only on VIEW timelines; a scroll timeline takes a bare offset.\n return view ? { rangeName: point?.phase ?? 'cover', offset: pct } : { offset: pct };\n}\n\n/**\n * Build the browser-native timeline (`mode: 'native'` / `auto`), or `null` when the platform\n * doesn't support scroll-driven WAAPI timelines — the caller then falls back to the\n * custom driver (D8: the option is a preference, never a requirement).\n */\nexport function createNativeScrollTimeline(\n subject: Element,\n config: PxAnimatorConfig | undefined,\n diag?: PxDiagnostics,\n): PxNativeScrollTimeline | null {\n // Exported, so the channel is optional and falls back to the console (review §5).\n const report = diag ?? createDiagnostics(undefined, '[PxAnimator]');\n if (!config || !isScrollTimeline(config)) return null;\n const scroll: PxScroll = config.scroll || {};\n const kind = scroll.kind ?? 'view';\n\n // Smoothing is a per-frame easing of progress, which a browser-native timeline (a direct\n // scroll→time mapping) cannot express. Honor the authored LOOK over the perf hint — D8\n // already makes `native` a preference rather than a requirement.\n if (scroll.smoothing) {\n report.warn(PxDiagnosticKind.platform, PxDiagnosticCode.scrollSmoothingNeedsOwnDriver);\n return null;\n }\n\n const g = globalThis as unknown as { ScrollTimeline?: NativeTimelineCtor; ViewTimeline?: NativeTimelineCtor };\n const view = kind === 'view';\n const Ctor = view ? g.ViewTimeline : g.ScrollTimeline;\n if (typeof Ctor !== 'function') return null;\n\n const axis = scroll.axis ?? 'block';\n let timeline: AnimationTimeline;\n try {\n if (view) {\n // `scroll.subject` is the same indirection the platform's own ViewTimeline takes.\n timeline = new Ctor({ subject: resolveScrollSubject(subject, scroll.subject, report), axis });\n } else {\n const source = scroll.source === 'root'\n ? documentScroller()\n : (findNearestScroller(subject, 'y') || findNearestScroller(subject, 'x') || documentScroller());\n timeline = new Ctor({ source, axis });\n }\n } catch (e) {\n report.warn(PxDiagnosticKind.platform, PxDiagnosticCode.scrollNativeUnavailable, e);\n return null;\n }\n\n return {\n timeline,\n rangeStart: nativeRangeOffset(scroll.range?.start, 0, view),\n rangeEnd: nativeRangeOffset(scroll.range?.end, 1, view),\n };\n}\n\n\nexport interface PxScrollDriver {\n /** Detach every listener/observer. Idempotent. */\n destroy(): void;\n /** Re-measure and emit now (also called once on attach). */\n refresh(): void;\n}\n\n/**\n * The nearest ancestor that can scroll on the given physical axis — the CSS \"scroll\n * container\" definition: computed overflow other than `visible`/`clip` counts\n * (`hidden` scrolls programmatically). Returns null when there is none, so callers fall\n * back to the document scroller.\n *\n * `<html>` and `<body>` are NEVER returned. CSS propagates the root element's overflow to\n * the VIEWPORT (and, when the root's is `visible`, the body's instead) — so with the very\n * common `body { overflow-y: auto }` the body computes as `auto` yet is not a scroll\n * container at all: the viewport scrolls, `body.scrollTop` stays 0, and `body`'s rect is\n * the full content box rather than the 100vh scrollport. Returning it produced a frozen,\n * badly-scaled progress (a doc that never advanced, or sat at a fixed mid-value). The\n * document scroller — which the caller measures with viewport semantics — is the right\n * answer for both elements.\n */\nexport function findNearestScroller(el: Element, axis: 'x' | 'y'): Element | null {\n const body = document.body;\n const root = document.documentElement;\n for (let p = el.parentElement; p; p = p.parentElement) {\n if (p === body || p === root) return null; // the viewport scrolls for these — see above\n const style = getComputedStyle(p);\n const overflow = axis === 'y' ? style.overflowY : style.overflowX;\n if (overflow === 'auto' || overflow === 'scroll' || overflow === 'hidden' || overflow === 'overlay') {\n return p;\n }\n }\n return null;\n}\n\nfunction documentScroller(): Element {\n return document.scrollingElement || document.documentElement;\n}\n\n/** `scroll.subject` keywords (anything else is treated as a CSS selector). */\nconst SUBJECT_PARENT = 'parent';\nconst SUBJECT_SCROLLER = 'scroller';\n\n/**\n * WHICH element's journey `kind: 'view'` measures — `scroll.subject`. Unset = the animation's\n * own `<svg>` (the original behavior).\n *\n * `'parent'` is the pinned-section answer and needs no knowledge of the host's markup: a\n * `position: sticky` element STOPS MOVING once stuck, so measuring the graphic itself freezes\n * progress for exactly the stretch that should be animating. Walking out to the outermost\n * sticky/fixed ancestor's container gives the element that really does scroll past — and its\n * `contain` phase is precisely the pinned stretch.\n *\n * Never throws and never returns null: an unresolvable selector warns and falls back to the\n * `<svg>`, because a silent freeze is indistinguishable from a broken animation.\n */\nexport function resolveScrollSubject(svgRoot: Element, subject: string | undefined, diag?: PxDiagnostics): Element {\n const report = diag ?? createDiagnostics(undefined, '[PxAnimator]');\n const spec = subject?.trim();\n if (!spec) return svgRoot;\n\n if (spec === SUBJECT_PARENT) {\n // Outermost sticky/fixed ancestor wins — nested sticky wrappers are common.\n let outermostPinned: Element | null = null;\n for (let p = svgRoot.parentElement; p && p !== document.body; p = p.parentElement) {\n const position = getComputedStyle(p).position;\n if (position === 'sticky' || position === 'fixed') outermostPinned = p;\n }\n return outermostPinned?.parentElement ?? svgRoot.parentElement ?? svgRoot;\n }\n\n if (spec === SUBJECT_SCROLLER) {\n return findNearestScroller(svgRoot, 'y') || findNearestScroller(svgRoot, 'x') || documentScroller();\n }\n\n let found: Element | null = null;\n try {\n found = document.querySelector(spec);\n } catch {\n // `document`: the bad selector is a VALUE IN THE FILE, so the fix is to the document...\n report.warn(PxDiagnosticKind.document, PxDiagnosticCode.scrollSubjectInvalid, spec);\n return svgRoot;\n }\n if (!found) {\n // ...whereas a valid selector that matches nothing is the page's business.\n report.warn(PxDiagnosticKind.host, PxDiagnosticCode.scrollSubjectNoMatch, spec);\n return svgRoot;\n }\n return found;\n}\n\n/**\n * Attach a scroll driver for `subject` (the animation's root `<svg>`).\n * Emits `onProgress(0..1)` — clamped, range-mapped — on attach and on every\n * scroll/resize tick. Returns `null` when the document isn't scroll-driven.\n */\nexport function createScrollDriver(\n subject: Element,\n config: PxAnimatorConfig | undefined,\n onProgress: (progress: number) => void,\n diag?: PxDiagnostics,\n): PxScrollDriver | null {\n if (!config || !isScrollTimeline(config)) return null;\n\n const report = diag ?? createDiagnostics(undefined, '[PxAnimator]');\n const scroll: PxScroll = config.scroll || {};\n const kind = scroll.kind ?? 'view';\n\n // WHAT is measured (`view` only): the SVG itself by default, or whatever `scroll.subject`\n // names — the pinned-section case measures the wrapper that actually scrolls past.\n const measured = resolveScrollSubject(subject, scroll.subject, report);\n\n // Scroller resolution (once, at attach): `view` always tracks the nearest scrollport;\n // `scroll` honors `source`. Axis resolves against the SCROLLER's writing mode.\n const nearest = findNearestScroller(subject, 'y') || findNearestScroller(subject, 'x');\n const scroller: Element = (kind === 'scroll' && scroll.source === 'root')\n ? documentScroller()\n : (nearest || documentScroller());\n const isRootScroller = scroller === documentScroller();\n const axis = scrollResolveAxis(scroll.axis, getComputedStyle(scroller).writingMode);\n\n const compute = (): number => {\n if (kind === 'scroll') {\n const offset = axis === 'y' ? scroller.scrollTop : scroller.scrollLeft;\n const maxOffset = axis === 'y'\n ? scroller.scrollHeight - scroller.clientHeight\n : scroller.scrollWidth - scroller.clientWidth;\n return scrollOffsetProgress(offset, maxOffset, scroll.range);\n }\n\n // view: the MEASURED element's journey across the scrollport, in scrollport coordinates.\n const subjectRect = measured.getBoundingClientRect();\n let portStart: number, portSize: number;\n if (isRootScroller) {\n portStart = 0;\n // documentElement.client* excludes scrollbars (window.inner* does not).\n portSize = axis === 'y' ? document.documentElement.clientHeight : document.documentElement.clientWidth;\n } else {\n const portRect = scroller.getBoundingClientRect();\n portStart = axis === 'y' ? portRect.top : portRect.left;\n portSize = axis === 'y' ? scroller.clientHeight : scroller.clientWidth;\n }\n const subjectStart = (axis === 'y' ? subjectRect.top : subjectRect.left) - portStart;\n const subjectSize = axis === 'y' ? subjectRect.height : subjectRect.width;\n return scrollViewProgress(subjectStart, subjectSize, portSize, scroll.range);\n };\n\n // ── smoothing (`scroll.smoothing`, GSAP's `scrub: <seconds>`) ───────────────────────\n // Without it, emitted progress IS the measurement. With it, the emitted value chases the\n // measurement with an exponential ease, so momentum scrolling and trackpad jitter read as\n // a glide instead of a snap. Frame-rate independent (`1 - exp(-dt/tau)`), and it SETTLES\n // exactly on the target rather than asymptotically near it.\n const smoothingSec = Math.max(0, scroll.smoothing ?? 0) / 1000; // schema is in ms\n // An exponential ease only approaches its target, so snap the last sliver and stop the rAF\n // loop rather than idling for another second. 0.1% of progress = 1ms of a 1s animation —\n // far below anything visible, and it keeps the tail off the battery.\n const SETTLE_EPSILON = 0.001;\n let destroyed = false;\n let smoothed: number | null = null; // null until the first emit (which is instant)\n let smoothRaf: number | null = null;\n let lastFrameMs = 0;\n\n const emit = (target: number) => {\n if (!smoothingSec) { onProgress(target); return; }\n if (smoothed === null) { smoothed = target; onProgress(target); return; } // no lag on load\n if (smoothRaf !== null) return; // already chasing\n lastFrameMs = 0;\n const step = (nowMs: number) => {\n smoothRaf = null;\n if (destroyed) return;\n const dtSec = lastFrameMs ? Math.min(0.1, (nowMs - lastFrameMs) / 1000) : 1 / 60;\n lastFrameMs = nowMs;\n const goal = compute(); // re-measure: the user may still be scrolling\n const k = 1 - Math.exp(-dtSec / smoothingSec);\n smoothed = smoothed! + (goal - smoothed!) * k;\n if (Math.abs(goal - smoothed!) < SETTLE_EPSILON) smoothed = goal; // land exactly\n onProgress(smoothed!);\n if (smoothed !== goal) smoothRaf = requestAnimationFrame(step);\n };\n smoothRaf = requestAnimationFrame(step);\n };\n\n // rAF coalescing: any number of scroll/resize events in a frame → one measurement.\n let rafId: number | null = null;\n const tick = () => {\n rafId = null;\n if (destroyed) return;\n emit(compute());\n };\n const schedule = () => {\n if (destroyed || rafId !== null) return;\n rafId = requestAnimationFrame(tick);\n };\n\n // Root scrolling fires on window; container scrolling on the container.\n const scrollTarget: EventTarget = isRootScroller ? window : scroller;\n scrollTarget.addEventListener('scroll', schedule, { passive: true });\n window.addEventListener('resize', schedule, { passive: true });\n\n let resizeObserver: ResizeObserver | undefined;\n if (typeof ResizeObserver !== 'undefined') {\n resizeObserver = new ResizeObserver(schedule);\n resizeObserver.observe(measured);\n if (measured !== subject) resizeObserver.observe(subject);\n if (!isRootScroller) resizeObserver.observe(scroller);\n }\n\n const driver: PxScrollDriver = {\n destroy: () => {\n if (destroyed) return;\n destroyed = true;\n scrollTarget.removeEventListener('scroll', schedule);\n window.removeEventListener('resize', schedule);\n resizeObserver?.disconnect();\n if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; }\n if (smoothRaf !== null) { cancelAnimationFrame(smoothRaf); smoothRaf = null; }\n },\n // `refresh` is a deliberate JUMP (attach, host relayout) — never eased.\n refresh: () => { if (!destroyed) { smoothed = compute(); onProgress(smoothed); } },\n };\n\n // Initial pose: reflect the CURRENT scroll position immediately (a page loaded\n // mid-scroll must not flash frame 0).\n driver.refresh();\n\n return driver;\n}\n\n\n/**\n * Pin the canvas on screen for the scrubbed stretch — GSAP's `pin: true`, expressed as\n * `position: sticky`. Sticky keeps the element's space in normal flow, so (unlike GSAP's\n * `position: fixed`) no spacer has to be injected into the host's layout to stop the page\n * collapsing. Optionally wraps the canvas in a tall block so the PLAYER creates the scroll\n * travel and the host page needs no CSS at all.\n *\n * Returns a cleanup that restores the DOM exactly. No-op (and no cleanup cost) when `pin` is off.\n */\n/** Height of the scrollport the canvas is held inside — the nearest y-scroller's, else the\n * document's. `clientHeight` (not `innerHeight`) so scrollbars are excluded, matching the\n * progress math above. Pinning is a `top` offset, so it is always the VERTICAL size. */\nfunction pinScrollportHeight(svgRoot: Element): number {\n const scroller = findNearestScroller(svgRoot, 'y');\n return scroller ? scroller.clientHeight : document.documentElement.clientHeight;\n}\n\nexport function applyScrollPin(svgRoot: Element, scroll: PxScroll | undefined): () => void {\n const styled = svgRoot as Element & Partial<ElementCSSInlineStyle>;\n if (!scroll?.pin || !styled.style) return () => { /* nothing pinned */ };\n\n const style = styled.style;\n const prevPosition = style.position;\n const prevTop = style.top;\n style.position = 'sticky';\n\n // WHERE it is held: `top` is the alignment position plus the `pinOffset` fine-tune.\n // `center`/`bottom` depend on the canvas's own height AND the scrollport height, neither\n // of which CSS can express for a sticky offset (a `top` percentage resolves against the\n // CONTAINING BLOCK, not the element), so they are measured and re-applied on resize.\n const alignFactor = scroll.pinAlign === 'center' ? 0.5 : scroll.pinAlign === 'bottom' ? 1 : 0;\n const applyTop = (): void => {\n const extra = scroll.pinOffset ?? 0;\n if (!alignFactor) {\n style.top = extra + 'px';\n return;\n }\n const portSize = pinScrollportHeight(svgRoot);\n const ownSize = svgRoot.getBoundingClientRect().height;\n style.top = Math.round((portSize - ownSize) * alignFactor + extra) + 'px';\n };\n applyTop();\n\n // Keep a measured offset correct as the viewport or the canvas resizes.\n let resizeObserver: ResizeObserver | null = null;\n const onWindowResize = alignFactor ? applyTop : null;\n if (alignFactor) {\n if (onWindowResize) window.addEventListener('resize', onWindowResize);\n if (typeof ResizeObserver !== 'undefined') {\n resizeObserver = new ResizeObserver(applyTop);\n resizeObserver.observe(svgRoot);\n }\n }\n\n // `pinDistance` (in viewport heights) — the travel the pin should last for.\n let wrapper: HTMLElement | null = null;\n const parent = svgRoot.parentElement;\n if (scroll.pinDistance && scroll.pinDistance > 0 && parent) {\n wrapper = document.createElement('div');\n wrapper.setAttribute('data-px-pin', '');\n wrapper.style.height = (scroll.pinDistance * 100) + 'vh';\n parent.insertBefore(wrapper, svgRoot);\n wrapper.appendChild(svgRoot);\n }\n\n return () => {\n if (onWindowResize) window.removeEventListener('resize', onWindowResize);\n resizeObserver?.disconnect();\n style.position = prevPosition;\n style.top = prevTop;\n if (wrapper?.parentElement) {\n wrapper.parentElement.insertBefore(svgRoot, wrapper);\n wrapper.remove();\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 { 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 { registerAnimator, withRegistryEvents } from '../registry/PxAnimatorRegistry';\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 registered(options, callbacks => buildOrReport(options, () => bindWithEngineChoice(doc, undefined, callbacks, null)));\n}\n\n/**\n * The page-wide registry (`getAllAnimators()`) for the pre-rendered builds: the returned\n * API is enrolled — a build that failed (the inert API, `isReady()` false) is not — and hears\n * play / pause / cancel / finish through the callbacks the engine is given.\n */\nfunction registered(options: PxPrerenderedAnimatorOptions, build: (callbacks: PxEngineCallbacks) => PxAnimatorApi): PxAnimatorApi {\n let apiRef: PxAnimatorApi | undefined;\n const api = build(withRegistryEvents(toEngineCallbacks(options), () => apiRef));\n if (api.isReady()) { apiRef = api; registerAnimator(api); }\n return api;\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 registered(options, callbacks => buildOrReport(options, () => {\n const animatorConfig = getAnimatorConfig(doc) || {};\n return finaliseAnimator(animatorConfig, callbacks, 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\nimport { toDomProps, type PxDefinitions, type PxDiagnostics, type PxNode } from '@pixodesk/svg-animator-core';\nimport { renderPxTree, type PxElementFactory } from '@pixodesk/svg-animator-core/internal';\n\n// Re-export from the historical home so the package surface is unchanged.\nexport { toDomProps };\n\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\n/**\n * The web player's element factory — the ONLY web-specific part of rendering. Every decision\n * about the document (tags, attribute names and values, sanitization, styles, text) is made by\n * core's `renderPxTree`, which the React and Vue components call with their own factories. So\n * the three cannot disagree about a document: they differ only in how an element is created.\n */\nconst createDomElement: PxElementFactory<Element> = ({ tag, attrs, style, children, text }) => {\n const element = document.createElementNS(SVG_NS, tag);\n\n for (const name of Object.keys(attrs)) element.setAttribute(name, attrs[name]);\n\n if (style) {\n const target = element.style as unknown as Record<string, string>;\n for (const prop of Object.keys(style)) target[prop] = style[prop];\n }\n\n // Children, or the node's own text — never both (assigning `textContent` would REPLACE\n // children already appended). `renderPxTree` has already decided which.\n for (const child of children) element.appendChild(child);\n if (text !== undefined) element.textContent = text;\n\n return element;\n};\n\n\n/**\n * Renders a PxNode tree to DOM elements.\n * @public @advanced\n */\nexport function renderNode(node: PxNode, _defs?: PxDefinitions, diag?: PxDiagnostics): Element | null {\n return renderPxTree(node, createDomElement, diag);\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 { applyAnimatorConfig, foldTimelineOverride, generateNewIds, getAnimatorConfig, isPxDocument, materializeAllInTree, PxDiagnosticCode, PxDiagnosticKind, resolveTimelineEngine, type PxTimelineEngine, validateNodeEffects, type PxAnimatedSvgDocument, type PxEngineCallbacks, type PxAnimatorConfigPatch, type PxAnimatorCallbacks, type PxPlaybackOverride, type PxPlatformAdapter } from '@pixodesk/svg-animator-core';\nimport { reportDocumentDiagnostics, createDiagnostics, PX_ANIM_ATTR_NAME, PX_ANIM_SRC_ATTR_NAME } from '@pixodesk/svg-animator-core/internal';\nimport { asThrownError, toEngineCallbacks } from '../shared/PxAnimatorCallbacks';\nimport { registerAnimator, withRegistryEvents } from '../registry/PxAnimatorRegistry';\nimport { bindWithEngineChoice } from '../engines/PxAnimatorBind';\nimport { renderNode } from '../dom/PxAnimatorDOM';\nimport { setupAnimationTriggers } from '../triggers/PxAnimatorTriggers';\nimport type { PxAnimatorApi } from '../shared/PxAnimatorWebTypes';\n\n// Re-export so the package surface keeps `generateNewIds` at its historical home.\nexport { generateNewIds };\n\n\n/**\n * Creates an animator instance from a normalized player config.\n * This is the internal implementation that both engines use.\n *\n * The engine choice plus the `resetOnFinish` / `debugGlobalName` handling live in\n * `PxAnimatorBind` so the pre-rendered builds share them verbatim — one code path, no\n * parallel pipeline. See dev-docs/plans/prerendered-player-builds.md.\n */\nfunction createAnimatorFromConfig(\n doc: PxAnimatedSvgDocument,\n adapter?: PxPlatformAdapter,\n callbacks?: PxEngineCallbacks,\n rootElement?: Element | null\n): PxAnimatorApi {\n return bindWithEngineChoice(doc, adapter, callbacks, rootElement);\n}\n\n\n/**\n * Creates an animator instance from an AnimatedSvgDocument.\n *\n * This function serves as the main entry point for the animation library. The engine comes from\n * `timeline.engine`: `auto` tries the browser's Web Animations API and falls back to the frame\n * loop, `native` and `js` force one — see `resolveTimelineEngine`.\n *\n * @param doc The animated SVG document.\n * @param callbacks Optional object with callback functions for animation lifecycle events (play, pause, finish, etc.).\n * @param containerElement Optional selector or element to render the SVG into.\n * @param providedRoot The root `<svg>` a caller rendered itself — used when there is no container.\n * @returns An PxAnimatorApi instance to programmatically control the animation.\n */\nfunction createAnimatorImpl(\n doc: PxAnimatedSvgDocument,\n adapter?: PxPlatformAdapter,\n callbacks?: PxEngineCallbacks,\n containerElement?: string | Element,\n patch?: PxAnimatorConfigPatch,\n resetTimeline?: boolean,\n providedRoot?: Element\n): PxAnimatorApi {\n\n // Validate every `node.effects` bucket against `PxEffectsSchema` and warn\n // about any shape drift. Doesn't mutate or block — the materializer tries\n // its best even when shapes are off, but a warning helps spot wire-format\n // regressions early.\n // Everything this player has to say goes through one channel (API review §5): the caller's\n // `onWarn` / `onError` if given, the console otherwise — unless `muteWarn` / `muteError` switch it off.\n const diag = createDiagnostics(callbacks, '[PxAnimator]');\n\n const effectsWarnings = validateNodeEffects(doc as any);\n for (const w of effectsWarnings) diag.warn(PxDiagnosticKind.document, PxDiagnosticCode.effectsShape, w);\n\n // …and the WHOLE-document check beside it. This is the boundary diagnostic: if a consumer's\n // build mangled property names, the keys reaching us are unrecognizable and this says so,\n // instead of the animation silently rendering nothing (dev-docs/plans/minification-boundary.md §3).\n reportDocumentDiagnostics(doc, '[PxAnimator] createAnimator');\n\n // The per-instance override, applied BEFORE anything reads the config. Everything below\n // depends on the final values: `timeline.engine` picks the engine, `duration` drives loop\n // expansion and motion-path sampling in `materializeAllInTree`, and `generateNewIds`\n // rewrites binding targets — a late patch would be read by none of them.\n if (patch !== undefined || resetTimeline) {\n const patched = applyAnimatorConfig(doc, patch ?? {}, { resetTimeline: !!resetTimeline });\n for (const w of patched.warnings) diag.warn(PxDiagnosticKind.usage, PxDiagnosticCode.timelineOverrideIgnored, w);\n doc = patched.doc;\n }\n\n // Decide the engine upfront so the materialization pipeline knows which\n // stages to run. `auto` and `native` resolve to the native (WAAPI) materialization; if the\n // native engine later declines the document at construction, the frame loop (`js`) is used\n // as fallback — slight over-materialization for that doc, but no correctness issue.\n const animatorConfig = getAnimatorConfig(doc) || {};\n const engine: PxTimelineEngine = resolveTimelineEngine(animatorConfig.engine);\n\n // Run the full document materialization pipeline:\n // effects → loops → motion-path (native engine only) → animated-use (native engine only)\n // → rest poses (so the frame shown BEFORE anything plays is the first frame)\n // The exact same function is exported for the Editor — no parallel pipeline.\n doc = materializeAllInTree(doc, engine);\n\n let rootElement: Element | null = null;\n\n // Render whenever there's a container. A document with no children is still a\n // document — it carries the viewBox/size that make it a viewport — so it renders as an\n // EMPTY `<svg>`. Gating on `doc.children` left `getRootElement()` answering `null`,\n // which a consumer cannot tell apart from \"the render failed\".\n if (containerElement) {\n\n doc = generateNewIds(doc); // Regenerate IDs so repeated calls to createAnimator(...) produce different ids in elements\n\n const containerEl = typeof containerElement === 'string' ?\n document.querySelector(containerElement) : containerElement;\n\n if (containerEl) {\n rootElement = renderNode(doc, undefined, diag);\n if (rootElement) {\n containerEl.replaceChildren(rootElement);\n }\n }\n }\n\n // A caller that rendered the document itself hands over the root it rendered — see\n // `PxInternalAnimatorOptions.rootElement`. A container render above always wins.\n if (!rootElement && providedRoot) rootElement = providedRoot;\n\n const api = createAnimatorFromConfig(doc, adapter, callbacks, rootElement);\n\n // The player put the SVG into the container, so destroy() takes it out again —\n // otherwise a frozen last frame lingers after the animator is gone. Scoped to\n // the container path on purpose: a root the caller rendered (the React / Vue\n // adapters, Mode B binding to an existing SVG) is theirs to remove.\n if (containerElement && rootElement) {\n const rendered = rootElement;\n const destroyNative = api.destroy.bind(api);\n api.destroy = () => {\n destroyNative();\n rendered.remove();\n };\n }\n\n return api;\n}\n\n// Re-exported so this module's public surface is unchanged; declared in\n// `PxAnimatorKeys` so entries can use it without importing this module. See there.\nexport { PX_ANIMATOR_DOC_KEY } from '../shared/PxAnimatorKeys';\n\n/**\n * Everything `createAnimator` takes. The playback override (`timeline`, `resetTimeline` and the\n * four shortcuts) and the callbacks are core's shared shapes — the SAME names, inline, as the\n * React, Vue and React Native components take (review §9) — so only what is web-specific is\n * declared here.\n * @public\n */\nexport interface PxAnimatorOptions extends PxPlaybackOverride, PxAnimatorCallbacks {\n /** URL to fetch the animation document from. Provide either this or `doc`, not both. */\n src?: string;\n /** The animation document, inline (see docs/format/README.md). Provide either this or `src`, not both. */\n doc?: PxAnimatedSvgDocument;\n /** CSS selector or element to render the SVG into. */\n container?: string | Element;\n}\n\n/**\n * What the framework COMPONENTS build the player with: the public options plus the `adapter`\n * that routes the frame loop's attribute writes to the elements they rendered themselves.\n *\n * NOT part of the public API (review §25.14). The React and Vue packages are its only callers;\n * `createAnimator`'s signature says `PxAnimatorOptions` on purpose — a page has a DOM to write\n * to, so for anyone else the option would only be a way to hold the player wrong. Exported as a\n * type so those packages can name it; never documented as an option.\n * @internal\n */\nexport interface PxInternalAnimatorOptions extends PxAnimatorOptions {\n /** A custom render target for the frame-loop engine (`PxPlatformAdapter`). */\n adapter?: PxPlatformAdapter;\n\n /**\n * The root `<svg>` the CALLER rendered, when it renders the document itself (the React and\n * Vue components). Triggers attach to it — pointer listeners, the visibility gate — and\n * without one they are not wired at all, so even a `load` trigger never fires.\n *\n * Otherwise the engines look the root up as `#` + the document's root id, which finds\n * nothing for the common document whose root `<svg>` has no id. The component already holds\n * the element, so it hands it over rather than have it guessed.\n */\n rootElement?: Element;\n}\n\n/** The one place `createAnimator` reads past its public signature — see `PxInternalAnimatorOptions`. */\nfunction isInternalOptions(options: PxAnimatorOptions): options is PxInternalAnimatorOptions {\n // EITHER internal key. Testing `adapter` alone meant a caller passing only `rootElement` —\n // what the React and Vue components do — had it silently ignored.\n return 'adapter' in options || 'rootElement' in options;\n}\n\n/**\n * The `createAnimator` spelling of the shared timeline fold (core owns the logic so the three\n * component packages and the plain-JS entry cannot drift).\n */\nexport function resolveTimelineOption(options: PxAnimatorOptions): PxAnimatorConfigPatch | undefined {\n const { timeline, duration, delay, iterations, start } = options;\n return foldTimelineOverride(timeline, { duration, delay, iterations, start });\n}\n\n/**\n * Creates an animator instance to control SVG animations.\n *\n * @param options.src URL to fetch the animation document from.\n * @param options.doc The animation document, inline.\n * @param options.container CSS selector or element to render the SVG into.\n * @returns A PxAnimatorApi instance to programmatically control the animation.\n * @public\n */\nexport function createAnimator(options: PxAnimatorOptions): PxAnimatorApi {\n\n const { src, doc, container, resetTimeline } = options;\n const adapter = isInternalOptions(options) ? options.adapter : undefined;\n const providedRoot = isInternalOptions(options) ? options.rootElement : undefined;\n const patch = resolveTimelineOption(options);\n // The registry hears play / pause / … through the engine callbacks, and names the PROXY\n // below — the object the caller holds — never the engine API behind it.\n let proxy: PxAnimatorApi | undefined;\n const callbacks = withRegistryEvents(toEngineCallbacks(options), () => proxy);\n\n // A wrong CALL throws — a bug at the call site, found the moment the line runs. A document\n // or environment that cannot play is reported through `onError` instead (the rule in core's\n // `PxDiagnostics`, review §25.1): the returned API stays inert and `isReady()` false.\n if (doc !== undefined && src !== undefined) {\n throw new Error('createAnimator: provide either `src` or `doc`, not both');\n }\n if (doc === undefined && src === undefined) {\n throw new Error('createAnimator: either `src` or `doc` is required');\n }\n\n let animator: PxAnimatorApi | null = null;\n\n // Control calls made before the player exists are queued and replayed (in order) once it\n // is ready, so e.g. `createAnimator({src}).play()` works as expected. Getters are not\n // queued — they return their \"not ready yet\" value until then. With `doc` the player is\n // ready before this returns; the same proxy then simply forwards.\n let pending: Array<(api: PxAnimatorApi) => void> | null = [];\n let destroyed = false;\n\n const enqueue = (call: (api: PxAnimatorApi) => void) => {\n if (animator) {\n call(animator);\n } else if (pending) {\n pending.push(call);\n }\n };\n\n const diag = createDiagnostics(callbacks, '[PxAnimator]');\n\n const ready = (api: PxAnimatorApi): void => {\n animator = api;\n const queued = pending;\n pending = null;\n queued?.forEach(call => call(api));\n };\n // The instance will not play: report once, drop the queue, stay inert.\n const failed = (kind: PxDiagnosticKind, code: PxDiagnosticCode, ...data: Array<unknown>): void => {\n pending = null;\n diag.error(kind, code, ...data);\n };\n // Building the player threw: a broken document past validation, or a player bug — never a\n // throw at the caller, which would land inside a fetch callback where no one can catch it.\n const build = (document: PxAnimatedSvgDocument): void => {\n try {\n ready(createAnimatorImpl(document, adapter, callbacks, container, patch, resetTimeline, providedRoot));\n } catch (e) {\n const err = asThrownError(e);\n failed(PxDiagnosticKind.internal, PxDiagnosticCode.buildFailed, err);\n }\n };\n\n // The proxy: forwards once the player exists, queues control calls until then. Built and\n // listed BEFORE the build, so a `load` trigger's first `play` during construction is\n // announced for it like every later one.\n proxy = {\n \"isReady\": () => !!animator,\n \"getRootElement\": () => animator ? animator.getRootElement() : null,\n \"isPlaying\": () => animator?.isPlaying() || false,\n \"play\": () => { enqueue(api => api.play()); },\n \"pause\": () => { enqueue(api => api.pause()); },\n \"cancel\": () => { enqueue(api => api.cancel()); },\n \"finish\": () => { enqueue(api => api.finish()); },\n \"setPlaybackRate\": (rate: number) => { enqueue(api => api.setPlaybackRate(rate)); },\n \"getCurrentTime\": () => animator ? animator.getCurrentTime() : null,\n \"setCurrentTime\": (time: number) => { enqueue(api => api.setCurrentTime(time)); },\n \"getCurrentProgress\": () => animator ? animator.getCurrentProgress() : null,\n \"setCurrentProgress\": (progress: number) => { enqueue(api => api.setCurrentProgress(progress)); },\n \"destroy\": () => {\n destroyed = true;\n pending = null; // drop any queued calls\n animator?.destroy();\n }\n };\n // Listed from this moment (`add`) until `destroy()` — a `src` still loading included.\n registerAnimator(proxy);\n\n if (doc !== undefined) {\n build(doc);\n } else {\n fetch(src!).then(res => res.json()).then(json => {\n if (destroyed) return; // destroy() was called before the document loaded\n if (isPxDocument(json)) build(json);\n else failed(PxDiagnosticKind.document, PxDiagnosticCode.invalidDocumentAtSrc, src);\n }).catch(err => {\n // `host`, not `document`: the file may be perfect — the page could not fetch it.\n failed(PxDiagnosticKind.host, PxDiagnosticCode.loadFailed, src, err?.message ?? String(err));\n });\n }\n\n return proxy;\n}\n\n/**\n * Scan and load for tags, e.g.\n * <div data-px-animation-src=\"animation.json\"></div>\n */\n/**\n * Everything `createAnimator` takes except the three the tag supplies (`src`, `container`) or\n * forbids (`data`): callbacks, the diagnostics channel, a playback override and its shortcuts.\n * @public\n */\nexport type PxTagAnimatorOptions = Omit<PxAnimatorOptions, 'src' | 'doc' | 'container'>;\n\n/**\n * Scan the page for `<div data-px-animation-src=\"animation.json\">` and create one player per\n * match, rendered into that element and stored on it. Safe to call repeatedly: elements that\n * already carry a player are skipped.\n *\n * `options` applies to EVERY player this call creates (review §15) — the same callbacks, the\n * same override. Omit it for the zero-config path.\n * @public\n */\nexport function loadTagAnimators(options?: PxTagAnimatorOptions) {\n const elements = document.querySelectorAll('[' + PX_ANIM_SRC_ATTR_NAME + ']');\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n if (!(element as any)[PX_ANIM_ATTR_NAME]) {\n const src = element.getAttribute(PX_ANIM_SRC_ATTR_NAME);\n if (src) {\n (element as any)[PX_ANIM_ATTR_NAME] = createAnimator({ ...options, src, container: element });\n }\n }\n }\n}\n\n// No module-level globals (API review §4). This file used to end by assigning\n// `window.createAnimator` / `loadTagAnimators` / `setupAnimationTriggers` whenever it loaded —\n// for every ESM and CJS consumer too, not just `<script>` pages. That could overwrite a page's\n// own `createAnimator`, and the side effect made the whole module untree-shakable.\n//\n// `<script>` users reach all three through `PixodeskAnimator.*` on the UMD build, which is what\n// the editor's exported SVG+JS calls. Older exported files are unaffected: they inline their own\n// player and assign these names themselves."],"mappings":"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","names":["_a","_b","api","document"]}
|