@hyperframes/player 0.8.142 → 0.8.144

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hyperframes-player.ts","../../parsers/src/compositionDuration.ts","../../core/src/runtime/domRealm.ts","../../core/src/runtime/compositionDimension.ts","../../parsers/src/compositionContract.ts","../../parsers/src/mediaDuration.ts","../../core/src/audioFx.ts","../../core/src/audioGain.ts","../../core/src/audioAutomation.ts","../../core/src/runtime/audioAutomationVolume.ts","../../core/src/speedRamp.ts","../../parsers/src/compositionContract.ts","../../core/src/runtime/playbackRate.ts","../../core/src/runtime/globals.ts","../../core/src/runtime/diagnostics.ts","../../core/src/runtime/authoredTiming.ts","../../core/src/mediaTiming.ts","../../core/src/runtime/startResolver.ts","../../core/src/runtime/state.ts","../../core/src/runtime/compositionLength.ts","../../core/src/studioPreviewMark.ts","../src/shouldInjectRuntime.ts","../src/timeline-adapters.ts","../src/runtime-url.ts","../src/composition-probe.ts","../src/styles.ts","../src/controls.ts","../src/controls-setup.ts","../src/iframe-dom.ts","../src/direct-timeline-clock.ts","../src/mediaObserverScope.ts","../../core/src/clipFacts.ts","../../core/src/runtime/clipWindow.ts","../../core/src/audioFade.ts","../../core/src/audibleVideo.ts","../../core/src/runtime/stackingContext.ts","../../core/src/inline-scripts/parityContract.ts","../../core/src/runtime/protocol.ts","../../core/src/runtime/timeline.ts","../../core/src/compositionReadiness.ts","../src/parent-media.ts","../src/play-range.ts","../src/playback-state.ts","../src/runtime-message-handler.ts","../src/runtime-in-srcdoc.ts","../src/shader-options.ts","../src/shader-loader-element.ts","../src/shader-loader-state.ts","../src/video-source.ts"],"sourcesContent":["import { CompositionProbe, type ProbeResult, readPositiveDimension } from \"./composition-probe.js\";\nimport { isControlsClick, setupControls, setupPoster } from \"./controls-setup.js\";\nimport { adoptShadowStyles, createCompositionIframe, scaleIframeToFit } from \"./iframe-dom.js\";\nimport { DirectTimelineClock } from \"./direct-timeline-clock.js\";\nimport { ParentMediaManager } from \"./parent-media.js\";\nimport { isRealmHtmlMediaElement } from \"./media-element-guards.js\";\nimport { handleRuntimeMessage } from \"./runtime-message-handler.js\";\nimport {\n isOutsidePlayRange,\n type PlayRange,\n playRangeStopTime,\n resolvePlayRange,\n} from \"./play-range.js\";\nimport {\n SHADER_CAPTURE_SCALE_ATTR,\n SHADER_LOADING_ATTR,\n RUNTIME_SRC_ATTR,\n type ShaderLoadingMode,\n getShaderCaptureScaleFromElement,\n getShaderModeFromElement,\n prepareSrcForElement,\n prepareSrcdocForElement,\n} from \"./shader-options.js\";\nimport { createShaderLoader } from \"./shader-loader-element.js\";\nimport { ShaderLoaderState } from \"./shader-loader-state.js\";\nimport { PLAYER_STYLES } from \"./styles.js\";\nimport { type DirectTimelineAdapter } from \"./timeline-adapters.js\";\nimport { createVideoSource, isVideoType, type VideoSource } from \"./video-source.js\";\nimport { frameDisplayScale, runtimeProtocolMetadata } from \"@hyperframes/core/runtime/protocol\";\nimport {\n FIRST_FRAME_READINESS_SCOPE,\n scanPendingCompositionAssets,\n settleFirstFrameCompositionReadiness,\n} from \"@hyperframes/core/composition-readiness\";\n\n// Playback-rate bounds mirror the runtime clamp in\n// packages/core/src/runtime/init.ts (applyPlaybackRate) and media.ts so the\n// player accepts the same range as the in-iframe runtime: an out-of-range rate\n// would otherwise drive the parent-proxied <audio> outside the bounds the\n// timeline itself respects. Clamping here also shields the native\n// HTMLMediaElement.playbackRate setter, which throws for extreme values in\n// production browsers.\nconst MIN_PLAYBACK_RATE = 0.1;\nconst MAX_PLAYBACK_RATE = 5;\nconst SANDBOX_ORIGIN_ATTR = \"sandbox-origin\";\nconst RUNTIME_DATA_DELIVERY_TIMEOUT_MS = 10_000;\n// Bounds how long the player waits on a same-origin composition's readiness\n// inputs (media, compute, paint-and-idle) before playing anyway — a stuck\n// asset or a composition that never goes quiet must not block playback forever.\nconst ASSETS_READY_TIMEOUT_MS = 8_000;\nconst ASSETS_LOADING_ATTR = \"assets-loading\";\n// \"player\" (default) draws the loading-assets card; \"none\" never does, like shader-loading=\"none\".\nconst ASSETS_LOADING_UI_ATTR = \"assets-loading-ui\";\nconst LOW_POWER_IDLE_ATTR = \"low-power-idle\";\nconst DISABLE_CLICK_TO_PLAY_ATTR = \"disable-click-to-play\";\nconst RANGE_START_ATTR = \"range-start\";\nconst RANGE_END_ATTR = \"range-end\";\n// paint-and-idle now always has a frame to wait on, so the overlay would\n// flash on every single Play without this debounce. ponytail: 150ms is\n// unmeasured, retune once there's production data on paint-and-idle timing.\nconst ASSETS_LOADING_SHOW_DELAY_MS = 150;\n\nexport type ColorGradingTarget =\n | string\n | {\n id?: string | null;\n hfId?: string | null;\n selector?: string | null;\n selectorIndex?: number | null;\n };\n\nexport type ColorGradingCompareState = {\n enabled: boolean;\n position?: number;\n softness?: number;\n lineWidth?: number;\n};\n\ntype RuntimeDataBridge = {\n setRuntimeData?: (channel: string, payload: unknown, requestId?: number) => void;\n clearRuntimeData?: (channel: string, requestId?: number) => void;\n};\n\ntype PendingRuntimeDataDelivery = {\n requestId: number;\n timeoutId: number;\n};\n\nfunction clampPlaybackRate(rate: number): number {\n if (!Number.isFinite(rate) || rate <= 0) return 1;\n return Math.max(MIN_PLAYBACK_RATE, Math.min(MAX_PLAYBACK_RATE, rate));\n}\n\nclass HyperframesPlayer extends HTMLElement {\n static get observedAttributes() {\n return [\n \"src\",\n \"srcdoc\",\n \"width\",\n \"height\",\n \"controls\",\n \"muted\",\n \"audio-locked\",\n \"volume\",\n \"poster\",\n \"playback-rate\",\n \"audio-src\",\n \"type\",\n SANDBOX_ORIGIN_ATTR,\n RUNTIME_SRC_ATTR,\n SHADER_CAPTURE_SCALE_ATTR,\n SHADER_LOADING_ATTR,\n ASSETS_LOADING_UI_ATTR,\n LOW_POWER_IDLE_ATTR,\n RANGE_START_ATTR,\n RANGE_END_ATTR,\n ];\n }\n\n private shadow: ShadowRoot;\n private container: HTMLDivElement;\n private iframe: HTMLIFrameElement;\n private posterEl: HTMLImageElement | null = null;\n private controlsApi: ReturnType<typeof setupControls> | null = null;\n private resizeObserver: ResizeObserver;\n private shaderLoader: ShaderLoaderState;\n private probe: CompositionProbe;\n\n private _ready = false;\n private _readyDocument: Document | null = null;\n private _connected = false;\n private _assetsReady = false;\n private _painted = false;\n private _pendingPlay = false;\n private _assetsGeneration = 0;\n private _assetsLoadingShowTimer: ReturnType<typeof setTimeout> | null = null;\n private _currentTime = 0;\n private _duration = 0;\n private _paused = true;\n /** True while the user is dragging the scrubber — makes seek() play audio at the\n * playhead (audible scrub) instead of positioning it silently. */\n private _scrubbing = false;\n private _lastUpdateMs = 0;\n private _volume = 1;\n private _compositionWidth = 1920;\n private _compositionHeight = 1080;\n private _rescaleWarned = false;\n private _directTimelineAdapter: DirectTimelineAdapter | null = null;\n private _directTimelineClock: DirectTimelineClock;\n private _parentTickRaf: number | null = null;\n private _media: ParentMediaManager;\n private _scenes: { id: string; start: number; duration: number }[] = [];\n private _runtimeFps = 30;\n private _runtimeBridgeReady = false;\n private _runtimeAssetsReadyGeneration = -1;\n private _runtimeData = new Map<string, unknown>();\n private _runtimeDataRequestId = 0;\n private _pendingRuntimeData = new Map<string, PendingRuntimeDataDelivery>();\n private _afterUpdate: Array<() => void> | null = null;\n private _videoSource: VideoSource | null = null;\n private _runtimeOwnsPlayRange = false;\n private _enteringRange = false;\n private _rangeClampReported = \"\";\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: \"open\" });\n\n adoptShadowStyles(this.shadow, PLAYER_STYLES);\n ({ container: this.container, iframe: this.iframe } = createCompositionIframe());\n this.shadow.appendChild(this.container);\n\n const loaderElements = createShaderLoader();\n this.shadow.appendChild(loaderElements.root);\n this.shaderLoader = new ShaderLoaderState(loaderElements);\n\n this._media = new ParentMediaManager({\n dispatchEvent: (e) => this._emit(e),\n getMuted: () => this.muted,\n getVolume: () => this._volume,\n getPlaybackRate: () => this.playbackRate,\n getCurrentTime: () => this._currentTime,\n isPaused: () => this._paused,\n });\n\n this._directTimelineClock = new DirectTimelineClock({\n onTimeUpdate: (currentTime, duration) => {\n this._currentTime = currentTime;\n this.controlsApi?.updateTime(currentTime, duration);\n this._emit(new CustomEvent(\"timeupdate\", { detail: { currentTime } }));\n },\n getLoop: () => this.loop,\n restart: () => {\n this.seek(this._playRange()?.start ?? 0);\n this.play();\n },\n onPaused: () => {\n if (this._media.audioOwner === \"parent\") this._media.pauseAll();\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this._emit(new Event(\"ended\"));\n },\n onEnded: () => this.loop,\n });\n\n this.probe = new CompositionProbe(this.iframe, {\n onReady: (result) => this._onProbeReady(result),\n onError: (message) => this._emit(new CustomEvent(\"error\", { detail: { message } })),\n });\n\n this.addEventListener(\"click\", (event) => {\n if (this.disableClickToPlay || isControlsClick(event)) return;\n if (this._paused) this.play();\n else this.pause();\n });\n\n this.resizeObserver = new ResizeObserver(() => this._rescale());\n this._onMessage = this._onMessage.bind(this);\n this._onIframeLoad = this._onIframeLoad.bind(this);\n // Before any host can listen; _onIframeLoad skips the blank-document load before connect.\n this.iframe.addEventListener(\"load\", this._onIframeLoad);\n }\n\n connectedCallback() {\n this._connected = true;\n this._applySandboxOriginPolicy();\n this.resizeObserver.observe(this);\n window.addEventListener(\"message\", this._onMessage);\n if (this.hasAttribute(\"controls\")) this._setupControls();\n if (this.hasAttribute(\"poster\"))\n this.posterEl = setupPoster(this.shadow, this.getAttribute(\"poster\"), this.posterEl);\n if (this.hasAttribute(\"audio-src\")) this._media.setupFromUrl(this.getAttribute(\"audio-src\")!);\n if (this.hasAttribute(\"srcdoc\"))\n this.iframe.srcdoc = prepareSrcdocForElement(this, this.getAttribute(\"srcdoc\")!);\n if (this.hasAttribute(\"src\")) this._loadSrc(this.getAttribute(\"src\")!);\n\n // Host-environment audio lock: when the embedding host (e.g. Claude\n // desktop) drops the `audio-locked` attribute, attributeChangedCallback\n // never fires for it, so apply the lock here based on UA detection.\n if (!this.hasAttribute(\"audio-locked\") && this._isLockedHostEnvironment()) {\n this._applyAudioLock(true);\n }\n }\n\n disconnectedCallback() {\n this._connected = false;\n this._teardownVideo();\n this._sendControl(\"pause\");\n this._stopIframeMedia();\n this.resizeObserver.disconnect();\n window.removeEventListener(\"message\", this._onMessage);\n this.probe.stop();\n this._directTimelineClock.stop();\n this._stopParentTickClock();\n this._directTimelineAdapter = null;\n this.shaderLoader.destroy();\n this._media.destroy();\n this.controlsApi?.destroy();\n this.controlsApi = null;\n this._paused = true;\n this._pendingPlay = false;\n this._abandonComposition(\"Player disconnected before runtime data was applied\");\n }\n\n // fallow-ignore-next-line complexity\n attributeChangedCallback(name: string, oldVal: string | null, val: string | null) {\n switch (name) {\n case \"src\":\n // Custom-element attributes are normally assigned before insertion (React does this for\n // every render). Navigating the inner iframe here would let its one-shot runtime `ready`\n // message fire before connectedCallback installs the parent message listener. Initial\n // attributes are applied below by connectedCallback; only live changes navigate here.\n if (!this.isConnected) break;\n if (val) this._navigateSrc(val);\n break;\n case \"type\": {\n const src = this.getAttribute(\"src\");\n if (!this.isConnected || src === null || !!this._videoSource === this._wantsVideo()) break;\n this._navigateSrc(src);\n break;\n }\n case \"srcdoc\":\n if (!this.isConnected) break;\n this._pendingPlay = false;\n this._abandonComposition(\"Composition navigated before runtime data was applied\");\n if (val !== null) {\n this._pauseForVideoSwitch();\n this._teardownVideo();\n this.iframe.srcdoc = prepareSrcdocForElement(this, val);\n } else {\n this.iframe.removeAttribute(\"srcdoc\");\n const src = this.getAttribute(\"src\");\n if (src !== null && this._wantsVideo()) this._navigateSrc(src);\n }\n break;\n case SANDBOX_ORIGIN_ATTR:\n this._applySandboxOriginPolicy(this.isConnected && oldVal !== val);\n break;\n // Reject NaN/zero/negative dimensions the same way the composition\n // probe does (a typo like width=\"abc\" or width=\"0\" would otherwise\n // reach scaleIframeToFit as scale(NaN) or a division by zero and\n // blank the player); fall back to the defaults instead.\n case \"width\":\n this._setCompositionSize(readPositiveDimension(val) ?? 1920, this._compositionHeight);\n break;\n case \"height\":\n this._setCompositionSize(this._compositionWidth, readPositiveDimension(val) ?? 1080);\n break;\n case \"controls\":\n if (val !== null) this._setupControls();\n else {\n this.controlsApi?.destroy();\n this.controlsApi = null;\n }\n break;\n case \"poster\":\n this.posterEl = setupPoster(this.shadow, val, this.posterEl);\n break;\n case \"playback-rate\": {\n const rate = clampPlaybackRate(parseFloat(val || \"1\"));\n this._media.updatePlaybackRate(rate);\n this._sendControl(\"set-playback-rate\", { playbackRate: rate });\n this._directTimelineAdapter?.timeScale?.(rate);\n this.controlsApi?.updateSpeed(rate);\n this._emit(new Event(\"ratechange\"));\n break;\n }\n case \"muted\":\n this._handleMutedChange(val);\n break;\n case \"audio-locked\":\n this._applyAudioLock(val !== null);\n break;\n case \"volume\": {\n const v = Math.max(0, Math.min(1, parseFloat(val || \"1\")));\n this._volume = v;\n this._syncVideoAudio();\n this._media.updateVolume(v);\n this._sendControl(\"set-volume\", { volume: v });\n this.controlsApi?.updateVolume(v);\n this._emit(new Event(\"volumechange\"));\n break;\n }\n case \"audio-src\":\n if (val) this._media.setupFromUrl(val);\n else this._media.teardownUrlAudio();\n break;\n case ASSETS_LOADING_UI_ATTR:\n if (val === \"none\") this.shaderLoader.hideAssetsLoading();\n break;\n case LOW_POWER_IDLE_ATTR:\n this._sendControl(\"set-idle-heartbeat\", { slow: val !== null });\n break;\n case RANGE_START_ATTR:\n case RANGE_END_ATTR:\n this._applyPlayRange(true);\n break;\n case SHADER_CAPTURE_SCALE_ATTR:\n case SHADER_LOADING_ATTR:\n case RUNTIME_SRC_ATTR:\n if (!this.isConnected) break;\n this._reloadShaderOptions();\n break;\n }\n }\n\n private _applySandboxOriginPolicy(reloadActiveDocument = false): void {\n if (this.hasAttribute(SANDBOX_ORIGIN_ATTR)) {\n this.iframe.sandbox.remove(\"allow-same-origin\");\n } else {\n this.iframe.sandbox.add(\"allow-same-origin\");\n }\n if (reloadActiveDocument) this._reloadForSandboxOriginPolicy();\n }\n\n private _reloadForSandboxOriginPolicy(): void {\n // A video does not play in the iframe, so neither reload applies to it.\n if (this._videoSource) return;\n this._abandonComposition(\"Sandbox policy changed before runtime data was applied\");\n const srcdoc = this.getAttribute(\"srcdoc\");\n if (srcdoc !== null) {\n this.iframe.srcdoc = prepareSrcdocForElement(this, srcdoc);\n return;\n }\n const src = this.getAttribute(\"src\");\n if (src === null) this.iframe.src = \"about:blank\";\n else this._loadSrc(src);\n }\n\n /**\n * The inner `<iframe>` rendering the composition. Use this when integrating\n * with tools that need `contentWindow` — `.contentWindow` on the\n * `<hyperframes-player>` element itself returns `null` (Shadow DOM).\n */\n get iframeElement(): HTMLIFrameElement {\n return this.iframe;\n }\n\n /** Scene list from the last-received runtime timeline message. Empty until\n * the composition runtime fires its first \"timeline\" postMessage. */\n get scenes(): { id: string; start: number; duration: number }[] {\n return this._scenes;\n }\n\n play() {\n this._play(true);\n }\n\n // fallow-ignore-next-line complexity\n private _play(announce: boolean) {\n if (this._ready && !this._assetsReady) {\n this._pendingPlay = true;\n return;\n }\n this._pendingPlay = false;\n this.posterEl?.remove();\n this.posterEl = null;\n const range = this._ready ? this._playRange() : null;\n if (range) {\n if (isOutsidePlayRange(this._currentTime, range, this._duration, this._runtimeFps)) {\n this.seek(range.start);\n this._enteringRange = true;\n }\n } else if (this._duration > 0 && this._currentTime >= this._duration) this.seek(0);\n // Must be set before _startParentTickClock so the RAF loop's `_paused`\n // check doesn't immediately self-terminate on the first callback.\n this._paused = false;\n const directTimelineStarted = this._tryDirectTimelinePlay();\n // Set when the probe hasn't resolved yet: retried from _onProbeReady /\n // _onRuntimeTimelineReady once ready, which is the call that actually\n // starts playback — this premature call must not ALSO dispatch \"play\"\n // for what hasn't started, or a host listener sees it fire twice.\n let queuedForReady = false;\n this._sendDisplayScale();\n if (!directTimelineStarted) {\n this._sendControl(\"play\");\n // Only start the parent tick clock once the composition is ready and\n // confirmed on the runtime bridge path (not the direct-timeline path).\n if (this._ready && !this._directTimelineAdapter) {\n this._startParentTickClock();\n } else if (!this._ready) {\n this._pendingPlay = true;\n queuedForReady = true;\n }\n }\n if (this._media.audioOwner === \"parent\") this._media.playAll();\n this.controlsApi?.updatePlaying(true);\n if (!queuedForReady && announce) this._emit(new Event(\"play\"));\n if (directTimelineStarted && this._directTimelineAdapter) {\n this._directTimelineClock.start(\n this._directTimelineAdapter,\n () => this._currentTime,\n () => this._duration,\n () => this._paused,\n () => this._clockStop(),\n );\n }\n }\n\n pause() {\n // A play queued while assets were still buffering must not survive an\n // explicit user pause — otherwise it fires once assets settle, seconds\n // after the user stopped playback.\n this._pendingPlay = false;\n if (!this._tryDirectTimelinePause()) this._sendControl(\"pause\");\n else this._showPausedVideoTime();\n this._directTimelineClock.stop();\n this._stopParentTickClock();\n if (this._media.audioOwner === \"parent\") this._media.pauseAll();\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this._emit(new Event(\"pause\"));\n }\n\n stopMedia() {\n this._sendControl(\"stop-media\");\n this._stopIframeMedia();\n this._media.stopAdoptedMedia();\n }\n\n seek(timeInSeconds: number) {\n // seek()'s own contract is that it lands paused, so a play still queued\n // from the asset-buffering window is cancelled here too — same reason as\n // pause() above.\n this._pendingPlay = false;\n if (!this._trySyncSeek(timeInSeconds) && !this._tryDirectTimelineSeek(timeInSeconds)) {\n this._sendControl(\"seek\", {\n timeSeconds: timeInSeconds,\n // Legacy runtimes read only `frame`. Protocol-v1 runtimes prefer\n // `timeSeconds`, so carrying both keeps cross-origin embeds seekable\n // while preserving seconds-first precision between current peers.\n frame: Math.round(timeInSeconds * this._runtimeFps),\n });\n }\n this._directTimelineClock.stop();\n this._stopParentTickClock();\n this._currentTime = timeInSeconds;\n if (this._media.audioOwner === \"parent\") {\n if (this._scrubbing) {\n // Audible scrub: play the proxy audio at the playhead so the viewer hears\n // the track as they drag. Each move re-seeks, restarting playback from the\n // new position. onScrubEnd settles back to silence via a normal seek.\n this._media.scrubAll(timeInSeconds);\n } else {\n // Pause BEFORE seek: leaving the proxy playing turns the next\n // `mirrorTime` drift-correction tick into a perpetual seek→play→drift→seek\n // stutter loop, where ~80ms of audio plays past the (now frozen) timeline,\n // then mirrorTime yanks `currentTime` back to match it. Symmetric with\n // `pause()` below.\n this._media.pauseAll();\n this._media.seekAll(timeInSeconds);\n }\n }\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n\n setColorGrading(target: ColorGradingTarget, grading: unknown) {\n this._sendControl(\"set-color-grading\", { target, grading });\n }\n\n clearColorGrading(target: ColorGradingTarget) {\n this._sendControl(\"set-color-grading\", { target, grading: null });\n }\n\n setColorGradingCompare(target: ColorGradingTarget, compare: ColorGradingCompareState) {\n this._sendControl(\"set-color-grading-compare\", { target, compare });\n }\n\n clearColorGradingCompare(target: ColorGradingTarget) {\n this._sendControl(\"set-color-grading-compare\", {\n target,\n compare: { enabled: false },\n });\n }\n\n /** Retain and deliver structured runtime data through the shared runtime protocol. */\n setRuntimeData(channel: string, payload: unknown): void {\n if (!/^[a-z][a-z0-9-]{0,63}$/.test(channel)) {\n throw new Error(`Invalid HyperFrames runtime-data channel: ${channel}`);\n }\n if (typeof structuredClone !== \"function\") {\n throw new Error(\n \"HyperFrames runtime data requires structuredClone support; refusing an unverified payload\",\n );\n }\n const retained = structuredClone(payload);\n this._runtimeData.set(channel, retained);\n this._deliverRuntimeData(channel, retained);\n }\n\n clearRuntimeData(channel: string): void {\n if (!/^[a-z][a-z0-9-]{0,63}$/.test(channel)) {\n throw new Error(`Invalid HyperFrames runtime-data channel: ${channel}`);\n }\n this._runtimeData.delete(channel);\n this._deliverRuntimeDataClear(channel);\n }\n\n get currentTime() {\n return this._currentTime;\n }\n set currentTime(t: number) {\n this.seek(t);\n }\n\n get duration() {\n return this._duration;\n }\n\n /** The composition's width, from the runtime or the `width` attribute. */\n get compositionWidth() {\n return this._compositionWidth;\n }\n /** The composition's height, from the runtime or the `height` attribute. */\n get compositionHeight() {\n return this._compositionHeight;\n }\n get paused() {\n return this._paused;\n }\n get ready() {\n return this._ready;\n }\n\n /** True once every readiness input (media, compute, paint-and-idle) has\n * settled or the wait timed out. Mirrors `assetsready`. Always true for\n * cross-origin compositions, which the player has no DOM access to wait on. */\n get assetsReady() {\n return this._assetsReady;\n }\n\n /** True once assets are ready and the loading panel has finished fading out,\n * so the document is what is on screen. Mirrors `painted`; resets per load. */\n get painted() {\n return this._painted;\n }\n\n get playbackRate() {\n return clampPlaybackRate(parseFloat(this.getAttribute(\"playback-rate\") || \"1\"));\n }\n set playbackRate(r: number) {\n this.setAttribute(\"playback-rate\", String(clampPlaybackRate(r)));\n }\n\n get shaderCaptureScale() {\n return getShaderCaptureScaleFromElement(this);\n }\n set shaderCaptureScale(scale: number) {\n this.setAttribute(SHADER_CAPTURE_SCALE_ATTR, String(scale));\n }\n\n get shaderLoading() {\n return getShaderModeFromElement(this);\n }\n set shaderLoading(mode: ShaderLoadingMode) {\n if (mode === \"composition\") this.removeAttribute(SHADER_LOADING_ATTR);\n else this.setAttribute(SHADER_LOADING_ATTR, mode);\n }\n\n get assetsLoadingUi(): \"player\" | \"none\" {\n return this.getAttribute(ASSETS_LOADING_UI_ATTR) === \"none\" ? \"none\" : \"player\";\n }\n set assetsLoadingUi(mode: \"player\" | \"none\") {\n if (mode === \"none\") this.setAttribute(ASSETS_LOADING_UI_ATTR, \"none\");\n else this.removeAttribute(ASSETS_LOADING_UI_ATTR);\n }\n\n get muted() {\n return this.hasAttribute(\"muted\");\n }\n set muted(m: boolean) {\n if (m) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get audioLocked() {\n return this.hasAttribute(\"audio-locked\");\n }\n set audioLocked(locked: boolean) {\n if (locked) this.setAttribute(\"audio-locked\", \"\");\n else this.removeAttribute(\"audio-locked\");\n }\n\n /**\n * Host renderers that strip unknown custom-element attributes before they\n * reach the DOM (observed on the Claude desktop Electron client) can defeat\n * `audio-locked` even when the host *intends* to lock audio. When we detect\n * such an environment, self-impose the same restriction the attribute would\n * apply. Web (browser) hosts preserve the attribute and don't need this.\n */\n private _isLockedHostEnvironment(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const ua = navigator.userAgent || \"\";\n // Claude desktop ships as an Electron app with a \"Claude/<version>\" UA token.\n return /\\bClaude\\/\\d/.test(ua) && /\\bElectron\\b/.test(ua);\n }\n\n /** True when audio playback must be locked: attribute OR host fallback. */\n private _isAudioLocked(): boolean {\n return this.hasAttribute(\"audio-locked\") || this._isLockedHostEnvironment();\n }\n\n private _isSlideshowPlayer(): boolean {\n return this.closest(\"hyperframes-slideshow\") !== null;\n }\n\n /** Apply a change to the `muted` attribute: re-assert under an audio lock,\n * else mute/unmute the media, sync the controls, and fire `volumechange`. */\n private _handleMutedChange(val: string | null): void {\n // While audio is locked, ignore any attempt to clear `muted` (host control,\n // stray script, raw `removeAttribute`) and re-assert it. The re-set fires\n // this callback again with val=\"\" (not null) so it mutes normally — no loop.\n if (val === null && this._isAudioLocked()) {\n this.setAttribute(\"muted\", \"\");\n return;\n }\n this._media.updateMuted(val !== null);\n this._syncVideoAudio();\n this._setIframeMediaMuted(val !== null);\n this._sendControl(\"set-muted\", { muted: val !== null });\n this.controlsApi?.updateMuted(val !== null);\n this._emit(new Event(\"volumechange\"));\n }\n\n /**\n * Host-mandated silent playback (e.g. embedded in a chat host): force mute\n * and hide the volume controls so the viewer cannot turn sound on. Unlocking\n * only unhides the controls — it does not auto-unmute; callers manage `muted`\n * explicitly after unlocking.\n */\n private _applyAudioLock(locked: boolean): void {\n if (locked) this.muted = true;\n this.controlsApi?.setVolumeControlsHidden(locked);\n }\n\n get volume() {\n return this._volume;\n }\n set volume(v: number) {\n this.setAttribute(\"volume\", String(Math.max(0, Math.min(1, v))));\n }\n\n get disableClickToPlay() {\n return this.hasAttribute(DISABLE_CLICK_TO_PLAY_ATTR);\n }\n set disableClickToPlay(disabled: boolean) {\n this.toggleAttribute(DISABLE_CLICK_TO_PLAY_ATTR, disabled);\n }\n\n get loop() {\n return this.hasAttribute(\"loop\");\n }\n set loop(l: boolean) {\n if (l) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n /** Film time where range playback starts; null (no `range-start`) starts at 0. */\n get rangeStart(): number | null {\n return this._readSeconds(RANGE_START_ATTR);\n }\n set rangeStart(seconds: number | null) {\n this._writeSeconds(RANGE_START_ATTR, seconds);\n }\n\n /** Film time where range playback ends or loops; null (no `range-end`) plays to the end.\n * When it ends inside the film, `currentTime` at `ended` is inside its last frame, before this. */\n get rangeEnd(): number | null {\n return this._readSeconds(RANGE_END_ATTR);\n }\n set rangeEnd(seconds: number | null) {\n this._writeSeconds(RANGE_END_ATTR, seconds);\n }\n\n private _readSeconds(name: string): number | null {\n const value = this.getAttribute(name);\n return value === null ? null : Number(value);\n }\n\n private _writeSeconds(name: string, seconds: number | null): void {\n if (seconds == null) this.removeAttribute(name);\n else this.setAttribute(name, String(seconds));\n }\n\n private _hasPlayRange(): boolean {\n return this.hasAttribute(RANGE_START_ATTR) || this.hasAttribute(RANGE_END_ATTR);\n }\n\n private _playRange(): PlayRange | null {\n return resolvePlayRange(this.rangeStart, this.rangeEnd, this._duration).range;\n }\n\n private _sendPlayRange(range: PlayRange | null): void {\n const endSeconds = range?.end ?? null;\n this._sendControl(\"set-play-range\", { startSeconds: range?.start ?? null, endSeconds });\n }\n\n /** A new range or duration: the runtime gets the new end and the host hears of a clamp. With\n * `park`, a paused player moves to the start and a playing one outside the range jumps there;\n * a queued play() starts at the range start itself. */\n private _applyPlayRange(park: boolean): void {\n const { range, clamped } = resolvePlayRange(this.rangeStart, this.rangeEnd, this._duration);\n this._sendPlayRange(range);\n if (!this._ready) return;\n this._reportRangeClamp(range, clamped);\n if (range && park && !this._pendingPlay) this._moveIntoPlayRange(range);\n }\n\n // Once per asked-for range and duration.\n private _reportRangeClamp(range: PlayRange | null, clamped: boolean): void {\n const clampKey = clamped ? `${this.rangeStart},${this.rangeEnd},${this._duration}` : \"\";\n if (clampKey === this._rangeClampReported) return;\n this._rangeClampReported = clampKey;\n if (!clamped) return;\n const detail = { rangeStart: range?.start ?? null, rangeEnd: range?.end ?? null };\n this._emit(\n new CustomEvent(\"rangeclamped\", { detail: { ...detail, duration: this._duration } }),\n );\n }\n\n private _moveIntoPlayRange(range: PlayRange): void {\n if (!isOutsidePlayRange(this._currentTime, range, this._duration, this._runtimeFps)) return;\n if (this._paused) this.seek(range.start);\n else this._jumpWhilePlaying(range.start);\n }\n\n /** A `play-range` runtime jumps by itself and a player clock seeks without pausing; an older\n * runtime is seeked and resumed, with no second `play` event. */\n private _jumpWhilePlaying(time: number): void {\n if (this._directTimelineAdapter) {\n this._directTimelineAdapter.seek(time, false);\n this._currentTime = time;\n } else if (!this._runtimeOwnsPlayRange) {\n this.seek(time);\n this._enteringRange = true;\n this._play(false);\n }\n }\n\n /** Where the player's own clock stops, and the time it then shows: mid last frame, since a video\n * seeked onto a frame boundary can decode the frame before it. */\n private _clockStop(): { end: number; shown: number } {\n const range = this._playRange();\n const end = range?.end ?? this._duration;\n if (!range) return { end, shown: end };\n const hold = playRangeStopTime(range, this._duration, this._runtimeFps);\n return { end, shown: hold < end ? (hold + end) / 2 : hold };\n }\n\n private _sendControl(action: string, extra: Record<string, unknown> = {}): boolean {\n try {\n const frameWindow = this.iframe.contentWindow;\n if (!frameWindow) {\n if (action === \"set-runtime-data\" || action === \"clear-runtime-data\") {\n this._rejectRuntimeDataDelivery(\n extra[\"channel\"],\n extra[\"requestId\"],\n \"Composition iframe is unavailable\",\n );\n }\n return false;\n }\n frameWindow.postMessage(\n {\n ...extra,\n source: \"hf-parent\",\n type: \"control\",\n action,\n ...runtimeProtocolMetadata(this._runtimeFps),\n },\n \"*\",\n );\n return true;\n } catch (error) {\n if (action === \"set-runtime-data\" || action === \"clear-runtime-data\") {\n this._rejectRuntimeDataDelivery(\n extra[\"channel\"],\n extra[\"requestId\"],\n error instanceof Error ? error.message : String(error),\n );\n }\n return false;\n }\n }\n\n private _deliverRuntimeData(channel: string, payload: unknown): void {\n if (!this.isConnected || !this._runtimeBridgeReady) return;\n const requestId = this._beginRuntimeDataDelivery(channel);\n if (this._trySetRuntimeDataDirect(channel, payload, requestId)) return;\n this._sendControl(\"set-runtime-data\", { channel, payload, requestId });\n }\n\n private _deliverRuntimeDataClear(channel: string): void {\n if (!this.isConnected || !this._runtimeBridgeReady) return;\n const requestId = this._beginRuntimeDataDelivery(channel);\n if (this._tryClearRuntimeDataDirect(channel, requestId)) return;\n this._sendControl(\"clear-runtime-data\", { channel, requestId });\n }\n\n private _trySetRuntimeDataDirect(channel: string, payload: unknown, requestId: number): boolean {\n try {\n const bridge = (\n this.iframe.contentWindow as (Window & { __hyperframes?: RuntimeDataBridge }) | null\n )?.__hyperframes;\n if (typeof bridge?.setRuntimeData !== \"function\") return false;\n bridge.setRuntimeData(channel, payload, requestId);\n return true;\n } catch {\n return false;\n }\n }\n\n private _tryClearRuntimeDataDirect(channel: string, requestId: number): boolean {\n try {\n const bridge = (\n this.iframe.contentWindow as (Window & { __hyperframes?: RuntimeDataBridge }) | null\n )?.__hyperframes;\n if (typeof bridge?.clearRuntimeData !== \"function\") return false;\n bridge.clearRuntimeData(channel, requestId);\n return true;\n } catch {\n return false;\n }\n }\n\n private _replayRuntimeData(): void {\n for (const [channel, payload] of this._runtimeData) {\n this._deliverRuntimeData(channel, payload);\n }\n }\n\n private _beginRuntimeDataDelivery(channel: string): number {\n const previous = this._pendingRuntimeData.get(channel);\n if (previous) window.clearTimeout(previous.timeoutId);\n this._runtimeDataRequestId += 1;\n const requestId = this._runtimeDataRequestId;\n const timeoutId = window.setTimeout(() => {\n this._rejectRuntimeDataDelivery(\n channel,\n requestId,\n `Runtime data delivery timed out after ${RUNTIME_DATA_DELIVERY_TIMEOUT_MS}ms`,\n );\n }, RUNTIME_DATA_DELIVERY_TIMEOUT_MS);\n this._pendingRuntimeData.set(channel, { requestId, timeoutId });\n return requestId;\n }\n\n private _resolveRuntimeDataDelivery(channel: unknown, requestId: unknown): void {\n const pending = this._takeRuntimeDataDelivery(channel, requestId);\n if (!pending) return;\n this._emit(\n new CustomEvent(\"runtimedataapplied\", {\n detail: { channel, requestId: pending.requestId },\n }),\n );\n }\n\n private _rejectRuntimeDataDelivery(channel: unknown, requestId: unknown, message: unknown): void {\n const pending = this._takeRuntimeDataDelivery(channel, requestId);\n if (!pending) return;\n this._emit(\n new CustomEvent(\"runtimedataerror\", {\n detail: {\n channel,\n requestId: pending.requestId,\n message: typeof message === \"string\" ? message : String(message),\n },\n }),\n );\n }\n\n private _takeRuntimeDataDelivery(\n channel: unknown,\n requestId: unknown,\n ): PendingRuntimeDataDelivery | null {\n if (\n typeof channel !== \"string\" ||\n typeof requestId !== \"number\" ||\n !Number.isSafeInteger(requestId)\n )\n return null;\n const pending = this._pendingRuntimeData.get(channel);\n if (!pending || pending.requestId !== requestId) return null;\n window.clearTimeout(pending.timeoutId);\n this._pendingRuntimeData.delete(channel);\n return pending;\n }\n\n private _rejectAllRuntimeDataDeliveries(message: string): void {\n for (const [channel, pending] of [...this._pendingRuntimeData]) {\n this._rejectRuntimeDataDelivery(channel, pending.requestId, message);\n }\n }\n\n /**\n * Returns the iframe's contentDocument if same-origin and reachable,\n * otherwise null. Accessing contentDocument can throw on cross-origin\n * iframes — this swallows that as a clean null sentinel.\n */\n private _getSameOriginIframeDocument(): Document | null {\n try {\n return this.iframe.contentDocument;\n } catch {\n return null;\n }\n }\n\n private _setIframeMediaMuted(muted: boolean): void {\n const iframeDoc = this._getSameOriginIframeDocument();\n if (!iframeDoc) return;\n for (const el of iframeDoc.querySelectorAll(\"video, audio\")) {\n if (isRealmHtmlMediaElement(el)) el.muted = muted || el.defaultMuted;\n }\n }\n\n private _stopIframeMedia(): void {\n const iframeDoc = this._getSameOriginIframeDocument();\n if (!iframeDoc) return;\n for (const el of iframeDoc.querySelectorAll(\"video, audio\")) {\n if (isRealmHtmlMediaElement(el)) el.pause();\n }\n }\n\n /**\n * Replay current bridge state to the iframe runtime. Triggered when the\n * runtime announces `{type: \"ready\"}` — repairs the race where the parent\n * posts control messages before the iframe's bridge listener is installed\n * (warm-cache reloads, the Claude desktop Electron client, anywhere the\n * iframe finishes loading after we've already called `set-muted` etc).\n * Re-sending current state is idempotent — even at default values it just\n * confirms what the runtime would have done anyway.\n */\n private _replayBridgeState(): void {\n this._sendControl(\"set-muted\", { muted: this.muted });\n this._sendControl(\"set-volume\", { volume: this._volume });\n this._sendControl(\"set-playback-rate\", { playbackRate: this.playbackRate });\n this._sendControl(\"set-native-media-sync-disabled\", {\n disabled: this._isSlideshowPlayer(),\n });\n this._sendControl(\"set-web-audio-media-disabled\", {\n disabled: this._isSlideshowPlayer(),\n });\n this._sendControl(\"set-idle-heartbeat\", { slow: this.hasAttribute(LOW_POWER_IDLE_ATTR) });\n this._sendDisplayScale();\n }\n\n private _sendDisplayScale(): void {\n const scale = frameDisplayScale(this.iframe);\n if (scale) this._sendControl(\"set-display-scale\", { scale });\n }\n\n private _reloadShaderOptions(): void {\n if (this._videoSource) return;\n // This navigates the frame, so readiness has to fall with it. Leaving\n // `_runtimeBridgeReady` true lets a delivery post into a document that is being\n // replaced, where it can only end in a delivery timeout rather than the immediate,\n // explanatory rejection the caller gets from every other navigating path.\n this._abandonComposition(\"Shader options changed before runtime data was applied\");\n if (this.hasAttribute(\"srcdoc\")) {\n this.iframe.srcdoc = prepareSrcdocForElement(this, this.getAttribute(\"srcdoc\") || \"\");\n return;\n }\n if (this.hasAttribute(\"src\")) this._loadSrc(this.getAttribute(\"src\") || \"\");\n }\n\n // A different source inherits no queued play.\n private _navigateSrc(src: string): void {\n this._pendingPlay = false;\n this._pauseForVideoSwitch();\n this._abandonComposition(\"Composition navigated before runtime data was applied\");\n this._loadSrc(src);\n }\n\n /** A `type=\"video/...\"` src plays in a `<video>`, unless `srcdoc` (which wins, as in an iframe)\n * shows a composition. */\n private _wantsVideo(): boolean {\n return isVideoType(this.getAttribute(\"type\")) && !this.hasAttribute(\"srcdoc\");\n }\n\n // Like a <video> given a new src, a switch into or out of a video starts paused.\n private _pauseForVideoSwitch(): void {\n if (!this._videoSource && !this._wantsVideo()) return;\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n }\n\n private _loadSrc(src: string): void {\n if (this._wantsVideo()) {\n this._loadVideo(src);\n return;\n }\n this._teardownVideo();\n this.iframe.src = prepareSrcForElement(this, src);\n }\n\n private _loadVideo(src: string): void {\n if (!this._videoSource) {\n this._videoSource = createVideoSource({\n onMetadata: (video) => this._applyThenEmit(() => this._onVideoReady(video)),\n onDurationChange: (video) => this._applyTimelineDuration(video.duration),\n onResize: (video) => this._applyVideoSize(video),\n // Follow a play or pause the page did not ask for, but not a queued one it since reversed.\n onPlay: (video) => {\n if (!video.paused && this._paused) this.play();\n },\n onPause: (video) => {\n if (video.paused && !video.ended && !this._paused) this.pause();\n },\n // A background tab runs no animation frames; a range's end is checked on timeupdate instead.\n onTimeUpdate: () => {\n if (document.hidden && this._playRange()) this._directTimelineClock.poll();\n },\n onError: (message, code) => {\n if (!this._paused) this.pause();\n this._emit(new CustomEvent(\"error\", { detail: { message, code } }));\n },\n onPlayRejected: (error) => this._onVideoPlayRejected(error),\n });\n this.container.appendChild(this._videoSource.video);\n this.iframe.hidden = true;\n // Unloads a composition this player was showing before it switched to video.\n this.iframe.src = \"about:blank\";\n }\n this.probe.stop();\n const { video, adapter } = this._videoSource;\n this._directTimelineAdapter = adapter;\n this._currentTime = 0;\n this._duration = 0;\n this.controlsApi?.updateTime(0, 0);\n this._syncVideoAudio();\n adapter.timeScale?.(this.playbackRate);\n video.src = src;\n }\n\n private _teardownVideo(): void {\n if (!this._videoSource) return;\n this._videoSource.destroy();\n this._videoSource = null;\n this.iframe.hidden = false;\n }\n\n private _syncVideoAudio(): void {\n if (!this._videoSource) return;\n this._videoSource.video.muted = this.muted;\n this._videoSource.video.volume = this._volume;\n }\n\n private _onVideoReady(video: HTMLVideoElement): void {\n this._applyTimelineDuration(video.duration);\n this._applyVideoSize(video);\n this._ready = true;\n this._dispatchReady();\n // A video has no composition assets to wait on; this settles at once.\n this._waitForAssetsReady(null);\n this._afterEvents(() => {\n if (this.hasAttribute(\"autoplay\") && this._paused) this.play();\n });\n }\n\n private _applyTimelineDuration(duration: number): void {\n if (!Number.isFinite(duration) || duration <= 0) return;\n this._setDuration(duration);\n this.controlsApi?.updateTime(this._currentTime, duration);\n }\n\n private _applyVideoSize(video: HTMLVideoElement): void {\n if (video.videoWidth > 0 && video.videoHeight > 0) {\n this._setCompositionSize(video.videoWidth, video.videoHeight);\n }\n }\n\n // Only a blocked play is a playbackerror: a failed load already fired `error`.\n private _onVideoPlayRejected(error: unknown): void {\n if (!(error instanceof DOMException && error.name === \"NotAllowedError\")) return;\n if (!this._paused) this.pause();\n this._emit(new CustomEvent(\"playbackerror\", { detail: { source: \"video\", error } }));\n }\n\n private _trySyncSeek(timeInSeconds: number): boolean {\n if (this._videoSource) return false;\n try {\n const win = this.iframe.contentWindow as\n | (Window & { __player?: { seek?: (t: number) => void } })\n | null;\n const player = win?.__player;\n if (typeof player?.seek !== \"function\") return false;\n player.seek.call(player, timeInSeconds);\n return true;\n } catch {\n return false;\n }\n }\n\n private _withDirectTimeline(fn: (tl: DirectTimelineAdapter) => void): boolean {\n const resolved = this._videoSource ? null : this.probe.resolveDirectTimelineAdapter();\n const tl = resolved || this._directTimelineAdapter;\n if (!tl) return false;\n try {\n fn(tl);\n if (resolved && resolved !== this._directTimelineAdapter) {\n this._applyTimelineDuration(resolved.duration());\n }\n this._directTimelineAdapter = tl;\n return true;\n } catch {\n return false;\n }\n }\n\n // GSAP seek() preserves play state; player seek() contract lands paused.\n private _tryDirectTimelineSeek(t: number): boolean {\n return this._withDirectTimeline((tl) => {\n // suppressEvents=false: fire the timeline's onUpdate so compositions that\n // drive scene visibility imperatively (via the root timeline's onUpdate,\n // e.g. slideshow decks) repaint on a paused seek — not only while playing.\n tl.seek(t, false);\n tl.pause();\n });\n }\n private _tryDirectTimelinePlay(): boolean {\n return this._withDirectTimeline((tl) => void tl.play());\n }\n private _tryDirectTimelinePause(): boolean {\n return this._withDirectTimeline((tl) => void tl.pause());\n }\n\n // The clock samples every ~100 ms; like a <video>, a pause reports the exact frame.\n private _showPausedVideoTime(): void {\n if (!this._videoSource) return;\n this._currentTime = this._videoSource.video.currentTime;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n this._emit(new CustomEvent(\"timeupdate\", { detail: { currentTime: this._currentTime } }));\n }\n\n /**\n * Widget-frame RAF loop that sends \"tick\" postMessages to the composition\n * iframe on every frame. Used for the runtime bridge path so that animation\n * advances even when the composition iframe's own rAF is throttled by\n * Chromium (e.g. deeply nested cross-origin iframes in Electron / Claude desktop).\n * The runtime skips a tick seek only when that timeline time is already\n * rendered. Host ticks still advance a throttled iframe.\n */\n private _startParentTickClock(): void {\n this._stopParentTickClock();\n const tick = () => {\n if (this._paused) {\n this._parentTickRaf = null;\n return;\n }\n this._sendControl(\"tick\");\n this._parentTickRaf = requestAnimationFrame(tick);\n };\n this._parentTickRaf = requestAnimationFrame(tick);\n }\n\n private _stopParentTickClock(): void {\n if (this._parentTickRaf === null) return;\n cancelAnimationFrame(this._parentTickRaf);\n this._parentTickRaf = null;\n }\n\n private _onMessage(e: MessageEvent) {\n // The iframe window outlives its documents: a late composition message must not reach a video.\n if (this._videoSource || this.probe.failed) return;\n this._applyThenEmit(() => this._handleRuntimeMessage(e));\n }\n\n private _handleRuntimeMessage(e: MessageEvent) {\n handleRuntimeMessage(e, this.iframe.contentWindow, {\n getPlaybackState: () => ({\n currentTime: this._currentTime,\n duration: this._duration,\n paused: this._paused,\n lastUpdateMs: this._lastUpdateMs,\n enteringRange: this._enteringRange,\n }),\n setPlaybackState: ({ currentTime, duration, paused, lastUpdateMs, enteringRange }) => {\n this._enteringRange = enteringRange === true;\n this._currentTime = currentTime;\n this._setDuration(duration);\n this._paused = paused;\n this._lastUpdateMs = lastUpdateMs;\n },\n getShaderLoadingMode: () => getShaderModeFromElement(this),\n shaderLoader: this.shaderLoader,\n setCompositionSize: (w, h) => this._setCompositionSize(w, h),\n sendControl: (action, extra) => this._sendControl(action, extra),\n getIframeDoc: () => this.iframe.contentDocument,\n onRuntimeReady: () => {\n this._runtimeBridgeReady = true;\n this._replayBridgeState();\n if (this._hasPlayRange()) this._sendPlayRange(this._playRange());\n this._replayRuntimeData();\n },\n onRuntimeAssetsReady: () => {\n if (this._runtimeAssetsReadyGeneration === this._assetsGeneration) {\n this._settleAssetsReady(this._assetsGeneration);\n }\n },\n onRuntimeDataApplied: (channel, requestId) =>\n this._resolveRuntimeDataDelivery(channel, requestId),\n onRuntimeDataError: (channel, requestId, message) =>\n this._rejectRuntimeDataDelivery(channel, requestId, message),\n onRuntimeTimelineReady: (duration, assetsReady) =>\n this._onRuntimeTimelineReady(duration, assetsReady),\n setRuntimeFps: (fps) => {\n this._runtimeFps = fps;\n },\n setRuntimeOwnsPlayRange: (owns) => {\n this._runtimeOwnsPlayRange = owns;\n },\n shouldPromoteMediaAutoplayFallback: () => !this._isSlideshowPlayer(),\n setScenes: (scenes) => {\n this._scenes = scenes;\n this._emit(new CustomEvent(\"scenes\", { detail: { scenes } }));\n },\n updateControlsTime: (t, d) => this.controlsApi?.updateTime(t, d),\n updateControlsPlaying: (p) => this.controlsApi?.updatePlaying(p),\n dispatchEvent: (ev) => this._emit(ev),\n seek: (t) => this.seek(t),\n play: () => this.play(),\n getLoop: () => this.loop,\n getPlayRange: () => this._playRange(),\n media: this._media,\n });\n }\n\n private _onRuntimeTimelineReady(duration: number, assetsReady: boolean | undefined) {\n if (this._ready) return;\n this.probe.stop();\n this._setDuration(duration);\n this._directTimelineAdapter = null;\n this._ready = true;\n this.controlsApi?.updateTime(this._currentTime, duration);\n this._dispatchReady();\n\n const doc = this._getSameOriginIframeDocument();\n if (doc) this._media.setupFromIframe(doc);\n\n this._replayBridgeState();\n this._setIframeMediaMuted(this.muted);\n this._waitForAssetsReady(doc, assetsReady);\n this._playWhenWanted();\n }\n\n private _onProbeReady(result: ProbeResult) {\n this._applyThenEmit(() => this._applyProbeResult(result));\n }\n\n private _applyProbeResult({ duration, adapter, compositionSize }: ProbeResult) {\n this._setDuration(duration);\n this._directTimelineAdapter = adapter.kind === \"direct-timeline\" ? adapter.timeline : null;\n if (compositionSize) this._setCompositionSize(compositionSize.width, compositionSize.height);\n this._ready = true;\n this.controlsApi?.updateTime(0, duration);\n this._dispatchReady();\n const doc = this._getSameOriginIframeDocument();\n if (doc) this._media.setupFromIframe(doc);\n this._setIframeMediaMuted(this.muted);\n this._waitForAssetsReady(doc);\n this._playWhenWanted();\n }\n\n /** Gates play() on composition readiness (media, compute, paint-and-idle),\n * bounded by ASSETS_READY_TIMEOUT_MS. The overlay is debounced by\n * ASSETS_LOADING_SHOW_DELAY_MS rather than shown the instant a wait\n * starts, since one is now pending on nearly every Play. */\n private _waitForAssetsReady(doc: Document | null, runtimeAssetsReady?: boolean): void {\n this._clearAssetsLoadingShowTimer();\n this._assetsReady = false;\n this._painted = false;\n // Invalidates any earlier wait still in flight (a composition swap, or\n // disconnect, mid-wait) — its eventual settle checks this and no-ops\n // rather than resolving a since-superseded generation.\n const generation = ++this._assetsGeneration;\n if (!doc) {\n // An opaque-origin iframe cannot be scanned from here; a runtime that\n // reports `assetsReady: false` runs the same scan itself and posts the result.\n if (runtimeAssetsReady === false) {\n this._runtimeAssetsReadyGeneration = generation;\n this._startAssetsLoadingOverlayTimer(generation);\n setTimeout(() => this._settleAssetsReady(generation), ASSETS_READY_TIMEOUT_MS);\n } else {\n this._settleAssetsReady(generation);\n }\n return;\n }\n settleFirstFrameCompositionReadiness(\n doc,\n ({ timedOut }) => {\n if (generation !== this._assetsGeneration) return;\n if (timedOut) this._warnStuckAssets(doc);\n this._settleAssetsReady(generation);\n },\n { timeoutMs: ASSETS_READY_TIMEOUT_MS },\n );\n if (!this._assetsReady) this._startAssetsLoadingOverlayTimer(generation);\n }\n\n private _startAssetsLoadingOverlayTimer(generation: number): void {\n this._assetsLoadingShowTimer = setTimeout(() => {\n this._assetsLoadingShowTimer = null;\n if (generation !== this._assetsGeneration || this._assetsReady) return;\n this.setAttribute(ASSETS_LOADING_ATTR, \"\");\n if (this.assetsLoadingUi !== \"none\") this.shaderLoader.showAssetsLoading();\n }, ASSETS_LOADING_SHOW_DELAY_MS);\n }\n\n private _hasPendingFirstFrameAssets(doc: Document): boolean {\n const { pendingMedia, pendingImages, fontsLoading } = scanPendingCompositionAssets(doc, {\n scope: FIRST_FRAME_READINESS_SCOPE,\n });\n return pendingMedia.length > 0 || pendingImages.length > 0 || fontsLoading;\n }\n\n /** Timeout diagnostic. Re-scans since some assets may have resolved by\n * now. Compute can cause the timeout, so it's reported too. A hidden\n * document can starve paint-and-idle of frames for the full 8s — that's\n * reported directly rather than inferred, since it can't be bounded. */\n private _warnStuckAssets(doc: Document): void {\n const { pendingMedia, pendingImages, fontsLoading } = scanPendingCompositionAssets(doc, {\n scope: FIRST_FRAME_READINESS_SCOPE,\n });\n const win = doc.defaultView as (Window & { __renderReady?: boolean }) | null;\n console.warn(\n `[hyperframes-player] assets-loading timed out after ${ASSETS_READY_TIMEOUT_MS}ms — playing anyway`,\n {\n stuckMedia: pendingMedia.map(\n (el) => el.currentSrc || el.getAttribute(\"src\") || `<${el.tagName.toLowerCase()}>`,\n ),\n stuckImages: pendingImages.map(\n (img) => img.currentSrc || img.getAttribute(\"src\") || \"<img>\",\n ),\n fontsLoading,\n computeReady: win?.__renderReady === true,\n documentHidden: doc.hidden === true,\n },\n );\n }\n\n private _settleAssetsReady(generation: number): void {\n if (generation !== this._assetsGeneration || this._assetsReady) return;\n this._clearAssetsLoadingShowTimer();\n this._assetsReady = true;\n this.removeAttribute(ASSETS_LOADING_ATTR);\n this.shaderLoader.hideAssetsLoading();\n this._emit(new Event(\"assetsready\"));\n this.shaderLoader.whenHidden(() => {\n if (generation !== this._assetsGeneration) return;\n this._painted = true;\n this._emit(new Event(\"painted\"));\n });\n this._afterEvents(() => {\n if (this._pendingPlay) this.play();\n });\n }\n\n /** Every host-driven navigation or teardown: the old document's handshake, asset wait and\n * data deliveries end here. A queued play is the caller's, so only its owners clear it. */\n private _abandonComposition(reason: string): void {\n this.probe.stop();\n this._ready = false;\n this._readyDocument = null;\n this._invalidateAssetsWait();\n this._releaseDocument();\n this._runtimeBridgeReady = false;\n this._rejectAllRuntimeDataDeliveries(reason);\n }\n\n private _releaseDocument(): void {\n this._runtimeOwnsPlayRange = false;\n this._enteringRange = false;\n this._directTimelineAdapter = null;\n this._directTimelineClock.stop();\n this._stopParentTickClock();\n this.shaderLoader.reset();\n this._media.resetForIframeLoad();\n }\n\n /** Abandons any in-flight asset wait — every `_ready = false` site calls\n * this first, so a stale wait's settle can't apply to what comes next. */\n private _invalidateAssetsWait(): void {\n this._clearAssetsLoadingShowTimer();\n this._assetsReady = false;\n this._painted = false;\n this._assetsGeneration++;\n this.removeAttribute(ASSETS_LOADING_ATTR);\n this.shaderLoader.hide();\n }\n\n private _clearAssetsLoadingShowTimer(): void {\n if (this._assetsLoadingShowTimer === null) return;\n clearTimeout(this._assetsLoadingShowTimer);\n this._assetsLoadingShowTimer = null;\n }\n\n private _dispatchReady(): void {\n this._readyDocument = this._getSameOriginIframeDocument();\n const detail = {\n duration: this._duration,\n compositionWidth: this._compositionWidth,\n compositionHeight: this._compositionHeight,\n };\n this._emit(new CustomEvent(\"ready\", { detail }));\n // Once ready: covers a size message that never came, and lets a zero-size player warn.\n this._rescale();\n this._rangeClampReported = \"\";\n if (this._hasPlayRange()) this._applyPlayRange(true);\n }\n\n /** `ready` carries the first duration; later changes fire `durationchange`. */\n private _setDuration(duration: number): void {\n if (duration === this._duration) return;\n this._duration = duration;\n if (!this._ready) return;\n this._emit(new CustomEvent(\"durationchange\", { detail: { duration } }));\n if (this._hasPlayRange()) this._applyPlayRange(false);\n }\n\n private _setCompositionSize(width: number, height: number): void {\n const changed = width !== this._compositionWidth || height !== this._compositionHeight;\n this._compositionWidth = width;\n this._compositionHeight = height;\n this._rescale();\n if (!changed) return;\n const detail = { compositionWidth: width, compositionHeight: height };\n this._emit(new CustomEvent(\"resize\", { detail }));\n }\n\n /** Runs one update (a runtime message, a probe result), then the events and actions it raised,\n * so every listener sees the whole update applied. */\n private _applyThenEmit(apply: () => void): void {\n // A nested update joins the outer one's queue.\n if (this._afterUpdate) {\n apply();\n return;\n }\n const queue: Array<() => void> = [];\n this._afterUpdate = queue;\n const errors: unknown[] = [];\n const run = (step: () => void) => {\n try {\n step();\n } catch (error) {\n errors.push(error);\n }\n };\n run(apply);\n // Still open while flushing: whatever a listener raises goes behind the rest.\n for (const action of queue) run(action);\n this._afterUpdate = null;\n if (errors.length > 0) throw errors[0];\n }\n\n /** Runs `action` once the current update's events have fired (at once outside an update). */\n private _afterEvents(action: () => void): void {\n if (this._afterUpdate) this._afterUpdate.push(action);\n else action();\n }\n\n /** Autoplay, or a play() made before ready, decided after `ready`'s listeners had their turn. */\n private _playWhenWanted(): void {\n this._afterEvents(() => {\n if (this.hasAttribute(\"autoplay\") || this._pendingPlay) this.play();\n });\n }\n\n /** Every event the player raises goes through here, so an update's events keep their order. */\n private _emit(event: Event): void {\n this._afterEvents(() => this.dispatchEvent(event));\n }\n\n private _rescale() {\n const applied = scaleIframeToFit(\n this,\n this.iframe,\n this._compositionWidth,\n this._compositionHeight,\n );\n // A no-op before \"ready\" is expected (element not painted yet). A no-op\n // once ready means the composition is stuck unscaled/untransformed —\n // pinned to the iframe's default top-left position — with no evidence of\n // why in the field. Surface it once (not on every ResizeObserver tick —\n // a legitimately hidden/zero-sized player, e.g. a collapsed tab or\n // off-screen carousel card, would otherwise spam the console forever).\n if (applied) this._sendDisplayScale();\n if (!applied && this._ready && !this._rescaleWarned) {\n this._rescaleWarned = true;\n console.warn(\"[hyperframes-player] rescale no-op after ready — zero-size player element\", {\n src: this.getAttribute(\"src\"),\n offsetWidth: this.offsetWidth,\n offsetHeight: this.offsetHeight,\n compositionWidth: this._compositionWidth,\n compositionHeight: this._compositionHeight,\n });\n }\n }\n\n private _onIframeLoad() {\n // In video mode the iframe only ever loads about:blank; its load must not reset the video.\n if (!this._connected || this._videoSource) return;\n // The runtime posts its timeline once its scripts ran, before or after `load`, and every\n // host-initiated navigation clears `_ready` first. So a ready player already holds this\n // document's handshake (an opaque origin reads as null); a paused runtime never posts it again.\n const doc = this._getSameOriginIframeDocument();\n if (this._ready && doc === this._readyDocument) {\n // Its asset wait scanned at DOMContentLoaded; a script may have added first-frame media since.\n if (doc && this._hasPendingFirstFrameAssets(doc)) this._waitForAssetsReady(doc);\n return;\n }\n\n this._ready = false;\n // The runtime installs its bridge once its scripts ran and posts `ready`, which can come before\n // the iframe's load event. Do not erase that authoritative handshake here: doing so strands\n // retained data set after load until a second `ready` that never comes. Source setters and\n // sandbox-policy reloads already clear bridge readiness before starting a navigation.\n this._invalidateAssetsWait();\n this._releaseDocument();\n this.probe.start();\n }\n\n private _setupControls() {\n if (this.controlsApi) return;\n this.controlsApi = setupControls(\n this.shadow,\n this.muted,\n this._volume,\n this.getAttribute(\"speed-presets\"),\n {\n onPlay: () => this.play(),\n onPause: () => this.pause(),\n onSeek: (f) => this.seek(f * this._duration),\n onScrubStart: () => {\n this._scrubbing = true;\n },\n onScrubEnd: () => {\n this._scrubbing = false;\n // Settle: a normal (silent) seek pauses the proxy audio at the final\n // scrub position, matching the paused playhead.\n this.seek(this._currentTime);\n },\n onSpeedChange: (s) => void (this.playbackRate = s),\n onMuteToggle: () => void (this.muted = !this.muted),\n onVolumeChange: (v) => void (this.volume = v),\n },\n this._isAudioLocked(),\n );\n }\n\n // Test-instrumentation pass-throughs (match original field names).\n get _audioOwner() {\n return this._media.audioOwner;\n }\n get _parentMedia() {\n return this._media.entries;\n }\n _mirrorParentMediaTime(t: number, opts?: { force?: boolean }) {\n this._media.mirrorTime(t, opts);\n }\n _promoteToParentProxy() {\n let d: Document | null = null;\n try {\n d = this.iframe.contentDocument;\n } catch {\n /* x-origin */\n }\n this._media.promoteToParentProxy(d, (t, o) => this._mirrorParentMediaTime(t, o));\n this._sendControl(\"set-media-output-muted\", { muted: true });\n }\n _observeDynamicMedia(doc: Document) {\n this._media.setupFromIframe(doc);\n }\n}\n\nif (!customElements.get(\"hyperframes-player\")) {\n customElements.define(\"hyperframes-player\", HyperframesPlayer);\n}\n\nexport { HyperframesPlayer };\nexport { formatTime, formatSpeed, SPEED_PRESETS } from \"./controls.js\";\nexport type { ControlsCallbacks, ControlsOptions } from \"./controls.js\";\nexport type { ShaderLoadingMode } from \"./shader-options.js\";\n","/** How long a composition is when nothing runs it: its authored `data-duration`, else the latest\n * end among its clips. A clip whose length is not known yet is counted, never guessed. */\n\nexport type CompositionDurationSource = \"authored\" | \"derived\" | \"unresolved\";\n\nexport interface CompositionDurationResult {\n seconds: number | null;\n source: CompositionDurationSource;\n /** Clips whose end is not known yet; a `derived` length is a lower bound while this is above 0. */\n pendingClips: number;\n /** Set only when source is \"unresolved\". */\n reason?: string;\n}\n\nexport interface ResolveCompositionDurationInput {\n authoredDurationSeconds: number | null;\n /** Each clip's end on the composition timeline, or null while its length is pending. */\n clipEndsSeconds: readonly (number | null)[];\n}\n\nexport const TIMING_TOLERANCE_SECONDS = 0.05;\n\nconst isPositiveFinite = (value: number | null): value is number =>\n value !== null && Number.isFinite(value) && value > 0;\n\nexport function resolveCompositionDuration(\n input: ResolveCompositionDurationInput,\n): CompositionDurationResult {\n const pendingClips = input.clipEndsSeconds.filter((end) => end === null).length;\n if (isPositiveFinite(input.authoredDurationSeconds)) {\n return { seconds: input.authoredDurationSeconds, source: \"authored\", pendingClips };\n }\n const known = input.clipEndsSeconds.filter(isPositiveFinite);\n if (known.length > 0) {\n return { seconds: Math.max(...known), source: \"derived\", pendingClips };\n }\n return {\n seconds: null,\n source: \"unresolved\",\n pendingClips,\n reason:\n pendingClips > 0\n ? \"every clip's length is still pending\"\n : \"no authored duration and no clip with a length\",\n };\n}\n","/**\n * Realm-independent \"what kind of node is this\" tests for composition DOM.\n *\n * THE INVARIANT: the runtime never assumes the composition DOM was built by the\n * same JavaScript realm the runtime is running in. It asks what a node IS (node\n * type, namespace, tag name), never which window's constructor made it.\n *\n * Why that matters. `node instanceof HTMLElement` compares against the\n * `HTMLElement` of the realm this module was loaded into, so it answers \"which\n * window built this node\", not \"what is this node\". One document can hold\n * elements from several realms at once, and a node keeps the prototype chain of\n * whichever realm created it however it got there.\n *\n * MEASURED, in the Studio preview, on some loads: `<html>`, `<head>` and\n * `<script>` carry the preview frame's prototypes while `<body>` and every\n * element under it carry the editor window's. Tagging each realm's\n * `Element.prototype` before any page script ran and reading the tag back off\n * the live nodes is how that was established. NOT established: how the nodes\n * come to be there. No `adoptNode`, `importNode` or `document.write` call was\n * caught doing it, and the frame is populated by `src`/`srcdoc`, so treat the\n * mechanism as an open question and the mixed realms as a fact.\n *\n * Either way `instanceof` does not throw and does not warn. It just returns\n * false, forever, for every element in the composition, and every guard written\n * as `if (!(node instanceof HTMLElement)) continue;` silently skips the whole\n * document. A whole pass goes dark with no error: the timed-element visibility\n * pass stops writing, so every scene paints on top of every other.\n *\n * `doc.defaultView.HTMLElement` is not a sufficient repair, which is why this\n * module replaced two hand-rolled attempts at it. It fixes the case where the\n * nodes belong to the document's own realm and still fails when they belong to\n * a third one. A structural test has no such case to get wrong.\n *\n * Scope of ownership: these predicates own the RUNTIME's answer to \"what kind of\n * node is this\", for HTML and SVG, which is everything a composition contains.\n * `packages/studio` still hand-rolls its own checks in several places and is not\n * covered by this module or by the lint guard that keeps `src/runtime` clean\n * (`scripts/lint-runtime-preview-guards.ts`).\n */\n\nconst XHTML_NS = \"http://www.w3.org/1999/xhtml\";\n\ninterface NodeLike {\n readonly nodeType?: unknown;\n readonly namespaceURI?: unknown;\n readonly localName?: unknown;\n}\n\n/** Any element node, from any realm (HTML, SVG, MathML). Mirrors `instanceof Element`. */\nexport function isElementNode(value: unknown): value is Element {\n return value != null && (value as NodeLike).nodeType === 1;\n}\n\n/**\n * An HTML element, from any realm. Mirrors `instanceof HTMLElement`, including\n * its exclusion of SVG/MathML: \"is an HTMLElement\" and \"is an element in the\n * XHTML namespace\" are the same set.\n */\nexport function isHtmlElement(value: unknown): value is HTMLElement {\n return isElementNode(value) && (value as NodeLike).namespaceURI === XHTML_NS;\n}\n\n/** Single tag, by value: a rest parameter here allocates on every call, and these\n * predicates run per element per seek and per animation frame. */\nfunction isHtmlTag(value: unknown, tag: string): boolean {\n return isHtmlElement(value) && (value as NodeLike).localName === tag;\n}\n\n/** `<video>`, from any realm. Mirrors `instanceof HTMLVideoElement`. */\nexport function isVideoElement(value: unknown): value is HTMLVideoElement {\n return isHtmlTag(value, \"video\");\n}\n\n/** `<audio>`, from any realm. Mirrors `instanceof HTMLAudioElement`. */\nexport function isAudioElement(value: unknown): value is HTMLAudioElement {\n return isHtmlTag(value, \"audio\");\n}\n\n/** `<video>` or `<audio>`, from any realm. Mirrors `instanceof HTMLMediaElement`. */\nexport function isMediaElement(value: unknown): value is HTMLMediaElement {\n return isVideoElement(value) || isAudioElement(value);\n}\n\n/** `<img>`, from any realm. Mirrors `instanceof HTMLImageElement`. */\nexport function isImageElement(value: unknown): value is HTMLImageElement {\n return isHtmlTag(value, \"img\");\n}\n\n/** `<canvas>`, from any realm. Mirrors `instanceof HTMLCanvasElement`. */\nexport function isCanvasElement(value: unknown): value is HTMLCanvasElement {\n return isHtmlTag(value, \"canvas\");\n}\n\n/** `<style>`, from any realm. Mirrors `instanceof HTMLStyleElement`. */\nexport function isStyleElement(value: unknown): value is HTMLStyleElement {\n return isHtmlTag(value, \"style\");\n}\n\n/** `<link>`, from any realm. Mirrors `instanceof HTMLLinkElement`. */\nexport function isLinkElement(value: unknown): value is HTMLLinkElement {\n return isHtmlTag(value, \"link\");\n}\n\n/**\n * An element that carries a CSS `style` map: HTML **or** SVG. Position edits and\n * transform writes apply to both (authored SVG `<text>`/shapes honour the CSS\n * `translate` longhand exactly as HTML does).\n */\nexport function isStylableElement(value: unknown): value is HTMLElement | SVGElement {\n return (\n isElementNode(value) &&\n ((value as NodeLike).namespaceURI === XHTML_NS ||\n (value as NodeLike).namespaceURI === \"http://www.w3.org/2000/svg\")\n );\n}\n","import { isHtmlElement } from \"./domRealm\";\n\n/** A `data-width`/`data-height` length in CSS px as the stage lays it out, fractions kept. */\nexport function parseLayoutDimension(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number.parseFloat(value);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n}\n\n/** The size the runtime reports to its host: whole px, read with `parseInt` as the renderer does. */\nexport function parseCompositionDimension(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number.parseInt(value, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n}\n\nexport function findRootCompositionElement(doc: ParentNode = document): HTMLElement | null {\n const explicitRoot = doc.querySelector('[data-composition-id][data-root=\"true\"]');\n if (isHtmlElement(explicitRoot)) return explicitRoot;\n // The first composition in document order never sits inside another one.\n return doc.querySelector<HTMLElement>(\"[data-composition-id]\");\n}\n","/**\n * Browser-safe authored composition contract.\n *\n * Source HTML is authored with data-start + data-duration + data-track-index.\n * data-end is compiler-derived and data-layer is legacy input only. Readers\n * accept legacy documents; writers always emit the canonical representation.\n */\n\nexport const COMPOSITION_CONTRACT_VERSION = 1 as const;\n\nexport const COMPOSITION_ATTRIBUTES = Object.freeze({\n start: \"data-start\",\n duration: \"data-duration\",\n trackIndex: \"data-track-index\",\n derivedEnd: \"data-end\",\n legacyTrack: \"data-layer\",\n} as const);\n\nexport const CANONICAL_AUTHORED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.start,\n COMPOSITION_ATTRIBUTES.duration,\n COMPOSITION_ATTRIBUTES.trackIndex,\n] as const);\n\nexport const DERIVED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n] as const);\n\nexport const LEGACY_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n] as const);\n\nexport type ReferenceExpression =\n | { kind: \"absolute\"; value: number }\n | { kind: \"reference\"; refId: string; offset: number };\n\nexport type ClipTimingDiagnosticCode =\n | \"invalid-start\"\n | \"unresolved-start-reference\"\n | \"invalid-duration\"\n | \"invalid-end\"\n | \"end-before-start\"\n | \"deprecated-end\"\n | \"conflicting-end\"\n | \"invalid-track-index\"\n | \"deprecated-layer\"\n | \"conflicting-layer\";\n\nexport interface ClipTimingDiagnostic {\n code: ClipTimingDiagnosticCode;\n attribute: string;\n value: string | null;\n}\n\nexport interface ClipAttributeReader {\n getAttribute(name: string): string | null;\n}\n\nexport interface ClipAttributeWriter extends ClipAttributeReader {\n setAttribute(name: string, value: string): void;\n removeAttribute(name: string): void;\n}\n\nexport interface ReadClipTimingOptions {\n /** Resolve a reference to the referenced clip's absolute end time. */\n resolveReferenceEnd?: (refId: string) => number | null | undefined;\n /** Start used when data-start is absent. Defaults to zero. */\n defaultStart?: number | null;\n}\n\nexport interface ClipTiming {\n startExpression: ReferenceExpression | null;\n start: number | null;\n duration: number | null;\n end: number | null;\n trackIndex: number;\n durationSource: \"duration\" | \"legacy-end\" | \"missing\" | \"invalid\";\n trackSource: \"track-index\" | \"legacy-layer\" | \"default\" | \"invalid\";\n diagnostics: ClipTimingDiagnostic[];\n}\n\nexport interface ClipTimingUpdate {\n start?: number | string | ReferenceExpression;\n duration?: number | null;\n trackIndex?: number | null;\n}\n\nexport class ClipTimingWriteError extends Error {\n readonly code: \"invalid-start\" | \"invalid-duration\" | \"invalid-track-index\";\n\n constructor(code: ClipTimingWriteError[\"code\"], message: string) {\n super(message);\n this.name = \"ClipTimingWriteError\";\n this.code = code;\n }\n}\n\n/** Parse a value to a finite number, or null if it is absent/invalid. */\nexport function parseNumeric(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nconst REFERENCE_ID_PATTERN = /^[A-Za-z0-9_.:-]+$/;\n\nfunction isAsciiDigitAt(value: string, index: number): boolean {\n const code = value.charCodeAt(index);\n return code >= 48 && code <= 57;\n}\n\nfunction skipDigitsLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && isAsciiDigitAt(value, cursor)) cursor--;\n return cursor;\n}\n\nfunction skipWhitespaceLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && (value[cursor] ?? \"\").trim() === \"\") cursor--;\n return cursor;\n}\n\nfunction findMagnitudeStart(value: string): number | null {\n const last = value.length - 1;\n if (!isAsciiDigitAt(value, last)) return null;\n let cursor = skipDigitsLeft(value, last);\n if (value[cursor] === \".\") cursor = skipDigitsLeft(value, cursor - 1);\n return cursor + 1;\n}\n\nfunction parseReferenceOffset(\n value: string,\n): { refId: string; operator: \"+\" | \"-\"; magnitude: number } | null {\n const magnitudeStart = findMagnitudeStart(value);\n if (magnitudeStart == null) return null;\n const operatorIndex = skipWhitespaceLeft(value, magnitudeStart - 1);\n const operator = value[operatorIndex];\n if (operator !== \"+\" && operator !== \"-\") return null;\n\n const refId = value.slice(0, operatorIndex).trim();\n if (!REFERENCE_ID_PATTERN.test(refId)) return null;\n const magnitude = Number(value.slice(magnitudeStart));\n if (!Number.isFinite(magnitude)) return null;\n return { refId, operator, magnitude };\n}\n\n/**\n * Parse the data-start grammar: absolute seconds, `clip-id`, or\n * `clip-id +/- offset`, where references resolve to the referenced clip's end.\n */\nexport function parseStartExpression(raw: string | null | undefined): ReferenceExpression | null {\n const normalized = (raw ?? \"\").trim();\n if (!normalized) return null;\n const absolute = parseNumeric(normalized);\n if (absolute != null) return { kind: \"absolute\", value: absolute };\n if (REFERENCE_ID_PATTERN.test(normalized)) {\n return { kind: \"reference\", refId: normalized, offset: 0 };\n }\n const reference = parseReferenceOffset(normalized);\n if (!reference) return null;\n return {\n kind: \"reference\",\n refId: reference.refId,\n offset: reference.operator === \"-\" ? -reference.magnitude : reference.magnitude,\n };\n}\n\nfunction pushDiagnostic(\n diagnostics: ClipTimingDiagnostic[],\n code: ClipTimingDiagnosticCode,\n attribute: string,\n value: string | null,\n): void {\n diagnostics.push({ code, attribute, value });\n}\n\nfunction resolveStart(\n expression: ReferenceExpression | null,\n rawStart: string | null,\n options: ReadClipTimingOptions,\n diagnostics: ClipTimingDiagnostic[],\n): number | null {\n if (rawStart == null || rawStart.trim() === \"\") {\n return options.defaultStart === undefined ? 0 : options.defaultStart;\n }\n if (!expression) {\n pushDiagnostic(diagnostics, \"invalid-start\", COMPOSITION_ATTRIBUTES.start, rawStart);\n return null;\n }\n if (expression.kind === \"absolute\") return Math.max(0, expression.value);\n const referencedEnd = options.resolveReferenceEnd?.(expression.refId);\n if (referencedEnd == null || !Number.isFinite(referencedEnd)) {\n pushDiagnostic(\n diagnostics,\n \"unresolved-start-reference\",\n COMPOSITION_ATTRIBUTES.start,\n rawStart,\n );\n return null;\n }\n return Math.max(0, referencedEnd + expression.offset);\n}\n\ntype DurationRead = Pick<ClipTiming, \"duration\" | \"end\" | \"durationSource\">;\ntype TrackRead = Pick<ClipTiming, \"trackIndex\" | \"trackSource\">;\n\n// Float pairs like `data-start=\"0.1\" + data-duration=\"0.2\"` add to\n// `0.30000000000000004`, so the compiler-emitted `data-end` string can be a few\n// ulps off the sum a reader computes from the canonical inputs. Any drift below\n// this ceiling is treated as equal for the derived-end reconciliation check —\n// well below one 60fps frame (~16.67 ms) and orders of magnitude above the\n// worst realistic IEEE-754 residual (~2e-16 s).\nconst DERIVED_END_EQUALITY_EPSILON_SECONDS = 1e-9;\n\nfunction derivedEndsAreConsistent(parsedEnd: number, canonicalEnd: number): boolean {\n return Math.abs(parsedEnd - canonicalEnd) <= DERIVED_END_EQUALITY_EPSILON_SECONDS;\n}\n\n// Reconcile a `data-end` attribute paired with canonical `data-duration`.\n//\n// The compiler (`compileTimingAttrs` in @hyperframes/core) writes\n// `data-end = data-start + data-duration` into the bundled HTML so the runtime\n// can key off a single attribute. That derived attribute is legitimate — it is\n// not a legacy authoring shape. Treating it as `deprecated-end` here made the\n// linter fire `deprecated_data_end` on the compiler's own output whenever\n// StaticGuard re-validated a bundled composition, producing a false-positive\n// cluster reported in the field (n=25+ across cli-feedback crons 61-68).\n//\n// We keep `deprecated-end` for the truly legacy shape (see `readLegacyEnd` —\n// `data-end` present without `data-duration`) and for the *conflicting* case\n// where an author left a stale `data-end` behind a canonical duration edit; a\n// stale end must not be silently overridden without a diagnostic.\nfunction diagnoseDerivedEnd(\n rawEnd: string,\n canonicalEnd: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): void {\n const parsedEnd = parseNumeric(rawEnd);\n if (parsedEnd == null) {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (canonicalEnd == null) {\n // Canonical duration exists but resolved end is unknown (e.g. unresolved\n // reference start). Preserve prior behavior — flag as deprecated so authors\n // remove the stale companion attribute.\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (derivedEndsAreConsistent(parsedEnd, canonicalEnd)) {\n // Compiler-derived (or manually consistent) `data-end` alongside\n // `data-duration` — silent. No diagnostic.\n return;\n }\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"conflicting-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n}\n\nfunction readCanonicalDuration(\n rawDuration: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const duration = parseNumeric(rawDuration);\n if (duration == null || duration < 0) {\n pushDiagnostic(diagnostics, \"invalid-duration\", COMPOSITION_ATTRIBUTES.duration, rawDuration);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return {\n duration,\n end: start == null ? null : start + duration,\n durationSource: \"duration\",\n };\n}\n\nfunction readLegacyEnd(\n rawEnd: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n const end = parseNumeric(rawEnd);\n if (end == null) {\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n if (start == null) return { duration: null, end, durationSource: \"legacy-end\" };\n if (end < start) {\n pushDiagnostic(diagnostics, \"end-before-start\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return { duration: end - start, end, durationSource: \"legacy-end\" };\n}\n\nfunction readDuration(\n attributes: ClipAttributeReader,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const rawDuration = attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration);\n const rawEnd = attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n if (rawDuration == null) {\n return rawEnd == null\n ? { duration: null, end: null, durationSource: \"missing\" }\n : readLegacyEnd(rawEnd, start, diagnostics);\n }\n const canonical = readCanonicalDuration(rawDuration, start, diagnostics);\n if (rawEnd != null) diagnoseDerivedEnd(rawEnd, canonical.end, diagnostics);\n return canonical;\n}\n\nfunction readTrackValue(\n rawValue: string,\n attribute: string,\n source: \"track-index\" | \"legacy-layer\",\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const trackIndex = parseNumeric(rawValue);\n if (trackIndex == null || !Number.isInteger(trackIndex)) {\n pushDiagnostic(diagnostics, \"invalid-track-index\", attribute, rawValue);\n return { trackIndex: 0, trackSource: \"invalid\" };\n }\n return { trackIndex, trackSource: source };\n}\n\nfunction readTrack(\n attributes: ClipAttributeReader,\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const rawTrack = attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n const rawLayer = attributes.getAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n if (rawTrack == null && rawLayer == null) return { trackIndex: 0, trackSource: \"default\" };\n if (rawTrack == null && rawLayer != null) {\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n return readTrackValue(\n rawLayer,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n \"legacy-layer\",\n diagnostics,\n );\n }\n const canonical = readTrackValue(\n rawTrack ?? \"\",\n COMPOSITION_ATTRIBUTES.trackIndex,\n \"track-index\",\n diagnostics,\n );\n if (rawLayer == null) return canonical;\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n const parsedLayer = parseNumeric(rawLayer);\n if (parsedLayer != null && parsedLayer !== canonical.trackIndex) {\n pushDiagnostic(diagnostics, \"conflicting-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n }\n return canonical;\n}\n\n/** Read canonical timing, accepting legacy attributes without preferring them. */\nexport function readClipTiming(\n attributes: ClipAttributeReader,\n options: ReadClipTimingOptions = {},\n): ClipTiming {\n const diagnostics: ClipTimingDiagnostic[] = [];\n const rawStart = attributes.getAttribute(COMPOSITION_ATTRIBUTES.start);\n const startExpression = parseStartExpression(rawStart);\n const start = resolveStart(startExpression, rawStart, options, diagnostics);\n const duration = readDuration(attributes, start, diagnostics);\n const track = readTrack(attributes, diagnostics);\n return { startExpression, start, ...duration, ...track, diagnostics };\n}\n\nfunction serializeStartNumber(start: number): string {\n if (!Number.isFinite(start)) {\n throw new ClipTimingWriteError(\"invalid-start\", \"start must be finite\");\n }\n return String(start);\n}\n\nfunction serializeStartReference(start: ReferenceExpression): string {\n if (start.kind === \"absolute\") return serializeStartNumber(start.value);\n if (!start.refId || !Number.isFinite(start.offset)) {\n throw new ClipTimingWriteError(\n \"invalid-start\",\n \"reference start must have an id and finite offset\",\n );\n }\n if (start.offset === 0) return start.refId;\n return `${start.refId} ${start.offset < 0 ? \"-\" : \"+\"} ${Math.abs(start.offset)}`;\n}\n\nfunction serializeStart(start: ClipTimingUpdate[\"start\"]): string {\n if (typeof start === \"number\") return serializeStartNumber(start);\n if (typeof start === \"object\" && start != null) return serializeStartReference(start);\n if (typeof start === \"string\" && parseStartExpression(start)) return start.trim();\n throw new ClipTimingWriteError(\"invalid-start\", `invalid start expression: ${start ?? \"\"}`);\n}\n\nfunction writeDuration(\n attributes: ClipAttributeWriter,\n duration: number | null | undefined,\n current: ClipTiming,\n): void {\n if (duration === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.duration);\n return;\n }\n if (duration === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n current.duration != null\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(current.duration));\n }\n return;\n }\n if (!Number.isFinite(duration) || duration < 0) {\n throw new ClipTimingWriteError(\n \"invalid-duration\",\n \"duration must be a finite, non-negative number\",\n );\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(duration));\n}\n\nfunction writeTrack(\n attributes: ClipAttributeWriter,\n trackIndex: number | null | undefined,\n current: ClipTiming,\n): void {\n if (trackIndex === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n return;\n }\n if (trackIndex === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex) == null &&\n current.trackSource === \"legacy-layer\"\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(current.trackIndex));\n }\n return;\n }\n if (!Number.isFinite(trackIndex) || !Number.isInteger(trackIndex)) {\n throw new ClipTimingWriteError(\"invalid-track-index\", \"trackIndex must be a finite integer\");\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(trackIndex));\n}\n\n/** Write only canonical authored timing and remove derived/legacy source attributes. */\nexport function writeClipTiming(\n attributes: ClipAttributeWriter,\n update: ClipTimingUpdate,\n): ClipTiming {\n const current = readClipTiming(attributes);\n if (update.start !== undefined) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.start, serializeStart(update.start));\n }\n writeDuration(attributes, update.duration, current);\n writeTrack(attributes, update.trackIndex, current);\n // A legacy end paired with an unresolved reference start cannot be converted\n // to data-duration without a reference resolver. On a duration-omitting edit\n // (for example, moving only the track), preserve that sole duration source\n // instead of canonicalizing it into data loss.\n const preserveUnresolvedLegacyEnd =\n update.duration === undefined &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd) != null &&\n current.duration == null;\n if (!preserveUnresolvedLegacyEnd) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n }\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n return readClipTiming(attributes);\n}\n","/** The one rule for a media element's timeline length; hosts only supply the source length. */\n\nimport { parseNumeric } from \"./compositionContract.js\";\n\nexport type MediaTag = \"video\" | \"audio\" | \"img\";\n\n/** Reads a named attribute's raw string value. Works for a DOM Element's `getAttribute`\n * (browser or linkedom) or a plain attrs-record lookup -- callers pick whichever they have. */\nexport type AttrReader = (name: string) => string | null | undefined;\n\nconst nonNegative = (raw: string | null | undefined): number | null => {\n const value = parseNumeric(raw);\n return value !== null && value >= 0 ? value : null;\n};\n\n/** Playback offset into the source: `data-playback-start`, falling back to the older\n * `data-media-start`; an invalid or negative value falls through to the next. One reader for\n * every host -- the runtime and `htmlParser` used to read this two different ways. */\nexport function readMediaOffsetSeconds(getAttr: AttrReader): number {\n return (\n nonNegative(getAttr(\"data-playback-start\")) ?? nonNegative(getAttr(\"data-media-start\")) ?? 0\n );\n}\n\n/** The one bound on a clip's playback rate; core re-exports it for the rate lane. */\nexport const MIN_PLAYBACK_RATE = 0.1;\nexport const MAX_PLAYBACK_RATE = 10;\n\n/** A playback rate clamped to the bounds above; anything non-positive or non-finite is 1. */\nexport function clampPlaybackRate(raw: number): number {\n return Number.isFinite(raw) && raw > 0\n ? Math.max(MIN_PLAYBACK_RATE, Math.min(MAX_PLAYBACK_RATE, raw))\n : 1;\n}\n\n/** `data-playback-rate`, parsed like the browser parses a native rate (`\"2x\"` is 2), else\n * `fallback` (a media element's own `defaultPlaybackRate` in the browser), clamped. */\nexport function readPlaybackRate(getAttr: AttrReader, fallback = 1): number {\n const authored = Number.parseFloat(getAttr(\"data-playback-rate\") ?? \"\");\n return clampPlaybackRate(Number.isFinite(authored) && authored > 0 ? authored : fallback);\n}\n\n/** `data-duration` when it is a positive number, else null. */\nexport function readDataDurationSeconds(getAttr: AttrReader): number | null {\n const duration = parseNumeric(getAttr(\"data-duration\"));\n return duration !== null && duration > 0 ? duration : null;\n}\n\n/** An authored trim: `data-duration` if positive, else `data-end - start` if `data-end` is\n * authored, else null (nothing authored). */\nexport function readAuthoredDurationSeconds(\n getAttr: AttrReader,\n startSeconds: number,\n): number | null {\n const duration = readDataDurationSeconds(getAttr);\n if (duration !== null) return duration;\n const end = parseNumeric(getAttr(\"data-end\"));\n return end !== null ? end - startSeconds : null;\n}\n\n/** The dropped-image default: what Studio gives a freshly dropped image before any trim.\n * One owner; everything needing an untrimmed image length imports this. */\nexport const DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS = 3;\n\n/** The arithmetic alone: no attribute reading, no DOM, no probing. */\nexport function resolveNaturalDurationSeconds(\n sourceDurationSeconds: number,\n mediaStartSeconds: number,\n playbackRate: number,\n): number | null {\n if (!Number.isFinite(sourceDurationSeconds)) return null;\n const remaining = Math.max(0, sourceDurationSeconds - mediaStartSeconds);\n return remaining / clampPlaybackRate(playbackRate);\n}\n\nexport type MediaDurationSource = \"authored\" | \"media\" | \"default\" | \"pending\";\n\nexport interface MediaDurationResult {\n seconds: number | null;\n source: MediaDurationSource;\n /** Set only when source is \"pending\": why nothing could be resolved yet. */\n reason?: string;\n}\n\nexport interface ResolveMediaDurationInput {\n tag: MediaTag;\n /** From `readAuthoredDurationSeconds`, or null when nothing is authored. */\n authoredDurationSeconds: number | null;\n /** The source's own length once a probe has answered, else null. Ignored for `img`. */\n sourceDurationSeconds: number | null;\n mediaStartSeconds: number;\n playbackRate: number;\n}\n\nexport function resolveMediaDuration(input: ResolveMediaDurationInput): MediaDurationResult {\n const { tag, authoredDurationSeconds, sourceDurationSeconds, mediaStartSeconds, playbackRate } =\n input;\n if (authoredDurationSeconds !== null && authoredDurationSeconds > 0) {\n return { seconds: authoredDurationSeconds, source: \"authored\" };\n }\n if (tag === \"img\") {\n return { seconds: DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS, source: \"default\" };\n }\n if (sourceDurationSeconds === null) {\n return { seconds: null, source: \"pending\", reason: \"source duration not yet probed\" };\n }\n const seconds = resolveNaturalDurationSeconds(\n sourceDurationSeconds,\n mediaStartSeconds,\n playbackRate,\n );\n if (seconds === null) {\n return { seconds: null, source: \"pending\", reason: \"source reported a non-finite duration\" };\n }\n return { seconds, source: \"media\" };\n}\n\n/** Readers not yet converted to `resolveMediaDuration`; each reader PR removes its entry, so this only shrinks. */\nexport const PENDING_MEDIA_DURATION_READERS = [\n \"core/src/runtime/init.ts (visibility end, clock, seek, WebAudio scheduling)\",\n \"core/src/compiler/timingCompiler.ts compileTag\",\n \"core/src/compiler/htmlCompiler.ts compileHtml\",\n \"producer/src/services/htmlCompiler.ts resolveMediaDuration\",\n \"engine/src/services/videoFrameExtractor.ts (extraction window)\",\n \"engine/src/services/audioMixer.ts\",\n \"parsers/src/htmlParser.ts (defaults to 5s, reads only data-media-start)\",\n \"studio (timelineDOM.ts, timelineElementHelpers.ts, useTimelineSyncCallbacks.ts)\",\n] as const;\n","/**\n * Audio FX chain: the one description of every effect that can be applied to an\n * audio track.\n *\n * Preview and render both run the same Web Audio graph — the studio in a live\n * AudioContext, the engine in an OfflineAudioContext inside the headless\n * browser it already drives. There is only one implementation of each effect,\n * so preview predicting the render is a property of the architecture rather\n * than something to measure and defend.\n *\n * This file holds what both ends need to agree on: the parameter set for each\n * effect with its usable range, and the id of the graph builder that realises\n * it. Parameters are declared in the units a person thinks in (dB, ms, Hz);\n * the graph builders convert where the Web Audio node wants something else.\n */\n\nexport const HF_AUDIO_FX_ATTR = \"data-fx-chain\";\n\n/**\n * The same attribute as a `dataset` / `dataAttributes` key — the `data-` prefix\n * is not part of that spelling.\n *\n * Derived rather than restated: the studio writes through\n * `HF_AUDIO_FX_ATTR` and reads through the key, so a hardcoded `\"fx-chain\"`\n * on the read side is a rename waiting to half-land.\n */\nexport const HF_AUDIO_FX_DATA_KEY = HF_AUDIO_FX_ATTR.slice(\"data-\".length);\n\n/** Chain files are versioned; a reader must refuse a version it doesn't know. */\nexport const HF_AUDIO_FX_CHAIN_VERSION = 1;\n\nexport type HfAudioFxGroup = \"filter\" | \"dynamics\" | \"nonlinear\" | \"time\";\n\nexport interface HfAudioFxNumberParam {\n kind: \"number\";\n key: string;\n label: string;\n /** Shown after the value in the panel; \"\" for a bare ratio. */\n unit: string;\n min: number;\n max: number;\n step: number;\n default: number;\n /** Frequency-style controls need a log knob to be usable. */\n scale?: \"linear\" | \"log\";\n /**\n * The knob is backed by an AudioParam, so an automation lane can drive it.\n *\n * Not every knob can be: a WaveShaper curve, a convolution impulse and a\n * worklet's `processorOptions` are all set wholesale rather than scheduled.\n * A graph builder must expose an AudioParam for every parameter flagged here\n * — `audioFxGraph.test.ts` builds each effect and checks it.\n */\n automatable?: boolean;\n /** One line explaining what turning this does, shown on the control. */\n hint?: string;\n}\n\nexport interface HfAudioFxEnumParam {\n kind: \"enum\";\n key: string;\n label: string;\n options: readonly { value: string; label: string }[];\n default: string;\n hint?: string;\n}\n\nexport type HfAudioFxParam = HfAudioFxNumberParam | HfAudioFxEnumParam;\n\nexport type HfAudioFxParamValues = Record<string, number | string>;\n\nexport interface HfAudioFxDef {\n id: string;\n label: string;\n group: HfAudioFxGroup;\n /** One sentence on what the effect is for, shown when adding it. */\n description: string;\n params: readonly HfAudioFxParam[];\n /**\n * Identifier for the Web Audio graph builder that realises this effect. Kept\n * as a string rather than a function so this module stays free of browser\n * globals and can be imported by the engine and the linter.\n */\n web: string;\n}\n\nconst freq = (\n key = \"frequency\",\n label = \"Frequency\",\n def = 1000,\n min = 20,\n max = 20000,\n): HfAudioFxNumberParam => ({\n kind: \"number\",\n key,\n label,\n unit: \"Hz\",\n min,\n max,\n step: 1,\n default: def,\n scale: \"log\",\n automatable: true,\n});\n\nconst qParam = (def = 0.707, hint = \"Bandwidth — higher is narrower.\"): HfAudioFxNumberParam => ({\n kind: \"number\",\n key: \"q\",\n label: \"Q\",\n unit: \"\",\n min: 0.1,\n max: 20,\n step: 0.01,\n default: def,\n scale: \"log\",\n automatable: true,\n hint,\n});\n\nconst gainDb = (min = -40, max = 40, def = 0): HfAudioFxNumberParam => ({\n kind: \"number\",\n key: \"gain\",\n label: \"Gain\",\n unit: \"dB\",\n min,\n max,\n step: 0.1,\n default: def,\n automatable: true,\n});\n\nconst poles: HfAudioFxEnumParam = {\n kind: \"enum\",\n key: \"poles\",\n label: \"Slope\",\n options: [\n { value: \"1\", label: \"6 dB/oct\" },\n { value: \"2\", label: \"12 dB/oct\" },\n ],\n default: \"2\",\n hint: \"Two poles is the usual biquad; one pole is gentler.\",\n};\n\n/**\n * Every effect, in panel order. Ranges are the usable span for each control;\n * a value that survives `normalizeAudioFxParams` is always safe to realise.\n */\nexport const HF_AUDIO_FX: readonly HfAudioFxDef[] = [\n {\n id: \"gain\",\n label: \"Gain\",\n group: \"dynamics\",\n description: \"Raise or lower the whole signal. Automate it to duck under something else.\",\n // Cuts go deep, boosts stay modest: this is a level stage for making room,\n // and a chain that can add 40 dB clips long before it is useful.\n params: [gainDb(-60, 12, 0)],\n web: \"gain-node\",\n },\n {\n id: \"peaking\",\n label: \"Peaking EQ\",\n group: \"filter\",\n description: \"Boost or cut a band, leaving everything either side alone.\",\n params: [freq(\"frequency\", \"Frequency\", 1000), gainDb(-40, 40, 0), qParam(1)],\n web: \"biquad-peaking\",\n },\n {\n id: \"lowshelf\",\n label: \"Low Shelf\",\n group: \"filter\",\n description: \"Lift or drop everything below the corner frequency.\",\n // No Q: the Web Audio spec leaves it unused for shelving filters, so the\n // control moved nothing — and being automatable, a lane drawn on it would\n // have been silently inert.\n params: [freq(\"frequency\", \"Frequency\", 200, 20, 2000), gainDb(-40, 40, 0)],\n web: \"biquad-lowshelf\",\n },\n {\n id: \"highshelf\",\n label: \"High Shelf\",\n group: \"filter\",\n description: \"Lift or drop everything above the corner frequency.\",\n params: [freq(\"frequency\", \"Frequency\", 4000, 500, 20000), gainDb(-40, 40, 0)],\n web: \"biquad-highshelf\",\n },\n {\n id: \"highpass\",\n label: \"High-pass\",\n group: \"filter\",\n description: \"Remove low frequencies — the usual fix for rumble on a voice.\",\n params: [freq(\"frequency\", \"Cutoff\", 300, 20, 20000), qParam(0.707), poles],\n web: \"biquad-highpass\",\n },\n {\n id: \"lowpass\",\n label: \"Low-pass\",\n group: \"filter\",\n description: \"Remove high frequencies — darkens or muffles a track.\",\n params: [freq(\"frequency\", \"Cutoff\", 8000, 100, 20000), qParam(0.707), poles],\n web: \"biquad-lowpass\",\n },\n\n {\n id: \"compressor\",\n label: \"Compressor\",\n group: \"dynamics\",\n description: \"Pull loud parts down so the quiet ones can come up.\",\n params: [\n {\n kind: \"number\",\n key: \"threshold\",\n label: \"Threshold\",\n unit: \"dB\",\n min: -60,\n max: 0,\n step: 0.5,\n default: -24,\n hint: \"Level above which the compressor starts working.\",\n },\n {\n kind: \"number\",\n key: \"ratio\",\n label: \"Ratio\",\n unit: \":1\",\n min: 1,\n max: 20,\n step: 0.1,\n default: 4,\n },\n {\n kind: \"number\",\n key: \"attack\",\n label: \"Attack\",\n unit: \"ms\",\n min: 0.01,\n max: 2000,\n step: 0.1,\n default: 20,\n scale: \"log\",\n },\n {\n kind: \"number\",\n key: \"release\",\n label: \"Release\",\n unit: \"ms\",\n min: 0.01,\n max: 9000,\n step: 1,\n default: 250,\n scale: \"log\",\n },\n {\n kind: \"number\",\n key: \"knee\",\n label: \"Knee\",\n unit: \"\",\n min: 1,\n max: 8,\n step: 0.01,\n default: 2.83,\n hint: \"1 is a hard corner; higher eases into it.\",\n },\n {\n kind: \"number\",\n key: \"makeup\",\n label: \"Makeup\",\n unit: \"dB\",\n min: 0,\n max: 36,\n step: 0.1,\n default: 0,\n },\n {\n kind: \"number\",\n key: \"mix\",\n label: \"Mix\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 1,\n hint: \"Below 1 blends the dry signal back in.\",\n },\n ],\n web: \"worklet-compressor\",\n },\n {\n id: \"limiter\",\n label: \"Limiter\",\n group: \"dynamics\",\n description: \"Hard ceiling — nothing gets past the limit.\",\n params: [\n {\n kind: \"number\",\n key: \"limit\",\n label: \"Ceiling\",\n unit: \"dB\",\n min: -24,\n max: 0,\n step: 0.1,\n default: -1,\n },\n {\n kind: \"number\",\n key: \"attack\",\n label: \"Attack\",\n unit: \"ms\",\n min: 0.1,\n max: 80,\n step: 0.1,\n default: 5,\n },\n {\n kind: \"number\",\n key: \"release\",\n label: \"Release\",\n unit: \"ms\",\n min: 1,\n max: 8000,\n step: 1,\n default: 50,\n scale: \"log\",\n },\n {\n kind: \"number\",\n key: \"level_out\",\n label: \"Output\",\n unit: \"dB\",\n min: -24,\n max: 24,\n step: 0.1,\n default: 0,\n },\n ],\n web: \"worklet-limiter\",\n },\n {\n id: \"gate\",\n label: \"Noise Gate\",\n group: \"dynamics\",\n description: \"Silence the track when it drops below the threshold.\",\n params: [\n {\n kind: \"number\",\n key: \"threshold\",\n label: \"Threshold\",\n unit: \"dB\",\n min: -80,\n max: 0,\n step: 0.5,\n default: -35,\n },\n {\n kind: \"number\",\n key: \"range\",\n label: \"Range\",\n unit: \"dB\",\n min: -80,\n max: 0,\n step: 0.5,\n default: -24,\n hint: \"How far down the gate pulls when closed.\",\n },\n {\n kind: \"number\",\n key: \"ratio\",\n label: \"Ratio\",\n unit: \":1\",\n min: 1,\n max: 20,\n step: 0.1,\n default: 10,\n },\n {\n kind: \"number\",\n key: \"attack\",\n label: \"Attack\",\n unit: \"ms\",\n min: 0.01,\n max: 9000,\n step: 0.1,\n default: 1,\n scale: \"log\",\n },\n {\n kind: \"number\",\n key: \"release\",\n label: \"Release\",\n unit: \"ms\",\n min: 0.01,\n max: 9000,\n step: 1,\n default: 100,\n scale: \"log\",\n },\n {\n kind: \"number\",\n key: \"knee\",\n label: \"Knee\",\n unit: \"\",\n min: 1,\n max: 8,\n step: 0.01,\n default: 2.83,\n },\n ],\n web: \"worklet-gate\",\n },\n\n {\n id: \"saturate\",\n label: \"Saturation\",\n group: \"nonlinear\",\n description: \"Soft-clip the waveform for warmth or outright distortion.\",\n params: [\n {\n kind: \"enum\",\n key: \"type\",\n label: \"Curve\",\n options: [\n { value: \"tanh\", label: \"Tanh\" },\n { value: \"atan\", label: \"Arctan\" },\n { value: \"cubic\", label: \"Cubic\" },\n { value: \"exp\", label: \"Exponential\" },\n { value: \"alg\", label: \"Algebraic\" },\n { value: \"quintic\", label: \"Quintic\" },\n { value: \"sin\", label: \"Sine\" },\n { value: \"erf\", label: \"Error function\" },\n { value: \"hard\", label: \"Hard clip\" },\n ],\n default: \"tanh\",\n },\n {\n kind: \"number\",\n key: \"threshold\",\n label: \"Threshold\",\n unit: \"dB\",\n min: -40,\n max: 0,\n step: 0.1,\n default: -6,\n },\n {\n kind: \"number\",\n key: \"output\",\n automatable: true,\n label: \"Output\",\n unit: \"dB\",\n min: -24,\n max: 24,\n step: 0.1,\n default: 0,\n },\n {\n kind: \"number\",\n key: \"oversample\",\n label: \"Oversample\",\n unit: \"x\",\n min: 1,\n max: 8,\n step: 1,\n default: 4,\n hint: \"Higher costs more but keeps aliasing down.\",\n },\n ],\n web: \"waveshaper\",\n },\n {\n id: \"bitcrush\",\n label: \"Bitcrush\",\n group: \"nonlinear\",\n description: \"Drop bit depth and sample rate for a lo-fi, digital sound.\",\n params: [\n {\n kind: \"number\",\n key: \"bits\",\n label: \"Bit depth\",\n unit: \"bit\",\n min: 1,\n max: 32,\n step: 0.1,\n default: 8,\n },\n {\n kind: \"number\",\n key: \"samples\",\n label: \"Sample hold\",\n unit: \"x\",\n min: 1,\n max: 250,\n step: 1,\n default: 1,\n hint: \"Repeats each sample N times — a crude downsample.\",\n },\n {\n kind: \"number\",\n key: \"mix\",\n label: \"Mix\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 1,\n },\n ],\n web: \"worklet-bitcrush\",\n },\n {\n id: \"pitchshift\",\n label: \"Pitch shift\",\n group: \"time\",\n // The granular algorithm reads from a 100 ms grain, so its output runs a\n // constant ~50 ms behind its input and nothing in the graph subtracts that\n // — there is no latency/pre-roll concept here yet. It is inaudible on its\n // own and audible against picture or against an unshifted track, so it is\n // stated rather than hidden. `semitones: 0` bypasses the node entirely.\n description: \"Shifts pitch up or down without changing playback speed. Adds ~50 ms of latency.\",\n params: [\n {\n kind: \"number\",\n key: \"semitones\",\n label: \"Semitones\",\n unit: \"st\",\n min: -12,\n max: 12,\n step: 1,\n default: 0,\n },\n {\n kind: \"number\",\n key: \"mix\",\n label: \"Mix\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 1,\n },\n ],\n web: \"worklet-pitchshift\",\n },\n\n {\n id: \"delay\",\n label: \"Delay\",\n group: \"time\",\n description: \"Repeating echoes behind the dry signal.\",\n params: [\n {\n kind: \"number\",\n key: \"time\",\n automatable: true,\n label: \"Time\",\n unit: \"ms\",\n min: 1,\n max: 5000,\n step: 1,\n default: 250,\n scale: \"log\",\n },\n // aecho rejects a decay of exactly 0 (\"out of allowed range: (0, 1]\"),\n // so the floor is a hair above silence rather than at it.\n {\n kind: \"number\",\n key: \"feedback\",\n automatable: true,\n label: \"Feedback\",\n unit: \"\",\n min: 0.01,\n max: 0.95,\n step: 0.01,\n default: 0.35,\n },\n {\n kind: \"number\",\n key: \"mix\",\n automatable: true,\n label: \"Mix\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.4,\n },\n ],\n web: \"delay-feedback\",\n },\n {\n id: \"chorus\",\n label: \"Chorus\",\n group: \"time\",\n description: \"Detuned copies of the signal for width and thickness.\",\n params: [\n {\n kind: \"number\",\n key: \"delay\",\n automatable: true,\n label: \"Delay\",\n unit: \"ms\",\n min: 1,\n max: 100,\n step: 0.1,\n default: 7,\n },\n {\n kind: \"number\",\n key: \"depth\",\n automatable: true,\n label: \"Depth\",\n unit: \"ms\",\n min: 0,\n max: 10,\n step: 0.01,\n default: 2,\n },\n {\n kind: \"number\",\n key: \"speed\",\n automatable: true,\n label: \"Rate\",\n unit: \"Hz\",\n min: 0.01,\n max: 10,\n step: 0.01,\n default: 1,\n },\n {\n kind: \"number\",\n key: \"mix\",\n automatable: true,\n label: \"Mix\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.5,\n },\n ],\n web: \"chorus-lfo\",\n },\n {\n id: \"phaser\",\n label: \"Phaser\",\n group: \"time\",\n description: \"Sweeping notches moving through the spectrum.\",\n params: [\n {\n kind: \"number\",\n key: \"in_gain\",\n automatable: true,\n label: \"Input\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.4,\n },\n {\n kind: \"number\",\n key: \"out_gain\",\n automatable: true,\n label: \"Output\",\n unit: \"\",\n min: 0,\n max: 2,\n step: 0.01,\n default: 0.74,\n },\n // aphaser advertises a delay range starting at 0 but rejects it at\n // runtime with \"delay is too small\"; 0.1 ms is the real floor.\n {\n kind: \"number\",\n key: \"delay\",\n label: \"Delay\",\n unit: \"ms\",\n min: 0.1,\n max: 5,\n step: 0.1,\n default: 3,\n },\n {\n kind: \"number\",\n key: \"decay\",\n label: \"Decay\",\n unit: \"\",\n min: 0,\n max: 0.99,\n step: 0.01,\n default: 0.4,\n },\n {\n kind: \"number\",\n key: \"speed\",\n automatable: true,\n label: \"Rate\",\n unit: \"Hz\",\n min: 0.1,\n max: 2,\n step: 0.01,\n default: 0.5,\n },\n {\n kind: \"enum\",\n key: \"type\",\n label: \"Waveform\",\n options: [\n { value: \"0\", label: \"Triangular\" },\n { value: \"1\", label: \"Sinusoidal\" },\n ],\n default: \"0\",\n },\n ],\n web: \"allpass-phaser\",\n },\n {\n id: \"reverb\",\n label: \"Reverb\",\n group: \"time\",\n description:\n \"Room tail. Both ends convolve the same generated impulse, so preview matches render.\",\n params: [\n {\n kind: \"number\",\n key: \"size\",\n label: \"Room size\",\n unit: \"\",\n min: 0.05,\n max: 1,\n step: 0.01,\n default: 0.7,\n },\n {\n kind: \"number\",\n key: \"damping\",\n label: \"Damping\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.5,\n hint: \"Higher rolls the top off the tail faster.\",\n },\n {\n kind: \"number\",\n key: \"wet\",\n automatable: true,\n label: \"Wet\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.35,\n },\n {\n kind: \"number\",\n key: \"dry\",\n automatable: true,\n label: \"Dry\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.7,\n },\n ],\n web: \"convolver\",\n },\n] as const;\n\nconst BY_ID = new Map(HF_AUDIO_FX.map((d) => [d.id, d]));\n\nexport function getAudioFxDef(id: string): HfAudioFxDef | undefined {\n return BY_ID.get(id);\n}\n\nexport const HF_AUDIO_FX_IDS: readonly string[] = HF_AUDIO_FX.map((d) => d.id);\n\n/** Every parameter at its declared default, ready to seed a freshly added effect. */\nexport function defaultAudioFxParams(id: string): HfAudioFxParamValues {\n const def = BY_ID.get(id);\n if (!def) return {};\n const out: HfAudioFxParamValues = {};\n for (const p of def.params) out[p.key] = p.default;\n return out;\n}\n\n/**\n * Clamp and fill a parameter set so it is always renderable: unknown keys are\n * dropped, missing keys take their default, numbers are clamped into their\n * declared range, and an unrecognised enum value falls back to its default.\n * A non-finite number is treated as missing rather than passed through, since\n * NaN reaching an AudioParam silences the node for the rest of the render.\n */\nexport function normalizeAudioFxParams(\n id: string,\n values: Readonly<HfAudioFxParamValues> | undefined,\n): HfAudioFxParamValues {\n const def = BY_ID.get(id);\n if (!def) return {};\n const out: HfAudioFxParamValues = {};\n for (const p of def.params) {\n const raw = values?.[p.key];\n if (p.kind === \"enum\") {\n const ok = p.options.some((o) => o.value === raw);\n out[p.key] = ok ? (raw as string) : p.default;\n continue;\n }\n // Only a number, or a string that actually spells one. `Number(null)`,\n // `Number(\"\")`, `Number(false)` and `Number([])` are all 0 and all pass\n // Number.isFinite, so a missing or blanked value used to clamp to 0 rather\n // than fall back to the declared default — and 0 is a legal value for most\n // of these knobs, so nothing downstream could tell. A compressor whose\n // threshold arrived as null sat at 0 dB and never engaged, silently,\n // instead of at its -24 dB default. `numberOrNull` in audioAutomation.ts\n // already guards exactly this.\n const n =\n typeof raw === \"number\"\n ? raw\n : typeof raw === \"string\" && raw.trim() !== \"\"\n ? Number(raw)\n : Number.NaN;\n out[p.key] = Number.isFinite(n) ? Math.min(p.max, Math.max(p.min, n)) : p.default;\n }\n return out;\n}\n\nexport interface HfAudioFxNode {\n /** Effect id from HF_AUDIO_FX. */\n type: string;\n /**\n * Stable handle for this node within its chain, minted when the node is\n * added. Automation lanes address nodes by id (`fx.<id>.<param>`) so that\n * reordering the chain never re-points a lane at a different effect. Older\n * chains have no ids; they load fine and simply cannot be automated until\n * the panel touches them.\n */\n id?: string;\n /** Set on nodes the carve analysis generated, so re-running replaces them\n * instead of stacking another set on top of hand-added effects. */\n fromCarve?: boolean;\n /**\n * Id of the preset that wrote this node, for the same reason `fromCarve`\n * exists: re-applying a preset replaces its own nodes rather than adding a\n * second copy, and the rack can brace them together under the preset's name.\n *\n * The id rather than a flag, because a chain can carry more than one preset\n * and each has to be able to find its own.\n */\n fromPreset?: string;\n /**\n * What the rack calls this node, when the effect's own name is not specific\n * enough to be useful.\n *\n * A peaking filter is \"Shape One Range\" wherever it appears, so a chain that\n * cuts mud at 250 Hz and lifts clarity at 3 kHz shows the same words twice\n * and an author cannot tell the two apart. A preset names each node for the\n * JOB it is doing instead — \"Reduce Mud\", \"Add Clarity\" — and the rack reads\n * as a list of things that were done rather than a list of filter types.\n */\n label?: string;\n /**\n * Id of the multi-band EQ that owns this node, when it is one of its bands.\n *\n * Same device as `fromCarve`: the module gathers its own nodes out of the\n * chain and presents them as one control surface, so an EQ needs no new\n * effect type and its bands stay ordinary filters underneath.\n */\n fromEq?: string;\n\n /**\n * How much of this node's preset is applied, 0..1 — the wet/dry blend the\n * graph wraps its run in.\n *\n * On every node of the run rather than beside the chain, because the chain has\n * nowhere else to put it: `HfAudioFxChain` is a version and a list of nodes,\n * and a preset is defined by which nodes carry its tag. The graph reads it off\n * the first node of each run. Absent means fully applied, which is what every\n * chain written before this means.\n */\n presetAmount?: number;\n /**\n * Set on the gain stage the leveller writes, so re-running replaces it rather\n * than stacking a second one — the same contract `fromCarve` has.\n */\n fromLeveller?: boolean;\n /** Absent means enabled — chain files written before the field existed still load. */\n enabled?: boolean;\n params?: HfAudioFxParamValues;\n}\n\nexport interface HfAudioFxChain {\n version: number;\n nodes: HfAudioFxNode[];\n}\n\nexport class AudioFxChainError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AudioFxChainError\";\n }\n}\n\n/**\n * Parse a chain file. Unknown effect ids are rejected rather than skipped: a\n * chain that silently loses a node would render differently from the project\n * the author saved, which is worse than refusing to render at all.\n */\nexport function parseAudioFxChain(json: string): HfAudioFxChain {\n let raw: unknown;\n try {\n raw = JSON.parse(json);\n } catch (err) {\n throw new AudioFxChainError(`Chain file is not valid JSON: ${(err as Error).message}`);\n }\n if (typeof raw !== \"object\" || raw === null) {\n throw new AudioFxChainError(\"Chain file must be a JSON object.\");\n }\n const obj = raw as { version?: unknown; nodes?: unknown };\n if (obj.version !== HF_AUDIO_FX_CHAIN_VERSION) {\n throw new AudioFxChainError(`Unsupported chain version: ${String(obj.version)}`);\n }\n if (!Array.isArray(obj.nodes)) {\n throw new AudioFxChainError(\"Chain file is missing a `nodes` array.\");\n }\n const nodes = obj.nodes.map(parseAudioFxNode);\n return { version: HF_AUDIO_FX_CHAIN_VERSION, nodes };\n}\n\n/** The nodes that should process audio, in order. */\nexport function enabledAudioFxNodes(chain: HfAudioFxChain): HfAudioFxNode[] {\n return chain.nodes.filter((n) => n.enabled !== false);\n}\n\n/** Serialise a chain for the `data-fx-chain` attribute. */\nexport function serializeAudioFxChain(chain: HfAudioFxChain): string {\n return JSON.stringify({\n version: HF_AUDIO_FX_CHAIN_VERSION,\n nodes: chain.nodes.map(serializeAudioFxNode),\n });\n}\n\n/**\n * Next free node id for a chain, as `n1`, `n2`, … — counted rather than random\n * so that adding an effect produces the same document on every machine, which\n * compositions require.\n */\nexport function mintAudioFxNodeId(chain: HfAudioFxChain): string {\n const taken = new Set(chain.nodes.map((n) => n.id).filter(Boolean));\n for (let i = 1; ; i += 1) {\n const id = `n${i}`;\n if (!taken.has(id)) return id;\n }\n}\n\n/** The shape a node is READ as: everything unknown until checked. */\ninterface RawAudioFxNode {\n type?: unknown;\n id?: unknown;\n enabled?: unknown;\n params?: unknown;\n fromCarve?: unknown;\n fromPreset?: unknown;\n label?: unknown;\n fromEq?: unknown;\n fromLeveller?: unknown;\n presetAmount?: unknown;\n}\n\n/**\n * One node out of a chain file, validated. Throws rather than dropping: a chain\n * that silently loses a node would render differently from the project the\n * author saved.\n */\nfunction parseAudioFxNode(n: unknown, i: number): HfAudioFxNode {\n if (typeof n !== \"object\" || n === null) {\n throw new AudioFxChainError(`Node ${i} is not an object.`);\n }\n const node = n as RawAudioFxNode;\n if (typeof node.type !== \"string\" || !BY_ID.has(node.type)) {\n throw new AudioFxChainError(`Node ${i} has unknown effect type: ${String(node.type)}`);\n }\n return withoutUndefined({\n type: node.type,\n id: nonEmptyString(node.id),\n fromCarve: onlyTrue(node.fromCarve),\n // fromPreset and label both survive the round trip or a preset stops being\n // able to find its own nodes after a reload: re-applying would stack a\n // second copy and the rack would lose the grouping it braces them with.\n fromPreset: nonEmptyString(node.fromPreset),\n label: nonEmptyString(node.label),\n fromEq: nonEmptyString(node.fromEq),\n fromLeveller: onlyTrue(node.fromLeveller),\n presetAmount: clampedPresetAmount(node.presetAmount),\n enabled: node.enabled !== false,\n params: normalizeAudioFxParams(\n node.type,\n (node.params ?? undefined) as HfAudioFxParamValues | undefined,\n ),\n });\n}\n\n/** One node as the `data-fx-chain` attribute carries it. Every optional field is\n * omitted when it holds its default, so a plain chain stays plain. */\nfunction serializeAudioFxNode(node: HfAudioFxNode) {\n return withoutUndefined({\n type: node.type,\n id: nonEmptyString(node.id),\n fromCarve: onlyTrue(node.fromCarve),\n fromPreset: nonEmptyString(node.fromPreset),\n label: nonEmptyString(node.label),\n fromEq: nonEmptyString(node.fromEq),\n fromLeveller: onlyTrue(node.fromLeveller),\n // Omitted when fully applied, so an untouched preset does not grow a field\n // in every chain that carries one.\n presetAmount: node.presetAmount === 1 ? undefined : clampedPresetAmount(node.presetAmount),\n // Only the non-default is written: a chain of enabled nodes stays plain.\n enabled: node.enabled === false ? (false as const) : undefined,\n params: normalizeAudioFxParams(node.type, node.params),\n });\n}\n\n/**\n * The field readers the two node codecs share.\n *\n * Written as readers rather than as conditional spreads inline: nine\n * `...(cond ? { x } : {})` clauses in one object literal is nine branches in a\n * function whose actual job is \"copy the fields that are set\", and it read as\n * complex because it was measured as complex.\n */\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value ? value : undefined;\n}\n\nfunction onlyTrue(value: unknown): true | undefined {\n return value === true ? true : undefined;\n}\n\n/** Clamped on the way in: the blend is two gains in opposition, and a value\n * outside 0..1 makes the dry leg negative rather than simply loud. */\nfunction clampedPresetAmount(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return undefined;\n return Math.min(1, Math.max(0, value));\n}\n\n/** Drop the keys whose reader returned undefined, so an absent field stays\n * absent rather than becoming an explicit `undefined` in the document. */\nfunction withoutUndefined<T extends object>(obj: T): T {\n for (const key of Object.keys(obj) as Array<keyof T>) {\n if (obj[key] === undefined) delete obj[key];\n }\n return obj;\n}\n","/**\n * Authoring gain for a media clip.\n *\n * HTMLMediaElement.volume is limited to 0..1, but HyperFrames' Web Audio\n * preview and FFmpeg render paths both support gain above unity. Keep the\n * shared ceiling here so Studio, preview, and render cannot drift.\n */\nexport const MAX_AUDIO_GAIN_DB = 12;\n/** Linear gain from a decibel value. Owner of the dB → gain formula. */\nexport function audioDbToGain(db: number): number {\n return 10 ** (db / 20);\n}\n/** Inverse of `audioDbToGain`. Caller must pass a positive finite gain. */\nexport function audioGainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n}\nexport const MAX_AUDIO_GAIN = audioDbToGain(MAX_AUDIO_GAIN_DB);\n\n/** Studio fader coordinates. Unity is deliberately the physical midpoint. */\nexport const AUDIO_GAIN_FADER_MIN = -100;\nexport const AUDIO_GAIN_FADER_MAX = 100;\n\nconst MIN_AUDIO_GAIN_DB = -60;\n\nexport function clampAudioGain(value: number): number {\n if (!Number.isFinite(value)) return 1;\n return Math.max(0, Math.min(MAX_AUDIO_GAIN, value));\n}\n\nexport function clampNativeMediaVolume(value: number): number {\n if (!Number.isFinite(value)) return 1;\n return Math.max(0, Math.min(1, value));\n}\n\n/**\n * Serialize an authored gain for `data-volume`.\n *\n * The fader travels in dB, so its stops are irrational (position -70 is\n * 10 ** (-42/20)). Rounding to two decimals — what the generic numeric\n * attribute formatter does — collapses the whole bottom of the fader onto\n * `\"0\"` (a hard mute) and makes the knob jump on release everywhere below\n * unity. Six decimals round-trip every integer fader stop back to itself.\n */\nexport function formatAudioGain(gain: number): string {\n return clampAudioGain(gain)\n .toFixed(6)\n .replace(/\\.?0+$/, \"\");\n}\n\n/**\n * Run `probe` with `el.volume` shadowed by an accessor that keeps the authored\n * value instead of the spec's [0,1] clamp.\n *\n * `HTMLMediaElement.volume` cannot hold gain above unity, so a clip authored\n * at `data-volume=\"1.95\"` reads back as 1 the moment the probe seeds it — and\n * a GSAP tween started from that seed fades from 0 dB rather than from the\n * authored boost. Both the FFmpeg mixer and the Web Audio transport carry gain\n * up to MAX_AUDIO_GAIN, so the clamp is a probe artefact, not a real ceiling.\n * The native setter still receives the clamped value, so nothing outside the\n * probe observes an out-of-range volume, and the shadow is removed afterwards.\n */\nexport function withUnclampedVolume<T>(el: HTMLMediaElement, probe: () => T): T {\n // Guarded for non-DOM runtimes: the probe that calls this is also reachable\n // from tests and tools that run outside a browser, where the clamped path is\n // the right (and only) answer.\n const descriptor =\n typeof HTMLMediaElement === \"undefined\"\n ? undefined\n : Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, \"volume\");\n const nativeGet = descriptor?.get;\n const nativeSet = descriptor?.set;\n if (!nativeGet || !nativeSet) return probe();\n\n let authored = Number(nativeGet.call(el));\n Object.defineProperty(el, \"volume\", {\n configurable: true,\n get: () => authored,\n set: (value: number) => {\n authored = Number(value);\n nativeSet.call(el, clampNativeMediaVolume(authored));\n },\n });\n try {\n return probe();\n } finally {\n delete (el as unknown as Record<\"volume\", unknown>).volume;\n nativeSet.call(el, clampNativeMediaVolume(authored));\n }\n}\n\nexport function audioFaderPositionToGain(position: number): number {\n const safe = Math.max(AUDIO_GAIN_FADER_MIN, Math.min(AUDIO_GAIN_FADER_MAX, position));\n if (safe === AUDIO_GAIN_FADER_MIN) return 0;\n const db =\n safe < 0\n ? (safe / Math.abs(AUDIO_GAIN_FADER_MIN)) * Math.abs(MIN_AUDIO_GAIN_DB)\n : (safe / AUDIO_GAIN_FADER_MAX) * MAX_AUDIO_GAIN_DB;\n return audioDbToGain(db);\n}\n\nexport function audioGainToFaderPosition(gain: number): number {\n const safe = clampAudioGain(gain);\n if (safe === 0) return AUDIO_GAIN_FADER_MIN;\n const db = audioGainToDb(safe);\n const position =\n db < 0\n ? (db / Math.abs(MIN_AUDIO_GAIN_DB)) * Math.abs(AUDIO_GAIN_FADER_MIN)\n : (db / MAX_AUDIO_GAIN_DB) * AUDIO_GAIN_FADER_MAX;\n return Math.max(AUDIO_GAIN_FADER_MIN, Math.min(AUDIO_GAIN_FADER_MAX, position));\n}\n\nexport function audioGainToText(gain: number): string {\n const safe = clampAudioGain(gain);\n if (safe === 0) return \"-∞ dB\";\n const db = audioGainToDb(safe);\n const rounded = Math.abs(db) < 0.05 ? 0 : db;\n return (rounded > 0 ? \"+\" : \"\") + rounded.toFixed(1) + \" dB\";\n}\n","/**\n * Automation envelopes for audio tracks.\n *\n * A lane is a list of breakpoints over one parameter — track volume, or one\n * knob of one effect in the track's FX chain. Ableton's clip-envelope model:\n * times are clip-local, so an envelope travels with the clip when it moves.\n *\n * Nothing here touches Web Audio. This module owns the format and the\n * interpolation, and the same `sampleAutomationLane` is used to draw the lane\n * in the timeline, to schedule it in preview, and to bake it at render — one\n * curve, three consumers, or the picture and the sound disagree.\n */\n\nimport { getAudioFxDef, type HfAudioFxChain } from \"./audioFx.js\";\nimport { MAX_AUDIO_GAIN } from \"./audioGain.js\";\nimport { MAX_PLAYBACK_RATE, MIN_PLAYBACK_RATE } from \"./playbackRateBounds.js\";\n\nexport const HF_AUDIO_AUTOMATION_ATTR = \"data-automation\";\n\n/** The same attribute as a `dataset` / `dataAttributes` key. See `HF_AUDIO_FX_DATA_KEY`. */\nexport const HF_AUDIO_AUTOMATION_DATA_KEY = HF_AUDIO_AUTOMATION_ATTR.slice(\"data-\".length);\n\n/** Automation files are versioned; a reader must refuse a version it doesn't know. */\nexport const HF_AUDIO_AUTOMATION_VERSION = 1;\n\n/**\n * A pathological document should not be able to hang the scheduler, which\n * expands every segment into scheduled ramps. Well past any hand-drawn\n * envelope; a lane is dense at 100 points.\n */\nexport const MAX_AUTOMATION_POINTS = 512;\n\nexport interface HfAutomationPoint {\n /** Seconds from the start of the clip, not the composition. */\n t: number;\n /** Value in the parameter's own unit — dB for a threshold, Hz for a cutoff. */\n v: number;\n /**\n * Curvature of the segment *leaving* this point, -1..1. Absent or 0 is a\n * straight line; positive holds low then rises late, negative rises early.\n */\n curve?: number;\n /**\n * An interior point the segment *leaving* this point passes through, in\n * normalised segment space: `viaX` is progress 0..1 between the two\n * breakpoints, `viaY` is how far the value has travelled by then. Both are\n * needed for either to mean anything; without them the segment falls back to\n * `curve` above, so everything authored before this existed reads unchanged.\n *\n * Two numbers rather than one because `curve` answers only \"how hard\", and an\n * exponent cannot say \"and where\". Every upward bend a single exponent can\n * draw has its deepest deviation in the first fifth of the segment, whatever\n * the author aimed at, and an exponent steep enough to reach a point near\n * either end runs past the ±1 the model accepts — so a bend dragged near the\n * right-hand breakpoint bulged on the left and then stopped following the\n * pointer at all. Naming the point the curve goes through makes both the\n * height and the position of the bend the author's to choose, and takes the\n * saturation with it: any interior point is reachable exactly.\n */\n viaX?: number;\n viaY?: number;\n}\n\nexport interface HfAutomationLane {\n /** `volume`, or `fx.<nodeId>.<paramKey>`. */\n target: string;\n points: HfAutomationPoint[];\n}\n\nexport interface HfAutomation {\n version: number;\n lanes: HfAutomationLane[];\n}\n\nexport class AudioAutomationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AudioAutomationError\";\n }\n}\n\nexport const VOLUME_TARGET = \"volume\";\n\n/** Playback-rate lane: `v` is a speed multiplier over clip-local time. See `speedRamp.ts`. */\nexport const RATE_TARGET = \"rate\";\n\nexport type HfAutomationTarget =\n | { kind: \"volume\" }\n | { kind: \"rate\" }\n | { kind: \"fx\"; nodeId: string; param: string }\n | { kind: \"preset\"; presetId: string };\n\n/** Split a target string. Returns null for anything unrecognised. */\nexport function parseAutomationTarget(target: string): HfAutomationTarget | null {\n if (target === VOLUME_TARGET) return { kind: \"volume\" };\n if (target === RATE_TARGET) return { kind: \"rate\" };\n const parts = target.split(\".\");\n // `fx.preset.<id>` before the 3-part fx form, because it IS a 3-part fx form\n // with a reserved node id — an effect can never be called \"preset\", since ids\n // are minted `n1`, `n2`, ….\n if (parts.length === 3 && parts[0] === \"fx\" && parts[1] === PRESET_TARGET_KEY) {\n const presetId = parts[2];\n return presetId ? { kind: \"preset\", presetId } : null;\n }\n if (parts.length !== 3 || parts[0] !== \"fx\") return null;\n const [, nodeId, param] = parts;\n if (!nodeId || !param) return null;\n return { kind: \"fx\", nodeId, param };\n}\n\nexport function fxAutomationTarget(nodeId: string, param: string): string {\n return `fx.${nodeId}.${param}`;\n}\n\n/** The reserved node-id slot that marks a whole-preset target. */\nconst PRESET_TARGET_KEY = \"preset\";\n\n/**\n * How much of a preset is applied, 0..1.\n *\n * A preset's nodes share no automatable parameter — and its worklet effects\n * expose no AudioParams at all — so there is nothing to aim a lane at\n * node-by-node. The graph wraps a preset's run in a wet/dry pair instead, and\n * this drives the blend: 0 is the dry signal untouched, 1 is the preset fully\n * applied, and between them it crossfades.\n */\nexport function presetAutomationTarget(presetId: string): string {\n return `fx.${PRESET_TARGET_KEY}.${presetId}`;\n}\n\n/** 0..1 blend, the same shape as a wet/dry mix knob. */\nexport const PRESET_RANGE: AutomationRange = {\n min: 0,\n max: 1,\n step: 0.01,\n unit: \"\",\n label: \"Amount\",\n scale: \"linear\",\n default: 1,\n};\n\n/**\n * The value range a lane is drawn and clamped against.\n *\n * Volume is linear over the full authoring gain range, matching `data-volume`\n * and the existing volume envelope machinery — no dB conversion enters the\n * volume path. Everything\n * else is read from the effect registry, so a lane can never offer a value the\n * renderer would reject, and the log-scaled knobs sweep the way a DAW's do.\n */\nexport interface AutomationRange {\n min: number;\n max: number;\n step: number;\n unit: string;\n label: string;\n scale: \"linear\" | \"log\";\n /** Where an empty lane draws its flat line, and what a new point starts at. */\n default: number;\n}\n\n/**\n * One ceiling for the fader, the lane, the preview transport and the render\n * mixer. Capping the lane at unity while the fader reached +12 dB made\n * automating a boosted clip silently discard the boost — and the panel\n * disables the fader while a lane owns it, so there was no way back.\n */\nexport const VOLUME_RANGE: AutomationRange = {\n min: 0,\n max: MAX_AUDIO_GAIN,\n step: 0.01,\n unit: \"\",\n label: \"Volume\",\n scale: \"linear\",\n default: 1,\n};\n\nexport const RATE_RANGE: AutomationRange = {\n min: MIN_PLAYBACK_RATE,\n max: MAX_PLAYBACK_RATE,\n step: 0.05,\n unit: \"x\",\n label: \"Speed\",\n scale: \"log\",\n default: 1,\n};\n\n/**\n * Resolve a lane's target against a chain. Returns null when the target names\n * a node or parameter that is not there — the effect was deleted, or the\n * parameter is an enum, which has no envelope between its values.\n */\nexport function resolveAutomationRange(\n target: string,\n chain: HfAudioFxChain | undefined,\n): AutomationRange | null {\n const parsed = parseAutomationTarget(target);\n if (!parsed) return null;\n if (parsed.kind === \"volume\") return VOLUME_RANGE;\n if (parsed.kind === \"rate\") return RATE_RANGE;\n if (parsed.kind === \"preset\") {\n // Only for a preset the chain actually carries, so a lane left behind by a\n // removed preset resolves to nothing and is dropped at read time — the same\n // contract an orphaned node lane has.\n const present = chain?.nodes.some((n) => n.fromPreset === parsed.presetId);\n return present ? { ...PRESET_RANGE, label: `${parsed.presetId} · Amount` } : null;\n }\n const node = chain?.nodes.find((n) => n.id === parsed.nodeId);\n if (!node) return null;\n const def = getAudioFxDef(node.type);\n const param = def?.params.find((p) => p.key === parsed.param);\n if (!param || param.kind !== \"number\") return null;\n return {\n min: param.min,\n max: param.max,\n step: param.step,\n unit: param.unit,\n label: `${def?.label ?? node.type} · ${param.label}`,\n scale: param.scale === \"log\" && param.min > 0 ? \"log\" : \"linear\",\n default: param.default,\n };\n}\n\n/**\n * Coerce a JSON field to a number, treating anything that is not already one —\n * or a string that plainly reads as one — as absent.\n *\n * Not `Number(raw)`: that turns `null`, `\"\"` and `[]` into 0, which for an\n * envelope means a silent drop to zero rather than a point the reader rejects.\n */\nfunction numberOrNull(raw: unknown): number | null {\n if (typeof raw === \"number\") return Number.isFinite(raw) ? raw : null;\n if (typeof raw === \"string\" && raw.trim() !== \"\") {\n const n = Number(raw);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nfunction clampCurve(v: unknown): number {\n const n = numberOrNull(v);\n if (n === null || n === 0) return 0;\n return Math.min(1, Math.max(-1, n));\n}\n\n/**\n * A via point, or null when there isn't a usable one.\n *\n * Only two things disqualify one: a coordinate outside the segment, which names a\n * point the curve does not travel through, and a point ON the straight line, which\n * is a straight line — storing that would claim a bend the segment does not have.\n *\n * Depth is deliberately NOT limited. An earlier version held bends to a maximum\n * slope so a segment could never leave a breakpoint steeply, and it was wrong twice\n * over: it capped how far a curve could be pulled, and clamping the two coordinates\n * separately could land a bend exactly on the straight line and flatten it mid-drag.\n * A steep departure is a smooth curve doing what it was asked; what must never\n * happen is a crease, and that is the sampler's job below, not a limit here.\n */\nfunction clampVia(rawX: unknown, rawY: unknown): { x: number; y: number } | null {\n const x = numberOrNull(rawX);\n const y = numberOrNull(rawY);\n if (x === null || y === null) return null;\n const inside = (n: number): number => Math.min(0.999, Math.max(0.001, n));\n const cx = inside(x);\n const cy = inside(y);\n if (Math.abs(cx - cy) < 1e-6) return null;\n return { x: cx, y: cy };\n}\n\n/**\n * The via point a segment will actually be drawn with, given one that was asked\n * for — pulled into the steady region, or null when it describes no bend.\n *\n * Exported so an editor can write what the model will honour rather than what the\n * pointer happened to ask for. Without it a lane's attribute claims a shape the\n * renderer quietly declines to draw, and the two only agree once the file makes a\n * round trip.\n */\nexport function steadyViaPoint(\n viaX: number | undefined,\n viaY: number | undefined,\n): { viaX: number; viaY: number } | null {\n const via = clampVia(viaX, viaY);\n return via ? { viaX: via.x, viaY: via.y } : null;\n}\n\n/**\n * The shape fields a point carries, cleaned: whichever of `curve` and the via\n * pair survive, each omitted when it describes nothing.\n *\n * The via pair is kept or dropped together. Half a via point says nothing, and\n * letting one coordinate through would leave the segment's shape depending on\n * which half survived a hand edit.\n */\nfunction shapeFields(\n p: HfAutomationPoint | undefined,\n): Pick<HfAutomationPoint, \"curve\" | \"viaX\" | \"viaY\"> {\n const curve = clampCurve(p?.curve);\n const via = clampVia(p?.viaX, p?.viaY);\n return {\n ...(curve ? { curve } : {}),\n ...(via ? { viaX: via.x, viaY: via.y } : {}),\n };\n}\n\n/**\n * Clean one point, or reject it.\n *\n * A missing or non-finite value reaching an AudioParam silences the node for\n * the rest of the render, so such a point is dropped rather than coerced.\n */\nfunction cleanPoint(\n p: HfAutomationPoint | undefined,\n range: AutomationRange | null,\n): HfAutomationPoint | null {\n const t = numberOrNull(p?.t);\n const v = numberOrNull(p?.v);\n if (t === null || v === null) return null;\n const clamped = range ? Math.min(range.max, Math.max(range.min, v)) : v;\n return { t: Math.max(0, t), v: clamped, ...shapeFields(p) };\n}\n\n/**\n * Order points, drop unusable ones, and collapse duplicate times.\n *\n * Later wins on a tie so that dragging a point onto another replaces it rather\n * than leaving an invisible one underneath.\n */\nfunction normalizePoints(\n points: readonly HfAutomationPoint[],\n range: AutomationRange | null,\n): HfAutomationPoint[] {\n const clean = points\n .map((p) => cleanPoint(p, range))\n .filter((p): p is HfAutomationPoint => p !== null)\n .sort((a, b) => a.t - b.t);\n const out: HfAutomationPoint[] = [];\n for (const p of clean) {\n if (out.length > 0 && out[out.length - 1]!.t === p.t) out[out.length - 1] = p;\n else out.push(p);\n }\n return out.slice(0, MAX_AUTOMATION_POINTS);\n}\n\n/**\n * Structural normalisation, with no knowledge of the chain: sort and clean the\n * points of every lane and drop lanes that carry none. Range clamping and\n * orphan removal need the chain and happen in `resolveAutomation`.\n */\nexport function normalizeAutomation(automation: HfAutomation): HfAutomation {\n const lanes: HfAutomationLane[] = [];\n for (const lane of automation.lanes) {\n if (!parseAutomationTarget(lane.target)) continue;\n const range =\n lane.target === VOLUME_TARGET\n ? VOLUME_RANGE\n : lane.target === RATE_TARGET\n ? RATE_RANGE\n : null;\n const points = normalizePoints(lane.points ?? [], range);\n if (points.length > 0) lanes.push({ target: lane.target, points });\n }\n return { version: HF_AUDIO_AUTOMATION_VERSION, lanes };\n}\n\n/**\n * Bind automation to a chain: clamp each lane into its parameter's declared\n * range and drop lanes whose target no longer exists.\n *\n * Dropping is deliberate. An envelope on a deleted effect has nothing to\n * drive, and keeping it would silently reattach if an unrelated effect later\n * took the same node id.\n */\nexport function resolveAutomation(\n automation: HfAutomation,\n chain: HfAudioFxChain | undefined,\n): HfAutomation {\n const lanes: HfAutomationLane[] = [];\n for (const lane of automation.lanes) {\n const range = resolveAutomationRange(lane.target, chain);\n if (!range) continue;\n const points = normalizePoints(lane.points, range);\n if (points.length > 0) lanes.push({ target: lane.target, points });\n }\n return { version: HF_AUDIO_AUTOMATION_VERSION, lanes };\n}\n\n/**\n * Parse an automation attribute.\n *\n * Malformed input throws rather than being skipped, matching the chain reader:\n * a track that quietly loses its envelope renders differently from the project\n * the author saved, which is worse than refusing.\n */\nexport function parseAutomation(json: string): HfAutomation {\n let raw: unknown;\n try {\n raw = JSON.parse(json);\n } catch (err) {\n throw new AudioAutomationError(`Automation is not valid JSON: ${(err as Error).message}`);\n }\n if (typeof raw !== \"object\" || raw === null) {\n throw new AudioAutomationError(\"Automation must be a JSON object.\");\n }\n const obj = raw as { version?: unknown; lanes?: unknown };\n if (obj.version !== HF_AUDIO_AUTOMATION_VERSION) {\n throw new AudioAutomationError(`Unsupported automation version: ${String(obj.version)}`);\n }\n if (!Array.isArray(obj.lanes)) {\n throw new AudioAutomationError(\"Automation is missing a `lanes` array.\");\n }\n const lanes: HfAutomationLane[] = obj.lanes.map((l, i) => {\n if (typeof l !== \"object\" || l === null) {\n throw new AudioAutomationError(`Lane ${i} is not an object.`);\n }\n const lane = l as { target?: unknown; points?: unknown };\n if (typeof lane.target !== \"string\" || !parseAutomationTarget(lane.target)) {\n throw new AudioAutomationError(`Lane ${i} has an unreadable target: ${String(lane.target)}`);\n }\n if (!Array.isArray(lane.points)) {\n throw new AudioAutomationError(`Lane ${i} is missing a \\`points\\` array.`);\n }\n return { target: lane.target, points: lane.points as HfAutomationPoint[] };\n });\n return normalizeAutomation({ version: HF_AUDIO_AUTOMATION_VERSION, lanes });\n}\n\n/** Serialise for the `data-automation` attribute. */\nexport function serializeAutomation(automation: HfAutomation): string {\n return JSON.stringify({\n version: HF_AUDIO_AUTOMATION_VERSION,\n lanes: automation.lanes.map((lane) => ({\n target: lane.target,\n points: lane.points.map((p) => ({\n t: p.t,\n v: p.v,\n ...(p.curve ? { curve: p.curve } : {}),\n ...(p.viaX !== undefined && p.viaY !== undefined ? { viaX: p.viaX, viaY: p.viaY } : {}),\n })),\n })),\n });\n}\n\n/**\n * Shape the 0..1 progress across a segment.\n *\n * `curve` is an exponent in disguise: 0 is linear, and the ends reach a\n * quarter-power and a fourth-power bend, which is about the range a DAW's\n * envelope handle covers before the segment stops reading as a curve.\n */\nexport function applyCurve(x: number, curve: number | undefined): number {\n if (!curve) return x;\n return Math.pow(x, Math.pow(2, 2 * curve));\n}\n\n/**\n * The conic that carries a segment through its via point: control point and weight\n * of a rational quadratic Bezier from (0,0) to (1,1).\n *\n * A rational quadratic at its own midparameter is `(P0 + 2wC + P1) / (2 + 2w)`, so\n * demanding that equal the via point `Q` fixes the control point for any weight:\n * `C = Q + (Q - M) / w`, with `M` the straight midpoint. The curve therefore passes\n * exactly through `Q` whatever the weight, and — because it does so at the\n * midparameter, with the two halves symmetric in parameter — its furthest departure\n * from the straight line is at `Q` too. The pointer is the apex, by construction.\n *\n * The weight is what buys reach. A plain quadratic (`w = 1`) needs its control point\n * at `2Q - M`, which leaves the segment as soon as `Q` is past the middle half — so\n * a plain quadratic simply cannot pass through a deep or off-centre point. Raising\n * the weight draws the control point back toward `Q`, so take the smallest weight\n * that keeps `C` inside the segment: inside is what makes progress monotone, and\n * smallest-that-fits is the broadest curve available through that point.\n *\n * Deep bends therefore come out tight and steep near the breakpoint they lean on.\n * That is the shape being asked for, and it stays a single smooth arc while doing\n * it — no join, no inflection, no crease.\n */\nfunction viaConic(viaX: number, viaY: number): { cx: number; cy: number; w: number } {\n const dx = viaX - 0.5;\n const dy = viaY - 0.5;\n const edge = 0.999;\n // Per axis, `C` stays inside when the weight is at least the pull divided by the\n // room left in the pull's own direction. A via point clamped to `edge` itself\n // leaves zero room — `edge - viaX` is exactly 0 — which would divide out to\n // Infinity and carry NaN into every downstream sample. Capped rather than left\n // unbounded: past this weight the arc already reads as touching the via point,\n // so the cap costs no visible reach.\n const MAX_WEIGHT = 1e6;\n const needX = dx > 0 ? dx / (edge - viaX) : dx < 0 ? -dx / (viaX - (1 - edge)) : 0;\n const needY = dy > 0 ? dy / (edge - viaY) : dy < 0 ? -dy / (viaY - (1 - edge)) : 0;\n const w = Math.min(MAX_WEIGHT, Math.max(1, needX, needY));\n return { cx: viaX + dx / w, cy: viaY + dy / w, w };\n}\n\n/**\n * Progress through a segment that passes through its via point: one conic arc, from\n * breakpoint to breakpoint.\n *\n * One curve, not two joined at the via point, because anything joined there can only\n * be as smooth as the join — and keeping such a join monotone forces its tangent\n * down to a fraction of the slope the curve arrives with, creasing the segment\n * exactly where the pointer is. A conic has no join and no inflection: its slope\n * moves one way from end to end, which is what makes it read as one curve rather\n * than a shape with features in it.\n */\nfunction shapeVia(x: number, viaX: number, viaY: number): number {\n const { cx, cy, w } = viaConic(viaX, viaY);\n // Both coordinates are quadratics over one shared denominator, so `x(t) = x`\n // rearranges into an ordinary quadratic in `t`: closed form, and the same answer on\n // every machine, which a render that has to match its own preview depends on.\n const spread = 2 - 2 * w;\n const a = x * spread - 1 + 2 * w * cx;\n const b = -x * spread - 2 * w * cx;\n const t = conicParam(a, b, x);\n const rest = 1 - t;\n const denominator = rest * rest + 2 * w * t * rest + t * t;\n if (!(denominator > 0)) return x;\n return (2 * w * cy * t * rest + t * t) / denominator;\n}\n\n/** The root of `a*t^2 + b*t + c` that lies on the arc the segment travels. */\nfunction conicParam(a: number, b: number, c: number): number {\n if (Math.abs(a) < 1e-12) return Math.abs(b) < 1e-12 ? c : -c / b;\n const root = Math.sqrt(Math.max(0, b * b - 4 * a * c));\n const first = (-b + root) / (2 * a);\n const second = (-b - root) / (2 * a);\n const onArc = (t: number): boolean => t >= -1e-9 && t <= 1 + 1e-9;\n if (onArc(first)) return Math.min(1, Math.max(0, first));\n if (onArc(second)) return Math.min(1, Math.max(0, second));\n return c;\n}\n\n/**\n * The progress a segment leaving `point` has reached at normalised `x`.\n *\n * A via point wins when it is there, since it says strictly more than an\n * exponent can. Everything authored before via points existed carries only\n * `curve` and takes the exponent path unchanged.\n */\nexport function shapeProgress(\n x: number,\n point: { curve?: number | undefined; viaX?: number | undefined; viaY?: number | undefined },\n): number {\n const via = clampVia(point.viaX, point.viaY);\n if (via) return shapeVia(Math.min(1, Math.max(0, x)), via.x, via.y);\n return applyCurve(x, point.curve);\n}\n\nfunction lerpValue(a: number, b: number, x: number, scale: \"linear\" | \"log\"): number {\n // A frequency sweep interpolated linearly spends almost all its time in the\n // top octave. Log-scaled parameters interpolate in log space so a 200 Hz to\n // 8 kHz move sounds like an even sweep, which is what the knob's own scale\n // already promises.\n if (scale === \"log\" && a > 0 && b > 0) {\n return Math.exp(Math.log(a) + (Math.log(b) - Math.log(a)) * x);\n }\n return a + (b - a) * x;\n}\n\n/**\n * Value of a lane at a clip-local time.\n *\n * Outside the points, the envelope holds — the first value before it starts\n * and the last value after it ends, so a lane never snaps to zero at the edges.\n */\nexport function sampleAutomationLane(\n lane: HfAutomationLane,\n t: number,\n scale: \"linear\" | \"log\" = \"linear\",\n): number {\n const pts = lane.points;\n if (pts.length === 0) return 0;\n const first = pts[0]!;\n if (t <= first.t) return first.v;\n const last = pts[pts.length - 1]!;\n if (t >= last.t) return last.v;\n\n let lo = 0;\n let hi = pts.length - 1;\n while (hi - lo > 1) {\n const mid = (lo + hi) >> 1;\n if (pts[mid]!.t <= t) lo = mid;\n else hi = mid;\n }\n const a = pts[lo]!;\n const b = pts[hi]!;\n const span = b.t - a.t;\n if (span <= 0) return b.v;\n return lerpValue(a.v, b.v, shapeProgress((t - a.t) / span, a), scale);\n}\n\n/** True when the lane is a single value, i.e. worth setting once and not scheduling. */\nexport function isConstantLane(lane: HfAutomationLane): boolean {\n return lane.points.length <= 1 || lane.points.every((p) => p.v === lane.points[0]!.v);\n}\n\n/**\n * Sample a lane onto evenly spaced times, for consumers that want a plain\n * curve rather than a breakpoint list: `setValueCurveAtTime`, the render-side\n * volume envelope, and the lane's own drawing code.\n */\nexport function sampleAutomationCurve(\n lane: HfAutomationLane,\n from: number,\n to: number,\n count: number,\n scale: \"linear\" | \"log\" = \"linear\",\n): Float32Array {\n const n = Math.max(2, Math.floor(count));\n const out = new Float32Array(n);\n const span = to - from;\n for (let i = 0; i < n; i += 1) {\n out[i] = sampleAutomationLane(lane, from + (span * i) / (n - 1), scale);\n }\n return out;\n}\n","/**\n * The volume lane's gain for an element at a moment in its clip.\n *\n * Volume is not scheduled onto the gain AudioParam the way FX parameters are.\n * The runtime rewrites that gain every tick from `data-volume` and the GSAP\n * seeked value, which would erase a scheduled envelope within a frame — so the\n * lane feeds the same per-tick path the probed `volumeKeyframes` already use,\n * and the render still bakes it into the samples.\n */\n\nimport {\n HF_AUDIO_AUTOMATION_ATTR,\n parseAutomation,\n sampleAutomationLane,\n VOLUME_TARGET,\n type HfAutomationLane,\n} from \"../audioAutomation.js\";\n\n/**\n * Parsed lanes by the attribute text they came from. Reparsing on every tick\n * would run the JSON parser 60 times a second per track for a value that only\n * changes when the author edits it.\n */\nconst cache = new Map<string, HfAutomationLane | null>();\nconst CACHE_LIMIT = 64;\n\n/** The lane for `target` inside a `data-automation` value, or null when absent or unreadable. */\nexport function laneFromAttr(raw: string, target: string = VOLUME_TARGET): HfAutomationLane | null {\n const key = `${target}\\0${raw}`;\n const hit = cache.get(key);\n if (hit !== undefined) return hit;\n let lane: HfAutomationLane | null = null;\n try {\n lane = parseAutomation(raw).lanes.find((l) => l.target === target) ?? null;\n } catch {\n // Unreadable automation plays the track flat rather than silencing it.\n lane = null;\n }\n if (cache.size > CACHE_LIMIT) cache.clear();\n cache.set(key, lane);\n return lane;\n}\n\n/**\n * Gain the lane asks for at `relTime` clip-local seconds, or null when the\n * element has no volume lane and the caller should fall back to its own rules.\n */\nexport function elementVolumeLaneGain(\n el: { getAttribute?(name: string): string | null },\n relTime: number,\n): number | null {\n const raw =\n (typeof el.getAttribute === \"function\" ? el.getAttribute(HF_AUDIO_AUTOMATION_ATTR) : null) ??\n \"\";\n if (!raw) return null;\n const lane = laneFromAttr(raw);\n if (!lane || lane.points.length === 0) return null;\n return sampleAutomationLane(lane, relTime);\n}\n","/** A ramp is a `rate` lane in `data-automation`; `sourceTimeAt` is the one rate-to-time mapping consumers share. */\n\nimport { RATE_TARGET, sampleAutomationLane, type HfAutomationLane } from \"./audioAutomation.js\";\nimport { laneFromAttr } from \"./runtime/audioAutomationVolume.js\";\nimport { MAX_PLAYBACK_RATE, MIN_PLAYBACK_RATE } from \"./playbackRateBounds.js\";\n\n/** A constant multiplier, or a lane whose `v` is the multiplier over clip-local time. */\nexport type RateSpec = number | HfAutomationLane;\n\nconst SHIFT_RESAMPLES = 16;\n\n/**\n * The lane as seen from `dt` seconds into the clip: what a renderer sees after it trims the clip's start.\n * A shaped segment (`curve`, `viaX`) cut in the middle is resampled, since its shape belongs to its left point.\n */\nexport function shiftRateLane(spec: RateSpec, dt: number): RateSpec {\n if (typeof spec === \"number\" || dt === 0) return spec;\n const next = spec.points.findIndex((p) => p.t > dt);\n const later = next < 0 ? [] : spec.points.slice(next).map((p) => ({ ...p, t: p.t - dt }));\n const left = next > 0 ? spec.points[next - 1] : undefined;\n const shaped = left !== undefined && (left.curve || left.viaX !== undefined);\n const cut = shaped && next > 0 ? spec.points[next]!.t - dt : 0;\n const samples = Array.from({ length: shaped ? SHIFT_RESAMPLES - 1 : 0 }, (_, k) => {\n const t = ((k + 1) * cut) / SHIFT_RESAMPLES;\n return { t, v: rateAt(spec, dt + t) };\n });\n return { ...spec, points: [{ t: 0, v: rateAt(spec, dt) }, ...samples, ...later] };\n}\n\nconst CELLS_PER_SEGMENT = 48;\n\ninterface RateTable {\n /** Ascending clip-local times and the source seconds consumed by each. */\n ts: Float64Array;\n ss: Float64Array;\n firstRate: number;\n lastRate: number;\n}\n\nconst tables = new WeakMap<HfAutomationLane, RateTable>();\n\nfunction buildTable(lane: HfAutomationLane): RateTable {\n const pts = lane.points;\n const firstRate = pts[0]!.v;\n const lastRate = pts[pts.length - 1]!.v;\n const ts: number[] = [0];\n const ss: number[] = [0];\n const push = (t: number) => {\n const prevT = ts[ts.length - 1]!;\n if (t <= prevT) return;\n const prevRate = sampleAutomationLane(lane, prevT, \"log\");\n ts.push(t);\n ss.push(\n ss[ss.length - 1]! + ((prevRate + sampleAutomationLane(lane, t, \"log\")) / 2) * (t - prevT),\n );\n };\n const first = Math.max(0, pts[0]!.t);\n push(first);\n for (let i = 1; i < pts.length; i += 1) {\n const a = pts[i - 1]!.t;\n const b = pts[i]!.t;\n if (b <= 0) continue;\n for (let k = 1; k <= CELLS_PER_SEGMENT; k += 1)\n push(Math.max(a, 0) + ((b - a) * k) / CELLS_PER_SEGMENT);\n }\n return { ts: Float64Array.from(ts), ss: Float64Array.from(ss), firstRate, lastRate };\n}\n\nfunction tableFor(lane: HfAutomationLane): RateTable {\n let table = tables.get(lane);\n if (!table) {\n table = buildTable(lane);\n tables.set(lane, table);\n }\n return table;\n}\n\nfunction usable(spec: RateSpec): spec is HfAutomationLane {\n return typeof spec !== \"number\" && spec.points.length > 0;\n}\n\n/** Speed multiplier at clip-local time `t`. */\nexport function rateAt(spec: RateSpec, t: number): number {\n if (typeof spec === \"number\") return spec;\n return usable(spec) ? sampleAutomationLane(spec, t, \"log\") : 1;\n}\n\n/** Piecewise-linear lookup of `x` in the ascending `xs`, read off `ys`; caller keeps `x` inside `xs`. */\nfunction interpolate(xs: Float64Array, ys: Float64Array, x: number): number {\n let lo = 0;\n let hi = xs.length - 1;\n while (hi - lo > 1) {\n const mid = (lo + hi) >> 1;\n if (xs[mid]! <= x) lo = mid;\n else hi = mid;\n }\n return ys[lo]! + ((ys[hi]! - ys[lo]!) * (x - xs[lo]!)) / (xs[hi]! - xs[lo]!);\n}\n\n/** Source seconds consumed by clip-local time `t` (the integral of the rate). */\nexport function sourceTimeAt(spec: RateSpec, t: number): number {\n if (typeof spec === \"number\") return t * spec;\n if (!usable(spec)) return t;\n const { ts, ss, firstRate, lastRate } = tableFor(spec);\n const last = ts.length - 1;\n if (t <= 0) return t * firstRate;\n if (t >= ts[last]!) return ss[last]! + (t - ts[last]!) * lastRate;\n return interpolate(ts, ss, t);\n}\n\n/** Inverse of `sourceTimeAt`: the clip-local time at which `source` seconds have been consumed. */\nexport function timeAtSourceTime(spec: RateSpec, source: number): number {\n if (typeof spec === \"number\") return source / spec;\n if (!usable(spec)) return source;\n const { ts, ss, firstRate, lastRate } = tableFor(spec);\n const last = ts.length - 1;\n if (source <= 0) return source / firstRate;\n if (source >= ss[last]!) return ts[last]! + (source - ss[last]!) / lastRate;\n return interpolate(ss, ts, source);\n}\n\n/** The `rate` lane inside a `data-automation` attribute value, or null when absent or unreadable. */\nexport function parseRateLane(raw: string | null | undefined): HfAutomationLane | null {\n return raw ? laneFromAttr(raw, RATE_TARGET) : null;\n}\n\n/** A clip's rate: its `rate` lane when it has one, otherwise the constant. */\nexport function resolveRateSpec(\n automationAttr: string | null | undefined,\n constant: number,\n): RateSpec {\n return parseRateLane(automationAttr) ?? constant;\n}\n\ninterface RatePreset {\n id: string;\n label: string;\n /** Points as [progress 0..1 through the clip, speed]. */\n points: ReadonlyArray<readonly [number, number]>;\n}\n\n/** The CapCut-style presets; `speedPresetLane` stretches one over a clip's duration. */\nexport const SPEED_PRESETS = [\n {\n id: \"montage\",\n label: \"Montage\",\n points: [\n [0, 0.9],\n [0.15, 0.3],\n [0.5, 2.5],\n [0.85, 0.3],\n [1, 0.9],\n ],\n },\n {\n id: \"hero\",\n label: \"Hero\",\n points: [\n [0, 1],\n [0.25, 1.6],\n [0.5, 0.25],\n [0.75, 1.6],\n [1, 1],\n ],\n },\n {\n id: \"bullet\",\n label: \"Bullet\",\n points: [\n [0, 3],\n [0.4, 3],\n [0.5, 0.15],\n [0.6, 3],\n [1, 3],\n ],\n },\n {\n id: \"jump-cut\",\n label: \"Jump cut\",\n points: [\n [0, 0.5],\n [0.24, 0.5],\n [0.26, 3],\n [0.5, 3],\n [0.52, 0.5],\n [0.74, 0.5],\n [0.76, 3],\n [1, 3],\n ],\n },\n {\n id: \"flash-in\",\n label: \"Flash in\",\n points: [\n [0, 0.3],\n [0.6, 0.6],\n [1, 5],\n ],\n },\n {\n id: \"flash-out\",\n label: \"Flash out\",\n points: [\n [0, 5],\n [0.4, 0.6],\n [1, 0.3],\n ],\n },\n {\n id: \"ramp-in\",\n label: \"Ramp in\",\n points: [\n [0, 0.5],\n [1, 1],\n ],\n },\n {\n id: \"ramp-out\",\n label: \"Ramp out\",\n points: [\n [0, 1],\n [1, 0.5],\n ],\n },\n {\n id: \"slowmo-middle\",\n label: \"Slow-mo middle\",\n points: [\n [0, 1],\n [1 / 3, 0.35],\n [2 / 3, 0.35],\n [1, 1],\n ],\n },\n] as const satisfies readonly RatePreset[];\n\nexport type SpeedPresetId = (typeof SPEED_PRESETS)[number][\"id\"];\n\nexport function speedPresetLane(id: SpeedPresetId, clipDuration: number): HfAutomationLane {\n const preset = SPEED_PRESETS.find((p) => p.id === id)!;\n return {\n target: RATE_TARGET,\n points: preset.points.map(([x, v]) => ({\n t: x * clipDuration,\n v: Math.min(MAX_PLAYBACK_RATE, Math.max(MIN_PLAYBACK_RATE, v)),\n })),\n };\n}\n","/**\n * Browser-safe authored composition contract.\n *\n * Source HTML is authored with data-start + data-duration + data-track-index.\n * data-end is compiler-derived and data-layer is legacy input only. Readers\n * accept legacy documents; writers always emit the canonical representation.\n */\n\nexport const COMPOSITION_CONTRACT_VERSION = 1 as const;\n\nexport const COMPOSITION_ATTRIBUTES = Object.freeze({\n start: \"data-start\",\n duration: \"data-duration\",\n trackIndex: \"data-track-index\",\n derivedEnd: \"data-end\",\n legacyTrack: \"data-layer\",\n} as const);\n\nexport const CANONICAL_AUTHORED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.start,\n COMPOSITION_ATTRIBUTES.duration,\n COMPOSITION_ATTRIBUTES.trackIndex,\n] as const);\n\nexport const DERIVED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n] as const);\n\nexport const LEGACY_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n] as const);\n\nexport type ReferenceExpression =\n | { kind: \"absolute\"; value: number }\n | { kind: \"reference\"; refId: string; offset: number };\n\nexport type ClipTimingDiagnosticCode =\n | \"invalid-start\"\n | \"unresolved-start-reference\"\n | \"invalid-duration\"\n | \"invalid-end\"\n | \"end-before-start\"\n | \"deprecated-end\"\n | \"conflicting-end\"\n | \"invalid-track-index\"\n | \"deprecated-layer\"\n | \"conflicting-layer\";\n\nexport interface ClipTimingDiagnostic {\n code: ClipTimingDiagnosticCode;\n attribute: string;\n value: string | null;\n}\n\nexport interface ClipAttributeReader {\n getAttribute(name: string): string | null;\n}\n\nexport interface ClipAttributeWriter extends ClipAttributeReader {\n setAttribute(name: string, value: string): void;\n removeAttribute(name: string): void;\n}\n\nexport interface ReadClipTimingOptions {\n /** Resolve a reference to the referenced clip's absolute end time. */\n resolveReferenceEnd?: (refId: string) => number | null | undefined;\n /** Start used when data-start is absent. Defaults to zero. */\n defaultStart?: number | null;\n}\n\nexport interface ClipTiming {\n startExpression: ReferenceExpression | null;\n start: number | null;\n duration: number | null;\n end: number | null;\n trackIndex: number;\n durationSource: \"duration\" | \"legacy-end\" | \"missing\" | \"invalid\";\n trackSource: \"track-index\" | \"legacy-layer\" | \"default\" | \"invalid\";\n diagnostics: ClipTimingDiagnostic[];\n}\n\nexport interface ClipTimingUpdate {\n start?: number | string | ReferenceExpression;\n duration?: number | null;\n trackIndex?: number | null;\n}\n\nexport class ClipTimingWriteError extends Error {\n readonly code: \"invalid-start\" | \"invalid-duration\" | \"invalid-track-index\";\n\n constructor(code: ClipTimingWriteError[\"code\"], message: string) {\n super(message);\n this.name = \"ClipTimingWriteError\";\n this.code = code;\n }\n}\n\n/** Parse a value to a finite number, or null if it is absent/invalid. */\nexport function parseNumeric(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nconst REFERENCE_ID_PATTERN = /^[A-Za-z0-9_.:-]+$/;\n\nfunction isAsciiDigitAt(value: string, index: number): boolean {\n const code = value.charCodeAt(index);\n return code >= 48 && code <= 57;\n}\n\nfunction skipDigitsLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && isAsciiDigitAt(value, cursor)) cursor--;\n return cursor;\n}\n\nfunction skipWhitespaceLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && (value[cursor] ?? \"\").trim() === \"\") cursor--;\n return cursor;\n}\n\nfunction findMagnitudeStart(value: string): number | null {\n const last = value.length - 1;\n if (!isAsciiDigitAt(value, last)) return null;\n let cursor = skipDigitsLeft(value, last);\n if (value[cursor] === \".\") cursor = skipDigitsLeft(value, cursor - 1);\n return cursor + 1;\n}\n\nfunction parseReferenceOffset(\n value: string,\n): { refId: string; operator: \"+\" | \"-\"; magnitude: number } | null {\n const magnitudeStart = findMagnitudeStart(value);\n if (magnitudeStart == null) return null;\n const operatorIndex = skipWhitespaceLeft(value, magnitudeStart - 1);\n const operator = value[operatorIndex];\n if (operator !== \"+\" && operator !== \"-\") return null;\n\n const refId = value.slice(0, operatorIndex).trim();\n if (!REFERENCE_ID_PATTERN.test(refId)) return null;\n const magnitude = Number(value.slice(magnitudeStart));\n if (!Number.isFinite(magnitude)) return null;\n return { refId, operator, magnitude };\n}\n\n/**\n * Parse the data-start grammar: absolute seconds, `clip-id`, or\n * `clip-id +/- offset`, where references resolve to the referenced clip's end.\n */\nexport function parseStartExpression(raw: string | null | undefined): ReferenceExpression | null {\n const normalized = (raw ?? \"\").trim();\n if (!normalized) return null;\n const absolute = parseNumeric(normalized);\n if (absolute != null) return { kind: \"absolute\", value: absolute };\n if (REFERENCE_ID_PATTERN.test(normalized)) {\n return { kind: \"reference\", refId: normalized, offset: 0 };\n }\n const reference = parseReferenceOffset(normalized);\n if (!reference) return null;\n return {\n kind: \"reference\",\n refId: reference.refId,\n offset: reference.operator === \"-\" ? -reference.magnitude : reference.magnitude,\n };\n}\n\nfunction pushDiagnostic(\n diagnostics: ClipTimingDiagnostic[],\n code: ClipTimingDiagnosticCode,\n attribute: string,\n value: string | null,\n): void {\n diagnostics.push({ code, attribute, value });\n}\n\nfunction resolveStart(\n expression: ReferenceExpression | null,\n rawStart: string | null,\n options: ReadClipTimingOptions,\n diagnostics: ClipTimingDiagnostic[],\n): number | null {\n if (rawStart == null || rawStart.trim() === \"\") {\n return options.defaultStart === undefined ? 0 : options.defaultStart;\n }\n if (!expression) {\n pushDiagnostic(diagnostics, \"invalid-start\", COMPOSITION_ATTRIBUTES.start, rawStart);\n return null;\n }\n if (expression.kind === \"absolute\") return Math.max(0, expression.value);\n const referencedEnd = options.resolveReferenceEnd?.(expression.refId);\n if (referencedEnd == null || !Number.isFinite(referencedEnd)) {\n pushDiagnostic(\n diagnostics,\n \"unresolved-start-reference\",\n COMPOSITION_ATTRIBUTES.start,\n rawStart,\n );\n return null;\n }\n return Math.max(0, referencedEnd + expression.offset);\n}\n\ntype DurationRead = Pick<ClipTiming, \"duration\" | \"end\" | \"durationSource\">;\ntype TrackRead = Pick<ClipTiming, \"trackIndex\" | \"trackSource\">;\n\n// Float pairs like `data-start=\"0.1\" + data-duration=\"0.2\"` add to\n// `0.30000000000000004`, so the compiler-emitted `data-end` string can be a few\n// ulps off the sum a reader computes from the canonical inputs. Any drift below\n// this ceiling is treated as equal for the derived-end reconciliation check —\n// well below one 60fps frame (~16.67 ms) and orders of magnitude above the\n// worst realistic IEEE-754 residual (~2e-16 s).\nconst DERIVED_END_EQUALITY_EPSILON_SECONDS = 1e-9;\n\nfunction derivedEndsAreConsistent(parsedEnd: number, canonicalEnd: number): boolean {\n return Math.abs(parsedEnd - canonicalEnd) <= DERIVED_END_EQUALITY_EPSILON_SECONDS;\n}\n\n// Reconcile a `data-end` attribute paired with canonical `data-duration`.\n//\n// The compiler (`compileTimingAttrs` in @hyperframes/core) writes\n// `data-end = data-start + data-duration` into the bundled HTML so the runtime\n// can key off a single attribute. That derived attribute is legitimate — it is\n// not a legacy authoring shape. Treating it as `deprecated-end` here made the\n// linter fire `deprecated_data_end` on the compiler's own output whenever\n// StaticGuard re-validated a bundled composition, producing a false-positive\n// cluster reported in the field (n=25+ across cli-feedback crons 61-68).\n//\n// We keep `deprecated-end` for the truly legacy shape (see `readLegacyEnd` —\n// `data-end` present without `data-duration`) and for the *conflicting* case\n// where an author left a stale `data-end` behind a canonical duration edit; a\n// stale end must not be silently overridden without a diagnostic.\nfunction diagnoseDerivedEnd(\n rawEnd: string,\n canonicalEnd: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): void {\n const parsedEnd = parseNumeric(rawEnd);\n if (parsedEnd == null) {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (canonicalEnd == null) {\n // Canonical duration exists but resolved end is unknown (e.g. unresolved\n // reference start). Preserve prior behavior — flag as deprecated so authors\n // remove the stale companion attribute.\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (derivedEndsAreConsistent(parsedEnd, canonicalEnd)) {\n // Compiler-derived (or manually consistent) `data-end` alongside\n // `data-duration` — silent. No diagnostic.\n return;\n }\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"conflicting-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n}\n\nfunction readCanonicalDuration(\n rawDuration: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const duration = parseNumeric(rawDuration);\n if (duration == null || duration < 0) {\n pushDiagnostic(diagnostics, \"invalid-duration\", COMPOSITION_ATTRIBUTES.duration, rawDuration);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return {\n duration,\n end: start == null ? null : start + duration,\n durationSource: \"duration\",\n };\n}\n\nfunction readLegacyEnd(\n rawEnd: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n const end = parseNumeric(rawEnd);\n if (end == null) {\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n if (start == null) return { duration: null, end, durationSource: \"legacy-end\" };\n if (end < start) {\n pushDiagnostic(diagnostics, \"end-before-start\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return { duration: end - start, end, durationSource: \"legacy-end\" };\n}\n\nfunction readDuration(\n attributes: ClipAttributeReader,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const rawDuration = attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration);\n const rawEnd = attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n if (rawDuration == null) {\n return rawEnd == null\n ? { duration: null, end: null, durationSource: \"missing\" }\n : readLegacyEnd(rawEnd, start, diagnostics);\n }\n const canonical = readCanonicalDuration(rawDuration, start, diagnostics);\n if (rawEnd != null) diagnoseDerivedEnd(rawEnd, canonical.end, diagnostics);\n return canonical;\n}\n\nfunction readTrackValue(\n rawValue: string,\n attribute: string,\n source: \"track-index\" | \"legacy-layer\",\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const trackIndex = parseNumeric(rawValue);\n if (trackIndex == null || !Number.isInteger(trackIndex)) {\n pushDiagnostic(diagnostics, \"invalid-track-index\", attribute, rawValue);\n return { trackIndex: 0, trackSource: \"invalid\" };\n }\n return { trackIndex, trackSource: source };\n}\n\nfunction readTrack(\n attributes: ClipAttributeReader,\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const rawTrack = attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n const rawLayer = attributes.getAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n if (rawTrack == null && rawLayer == null) return { trackIndex: 0, trackSource: \"default\" };\n if (rawTrack == null && rawLayer != null) {\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n return readTrackValue(\n rawLayer,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n \"legacy-layer\",\n diagnostics,\n );\n }\n const canonical = readTrackValue(\n rawTrack ?? \"\",\n COMPOSITION_ATTRIBUTES.trackIndex,\n \"track-index\",\n diagnostics,\n );\n if (rawLayer == null) return canonical;\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n const parsedLayer = parseNumeric(rawLayer);\n if (parsedLayer != null && parsedLayer !== canonical.trackIndex) {\n pushDiagnostic(diagnostics, \"conflicting-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n }\n return canonical;\n}\n\n/** Read canonical timing, accepting legacy attributes without preferring them. */\nexport function readClipTiming(\n attributes: ClipAttributeReader,\n options: ReadClipTimingOptions = {},\n): ClipTiming {\n const diagnostics: ClipTimingDiagnostic[] = [];\n const rawStart = attributes.getAttribute(COMPOSITION_ATTRIBUTES.start);\n const startExpression = parseStartExpression(rawStart);\n const start = resolveStart(startExpression, rawStart, options, diagnostics);\n const duration = readDuration(attributes, start, diagnostics);\n const track = readTrack(attributes, diagnostics);\n return { startExpression, start, ...duration, ...track, diagnostics };\n}\n\nfunction serializeStartNumber(start: number): string {\n if (!Number.isFinite(start)) {\n throw new ClipTimingWriteError(\"invalid-start\", \"start must be finite\");\n }\n return String(start);\n}\n\nfunction serializeStartReference(start: ReferenceExpression): string {\n if (start.kind === \"absolute\") return serializeStartNumber(start.value);\n if (!start.refId || !Number.isFinite(start.offset)) {\n throw new ClipTimingWriteError(\n \"invalid-start\",\n \"reference start must have an id and finite offset\",\n );\n }\n if (start.offset === 0) return start.refId;\n return `${start.refId} ${start.offset < 0 ? \"-\" : \"+\"} ${Math.abs(start.offset)}`;\n}\n\nfunction serializeStart(start: ClipTimingUpdate[\"start\"]): string {\n if (typeof start === \"number\") return serializeStartNumber(start);\n if (typeof start === \"object\" && start != null) return serializeStartReference(start);\n if (typeof start === \"string\" && parseStartExpression(start)) return start.trim();\n throw new ClipTimingWriteError(\"invalid-start\", `invalid start expression: ${start ?? \"\"}`);\n}\n\nfunction writeDuration(\n attributes: ClipAttributeWriter,\n duration: number | null | undefined,\n current: ClipTiming,\n): void {\n if (duration === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.duration);\n return;\n }\n if (duration === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n current.duration != null\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(current.duration));\n }\n return;\n }\n if (!Number.isFinite(duration) || duration < 0) {\n throw new ClipTimingWriteError(\n \"invalid-duration\",\n \"duration must be a finite, non-negative number\",\n );\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(duration));\n}\n\nfunction writeTrack(\n attributes: ClipAttributeWriter,\n trackIndex: number | null | undefined,\n current: ClipTiming,\n): void {\n if (trackIndex === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n return;\n }\n if (trackIndex === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex) == null &&\n current.trackSource === \"legacy-layer\"\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(current.trackIndex));\n }\n return;\n }\n if (!Number.isFinite(trackIndex) || !Number.isInteger(trackIndex)) {\n throw new ClipTimingWriteError(\"invalid-track-index\", \"trackIndex must be a finite integer\");\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(trackIndex));\n}\n\n/** Write only canonical authored timing and remove derived/legacy source attributes. */\nexport function writeClipTiming(\n attributes: ClipAttributeWriter,\n update: ClipTimingUpdate,\n): ClipTiming {\n const current = readClipTiming(attributes);\n if (update.start !== undefined) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.start, serializeStart(update.start));\n }\n writeDuration(attributes, update.duration, current);\n writeTrack(attributes, update.trackIndex, current);\n // A legacy end paired with an unresolved reference start cannot be converted\n // to data-duration without a reference resolver. On a duration-omitting edit\n // (for example, moving only the track), preserve that sole duration source\n // instead of canonicalizing it into data loss.\n const preserveUnresolvedLegacyEnd =\n update.duration === undefined &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd) != null &&\n current.duration == null;\n if (!preserveUnresolvedLegacyEnd) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n }\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n return readClipTiming(attributes);\n}\n","import {\n clampPlaybackRate,\n readAuthoredDurationSeconds,\n readDataDurationSeconds,\n readMediaOffsetSeconds,\n readPlaybackRate,\n resolveMediaDuration,\n resolveNaturalDurationSeconds,\n} from \"@hyperframes/parsers/media-duration\";\nimport { resolveRateSpec, timeAtSourceTime, type RateSpec } from \"../speedRamp\";\nimport { isImageElement, isMediaElement } from \"./domRealm\";\nimport { parseNumeric } from \"./startExpression\";\n\nexport const normalizePlaybackRate = clampPlaybackRate;\n\n/** Parse a literal numeric timing attribute without accepting trailing units or garbage. */\nexport function parseStrictFiniteTimingNumber(raw: string | null | undefined): number | null {\n return parseNumeric(raw);\n}\n\nexport function readElementPlaybackRate(el: Pick<Element, \"getAttribute\">): number {\n return readPlaybackRate(\n (name) => el.getAttribute(name),\n isMediaElement(el) ? el.defaultPlaybackRate : 1,\n );\n}\n\n/** A constant rate clamped to the shared range; a lane is already normalised by its parser. */\nexport function normalizeRateSpec(spec: RateSpec | undefined): RateSpec {\n return typeof spec === \"object\" ? spec : normalizePlaybackRate(spec ?? 1);\n}\n\n/** The clip's rate: its `rate` lane when present, otherwise the constant rate. */\nexport function readElementRateSpec(el: Pick<Element, \"getAttribute\">): RateSpec {\n return resolveRateSpec(el.getAttribute(\"data-automation\"), readElementPlaybackRate(el));\n}\n\nexport function readMediaStart(el: Pick<Element, \"getAttribute\">): number {\n return readMediaOffsetSeconds((name) => el.getAttribute(name));\n}\n\nexport function resolveNaturalMediaTimelineDuration(\n el: Pick<Element, \"getAttribute\">,\n sourceDuration: number,\n): number | null {\n return resolveNaturalMediaTimelineDurationFromValues(\n sourceDuration,\n readMediaStart(el),\n readElementRateSpec(el),\n );\n}\n\n/**\n * How long a media element occupies the timeline: an authored `data-duration` trim, otherwise\n * the source's natural length adjusted for playback start and rate (lane-aware). `null` while\n * the source has not reported a duration yet. The authored/pending decision is the shared\n * parsers resolver's; only a `rate` lane's arithmetic stays here, because lanes live in core.\n */\nexport function resolveMediaElementDurationSeconds(\n el: Pick<Element, \"getAttribute\"> & { duration: number },\n): number | null {\n const resolved = resolveMediaDuration({\n tag: \"video\", // video and audio resolve identically\n authoredDurationSeconds: readDataDurationSeconds((name) => el.getAttribute(name)),\n sourceDurationSeconds: Number.isFinite(el.duration) ? el.duration : null,\n mediaStartSeconds: readMediaStart(el),\n playbackRate: readElementPlaybackRate(el),\n });\n return resolved.source === \"media\"\n ? resolveNaturalMediaTimelineDuration(el, el.duration)\n : resolved.seconds;\n}\n\n/** A timed `<img>` (`data-start` or `data-track-index`) gets the dropped-image default unless\n * trimmed by `data-duration` or `data-end`; a bare `<img>` is a static layer. `null` otherwise. */\nexport function resolveTimedImageDurationSeconds(el: Element, startSeconds = 0): number | null {\n if (!isImageElement(el)) return null;\n if (!el.hasAttribute(\"data-start\") && !el.hasAttribute(\"data-track-index\")) return null;\n return resolveMediaDuration({\n tag: \"img\",\n authoredDurationSeconds: readAuthoredDurationSeconds(\n (name) => el.getAttribute(name),\n startSeconds,\n ),\n sourceDurationSeconds: null,\n mediaStartSeconds: 0,\n playbackRate: 1,\n }).seconds;\n}\n\n/** A constant rate goes through the shared arithmetic; a `rate` lane integrates over its points. */\nexport function resolveNaturalMediaTimelineDurationFromValues(\n sourceDuration: number,\n mediaStart: number,\n playbackRate: RateSpec,\n): number | null {\n if (typeof playbackRate === \"number\") {\n return resolveNaturalDurationSeconds(sourceDuration, mediaStart, playbackRate);\n }\n if (!Number.isFinite(sourceDuration)) return null;\n return timeAtSourceTime(playbackRate, Math.max(0, sourceDuration - mediaStart));\n}\n","export interface HFDebugSurface {\n __hfDebug?: boolean;\n __HYPERFRAMES_DEBUG?: boolean;\n __hf?: {\n onSwallowed?: (event: { label: string; error: unknown }) => void;\n };\n}\n\nexport function getDebugSurface(): HFDebugSurface {\n return globalThis as HFDebugSurface;\n}\n","/**\n * Runtime diagnostic helpers for best-effort operations.\n *\n * Many runtime operations (postMessage to a parent frame, `media.play()` /\n * `pause()` / `currentTime=`, timeline `seek()`, anime.js feature detection,\n * etc.) can throw under perfectly normal conditions: the parent frame is\n * cross-origin, autoplay is denied, the media element was just removed from\n * the DOM, the timeline has been disposed, the host page does not include\n * anime.js. The right behaviour in each case is \"tried, didn't work, move\n * on\" — but emitting nothing makes silent failures invisible to anyone\n * debugging a genuinely broken composition, and the bare `catch {}` shape\n * also trips strict lint configurations on the inlined runtime IIFE.\n *\n * `swallow(label, err)` is the single funnel for these intentional silences.\n * It dispatches to:\n *\n * - `console.debug` with the label, the error, and a `[hyperframes]` prefix\n * when `window.__hfDebug === true` (or the legacy `__HYPERFRAMES_DEBUG`\n * env-style global). Quiet by default; flip the flag in DevTools when\n * hunting a regression.\n * - A custom `__hf.onSwallowed` handler if installed — lets the studio /\n * embeddings collect runtime swallow events without polluting the page\n * console.\n *\n * Production behaviour without either flag set: completely silent, just\n * like the original empty `catch {}`. The shape is also lint-clean — the\n * helper call is a real statement, so no `no-empty` warnings ship in the\n * inlined IIFE.\n */\nimport { getDebugSurface } from \"./globals.js\";\n\nexport function swallow(label: string, error?: unknown): void {\n if (typeof window === \"undefined\") return;\n const w = getDebugSurface();\n\n const handler = w.__hf?.onSwallowed;\n if (handler) {\n try {\n handler({ label, error });\n } catch (handlerError) {\n // Don't recurse into swallow() — a consumer hook that throws\n // shouldn't be allowed to take down the runtime, and routing the\n // failure back through swallow() would loop. Drop on the floor;\n // the original error already had its surface above.\n void handlerError;\n }\n }\n\n if (w.__hfDebug || w.__HYPERFRAMES_DEBUG) {\n // eslint-disable-next-line no-console -- intentional debug surface\n console.debug(`[hyperframes] ${label} swallowed:`, error);\n }\n}\n","export type AuthoredTimingValue = string | number | null | undefined;\n\nexport const AUTHORED_DURATION_ATTR = \"data-hf-authored-duration\";\nexport const AUTHORED_END_ATTR = \"data-hf-authored-end\";\n\nexport interface RawAuthoredTiming {\n start?: AuthoredTimingValue;\n duration?: AuthoredTimingValue;\n authoredDuration?: AuthoredTimingValue;\n end?: AuthoredTimingValue;\n authoredEnd?: AuthoredTimingValue;\n}\n\nexport interface AuthoredTimingWindow {\n start: number;\n duration: number | null;\n end: number | null;\n}\n\nfunction finiteNumber(value: AuthoredTimingValue): number | null {\n if (value == null) return null;\n if (typeof value === \"string\" && value.trim() === \"\") return null;\n const parsed = typeof value === \"number\" ? value : Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nexport function resolveAuthoredTimingWindow(\n values: RawAuthoredTiming,\n): AuthoredTimingWindow | null {\n const parsedStart = finiteNumber(values.start);\n if (parsedStart == null) return null;\n const start = Math.max(0, parsedStart);\n\n const publicDuration = finiteNumber(values.duration);\n const preservedDuration = finiteNumber(values.authoredDuration);\n const duration =\n publicDuration != null && publicDuration > 0\n ? publicDuration\n : preservedDuration != null && preservedDuration > 0\n ? preservedDuration\n : null;\n if (duration != null) return { start, duration, end: start + duration };\n\n const publicEnd = finiteNumber(values.end);\n const preservedEnd = finiteNumber(values.authoredEnd);\n const end =\n publicEnd != null && publicEnd > start\n ? publicEnd\n : preservedEnd != null && preservedEnd > start\n ? preservedEnd\n : null;\n return { start, duration: end == null ? null : end - start, end };\n}\n","export const MEDIA_START_BASIS_ATTR = \"data-hf-media-start-basis\";\n\nexport type MediaStartBasis = \"local\" | \"global\";\n\nexport function readMediaStartBasis(value: string | null | undefined): MediaStartBasis {\n return value?.trim().toLowerCase() === \"global\" ? \"global\" : \"local\";\n}\n\n/** Resolve authored media time onto the root timeline. Nested media is local\n * by default; only an explicit compatibility marker preserves a legacy\n * root-global value. */\nexport function resolveAbsoluteMediaStartSeconds(input: {\n authoredStart: number;\n hostStart: number;\n basis?: string | null;\n}): number {\n return readMediaStartBasis(input.basis) === \"global\"\n ? input.authoredStart\n : input.hostStart + input.authoredStart;\n}\n\n/** The one rule for a media element's root-timeline start; the runtime and the CLI both call it.\n * With no literal start, an auto-injected start, or a host at t<=0 there is nothing for the basis\n * to disambiguate, so the ordinary start resolution applies. */\nexport function resolveMediaStartSeconds(\n input: MediaStartInput & { ordinaryStart: () => number },\n): number {\n if (!hasLiteralNestedStart(input)) return input.ordinaryStart();\n return resolveAbsoluteMediaStartSeconds({\n authoredStart: input.authoredStart,\n hostStart: input.hostStart,\n basis: input.basis,\n });\n}\n\n/** True when the authored start is already root time, so no host offset applies. */\nexport function isRootGlobalMediaStart(input: MediaStartInput): boolean {\n return hasLiteralNestedStart(input) && readMediaStartBasis(input.basis) === \"global\";\n}\n\nexport interface MediaStartInput {\n authoredStart: number | null;\n hostStart: number;\n hasAutoStart: boolean;\n basis?: string | null;\n}\n\nfunction hasLiteralNestedStart(\n input: MediaStartInput,\n): input is MediaStartInput & { authoredStart: number } {\n return !input.hasAutoStart && input.authoredStart != null && input.hostStart > 0;\n}\n","import type { RuntimeTimelineLike } from \"./types\";\nimport { swallow } from \"./diagnostics\";\nimport { resolveAuthoredTimingWindow } from \"./authoredTiming\";\n// Straight from playbackRate, not through media.ts's re-export: media.ts\n// imports mediaVolumeEnvelope, which needs this resolver, and the round trip\n// would be an import cycle.\nimport {\n parseStrictFiniteTimingNumber,\n resolveNaturalMediaTimelineDuration,\n resolveTimedImageDurationSeconds,\n} from \"./playbackRate\";\nimport { isMediaElement } from \"./domRealm\";\nimport { parseStartExpression } from \"./startExpression\";\nimport {\n isRootGlobalMediaStart,\n MEDIA_START_BASIS_ATTR,\n resolveMediaStartSeconds,\n type MediaStartInput,\n} from \"../mediaTiming\";\n\nexport function createRuntimeStartTimeResolver(params: {\n timelineRegistry?: Record<string, Pick<RuntimeTimelineLike, \"duration\"> | undefined>;\n includeAuthoredTimingAttrs?: boolean;\n /**\n * The document that reference lookups (`data-start=\"intro + 2\"`) resolve\n * against. Defaults to the global `document` — the runtime bundle's own\n * realm. Hosts driving a composition in an IFRAME must pass that iframe's\n * document, or every reference silently resolves against the host page.\n */\n documentRef?: Document;\n}): {\n resolveStartForElement: (element: Element, fallback?: number) => number;\n resolveDurationForElement: (element: Element) => number | null;\n resolveMediaStartForElement: (element: Element) => number;\n resolveHostStartForElement: (element: Element) => number;\n isRootGlobalMediaStartForElement: (element: Element) => boolean;\n} {\n const timelineRegistry = params.timelineRegistry ?? {};\n const includeAuthoredTimingAttrs = params.includeAuthoredTimingAttrs ?? false;\n const doc = params.documentRef ?? document;\n const startCache = new WeakMap<Element, number | null>();\n const durationCache = new WeakMap<Element, number | null>();\n const visiting = new Set<Element>();\n\n const findReferenceTarget = (refId: string): Element | null => {\n const byId = doc.getElementById(refId);\n if (byId) return byId;\n return (\n (doc.querySelector(`[data-composition-id=\"${CSS.escape(refId)}\"]`) as Element | null) ?? null\n );\n };\n\n const resolveDurationForElement = (element: Element): number | null => {\n const cached = durationCache.get(element);\n if (cached !== undefined) return cached;\n let resolved: number | null = null;\n const durationTiming = resolveAuthoredTimingWindow({\n start: 0,\n duration: element.getAttribute(\"data-duration\"),\n authoredDuration: includeAuthoredTimingAttrs\n ? element.getAttribute(\"data-hf-authored-duration\")\n : null,\n });\n if (durationTiming?.duration != null && durationTiming.duration > 0) {\n resolved = durationTiming.duration;\n }\n if (resolved == null || resolved <= 0) {\n const start = resolveStartForElementInternal(element, 0);\n const endTiming = resolveAuthoredTimingWindow({\n start,\n end: element.getAttribute(\"data-end\"),\n authoredEnd: includeAuthoredTimingAttrs\n ? element.getAttribute(\"data-hf-authored-end\")\n : null,\n });\n if (endTiming?.duration != null && endTiming.duration > 0) {\n resolved = endTiming.duration;\n }\n }\n if ((resolved == null || resolved <= 0) && isMediaElement(element)) {\n resolved = resolveNaturalMediaTimelineDuration(element, element.duration);\n }\n if (resolved == null || resolved <= 0) resolved = resolveTimedImageDurationSeconds(element);\n if (resolved == null || resolved <= 0) {\n const compositionId = element.getAttribute(\"data-composition-id\");\n if (compositionId) {\n const timeline = timelineRegistry[compositionId] ?? null;\n if (timeline && typeof timeline.duration === \"function\") {\n try {\n const timelineDuration = Number(timeline.duration());\n if (Number.isFinite(timelineDuration) && timelineDuration > 0) {\n resolved = timelineDuration;\n }\n } catch (err) {\n // ignore broken timeline impls\n swallow(\"runtime.startResolver.site1\", err);\n }\n }\n }\n }\n if (resolved != null && Number.isFinite(resolved) && resolved > 0) {\n durationCache.set(element, resolved);\n return resolved;\n }\n durationCache.set(element, null);\n return null;\n };\n\n const resolveHostOffsetForElement = (element: Element, fallback: number): number => {\n if (element.hasAttribute(\"data-composition-id\")) {\n const parentComposition = element.parentElement?.closest(\"[data-composition-id]\");\n if (!parentComposition) return 0;\n return resolveStartForElementInternal(parentComposition, fallback);\n }\n const compositionRoot = element.closest(\"[data-composition-id]\");\n if (!compositionRoot) return 0;\n return resolveStartForElementInternal(compositionRoot, fallback);\n };\n\n const resolveStartForElementInternal = (element: Element, fallback: number): number => {\n const cached = startCache.get(element);\n if (cached !== undefined) {\n return cached == null ? fallback : cached;\n }\n if (visiting.has(element)) {\n return fallback;\n }\n visiting.add(element);\n try {\n const expression = parseStartExpression(element.getAttribute(\"data-start\"));\n if (!expression) {\n // If this element is a loaded composition inner root (has data-composition-id\n // but no data-start), walk up to the host parent which carries the actual\n // timing. This happens when the host uses a different data-composition-id\n // than the loaded file — e.g. host=\"montage\" but file has \"scene-10\", or\n // when the host itself has no data-composition-id at all (an \"anonymous\"\n // host) and the composition's own id was restored onto the inlined wrapper.\n // Check data-composition-src (runtime, not yet inlined), data-composition-id\n // (bundled/compiled host with its own id), and data-composition-file (the\n // marker every inlined host gets, compiled or bundled, once\n // data-composition-src is stripped — covers the anonymous-host case).\n if (element.hasAttribute(\"data-composition-id\")) {\n const parent = element.parentElement;\n if (\n parent &&\n (parent.hasAttribute(\"data-composition-src\") ||\n parent.hasAttribute(\"data-composition-id\") ||\n parent.hasAttribute(\"data-composition-file\"))\n ) {\n const parentStart = resolveStartForElementInternal(parent, fallback);\n startCache.set(element, parentStart);\n return parentStart;\n }\n }\n startCache.set(element, fallback);\n return fallback;\n }\n if (expression.kind === \"absolute\") {\n const absolute = Math.max(0, expression.value);\n const resolved = Math.max(0, resolveHostOffsetForElement(element, fallback) + absolute);\n startCache.set(element, resolved);\n return resolved;\n }\n const target = findReferenceTarget(expression.refId);\n if (!target) {\n startCache.set(element, fallback);\n return fallback;\n }\n const targetStart = resolveStartForElementInternal(target, 0);\n const targetDuration = resolveDurationForElement(target);\n if (targetDuration == null || targetDuration <= 0) {\n const unresolved = Math.max(0, targetStart + expression.offset);\n startCache.set(element, unresolved);\n return unresolved;\n }\n const resolved = Math.max(0, targetStart + targetDuration + expression.offset);\n startCache.set(element, resolved);\n return resolved;\n } finally {\n visiting.delete(element);\n }\n };\n\n /**\n * The ONE owner of \"when does this media element start on the root timeline\".\n *\n * A media element is not a plain timed clip: `data-hf-media-start-basis`\n * decides whether its `data-start` is composition-local (the default, so the\n * host offset is added) or a legacy root-global timestamp (already absolute,\n * so adding the host offset double-counts it). Anything that derives a media\n * start from attributes — the clip manifest, the visibility pass, the media\n * cache, WebAudio scheduling — must come through here, or the timeline the\n * editor draws stops matching the timeline that plays.\n */\n const mediaStartInput = (element: Element): MediaStartInput => {\n const compositionRoot = element.closest(\"[data-composition-id]\");\n return {\n authoredStart: parseStrictFiniteTimingNumber(element.getAttribute(\"data-start\")),\n hostStart: compositionRoot ? resolveStartForElementInternal(compositionRoot, 0) : 0,\n hasAutoStart: element.hasAttribute(\"data-hf-auto-start\"),\n basis: element.getAttribute(MEDIA_START_BASIS_ATTR),\n };\n };\n\n const resolveMediaStartForElement = (element: Element): number => {\n const input = mediaStartInput(element);\n return resolveMediaStartSeconds({\n ...input,\n ordinaryStart: () => resolveStartForElementInternal(element, input.hostStart),\n });\n };\n\n const isRootGlobalMediaStartForElement = (element: Element): boolean =>\n isMediaElement(element) && isRootGlobalMediaStart(mediaStartInput(element));\n\n return {\n resolveStartForElement: (element: Element, fallback = 0) =>\n resolveStartForElementInternal(element, Math.max(0, fallback)),\n resolveDurationForElement: (element: Element) => resolveDurationForElement(element),\n resolveMediaStartForElement,\n resolveHostStartForElement: (element: Element) => resolveHostOffsetForElement(element, 0),\n isRootGlobalMediaStartForElement,\n };\n}\n\nexport type { RuntimeTimelineLike } from \"./types\";\n","import type { RuntimeDeterministicAdapter, RuntimeTimelineLike } from \"./types\";\nimport type { RuntimeMediaClip } from \"./media\";\nimport type { TransportClock } from \"./clock\";\n\nexport type RuntimeState = {\n capturedTimeline: RuntimeTimelineLike | null;\n isPlaying: boolean;\n currentTime: number;\n deterministicAdapters: RuntimeDeterministicAdapter[];\n canonicalFps: number;\n bridgeMuted: boolean;\n bridgeVolume: number;\n /**\n * Internal mute of audible media output, owned by the audio-ownership\n * protocol between the parent (`<hyperframes-player>`) and this runtime.\n * Independent of `bridgeMuted` (the user's mute preference). When the\n * parent takes over audible playback via parent-frame proxies, it sets\n * this to `true` so the runtime keeps driving timed media for frame\n * accuracy but produces no audio of its own.\n */\n mediaOutputMuted: boolean;\n /**\n * Disable runtime ownership of native media elements. Slideshow presenter\n * mode keeps the slide timeline paused while users interact with embedded\n * media, so the runtime must not auto-play, auto-pause, seek, or volume-sync\n * those native elements on every transport tick.\n */\n nativeMediaSyncDisabled: boolean;\n /**\n * Disable the runtime's WebAudio replacement for native <audio> elements.\n * Slideshow presenter/audience windows mirror native media element events\n * across browsers, so muting those elements for WebAudio ownership breaks\n * audible presenter playback and remote sync.\n */\n webAudioMediaDisabled: boolean;\n /**\n * Latch so the `media-autoplay-blocked` outbound message is posted at most\n * once per runtime session. The parent only needs the first signal — it\n * takes over playback and further rejections are the same problem.\n */\n mediaAutoplayBlockedPosted: boolean;\n /**\n * One-shot flag: force a hard media sync on the next tick. Set on\n * play/pause/seek/rate transitions to immediately correct any\n * accumulated sub-threshold drift from pause/play toggling.\n * Consumed (reset to false) by `syncMediaForCurrentState`.\n */\n mediaForceSyncNextTick: boolean;\n playbackRate: number;\n bridgeLastPostedFrame: number;\n bridgeLastPostedAt: number;\n bridgeLastPostedPlaying: boolean;\n bridgeLastPostedMuted: boolean;\n /**\n * Max interval (ms) between outbound timeline samples on the parent-frame\n * control bridge. The bridge posts on every changed frame, but also at\n * least once per this interval so a paused/idle timeline still confirms\n * its position to any listener.\n *\n * **Cross-reference (do not change in isolation)**: the parent-frame\n * audio-mirror loop in `<hyperframes-player>` waits for\n * `MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES` consecutive over-threshold\n * samples before issuing a `currentTime` correction. The product of\n * those two constants is the worst-case A/V re-sync latency:\n *\n * worst_case_correction_latency_ms\n * ≈ MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES × bridgeMaxPostIntervalMs\n *\n * Today: `2 × 80 ms = 160 ms`, which sits comfortably under the\n * perceptual A/V re-sync tolerance. If you raise this interval, audit\n * `MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES` in\n * `packages/player/src/parent-media.ts` — leaving it at `2` will\n * silently push correction latency past the tolerance budget.\n */\n bridgeMaxPostIntervalMs: number;\n controlBridgeHandler: ((event: MessageEvent) => void) | null;\n beforeUnloadHandler: (() => void) | null;\n injectedCompStyles: HTMLStyleElement[];\n injectedCompScripts: HTMLScriptElement[];\n injectedCompLinks: HTMLLinkElement[];\n cachedTimedMediaEls: Array<HTMLVideoElement | HTMLAudioElement>;\n cachedMediaClips: RuntimeMediaClip[];\n cachedVideoClips: RuntimeMediaClip[];\n cachedMediaTimelineDurationSeconds: number;\n tornDown: boolean;\n /**\n * Single-clock transport. The sole time authority — GSAP is always\n * paused and seeked to `clock.now()` on each rAF tick. Eliminates\n * the two-clock drift problem described in issue #668.\n */\n transportClock: TransportClock | null;\n /** rAF ID for the single-clock tick loop. */\n transportRafId: number | null;\n};\n\nexport function createRuntimeState(): RuntimeState {\n return {\n capturedTimeline: null,\n isPlaying: false,\n currentTime: 0,\n deterministicAdapters: [],\n canonicalFps: 30,\n bridgeMuted: false,\n bridgeVolume: 1,\n mediaOutputMuted: false,\n nativeMediaSyncDisabled: false,\n webAudioMediaDisabled: false,\n mediaAutoplayBlockedPosted: false,\n mediaForceSyncNextTick: false,\n playbackRate: 1,\n bridgeLastPostedFrame: -1,\n bridgeLastPostedAt: 0,\n bridgeLastPostedPlaying: false,\n bridgeLastPostedMuted: false,\n bridgeMaxPostIntervalMs: 80,\n controlBridgeHandler: null,\n beforeUnloadHandler: null,\n injectedCompStyles: [],\n injectedCompScripts: [],\n injectedCompLinks: [],\n cachedTimedMediaEls: [],\n cachedMediaClips: [],\n cachedVideoClips: [],\n cachedMediaTimelineDurationSeconds: 0,\n tornDown: false,\n transportClock: null,\n transportRafId: null,\n };\n}\n","import {\n resolveCompositionDuration,\n type CompositionDurationResult,\n} from \"@hyperframes/parsers/composition-duration\";\nimport { findRootCompositionElement, parseCompositionDimension } from \"./compositionDimension\";\nimport { isMediaElement } from \"./domRealm\";\nimport { parseStrictFiniteTimingNumber, resolveMediaElementDurationSeconds } from \"./playbackRate\";\nimport { createRuntimeStartTimeResolver } from \"./startResolver\";\nimport { createRuntimeState } from \"./state\";\n\nexport { findRootCompositionElement };\n\n/** One frame at 60 fps: a timeline, floor or fallback this short or shorter is no length at all. */\nexport const MIN_VALID_TIMELINE_DURATION_SECONDS = 1 / 60;\n\ntype StartResolver = Pick<\n ReturnType<typeof createRuntimeStartTimeResolver>,\n \"resolveStartForElement\" | \"resolveDurationForElement\"\n>;\n\nconst aboveOneFrame = (seconds: number | null): number | null =>\n seconds !== null && seconds > MIN_VALID_TIMELINE_DURATION_SECONDS ? seconds : null;\n\n/** The size the runtime reports to its host. */\nexport function readCompositionSize(root: Element): {\n width: number | null;\n height: number | null;\n} {\n return {\n width: parseCompositionDimension(root.getAttribute(\"data-width\")),\n height: parseCompositionDimension(root.getAttribute(\"data-height\")),\n };\n}\n\n/** The latest end among the document's timed video and audio, at their absolute starts. */\nexport function resolveMediaWindowDurationSeconds(\n doc: ParentNode,\n media: {\n mediaStart: (el: HTMLMediaElement) => number;\n mediaDuration: (el: HTMLMediaElement) => number | null;\n },\n): number | null {\n const clipEnds: number[] = [];\n for (const node of doc.querySelectorAll<HTMLMediaElement>(\n \"video[data-start], audio[data-start]\",\n )) {\n const start = media.mediaStart(node);\n if (!Number.isFinite(start)) continue;\n const duration = media.mediaDuration(node);\n if (duration == null || duration <= MIN_VALID_TIMELINE_DURATION_SECONDS) continue;\n clipEnds.push(Math.max(0, start) + duration);\n }\n return aboveOneFrame(\n resolveCompositionDuration({ authoredDurationSeconds: null, clipEndsSeconds: clipEnds })\n .seconds,\n );\n}\n\n/** The latest of the root's declared length and its direct sub-compositions' ends. */\nexport function resolveAuthoredCompositionFloorSeconds(\n root: Element,\n startResolver: StartResolver,\n): number | null {\n const subCompositionEnds: number[] = [];\n for (const node of root.querySelectorAll(\"[data-composition-id][data-start]\")) {\n if (node.parentElement?.closest(\"[data-composition-id]\") !== root) continue;\n const start = startResolver.resolveStartForElement(node, 0);\n const duration = startResolver.resolveDurationForElement(node);\n if (!Number.isFinite(start) || duration == null || duration <= 0) continue;\n subCompositionEnds.push(Math.max(0, start) + duration);\n }\n const declared = parseStrictFiniteTimingNumber(root.getAttribute(\"data-duration\"));\n return aboveOneFrame(\n resolveCompositionDuration({\n authoredDurationSeconds: null,\n clipEndsSeconds: [declared, ...subCompositionEnds],\n }).seconds,\n );\n}\n\nconst isCompositionHost = (node: Element): boolean =>\n node.hasAttribute(\"data-composition-id\") || node.hasAttribute(\"data-composition-src\");\n\n/** The last-resort length: the latest end among the root's timed clips. Until every clip's length\n * is known it is unresolved, so a renderer that reads it once cannot lock in a short one.\n * `unregisteredLottie`: a Lottie library is loaded, so animations may still register. */\nexport function resolveContentDerivedDuration(\n root: Element,\n startResolver: StartResolver,\n { unregisteredLottie }: { unregisteredLottie: boolean },\n): CompositionDurationResult {\n const clipEnds: Array<number | null> = [];\n for (const node of root.querySelectorAll(\"[data-start]\")) {\n const start = startResolver.resolveStartForElement(node, 0);\n if (!Number.isFinite(start)) continue;\n const duration = startResolver.resolveDurationForElement(node);\n if (duration != null) clipEnds.push(Math.max(0, start) + duration);\n else if (isMediaElement(node) || isCompositionHost(node)) clipEnds.push(null);\n }\n if (unregisteredLottie || root.querySelector(\"[data-lottie-src]\")) clipEnds.push(null);\n const result = resolveCompositionDuration({\n authoredDurationSeconds: null,\n clipEndsSeconds: clipEnds,\n });\n return result.pendingClips > 0\n ? { ...result, seconds: null, source: \"unresolved\", reason: \"a clip's length is pending\" }\n : result;\n}\n\nexport type CompositionLengthInputs = {\n declared: number | null;\n // Read only when nothing is declared: they can run author or adapter code.\n timeline: () => number | null;\n floors: () => ReadonlyArray<number | null>;\n fallback: number;\n derived: () => number;\n};\n\n/** A declared root length is the length (a longer timeline is cut off); else the timeline, the\n * floors and the fallback, whichever is longest; else the length derived from the clips. */\nexport function resolveCompositionLengthSeconds(input: CompositionLengthInputs): number {\n if (input.declared !== null && Number.isFinite(input.declared) && input.declared > 0) {\n return input.declared;\n }\n const timeline = aboveOneFrame(input.timeline());\n const floor = Math.max(0, ...input.floors().map((seconds) => seconds ?? 0));\n const fallback =\n Number.isFinite(input.fallback) && input.fallback > MIN_VALID_TIMELINE_DURATION_SECONDS\n ? input.fallback\n : 0;\n let seconds: number;\n if (timeline !== null) seconds = Math.max(timeline, floor, fallback);\n else if (aboveOneFrame(floor) !== null) seconds = Math.max(floor, fallback);\n else if (fallback > 0) seconds = fallback;\n else seconds = input.derived();\n return seconds > 0 ? seconds : 0;\n}\n\n/** Size, frame rate and length of a composition nothing is running (a server-parsed or detached\n * document): no timelines, no adapters, and media without a known source length is pending. */\nexport function readStaticCompositionMeta(\n doc: Document,\n): { width: number | null; height: number | null; fps: number; durationSeconds: number } | null {\n const root = findRootCompositionElement(doc);\n if (!root) return null;\n const resolver = createRuntimeStartTimeResolver({\n timelineRegistry: {},\n includeAuthoredTimingAttrs: true,\n documentRef: doc,\n });\n const durationSeconds = resolveCompositionLengthSeconds({\n declared: parseStrictFiniteTimingNumber(root.getAttribute(\"data-duration\")),\n timeline: () => null,\n floors: () => [\n resolveMediaWindowDurationSeconds(doc, {\n mediaStart: resolver.resolveMediaStartForElement,\n mediaDuration: resolveMediaElementDurationSeconds,\n }),\n resolveAuthoredCompositionFloorSeconds(root, resolver),\n ],\n fallback: 0,\n derived: () =>\n resolveContentDerivedDuration(root, resolver, { unregisteredLottie: false }).seconds ?? 0,\n });\n return { ...readCompositionSize(root), fps: createRuntimeState().canonicalFps, durationSeconds };\n}\n","export const STUDIO_PREVIEW_MARK_META = \"hyperframes-studio-preview\";\nexport const STUDIO_PREVIEW_UPCOMING_ATTR = \"data-hf-upcoming\";\nexport const STUDIO_PREVIEW_LAZY_ATTR = \"data-hf-preview-lazy\";\nexport const STUDIO_PREVIEW_ERRORS = \"__hfPreviewErrors\";\n","/**\n * Decide whether the player should inject the HyperFrames runtime on the\n * current probe tick.\n *\n * The player polls the loaded iframe every 200ms to discover either:\n * - a runtime bridge already installed (`window.__hf` / `window.__player`), or\n * - GSAP timelines registered at `window.__timelines`.\n *\n * Two classes of composition require different injection timing:\n *\n * Nested — the composition uses `data-composition-src` on child elements to\n * lazy-load sub-scenes. The runtime is what loads those children, so the\n * composition cannot possibly render on its own. We inject immediately; if\n * we waited, an inline pre-runtime `gsap.timeline` (common for authoring a\n * preview before the runtime rebuilds the master timeline) would register\n * at `__timelines[\"main\"]` with a partial duration, and the adapter path\n * would then lock the player into `ready` against that incomplete timeline.\n *\n * Self-contained — the composition has no nested scenes and ships all of\n * its animation inline (timelines registered under `__timelines`). These\n * don't strictly need the runtime; the adapter can drive them directly.\n * We give the adapter path first shot (a 5-tick grace period) and only\n * inject the runtime as a fallback if no adapter emerges.\n */\nexport interface ProbeState {\n hasRuntime: boolean;\n hasTimelines: boolean;\n hasNestedCompositions: boolean;\n runtimeInjected: boolean;\n attempts: number;\n}\n\nexport function shouldInjectRuntime(state: ProbeState): boolean {\n if (state.hasRuntime || state.runtimeInjected) return false;\n if (state.hasNestedCompositions) return true;\n if (state.hasTimelines && state.attempts >= 5) return true;\n return false;\n}\n","/**\n * Types and type-guards for the playback adapter paths the player supports:\n *\n * - `RuntimeDurationAdapter` — the HyperFrames runtime exposes `window.__player`\n * with a `getDuration()` method. This is the standard path for compositions\n * served through the runtime bridge.\n *\n * - `DirectTimelineAdapter` — same-origin standalone compositions can expose\n * their GSAP master timeline at `window.__timelines` without installing the\n * full runtime. The player drives play/pause/seek directly against the\n * timeline object, bypassing the postMessage bridge.\n *\n * `PlaybackDurationAdapter` is the discriminated union the probe interval\n * returns after deciding which path is available.\n */\n\nexport interface RuntimeDurationAdapter {\n getDuration: () => number;\n}\n\nexport interface DirectTimelineAdapter {\n duration: () => number;\n time: () => number;\n // suppressEvents mirrors GSAP's timeline.seek(position, suppressEvents); pass\n // false to fire onUpdate (so imperative-visibility compositions repaint on seek).\n seek: (timeInSeconds: number, suppressEvents?: boolean) => unknown;\n play: () => unknown;\n pause: () => unknown;\n /** Optional: set playback rate (e.g. GSAP's timeScale). Called when the player's playbackRate changes. */\n timeScale?: (scale: number) => unknown;\n}\n\nexport type PlaybackDurationAdapter =\n | { kind: \"document\"; getDuration: () => number }\n | { kind: \"runtime\"; getDuration: () => number }\n | { kind: \"direct-timeline\"; timeline: DirectTimelineAdapter; getDuration: () => number };\n\nexport function isObjectRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport function isRuntimeDurationAdapter(value: unknown): value is RuntimeDurationAdapter {\n return isObjectRecord(value) && typeof value.getDuration === \"function\";\n}\n\nexport function isDirectTimelineAdapter(value: unknown): value is DirectTimelineAdapter {\n return (\n isObjectRecord(value) &&\n typeof value.duration === \"function\" &&\n typeof value.time === \"function\" &&\n typeof value.seek === \"function\" &&\n typeof value.play === \"function\" &&\n typeof value.pause === \"function\"\n );\n}\n","/**\n * Where the runtime comes from.\n *\n * Split out of composition-probe so the probe's late injection and the srcdoc's\n * parse-time injection cannot drift onto different URLs.\n */\ndeclare const __HYPERFRAMES_RUNTIME_CDN_URL__: string;\n\nexport function runtimeCdnUrlForVersion(version: string): string {\n if (!/^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/.test(version)) {\n throw new Error(`Invalid HyperFrames runtime version: ${version}`);\n }\n return `https://cdn.jsdelivr.net/npm/@hyperframes/core@${version}/dist/hyperframe.runtime.iife.js`;\n}\n\nexport const RUNTIME_CDN_URL =\n typeof __HYPERFRAMES_RUNTIME_CDN_URL__ === \"string\"\n ? __HYPERFRAMES_RUNTIME_CDN_URL__\n : runtimeCdnUrlForVersion(\"0.0.0-dev\");\n","/**\n * Probes an iframe document to discover the composition's playback adapter\n * and detect whether the HyperFrames runtime needs to be injected.\n *\n * The probe interval polls every 200 ms until one of:\n * - An adapter or the document resolves with a positive duration, or\n * - 40 attempts (~8 s) expire without a result.\n *\n * The `CompositionProbe` class owns the interval; the caller must call\n * `stop()` on disconnect or src change.\n */\n\nimport { readStaticCompositionMeta } from \"@hyperframes/core/runtime/composition-length\";\nimport { STUDIO_PREVIEW_ERRORS } from \"@hyperframes/core/studio-preview-mark\";\nimport { shouldInjectRuntime } from \"./shouldInjectRuntime.js\";\nimport {\n type DirectTimelineAdapter,\n type PlaybackDurationAdapter,\n isDirectTimelineAdapter,\n isObjectRecord,\n isRuntimeDurationAdapter,\n} from \"./timeline-adapters.js\";\n\nimport { RUNTIME_CDN_URL, runtimeCdnUrlForVersion } from \"./runtime-url.js\";\n\nexport { runtimeCdnUrlForVersion };\n\nexport interface ProbeResult {\n duration: number;\n adapter: PlaybackDurationAdapter;\n /** Resolved composition dimensions, if present in the document. */\n compositionSize: { width: number; height: number } | null;\n}\n\nexport interface ProbeCallbacks {\n onReady: (result: ProbeResult) => void;\n onError: (message: string) => void;\n /** Called when runtime is successfully injected (informational). */\n onRuntimeInjected?: () => void;\n}\n\n/**\n * Parse a composition dimension, rejecting anything that isn't a positive\n * finite number. Exported because the `width`/`height` attribute handlers in\n * hyperframes-player.ts need the same guard: dimensions feed\n * scaleIframeToFit's `w / compositionWidth` division, where NaN produces an\n * invalid `scale(NaN)` transform and zero a division by zero — both render\n * the player blank with no signal.\n */\nexport function readPositiveDimension(value: string | null): number | null {\n if (value === null) return null;\n const parsed = Number.parseInt(value, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n}\n\nexport function readCompositionSizeFromDocument(\n doc: Document | null | undefined,\n): { width: number; height: number } | null {\n const root =\n doc?.querySelector(\"[data-composition-id][data-width][data-height]\") ??\n doc?.querySelector(\"[data-width][data-height]\");\n if (!root) return null;\n const width = readPositiveDimension(root.getAttribute(\"data-width\"));\n const height = readPositiveDimension(root.getAttribute(\"data-height\"));\n return width !== null && height !== null ? { width, height } : null;\n}\n\ntype ProbeOutcome =\n | { kind: \"ready\"; result: ProbeResult }\n | { kind: \"error\"; message: string }\n | null;\n\nfunction firstAuthorError(errors: unknown): string | null {\n if (!Array.isArray(errors)) return null;\n return (\n errors.find(\n (value): value is string =>\n typeof value === \"string\" && value.trim() !== \"\" && value !== \"[object Event]\",\n ) ?? null\n );\n}\n\nexport class CompositionProbe {\n private _interval: ReturnType<typeof setInterval> | null = null;\n private _runtimeInjected = false;\n private _failure: { document: Document | null } | null = null;\n\n get failed(): boolean {\n return this._failure !== null && this._failure.document === this._iframe.contentDocument;\n }\n\n constructor(\n private readonly _iframe: HTMLIFrameElement,\n private readonly _callbacks: ProbeCallbacks,\n ) {}\n\n // fallow-ignore-next-line unused-class-member\n get runtimeInjected(): boolean {\n return this._runtimeInjected;\n }\n\n /** Start or restart the probe, stopping the active interval first. */\n start(): void {\n this.stop();\n this._failure = null;\n this._runtimeInjected = false;\n let attempts = 0;\n\n this._interval = setInterval(() => {\n attempts++;\n let outcome: ProbeOutcome = null;\n try {\n outcome = this._poll(attempts);\n } catch (error) {\n if (!(error instanceof DOMException && error.name === \"SecurityError\")) {\n outcome = {\n kind: \"error\",\n message: error instanceof Error ? error.message : String(error),\n };\n }\n }\n if (outcome) {\n this.stop();\n if (outcome.kind === \"error\") {\n this._failure = { document: this._iframe.contentDocument };\n this._callbacks.onError(outcome.message);\n } else this._callbacks.onReady(outcome.result);\n return;\n }\n if (attempts >= 40) {\n this.stop();\n this._failure = { document: this._iframe.contentDocument };\n this._callbacks.onError(\"Composition timeline not found after 8s\");\n }\n }, 200);\n }\n\n private _poll(attempts: number): ProbeOutcome {\n const win = this._iframe.contentWindow;\n if (!win) return null;\n const message = firstAuthorError(Reflect.get(win, STUDIO_PREVIEW_ERRORS));\n if (message !== null) return { kind: \"error\", message };\n const doc = this._iframe.contentDocument;\n const timelines = Reflect.get(win, \"__timelines\");\n const hasRuntime = this.hasRuntimeBridge(win);\n if (\n shouldInjectRuntime({\n hasRuntime,\n hasTimelines: isObjectRecord(timelines) && Object.keys(timelines).length > 0,\n hasNestedCompositions: !!doc?.querySelector(\"[data-composition-src]\"),\n runtimeInjected: this._runtimeInjected,\n attempts,\n })\n ) {\n this._injectRuntime();\n return null;\n }\n if (this._runtimeInjected && !hasRuntime) return null;\n const result = this._resolveReadyResult(win, doc);\n return result ? { kind: \"ready\", result } : null;\n }\n\n private _resolveReadyResult(win: Window, doc: Document | null): ProbeResult | null {\n const adapter: PlaybackDurationAdapter = this._resolvePlaybackDurationAdapter(win) ?? {\n kind: \"document\",\n getDuration: () => 0,\n };\n let duration = adapter.getDuration();\n if (!Number.isFinite(duration) || duration <= 0) {\n if (adapter.kind === \"direct-timeline\") return null;\n duration = doc ? (readStaticCompositionMeta(doc)?.durationSeconds ?? 0) : 0;\n }\n if (duration <= 0) return null;\n return {\n duration,\n adapter: { ...adapter, getDuration: () => duration },\n compositionSize: readCompositionSizeFromDocument(doc),\n };\n }\n\n stop(): void {\n if (!this.failed) this._failure = null;\n if (this._interval !== null) {\n clearInterval(this._interval);\n this._interval = null;\n }\n }\n\n // ── Adapter resolution (same-origin only) ────────────────────────────────\n\n resolveDirectTimelineAdapter(): DirectTimelineAdapter | null {\n try {\n const win = this._iframe.contentWindow;\n if (!win) return null;\n return this._resolveDirectTimelineAdapterFromWindow(win);\n } catch {\n return null;\n }\n }\n\n // fallow-ignore-next-line unused-class-member\n resolveDirectTimelineAdapterFromWindow(win: Window): DirectTimelineAdapter | null {\n return this._resolveDirectTimelineAdapterFromWindow(win);\n }\n\n hasRuntimeBridge(win: Window): boolean {\n return Reflect.get(win, \"__hf\") !== undefined || isObjectRecord(Reflect.get(win, \"__player\"));\n }\n\n // ── Private ──────────────────────────────────────────────────────────────\n\n private _injectRuntime(): void {\n this._runtimeInjected = true;\n try {\n const doc = this._iframe.contentDocument;\n if (!doc) return;\n const script = doc.createElement(\"script\");\n script.src = RUNTIME_CDN_URL;\n (doc.head || doc.documentElement).appendChild(script);\n this._callbacks.onRuntimeInjected?.();\n } catch {\n /* cross-origin — can't inject */\n }\n }\n\n private _resolveDirectTimelineAdapterFromWindow(win: Window): DirectTimelineAdapter | null {\n if (this.hasRuntimeBridge(win)) return null;\n\n const timelines = Reflect.get(win, \"__timelines\");\n if (!isObjectRecord(timelines)) return null;\n\n const keys = Object.keys(timelines);\n if (keys.length === 0) return null;\n\n const rootId = this._iframe.contentDocument\n ?.querySelector(\"[data-composition-id]\")\n ?.getAttribute(\"data-composition-id\");\n const key = rootId && rootId in timelines ? rootId : keys[keys.length - 1];\n const timeline = timelines[key];\n return isDirectTimelineAdapter(timeline) ? timeline : null;\n }\n\n private _resolvePlaybackDurationAdapter(win: Window): PlaybackDurationAdapter | null {\n const runtimePlayer = Reflect.get(win, \"__player\");\n if (isRuntimeDurationAdapter(runtimePlayer)) {\n return { kind: \"runtime\", getDuration: () => runtimePlayer.getDuration() };\n }\n\n const timeline = this._resolveDirectTimelineAdapterFromWindow(win);\n if (timeline) {\n return {\n kind: \"direct-timeline\",\n timeline,\n getDuration: () => timeline.duration(),\n };\n }\n\n return null;\n }\n}\n","export const PLAYER_STYLES = /* css */ `\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n background: #000;\n contain: layout style;\n }\n\n .hfp-container {\n position: absolute;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n }\n\n\n .hfp-iframe {\n position: absolute;\n top: 50%;\n left: 50%;\n border: none;\n pointer-events: none;\n }\n\n /* Opt-in: an interactive composition (e.g. a live slideshow/app with playable\n media or controls) — let pointer events reach the iframe content. */\n :host([interactive]) .hfp-container,\n :host([interactive]) .hfp-iframe {\n pointer-events: auto;\n }\n\n .hfp-video {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: contain;\n pointer-events: none;\n }\n\n .hfp-poster {\n position: absolute;\n inset: 0;\n object-fit: contain;\n z-index: 1;\n pointer-events: none;\n }\n\n .hfp-shader-loader {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: grid;\n place-items: center;\n visibility: hidden;\n opacity: 0;\n pointer-events: none;\n background: #030504;\n color: #f4f7fb;\n cursor: default;\n user-select: none;\n -webkit-user-select: none;\n transition: opacity 420ms ease-out, visibility 420ms ease-out;\n }\n\n .hfp-shader-loader.hfp-visible,\n .hfp-shader-loader.hfp-hiding {\n visibility: visible;\n }\n\n .hfp-shader-loader.hfp-visible {\n opacity: 1;\n pointer-events: auto;\n }\n\n .hfp-shader-loader.hfp-hiding {\n opacity: 0;\n pointer-events: none;\n }\n\n .hfp-shader-loader-panel {\n display: grid;\n grid-template-rows: 86px 40px 26px 12px 44px;\n justify-items: center;\n align-items: center;\n gap: 8px;\n width: min(620px, 82%);\n text-align: center;\n font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n }\n\n .hfp-shader-loader-mark {\n width: 86px;\n height: 86px;\n display: grid;\n place-items: center;\n overflow: visible;\n }\n\n .hfp-shader-loader-mark svg {\n display: block;\n overflow: visible;\n filter: drop-shadow(0 0 5px rgba(79, 219, 94, 0.16));\n pointer-events: none;\n }\n\n .hfp-shader-loader-title {\n width: 100%;\n height: 40px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n font-size: 26px;\n line-height: 40px;\n font-weight: 700;\n letter-spacing: 0;\n }\n\n .hfp-shader-loader-title-text {\n color: transparent;\n background: linear-gradient(\n 90deg,\n rgba(244, 247, 251, 0.84) 0%,\n #ffffff 42%,\n #80efe4 52%,\n #ffffff 62%,\n rgba(244, 247, 251, 0.84) 100%\n );\n background-size: 220% 100%;\n -webkit-background-clip: text;\n background-clip: text;\n animation: hfp-shader-loader-sheen 1.9s linear infinite;\n }\n\n .hfp-shader-loader:not(.hfp-visible):not(.hfp-hiding) .hfp-shader-loader-title-text {\n animation-play-state: paused;\n }\n\n .hfp-shader-loader-detail {\n width: 100%;\n height: 26px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n color: rgba(244, 247, 251, 0.62);\n font-size: 15px;\n line-height: 26px;\n font-weight: 500;\n }\n\n .hfp-shader-loader-track {\n width: min(360px, 100%);\n height: 8px;\n overflow: hidden;\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.1);\n }\n\n .hfp-shader-loader-fill {\n width: 100%;\n height: 100%;\n border-radius: inherit;\n background: linear-gradient(90deg, #06e3fa, #4fdb5e);\n transform: scaleX(0);\n transform-origin: left center;\n transition: transform 160ms ease;\n }\n\n .hfp-shader-loader-progress {\n width: min(420px, 100%);\n height: 44px;\n display: grid;\n grid-template-rows: repeat(2, 22px);\n color: rgba(244, 247, 251, 0.48);\n font: 600 13px/22px \"IBM Plex Mono\", \"SF Mono\", \"Fira Code\", \"Courier New\", monospace;\n font-variant-numeric: tabular-nums;\n }\n\n .hfp-shader-loader-row {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 74px;\n align-items: center;\n column-gap: 20px;\n width: 100%;\n white-space: nowrap;\n }\n\n .hfp-shader-loader-label {\n min-width: 0;\n overflow: hidden;\n text-align: left;\n text-overflow: ellipsis;\n }\n\n .hfp-shader-loader-value {\n text-align: right;\n }\n\n @keyframes hfp-shader-loader-sheen {\n from {\n background-position: 140% 0;\n }\n to {\n background-position: -140% 0;\n }\n }\n\n /* ── Theming via CSS custom properties ──\n *\n * Override from outside the shadow DOM:\n * hyperframes-player {\n * --hfp-controls-bg: linear-gradient(transparent, rgba(0,0,0,0.9));\n * --hfp-accent: #ff6b6b;\n * --hfp-font: \"Inter\", sans-serif;\n * }\n */\n\n .hfp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n gap: var(--hfp-controls-gap, 12px);\n padding: var(--hfp-controls-padding, 8px 16px);\n background: var(--hfp-controls-bg, linear-gradient(transparent, rgba(0, 0, 0, 0.7)));\n color: var(--hfp-color, #fff);\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: var(--hfp-font-size, 13px);\n z-index: 10;\n pointer-events: auto;\n opacity: 1;\n transition: opacity 0.3s ease;\n user-select: none;\n }\n\n .hfp-controls.hfp-hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .hfp-play-btn {\n position: relative;\n background: none;\n border: none;\n color: var(--hfp-color, #fff);\n cursor: pointer;\n padding: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n z-index: 10;\n }\n\n .hfp-play-btn:hover {\n opacity: 0.8;\n }\n\n /* Stacked play/pause glyphs that crossfade-morph on toggle (rotate + scale). */\n .hfp-play-btn .hfp-ico {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n opacity 200ms ease,\n transform 220ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n .hfp-play-btn .hfp-ico-play {\n opacity: 1;\n transform: rotate(0) scale(1);\n }\n .hfp-play-btn .hfp-ico-pause {\n opacity: 0;\n transform: rotate(-90deg) scale(0.4);\n }\n .hfp-play-btn.hfp-playing .hfp-ico-play {\n opacity: 0;\n transform: rotate(90deg) scale(0.4);\n }\n .hfp-play-btn.hfp-playing .hfp-ico-pause {\n opacity: 1;\n transform: rotate(0) scale(1);\n }\n @media (prefers-reduced-motion: reduce) {\n .hfp-play-btn .hfp-ico {\n transition-duration: 0ms;\n transform: none;\n }\n }\n\n .hfp-play-btn svg,\n .hfp-play-btn svg * {\n pointer-events: none;\n }\n\n .hfp-scrubber {\n flex: 1;\n min-width: 0;\n height: var(--hfp-scrubber-height, 4px);\n background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));\n border-radius: var(--hfp-scrubber-radius, 2px);\n cursor: pointer;\n position: relative;\n overflow: hidden;\n }\n\n .hfp-scrubber:hover {\n height: var(--hfp-scrubber-height-hover, 6px);\n }\n\n .hfp-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--hfp-accent, #fff);\n pointer-events: none;\n }\n\n .hfp-time {\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n opacity: 0.9;\n }\n\n .hfp-speed-wrap {\n position: relative;\n flex-shrink: 0;\n }\n\n .hfp-speed-btn {\n background: var(--hfp-speed-btn-bg, rgba(255, 255, 255, 0.15));\n border: none;\n border-radius: var(--hfp-speed-btn-radius, 4px);\n color: var(--hfp-color, #fff);\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n padding: 4px 8px;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease;\n }\n\n .hfp-speed-btn:hover {\n background: var(--hfp-speed-btn-bg-hover, rgba(255, 255, 255, 0.3));\n }\n\n .hfp-speed-menu {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background: var(--hfp-menu-bg, rgba(20, 20, 20, 0.95));\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--hfp-menu-border, rgba(255, 255, 255, 0.1));\n border-radius: var(--hfp-menu-radius, 8px);\n padding: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 80px;\n opacity: 0;\n visibility: hidden;\n transform: translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;\n box-shadow: var(--hfp-menu-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));\n }\n\n .hfp-speed-menu.hfp-open {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\n }\n\n .hfp-speed-option {\n background: none;\n border: none;\n border-radius: 4px;\n color: var(--hfp-menu-color, rgba(255, 255, 255, 0.7));\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n padding: 6px 12px;\n text-align: left;\n transition: background 0.1s ease, color 0.1s ease;\n white-space: nowrap;\n }\n\n .hfp-speed-option:hover {\n background: var(--hfp-menu-hover-bg, rgba(255, 255, 255, 0.1));\n color: var(--hfp-color, #fff);\n }\n\n .hfp-speed-option.hfp-active {\n color: var(--hfp-accent, #fff);\n font-weight: 600;\n }\n\n .hfp-volume-wrap {\n position: relative;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 0;\n }\n\n .hfp-mute-btn {\n background: none;\n border: none;\n color: var(--hfp-color, #fff);\n cursor: pointer;\n padding: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n flex-shrink: 0;\n }\n\n .hfp-mute-btn:hover {\n opacity: 0.8;\n }\n\n .hfp-mute-btn svg,\n .hfp-mute-btn svg * {\n pointer-events: none;\n }\n\n .hfp-volume-slider-wrap {\n width: 0;\n overflow: hidden;\n transition: width 0.2s ease;\n display: flex;\n align-items: center;\n }\n\n .hfp-volume-wrap:hover .hfp-volume-slider-wrap {\n width: 64px;\n }\n\n .hfp-volume-slider {\n width: 56px;\n height: var(--hfp-scrubber-height, 4px);\n background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));\n border-radius: var(--hfp-scrubber-radius, 2px);\n cursor: pointer;\n position: relative;\n overflow: hidden;\n margin-left: 4px;\n margin-right: 4px;\n }\n\n .hfp-volume-fill {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--hfp-accent, #fff);\n pointer-events: none;\n }\n`;\n\n// Play glyph: the right-hand blade from the HyperFrames favicon, framed to its\n// bounding box so it fills the icon. Points right, like a play triangle.\nexport const PLAY_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"46 21 54 56\" fill=\"currentColor\"><path d=\"M87.5129 57.5141L56.9696 73.5433C52.8371 75.7098 48.7046 73.2553 49.6688 69.2104L58.9483 30.1391C59.9125 26.0942 65.2097 23.6397 68.3154 25.8062L91.2447 41.8354C96.4668 45.4796 94.4631 53.8699 87.5129 57.5141Z\"/></svg>`;\nexport const PAUSE_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><rect x=\"3\" y=\"2\" width=\"4\" height=\"14\"/><rect x=\"11\" y=\"2\" width=\"4\" height=\"14\"/></svg>`;\nexport const VOLUME_HIGH_ICON = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3 9v6h4l5 5V4L7 9H3z\"/><path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z\"/><path d=\"M14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\"/></svg>`;\nexport const VOLUME_LOW_ICON = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3 9v6h4l5 5V4L7 9H3z\"/><path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z\"/></svg>`;\nexport const VOLUME_MUTED_ICON = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3 9v6h4l5 5V4L7 9H3z\"/><path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z\" opacity=\"0.3\"/><line x1=\"18\" y1=\"7\" x2=\"14\" y2=\"17\" stroke=\"currentColor\" stroke-width=\"2\"/></svg>`;\n\n/**\n * Process-wide cache for the constructed PLAYER_STYLES sheet. Lazy so the\n * module stays SSR-safe (CSSStyleSheet is window-scoped) and so a single\n * sheet can be shared across every shadow root via `adoptedStyleSheets` —\n * the studio thumbnail grid renders dozens of players, and avoiding N\n * duplicate `<style>` parses + style-recalc invalidations is the win here.\n *\n * `null` after a failed construction attempt = \"fall back forever in this\n * process\" (the usual cause is a missing constructor in older runtimes;\n * retrying every call would just throw the same way).\n */\nlet sharedSheet: CSSStyleSheet | null | undefined;\n\n/**\n * Returns the shared player stylesheet, or `null` if constructable\n * stylesheets aren't available in this environment.\n *\n * The result is memoized for the life of the module — every shadow root\n * adopts the same `CSSStyleSheet` instance.\n */\nexport function getSharedPlayerStyleSheet(): CSSStyleSheet | null {\n if (sharedSheet !== undefined) return sharedSheet;\n\n if (typeof CSSStyleSheet === \"undefined\") {\n sharedSheet = null;\n return null;\n }\n\n try {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(PLAYER_STYLES);\n sharedSheet = sheet;\n return sheet;\n } catch {\n sharedSheet = null;\n return null;\n }\n}\n\n/**\n * Internal hook for tests to clear the memoized sheet. Not part of the\n * public API.\n */\nexport function _resetSharedPlayerStyleSheet(): void {\n sharedSheet = undefined;\n}\n\n/**\n * Install PLAYER_STYLES into a player shadow root. Prefers the shared\n * constructable stylesheet (one parse, one rule tree, N adopters) and\n * falls back to a per-instance `<style>` element when the host runtime\n * lacks `adoptedStyleSheets` support.\n *\n * Idempotent: re-applying to a root that already adopts the shared sheet\n * is a no-op. Pre-existing adopted sheets are preserved (we append, never\n * replace), so callers further up the chain can keep their styles.\n */\nexport function applyPlayerStyles(shadow: ShadowRoot): void {\n const sheet = getSharedPlayerStyleSheet();\n const adopted = (shadow as ShadowRoot & { adoptedStyleSheets?: CSSStyleSheet[] })\n .adoptedStyleSheets;\n\n if (sheet && Array.isArray(adopted)) {\n if (!adopted.includes(sheet)) {\n (shadow as ShadowRoot & { adoptedStyleSheets: CSSStyleSheet[] }).adoptedStyleSheets = [\n ...adopted,\n sheet,\n ];\n }\n return;\n }\n\n const style = document.createElement(\"style\");\n style.textContent = PLAYER_STYLES;\n shadow.appendChild(style);\n}\n","import {\n PLAY_ICON,\n PAUSE_ICON,\n VOLUME_HIGH_ICON,\n VOLUME_LOW_ICON,\n VOLUME_MUTED_ICON,\n} from \"./styles.js\";\n\nexport interface ControlsCallbacks {\n onPlay: () => void;\n onPause: () => void;\n onSeek: (fraction: number) => void;\n /** Scrub drag started (mousedown/touchstart on the scrubber). */\n onScrubStart?: () => void;\n /** Scrub drag ended (mouseup/touchend). */\n onScrubEnd?: () => void;\n onSpeedChange: (speed: number) => void;\n onMuteToggle: () => void;\n onVolumeChange: (volume: number) => void;\n}\n\n/** Default logarithmic speed presets — each step roughly doubles/halves. */\nexport const SPEED_PRESETS = [0.25, 0.5, 1, 1.5, 2, 4] as const;\n\nexport interface ControlsOptions {\n /** Speed presets shown in the menu. Defaults to SPEED_PRESETS. */\n speedPresets?: readonly number[];\n /**\n * When true, the volume controls (mute button + volume slider) are hidden so\n * the viewer cannot change the audio state. Backs the `audio-locked`\n * attribute on `<hyperframes-player>`, which enforces host-mandated silent\n * playback (e.g. a HyperFrames project embedded in a chat host). Toggleable\n * at runtime via the returned `setVolumeControlsHidden`.\n */\n audioLocked?: boolean;\n}\n\nexport function formatSpeed(speed: number): string {\n return Number.isInteger(speed) ? `${speed}x` : `${speed}x`;\n}\n\nexport function formatTime(seconds: number): string {\n // Handle non-finite values gracefully\n if (!Number.isFinite(seconds) || seconds < 0) {\n return \"0:00\";\n }\n const s = Math.floor(seconds);\n const m = Math.floor(s / 60);\n const sec = s % 60;\n return `${m}:${sec.toString().padStart(2, \"0\")}`;\n}\n\nexport function createControls(\n parent: ShadowRoot | HTMLElement,\n callbacks: ControlsCallbacks,\n options: ControlsOptions = {},\n): {\n updateTime: (current: number, duration: number) => void;\n updatePlaying: (playing: boolean) => void;\n updateSpeed: (speed: number) => void;\n updateMuted: (muted: boolean) => void;\n updateVolume: (volume: number) => void;\n setVolumeControlsHidden: (hidden: boolean) => void;\n show: () => void;\n hide: () => void;\n destroy: () => void;\n} {\n const presets = options.speedPresets ?? SPEED_PRESETS;\n\n const controls = document.createElement(\"div\");\n controls.className = \"hfp-controls\";\n // Keep overlay interactions from falling through to the host-level click toggle.\n controls.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n });\n\n const playBtn = document.createElement(\"button\");\n playBtn.className = \"hfp-play-btn\";\n playBtn.type = \"button\";\n // Both glyphs stay in the DOM, stacked; toggling `hfp-playing` crossfade-morphs\n // between them (see styles.ts) instead of swapping innerHTML, which would kill\n // the transition.\n playBtn.innerHTML = `<span class=\"hfp-ico hfp-ico-play\">${PLAY_ICON}</span><span class=\"hfp-ico hfp-ico-pause\">${PAUSE_ICON}</span>`;\n playBtn.setAttribute(\"aria-label\", \"Play\");\n\n const scrubber = document.createElement(\"div\");\n scrubber.className = \"hfp-scrubber\";\n const progress = document.createElement(\"div\");\n progress.className = \"hfp-progress\";\n progress.style.width = \"0%\";\n scrubber.appendChild(progress);\n\n const time = document.createElement(\"span\");\n time.className = \"hfp-time\";\n time.textContent = \"0:00 / 0:00\";\n\n const speedWrap = document.createElement(\"div\");\n speedWrap.className = \"hfp-speed-wrap\";\n\n const speedBtn = document.createElement(\"button\");\n speedBtn.className = \"hfp-speed-btn\";\n speedBtn.type = \"button\";\n speedBtn.textContent = \"1x\";\n speedBtn.setAttribute(\"aria-label\", \"Playback speed\");\n\n const speedMenu = document.createElement(\"div\");\n speedMenu.className = \"hfp-speed-menu\";\n speedMenu.setAttribute(\"role\", \"menu\");\n for (const preset of presets) {\n const item = document.createElement(\"button\");\n item.className = \"hfp-speed-option\";\n item.type = \"button\";\n item.setAttribute(\"role\", \"menuitem\");\n item.dataset.speed = String(preset);\n item.textContent = formatSpeed(preset);\n if (preset === 1) item.classList.add(\"hfp-active\");\n speedMenu.appendChild(item);\n }\n\n speedWrap.appendChild(speedMenu);\n speedWrap.appendChild(speedBtn);\n\n const volumeWrap = document.createElement(\"div\");\n volumeWrap.className = \"hfp-volume-wrap\";\n\n const muteBtn = document.createElement(\"button\");\n muteBtn.className = \"hfp-mute-btn\";\n muteBtn.type = \"button\";\n muteBtn.innerHTML = VOLUME_HIGH_ICON;\n muteBtn.setAttribute(\"aria-label\", \"Mute\");\n\n const volumeSliderWrap = document.createElement(\"div\");\n volumeSliderWrap.className = \"hfp-volume-slider-wrap\";\n\n const volumeSlider = document.createElement(\"div\");\n volumeSlider.className = \"hfp-volume-slider\";\n volumeSlider.setAttribute(\"role\", \"slider\");\n volumeSlider.setAttribute(\"aria-label\", \"Volume\");\n volumeSlider.setAttribute(\"aria-valuemin\", \"0\");\n volumeSlider.setAttribute(\"aria-valuemax\", \"100\");\n volumeSlider.setAttribute(\"aria-valuenow\", \"100\");\n volumeSlider.tabIndex = 0;\n const volumeFill = document.createElement(\"div\");\n volumeFill.className = \"hfp-volume-fill\";\n volumeFill.style.width = \"100%\";\n volumeSlider.appendChild(volumeFill);\n volumeSliderWrap.appendChild(volumeSlider);\n\n volumeWrap.appendChild(volumeSliderWrap);\n volumeWrap.appendChild(muteBtn);\n\n // Audio-locked: hide the whole volume control (mute toggle + slider) so the\n // viewer has no UI path to turn sound on. The player still mutes the media\n // independently via the `muted` attribute; this only removes the controls.\n if (options.audioLocked) volumeWrap.style.display = \"none\";\n\n controls.appendChild(playBtn);\n controls.appendChild(scrubber);\n controls.appendChild(time);\n controls.appendChild(volumeWrap);\n controls.appendChild(speedWrap);\n parent.appendChild(controls);\n\n let isPlaying = false;\n let isMuted = false;\n let currentVolume = 1;\n let hideTimeout: ReturnType<typeof setTimeout> | null = null;\n let speedIndex = presets.indexOf(1); // start at 1x\n if (speedIndex === -1) speedIndex = 0;\n\n const getVolumeIcon = (muted: boolean, volume: number): string => {\n if (muted) return VOLUME_MUTED_ICON;\n if (volume === 0) return VOLUME_LOW_ICON;\n if (volume < 0.5) return VOLUME_LOW_ICON;\n return VOLUME_HIGH_ICON;\n };\n\n playBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (isPlaying) callbacks.onPause();\n else callbacks.onPlay();\n });\n\n muteBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n callbacks.onMuteToggle();\n });\n\n let volumeScrubbing = false;\n\n const handleVolumeAt = (clientX: number) => {\n const rect = volumeSlider.getBoundingClientRect();\n const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n currentVolume = fraction;\n volumeFill.style.width = `${fraction * 100}%`;\n volumeSlider.setAttribute(\"aria-valuenow\", String(Math.round(fraction * 100)));\n if (isMuted && fraction > 0) callbacks.onMuteToggle();\n muteBtn.innerHTML = getVolumeIcon(isMuted, fraction);\n callbacks.onVolumeChange(fraction);\n };\n\n volumeSlider.addEventListener(\"mousedown\", (e) => {\n e.stopPropagation();\n volumeScrubbing = true;\n handleVolumeAt(e.clientX);\n });\n const onVolumeMouseMove = (e: MouseEvent) => {\n if (volumeScrubbing) handleVolumeAt(e.clientX);\n };\n const onVolumeMouseUp = () => {\n volumeScrubbing = false;\n };\n document.addEventListener(\"mousemove\", onVolumeMouseMove);\n document.addEventListener(\"mouseup\", onVolumeMouseUp);\n\n volumeSlider.addEventListener(\n \"touchstart\",\n (e) => {\n volumeScrubbing = true;\n const touch = e.touches[0];\n if (touch) handleVolumeAt(touch.clientX);\n },\n { passive: true },\n );\n const onVolumeTouchMove = (e: TouchEvent) => {\n if (volumeScrubbing) {\n const touch = e.touches[0];\n if (touch) handleVolumeAt(touch.clientX);\n }\n };\n const onVolumeTouchEnd = () => {\n volumeScrubbing = false;\n };\n document.addEventListener(\"touchmove\", onVolumeTouchMove, { passive: true });\n document.addEventListener(\"touchend\", onVolumeTouchEnd);\n\n const VOLUME_STEP = 0.05;\n volumeSlider.addEventListener(\"keydown\", (e) => {\n let newVol = currentVolume;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowUp\") {\n newVol = Math.min(1, currentVolume + VOLUME_STEP);\n } else if (e.key === \"ArrowLeft\" || e.key === \"ArrowDown\") {\n newVol = Math.max(0, currentVolume - VOLUME_STEP);\n } else {\n return;\n }\n e.preventDefault();\n e.stopPropagation();\n currentVolume = newVol;\n volumeFill.style.width = `${newVol * 100}%`;\n volumeSlider.setAttribute(\"aria-valuenow\", String(Math.round(newVol * 100)));\n if (isMuted && newVol > 0) callbacks.onMuteToggle();\n muteBtn.innerHTML = getVolumeIcon(isMuted, newVol);\n callbacks.onVolumeChange(newVol);\n });\n\n const setActiveOption = (speed: number) => {\n for (const opt of speedMenu.querySelectorAll(\".hfp-speed-option\")) {\n opt.classList.toggle(\"hfp-active\", (opt as HTMLElement).dataset.speed === String(speed));\n }\n };\n\n speedBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const isOpen = speedMenu.classList.toggle(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", String(isOpen));\n });\n\n speedMenu.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const target = (e.target as HTMLElement).closest(\".hfp-speed-option\") as HTMLElement | null;\n if (!target) return;\n const newSpeed = parseFloat(target.dataset.speed!);\n speedIndex = presets.indexOf(newSpeed);\n speedBtn.textContent = formatSpeed(newSpeed);\n setActiveOption(newSpeed);\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n callbacks.onSpeedChange(newSpeed);\n });\n\n // Close menu when clicking outside\n const onDocClick = () => {\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n };\n document.addEventListener(\"click\", onDocClick);\n\n const handleScrubAt = (clientX: number) => {\n const rect = scrubber.getBoundingClientRect();\n const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n callbacks.onSeek(fraction);\n };\n\n let scrubbing = false;\n\n scrubber.addEventListener(\"mousedown\", (e) => {\n e.stopPropagation();\n scrubbing = true;\n callbacks.onScrubStart?.();\n handleScrubAt(e.clientX);\n });\n const onMouseMove = (e: MouseEvent) => {\n if (scrubbing) handleScrubAt(e.clientX);\n };\n const onMouseUp = () => {\n if (scrubbing) {\n scrubbing = false;\n callbacks.onScrubEnd?.();\n }\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n\n scrubber.addEventListener(\n \"touchstart\",\n (e) => {\n scrubbing = true;\n callbacks.onScrubStart?.();\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n },\n { passive: true },\n );\n const onTouchMove = (e: TouchEvent) => {\n if (scrubbing) {\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n }\n };\n const onTouchEnd = () => {\n if (scrubbing) {\n scrubbing = false;\n callbacks.onScrubEnd?.();\n }\n };\n document.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n document.addEventListener(\"touchend\", onTouchEnd);\n\n const startHideTimer = () => {\n if (hideTimeout) clearTimeout(hideTimeout);\n hideTimeout = setTimeout(() => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n }, 3000);\n };\n\n const host = parent instanceof ShadowRoot ? (parent.host as HTMLElement) : parent;\n const onHostMouseMove = () => {\n controls.classList.remove(\"hfp-hidden\");\n startHideTimer();\n };\n const onHostMouseLeave = () => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n };\n host.addEventListener(\"mousemove\", onHostMouseMove);\n host.addEventListener(\"mouseleave\", onHostMouseLeave);\n\n return {\n updateTime(current: number, duration: number) {\n // Defensive: source should already clamp, but guard here so the UI never overflows.\n const clampedCurrent = duration > 0 ? Math.min(current, duration) : current;\n const pct = duration > 0 ? (clampedCurrent / duration) * 100 : 0;\n progress.style.width = `${pct}%`;\n time.textContent = `${formatTime(clampedCurrent)} / ${formatTime(duration)}`;\n },\n updatePlaying(playing: boolean) {\n isPlaying = playing;\n playBtn.classList.toggle(\"hfp-playing\", playing);\n playBtn.setAttribute(\"aria-label\", playing ? \"Pause\" : \"Play\");\n if (playing) startHideTimer();\n else controls.classList.remove(\"hfp-hidden\");\n },\n updateSpeed(speed: number) {\n const idx = presets.indexOf(speed);\n if (idx !== -1) speedIndex = idx;\n speedBtn.textContent = formatSpeed(speed);\n setActiveOption(speed);\n },\n updateMuted(muted: boolean) {\n isMuted = muted;\n muteBtn.innerHTML = getVolumeIcon(muted, currentVolume);\n muteBtn.setAttribute(\"aria-label\", muted ? \"Unmute\" : \"Mute\");\n },\n updateVolume(volume: number) {\n currentVolume = volume;\n volumeFill.style.width = `${volume * 100}%`;\n volumeSlider.setAttribute(\"aria-valuenow\", String(Math.round(volume * 100)));\n muteBtn.innerHTML = getVolumeIcon(isMuted, volume);\n },\n setVolumeControlsHidden(hidden: boolean) {\n volumeWrap.style.display = hidden ? \"none\" : \"\";\n },\n show() {\n controls.style.display = \"\";\n },\n hide() {\n controls.style.display = \"none\";\n },\n destroy() {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.removeEventListener(\"touchmove\", onTouchMove);\n document.removeEventListener(\"touchend\", onTouchEnd);\n document.removeEventListener(\"mousemove\", onVolumeMouseMove);\n document.removeEventListener(\"mouseup\", onVolumeMouseUp);\n document.removeEventListener(\"touchmove\", onVolumeTouchMove);\n document.removeEventListener(\"touchend\", onVolumeTouchEnd);\n document.removeEventListener(\"click\", onDocClick);\n host.removeEventListener(\"mousemove\", onHostMouseMove);\n host.removeEventListener(\"mouseleave\", onHostMouseLeave);\n if (hideTimeout) clearTimeout(hideTimeout);\n controls.remove();\n },\n };\n}\n","/**\n * Helpers for wiring the player's optional UI elements: the playback controls\n * bar, the poster image overlay, and input-filtering utilities.\n *\n * Extracted from the web component so the setup logic doesn't inflate the\n * class body. These are pure imperative DOM operations; they carry no\n * persistent state beyond what the caller tracks.\n */\n\nimport { createControls, type ControlsCallbacks, type ControlsOptions } from \"./controls.js\";\n\n/**\n * Create the playback controls overlay and attach it to `parent`.\n * Returns the controls API object. A no-op guard (returns the existing API)\n * must be enforced by the caller — this function always constructs.\n */\nexport function setupControls(\n parent: ShadowRoot,\n muted: boolean,\n volume: number,\n speedPresetsAttr: string | null,\n callbacks: ControlsCallbacks,\n audioLocked = false,\n): ReturnType<typeof createControls> {\n const speedPresets = speedPresetsAttr\n ? speedPresetsAttr\n .split(\",\")\n .map(Number)\n .filter((n) => !isNaN(n) && n > 0)\n : undefined;\n const options: ControlsOptions = {\n ...(speedPresets ? { speedPresets } : {}),\n audioLocked,\n };\n const api = createControls(parent, callbacks, options);\n api.updateMuted(muted);\n api.updateVolume(volume);\n return api;\n}\n\n/**\n * Set up or remove the poster image element in `parent`.\n *\n * - When `posterUrl` is non-empty, creates the `<img>` if needed and sets src.\n * - When `posterUrl` is null/empty, removes the existing element (if any).\n *\n * Returns the current poster element (possibly newly created) or `null`.\n */\nexport function setupPoster(\n parent: ShadowRoot,\n posterUrl: string | null,\n existing: HTMLImageElement | null,\n): HTMLImageElement | null {\n if (!posterUrl) {\n existing?.remove();\n return null;\n }\n if (!existing) {\n existing = document.createElement(\"img\");\n existing.className = \"hfp-poster\";\n parent.appendChild(existing);\n }\n existing.src = posterUrl;\n return existing;\n}\n\n/**\n * Returns `true` when `event` originated inside an `hfp-controls` element.\n * Used to prevent the bare-player-surface click handler from double-firing\n * when the user clicks an overlay control button.\n */\nexport function isControlsClick(event: Event): boolean {\n return event\n .composedPath()\n .some((t) => t instanceof HTMLElement && t.classList.contains(\"hfp-controls\"));\n}\n","/**\n * DOM setup helpers for the player's shadow root.\n *\n * Keeps constructor boilerplate out of the web component class body.\n */\n\n// Cached Constructable Stylesheet shared across all player instances.\nlet _sharedSheet: CSSStyleSheet | null = null;\n\n/**\n * Adopt `cssText` into `shadow` via a shared Constructable Stylesheet when the\n * browser supports it, falling back to a `<style>` element injection. The sheet\n * is cached on first creation and reused across all player instances.\n */\nexport function adoptShadowStyles(shadow: ShadowRoot, cssText: string): void {\n if (typeof CSSStyleSheet !== \"undefined\") {\n try {\n if (!_sharedSheet) {\n _sharedSheet = new CSSStyleSheet();\n _sharedSheet.replaceSync(cssText);\n }\n shadow.adoptedStyleSheets = [_sharedSheet];\n return;\n } catch {\n /* fallthrough */\n }\n }\n const style = document.createElement(\"style\");\n style.textContent = cssText;\n shadow.appendChild(style);\n}\n\n/**\n * Creates and configures the iframe element that hosts the composition, plus\n * the wrapper container div. Returns handles to both so the constructor can\n * attach them to the shadow root and track references without inlining the\n * boilerplate.\n */\nexport function createCompositionIframe(): {\n container: HTMLDivElement;\n iframe: HTMLIFrameElement;\n} {\n const container = document.createElement(\"div\");\n container.className = \"hfp-container\";\n\n const iframe = document.createElement(\"iframe\");\n iframe.className = \"hfp-iframe\";\n iframe.sandbox.add(\"allow-scripts\", \"allow-same-origin\");\n iframe.allow = \"autoplay; fullscreen\";\n iframe.referrerPolicy = \"no-referrer\";\n iframe.title = \"HyperFrames Composition\";\n\n container.appendChild(iframe);\n return { container, iframe };\n}\n\n/**\n * Scale the iframe so the composition fits inside the player element while\n * preserving aspect ratio. No-ops when the player has no painted size yet.\n * Returns whether the transform was actually applied, so callers can tell a\n * real no-op (still 0×0) apart from a successful rescale.\n */\nexport function scaleIframeToFit(\n playerElement: HTMLElement,\n iframe: HTMLIFrameElement,\n compositionWidth: number,\n compositionHeight: number,\n): boolean {\n const w = playerElement.offsetWidth;\n const h = playerElement.offsetHeight;\n if (w === 0 || h === 0) return false;\n const scale = Math.min(w / compositionWidth, h / compositionHeight);\n iframe.style.width = `${compositionWidth}px`;\n iframe.style.height = `${compositionHeight}px`;\n iframe.style.transform = `translate(-50%, -50%) scale(${scale})`;\n return true;\n}\n","/**\n * rAF-based clock that polls a `DirectTimelineAdapter` for current time and\n * drives the player's time/playback-ended callbacks.\n *\n * Used for same-origin standalone GSAP compositions that expose\n * `window.__timelines` but have no runtime bridge — the player drives them\n * directly through the adapter instead of going through postMessage.\n */\n\nimport type { DirectTimelineAdapter } from \"./timeline-adapters.js\";\n\nconst UI_UPDATE_INTERVAL_MS = 100;\nconst CURRENT_TIME_ROUNDING_S = 1e-3;\n\nexport interface ClockCallbacks {\n /** Called every ~100ms and on completion with the current time. */\n onTimeUpdate: (currentTime: number, duration: number) => void;\n /** Called when playback reaches the end. Return true to loop (seek+play). */\n onEnded: () => boolean;\n /** Get the current loop flag. */\n getLoop: () => boolean;\n /** Trigger a seek-then-play loop restart. */\n restart: () => void;\n /** Notify that playback has paused (from the timeline side). */\n onPaused: () => void;\n}\n\n// A range ending inside the film stops a check early, so the frame past it does not show.\nfunction reachedStop(\n time: number,\n lookAhead: number,\n stop: { end: number; shown: number },\n): boolean {\n if (stop.end <= 0) return false;\n const early = stop.shown < stop.end && lookAhead > 0 ? lookAhead + CURRENT_TIME_ROUNDING_S : 0;\n return time + early >= stop.end;\n}\n\nexport class DirectTimelineClock {\n private _raf: number | null = null;\n private _lastUpdateMs = 0;\n private _tick: (() => void) | null = null;\n\n constructor(private readonly _callbacks: ClockCallbacks) {}\n\n start(\n timeline: DirectTimelineAdapter,\n getCurrentTime: () => number,\n getDuration: () => number,\n isPaused: () => boolean,\n getStop: () => { end: number; shown: number },\n ): void {\n this.stop();\n let lastTime: number | null = null;\n let lastStep = 0;\n let lookAhead = 0;\n\n const tick = () => {\n if (isPaused()) {\n this._raf = null;\n return;\n }\n\n let currentTime: number;\n try {\n currentTime = timeline.time();\n } catch {\n this._raf = null;\n return;\n }\n\n const duration = getDuration();\n const stop = getStop();\n if (stop.end > 0) currentTime = Math.min(currentTime, stop.end);\n\n // The smaller of the last two moves, so one slow frame or a jump does not end the range early.\n const step = lastTime === null ? 0 : currentTime - lastTime;\n if (step !== 0) {\n lookAhead = Math.min(step, lastStep);\n lastStep = step;\n }\n lastTime = currentTime;\n const completedPlayback = reachedStop(currentTime, lookAhead, stop);\n if (completedPlayback) currentTime = stop.shown;\n const now = performance.now();\n\n if (now - this._lastUpdateMs > UI_UPDATE_INTERVAL_MS || completedPlayback) {\n this._lastUpdateMs = now;\n this._callbacks.onTimeUpdate(currentTime, duration);\n }\n\n if (completedPlayback) {\n if (this._callbacks.getLoop()) {\n this._callbacks.restart();\n return;\n }\n try {\n timeline.pause();\n if (stop.shown < stop.end) timeline.seek(stop.shown, false);\n } catch {\n /* ignore */\n }\n this._callbacks.onPaused();\n this._raf = null;\n return;\n }\n\n this._raf = requestAnimationFrame(tick);\n };\n\n this._tick = tick;\n this._raf = requestAnimationFrame(tick);\n }\n\n /** Runs one check now, for a hidden tab, which runs no animation frames. */\n poll(): void {\n if (this._raf === null || !this._tick) return;\n cancelAnimationFrame(this._raf);\n this._tick();\n }\n\n stop(): void {\n if (this._raf === null) return;\n cancelAnimationFrame(this._raf);\n this._raf = null;\n }\n\n get isRunning(): boolean {\n return this._raf !== null;\n }\n}\n","/**\n * Internal helper for scoping the player's media MutationObserver to the\n * composition tree inside the iframe.\n *\n * Not part of the package's public API — kept in its own module so the\n * decision logic can be exercised by unit tests without exposing it through\n * the player entry point.\n */\n\n/**\n * Pick the elements inside `doc` that the media MutationObserver should\n * attach to.\n *\n * Compositions mount inside `[data-composition-id]` host elements — the\n * runtime root and any sub-composition hosts that `compositionLoader` writes\n * into them. Watching only those hosts (with `subtree: true`) catches every\n * late-arriving timed media element from sub-composition activation, while\n * filtering out churn from analytics tags, runtime telemetry markers, and\n * other out-of-host nodes that the runtime appends straight to `<body>`\n * during bootstrap.\n *\n * Nested hosts are filtered out — they're already covered by their nearest\n * host ancestor's subtree observation, so observing them too would deliver\n * each callback twice and double-count adoption work.\n *\n * Falls back to `[doc.body]` when no composition hosts are present, which\n * preserves the previous behavior for documents that aren't yet (or never\n * will be) composition-structured. Returns an empty array when neither a\n * host nor a body is available — the caller should treat that as \"nothing\n * to observe\".\n *\n * When the scoped path is taken but the body still carries timed media\n * outside every host, a `console.warn` fires once per call as a forensic\n * signal: the new scope skips that media, so any `<audio data-start>` /\n * `<video data-start>` injected at body level will silently never get a\n * parent-frame proxy. Today every runtime path appends inside a host so\n * this branch shouldn't trip; if it does, the warn surfaces the drift\n * immediately rather than presenting as a missing-audio bug downstream.\n */\nexport function selectMediaObserverTargets(doc: Document): Element[] {\n const all = Array.from(doc.querySelectorAll<Element>(\"[data-composition-id]\"));\n if (all.length === 0) {\n return doc.body ? [doc.body] : [];\n }\n\n const topLevel: Element[] = [];\n for (const el of all) {\n if (!hasCompositionAncestor(el)) {\n topLevel.push(el);\n }\n }\n\n warnOnUnscopedTimedMedia(doc);\n return topLevel;\n}\n\n/**\n * Forensic guard: with composition hosts present the observer attaches only\n * to those subtrees, so any timed media sitting at body level (or under a\n * non-host wrapper) is invisible to the adoption pipeline. Walk the body for\n * `[data-start]` audio/video that has no `[data-composition-id]` ancestor\n * and emit a single `console.warn` listing the orphans. The walk is cheap\n * (one `querySelectorAll` over a typed selector + a `closest` per match)\n * and only runs on the scoped path, so the no-host fallback retains its\n * legacy behavior with zero extra work.\n */\nfunction warnOnUnscopedTimedMedia(doc: Document): void {\n const body = doc.body;\n if (!body) return;\n if (typeof console === \"undefined\" || typeof console.warn !== \"function\") return;\n\n const candidates = body.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n if (candidates.length === 0) return;\n\n const orphans: HTMLMediaElement[] = [];\n for (const el of candidates) {\n if (!el.closest(\"[data-composition-id]\")) orphans.push(el);\n }\n if (orphans.length === 0) return;\n\n console.warn(\n \"[hyperframes-player] selectMediaObserverTargets: composition hosts are present, \" +\n `but ${orphans.length} body-level timed media element(s) sit outside every ` +\n \"[data-composition-id] subtree and will not be observed. Move them inside a \" +\n \"composition host or the parent-frame proxy will never adopt them.\",\n orphans,\n );\n}\n\nfunction hasCompositionAncestor(el: Element): boolean {\n let cursor = el.parentElement;\n while (cursor) {\n if (cursor.hasAttribute(\"data-composition-id\")) return true;\n cursor = cursor.parentElement;\n }\n return false;\n}\n","/** Plain-field clip facts shared by the Ask-agent prompt, `studio_look` and `hyperframes timeline`. */\n\nexport interface ClipLane {\n /** `volume`, or `fx.<nodeId>.<param>`. */\n target: string;\n /** `t` is seconds from the start of the clip, not the composition. */\n points: { t: number; v: number }[];\n}\n\nexport interface ClipFact {\n id: string;\n label: string | null;\n kind: string;\n start: number;\n duration: number;\n end: number;\n /** The `data-track-index` as written in the source file. */\n trackIndex: number;\n src: string | null;\n sourceFile: string | null;\n /** `null` when `data-volume` is not authored (the clip plays at 1). */\n volume: number | null;\n lanes: ClipLane[];\n /** `null` at normal speed: not authored, or 1 (the manifest defaults it to 1). */\n playbackRate: number | null;\n audioGroup: string | null;\n role: string | null;\n}\n\nexport const byStart = (a: ClipFact, b: ClipFact) =>\n a.start - b.start || a.trackIndex - b.trackIndex;\n\nconst roundTo3 = (n: number) => Math.round(n * 1000) / 1000;\n\nconst num = (n: number) => String(roundTo3(n));\n\nexport const instantTolerance = (time: number) => 4 * Number.EPSILON * Math.max(1, Math.abs(time));\n\n/** Float sums like 19.8 + 6.4 miss 26.2 by a rounding step; a few steps, scaled to the time, are one instant. */\nexport function sameInstant(a: number, b: number): boolean {\n if (!Number.isFinite(a) || !Number.isFinite(b)) return a === b;\n return Math.abs(a - b) <= instantTolerance(Math.max(Math.abs(a), Math.abs(b)));\n}\n\nconst isBefore = (a: number, b: number) => a < b && !sameInstant(a, b);\n\n/** For timeline lanes: a zero-length clip inside another still counts, so it gets a lane of its own. */\nexport function spansOverlap(aStart: number, aEnd: number, bStart: number, bEnd: number): boolean {\n return isBefore(aStart, bEnd) && isBefore(bStart, aEnd);\n}\n\nexport function spansShareTime(\n aStart: number,\n aEnd: number,\n bStart: number,\n bEnd: number,\n): boolean {\n return isBefore(Math.max(aStart, bStart), Math.min(aEnd, bEnd));\n}\n\nexport const isInsideSpan = (time: number, start: number, end: number) =>\n isBefore(start, time) && isBefore(time, end);\n\nexport function formatClipLine(clip: ClipFact): string {\n const parts = [\n `${clip.kind} \"${clip.id}\"`,\n clip.src && `src=${clip.src}`,\n `start=${num(clip.start)}`,\n `duration=${num(clip.duration)}`,\n `end=${num(clip.end)}`,\n `track=${clip.trackIndex}`,\n clip.volume !== null && `volume=${num(clip.volume)}`,\n clip.playbackRate !== null && `rate=${num(clip.playbackRate)}`,\n clip.audioGroup && `group=${clip.audioGroup}`,\n clip.role && `role=${clip.role}`,\n clip.sourceFile && `file=${clip.sourceFile}`,\n ...clip.lanes.map(\n (lane) =>\n `${lane.target}-lane=[${lane.points.map((p) => `${num(p.t)}:${num(p.v)}`).join(\", \")}]`,\n ),\n ];\n return `- ${parts.filter(Boolean).join(\" \")}`;\n}\n","import { sameInstant } from \"../clipFacts\";\n\nexport const hasClipStarted = (time: number, start: number) =>\n time >= start || sameInstant(time, start);\n\n/** Half-open: two back-to-back clips never both hold the shared boundary instant, float sums included. */\nexport const isInClipWindow = (time: number, start: number, end: number): boolean =>\n hasClipStarted(time, start) && time < end && !sameInstant(time, end);\n\nconst TERMINAL_EPSILON_SECONDS = 1e-6;\n\n/**\n * Visibility window: half-open, except a clip that runs to the composition's end also owns\n * the terminal instant, so a finished film rests on its last frame instead of going blank.\n */\nexport const isClipVisibleAt = (\n time: number,\n start: number,\n end: number,\n compositionDuration: number,\n): boolean =>\n isInClipWindow(time, start, end) ||\n (hasClipStarted(time, start) &&\n compositionDuration > 0 &&\n end >= compositionDuration - TERMINAL_EPSILON_SECONDS);\n","/** Clip-edge fades: `data-fade-in` / `data-fade-out` in seconds, linear, multiplied on the resolved level. */\n\nexport const HF_AUDIO_FADE_IN_ATTR = \"data-fade-in\";\nexport const HF_AUDIO_FADE_OUT_ATTR = \"data-fade-out\";\n\nexport const HF_AUDIO_FADE_IN_DATA_KEY = HF_AUDIO_FADE_IN_ATTR.slice(\"data-\".length);\nexport const HF_AUDIO_FADE_OUT_DATA_KEY = HF_AUDIO_FADE_OUT_ATTR.slice(\"data-\".length);\n\nexport interface AudioFades {\n /** Seconds from the clip start over which gain rises 0 → 1. */\n fadeIn: number;\n /** Seconds before the clip end over which gain falls 1 → 0. */\n fadeOut: number;\n}\n\nexport const NO_FADES: Readonly<AudioFades> = Object.freeze({ fadeIn: 0, fadeOut: 0 });\n\nfunction finiteFadeSeconds(value: number): number {\n return Number.isFinite(value) && value > 0 ? value : 0;\n}\n\n/** Attribute text as seconds: finite and positive, else 0 (no fade). */\nexport function readFadeSeconds(raw: string | null | undefined): number {\n if (raw == null || raw === \"\") return 0;\n return finiteFadeSeconds(Number.parseFloat(raw));\n}\n\nexport function readElementFades(el: { getAttribute(name: string): string | null }): AudioFades {\n return {\n fadeIn: readFadeSeconds(el.getAttribute(HF_AUDIO_FADE_IN_ATTR)),\n fadeOut: readFadeSeconds(el.getAttribute(HF_AUDIO_FADE_OUT_ATTR)),\n };\n}\n\n/** Scale fades that outrun the clip so they meet inside it. Infinite duration keeps them as authored. */\nexport function clampFadesToDuration(fades: AudioFades, duration: number): AudioFades {\n const fadeIn = finiteFadeSeconds(fades.fadeIn);\n const fadeOut = finiteFadeSeconds(fades.fadeOut);\n if (!Number.isFinite(duration) || duration <= 0) return { fadeIn, fadeOut };\n const total = fadeIn + fadeOut;\n if (total <= duration) return { fadeIn, fadeOut };\n const scale = duration / total;\n return { fadeIn: fadeIn * scale, fadeOut: fadeOut * scale };\n}\n\n/** Gain at `elapsed` into a clip of `duration`. 1 where no fade runs; the two multiply where they meet. */\nexport function fadeGain(elapsed: number, duration: number, fades: AudioFades): number {\n if (!Number.isFinite(elapsed)) return 1;\n const { fadeIn, fadeOut } = clampFadesToDuration(fades, duration);\n let gain = 1;\n if (fadeIn > 0) gain *= clamp01(elapsed / fadeIn);\n if (fadeOut > 0 && Number.isFinite(duration)) gain *= clamp01((duration - elapsed) / fadeOut);\n return gain;\n}\n\n/** Attribute text Studio writes: up to 2 decimals, no trailing zeros. */\nexport function formatFadeSeconds(seconds: number): string {\n const rounded = Math.round(finiteFadeSeconds(seconds) * 100) / 100;\n return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(2).replace(/0+$/, \"\");\n}\n\nfunction clamp01(value: number): number {\n if (!Number.isFinite(value) || value < 0) return 0;\n return value > 1 ? 1 : value;\n}\n","export const AUDIBLE_VIDEO_QUALIFIER =\n ':not([muted]):is(:not([data-has-audio]),[data-has-audio=\"true\"])';\n\nexport const AUDIBLE_MEDIA_SELECTOR = `audio[data-start], video[data-start]${AUDIBLE_VIDEO_QUALIFIER}`;\n\nexport function isAudibleVideoElement(el: {\n tagName: string;\n hasAttribute(name: string): boolean;\n getAttribute(name: string): string | null;\n}): boolean {\n if (el.tagName.toLowerCase() !== \"video\" || el.hasAttribute(\"muted\")) return false;\n const declared = el.getAttribute(\"data-has-audio\");\n return declared === null || declared === \"true\";\n}\n\nexport function audibleVideoNeedsWebAudio(fields: {\n volume?: number | null;\n fxChain?: string | null;\n automation?: string | null;\n audioGroup?: string | null;\n}): boolean {\n return (\n (fields.volume ?? 1) > 1 || Boolean(fields.fxChain || fields.automation || fields.audioGroup)\n );\n}\n","export const ROOT_CSS_STACKING_CONTEXT_ID = \"css:root\";\n\nconst NON_NONE_STACKING_CONTEXT_PROPERTIES = [\n \"backdrop-filter\",\n \"clip-path\",\n \"filter\",\n \"mask\",\n \"mask-border-source\",\n \"mask-image\",\n \"perspective\",\n \"rotate\",\n \"scale\",\n \"transform\",\n \"translate\",\n \"-webkit-mask-image\",\n] as const;\nconst STACKING_CONTEXT_WILL_CHANGE_PROPERTIES = new Set([\n ...NON_NONE_STACKING_CONTEXT_PROPERTIES,\n \"contain\",\n \"isolation\",\n \"mask-border\",\n \"mix-blend-mode\",\n \"opacity\",\n]);\nconst Z_INDEXED_POSITIONS = new Set([\"absolute\", \"relative\"]);\nconst Z_INDEXED_PARENT_DISPLAYS = new Set([\"flex\", \"inline-flex\", \"grid\", \"inline-grid\"]);\nconst CONTAINMENT_VALUES = new Set([\"layout\", \"paint\", \"strict\", \"content\"]);\nconst STACKING_CONTAINER_TYPES = new Set([\"size\", \"inline-size\"]);\n\nfunction createsPositionedStackingContext(style: CSSStyleDeclaration): boolean {\n return style.position === \"fixed\" || style.position === \"sticky\";\n}\n\nfunction createsZIndexedStackingContext(element: HTMLElement, style: CSSStyleDeclaration): boolean {\n if (style.zIndex === \"auto\" || style.zIndex === \"\") return false;\n if (Z_INDEXED_POSITIONS.has(style.position)) return true;\n const parentDisplay = element.parentElement\n ? element.ownerDocument.defaultView?.getComputedStyle(element.parentElement).display\n : null;\n return parentDisplay != null && Z_INDEXED_PARENT_DISPLAYS.has(parentDisplay);\n}\n\nfunction createsVisualEffectStackingContext(style: CSSStyleDeclaration): boolean {\n const opacity = Number.parseFloat(style.opacity);\n if (Number.isFinite(opacity) && opacity < 1) return true;\n if (style.getPropertyValue(\"isolation\") === \"isolate\") return true;\n const mixBlendMode = style.getPropertyValue(\"mix-blend-mode\");\n if (mixBlendMode && mixBlendMode !== \"normal\") return true;\n return NON_NONE_STACKING_CONTEXT_PROPERTIES.some((property) => {\n const value = style.getPropertyValue(property);\n return value !== \"\" && value !== \"none\";\n });\n}\n\nfunction createsContainmentStackingContext(style: CSSStyleDeclaration): boolean {\n const contain = style.getPropertyValue(\"contain\").split(/\\s+/);\n if (contain.some((value) => CONTAINMENT_VALUES.has(value))) return true;\n return STACKING_CONTAINER_TYPES.has(style.getPropertyValue(\"container-type\"));\n}\n\nfunction createsWillChangeStackingContext(style: CSSStyleDeclaration): boolean {\n return style\n .getPropertyValue(\"will-change\")\n .split(\",\")\n .map((property) => property.trim())\n .some((property) => STACKING_CONTEXT_WILL_CHANGE_PROPERTIES.has(property));\n}\n\nfunction createsCssStackingContext(element: HTMLElement, style: CSSStyleDeclaration): boolean {\n if (createsPositionedStackingContext(style)) return true;\n if (createsZIndexedStackingContext(element, style)) return true;\n if (createsVisualEffectStackingContext(style)) return true;\n if (createsContainmentStackingContext(style)) return true;\n return createsWillChangeStackingContext(style);\n}\n\n/** Stable within one DOM revision and unique even when authors duplicate ids. */\nfunction cssStackingContextPath(element: Element): string {\n const indexes: number[] = [];\n let cursor: Element | null = element;\n while (cursor?.parentElement) {\n indexes.push(Array.prototype.indexOf.call(cursor.parentElement.children, cursor));\n cursor = cursor.parentElement;\n }\n return `css:${indexes.reverse().join(\".\")}`;\n}\n\n/**\n * Leaf z-index participates in the nearest ANCESTOR stacking context. Exclude\n * the leaf itself: an element that establishes a context still compares its own\n * z-index with siblings in its parent's context.\n */\nexport function resolveCssStackingContextId(node: Element): string {\n const win = node.ownerDocument.defaultView;\n if (!win) return ROOT_CSS_STACKING_CONTEXT_ID;\n let cursor = node.parentElement;\n while (cursor && cursor !== node.ownerDocument.documentElement) {\n try {\n if (createsCssStackingContext(cursor, win.getComputedStyle(cursor))) {\n return cssStackingContextPath(cursor);\n }\n } catch {\n return ROOT_CSS_STACKING_CONTEXT_ID;\n }\n cursor = cursor.parentElement;\n }\n return ROOT_CSS_STACKING_CONTEXT_ID;\n}\n","export const MEDIA_VISUAL_STYLE_PROPERTIES = [\n \"width\",\n \"height\",\n \"top\",\n \"left\",\n \"right\",\n \"bottom\",\n \"inset\",\n \"object-fit\",\n \"object-position\",\n \"z-index\",\n \"opacity\",\n \"visibility\",\n \"filter\",\n \"mix-blend-mode\",\n \"backdrop-filter\",\n \"border-radius\",\n \"overflow\",\n \"clip-path\",\n \"mask\",\n \"mask-image\",\n \"mask-size\",\n \"mask-position\",\n \"mask-repeat\",\n \"transform\",\n \"transform-origin\",\n \"translate\",\n \"rotate\",\n \"scale\",\n \"box-sizing\",\n] as const;\n\nexport type MediaVisualStyleProperty = (typeof MEDIA_VISUAL_STYLE_PROPERTIES)[number];\n\nconst FRAME_BOUNDARY_EPSILON = 1e-3;\n\nfunction safeFps(fps: number): number {\n return Number.isFinite(fps) && fps > 0 ? fps : 30;\n}\n\nfunction safeTime(timeSeconds: number): number {\n return Number.isFinite(timeSeconds) && timeSeconds > 0 ? timeSeconds : 0;\n}\n\nexport function quantizeTimeToFrame(timeSeconds: number, fps: number): number {\n const grid = safeFps(fps);\n const frameIndex = Math.floor(safeTime(timeSeconds) * grid + 1e-9);\n return frameIndex / grid;\n}\n\n/**\n * The grid a deterministic render seek lands on.\n *\n * With no `subFrameDivisions` this is the output frame grid, unchanged. Sub-frame\n * sampling (motion blur) passes an integer subdivision count, which can only refine\n * the output grid, never move a frame-time instant off it.\n *\n * The finer grid rounds where the frame grid floors. A sub-frame time makes a float\n * round trip (the caller builds `ticks / (fps * divisions)`, this recomputes\n * `time * fps * divisions`), and past a few million ticks that error exceeds the\n * floor epsilon, which would silently drop a sample one tick early.\n */\nexport function quantizeSeekTime(\n timeSeconds: number,\n fps: number,\n subFrameDivisions?: number,\n): number {\n const divisions = Number.isInteger(subFrameDivisions) ? (subFrameDivisions as number) : 1;\n if (divisions <= 1) return quantizeTimeToFrame(timeSeconds, fps);\n const grid = safeFps(fps) * divisions;\n return Math.round(safeTime(timeSeconds) * grid) / grid;\n}\n\n/** Snap decimal noise near a frame boundary without moving genuinely fractional timing. */\nexport function snapTimeToFrameBoundary(timeSeconds: number, fps: number): number {\n const grid = safeFps(fps);\n const time = safeTime(timeSeconds);\n const framePosition = time * grid;\n const nearestFrame = Math.round(framePosition);\n return Math.abs(framePosition - nearestFrame) <= FRAME_BOUNDARY_EPSILON\n ? nearestFrame / grid\n : time;\n}\n\n/** The window export shows a clip in: both ends snapped like its seeks, so a hair off a frame counts as on it. */\nexport function exportClipWindow(\n start: number,\n end: number,\n fps: number,\n): { start: number; end: number } {\n return {\n start: snapTimeToFrameBoundary(start, fps),\n end: Number.isFinite(end) ? snapTimeToFrameBoundary(end, fps) : end,\n };\n}\n\nexport function copyMediaVisualStyles(\n targetStyle: CSSStyleDeclaration,\n sourceStyle: CSSStyleDeclaration,\n properties: readonly string[] = MEDIA_VISUAL_STYLE_PROPERTIES,\n): void {\n for (const property of properties) {\n const value = sourceStyle.getPropertyValue(property);\n if (value) {\n targetStyle.setProperty(property, value);\n }\n }\n}\n","export const RUNTIME_PROTOCOL_VERSION = 1 as const;\n\nexport const RUNTIME_FILLER = \"hf-runtime-filler\";\n\nexport const RUNTIME_PROTOCOL_CAPABILITIES = [\n \"seconds-time\",\n \"rational-fps\",\n \"seek-keep-playing\",\n \"composition-manifest-v1\",\n \"runtime-data\",\n \"play-range\",\n] as const;\n\nexport type RuntimeProtocolFps = {\n numerator: number;\n denominator: number;\n};\n\nexport type RuntimeProtocolV1 = {\n protocolVersion: typeof RUNTIME_PROTOCOL_VERSION;\n capabilities: readonly string[];\n fps: RuntimeProtocolFps;\n};\n\nexport type RuntimeProtocolInspection =\n | { status: \"legacy\"; fps: number }\n | { status: \"supported\"; fps: number; metadata: RuntimeProtocolV1 }\n | {\n status: \"unsupported\";\n code: \"unsupported_protocol_version\" | \"invalid_protocol_metadata\";\n receivedVersion: unknown;\n };\n\nfunction greatestCommonDivisor(a: number, b: number): number {\n let left = Math.abs(a);\n let right = Math.abs(b);\n while (right !== 0) {\n const remainder = left % right;\n left = right;\n right = remainder;\n }\n return left || 1;\n}\n\nexport function runtimeProtocolFpsFromNumber(value: number): RuntimeProtocolFps {\n const safe = Number.isFinite(value) && value > 0 ? value : 30;\n const denominator = Number.isInteger(safe) ? 1 : 1_000_000;\n const numerator = Math.round(safe * denominator);\n const divisor = greatestCommonDivisor(numerator, denominator);\n return { numerator: numerator / divisor, denominator: denominator / divisor };\n}\n\nexport function playRangeHoldTime(start: number, end: number, fps: number): number {\n return Math.max(start, (Math.ceil(end * fps - 1e-6) - 1) / fps);\n}\n\nexport function runtimeProtocolFpsToNumber(value: unknown): number | null {\n if (typeof value !== \"object\" || value === null) return null;\n const fps = value as Partial<RuntimeProtocolFps>;\n if (!Number.isFinite(fps.numerator) || !Number.isFinite(fps.denominator)) return null;\n if ((fps.numerator ?? 0) <= 0 || (fps.denominator ?? 0) <= 0) return null;\n return Number(fps.numerator) / Number(fps.denominator);\n}\n\nexport function runtimeProtocolMetadata(fps: number): RuntimeProtocolV1 {\n return {\n protocolVersion: RUNTIME_PROTOCOL_VERSION,\n capabilities: RUNTIME_PROTOCOL_CAPABILITIES,\n fps: runtimeProtocolFpsFromNumber(fps),\n };\n}\n\nexport function frameDisplayScale(frame: HTMLElement): number | null {\n const layoutWidth = frame.offsetWidth;\n const shownWidth = frame.getBoundingClientRect().width;\n return layoutWidth > 0 && shownWidth > 0 ? shownWidth / layoutWidth : null;\n}\n\nfunction hasDeclaredCapabilities(value: unknown): boolean {\n return Array.isArray(value) && value.every((capability) => typeof capability === \"string\");\n}\n\nexport function inspectRuntimeProtocol(value: unknown, legacyFps = 30): RuntimeProtocolInspection {\n if (typeof value !== \"object\" || value === null) return { status: \"legacy\", fps: legacyFps };\n const message = value as Record<string, unknown>;\n if (message.protocolVersion === undefined) return { status: \"legacy\", fps: legacyFps };\n if (message.protocolVersion !== RUNTIME_PROTOCOL_VERSION) {\n return {\n status: \"unsupported\",\n code: \"unsupported_protocol_version\",\n receivedVersion: message.protocolVersion,\n };\n }\n const fps = runtimeProtocolFpsToNumber(message.fps);\n if (fps === null || !hasDeclaredCapabilities(message.capabilities)) {\n return {\n status: \"unsupported\",\n code: \"invalid_protocol_metadata\",\n receivedVersion: message.protocolVersion,\n };\n }\n return {\n status: \"supported\",\n fps,\n metadata: message as RuntimeProtocolV1,\n };\n}\n","import type {\n RuntimeTimelineClip,\n RuntimeTimelineMessage,\n RuntimeTimelineScene,\n RuntimeTimelineLike,\n} from \"./types\";\nimport { stableClipId } from \"./clipTree\";\nimport { findRootCompositionElement, parseCompositionDimension } from \"./compositionDimension\";\nimport {\n AUTHORED_DURATION_ATTR,\n AUTHORED_END_ATTR,\n resolveAuthoredTimingWindow,\n} from \"./authoredTiming\";\nimport { swallow } from \"./diagnostics\";\nimport { readElementPlaybackRate, readElementPlaybackStart } from \"./media\";\nimport {\n parseStrictFiniteTimingNumber,\n resolveMediaElementDurationSeconds,\n resolveNaturalMediaTimelineDuration,\n resolveTimedImageDurationSeconds,\n} from \"./playbackRate\";\nimport { resolveCssStackingContextId } from \"./stackingContext\";\nimport { createRuntimeStartTimeResolver } from \"./startResolver\";\nimport { isClipVisibleAt } from \"./clipWindow\";\nimport { exportClipWindow } from \"../inline-scripts/parityContract\";\nimport { isSceneLikeCompositionId } from \"../slideshow/index.js\";\nimport { COMPOSITION_CONTRACT_VERSION } from \"../compositionContract.js\";\nimport { runtimeProtocolMetadata } from \"./protocol.js\";\nimport { isElementNode, isMediaElement } from \"./domRealm\";\n\n/** A root timeline this long is an endless loop, not a film: GSAP reports 1e10 s for `repeat: -1`.\n * Studio's sanitizeDurationSeconds rejects the same length. Animations that simply end past the\n * voiceover are real duration, and the runtime player already plays them. */\nexport const LOOP_INFLATED_TIMELINE_SECONDS = 7200;\n\nexport function isRuntimeElementVisibleAt(\n rawNode: HTMLElement,\n options: {\n currentTime: number;\n compositionDuration: number;\n canonicalFps: number;\n exportRenderSeek: boolean;\n timelineRegistry: Record<string, RuntimeTimelineLike | undefined>;\n resolver: ReturnType<typeof createRuntimeStartTimeResolver>;\n },\n): boolean {\n const tag = rawNode.tagName.toLowerCase();\n if (tag === \"script\" || tag === \"style\" || tag === \"link\" || tag === \"meta\") {\n return false;\n }\n\n const isMedia = tag === \"video\" || tag === \"audio\";\n const start = isMedia\n ? options.resolver.resolveMediaStartForElement(rawNode)\n : options.resolver.resolveStartForElement(rawNode, 0);\n let duration = options.resolver.resolveDurationForElement(rawNode);\n const compId = rawNode.getAttribute(\"data-composition-id\");\n if (compId) {\n const compTimeline = options.timelineRegistry[compId];\n const liveDuration =\n compTimeline && typeof compTimeline.duration === \"function\"\n ? Number(compTimeline.duration())\n : null;\n const hasAuthoredTiming =\n rawNode.hasAttribute(\"data-duration\") ||\n rawNode.hasAttribute(\"data-end\") ||\n rawNode.hasAttribute(AUTHORED_DURATION_ATTR) ||\n rawNode.hasAttribute(AUTHORED_END_ATTR);\n if (\n !hasAuthoredTiming &&\n (duration == null || duration <= 0) &&\n liveDuration != null &&\n Number.isFinite(liveDuration) &&\n liveDuration > 0\n ) {\n duration = liveDuration;\n }\n }\n const computedEnd =\n duration != null && duration > 0 ? start + duration : Number.POSITIVE_INFINITY;\n // Export seeks snap to frame boundaries; interactive visibility uses authored seconds.\n const clipWindow = options.exportRenderSeek\n ? exportClipWindow(start, computedEnd, options.canonicalFps)\n : { start, end: computedEnd };\n return isClipVisibleAt(\n options.currentTime,\n clipWindow.start,\n clipWindow.end,\n options.compositionDuration,\n );\n}\n\nfunction parseNum(value: string | null | undefined): number | null {\n return parseStrictFiniteTimingNumber(value);\n}\n\nfunction parseElementDurationAttr(element: Element): number | null {\n const publicDuration = element.getAttribute(\"data-duration\");\n const authoredDuration = element.getAttribute(AUTHORED_DURATION_ATTR);\n const resolved = resolveAuthoredTimingWindow({\n start: 0,\n duration: publicDuration,\n authoredDuration,\n })?.duration;\n if (resolved != null) return resolved;\n const hasExplicitNonpositive = [publicDuration, authoredDuration]\n .map(parseNum)\n .some((duration) => duration != null && duration <= 0);\n return hasExplicitNonpositive ? 0 : null;\n}\n\nfunction parseElementEndAttr(element: Element): number | null {\n return (\n resolveAuthoredTimingWindow({\n start: 0,\n end: element.getAttribute(\"data-end\"),\n authoredEnd: element.getAttribute(AUTHORED_END_ATTR),\n })?.end ?? null\n );\n}\n\nfunction readInlineZIndex(element: Element): number {\n try {\n const inline = (element as HTMLElement).style?.zIndex;\n if (inline && inline !== \"auto\") {\n const parsed = parseInt(inline, 10);\n if (Number.isFinite(parsed)) return parsed;\n }\n return 0;\n } catch {\n return 0;\n }\n}\n\nfunction maxDefinedNumber(...values: Array<number | null>): number | null {\n const finite = values.filter((value): value is number => Number.isFinite(value ?? null));\n if (finite.length === 0) return null;\n return Math.max(...finite);\n}\n\n/**\n * Parse an authored track attribute, honoring 0 (a valid top-lane index).\n * `parseInt(...) || fallback` silently replaced authored track 0 with the\n * synthetic fallback, so track-0 clips drifted to the bottom of the timeline.\n */\nexport function parseAuthoredTrack(el: Element, fallback: number): number {\n const raw = el.getAttribute(\"data-track-index\") ?? el.getAttribute(\"data-track\");\n if (raw == null) return fallback;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : fallback;\n}\n\nfunction toAbsoluteAssetUrl(rawValue: string | null | undefined): string | null {\n const raw = String(rawValue ?? \"\").trim();\n if (!raw) return null;\n const lowered = raw.toLowerCase();\n if (lowered.startsWith(\"data:\") || lowered.startsWith(\"javascript:\")) return null;\n try {\n return new URL(raw, document.baseURI).toString();\n } catch {\n return raw;\n }\n}\n\nfunction resolveNodeAssetUrl(node: Element): string | null {\n const src = node.getAttribute(\"src\") ?? node.getAttribute(\"data-src\");\n if (src) return toAbsoluteAssetUrl(src);\n const compositionSrc = node.getAttribute(\"data-composition-src\");\n if (compositionSrc) return toAbsoluteAssetUrl(compositionSrc);\n const mediaDescendant = node.querySelector(\"img[src], video[src], audio[src], source[src]\");\n if (!mediaDescendant) return null;\n return toAbsoluteAssetUrl(mediaDescendant.getAttribute(\"src\"));\n}\n\nfunction getFirstClassToken(node: Element): string | null {\n const className = (node as HTMLElement).className;\n if (typeof className !== \"string\") return null;\n return (\n className\n .split(/\\s+/)\n .map((value) => value.trim())\n .find((value) => value && value !== \"clip\" && !value.startsWith(\"__hf-\")) ?? null\n );\n}\n\nfunction filenameFromAssetUrl(url: string | null): string | null {\n if (!url) return null;\n let filename: string | undefined;\n try {\n const parsed = new URL(url, document.baseURI);\n filename = parsed.pathname.split(\"/\").filter(Boolean).at(-1);\n } catch {\n filename = url\n .replace(/[?#].*$/, \"\")\n .split(/[\\\\/]/)\n .filter(Boolean)\n .at(-1);\n }\n if (!filename) return null;\n try {\n return decodeURIComponent(filename);\n } catch {\n return filename;\n }\n}\n\nfunction textPreview(node: Element): string | null {\n const text = node.textContent?.replace(/\\s+/g, \" \").trim();\n if (!text) return null;\n return text.length > 32 ? `${text.slice(0, 31)}...` : text;\n}\n\nfunction humanizeTimelineToken(value: string): string {\n const normalized = value\n .replace(/\\.[^.]+$/i, \"\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n if (!normalized) return value;\n return normalized.replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\nfunction authoredCompositionId(node: Element): string | null {\n return (\n node.getAttribute(\"data-hf-original-composition-id\") ?? node.getAttribute(\"data-composition-id\")\n );\n}\n\nfunction buildTimelineClipLabel(node: Element, kind: RuntimeTimelineClip[\"kind\"], ordinal: number) {\n const explicit =\n node.getAttribute(\"data-timeline-label\") ??\n node.getAttribute(\"data-label\") ??\n node.getAttribute(\"aria-label\") ??\n null;\n if (explicit?.trim()) return explicit.trim();\n\n const compositionId = authoredCompositionId(node);\n if (compositionId) return humanizeTimelineToken(compositionId);\n\n const id = (node as HTMLElement).id;\n if (id) return humanizeTimelineToken(id);\n\n const classToken = getFirstClassToken(node);\n if (classToken) return humanizeTimelineToken(classToken);\n\n const assetName = filenameFromAssetUrl(resolveNodeAssetUrl(node));\n if (assetName) return humanizeTimelineToken(assetName);\n\n const text = textPreview(node);\n if (text) return text;\n\n return `${humanizeTimelineToken(kind)} ${ordinal + 1}`;\n}\n\nexport function collectRuntimeTimelinePayload(params: {\n canonicalFps: number;\n}): RuntimeTimelineMessage {\n const runtimeWindow = window as Window & {\n __timelines?: Record<string, RuntimeTimelineLike | undefined>;\n };\n const timelineRegistry = runtimeWindow.__timelines ?? {};\n const startResolver = createRuntimeStartTimeResolver({\n timelineRegistry,\n includeAuthoredTimingAttrs: true,\n });\n const resolveTimelineDurationSeconds = (compositionId: string | null): number | null => {\n if (!compositionId) return null;\n const timeline = timelineRegistry[compositionId] ?? null;\n if (!timeline || typeof timeline.duration !== \"function\") return null;\n try {\n const duration = Number(timeline.duration());\n return Number.isFinite(duration) && duration > 0 ? duration : null;\n } catch {\n return null;\n }\n };\n const resolveMediaWindowEndSeconds = (): number | null => {\n const mediaNodes = Array.from(\n document.querySelectorAll(\"video[data-start], audio[data-start]\"),\n ) as Array<HTMLVideoElement | HTMLAudioElement>;\n if (mediaNodes.length === 0) return null;\n let maxWindowEndSeconds = 0;\n for (const mediaNode of mediaNodes) {\n const start = startResolver.resolveMediaStartForElement(mediaNode);\n if (!Number.isFinite(start)) continue;\n const duration = resolveMediaElementDurationSeconds(mediaNode);\n if (duration == null || duration <= 0) continue;\n maxWindowEndSeconds = Math.max(maxWindowEndSeconds, Math.max(0, start) + duration);\n }\n return maxWindowEndSeconds > 0 ? maxWindowEndSeconds : null;\n };\n const resolveNearestCompositionContext = (\n node: Element,\n root: Element | null,\n ): {\n parentCompositionId: string | null;\n compositionAncestors: string[];\n inheritedStart: number | null;\n inheritedDuration: number | null;\n } => {\n const ancestors: string[] = [];\n let inheritedStart: number | null = null;\n let inheritedDuration: number | null = null;\n let parentCompositionId: string | null = null;\n let cursor = node.parentElement;\n while (cursor) {\n const compositionId = cursor.getAttribute(\"data-composition-id\");\n if (compositionId) {\n ancestors.push(compositionId);\n if (!parentCompositionId && cursor !== root) {\n parentCompositionId = compositionId;\n }\n if (inheritedStart == null) {\n inheritedStart = startResolver.resolveStartForElement(cursor, 0);\n }\n if (inheritedDuration == null) {\n inheritedDuration =\n parseNum(cursor.getAttribute(\"data-duration\")) ??\n resolveTimelineDurationSeconds(compositionId) ??\n null;\n }\n }\n cursor = cursor.parentElement;\n }\n return {\n parentCompositionId,\n compositionAncestors: ancestors.reverse(),\n inheritedStart,\n inheritedDuration,\n };\n };\n\n const root = findRootCompositionElement();\n const compositionNodes = Array.from(document.querySelectorAll(\"[data-composition-id]\"));\n const rootCompositionId = root?.getAttribute(\"data-composition-id\") ?? null;\n const rootCompositionStart = root ? startResolver.resolveStartForElement(root, 0) : 0;\n const mediaWindowEnd = resolveMediaWindowEndSeconds();\n const mediaWindowDuration =\n mediaWindowEnd != null ? Math.max(0, mediaWindowEnd - Math.max(0, rootCompositionStart)) : null;\n const rootDurationFromTimeline = resolveTimelineDurationSeconds(rootCompositionId);\n const rootDurationFromAttr = parseElementDurationAttr(root ?? document.body);\n const compositionWindowEnd = maxDefinedNumber(\n ...compositionNodes\n .filter((node) => node !== root)\n .map((node) => {\n const start = startResolver.resolveStartForElement(node, 0);\n const duration =\n startResolver.resolveDurationForElement(node) ??\n resolveTimelineDurationSeconds(node.getAttribute(\"data-composition-id\")) ??\n null;\n if (!Number.isFinite(start) || duration == null || duration <= 0) return null;\n return Math.max(0, start) + duration;\n }),\n );\n const compositionWindowDuration =\n compositionWindowEnd != null\n ? Math.max(0, compositionWindowEnd - Math.max(0, rootCompositionStart))\n : null;\n const timelineDurationCandidate =\n typeof rootDurationFromTimeline === \"number\" &&\n Number.isFinite(rootDurationFromTimeline) &&\n rootDurationFromTimeline > 0\n ? rootDurationFromTimeline\n : null;\n const attrDurationCandidate =\n typeof rootDurationFromAttr === \"number\" &&\n Number.isFinite(rootDurationFromAttr) &&\n rootDurationFromAttr > 0\n ? rootDurationFromAttr\n : null;\n const mediaWindowDurationCandidate =\n typeof mediaWindowDuration === \"number\" &&\n Number.isFinite(mediaWindowDuration) &&\n mediaWindowDuration > 0\n ? mediaWindowDuration\n : null;\n const compositionWindowDurationCandidate =\n typeof compositionWindowDuration === \"number\" &&\n Number.isFinite(compositionWindowDuration) &&\n compositionWindowDuration > 0\n ? compositionWindowDuration\n : null;\n const finiteWindowFloor = maxDefinedNumber(\n mediaWindowDurationCandidate,\n compositionWindowDurationCandidate,\n );\n const timelineLooksLoopInflated =\n timelineDurationCandidate != null &&\n finiteWindowFloor != null &&\n timelineDurationCandidate >= LOOP_INFLATED_TIMELINE_SECONDS;\n // Prefer explicit authored root duration first.\n // If absent, guard against loop-inflated GSAP durations by trusting finite media window.\n const preferredRootDuration =\n attrDurationCandidate ??\n (timelineLooksLoopInflated\n ? finiteWindowFloor\n : maxDefinedNumber(\n timelineDurationCandidate,\n mediaWindowDurationCandidate,\n compositionWindowDurationCandidate,\n ));\n const rootCompositionDuration = preferredRootDuration ?? null;\n const rootCompositionEnd =\n rootCompositionDuration != null ? rootCompositionStart + rootCompositionDuration : null;\n const timelineWindowEnd =\n rootCompositionEnd ??\n (typeof mediaWindowEnd === \"number\" && Number.isFinite(mediaWindowEnd) && mediaWindowEnd > 0\n ? mediaWindowEnd\n : null);\n const clampDurationToRootWindow = (start: number, duration: number): number => {\n if (!Number.isFinite(duration) || duration <= 0) return 0;\n if (timelineWindowEnd == null || !Number.isFinite(timelineWindowEnd)) return duration;\n if (!Number.isFinite(start) || start >= timelineWindowEnd) return 0;\n return Math.max(0, Math.min(duration, timelineWindowEnd - start));\n };\n const clips: RuntimeTimelineClip[] = [];\n const scenes: RuntimeTimelineScene[] = [];\n // Only collect elements that are explicitly part of the timeline:\n // - Elements with data-start or data-track-index (timed clips)\n // - Elements with data-composition-id (sub-compositions)\n // - Media elements (video, audio, img)\n // Elements without data-start (e.g. GSAP-animated scenes) are not included\n // as clips — they have no declared timing so the timeline can't show their\n // actual visibility window. They can still appear as scenes via the separate\n // scene collection below.\n const nodes = Array.from(\n document.querySelectorAll(\n \"[data-start], [data-track-index], [data-composition-id], video, audio, img\",\n ),\n );\n let maxEnd = 0;\n for (const [i, node] of nodes.entries()) {\n if (node === root) continue;\n if ([\"SCRIPT\", \"STYLE\", \"LINK\", \"META\", \"TEMPLATE\", \"NOSCRIPT\"].includes(node.tagName))\n continue;\n const compositionContext = resolveNearestCompositionContext(node, root);\n const tag = node.tagName.toLowerCase();\n const start =\n tag === \"video\" || tag === \"audio\"\n ? startResolver.resolveMediaStartForElement(node)\n : startResolver.resolveStartForElement(node, compositionContext.inheritedStart ?? 0);\n const nodeCompositionId = node.getAttribute(\"data-composition-id\");\n let duration = parseElementDurationAttr(node);\n if (duration == null && nodeCompositionId && nodeCompositionId !== rootCompositionId) {\n duration = resolveTimelineDurationSeconds(nodeCompositionId);\n }\n if (duration == null && isMediaElement(node)) {\n if (Number.isFinite(node.duration)) {\n duration = resolveNaturalMediaTimelineDuration(node, node.duration);\n }\n }\n if (duration == null) duration = resolveTimedImageDurationSeconds(node, start);\n if (duration == null) {\n const inheritedDuration = compositionContext.inheritedDuration;\n if (inheritedDuration != null && inheritedDuration > 0) {\n const inheritedStart = compositionContext.inheritedStart ?? 0;\n const inheritedEnd = inheritedStart + inheritedDuration;\n duration = Math.max(0, inheritedEnd - start);\n }\n }\n if (duration == null || duration <= 0) continue;\n duration = clampDurationToRootWindow(start, duration);\n if (duration <= 0) continue;\n const end = start + duration;\n maxEnd = Math.max(maxEnd, end);\n const kind: RuntimeTimelineClip[\"kind\"] =\n nodeCompositionId && nodeCompositionId !== rootCompositionId\n ? \"composition\"\n : tag === \"video\"\n ? \"video\"\n : tag === \"audio\"\n ? \"audio\"\n : tag === \"img\"\n ? \"image\"\n : \"element\";\n clips.push({\n id: stableClipId(node) ?? nodeCompositionId ?? null,\n label: buildTimelineClipLabel(node, kind, clips.length),\n start,\n duration,\n track: parseAuthoredTrack(node, i),\n zIndex: readInlineZIndex(node),\n stackingContextId: resolveCssStackingContextId(node),\n kind,\n tagName: tag,\n compositionId: node.getAttribute(\"data-composition-id\"),\n compositionAncestors: compositionContext.compositionAncestors,\n parentCompositionId: compositionContext.parentCompositionId,\n nodePath: null,\n compositionSrc: toAbsoluteAssetUrl(node.getAttribute(\"data-composition-src\")),\n playbackStart: readElementPlaybackStart(node),\n playbackRate: readElementPlaybackRate(node),\n assetUrl: resolveNodeAssetUrl(node),\n timelineRole: node.getAttribute(\"data-timeline-role\"),\n timelineLabel: node.getAttribute(\"data-timeline-label\"),\n timelineGroup: node.getAttribute(\"data-timeline-group\"),\n timelinePriority: parseNum(node.getAttribute(\"data-timeline-priority\")),\n });\n }\n // ── GSAP introspection ──────────────────────────────────────────────────\n // Discover elements animated by GSAP that weren't picked up by the DOM query\n // (e.g. scene divs controlled purely via opacity/display tweens).\n // Introspect the master timeline's tweens to find their targets and time ranges.\n // ── GSAP introspection ──────────────────────────────────────────────────\n // Discover scene-level elements animated by GSAP that weren't picked up by\n // the DOM query. Introspect the master timeline's tweens, resolve absolute\n // time ranges, and bubble child tween ranges up to their nearest scene-level\n // ancestor (direct child of root with an id).\n const gsapClipIds = new Set(clips.map((c) => c.id));\n const rootCompositionIdForGsap = root?.getAttribute(\"data-composition-id\") ?? null;\n const masterTimeline = rootCompositionIdForGsap\n ? (timelineRegistry[rootCompositionIdForGsap] ?? null)\n : null;\n if (masterTimeline && root) {\n type GsapTween = {\n targets?: () => Element[];\n startTime?: () => number;\n duration?: () => number;\n parent?: GsapTween;\n };\n const tlWithChildren = masterTimeline as typeof masterTimeline & {\n getChildren?: (nested: boolean, tweens: boolean, timelines: boolean) => GsapTween[];\n };\n if (typeof tlWithChildren.getChildren === \"function\") {\n try {\n const tweens = tlWithChildren.getChildren(true, true, false) ?? [];\n // Build a set of direct children of root that have an id — these are\n // scene-level containers. Tween ranges on their descendants get bubbled\n // up to expand the scene's time range.\n const sceneElements = new Map<Element, { id: string; start: number; end: number }>();\n for (const child of root.children) {\n const childEl = child as HTMLElement;\n if (!childEl.id) continue;\n const tag = childEl.tagName.toLowerCase();\n if (tag === \"script\" || tag === \"style\" || tag === \"link\") continue;\n sceneElements.set(childEl, { id: childEl.id, start: Infinity, end: -Infinity });\n }\n // Find the scene-level ancestor for a given element\n const findSceneAncestor = (el: Element): Element | null => {\n let cursor: Element | null = el;\n while (cursor) {\n if (sceneElements.has(cursor)) return cursor;\n if (cursor === root) return null;\n cursor = cursor.parentElement;\n }\n return null;\n };\n // Walk all tweens and accumulate time ranges per scene element\n for (const tween of tweens) {\n if (typeof tween.targets !== \"function\") continue;\n if (typeof tween.startTime !== \"function\" || typeof tween.duration !== \"function\")\n continue;\n let tweenStart = tween.startTime();\n let parent = tween.parent;\n while (parent && parent !== masterTimeline && typeof parent.startTime === \"function\") {\n tweenStart += parent.startTime();\n parent = parent.parent;\n }\n const tweenEnd = tweenStart + tween.duration();\n if (!Number.isFinite(tweenStart) || !Number.isFinite(tweenEnd)) continue;\n for (const target of tween.targets()) {\n if (!isElementNode(target)) continue;\n // Bubble up to the scene-level ancestor\n const scene = findSceneAncestor(target);\n if (!scene) continue;\n const range = sceneElements.get(scene);\n if (!range) continue;\n range.start = Math.min(range.start, tweenStart);\n range.end = Math.max(range.end, tweenEnd);\n }\n }\n // Create clips for scene elements that have tween ranges\n const gsapTrack = clips.length > 0 ? Math.max(...clips.map((c) => c.track)) + 1 : 0;\n for (const [element, range] of sceneElements) {\n if (range.start === Infinity || range.end === -Infinity) continue;\n const el = element as HTMLElement;\n if (gsapClipIds.has(el.id)) continue;\n const duration = Math.max(0, range.end - range.start);\n if (duration <= 0) continue;\n const clampedDuration = clampDurationToRootWindow(range.start, duration);\n if (clampedDuration <= 0) continue;\n maxEnd = Math.max(maxEnd, range.start + clampedDuration);\n clips.push({\n id: el.id,\n label:\n el.getAttribute(\"data-timeline-label\") ??\n el.getAttribute(\"data-label\") ??\n el.getAttribute(\"aria-label\") ??\n el.id,\n start: range.start,\n duration: clampedDuration,\n track: parseAuthoredTrack(el, gsapTrack),\n zIndex: readInlineZIndex(el),\n stackingContextId: resolveCssStackingContextId(el),\n kind: \"element\",\n tagName: el.tagName.toLowerCase(),\n compositionId: el.getAttribute(\"data-composition-id\"),\n compositionAncestors: rootCompositionIdForGsap ? [rootCompositionIdForGsap] : [],\n parentCompositionId: rootCompositionIdForGsap,\n nodePath: null,\n compositionSrc: null,\n playbackStart: readElementPlaybackStart(el),\n playbackRate: readElementPlaybackRate(el),\n assetUrl: null,\n timelineRole: el.getAttribute(\"data-timeline-role\"),\n timelineLabel: el.getAttribute(\"data-timeline-label\"),\n timelineGroup: el.getAttribute(\"data-timeline-group\"),\n timelinePriority: parseNum(el.getAttribute(\"data-timeline-priority\")),\n });\n gsapClipIds.add(el.id);\n }\n } catch (err) {\n // GSAP introspection is best-effort — don't break timeline if it fails\n swallow(\"runtime.timeline.site1\", err);\n }\n }\n }\n\n // ── Persistent overlays ─────────────────────────────────────────────────\n // Direct children of root that are pure structural overlays should only\n // surface in the timeline when authors explicitly opt them in. Otherwise\n // background layers like \"backdrop\" make the whole composition read as a\n // long clip, which is misleading in Studio.\n if (root && rootCompositionDuration != null && rootCompositionDuration > 0) {\n const overlayTrack = clips.length > 0 ? Math.max(...clips.map((c) => c.track)) + 1 : 0;\n for (const child of root.children) {\n const el = child as HTMLElement;\n if (!el.id) continue;\n if (gsapClipIds.has(el.id)) continue;\n const timelineRole = el.getAttribute(\"data-timeline-role\");\n if (timelineRole !== \"overlay\" && timelineRole !== \"persistent-overlay\") continue;\n const tag = el.tagName.toLowerCase();\n if (tag === \"script\" || tag === \"style\" || tag === \"link\" || tag === \"meta\") continue;\n // Skip elements that are invisible (display:none in their CSS class)\n const computed = window.getComputedStyle(el);\n if (computed.display === \"none\") continue;\n const clampedDuration = clampDurationToRootWindow(0, rootCompositionDuration);\n if (clampedDuration <= 0) continue;\n maxEnd = Math.max(maxEnd, clampedDuration);\n clips.push({\n id: el.id,\n label:\n el.getAttribute(\"data-timeline-label\") ??\n el.getAttribute(\"data-label\") ??\n el.getAttribute(\"aria-label\") ??\n el.id,\n start: 0,\n duration: clampedDuration,\n track: parseAuthoredTrack(el, overlayTrack),\n zIndex: readInlineZIndex(el),\n stackingContextId: resolveCssStackingContextId(el),\n kind: \"element\",\n tagName: tag,\n compositionId: el.getAttribute(\"data-composition-id\"),\n compositionAncestors: rootCompositionIdForGsap ? [rootCompositionIdForGsap] : [],\n parentCompositionId: rootCompositionIdForGsap,\n nodePath: null,\n compositionSrc: null,\n playbackStart: readElementPlaybackStart(el),\n playbackRate: readElementPlaybackRate(el),\n assetUrl: null,\n timelineRole,\n timelineLabel: el.getAttribute(\"data-timeline-label\"),\n timelineGroup: el.getAttribute(\"data-timeline-group\"),\n timelinePriority: parseNum(el.getAttribute(\"data-timeline-priority\")),\n });\n gsapClipIds.add(el.id);\n }\n }\n\n // Track assignment honors the authored data-track-index verbatim: a clip stays\n // on the track it was placed on, regardless of kind. (Previously mixed-kind\n // tracks were split onto separate rows, but that renumbered tracks — breaking\n // \"drop a clip onto an existing track\" and causing the written track to drift\n // from the displayed one on every move. Track index is display-only; render\n // never reads it, so honoring it verbatim is the correct NLE behavior.)\n\n for (const compositionNode of compositionNodes) {\n if (compositionNode === root) continue;\n const compositionId = compositionNode.getAttribute(\"data-composition-id\");\n if (!compositionId || !isSceneLikeCompositionId(compositionId)) continue;\n const start = startResolver.resolveStartForElement(compositionNode, 0);\n let durationFromAttr = parseElementDurationAttr(compositionNode);\n if (\n (durationFromAttr == null || durationFromAttr <= 0) &&\n parseElementEndAttr(compositionNode) != null\n ) {\n const end = parseElementEndAttr(compositionNode)!;\n durationFromAttr = Math.max(0, end - start);\n }\n const durationFromTimeline = resolveTimelineDurationSeconds(compositionId);\n const duration =\n durationFromAttr && durationFromAttr > 0 ? durationFromAttr : durationFromTimeline;\n if (duration == null || duration <= 0) continue;\n const clampedDuration = clampDurationToRootWindow(start, duration);\n if (clampedDuration <= 0) continue;\n scenes.push({\n id: compositionId,\n label:\n compositionNode.getAttribute(\"data-label\") ??\n authoredCompositionId(compositionNode) ??\n compositionId,\n start,\n duration: clampedDuration,\n thumbnailUrl: toAbsoluteAssetUrl(compositionNode.getAttribute(\"data-thumbnail-url\")),\n avatarName: null,\n });\n }\n // Timeline payload duration should reflect the playable composition window,\n // not just the furthest currently-surfaced clip. Studio can intentionally\n // hide structural/background tracks from the timeline UI; if we collapse the\n // payload duration down to the last visible clip end, the controls jump even\n // though playback still runs for the full authored root duration.\n const knownDuration = Math.max(maxEnd || 0, rootCompositionDuration ?? 0);\n const safeDuration = knownDuration > 0 ? knownDuration : 1;\n const durationInFrames = Math.max(1, Math.ceil(safeDuration * Math.max(1, params.canonicalFps)));\n return {\n ...runtimeProtocolMetadata(params.canonicalFps),\n source: \"hf-preview\",\n type: \"timeline\",\n compositionContractVersion: COMPOSITION_CONTRACT_VERSION,\n durationSeconds: safeDuration,\n durationInFrames,\n clips,\n scenes,\n compositionWidth: parseCompositionDimension(root?.getAttribute(\"data-width\")) ?? 1920,\n compositionHeight: parseCompositionDimension(root?.getAttribute(\"data-height\")) ?? 1080,\n };\n}\n","import { createRuntimeStartTimeResolver } from \"./runtime/startResolver.js\";\nimport { isRuntimeElementVisibleAt } from \"./runtime/timeline.js\";\nimport type { RuntimeTimelineLike } from \"./runtime/types.js\";\nimport { STUDIO_PREVIEW_MARK_META } from \"./studioPreviewMark.js\";\n\n/** A composition is \"ready\" once every declared input settles, not just once\n * its duration is known. Each input returns null (nothing to wait on) or a\n * promise that resolves once it settles, and must stop its own pending work\n * (timer, rAF, listener) once `signal` aborts — settleCompositionReadiness\n * aborts it the moment the race settles, win or timeout. */\nexport type CompositionReadinessInput = (\n doc: Document,\n signal: AbortSignal,\n) => Promise<void> | null;\n\nexport interface PendingCompositionAssets {\n pendingMedia: HTMLMediaElement[];\n pendingImages: HTMLImageElement[];\n fontsLoading: boolean;\n}\n\nexport type CompositionReadinessScope = \"all\" | \"first-frame\";\n\nexport const FIRST_FRAME_READINESS_SCOPE: CompositionReadinessScope = \"first-frame\";\n\nexport interface CompositionReadinessOptions {\n scope?: CompositionReadinessScope;\n}\n\n// HTMLMediaElement.HAVE_FUTURE_DATA per spec, used as a literal because not\n// every DOM implementation defines the named static (e.g. happy-dom leaves\n// it undefined).\nconst HAVE_FUTURE_DATA = 3;\n\n// `doc` is a foreign (same-origin) iframe document, so its elements belong to\n// that iframe's own realm — instanceof checks against this window's globals\n// would silently reject every one of them. Resolve the class from the node's\n// own defaultView first.\nexport function isRealmElement(node: Node): node is Element {\n const view = node.ownerDocument?.defaultView;\n if (view && node instanceof view.Element) return true;\n return node instanceof Element;\n}\n\nexport function isRealmHtmlMediaElement(node: Node): node is HTMLMediaElement {\n if (!isRealmElement(node)) return false;\n if (node.tagName !== \"AUDIO\" && node.tagName !== \"VIDEO\") return false;\n const view = node.ownerDocument?.defaultView;\n if (view && node instanceof view.HTMLMediaElement) return true;\n return node instanceof HTMLMediaElement;\n}\n\nfunction isTimedElement(element: Element): boolean {\n return element.hasAttribute(\"data-start\") || element.hasAttribute(\"data-track-index\");\n}\n\nfunction isActiveAtFirstFrame(\n element: Element,\n resolver: ReturnType<typeof createRuntimeStartTimeResolver>,\n timelineRegistry: Record<string, RuntimeTimelineLike | undefined>,\n): boolean {\n let current: Element | null = element;\n while (current) {\n if (isTimedElement(current)) {\n if (\n !isRuntimeElementVisibleAt(current as HTMLElement, {\n currentTime: 0,\n compositionDuration: Number.POSITIVE_INFINITY,\n canonicalFps: 30,\n exportRenderSeek: false,\n timelineRegistry,\n resolver,\n })\n )\n return false;\n }\n current = current.parentElement;\n }\n return true;\n}\n\nfunction shouldIncludeAsset(\n element: Element,\n scope: CompositionReadinessScope,\n resolver: ReturnType<typeof createRuntimeStartTimeResolver>,\n timelineRegistry: Record<string, RuntimeTimelineLike | undefined>,\n): boolean {\n if (scope === \"all\") return true;\n return isActiveAtFirstFrame(element, resolver, timelineRegistry);\n}\n\n/** One DOM pass for every declared-media asset not yet ready. */\nexport function scanPendingCompositionAssets(\n doc: Document,\n { scope = \"all\" }: CompositionReadinessOptions = {},\n): PendingCompositionAssets {\n const runtimeWindow = doc.defaultView as\n | (Window & {\n __timelines?: Record<string, import(\"./runtime/types\").RuntimeTimelineLike | undefined>;\n })\n | null;\n const resolver = createRuntimeStartTimeResolver({\n documentRef: doc,\n timelineRegistry: runtimeWindow?.__timelines,\n includeAuthoredTimingAttrs: true,\n });\n // Studio's preview buffers a clip off the first frame only once it comes due, as with a lazy image.\n const defersMedia = doc.querySelector(`meta[name=\"${STUDIO_PREVIEW_MARK_META}\"]`) !== null;\n const pendingMedia = Array.from(doc.querySelectorAll(\"video, audio\"))\n .filter(isRealmHtmlMediaElement)\n .filter((el) =>\n shouldIncludeAsset(\n el,\n defersMedia && el.preload !== \"auto\" ? FIRST_FRAME_READINESS_SCOPE : scope,\n resolver,\n runtimeWindow?.__timelines ?? {},\n ),\n )\n .filter((el) => el.readyState < HAVE_FUTURE_DATA);\n // A lazy image off the first frame loads only once shown, so no scope can wait on it.\n const pendingImages = Array.from(doc.querySelectorAll(\"img\"))\n .filter((img) =>\n shouldIncludeAsset(\n img,\n img.getAttribute(\"loading\")?.toLowerCase() === \"lazy\" ? FIRST_FRAME_READINESS_SCOPE : scope,\n resolver,\n runtimeWindow?.__timelines ?? {},\n ),\n )\n .filter((img) => !img.complete);\n const fontsLoading = doc.fonts?.status === \"loading\";\n return { pendingMedia, pendingImages, fontsLoading };\n}\n\nfunction collectPendingCompositionAssets(\n doc: Document,\n { pendingMedia, pendingImages, fontsLoading }: PendingCompositionAssets,\n signal: AbortSignal,\n): Promise<void> {\n const mediaReady = pendingMedia.map(\n (el) =>\n new Promise<void>((resolve) => {\n // init.ts's eager preload pass may have already errored this element\n // before this input ran; a DOM error event doesn't refire, so a\n // listener attached now would wait for the shared 8s timeout instead.\n if (el.error) {\n resolve();\n return;\n }\n const onSettled = () => {\n el.removeEventListener(\"canplay\", onSettled);\n el.removeEventListener(\"error\", onSettled);\n signal.removeEventListener(\"abort\", onSettled);\n resolve();\n };\n el.addEventListener(\"canplay\", onSettled);\n el.addEventListener(\"error\", onSettled);\n signal.addEventListener(\"abort\", onSettled, { once: true });\n }),\n );\n const imagesReady = pendingImages.map((img) =>\n img.decode ? img.decode().catch(() => {}) : Promise.resolve(),\n );\n const fontsReady = fontsLoading && doc.fonts ? doc.fonts.ready.then(() => {}) : Promise.resolve();\n return Promise.all([...mediaReady, ...imagesReady, fontsReady]).then(() => {});\n}\n\n/** Declared-media readiness input: waits on the composition's own video,\n * audio, image and font-face loads. */\nexport function mediaReadinessInput(\n doc: Document,\n signal: AbortSignal,\n { scope = \"all\" }: CompositionReadinessOptions = {},\n): Promise<void> | null {\n const scan = scanPendingCompositionAssets(doc, { scope });\n if (scan.pendingMedia.length === 0 && scan.pendingImages.length === 0 && !scan.fontsLoading) {\n return null;\n }\n return collectPendingCompositionAssets(doc, scan, signal);\n}\n\nconst RENDER_READY_POLL_MS = 50;\n\n// `window.__renderReady` is declared globally in runtime/window.d.ts, but that\n// file lives under src/runtime — excluded from this package's own build\n// program (see tsconfig.json) — so it isn't visible here. Same flag, declared\n// locally instead of depending on a global merge from outside this file's scope.\n// `__hf` is omitted from Window so the runtime program's strict shape can't conflict with `unknown`.\ninterface RuntimeReadinessWindow extends Omit<Window, \"__hf\"> {\n __renderReady?: boolean;\n // __renderReady is only ever set by init.ts, which always sets __hf\n // first (`window.__hf = window.__hf || {}`) — a doc with no __hf can\n // never get __renderReady either, so this alone is enough to gate on\n // (unlike composition-probe.ts's hasRuntime, this doesn't need __player too).\n __hf?: unknown;\n}\n\n/** Declared-compute readiness input: waits on `window.__renderReady`. A\n * probe-only composition (no HyperFrames runtime injected) never sets\n * it — __hf absent means nothing to wait on, not an 8s poll for a flag\n * that was never going to flip. */\nexport function computeReadinessInput(doc: Document, signal: AbortSignal): Promise<void> | null {\n const win = doc.defaultView as RuntimeReadinessWindow | null;\n if (!win) return null;\n if (win.__renderReady) return null;\n if (!win.__hf) return null;\n return new Promise<void>((resolve) => {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const finish = () => {\n if (timeoutId !== undefined) clearTimeout(timeoutId);\n signal.removeEventListener(\"abort\", finish);\n resolve();\n };\n if (signal.aborted) {\n finish();\n return;\n }\n signal.addEventListener(\"abort\", finish, { once: true });\n const poll = () => {\n if (win.__renderReady) {\n finish();\n return;\n }\n timeoutId = setTimeout(poll, RENDER_READY_POLL_MS);\n };\n poll();\n });\n}\n\n// Gaps under this bound count as quiet on any document.\nconst IDLE_FRAME_GAP_MS = 50;\n// Ordinary frame jitter can span 1.5x the document's shortest observed gap.\nconst IDLE_FRAME_GAP_RATIO = 1.5;\n// Two consecutive quiet frames, not one: a single fast gap can follow\n// directly after the busy work finishes mid-frame and says nothing about\n// whether the next frame is also free.\nconst IDLE_FRAMES_REQUIRED = 2;\n// Bounds continuing stalls. A backgrounded document with no frames still\n// rides the shared timeout; this budget only bounds frames that arrive.\n// ponytail: 1500ms is unmeasured; retune with frame traces.\nconst MAX_PAINT_WAIT_MS = 1_500;\n\n// Resolves with -1 on abort instead of rejecting: every caller already\n// re-checks `signal.aborted` right after awaiting this, so a sentinel value\n// is enough and keeps the abort path a plain early-return, not a try/catch.\nfunction nextAnimationFrame(win: Window, signal: AbortSignal): Promise<number> {\n return new Promise((resolve) => {\n if (signal.aborted) {\n resolve(-1);\n return;\n }\n let id = 0;\n const onAbort = () => {\n win.cancelAnimationFrame?.(id);\n resolve(-1);\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n // A host whose rAF calls back synchronously reaches onAbort in the\n // callback, so it must already be declared above.\n id = win.requestAnimationFrame((ts) => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve(ts);\n });\n });\n}\n\n/** Composition-agnostic readiness input: waits for a frame to paint, then\n * two consecutive quiet frame gaps — an early-out once the main thread is\n * free relative to its own frame cadence. See MAX_PAINT_WAIT_MS for\n * what this bound does and does not cover. Compute and media inputs own completion. */\nexport function paintAndIdleReadinessInput(\n doc: Document,\n signal: AbortSignal,\n): Promise<void> | null {\n const win = doc.defaultView;\n if (!win) return null;\n return (async () => {\n const firstTs = await nextAnimationFrame(win, signal);\n if (signal.aborted) return;\n let lastTs = await nextAnimationFrame(win, signal);\n let shortestGap = lastTs - firstTs;\n let quietStreak = 0;\n while (!signal.aborted && quietStreak < IDLE_FRAMES_REQUIRED) {\n if (lastTs - firstTs >= MAX_PAINT_WAIT_MS) return;\n const ts = await nextAnimationFrame(win, signal);\n if (signal.aborted) return;\n const gap = ts - lastTs;\n shortestGap = Math.min(shortestGap, gap);\n const quiet = gap < IDLE_FRAME_GAP_MS || gap <= shortestGap * IDLE_FRAME_GAP_RATIO;\n quietStreak = quiet ? quietStreak + 1 : 0;\n lastTs = ts;\n }\n })();\n}\n\nconst DEFAULT_TIMEOUT_MS = 8_000;\n\nexport interface CompositionReadinessResult {\n timedOut: boolean;\n}\n\n/** Invokes `onSettled` once every input settles, or the timeout elapses.\n * Calls back synchronously, before returning, when nothing is pending — a\n * caller that plays right after this call sees the decision already\n * applied, same as before this gate existed. With the default inputs this\n * only happens for a document with no `defaultView`, since\n * paintAndIdleReadinessInput always has a frame to wait on otherwise. */\nexport function settleCompositionReadiness(\n doc: Document,\n onSettled: (result: CompositionReadinessResult) => void,\n opts: CompositionReadinessOptions & {\n inputs?: CompositionReadinessInput[];\n timeoutMs?: number;\n } = {},\n): void {\n const inputs = opts.inputs ?? [\n (inputDoc, signal) => mediaReadinessInput(inputDoc, signal, { scope: opts.scope }),\n computeReadinessInput,\n paintAndIdleReadinessInput,\n ];\n const controller = new AbortController();\n const pending = inputs\n .map((input) => input(doc, controller.signal))\n .filter((p): p is Promise<void> => p !== null);\n if (pending.length === 0) {\n onSettled({ timedOut: false });\n return;\n }\n\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n let timeoutId: ReturnType<typeof setTimeout>;\n const timeout = new Promise<\"timed-out\">((resolve) => {\n timeoutId = setTimeout(() => resolve(\"timed-out\"), timeoutMs);\n });\n // A timeout win leaves the losing inputs' promises pending forever unless\n // told to stop: this abort is what lets computeReadinessInput clear its\n // poll timer and paintAndIdleReadinessInput cancel its rAF chain instead\n // of running for the life of the page.\n Promise.race([Promise.all(pending).then(() => \"done\" as const), timeout]).then((result) => {\n clearTimeout(timeoutId);\n controller.abort();\n onSettled({ timedOut: result === \"timed-out\" });\n });\n}\n\nexport function settleFirstFrameCompositionReadiness(\n doc: Document,\n onSettled: (result: CompositionReadinessResult) => void,\n opts: Omit<\n CompositionReadinessOptions & {\n inputs?: CompositionReadinessInput[];\n timeoutMs?: number;\n },\n \"scope\"\n > = {},\n): void {\n settleCompositionReadiness(doc, onSettled, {\n ...opts,\n scope: FIRST_FRAME_READINESS_SCOPE,\n });\n}\n","/**\n * Parent-frame media proxy subsystem.\n *\n * Maintains mirror copies of the iframe's timed `<audio>`/`<video>` elements\n * in the parent frame so that mobile browsers — which gate `el.play()` on user\n * activation in the *same* frame — can still produce audible output via proxies\n * the parent controls directly.\n *\n * See the class-level JSDoc on `HyperframesPlayer` for the full ownership model.\n */\n\nimport { selectMediaObserverTargets } from \"./mediaObserverScope.js\";\nimport { isRealmElement, isRealmHtmlMediaElement } from \"./media-element-guards.js\";\nimport { isInClipWindow } from \"@hyperframes/core/runtime/clip-window\";\nimport { readClipTiming } from \"@hyperframes/core/composition-contract\";\n\n/** Minimum absolute drift before a currentTime correction is attempted. */\nconst MIRROR_DRIFT_THRESHOLD_SECONDS = 0.05;\n\n/**\n * How many *consecutive* over-threshold samples are required before issuing a\n * `currentTime` write. Absorbs single-sample jitter (GC pause, slow bridge\n * tick) without thrashing. Forced calls bypass this gate.\n *\n * Worst-case correction latency ≈ this × bridgeMaxPostIntervalMs (80 ms in\n * core/runtime/state.ts) = 160 ms — well under human A/V re-sync tolerance.\n */\nconst MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES = 2;\n\nexport interface ProxyEntry {\n el: HTMLMediaElement;\n start: number;\n duration: number;\n /**\n * The iframe media element this proxy mirrors, when adopted from the DOM.\n * Its `data-start`/`data-duration` are re-read each tick so live timeline\n * edits (trim/move) bound the proxy correctly. Null for URL-driven proxies.\n */\n source?: HTMLMediaElement | null;\n /**\n * Count of consecutive steady-state samples in which the proxy's\n * `currentTime` was found drifted beyond `MIRROR_DRIFT_THRESHOLD_SECONDS`.\n * Reset on every in-threshold sample. A write is only issued once this\n * reaches `MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES`, absorbing\n * single-sample jitter without thrashing.\n */\n driftSamples: number;\n}\n\nexport class ParentMediaManager {\n private _entries: ProxyEntry[] = [];\n private _mediaObserver?: MutationObserver;\n private _playbackErrorPosted = false;\n private _audioOwner: \"runtime\" | \"parent\" = \"runtime\";\n /** The proxy created from the `audio-src` attribute, tracked so it can be\n * replaced or cleared instead of accumulating on every attribute change. */\n private _urlAudioEntry: ProxyEntry | null = null;\n private _urlAudioSrc: string | null = null;\n\n private readonly _dispatchEvent: (event: Event) => void;\n private readonly _getMuted: () => boolean;\n private readonly _getVolume: () => number;\n private readonly _getPlaybackRate: () => number;\n private readonly _getCurrentTime: () => number;\n private readonly _isPaused: () => boolean;\n\n constructor(opts: {\n dispatchEvent: (event: Event) => void;\n getMuted: () => boolean;\n getVolume: () => number;\n getPlaybackRate: () => number;\n getCurrentTime: () => number;\n isPaused: () => boolean;\n }) {\n this._dispatchEvent = opts.dispatchEvent;\n this._getMuted = opts.getMuted;\n this._getVolume = opts.getVolume;\n this._getPlaybackRate = opts.getPlaybackRate;\n this._getCurrentTime = opts.getCurrentTime;\n this._isPaused = opts.isPaused;\n }\n\n get audioOwner(): \"runtime\" | \"parent\" {\n return this._audioOwner;\n }\n\n /** Exposed for test instrumentation only — do not use in production code. */\n get entries(): ProxyEntry[] {\n return this._entries;\n }\n\n resetForIframeLoad(): void {\n this._playbackErrorPosted = false;\n const wasPromoted = this._audioOwner === \"parent\";\n this._audioOwner = \"runtime\";\n this.pauseAll();\n for (const m of this._entries) if (m !== this._urlAudioEntry) m.el.src = \"\";\n this._entries = this._urlAudioEntry ? [this._urlAudioEntry] : [];\n if (this._urlAudioSrc && !this._urlAudioEntry)\n this._urlAudioEntry = this._createEntry(this._urlAudioSrc, \"audio\", 0, Infinity);\n this.teardownObserver();\n if (wasPromoted) {\n this._dispatchEvent(\n new CustomEvent(\"audioownershipchange\", {\n detail: { owner: \"runtime\", reason: \"iframe-reload\" },\n }),\n );\n }\n }\n\n destroy(): void {\n this.teardownObserver();\n for (const m of this._entries) {\n m.el.pause();\n m.el.src = \"\";\n }\n this._entries = [];\n this._urlAudioEntry = null;\n this._urlAudioSrc = null;\n this._audioOwner = \"runtime\";\n this._playbackErrorPosted = false;\n }\n\n updateMuted(muted: boolean): void {\n for (const m of this._entries) m.el.muted = muted;\n }\n\n updateVolume(volume: number): void {\n for (const m of this._entries) m.el.volume = volume;\n }\n\n updatePlaybackRate(rate: number): void {\n for (const m of this._entries) m.el.playbackRate = rate;\n }\n\n private _playEntry(m: ProxyEntry): void {\n if (!m.el.src) return;\n m.el.play().catch((err: unknown) => this._reportPlaybackError(err));\n }\n\n // Play only if the current playhead is inside the clip's (live) window, so\n // bulk starts (playAll / adopt) don't blip audio for clips outside their\n // window until the next mirrorTime tick gates them off.\n private _playEntryIfActive(m: ProxyEntry): void {\n this._refreshEntryBounds(m);\n if (!this._inWindow(m, this._getCurrentTime())) return;\n this._playEntry(m);\n }\n\n // Re-read the source clip's live timing so trims/moves bound the proxy\n // (adopt-time values go stale when the timeline is edited).\n private _refreshEntryBounds(m: ProxyEntry): void {\n if (!m.source?.isConnected) return;\n // Guard against a malformed (non-numeric) attribute parsing to NaN: an NaN\n // duration makes every window check pass, so the\n // gate never closes and the proxy plays past its clip end.\n const timing = readClipTiming(m.source);\n m.start = timing.start ?? 0;\n m.duration =\n timing.duration != null && timing.duration > 0 ? timing.duration : Number.POSITIVE_INFINITY;\n }\n\n // Pause the proxy outside its clip window; resume it on re-entry during\n // parent-owned playback. Returns whether the proxy is within the window.\n private _inWindow(m: ProxyEntry, timeSeconds: number): boolean {\n return isInClipWindow(timeSeconds, m.start, m.start + m.duration);\n }\n\n private _gateEntryPlayback(m: ProxyEntry, timeSeconds: number): boolean {\n if (!this._inWindow(m, timeSeconds)) {\n if (!m.el.paused) m.el.pause();\n m.driftSamples = 0;\n return false;\n }\n if (this._audioOwner === \"parent\" && !this._isPaused() && m.el.paused) this._playEntry(m);\n return true;\n }\n\n playAll(): void {\n for (const m of this._entries) this._playEntryIfActive(m);\n }\n\n pauseAll(): void {\n for (const m of this._entries) m.el.pause();\n }\n\n stopAdoptedMedia(): void {\n for (const m of this._entries) {\n if (m.source) m.el.pause();\n }\n }\n\n seekAll(timeInSeconds: number): void {\n for (const m of this._entries) {\n // Re-read live bounds so a trim/move just before a paused scrub gates and\n // positions against the current clip window, not the adopt-time one.\n this._refreshEntryBounds(m);\n if (this._inWindow(m, timeInSeconds)) m.el.currentTime = timeInSeconds - m.start;\n }\n }\n\n // Audible scrub: position every proxy at `timeInSeconds` AND play the ones whose\n // clip window covers it, so the viewer hears the track under the playhead while\n // dragging the scrubber (vs seekAll, which positions silently). Each drag move\n // re-seeks to the new position, so playback restarts from the playhead and you\n // hear the audio you're scrubbing over. The caller settles back to silence on\n // scrub end (a normal pause+seekAll). Muted proxies stay silent (play() is a no-op\n // for output). Out-of-window proxies are paused.\n scrubAll(timeInSeconds: number): void {\n for (const m of this._entries) {\n this._refreshEntryBounds(m);\n if (this._inWindow(m, timeInSeconds)) {\n m.el.currentTime = timeInSeconds - m.start;\n this._playEntry(m);\n } else if (!m.el.paused) {\n m.el.pause();\n }\n }\n }\n\n /**\n * Mirror parent-proxy `currentTime` to the iframe timeline, with optional\n * jitter-coalescing. Pass `{ force: true }` for alignment moments (ownership\n * promotion, new proxy initialization) where drift must be corrected\n * immediately.\n */\n mirrorTime(timelineSeconds: number, options?: { force?: boolean }): void {\n const force = options?.force === true;\n for (const m of this._entries) {\n this._refreshEntryBounds(m);\n if (!this._gateEntryPlayback(m, timelineSeconds)) continue;\n const relTime = timelineSeconds - m.start;\n if (Math.abs(m.el.currentTime - relTime) > MIRROR_DRIFT_THRESHOLD_SECONDS) {\n m.driftSamples += 1;\n if (force || m.driftSamples >= MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES) {\n m.el.currentTime = relTime;\n m.driftSamples = 0;\n }\n } else {\n m.driftSamples = 0;\n }\n }\n }\n\n /**\n * Take ownership of audible playback in response to the runtime's\n * `media-autoplay-blocked` signal. Idempotent.\n *\n * The caller is responsible for muting the iframe's own media output via the\n * postMessage bridge (`set-media-output-muted`) after calling this.\n */\n /**\n * Take ownership of audible playback. Idempotent. The `onMirror` callback\n * is called with the current timeline time and `{ force: true }` so the\n * caller's mirror implementation runs (enabling test spies on the player\n * to fire). If omitted, `mirrorTime` is called directly.\n */\n promoteToParentProxy(\n iframeDoc: Document | null,\n onMirror?: (t: number, opts: { force: boolean }) => void,\n ): void {\n if (this._audioOwner === \"parent\") return;\n this._audioOwner = \"parent\";\n\n // Synchronously mute iframe media to close the race window.\n if (iframeDoc) {\n for (const el of iframeDoc.querySelectorAll(\"video, audio\")) {\n if (isRealmHtmlMediaElement(el)) el.muted = true;\n }\n }\n for (const m of [...this._entries]) this._repointToSource(m);\n\n // One-shot alignment — bypass jitter-coalescing gate.\n const t = this._getCurrentTime();\n if (onMirror) onMirror(t, { force: true });\n else this.mirrorTime(t, { force: true });\n if (!this._isPaused()) this.playAll();\n\n this._dispatchEvent(\n new CustomEvent(\"audioownershipchange\", {\n detail: { owner: \"parent\", reason: \"autoplay-blocked\" },\n }),\n );\n }\n\n /**\n * Set up proxies for all timed media currently in the iframe document, then\n * install a MutationObserver for media added later (sub-composition activation).\n */\n setupFromIframe(iframeDoc: Document): void {\n const mediaEls = iframeDoc.querySelectorAll(\"audio[data-start], video[data-start]\");\n for (const iframeEl of mediaEls) {\n if (isRealmHtmlMediaElement(iframeEl)) this._adoptIframeMedia(iframeEl);\n }\n this._observeDynamicMedia(iframeDoc);\n }\n\n /**\n * Set (or replace) the parent-frame audio proxy driven by the `audio-src`\n * attribute. Re-setting with a different URL tears down the previous proxy\n * first, so changing `audio-src` swaps the track instead of stacking a\n * second one that keeps preloading and plays in parallel.\n */\n setupFromUrl(audioSrc: string): void {\n if (this._urlAudioSrc === audioSrc && this._urlAudioEntry) return;\n this.teardownUrlAudio();\n const entry = this._createEntry(audioSrc, \"audio\", 0, Infinity);\n // Null when the composition already has a proxy for this URL: that one stays the\n // composition's, and a reset creates ours once that document's proxies are gone.\n this._urlAudioEntry = entry;\n this._urlAudioSrc = audioSrc;\n // If the parent already owns playback, bring the fresh proxy online so a\n // mid-playback swap is not silent until the next play tick.\n if (entry && this._audioOwner === \"parent\" && !this._isPaused()) {\n this.mirrorTime(this._getCurrentTime(), { force: true });\n this.playAll();\n }\n }\n\n /** Tear down the `audio-src` proxy (used when the attribute is removed). */\n teardownUrlAudio(): void {\n const entry = this._urlAudioEntry;\n this._urlAudioEntry = null;\n this._urlAudioSrc = null;\n if (!entry) return;\n entry.el.pause();\n entry.el.src = \"\";\n const idx = this._entries.indexOf(entry);\n if (idx !== -1) this._entries.splice(idx, 1);\n }\n\n teardownObserver(): void {\n this._mediaObserver?.disconnect();\n this._mediaObserver = undefined;\n }\n\n // ── Private ──────────────────────────────────────────────────────────────\n\n private _reportPlaybackError(err: unknown): void {\n if (this._playbackErrorPosted) return;\n this._playbackErrorPosted = true;\n this._dispatchEvent(\n new CustomEvent(\"playbackerror\", { detail: { source: \"parent-proxy\", error: err } }),\n );\n }\n\n /**\n * Create a parent-frame media element and start preloading it. Returns the\n * new entry, or `null` if a proxy for this src already exists (dedup).\n */\n private _createEntry(\n src: string,\n tag: \"audio\" | \"video\",\n start: number,\n duration: number,\n source?: HTMLMediaElement | null,\n ): ProxyEntry | null {\n if (this._urlTaken(src)) return null;\n\n const el = tag === \"video\" ? document.createElement(\"video\") : new Audio();\n el.preload = \"auto\";\n el.src = src;\n el.load();\n el.muted = this._getMuted();\n el.volume = this._getVolume();\n const rate = this._getPlaybackRate();\n if (rate !== 1) el.playbackRate = rate;\n\n const entry: ProxyEntry = { el, start, duration, driftSamples: 0, source };\n this._entries.push(entry);\n return entry;\n }\n\n /** Resolve an iframe media element's source to an absolute URL, or null. */\n private _resolveIframeMediaSrc(iframeEl: HTMLMediaElement): string | null {\n const rawSrc =\n iframeEl.getAttribute(\"src\") || iframeEl.querySelector(\"source\")?.getAttribute(\"src\");\n return rawSrc ? new URL(rawSrc, iframeEl.ownerDocument.baseURI).href : null;\n }\n\n // fallow-ignore-next-line complexity\n private _adoptIframeMedia(iframeEl: HTMLMediaElement): void {\n // Skip elements the preloader has demoted: the observer adopts one when its preload turns\n // \"auto\" and drops its proxy, which fetches the whole file, when it turns back.\n if (iframeEl.preload === \"metadata\" || iframeEl.preload === \"none\") return;\n\n const src = this._resolveIframeMediaSrc(iframeEl);\n if (!src) return;\n\n const timing = readClipTiming(iframeEl);\n const start = timing.start ?? 0;\n const duration = timing.duration ?? Number.POSITIVE_INFINITY;\n const tag = iframeEl.tagName === \"VIDEO\" ? (\"video\" as const) : (\"audio\" as const);\n\n const created = this._createEntry(src, tag, start, duration, iframeEl);\n\n // If already under parent ownership and playing, the new proxy must catch\n // up immediately — bypass the jitter-coalescing gate.\n if (created && this._audioOwner === \"parent\") {\n this.mirrorTime(this._getCurrentTime(), { force: true });\n if (!this._isPaused()) this._playEntryIfActive(created);\n }\n }\n\n // The runtime re-points a clip (its preview copy, a new bound file). A proxy that is not\n // playing keeps its file until the parent takes over, so the copy is not fetched twice.\n private _followIframeMediaSrc(iframeEl: HTMLMediaElement): void {\n const entry = this._entries.find((m) => m.source === iframeEl);\n if (!entry) return this._adoptIframeMedia(iframeEl);\n if (this._audioOwner !== \"parent\" || !this._repointToSource(entry)) return;\n this.mirrorTime(this._getCurrentTime(), { force: true });\n if (!this._isPaused()) this._playEntryIfActive(entry);\n }\n\n /** One proxy per file: a second clip on the same file is heard through the first one's proxy.\n * A proxy's file is its clip's current one (a clip that left the page owns none). */\n private _urlTaken(src: string, except?: ProxyEntry): boolean {\n return this._entries.some((m) => {\n if (m === except || (m.source && !m.source.isConnected)) return false;\n return ((m.source && this._resolveIframeMediaSrc(m.source)) || m.el.src) === src;\n });\n }\n\n private _repointToSource(entry: ProxyEntry): boolean {\n const src = entry.source ? this._resolveIframeMediaSrc(entry.source) : null;\n if (!src || entry.el.src === src) return false;\n if (this._urlTaken(src, entry)) {\n this._removeEntry(entry);\n return false;\n }\n entry.el.src = src;\n entry.el.load();\n return true;\n }\n\n private _detachIframeMedia(iframeEl: HTMLMediaElement): void {\n const entry = this._entries.find((m) => m.source === iframeEl);\n if (entry) this._removeEntry(entry);\n }\n\n private _removeEntry(entry: ProxyEntry): void {\n entry.el.pause();\n entry.el.src = \"\";\n this._entries.splice(this._entries.indexOf(entry), 1);\n }\n\n private _observeDynamicMedia(doc: Document): void {\n this.teardownObserver();\n if (typeof MutationObserver === \"undefined\" || !doc.body) return;\n\n // fallow-ignore-next-line complexity\n const obs = new MutationObserver((mutations) => {\n for (const m of mutations) {\n if (m.type === \"attributes\" && m.attributeName === \"src\") {\n const target = m.target;\n if (\n isRealmHtmlMediaElement(target) &&\n target.matches(\"audio[data-start], video[data-start]\")\n ) {\n this._followIframeMediaSrc(target);\n }\n continue;\n }\n if (m.type === \"attributes\" && m.attributeName === \"preload\") {\n const target = m.target;\n if (\n isRealmHtmlMediaElement(target) &&\n target.matches(\"audio[data-start], video[data-start]\")\n ) {\n if (target.preload === \"auto\") this._adoptIframeMedia(target);\n else this._detachIframeMedia(target);\n }\n continue;\n }\n\n for (const added of m.addedNodes) {\n if (!isRealmElement(added)) continue;\n const candidates: HTMLMediaElement[] = [];\n if (\n isRealmHtmlMediaElement(added) &&\n added.matches(\"audio[data-start], video[data-start]\")\n ) {\n candidates.push(added);\n }\n const inside = added.querySelectorAll(\"audio[data-start], video[data-start]\");\n for (const el of inside) {\n if (isRealmHtmlMediaElement(el)) candidates.push(el);\n }\n for (const el of candidates) this._adoptIframeMedia(el);\n }\n\n for (const removed of m.removedNodes) {\n if (!isRealmElement(removed)) continue;\n const dropped: HTMLMediaElement[] = [];\n if (\n isRealmHtmlMediaElement(removed) &&\n removed.matches(\"audio[data-start], video[data-start]\")\n ) {\n dropped.push(removed);\n }\n const inside = removed.querySelectorAll(\"audio[data-start], video[data-start]\");\n for (const el of inside) {\n if (isRealmHtmlMediaElement(el)) dropped.push(el);\n }\n for (const el of dropped) this._detachIframeMedia(el);\n }\n }\n });\n\n const observeOpts: MutationObserverInit = {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"preload\", \"src\"],\n };\n\n const targets = selectMediaObserverTargets(doc);\n for (const target of targets) {\n obs.observe(target, observeOpts);\n }\n this._mediaObserver = obs;\n }\n}\n","// Range playback: the `range-start` / `range-end` attributes, in film seconds.\n\nimport { playRangeHoldTime } from \"@hyperframes/core/runtime/protocol\";\n\nexport interface PlayRange {\n start: number;\n end: number | null;\n}\n\n/** The range the player applies for the attributes, and whether it differs from the one asked for:\n * an end past the film is cut to the film's end, and an empty or negative range applies no range. */\nexport function resolvePlayRange(\n start: number | null,\n end: number | null,\n duration: number,\n): { range: PlayRange | null; clamped: boolean } {\n if (start === null && end === null) return { range: null, clamped: false };\n const from = start ?? 0;\n const cut = end !== null && duration > 0 && end > duration;\n const to = cut ? duration : end;\n const limit = to ?? (duration > 0 ? duration : Infinity);\n if (!(from >= 0) || !(limit > from)) return { range: null, clamped: true };\n return { range: { start: from, end: to }, clamped: cut };\n}\n\nexport function playRangeStopTime(range: PlayRange, duration: number, fps: number): number {\n const end = range.end ?? duration;\n return end < duration ? playRangeHoldTime(range.start, end, fps) : end;\n}\n\nexport function isOutsidePlayRange(\n time: number,\n range: PlayRange,\n duration: number,\n fps: number,\n): boolean {\n return time < range.start || time >= playRangeStopTime(range, duration, fps);\n}\n","/**\n * Pure playback-state update logic for the `state` message from the runtime.\n *\n * Extracted from the web component so the state-transition rules — loop\n * handling, play/pause mirroring, completion detection — can be read and\n * exercised independently.\n */\n\nimport type { ParentMediaManager } from \"./parent-media.js\";\nimport { type PlayRange, playRangeStopTime } from \"./play-range.js\";\n\nconst UI_UPDATE_INTERVAL_MS = 100;\n\nexport interface PlaybackState {\n currentTime: number;\n duration: number;\n paused: boolean;\n lastUpdateMs: number;\n enteringRange?: boolean;\n}\n\nexport interface PlaybackStateCallbacks {\n updateControlsTime: (current: number, duration: number) => void;\n updateControlsPlaying: (playing: boolean) => void;\n dispatchEvent: (event: Event) => void;\n seek: (t: number) => void;\n play: () => void;\n getLoop: () => boolean;\n getPlayRange?: () => PlayRange | null;\n media: ParentMediaManager;\n}\n\ntype RuntimeStateData = {\n frame: number;\n isPlaying: boolean;\n currentTime?: number;\n ended?: boolean;\n ownsRange?: boolean;\n};\n\n/** The runtime's exact time when it sends one (older runtimes send only the whole frame). */\nfunction runtimeTime(data: RuntimeStateData, fps: number): number {\n return typeof data.currentTime === \"number\" && Number.isFinite(data.currentTime)\n ? data.currentTime\n : (data.frame ?? 0) / fps;\n}\n\n/** A stopped runtime's `ended: false` is a pause; otherwise reaching the length is the end. */\nfunction isAtEnd(data: RuntimeStateData, time: number, duration: number): boolean {\n if (duration <= 0) return false;\n if (data.ended === true) return true;\n if (data.ended === false && !data.isPlaying) return false;\n return time >= duration;\n}\n\ntype PlayBounds = {\n start: number;\n shownAtEnd: number;\n atEnd: boolean;\n stopRuntime: boolean;\n entering: boolean;\n};\n\n/** A `play-range` runtime says when a range ended. For an older one the player reads the time,\n * and after it jumps into the range only from a state inside it, never a stale one. */\nfunction playBounds(\n data: RuntimeStateData,\n time: number,\n current: PlaybackState,\n playing: boolean,\n range: PlayRange | null | undefined,\n fps: number,\n): PlayBounds {\n const { duration } = current;\n if (!range) {\n const atEnd = isAtEnd(data, time, duration);\n return { start: 0, shownAtEnd: duration, atEnd, stopRuntime: false, entering: false };\n }\n if (data.ownsRange) {\n const atEnd = playing && data.ended === true && !data.isPlaying;\n return { start: range.start, shownAtEnd: time, atEnd, stopRuntime: false, entering: false };\n }\n return olderRuntimeBounds(data, time, current, playing, range, fps);\n}\n\nfunction olderRuntimeBounds(\n data: RuntimeStateData,\n time: number,\n current: PlaybackState,\n playing: boolean,\n range: PlayRange,\n fps: number,\n): PlayBounds {\n const end = range.end ?? current.duration;\n const entering = current.enteringRange === true && (time < range.start || time >= end);\n return {\n start: range.start,\n shownAtEnd: playRangeStopTime(range, current.duration, fps),\n atEnd: playing && !entering && isAtEnd(data, time, end),\n stopRuntime: data.isPlaying,\n entering,\n };\n}\n\n/**\n * Process a `state` message from the runtime and return the next state.\n * Side effects (controls updates, events, media mirroring) are fired through\n * `callbacks`. The caller must commit the returned state object.\n */\nexport function applyRuntimeStateMessage(\n data: RuntimeStateData,\n fps: number,\n current: PlaybackState,\n callbacks: PlaybackStateCallbacks,\n): PlaybackState {\n const rawTime = runtimeTime(data, fps);\n const wasPlaying = !current.paused;\n const playing = wasPlaying || data.isPlaying;\n const range = callbacks.getPlayRange?.();\n const bounds = playBounds(data, rawTime, current, playing, range, fps);\n const { atEnd, start: loopStart } = bounds;\n const clampedTime = current.duration > 0 ? Math.min(rawTime, current.duration) : rawTime;\n const currentTime = atEnd ? bounds.shownAtEnd : clampedTime;\n const nextPaused = !data.isPlaying;\n const completedPlayback = atEnd && playing;\n\n if (completedPlayback && callbacks.getLoop()) {\n if (callbacks.media.audioOwner === \"parent\") callbacks.media.pauseAll();\n callbacks.seek(loopStart);\n callbacks.play();\n // play() sets paused=false; reflect that in the returned state so the\n // caller's destructure doesn't overwrite it with the stale nextPaused value.\n return { ...current, currentTime: loopStart, paused: false, enteringRange: true };\n }\n\n const next = { ...current, currentTime, paused: nextPaused, enteringRange: bounds.entering };\n\n if (callbacks.media.audioOwner === \"parent\") {\n if (wasPlaying && nextPaused) {\n callbacks.media.pauseAll();\n } else if (!wasPlaying && !nextPaused) {\n callbacks.media.playAll();\n }\n callbacks.media.mirrorTime(currentTime);\n }\n\n const now = performance.now();\n const playStateChanged = nextPaused !== current.paused;\n if (now - current.lastUpdateMs > UI_UPDATE_INTERVAL_MS || playStateChanged) {\n next.lastUpdateMs = now;\n callbacks.updateControlsTime(currentTime, current.duration);\n callbacks.updateControlsPlaying(!nextPaused);\n callbacks.dispatchEvent(new CustomEvent(\"timeupdate\", { detail: { currentTime } }));\n }\n\n if (completedPlayback) {\n // A runtime without play-range plays on past the range: stop it on the last frame inside.\n if (bounds.stopRuntime) callbacks.seek(currentTime);\n if (callbacks.media.audioOwner === \"parent\") callbacks.media.pauseAll();\n next.paused = true;\n callbacks.updateControlsPlaying(false);\n callbacks.dispatchEvent(new Event(\"ended\"));\n }\n\n return next;\n}\n","/**\n * Routes postMessages from the composition iframe to the appropriate handlers.\n *\n * Accepts the raw MessageEvent and delegates through typed callbacks so the\n * web component keeps its state fields private and this module stays stateless.\n */\n\nimport {\n applyRuntimeStateMessage,\n type PlaybackState,\n type PlaybackStateCallbacks,\n} from \"./playback-state.js\";\nimport type { ShaderLoaderState } from \"./shader-loader-state.js\";\nimport type { ShaderTransitionState } from \"./shader-options.js\";\nimport { inspectRuntimeProtocol } from \"@hyperframes/core/runtime/protocol\";\n\ntype SceneRecord = { id: string; start: number; duration: number };\n\nfunction extractScenes(raw: unknown): SceneRecord[] {\n if (!Array.isArray(raw)) return [];\n return raw.filter(\n (s): s is SceneRecord =>\n typeof s === \"object\" &&\n s !== null &&\n typeof (s as Record<string, unknown>)[\"id\"] === \"string\" &&\n typeof (s as Record<string, unknown>)[\"start\"] === \"number\" &&\n typeof (s as Record<string, unknown>)[\"duration\"] === \"number\",\n );\n}\n\nexport interface MessageHandlerCallbacks extends PlaybackStateCallbacks {\n getPlaybackState: () => PlaybackState;\n setPlaybackState: (next: PlaybackState) => void;\n getShaderLoadingMode: () => string;\n shaderLoader: ShaderLoaderState;\n setCompositionSize: (width: number, height: number) => void;\n sendControl: (action: string, extra?: Record<string, unknown>) => void;\n getIframeDoc: () => Document | null;\n /** Invoked when the iframe runtime posts `{type: \"ready\"}` — the player\n * uses it to replay current bridge state (mute, volume, playback rate) so\n * control messages sent before the iframe's listener registered aren't lost. */\n onRuntimeReady: () => void;\n /** Invoked when the runtime reports its composition assets settled. */\n onRuntimeAssetsReady?: (timedOut: boolean) => void;\n onRuntimeDataApplied?: (channel: unknown, requestId: unknown) => void;\n onRuntimeDataError?: (channel: unknown, requestId: unknown, message: unknown) => void;\n /** Invoked when the runtime posts a finite positive timeline duration. The\n * player uses this as the cross-origin readiness signal because the\n * same-origin composition probe cannot inspect CDN iframes. */\n onRuntimeTimelineReady: (duration: number, assetsReady: boolean | undefined) => void;\n setRuntimeFps?: (fps: number) => void;\n setRuntimeOwnsPlayRange?: (owns: boolean) => void;\n /** Called with the scene list whenever a \"timeline\" message is received. */\n setScenes: (scenes: SceneRecord[]) => void;\n /** Return false to ignore the iframe runtime's audible-media autoplay fallback.\n * Slideshow embeds keep iframe media under native element ownership because\n * presenter/audience sync mirrors those media events directly. */\n shouldPromoteMediaAutoplayFallback?: () => boolean;\n}\n\n// fallow-ignore-next-line complexity\nexport function handleRuntimeMessage(\n event: MessageEvent,\n frameWindow: Window | null,\n callbacks: MessageHandlerCallbacks,\n): void {\n if (event.source !== frameWindow) return;\n const data = event.data as Record<string, unknown> | undefined;\n if (!data || data[\"source\"] !== \"hf-preview\") return;\n const protocol = inspectRuntimeProtocol(data);\n if (protocol.status === \"unsupported\") {\n callbacks.dispatchEvent(\n new CustomEvent(\"runtimeprotocolerror\", {\n detail: { code: protocol.code, receivedVersion: protocol.receivedVersion },\n }),\n );\n return;\n }\n callbacks.setRuntimeFps?.(protocol.fps);\n const ownsPlayRange =\n protocol.status === \"supported\" && protocol.metadata.capabilities.includes(\"play-range\");\n callbacks.setRuntimeOwnsPlayRange?.(ownsPlayRange);\n\n if (data[\"type\"] === \"shader-transition-state\") {\n const state: ShaderTransitionState =\n data[\"state\"] && typeof data[\"state\"] === \"object\"\n ? (data[\"state\"] as ShaderTransitionState)\n : {};\n callbacks.shaderLoader.update(state, callbacks.getShaderLoadingMode());\n callbacks.dispatchEvent(\n new CustomEvent(\"shadertransitionstate\", {\n detail: { compositionId: data[\"compositionId\"], state },\n }),\n );\n return;\n }\n\n if (data[\"type\"] === \"ready\") {\n callbacks.onRuntimeReady();\n return;\n }\n\n if (data[\"type\"] === \"assets-ready\") {\n callbacks.onRuntimeAssetsReady?.(data[\"timedOut\"] === true);\n return;\n }\n\n if (data[\"type\"] === \"runtime-data-error\") {\n callbacks.onRuntimeDataError?.(data[\"channel\"], data[\"requestId\"], data[\"message\"]);\n return;\n }\n\n if (data[\"type\"] === \"runtime-data-applied\") {\n callbacks.onRuntimeDataApplied?.(data[\"channel\"], data[\"requestId\"]);\n return;\n }\n\n if (data[\"type\"] === \"state\") {\n callbacks.setPlaybackState(\n applyRuntimeStateMessage(\n {\n frame: (data[\"frame\"] as number) ?? 0,\n currentTime: data[\"currentTime\"] as number | undefined,\n ended: typeof data[\"ended\"] === \"boolean\" ? data[\"ended\"] : undefined,\n isPlaying: !!data[\"isPlaying\"],\n ownsRange: ownsPlayRange,\n },\n protocol.fps,\n callbacks.getPlaybackState(),\n callbacks,\n ),\n );\n return;\n }\n\n if (data[\"type\"] === \"media-autoplay-blocked\") {\n if (callbacks.shouldPromoteMediaAutoplayFallback?.() === false) return;\n let iframeDoc: Document | null = null;\n try {\n iframeDoc = callbacks.getIframeDoc();\n } catch {\n /* cross-origin */\n }\n callbacks.media.promoteToParentProxy(iframeDoc, (t, opts) =>\n callbacks.media.mirrorTime(t, opts),\n );\n callbacks.sendControl(\"set-media-output-muted\", { muted: true });\n return;\n }\n\n if (data[\"type\"] === \"timeline\" && (data[\"durationInFrames\"] as number) > 0) {\n const declaredDuration = Number(data[\"durationSeconds\"]);\n const frameDuration = Number(data[\"durationInFrames\"]);\n const duration =\n Number.isFinite(declaredDuration) && declaredDuration > 0\n ? declaredDuration\n : frameDuration / protocol.fps;\n if (\n Number.isFinite(data[\"compositionWidth\"]) &&\n (data[\"compositionWidth\"] as number) > 0 &&\n Number.isFinite(data[\"compositionHeight\"]) &&\n (data[\"compositionHeight\"] as number) > 0\n ) {\n callbacks.setCompositionSize(\n data[\"compositionWidth\"] as number,\n data[\"compositionHeight\"] as number,\n );\n }\n if (Number.isFinite(duration) && duration > 0) {\n const pb = callbacks.getPlaybackState();\n callbacks.setPlaybackState({ ...pb, duration });\n callbacks.updateControlsTime(pb.currentTime, duration);\n callbacks.onRuntimeTimelineReady(\n duration,\n typeof data[\"assetsReady\"] === \"boolean\" ? data[\"assetsReady\"] : undefined,\n );\n }\n callbacks.setScenes(extractScenes(data[\"scenes\"]));\n return;\n }\n\n if (\n data[\"type\"] === \"stage-size\" &&\n // Finite-check like the timeline branch above: `> 0` alone lets\n // Infinity through, which scales the iframe to 0 and blanks it.\n Number.isFinite(data[\"width\"]) &&\n (data[\"width\"] as number) > 0 &&\n Number.isFinite(data[\"height\"]) &&\n (data[\"height\"] as number) > 0\n ) {\n callbacks.setCompositionSize(data[\"width\"] as number, data[\"height\"] as number);\n }\n}\n","/**\n * Put the runtime in the document's head, before anything in the body runs.\n *\n * The probe injects the runtime by appending a `<script src>` to an already\n * loaded document, and only once it has a reason to: a nested composition, or\n * five polls with a timeline present. Both are far too late for a COMPONENT.\n *\n * A component is markup pasted into a composition, and it reads its values in\n * an inline IIFE that runs while the body is being parsed:\n *\n * var vars = window.__hyperframes && window.__hyperframes.getVariables\n * ? window.__hyperframes.getVariables()\n * : {};\n *\n * With the runtime arriving afterwards, that guard always took the empty\n * branch, so the component fell back to the defaults hardcoded in its own\n * script and every chosen value was ignored. On the catalog page that looked\n * like the customise panel doing nothing: badge-pop with count 10 and a green\n * accent rendered 3, in red.\n *\n * A classic external `<script src>` in `<head>` is parser-blocking, so moving\n * the same URL into the srcdoc fixes the ordering without changing what is\n * loaded. A CLI render never had this problem because the engine already puts\n * the runtime ahead of body scripts.\n */\n\n/** Already carrying the runtime: a CLI-rendered page, or a second preparation. */\nfunction alreadyHasRuntime(html: string, runtimeUrl: string): boolean {\n if (html.includes(runtimeUrl)) return true;\n // The engine inlines the runtime rather than linking it, and it defines this\n // global on the way in. Matching the source avoids a second, redundant copy.\n return /hyperframe\\.runtime\\.iife\\.js|__hyperframes\\s*=/.test(html);\n}\n\ntype OpeningTag = { index: number; end: number };\nconst OPENING_TAG_BOUNDARIES = new Set<string | undefined>([\">\", \" \", \"\\t\", \"\\n\", \"\\r\", \"\\f\"]);\n\n/** Find an opening tag in one linear pass, without a backtracking regex over caller-owned HTML. */\nfunction findOpeningTag(html: string, tagName: string): OpeningTag | null {\n const lower = html.toLowerCase();\n const prefix = `<${tagName}`;\n let from = 0;\n while (from < lower.length) {\n const index = lower.indexOf(prefix, from);\n if (index < 0) return null;\n const boundary = lower[index + prefix.length];\n if (OPENING_TAG_BOUNDARIES.has(boundary)) {\n const close = lower.indexOf(\">\", index + prefix.length);\n if (close < 0) return null;\n return { index, end: close + 1 };\n }\n from = index + prefix.length;\n }\n return null;\n}\n\nexport function ensureRuntimeBeforeBodyScripts(html: string, runtimeUrl: string): string {\n if (!html || alreadyHasRuntime(html, runtimeUrl)) return html;\n\n const tag = `<script src=\"${runtimeUrl}\"></script>`;\n const head = findOpeningTag(html, \"head\");\n if (head) {\n return html.slice(0, head.end) + tag + html.slice(head.end);\n }\n // No head: get in before <body> so body scripts still see the runtime. A\n // fragment with neither lands at the front, which is the same guarantee.\n const body = findOpeningTag(html, \"body\");\n if (body) return html.slice(0, body.index) + tag + html.slice(body.index);\n const htmlTag = findOpeningTag(html, \"html\");\n if (htmlTag) {\n return html.slice(0, htmlTag.end) + tag + html.slice(htmlTag.end);\n }\n return tag + html;\n}\n","/**\n * Shader transition option types, constants, and pure helper functions for\n * injecting shader capture scale and loading mode parameters into composition\n * URLs and srcdoc HTML.\n */\n\nimport { ensureRuntimeBeforeBodyScripts } from \"./runtime-in-srcdoc.js\";\nimport { RUNTIME_CDN_URL } from \"./runtime-url.js\";\n\nexport const SHADER_CAPTURE_SCALE_ATTR = \"shader-capture-scale\";\nexport const SHADER_LOADING_ATTR = \"shader-loading\";\nexport const RUNTIME_SRC_ATTR = \"runtime-src\";\nconst SHADER_CAPTURE_SCALE_PARAM = \"__hf_shader_capture_scale\";\nconst SHADER_LOADING_PARAM = \"__hf_shader_loading\";\n\nexport const SHADER_LOADING_PHRASES = [\n \"Preparing scene transitions\",\n \"Sampling outgoing scene motion\",\n \"Sampling incoming scene motion\",\n \"Caching transition frames\",\n \"Finalizing transition preview\",\n];\n\nexport type ShaderLoadingMode = \"composition\" | \"player\" | \"none\";\n\nexport interface ShaderTransitionState {\n ready?: boolean;\n progress?: number;\n total?: number;\n currentTransition?: number;\n transitionTotal?: number;\n transitionFrame?: number;\n transitionFrames?: number;\n phase?: \"cached\" | \"capturing\" | \"finalizing\";\n loading?: boolean;\n}\n\nfunction normalizeShaderCaptureScale(value: string | null): string | null {\n if (value === null) return null;\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed <= 0) return null;\n return String(Math.min(1, Math.max(0.25, parsed)));\n}\n\nfunction normalizeShaderLoadingMode(value: string | null): ShaderLoadingMode {\n if (value === null || value.trim() === \"\") return \"composition\";\n const normalized = value.trim().toLowerCase();\n if (\n normalized === \"none\" ||\n normalized === \"false\" ||\n normalized === \"0\" ||\n normalized === \"off\"\n ) {\n return \"none\";\n }\n if (\n normalized === \"player\" ||\n normalized === \"true\" ||\n normalized === \"1\" ||\n normalized === \"on\"\n ) {\n return \"player\";\n }\n return \"composition\";\n}\n\n/** Drop one of our own keys from raw `a=1&b=2` pairs, matched by name so that\n * nothing around it has to be decoded. */\nfunction withoutParam(pairs: string[], key: string): string[] {\n return pairs.filter((pair) => pair !== \"\" && pair.split(\"=\")[0] !== key);\n}\n\n/**\n * The player's own params, appended to the query the composition author wrote\n * rather than merged into a re-serialized copy of it.\n *\n * `new URLSearchParams(query).toString()` is a form-encoding round trip: it\n * re-encodes the *whole* query as application/x-www-form-urlencoded, which\n * writes every space as `+`. A composition reading its own query with\n * `decodeURIComponent` — percent-decoding, which leaves `+` alone — cannot undo\n * that, so a value of \"Ship it today\" arrived on the page as \"Ship+it+today\".\n * The two codecs are not inverses, and the player has no business picking one\n * for a query it is only passing along. The author's bytes now travel through\n * byte-identical; only our two keys are rewritten.\n */\nfunction withShaderQueryParams(\n src: string,\n scale: string | null,\n loadingMode: ShaderLoadingMode,\n): string {\n const hashIndex = src.indexOf(\"#\");\n const beforeHash = hashIndex >= 0 ? src.slice(0, hashIndex) : src;\n const hash = hashIndex >= 0 ? src.slice(hashIndex) : \"\";\n const queryIndex = beforeHash.indexOf(\"?\");\n const path = queryIndex >= 0 ? beforeHash.slice(0, queryIndex) : beforeHash;\n const query = queryIndex >= 0 ? beforeHash.slice(queryIndex + 1) : \"\";\n let pairs = withoutParam(query.split(\"&\"), SHADER_CAPTURE_SCALE_PARAM);\n pairs = withoutParam(pairs, SHADER_LOADING_PARAM);\n if (scale !== null) pairs.push(`${SHADER_CAPTURE_SCALE_PARAM}=${encodeURIComponent(scale)}`);\n if (loadingMode !== \"composition\") {\n pairs.push(`${SHADER_LOADING_PARAM}=${encodeURIComponent(loadingMode)}`);\n }\n const nextQuery = pairs.join(\"&\");\n return `${path}${nextQuery ? `?${nextQuery}` : \"\"}${hash}`;\n}\n\nfunction injectShaderOptionsIntoSrcdoc(\n html: string,\n scale: string | null,\n loadingMode: ShaderLoadingMode,\n): string {\n if (scale === null && loadingMode === \"composition\") return html;\n const lines: string[] = [];\n if (scale !== null) lines.push(`window.__HF_SHADER_CAPTURE_SCALE=${JSON.stringify(scale)};`);\n if (loadingMode !== \"composition\") {\n lines.push(`window.__HF_SHADER_LOADING=${JSON.stringify(loadingMode)};`);\n }\n const script = `<script data-hyperframes-player-shader-options>${lines.join(\"\")}</script>`;\n if (/<head\\b[^>]*>/i.test(html))\n return html.replace(/<head\\b[^>]*>/i, (match) => `${match}${script}`);\n if (/<html\\b[^>]*>/i.test(html))\n return html.replace(/<html\\b[^>]*>/i, (match) => `${match}${script}`);\n return `${script}${html}`;\n}\n\n/**\n * Convenience wrappers that read shader attributes directly from an element,\n * avoiding boilerplate in the web component class body.\n */\n\nexport function getShaderModeFromElement(el: Element): ShaderLoadingMode {\n return normalizeShaderLoadingMode(el.getAttribute(SHADER_LOADING_ATTR));\n}\n\nexport function getShaderCaptureScaleFromElement(el: Element): number {\n return Number(normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)) ?? \"1\");\n}\n\nexport function prepareSrcForElement(el: Element, src: string): string {\n return withShaderQueryParams(\n src,\n normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)),\n getShaderModeFromElement(el),\n );\n}\n\nexport function prepareSrcdocForElement(el: Element, srcdoc: string): string {\n // Runtime first, and in the head: a component's inline script reads its\n // variables while the body is parsing, long before the probe's own injection\n // could land. See runtime-in-srcdoc.ts.\n return ensureRuntimeBeforeBodyScripts(\n injectShaderOptionsIntoSrcdoc(\n srcdoc,\n normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)),\n getShaderModeFromElement(el),\n ),\n runtimeSrcFromElement(el),\n );\n}\n\nfunction runtimeSrcFromElement(el: Element): string {\n const configured = el.getAttribute(RUNTIME_SRC_ATTR)?.trim();\n if (!configured) return RUNTIME_CDN_URL;\n try {\n const url = new URL(configured, document.baseURI);\n // A srcdoc frame runs with `allow-same-origin` by default, so it inherits the embedder's\n // origin. An unrestricted host here would therefore be arbitrary script execution in the\n // embedding page, reachable through a prop bag since React spreads unknown props onto\n // custom elements. Loopback and same-origin cover the local rig this exists for; a foreign\n // origin has to be a deliberate, named opt-in rather than a scheme check falling through.\n const okScheme = url.protocol === \"http:\" || url.protocol === \"https:\";\n const loopback =\n url.hostname === \"127.0.0.1\" || url.hostname === \"localhost\" || url.hostname === \"[::1]\";\n return okScheme && (loopback || url.origin === location.origin) ? url.href : RUNTIME_CDN_URL;\n } catch {\n return RUNTIME_CDN_URL;\n }\n}\n","/**\n * Factory for the shader-transition loading overlay DOM tree.\n *\n * Kept in its own module so the ~100-line DOM construction stays out of the\n * web component class body. The returned `ShaderLoaderElements` bag gives the\n * component direct handles to the nodes it needs to update without querying\n * the shadow DOM on every state change.\n */\n\nimport { SHADER_LOADING_PHRASES } from \"./shader-options.js\";\n\nexport interface ShaderLoaderElements {\n root: HTMLDivElement;\n fill: HTMLDivElement;\n title: HTMLSpanElement;\n detail: HTMLDivElement;\n transitionValue: HTMLSpanElement;\n transitionRow: HTMLDivElement;\n frameLabel: HTMLSpanElement;\n frameValue: HTMLSpanElement;\n frameRow: HTMLDivElement;\n}\n\nexport function createShaderLoader(): ShaderLoaderElements {\n const root = document.createElement(\"div\");\n root.className = \"hfp-shader-loader\";\n root.setAttribute(\"role\", \"status\");\n root.setAttribute(\"aria-live\", \"polite\");\n root.setAttribute(\"aria-label\", \"Preparing scene transitions\");\n root.setAttribute(\"data-hyperframes-ignore\", \"\");\n root.draggable = false;\n\n const blockOverlayInteraction = (event: Event) => {\n event.preventDefault();\n event.stopPropagation();\n };\n for (const eventName of [\n \"selectstart\",\n \"dragstart\",\n \"pointerdown\",\n \"mousedown\",\n \"click\",\n \"dblclick\",\n \"contextmenu\",\n \"touchstart\",\n ]) {\n root.addEventListener(eventName, blockOverlayInteraction, { capture: true });\n }\n\n const panel = document.createElement(\"div\");\n panel.className = \"hfp-shader-loader-panel\";\n panel.draggable = false;\n\n const markFrame = document.createElement(\"div\");\n markFrame.className = \"hfp-shader-loader-mark\";\n markFrame.draggable = false;\n markFrame.innerHTML = [\n '<svg width=\"78\" height=\"78\" viewBox=\"0 0 100 100\" fill=\"none\" aria-hidden=\"true\" draggable=\"false\">',\n '<path d=\"M10.1851 57.8021L33.1145 73.8313C36.2202 75.9978 41.5173 73.5433 42.4816 69.4984L51.7611 30.4271C52.7253 26.3822 48.5802 23.9277 44.4602 26.0942L13.917 42.1235C6.96677 45.7676 4.97564 54.1579 10.1851 57.8021Z\" fill=\"url(#hfp-shader-loader-grad-left)\"/>',\n '<path d=\"M87.5129 57.5141L56.9696 73.5433C52.8371 75.7098 48.7046 73.2553 49.6688 69.2104L58.9483 30.1391C59.9125 26.0942 65.2097 23.6397 68.3154 25.8062L91.2447 41.8354C96.4668 45.4796 94.4631 53.8699 87.5129 57.5141Z\" fill=\"url(#hfp-shader-loader-grad-right)\"/>',\n \"<defs>\",\n '<linearGradient id=\"hfp-shader-loader-grad-left\" x1=\"48.5676\" y1=\"25\" x2=\"44.7804\" y2=\"71.9384\" gradientUnits=\"userSpaceOnUse\">',\n '<stop stop-color=\"#06E3FA\"/>',\n '<stop offset=\"1\" stop-color=\"#4FDB5E\"/>',\n \"</linearGradient>\",\n '<linearGradient id=\"hfp-shader-loader-grad-right\" x1=\"54.8282\" y1=\"73.8392\" x2=\"72.0989\" y2=\"32.8932\" gradientUnits=\"userSpaceOnUse\">',\n '<stop stop-color=\"#06E3FA\"/>',\n '<stop offset=\"1\" stop-color=\"#4FDB5E\"/>',\n \"</linearGradient>\",\n \"</defs>\",\n \"</svg>\",\n ].join(\"\");\n\n const titleContainer = document.createElement(\"div\");\n titleContainer.className = \"hfp-shader-loader-title\";\n const titleText = document.createElement(\"span\");\n titleText.className = \"hfp-shader-loader-title-text\";\n titleText.textContent = SHADER_LOADING_PHRASES[0] || \"Preparing scene transitions\";\n titleContainer.appendChild(titleText);\n\n const detail = document.createElement(\"div\");\n detail.className = \"hfp-shader-loader-detail\";\n detail.textContent = \"Rendering animated scene samples for shader transitions.\";\n\n const track = document.createElement(\"div\");\n track.className = \"hfp-shader-loader-track\";\n track.setAttribute(\"aria-hidden\", \"true\");\n const fill = document.createElement(\"div\");\n fill.className = \"hfp-shader-loader-fill\";\n track.appendChild(fill);\n\n const progress = document.createElement(\"div\");\n progress.className = \"hfp-shader-loader-progress\";\n const createProgressRow = (labelText: string) => {\n const row = document.createElement(\"div\");\n row.className = \"hfp-shader-loader-row\";\n const label = document.createElement(\"span\");\n label.className = \"hfp-shader-loader-label\";\n label.textContent = labelText;\n const value = document.createElement(\"span\");\n value.className = \"hfp-shader-loader-value\";\n row.appendChild(label);\n row.appendChild(value);\n progress.appendChild(row);\n return { row, label, value };\n };\n const transitionStatus = createProgressRow(\"transition\");\n const frameStatus = createProgressRow(\"transition frame\");\n\n panel.appendChild(markFrame);\n panel.appendChild(titleContainer);\n panel.appendChild(detail);\n panel.appendChild(track);\n panel.appendChild(progress);\n root.appendChild(panel);\n\n return {\n root,\n fill,\n title: titleText,\n detail,\n transitionValue: transitionStatus.value,\n transitionRow: transitionStatus.row,\n frameLabel: frameStatus.label,\n frameValue: frameStatus.value,\n frameRow: frameStatus.row,\n };\n}\n","/**\n * Runtime state controller for the shader-transition loading overlay.\n *\n * Manages show/hide transitions (with a CSS fade-out delay) and updates\n * the progress bar, phrase text, and detail rows from `ShaderTransitionState`\n * messages received from the iframe.\n *\n * Holds direct references to the DOM nodes created by `createShaderLoader`\n * so state updates never touch the shadow-DOM query API at runtime.\n */\n\nimport { SHADER_LOADING_PHRASES, type ShaderTransitionState } from \"./shader-options.js\";\nimport type { ShaderLoaderElements } from \"./shader-loader-element.js\";\n\nconst HIDE_TRANSITION_MS = 420;\n\n/** A progress row shows only while it has a value, so no bare label is left on the card. */\nfunction setRowValue(row: HTMLElement, value: HTMLElement, text: string): void {\n value.textContent = text;\n row.style.visibility = text ? \"visible\" : \"hidden\";\n}\n\nexport class ShaderLoaderState {\n private readonly _el: ShaderLoaderElements;\n private _hideTimeout: ReturnType<typeof setTimeout> | null = null;\n private _hiddenCallbacks: Array<() => void> = [];\n private _drawingAssets = false;\n\n constructor(elements: ShaderLoaderElements) {\n this._el = elements;\n }\n\n show(): void {\n if (this._hideTimeout) {\n clearTimeout(this._hideTimeout);\n this._hideTimeout = null;\n }\n this._el.root.classList.remove(\"hfp-hiding\");\n this._el.root.classList.add(\"hfp-visible\");\n }\n\n hide(): void {\n if (this._el.root.classList.contains(\"hfp-hiding\")) {\n if (!this._hideTimeout) this._scheduleCleanup();\n return;\n }\n if (!this._el.root.classList.contains(\"hfp-visible\")) return;\n this._el.root.classList.add(\"hfp-hiding\");\n this._el.root.classList.remove(\"hfp-visible\");\n this._scheduleCleanup();\n }\n\n /** Runs `cb` once the panel is fully gone: now if it is not raised or\n * fading, otherwise when the fade's cleanup fires. */\n whenHidden(cb: () => void): void {\n const cls = this._el.root.classList;\n if (cls.contains(\"hfp-visible\") || cls.contains(\"hfp-hiding\")) this._hiddenCallbacks.push(cb);\n else cb();\n }\n\n private _flushHidden(): void {\n const callbacks = this._hiddenCallbacks;\n this._hiddenCallbacks = [];\n for (const cb of callbacks) cb();\n }\n\n reset(): void {\n if (this._hideTimeout) {\n clearTimeout(this._hideTimeout);\n this._hideTimeout = null;\n }\n this._el.root.classList.remove(\"hfp-visible\", \"hfp-hiding\");\n this._drawingAssets = false;\n this._flushHidden();\n this._el.fill.style.transform = \"scaleX(0)\";\n setRowValue(this._el.transitionRow, this._el.transitionValue, \"\");\n setRowValue(this._el.frameRow, this._el.frameValue, \"\");\n }\n\n // fallow-ignore-next-line unused-class-member, complexity\n update(status: ShaderTransitionState, loadingMode: string): void {\n const draws = loadingMode === \"player\" && status.loading && !status.ready;\n // A shader message that draws nothing leaves the Loading assets card to its own owner.\n if (this._drawingAssets && !draws) return;\n if (loadingMode !== \"player\") {\n this.reset();\n return;\n }\n if (status.ready || !status.loading) {\n this.hide();\n return;\n }\n // showAssetsLoading() may have left \"Loading assets\" here for a prior\n // show; this path owns the label whenever it's the one drawing the panel.\n this._el.root.setAttribute(\"aria-label\", \"Preparing scene transitions\");\n this._drawingAssets = false;\n\n const progress =\n typeof status.progress === \"number\" && Number.isFinite(status.progress) ? status.progress : 0;\n const total =\n typeof status.total === \"number\" && Number.isFinite(status.total) ? status.total : 0;\n const ratio = total > 0 ? Math.min(1, Math.max(0, progress / total)) : 0;\n\n const phraseIndex = Math.min(\n SHADER_LOADING_PHRASES.length - 1,\n Math.floor(ratio * SHADER_LOADING_PHRASES.length),\n );\n this._el.title.textContent =\n SHADER_LOADING_PHRASES[phraseIndex] || \"Preparing scene transitions\";\n\n this._el.detail.textContent =\n status.phase === \"cached\"\n ? \"Loading cached transition frames before playback.\"\n : status.phase === \"finalizing\"\n ? \"Uploading transition textures for smooth playback.\"\n : \"Rendering animated scene samples for shader transitions.\";\n\n this._el.fill.style.transform = `scaleX(${ratio})`;\n\n // fallow-ignore-next-line code-duplication\n const transitionValue =\n status.currentTransition !== undefined && status.transitionTotal !== undefined\n ? `${status.currentTransition}/${status.transitionTotal}`\n : total > 0\n ? `${progress}/${total}`\n : \"\";\n\n const frameValue =\n status.transitionFrame !== undefined && status.transitionFrames !== undefined\n ? `${status.transitionFrame}/${status.transitionFrames}`\n : \"\";\n\n this._el.frameLabel.textContent =\n status.phase === \"cached\"\n ? \"cached transition frames\"\n : status.phase === \"finalizing\"\n ? \"finalizing transition frames\"\n : \"rendering transition frames\";\n\n setRowValue(this._el.transitionRow, this._el.transitionValue, transitionValue);\n setRowValue(this._el.frameRow, this._el.frameValue, frameValue);\n this._el.root.setAttribute(\"aria-valuenow\", String(Math.round(ratio * 100)));\n this.show();\n }\n\n /** Second reason to show the same overlay — reuses the shader-transition\n * panel's DOM rather than drawing a second one. */\n showAssetsLoading(): void {\n this.reset();\n this._el.title.textContent = \"Loading assets\";\n this._el.detail.textContent = \"Waiting for images, video and fonts to finish loading.\";\n this._el.root.setAttribute(\"aria-label\", \"Loading assets\");\n this._drawingAssets = true;\n this.show();\n }\n\n /** Hides the panel only while it is the Loading assets card, never a shader load drawn over it. */\n hideAssetsLoading(): void {\n if (this._drawingAssets) this.hide();\n }\n\n // fallow-ignore-next-line unused-class-member\n get hideTimeout(): ReturnType<typeof setTimeout> | null {\n return this._hideTimeout;\n }\n\n destroy(): void {\n if (this._hideTimeout) {\n clearTimeout(this._hideTimeout);\n this._hideTimeout = null;\n }\n this._hiddenCallbacks = [];\n }\n\n private _scheduleCleanup(): void {\n if (this._hideTimeout) clearTimeout(this._hideTimeout);\n this._hideTimeout = setTimeout(() => {\n this._el.root.classList.remove(\"hfp-hiding\");\n this._hideTimeout = null;\n this._flushHidden();\n }, HIDE_TRANSITION_MS);\n }\n}\n","// Video mode: a `<video playsinline>` behind the direct-timeline adapter a same-origin\n// `__timelines` composition uses, so play, seek, loop and `ended` share one code path.\n\nimport type { DirectTimelineAdapter } from \"./timeline-adapters.js\";\n\nexport function isVideoType(type: string | null): boolean {\n return type?.trim().toLowerCase().startsWith(\"video/\") ?? false;\n}\n\nexport interface VideoSourceCallbacks {\n onMetadata: (video: HTMLVideoElement) => void;\n onDurationChange: (video: HTMLVideoElement) => void;\n onResize: (video: HTMLVideoElement) => void;\n onPlay: (video: HTMLVideoElement) => void;\n onPause: (video: HTMLVideoElement) => void;\n onTimeUpdate: () => void;\n onError: (message: string, code: number | null) => void;\n onPlayRejected: (error: unknown) => void;\n}\n\nexport interface VideoSource {\n video: HTMLVideoElement;\n adapter: DirectTimelineAdapter;\n destroy: () => void;\n}\n\nexport function createVideoSource(callbacks: VideoSourceCallbacks): VideoSource {\n const video = document.createElement(\"video\");\n video.className = \"hfp-video\";\n video.playsInline = true;\n video.preload = \"metadata\";\n\n const listening = new AbortController();\n const { signal } = listening;\n video.addEventListener(\"loadedmetadata\", () => callbacks.onMetadata(video), { signal });\n video.addEventListener(\"durationchange\", () => callbacks.onDurationChange(video), { signal });\n video.addEventListener(\"resize\", () => callbacks.onResize(video), { signal });\n video.addEventListener(\"play\", () => callbacks.onPlay(video), { signal });\n video.addEventListener(\"pause\", () => callbacks.onPause(video), { signal });\n video.addEventListener(\"timeupdate\", () => callbacks.onTimeUpdate(), { signal });\n video.addEventListener(\n \"error\",\n () =>\n callbacks.onError(video.error?.message || \"Video failed to load\", video.error?.code ?? null),\n { signal },\n );\n\n const adapter: DirectTimelineAdapter = {\n duration: () => video.duration,\n time: () => video.currentTime,\n seek: (timeInSeconds) => {\n video.currentTime = timeInSeconds;\n },\n // A rejection that lands after destroy() belongs to a source that is gone.\n play: () =>\n video.play().catch((error: unknown) => {\n if (!signal.aborted) callbacks.onPlayRejected(error);\n }),\n pause: () => video.pause(),\n // The default rate survives a new src; the current rate alone would reset to it.\n timeScale: (rate) => {\n video.defaultPlaybackRate = rate;\n video.playbackRate = rate;\n },\n };\n\n return {\n video,\n adapter,\n destroy: () => {\n listening.abort();\n video.pause();\n video.removeAttribute(\"src\");\n video.load();\n video.remove();\n },\n };\n}\n"],"mappings":"ubAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,uBAAAE,GAAA,kBAAAC,GAAA,gBAAAC,EAAA,eAAAC,KAAA,eAAAC,GAAAN,ICsBA,IAAMO,GAAoBC,GACxBA,IAAU,MAAQ,OAAO,SAASA,CAAK,GAAKA,EAAQ,EAE/C,SAASC,GACdC,EAC2B,CAC3B,IAAMC,EAAeD,EAAM,gBAAgB,OAAQE,GAAQA,IAAQ,IAAI,EAAE,OACzE,GAAIL,GAAiBG,EAAM,uBAAuB,EAChD,MAAO,CAAE,QAASA,EAAM,wBAAyB,OAAQ,WAAY,aAAAC,CAAa,EAEpF,IAAME,EAAQH,EAAM,gBAAgB,OAAOH,EAAgB,EAC3D,OAAIM,EAAM,OAAS,EACV,CAAE,QAAS,KAAK,IAAI,GAAGA,CAAK,EAAG,OAAQ,UAAW,aAAAF,CAAa,EAEjE,CACL,QAAS,KACT,OAAQ,aACR,aAAAA,EACA,OACEA,EAAe,EACX,uCACA,gDACR,CACF,CCLA,IAAMG,GAAW,+BASX,SAAUC,GAAcC,EAAc,CAC1C,OAAOA,GAAS,MAASA,EAAmB,WAAa,CAC3D,CAOM,SAAUC,GAAcD,EAAc,CAC1C,OAAOD,GAAcC,CAAK,GAAMA,EAAmB,eAAiBF,EACtE,CAIA,SAASI,GAAUF,EAAgBG,EAAW,CAC5C,OAAOF,GAAcD,CAAK,GAAMA,EAAmB,YAAcG,CACnE,CAGM,SAAUC,GAAeJ,EAAc,CAC3C,OAAOE,GAAUF,EAAO,OAAO,CACjC,CAGM,SAAUK,GAAeL,EAAc,CAC3C,OAAOE,GAAUF,EAAO,OAAO,CACjC,CAGM,SAAUM,EAAeN,EAAc,CAC3C,OAAOI,GAAeJ,CAAK,GAAKK,GAAeL,CAAK,CACtD,CAGM,SAAUO,GAAeP,EAAc,CAC3C,OAAOE,GAAUF,EAAO,KAAK,CAC/B,CC5EM,SAAUQ,GAA0BC,EAAgC,CACxE,GAAIA,GAAS,MAAQA,EAAM,KAAI,IAAO,GAAI,OAAO,KACjD,IAAMC,EAAS,OAAO,SAASD,EAAO,EAAE,EACxC,OAAO,OAAO,SAASC,CAAM,GAAKA,EAAS,EAAIA,EAAS,IAC1D,CAEM,SAAUC,GAA2BC,EAAkB,SAAQ,CACnE,IAAMC,EAAeD,EAAI,cAAc,yCAAyC,EAChF,OAAIE,GAAcD,CAAY,EAAUA,EAEjCD,EAAI,cAA2B,uBAAuB,CAC/D,CCXO,IAAMG,EAAyB,OAAO,OAAO,CAClD,MAAO,aACP,SAAU,gBACV,WAAY,mBACZ,WAAY,WACZ,YAAa,YACf,CAAU,EAEGC,GAAuC,OAAO,OAAO,CAChED,EAAuB,MACvBA,EAAuB,SACvBA,EAAuB,UACzB,CAAU,EAEGE,GAA4B,OAAO,OAAO,CACrDF,EAAuB,UACzB,CAAU,EAEGG,GAA2B,OAAO,OAAO,CACpDH,EAAuB,WACvBA,EAAuB,WACzB,CAAU,EAoEH,SAASI,GAAaC,EAAiD,CAC5E,GAAIA,GAAS,MAAQA,EAAM,KAAK,IAAM,GAAI,OAAO,KACjD,IAAMC,EAAS,OAAOD,CAAK,EAC3B,OAAO,OAAO,SAASC,CAAM,EAAIA,EAAS,IAC5C,CC7FA,IAAMC,GAAeC,GAAkD,CACrE,IAAMH,EAAQD,GAAaI,CAAG,EAC9B,OAAOH,IAAU,MAAQA,GAAS,EAAIA,EAAQ,IAChD,EAKO,SAASI,GAAuBC,EAA6B,CAClE,OACEH,GAAYG,EAAQ,qBAAqB,CAAC,GAAKH,GAAYG,EAAQ,kBAAkB,CAAC,GAAK,CAE/F,CAGO,IAAMC,EAAoB,GACpBC,EAAoB,GAG1B,SAASC,GAAkBL,EAAqB,CACrD,OAAO,OAAO,SAASA,CAAG,GAAKA,EAAM,EACjC,KAAK,IAAIG,EAAmB,KAAK,IAAIC,EAAmBJ,CAAG,CAAC,EAC5D,CACN,CAIO,SAASM,GAAiBJ,EAAqBK,EAAW,EAAW,CAC1E,IAAMC,EAAW,OAAO,WAAWN,EAAQ,oBAAoB,GAAK,EAAE,EACtE,OAAOG,GAAkB,OAAO,SAASG,CAAQ,GAAKA,EAAW,EAAIA,EAAWD,CAAQ,CAC1F,CAGO,SAASE,GAAwBP,EAAoC,CAC1E,IAAMQ,EAAWd,GAAaM,EAAQ,eAAe,CAAC,EACtD,OAAOQ,IAAa,MAAQA,EAAW,EAAIA,EAAW,IACxD,CAIO,SAASC,GACdT,EACAU,EACe,CACf,IAAMF,EAAWD,GAAwBP,CAAO,EAChD,GAAIQ,IAAa,KAAM,OAAOA,EAC9B,IAAMG,EAAMjB,GAAaM,EAAQ,UAAU,CAAC,EAC5C,OAAOW,IAAQ,KAAOA,EAAMD,EAAe,IAC7C,CAIO,IAAME,GAA0C,EAGhD,SAASC,GACdC,EACAC,EACAC,EACe,CACf,OAAK,OAAO,SAASF,CAAqB,EACxB,KAAK,IAAI,EAAGA,EAAwBC,CAAiB,EACpDZ,GAAkBa,CAAY,EAFG,IAGtD,CAqBO,SAASC,GAAqBC,EAAuD,CAC1F,GAAM,CAAE,IAAAC,EAAK,wBAAAC,EAAyB,sBAAAN,EAAuB,kBAAAC,EAAmB,aAAAC,CAAa,EAC3FE,EACF,GAAIE,IAA4B,MAAQA,EAA0B,EAChE,MAAO,CAAE,QAASA,EAAyB,OAAQ,UAAW,EAEhE,GAAID,IAAQ,MACV,MAAO,CAAE,QAASP,GAAyC,OAAQ,SAAU,EAE/E,GAAIE,IAA0B,KAC5B,MAAO,CAAE,QAAS,KAAM,OAAQ,UAAW,OAAQ,gCAAiC,EAEtF,IAAMO,EAAUR,GACdC,EACAC,EACAC,CACF,EACA,OAAIK,IAAY,KACP,CAAE,QAAS,KAAM,OAAQ,UAAW,OAAQ,uCAAwC,EAEtF,CAAE,QAAAA,EAAS,OAAQ,OAAQ,CACpC,CCnGO,IAAMC,GAAmB,gBAUnBC,GAAuBD,GAAiB,MAAM,CAAc,EA4DzE,IAAME,EAAO,CACXC,EAAM,YACNC,EAAQ,YACRC,EAAM,IACNC,EAAM,GACNC,EAAM,OACoB,CAC1B,KAAM,SACN,IAAAJ,EACA,MAAAC,EACA,KAAM,KACN,IAAAE,EACA,IAAAC,EACA,KAAM,EACN,QAASF,EACT,MAAO,MACP,YAAa,KAGTG,GAAS,CAACH,EAAM,KAAOI,EAAO,0CAA6D,CAC/F,KAAM,SACN,IAAK,IACL,MAAO,IACP,KAAM,GACN,IAAK,GACL,IAAK,GACL,KAAM,IACN,QAASJ,EACT,MAAO,MACP,YAAa,GACb,KAAAI,IAGIC,GAAS,CAACJ,EAAM,IAAKC,EAAM,GAAIF,EAAM,KAA6B,CACtE,KAAM,SACN,IAAK,OACL,MAAO,OACP,KAAM,KACN,IAAAC,EACA,IAAAC,EACA,KAAM,GACN,QAASF,EACT,YAAa,KAGTM,GAA4B,CAChC,KAAM,OACN,IAAK,QACL,MAAO,QACP,QAAS,CACP,CAAE,MAAO,IAAK,MAAO,UAAU,EAC/B,CAAE,MAAO,IAAK,MAAO,WAAW,GAElC,QAAS,IACT,KAAM,uDAOKC,GAAuC,CAClD,CACE,GAAI,OACJ,MAAO,OACP,MAAO,WACP,YAAa,6EAGb,OAAQ,CAACF,GAAO,IAAK,GAAI,CAAC,CAAC,EAC3B,IAAK,aAEP,CACE,GAAI,UACJ,MAAO,aACP,MAAO,SACP,YAAa,6DACb,OAAQ,CAACR,EAAK,YAAa,YAAa,GAAI,EAAGQ,GAAO,IAAK,GAAI,CAAC,EAAGF,GAAO,CAAC,CAAC,EAC5E,IAAK,kBAEP,CACE,GAAI,WACJ,MAAO,YACP,MAAO,SACP,YAAa,sDAIb,OAAQ,CAACN,EAAK,YAAa,YAAa,IAAK,GAAI,GAAI,EAAGQ,GAAO,IAAK,GAAI,CAAC,CAAC,EAC1E,IAAK,mBAEP,CACE,GAAI,YACJ,MAAO,aACP,MAAO,SACP,YAAa,sDACb,OAAQ,CAACR,EAAK,YAAa,YAAa,IAAM,IAAK,GAAK,EAAGQ,GAAO,IAAK,GAAI,CAAC,CAAC,EAC7E,IAAK,oBAEP,CACE,GAAI,WACJ,MAAO,YACP,MAAO,SACP,YAAa,qEACb,OAAQ,CAACR,EAAK,YAAa,SAAU,IAAK,GAAI,GAAK,EAAGM,GAAO,IAAK,EAAGG,EAAK,EAC1E,IAAK,mBAEP,CACE,GAAI,UACJ,MAAO,WACP,MAAO,SACP,YAAa,6DACb,OAAQ,CAACT,EAAK,YAAa,SAAU,IAAM,IAAK,GAAK,EAAGM,GAAO,IAAK,EAAGG,EAAK,EAC5E,IAAK,kBAGP,CACE,GAAI,aACJ,MAAO,aACP,MAAO,WACP,YAAa,sDACb,OAAQ,CACN,CACE,KAAM,SACN,IAAK,YACL,MAAO,YACP,KAAM,KACN,IAAK,IACL,IAAK,EACL,KAAM,GACN,QAAS,IACT,KAAM,oDAER,CACE,KAAM,SACN,IAAK,QACL,MAAO,QACP,KAAM,KACN,IAAK,EACL,IAAK,GACL,KAAM,GACN,QAAS,GAEX,CACE,KAAM,SACN,IAAK,SACL,MAAO,SACP,KAAM,KACN,IAAK,IACL,IAAK,IACL,KAAM,GACN,QAAS,GACT,MAAO,OAET,CACE,KAAM,SACN,IAAK,UACL,MAAO,UACP,KAAM,KACN,IAAK,IACL,IAAK,IACL,KAAM,EACN,QAAS,IACT,MAAO,OAET,CACE,KAAM,SACN,IAAK,OACL,MAAO,OACP,KAAM,GACN,IAAK,EACL,IAAK,EACL,KAAM,IACN,QAAS,KACT,KAAM,6CAER,CACE,KAAM,SACN,IAAK,SACL,MAAO,SACP,KAAM,KACN,IAAK,EACL,IAAK,GACL,KAAM,GACN,QAAS,GAEX,CACE,KAAM,SACN,IAAK,MACL,MAAO,MACP,KAAM,GACN,IAAK,EACL,IAAK,EACL,KAAM,IACN,QAAS,EACT,KAAM,2CAGV,IAAK,sBAEP,CACE,GAAI,UACJ,MAAO,UACP,MAAO,WACP,YAAa,mDACb,OAAQ,CACN,CACE,KAAM,SACN,IAAK,QACL,MAAO,UACP,KAAM,KACN,IAAK,IACL,IAAK,EACL,KAAM,GACN,QAAS,IAEX,CACE,KAAM,SACN,IAAK,SACL,MAAO,SACP,KAAM,KACN,IAAK,GACL,IAAK,GACL,KAAM,GACN,QAAS,GAEX,CACE,KAAM,SACN,IAAK,UACL,MAAO,UACP,KAAM,KACN,IAAK,EACL,IAAK,IACL,KAAM,EACN,QAAS,GACT,MAAO,OAET,CACE,KAAM,SACN,IAAK,YACL,MAAO,SACP,KAAM,KACN,IAAK,IACL,IAAK,GACL,KAAM,GACN,QAAS,IAGb,IAAK,mBAEP,CACE,GAAI,OACJ,MAAO,aACP,MAAO,WACP,YAAa,uDACb,OAAQ,CACN,CACE,KAAM,SACN,IAAK,YACL,MAAO,YACP,KAAM,KACN,IAAK,IACL,IAAK,EACL,KAAM,GACN,QAAS,KAEX,CACE,KAAM,SACN,IAAK,QACL,MAAO,QACP,KAAM,KACN,IAAK,IACL,IAAK,EACL,KAAM,GACN,QAAS,IACT,KAAM,4CAER,CACE,KAAM,SACN,IAAK,QACL,MAAO,QACP,KAAM,KACN,IAAK,EACL,IAAK,GACL,KAAM,GACN,QAAS,IAEX,CACE,KAAM,SACN,IAAK,SACL,MAAO,SACP,KAAM,KACN,IAAK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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["hyperframes_player_exports","__export","HyperframesPlayer","SPEED_PRESETS","formatSpeed","formatTime","__toCommonJS","isPositiveFinite","value","resolveCompositionDuration","input","pendingClips","end","known","XHTML_NS","isElementNode","value","isHtmlElement","isHtmlTag","tag","isVideoElement","isAudioElement","isMediaElement","isImageElement","parseCompositionDimension","value","parsed","findRootCompositionElement","doc","explicitRoot","isHtmlElement","COMPOSITION_ATTRIBUTES","CANONICAL_AUTHORED_TIMING_ATTRIBUTES","DERIVED_TIMING_ATTRIBUTES","LEGACY_TIMING_ATTRIBUTES","parseNumeric","value","parsed","nonNegative","raw","readMediaOffsetSeconds","getAttr","MIN_PLAYBACK_RATE","MAX_PLAYBACK_RATE","clampPlaybackRate","readPlaybackRate","fallback","authored","readDataDurationSeconds","duration","readAuthoredDurationSeconds","startSeconds","end","DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS","resolveNaturalDurationSeconds","sourceDurationSeconds","mediaStartSeconds","playbackRate","resolveMediaDuration","input","tag","authoredDurationSeconds","seconds","HF_AUDIO_FX_ATTR","HF_AUDIO_FX_DATA_KEY","freq","key","label","def","min","max","qParam","hint","gainDb","poles","HF_AUDIO_FX","BY_ID","d","HF_AUDIO_FX_IDS","HF_AUDIO_FX","d","audioDbToGain","db","MAX_AUDIO_GAIN","audioDbToGain","HF_AUDIO_AUTOMATION_ATTR","HF_AUDIO_AUTOMATION_DATA_KEY","HF_AUDIO_AUTOMATION_VERSION","MAX_AUTOMATION_POINTS","AudioAutomationError","message","VOLUME_TARGET","RATE_TARGET","parseAutomationTarget","target","parts","PRESET_TARGET_KEY","presetId","nodeId","param","PRESET_TARGET_KEY","VOLUME_RANGE","MAX_AUDIO_GAIN","RATE_RANGE","MIN_PLAYBACK_RATE","MAX_PLAYBACK_RATE","numberOrNull","raw","n","clampCurve","v","clampVia","rawX","rawY","x","y","inside","cx","cy","shapeFields","p","curve","clampCurve","via","clampVia","cleanPoint","range","numberOrNull","v","clamped","normalizePoints","points","clean","a","b","out","MAX_AUTOMATION_POINTS","normalizeAutomation","automation","lanes","lane","parseAutomationTarget","VOLUME_TARGET","VOLUME_RANGE","RATE_TARGET","RATE_RANGE","HF_AUDIO_AUTOMATION_VERSION","parseAutomation","json","raw","err","AudioAutomationError","obj","HF_AUDIO_AUTOMATION_VERSION","lanes","l","i","lane","parseAutomationTarget","normalizeAutomation","applyCurve","x","curve","viaConic","viaX","viaY","dx","dy","edge","MAX_WEIGHT","needX","needY","w","shapeVia","cx","cy","spread","a","b","t","conicParam","rest","denominator","c","root","first","second","onArc","shapeProgress","point","via","clampVia","lerpValue","scale","sampleAutomationLane","lane","pts","last","lo","hi","mid","span","cache","CACHE_LIMIT","laneFromAttr","raw","target","VOLUME_TARGET","key","hit","lane","parseAutomation","l","CELLS_PER_SEGMENT","tables","buildTable","lane","pts","firstRate","lastRate","ts","ss","push","t","prevT","prevRate","sampleAutomationLane","first","i","a","b","k","tableFor","table","usable","spec","interpolate","xs","ys","x","lo","hi","mid","timeAtSourceTime","spec","source","usable","ts","ss","firstRate","lastRate","tableFor","last","interpolate","parseRateLane","raw","laneFromAttr","RATE_TARGET","resolveRateSpec","automationAttr","constant","SPEED_PRESETS","COMPOSITION_ATTRIBUTES","CANONICAL_AUTHORED_TIMING_ATTRIBUTES","DERIVED_TIMING_ATTRIBUTES","LEGACY_TIMING_ATTRIBUTES","parseNumeric","value","parsed","REFERENCE_ID_PATTERN","isAsciiDigitAt","index","code","skipDigitsLeft","start","cursor","skipWhitespaceLeft","findMagnitudeStart","last","parseReferenceOffset","magnitudeStart","operatorIndex","operator","refId","magnitude","parseStartExpression","raw","normalized","absolute","reference","pushDiagnostic","diagnostics","attribute","resolveStart","expression","rawStart","options","COMPOSITION_ATTRIBUTES","referencedEnd","DERIVED_END_EQUALITY_EPSILON_SECONDS","derivedEndsAreConsistent","parsedEnd","canonicalEnd","diagnoseDerivedEnd","rawEnd","readCanonicalDuration","rawDuration","duration","readLegacyEnd","end","readDuration","attributes","canonical","readTrackValue","rawValue","source","trackIndex","readTrack","rawTrack","rawLayer","parsedLayer","readClipTiming","startExpression","track","parseStrictFiniteTimingNumber","raw","parseNumeric","readElementPlaybackRate","el","readPlaybackRate","name","isMediaElement","readElementRateSpec","el","resolveRateSpec","readElementPlaybackRate","readMediaStart","readMediaOffsetSeconds","name","resolveNaturalMediaTimelineDuration","sourceDuration","resolveNaturalMediaTimelineDurationFromValues","resolveMediaElementDurationSeconds","resolved","resolveMediaDuration","readDataDurationSeconds","resolveTimedImageDurationSeconds","startSeconds","isImageElement","readAuthoredDurationSeconds","mediaStart","playbackRate","resolveNaturalDurationSeconds","timeAtSourceTime","getDebugSurface","swallow","label","error","w","getDebugSurface","handler","handlerError","AUTHORED_DURATION_ATTR","AUTHORED_END_ATTR","finiteNumber","value","parsed","resolveAuthoredTimingWindow","values","parsedStart","start","publicDuration","preservedDuration","duration","publicEnd","preservedEnd","end","MEDIA_START_BASIS_ATTR","readMediaStartBasis","value","resolveAbsoluteMediaStartSeconds","input","resolveMediaStartSeconds","hasLiteralNestedStart","isRootGlobalMediaStart","createRuntimeStartTimeResolver","params","timelineRegistry","includeAuthoredTimingAttrs","doc","startCache","durationCache","visiting","findReferenceTarget","refId","byId","resolveDurationForElement","element","cached","resolved","durationTiming","resolveAuthoredTimingWindow","start","resolveStartForElementInternal","endTiming","isMediaElement","resolveNaturalMediaTimelineDuration","resolveTimedImageDurationSeconds","compositionId","timeline","timelineDuration","err","swallow","resolveHostOffsetForElement","fallback","parentComposition","compositionRoot","expression","parseStartExpression","parent","parentStart","absolute","target","targetStart","targetDuration","unresolved","mediaStartInput","parseStrictFiniteTimingNumber","MEDIA_START_BASIS_ATTR","input","resolveMediaStartSeconds","isRootGlobalMediaStart","createRuntimeState","MIN_VALID_TIMELINE_DURATION_SECONDS","aboveOneFrame","seconds","readCompositionSize","root","parseCompositionDimension","resolveMediaWindowDurationSeconds","doc","media","clipEnds","node","start","duration","resolveCompositionDuration","resolveAuthoredCompositionFloorSeconds","startResolver","subCompositionEnds","declared","parseStrictFiniteTimingNumber","isCompositionHost","resolveContentDerivedDuration","unregisteredLottie","isMediaElement","result","resolveCompositionLengthSeconds","input","timeline","floor","fallback","readStaticCompositionMeta","findRootCompositionElement","resolver","createRuntimeStartTimeResolver","durationSeconds","resolveMediaElementDurationSeconds","createRuntimeState","STUDIO_PREVIEW_MARK_META","STUDIO_PREVIEW_ERRORS","shouldInjectRuntime","state","isObjectRecord","value","isRuntimeDurationAdapter","isDirectTimelineAdapter","RUNTIME_CDN_URL","readPositiveDimension","value","parsed","readCompositionSizeFromDocument","doc","root","width","height","firstAuthorError","errors","CompositionProbe","_iframe","_callbacks","attempts","outcome","error","win","message","STUDIO_PREVIEW_ERRORS","timelines","hasRuntime","shouldInjectRuntime","isObjectRecord","result","adapter","duration","readStaticCompositionMeta","script","RUNTIME_CDN_URL","keys","rootId","key","timeline","isDirectTimelineAdapter","runtimePlayer","isRuntimeDurationAdapter","PLAYER_STYLES","PLAY_ICON","PAUSE_ICON","VOLUME_HIGH_ICON","VOLUME_LOW_ICON","VOLUME_MUTED_ICON","SPEED_PRESETS","formatSpeed","speed","formatTime","seconds","s","m","sec","createControls","parent","callbacks","options","presets","controls","e","playBtn","PLAY_ICON","PAUSE_ICON","scrubber","progress","time","speedWrap","speedBtn","speedMenu","preset","item","volumeWrap","muteBtn","VOLUME_HIGH_ICON","volumeSliderWrap","volumeSlider","volumeFill","isPlaying","isMuted","currentVolume","hideTimeout","speedIndex","getVolumeIcon","muted","volume","VOLUME_MUTED_ICON","VOLUME_LOW_ICON","volumeScrubbing","handleVolumeAt","clientX","rect","fraction","onVolumeMouseMove","onVolumeMouseUp","touch","onVolumeTouchMove","onVolumeTouchEnd","VOLUME_STEP","newVol","setActiveOption","opt","isOpen","target","newSpeed","onDocClick","handleScrubAt","scrubbing","onMouseMove","onMouseUp","onTouchMove","onTouchEnd","startHideTimer","host","onHostMouseMove","onHostMouseLeave","current","duration","clampedCurrent","pct","playing","idx","hidden","setupControls","parent","muted","volume","speedPresetsAttr","callbacks","audioLocked","speedPresets","n","options","api","createControls","setupPoster","posterUrl","existing","isControlsClick","event","t","_sharedSheet","adoptShadowStyles","shadow","cssText","style","createCompositionIframe","container","iframe","scaleIframeToFit","playerElement","compositionWidth","compositionHeight","w","h","scale","reachedStop","time","lookAhead","stop","early","DirectTimelineClock","_callbacks","timeline","getCurrentTime","getDuration","isPaused","getStop","lastTime","lastStep","tick","currentTime","duration","step","completedPlayback","now","selectMediaObserverTargets","doc","all","topLevel","el","hasCompositionAncestor","warnOnUnscopedTimedMedia","body","candidates","orphans","cursor","instantTolerance","time","sameInstant","a","b","hasClipStarted","time","start","sameInstant","isInClipWindow","end","TERMINAL_EPSILON_SECONDS","isClipVisibleAt","compositionDuration","HF_AUDIO_FADE_IN_ATTR","HF_AUDIO_FADE_OUT_ATTR","HF_AUDIO_FADE_IN_DATA_KEY","HF_AUDIO_FADE_OUT_DATA_KEY","NO_FADES","AUDIBLE_VIDEO_QUALIFIER","AUDIBLE_MEDIA_SELECTOR","NON_NONE_STACKING_CONTEXT_PROPERTIES","STACKING_CONTEXT_WILL_CHANGE_PROPERTIES","safeFps","fps","safeTime","timeSeconds","snapTimeToFrameBoundary","timeSeconds","fps","grid","safeFps","time","safeTime","framePosition","nearestFrame","exportClipWindow","start","end","RUNTIME_PROTOCOL_CAPABILITIES","greatestCommonDivisor","a","b","left","right","remainder","runtimeProtocolFpsFromNumber","value","safe","denominator","numerator","divisor","playRangeHoldTime","start","end","fps","runtimeProtocolFpsToNumber","runtimeProtocolMetadata","frameDisplayScale","frame","layoutWidth","shownWidth","hasDeclaredCapabilities","capability","inspectRuntimeProtocol","legacyFps","message","isRuntimeElementVisibleAt","rawNode","options","tag","start","duration","compId","compTimeline","liveDuration","AUTHORED_DURATION_ATTR","AUTHORED_END_ATTR","computedEnd","clipWindow","exportClipWindow","isClipVisibleAt","FIRST_FRAME_READINESS_SCOPE","HAVE_FUTURE_DATA","isRealmElement","node","view","isRealmHtmlMediaElement","isTimedElement","element","isActiveAtFirstFrame","resolver","timelineRegistry","current","isRuntimeElementVisibleAt","shouldIncludeAsset","scope","scanPendingCompositionAssets","doc","runtimeWindow","createRuntimeStartTimeResolver","defersMedia","STUDIO_PREVIEW_MARK_META","pendingMedia","el","pendingImages","img","fontsLoading","collectPendingCompositionAssets","signal","mediaReady","resolve","onSettled","imagesReady","fontsReady","mediaReadinessInput","scan","RENDER_READY_POLL_MS","computeReadinessInput","win","timeoutId","finish","poll","IDLE_FRAME_GAP_MS","IDLE_FRAME_GAP_RATIO","IDLE_FRAMES_REQUIRED","MAX_PAINT_WAIT_MS","nextAnimationFrame","id","onAbort","ts","paintAndIdleReadinessInput","firstTs","lastTs","shortestGap","quietStreak","gap","DEFAULT_TIMEOUT_MS","settleCompositionReadiness","opts","inputs","inputDoc","controller","pending","input","p","timeoutMs","timeout","result","settleFirstFrameCompositionReadiness","MIRROR_DRIFT_THRESHOLD_SECONDS","MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES","ParentMediaManager","opts","wasPromoted","m","muted","volume","rate","err","timing","readClipTiming","timeSeconds","isInClipWindow","timeInSeconds","timelineSeconds","options","force","relTime","iframeDoc","onMirror","el","isRealmHtmlMediaElement","t","mediaEls","iframeEl","audioSrc","entry","idx","src","tag","start","duration","source","rawSrc","created","except","doc","obs","mutations","target","added","isRealmElement","candidates","inside","removed","dropped","observeOpts","targets","selectMediaObserverTargets","resolvePlayRange","start","end","duration","from","cut","to","limit","playRangeStopTime","range","fps","playRangeHoldTime","isOutsidePlayRange","time","UI_UPDATE_INTERVAL_MS","runtimeTime","data","fps","isAtEnd","time","duration","playBounds","current","playing","range","atEnd","olderRuntimeBounds","end","entering","playRangeStopTime","applyRuntimeStateMessage","callbacks","rawTime","wasPlaying","bounds","loopStart","clampedTime","currentTime","nextPaused","completedPlayback","next","now","playStateChanged","extractScenes","raw","s","handleRuntimeMessage","event","frameWindow","callbacks","data","protocol","inspectRuntimeProtocol","ownsPlayRange","state","applyRuntimeStateMessage","iframeDoc","t","opts","declaredDuration","frameDuration","duration","pb","alreadyHasRuntime","html","runtimeUrl","OPENING_TAG_BOUNDARIES","findOpeningTag","tagName","lower","prefix","from","index","boundary","close","ensureRuntimeBeforeBodyScripts","tag","head","body","htmlTag","SHADER_CAPTURE_SCALE_ATTR","SHADER_LOADING_ATTR","RUNTIME_SRC_ATTR","SHADER_CAPTURE_SCALE_PARAM","SHADER_LOADING_PARAM","SHADER_LOADING_PHRASES","normalizeShaderCaptureScale","value","parsed","normalizeShaderLoadingMode","normalized","withoutParam","pairs","key","pair","withShaderQueryParams","src","scale","loadingMode","hashIndex","beforeHash","hash","queryIndex","path","query","nextQuery","injectShaderOptionsIntoSrcdoc","html","lines","script","match","getShaderModeFromElement","el","getShaderCaptureScaleFromElement","prepareSrcForElement","prepareSrcdocForElement","srcdoc","ensureRuntimeBeforeBodyScripts","runtimeSrcFromElement","configured","RUNTIME_CDN_URL","url","okScheme","loopback","createShaderLoader","root","blockOverlayInteraction","event","eventName","panel","markFrame","titleContainer","titleText","SHADER_LOADING_PHRASES","detail","track","fill","progress","createProgressRow","labelText","row","label","value","transitionStatus","frameStatus","HIDE_TRANSITION_MS","setRowValue","row","value","text","ShaderLoaderState","elements","cb","cls","callbacks","status","loadingMode","draws","progress","total","ratio","phraseIndex","SHADER_LOADING_PHRASES","transitionValue","frameValue","isVideoType","type","createVideoSource","callbacks","video","listening","signal","timeInSeconds","error","rate","MIN_PLAYBACK_RATE","MAX_PLAYBACK_RATE","SANDBOX_ORIGIN_ATTR","RUNTIME_DATA_DELIVERY_TIMEOUT_MS","ASSETS_READY_TIMEOUT_MS","ASSETS_LOADING_ATTR","ASSETS_LOADING_UI_ATTR","LOW_POWER_IDLE_ATTR","DISABLE_CLICK_TO_PLAY_ATTR","RANGE_START_ATTR","RANGE_END_ATTR","ASSETS_LOADING_SHOW_DELAY_MS","clampPlaybackRate","rate","HyperframesPlayer","RUNTIME_SRC_ATTR","SHADER_CAPTURE_SCALE_ATTR","SHADER_LOADING_ATTR","adoptShadowStyles","PLAYER_STYLES","createCompositionIframe","loaderElements","createShaderLoader","ShaderLoaderState","ParentMediaManager","e","DirectTimelineClock","currentTime","duration","CompositionProbe","result","message","event","isControlsClick","setupPoster","prepareSrcdocForElement","name","oldVal","val","src","readPositiveDimension","v","reloadActiveDocument","srcdoc","announce","range","isOutsidePlayRange","directTimelineStarted","queuedForReady","timeInSeconds","target","grading","compare","channel","payload","retained","t","r","getShaderCaptureScaleFromElement","scale","getShaderModeFromElement","mode","m","locked","ua","disabled","l","seconds","value","resolvePlayRange","endSeconds","park","clamped","clampKey","detail","time","end","hold","playRangeStopTime","action","extra","frameWindow","runtimeProtocolMetadata","error","requestId","bridge","previous","timeoutId","pending","muted","iframeDoc","el","isRealmHtmlMediaElement","frameDisplayScale","isVideoType","prepareSrcForElement","createVideoSource","video","code","adapter","player","fn","resolved","tl","tick","handleRuntimeMessage","paused","lastUpdateMs","enteringRange","w","h","assetsReady","fps","owns","scenes","d","p","ev","doc","compositionSize","runtimeAssetsReady","generation","settleFirstFrameCompositionReadiness","timedOut","pendingMedia","pendingImages","fontsLoading","scanPendingCompositionAssets","FIRST_FRAME_READINESS_SCOPE","win","img","reason","width","height","changed","apply","queue","errors","run","step","applied","scaleIframeToFit","setupControls","f","s","opts","o"]}
1
+ {"version":3,"sources":["../src/hyperframes-player.ts","../../parsers/src/compositionDuration.ts","../../core/src/runtime/domRealm.ts","../../core/src/runtime/compositionDimension.ts","../../parsers/src/compositionContract.ts","../../parsers/src/mediaDuration.ts","../../core/src/audioFx.ts","../../core/src/audioGain.ts","../../core/src/audioAutomation.ts","../../core/src/runtime/audioAutomationVolume.ts","../../core/src/speedRamp.ts","../../parsers/src/compositionContract.ts","../../core/src/runtime/playbackRate.ts","../../core/src/runtime/globals.ts","../../core/src/runtime/diagnostics.ts","../../core/src/runtime/authoredTiming.ts","../../core/src/mediaTiming.ts","../../core/src/runtime/startResolver.ts","../../core/src/runtime/state.ts","../../core/src/runtime/compositionLength.ts","../../core/src/studioPreviewMark.ts","../src/shouldInjectRuntime.ts","../src/timeline-adapters.ts","../src/runtime-url.ts","../src/composition-probe.ts","../src/styles.ts","../src/controls.ts","../src/controls-setup.ts","../src/iframe-dom.ts","../src/direct-timeline-clock.ts","../src/mediaObserverScope.ts","../../core/src/clipFacts.ts","../../core/src/runtime/clipWindow.ts","../../core/src/audioFade.ts","../../core/src/audibleVideo.ts","../../core/src/runtime/stackingContext.ts","../../core/src/inline-scripts/parityContract.ts","../../core/src/runtime/protocol.ts","../../core/src/runtime/timeline.ts","../../core/src/compositionReadiness.ts","../src/parent-media.ts","../src/play-range.ts","../src/playback-state.ts","../src/runtime-message-handler.ts","../src/runtime-in-srcdoc.ts","../src/shader-options.ts","../src/shader-loader-element.ts","../src/shader-loader-state.ts","../src/video-source.ts"],"sourcesContent":["import { CompositionProbe, type ProbeResult, readPositiveDimension } from \"./composition-probe.js\";\nimport { isControlsClick, setupControls, setupPoster } from \"./controls-setup.js\";\nimport { adoptShadowStyles, createCompositionIframe, scaleIframeToFit } from \"./iframe-dom.js\";\nimport { DirectTimelineClock } from \"./direct-timeline-clock.js\";\nimport { ParentMediaManager } from \"./parent-media.js\";\nimport { isRealmHtmlMediaElement } from \"./media-element-guards.js\";\nimport { handleRuntimeMessage } from \"./runtime-message-handler.js\";\nimport {\n isOutsidePlayRange,\n type PlayRange,\n playRangeStopTime,\n resolvePlayRange,\n} from \"./play-range.js\";\nimport {\n SHADER_CAPTURE_SCALE_ATTR,\n SHADER_LOADING_ATTR,\n RUNTIME_SRC_ATTR,\n type ShaderLoadingMode,\n getShaderCaptureScaleFromElement,\n getShaderModeFromElement,\n prepareSrcForElement,\n prepareSrcdocForElement,\n runtimeSrcFromElement,\n} from \"./shader-options.js\";\nimport { createShaderLoader } from \"./shader-loader-element.js\";\nimport { ShaderLoaderState } from \"./shader-loader-state.js\";\nimport { PLAYER_STYLES } from \"./styles.js\";\nimport { type DirectTimelineAdapter } from \"./timeline-adapters.js\";\nimport { createVideoSource, isVideoType, type VideoSource } from \"./video-source.js\";\nimport { frameDisplayScale, runtimeProtocolMetadata } from \"@hyperframes/core/runtime/protocol\";\nimport {\n FIRST_FRAME_READINESS_SCOPE,\n scanPendingCompositionAssets,\n settleFirstFrameCompositionReadiness,\n} from \"@hyperframes/core/composition-readiness\";\n\n// Playback-rate bounds mirror the runtime clamp in\n// packages/core/src/runtime/init.ts (applyPlaybackRate) and media.ts so the\n// player accepts the same range as the in-iframe runtime: an out-of-range rate\n// would otherwise drive the parent-proxied <audio> outside the bounds the\n// timeline itself respects. Clamping here also shields the native\n// HTMLMediaElement.playbackRate setter, which throws for extreme values in\n// production browsers.\nconst MIN_PLAYBACK_RATE = 0.1;\nconst MAX_PLAYBACK_RATE = 5;\nconst SANDBOX_ORIGIN_ATTR = \"sandbox-origin\";\nconst RUNTIME_DATA_DELIVERY_TIMEOUT_MS = 10_000;\n// Bounds how long the player waits on a same-origin composition's readiness\n// inputs (media, compute, paint-and-idle) before playing anyway — a stuck\n// asset or a composition that never goes quiet must not block playback forever.\nconst ASSETS_READY_TIMEOUT_MS = 8_000;\nconst ASSETS_LOADING_ATTR = \"assets-loading\";\n// \"player\" (default) draws the loading-assets card; \"none\" never does, like shader-loading=\"none\".\nconst ASSETS_LOADING_UI_ATTR = \"assets-loading-ui\";\nconst LOW_POWER_IDLE_ATTR = \"low-power-idle\";\nconst DISABLE_CLICK_TO_PLAY_ATTR = \"disable-click-to-play\";\nconst RANGE_START_ATTR = \"range-start\";\nconst RANGE_END_ATTR = \"range-end\";\n// paint-and-idle now always has a frame to wait on, so the overlay would\n// flash on every single Play without this debounce. ponytail: 150ms is\n// unmeasured, retune once there's production data on paint-and-idle timing.\nconst ASSETS_LOADING_SHOW_DELAY_MS = 150;\n\nexport type ColorGradingTarget =\n | string\n | {\n id?: string | null;\n hfId?: string | null;\n selector?: string | null;\n selectorIndex?: number | null;\n };\n\nexport type ColorGradingCompareState = {\n enabled: boolean;\n position?: number;\n softness?: number;\n lineWidth?: number;\n};\n\ntype RuntimeDataBridge = {\n setRuntimeData?: (channel: string, payload: unknown, requestId?: number) => void;\n clearRuntimeData?: (channel: string, requestId?: number) => void;\n};\n\ntype PendingRuntimeDataDelivery = {\n requestId: number;\n timeoutId: number;\n};\n\nfunction clampPlaybackRate(rate: number): number {\n if (!Number.isFinite(rate) || rate <= 0) return 1;\n return Math.max(MIN_PLAYBACK_RATE, Math.min(MAX_PLAYBACK_RATE, rate));\n}\n\nclass HyperframesPlayer extends HTMLElement {\n static get observedAttributes() {\n return [\n \"src\",\n \"srcdoc\",\n \"width\",\n \"height\",\n \"controls\",\n \"muted\",\n \"audio-locked\",\n \"volume\",\n \"poster\",\n \"playback-rate\",\n \"audio-src\",\n \"type\",\n SANDBOX_ORIGIN_ATTR,\n RUNTIME_SRC_ATTR,\n SHADER_CAPTURE_SCALE_ATTR,\n SHADER_LOADING_ATTR,\n ASSETS_LOADING_UI_ATTR,\n LOW_POWER_IDLE_ATTR,\n RANGE_START_ATTR,\n RANGE_END_ATTR,\n ];\n }\n\n private shadow: ShadowRoot;\n private container: HTMLDivElement;\n private iframe: HTMLIFrameElement;\n private posterEl: HTMLImageElement | null = null;\n private controlsApi: ReturnType<typeof setupControls> | null = null;\n private resizeObserver: ResizeObserver;\n private shaderLoader: ShaderLoaderState;\n private probe: CompositionProbe;\n\n private _ready = false;\n private _readyDocument: Document | null = null;\n private _connected = false;\n private _assetsReady = false;\n private _painted = false;\n private _pendingPlay = false;\n private _assetsGeneration = 0;\n private _assetsLoadingShowTimer: ReturnType<typeof setTimeout> | null = null;\n private _currentTime = 0;\n private _duration = 0;\n private _paused = true;\n /** True while the user is dragging the scrubber — makes seek() play audio at the\n * playhead (audible scrub) instead of positioning it silently. */\n private _scrubbing = false;\n private _lastUpdateMs = 0;\n private _volume = 1;\n private _compositionWidth = 1920;\n private _compositionHeight = 1080;\n private _rescaleWarned = false;\n private _directTimelineAdapter: DirectTimelineAdapter | null = null;\n private _directTimelineClock: DirectTimelineClock;\n private _parentTickRaf: number | null = null;\n private _media: ParentMediaManager;\n private _scenes: { id: string; start: number; duration: number }[] = [];\n private _runtimeFps = 30;\n private _runtimeBridgeReady = false;\n private _runtimeAssetsReadyGeneration = -1;\n private _runtimeData = new Map<string, unknown>();\n private _runtimeDataRequestId = 0;\n private _pendingRuntimeData = new Map<string, PendingRuntimeDataDelivery>();\n private _afterUpdate: Array<() => void> | null = null;\n private _videoSource: VideoSource | null = null;\n private _runtimeOwnsPlayRange = false;\n private _enteringRange = false;\n private _rangeClampReported = \"\";\n\n constructor() {\n super();\n this.shadow = this.attachShadow({ mode: \"open\" });\n\n adoptShadowStyles(this.shadow, PLAYER_STYLES);\n ({ container: this.container, iframe: this.iframe } = createCompositionIframe());\n this.shadow.appendChild(this.container);\n\n const loaderElements = createShaderLoader();\n this.shadow.appendChild(loaderElements.root);\n this.shaderLoader = new ShaderLoaderState(loaderElements);\n\n this._media = new ParentMediaManager({\n dispatchEvent: (e) => this._emit(e),\n getMuted: () => this.muted,\n getVolume: () => this._volume,\n getPlaybackRate: () => this.playbackRate,\n getCurrentTime: () => this._currentTime,\n isPaused: () => this._paused,\n });\n\n this._directTimelineClock = new DirectTimelineClock({\n onTimeUpdate: (currentTime, duration) => {\n this._currentTime = currentTime;\n this.controlsApi?.updateTime(currentTime, duration);\n this._emit(new CustomEvent(\"timeupdate\", { detail: { currentTime } }));\n },\n getLoop: () => this.loop,\n restart: () => {\n this.seek(this._playRange()?.start ?? 0);\n this.play();\n },\n onPaused: () => {\n if (this._media.audioOwner === \"parent\") this._media.pauseAll();\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this._emit(new Event(\"ended\"));\n },\n onEnded: () => this.loop,\n });\n\n this.probe = new CompositionProbe(this.iframe, {\n resolveRuntimeUrl: () => runtimeSrcFromElement(this),\n onReady: (result) => this._onProbeReady(result),\n onError: (message) => this._emit(new CustomEvent(\"error\", { detail: { message } })),\n });\n\n this.addEventListener(\"click\", (event) => {\n if (this.disableClickToPlay || isControlsClick(event)) return;\n if (this._paused) this.play();\n else this.pause();\n });\n\n this.resizeObserver = new ResizeObserver(() => this._rescale());\n this._onMessage = this._onMessage.bind(this);\n this._onIframeLoad = this._onIframeLoad.bind(this);\n // Before any host can listen; _onIframeLoad skips the blank-document load before connect.\n this.iframe.addEventListener(\"load\", this._onIframeLoad);\n }\n\n connectedCallback() {\n this._connected = true;\n this._applySandboxOriginPolicy();\n this.resizeObserver.observe(this);\n window.addEventListener(\"message\", this._onMessage);\n if (this.hasAttribute(\"controls\")) this._setupControls();\n if (this.hasAttribute(\"poster\"))\n this.posterEl = setupPoster(this.shadow, this.getAttribute(\"poster\"), this.posterEl);\n if (this.hasAttribute(\"audio-src\")) this._media.setupFromUrl(this.getAttribute(\"audio-src\")!);\n if (this.hasAttribute(\"srcdoc\"))\n this.iframe.srcdoc = prepareSrcdocForElement(this, this.getAttribute(\"srcdoc\")!);\n if (this.hasAttribute(\"src\")) this._loadSrc(this.getAttribute(\"src\")!);\n\n // Host-environment audio lock: when the embedding host (e.g. Claude\n // desktop) drops the `audio-locked` attribute, attributeChangedCallback\n // never fires for it, so apply the lock here based on UA detection.\n if (!this.hasAttribute(\"audio-locked\") && this._isLockedHostEnvironment()) {\n this._applyAudioLock(true);\n }\n }\n\n disconnectedCallback() {\n this._connected = false;\n this._teardownVideo();\n this._sendControl(\"pause\");\n this._stopIframeMedia();\n this.resizeObserver.disconnect();\n window.removeEventListener(\"message\", this._onMessage);\n this.probe.stop();\n this._directTimelineClock.stop();\n this._stopParentTickClock();\n this._directTimelineAdapter = null;\n this.shaderLoader.destroy();\n this._media.destroy();\n this.controlsApi?.destroy();\n this.controlsApi = null;\n this._paused = true;\n this._pendingPlay = false;\n this._abandonComposition(\"Player disconnected before runtime data was applied\");\n }\n\n // fallow-ignore-next-line complexity\n attributeChangedCallback(name: string, oldVal: string | null, val: string | null) {\n switch (name) {\n case \"src\":\n // Custom-element attributes are normally assigned before insertion (React does this for\n // every render). Navigating the inner iframe here would let its one-shot runtime `ready`\n // message fire before connectedCallback installs the parent message listener. Initial\n // attributes are applied below by connectedCallback; only live changes navigate here.\n if (!this.isConnected) break;\n if (val) this._navigateSrc(val);\n break;\n case \"type\": {\n const src = this.getAttribute(\"src\");\n if (!this.isConnected || src === null || !!this._videoSource === this._wantsVideo()) break;\n this._navigateSrc(src);\n break;\n }\n case \"srcdoc\":\n if (!this.isConnected) break;\n this._pendingPlay = false;\n this._abandonComposition(\"Composition navigated before runtime data was applied\");\n if (val !== null) {\n this._pauseForVideoSwitch();\n this._teardownVideo();\n this.iframe.srcdoc = prepareSrcdocForElement(this, val);\n } else {\n this.iframe.removeAttribute(\"srcdoc\");\n const src = this.getAttribute(\"src\");\n if (src !== null && this._wantsVideo()) this._navigateSrc(src);\n }\n break;\n case SANDBOX_ORIGIN_ATTR:\n this._applySandboxOriginPolicy(this.isConnected && oldVal !== val);\n break;\n // Reject NaN/zero/negative dimensions the same way the composition\n // probe does (a typo like width=\"abc\" or width=\"0\" would otherwise\n // reach scaleIframeToFit as scale(NaN) or a division by zero and\n // blank the player); fall back to the defaults instead.\n case \"width\":\n this._setCompositionSize(readPositiveDimension(val) ?? 1920, this._compositionHeight);\n break;\n case \"height\":\n this._setCompositionSize(this._compositionWidth, readPositiveDimension(val) ?? 1080);\n break;\n case \"controls\":\n if (val !== null) this._setupControls();\n else {\n this.controlsApi?.destroy();\n this.controlsApi = null;\n }\n break;\n case \"poster\":\n this.posterEl = setupPoster(this.shadow, val, this.posterEl);\n break;\n case \"playback-rate\": {\n const rate = clampPlaybackRate(parseFloat(val || \"1\"));\n this._media.updatePlaybackRate(rate);\n this._sendControl(\"set-playback-rate\", { playbackRate: rate });\n this._directTimelineAdapter?.timeScale?.(rate);\n this.controlsApi?.updateSpeed(rate);\n this._emit(new Event(\"ratechange\"));\n break;\n }\n case \"muted\":\n this._handleMutedChange(val);\n break;\n case \"audio-locked\":\n this._applyAudioLock(val !== null);\n break;\n case \"volume\": {\n const v = Math.max(0, Math.min(1, parseFloat(val || \"1\")));\n this._volume = v;\n this._syncVideoAudio();\n this._media.updateVolume(v);\n this._sendControl(\"set-volume\", { volume: v });\n this.controlsApi?.updateVolume(v);\n this._emit(new Event(\"volumechange\"));\n break;\n }\n case \"audio-src\":\n if (val) this._media.setupFromUrl(val);\n else this._media.teardownUrlAudio();\n break;\n case ASSETS_LOADING_UI_ATTR:\n if (val === \"none\") this.shaderLoader.hideAssetsLoading();\n break;\n case LOW_POWER_IDLE_ATTR:\n this._sendControl(\"set-idle-heartbeat\", { slow: val !== null });\n break;\n case RANGE_START_ATTR:\n case RANGE_END_ATTR:\n this._applyPlayRange(true);\n break;\n case SHADER_CAPTURE_SCALE_ATTR:\n case SHADER_LOADING_ATTR:\n case RUNTIME_SRC_ATTR:\n if (!this.isConnected) break;\n this._reloadShaderOptions();\n break;\n }\n }\n\n private _applySandboxOriginPolicy(reloadActiveDocument = false): void {\n if (this.hasAttribute(SANDBOX_ORIGIN_ATTR)) {\n this.iframe.sandbox.remove(\"allow-same-origin\");\n } else {\n this.iframe.sandbox.add(\"allow-same-origin\");\n }\n if (reloadActiveDocument) this._reloadForSandboxOriginPolicy();\n }\n\n private _reloadForSandboxOriginPolicy(): void {\n // A video does not play in the iframe, so neither reload applies to it.\n if (this._videoSource) return;\n this._abandonComposition(\"Sandbox policy changed before runtime data was applied\");\n const srcdoc = this.getAttribute(\"srcdoc\");\n if (srcdoc !== null) {\n this.iframe.srcdoc = prepareSrcdocForElement(this, srcdoc);\n return;\n }\n const src = this.getAttribute(\"src\");\n if (src === null) this.iframe.src = \"about:blank\";\n else this._loadSrc(src);\n }\n\n /**\n * The inner `<iframe>` rendering the composition. Use this when integrating\n * with tools that need `contentWindow` — `.contentWindow` on the\n * `<hyperframes-player>` element itself returns `null` (Shadow DOM).\n */\n get iframeElement(): HTMLIFrameElement {\n return this.iframe;\n }\n\n /** Scene list from the last-received runtime timeline message. Empty until\n * the composition runtime fires its first \"timeline\" postMessage. */\n get scenes(): { id: string; start: number; duration: number }[] {\n return this._scenes;\n }\n\n play() {\n this._play(true);\n }\n\n // fallow-ignore-next-line complexity\n private _play(announce: boolean) {\n if (this._ready && !this._assetsReady) {\n this._pendingPlay = true;\n return;\n }\n this._pendingPlay = false;\n this.posterEl?.remove();\n this.posterEl = null;\n const range = this._ready ? this._playRange() : null;\n if (range) {\n if (isOutsidePlayRange(this._currentTime, range, this._duration, this._runtimeFps)) {\n this.seek(range.start);\n this._enteringRange = true;\n }\n } else if (this._duration > 0 && this._currentTime >= this._duration) this.seek(0);\n // Must be set before _startParentTickClock so the RAF loop's `_paused`\n // check doesn't immediately self-terminate on the first callback.\n this._paused = false;\n const directTimelineStarted = this._tryDirectTimelinePlay();\n // Set when the probe hasn't resolved yet: retried from _onProbeReady /\n // _onRuntimeTimelineReady once ready, which is the call that actually\n // starts playback — this premature call must not ALSO dispatch \"play\"\n // for what hasn't started, or a host listener sees it fire twice.\n let queuedForReady = false;\n this._sendDisplayScale();\n if (!directTimelineStarted) {\n this._sendControl(\"play\");\n // Only start the parent tick clock once the composition is ready and\n // confirmed on the runtime bridge path (not the direct-timeline path).\n if (this._ready && !this._directTimelineAdapter) {\n this._startParentTickClock();\n } else if (!this._ready) {\n this._pendingPlay = true;\n queuedForReady = true;\n }\n }\n if (this._media.audioOwner === \"parent\") this._media.playAll();\n this.controlsApi?.updatePlaying(true);\n if (!queuedForReady && announce) this._emit(new Event(\"play\"));\n if (directTimelineStarted && this._directTimelineAdapter) {\n this._directTimelineClock.start(\n this._directTimelineAdapter,\n () => this._currentTime,\n () => this._duration,\n () => this._paused,\n () => this._clockStop(),\n );\n }\n }\n\n pause() {\n // A play queued while assets were still buffering must not survive an\n // explicit user pause — otherwise it fires once assets settle, seconds\n // after the user stopped playback.\n this._pendingPlay = false;\n if (!this._tryDirectTimelinePause()) this._sendControl(\"pause\");\n else this._showPausedVideoTime();\n this._directTimelineClock.stop();\n this._stopParentTickClock();\n if (this._media.audioOwner === \"parent\") this._media.pauseAll();\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this._emit(new Event(\"pause\"));\n }\n\n stopMedia() {\n this._sendControl(\"stop-media\");\n this._stopIframeMedia();\n this._media.stopAdoptedMedia();\n }\n\n seek(timeInSeconds: number) {\n // seek()'s own contract is that it lands paused, so a play still queued\n // from the asset-buffering window is cancelled here too — same reason as\n // pause() above.\n this._pendingPlay = false;\n if (!this._trySyncSeek(timeInSeconds) && !this._tryDirectTimelineSeek(timeInSeconds)) {\n this._sendControl(\"seek\", {\n timeSeconds: timeInSeconds,\n // Legacy runtimes read only `frame`. Protocol-v1 runtimes prefer\n // `timeSeconds`, so carrying both keeps cross-origin embeds seekable\n // while preserving seconds-first precision between current peers.\n frame: Math.round(timeInSeconds * this._runtimeFps),\n });\n }\n this._directTimelineClock.stop();\n this._stopParentTickClock();\n this._currentTime = timeInSeconds;\n if (this._media.audioOwner === \"parent\") {\n if (this._scrubbing) {\n // Audible scrub: play the proxy audio at the playhead so the viewer hears\n // the track as they drag. Each move re-seeks, restarting playback from the\n // new position. onScrubEnd settles back to silence via a normal seek.\n this._media.scrubAll(timeInSeconds);\n } else {\n // Pause BEFORE seek: leaving the proxy playing turns the next\n // `mirrorTime` drift-correction tick into a perpetual seek→play→drift→seek\n // stutter loop, where ~80ms of audio plays past the (now frozen) timeline,\n // then mirrorTime yanks `currentTime` back to match it. Symmetric with\n // `pause()` below.\n this._media.pauseAll();\n this._media.seekAll(timeInSeconds);\n }\n }\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n }\n\n setColorGrading(target: ColorGradingTarget, grading: unknown) {\n this._sendControl(\"set-color-grading\", { target, grading });\n }\n\n clearColorGrading(target: ColorGradingTarget) {\n this._sendControl(\"set-color-grading\", { target, grading: null });\n }\n\n setColorGradingCompare(target: ColorGradingTarget, compare: ColorGradingCompareState) {\n this._sendControl(\"set-color-grading-compare\", { target, compare });\n }\n\n clearColorGradingCompare(target: ColorGradingTarget) {\n this._sendControl(\"set-color-grading-compare\", {\n target,\n compare: { enabled: false },\n });\n }\n\n /** Retain and deliver structured runtime data through the shared runtime protocol. */\n setRuntimeData(channel: string, payload: unknown): void {\n if (!/^[a-z][a-z0-9-]{0,63}$/.test(channel)) {\n throw new Error(`Invalid HyperFrames runtime-data channel: ${channel}`);\n }\n if (typeof structuredClone !== \"function\") {\n throw new Error(\n \"HyperFrames runtime data requires structuredClone support; refusing an unverified payload\",\n );\n }\n const retained = structuredClone(payload);\n this._runtimeData.set(channel, retained);\n this._deliverRuntimeData(channel, retained);\n }\n\n clearRuntimeData(channel: string): void {\n if (!/^[a-z][a-z0-9-]{0,63}$/.test(channel)) {\n throw new Error(`Invalid HyperFrames runtime-data channel: ${channel}`);\n }\n this._runtimeData.delete(channel);\n this._deliverRuntimeDataClear(channel);\n }\n\n get currentTime() {\n return this._currentTime;\n }\n set currentTime(t: number) {\n this.seek(t);\n }\n\n get duration() {\n return this._duration;\n }\n\n /** The composition's width, from the runtime or the `width` attribute. */\n get compositionWidth() {\n return this._compositionWidth;\n }\n /** The composition's height, from the runtime or the `height` attribute. */\n get compositionHeight() {\n return this._compositionHeight;\n }\n get paused() {\n return this._paused;\n }\n get ready() {\n return this._ready;\n }\n\n /** True once every readiness input (media, compute, paint-and-idle) has\n * settled or the wait timed out. Mirrors `assetsready`. Always true for\n * cross-origin compositions, which the player has no DOM access to wait on. */\n get assetsReady() {\n return this._assetsReady;\n }\n\n /** True once assets are ready and the loading panel has finished fading out,\n * so the document is what is on screen. Mirrors `painted`; resets per load. */\n get painted() {\n return this._painted;\n }\n\n get playbackRate() {\n return clampPlaybackRate(parseFloat(this.getAttribute(\"playback-rate\") || \"1\"));\n }\n set playbackRate(r: number) {\n this.setAttribute(\"playback-rate\", String(clampPlaybackRate(r)));\n }\n\n get shaderCaptureScale() {\n return getShaderCaptureScaleFromElement(this);\n }\n set shaderCaptureScale(scale: number) {\n this.setAttribute(SHADER_CAPTURE_SCALE_ATTR, String(scale));\n }\n\n get shaderLoading() {\n return getShaderModeFromElement(this);\n }\n set shaderLoading(mode: ShaderLoadingMode) {\n if (mode === \"composition\") this.removeAttribute(SHADER_LOADING_ATTR);\n else this.setAttribute(SHADER_LOADING_ATTR, mode);\n }\n\n get assetsLoadingUi(): \"player\" | \"none\" {\n return this.getAttribute(ASSETS_LOADING_UI_ATTR) === \"none\" ? \"none\" : \"player\";\n }\n set assetsLoadingUi(mode: \"player\" | \"none\") {\n if (mode === \"none\") this.setAttribute(ASSETS_LOADING_UI_ATTR, \"none\");\n else this.removeAttribute(ASSETS_LOADING_UI_ATTR);\n }\n\n get muted() {\n return this.hasAttribute(\"muted\");\n }\n set muted(m: boolean) {\n if (m) this.setAttribute(\"muted\", \"\");\n else this.removeAttribute(\"muted\");\n }\n\n get audioLocked() {\n return this.hasAttribute(\"audio-locked\");\n }\n set audioLocked(locked: boolean) {\n if (locked) this.setAttribute(\"audio-locked\", \"\");\n else this.removeAttribute(\"audio-locked\");\n }\n\n /**\n * Host renderers that strip unknown custom-element attributes before they\n * reach the DOM (observed on the Claude desktop Electron client) can defeat\n * `audio-locked` even when the host *intends* to lock audio. When we detect\n * such an environment, self-impose the same restriction the attribute would\n * apply. Web (browser) hosts preserve the attribute and don't need this.\n */\n private _isLockedHostEnvironment(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const ua = navigator.userAgent || \"\";\n // Claude desktop ships as an Electron app with a \"Claude/<version>\" UA token.\n return /\\bClaude\\/\\d/.test(ua) && /\\bElectron\\b/.test(ua);\n }\n\n /** True when audio playback must be locked: attribute OR host fallback. */\n private _isAudioLocked(): boolean {\n return this.hasAttribute(\"audio-locked\") || this._isLockedHostEnvironment();\n }\n\n private _isSlideshowPlayer(): boolean {\n return this.closest(\"hyperframes-slideshow\") !== null;\n }\n\n /** Apply a change to the `muted` attribute: re-assert under an audio lock,\n * else mute/unmute the media, sync the controls, and fire `volumechange`. */\n private _handleMutedChange(val: string | null): void {\n // While audio is locked, ignore any attempt to clear `muted` (host control,\n // stray script, raw `removeAttribute`) and re-assert it. The re-set fires\n // this callback again with val=\"\" (not null) so it mutes normally — no loop.\n if (val === null && this._isAudioLocked()) {\n this.setAttribute(\"muted\", \"\");\n return;\n }\n this._media.updateMuted(val !== null);\n this._syncVideoAudio();\n this._setIframeMediaMuted(val !== null);\n this._sendControl(\"set-muted\", { muted: val !== null });\n this.controlsApi?.updateMuted(val !== null);\n this._emit(new Event(\"volumechange\"));\n }\n\n /**\n * Host-mandated silent playback (e.g. embedded in a chat host): force mute\n * and hide the volume controls so the viewer cannot turn sound on. Unlocking\n * only unhides the controls — it does not auto-unmute; callers manage `muted`\n * explicitly after unlocking.\n */\n private _applyAudioLock(locked: boolean): void {\n if (locked) this.muted = true;\n this.controlsApi?.setVolumeControlsHidden(locked);\n }\n\n get volume() {\n return this._volume;\n }\n set volume(v: number) {\n this.setAttribute(\"volume\", String(Math.max(0, Math.min(1, v))));\n }\n\n get disableClickToPlay() {\n return this.hasAttribute(DISABLE_CLICK_TO_PLAY_ATTR);\n }\n set disableClickToPlay(disabled: boolean) {\n this.toggleAttribute(DISABLE_CLICK_TO_PLAY_ATTR, disabled);\n }\n\n get loop() {\n return this.hasAttribute(\"loop\");\n }\n set loop(l: boolean) {\n if (l) this.setAttribute(\"loop\", \"\");\n else this.removeAttribute(\"loop\");\n }\n\n /** Film time where range playback starts; null (no `range-start`) starts at 0. */\n get rangeStart(): number | null {\n return this._readSeconds(RANGE_START_ATTR);\n }\n set rangeStart(seconds: number | null) {\n this._writeSeconds(RANGE_START_ATTR, seconds);\n }\n\n /** Film time where range playback ends or loops; null (no `range-end`) plays to the end.\n * When it ends inside the film, `currentTime` at `ended` is inside its last frame, before this. */\n get rangeEnd(): number | null {\n return this._readSeconds(RANGE_END_ATTR);\n }\n set rangeEnd(seconds: number | null) {\n this._writeSeconds(RANGE_END_ATTR, seconds);\n }\n\n private _readSeconds(name: string): number | null {\n const value = this.getAttribute(name);\n return value === null ? null : Number(value);\n }\n\n private _writeSeconds(name: string, seconds: number | null): void {\n if (seconds == null) this.removeAttribute(name);\n else this.setAttribute(name, String(seconds));\n }\n\n private _hasPlayRange(): boolean {\n return this.hasAttribute(RANGE_START_ATTR) || this.hasAttribute(RANGE_END_ATTR);\n }\n\n private _playRange(): PlayRange | null {\n return resolvePlayRange(this.rangeStart, this.rangeEnd, this._duration).range;\n }\n\n private _sendPlayRange(range: PlayRange | null): void {\n const endSeconds = range?.end ?? null;\n this._sendControl(\"set-play-range\", { startSeconds: range?.start ?? null, endSeconds });\n }\n\n /** A new range or duration: the runtime gets the new end and the host hears of a clamp. With\n * `park`, a paused player moves to the start and a playing one outside the range jumps there;\n * a queued play() starts at the range start itself. */\n private _applyPlayRange(park: boolean): void {\n const { range, clamped } = resolvePlayRange(this.rangeStart, this.rangeEnd, this._duration);\n this._sendPlayRange(range);\n if (!this._ready) return;\n this._reportRangeClamp(range, clamped);\n if (range && park && !this._pendingPlay) this._moveIntoPlayRange(range);\n }\n\n // Once per asked-for range and duration.\n private _reportRangeClamp(range: PlayRange | null, clamped: boolean): void {\n const clampKey = clamped ? `${this.rangeStart},${this.rangeEnd},${this._duration}` : \"\";\n if (clampKey === this._rangeClampReported) return;\n this._rangeClampReported = clampKey;\n if (!clamped) return;\n const detail = { rangeStart: range?.start ?? null, rangeEnd: range?.end ?? null };\n this._emit(\n new CustomEvent(\"rangeclamped\", { detail: { ...detail, duration: this._duration } }),\n );\n }\n\n private _moveIntoPlayRange(range: PlayRange): void {\n if (!isOutsidePlayRange(this._currentTime, range, this._duration, this._runtimeFps)) return;\n if (this._paused) this.seek(range.start);\n else this._jumpWhilePlaying(range.start);\n }\n\n /** A `play-range` runtime jumps by itself and a player clock seeks without pausing; an older\n * runtime is seeked and resumed, with no second `play` event. */\n private _jumpWhilePlaying(time: number): void {\n if (this._directTimelineAdapter) {\n this._directTimelineAdapter.seek(time, false);\n this._currentTime = time;\n } else if (!this._runtimeOwnsPlayRange) {\n this.seek(time);\n this._enteringRange = true;\n this._play(false);\n }\n }\n\n /** Where the player's own clock stops, and the time it then shows: mid last frame, since a video\n * seeked onto a frame boundary can decode the frame before it. */\n private _clockStop(): { end: number; shown: number } {\n const range = this._playRange();\n const end = range?.end ?? this._duration;\n if (!range) return { end, shown: end };\n const hold = playRangeStopTime(range, this._duration, this._runtimeFps);\n return { end, shown: hold < end ? (hold + end) / 2 : hold };\n }\n\n private _sendControl(action: string, extra: Record<string, unknown> = {}): boolean {\n try {\n const frameWindow = this.iframe.contentWindow;\n if (!frameWindow) {\n if (action === \"set-runtime-data\" || action === \"clear-runtime-data\") {\n this._rejectRuntimeDataDelivery(\n extra[\"channel\"],\n extra[\"requestId\"],\n \"Composition iframe is unavailable\",\n );\n }\n return false;\n }\n frameWindow.postMessage(\n {\n ...extra,\n source: \"hf-parent\",\n type: \"control\",\n action,\n ...runtimeProtocolMetadata(this._runtimeFps),\n },\n \"*\",\n );\n return true;\n } catch (error) {\n if (action === \"set-runtime-data\" || action === \"clear-runtime-data\") {\n this._rejectRuntimeDataDelivery(\n extra[\"channel\"],\n extra[\"requestId\"],\n error instanceof Error ? error.message : String(error),\n );\n }\n return false;\n }\n }\n\n private _deliverRuntimeData(channel: string, payload: unknown): void {\n if (!this.isConnected || !this._runtimeBridgeReady) return;\n const requestId = this._beginRuntimeDataDelivery(channel);\n if (this._trySetRuntimeDataDirect(channel, payload, requestId)) return;\n this._sendControl(\"set-runtime-data\", { channel, payload, requestId });\n }\n\n private _deliverRuntimeDataClear(channel: string): void {\n if (!this.isConnected || !this._runtimeBridgeReady) return;\n const requestId = this._beginRuntimeDataDelivery(channel);\n if (this._tryClearRuntimeDataDirect(channel, requestId)) return;\n this._sendControl(\"clear-runtime-data\", { channel, requestId });\n }\n\n private _trySetRuntimeDataDirect(channel: string, payload: unknown, requestId: number): boolean {\n try {\n const bridge = (\n this.iframe.contentWindow as (Window & { __hyperframes?: RuntimeDataBridge }) | null\n )?.__hyperframes;\n if (typeof bridge?.setRuntimeData !== \"function\") return false;\n bridge.setRuntimeData(channel, payload, requestId);\n return true;\n } catch {\n return false;\n }\n }\n\n private _tryClearRuntimeDataDirect(channel: string, requestId: number): boolean {\n try {\n const bridge = (\n this.iframe.contentWindow as (Window & { __hyperframes?: RuntimeDataBridge }) | null\n )?.__hyperframes;\n if (typeof bridge?.clearRuntimeData !== \"function\") return false;\n bridge.clearRuntimeData(channel, requestId);\n return true;\n } catch {\n return false;\n }\n }\n\n private _replayRuntimeData(): void {\n for (const [channel, payload] of this._runtimeData) {\n this._deliverRuntimeData(channel, payload);\n }\n }\n\n private _beginRuntimeDataDelivery(channel: string): number {\n const previous = this._pendingRuntimeData.get(channel);\n if (previous) window.clearTimeout(previous.timeoutId);\n this._runtimeDataRequestId += 1;\n const requestId = this._runtimeDataRequestId;\n const timeoutId = window.setTimeout(() => {\n this._rejectRuntimeDataDelivery(\n channel,\n requestId,\n `Runtime data delivery timed out after ${RUNTIME_DATA_DELIVERY_TIMEOUT_MS}ms`,\n );\n }, RUNTIME_DATA_DELIVERY_TIMEOUT_MS);\n this._pendingRuntimeData.set(channel, { requestId, timeoutId });\n return requestId;\n }\n\n private _resolveRuntimeDataDelivery(channel: unknown, requestId: unknown): void {\n const pending = this._takeRuntimeDataDelivery(channel, requestId);\n if (!pending) return;\n this._emit(\n new CustomEvent(\"runtimedataapplied\", {\n detail: { channel, requestId: pending.requestId },\n }),\n );\n }\n\n private _rejectRuntimeDataDelivery(channel: unknown, requestId: unknown, message: unknown): void {\n const pending = this._takeRuntimeDataDelivery(channel, requestId);\n if (!pending) return;\n this._emit(\n new CustomEvent(\"runtimedataerror\", {\n detail: {\n channel,\n requestId: pending.requestId,\n message: typeof message === \"string\" ? message : String(message),\n },\n }),\n );\n }\n\n private _takeRuntimeDataDelivery(\n channel: unknown,\n requestId: unknown,\n ): PendingRuntimeDataDelivery | null {\n if (\n typeof channel !== \"string\" ||\n typeof requestId !== \"number\" ||\n !Number.isSafeInteger(requestId)\n )\n return null;\n const pending = this._pendingRuntimeData.get(channel);\n if (!pending || pending.requestId !== requestId) return null;\n window.clearTimeout(pending.timeoutId);\n this._pendingRuntimeData.delete(channel);\n return pending;\n }\n\n private _rejectAllRuntimeDataDeliveries(message: string): void {\n for (const [channel, pending] of [...this._pendingRuntimeData]) {\n this._rejectRuntimeDataDelivery(channel, pending.requestId, message);\n }\n }\n\n /**\n * Returns the iframe's contentDocument if same-origin and reachable,\n * otherwise null. Accessing contentDocument can throw on cross-origin\n * iframes — this swallows that as a clean null sentinel.\n */\n private _getSameOriginIframeDocument(): Document | null {\n try {\n return this.iframe.contentDocument;\n } catch {\n return null;\n }\n }\n\n private _setIframeMediaMuted(muted: boolean): void {\n const iframeDoc = this._getSameOriginIframeDocument();\n if (!iframeDoc) return;\n for (const el of iframeDoc.querySelectorAll(\"video, audio\")) {\n if (isRealmHtmlMediaElement(el)) el.muted = muted || el.defaultMuted;\n }\n }\n\n private _stopIframeMedia(): void {\n const iframeDoc = this._getSameOriginIframeDocument();\n if (!iframeDoc) return;\n for (const el of iframeDoc.querySelectorAll(\"video, audio\")) {\n if (isRealmHtmlMediaElement(el)) el.pause();\n }\n }\n\n /**\n * Replay current bridge state to the iframe runtime. Triggered when the\n * runtime announces `{type: \"ready\"}` — repairs the race where the parent\n * posts control messages before the iframe's bridge listener is installed\n * (warm-cache reloads, the Claude desktop Electron client, anywhere the\n * iframe finishes loading after we've already called `set-muted` etc).\n * Re-sending current state is idempotent — even at default values it just\n * confirms what the runtime would have done anyway.\n */\n private _replayBridgeState(): void {\n this._sendControl(\"set-muted\", { muted: this.muted });\n this._sendControl(\"set-volume\", { volume: this._volume });\n this._sendControl(\"set-playback-rate\", { playbackRate: this.playbackRate });\n this._sendControl(\"set-native-media-sync-disabled\", {\n disabled: this._isSlideshowPlayer(),\n });\n this._sendControl(\"set-web-audio-media-disabled\", {\n disabled: this._isSlideshowPlayer(),\n });\n this._sendControl(\"set-idle-heartbeat\", { slow: this.hasAttribute(LOW_POWER_IDLE_ATTR) });\n this._sendDisplayScale();\n }\n\n private _sendDisplayScale(): void {\n const scale = frameDisplayScale(this.iframe);\n if (scale) this._sendControl(\"set-display-scale\", { scale });\n }\n\n private _reloadShaderOptions(): void {\n if (this._videoSource) return;\n // This navigates the frame, so readiness has to fall with it. Leaving\n // `_runtimeBridgeReady` true lets a delivery post into a document that is being\n // replaced, where it can only end in a delivery timeout rather than the immediate,\n // explanatory rejection the caller gets from every other navigating path.\n this._abandonComposition(\"Shader options changed before runtime data was applied\");\n if (this.hasAttribute(\"srcdoc\")) {\n this.iframe.srcdoc = prepareSrcdocForElement(this, this.getAttribute(\"srcdoc\") || \"\");\n return;\n }\n if (this.hasAttribute(\"src\")) this._loadSrc(this.getAttribute(\"src\") || \"\");\n }\n\n // A different source inherits no queued play.\n private _navigateSrc(src: string): void {\n this._pendingPlay = false;\n this._pauseForVideoSwitch();\n this._abandonComposition(\"Composition navigated before runtime data was applied\");\n this._loadSrc(src);\n }\n\n /** A `type=\"video/...\"` src plays in a `<video>`, unless `srcdoc` (which wins, as in an iframe)\n * shows a composition. */\n private _wantsVideo(): boolean {\n return isVideoType(this.getAttribute(\"type\")) && !this.hasAttribute(\"srcdoc\");\n }\n\n // Like a <video> given a new src, a switch into or out of a video starts paused.\n private _pauseForVideoSwitch(): void {\n if (!this._videoSource && !this._wantsVideo()) return;\n this._paused = true;\n this.controlsApi?.updatePlaying(false);\n }\n\n private _loadSrc(src: string): void {\n if (this._wantsVideo()) {\n this._loadVideo(src);\n return;\n }\n this._teardownVideo();\n this.iframe.src = prepareSrcForElement(this, src);\n }\n\n private _loadVideo(src: string): void {\n if (!this._videoSource) {\n this._videoSource = createVideoSource({\n onMetadata: (video) => this._applyThenEmit(() => this._onVideoReady(video)),\n onDurationChange: (video) => this._applyTimelineDuration(video.duration),\n onResize: (video) => this._applyVideoSize(video),\n // Follow a play or pause the page did not ask for, but not a queued one it since reversed.\n onPlay: (video) => {\n if (!video.paused && this._paused) this.play();\n },\n onPause: (video) => {\n if (video.paused && !video.ended && !this._paused) this.pause();\n },\n // A background tab runs no animation frames; a range's end is checked on timeupdate instead.\n onTimeUpdate: () => {\n if (document.hidden && this._playRange()) this._directTimelineClock.poll();\n },\n onError: (message, code) => {\n if (!this._paused) this.pause();\n this._emit(new CustomEvent(\"error\", { detail: { message, code } }));\n },\n onPlayRejected: (error) => this._onVideoPlayRejected(error),\n });\n this.container.appendChild(this._videoSource.video);\n this.iframe.hidden = true;\n // Unloads a composition this player was showing before it switched to video.\n this.iframe.src = \"about:blank\";\n }\n this.probe.stop();\n const { video, adapter } = this._videoSource;\n this._directTimelineAdapter = adapter;\n this._currentTime = 0;\n this._duration = 0;\n this.controlsApi?.updateTime(0, 0);\n this._syncVideoAudio();\n adapter.timeScale?.(this.playbackRate);\n video.src = src;\n }\n\n private _teardownVideo(): void {\n if (!this._videoSource) return;\n this._videoSource.destroy();\n this._videoSource = null;\n this.iframe.hidden = false;\n }\n\n private _syncVideoAudio(): void {\n if (!this._videoSource) return;\n this._videoSource.video.muted = this.muted;\n this._videoSource.video.volume = this._volume;\n }\n\n private _onVideoReady(video: HTMLVideoElement): void {\n this._applyTimelineDuration(video.duration);\n this._applyVideoSize(video);\n this._ready = true;\n this._dispatchReady();\n // A video has no composition assets to wait on; this settles at once.\n this._waitForAssetsReady(null);\n this._afterEvents(() => {\n if (this.hasAttribute(\"autoplay\") && this._paused) this.play();\n });\n }\n\n private _applyTimelineDuration(duration: number): void {\n if (!Number.isFinite(duration) || duration <= 0) return;\n this._setDuration(duration);\n this.controlsApi?.updateTime(this._currentTime, duration);\n }\n\n private _applyVideoSize(video: HTMLVideoElement): void {\n if (video.videoWidth > 0 && video.videoHeight > 0) {\n this._setCompositionSize(video.videoWidth, video.videoHeight);\n }\n }\n\n // Only a blocked play is a playbackerror: a failed load already fired `error`.\n private _onVideoPlayRejected(error: unknown): void {\n if (!(error instanceof DOMException && error.name === \"NotAllowedError\")) return;\n if (!this._paused) this.pause();\n this._emit(new CustomEvent(\"playbackerror\", { detail: { source: \"video\", error } }));\n }\n\n private _trySyncSeek(timeInSeconds: number): boolean {\n if (this._videoSource) return false;\n try {\n const win = this.iframe.contentWindow as\n | (Window & { __player?: { seek?: (t: number) => void } })\n | null;\n const player = win?.__player;\n if (typeof player?.seek !== \"function\") return false;\n player.seek.call(player, timeInSeconds);\n return true;\n } catch {\n return false;\n }\n }\n\n private _withDirectTimeline(fn: (tl: DirectTimelineAdapter) => void): boolean {\n const resolved = this._videoSource ? null : this.probe.resolveDirectTimelineAdapter();\n const tl = resolved || this._directTimelineAdapter;\n if (!tl) return false;\n try {\n fn(tl);\n if (resolved && resolved !== this._directTimelineAdapter) {\n this._applyTimelineDuration(resolved.duration());\n }\n this._directTimelineAdapter = tl;\n return true;\n } catch {\n return false;\n }\n }\n\n // GSAP seek() preserves play state; player seek() contract lands paused.\n private _tryDirectTimelineSeek(t: number): boolean {\n return this._withDirectTimeline((tl) => {\n // suppressEvents=false: fire the timeline's onUpdate so compositions that\n // drive scene visibility imperatively (via the root timeline's onUpdate,\n // e.g. slideshow decks) repaint on a paused seek — not only while playing.\n tl.seek(t, false);\n tl.pause();\n });\n }\n private _tryDirectTimelinePlay(): boolean {\n return this._withDirectTimeline((tl) => void tl.play());\n }\n private _tryDirectTimelinePause(): boolean {\n return this._withDirectTimeline((tl) => void tl.pause());\n }\n\n // The clock samples every ~100 ms; like a <video>, a pause reports the exact frame.\n private _showPausedVideoTime(): void {\n if (!this._videoSource) return;\n this._currentTime = this._videoSource.video.currentTime;\n this.controlsApi?.updateTime(this._currentTime, this._duration);\n this._emit(new CustomEvent(\"timeupdate\", { detail: { currentTime: this._currentTime } }));\n }\n\n /**\n * Widget-frame RAF loop that sends \"tick\" postMessages to the composition\n * iframe on every frame. Used for the runtime bridge path so that animation\n * advances even when the composition iframe's own rAF is throttled by\n * Chromium (e.g. deeply nested cross-origin iframes in Electron / Claude desktop).\n * The runtime skips a tick seek only when that timeline time is already\n * rendered. Host ticks still advance a throttled iframe.\n */\n private _startParentTickClock(): void {\n this._stopParentTickClock();\n const tick = () => {\n if (this._paused) {\n this._parentTickRaf = null;\n return;\n }\n this._sendControl(\"tick\");\n this._parentTickRaf = requestAnimationFrame(tick);\n };\n this._parentTickRaf = requestAnimationFrame(tick);\n }\n\n private _stopParentTickClock(): void {\n if (this._parentTickRaf === null) return;\n cancelAnimationFrame(this._parentTickRaf);\n this._parentTickRaf = null;\n }\n\n private _onMessage(e: MessageEvent) {\n // The iframe window outlives its documents: a late composition message must not reach a video.\n if (this._videoSource || this.probe.failed) return;\n this._applyThenEmit(() => this._handleRuntimeMessage(e));\n }\n\n private _handleRuntimeMessage(e: MessageEvent) {\n handleRuntimeMessage(e, this.iframe.contentWindow, {\n getPlaybackState: () => ({\n currentTime: this._currentTime,\n duration: this._duration,\n paused: this._paused,\n lastUpdateMs: this._lastUpdateMs,\n enteringRange: this._enteringRange,\n }),\n setPlaybackState: ({ currentTime, duration, paused, lastUpdateMs, enteringRange }) => {\n this._enteringRange = enteringRange === true;\n this._currentTime = currentTime;\n this._setDuration(duration);\n this._paused = paused;\n this._lastUpdateMs = lastUpdateMs;\n },\n getShaderLoadingMode: () => getShaderModeFromElement(this),\n shaderLoader: this.shaderLoader,\n setCompositionSize: (w, h) => this._setCompositionSize(w, h),\n sendControl: (action, extra) => this._sendControl(action, extra),\n getIframeDoc: () => this.iframe.contentDocument,\n onRuntimeReady: () => {\n this._runtimeBridgeReady = true;\n this._replayBridgeState();\n if (this._hasPlayRange()) this._sendPlayRange(this._playRange());\n this._replayRuntimeData();\n },\n onRuntimeAssetsReady: () => {\n if (this._runtimeAssetsReadyGeneration === this._assetsGeneration) {\n this._settleAssetsReady(this._assetsGeneration);\n }\n },\n onRuntimeDataApplied: (channel, requestId) =>\n this._resolveRuntimeDataDelivery(channel, requestId),\n onRuntimeDataError: (channel, requestId, message) =>\n this._rejectRuntimeDataDelivery(channel, requestId, message),\n onRuntimeTimelineReady: (duration, assetsReady) =>\n this._onRuntimeTimelineReady(duration, assetsReady),\n setRuntimeFps: (fps) => {\n this._runtimeFps = fps;\n },\n setRuntimeOwnsPlayRange: (owns) => {\n this._runtimeOwnsPlayRange = owns;\n },\n shouldPromoteMediaAutoplayFallback: () => !this._isSlideshowPlayer(),\n setScenes: (scenes) => {\n this._scenes = scenes;\n this._emit(new CustomEvent(\"scenes\", { detail: { scenes } }));\n },\n updateControlsTime: (t, d) => this.controlsApi?.updateTime(t, d),\n updateControlsPlaying: (p) => this.controlsApi?.updatePlaying(p),\n dispatchEvent: (ev) => this._emit(ev),\n seek: (t) => this.seek(t),\n play: () => this.play(),\n getLoop: () => this.loop,\n getPlayRange: () => this._playRange(),\n media: this._media,\n });\n }\n\n private _onRuntimeTimelineReady(duration: number, assetsReady: boolean | undefined) {\n if (this._ready) return;\n this.probe.stop();\n this._setDuration(duration);\n this._directTimelineAdapter = null;\n this._ready = true;\n this.controlsApi?.updateTime(this._currentTime, duration);\n this._dispatchReady();\n\n const doc = this._getSameOriginIframeDocument();\n if (doc) this._media.setupFromIframe(doc);\n\n this._replayBridgeState();\n this._setIframeMediaMuted(this.muted);\n this._waitForAssetsReady(doc, assetsReady);\n this._playWhenWanted();\n }\n\n private _onProbeReady(result: ProbeResult) {\n this._applyThenEmit(() => this._applyProbeResult(result));\n }\n\n private _applyProbeResult({ duration, adapter, compositionSize }: ProbeResult) {\n this._setDuration(duration);\n this._directTimelineAdapter = adapter.kind === \"direct-timeline\" ? adapter.timeline : null;\n if (compositionSize) this._setCompositionSize(compositionSize.width, compositionSize.height);\n this._ready = true;\n this.controlsApi?.updateTime(0, duration);\n this._dispatchReady();\n const doc = this._getSameOriginIframeDocument();\n if (doc) this._media.setupFromIframe(doc);\n this._setIframeMediaMuted(this.muted);\n this._waitForAssetsReady(doc);\n this._playWhenWanted();\n }\n\n /** Gates play() on composition readiness (media, compute, paint-and-idle),\n * bounded by ASSETS_READY_TIMEOUT_MS. The overlay is debounced by\n * ASSETS_LOADING_SHOW_DELAY_MS rather than shown the instant a wait\n * starts, since one is now pending on nearly every Play. */\n private _waitForAssetsReady(doc: Document | null, runtimeAssetsReady?: boolean): void {\n this._clearAssetsLoadingShowTimer();\n this._assetsReady = false;\n this._painted = false;\n // Invalidates any earlier wait still in flight (a composition swap, or\n // disconnect, mid-wait) — its eventual settle checks this and no-ops\n // rather than resolving a since-superseded generation.\n const generation = ++this._assetsGeneration;\n if (!doc) {\n // An opaque-origin iframe cannot be scanned from here; a runtime that\n // reports `assetsReady: false` runs the same scan itself and posts the result.\n if (runtimeAssetsReady === false) {\n this._runtimeAssetsReadyGeneration = generation;\n this._startAssetsLoadingOverlayTimer(generation);\n setTimeout(() => this._settleAssetsReady(generation), ASSETS_READY_TIMEOUT_MS);\n } else {\n this._settleAssetsReady(generation);\n }\n return;\n }\n settleFirstFrameCompositionReadiness(\n doc,\n ({ timedOut }) => {\n if (generation !== this._assetsGeneration) return;\n if (timedOut) this._warnStuckAssets(doc);\n this._settleAssetsReady(generation);\n },\n { timeoutMs: ASSETS_READY_TIMEOUT_MS },\n );\n if (!this._assetsReady) this._startAssetsLoadingOverlayTimer(generation);\n }\n\n private _startAssetsLoadingOverlayTimer(generation: number): void {\n this._assetsLoadingShowTimer = setTimeout(() => {\n this._assetsLoadingShowTimer = null;\n if (generation !== this._assetsGeneration || this._assetsReady) return;\n this.setAttribute(ASSETS_LOADING_ATTR, \"\");\n if (this.assetsLoadingUi !== \"none\") this.shaderLoader.showAssetsLoading();\n }, ASSETS_LOADING_SHOW_DELAY_MS);\n }\n\n private _hasPendingFirstFrameAssets(doc: Document): boolean {\n const { pendingMedia, pendingImages, fontsLoading } = scanPendingCompositionAssets(doc, {\n scope: FIRST_FRAME_READINESS_SCOPE,\n });\n return pendingMedia.length > 0 || pendingImages.length > 0 || fontsLoading;\n }\n\n /** Timeout diagnostic. Re-scans since some assets may have resolved by\n * now. Compute can cause the timeout, so it's reported too. A hidden\n * document can starve paint-and-idle of frames for the full 8s — that's\n * reported directly rather than inferred, since it can't be bounded. */\n private _warnStuckAssets(doc: Document): void {\n const { pendingMedia, pendingImages, fontsLoading } = scanPendingCompositionAssets(doc, {\n scope: FIRST_FRAME_READINESS_SCOPE,\n });\n const win = doc.defaultView as (Window & { __renderReady?: boolean }) | null;\n console.warn(\n `[hyperframes-player] assets-loading timed out after ${ASSETS_READY_TIMEOUT_MS}ms — playing anyway`,\n {\n stuckMedia: pendingMedia.map(\n (el) => el.currentSrc || el.getAttribute(\"src\") || `<${el.tagName.toLowerCase()}>`,\n ),\n stuckImages: pendingImages.map(\n (img) => img.currentSrc || img.getAttribute(\"src\") || \"<img>\",\n ),\n fontsLoading,\n computeReady: win?.__renderReady === true,\n documentHidden: doc.hidden === true,\n },\n );\n }\n\n private _settleAssetsReady(generation: number): void {\n if (generation !== this._assetsGeneration || this._assetsReady) return;\n this._clearAssetsLoadingShowTimer();\n this._assetsReady = true;\n this.removeAttribute(ASSETS_LOADING_ATTR);\n this.shaderLoader.hideAssetsLoading();\n this._emit(new Event(\"assetsready\"));\n this.shaderLoader.whenHidden(() => {\n if (generation !== this._assetsGeneration) return;\n this._painted = true;\n this._emit(new Event(\"painted\"));\n });\n this._afterEvents(() => {\n if (this._pendingPlay) this.play();\n });\n }\n\n /** Every host-driven navigation or teardown: the old document's handshake, asset wait and\n * data deliveries end here. A queued play is the caller's, so only its owners clear it. */\n private _abandonComposition(reason: string): void {\n this.probe.stop();\n this._ready = false;\n this._readyDocument = null;\n this._invalidateAssetsWait();\n this._releaseDocument();\n this._runtimeBridgeReady = false;\n this._rejectAllRuntimeDataDeliveries(reason);\n }\n\n private _releaseDocument(): void {\n this._runtimeOwnsPlayRange = false;\n this._enteringRange = false;\n this._directTimelineAdapter = null;\n this._directTimelineClock.stop();\n this._stopParentTickClock();\n this.shaderLoader.reset();\n this._media.resetForIframeLoad();\n }\n\n /** Abandons any in-flight asset wait — every `_ready = false` site calls\n * this first, so a stale wait's settle can't apply to what comes next. */\n private _invalidateAssetsWait(): void {\n this._clearAssetsLoadingShowTimer();\n this._assetsReady = false;\n this._painted = false;\n this._assetsGeneration++;\n this.removeAttribute(ASSETS_LOADING_ATTR);\n this.shaderLoader.hide();\n }\n\n private _clearAssetsLoadingShowTimer(): void {\n if (this._assetsLoadingShowTimer === null) return;\n clearTimeout(this._assetsLoadingShowTimer);\n this._assetsLoadingShowTimer = null;\n }\n\n private _dispatchReady(): void {\n this._readyDocument = this._getSameOriginIframeDocument();\n const detail = {\n duration: this._duration,\n compositionWidth: this._compositionWidth,\n compositionHeight: this._compositionHeight,\n };\n this._emit(new CustomEvent(\"ready\", { detail }));\n // Once ready: covers a size message that never came, and lets a zero-size player warn.\n this._rescale();\n this._rangeClampReported = \"\";\n if (this._hasPlayRange()) this._applyPlayRange(true);\n }\n\n /** `ready` carries the first duration; later changes fire `durationchange`. */\n private _setDuration(duration: number): void {\n if (duration === this._duration) return;\n this._duration = duration;\n if (!this._ready) return;\n this._emit(new CustomEvent(\"durationchange\", { detail: { duration } }));\n if (this._hasPlayRange()) this._applyPlayRange(false);\n }\n\n private _setCompositionSize(width: number, height: number): void {\n const changed = width !== this._compositionWidth || height !== this._compositionHeight;\n this._compositionWidth = width;\n this._compositionHeight = height;\n this._rescale();\n if (!changed) return;\n const detail = { compositionWidth: width, compositionHeight: height };\n this._emit(new CustomEvent(\"resize\", { detail }));\n }\n\n /** Runs one update (a runtime message, a probe result), then the events and actions it raised,\n * so every listener sees the whole update applied. */\n private _applyThenEmit(apply: () => void): void {\n // A nested update joins the outer one's queue.\n if (this._afterUpdate) {\n apply();\n return;\n }\n const queue: Array<() => void> = [];\n this._afterUpdate = queue;\n const errors: unknown[] = [];\n const run = (step: () => void) => {\n try {\n step();\n } catch (error) {\n errors.push(error);\n }\n };\n run(apply);\n // Still open while flushing: whatever a listener raises goes behind the rest.\n for (const action of queue) run(action);\n this._afterUpdate = null;\n if (errors.length > 0) throw errors[0];\n }\n\n /** Runs `action` once the current update's events have fired (at once outside an update). */\n private _afterEvents(action: () => void): void {\n if (this._afterUpdate) this._afterUpdate.push(action);\n else action();\n }\n\n /** Autoplay, or a play() made before ready, decided after `ready`'s listeners had their turn. */\n private _playWhenWanted(): void {\n this._afterEvents(() => {\n if (this.hasAttribute(\"autoplay\") || this._pendingPlay) this.play();\n });\n }\n\n /** Every event the player raises goes through here, so an update's events keep their order. */\n private _emit(event: Event): void {\n this._afterEvents(() => this.dispatchEvent(event));\n }\n\n private _rescale() {\n const applied = scaleIframeToFit(\n this,\n this.iframe,\n this._compositionWidth,\n this._compositionHeight,\n );\n // A no-op before \"ready\" is expected (element not painted yet). A no-op\n // once ready means the composition is stuck unscaled/untransformed —\n // pinned to the iframe's default top-left position — with no evidence of\n // why in the field. Surface it once (not on every ResizeObserver tick —\n // a legitimately hidden/zero-sized player, e.g. a collapsed tab or\n // off-screen carousel card, would otherwise spam the console forever).\n if (applied) this._sendDisplayScale();\n if (!applied && this._ready && !this._rescaleWarned) {\n this._rescaleWarned = true;\n console.warn(\"[hyperframes-player] rescale no-op after ready — zero-size player element\", {\n src: this.getAttribute(\"src\"),\n offsetWidth: this.offsetWidth,\n offsetHeight: this.offsetHeight,\n compositionWidth: this._compositionWidth,\n compositionHeight: this._compositionHeight,\n });\n }\n }\n\n private _onIframeLoad() {\n // In video mode the iframe only ever loads about:blank; its load must not reset the video.\n if (!this._connected || this._videoSource) return;\n // The runtime posts its timeline once its scripts ran, before or after `load`, and every\n // host-initiated navigation clears `_ready` first. So a ready player already holds this\n // document's handshake (an opaque origin reads as null); a paused runtime never posts it again.\n const doc = this._getSameOriginIframeDocument();\n if (this._ready && doc === this._readyDocument) {\n // Its asset wait scanned at DOMContentLoaded; a script may have added first-frame media since.\n if (doc && this._hasPendingFirstFrameAssets(doc)) this._waitForAssetsReady(doc);\n return;\n }\n\n this._ready = false;\n // The runtime installs its bridge once its scripts ran and posts `ready`, which can come before\n // the iframe's load event. Do not erase that authoritative handshake here: doing so strands\n // retained data set after load until a second `ready` that never comes. Source setters and\n // sandbox-policy reloads already clear bridge readiness before starting a navigation.\n this._invalidateAssetsWait();\n this._releaseDocument();\n this.probe.start();\n }\n\n private _setupControls() {\n if (this.controlsApi) return;\n this.controlsApi = setupControls(\n this.shadow,\n this.muted,\n this._volume,\n this.getAttribute(\"speed-presets\"),\n {\n onPlay: () => this.play(),\n onPause: () => this.pause(),\n onSeek: (f) => this.seek(f * this._duration),\n onScrubStart: () => {\n this._scrubbing = true;\n },\n onScrubEnd: () => {\n this._scrubbing = false;\n // Settle: a normal (silent) seek pauses the proxy audio at the final\n // scrub position, matching the paused playhead.\n this.seek(this._currentTime);\n },\n onSpeedChange: (s) => void (this.playbackRate = s),\n onMuteToggle: () => void (this.muted = !this.muted),\n onVolumeChange: (v) => void (this.volume = v),\n },\n this._isAudioLocked(),\n );\n }\n\n // Test-instrumentation pass-throughs (match original field names).\n get _audioOwner() {\n return this._media.audioOwner;\n }\n get _parentMedia() {\n return this._media.entries;\n }\n _mirrorParentMediaTime(t: number, opts?: { force?: boolean }) {\n this._media.mirrorTime(t, opts);\n }\n _promoteToParentProxy() {\n let d: Document | null = null;\n try {\n d = this.iframe.contentDocument;\n } catch {\n /* x-origin */\n }\n this._media.promoteToParentProxy(d, (t, o) => this._mirrorParentMediaTime(t, o));\n this._sendControl(\"set-media-output-muted\", { muted: true });\n }\n _observeDynamicMedia(doc: Document) {\n this._media.setupFromIframe(doc);\n }\n}\n\nif (!customElements.get(\"hyperframes-player\")) {\n customElements.define(\"hyperframes-player\", HyperframesPlayer);\n}\n\nexport { HyperframesPlayer };\nexport { formatTime, formatSpeed, SPEED_PRESETS } from \"./controls.js\";\nexport type { ControlsCallbacks, ControlsOptions } from \"./controls.js\";\nexport type { ShaderLoadingMode } from \"./shader-options.js\";\n","/** How long a composition is when nothing runs it: its authored `data-duration`, else the latest\n * end among its clips. A clip whose length is not known yet is counted, never guessed. */\n\nexport type CompositionDurationSource = \"authored\" | \"derived\" | \"unresolved\";\n\nexport interface CompositionDurationResult {\n seconds: number | null;\n source: CompositionDurationSource;\n /** Clips whose end is not known yet; a `derived` length is a lower bound while this is above 0. */\n pendingClips: number;\n /** Set only when source is \"unresolved\". */\n reason?: string;\n}\n\nexport interface ResolveCompositionDurationInput {\n authoredDurationSeconds: number | null;\n /** Each clip's end on the composition timeline, or null while its length is pending. */\n clipEndsSeconds: readonly (number | null)[];\n}\n\nexport const TIMING_TOLERANCE_SECONDS = 0.05;\n\nconst isPositiveFinite = (value: number | null): value is number =>\n value !== null && Number.isFinite(value) && value > 0;\n\nexport function resolveCompositionDuration(\n input: ResolveCompositionDurationInput,\n): CompositionDurationResult {\n const pendingClips = input.clipEndsSeconds.filter((end) => end === null).length;\n if (isPositiveFinite(input.authoredDurationSeconds)) {\n return { seconds: input.authoredDurationSeconds, source: \"authored\", pendingClips };\n }\n const known = input.clipEndsSeconds.filter(isPositiveFinite);\n if (known.length > 0) {\n return { seconds: Math.max(...known), source: \"derived\", pendingClips };\n }\n return {\n seconds: null,\n source: \"unresolved\",\n pendingClips,\n reason:\n pendingClips > 0\n ? \"every clip's length is still pending\"\n : \"no authored duration and no clip with a length\",\n };\n}\n","/**\n * Realm-independent \"what kind of node is this\" tests for composition DOM.\n *\n * THE INVARIANT: the runtime never assumes the composition DOM was built by the\n * same JavaScript realm the runtime is running in. It asks what a node IS (node\n * type, namespace, tag name), never which window's constructor made it.\n *\n * Why that matters. `node instanceof HTMLElement` compares against the\n * `HTMLElement` of the realm this module was loaded into, so it answers \"which\n * window built this node\", not \"what is this node\". One document can hold\n * elements from several realms at once, and a node keeps the prototype chain of\n * whichever realm created it however it got there.\n *\n * MEASURED, in the Studio preview, on some loads: `<html>`, `<head>` and\n * `<script>` carry the preview frame's prototypes while `<body>` and every\n * element under it carry the editor window's. Tagging each realm's\n * `Element.prototype` before any page script ran and reading the tag back off\n * the live nodes is how that was established. NOT established: how the nodes\n * come to be there. No `adoptNode`, `importNode` or `document.write` call was\n * caught doing it, and the frame is populated by `src`/`srcdoc`, so treat the\n * mechanism as an open question and the mixed realms as a fact.\n *\n * Either way `instanceof` does not throw and does not warn. It just returns\n * false, forever, for every element in the composition, and every guard written\n * as `if (!(node instanceof HTMLElement)) continue;` silently skips the whole\n * document. A whole pass goes dark with no error: the timed-element visibility\n * pass stops writing, so every scene paints on top of every other.\n *\n * `doc.defaultView.HTMLElement` is not a sufficient repair, which is why this\n * module replaced two hand-rolled attempts at it. It fixes the case where the\n * nodes belong to the document's own realm and still fails when they belong to\n * a third one. A structural test has no such case to get wrong.\n *\n * Scope of ownership: these predicates own the RUNTIME's answer to \"what kind of\n * node is this\", for HTML and SVG, which is everything a composition contains.\n * `packages/studio` still hand-rolls its own checks in several places and is not\n * covered by this module or by the lint guard that keeps `src/runtime` clean\n * (`scripts/lint-runtime-preview-guards.ts`).\n */\n\nconst XHTML_NS = \"http://www.w3.org/1999/xhtml\";\n\ninterface NodeLike {\n readonly nodeType?: unknown;\n readonly namespaceURI?: unknown;\n readonly localName?: unknown;\n}\n\n/** Any element node, from any realm (HTML, SVG, MathML). Mirrors `instanceof Element`. */\nexport function isElementNode(value: unknown): value is Element {\n return value != null && (value as NodeLike).nodeType === 1;\n}\n\n/**\n * An HTML element, from any realm. Mirrors `instanceof HTMLElement`, including\n * its exclusion of SVG/MathML: \"is an HTMLElement\" and \"is an element in the\n * XHTML namespace\" are the same set.\n */\nexport function isHtmlElement(value: unknown): value is HTMLElement {\n return isElementNode(value) && (value as NodeLike).namespaceURI === XHTML_NS;\n}\n\n/** Single tag, by value: a rest parameter here allocates on every call, and these\n * predicates run per element per seek and per animation frame. */\nfunction isHtmlTag(value: unknown, tag: string): boolean {\n return isHtmlElement(value) && (value as NodeLike).localName === tag;\n}\n\n/** `<video>`, from any realm. Mirrors `instanceof HTMLVideoElement`. */\nexport function isVideoElement(value: unknown): value is HTMLVideoElement {\n return isHtmlTag(value, \"video\");\n}\n\n/** `<audio>`, from any realm. Mirrors `instanceof HTMLAudioElement`. */\nexport function isAudioElement(value: unknown): value is HTMLAudioElement {\n return isHtmlTag(value, \"audio\");\n}\n\n/** `<video>` or `<audio>`, from any realm. Mirrors `instanceof HTMLMediaElement`. */\nexport function isMediaElement(value: unknown): value is HTMLMediaElement {\n return isVideoElement(value) || isAudioElement(value);\n}\n\n/** `<img>`, from any realm. Mirrors `instanceof HTMLImageElement`. */\nexport function isImageElement(value: unknown): value is HTMLImageElement {\n return isHtmlTag(value, \"img\");\n}\n\n/** `<canvas>`, from any realm. Mirrors `instanceof HTMLCanvasElement`. */\nexport function isCanvasElement(value: unknown): value is HTMLCanvasElement {\n return isHtmlTag(value, \"canvas\");\n}\n\n/** `<style>`, from any realm. Mirrors `instanceof HTMLStyleElement`. */\nexport function isStyleElement(value: unknown): value is HTMLStyleElement {\n return isHtmlTag(value, \"style\");\n}\n\n/** `<link>`, from any realm. Mirrors `instanceof HTMLLinkElement`. */\nexport function isLinkElement(value: unknown): value is HTMLLinkElement {\n return isHtmlTag(value, \"link\");\n}\n\n/**\n * An element that carries a CSS `style` map: HTML **or** SVG. Position edits and\n * transform writes apply to both (authored SVG `<text>`/shapes honour the CSS\n * `translate` longhand exactly as HTML does).\n */\nexport function isStylableElement(value: unknown): value is HTMLElement | SVGElement {\n return (\n isElementNode(value) &&\n ((value as NodeLike).namespaceURI === XHTML_NS ||\n (value as NodeLike).namespaceURI === \"http://www.w3.org/2000/svg\")\n );\n}\n","import { isHtmlElement } from \"./domRealm\";\n\n/** A `data-width`/`data-height` length in CSS px as the stage lays it out, fractions kept. */\nexport function parseLayoutDimension(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number.parseFloat(value);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n}\n\n/** The size the runtime reports to its host: whole px, read with `parseInt` as the renderer does. */\nexport function parseCompositionDimension(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number.parseInt(value, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n}\n\nexport function findRootCompositionElement(doc: ParentNode = document): HTMLElement | null {\n const explicitRoot = doc.querySelector('[data-composition-id][data-root=\"true\"]');\n if (isHtmlElement(explicitRoot)) return explicitRoot;\n // The first composition in document order never sits inside another one.\n return doc.querySelector<HTMLElement>(\"[data-composition-id]\");\n}\n","/**\n * Browser-safe authored composition contract.\n *\n * Source HTML is authored with data-start + data-duration + data-track-index.\n * data-end is compiler-derived and data-layer is legacy input only. Readers\n * accept legacy documents; writers always emit the canonical representation.\n */\n\nexport const COMPOSITION_CONTRACT_VERSION = 1 as const;\n\nexport const COMPOSITION_ATTRIBUTES = Object.freeze({\n start: \"data-start\",\n duration: \"data-duration\",\n trackIndex: \"data-track-index\",\n derivedEnd: \"data-end\",\n legacyTrack: \"data-layer\",\n} as const);\n\nexport const CANONICAL_AUTHORED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.start,\n COMPOSITION_ATTRIBUTES.duration,\n COMPOSITION_ATTRIBUTES.trackIndex,\n] as const);\n\nexport const DERIVED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n] as const);\n\nexport const LEGACY_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n] as const);\n\nexport type ReferenceExpression =\n | { kind: \"absolute\"; value: number }\n | { kind: \"reference\"; refId: string; offset: number };\n\nexport type ClipTimingDiagnosticCode =\n | \"invalid-start\"\n | \"unresolved-start-reference\"\n | \"invalid-duration\"\n | \"invalid-end\"\n | \"end-before-start\"\n | \"deprecated-end\"\n | \"conflicting-end\"\n | \"invalid-track-index\"\n | \"deprecated-layer\"\n | \"conflicting-layer\";\n\nexport interface ClipTimingDiagnostic {\n code: ClipTimingDiagnosticCode;\n attribute: string;\n value: string | null;\n}\n\nexport interface ClipAttributeReader {\n getAttribute(name: string): string | null;\n}\n\nexport interface ClipAttributeWriter extends ClipAttributeReader {\n setAttribute(name: string, value: string): void;\n removeAttribute(name: string): void;\n}\n\nexport interface ReadClipTimingOptions {\n /** Resolve a reference to the referenced clip's absolute end time. */\n resolveReferenceEnd?: (refId: string) => number | null | undefined;\n /** Start used when data-start is absent. Defaults to zero. */\n defaultStart?: number | null;\n}\n\nexport interface ClipTiming {\n startExpression: ReferenceExpression | null;\n start: number | null;\n duration: number | null;\n end: number | null;\n trackIndex: number;\n durationSource: \"duration\" | \"legacy-end\" | \"missing\" | \"invalid\";\n trackSource: \"track-index\" | \"legacy-layer\" | \"default\" | \"invalid\";\n diagnostics: ClipTimingDiagnostic[];\n}\n\nexport interface ClipTimingUpdate {\n start?: number | string | ReferenceExpression;\n duration?: number | null;\n trackIndex?: number | null;\n}\n\nexport class ClipTimingWriteError extends Error {\n readonly code: \"invalid-start\" | \"invalid-duration\" | \"invalid-track-index\";\n\n constructor(code: ClipTimingWriteError[\"code\"], message: string) {\n super(message);\n this.name = \"ClipTimingWriteError\";\n this.code = code;\n }\n}\n\n/** Parse a value to a finite number, or null if it is absent/invalid. */\nexport function parseNumeric(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nconst REFERENCE_ID_PATTERN = /^[A-Za-z0-9_.:-]+$/;\n\nfunction isAsciiDigitAt(value: string, index: number): boolean {\n const code = value.charCodeAt(index);\n return code >= 48 && code <= 57;\n}\n\nfunction skipDigitsLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && isAsciiDigitAt(value, cursor)) cursor--;\n return cursor;\n}\n\nfunction skipWhitespaceLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && (value[cursor] ?? \"\").trim() === \"\") cursor--;\n return cursor;\n}\n\nfunction findMagnitudeStart(value: string): number | null {\n const last = value.length - 1;\n if (!isAsciiDigitAt(value, last)) return null;\n let cursor = skipDigitsLeft(value, last);\n if (value[cursor] === \".\") cursor = skipDigitsLeft(value, cursor - 1);\n return cursor + 1;\n}\n\nfunction parseReferenceOffset(\n value: string,\n): { refId: string; operator: \"+\" | \"-\"; magnitude: number } | null {\n const magnitudeStart = findMagnitudeStart(value);\n if (magnitudeStart == null) return null;\n const operatorIndex = skipWhitespaceLeft(value, magnitudeStart - 1);\n const operator = value[operatorIndex];\n if (operator !== \"+\" && operator !== \"-\") return null;\n\n const refId = value.slice(0, operatorIndex).trim();\n if (!REFERENCE_ID_PATTERN.test(refId)) return null;\n const magnitude = Number(value.slice(magnitudeStart));\n if (!Number.isFinite(magnitude)) return null;\n return { refId, operator, magnitude };\n}\n\n/**\n * Parse the data-start grammar: absolute seconds, `clip-id`, or\n * `clip-id +/- offset`, where references resolve to the referenced clip's end.\n */\nexport function parseStartExpression(raw: string | null | undefined): ReferenceExpression | null {\n const normalized = (raw ?? \"\").trim();\n if (!normalized) return null;\n const absolute = parseNumeric(normalized);\n if (absolute != null) return { kind: \"absolute\", value: absolute };\n if (REFERENCE_ID_PATTERN.test(normalized)) {\n return { kind: \"reference\", refId: normalized, offset: 0 };\n }\n const reference = parseReferenceOffset(normalized);\n if (!reference) return null;\n return {\n kind: \"reference\",\n refId: reference.refId,\n offset: reference.operator === \"-\" ? -reference.magnitude : reference.magnitude,\n };\n}\n\nfunction pushDiagnostic(\n diagnostics: ClipTimingDiagnostic[],\n code: ClipTimingDiagnosticCode,\n attribute: string,\n value: string | null,\n): void {\n diagnostics.push({ code, attribute, value });\n}\n\nfunction resolveStart(\n expression: ReferenceExpression | null,\n rawStart: string | null,\n options: ReadClipTimingOptions,\n diagnostics: ClipTimingDiagnostic[],\n): number | null {\n if (rawStart == null || rawStart.trim() === \"\") {\n return options.defaultStart === undefined ? 0 : options.defaultStart;\n }\n if (!expression) {\n pushDiagnostic(diagnostics, \"invalid-start\", COMPOSITION_ATTRIBUTES.start, rawStart);\n return null;\n }\n if (expression.kind === \"absolute\") return Math.max(0, expression.value);\n const referencedEnd = options.resolveReferenceEnd?.(expression.refId);\n if (referencedEnd == null || !Number.isFinite(referencedEnd)) {\n pushDiagnostic(\n diagnostics,\n \"unresolved-start-reference\",\n COMPOSITION_ATTRIBUTES.start,\n rawStart,\n );\n return null;\n }\n return Math.max(0, referencedEnd + expression.offset);\n}\n\ntype DurationRead = Pick<ClipTiming, \"duration\" | \"end\" | \"durationSource\">;\ntype TrackRead = Pick<ClipTiming, \"trackIndex\" | \"trackSource\">;\n\n// Float pairs like `data-start=\"0.1\" + data-duration=\"0.2\"` add to\n// `0.30000000000000004`, so the compiler-emitted `data-end` string can be a few\n// ulps off the sum a reader computes from the canonical inputs. Any drift below\n// this ceiling is treated as equal for the derived-end reconciliation check —\n// well below one 60fps frame (~16.67 ms) and orders of magnitude above the\n// worst realistic IEEE-754 residual (~2e-16 s).\nconst DERIVED_END_EQUALITY_EPSILON_SECONDS = 1e-9;\n\nfunction derivedEndsAreConsistent(parsedEnd: number, canonicalEnd: number): boolean {\n return Math.abs(parsedEnd - canonicalEnd) <= DERIVED_END_EQUALITY_EPSILON_SECONDS;\n}\n\n// Reconcile a `data-end` attribute paired with canonical `data-duration`.\n//\n// The compiler (`compileTimingAttrs` in @hyperframes/core) writes\n// `data-end = data-start + data-duration` into the bundled HTML so the runtime\n// can key off a single attribute. That derived attribute is legitimate — it is\n// not a legacy authoring shape. Treating it as `deprecated-end` here made the\n// linter fire `deprecated_data_end` on the compiler's own output whenever\n// StaticGuard re-validated a bundled composition, producing a false-positive\n// cluster reported in the field (n=25+ across cli-feedback crons 61-68).\n//\n// We keep `deprecated-end` for the truly legacy shape (see `readLegacyEnd` —\n// `data-end` present without `data-duration`) and for the *conflicting* case\n// where an author left a stale `data-end` behind a canonical duration edit; a\n// stale end must not be silently overridden without a diagnostic.\nfunction diagnoseDerivedEnd(\n rawEnd: string,\n canonicalEnd: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): void {\n const parsedEnd = parseNumeric(rawEnd);\n if (parsedEnd == null) {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (canonicalEnd == null) {\n // Canonical duration exists but resolved end is unknown (e.g. unresolved\n // reference start). Preserve prior behavior — flag as deprecated so authors\n // remove the stale companion attribute.\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (derivedEndsAreConsistent(parsedEnd, canonicalEnd)) {\n // Compiler-derived (or manually consistent) `data-end` alongside\n // `data-duration` — silent. No diagnostic.\n return;\n }\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"conflicting-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n}\n\nfunction readCanonicalDuration(\n rawDuration: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const duration = parseNumeric(rawDuration);\n if (duration == null || duration < 0) {\n pushDiagnostic(diagnostics, \"invalid-duration\", COMPOSITION_ATTRIBUTES.duration, rawDuration);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return {\n duration,\n end: start == null ? null : start + duration,\n durationSource: \"duration\",\n };\n}\n\nfunction readLegacyEnd(\n rawEnd: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n const end = parseNumeric(rawEnd);\n if (end == null) {\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n if (start == null) return { duration: null, end, durationSource: \"legacy-end\" };\n if (end < start) {\n pushDiagnostic(diagnostics, \"end-before-start\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return { duration: end - start, end, durationSource: \"legacy-end\" };\n}\n\nfunction readDuration(\n attributes: ClipAttributeReader,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const rawDuration = attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration);\n const rawEnd = attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n if (rawDuration == null) {\n return rawEnd == null\n ? { duration: null, end: null, durationSource: \"missing\" }\n : readLegacyEnd(rawEnd, start, diagnostics);\n }\n const canonical = readCanonicalDuration(rawDuration, start, diagnostics);\n if (rawEnd != null) diagnoseDerivedEnd(rawEnd, canonical.end, diagnostics);\n return canonical;\n}\n\nfunction readTrackValue(\n rawValue: string,\n attribute: string,\n source: \"track-index\" | \"legacy-layer\",\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const trackIndex = parseNumeric(rawValue);\n if (trackIndex == null || !Number.isInteger(trackIndex)) {\n pushDiagnostic(diagnostics, \"invalid-track-index\", attribute, rawValue);\n return { trackIndex: 0, trackSource: \"invalid\" };\n }\n return { trackIndex, trackSource: source };\n}\n\nfunction readTrack(\n attributes: ClipAttributeReader,\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const rawTrack = attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n const rawLayer = attributes.getAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n if (rawTrack == null && rawLayer == null) return { trackIndex: 0, trackSource: \"default\" };\n if (rawTrack == null && rawLayer != null) {\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n return readTrackValue(\n rawLayer,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n \"legacy-layer\",\n diagnostics,\n );\n }\n const canonical = readTrackValue(\n rawTrack ?? \"\",\n COMPOSITION_ATTRIBUTES.trackIndex,\n \"track-index\",\n diagnostics,\n );\n if (rawLayer == null) return canonical;\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n const parsedLayer = parseNumeric(rawLayer);\n if (parsedLayer != null && parsedLayer !== canonical.trackIndex) {\n pushDiagnostic(diagnostics, \"conflicting-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n }\n return canonical;\n}\n\n/** Read canonical timing, accepting legacy attributes without preferring them. */\nexport function readClipTiming(\n attributes: ClipAttributeReader,\n options: ReadClipTimingOptions = {},\n): ClipTiming {\n const diagnostics: ClipTimingDiagnostic[] = [];\n const rawStart = attributes.getAttribute(COMPOSITION_ATTRIBUTES.start);\n const startExpression = parseStartExpression(rawStart);\n const start = resolveStart(startExpression, rawStart, options, diagnostics);\n const duration = readDuration(attributes, start, diagnostics);\n const track = readTrack(attributes, diagnostics);\n return { startExpression, start, ...duration, ...track, diagnostics };\n}\n\nfunction serializeStartNumber(start: number): string {\n if (!Number.isFinite(start)) {\n throw new ClipTimingWriteError(\"invalid-start\", \"start must be finite\");\n }\n return String(start);\n}\n\nfunction serializeStartReference(start: ReferenceExpression): string {\n if (start.kind === \"absolute\") return serializeStartNumber(start.value);\n if (!start.refId || !Number.isFinite(start.offset)) {\n throw new ClipTimingWriteError(\n \"invalid-start\",\n \"reference start must have an id and finite offset\",\n );\n }\n if (start.offset === 0) return start.refId;\n return `${start.refId} ${start.offset < 0 ? \"-\" : \"+\"} ${Math.abs(start.offset)}`;\n}\n\nfunction serializeStart(start: ClipTimingUpdate[\"start\"]): string {\n if (typeof start === \"number\") return serializeStartNumber(start);\n if (typeof start === \"object\" && start != null) return serializeStartReference(start);\n if (typeof start === \"string\" && parseStartExpression(start)) return start.trim();\n throw new ClipTimingWriteError(\"invalid-start\", `invalid start expression: ${start ?? \"\"}`);\n}\n\nfunction writeDuration(\n attributes: ClipAttributeWriter,\n duration: number | null | undefined,\n current: ClipTiming,\n): void {\n if (duration === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.duration);\n return;\n }\n if (duration === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n current.duration != null\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(current.duration));\n }\n return;\n }\n if (!Number.isFinite(duration) || duration < 0) {\n throw new ClipTimingWriteError(\n \"invalid-duration\",\n \"duration must be a finite, non-negative number\",\n );\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(duration));\n}\n\nfunction writeTrack(\n attributes: ClipAttributeWriter,\n trackIndex: number | null | undefined,\n current: ClipTiming,\n): void {\n if (trackIndex === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n return;\n }\n if (trackIndex === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex) == null &&\n current.trackSource === \"legacy-layer\"\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(current.trackIndex));\n }\n return;\n }\n if (!Number.isFinite(trackIndex) || !Number.isInteger(trackIndex)) {\n throw new ClipTimingWriteError(\"invalid-track-index\", \"trackIndex must be a finite integer\");\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(trackIndex));\n}\n\n/** Write only canonical authored timing and remove derived/legacy source attributes. */\nexport function writeClipTiming(\n attributes: ClipAttributeWriter,\n update: ClipTimingUpdate,\n): ClipTiming {\n const current = readClipTiming(attributes);\n if (update.start !== undefined) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.start, serializeStart(update.start));\n }\n writeDuration(attributes, update.duration, current);\n writeTrack(attributes, update.trackIndex, current);\n // A legacy end paired with an unresolved reference start cannot be converted\n // to data-duration without a reference resolver. On a duration-omitting edit\n // (for example, moving only the track), preserve that sole duration source\n // instead of canonicalizing it into data loss.\n const preserveUnresolvedLegacyEnd =\n update.duration === undefined &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd) != null &&\n current.duration == null;\n if (!preserveUnresolvedLegacyEnd) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n }\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n return readClipTiming(attributes);\n}\n","/** The one rule for a media element's timeline length; hosts only supply the source length. */\n\nimport { parseNumeric } from \"./compositionContract.js\";\n\nexport type MediaTag = \"video\" | \"audio\" | \"img\";\n\n/** Reads a named attribute's raw string value. Works for a DOM Element's `getAttribute`\n * (browser or linkedom) or a plain attrs-record lookup -- callers pick whichever they have. */\nexport type AttrReader = (name: string) => string | null | undefined;\n\nconst nonNegative = (raw: string | null | undefined): number | null => {\n const value = parseNumeric(raw);\n return value !== null && value >= 0 ? value : null;\n};\n\n/** Playback offset into the source: `data-playback-start`, falling back to the older\n * `data-media-start`; an invalid or negative value falls through to the next. One reader for\n * every host -- the runtime and `htmlParser` used to read this two different ways. */\nexport function readMediaOffsetSeconds(getAttr: AttrReader): number {\n return (\n nonNegative(getAttr(\"data-playback-start\")) ?? nonNegative(getAttr(\"data-media-start\")) ?? 0\n );\n}\n\n/** The one bound on a clip's playback rate; core re-exports it for the rate lane. */\nexport const MIN_PLAYBACK_RATE = 0.1;\nexport const MAX_PLAYBACK_RATE = 10;\n\n/** A playback rate clamped to the bounds above; anything non-positive or non-finite is 1. */\nexport function clampPlaybackRate(raw: number): number {\n return Number.isFinite(raw) && raw > 0\n ? Math.max(MIN_PLAYBACK_RATE, Math.min(MAX_PLAYBACK_RATE, raw))\n : 1;\n}\n\n/** `data-playback-rate`, parsed like the browser parses a native rate (`\"2x\"` is 2), else\n * `fallback` (a media element's own `defaultPlaybackRate` in the browser), clamped. */\nexport function readPlaybackRate(getAttr: AttrReader, fallback = 1): number {\n const authored = Number.parseFloat(getAttr(\"data-playback-rate\") ?? \"\");\n return clampPlaybackRate(Number.isFinite(authored) && authored > 0 ? authored : fallback);\n}\n\n/** `data-duration` when it is a positive number, else null. */\nexport function readDataDurationSeconds(getAttr: AttrReader): number | null {\n const duration = parseNumeric(getAttr(\"data-duration\"));\n return duration !== null && duration > 0 ? duration : null;\n}\n\n/** An authored trim: `data-duration` if positive, else `data-end - start` if `data-end` is\n * authored, else null (nothing authored). */\nexport function readAuthoredDurationSeconds(\n getAttr: AttrReader,\n startSeconds: number,\n): number | null {\n const duration = readDataDurationSeconds(getAttr);\n if (duration !== null) return duration;\n const end = parseNumeric(getAttr(\"data-end\"));\n return end !== null ? end - startSeconds : null;\n}\n\n/** The dropped-image default: what Studio gives a freshly dropped image before any trim.\n * One owner; everything needing an untrimmed image length imports this. */\nexport const DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS = 3;\n\n/** The arithmetic alone: no attribute reading, no DOM, no probing. */\nexport function resolveNaturalDurationSeconds(\n sourceDurationSeconds: number,\n mediaStartSeconds: number,\n playbackRate: number,\n): number | null {\n if (!Number.isFinite(sourceDurationSeconds)) return null;\n const remaining = Math.max(0, sourceDurationSeconds - mediaStartSeconds);\n return remaining / clampPlaybackRate(playbackRate);\n}\n\nexport type MediaDurationSource = \"authored\" | \"media\" | \"default\" | \"pending\";\n\nexport interface MediaDurationResult {\n seconds: number | null;\n source: MediaDurationSource;\n /** Set only when source is \"pending\": why nothing could be resolved yet. */\n reason?: string;\n}\n\nexport interface ResolveMediaDurationInput {\n tag: MediaTag;\n /** From `readAuthoredDurationSeconds`, or null when nothing is authored. */\n authoredDurationSeconds: number | null;\n /** The source's own length once a probe has answered, else null. Ignored for `img`. */\n sourceDurationSeconds: number | null;\n mediaStartSeconds: number;\n playbackRate: number;\n}\n\nexport function resolveMediaDuration(input: ResolveMediaDurationInput): MediaDurationResult {\n const { tag, authoredDurationSeconds, sourceDurationSeconds, mediaStartSeconds, playbackRate } =\n input;\n if (authoredDurationSeconds !== null && authoredDurationSeconds > 0) {\n return { seconds: authoredDurationSeconds, source: \"authored\" };\n }\n if (tag === \"img\") {\n return { seconds: DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS, source: \"default\" };\n }\n if (sourceDurationSeconds === null) {\n return { seconds: null, source: \"pending\", reason: \"source duration not yet probed\" };\n }\n const seconds = resolveNaturalDurationSeconds(\n sourceDurationSeconds,\n mediaStartSeconds,\n playbackRate,\n );\n if (seconds === null) {\n return { seconds: null, source: \"pending\", reason: \"source reported a non-finite duration\" };\n }\n return { seconds, source: \"media\" };\n}\n\n/** Readers not yet converted to `resolveMediaDuration`; each reader PR removes its entry, so this only shrinks. */\nexport const PENDING_MEDIA_DURATION_READERS = [\n \"core/src/runtime/init.ts (visibility end, clock, seek, WebAudio scheduling)\",\n \"core/src/compiler/timingCompiler.ts compileTag\",\n \"core/src/compiler/htmlCompiler.ts compileHtml\",\n \"producer/src/services/htmlCompiler.ts resolveMediaDuration\",\n \"engine/src/services/videoFrameExtractor.ts (extraction window)\",\n \"engine/src/services/audioMixer.ts\",\n \"parsers/src/htmlParser.ts (defaults to 5s, reads only data-media-start)\",\n \"studio (timelineDOM.ts, timelineElementHelpers.ts, useTimelineSyncCallbacks.ts)\",\n] as const;\n","/**\n * Audio FX chain: the one description of every effect that can be applied to an\n * audio track.\n *\n * Preview and render both run the same Web Audio graph — the studio in a live\n * AudioContext, the engine in an OfflineAudioContext inside the headless\n * browser it already drives. There is only one implementation of each effect,\n * so preview predicting the render is a property of the architecture rather\n * than something to measure and defend.\n *\n * This file holds what both ends need to agree on: the parameter set for each\n * effect with its usable range, and the id of the graph builder that realises\n * it. Parameters are declared in the units a person thinks in (dB, ms, Hz);\n * the graph builders convert where the Web Audio node wants something else.\n */\n\nexport const HF_AUDIO_FX_ATTR = \"data-fx-chain\";\n\n/**\n * The same attribute as a `dataset` / `dataAttributes` key — the `data-` prefix\n * is not part of that spelling.\n *\n * Derived rather than restated: the studio writes through\n * `HF_AUDIO_FX_ATTR` and reads through the key, so a hardcoded `\"fx-chain\"`\n * on the read side is a rename waiting to half-land.\n */\nexport const HF_AUDIO_FX_DATA_KEY = HF_AUDIO_FX_ATTR.slice(\"data-\".length);\n\n/** Chain files are versioned; a reader must refuse a version it doesn't know. */\nexport const HF_AUDIO_FX_CHAIN_VERSION = 1;\n\nexport type HfAudioFxGroup = \"filter\" | \"dynamics\" | \"nonlinear\" | \"time\";\n\nexport interface HfAudioFxNumberParam {\n kind: \"number\";\n key: string;\n label: string;\n /** Shown after the value in the panel; \"\" for a bare ratio. */\n unit: string;\n min: number;\n max: number;\n step: number;\n default: number;\n /** Frequency-style controls need a log knob to be usable. */\n scale?: \"linear\" | \"log\";\n /**\n * The knob is backed by an AudioParam, so an automation lane can drive it.\n *\n * Not every knob can be: a WaveShaper curve, a convolution impulse and a\n * worklet's `processorOptions` are all set wholesale rather than scheduled.\n * A graph builder must expose an AudioParam for every parameter flagged here\n * — `audioFxGraph.test.ts` builds each effect and checks it.\n */\n automatable?: boolean;\n /** One line explaining what turning this does, shown on the control. */\n hint?: string;\n}\n\nexport interface HfAudioFxEnumParam {\n kind: \"enum\";\n key: string;\n label: string;\n options: readonly { value: string; label: string }[];\n default: string;\n hint?: string;\n}\n\nexport type HfAudioFxParam = HfAudioFxNumberParam | HfAudioFxEnumParam;\n\nexport type HfAudioFxParamValues = Record<string, number | string>;\n\nexport interface HfAudioFxDef {\n id: string;\n label: string;\n group: HfAudioFxGroup;\n /** One sentence on what the effect is for, shown when adding it. */\n description: string;\n params: readonly HfAudioFxParam[];\n /**\n * Identifier for the Web Audio graph builder that realises this effect. Kept\n * as a string rather than a function so this module stays free of browser\n * globals and can be imported by the engine and the linter.\n */\n web: string;\n}\n\nconst freq = (\n key = \"frequency\",\n label = \"Frequency\",\n def = 1000,\n min = 20,\n max = 20000,\n): HfAudioFxNumberParam => ({\n kind: \"number\",\n key,\n label,\n unit: \"Hz\",\n min,\n max,\n step: 1,\n default: def,\n scale: \"log\",\n automatable: true,\n});\n\nconst qParam = (def = 0.707, hint = \"Bandwidth — higher is narrower.\"): HfAudioFxNumberParam => ({\n kind: \"number\",\n key: \"q\",\n label: \"Q\",\n unit: \"\",\n min: 0.1,\n max: 20,\n step: 0.01,\n default: def,\n scale: \"log\",\n automatable: true,\n hint,\n});\n\nconst gainDb = (min = -40, max = 40, def = 0): HfAudioFxNumberParam => ({\n kind: \"number\",\n key: \"gain\",\n label: \"Gain\",\n unit: \"dB\",\n min,\n max,\n step: 0.1,\n default: def,\n automatable: true,\n});\n\nconst poles: HfAudioFxEnumParam = {\n kind: \"enum\",\n key: \"poles\",\n label: \"Slope\",\n options: [\n { value: \"1\", label: \"6 dB/oct\" },\n { value: \"2\", label: \"12 dB/oct\" },\n ],\n default: \"2\",\n hint: \"Two poles is the usual biquad; one pole is gentler.\",\n};\n\n/**\n * Every effect, in panel order. Ranges are the usable span for each control;\n * a value that survives `normalizeAudioFxParams` is always safe to realise.\n */\nexport const HF_AUDIO_FX: readonly HfAudioFxDef[] = [\n {\n id: \"gain\",\n label: \"Gain\",\n group: \"dynamics\",\n description: \"Raise or lower the whole signal. Automate it to duck under something else.\",\n // Cuts go deep, boosts stay modest: this is a level stage for making room,\n // and a chain that can add 40 dB clips long before it is useful.\n params: [gainDb(-60, 12, 0)],\n web: \"gain-node\",\n },\n {\n id: \"peaking\",\n label: \"Peaking EQ\",\n group: \"filter\",\n description: \"Boost or cut a band, leaving everything either side alone.\",\n params: [freq(\"frequency\", \"Frequency\", 1000), gainDb(-40, 40, 0), qParam(1)],\n web: \"biquad-peaking\",\n },\n {\n id: \"lowshelf\",\n label: \"Low Shelf\",\n group: \"filter\",\n description: \"Lift or drop everything below the corner frequency.\",\n // No Q: the Web Audio spec leaves it unused for shelving filters, so the\n // control moved nothing — and being automatable, a lane drawn on it would\n // have been silently inert.\n params: [freq(\"frequency\", \"Frequency\", 200, 20, 2000), gainDb(-40, 40, 0)],\n web: \"biquad-lowshelf\",\n },\n {\n id: \"highshelf\",\n label: \"High Shelf\",\n group: \"filter\",\n description: \"Lift or drop everything above the corner frequency.\",\n params: [freq(\"frequency\", \"Frequency\", 4000, 500, 20000), gainDb(-40, 40, 0)],\n web: \"biquad-highshelf\",\n },\n {\n id: \"highpass\",\n label: \"High-pass\",\n group: \"filter\",\n description: \"Remove low frequencies — the usual fix for rumble on a voice.\",\n params: [freq(\"frequency\", \"Cutoff\", 300, 20, 20000), qParam(0.707), poles],\n web: \"biquad-highpass\",\n },\n {\n id: \"lowpass\",\n label: \"Low-pass\",\n group: \"filter\",\n description: \"Remove high frequencies — darkens or muffles a track.\",\n params: [freq(\"frequency\", \"Cutoff\", 8000, 100, 20000), qParam(0.707), poles],\n web: \"biquad-lowpass\",\n },\n\n {\n id: \"compressor\",\n label: \"Compressor\",\n group: \"dynamics\",\n description: \"Pull loud parts down so the quiet ones can come up.\",\n params: [\n {\n kind: \"number\",\n key: \"threshold\",\n label: \"Threshold\",\n unit: \"dB\",\n min: -60,\n max: 0,\n step: 0.5,\n default: -24,\n hint: \"Level above which the compressor starts working.\",\n },\n {\n kind: \"number\",\n key: \"ratio\",\n label: \"Ratio\",\n unit: \":1\",\n min: 1,\n max: 20,\n step: 0.1,\n default: 4,\n },\n {\n kind: \"number\",\n key: \"attack\",\n label: \"Attack\",\n unit: \"ms\",\n min: 0.01,\n max: 2000,\n step: 0.1,\n default: 20,\n scale: \"log\",\n },\n {\n kind: \"number\",\n key: \"release\",\n label: \"Release\",\n unit: \"ms\",\n min: 0.01,\n max: 9000,\n step: 1,\n default: 250,\n scale: \"log\",\n },\n {\n kind: \"number\",\n key: \"knee\",\n label: \"Knee\",\n unit: \"\",\n min: 1,\n max: 8,\n step: 0.01,\n default: 2.83,\n hint: \"1 is a hard corner; higher eases into it.\",\n },\n {\n kind: \"number\",\n key: \"makeup\",\n label: \"Makeup\",\n unit: \"dB\",\n min: 0,\n max: 36,\n step: 0.1,\n default: 0,\n },\n {\n kind: \"number\",\n key: \"mix\",\n label: \"Mix\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 1,\n hint: \"Below 1 blends the dry signal back in.\",\n },\n ],\n web: \"worklet-compressor\",\n },\n {\n id: \"limiter\",\n label: \"Limiter\",\n group: \"dynamics\",\n description: \"Hard ceiling — nothing gets past the limit.\",\n params: [\n {\n kind: \"number\",\n key: \"limit\",\n label: \"Ceiling\",\n unit: \"dB\",\n min: -24,\n max: 0,\n step: 0.1,\n default: -1,\n },\n {\n kind: \"number\",\n key: \"attack\",\n label: \"Attack\",\n unit: \"ms\",\n min: 0.1,\n max: 80,\n step: 0.1,\n default: 5,\n },\n {\n kind: \"number\",\n key: \"release\",\n label: \"Release\",\n unit: \"ms\",\n min: 1,\n max: 8000,\n step: 1,\n default: 50,\n scale: \"log\",\n },\n {\n kind: \"number\",\n key: \"level_out\",\n label: \"Output\",\n unit: \"dB\",\n min: -24,\n max: 24,\n step: 0.1,\n default: 0,\n },\n ],\n web: \"worklet-limiter\",\n },\n {\n id: \"gate\",\n label: \"Noise Gate\",\n group: \"dynamics\",\n description: \"Silence the track when it drops below the threshold.\",\n params: [\n {\n kind: \"number\",\n key: \"threshold\",\n label: \"Threshold\",\n unit: \"dB\",\n min: -80,\n max: 0,\n step: 0.5,\n default: -35,\n },\n {\n kind: \"number\",\n key: \"range\",\n label: \"Range\",\n unit: \"dB\",\n min: -80,\n max: 0,\n step: 0.5,\n default: -24,\n hint: \"How far down the gate pulls when closed.\",\n },\n {\n kind: \"number\",\n key: \"ratio\",\n label: \"Ratio\",\n unit: \":1\",\n min: 1,\n max: 20,\n step: 0.1,\n default: 10,\n },\n {\n kind: \"number\",\n key: \"attack\",\n label: \"Attack\",\n unit: \"ms\",\n min: 0.01,\n max: 9000,\n step: 0.1,\n default: 1,\n scale: \"log\",\n },\n {\n kind: \"number\",\n key: \"release\",\n label: \"Release\",\n unit: \"ms\",\n min: 0.01,\n max: 9000,\n step: 1,\n default: 100,\n scale: \"log\",\n },\n {\n kind: \"number\",\n key: \"knee\",\n label: \"Knee\",\n unit: \"\",\n min: 1,\n max: 8,\n step: 0.01,\n default: 2.83,\n },\n ],\n web: \"worklet-gate\",\n },\n\n {\n id: \"saturate\",\n label: \"Saturation\",\n group: \"nonlinear\",\n description: \"Soft-clip the waveform for warmth or outright distortion.\",\n params: [\n {\n kind: \"enum\",\n key: \"type\",\n label: \"Curve\",\n options: [\n { value: \"tanh\", label: \"Tanh\" },\n { value: \"atan\", label: \"Arctan\" },\n { value: \"cubic\", label: \"Cubic\" },\n { value: \"exp\", label: \"Exponential\" },\n { value: \"alg\", label: \"Algebraic\" },\n { value: \"quintic\", label: \"Quintic\" },\n { value: \"sin\", label: \"Sine\" },\n { value: \"erf\", label: \"Error function\" },\n { value: \"hard\", label: \"Hard clip\" },\n ],\n default: \"tanh\",\n },\n {\n kind: \"number\",\n key: \"threshold\",\n label: \"Threshold\",\n unit: \"dB\",\n min: -40,\n max: 0,\n step: 0.1,\n default: -6,\n },\n {\n kind: \"number\",\n key: \"output\",\n automatable: true,\n label: \"Output\",\n unit: \"dB\",\n min: -24,\n max: 24,\n step: 0.1,\n default: 0,\n },\n {\n kind: \"number\",\n key: \"oversample\",\n label: \"Oversample\",\n unit: \"x\",\n min: 1,\n max: 8,\n step: 1,\n default: 4,\n hint: \"Higher costs more but keeps aliasing down.\",\n },\n ],\n web: \"waveshaper\",\n },\n {\n id: \"bitcrush\",\n label: \"Bitcrush\",\n group: \"nonlinear\",\n description: \"Drop bit depth and sample rate for a lo-fi, digital sound.\",\n params: [\n {\n kind: \"number\",\n key: \"bits\",\n label: \"Bit depth\",\n unit: \"bit\",\n min: 1,\n max: 32,\n step: 0.1,\n default: 8,\n },\n {\n kind: \"number\",\n key: \"samples\",\n label: \"Sample hold\",\n unit: \"x\",\n min: 1,\n max: 250,\n step: 1,\n default: 1,\n hint: \"Repeats each sample N times — a crude downsample.\",\n },\n {\n kind: \"number\",\n key: \"mix\",\n label: \"Mix\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 1,\n },\n ],\n web: \"worklet-bitcrush\",\n },\n {\n id: \"pitchshift\",\n label: \"Pitch shift\",\n group: \"time\",\n // The granular algorithm reads from a 100 ms grain, so its output runs a\n // constant ~50 ms behind its input and nothing in the graph subtracts that\n // — there is no latency/pre-roll concept here yet. It is inaudible on its\n // own and audible against picture or against an unshifted track, so it is\n // stated rather than hidden. `semitones: 0` bypasses the node entirely.\n description: \"Shifts pitch up or down without changing playback speed. Adds ~50 ms of latency.\",\n params: [\n {\n kind: \"number\",\n key: \"semitones\",\n label: \"Semitones\",\n unit: \"st\",\n min: -12,\n max: 12,\n step: 1,\n default: 0,\n },\n {\n kind: \"number\",\n key: \"mix\",\n label: \"Mix\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 1,\n },\n ],\n web: \"worklet-pitchshift\",\n },\n\n {\n id: \"delay\",\n label: \"Delay\",\n group: \"time\",\n description: \"Repeating echoes behind the dry signal.\",\n params: [\n {\n kind: \"number\",\n key: \"time\",\n automatable: true,\n label: \"Time\",\n unit: \"ms\",\n min: 1,\n max: 5000,\n step: 1,\n default: 250,\n scale: \"log\",\n },\n // aecho rejects a decay of exactly 0 (\"out of allowed range: (0, 1]\"),\n // so the floor is a hair above silence rather than at it.\n {\n kind: \"number\",\n key: \"feedback\",\n automatable: true,\n label: \"Feedback\",\n unit: \"\",\n min: 0.01,\n max: 0.95,\n step: 0.01,\n default: 0.35,\n },\n {\n kind: \"number\",\n key: \"mix\",\n automatable: true,\n label: \"Mix\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.4,\n },\n ],\n web: \"delay-feedback\",\n },\n {\n id: \"chorus\",\n label: \"Chorus\",\n group: \"time\",\n description: \"Detuned copies of the signal for width and thickness.\",\n params: [\n {\n kind: \"number\",\n key: \"delay\",\n automatable: true,\n label: \"Delay\",\n unit: \"ms\",\n min: 1,\n max: 100,\n step: 0.1,\n default: 7,\n },\n {\n kind: \"number\",\n key: \"depth\",\n automatable: true,\n label: \"Depth\",\n unit: \"ms\",\n min: 0,\n max: 10,\n step: 0.01,\n default: 2,\n },\n {\n kind: \"number\",\n key: \"speed\",\n automatable: true,\n label: \"Rate\",\n unit: \"Hz\",\n min: 0.01,\n max: 10,\n step: 0.01,\n default: 1,\n },\n {\n kind: \"number\",\n key: \"mix\",\n automatable: true,\n label: \"Mix\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.5,\n },\n ],\n web: \"chorus-lfo\",\n },\n {\n id: \"phaser\",\n label: \"Phaser\",\n group: \"time\",\n description: \"Sweeping notches moving through the spectrum.\",\n params: [\n {\n kind: \"number\",\n key: \"in_gain\",\n automatable: true,\n label: \"Input\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.4,\n },\n {\n kind: \"number\",\n key: \"out_gain\",\n automatable: true,\n label: \"Output\",\n unit: \"\",\n min: 0,\n max: 2,\n step: 0.01,\n default: 0.74,\n },\n // aphaser advertises a delay range starting at 0 but rejects it at\n // runtime with \"delay is too small\"; 0.1 ms is the real floor.\n {\n kind: \"number\",\n key: \"delay\",\n label: \"Delay\",\n unit: \"ms\",\n min: 0.1,\n max: 5,\n step: 0.1,\n default: 3,\n },\n {\n kind: \"number\",\n key: \"decay\",\n label: \"Decay\",\n unit: \"\",\n min: 0,\n max: 0.99,\n step: 0.01,\n default: 0.4,\n },\n {\n kind: \"number\",\n key: \"speed\",\n automatable: true,\n label: \"Rate\",\n unit: \"Hz\",\n min: 0.1,\n max: 2,\n step: 0.01,\n default: 0.5,\n },\n {\n kind: \"enum\",\n key: \"type\",\n label: \"Waveform\",\n options: [\n { value: \"0\", label: \"Triangular\" },\n { value: \"1\", label: \"Sinusoidal\" },\n ],\n default: \"0\",\n },\n ],\n web: \"allpass-phaser\",\n },\n {\n id: \"reverb\",\n label: \"Reverb\",\n group: \"time\",\n description:\n \"Room tail. Both ends convolve the same generated impulse, so preview matches render.\",\n params: [\n {\n kind: \"number\",\n key: \"size\",\n label: \"Room size\",\n unit: \"\",\n min: 0.05,\n max: 1,\n step: 0.01,\n default: 0.7,\n },\n {\n kind: \"number\",\n key: \"damping\",\n label: \"Damping\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.5,\n hint: \"Higher rolls the top off the tail faster.\",\n },\n {\n kind: \"number\",\n key: \"wet\",\n automatable: true,\n label: \"Wet\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.35,\n },\n {\n kind: \"number\",\n key: \"dry\",\n automatable: true,\n label: \"Dry\",\n unit: \"\",\n min: 0,\n max: 1,\n step: 0.01,\n default: 0.7,\n },\n ],\n web: \"convolver\",\n },\n] as const;\n\nconst BY_ID = new Map(HF_AUDIO_FX.map((d) => [d.id, d]));\n\nexport function getAudioFxDef(id: string): HfAudioFxDef | undefined {\n return BY_ID.get(id);\n}\n\nexport const HF_AUDIO_FX_IDS: readonly string[] = HF_AUDIO_FX.map((d) => d.id);\n\n/** Every parameter at its declared default, ready to seed a freshly added effect. */\nexport function defaultAudioFxParams(id: string): HfAudioFxParamValues {\n const def = BY_ID.get(id);\n if (!def) return {};\n const out: HfAudioFxParamValues = {};\n for (const p of def.params) out[p.key] = p.default;\n return out;\n}\n\n/**\n * Clamp and fill a parameter set so it is always renderable: unknown keys are\n * dropped, missing keys take their default, numbers are clamped into their\n * declared range, and an unrecognised enum value falls back to its default.\n * A non-finite number is treated as missing rather than passed through, since\n * NaN reaching an AudioParam silences the node for the rest of the render.\n */\nexport function normalizeAudioFxParams(\n id: string,\n values: Readonly<HfAudioFxParamValues> | undefined,\n): HfAudioFxParamValues {\n const def = BY_ID.get(id);\n if (!def) return {};\n const out: HfAudioFxParamValues = {};\n for (const p of def.params) {\n const raw = values?.[p.key];\n if (p.kind === \"enum\") {\n const ok = p.options.some((o) => o.value === raw);\n out[p.key] = ok ? (raw as string) : p.default;\n continue;\n }\n // Only a number, or a string that actually spells one. `Number(null)`,\n // `Number(\"\")`, `Number(false)` and `Number([])` are all 0 and all pass\n // Number.isFinite, so a missing or blanked value used to clamp to 0 rather\n // than fall back to the declared default — and 0 is a legal value for most\n // of these knobs, so nothing downstream could tell. A compressor whose\n // threshold arrived as null sat at 0 dB and never engaged, silently,\n // instead of at its -24 dB default. `numberOrNull` in audioAutomation.ts\n // already guards exactly this.\n const n =\n typeof raw === \"number\"\n ? raw\n : typeof raw === \"string\" && raw.trim() !== \"\"\n ? Number(raw)\n : Number.NaN;\n out[p.key] = Number.isFinite(n) ? Math.min(p.max, Math.max(p.min, n)) : p.default;\n }\n return out;\n}\n\nexport interface HfAudioFxNode {\n /** Effect id from HF_AUDIO_FX. */\n type: string;\n /**\n * Stable handle for this node within its chain, minted when the node is\n * added. Automation lanes address nodes by id (`fx.<id>.<param>`) so that\n * reordering the chain never re-points a lane at a different effect. Older\n * chains have no ids; they load fine and simply cannot be automated until\n * the panel touches them.\n */\n id?: string;\n /** Set on nodes the carve analysis generated, so re-running replaces them\n * instead of stacking another set on top of hand-added effects. */\n fromCarve?: boolean;\n /**\n * Id of the preset that wrote this node, for the same reason `fromCarve`\n * exists: re-applying a preset replaces its own nodes rather than adding a\n * second copy, and the rack can brace them together under the preset's name.\n *\n * The id rather than a flag, because a chain can carry more than one preset\n * and each has to be able to find its own.\n */\n fromPreset?: string;\n /**\n * What the rack calls this node, when the effect's own name is not specific\n * enough to be useful.\n *\n * A peaking filter is \"Shape One Range\" wherever it appears, so a chain that\n * cuts mud at 250 Hz and lifts clarity at 3 kHz shows the same words twice\n * and an author cannot tell the two apart. A preset names each node for the\n * JOB it is doing instead — \"Reduce Mud\", \"Add Clarity\" — and the rack reads\n * as a list of things that were done rather than a list of filter types.\n */\n label?: string;\n /**\n * Id of the multi-band EQ that owns this node, when it is one of its bands.\n *\n * Same device as `fromCarve`: the module gathers its own nodes out of the\n * chain and presents them as one control surface, so an EQ needs no new\n * effect type and its bands stay ordinary filters underneath.\n */\n fromEq?: string;\n\n /**\n * How much of this node's preset is applied, 0..1 — the wet/dry blend the\n * graph wraps its run in.\n *\n * On every node of the run rather than beside the chain, because the chain has\n * nowhere else to put it: `HfAudioFxChain` is a version and a list of nodes,\n * and a preset is defined by which nodes carry its tag. The graph reads it off\n * the first node of each run. Absent means fully applied, which is what every\n * chain written before this means.\n */\n presetAmount?: number;\n /**\n * Set on the gain stage the leveller writes, so re-running replaces it rather\n * than stacking a second one — the same contract `fromCarve` has.\n */\n fromLeveller?: boolean;\n /** Absent means enabled — chain files written before the field existed still load. */\n enabled?: boolean;\n params?: HfAudioFxParamValues;\n}\n\nexport interface HfAudioFxChain {\n version: number;\n nodes: HfAudioFxNode[];\n}\n\nexport class AudioFxChainError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AudioFxChainError\";\n }\n}\n\n/**\n * Parse a chain file. Unknown effect ids are rejected rather than skipped: a\n * chain that silently loses a node would render differently from the project\n * the author saved, which is worse than refusing to render at all.\n */\nexport function parseAudioFxChain(json: string): HfAudioFxChain {\n let raw: unknown;\n try {\n raw = JSON.parse(json);\n } catch (err) {\n throw new AudioFxChainError(`Chain file is not valid JSON: ${(err as Error).message}`);\n }\n if (typeof raw !== \"object\" || raw === null) {\n throw new AudioFxChainError(\"Chain file must be a JSON object.\");\n }\n const obj = raw as { version?: unknown; nodes?: unknown };\n if (obj.version !== HF_AUDIO_FX_CHAIN_VERSION) {\n throw new AudioFxChainError(`Unsupported chain version: ${String(obj.version)}`);\n }\n if (!Array.isArray(obj.nodes)) {\n throw new AudioFxChainError(\"Chain file is missing a `nodes` array.\");\n }\n const nodes = obj.nodes.map(parseAudioFxNode);\n return { version: HF_AUDIO_FX_CHAIN_VERSION, nodes };\n}\n\n/** The nodes that should process audio, in order. */\nexport function enabledAudioFxNodes(chain: HfAudioFxChain): HfAudioFxNode[] {\n return chain.nodes.filter((n) => n.enabled !== false);\n}\n\n/** Serialise a chain for the `data-fx-chain` attribute. */\nexport function serializeAudioFxChain(chain: HfAudioFxChain): string {\n return JSON.stringify({\n version: HF_AUDIO_FX_CHAIN_VERSION,\n nodes: chain.nodes.map(serializeAudioFxNode),\n });\n}\n\n/**\n * Next free node id for a chain, as `n1`, `n2`, … — counted rather than random\n * so that adding an effect produces the same document on every machine, which\n * compositions require.\n */\nexport function mintAudioFxNodeId(chain: HfAudioFxChain): string {\n const taken = new Set(chain.nodes.map((n) => n.id).filter(Boolean));\n for (let i = 1; ; i += 1) {\n const id = `n${i}`;\n if (!taken.has(id)) return id;\n }\n}\n\n/** The shape a node is READ as: everything unknown until checked. */\ninterface RawAudioFxNode {\n type?: unknown;\n id?: unknown;\n enabled?: unknown;\n params?: unknown;\n fromCarve?: unknown;\n fromPreset?: unknown;\n label?: unknown;\n fromEq?: unknown;\n fromLeveller?: unknown;\n presetAmount?: unknown;\n}\n\n/**\n * One node out of a chain file, validated. Throws rather than dropping: a chain\n * that silently loses a node would render differently from the project the\n * author saved.\n */\nfunction parseAudioFxNode(n: unknown, i: number): HfAudioFxNode {\n if (typeof n !== \"object\" || n === null) {\n throw new AudioFxChainError(`Node ${i} is not an object.`);\n }\n const node = n as RawAudioFxNode;\n if (typeof node.type !== \"string\" || !BY_ID.has(node.type)) {\n throw new AudioFxChainError(`Node ${i} has unknown effect type: ${String(node.type)}`);\n }\n return withoutUndefined({\n type: node.type,\n id: nonEmptyString(node.id),\n fromCarve: onlyTrue(node.fromCarve),\n // fromPreset and label both survive the round trip or a preset stops being\n // able to find its own nodes after a reload: re-applying would stack a\n // second copy and the rack would lose the grouping it braces them with.\n fromPreset: nonEmptyString(node.fromPreset),\n label: nonEmptyString(node.label),\n fromEq: nonEmptyString(node.fromEq),\n fromLeveller: onlyTrue(node.fromLeveller),\n presetAmount: clampedPresetAmount(node.presetAmount),\n enabled: node.enabled !== false,\n params: normalizeAudioFxParams(\n node.type,\n (node.params ?? undefined) as HfAudioFxParamValues | undefined,\n ),\n });\n}\n\n/** One node as the `data-fx-chain` attribute carries it. Every optional field is\n * omitted when it holds its default, so a plain chain stays plain. */\nfunction serializeAudioFxNode(node: HfAudioFxNode) {\n return withoutUndefined({\n type: node.type,\n id: nonEmptyString(node.id),\n fromCarve: onlyTrue(node.fromCarve),\n fromPreset: nonEmptyString(node.fromPreset),\n label: nonEmptyString(node.label),\n fromEq: nonEmptyString(node.fromEq),\n fromLeveller: onlyTrue(node.fromLeveller),\n // Omitted when fully applied, so an untouched preset does not grow a field\n // in every chain that carries one.\n presetAmount: node.presetAmount === 1 ? undefined : clampedPresetAmount(node.presetAmount),\n // Only the non-default is written: a chain of enabled nodes stays plain.\n enabled: node.enabled === false ? (false as const) : undefined,\n params: normalizeAudioFxParams(node.type, node.params),\n });\n}\n\n/**\n * The field readers the two node codecs share.\n *\n * Written as readers rather than as conditional spreads inline: nine\n * `...(cond ? { x } : {})` clauses in one object literal is nine branches in a\n * function whose actual job is \"copy the fields that are set\", and it read as\n * complex because it was measured as complex.\n */\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === \"string\" && value ? value : undefined;\n}\n\nfunction onlyTrue(value: unknown): true | undefined {\n return value === true ? true : undefined;\n}\n\n/** Clamped on the way in: the blend is two gains in opposition, and a value\n * outside 0..1 makes the dry leg negative rather than simply loud. */\nfunction clampedPresetAmount(value: unknown): number | undefined {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return undefined;\n return Math.min(1, Math.max(0, value));\n}\n\n/** Drop the keys whose reader returned undefined, so an absent field stays\n * absent rather than becoming an explicit `undefined` in the document. */\nfunction withoutUndefined<T extends object>(obj: T): T {\n for (const key of Object.keys(obj) as Array<keyof T>) {\n if (obj[key] === undefined) delete obj[key];\n }\n return obj;\n}\n","/**\n * Authoring gain for a media clip.\n *\n * HTMLMediaElement.volume is limited to 0..1, but HyperFrames' Web Audio\n * preview and FFmpeg render paths both support gain above unity. Keep the\n * shared ceiling here so Studio, preview, and render cannot drift.\n */\nexport const MAX_AUDIO_GAIN_DB = 12;\n/** Linear gain from a decibel value. Owner of the dB → gain formula. */\nexport function audioDbToGain(db: number): number {\n return 10 ** (db / 20);\n}\n/** Inverse of `audioDbToGain`. Caller must pass a positive finite gain. */\nexport function audioGainToDb(gain: number): number {\n return 20 * Math.log10(gain);\n}\nexport const MAX_AUDIO_GAIN = audioDbToGain(MAX_AUDIO_GAIN_DB);\n\n/** Studio fader coordinates. Unity is deliberately the physical midpoint. */\nexport const AUDIO_GAIN_FADER_MIN = -100;\nexport const AUDIO_GAIN_FADER_MAX = 100;\n\nconst MIN_AUDIO_GAIN_DB = -60;\n\nexport function clampAudioGain(value: number): number {\n if (!Number.isFinite(value)) return 1;\n return Math.max(0, Math.min(MAX_AUDIO_GAIN, value));\n}\n\nexport function clampNativeMediaVolume(value: number): number {\n if (!Number.isFinite(value)) return 1;\n return Math.max(0, Math.min(1, value));\n}\n\n/**\n * Serialize an authored gain for `data-volume`.\n *\n * The fader travels in dB, so its stops are irrational (position -70 is\n * 10 ** (-42/20)). Rounding to two decimals — what the generic numeric\n * attribute formatter does — collapses the whole bottom of the fader onto\n * `\"0\"` (a hard mute) and makes the knob jump on release everywhere below\n * unity. Six decimals round-trip every integer fader stop back to itself.\n */\nexport function formatAudioGain(gain: number): string {\n return clampAudioGain(gain)\n .toFixed(6)\n .replace(/\\.?0+$/, \"\");\n}\n\n/**\n * Run `probe` with `el.volume` shadowed by an accessor that keeps the authored\n * value instead of the spec's [0,1] clamp.\n *\n * `HTMLMediaElement.volume` cannot hold gain above unity, so a clip authored\n * at `data-volume=\"1.95\"` reads back as 1 the moment the probe seeds it — and\n * a GSAP tween started from that seed fades from 0 dB rather than from the\n * authored boost. Both the FFmpeg mixer and the Web Audio transport carry gain\n * up to MAX_AUDIO_GAIN, so the clamp is a probe artefact, not a real ceiling.\n * The native setter still receives the clamped value, so nothing outside the\n * probe observes an out-of-range volume, and the shadow is removed afterwards.\n */\nexport function withUnclampedVolume<T>(el: HTMLMediaElement, probe: () => T): T {\n // Guarded for non-DOM runtimes: the probe that calls this is also reachable\n // from tests and tools that run outside a browser, where the clamped path is\n // the right (and only) answer.\n const descriptor =\n typeof HTMLMediaElement === \"undefined\"\n ? undefined\n : Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, \"volume\");\n const nativeGet = descriptor?.get;\n const nativeSet = descriptor?.set;\n if (!nativeGet || !nativeSet) return probe();\n\n let authored = Number(nativeGet.call(el));\n Object.defineProperty(el, \"volume\", {\n configurable: true,\n get: () => authored,\n set: (value: number) => {\n authored = Number(value);\n nativeSet.call(el, clampNativeMediaVolume(authored));\n },\n });\n try {\n return probe();\n } finally {\n delete (el as unknown as Record<\"volume\", unknown>).volume;\n nativeSet.call(el, clampNativeMediaVolume(authored));\n }\n}\n\nexport function audioFaderPositionToGain(position: number): number {\n const safe = Math.max(AUDIO_GAIN_FADER_MIN, Math.min(AUDIO_GAIN_FADER_MAX, position));\n if (safe === AUDIO_GAIN_FADER_MIN) return 0;\n const db =\n safe < 0\n ? (safe / Math.abs(AUDIO_GAIN_FADER_MIN)) * Math.abs(MIN_AUDIO_GAIN_DB)\n : (safe / AUDIO_GAIN_FADER_MAX) * MAX_AUDIO_GAIN_DB;\n return audioDbToGain(db);\n}\n\nexport function audioGainToFaderPosition(gain: number): number {\n const safe = clampAudioGain(gain);\n if (safe === 0) return AUDIO_GAIN_FADER_MIN;\n const db = audioGainToDb(safe);\n const position =\n db < 0\n ? (db / Math.abs(MIN_AUDIO_GAIN_DB)) * Math.abs(AUDIO_GAIN_FADER_MIN)\n : (db / MAX_AUDIO_GAIN_DB) * AUDIO_GAIN_FADER_MAX;\n return Math.max(AUDIO_GAIN_FADER_MIN, Math.min(AUDIO_GAIN_FADER_MAX, position));\n}\n\nexport function audioGainToText(gain: number): string {\n const safe = clampAudioGain(gain);\n if (safe === 0) return \"-∞ dB\";\n const db = audioGainToDb(safe);\n const rounded = Math.abs(db) < 0.05 ? 0 : db;\n return (rounded > 0 ? \"+\" : \"\") + rounded.toFixed(1) + \" dB\";\n}\n","/**\n * Automation envelopes for audio tracks.\n *\n * A lane is a list of breakpoints over one parameter — track volume, or one\n * knob of one effect in the track's FX chain. Ableton's clip-envelope model:\n * times are clip-local, so an envelope travels with the clip when it moves.\n *\n * Nothing here touches Web Audio. This module owns the format and the\n * interpolation, and the same `sampleAutomationLane` is used to draw the lane\n * in the timeline, to schedule it in preview, and to bake it at render — one\n * curve, three consumers, or the picture and the sound disagree.\n */\n\nimport { getAudioFxDef, type HfAudioFxChain } from \"./audioFx.js\";\nimport { MAX_AUDIO_GAIN } from \"./audioGain.js\";\nimport { MAX_PLAYBACK_RATE, MIN_PLAYBACK_RATE } from \"./playbackRateBounds.js\";\n\nexport const HF_AUDIO_AUTOMATION_ATTR = \"data-automation\";\n\n/** The same attribute as a `dataset` / `dataAttributes` key. See `HF_AUDIO_FX_DATA_KEY`. */\nexport const HF_AUDIO_AUTOMATION_DATA_KEY = HF_AUDIO_AUTOMATION_ATTR.slice(\"data-\".length);\n\n/** Automation files are versioned; a reader must refuse a version it doesn't know. */\nexport const HF_AUDIO_AUTOMATION_VERSION = 1;\n\n/**\n * A pathological document should not be able to hang the scheduler, which\n * expands every segment into scheduled ramps. Well past any hand-drawn\n * envelope; a lane is dense at 100 points.\n */\nexport const MAX_AUTOMATION_POINTS = 512;\n\nexport interface HfAutomationPoint {\n /** Seconds from the start of the clip, not the composition. */\n t: number;\n /** Value in the parameter's own unit — dB for a threshold, Hz for a cutoff. */\n v: number;\n /**\n * Curvature of the segment *leaving* this point, -1..1. Absent or 0 is a\n * straight line; positive holds low then rises late, negative rises early.\n */\n curve?: number;\n /**\n * An interior point the segment *leaving* this point passes through, in\n * normalised segment space: `viaX` is progress 0..1 between the two\n * breakpoints, `viaY` is how far the value has travelled by then. Both are\n * needed for either to mean anything; without them the segment falls back to\n * `curve` above, so everything authored before this existed reads unchanged.\n *\n * Two numbers rather than one because `curve` answers only \"how hard\", and an\n * exponent cannot say \"and where\". Every upward bend a single exponent can\n * draw has its deepest deviation in the first fifth of the segment, whatever\n * the author aimed at, and an exponent steep enough to reach a point near\n * either end runs past the ±1 the model accepts — so a bend dragged near the\n * right-hand breakpoint bulged on the left and then stopped following the\n * pointer at all. Naming the point the curve goes through makes both the\n * height and the position of the bend the author's to choose, and takes the\n * saturation with it: any interior point is reachable exactly.\n */\n viaX?: number;\n viaY?: number;\n}\n\nexport interface HfAutomationLane {\n /** `volume`, or `fx.<nodeId>.<paramKey>`. */\n target: string;\n points: HfAutomationPoint[];\n}\n\nexport interface HfAutomation {\n version: number;\n lanes: HfAutomationLane[];\n}\n\nexport class AudioAutomationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"AudioAutomationError\";\n }\n}\n\nexport const VOLUME_TARGET = \"volume\";\n\n/** Playback-rate lane: `v` is a speed multiplier over clip-local time. See `speedRamp.ts`. */\nexport const RATE_TARGET = \"rate\";\n\nexport type HfAutomationTarget =\n | { kind: \"volume\" }\n | { kind: \"rate\" }\n | { kind: \"fx\"; nodeId: string; param: string }\n | { kind: \"preset\"; presetId: string };\n\n/** Split a target string. Returns null for anything unrecognised. */\nexport function parseAutomationTarget(target: string): HfAutomationTarget | null {\n if (target === VOLUME_TARGET) return { kind: \"volume\" };\n if (target === RATE_TARGET) return { kind: \"rate\" };\n const parts = target.split(\".\");\n // `fx.preset.<id>` before the 3-part fx form, because it IS a 3-part fx form\n // with a reserved node id — an effect can never be called \"preset\", since ids\n // are minted `n1`, `n2`, ….\n if (parts.length === 3 && parts[0] === \"fx\" && parts[1] === PRESET_TARGET_KEY) {\n const presetId = parts[2];\n return presetId ? { kind: \"preset\", presetId } : null;\n }\n if (parts.length !== 3 || parts[0] !== \"fx\") return null;\n const [, nodeId, param] = parts;\n if (!nodeId || !param) return null;\n return { kind: \"fx\", nodeId, param };\n}\n\nexport function fxAutomationTarget(nodeId: string, param: string): string {\n return `fx.${nodeId}.${param}`;\n}\n\n/** The reserved node-id slot that marks a whole-preset target. */\nconst PRESET_TARGET_KEY = \"preset\";\n\n/**\n * How much of a preset is applied, 0..1.\n *\n * A preset's nodes share no automatable parameter — and its worklet effects\n * expose no AudioParams at all — so there is nothing to aim a lane at\n * node-by-node. The graph wraps a preset's run in a wet/dry pair instead, and\n * this drives the blend: 0 is the dry signal untouched, 1 is the preset fully\n * applied, and between them it crossfades.\n */\nexport function presetAutomationTarget(presetId: string): string {\n return `fx.${PRESET_TARGET_KEY}.${presetId}`;\n}\n\n/** 0..1 blend, the same shape as a wet/dry mix knob. */\nexport const PRESET_RANGE: AutomationRange = {\n min: 0,\n max: 1,\n step: 0.01,\n unit: \"\",\n label: \"Amount\",\n scale: \"linear\",\n default: 1,\n};\n\n/**\n * The value range a lane is drawn and clamped against.\n *\n * Volume is linear over the full authoring gain range, matching `data-volume`\n * and the existing volume envelope machinery — no dB conversion enters the\n * volume path. Everything\n * else is read from the effect registry, so a lane can never offer a value the\n * renderer would reject, and the log-scaled knobs sweep the way a DAW's do.\n */\nexport interface AutomationRange {\n min: number;\n max: number;\n step: number;\n unit: string;\n label: string;\n scale: \"linear\" | \"log\";\n /** Where an empty lane draws its flat line, and what a new point starts at. */\n default: number;\n}\n\n/**\n * One ceiling for the fader, the lane, the preview transport and the render\n * mixer. Capping the lane at unity while the fader reached +12 dB made\n * automating a boosted clip silently discard the boost — and the panel\n * disables the fader while a lane owns it, so there was no way back.\n */\nexport const VOLUME_RANGE: AutomationRange = {\n min: 0,\n max: MAX_AUDIO_GAIN,\n step: 0.01,\n unit: \"\",\n label: \"Volume\",\n scale: \"linear\",\n default: 1,\n};\n\nexport const RATE_RANGE: AutomationRange = {\n min: MIN_PLAYBACK_RATE,\n max: MAX_PLAYBACK_RATE,\n step: 0.05,\n unit: \"x\",\n label: \"Speed\",\n scale: \"log\",\n default: 1,\n};\n\n/**\n * Resolve a lane's target against a chain. Returns null when the target names\n * a node or parameter that is not there — the effect was deleted, or the\n * parameter is an enum, which has no envelope between its values.\n */\nexport function resolveAutomationRange(\n target: string,\n chain: HfAudioFxChain | undefined,\n): AutomationRange | null {\n const parsed = parseAutomationTarget(target);\n if (!parsed) return null;\n if (parsed.kind === \"volume\") return VOLUME_RANGE;\n if (parsed.kind === \"rate\") return RATE_RANGE;\n if (parsed.kind === \"preset\") {\n // Only for a preset the chain actually carries, so a lane left behind by a\n // removed preset resolves to nothing and is dropped at read time — the same\n // contract an orphaned node lane has.\n const present = chain?.nodes.some((n) => n.fromPreset === parsed.presetId);\n return present ? { ...PRESET_RANGE, label: `${parsed.presetId} · Amount` } : null;\n }\n const node = chain?.nodes.find((n) => n.id === parsed.nodeId);\n if (!node) return null;\n const def = getAudioFxDef(node.type);\n const param = def?.params.find((p) => p.key === parsed.param);\n if (!param || param.kind !== \"number\") return null;\n return {\n min: param.min,\n max: param.max,\n step: param.step,\n unit: param.unit,\n label: `${def?.label ?? node.type} · ${param.label}`,\n scale: param.scale === \"log\" && param.min > 0 ? \"log\" : \"linear\",\n default: param.default,\n };\n}\n\n/**\n * Coerce a JSON field to a number, treating anything that is not already one —\n * or a string that plainly reads as one — as absent.\n *\n * Not `Number(raw)`: that turns `null`, `\"\"` and `[]` into 0, which for an\n * envelope means a silent drop to zero rather than a point the reader rejects.\n */\nfunction numberOrNull(raw: unknown): number | null {\n if (typeof raw === \"number\") return Number.isFinite(raw) ? raw : null;\n if (typeof raw === \"string\" && raw.trim() !== \"\") {\n const n = Number(raw);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nfunction clampCurve(v: unknown): number {\n const n = numberOrNull(v);\n if (n === null || n === 0) return 0;\n return Math.min(1, Math.max(-1, n));\n}\n\n/**\n * A via point, or null when there isn't a usable one.\n *\n * Only two things disqualify one: a coordinate outside the segment, which names a\n * point the curve does not travel through, and a point ON the straight line, which\n * is a straight line — storing that would claim a bend the segment does not have.\n *\n * Depth is deliberately NOT limited. An earlier version held bends to a maximum\n * slope so a segment could never leave a breakpoint steeply, and it was wrong twice\n * over: it capped how far a curve could be pulled, and clamping the two coordinates\n * separately could land a bend exactly on the straight line and flatten it mid-drag.\n * A steep departure is a smooth curve doing what it was asked; what must never\n * happen is a crease, and that is the sampler's job below, not a limit here.\n */\nfunction clampVia(rawX: unknown, rawY: unknown): { x: number; y: number } | null {\n const x = numberOrNull(rawX);\n const y = numberOrNull(rawY);\n if (x === null || y === null) return null;\n const inside = (n: number): number => Math.min(0.999, Math.max(0.001, n));\n const cx = inside(x);\n const cy = inside(y);\n if (Math.abs(cx - cy) < 1e-6) return null;\n return { x: cx, y: cy };\n}\n\n/**\n * The via point a segment will actually be drawn with, given one that was asked\n * for — pulled into the steady region, or null when it describes no bend.\n *\n * Exported so an editor can write what the model will honour rather than what the\n * pointer happened to ask for. Without it a lane's attribute claims a shape the\n * renderer quietly declines to draw, and the two only agree once the file makes a\n * round trip.\n */\nexport function steadyViaPoint(\n viaX: number | undefined,\n viaY: number | undefined,\n): { viaX: number; viaY: number } | null {\n const via = clampVia(viaX, viaY);\n return via ? { viaX: via.x, viaY: via.y } : null;\n}\n\n/**\n * The shape fields a point carries, cleaned: whichever of `curve` and the via\n * pair survive, each omitted when it describes nothing.\n *\n * The via pair is kept or dropped together. Half a via point says nothing, and\n * letting one coordinate through would leave the segment's shape depending on\n * which half survived a hand edit.\n */\nfunction shapeFields(\n p: HfAutomationPoint | undefined,\n): Pick<HfAutomationPoint, \"curve\" | \"viaX\" | \"viaY\"> {\n const curve = clampCurve(p?.curve);\n const via = clampVia(p?.viaX, p?.viaY);\n return {\n ...(curve ? { curve } : {}),\n ...(via ? { viaX: via.x, viaY: via.y } : {}),\n };\n}\n\n/**\n * Clean one point, or reject it.\n *\n * A missing or non-finite value reaching an AudioParam silences the node for\n * the rest of the render, so such a point is dropped rather than coerced.\n */\nfunction cleanPoint(\n p: HfAutomationPoint | undefined,\n range: AutomationRange | null,\n): HfAutomationPoint | null {\n const t = numberOrNull(p?.t);\n const v = numberOrNull(p?.v);\n if (t === null || v === null) return null;\n const clamped = range ? Math.min(range.max, Math.max(range.min, v)) : v;\n return { t: Math.max(0, t), v: clamped, ...shapeFields(p) };\n}\n\n/**\n * Order points, drop unusable ones, and collapse duplicate times.\n *\n * Later wins on a tie so that dragging a point onto another replaces it rather\n * than leaving an invisible one underneath.\n */\nfunction normalizePoints(\n points: readonly HfAutomationPoint[],\n range: AutomationRange | null,\n): HfAutomationPoint[] {\n const clean = points\n .map((p) => cleanPoint(p, range))\n .filter((p): p is HfAutomationPoint => p !== null)\n .sort((a, b) => a.t - b.t);\n const out: HfAutomationPoint[] = [];\n for (const p of clean) {\n if (out.length > 0 && out[out.length - 1]!.t === p.t) out[out.length - 1] = p;\n else out.push(p);\n }\n return out.slice(0, MAX_AUTOMATION_POINTS);\n}\n\n/**\n * Structural normalisation, with no knowledge of the chain: sort and clean the\n * points of every lane and drop lanes that carry none. Range clamping and\n * orphan removal need the chain and happen in `resolveAutomation`.\n */\nexport function normalizeAutomation(automation: HfAutomation): HfAutomation {\n const lanes: HfAutomationLane[] = [];\n for (const lane of automation.lanes) {\n if (!parseAutomationTarget(lane.target)) continue;\n const range =\n lane.target === VOLUME_TARGET\n ? VOLUME_RANGE\n : lane.target === RATE_TARGET\n ? RATE_RANGE\n : null;\n const points = normalizePoints(lane.points ?? [], range);\n if (points.length > 0) lanes.push({ target: lane.target, points });\n }\n return { version: HF_AUDIO_AUTOMATION_VERSION, lanes };\n}\n\n/**\n * Bind automation to a chain: clamp each lane into its parameter's declared\n * range and drop lanes whose target no longer exists.\n *\n * Dropping is deliberate. An envelope on a deleted effect has nothing to\n * drive, and keeping it would silently reattach if an unrelated effect later\n * took the same node id.\n */\nexport function resolveAutomation(\n automation: HfAutomation,\n chain: HfAudioFxChain | undefined,\n): HfAutomation {\n const lanes: HfAutomationLane[] = [];\n for (const lane of automation.lanes) {\n const range = resolveAutomationRange(lane.target, chain);\n if (!range) continue;\n const points = normalizePoints(lane.points, range);\n if (points.length > 0) lanes.push({ target: lane.target, points });\n }\n return { version: HF_AUDIO_AUTOMATION_VERSION, lanes };\n}\n\n/**\n * Parse an automation attribute.\n *\n * Malformed input throws rather than being skipped, matching the chain reader:\n * a track that quietly loses its envelope renders differently from the project\n * the author saved, which is worse than refusing.\n */\nexport function parseAutomation(json: string): HfAutomation {\n let raw: unknown;\n try {\n raw = JSON.parse(json);\n } catch (err) {\n throw new AudioAutomationError(`Automation is not valid JSON: ${(err as Error).message}`);\n }\n if (typeof raw !== \"object\" || raw === null) {\n throw new AudioAutomationError(\"Automation must be a JSON object.\");\n }\n const obj = raw as { version?: unknown; lanes?: unknown };\n if (obj.version !== HF_AUDIO_AUTOMATION_VERSION) {\n throw new AudioAutomationError(`Unsupported automation version: ${String(obj.version)}`);\n }\n if (!Array.isArray(obj.lanes)) {\n throw new AudioAutomationError(\"Automation is missing a `lanes` array.\");\n }\n const lanes: HfAutomationLane[] = obj.lanes.map((l, i) => {\n if (typeof l !== \"object\" || l === null) {\n throw new AudioAutomationError(`Lane ${i} is not an object.`);\n }\n const lane = l as { target?: unknown; points?: unknown };\n if (typeof lane.target !== \"string\" || !parseAutomationTarget(lane.target)) {\n throw new AudioAutomationError(`Lane ${i} has an unreadable target: ${String(lane.target)}`);\n }\n if (!Array.isArray(lane.points)) {\n throw new AudioAutomationError(`Lane ${i} is missing a \\`points\\` array.`);\n }\n return { target: lane.target, points: lane.points as HfAutomationPoint[] };\n });\n return normalizeAutomation({ version: HF_AUDIO_AUTOMATION_VERSION, lanes });\n}\n\n/** Serialise for the `data-automation` attribute. */\nexport function serializeAutomation(automation: HfAutomation): string {\n return JSON.stringify({\n version: HF_AUDIO_AUTOMATION_VERSION,\n lanes: automation.lanes.map((lane) => ({\n target: lane.target,\n points: lane.points.map((p) => ({\n t: p.t,\n v: p.v,\n ...(p.curve ? { curve: p.curve } : {}),\n ...(p.viaX !== undefined && p.viaY !== undefined ? { viaX: p.viaX, viaY: p.viaY } : {}),\n })),\n })),\n });\n}\n\n/**\n * Shape the 0..1 progress across a segment.\n *\n * `curve` is an exponent in disguise: 0 is linear, and the ends reach a\n * quarter-power and a fourth-power bend, which is about the range a DAW's\n * envelope handle covers before the segment stops reading as a curve.\n */\nexport function applyCurve(x: number, curve: number | undefined): number {\n if (!curve) return x;\n return Math.pow(x, Math.pow(2, 2 * curve));\n}\n\n/**\n * The conic that carries a segment through its via point: control point and weight\n * of a rational quadratic Bezier from (0,0) to (1,1).\n *\n * A rational quadratic at its own midparameter is `(P0 + 2wC + P1) / (2 + 2w)`, so\n * demanding that equal the via point `Q` fixes the control point for any weight:\n * `C = Q + (Q - M) / w`, with `M` the straight midpoint. The curve therefore passes\n * exactly through `Q` whatever the weight, and — because it does so at the\n * midparameter, with the two halves symmetric in parameter — its furthest departure\n * from the straight line is at `Q` too. The pointer is the apex, by construction.\n *\n * The weight is what buys reach. A plain quadratic (`w = 1`) needs its control point\n * at `2Q - M`, which leaves the segment as soon as `Q` is past the middle half — so\n * a plain quadratic simply cannot pass through a deep or off-centre point. Raising\n * the weight draws the control point back toward `Q`, so take the smallest weight\n * that keeps `C` inside the segment: inside is what makes progress monotone, and\n * smallest-that-fits is the broadest curve available through that point.\n *\n * Deep bends therefore come out tight and steep near the breakpoint they lean on.\n * That is the shape being asked for, and it stays a single smooth arc while doing\n * it — no join, no inflection, no crease.\n */\nfunction viaConic(viaX: number, viaY: number): { cx: number; cy: number; w: number } {\n const dx = viaX - 0.5;\n const dy = viaY - 0.5;\n const edge = 0.999;\n // Per axis, `C` stays inside when the weight is at least the pull divided by the\n // room left in the pull's own direction. A via point clamped to `edge` itself\n // leaves zero room — `edge - viaX` is exactly 0 — which would divide out to\n // Infinity and carry NaN into every downstream sample. Capped rather than left\n // unbounded: past this weight the arc already reads as touching the via point,\n // so the cap costs no visible reach.\n const MAX_WEIGHT = 1e6;\n const needX = dx > 0 ? dx / (edge - viaX) : dx < 0 ? -dx / (viaX - (1 - edge)) : 0;\n const needY = dy > 0 ? dy / (edge - viaY) : dy < 0 ? -dy / (viaY - (1 - edge)) : 0;\n const w = Math.min(MAX_WEIGHT, Math.max(1, needX, needY));\n return { cx: viaX + dx / w, cy: viaY + dy / w, w };\n}\n\n/**\n * Progress through a segment that passes through its via point: one conic arc, from\n * breakpoint to breakpoint.\n *\n * One curve, not two joined at the via point, because anything joined there can only\n * be as smooth as the join — and keeping such a join monotone forces its tangent\n * down to a fraction of the slope the curve arrives with, creasing the segment\n * exactly where the pointer is. A conic has no join and no inflection: its slope\n * moves one way from end to end, which is what makes it read as one curve rather\n * than a shape with features in it.\n */\nfunction shapeVia(x: number, viaX: number, viaY: number): number {\n const { cx, cy, w } = viaConic(viaX, viaY);\n // Both coordinates are quadratics over one shared denominator, so `x(t) = x`\n // rearranges into an ordinary quadratic in `t`: closed form, and the same answer on\n // every machine, which a render that has to match its own preview depends on.\n const spread = 2 - 2 * w;\n const a = x * spread - 1 + 2 * w * cx;\n const b = -x * spread - 2 * w * cx;\n const t = conicParam(a, b, x);\n const rest = 1 - t;\n const denominator = rest * rest + 2 * w * t * rest + t * t;\n if (!(denominator > 0)) return x;\n return (2 * w * cy * t * rest + t * t) / denominator;\n}\n\n/** The root of `a*t^2 + b*t + c` that lies on the arc the segment travels. */\nfunction conicParam(a: number, b: number, c: number): number {\n if (Math.abs(a) < 1e-12) return Math.abs(b) < 1e-12 ? c : -c / b;\n const root = Math.sqrt(Math.max(0, b * b - 4 * a * c));\n const first = (-b + root) / (2 * a);\n const second = (-b - root) / (2 * a);\n const onArc = (t: number): boolean => t >= -1e-9 && t <= 1 + 1e-9;\n if (onArc(first)) return Math.min(1, Math.max(0, first));\n if (onArc(second)) return Math.min(1, Math.max(0, second));\n return c;\n}\n\n/**\n * The progress a segment leaving `point` has reached at normalised `x`.\n *\n * A via point wins when it is there, since it says strictly more than an\n * exponent can. Everything authored before via points existed carries only\n * `curve` and takes the exponent path unchanged.\n */\nexport function shapeProgress(\n x: number,\n point: { curve?: number | undefined; viaX?: number | undefined; viaY?: number | undefined },\n): number {\n const via = clampVia(point.viaX, point.viaY);\n if (via) return shapeVia(Math.min(1, Math.max(0, x)), via.x, via.y);\n return applyCurve(x, point.curve);\n}\n\nfunction lerpValue(a: number, b: number, x: number, scale: \"linear\" | \"log\"): number {\n // A frequency sweep interpolated linearly spends almost all its time in the\n // top octave. Log-scaled parameters interpolate in log space so a 200 Hz to\n // 8 kHz move sounds like an even sweep, which is what the knob's own scale\n // already promises.\n if (scale === \"log\" && a > 0 && b > 0) {\n return Math.exp(Math.log(a) + (Math.log(b) - Math.log(a)) * x);\n }\n return a + (b - a) * x;\n}\n\n/**\n * Value of a lane at a clip-local time.\n *\n * Outside the points, the envelope holds — the first value before it starts\n * and the last value after it ends, so a lane never snaps to zero at the edges.\n */\nexport function sampleAutomationLane(\n lane: HfAutomationLane,\n t: number,\n scale: \"linear\" | \"log\" = \"linear\",\n): number {\n const pts = lane.points;\n if (pts.length === 0) return 0;\n const first = pts[0]!;\n if (t <= first.t) return first.v;\n const last = pts[pts.length - 1]!;\n if (t >= last.t) return last.v;\n\n let lo = 0;\n let hi = pts.length - 1;\n while (hi - lo > 1) {\n const mid = (lo + hi) >> 1;\n if (pts[mid]!.t <= t) lo = mid;\n else hi = mid;\n }\n const a = pts[lo]!;\n const b = pts[hi]!;\n const span = b.t - a.t;\n if (span <= 0) return b.v;\n return lerpValue(a.v, b.v, shapeProgress((t - a.t) / span, a), scale);\n}\n\n/** True when the lane is a single value, i.e. worth setting once and not scheduling. */\nexport function isConstantLane(lane: HfAutomationLane): boolean {\n return lane.points.length <= 1 || lane.points.every((p) => p.v === lane.points[0]!.v);\n}\n\n/**\n * Sample a lane onto evenly spaced times, for consumers that want a plain\n * curve rather than a breakpoint list: `setValueCurveAtTime`, the render-side\n * volume envelope, and the lane's own drawing code.\n */\nexport function sampleAutomationCurve(\n lane: HfAutomationLane,\n from: number,\n to: number,\n count: number,\n scale: \"linear\" | \"log\" = \"linear\",\n): Float32Array {\n const n = Math.max(2, Math.floor(count));\n const out = new Float32Array(n);\n const span = to - from;\n for (let i = 0; i < n; i += 1) {\n out[i] = sampleAutomationLane(lane, from + (span * i) / (n - 1), scale);\n }\n return out;\n}\n","/**\n * The volume lane's gain for an element at a moment in its clip.\n *\n * Volume is not scheduled onto the gain AudioParam the way FX parameters are.\n * The runtime rewrites that gain every tick from `data-volume` and the GSAP\n * seeked value, which would erase a scheduled envelope within a frame — so the\n * lane feeds the same per-tick path the probed `volumeKeyframes` already use,\n * and the render still bakes it into the samples.\n */\n\nimport {\n HF_AUDIO_AUTOMATION_ATTR,\n parseAutomation,\n sampleAutomationLane,\n VOLUME_TARGET,\n type HfAutomationLane,\n} from \"../audioAutomation.js\";\n\n/**\n * Parsed lanes by the attribute text they came from. Reparsing on every tick\n * would run the JSON parser 60 times a second per track for a value that only\n * changes when the author edits it.\n */\nconst cache = new Map<string, HfAutomationLane | null>();\nconst CACHE_LIMIT = 64;\n\n/** The lane for `target` inside a `data-automation` value, or null when absent or unreadable. */\nexport function laneFromAttr(raw: string, target: string = VOLUME_TARGET): HfAutomationLane | null {\n const key = `${target}\\0${raw}`;\n const hit = cache.get(key);\n if (hit !== undefined) return hit;\n let lane: HfAutomationLane | null = null;\n try {\n lane = parseAutomation(raw).lanes.find((l) => l.target === target) ?? null;\n } catch {\n // Unreadable automation plays the track flat rather than silencing it.\n lane = null;\n }\n if (cache.size > CACHE_LIMIT) cache.clear();\n cache.set(key, lane);\n return lane;\n}\n\n/**\n * Gain the lane asks for at `relTime` clip-local seconds, or null when the\n * element has no volume lane and the caller should fall back to its own rules.\n */\nexport function elementVolumeLaneGain(\n el: { getAttribute?(name: string): string | null },\n relTime: number,\n): number | null {\n const raw =\n (typeof el.getAttribute === \"function\" ? el.getAttribute(HF_AUDIO_AUTOMATION_ATTR) : null) ??\n \"\";\n if (!raw) return null;\n const lane = laneFromAttr(raw);\n if (!lane || lane.points.length === 0) return null;\n return sampleAutomationLane(lane, relTime);\n}\n","/** A ramp is a `rate` lane in `data-automation`; `sourceTimeAt` is the one rate-to-time mapping consumers share. */\n\nimport { RATE_TARGET, sampleAutomationLane, type HfAutomationLane } from \"./audioAutomation.js\";\nimport { laneFromAttr } from \"./runtime/audioAutomationVolume.js\";\nimport { MAX_PLAYBACK_RATE, MIN_PLAYBACK_RATE } from \"./playbackRateBounds.js\";\n\n/** A constant multiplier, or a lane whose `v` is the multiplier over clip-local time. */\nexport type RateSpec = number | HfAutomationLane;\n\nconst SHIFT_RESAMPLES = 16;\n\n/**\n * The lane as seen from `dt` seconds into the clip: what a renderer sees after it trims the clip's start.\n * A shaped segment (`curve`, `viaX`) cut in the middle is resampled, since its shape belongs to its left point.\n */\nexport function shiftRateLane(spec: RateSpec, dt: number): RateSpec {\n if (typeof spec === \"number\" || dt === 0) return spec;\n const next = spec.points.findIndex((p) => p.t > dt);\n const later = next < 0 ? [] : spec.points.slice(next).map((p) => ({ ...p, t: p.t - dt }));\n const left = next > 0 ? spec.points[next - 1] : undefined;\n const shaped = left !== undefined && (left.curve || left.viaX !== undefined);\n const cut = shaped && next > 0 ? spec.points[next]!.t - dt : 0;\n const samples = Array.from({ length: shaped ? SHIFT_RESAMPLES - 1 : 0 }, (_, k) => {\n const t = ((k + 1) * cut) / SHIFT_RESAMPLES;\n return { t, v: rateAt(spec, dt + t) };\n });\n return { ...spec, points: [{ t: 0, v: rateAt(spec, dt) }, ...samples, ...later] };\n}\n\nconst CELLS_PER_SEGMENT = 48;\n\ninterface RateTable {\n /** Ascending clip-local times and the source seconds consumed by each. */\n ts: Float64Array;\n ss: Float64Array;\n firstRate: number;\n lastRate: number;\n}\n\nconst tables = new WeakMap<HfAutomationLane, RateTable>();\n\nfunction buildTable(lane: HfAutomationLane): RateTable {\n const pts = lane.points;\n const firstRate = pts[0]!.v;\n const lastRate = pts[pts.length - 1]!.v;\n const ts: number[] = [0];\n const ss: number[] = [0];\n const push = (t: number) => {\n const prevT = ts[ts.length - 1]!;\n if (t <= prevT) return;\n const prevRate = sampleAutomationLane(lane, prevT, \"log\");\n ts.push(t);\n ss.push(\n ss[ss.length - 1]! + ((prevRate + sampleAutomationLane(lane, t, \"log\")) / 2) * (t - prevT),\n );\n };\n const first = Math.max(0, pts[0]!.t);\n push(first);\n for (let i = 1; i < pts.length; i += 1) {\n const a = pts[i - 1]!.t;\n const b = pts[i]!.t;\n if (b <= 0) continue;\n for (let k = 1; k <= CELLS_PER_SEGMENT; k += 1)\n push(Math.max(a, 0) + ((b - a) * k) / CELLS_PER_SEGMENT);\n }\n return { ts: Float64Array.from(ts), ss: Float64Array.from(ss), firstRate, lastRate };\n}\n\nfunction tableFor(lane: HfAutomationLane): RateTable {\n let table = tables.get(lane);\n if (!table) {\n table = buildTable(lane);\n tables.set(lane, table);\n }\n return table;\n}\n\nfunction usable(spec: RateSpec): spec is HfAutomationLane {\n return typeof spec !== \"number\" && spec.points.length > 0;\n}\n\n/** Speed multiplier at clip-local time `t`. */\nexport function rateAt(spec: RateSpec, t: number): number {\n if (typeof spec === \"number\") return spec;\n return usable(spec) ? sampleAutomationLane(spec, t, \"log\") : 1;\n}\n\n/** Piecewise-linear lookup of `x` in the ascending `xs`, read off `ys`; caller keeps `x` inside `xs`. */\nfunction interpolate(xs: Float64Array, ys: Float64Array, x: number): number {\n let lo = 0;\n let hi = xs.length - 1;\n while (hi - lo > 1) {\n const mid = (lo + hi) >> 1;\n if (xs[mid]! <= x) lo = mid;\n else hi = mid;\n }\n return ys[lo]! + ((ys[hi]! - ys[lo]!) * (x - xs[lo]!)) / (xs[hi]! - xs[lo]!);\n}\n\n/** Source seconds consumed by clip-local time `t` (the integral of the rate). */\nexport function sourceTimeAt(spec: RateSpec, t: number): number {\n if (typeof spec === \"number\") return t * spec;\n if (!usable(spec)) return t;\n const { ts, ss, firstRate, lastRate } = tableFor(spec);\n const last = ts.length - 1;\n if (t <= 0) return t * firstRate;\n if (t >= ts[last]!) return ss[last]! + (t - ts[last]!) * lastRate;\n return interpolate(ts, ss, t);\n}\n\n/** Inverse of `sourceTimeAt`: the clip-local time at which `source` seconds have been consumed. */\nexport function timeAtSourceTime(spec: RateSpec, source: number): number {\n if (typeof spec === \"number\") return source / spec;\n if (!usable(spec)) return source;\n const { ts, ss, firstRate, lastRate } = tableFor(spec);\n const last = ts.length - 1;\n if (source <= 0) return source / firstRate;\n if (source >= ss[last]!) return ts[last]! + (source - ss[last]!) / lastRate;\n return interpolate(ss, ts, source);\n}\n\n/** The `rate` lane inside a `data-automation` attribute value, or null when absent or unreadable. */\nexport function parseRateLane(raw: string | null | undefined): HfAutomationLane | null {\n return raw ? laneFromAttr(raw, RATE_TARGET) : null;\n}\n\n/** A clip's rate: its `rate` lane when it has one, otherwise the constant. */\nexport function resolveRateSpec(\n automationAttr: string | null | undefined,\n constant: number,\n): RateSpec {\n return parseRateLane(automationAttr) ?? constant;\n}\n\ninterface RatePreset {\n id: string;\n label: string;\n /** Points as [progress 0..1 through the clip, speed]. */\n points: ReadonlyArray<readonly [number, number]>;\n}\n\n/** The CapCut-style presets; `speedPresetLane` stretches one over a clip's duration. */\nexport const SPEED_PRESETS = [\n {\n id: \"montage\",\n label: \"Montage\",\n points: [\n [0, 0.9],\n [0.15, 0.3],\n [0.5, 2.5],\n [0.85, 0.3],\n [1, 0.9],\n ],\n },\n {\n id: \"hero\",\n label: \"Hero\",\n points: [\n [0, 1],\n [0.25, 1.6],\n [0.5, 0.25],\n [0.75, 1.6],\n [1, 1],\n ],\n },\n {\n id: \"bullet\",\n label: \"Bullet\",\n points: [\n [0, 3],\n [0.4, 3],\n [0.5, 0.15],\n [0.6, 3],\n [1, 3],\n ],\n },\n {\n id: \"jump-cut\",\n label: \"Jump cut\",\n points: [\n [0, 0.5],\n [0.24, 0.5],\n [0.26, 3],\n [0.5, 3],\n [0.52, 0.5],\n [0.74, 0.5],\n [0.76, 3],\n [1, 3],\n ],\n },\n {\n id: \"flash-in\",\n label: \"Flash in\",\n points: [\n [0, 0.3],\n [0.6, 0.6],\n [1, 5],\n ],\n },\n {\n id: \"flash-out\",\n label: \"Flash out\",\n points: [\n [0, 5],\n [0.4, 0.6],\n [1, 0.3],\n ],\n },\n {\n id: \"ramp-in\",\n label: \"Ramp in\",\n points: [\n [0, 0.5],\n [1, 1],\n ],\n },\n {\n id: \"ramp-out\",\n label: \"Ramp out\",\n points: [\n [0, 1],\n [1, 0.5],\n ],\n },\n {\n id: \"slowmo-middle\",\n label: \"Slow-mo middle\",\n points: [\n [0, 1],\n [1 / 3, 0.35],\n [2 / 3, 0.35],\n [1, 1],\n ],\n },\n] as const satisfies readonly RatePreset[];\n\nexport type SpeedPresetId = (typeof SPEED_PRESETS)[number][\"id\"];\n\nexport function speedPresetLane(id: SpeedPresetId, clipDuration: number): HfAutomationLane {\n const preset = SPEED_PRESETS.find((p) => p.id === id)!;\n return {\n target: RATE_TARGET,\n points: preset.points.map(([x, v]) => ({\n t: x * clipDuration,\n v: Math.min(MAX_PLAYBACK_RATE, Math.max(MIN_PLAYBACK_RATE, v)),\n })),\n };\n}\n","/**\n * Browser-safe authored composition contract.\n *\n * Source HTML is authored with data-start + data-duration + data-track-index.\n * data-end is compiler-derived and data-layer is legacy input only. Readers\n * accept legacy documents; writers always emit the canonical representation.\n */\n\nexport const COMPOSITION_CONTRACT_VERSION = 1 as const;\n\nexport const COMPOSITION_ATTRIBUTES = Object.freeze({\n start: \"data-start\",\n duration: \"data-duration\",\n trackIndex: \"data-track-index\",\n derivedEnd: \"data-end\",\n legacyTrack: \"data-layer\",\n} as const);\n\nexport const CANONICAL_AUTHORED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.start,\n COMPOSITION_ATTRIBUTES.duration,\n COMPOSITION_ATTRIBUTES.trackIndex,\n] as const);\n\nexport const DERIVED_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n] as const);\n\nexport const LEGACY_TIMING_ATTRIBUTES = Object.freeze([\n COMPOSITION_ATTRIBUTES.derivedEnd,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n] as const);\n\nexport type ReferenceExpression =\n | { kind: \"absolute\"; value: number }\n | { kind: \"reference\"; refId: string; offset: number };\n\nexport type ClipTimingDiagnosticCode =\n | \"invalid-start\"\n | \"unresolved-start-reference\"\n | \"invalid-duration\"\n | \"invalid-end\"\n | \"end-before-start\"\n | \"deprecated-end\"\n | \"conflicting-end\"\n | \"invalid-track-index\"\n | \"deprecated-layer\"\n | \"conflicting-layer\";\n\nexport interface ClipTimingDiagnostic {\n code: ClipTimingDiagnosticCode;\n attribute: string;\n value: string | null;\n}\n\nexport interface ClipAttributeReader {\n getAttribute(name: string): string | null;\n}\n\nexport interface ClipAttributeWriter extends ClipAttributeReader {\n setAttribute(name: string, value: string): void;\n removeAttribute(name: string): void;\n}\n\nexport interface ReadClipTimingOptions {\n /** Resolve a reference to the referenced clip's absolute end time. */\n resolveReferenceEnd?: (refId: string) => number | null | undefined;\n /** Start used when data-start is absent. Defaults to zero. */\n defaultStart?: number | null;\n}\n\nexport interface ClipTiming {\n startExpression: ReferenceExpression | null;\n start: number | null;\n duration: number | null;\n end: number | null;\n trackIndex: number;\n durationSource: \"duration\" | \"legacy-end\" | \"missing\" | \"invalid\";\n trackSource: \"track-index\" | \"legacy-layer\" | \"default\" | \"invalid\";\n diagnostics: ClipTimingDiagnostic[];\n}\n\nexport interface ClipTimingUpdate {\n start?: number | string | ReferenceExpression;\n duration?: number | null;\n trackIndex?: number | null;\n}\n\nexport class ClipTimingWriteError extends Error {\n readonly code: \"invalid-start\" | \"invalid-duration\" | \"invalid-track-index\";\n\n constructor(code: ClipTimingWriteError[\"code\"], message: string) {\n super(message);\n this.name = \"ClipTimingWriteError\";\n this.code = code;\n }\n}\n\n/** Parse a value to a finite number, or null if it is absent/invalid. */\nexport function parseNumeric(value: string | null | undefined): number | null {\n if (value == null || value.trim() === \"\") return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nconst REFERENCE_ID_PATTERN = /^[A-Za-z0-9_.:-]+$/;\n\nfunction isAsciiDigitAt(value: string, index: number): boolean {\n const code = value.charCodeAt(index);\n return code >= 48 && code <= 57;\n}\n\nfunction skipDigitsLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && isAsciiDigitAt(value, cursor)) cursor--;\n return cursor;\n}\n\nfunction skipWhitespaceLeft(value: string, start: number): number {\n let cursor = start;\n while (cursor >= 0 && (value[cursor] ?? \"\").trim() === \"\") cursor--;\n return cursor;\n}\n\nfunction findMagnitudeStart(value: string): number | null {\n const last = value.length - 1;\n if (!isAsciiDigitAt(value, last)) return null;\n let cursor = skipDigitsLeft(value, last);\n if (value[cursor] === \".\") cursor = skipDigitsLeft(value, cursor - 1);\n return cursor + 1;\n}\n\nfunction parseReferenceOffset(\n value: string,\n): { refId: string; operator: \"+\" | \"-\"; magnitude: number } | null {\n const magnitudeStart = findMagnitudeStart(value);\n if (magnitudeStart == null) return null;\n const operatorIndex = skipWhitespaceLeft(value, magnitudeStart - 1);\n const operator = value[operatorIndex];\n if (operator !== \"+\" && operator !== \"-\") return null;\n\n const refId = value.slice(0, operatorIndex).trim();\n if (!REFERENCE_ID_PATTERN.test(refId)) return null;\n const magnitude = Number(value.slice(magnitudeStart));\n if (!Number.isFinite(magnitude)) return null;\n return { refId, operator, magnitude };\n}\n\n/**\n * Parse the data-start grammar: absolute seconds, `clip-id`, or\n * `clip-id +/- offset`, where references resolve to the referenced clip's end.\n */\nexport function parseStartExpression(raw: string | null | undefined): ReferenceExpression | null {\n const normalized = (raw ?? \"\").trim();\n if (!normalized) return null;\n const absolute = parseNumeric(normalized);\n if (absolute != null) return { kind: \"absolute\", value: absolute };\n if (REFERENCE_ID_PATTERN.test(normalized)) {\n return { kind: \"reference\", refId: normalized, offset: 0 };\n }\n const reference = parseReferenceOffset(normalized);\n if (!reference) return null;\n return {\n kind: \"reference\",\n refId: reference.refId,\n offset: reference.operator === \"-\" ? -reference.magnitude : reference.magnitude,\n };\n}\n\nfunction pushDiagnostic(\n diagnostics: ClipTimingDiagnostic[],\n code: ClipTimingDiagnosticCode,\n attribute: string,\n value: string | null,\n): void {\n diagnostics.push({ code, attribute, value });\n}\n\nfunction resolveStart(\n expression: ReferenceExpression | null,\n rawStart: string | null,\n options: ReadClipTimingOptions,\n diagnostics: ClipTimingDiagnostic[],\n): number | null {\n if (rawStart == null || rawStart.trim() === \"\") {\n return options.defaultStart === undefined ? 0 : options.defaultStart;\n }\n if (!expression) {\n pushDiagnostic(diagnostics, \"invalid-start\", COMPOSITION_ATTRIBUTES.start, rawStart);\n return null;\n }\n if (expression.kind === \"absolute\") return Math.max(0, expression.value);\n const referencedEnd = options.resolveReferenceEnd?.(expression.refId);\n if (referencedEnd == null || !Number.isFinite(referencedEnd)) {\n pushDiagnostic(\n diagnostics,\n \"unresolved-start-reference\",\n COMPOSITION_ATTRIBUTES.start,\n rawStart,\n );\n return null;\n }\n return Math.max(0, referencedEnd + expression.offset);\n}\n\ntype DurationRead = Pick<ClipTiming, \"duration\" | \"end\" | \"durationSource\">;\ntype TrackRead = Pick<ClipTiming, \"trackIndex\" | \"trackSource\">;\n\n// Float pairs like `data-start=\"0.1\" + data-duration=\"0.2\"` add to\n// `0.30000000000000004`, so the compiler-emitted `data-end` string can be a few\n// ulps off the sum a reader computes from the canonical inputs. Any drift below\n// this ceiling is treated as equal for the derived-end reconciliation check —\n// well below one 60fps frame (~16.67 ms) and orders of magnitude above the\n// worst realistic IEEE-754 residual (~2e-16 s).\nconst DERIVED_END_EQUALITY_EPSILON_SECONDS = 1e-9;\n\nfunction derivedEndsAreConsistent(parsedEnd: number, canonicalEnd: number): boolean {\n return Math.abs(parsedEnd - canonicalEnd) <= DERIVED_END_EQUALITY_EPSILON_SECONDS;\n}\n\n// Reconcile a `data-end` attribute paired with canonical `data-duration`.\n//\n// The compiler (`compileTimingAttrs` in @hyperframes/core) writes\n// `data-end = data-start + data-duration` into the bundled HTML so the runtime\n// can key off a single attribute. That derived attribute is legitimate — it is\n// not a legacy authoring shape. Treating it as `deprecated-end` here made the\n// linter fire `deprecated_data_end` on the compiler's own output whenever\n// StaticGuard re-validated a bundled composition, producing a false-positive\n// cluster reported in the field (n=25+ across cli-feedback crons 61-68).\n//\n// We keep `deprecated-end` for the truly legacy shape (see `readLegacyEnd` —\n// `data-end` present without `data-duration`) and for the *conflicting* case\n// where an author left a stale `data-end` behind a canonical duration edit; a\n// stale end must not be silently overridden without a diagnostic.\nfunction diagnoseDerivedEnd(\n rawEnd: string,\n canonicalEnd: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): void {\n const parsedEnd = parseNumeric(rawEnd);\n if (parsedEnd == null) {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (canonicalEnd == null) {\n // Canonical duration exists but resolved end is unknown (e.g. unresolved\n // reference start). Preserve prior behavior — flag as deprecated so authors\n // remove the stale companion attribute.\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return;\n }\n if (derivedEndsAreConsistent(parsedEnd, canonicalEnd)) {\n // Compiler-derived (or manually consistent) `data-end` alongside\n // `data-duration` — silent. No diagnostic.\n return;\n }\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n pushDiagnostic(diagnostics, \"conflicting-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n}\n\nfunction readCanonicalDuration(\n rawDuration: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const duration = parseNumeric(rawDuration);\n if (duration == null || duration < 0) {\n pushDiagnostic(diagnostics, \"invalid-duration\", COMPOSITION_ATTRIBUTES.duration, rawDuration);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return {\n duration,\n end: start == null ? null : start + duration,\n durationSource: \"duration\",\n };\n}\n\nfunction readLegacyEnd(\n rawEnd: string,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n pushDiagnostic(diagnostics, \"deprecated-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n const end = parseNumeric(rawEnd);\n if (end == null) {\n pushDiagnostic(diagnostics, \"invalid-end\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n if (start == null) return { duration: null, end, durationSource: \"legacy-end\" };\n if (end < start) {\n pushDiagnostic(diagnostics, \"end-before-start\", COMPOSITION_ATTRIBUTES.derivedEnd, rawEnd);\n return { duration: null, end: null, durationSource: \"invalid\" };\n }\n return { duration: end - start, end, durationSource: \"legacy-end\" };\n}\n\nfunction readDuration(\n attributes: ClipAttributeReader,\n start: number | null,\n diagnostics: ClipTimingDiagnostic[],\n): DurationRead {\n const rawDuration = attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration);\n const rawEnd = attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n if (rawDuration == null) {\n return rawEnd == null\n ? { duration: null, end: null, durationSource: \"missing\" }\n : readLegacyEnd(rawEnd, start, diagnostics);\n }\n const canonical = readCanonicalDuration(rawDuration, start, diagnostics);\n if (rawEnd != null) diagnoseDerivedEnd(rawEnd, canonical.end, diagnostics);\n return canonical;\n}\n\nfunction readTrackValue(\n rawValue: string,\n attribute: string,\n source: \"track-index\" | \"legacy-layer\",\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const trackIndex = parseNumeric(rawValue);\n if (trackIndex == null || !Number.isInteger(trackIndex)) {\n pushDiagnostic(diagnostics, \"invalid-track-index\", attribute, rawValue);\n return { trackIndex: 0, trackSource: \"invalid\" };\n }\n return { trackIndex, trackSource: source };\n}\n\nfunction readTrack(\n attributes: ClipAttributeReader,\n diagnostics: ClipTimingDiagnostic[],\n): TrackRead {\n const rawTrack = attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n const rawLayer = attributes.getAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n if (rawTrack == null && rawLayer == null) return { trackIndex: 0, trackSource: \"default\" };\n if (rawTrack == null && rawLayer != null) {\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n return readTrackValue(\n rawLayer,\n COMPOSITION_ATTRIBUTES.legacyTrack,\n \"legacy-layer\",\n diagnostics,\n );\n }\n const canonical = readTrackValue(\n rawTrack ?? \"\",\n COMPOSITION_ATTRIBUTES.trackIndex,\n \"track-index\",\n diagnostics,\n );\n if (rawLayer == null) return canonical;\n pushDiagnostic(diagnostics, \"deprecated-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n const parsedLayer = parseNumeric(rawLayer);\n if (parsedLayer != null && parsedLayer !== canonical.trackIndex) {\n pushDiagnostic(diagnostics, \"conflicting-layer\", COMPOSITION_ATTRIBUTES.legacyTrack, rawLayer);\n }\n return canonical;\n}\n\n/** Read canonical timing, accepting legacy attributes without preferring them. */\nexport function readClipTiming(\n attributes: ClipAttributeReader,\n options: ReadClipTimingOptions = {},\n): ClipTiming {\n const diagnostics: ClipTimingDiagnostic[] = [];\n const rawStart = attributes.getAttribute(COMPOSITION_ATTRIBUTES.start);\n const startExpression = parseStartExpression(rawStart);\n const start = resolveStart(startExpression, rawStart, options, diagnostics);\n const duration = readDuration(attributes, start, diagnostics);\n const track = readTrack(attributes, diagnostics);\n return { startExpression, start, ...duration, ...track, diagnostics };\n}\n\nfunction serializeStartNumber(start: number): string {\n if (!Number.isFinite(start)) {\n throw new ClipTimingWriteError(\"invalid-start\", \"start must be finite\");\n }\n return String(start);\n}\n\nfunction serializeStartReference(start: ReferenceExpression): string {\n if (start.kind === \"absolute\") return serializeStartNumber(start.value);\n if (!start.refId || !Number.isFinite(start.offset)) {\n throw new ClipTimingWriteError(\n \"invalid-start\",\n \"reference start must have an id and finite offset\",\n );\n }\n if (start.offset === 0) return start.refId;\n return `${start.refId} ${start.offset < 0 ? \"-\" : \"+\"} ${Math.abs(start.offset)}`;\n}\n\nfunction serializeStart(start: ClipTimingUpdate[\"start\"]): string {\n if (typeof start === \"number\") return serializeStartNumber(start);\n if (typeof start === \"object\" && start != null) return serializeStartReference(start);\n if (typeof start === \"string\" && parseStartExpression(start)) return start.trim();\n throw new ClipTimingWriteError(\"invalid-start\", `invalid start expression: ${start ?? \"\"}`);\n}\n\nfunction writeDuration(\n attributes: ClipAttributeWriter,\n duration: number | null | undefined,\n current: ClipTiming,\n): void {\n if (duration === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.duration);\n return;\n }\n if (duration === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n current.duration != null\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(current.duration));\n }\n return;\n }\n if (!Number.isFinite(duration) || duration < 0) {\n throw new ClipTimingWriteError(\n \"invalid-duration\",\n \"duration must be a finite, non-negative number\",\n );\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.duration, String(duration));\n}\n\nfunction writeTrack(\n attributes: ClipAttributeWriter,\n trackIndex: number | null | undefined,\n current: ClipTiming,\n): void {\n if (trackIndex === null) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.trackIndex);\n return;\n }\n if (trackIndex === undefined) {\n if (\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.trackIndex) == null &&\n current.trackSource === \"legacy-layer\"\n ) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(current.trackIndex));\n }\n return;\n }\n if (!Number.isFinite(trackIndex) || !Number.isInteger(trackIndex)) {\n throw new ClipTimingWriteError(\"invalid-track-index\", \"trackIndex must be a finite integer\");\n }\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.trackIndex, String(trackIndex));\n}\n\n/** Write only canonical authored timing and remove derived/legacy source attributes. */\nexport function writeClipTiming(\n attributes: ClipAttributeWriter,\n update: ClipTimingUpdate,\n): ClipTiming {\n const current = readClipTiming(attributes);\n if (update.start !== undefined) {\n attributes.setAttribute(COMPOSITION_ATTRIBUTES.start, serializeStart(update.start));\n }\n writeDuration(attributes, update.duration, current);\n writeTrack(attributes, update.trackIndex, current);\n // A legacy end paired with an unresolved reference start cannot be converted\n // to data-duration without a reference resolver. On a duration-omitting edit\n // (for example, moving only the track), preserve that sole duration source\n // instead of canonicalizing it into data loss.\n const preserveUnresolvedLegacyEnd =\n update.duration === undefined &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.duration) == null &&\n attributes.getAttribute(COMPOSITION_ATTRIBUTES.derivedEnd) != null &&\n current.duration == null;\n if (!preserveUnresolvedLegacyEnd) {\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.derivedEnd);\n }\n attributes.removeAttribute(COMPOSITION_ATTRIBUTES.legacyTrack);\n return readClipTiming(attributes);\n}\n","import {\n clampPlaybackRate,\n readAuthoredDurationSeconds,\n readDataDurationSeconds,\n readMediaOffsetSeconds,\n readPlaybackRate,\n resolveMediaDuration,\n resolveNaturalDurationSeconds,\n} from \"@hyperframes/parsers/media-duration\";\nimport { resolveRateSpec, timeAtSourceTime, type RateSpec } from \"../speedRamp\";\nimport { isImageElement, isMediaElement } from \"./domRealm\";\nimport { parseNumeric } from \"./startExpression\";\n\nexport const normalizePlaybackRate = clampPlaybackRate;\n\n/** Parse a literal numeric timing attribute without accepting trailing units or garbage. */\nexport function parseStrictFiniteTimingNumber(raw: string | null | undefined): number | null {\n return parseNumeric(raw);\n}\n\nexport function readElementPlaybackRate(el: Pick<Element, \"getAttribute\">): number {\n return readPlaybackRate(\n (name) => el.getAttribute(name),\n isMediaElement(el) ? el.defaultPlaybackRate : 1,\n );\n}\n\n/** A constant rate clamped to the shared range; a lane is already normalised by its parser. */\nexport function normalizeRateSpec(spec: RateSpec | undefined): RateSpec {\n return typeof spec === \"object\" ? spec : normalizePlaybackRate(spec ?? 1);\n}\n\n/** The clip's rate: its `rate` lane when present, otherwise the constant rate. */\nexport function readElementRateSpec(el: Pick<Element, \"getAttribute\">): RateSpec {\n return resolveRateSpec(el.getAttribute(\"data-automation\"), readElementPlaybackRate(el));\n}\n\nexport function readMediaStart(el: Pick<Element, \"getAttribute\">): number {\n return readMediaOffsetSeconds((name) => el.getAttribute(name));\n}\n\nexport function resolveNaturalMediaTimelineDuration(\n el: Pick<Element, \"getAttribute\">,\n sourceDuration: number,\n): number | null {\n return resolveNaturalMediaTimelineDurationFromValues(\n sourceDuration,\n readMediaStart(el),\n readElementRateSpec(el),\n );\n}\n\n/**\n * How long a media element occupies the timeline: an authored `data-duration` trim, otherwise\n * the source's natural length adjusted for playback start and rate (lane-aware). `null` while\n * the source has not reported a duration yet. The authored/pending decision is the shared\n * parsers resolver's; only a `rate` lane's arithmetic stays here, because lanes live in core.\n */\nexport function resolveMediaElementDurationSeconds(\n el: Pick<Element, \"getAttribute\"> & { duration: number },\n): number | null {\n const resolved = resolveMediaDuration({\n tag: \"video\", // video and audio resolve identically\n authoredDurationSeconds: readDataDurationSeconds((name) => el.getAttribute(name)),\n sourceDurationSeconds: Number.isFinite(el.duration) ? el.duration : null,\n mediaStartSeconds: readMediaStart(el),\n playbackRate: readElementPlaybackRate(el),\n });\n return resolved.source === \"media\"\n ? resolveNaturalMediaTimelineDuration(el, el.duration)\n : resolved.seconds;\n}\n\n/** A timed `<img>` (`data-start` or `data-track-index`) gets the dropped-image default unless\n * trimmed by `data-duration` or `data-end`; a bare `<img>` is a static layer. `null` otherwise. */\nexport function resolveTimedImageDurationSeconds(el: Element, startSeconds = 0): number | null {\n if (!isImageElement(el)) return null;\n if (!el.hasAttribute(\"data-start\") && !el.hasAttribute(\"data-track-index\")) return null;\n return resolveMediaDuration({\n tag: \"img\",\n authoredDurationSeconds: readAuthoredDurationSeconds(\n (name) => el.getAttribute(name),\n startSeconds,\n ),\n sourceDurationSeconds: null,\n mediaStartSeconds: 0,\n playbackRate: 1,\n }).seconds;\n}\n\n/** A constant rate goes through the shared arithmetic; a `rate` lane integrates over its points. */\nexport function resolveNaturalMediaTimelineDurationFromValues(\n sourceDuration: number,\n mediaStart: number,\n playbackRate: RateSpec,\n): number | null {\n if (typeof playbackRate === \"number\") {\n return resolveNaturalDurationSeconds(sourceDuration, mediaStart, playbackRate);\n }\n if (!Number.isFinite(sourceDuration)) return null;\n return timeAtSourceTime(playbackRate, Math.max(0, sourceDuration - mediaStart));\n}\n","export interface HFDebugSurface {\n __hfDebug?: boolean;\n __HYPERFRAMES_DEBUG?: boolean;\n __hf?: {\n onSwallowed?: (event: { label: string; error: unknown }) => void;\n };\n}\n\nexport function getDebugSurface(): HFDebugSurface {\n return globalThis as HFDebugSurface;\n}\n","/**\n * Runtime diagnostic helpers for best-effort operations.\n *\n * Many runtime operations (postMessage to a parent frame, `media.play()` /\n * `pause()` / `currentTime=`, timeline `seek()`, anime.js feature detection,\n * etc.) can throw under perfectly normal conditions: the parent frame is\n * cross-origin, autoplay is denied, the media element was just removed from\n * the DOM, the timeline has been disposed, the host page does not include\n * anime.js. The right behaviour in each case is \"tried, didn't work, move\n * on\" — but emitting nothing makes silent failures invisible to anyone\n * debugging a genuinely broken composition, and the bare `catch {}` shape\n * also trips strict lint configurations on the inlined runtime IIFE.\n *\n * `swallow(label, err)` is the single funnel for these intentional silences.\n * It dispatches to:\n *\n * - `console.debug` with the label, the error, and a `[hyperframes]` prefix\n * when `window.__hfDebug === true` (or the legacy `__HYPERFRAMES_DEBUG`\n * env-style global). Quiet by default; flip the flag in DevTools when\n * hunting a regression.\n * - A custom `__hf.onSwallowed` handler if installed — lets the studio /\n * embeddings collect runtime swallow events without polluting the page\n * console.\n *\n * Production behaviour without either flag set: completely silent, just\n * like the original empty `catch {}`. The shape is also lint-clean — the\n * helper call is a real statement, so no `no-empty` warnings ship in the\n * inlined IIFE.\n */\nimport { getDebugSurface } from \"./globals.js\";\n\nexport function swallow(label: string, error?: unknown): void {\n if (typeof window === \"undefined\") return;\n const w = getDebugSurface();\n\n const handler = w.__hf?.onSwallowed;\n if (handler) {\n try {\n handler({ label, error });\n } catch (handlerError) {\n // Don't recurse into swallow() — a consumer hook that throws\n // shouldn't be allowed to take down the runtime, and routing the\n // failure back through swallow() would loop. Drop on the floor;\n // the original error already had its surface above.\n void handlerError;\n }\n }\n\n if (w.__hfDebug || w.__HYPERFRAMES_DEBUG) {\n // eslint-disable-next-line no-console -- intentional debug surface\n console.debug(`[hyperframes] ${label} swallowed:`, error);\n }\n}\n","export type AuthoredTimingValue = string | number | null | undefined;\n\nexport const AUTHORED_DURATION_ATTR = \"data-hf-authored-duration\";\nexport const AUTHORED_END_ATTR = \"data-hf-authored-end\";\n\nexport interface RawAuthoredTiming {\n start?: AuthoredTimingValue;\n duration?: AuthoredTimingValue;\n authoredDuration?: AuthoredTimingValue;\n end?: AuthoredTimingValue;\n authoredEnd?: AuthoredTimingValue;\n}\n\nexport interface AuthoredTimingWindow {\n start: number;\n duration: number | null;\n end: number | null;\n}\n\nfunction finiteNumber(value: AuthoredTimingValue): number | null {\n if (value == null) return null;\n if (typeof value === \"string\" && value.trim() === \"\") return null;\n const parsed = typeof value === \"number\" ? value : Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nexport function resolveAuthoredTimingWindow(\n values: RawAuthoredTiming,\n): AuthoredTimingWindow | null {\n const parsedStart = finiteNumber(values.start);\n if (parsedStart == null) return null;\n const start = Math.max(0, parsedStart);\n\n const publicDuration = finiteNumber(values.duration);\n const preservedDuration = finiteNumber(values.authoredDuration);\n const duration =\n publicDuration != null && publicDuration > 0\n ? publicDuration\n : preservedDuration != null && preservedDuration > 0\n ? preservedDuration\n : null;\n if (duration != null) return { start, duration, end: start + duration };\n\n const publicEnd = finiteNumber(values.end);\n const preservedEnd = finiteNumber(values.authoredEnd);\n const end =\n publicEnd != null && publicEnd > start\n ? publicEnd\n : preservedEnd != null && preservedEnd > start\n ? preservedEnd\n : null;\n return { start, duration: end == null ? null : end - start, end };\n}\n","export const MEDIA_START_BASIS_ATTR = \"data-hf-media-start-basis\";\n\nexport type MediaStartBasis = \"local\" | \"global\";\n\nexport function readMediaStartBasis(value: string | null | undefined): MediaStartBasis {\n return value?.trim().toLowerCase() === \"global\" ? \"global\" : \"local\";\n}\n\n/** Resolve authored media time onto the root timeline. Nested media is local\n * by default; only an explicit compatibility marker preserves a legacy\n * root-global value. */\nexport function resolveAbsoluteMediaStartSeconds(input: {\n authoredStart: number;\n hostStart: number;\n basis?: string | null;\n}): number {\n return readMediaStartBasis(input.basis) === \"global\"\n ? input.authoredStart\n : input.hostStart + input.authoredStart;\n}\n\n/** The one rule for a media element's root-timeline start; the runtime and the CLI both call it.\n * With no literal start, an auto-injected start, or a host at t<=0 there is nothing for the basis\n * to disambiguate, so the ordinary start resolution applies. */\nexport function resolveMediaStartSeconds(\n input: MediaStartInput & { ordinaryStart: () => number },\n): number {\n if (!hasLiteralNestedStart(input)) return input.ordinaryStart();\n return resolveAbsoluteMediaStartSeconds({\n authoredStart: input.authoredStart,\n hostStart: input.hostStart,\n basis: input.basis,\n });\n}\n\n/** True when the authored start is already root time, so no host offset applies. */\nexport function isRootGlobalMediaStart(input: MediaStartInput): boolean {\n return hasLiteralNestedStart(input) && readMediaStartBasis(input.basis) === \"global\";\n}\n\nexport interface MediaStartInput {\n authoredStart: number | null;\n hostStart: number;\n hasAutoStart: boolean;\n basis?: string | null;\n}\n\nfunction hasLiteralNestedStart(\n input: MediaStartInput,\n): input is MediaStartInput & { authoredStart: number } {\n return !input.hasAutoStart && input.authoredStart != null && input.hostStart > 0;\n}\n","import type { RuntimeTimelineLike } from \"./types\";\nimport { swallow } from \"./diagnostics\";\nimport { resolveAuthoredTimingWindow } from \"./authoredTiming\";\n// Straight from playbackRate, not through media.ts's re-export: media.ts\n// imports mediaVolumeEnvelope, which needs this resolver, and the round trip\n// would be an import cycle.\nimport {\n parseStrictFiniteTimingNumber,\n resolveNaturalMediaTimelineDuration,\n resolveTimedImageDurationSeconds,\n} from \"./playbackRate\";\nimport { isMediaElement } from \"./domRealm\";\nimport { parseStartExpression } from \"./startExpression\";\nimport {\n isRootGlobalMediaStart,\n MEDIA_START_BASIS_ATTR,\n resolveMediaStartSeconds,\n type MediaStartInput,\n} from \"../mediaTiming\";\n\nexport function createRuntimeStartTimeResolver(params: {\n timelineRegistry?: Record<string, Pick<RuntimeTimelineLike, \"duration\"> | undefined>;\n includeAuthoredTimingAttrs?: boolean;\n /**\n * The document that reference lookups (`data-start=\"intro + 2\"`) resolve\n * against. Defaults to the global `document` — the runtime bundle's own\n * realm. Hosts driving a composition in an IFRAME must pass that iframe's\n * document, or every reference silently resolves against the host page.\n */\n documentRef?: Document;\n}): {\n resolveStartForElement: (element: Element, fallback?: number) => number;\n resolveDurationForElement: (element: Element) => number | null;\n resolveMediaStartForElement: (element: Element) => number;\n resolveHostStartForElement: (element: Element) => number;\n isRootGlobalMediaStartForElement: (element: Element) => boolean;\n} {\n const timelineRegistry = params.timelineRegistry ?? {};\n const includeAuthoredTimingAttrs = params.includeAuthoredTimingAttrs ?? false;\n const doc = params.documentRef ?? document;\n const startCache = new WeakMap<Element, number | null>();\n const durationCache = new WeakMap<Element, number | null>();\n const visiting = new Set<Element>();\n\n const findReferenceTarget = (refId: string): Element | null => {\n const byId = doc.getElementById(refId);\n if (byId) return byId;\n return (\n (doc.querySelector(`[data-composition-id=\"${CSS.escape(refId)}\"]`) as Element | null) ?? null\n );\n };\n\n const resolveDurationForElement = (element: Element): number | null => {\n const cached = durationCache.get(element);\n if (cached !== undefined) return cached;\n let resolved: number | null = null;\n const durationTiming = resolveAuthoredTimingWindow({\n start: 0,\n duration: element.getAttribute(\"data-duration\"),\n authoredDuration: includeAuthoredTimingAttrs\n ? element.getAttribute(\"data-hf-authored-duration\")\n : null,\n });\n if (durationTiming?.duration != null && durationTiming.duration > 0) {\n resolved = durationTiming.duration;\n }\n if (resolved == null || resolved <= 0) {\n const start = resolveStartForElementInternal(element, 0);\n const endTiming = resolveAuthoredTimingWindow({\n start,\n end: element.getAttribute(\"data-end\"),\n authoredEnd: includeAuthoredTimingAttrs\n ? element.getAttribute(\"data-hf-authored-end\")\n : null,\n });\n if (endTiming?.duration != null && endTiming.duration > 0) {\n resolved = endTiming.duration;\n }\n }\n if ((resolved == null || resolved <= 0) && isMediaElement(element)) {\n resolved = resolveNaturalMediaTimelineDuration(element, element.duration);\n }\n if (resolved == null || resolved <= 0) resolved = resolveTimedImageDurationSeconds(element);\n if (resolved == null || resolved <= 0) {\n const compositionId = element.getAttribute(\"data-composition-id\");\n if (compositionId) {\n const timeline = timelineRegistry[compositionId] ?? null;\n if (timeline && typeof timeline.duration === \"function\") {\n try {\n const timelineDuration = Number(timeline.duration());\n if (Number.isFinite(timelineDuration) && timelineDuration > 0) {\n resolved = timelineDuration;\n }\n } catch (err) {\n // ignore broken timeline impls\n swallow(\"runtime.startResolver.site1\", err);\n }\n }\n }\n }\n if (resolved != null && Number.isFinite(resolved) && resolved > 0) {\n durationCache.set(element, resolved);\n return resolved;\n }\n durationCache.set(element, null);\n return null;\n };\n\n const resolveHostOffsetForElement = (element: Element, fallback: number): number => {\n if (element.hasAttribute(\"data-composition-id\")) {\n const parentComposition = element.parentElement?.closest(\"[data-composition-id]\");\n if (!parentComposition) return 0;\n return resolveStartForElementInternal(parentComposition, fallback);\n }\n const compositionRoot = element.closest(\"[data-composition-id]\");\n if (!compositionRoot) return 0;\n return resolveStartForElementInternal(compositionRoot, fallback);\n };\n\n const resolveStartForElementInternal = (element: Element, fallback: number): number => {\n const cached = startCache.get(element);\n if (cached !== undefined) {\n return cached == null ? fallback : cached;\n }\n if (visiting.has(element)) {\n return fallback;\n }\n visiting.add(element);\n try {\n const expression = parseStartExpression(element.getAttribute(\"data-start\"));\n if (!expression) {\n // If this element is a loaded composition inner root (has data-composition-id\n // but no data-start), walk up to the host parent which carries the actual\n // timing. This happens when the host uses a different data-composition-id\n // than the loaded file — e.g. host=\"montage\" but file has \"scene-10\", or\n // when the host itself has no data-composition-id at all (an \"anonymous\"\n // host) and the composition's own id was restored onto the inlined wrapper.\n // Check data-composition-src (runtime, not yet inlined), data-composition-id\n // (bundled/compiled host with its own id), and data-composition-file (the\n // marker every inlined host gets, compiled or bundled, once\n // data-composition-src is stripped — covers the anonymous-host case).\n if (element.hasAttribute(\"data-composition-id\")) {\n const parent = element.parentElement;\n if (\n parent &&\n (parent.hasAttribute(\"data-composition-src\") ||\n parent.hasAttribute(\"data-composition-id\") ||\n parent.hasAttribute(\"data-composition-file\"))\n ) {\n const parentStart = resolveStartForElementInternal(parent, fallback);\n startCache.set(element, parentStart);\n return parentStart;\n }\n }\n startCache.set(element, fallback);\n return fallback;\n }\n if (expression.kind === \"absolute\") {\n const absolute = Math.max(0, expression.value);\n const resolved = Math.max(0, resolveHostOffsetForElement(element, fallback) + absolute);\n startCache.set(element, resolved);\n return resolved;\n }\n const target = findReferenceTarget(expression.refId);\n if (!target) {\n startCache.set(element, fallback);\n return fallback;\n }\n const targetStart = resolveStartForElementInternal(target, 0);\n const targetDuration = resolveDurationForElement(target);\n if (targetDuration == null || targetDuration <= 0) {\n const unresolved = Math.max(0, targetStart + expression.offset);\n startCache.set(element, unresolved);\n return unresolved;\n }\n const resolved = Math.max(0, targetStart + targetDuration + expression.offset);\n startCache.set(element, resolved);\n return resolved;\n } finally {\n visiting.delete(element);\n }\n };\n\n /**\n * The ONE owner of \"when does this media element start on the root timeline\".\n *\n * A media element is not a plain timed clip: `data-hf-media-start-basis`\n * decides whether its `data-start` is composition-local (the default, so the\n * host offset is added) or a legacy root-global timestamp (already absolute,\n * so adding the host offset double-counts it). Anything that derives a media\n * start from attributes — the clip manifest, the visibility pass, the media\n * cache, WebAudio scheduling — must come through here, or the timeline the\n * editor draws stops matching the timeline that plays.\n */\n const mediaStartInput = (element: Element): MediaStartInput => {\n const compositionRoot = element.closest(\"[data-composition-id]\");\n return {\n authoredStart: parseStrictFiniteTimingNumber(element.getAttribute(\"data-start\")),\n hostStart: compositionRoot ? resolveStartForElementInternal(compositionRoot, 0) : 0,\n hasAutoStart: element.hasAttribute(\"data-hf-auto-start\"),\n basis: element.getAttribute(MEDIA_START_BASIS_ATTR),\n };\n };\n\n const resolveMediaStartForElement = (element: Element): number => {\n const input = mediaStartInput(element);\n return resolveMediaStartSeconds({\n ...input,\n ordinaryStart: () => resolveStartForElementInternal(element, input.hostStart),\n });\n };\n\n const isRootGlobalMediaStartForElement = (element: Element): boolean =>\n isMediaElement(element) && isRootGlobalMediaStart(mediaStartInput(element));\n\n return {\n resolveStartForElement: (element: Element, fallback = 0) =>\n resolveStartForElementInternal(element, Math.max(0, fallback)),\n resolveDurationForElement: (element: Element) => resolveDurationForElement(element),\n resolveMediaStartForElement,\n resolveHostStartForElement: (element: Element) => resolveHostOffsetForElement(element, 0),\n isRootGlobalMediaStartForElement,\n };\n}\n\nexport type { RuntimeTimelineLike } from \"./types\";\n","import type { RuntimeDeterministicAdapter, RuntimeTimelineLike } from \"./types\";\nimport type { RuntimeMediaClip } from \"./media\";\nimport type { TransportClock } from \"./clock\";\n\nexport type RuntimeState = {\n capturedTimeline: RuntimeTimelineLike | null;\n isPlaying: boolean;\n currentTime: number;\n deterministicAdapters: RuntimeDeterministicAdapter[];\n canonicalFps: number;\n bridgeMuted: boolean;\n bridgeVolume: number;\n /**\n * Internal mute of audible media output, owned by the audio-ownership\n * protocol between the parent (`<hyperframes-player>`) and this runtime.\n * Independent of `bridgeMuted` (the user's mute preference). When the\n * parent takes over audible playback via parent-frame proxies, it sets\n * this to `true` so the runtime keeps driving timed media for frame\n * accuracy but produces no audio of its own.\n */\n mediaOutputMuted: boolean;\n /**\n * Disable runtime ownership of native media elements. Slideshow presenter\n * mode keeps the slide timeline paused while users interact with embedded\n * media, so the runtime must not auto-play, auto-pause, seek, or volume-sync\n * those native elements on every transport tick.\n */\n nativeMediaSyncDisabled: boolean;\n /**\n * Disable the runtime's WebAudio replacement for native <audio> elements.\n * Slideshow presenter/audience windows mirror native media element events\n * across browsers, so muting those elements for WebAudio ownership breaks\n * audible presenter playback and remote sync.\n */\n webAudioMediaDisabled: boolean;\n /**\n * Latch so the `media-autoplay-blocked` outbound message is posted at most\n * once per runtime session. The parent only needs the first signal — it\n * takes over playback and further rejections are the same problem.\n */\n mediaAutoplayBlockedPosted: boolean;\n /**\n * One-shot flag: force a hard media sync on the next tick. Set on\n * play/pause/seek/rate transitions to immediately correct any\n * accumulated sub-threshold drift from pause/play toggling.\n * Consumed (reset to false) by `syncMediaForCurrentState`.\n */\n mediaForceSyncNextTick: boolean;\n playbackRate: number;\n bridgeLastPostedFrame: number;\n bridgeLastPostedAt: number;\n bridgeLastPostedPlaying: boolean;\n bridgeLastPostedMuted: boolean;\n /**\n * Max interval (ms) between outbound timeline samples on the parent-frame\n * control bridge. The bridge posts on every changed frame, but also at\n * least once per this interval so a paused/idle timeline still confirms\n * its position to any listener.\n *\n * **Cross-reference (do not change in isolation)**: the parent-frame\n * audio-mirror loop in `<hyperframes-player>` waits for\n * `MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES` consecutive over-threshold\n * samples before issuing a `currentTime` correction. The product of\n * those two constants is the worst-case A/V re-sync latency:\n *\n * worst_case_correction_latency_ms\n * ≈ MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES × bridgeMaxPostIntervalMs\n *\n * Today: `2 × 80 ms = 160 ms`, which sits comfortably under the\n * perceptual A/V re-sync tolerance. If you raise this interval, audit\n * `MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES` in\n * `packages/player/src/parent-media.ts` — leaving it at `2` will\n * silently push correction latency past the tolerance budget.\n */\n bridgeMaxPostIntervalMs: number;\n controlBridgeHandler: ((event: MessageEvent) => void) | null;\n beforeUnloadHandler: (() => void) | null;\n injectedCompStyles: HTMLStyleElement[];\n injectedCompScripts: HTMLScriptElement[];\n injectedCompLinks: HTMLLinkElement[];\n cachedTimedMediaEls: Array<HTMLVideoElement | HTMLAudioElement>;\n cachedMediaClips: RuntimeMediaClip[];\n cachedVideoClips: RuntimeMediaClip[];\n cachedMediaTimelineDurationSeconds: number;\n tornDown: boolean;\n /**\n * Single-clock transport. The sole time authority — GSAP is always\n * paused and seeked to `clock.now()` on each rAF tick. Eliminates\n * the two-clock drift problem described in issue #668.\n */\n transportClock: TransportClock | null;\n /** rAF ID for the single-clock tick loop. */\n transportRafId: number | null;\n};\n\nexport function createRuntimeState(): RuntimeState {\n return {\n capturedTimeline: null,\n isPlaying: false,\n currentTime: 0,\n deterministicAdapters: [],\n canonicalFps: 30,\n bridgeMuted: false,\n bridgeVolume: 1,\n mediaOutputMuted: false,\n nativeMediaSyncDisabled: false,\n webAudioMediaDisabled: false,\n mediaAutoplayBlockedPosted: false,\n mediaForceSyncNextTick: false,\n playbackRate: 1,\n bridgeLastPostedFrame: -1,\n bridgeLastPostedAt: 0,\n bridgeLastPostedPlaying: false,\n bridgeLastPostedMuted: false,\n bridgeMaxPostIntervalMs: 80,\n controlBridgeHandler: null,\n beforeUnloadHandler: null,\n injectedCompStyles: [],\n injectedCompScripts: [],\n injectedCompLinks: [],\n cachedTimedMediaEls: [],\n cachedMediaClips: [],\n cachedVideoClips: [],\n cachedMediaTimelineDurationSeconds: 0,\n tornDown: false,\n transportClock: null,\n transportRafId: null,\n };\n}\n","import {\n resolveCompositionDuration,\n type CompositionDurationResult,\n} from \"@hyperframes/parsers/composition-duration\";\nimport { findRootCompositionElement, parseCompositionDimension } from \"./compositionDimension\";\nimport { isMediaElement } from \"./domRealm\";\nimport { parseStrictFiniteTimingNumber, resolveMediaElementDurationSeconds } from \"./playbackRate\";\nimport { createRuntimeStartTimeResolver } from \"./startResolver\";\nimport { createRuntimeState } from \"./state\";\n\nexport { findRootCompositionElement };\n\nexport const LOOP_INFLATED_TIMELINE_SECONDS = 7200;\n\n/** One frame at 60 fps: a timeline, floor or fallback this short or shorter is no length at all. */\nexport const MIN_VALID_TIMELINE_DURATION_SECONDS = 1 / 60;\n\ntype StartResolver = Pick<\n ReturnType<typeof createRuntimeStartTimeResolver>,\n \"resolveStartForElement\" | \"resolveDurationForElement\"\n>;\n\nconst aboveOneFrame = (seconds: number | null): number | null =>\n seconds !== null && seconds > MIN_VALID_TIMELINE_DURATION_SECONDS ? seconds : null;\n\n/** The size the runtime reports to its host. */\nexport function readCompositionSize(root: Element): {\n width: number | null;\n height: number | null;\n} {\n return {\n width: parseCompositionDimension(root.getAttribute(\"data-width\")),\n height: parseCompositionDimension(root.getAttribute(\"data-height\")),\n };\n}\n\n/** The latest end among the document's timed video and audio, at their absolute starts. */\nexport function resolveMediaWindowDurationSeconds(\n doc: ParentNode,\n media: {\n mediaStart: (el: HTMLMediaElement) => number;\n mediaDuration: (el: HTMLMediaElement) => number | null;\n },\n): number | null {\n const clipEnds: number[] = [];\n for (const node of doc.querySelectorAll<HTMLMediaElement>(\n \"video[data-start], audio[data-start]\",\n )) {\n const start = media.mediaStart(node);\n if (!Number.isFinite(start)) continue;\n const duration = media.mediaDuration(node);\n if (duration == null || duration <= MIN_VALID_TIMELINE_DURATION_SECONDS) continue;\n clipEnds.push(Math.max(0, start) + duration);\n }\n return aboveOneFrame(\n resolveCompositionDuration({ authoredDurationSeconds: null, clipEndsSeconds: clipEnds })\n .seconds,\n );\n}\n\n/** The latest of the root's declared length and its direct sub-compositions' ends. */\nexport function resolveAuthoredCompositionFloorSeconds(\n root: Element,\n startResolver: StartResolver,\n): number | null {\n const subCompositionEnds: number[] = [];\n for (const node of root.querySelectorAll(\"[data-composition-id][data-start]\")) {\n if (node.parentElement?.closest(\"[data-composition-id]\") !== root) continue;\n const start = startResolver.resolveStartForElement(node, 0);\n const duration = startResolver.resolveDurationForElement(node);\n if (!Number.isFinite(start) || duration == null || duration <= 0) continue;\n subCompositionEnds.push(Math.max(0, start) + duration);\n }\n const declared = parseStrictFiniteTimingNumber(root.getAttribute(\"data-duration\"));\n return aboveOneFrame(\n resolveCompositionDuration({\n authoredDurationSeconds: null,\n clipEndsSeconds: [declared, ...subCompositionEnds],\n }).seconds,\n );\n}\n\nconst isCompositionHost = (node: Element): boolean =>\n node.hasAttribute(\"data-composition-id\") || node.hasAttribute(\"data-composition-src\");\n\n/** The last-resort length: the latest end among the root's timed clips. Until every clip's length\n * is known it is unresolved, so a renderer that reads it once cannot lock in a short one.\n * `unregisteredLottie`: a Lottie library is loaded, so animations may still register. */\nexport function resolveContentDerivedDuration(\n root: Element,\n startResolver: StartResolver,\n { unregisteredLottie }: { unregisteredLottie: boolean },\n): CompositionDurationResult {\n const clipEnds: Array<number | null> = [];\n for (const node of root.querySelectorAll(\"[data-start]\")) {\n const start = startResolver.resolveStartForElement(node, 0);\n if (!Number.isFinite(start)) continue;\n const duration = startResolver.resolveDurationForElement(node);\n if (duration != null) clipEnds.push(Math.max(0, start) + duration);\n else if (isMediaElement(node) || isCompositionHost(node)) clipEnds.push(null);\n }\n if (unregisteredLottie || root.querySelector(\"[data-lottie-src]\")) clipEnds.push(null);\n const result = resolveCompositionDuration({\n authoredDurationSeconds: null,\n clipEndsSeconds: clipEnds,\n });\n return result.pendingClips > 0\n ? { ...result, seconds: null, source: \"unresolved\", reason: \"a clip's length is pending\" }\n : result;\n}\n\nexport type CompositionLengthInputs = {\n declared: number | null;\n // Read only when nothing is declared: they can run author or adapter code.\n timeline: () => number | null;\n floors: () => ReadonlyArray<number | null>;\n fallback: number;\n derived: () => number;\n};\n\n/** A declared root length is the length; else the longest of timeline, floors and fallback (a\n * loop-inflated timeline yields to a floor or fallback); else the length derived from the clips. */\nexport function resolveCompositionLengthSeconds(input: CompositionLengthInputs): number {\n if (input.declared !== null && Number.isFinite(input.declared) && input.declared > 0) {\n return input.declared;\n }\n const rawTimeline = aboveOneFrame(input.timeline());\n const floor = Math.max(0, ...input.floors().map((seconds) => seconds ?? 0));\n const fallback =\n Number.isFinite(input.fallback) && input.fallback > MIN_VALID_TIMELINE_DURATION_SECONDS\n ? input.fallback\n : 0;\n const floorOrFallback = Math.max(floor, fallback);\n const loopInflated =\n rawTimeline !== null &&\n rawTimeline >= LOOP_INFLATED_TIMELINE_SECONDS &&\n aboveOneFrame(floorOrFallback) !== null;\n const timeline = loopInflated ? null : rawTimeline;\n let seconds: number;\n if (timeline !== null) seconds = Math.max(timeline, floorOrFallback);\n else if (aboveOneFrame(floor) !== null) seconds = floorOrFallback;\n else if (fallback > 0) seconds = fallback;\n else seconds = input.derived();\n return seconds > 0 ? seconds : 0;\n}\n\n/** Size, frame rate and length of a composition nothing is running (a server-parsed or detached\n * document): no timelines, no adapters, and media without a known source length is pending. */\nexport function readStaticCompositionMeta(\n doc: Document,\n): { width: number | null; height: number | null; fps: number; durationSeconds: number } | null {\n const root = findRootCompositionElement(doc);\n if (!root) return null;\n const resolver = createRuntimeStartTimeResolver({\n timelineRegistry: {},\n includeAuthoredTimingAttrs: true,\n documentRef: doc,\n });\n const durationSeconds = resolveCompositionLengthSeconds({\n declared: parseStrictFiniteTimingNumber(root.getAttribute(\"data-duration\")),\n timeline: () => null,\n floors: () => [\n resolveMediaWindowDurationSeconds(doc, {\n mediaStart: resolver.resolveMediaStartForElement,\n mediaDuration: resolveMediaElementDurationSeconds,\n }),\n resolveAuthoredCompositionFloorSeconds(root, resolver),\n ],\n fallback: 0,\n derived: () =>\n resolveContentDerivedDuration(root, resolver, { unregisteredLottie: false }).seconds ?? 0,\n });\n return { ...readCompositionSize(root), fps: createRuntimeState().canonicalFps, durationSeconds };\n}\n","export const STUDIO_PREVIEW_MARK_META = \"hyperframes-studio-preview\";\nexport const STUDIO_PREVIEW_UPCOMING_ATTR = \"data-hf-upcoming\";\nexport const STUDIO_PREVIEW_LAZY_ATTR = \"data-hf-preview-lazy\";\nexport const STUDIO_PREVIEW_ERRORS = \"__hfPreviewErrors\";\nexport const PREVIEW_RASTER_ATTR = \"data-hf-preview-raster\";\nexport const PREVIEW_ONLY_ATTRS = [\n STUDIO_PREVIEW_UPCOMING_ATTR,\n STUDIO_PREVIEW_LAZY_ATTR,\n PREVIEW_RASTER_ATTR,\n] as const;\n","/**\n * Decide whether the player should inject the HyperFrames runtime on the\n * current probe tick.\n *\n * The player polls the loaded iframe every 200ms to discover either:\n * - a runtime bridge already installed (`window.__player`), or\n * - GSAP timelines registered at `window.__timelines`.\n *\n * Two classes of composition require different injection timing:\n *\n * Nested — the composition uses `data-composition-src` on child elements to\n * lazy-load sub-scenes. The runtime is what loads those children, so the\n * composition cannot possibly render on its own. We inject immediately; if\n * we waited, an inline pre-runtime `gsap.timeline` (common for authoring a\n * preview before the runtime rebuilds the master timeline) would register\n * at `__timelines[\"main\"]` with a partial duration, and the adapter path\n * would then lock the player into `ready` against that incomplete timeline.\n *\n * Self-contained — the composition has no nested scenes and ships all of\n * its animation inline (timelines registered under `__timelines`). These\n * don't strictly need the runtime; the adapter can drive them directly.\n * We give the adapter path first shot (a 5-tick grace period) and only\n * inject the runtime as a fallback if no adapter emerges.\n */\nexport interface ProbeState {\n hasRuntime: boolean;\n hasTimelines: boolean;\n hasNestedCompositions: boolean;\n runtimeInjected: boolean;\n attempts: number;\n}\n\nexport function shouldInjectRuntime(state: ProbeState): boolean {\n if (state.hasRuntime || state.runtimeInjected) return false;\n if (state.hasNestedCompositions) return true;\n if (state.hasTimelines && state.attempts >= 5) return true;\n return false;\n}\n","/**\n * Types and type-guards for the playback adapter paths the player supports:\n *\n * - `RuntimeDurationAdapter` — the HyperFrames runtime exposes `window.__player`\n * with a `getDuration()` method. This is the standard path for compositions\n * served through the runtime bridge.\n *\n * - `DirectTimelineAdapter` — same-origin standalone compositions can expose\n * their GSAP master timeline at `window.__timelines` without installing the\n * full runtime. The player drives play/pause/seek directly against the\n * timeline object, bypassing the postMessage bridge.\n *\n * `PlaybackDurationAdapter` is the discriminated union the probe interval\n * returns after deciding which path is available.\n */\n\nexport interface RuntimeDurationAdapter {\n getDuration: () => number;\n}\n\nexport interface DirectTimelineAdapter {\n duration: () => number;\n time: () => number;\n // suppressEvents mirrors GSAP's timeline.seek(position, suppressEvents); pass\n // false to fire onUpdate (so imperative-visibility compositions repaint on seek).\n seek: (timeInSeconds: number, suppressEvents?: boolean) => unknown;\n play: () => unknown;\n pause: () => unknown;\n /** Optional: set playback rate (e.g. GSAP's timeScale). Called when the player's playbackRate changes. */\n timeScale?: (scale: number) => unknown;\n}\n\nexport type PlaybackDurationAdapter =\n | { kind: \"document\"; getDuration: () => number }\n | { kind: \"runtime\"; getDuration: () => number }\n | { kind: \"direct-timeline\"; timeline: DirectTimelineAdapter; getDuration: () => number };\n\nexport function isObjectRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nexport function isRuntimeDurationAdapter(value: unknown): value is RuntimeDurationAdapter {\n return isObjectRecord(value) && typeof value.getDuration === \"function\";\n}\n\nexport function isDirectTimelineAdapter(value: unknown): value is DirectTimelineAdapter {\n return (\n isObjectRecord(value) &&\n typeof value.duration === \"function\" &&\n typeof value.time === \"function\" &&\n typeof value.seek === \"function\" &&\n typeof value.play === \"function\" &&\n typeof value.pause === \"function\"\n );\n}\n","/**\n * Where the runtime comes from.\n *\n * Split out of composition-probe so the probe's late injection and the srcdoc's\n * parse-time injection cannot drift onto different URLs.\n */\ndeclare const __HYPERFRAMES_RUNTIME_CDN_URL__: string;\n\nexport function runtimeCdnUrlForVersion(version: string): string {\n if (!/^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/.test(version)) {\n throw new Error(`Invalid HyperFrames runtime version: ${version}`);\n }\n return `https://cdn.jsdelivr.net/npm/@hyperframes/core@${version}/dist/hyperframe.runtime.iife.js`;\n}\n\nexport const RUNTIME_CDN_URL =\n typeof __HYPERFRAMES_RUNTIME_CDN_URL__ === \"string\"\n ? __HYPERFRAMES_RUNTIME_CDN_URL__\n : runtimeCdnUrlForVersion(\"0.0.0-dev\");\n","/**\n * Probes an iframe document to discover the composition's playback adapter\n * and detect whether the HyperFrames runtime needs to be injected.\n *\n * The probe interval polls every 200 ms until one of:\n * - An adapter or the document resolves with a positive duration, or\n * - 40 attempts (~8 s) expire without a result.\n *\n * The `CompositionProbe` class owns the interval; the caller must call\n * `stop()` on disconnect or src change.\n */\n\nimport { readStaticCompositionMeta } from \"@hyperframes/core/runtime/composition-length\";\nimport { STUDIO_PREVIEW_ERRORS } from \"@hyperframes/core/studio-preview-mark\";\nimport { shouldInjectRuntime } from \"./shouldInjectRuntime.js\";\nimport {\n type DirectTimelineAdapter,\n type PlaybackDurationAdapter,\n isDirectTimelineAdapter,\n isObjectRecord,\n isRuntimeDurationAdapter,\n} from \"./timeline-adapters.js\";\n\nimport { RUNTIME_CDN_URL, runtimeCdnUrlForVersion } from \"./runtime-url.js\";\n\nexport { runtimeCdnUrlForVersion };\n\nexport interface ProbeResult {\n duration: number;\n adapter: PlaybackDurationAdapter;\n /** Resolved composition dimensions, if present in the document. */\n compositionSize: { width: number; height: number } | null;\n}\n\nexport interface ProbeCallbacks {\n onReady: (result: ProbeResult) => void;\n onError: (message: string) => void;\n /** Called when runtime is successfully injected (informational). */\n onRuntimeInjected?: () => void;\n resolveRuntimeUrl?: () => string;\n}\n\n/**\n * Parse a composition dimension, rejecting anything that isn't a positive\n * finite number. Exported because the `width`/`height` attribute handlers in\n * hyperframes-player.ts need the same guard: dimensions feed\n * scaleIframeToFit's `w / compositionWidth` division, where NaN produces an\n * invalid `scale(NaN)` transform and zero a division by zero — both render\n * the player blank with no signal.\n */\nexport function readPositiveDimension(value: string | null): number | null {\n if (value === null) return null;\n const parsed = Number.parseInt(value, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n}\n\nexport function readCompositionSizeFromDocument(\n doc: Document | null | undefined,\n): { width: number; height: number } | null {\n const root =\n doc?.querySelector(\"[data-composition-id][data-width][data-height]\") ??\n doc?.querySelector(\"[data-width][data-height]\");\n if (!root) return null;\n const width = readPositiveDimension(root.getAttribute(\"data-width\"));\n const height = readPositiveDimension(root.getAttribute(\"data-height\"));\n return width !== null && height !== null ? { width, height } : null;\n}\n\ntype ProbeOutcome =\n | { kind: \"ready\"; result: ProbeResult }\n | { kind: \"error\"; message: string }\n | null;\n\nfunction firstAuthorError(errors: unknown): string | null {\n if (!Array.isArray(errors)) return null;\n return (\n errors.find(\n (value): value is string =>\n typeof value === \"string\" && value.trim() !== \"\" && value !== \"[object Event]\",\n ) ?? null\n );\n}\n\nexport class CompositionProbe {\n private _interval: ReturnType<typeof setInterval> | null = null;\n private _runtimeInjected = false;\n private _runtimeScript: HTMLScriptElement | null = null;\n private _failure: { document: Document | null } | null = null;\n\n get failed(): boolean {\n return this._failure !== null && this._failure.document === this._iframe.contentDocument;\n }\n\n constructor(\n private readonly _iframe: HTMLIFrameElement,\n private readonly _callbacks: ProbeCallbacks,\n ) {}\n\n // fallow-ignore-next-line unused-class-member\n get runtimeInjected(): boolean {\n return this._runtimeInjected;\n }\n\n /** Start or restart the probe, stopping the active interval first. */\n start(): void {\n this.stop();\n this._failure = null;\n this._runtimeInjected = false;\n let attempts = 0;\n\n this._interval = setInterval(() => {\n attempts++;\n let outcome: ProbeOutcome = null;\n try {\n outcome = this._poll(attempts);\n } catch (error) {\n if (!(error instanceof DOMException && error.name === \"SecurityError\")) {\n outcome = {\n kind: \"error\",\n message: error instanceof Error ? error.message : String(error),\n };\n }\n }\n if (outcome) {\n this.stop();\n if (outcome.kind === \"error\") {\n this._failure = { document: this._iframe.contentDocument };\n this._callbacks.onError(outcome.message);\n } else this._callbacks.onReady(outcome.result);\n return;\n }\n if (attempts >= 40) {\n this.stop();\n this._failure = { document: this._iframe.contentDocument };\n this._callbacks.onError(\"Composition timeline not found after 8s\");\n }\n }, 200);\n }\n\n private _poll(attempts: number): ProbeOutcome {\n const win = this._iframe.contentWindow;\n if (!win) return null;\n const message = firstAuthorError(Reflect.get(win, STUDIO_PREVIEW_ERRORS));\n if (message !== null) return { kind: \"error\", message };\n const doc = this._iframe.contentDocument;\n const timelines = Reflect.get(win, \"__timelines\");\n const hasRuntime = this.hasRuntimeBridge(win);\n if (\n shouldInjectRuntime({\n hasRuntime,\n hasTimelines: isObjectRecord(timelines) && Object.keys(timelines).length > 0,\n hasNestedCompositions: !!doc?.querySelector(\"[data-composition-src]\"),\n runtimeInjected: this._runtimeInjected,\n attempts,\n })\n ) {\n this._injectRuntime();\n return null;\n }\n if (this._runtimeInjected && !hasRuntime) return null;\n const result = this._resolveReadyResult(win, doc);\n return result ? { kind: \"ready\", result } : null;\n }\n\n private _resolveReadyResult(win: Window, doc: Document | null): ProbeResult | null {\n const adapter: PlaybackDurationAdapter = this._resolvePlaybackDurationAdapter(win) ?? {\n kind: \"document\",\n getDuration: () => 0,\n };\n let duration = adapter.getDuration();\n if (!Number.isFinite(duration) || duration <= 0) {\n if (adapter.kind === \"direct-timeline\") return null;\n duration = doc ? (readStaticCompositionMeta(doc)?.durationSeconds ?? 0) : 0;\n }\n if (duration <= 0) return null;\n return {\n duration,\n adapter: { ...adapter, getDuration: () => duration },\n compositionSize: readCompositionSizeFromDocument(doc),\n };\n }\n\n stop(): void {\n if (this._runtimeScript) {\n this._runtimeScript.onerror = null;\n this._runtimeScript = null;\n }\n if (!this.failed) this._failure = null;\n if (this._interval !== null) {\n clearInterval(this._interval);\n this._interval = null;\n }\n }\n\n // ── Adapter resolution (same-origin only) ────────────────────────────────\n\n resolveDirectTimelineAdapter(): DirectTimelineAdapter | null {\n try {\n const win = this._iframe.contentWindow;\n if (!win) return null;\n return this._resolveDirectTimelineAdapterFromWindow(win);\n } catch {\n return null;\n }\n }\n\n // fallow-ignore-next-line unused-class-member\n resolveDirectTimelineAdapterFromWindow(win: Window): DirectTimelineAdapter | null {\n return this._resolveDirectTimelineAdapterFromWindow(win);\n }\n\n hasRuntimeBridge(win: Window): boolean {\n return isObjectRecord(Reflect.get(win, \"__player\"));\n }\n\n // ── Private ──────────────────────────────────────────────────────────────\n\n private _injectRuntime(): void {\n this._runtimeInjected = true;\n try {\n const doc = this._iframe.contentDocument;\n if (!doc) return;\n const script = doc.createElement(\"script\");\n script.src = this._callbacks.resolveRuntimeUrl?.() ?? RUNTIME_CDN_URL;\n this._runtimeScript = script;\n script.onerror = () => {\n if (this._runtimeScript !== script || this._iframe.contentDocument !== doc) return;\n this.stop();\n this._failure = { document: doc };\n this._callbacks.onError(\"HyperFrames runtime failed to load from \" + script.src);\n };\n (doc.head || doc.documentElement).appendChild(script);\n this._callbacks.onRuntimeInjected?.();\n } catch {\n /* cross-origin — can't inject */\n }\n }\n\n private _resolveDirectTimelineAdapterFromWindow(win: Window): DirectTimelineAdapter | null {\n if (this.hasRuntimeBridge(win)) return null;\n\n const timelines = Reflect.get(win, \"__timelines\");\n if (!isObjectRecord(timelines)) return null;\n\n const keys = Object.keys(timelines);\n if (keys.length === 0) return null;\n\n const rootId = this._iframe.contentDocument\n ?.querySelector(\"[data-composition-id]\")\n ?.getAttribute(\"data-composition-id\");\n const key = rootId && rootId in timelines ? rootId : keys[keys.length - 1];\n const timeline = timelines[key];\n return isDirectTimelineAdapter(timeline) ? timeline : null;\n }\n\n private _resolvePlaybackDurationAdapter(win: Window): PlaybackDurationAdapter | null {\n const runtimePlayer = Reflect.get(win, \"__player\");\n if (isRuntimeDurationAdapter(runtimePlayer)) {\n return { kind: \"runtime\", getDuration: () => runtimePlayer.getDuration() };\n }\n\n const timeline = this._resolveDirectTimelineAdapterFromWindow(win);\n if (timeline) {\n return {\n kind: \"direct-timeline\",\n timeline,\n getDuration: () => timeline.duration(),\n };\n }\n\n return null;\n }\n}\n","export const PLAYER_STYLES = /* css */ `\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n background: #000;\n contain: layout style;\n }\n\n .hfp-container {\n position: absolute;\n inset: 0;\n overflow: hidden;\n pointer-events: none;\n }\n\n\n .hfp-iframe {\n position: absolute;\n top: 50%;\n left: 50%;\n border: none;\n pointer-events: none;\n }\n\n /* Opt-in: an interactive composition (e.g. a live slideshow/app with playable\n media or controls) — let pointer events reach the iframe content. */\n :host([interactive]) .hfp-container,\n :host([interactive]) .hfp-iframe {\n pointer-events: auto;\n }\n\n .hfp-video {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n object-fit: contain;\n pointer-events: none;\n }\n\n .hfp-poster {\n position: absolute;\n inset: 0;\n object-fit: contain;\n z-index: 1;\n pointer-events: none;\n }\n\n .hfp-shader-loader {\n position: absolute;\n inset: 0;\n z-index: 20;\n display: grid;\n place-items: center;\n visibility: hidden;\n opacity: 0;\n pointer-events: none;\n background: #030504;\n color: #f4f7fb;\n cursor: default;\n user-select: none;\n -webkit-user-select: none;\n transition: opacity 420ms ease-out, visibility 420ms ease-out;\n }\n\n .hfp-shader-loader.hfp-visible,\n .hfp-shader-loader.hfp-hiding {\n visibility: visible;\n }\n\n .hfp-shader-loader.hfp-visible {\n opacity: 1;\n pointer-events: auto;\n }\n\n .hfp-shader-loader.hfp-hiding {\n opacity: 0;\n pointer-events: none;\n }\n\n .hfp-shader-loader-panel {\n display: grid;\n grid-template-rows: 86px 40px 26px 12px 44px;\n justify-items: center;\n align-items: center;\n gap: 8px;\n width: min(620px, 82%);\n text-align: center;\n font-family: Inter, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n }\n\n .hfp-shader-loader-mark {\n width: 86px;\n height: 86px;\n display: grid;\n place-items: center;\n overflow: visible;\n }\n\n .hfp-shader-loader-mark svg {\n display: block;\n overflow: visible;\n filter: drop-shadow(0 0 5px rgba(79, 219, 94, 0.16));\n pointer-events: none;\n }\n\n .hfp-shader-loader-title {\n width: 100%;\n height: 40px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n font-size: 26px;\n line-height: 40px;\n font-weight: 700;\n letter-spacing: 0;\n }\n\n .hfp-shader-loader-title-text {\n color: transparent;\n background: linear-gradient(\n 90deg,\n rgba(244, 247, 251, 0.84) 0%,\n #ffffff 42%,\n #80efe4 52%,\n #ffffff 62%,\n rgba(244, 247, 251, 0.84) 100%\n );\n background-size: 220% 100%;\n -webkit-background-clip: text;\n background-clip: text;\n animation: hfp-shader-loader-sheen 1.9s linear infinite;\n }\n\n .hfp-shader-loader:not(.hfp-visible):not(.hfp-hiding) .hfp-shader-loader-title-text {\n animation-play-state: paused;\n }\n\n .hfp-shader-loader-detail {\n width: 100%;\n height: 26px;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n color: rgba(244, 247, 251, 0.62);\n font-size: 15px;\n line-height: 26px;\n font-weight: 500;\n }\n\n .hfp-shader-loader-track {\n width: min(360px, 100%);\n height: 8px;\n overflow: hidden;\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.1);\n }\n\n .hfp-shader-loader-fill {\n width: 100%;\n height: 100%;\n border-radius: inherit;\n background: linear-gradient(90deg, #06e3fa, #4fdb5e);\n transform: scaleX(0);\n transform-origin: left center;\n transition: transform 160ms ease;\n }\n\n .hfp-shader-loader-progress {\n width: min(420px, 100%);\n height: 44px;\n display: grid;\n grid-template-rows: repeat(2, 22px);\n color: rgba(244, 247, 251, 0.48);\n font: 600 13px/22px \"IBM Plex Mono\", \"SF Mono\", \"Fira Code\", \"Courier New\", monospace;\n font-variant-numeric: tabular-nums;\n }\n\n .hfp-shader-loader-row {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 74px;\n align-items: center;\n column-gap: 20px;\n width: 100%;\n white-space: nowrap;\n }\n\n .hfp-shader-loader-label {\n min-width: 0;\n overflow: hidden;\n text-align: left;\n text-overflow: ellipsis;\n }\n\n .hfp-shader-loader-value {\n text-align: right;\n }\n\n @keyframes hfp-shader-loader-sheen {\n from {\n background-position: 140% 0;\n }\n to {\n background-position: -140% 0;\n }\n }\n\n /* ── Theming via CSS custom properties ──\n *\n * Override from outside the shadow DOM:\n * hyperframes-player {\n * --hfp-controls-bg: linear-gradient(transparent, rgba(0,0,0,0.9));\n * --hfp-accent: #ff6b6b;\n * --hfp-font: \"Inter\", sans-serif;\n * }\n */\n\n .hfp-controls {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n gap: var(--hfp-controls-gap, 12px);\n padding: var(--hfp-controls-padding, 8px 16px);\n background: var(--hfp-controls-bg, linear-gradient(transparent, rgba(0, 0, 0, 0.7)));\n color: var(--hfp-color, #fff);\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: var(--hfp-font-size, 13px);\n z-index: 10;\n pointer-events: auto;\n opacity: 1;\n transition: opacity 0.3s ease;\n user-select: none;\n }\n\n .hfp-controls.hfp-hidden {\n opacity: 0;\n pointer-events: none;\n }\n\n .hfp-play-btn {\n position: relative;\n background: none;\n border: none;\n color: var(--hfp-color, #fff);\n cursor: pointer;\n padding: 8px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n flex-shrink: 0;\n z-index: 10;\n }\n\n .hfp-play-btn:hover {\n opacity: 0.8;\n }\n\n /* Stacked play/pause glyphs that crossfade-morph on toggle (rotate + scale). */\n .hfp-play-btn .hfp-ico {\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n opacity 200ms ease,\n transform 220ms cubic-bezier(0.4, 0, 0.2, 1);\n }\n .hfp-play-btn .hfp-ico-play {\n opacity: 1;\n transform: rotate(0) scale(1);\n }\n .hfp-play-btn .hfp-ico-pause {\n opacity: 0;\n transform: rotate(-90deg) scale(0.4);\n }\n .hfp-play-btn.hfp-playing .hfp-ico-play {\n opacity: 0;\n transform: rotate(90deg) scale(0.4);\n }\n .hfp-play-btn.hfp-playing .hfp-ico-pause {\n opacity: 1;\n transform: rotate(0) scale(1);\n }\n @media (prefers-reduced-motion: reduce) {\n .hfp-play-btn .hfp-ico {\n transition-duration: 0ms;\n transform: none;\n }\n }\n\n .hfp-play-btn svg,\n .hfp-play-btn svg * {\n pointer-events: none;\n }\n\n .hfp-scrubber {\n flex: 1;\n min-width: 0;\n height: var(--hfp-scrubber-height, 4px);\n background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));\n border-radius: var(--hfp-scrubber-radius, 2px);\n cursor: pointer;\n position: relative;\n overflow: hidden;\n }\n\n .hfp-scrubber:hover {\n height: var(--hfp-scrubber-height-hover, 6px);\n }\n\n .hfp-progress {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--hfp-accent, #fff);\n pointer-events: none;\n }\n\n .hfp-time {\n flex-shrink: 0;\n font-variant-numeric: tabular-nums;\n opacity: 0.9;\n }\n\n .hfp-speed-wrap {\n position: relative;\n flex-shrink: 0;\n }\n\n .hfp-speed-btn {\n background: var(--hfp-speed-btn-bg, rgba(255, 255, 255, 0.15));\n border: none;\n border-radius: var(--hfp-speed-btn-radius, 4px);\n color: var(--hfp-color, #fff);\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n padding: 4px 8px;\n min-width: 40px;\n text-align: center;\n transition: background 0.15s ease;\n }\n\n .hfp-speed-btn:hover {\n background: var(--hfp-speed-btn-bg-hover, rgba(255, 255, 255, 0.3));\n }\n\n .hfp-speed-menu {\n position: absolute;\n bottom: calc(100% + 8px);\n right: 0;\n background: var(--hfp-menu-bg, rgba(20, 20, 20, 0.95));\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n border: 1px solid var(--hfp-menu-border, rgba(255, 255, 255, 0.1));\n border-radius: var(--hfp-menu-radius, 8px);\n padding: 4px;\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 80px;\n opacity: 0;\n visibility: hidden;\n transform: translateY(4px);\n transition: opacity 0.15s ease, transform 0.15s ease, visibility 0.15s;\n box-shadow: var(--hfp-menu-shadow, 0 8px 24px rgba(0, 0, 0, 0.4));\n }\n\n .hfp-speed-menu.hfp-open {\n opacity: 1;\n visibility: visible;\n transform: translateY(0);\n }\n\n .hfp-speed-option {\n background: none;\n border: none;\n border-radius: 4px;\n color: var(--hfp-menu-color, rgba(255, 255, 255, 0.7));\n cursor: pointer;\n font-family: var(--hfp-font, system-ui, -apple-system, sans-serif);\n font-size: 13px;\n font-variant-numeric: tabular-nums;\n padding: 6px 12px;\n text-align: left;\n transition: background 0.1s ease, color 0.1s ease;\n white-space: nowrap;\n }\n\n .hfp-speed-option:hover {\n background: var(--hfp-menu-hover-bg, rgba(255, 255, 255, 0.1));\n color: var(--hfp-color, #fff);\n }\n\n .hfp-speed-option.hfp-active {\n color: var(--hfp-accent, #fff);\n font-weight: 600;\n }\n\n .hfp-volume-wrap {\n position: relative;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 0;\n }\n\n .hfp-mute-btn {\n background: none;\n border: none;\n color: var(--hfp-color, #fff);\n cursor: pointer;\n padding: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n flex-shrink: 0;\n }\n\n .hfp-mute-btn:hover {\n opacity: 0.8;\n }\n\n .hfp-mute-btn svg,\n .hfp-mute-btn svg * {\n pointer-events: none;\n }\n\n .hfp-volume-slider-wrap {\n width: 0;\n overflow: hidden;\n transition: width 0.2s ease;\n display: flex;\n align-items: center;\n }\n\n .hfp-volume-wrap:hover .hfp-volume-slider-wrap {\n width: 64px;\n }\n\n .hfp-volume-slider {\n width: 56px;\n height: var(--hfp-scrubber-height, 4px);\n background: var(--hfp-scrubber-bg, rgba(255, 255, 255, 0.3));\n border-radius: var(--hfp-scrubber-radius, 2px);\n cursor: pointer;\n position: relative;\n overflow: hidden;\n margin-left: 4px;\n margin-right: 4px;\n }\n\n .hfp-volume-fill {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n background: var(--hfp-accent, #fff);\n pointer-events: none;\n }\n`;\n\n// Play glyph: the right-hand blade from the HyperFrames favicon, framed to its\n// bounding box so it fills the icon. Points right, like a play triangle.\nexport const PLAY_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"46 21 54 56\" fill=\"currentColor\"><path d=\"M87.5129 57.5141L56.9696 73.5433C52.8371 75.7098 48.7046 73.2553 49.6688 69.2104L58.9483 30.1391C59.9125 26.0942 65.2097 23.6397 68.3154 25.8062L91.2447 41.8354C96.4668 45.4796 94.4631 53.8699 87.5129 57.5141Z\"/></svg>`;\nexport const PAUSE_ICON = `<svg width=\"24\" height=\"24\" viewBox=\"0 0 18 18\" fill=\"currentColor\"><rect x=\"3\" y=\"2\" width=\"4\" height=\"14\"/><rect x=\"11\" y=\"2\" width=\"4\" height=\"14\"/></svg>`;\nexport const VOLUME_HIGH_ICON = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3 9v6h4l5 5V4L7 9H3z\"/><path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z\"/><path d=\"M14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z\"/></svg>`;\nexport const VOLUME_LOW_ICON = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3 9v6h4l5 5V4L7 9H3z\"/><path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z\"/></svg>`;\nexport const VOLUME_MUTED_ICON = `<svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M3 9v6h4l5 5V4L7 9H3z\"/><path d=\"M16.5 12c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z\" opacity=\"0.3\"/><line x1=\"18\" y1=\"7\" x2=\"14\" y2=\"17\" stroke=\"currentColor\" stroke-width=\"2\"/></svg>`;\n\n/**\n * Process-wide cache for the constructed PLAYER_STYLES sheet. Lazy so the\n * module stays SSR-safe (CSSStyleSheet is window-scoped) and so a single\n * sheet can be shared across every shadow root via `adoptedStyleSheets` —\n * the studio thumbnail grid renders dozens of players, and avoiding N\n * duplicate `<style>` parses + style-recalc invalidations is the win here.\n *\n * `null` after a failed construction attempt = \"fall back forever in this\n * process\" (the usual cause is a missing constructor in older runtimes;\n * retrying every call would just throw the same way).\n */\nlet sharedSheet: CSSStyleSheet | null | undefined;\n\n/**\n * Returns the shared player stylesheet, or `null` if constructable\n * stylesheets aren't available in this environment.\n *\n * The result is memoized for the life of the module — every shadow root\n * adopts the same `CSSStyleSheet` instance.\n */\nexport function getSharedPlayerStyleSheet(): CSSStyleSheet | null {\n if (sharedSheet !== undefined) return sharedSheet;\n\n if (typeof CSSStyleSheet === \"undefined\") {\n sharedSheet = null;\n return null;\n }\n\n try {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(PLAYER_STYLES);\n sharedSheet = sheet;\n return sheet;\n } catch {\n sharedSheet = null;\n return null;\n }\n}\n\n/**\n * Internal hook for tests to clear the memoized sheet. Not part of the\n * public API.\n */\nexport function _resetSharedPlayerStyleSheet(): void {\n sharedSheet = undefined;\n}\n\n/**\n * Install PLAYER_STYLES into a player shadow root. Prefers the shared\n * constructable stylesheet (one parse, one rule tree, N adopters) and\n * falls back to a per-instance `<style>` element when the host runtime\n * lacks `adoptedStyleSheets` support.\n *\n * Idempotent: re-applying to a root that already adopts the shared sheet\n * is a no-op. Pre-existing adopted sheets are preserved (we append, never\n * replace), so callers further up the chain can keep their styles.\n */\nexport function applyPlayerStyles(shadow: ShadowRoot): void {\n const sheet = getSharedPlayerStyleSheet();\n const adopted = (shadow as ShadowRoot & { adoptedStyleSheets?: CSSStyleSheet[] })\n .adoptedStyleSheets;\n\n if (sheet && Array.isArray(adopted)) {\n if (!adopted.includes(sheet)) {\n (shadow as ShadowRoot & { adoptedStyleSheets: CSSStyleSheet[] }).adoptedStyleSheets = [\n ...adopted,\n sheet,\n ];\n }\n return;\n }\n\n const style = document.createElement(\"style\");\n style.textContent = PLAYER_STYLES;\n shadow.appendChild(style);\n}\n","import {\n PLAY_ICON,\n PAUSE_ICON,\n VOLUME_HIGH_ICON,\n VOLUME_LOW_ICON,\n VOLUME_MUTED_ICON,\n} from \"./styles.js\";\n\nexport interface ControlsCallbacks {\n onPlay: () => void;\n onPause: () => void;\n onSeek: (fraction: number) => void;\n /** Scrub drag started (mousedown/touchstart on the scrubber). */\n onScrubStart?: () => void;\n /** Scrub drag ended (mouseup/touchend). */\n onScrubEnd?: () => void;\n onSpeedChange: (speed: number) => void;\n onMuteToggle: () => void;\n onVolumeChange: (volume: number) => void;\n}\n\n/** Default logarithmic speed presets — each step roughly doubles/halves. */\nexport const SPEED_PRESETS = [0.25, 0.5, 1, 1.5, 2, 4] as const;\n\nexport interface ControlsOptions {\n /** Speed presets shown in the menu. Defaults to SPEED_PRESETS. */\n speedPresets?: readonly number[];\n /**\n * When true, the volume controls (mute button + volume slider) are hidden so\n * the viewer cannot change the audio state. Backs the `audio-locked`\n * attribute on `<hyperframes-player>`, which enforces host-mandated silent\n * playback (e.g. a HyperFrames project embedded in a chat host). Toggleable\n * at runtime via the returned `setVolumeControlsHidden`.\n */\n audioLocked?: boolean;\n}\n\nexport function formatSpeed(speed: number): string {\n return Number.isInteger(speed) ? `${speed}x` : `${speed}x`;\n}\n\nexport function formatTime(seconds: number): string {\n // Handle non-finite values gracefully\n if (!Number.isFinite(seconds) || seconds < 0) {\n return \"0:00\";\n }\n const s = Math.floor(seconds);\n const m = Math.floor(s / 60);\n const sec = s % 60;\n return `${m}:${sec.toString().padStart(2, \"0\")}`;\n}\n\nexport function createControls(\n parent: ShadowRoot | HTMLElement,\n callbacks: ControlsCallbacks,\n options: ControlsOptions = {},\n): {\n updateTime: (current: number, duration: number) => void;\n updatePlaying: (playing: boolean) => void;\n updateSpeed: (speed: number) => void;\n updateMuted: (muted: boolean) => void;\n updateVolume: (volume: number) => void;\n setVolumeControlsHidden: (hidden: boolean) => void;\n show: () => void;\n hide: () => void;\n destroy: () => void;\n} {\n const presets = options.speedPresets ?? SPEED_PRESETS;\n\n const controls = document.createElement(\"div\");\n controls.className = \"hfp-controls\";\n // Keep overlay interactions from falling through to the host-level click toggle.\n controls.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n });\n\n const playBtn = document.createElement(\"button\");\n playBtn.className = \"hfp-play-btn\";\n playBtn.type = \"button\";\n // Both glyphs stay in the DOM, stacked; toggling `hfp-playing` crossfade-morphs\n // between them (see styles.ts) instead of swapping innerHTML, which would kill\n // the transition.\n playBtn.innerHTML = `<span class=\"hfp-ico hfp-ico-play\">${PLAY_ICON}</span><span class=\"hfp-ico hfp-ico-pause\">${PAUSE_ICON}</span>`;\n playBtn.setAttribute(\"aria-label\", \"Play\");\n\n const scrubber = document.createElement(\"div\");\n scrubber.className = \"hfp-scrubber\";\n const progress = document.createElement(\"div\");\n progress.className = \"hfp-progress\";\n progress.style.width = \"0%\";\n scrubber.appendChild(progress);\n\n const time = document.createElement(\"span\");\n time.className = \"hfp-time\";\n time.textContent = \"0:00 / 0:00\";\n\n const speedWrap = document.createElement(\"div\");\n speedWrap.className = \"hfp-speed-wrap\";\n\n const speedBtn = document.createElement(\"button\");\n speedBtn.className = \"hfp-speed-btn\";\n speedBtn.type = \"button\";\n speedBtn.textContent = \"1x\";\n speedBtn.setAttribute(\"aria-label\", \"Playback speed\");\n\n const speedMenu = document.createElement(\"div\");\n speedMenu.className = \"hfp-speed-menu\";\n speedMenu.setAttribute(\"role\", \"menu\");\n for (const preset of presets) {\n const item = document.createElement(\"button\");\n item.className = \"hfp-speed-option\";\n item.type = \"button\";\n item.setAttribute(\"role\", \"menuitem\");\n item.dataset.speed = String(preset);\n item.textContent = formatSpeed(preset);\n if (preset === 1) item.classList.add(\"hfp-active\");\n speedMenu.appendChild(item);\n }\n\n speedWrap.appendChild(speedMenu);\n speedWrap.appendChild(speedBtn);\n\n const volumeWrap = document.createElement(\"div\");\n volumeWrap.className = \"hfp-volume-wrap\";\n\n const muteBtn = document.createElement(\"button\");\n muteBtn.className = \"hfp-mute-btn\";\n muteBtn.type = \"button\";\n muteBtn.innerHTML = VOLUME_HIGH_ICON;\n muteBtn.setAttribute(\"aria-label\", \"Mute\");\n\n const volumeSliderWrap = document.createElement(\"div\");\n volumeSliderWrap.className = \"hfp-volume-slider-wrap\";\n\n const volumeSlider = document.createElement(\"div\");\n volumeSlider.className = \"hfp-volume-slider\";\n volumeSlider.setAttribute(\"role\", \"slider\");\n volumeSlider.setAttribute(\"aria-label\", \"Volume\");\n volumeSlider.setAttribute(\"aria-valuemin\", \"0\");\n volumeSlider.setAttribute(\"aria-valuemax\", \"100\");\n volumeSlider.setAttribute(\"aria-valuenow\", \"100\");\n volumeSlider.tabIndex = 0;\n const volumeFill = document.createElement(\"div\");\n volumeFill.className = \"hfp-volume-fill\";\n volumeFill.style.width = \"100%\";\n volumeSlider.appendChild(volumeFill);\n volumeSliderWrap.appendChild(volumeSlider);\n\n volumeWrap.appendChild(volumeSliderWrap);\n volumeWrap.appendChild(muteBtn);\n\n // Audio-locked: hide the whole volume control (mute toggle + slider) so the\n // viewer has no UI path to turn sound on. The player still mutes the media\n // independently via the `muted` attribute; this only removes the controls.\n if (options.audioLocked) volumeWrap.style.display = \"none\";\n\n controls.appendChild(playBtn);\n controls.appendChild(scrubber);\n controls.appendChild(time);\n controls.appendChild(volumeWrap);\n controls.appendChild(speedWrap);\n parent.appendChild(controls);\n\n let isPlaying = false;\n let isMuted = false;\n let currentVolume = 1;\n let hideTimeout: ReturnType<typeof setTimeout> | null = null;\n let speedIndex = presets.indexOf(1); // start at 1x\n if (speedIndex === -1) speedIndex = 0;\n\n const getVolumeIcon = (muted: boolean, volume: number): string => {\n if (muted) return VOLUME_MUTED_ICON;\n if (volume === 0) return VOLUME_LOW_ICON;\n if (volume < 0.5) return VOLUME_LOW_ICON;\n return VOLUME_HIGH_ICON;\n };\n\n playBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n if (isPlaying) callbacks.onPause();\n else callbacks.onPlay();\n });\n\n muteBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n callbacks.onMuteToggle();\n });\n\n let volumeScrubbing = false;\n\n const handleVolumeAt = (clientX: number) => {\n const rect = volumeSlider.getBoundingClientRect();\n const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n currentVolume = fraction;\n volumeFill.style.width = `${fraction * 100}%`;\n volumeSlider.setAttribute(\"aria-valuenow\", String(Math.round(fraction * 100)));\n if (isMuted && fraction > 0) callbacks.onMuteToggle();\n muteBtn.innerHTML = getVolumeIcon(isMuted, fraction);\n callbacks.onVolumeChange(fraction);\n };\n\n volumeSlider.addEventListener(\"mousedown\", (e) => {\n e.stopPropagation();\n volumeScrubbing = true;\n handleVolumeAt(e.clientX);\n });\n const onVolumeMouseMove = (e: MouseEvent) => {\n if (volumeScrubbing) handleVolumeAt(e.clientX);\n };\n const onVolumeMouseUp = () => {\n volumeScrubbing = false;\n };\n document.addEventListener(\"mousemove\", onVolumeMouseMove);\n document.addEventListener(\"mouseup\", onVolumeMouseUp);\n\n volumeSlider.addEventListener(\n \"touchstart\",\n (e) => {\n volumeScrubbing = true;\n const touch = e.touches[0];\n if (touch) handleVolumeAt(touch.clientX);\n },\n { passive: true },\n );\n const onVolumeTouchMove = (e: TouchEvent) => {\n if (volumeScrubbing) {\n const touch = e.touches[0];\n if (touch) handleVolumeAt(touch.clientX);\n }\n };\n const onVolumeTouchEnd = () => {\n volumeScrubbing = false;\n };\n document.addEventListener(\"touchmove\", onVolumeTouchMove, { passive: true });\n document.addEventListener(\"touchend\", onVolumeTouchEnd);\n\n const VOLUME_STEP = 0.05;\n volumeSlider.addEventListener(\"keydown\", (e) => {\n let newVol = currentVolume;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowUp\") {\n newVol = Math.min(1, currentVolume + VOLUME_STEP);\n } else if (e.key === \"ArrowLeft\" || e.key === \"ArrowDown\") {\n newVol = Math.max(0, currentVolume - VOLUME_STEP);\n } else {\n return;\n }\n e.preventDefault();\n e.stopPropagation();\n currentVolume = newVol;\n volumeFill.style.width = `${newVol * 100}%`;\n volumeSlider.setAttribute(\"aria-valuenow\", String(Math.round(newVol * 100)));\n if (isMuted && newVol > 0) callbacks.onMuteToggle();\n muteBtn.innerHTML = getVolumeIcon(isMuted, newVol);\n callbacks.onVolumeChange(newVol);\n });\n\n const setActiveOption = (speed: number) => {\n for (const opt of speedMenu.querySelectorAll(\".hfp-speed-option\")) {\n opt.classList.toggle(\"hfp-active\", (opt as HTMLElement).dataset.speed === String(speed));\n }\n };\n\n speedBtn.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const isOpen = speedMenu.classList.toggle(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", String(isOpen));\n });\n\n speedMenu.addEventListener(\"click\", (e) => {\n e.stopPropagation();\n const target = (e.target as HTMLElement).closest(\".hfp-speed-option\") as HTMLElement | null;\n if (!target) return;\n const newSpeed = parseFloat(target.dataset.speed!);\n speedIndex = presets.indexOf(newSpeed);\n speedBtn.textContent = formatSpeed(newSpeed);\n setActiveOption(newSpeed);\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n callbacks.onSpeedChange(newSpeed);\n });\n\n // Close menu when clicking outside\n const onDocClick = () => {\n speedMenu.classList.remove(\"hfp-open\");\n speedBtn.setAttribute(\"aria-expanded\", \"false\");\n };\n document.addEventListener(\"click\", onDocClick);\n\n const handleScrubAt = (clientX: number) => {\n const rect = scrubber.getBoundingClientRect();\n const fraction = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n callbacks.onSeek(fraction);\n };\n\n let scrubbing = false;\n\n scrubber.addEventListener(\"mousedown\", (e) => {\n e.stopPropagation();\n scrubbing = true;\n callbacks.onScrubStart?.();\n handleScrubAt(e.clientX);\n });\n const onMouseMove = (e: MouseEvent) => {\n if (scrubbing) handleScrubAt(e.clientX);\n };\n const onMouseUp = () => {\n if (scrubbing) {\n scrubbing = false;\n callbacks.onScrubEnd?.();\n }\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n\n scrubber.addEventListener(\n \"touchstart\",\n (e) => {\n scrubbing = true;\n callbacks.onScrubStart?.();\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n },\n { passive: true },\n );\n const onTouchMove = (e: TouchEvent) => {\n if (scrubbing) {\n const touch = e.touches[0];\n if (touch) handleScrubAt(touch.clientX);\n }\n };\n const onTouchEnd = () => {\n if (scrubbing) {\n scrubbing = false;\n callbacks.onScrubEnd?.();\n }\n };\n document.addEventListener(\"touchmove\", onTouchMove, { passive: true });\n document.addEventListener(\"touchend\", onTouchEnd);\n\n const startHideTimer = () => {\n if (hideTimeout) clearTimeout(hideTimeout);\n hideTimeout = setTimeout(() => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n }, 3000);\n };\n\n const host = parent instanceof ShadowRoot ? (parent.host as HTMLElement) : parent;\n const onHostMouseMove = () => {\n controls.classList.remove(\"hfp-hidden\");\n startHideTimer();\n };\n const onHostMouseLeave = () => {\n if (isPlaying) controls.classList.add(\"hfp-hidden\");\n };\n host.addEventListener(\"mousemove\", onHostMouseMove);\n host.addEventListener(\"mouseleave\", onHostMouseLeave);\n\n return {\n updateTime(current: number, duration: number) {\n // Defensive: source should already clamp, but guard here so the UI never overflows.\n const clampedCurrent = duration > 0 ? Math.min(current, duration) : current;\n const pct = duration > 0 ? (clampedCurrent / duration) * 100 : 0;\n progress.style.width = `${pct}%`;\n time.textContent = `${formatTime(clampedCurrent)} / ${formatTime(duration)}`;\n },\n updatePlaying(playing: boolean) {\n isPlaying = playing;\n playBtn.classList.toggle(\"hfp-playing\", playing);\n playBtn.setAttribute(\"aria-label\", playing ? \"Pause\" : \"Play\");\n if (playing) startHideTimer();\n else controls.classList.remove(\"hfp-hidden\");\n },\n updateSpeed(speed: number) {\n const idx = presets.indexOf(speed);\n if (idx !== -1) speedIndex = idx;\n speedBtn.textContent = formatSpeed(speed);\n setActiveOption(speed);\n },\n updateMuted(muted: boolean) {\n isMuted = muted;\n muteBtn.innerHTML = getVolumeIcon(muted, currentVolume);\n muteBtn.setAttribute(\"aria-label\", muted ? \"Unmute\" : \"Mute\");\n },\n updateVolume(volume: number) {\n currentVolume = volume;\n volumeFill.style.width = `${volume * 100}%`;\n volumeSlider.setAttribute(\"aria-valuenow\", String(Math.round(volume * 100)));\n muteBtn.innerHTML = getVolumeIcon(isMuted, volume);\n },\n setVolumeControlsHidden(hidden: boolean) {\n volumeWrap.style.display = hidden ? \"none\" : \"\";\n },\n show() {\n controls.style.display = \"\";\n },\n hide() {\n controls.style.display = \"none\";\n },\n destroy() {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.removeEventListener(\"touchmove\", onTouchMove);\n document.removeEventListener(\"touchend\", onTouchEnd);\n document.removeEventListener(\"mousemove\", onVolumeMouseMove);\n document.removeEventListener(\"mouseup\", onVolumeMouseUp);\n document.removeEventListener(\"touchmove\", onVolumeTouchMove);\n document.removeEventListener(\"touchend\", onVolumeTouchEnd);\n document.removeEventListener(\"click\", onDocClick);\n host.removeEventListener(\"mousemove\", onHostMouseMove);\n host.removeEventListener(\"mouseleave\", onHostMouseLeave);\n if (hideTimeout) clearTimeout(hideTimeout);\n controls.remove();\n },\n };\n}\n","/**\n * Helpers for wiring the player's optional UI elements: the playback controls\n * bar, the poster image overlay, and input-filtering utilities.\n *\n * Extracted from the web component so the setup logic doesn't inflate the\n * class body. These are pure imperative DOM operations; they carry no\n * persistent state beyond what the caller tracks.\n */\n\nimport { createControls, type ControlsCallbacks, type ControlsOptions } from \"./controls.js\";\n\n/**\n * Create the playback controls overlay and attach it to `parent`.\n * Returns the controls API object. A no-op guard (returns the existing API)\n * must be enforced by the caller — this function always constructs.\n */\nexport function setupControls(\n parent: ShadowRoot,\n muted: boolean,\n volume: number,\n speedPresetsAttr: string | null,\n callbacks: ControlsCallbacks,\n audioLocked = false,\n): ReturnType<typeof createControls> {\n const speedPresets = speedPresetsAttr\n ? speedPresetsAttr\n .split(\",\")\n .map(Number)\n .filter((n) => !isNaN(n) && n > 0)\n : undefined;\n const options: ControlsOptions = {\n ...(speedPresets ? { speedPresets } : {}),\n audioLocked,\n };\n const api = createControls(parent, callbacks, options);\n api.updateMuted(muted);\n api.updateVolume(volume);\n return api;\n}\n\n/**\n * Set up or remove the poster image element in `parent`.\n *\n * - When `posterUrl` is non-empty, creates the `<img>` if needed and sets src.\n * - When `posterUrl` is null/empty, removes the existing element (if any).\n *\n * Returns the current poster element (possibly newly created) or `null`.\n */\nexport function setupPoster(\n parent: ShadowRoot,\n posterUrl: string | null,\n existing: HTMLImageElement | null,\n): HTMLImageElement | null {\n if (!posterUrl) {\n existing?.remove();\n return null;\n }\n if (!existing) {\n existing = document.createElement(\"img\");\n existing.className = \"hfp-poster\";\n parent.appendChild(existing);\n }\n existing.src = posterUrl;\n return existing;\n}\n\n/**\n * Returns `true` when `event` originated inside an `hfp-controls` element.\n * Used to prevent the bare-player-surface click handler from double-firing\n * when the user clicks an overlay control button.\n */\nexport function isControlsClick(event: Event): boolean {\n return event\n .composedPath()\n .some((t) => t instanceof HTMLElement && t.classList.contains(\"hfp-controls\"));\n}\n","/**\n * DOM setup helpers for the player's shadow root.\n *\n * Keeps constructor boilerplate out of the web component class body.\n */\n\n// Cached Constructable Stylesheet shared across all player instances.\nlet _sharedSheet: CSSStyleSheet | null = null;\n\n/**\n * Adopt `cssText` into `shadow` via a shared Constructable Stylesheet when the\n * browser supports it, falling back to a `<style>` element injection. The sheet\n * is cached on first creation and reused across all player instances.\n */\nexport function adoptShadowStyles(shadow: ShadowRoot, cssText: string): void {\n if (typeof CSSStyleSheet !== \"undefined\") {\n try {\n if (!_sharedSheet) {\n _sharedSheet = new CSSStyleSheet();\n _sharedSheet.replaceSync(cssText);\n }\n shadow.adoptedStyleSheets = [_sharedSheet];\n return;\n } catch {\n /* fallthrough */\n }\n }\n const style = document.createElement(\"style\");\n style.textContent = cssText;\n shadow.appendChild(style);\n}\n\n/**\n * Creates and configures the iframe element that hosts the composition, plus\n * the wrapper container div. Returns handles to both so the constructor can\n * attach them to the shadow root and track references without inlining the\n * boilerplate.\n */\nexport function createCompositionIframe(): {\n container: HTMLDivElement;\n iframe: HTMLIFrameElement;\n} {\n const container = document.createElement(\"div\");\n container.className = \"hfp-container\";\n\n const iframe = document.createElement(\"iframe\");\n iframe.className = \"hfp-iframe\";\n iframe.sandbox.add(\"allow-scripts\", \"allow-same-origin\");\n iframe.allow = \"autoplay; fullscreen\";\n iframe.referrerPolicy = \"no-referrer\";\n iframe.title = \"HyperFrames Composition\";\n\n container.appendChild(iframe);\n return { container, iframe };\n}\n\n/**\n * Scale the iframe so the composition fits inside the player element while\n * preserving aspect ratio. No-ops when the player has no painted size yet.\n * Returns whether the transform was actually applied, so callers can tell a\n * real no-op (still 0×0) apart from a successful rescale.\n */\nexport function scaleIframeToFit(\n playerElement: HTMLElement,\n iframe: HTMLIFrameElement,\n compositionWidth: number,\n compositionHeight: number,\n): boolean {\n const w = playerElement.offsetWidth;\n const h = playerElement.offsetHeight;\n if (w === 0 || h === 0) return false;\n const scale = Math.min(w / compositionWidth, h / compositionHeight);\n iframe.style.width = `${compositionWidth}px`;\n iframe.style.height = `${compositionHeight}px`;\n iframe.style.transform = `translate(-50%, -50%) scale(${scale})`;\n return true;\n}\n","/**\n * rAF-based clock that polls a `DirectTimelineAdapter` for current time and\n * drives the player's time/playback-ended callbacks.\n *\n * Used for same-origin standalone GSAP compositions that expose\n * `window.__timelines` but have no runtime bridge — the player drives them\n * directly through the adapter instead of going through postMessage.\n */\n\nimport type { DirectTimelineAdapter } from \"./timeline-adapters.js\";\n\nconst UI_UPDATE_INTERVAL_MS = 100;\nconst CURRENT_TIME_ROUNDING_S = 1e-3;\n\nexport interface ClockCallbacks {\n /** Called every ~100ms and on completion with the current time. */\n onTimeUpdate: (currentTime: number, duration: number) => void;\n /** Called when playback reaches the end. Return true to loop (seek+play). */\n onEnded: () => boolean;\n /** Get the current loop flag. */\n getLoop: () => boolean;\n /** Trigger a seek-then-play loop restart. */\n restart: () => void;\n /** Notify that playback has paused (from the timeline side). */\n onPaused: () => void;\n}\n\n// A range ending inside the film stops a check early, so the frame past it does not show.\nfunction reachedStop(\n time: number,\n lookAhead: number,\n stop: { end: number; shown: number },\n): boolean {\n if (stop.end <= 0) return false;\n const early = stop.shown < stop.end && lookAhead > 0 ? lookAhead + CURRENT_TIME_ROUNDING_S : 0;\n return time + early >= stop.end;\n}\n\nexport class DirectTimelineClock {\n private _raf: number | null = null;\n private _lastUpdateMs = 0;\n private _tick: (() => void) | null = null;\n\n constructor(private readonly _callbacks: ClockCallbacks) {}\n\n start(\n timeline: DirectTimelineAdapter,\n getCurrentTime: () => number,\n getDuration: () => number,\n isPaused: () => boolean,\n getStop: () => { end: number; shown: number },\n ): void {\n this.stop();\n let lastTime: number | null = null;\n let lastStep = 0;\n let lookAhead = 0;\n\n const tick = () => {\n if (isPaused()) {\n this._raf = null;\n return;\n }\n\n let currentTime: number;\n try {\n currentTime = timeline.time();\n } catch {\n this._raf = null;\n return;\n }\n\n const duration = getDuration();\n const stop = getStop();\n if (stop.end > 0) currentTime = Math.min(currentTime, stop.end);\n\n // The smaller of the last two moves, so one slow frame or a jump does not end the range early.\n const step = lastTime === null ? 0 : currentTime - lastTime;\n if (step !== 0) {\n lookAhead = Math.min(step, lastStep);\n lastStep = step;\n }\n lastTime = currentTime;\n const completedPlayback = reachedStop(currentTime, lookAhead, stop);\n if (completedPlayback) currentTime = stop.shown;\n const now = performance.now();\n\n if (now - this._lastUpdateMs > UI_UPDATE_INTERVAL_MS || completedPlayback) {\n this._lastUpdateMs = now;\n this._callbacks.onTimeUpdate(currentTime, duration);\n }\n\n if (completedPlayback) {\n if (this._callbacks.getLoop()) {\n this._callbacks.restart();\n return;\n }\n try {\n timeline.pause();\n if (stop.shown < stop.end) timeline.seek(stop.shown, false);\n } catch {\n /* ignore */\n }\n this._callbacks.onPaused();\n this._raf = null;\n return;\n }\n\n this._raf = requestAnimationFrame(tick);\n };\n\n this._tick = tick;\n this._raf = requestAnimationFrame(tick);\n }\n\n /** Runs one check now, for a hidden tab, which runs no animation frames. */\n poll(): void {\n if (this._raf === null || !this._tick) return;\n cancelAnimationFrame(this._raf);\n this._tick();\n }\n\n stop(): void {\n if (this._raf === null) return;\n cancelAnimationFrame(this._raf);\n this._raf = null;\n }\n\n get isRunning(): boolean {\n return this._raf !== null;\n }\n}\n","/**\n * Internal helper for scoping the player's media MutationObserver to the\n * composition tree inside the iframe.\n *\n * Not part of the package's public API — kept in its own module so the\n * decision logic can be exercised by unit tests without exposing it through\n * the player entry point.\n */\n\n/**\n * Pick the elements inside `doc` that the media MutationObserver should\n * attach to.\n *\n * Compositions mount inside `[data-composition-id]` host elements — the\n * runtime root and any sub-composition hosts that `compositionLoader` writes\n * into them. Watching only those hosts (with `subtree: true`) catches every\n * late-arriving timed media element from sub-composition activation, while\n * filtering out churn from analytics tags, runtime telemetry markers, and\n * other out-of-host nodes that the runtime appends straight to `<body>`\n * during bootstrap.\n *\n * Nested hosts are filtered out — they're already covered by their nearest\n * host ancestor's subtree observation, so observing them too would deliver\n * each callback twice and double-count adoption work.\n *\n * Falls back to `[doc.body]` when no composition hosts are present, which\n * preserves the previous behavior for documents that aren't yet (or never\n * will be) composition-structured. Returns an empty array when neither a\n * host nor a body is available — the caller should treat that as \"nothing\n * to observe\".\n *\n * When the scoped path is taken but the body still carries timed media\n * outside every host, a `console.warn` fires once per call as a forensic\n * signal: the new scope skips that media, so any `<audio data-start>` /\n * `<video data-start>` injected at body level will silently never get a\n * parent-frame proxy. Today every runtime path appends inside a host so\n * this branch shouldn't trip; if it does, the warn surfaces the drift\n * immediately rather than presenting as a missing-audio bug downstream.\n */\nexport function selectMediaObserverTargets(doc: Document): Element[] {\n const all = Array.from(doc.querySelectorAll<Element>(\"[data-composition-id]\"));\n if (all.length === 0) {\n return doc.body ? [doc.body] : [];\n }\n\n const topLevel: Element[] = [];\n for (const el of all) {\n if (!hasCompositionAncestor(el)) {\n topLevel.push(el);\n }\n }\n\n warnOnUnscopedTimedMedia(doc);\n return topLevel;\n}\n\n/**\n * Forensic guard: with composition hosts present the observer attaches only\n * to those subtrees, so any timed media sitting at body level (or under a\n * non-host wrapper) is invisible to the adoption pipeline. Walk the body for\n * `[data-start]` audio/video that has no `[data-composition-id]` ancestor\n * and emit a single `console.warn` listing the orphans. The walk is cheap\n * (one `querySelectorAll` over a typed selector + a `closest` per match)\n * and only runs on the scoped path, so the no-host fallback retains its\n * legacy behavior with zero extra work.\n */\nfunction warnOnUnscopedTimedMedia(doc: Document): void {\n const body = doc.body;\n if (!body) return;\n if (typeof console === \"undefined\" || typeof console.warn !== \"function\") return;\n\n const candidates = body.querySelectorAll<HTMLMediaElement>(\n \"audio[data-start], video[data-start]\",\n );\n if (candidates.length === 0) return;\n\n const orphans: HTMLMediaElement[] = [];\n for (const el of candidates) {\n if (!el.closest(\"[data-composition-id]\")) orphans.push(el);\n }\n if (orphans.length === 0) return;\n\n console.warn(\n \"[hyperframes-player] selectMediaObserverTargets: composition hosts are present, \" +\n `but ${orphans.length} body-level timed media element(s) sit outside every ` +\n \"[data-composition-id] subtree and will not be observed. Move them inside a \" +\n \"composition host or the parent-frame proxy will never adopt them.\",\n orphans,\n );\n}\n\nfunction hasCompositionAncestor(el: Element): boolean {\n let cursor = el.parentElement;\n while (cursor) {\n if (cursor.hasAttribute(\"data-composition-id\")) return true;\n cursor = cursor.parentElement;\n }\n return false;\n}\n","/** Plain-field clip facts shared by the Ask-agent prompt, `studio_look` and `hyperframes timeline`. */\n\nexport interface ClipLane {\n /** `volume`, or `fx.<nodeId>.<param>`. */\n target: string;\n /** `t` is seconds from the start of the clip, not the composition. */\n points: { t: number; v: number }[];\n}\n\nexport interface ClipFact {\n id: string;\n label: string | null;\n kind: string;\n start: number;\n duration: number;\n end: number;\n /** The `data-track-index` as written in the source file. */\n trackIndex: number;\n src: string | null;\n sourceFile: string | null;\n /** `null` when `data-volume` is not authored (the clip plays at 1). */\n volume: number | null;\n lanes: ClipLane[];\n /** `null` at normal speed: not authored, or 1 (the manifest defaults it to 1). */\n playbackRate: number | null;\n audioGroup: string | null;\n role: string | null;\n}\n\nexport const byStart = (a: ClipFact, b: ClipFact) =>\n a.start - b.start || a.trackIndex - b.trackIndex;\n\nconst roundTo3 = (n: number) => Math.round(n * 1000) / 1000;\n\nconst num = (n: number) => String(roundTo3(n));\n\nexport const instantTolerance = (time: number) => 4 * Number.EPSILON * Math.max(1, Math.abs(time));\n\n/** Float sums like 19.8 + 6.4 miss 26.2 by a rounding step; a few steps, scaled to the time, are one instant. */\nexport function sameInstant(a: number, b: number): boolean {\n if (!Number.isFinite(a) || !Number.isFinite(b)) return a === b;\n return Math.abs(a - b) <= instantTolerance(Math.max(Math.abs(a), Math.abs(b)));\n}\n\nconst isBefore = (a: number, b: number) => a < b && !sameInstant(a, b);\n\n/** For timeline lanes: a zero-length clip inside another still counts, so it gets a lane of its own. */\nexport function spansOverlap(aStart: number, aEnd: number, bStart: number, bEnd: number): boolean {\n return isBefore(aStart, bEnd) && isBefore(bStart, aEnd);\n}\n\nexport function spansShareTime(\n aStart: number,\n aEnd: number,\n bStart: number,\n bEnd: number,\n): boolean {\n return isBefore(Math.max(aStart, bStart), Math.min(aEnd, bEnd));\n}\n\nexport const isInsideSpan = (time: number, start: number, end: number) =>\n isBefore(start, time) && isBefore(time, end);\n\nexport function formatClipLine(clip: ClipFact): string {\n const parts = [\n `${clip.kind} \"${clip.id}\"`,\n clip.src && `src=${clip.src}`,\n `start=${num(clip.start)}`,\n `duration=${num(clip.duration)}`,\n `end=${num(clip.end)}`,\n `track=${clip.trackIndex}`,\n clip.volume !== null && `volume=${num(clip.volume)}`,\n clip.playbackRate !== null && `rate=${num(clip.playbackRate)}`,\n clip.audioGroup && `group=${clip.audioGroup}`,\n clip.role && `role=${clip.role}`,\n clip.sourceFile && `file=${clip.sourceFile}`,\n ...clip.lanes.map(\n (lane) =>\n `${lane.target}-lane=[${lane.points.map((p) => `${num(p.t)}:${num(p.v)}`).join(\", \")}]`,\n ),\n ];\n return `- ${parts.filter(Boolean).join(\" \")}`;\n}\n","import { sameInstant } from \"../clipFacts\";\n\nexport const hasClipStarted = (time: number, start: number) =>\n time >= start || sameInstant(time, start);\n\n/** Half-open: two back-to-back clips never both hold the shared boundary instant, float sums included. */\nexport const isInClipWindow = (time: number, start: number, end: number): boolean =>\n hasClipStarted(time, start) && time < end && !sameInstant(time, end);\n\nconst TERMINAL_EPSILON_SECONDS = 1e-6;\n\n/**\n * Visibility window: half-open, except a clip that runs to the composition's end also owns\n * the terminal instant, so a finished film rests on its last frame instead of going blank.\n */\nexport const isClipVisibleAt = (\n time: number,\n start: number,\n end: number,\n compositionDuration: number,\n): boolean =>\n isInClipWindow(time, start, end) ||\n (hasClipStarted(time, start) &&\n compositionDuration > 0 &&\n end >= compositionDuration - TERMINAL_EPSILON_SECONDS);\n","/** Clip-edge fades: `data-fade-in` / `data-fade-out` in seconds, linear, multiplied on the resolved level. */\n\nexport const HF_AUDIO_FADE_IN_ATTR = \"data-fade-in\";\nexport const HF_AUDIO_FADE_OUT_ATTR = \"data-fade-out\";\n\nexport const HF_AUDIO_FADE_IN_DATA_KEY = HF_AUDIO_FADE_IN_ATTR.slice(\"data-\".length);\nexport const HF_AUDIO_FADE_OUT_DATA_KEY = HF_AUDIO_FADE_OUT_ATTR.slice(\"data-\".length);\n\nexport interface AudioFades {\n /** Seconds from the clip start over which gain rises 0 → 1. */\n fadeIn: number;\n /** Seconds before the clip end over which gain falls 1 → 0. */\n fadeOut: number;\n}\n\nexport const NO_FADES: Readonly<AudioFades> = Object.freeze({ fadeIn: 0, fadeOut: 0 });\n\nfunction finiteFadeSeconds(value: number): number {\n return Number.isFinite(value) && value > 0 ? value : 0;\n}\n\n/** Attribute text as seconds: finite and positive, else 0 (no fade). */\nexport function readFadeSeconds(raw: string | null | undefined): number {\n if (raw == null || raw === \"\") return 0;\n return finiteFadeSeconds(Number.parseFloat(raw));\n}\n\nexport function readElementFades(el: { getAttribute(name: string): string | null }): AudioFades {\n return {\n fadeIn: readFadeSeconds(el.getAttribute(HF_AUDIO_FADE_IN_ATTR)),\n fadeOut: readFadeSeconds(el.getAttribute(HF_AUDIO_FADE_OUT_ATTR)),\n };\n}\n\n/** Scale fades that outrun the clip so they meet inside it. Infinite duration keeps them as authored. */\nexport function clampFadesToDuration(fades: AudioFades, duration: number): AudioFades {\n const fadeIn = finiteFadeSeconds(fades.fadeIn);\n const fadeOut = finiteFadeSeconds(fades.fadeOut);\n if (!Number.isFinite(duration) || duration <= 0) return { fadeIn, fadeOut };\n const total = fadeIn + fadeOut;\n if (total <= duration) return { fadeIn, fadeOut };\n const scale = duration / total;\n return { fadeIn: fadeIn * scale, fadeOut: fadeOut * scale };\n}\n\n/** Gain at `elapsed` into a clip of `duration`. 1 where no fade runs; the two multiply where they meet. */\nexport function fadeGain(elapsed: number, duration: number, fades: AudioFades): number {\n if (!Number.isFinite(elapsed)) return 1;\n const { fadeIn, fadeOut } = clampFadesToDuration(fades, duration);\n let gain = 1;\n if (fadeIn > 0) gain *= clamp01(elapsed / fadeIn);\n if (fadeOut > 0 && Number.isFinite(duration)) gain *= clamp01((duration - elapsed) / fadeOut);\n return gain;\n}\n\n/** Attribute text Studio writes: up to 2 decimals, no trailing zeros. */\nexport function formatFadeSeconds(seconds: number): string {\n const rounded = Math.round(finiteFadeSeconds(seconds) * 100) / 100;\n return Number.isInteger(rounded) ? `${rounded}` : rounded.toFixed(2).replace(/0+$/, \"\");\n}\n\nfunction clamp01(value: number): number {\n if (!Number.isFinite(value) || value < 0) return 0;\n return value > 1 ? 1 : value;\n}\n","export const AUDIBLE_VIDEO_QUALIFIER =\n ':not([muted]):is(:not([data-has-audio]),[data-has-audio=\"true\"])';\n\nexport const AUDIBLE_MEDIA_SELECTOR = `audio[data-start], video[data-start]${AUDIBLE_VIDEO_QUALIFIER}`;\n\nexport function isAudibleVideoElement(el: {\n tagName: string;\n hasAttribute(name: string): boolean;\n getAttribute(name: string): string | null;\n}): boolean {\n if (el.tagName.toLowerCase() !== \"video\" || el.hasAttribute(\"muted\")) return false;\n const declared = el.getAttribute(\"data-has-audio\");\n return declared === null || declared === \"true\";\n}\n\nexport function audibleVideoNeedsWebAudio(fields: {\n volume?: number | null;\n fxChain?: string | null;\n automation?: string | null;\n audioGroup?: string | null;\n}): boolean {\n return (\n (fields.volume ?? 1) > 1 || Boolean(fields.fxChain || fields.automation || fields.audioGroup)\n );\n}\n","import { PREVIEW_RASTER_ATTR } from \"../studioPreviewMark\";\n\nexport const ROOT_CSS_STACKING_CONTEXT_ID = \"css:root\";\n\nconst NON_NONE_STACKING_CONTEXT_PROPERTIES = [\n \"backdrop-filter\",\n \"clip-path\",\n \"filter\",\n \"mask\",\n \"mask-border-source\",\n \"mask-image\",\n \"perspective\",\n \"rotate\",\n \"scale\",\n \"transform\",\n \"translate\",\n \"-webkit-mask-image\",\n] as const;\nconst STACKING_CONTEXT_WILL_CHANGE_PROPERTIES = new Set([\n ...NON_NONE_STACKING_CONTEXT_PROPERTIES,\n \"contain\",\n \"isolation\",\n \"mask-border\",\n \"mix-blend-mode\",\n \"opacity\",\n]);\nconst Z_INDEXED_POSITIONS = new Set([\"absolute\", \"relative\"]);\nconst Z_INDEXED_PARENT_DISPLAYS = new Set([\"flex\", \"inline-flex\", \"grid\", \"inline-grid\"]);\nconst CONTAINMENT_VALUES = new Set([\"layout\", \"paint\", \"strict\", \"content\"]);\nconst STACKING_CONTAINER_TYPES = new Set([\"size\", \"inline-size\"]);\n\nfunction createsPositionedStackingContext(style: CSSStyleDeclaration): boolean {\n return style.position === \"fixed\" || style.position === \"sticky\";\n}\n\nfunction createsZIndexedStackingContext(element: HTMLElement, style: CSSStyleDeclaration): boolean {\n if (style.zIndex === \"auto\" || style.zIndex === \"\") return false;\n if (Z_INDEXED_POSITIONS.has(style.position)) return true;\n const parentDisplay = element.parentElement\n ? element.ownerDocument.defaultView?.getComputedStyle(element.parentElement).display\n : null;\n return parentDisplay != null && Z_INDEXED_PARENT_DISPLAYS.has(parentDisplay);\n}\n\nfunction createsVisualEffectStackingContext(style: CSSStyleDeclaration): boolean {\n const opacity = Number.parseFloat(style.opacity);\n if (Number.isFinite(opacity) && opacity < 1) return true;\n if (style.getPropertyValue(\"isolation\") === \"isolate\") return true;\n const mixBlendMode = style.getPropertyValue(\"mix-blend-mode\");\n if (mixBlendMode && mixBlendMode !== \"normal\") return true;\n return NON_NONE_STACKING_CONTEXT_PROPERTIES.some((property) => {\n const value = style.getPropertyValue(property);\n return value !== \"\" && value !== \"none\";\n });\n}\n\nfunction createsContainmentStackingContext(style: CSSStyleDeclaration): boolean {\n const contain = style.getPropertyValue(\"contain\").split(/\\s+/);\n if (contain.some((value) => CONTAINMENT_VALUES.has(value))) return true;\n return STACKING_CONTAINER_TYPES.has(style.getPropertyValue(\"container-type\"));\n}\n\nfunction createsWillChangeStackingContext(style: CSSStyleDeclaration): boolean {\n return style\n .getPropertyValue(\"will-change\")\n .split(\",\")\n .map((property) => property.trim())\n .some((property) => STACKING_CONTEXT_WILL_CHANGE_PROPERTIES.has(property));\n}\n\nfunction createsCssStackingContext(element: HTMLElement, style: CSSStyleDeclaration): boolean {\n if (element.hasAttribute(PREVIEW_RASTER_ATTR)) return true;\n if (createsPositionedStackingContext(style)) return true;\n if (createsZIndexedStackingContext(element, style)) return true;\n if (createsVisualEffectStackingContext(style)) return true;\n if (createsContainmentStackingContext(style)) return true;\n return createsWillChangeStackingContext(style);\n}\n\n/** Stable within one DOM revision and unique even when authors duplicate ids. */\nfunction cssStackingContextPath(element: Element): string {\n const indexes: number[] = [];\n let cursor: Element | null = element;\n while (cursor?.parentElement) {\n indexes.push(Array.prototype.indexOf.call(cursor.parentElement.children, cursor));\n cursor = cursor.parentElement;\n }\n return `css:${indexes.reverse().join(\".\")}`;\n}\n\n/**\n * Leaf z-index participates in the nearest ANCESTOR stacking context. Exclude\n * the leaf itself: an element that establishes a context still compares its own\n * z-index with siblings in its parent's context.\n */\nexport function resolveCssStackingContextId(node: Element): string {\n const win = node.ownerDocument.defaultView;\n if (!win) return ROOT_CSS_STACKING_CONTEXT_ID;\n let cursor = node.parentElement;\n while (cursor && cursor !== node.ownerDocument.documentElement) {\n try {\n if (createsCssStackingContext(cursor, win.getComputedStyle(cursor))) {\n return cssStackingContextPath(cursor);\n }\n } catch {\n return ROOT_CSS_STACKING_CONTEXT_ID;\n }\n cursor = cursor.parentElement;\n }\n return ROOT_CSS_STACKING_CONTEXT_ID;\n}\n","export const MEDIA_VISUAL_STYLE_PROPERTIES = [\n \"width\",\n \"height\",\n \"top\",\n \"left\",\n \"right\",\n \"bottom\",\n \"inset\",\n \"object-fit\",\n \"object-position\",\n \"z-index\",\n \"opacity\",\n \"visibility\",\n \"filter\",\n \"mix-blend-mode\",\n \"backdrop-filter\",\n \"border-radius\",\n \"overflow\",\n \"clip-path\",\n \"mask\",\n \"mask-image\",\n \"mask-size\",\n \"mask-position\",\n \"mask-repeat\",\n \"transform\",\n \"transform-origin\",\n \"translate\",\n \"rotate\",\n \"scale\",\n \"box-sizing\",\n] as const;\n\nexport type MediaVisualStyleProperty = (typeof MEDIA_VISUAL_STYLE_PROPERTIES)[number];\n\nconst FRAME_BOUNDARY_EPSILON = 1e-3;\n\nfunction safeFps(fps: number): number {\n return Number.isFinite(fps) && fps > 0 ? fps : 30;\n}\n\nfunction safeTime(timeSeconds: number): number {\n return Number.isFinite(timeSeconds) && timeSeconds > 0 ? timeSeconds : 0;\n}\n\nexport function quantizeTimeToFrame(timeSeconds: number, fps: number): number {\n const grid = safeFps(fps);\n const frameIndex = Math.floor(safeTime(timeSeconds) * grid + 1e-9);\n return frameIndex / grid;\n}\n\n/**\n * The grid a deterministic render seek lands on.\n *\n * With no `subFrameDivisions` this is the output frame grid, unchanged. Sub-frame\n * sampling (motion blur) passes an integer subdivision count, which can only refine\n * the output grid, never move a frame-time instant off it.\n *\n * The finer grid rounds where the frame grid floors. A sub-frame time makes a float\n * round trip (the caller builds `ticks / (fps * divisions)`, this recomputes\n * `time * fps * divisions`), and past a few million ticks that error exceeds the\n * floor epsilon, which would silently drop a sample one tick early.\n */\nexport function quantizeSeekTime(\n timeSeconds: number,\n fps: number,\n subFrameDivisions?: number,\n): number {\n const divisions = Number.isInteger(subFrameDivisions) ? (subFrameDivisions as number) : 1;\n if (divisions <= 1) return quantizeTimeToFrame(timeSeconds, fps);\n const grid = safeFps(fps) * divisions;\n return Math.round(safeTime(timeSeconds) * grid) / grid;\n}\n\n/** Snap decimal noise near a frame boundary without moving genuinely fractional timing. */\nexport function snapTimeToFrameBoundary(timeSeconds: number, fps: number): number {\n const grid = safeFps(fps);\n const time = safeTime(timeSeconds);\n const framePosition = time * grid;\n const nearestFrame = Math.round(framePosition);\n return Math.abs(framePosition - nearestFrame) <= FRAME_BOUNDARY_EPSILON\n ? nearestFrame / grid\n : time;\n}\n\n/** The window export shows a clip in: both ends snapped like its seeks, so a hair off a frame counts as on it. */\nexport function exportClipWindow(\n start: number,\n end: number,\n fps: number,\n): { start: number; end: number } {\n return {\n start: snapTimeToFrameBoundary(start, fps),\n end: Number.isFinite(end) ? snapTimeToFrameBoundary(end, fps) : end,\n };\n}\n\nexport function copyMediaVisualStyles(\n targetStyle: CSSStyleDeclaration,\n sourceStyle: CSSStyleDeclaration,\n properties: readonly string[] = MEDIA_VISUAL_STYLE_PROPERTIES,\n): void {\n for (const property of properties) {\n const value = sourceStyle.getPropertyValue(property);\n if (value) {\n targetStyle.setProperty(property, value);\n }\n }\n}\n","export const RUNTIME_PROTOCOL_VERSION = 1 as const;\n\nexport const RUNTIME_FILLER = \"hf-runtime-filler\";\n\nexport const RUNTIME_PROTOCOL_CAPABILITIES = [\n \"seconds-time\",\n \"rational-fps\",\n \"seek-keep-playing\",\n \"composition-manifest-v1\",\n \"runtime-data\",\n \"play-range\",\n] as const;\n\nexport type RuntimeProtocolFps = {\n numerator: number;\n denominator: number;\n};\n\nexport type RuntimeProtocolV1 = {\n protocolVersion: typeof RUNTIME_PROTOCOL_VERSION;\n capabilities: readonly string[];\n fps: RuntimeProtocolFps;\n};\n\nexport type RuntimeProtocolInspection =\n | { status: \"legacy\"; fps: number }\n | { status: \"supported\"; fps: number; metadata: RuntimeProtocolV1 }\n | {\n status: \"unsupported\";\n code: \"unsupported_protocol_version\" | \"invalid_protocol_metadata\";\n receivedVersion: unknown;\n };\n\nfunction greatestCommonDivisor(a: number, b: number): number {\n let left = Math.abs(a);\n let right = Math.abs(b);\n while (right !== 0) {\n const remainder = left % right;\n left = right;\n right = remainder;\n }\n return left || 1;\n}\n\nexport function runtimeProtocolFpsFromNumber(value: number): RuntimeProtocolFps {\n const safe = Number.isFinite(value) && value > 0 ? value : 30;\n const denominator = Number.isInteger(safe) ? 1 : 1_000_000;\n const numerator = Math.round(safe * denominator);\n const divisor = greatestCommonDivisor(numerator, denominator);\n return { numerator: numerator / divisor, denominator: denominator / divisor };\n}\n\nexport function playRangeHoldTime(start: number, end: number, fps: number): number {\n return Math.max(start, (Math.ceil(end * fps - 1e-6) - 1) / fps);\n}\n\nexport function runtimeProtocolFpsToNumber(value: unknown): number | null {\n if (typeof value !== \"object\" || value === null) return null;\n const fps = value as Partial<RuntimeProtocolFps>;\n if (!Number.isFinite(fps.numerator) || !Number.isFinite(fps.denominator)) return null;\n if ((fps.numerator ?? 0) <= 0 || (fps.denominator ?? 0) <= 0) return null;\n return Number(fps.numerator) / Number(fps.denominator);\n}\n\nexport function runtimeProtocolMetadata(fps: number): RuntimeProtocolV1 {\n return {\n protocolVersion: RUNTIME_PROTOCOL_VERSION,\n capabilities: RUNTIME_PROTOCOL_CAPABILITIES,\n fps: runtimeProtocolFpsFromNumber(fps),\n };\n}\n\nexport function frameDisplayScale(frame: HTMLElement): number | null {\n const layoutWidth = frame.offsetWidth;\n const shownWidth = frame.getBoundingClientRect().width;\n return layoutWidth > 0 && shownWidth > 0 ? shownWidth / layoutWidth : null;\n}\n\nfunction hasDeclaredCapabilities(value: unknown): boolean {\n return Array.isArray(value) && value.every((capability) => typeof capability === \"string\");\n}\n\nexport function inspectRuntimeProtocol(value: unknown, legacyFps = 30): RuntimeProtocolInspection {\n if (typeof value !== \"object\" || value === null) return { status: \"legacy\", fps: legacyFps };\n const message = value as Record<string, unknown>;\n if (message.protocolVersion === undefined) return { status: \"legacy\", fps: legacyFps };\n if (message.protocolVersion !== RUNTIME_PROTOCOL_VERSION) {\n return {\n status: \"unsupported\",\n code: \"unsupported_protocol_version\",\n receivedVersion: message.protocolVersion,\n };\n }\n const fps = runtimeProtocolFpsToNumber(message.fps);\n if (fps === null || !hasDeclaredCapabilities(message.capabilities)) {\n return {\n status: \"unsupported\",\n code: \"invalid_protocol_metadata\",\n receivedVersion: message.protocolVersion,\n };\n }\n return {\n status: \"supported\",\n fps,\n metadata: message as RuntimeProtocolV1,\n };\n}\n","import type {\n RuntimeTimelineClip,\n RuntimeTimelineMessage,\n RuntimeTimelineScene,\n RuntimeTimelineLike,\n} from \"./types\";\nimport { stableClipId } from \"./clipTree\";\nimport { findRootCompositionElement, parseCompositionDimension } from \"./compositionDimension\";\nimport {\n AUTHORED_DURATION_ATTR,\n AUTHORED_END_ATTR,\n resolveAuthoredTimingWindow,\n} from \"./authoredTiming\";\nimport { swallow } from \"./diagnostics\";\nimport { readElementPlaybackRate, readElementPlaybackStart } from \"./media\";\nimport {\n parseStrictFiniteTimingNumber,\n resolveMediaElementDurationSeconds,\n resolveNaturalMediaTimelineDuration,\n resolveTimedImageDurationSeconds,\n} from \"./playbackRate\";\nimport { resolveCssStackingContextId } from \"./stackingContext\";\nimport { createRuntimeStartTimeResolver } from \"./startResolver\";\nimport { isClipVisibleAt } from \"./clipWindow\";\nimport { exportClipWindow } from \"../inline-scripts/parityContract\";\nimport { isSceneLikeCompositionId } from \"../slideshow/index.js\";\nimport { COMPOSITION_CONTRACT_VERSION } from \"../compositionContract.js\";\nimport { runtimeProtocolMetadata } from \"./protocol.js\";\nimport { isElementNode, isMediaElement } from \"./domRealm\";\nimport { LOOP_INFLATED_TIMELINE_SECONDS } from \"./compositionLength\";\n\nexport function isRuntimeElementVisibleAt(\n rawNode: HTMLElement,\n options: {\n currentTime: number;\n compositionDuration: number;\n canonicalFps: number;\n exportRenderSeek: boolean;\n timelineRegistry: Record<string, RuntimeTimelineLike | undefined>;\n resolver: ReturnType<typeof createRuntimeStartTimeResolver>;\n },\n): boolean {\n const tag = rawNode.tagName.toLowerCase();\n if (tag === \"script\" || tag === \"style\" || tag === \"link\" || tag === \"meta\") {\n return false;\n }\n\n const isMedia = tag === \"video\" || tag === \"audio\";\n const start = isMedia\n ? options.resolver.resolveMediaStartForElement(rawNode)\n : options.resolver.resolveStartForElement(rawNode, 0);\n let duration = options.resolver.resolveDurationForElement(rawNode);\n const compId = rawNode.getAttribute(\"data-composition-id\");\n if (compId) {\n const compTimeline = options.timelineRegistry[compId];\n const liveDuration =\n compTimeline && typeof compTimeline.duration === \"function\"\n ? Number(compTimeline.duration())\n : null;\n const hasAuthoredTiming =\n rawNode.hasAttribute(\"data-duration\") ||\n rawNode.hasAttribute(\"data-end\") ||\n rawNode.hasAttribute(AUTHORED_DURATION_ATTR) ||\n rawNode.hasAttribute(AUTHORED_END_ATTR);\n if (\n !hasAuthoredTiming &&\n (duration == null || duration <= 0) &&\n liveDuration != null &&\n Number.isFinite(liveDuration) &&\n liveDuration > 0\n ) {\n duration = liveDuration;\n }\n }\n const computedEnd =\n duration != null && duration > 0 ? start + duration : Number.POSITIVE_INFINITY;\n // Export seeks snap to frame boundaries; interactive visibility uses authored seconds.\n const clipWindow = options.exportRenderSeek\n ? exportClipWindow(start, computedEnd, options.canonicalFps)\n : { start, end: computedEnd };\n return isClipVisibleAt(\n options.currentTime,\n clipWindow.start,\n clipWindow.end,\n options.compositionDuration,\n );\n}\n\nfunction parseNum(value: string | null | undefined): number | null {\n return parseStrictFiniteTimingNumber(value);\n}\n\nfunction parseElementDurationAttr(element: Element): number | null {\n const publicDuration = element.getAttribute(\"data-duration\");\n const authoredDuration = element.getAttribute(AUTHORED_DURATION_ATTR);\n const resolved = resolveAuthoredTimingWindow({\n start: 0,\n duration: publicDuration,\n authoredDuration,\n })?.duration;\n if (resolved != null) return resolved;\n const hasExplicitNonpositive = [publicDuration, authoredDuration]\n .map(parseNum)\n .some((duration) => duration != null && duration <= 0);\n return hasExplicitNonpositive ? 0 : null;\n}\n\nfunction parseElementEndAttr(element: Element): number | null {\n return (\n resolveAuthoredTimingWindow({\n start: 0,\n end: element.getAttribute(\"data-end\"),\n authoredEnd: element.getAttribute(AUTHORED_END_ATTR),\n })?.end ?? null\n );\n}\n\nfunction readInlineZIndex(element: Element): number {\n try {\n const inline = (element as HTMLElement).style?.zIndex;\n if (inline && inline !== \"auto\") {\n const parsed = parseInt(inline, 10);\n if (Number.isFinite(parsed)) return parsed;\n }\n return 0;\n } catch {\n return 0;\n }\n}\n\nfunction maxDefinedNumber(...values: Array<number | null>): number | null {\n const finite = values.filter((value): value is number => Number.isFinite(value ?? null));\n if (finite.length === 0) return null;\n return Math.max(...finite);\n}\n\n/**\n * Parse an authored track attribute, honoring 0 (a valid top-lane index).\n * `parseInt(...) || fallback` silently replaced authored track 0 with the\n * synthetic fallback, so track-0 clips drifted to the bottom of the timeline.\n */\nexport function parseAuthoredTrack(el: Element, fallback: number): number {\n const raw = el.getAttribute(\"data-track-index\") ?? el.getAttribute(\"data-track\");\n if (raw == null) return fallback;\n const parsed = Number.parseInt(raw, 10);\n return Number.isFinite(parsed) ? parsed : fallback;\n}\n\nfunction toAbsoluteAssetUrl(rawValue: string | null | undefined): string | null {\n const raw = String(rawValue ?? \"\").trim();\n if (!raw) return null;\n const lowered = raw.toLowerCase();\n if (lowered.startsWith(\"data:\") || lowered.startsWith(\"javascript:\")) return null;\n try {\n return new URL(raw, document.baseURI).toString();\n } catch {\n return raw;\n }\n}\n\nfunction resolveNodeAssetUrl(node: Element): string | null {\n const src = node.getAttribute(\"src\") ?? node.getAttribute(\"data-src\");\n if (src) return toAbsoluteAssetUrl(src);\n const compositionSrc = node.getAttribute(\"data-composition-src\");\n if (compositionSrc) return toAbsoluteAssetUrl(compositionSrc);\n const mediaDescendant = node.querySelector(\"img[src], video[src], audio[src], source[src]\");\n if (!mediaDescendant) return null;\n return toAbsoluteAssetUrl(mediaDescendant.getAttribute(\"src\"));\n}\n\nfunction getFirstClassToken(node: Element): string | null {\n const className = (node as HTMLElement).className;\n if (typeof className !== \"string\") return null;\n return (\n className\n .split(/\\s+/)\n .map((value) => value.trim())\n .find((value) => value && value !== \"clip\" && !value.startsWith(\"__hf-\")) ?? null\n );\n}\n\nfunction filenameFromAssetUrl(url: string | null): string | null {\n if (!url) return null;\n let filename: string | undefined;\n try {\n const parsed = new URL(url, document.baseURI);\n filename = parsed.pathname.split(\"/\").filter(Boolean).at(-1);\n } catch {\n filename = url\n .replace(/[?#].*$/, \"\")\n .split(/[\\\\/]/)\n .filter(Boolean)\n .at(-1);\n }\n if (!filename) return null;\n try {\n return decodeURIComponent(filename);\n } catch {\n return filename;\n }\n}\n\nfunction textPreview(node: Element): string | null {\n const text = node.textContent?.replace(/\\s+/g, \" \").trim();\n if (!text) return null;\n return text.length > 32 ? `${text.slice(0, 31)}...` : text;\n}\n\nfunction humanizeTimelineToken(value: string): string {\n const normalized = value\n .replace(/\\.[^.]+$/i, \"\")\n .replace(/[-_]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n if (!normalized) return value;\n return normalized.replace(/\\b\\w/g, (char) => char.toUpperCase());\n}\n\nfunction authoredCompositionId(node: Element): string | null {\n return (\n node.getAttribute(\"data-hf-original-composition-id\") ?? node.getAttribute(\"data-composition-id\")\n );\n}\n\nfunction buildTimelineClipLabel(node: Element, kind: RuntimeTimelineClip[\"kind\"], ordinal: number) {\n const explicit =\n node.getAttribute(\"data-timeline-label\") ??\n node.getAttribute(\"data-label\") ??\n node.getAttribute(\"aria-label\") ??\n null;\n if (explicit?.trim()) return explicit.trim();\n\n const compositionId = authoredCompositionId(node);\n if (compositionId) return humanizeTimelineToken(compositionId);\n\n const id = (node as HTMLElement).id;\n if (id) return humanizeTimelineToken(id);\n\n const classToken = getFirstClassToken(node);\n if (classToken) return humanizeTimelineToken(classToken);\n\n const assetName = filenameFromAssetUrl(resolveNodeAssetUrl(node));\n if (assetName) return humanizeTimelineToken(assetName);\n\n const text = textPreview(node);\n if (text) return text;\n\n return `${humanizeTimelineToken(kind)} ${ordinal + 1}`;\n}\n\nexport function collectRuntimeTimelinePayload(params: {\n canonicalFps: number;\n}): RuntimeTimelineMessage {\n const runtimeWindow = window as Window & {\n __timelines?: Record<string, RuntimeTimelineLike | undefined>;\n };\n const timelineRegistry = runtimeWindow.__timelines ?? {};\n const startResolver = createRuntimeStartTimeResolver({\n timelineRegistry,\n includeAuthoredTimingAttrs: true,\n });\n const resolveTimelineDurationSeconds = (compositionId: string | null): number | null => {\n if (!compositionId) return null;\n const timeline = timelineRegistry[compositionId] ?? null;\n if (!timeline || typeof timeline.duration !== \"function\") return null;\n try {\n const duration = Number(timeline.duration());\n return Number.isFinite(duration) && duration > 0 ? duration : null;\n } catch {\n return null;\n }\n };\n const resolveMediaWindowEndSeconds = (): number | null => {\n const mediaNodes = Array.from(\n document.querySelectorAll(\"video[data-start], audio[data-start]\"),\n ) as Array<HTMLVideoElement | HTMLAudioElement>;\n if (mediaNodes.length === 0) return null;\n let maxWindowEndSeconds = 0;\n for (const mediaNode of mediaNodes) {\n const start = startResolver.resolveMediaStartForElement(mediaNode);\n if (!Number.isFinite(start)) continue;\n const duration = resolveMediaElementDurationSeconds(mediaNode);\n if (duration == null || duration <= 0) continue;\n maxWindowEndSeconds = Math.max(maxWindowEndSeconds, Math.max(0, start) + duration);\n }\n return maxWindowEndSeconds > 0 ? maxWindowEndSeconds : null;\n };\n const resolveNearestCompositionContext = (\n node: Element,\n root: Element | null,\n ): {\n parentCompositionId: string | null;\n compositionAncestors: string[];\n inheritedStart: number | null;\n inheritedDuration: number | null;\n } => {\n const ancestors: string[] = [];\n let inheritedStart: number | null = null;\n let inheritedDuration: number | null = null;\n let parentCompositionId: string | null = null;\n let cursor = node.parentElement;\n while (cursor) {\n const compositionId = cursor.getAttribute(\"data-composition-id\");\n if (compositionId) {\n ancestors.push(compositionId);\n if (!parentCompositionId && cursor !== root) {\n parentCompositionId = compositionId;\n }\n if (inheritedStart == null) {\n inheritedStart = startResolver.resolveStartForElement(cursor, 0);\n }\n if (inheritedDuration == null) {\n inheritedDuration =\n parseNum(cursor.getAttribute(\"data-duration\")) ??\n resolveTimelineDurationSeconds(compositionId) ??\n null;\n }\n }\n cursor = cursor.parentElement;\n }\n return {\n parentCompositionId,\n compositionAncestors: ancestors.reverse(),\n inheritedStart,\n inheritedDuration,\n };\n };\n\n const root = findRootCompositionElement();\n const compositionNodes = Array.from(document.querySelectorAll(\"[data-composition-id]\"));\n const rootCompositionId = root?.getAttribute(\"data-composition-id\") ?? null;\n const rootCompositionStart = root ? startResolver.resolveStartForElement(root, 0) : 0;\n const mediaWindowEnd = resolveMediaWindowEndSeconds();\n const mediaWindowDuration =\n mediaWindowEnd != null ? Math.max(0, mediaWindowEnd - Math.max(0, rootCompositionStart)) : null;\n const rootDurationFromTimeline = resolveTimelineDurationSeconds(rootCompositionId);\n const rootDurationFromAttr = parseElementDurationAttr(root ?? document.body);\n const compositionWindowEnd = maxDefinedNumber(\n ...compositionNodes\n .filter((node) => node !== root)\n .map((node) => {\n const start = startResolver.resolveStartForElement(node, 0);\n const duration =\n startResolver.resolveDurationForElement(node) ??\n resolveTimelineDurationSeconds(node.getAttribute(\"data-composition-id\")) ??\n null;\n if (!Number.isFinite(start) || duration == null || duration <= 0) return null;\n return Math.max(0, start) + duration;\n }),\n );\n const compositionWindowDuration =\n compositionWindowEnd != null\n ? Math.max(0, compositionWindowEnd - Math.max(0, rootCompositionStart))\n : null;\n const timelineDurationCandidate =\n typeof rootDurationFromTimeline === \"number\" &&\n Number.isFinite(rootDurationFromTimeline) &&\n rootDurationFromTimeline > 0\n ? rootDurationFromTimeline\n : null;\n const attrDurationCandidate =\n typeof rootDurationFromAttr === \"number\" &&\n Number.isFinite(rootDurationFromAttr) &&\n rootDurationFromAttr > 0\n ? rootDurationFromAttr\n : null;\n const mediaWindowDurationCandidate =\n typeof mediaWindowDuration === \"number\" &&\n Number.isFinite(mediaWindowDuration) &&\n mediaWindowDuration > 0\n ? mediaWindowDuration\n : null;\n const compositionWindowDurationCandidate =\n typeof compositionWindowDuration === \"number\" &&\n Number.isFinite(compositionWindowDuration) &&\n compositionWindowDuration > 0\n ? compositionWindowDuration\n : null;\n const finiteWindowFloor = maxDefinedNumber(\n mediaWindowDurationCandidate,\n compositionWindowDurationCandidate,\n );\n const timelineLooksLoopInflated =\n timelineDurationCandidate != null &&\n finiteWindowFloor != null &&\n timelineDurationCandidate >= LOOP_INFLATED_TIMELINE_SECONDS;\n // Prefer explicit authored root duration first.\n // If absent, guard against loop-inflated GSAP durations by trusting finite media window.\n const preferredRootDuration =\n attrDurationCandidate ??\n (timelineLooksLoopInflated\n ? finiteWindowFloor\n : maxDefinedNumber(\n timelineDurationCandidate,\n mediaWindowDurationCandidate,\n compositionWindowDurationCandidate,\n ));\n const rootCompositionDuration = preferredRootDuration ?? null;\n const rootCompositionEnd =\n rootCompositionDuration != null ? rootCompositionStart + rootCompositionDuration : null;\n const timelineWindowEnd =\n rootCompositionEnd ??\n (typeof mediaWindowEnd === \"number\" && Number.isFinite(mediaWindowEnd) && mediaWindowEnd > 0\n ? mediaWindowEnd\n : null);\n const clampDurationToRootWindow = (start: number, duration: number): number => {\n if (!Number.isFinite(duration) || duration <= 0) return 0;\n if (timelineWindowEnd == null || !Number.isFinite(timelineWindowEnd)) return duration;\n if (!Number.isFinite(start) || start >= timelineWindowEnd) return 0;\n return Math.max(0, Math.min(duration, timelineWindowEnd - start));\n };\n const clips: RuntimeTimelineClip[] = [];\n const scenes: RuntimeTimelineScene[] = [];\n // Only collect elements that are explicitly part of the timeline:\n // - Elements with data-start or data-track-index (timed clips)\n // - Elements with data-composition-id (sub-compositions)\n // - Media elements (video, audio, img)\n // Elements without data-start (e.g. GSAP-animated scenes) are not included\n // as clips — they have no declared timing so the timeline can't show their\n // actual visibility window. They can still appear as scenes via the separate\n // scene collection below.\n const nodes = Array.from(\n document.querySelectorAll(\n \"[data-start], [data-track-index], [data-composition-id], video, audio, img\",\n ),\n );\n let maxEnd = 0;\n for (const [i, node] of nodes.entries()) {\n if (node === root) continue;\n if ([\"SCRIPT\", \"STYLE\", \"LINK\", \"META\", \"TEMPLATE\", \"NOSCRIPT\"].includes(node.tagName))\n continue;\n const compositionContext = resolveNearestCompositionContext(node, root);\n const tag = node.tagName.toLowerCase();\n const start =\n tag === \"video\" || tag === \"audio\"\n ? startResolver.resolveMediaStartForElement(node)\n : startResolver.resolveStartForElement(node, compositionContext.inheritedStart ?? 0);\n const nodeCompositionId = node.getAttribute(\"data-composition-id\");\n let duration = parseElementDurationAttr(node);\n if (duration == null && nodeCompositionId && nodeCompositionId !== rootCompositionId) {\n duration = resolveTimelineDurationSeconds(nodeCompositionId);\n }\n if (duration == null && isMediaElement(node)) {\n if (Number.isFinite(node.duration)) {\n duration = resolveNaturalMediaTimelineDuration(node, node.duration);\n }\n }\n if (duration == null) duration = resolveTimedImageDurationSeconds(node, start);\n if (duration == null) {\n const inheritedDuration = compositionContext.inheritedDuration;\n if (inheritedDuration != null && inheritedDuration > 0) {\n const inheritedStart = compositionContext.inheritedStart ?? 0;\n const inheritedEnd = inheritedStart + inheritedDuration;\n duration = Math.max(0, inheritedEnd - start);\n }\n }\n if (duration == null || duration <= 0) continue;\n duration = clampDurationToRootWindow(start, duration);\n if (duration <= 0) continue;\n const end = start + duration;\n maxEnd = Math.max(maxEnd, end);\n const kind: RuntimeTimelineClip[\"kind\"] =\n nodeCompositionId && nodeCompositionId !== rootCompositionId\n ? \"composition\"\n : tag === \"video\"\n ? \"video\"\n : tag === \"audio\"\n ? \"audio\"\n : tag === \"img\"\n ? \"image\"\n : \"element\";\n clips.push({\n id: stableClipId(node) ?? nodeCompositionId ?? null,\n label: buildTimelineClipLabel(node, kind, clips.length),\n start,\n duration,\n track: parseAuthoredTrack(node, i),\n zIndex: readInlineZIndex(node),\n stackingContextId: resolveCssStackingContextId(node),\n kind,\n tagName: tag,\n compositionId: node.getAttribute(\"data-composition-id\"),\n compositionAncestors: compositionContext.compositionAncestors,\n parentCompositionId: compositionContext.parentCompositionId,\n nodePath: null,\n compositionSrc: toAbsoluteAssetUrl(node.getAttribute(\"data-composition-src\")),\n playbackStart: readElementPlaybackStart(node),\n playbackRate: readElementPlaybackRate(node),\n assetUrl: resolveNodeAssetUrl(node),\n timelineRole: node.getAttribute(\"data-timeline-role\"),\n timelineLabel: node.getAttribute(\"data-timeline-label\"),\n timelineGroup: node.getAttribute(\"data-timeline-group\"),\n timelinePriority: parseNum(node.getAttribute(\"data-timeline-priority\")),\n });\n }\n // ── GSAP introspection ──────────────────────────────────────────────────\n // Discover elements animated by GSAP that weren't picked up by the DOM query\n // (e.g. scene divs controlled purely via opacity/display tweens).\n // Introspect the master timeline's tweens to find their targets and time ranges.\n // ── GSAP introspection ──────────────────────────────────────────────────\n // Discover scene-level elements animated by GSAP that weren't picked up by\n // the DOM query. Introspect the master timeline's tweens, resolve absolute\n // time ranges, and bubble child tween ranges up to their nearest scene-level\n // ancestor (direct child of root with an id).\n const gsapClipIds = new Set(clips.map((c) => c.id));\n const rootCompositionIdForGsap = root?.getAttribute(\"data-composition-id\") ?? null;\n const masterTimeline = rootCompositionIdForGsap\n ? (timelineRegistry[rootCompositionIdForGsap] ?? null)\n : null;\n if (masterTimeline && root) {\n type GsapTween = {\n targets?: () => Element[];\n startTime?: () => number;\n duration?: () => number;\n parent?: GsapTween;\n };\n const tlWithChildren = masterTimeline as typeof masterTimeline & {\n getChildren?: (nested: boolean, tweens: boolean, timelines: boolean) => GsapTween[];\n };\n if (typeof tlWithChildren.getChildren === \"function\") {\n try {\n const tweens = tlWithChildren.getChildren(true, true, false) ?? [];\n // Build a set of direct children of root that have an id — these are\n // scene-level containers. Tween ranges on their descendants get bubbled\n // up to expand the scene's time range.\n const sceneElements = new Map<Element, { id: string; start: number; end: number }>();\n for (const child of root.children) {\n const childEl = child as HTMLElement;\n if (!childEl.id) continue;\n const tag = childEl.tagName.toLowerCase();\n if (tag === \"script\" || tag === \"style\" || tag === \"link\") continue;\n sceneElements.set(childEl, { id: childEl.id, start: Infinity, end: -Infinity });\n }\n // Find the scene-level ancestor for a given element\n const findSceneAncestor = (el: Element): Element | null => {\n let cursor: Element | null = el;\n while (cursor) {\n if (sceneElements.has(cursor)) return cursor;\n if (cursor === root) return null;\n cursor = cursor.parentElement;\n }\n return null;\n };\n // Walk all tweens and accumulate time ranges per scene element\n for (const tween of tweens) {\n if (typeof tween.targets !== \"function\") continue;\n if (typeof tween.startTime !== \"function\" || typeof tween.duration !== \"function\")\n continue;\n let tweenStart = tween.startTime();\n let parent = tween.parent;\n while (parent && parent !== masterTimeline && typeof parent.startTime === \"function\") {\n tweenStart += parent.startTime();\n parent = parent.parent;\n }\n const tweenEnd = tweenStart + tween.duration();\n if (!Number.isFinite(tweenStart) || !Number.isFinite(tweenEnd)) continue;\n for (const target of tween.targets()) {\n if (!isElementNode(target)) continue;\n // Bubble up to the scene-level ancestor\n const scene = findSceneAncestor(target);\n if (!scene) continue;\n const range = sceneElements.get(scene);\n if (!range) continue;\n range.start = Math.min(range.start, tweenStart);\n range.end = Math.max(range.end, tweenEnd);\n }\n }\n // Create clips for scene elements that have tween ranges\n const gsapTrack = clips.length > 0 ? Math.max(...clips.map((c) => c.track)) + 1 : 0;\n for (const [element, range] of sceneElements) {\n if (range.start === Infinity || range.end === -Infinity) continue;\n const el = element as HTMLElement;\n if (gsapClipIds.has(el.id)) continue;\n const duration = Math.max(0, range.end - range.start);\n if (duration <= 0) continue;\n const clampedDuration = clampDurationToRootWindow(range.start, duration);\n if (clampedDuration <= 0) continue;\n maxEnd = Math.max(maxEnd, range.start + clampedDuration);\n clips.push({\n id: el.id,\n label:\n el.getAttribute(\"data-timeline-label\") ??\n el.getAttribute(\"data-label\") ??\n el.getAttribute(\"aria-label\") ??\n el.id,\n start: range.start,\n duration: clampedDuration,\n track: parseAuthoredTrack(el, gsapTrack),\n zIndex: readInlineZIndex(el),\n stackingContextId: resolveCssStackingContextId(el),\n kind: \"element\",\n tagName: el.tagName.toLowerCase(),\n compositionId: el.getAttribute(\"data-composition-id\"),\n compositionAncestors: rootCompositionIdForGsap ? [rootCompositionIdForGsap] : [],\n parentCompositionId: rootCompositionIdForGsap,\n nodePath: null,\n compositionSrc: null,\n playbackStart: readElementPlaybackStart(el),\n playbackRate: readElementPlaybackRate(el),\n assetUrl: null,\n timelineRole: el.getAttribute(\"data-timeline-role\"),\n timelineLabel: el.getAttribute(\"data-timeline-label\"),\n timelineGroup: el.getAttribute(\"data-timeline-group\"),\n timelinePriority: parseNum(el.getAttribute(\"data-timeline-priority\")),\n });\n gsapClipIds.add(el.id);\n }\n } catch (err) {\n // GSAP introspection is best-effort — don't break timeline if it fails\n swallow(\"runtime.timeline.site1\", err);\n }\n }\n }\n\n // ── Persistent overlays ─────────────────────────────────────────────────\n // Direct children of root that are pure structural overlays should only\n // surface in the timeline when authors explicitly opt them in. Otherwise\n // background layers like \"backdrop\" make the whole composition read as a\n // long clip, which is misleading in Studio.\n if (root && rootCompositionDuration != null && rootCompositionDuration > 0) {\n const overlayTrack = clips.length > 0 ? Math.max(...clips.map((c) => c.track)) + 1 : 0;\n for (const child of root.children) {\n const el = child as HTMLElement;\n if (!el.id) continue;\n if (gsapClipIds.has(el.id)) continue;\n const timelineRole = el.getAttribute(\"data-timeline-role\");\n if (timelineRole !== \"overlay\" && timelineRole !== \"persistent-overlay\") continue;\n const tag = el.tagName.toLowerCase();\n if (tag === \"script\" || tag === \"style\" || tag === \"link\" || tag === \"meta\") continue;\n // Skip elements that are invisible (display:none in their CSS class)\n const computed = window.getComputedStyle(el);\n if (computed.display === \"none\") continue;\n const clampedDuration = clampDurationToRootWindow(0, rootCompositionDuration);\n if (clampedDuration <= 0) continue;\n maxEnd = Math.max(maxEnd, clampedDuration);\n clips.push({\n id: el.id,\n label:\n el.getAttribute(\"data-timeline-label\") ??\n el.getAttribute(\"data-label\") ??\n el.getAttribute(\"aria-label\") ??\n el.id,\n start: 0,\n duration: clampedDuration,\n track: parseAuthoredTrack(el, overlayTrack),\n zIndex: readInlineZIndex(el),\n stackingContextId: resolveCssStackingContextId(el),\n kind: \"element\",\n tagName: tag,\n compositionId: el.getAttribute(\"data-composition-id\"),\n compositionAncestors: rootCompositionIdForGsap ? [rootCompositionIdForGsap] : [],\n parentCompositionId: rootCompositionIdForGsap,\n nodePath: null,\n compositionSrc: null,\n playbackStart: readElementPlaybackStart(el),\n playbackRate: readElementPlaybackRate(el),\n assetUrl: null,\n timelineRole,\n timelineLabel: el.getAttribute(\"data-timeline-label\"),\n timelineGroup: el.getAttribute(\"data-timeline-group\"),\n timelinePriority: parseNum(el.getAttribute(\"data-timeline-priority\")),\n });\n gsapClipIds.add(el.id);\n }\n }\n\n // Track assignment honors the authored data-track-index verbatim: a clip stays\n // on the track it was placed on, regardless of kind. (Previously mixed-kind\n // tracks were split onto separate rows, but that renumbered tracks — breaking\n // \"drop a clip onto an existing track\" and causing the written track to drift\n // from the displayed one on every move. Track index is display-only; render\n // never reads it, so honoring it verbatim is the correct NLE behavior.)\n\n for (const compositionNode of compositionNodes) {\n if (compositionNode === root) continue;\n const compositionId = compositionNode.getAttribute(\"data-composition-id\");\n if (!compositionId || !isSceneLikeCompositionId(compositionId)) continue;\n const start = startResolver.resolveStartForElement(compositionNode, 0);\n let durationFromAttr = parseElementDurationAttr(compositionNode);\n if (\n (durationFromAttr == null || durationFromAttr <= 0) &&\n parseElementEndAttr(compositionNode) != null\n ) {\n const end = parseElementEndAttr(compositionNode)!;\n durationFromAttr = Math.max(0, end - start);\n }\n const durationFromTimeline = resolveTimelineDurationSeconds(compositionId);\n const duration =\n durationFromAttr && durationFromAttr > 0 ? durationFromAttr : durationFromTimeline;\n if (duration == null || duration <= 0) continue;\n const clampedDuration = clampDurationToRootWindow(start, duration);\n if (clampedDuration <= 0) continue;\n scenes.push({\n id: compositionId,\n label:\n compositionNode.getAttribute(\"data-label\") ??\n authoredCompositionId(compositionNode) ??\n compositionId,\n start,\n duration: clampedDuration,\n thumbnailUrl: toAbsoluteAssetUrl(compositionNode.getAttribute(\"data-thumbnail-url\")),\n avatarName: null,\n });\n }\n // Timeline payload duration should reflect the playable composition window,\n // not just the furthest currently-surfaced clip. Studio can intentionally\n // hide structural/background tracks from the timeline UI; if we collapse the\n // payload duration down to the last visible clip end, the controls jump even\n // though playback still runs for the full authored root duration.\n const knownDuration = Math.max(maxEnd || 0, rootCompositionDuration ?? 0);\n const safeDuration = knownDuration > 0 ? knownDuration : 1;\n const durationInFrames = Math.max(1, Math.ceil(safeDuration * Math.max(1, params.canonicalFps)));\n return {\n ...runtimeProtocolMetadata(params.canonicalFps),\n source: \"hf-preview\",\n type: \"timeline\",\n compositionContractVersion: COMPOSITION_CONTRACT_VERSION,\n durationSeconds: safeDuration,\n durationInFrames,\n clips,\n scenes,\n compositionWidth: parseCompositionDimension(root?.getAttribute(\"data-width\")) ?? 1920,\n compositionHeight: parseCompositionDimension(root?.getAttribute(\"data-height\")) ?? 1080,\n };\n}\n","import { createRuntimeStartTimeResolver } from \"./runtime/startResolver.js\";\nimport { isRuntimeElementVisibleAt } from \"./runtime/timeline.js\";\nimport type { RuntimeTimelineLike } from \"./runtime/types.js\";\nimport { STUDIO_PREVIEW_MARK_META } from \"./studioPreviewMark.js\";\n\n/** A composition is \"ready\" once every declared input settles, not just once\n * its duration is known. Each input returns null (nothing to wait on) or a\n * promise that resolves once it settles, and must stop its own pending work\n * (timer, rAF, listener) once `signal` aborts — settleCompositionReadiness\n * aborts it the moment the race settles, win or timeout. */\nexport type CompositionReadinessInput = (\n doc: Document,\n signal: AbortSignal,\n) => Promise<void> | null;\n\nexport interface PendingCompositionAssets {\n pendingMedia: HTMLMediaElement[];\n pendingImages: HTMLImageElement[];\n fontsLoading: boolean;\n}\n\nexport type CompositionReadinessScope = \"all\" | \"first-frame\";\n\nexport const FIRST_FRAME_READINESS_SCOPE: CompositionReadinessScope = \"first-frame\";\n\nexport interface CompositionReadinessOptions {\n scope?: CompositionReadinessScope;\n}\n\n// HTMLMediaElement.HAVE_FUTURE_DATA per spec, used as a literal because not\n// every DOM implementation defines the named static (e.g. happy-dom leaves\n// it undefined).\nconst HAVE_FUTURE_DATA = 3;\n\n// `doc` is a foreign (same-origin) iframe document, so its elements belong to\n// that iframe's own realm — instanceof checks against this window's globals\n// would silently reject every one of them. Resolve the class from the node's\n// own defaultView first.\nexport function isRealmElement(node: Node): node is Element {\n const view = node.ownerDocument?.defaultView;\n if (view && node instanceof view.Element) return true;\n return node instanceof Element;\n}\n\nexport function isRealmHtmlMediaElement(node: Node): node is HTMLMediaElement {\n if (!isRealmElement(node)) return false;\n if (node.tagName !== \"AUDIO\" && node.tagName !== \"VIDEO\") return false;\n const view = node.ownerDocument?.defaultView;\n if (view && node instanceof view.HTMLMediaElement) return true;\n return node instanceof HTMLMediaElement;\n}\n\nfunction isTimedElement(element: Element): boolean {\n return element.hasAttribute(\"data-start\") || element.hasAttribute(\"data-track-index\");\n}\n\nfunction isActiveAtFirstFrame(\n element: Element,\n resolver: ReturnType<typeof createRuntimeStartTimeResolver>,\n timelineRegistry: Record<string, RuntimeTimelineLike | undefined>,\n): boolean {\n let current: Element | null = element;\n while (current) {\n if (isTimedElement(current)) {\n if (\n !isRuntimeElementVisibleAt(current as HTMLElement, {\n currentTime: 0,\n compositionDuration: Number.POSITIVE_INFINITY,\n canonicalFps: 30,\n exportRenderSeek: false,\n timelineRegistry,\n resolver,\n })\n )\n return false;\n }\n current = current.parentElement;\n }\n return true;\n}\n\nfunction shouldIncludeAsset(\n element: Element,\n scope: CompositionReadinessScope,\n resolver: ReturnType<typeof createRuntimeStartTimeResolver>,\n timelineRegistry: Record<string, RuntimeTimelineLike | undefined>,\n): boolean {\n if (scope === \"all\") return true;\n return isActiveAtFirstFrame(element, resolver, timelineRegistry);\n}\n\n/** One DOM pass for every declared-media asset not yet ready. */\nexport function scanPendingCompositionAssets(\n doc: Document,\n { scope = \"all\" }: CompositionReadinessOptions = {},\n): PendingCompositionAssets {\n const runtimeWindow = doc.defaultView as\n | (Window & {\n __timelines?: Record<string, import(\"./runtime/types\").RuntimeTimelineLike | undefined>;\n })\n | null;\n const resolver = createRuntimeStartTimeResolver({\n documentRef: doc,\n timelineRegistry: runtimeWindow?.__timelines,\n includeAuthoredTimingAttrs: true,\n });\n // Studio's preview buffers a clip off the first frame only once it comes due, as with a lazy image.\n const defersMedia = doc.querySelector(`meta[name=\"${STUDIO_PREVIEW_MARK_META}\"]`) !== null;\n const pendingMedia = Array.from(doc.querySelectorAll(\"video, audio\"))\n .filter(isRealmHtmlMediaElement)\n .filter((el) =>\n shouldIncludeAsset(\n el,\n defersMedia && el.preload !== \"auto\" ? FIRST_FRAME_READINESS_SCOPE : scope,\n resolver,\n runtimeWindow?.__timelines ?? {},\n ),\n )\n .filter((el) => el.readyState < HAVE_FUTURE_DATA);\n // A lazy image off the first frame loads only once shown, so no scope can wait on it.\n const pendingImages = Array.from(doc.querySelectorAll(\"img\"))\n .filter((img) =>\n shouldIncludeAsset(\n img,\n img.getAttribute(\"loading\")?.toLowerCase() === \"lazy\" ? FIRST_FRAME_READINESS_SCOPE : scope,\n resolver,\n runtimeWindow?.__timelines ?? {},\n ),\n )\n .filter((img) => !img.complete);\n const fontsLoading = doc.fonts?.status === \"loading\";\n return { pendingMedia, pendingImages, fontsLoading };\n}\n\nfunction collectPendingCompositionAssets(\n doc: Document,\n { pendingMedia, pendingImages, fontsLoading }: PendingCompositionAssets,\n signal: AbortSignal,\n): Promise<void> {\n const mediaReady = pendingMedia.map(\n (el) =>\n new Promise<void>((resolve) => {\n // init.ts's eager preload pass may have already errored this element\n // before this input ran; a DOM error event doesn't refire, so a\n // listener attached now would wait for the shared 8s timeout instead.\n if (el.error) {\n resolve();\n return;\n }\n const onSettled = () => {\n el.removeEventListener(\"canplay\", onSettled);\n el.removeEventListener(\"error\", onSettled);\n signal.removeEventListener(\"abort\", onSettled);\n resolve();\n };\n el.addEventListener(\"canplay\", onSettled);\n el.addEventListener(\"error\", onSettled);\n signal.addEventListener(\"abort\", onSettled, { once: true });\n }),\n );\n const imagesReady = pendingImages.map((img) =>\n img.decode ? img.decode().catch(() => {}) : Promise.resolve(),\n );\n const fontsReady = fontsLoading && doc.fonts ? doc.fonts.ready.then(() => {}) : Promise.resolve();\n return Promise.all([...mediaReady, ...imagesReady, fontsReady]).then(() => {});\n}\n\n/** Declared-media readiness input: waits on the composition's own video,\n * audio, image and font-face loads. */\nexport function mediaReadinessInput(\n doc: Document,\n signal: AbortSignal,\n { scope = \"all\" }: CompositionReadinessOptions = {},\n): Promise<void> | null {\n const scan = scanPendingCompositionAssets(doc, { scope });\n if (scan.pendingMedia.length === 0 && scan.pendingImages.length === 0 && !scan.fontsLoading) {\n return null;\n }\n return collectPendingCompositionAssets(doc, scan, signal);\n}\n\nconst RENDER_READY_POLL_MS = 50;\n\n// `window.__renderReady` is declared globally in runtime/window.d.ts, but that\n// file lives under src/runtime — excluded from this package's own build\n// program (see tsconfig.json) — so it isn't visible here. Same flag, declared\n// locally instead of depending on a global merge from outside this file's scope.\n// `__hf` is omitted from Window so the runtime program's strict shape can't conflict with `unknown`.\ninterface RuntimeReadinessWindow extends Omit<Window, \"__hf\"> {\n __renderReady?: boolean;\n // __renderReady is only ever set by init.ts, which always sets __hf\n // first (`window.__hf = window.__hf || {}`) — a doc with no __hf can\n // never get __renderReady either, so this alone is enough to gate on\n // (unlike composition-probe.ts's hasRuntime, this doesn't need __player too).\n __hf?: unknown;\n}\n\n/** Declared-compute readiness input: waits on `window.__renderReady`. A\n * probe-only composition (no HyperFrames runtime injected) never sets\n * it — __hf absent means nothing to wait on, not an 8s poll for a flag\n * that was never going to flip. */\nexport function computeReadinessInput(doc: Document, signal: AbortSignal): Promise<void> | null {\n const win = doc.defaultView as RuntimeReadinessWindow | null;\n if (!win) return null;\n if (win.__renderReady) return null;\n if (!win.__hf) return null;\n return new Promise<void>((resolve) => {\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const finish = () => {\n if (timeoutId !== undefined) clearTimeout(timeoutId);\n signal.removeEventListener(\"abort\", finish);\n resolve();\n };\n if (signal.aborted) {\n finish();\n return;\n }\n signal.addEventListener(\"abort\", finish, { once: true });\n const poll = () => {\n if (win.__renderReady) {\n finish();\n return;\n }\n timeoutId = setTimeout(poll, RENDER_READY_POLL_MS);\n };\n poll();\n });\n}\n\n// Gaps under this bound count as quiet on any document.\nconst IDLE_FRAME_GAP_MS = 50;\n// Ordinary frame jitter can span 1.5x the document's shortest observed gap.\nconst IDLE_FRAME_GAP_RATIO = 1.5;\n// Two consecutive quiet frames, not one: a single fast gap can follow\n// directly after the busy work finishes mid-frame and says nothing about\n// whether the next frame is also free.\nconst IDLE_FRAMES_REQUIRED = 2;\n// Bounds continuing stalls. A backgrounded document with no frames still\n// rides the shared timeout; this budget only bounds frames that arrive.\n// ponytail: 1500ms is unmeasured; retune with frame traces.\nconst MAX_PAINT_WAIT_MS = 1_500;\n\n// Resolves with -1 on abort instead of rejecting: every caller already\n// re-checks `signal.aborted` right after awaiting this, so a sentinel value\n// is enough and keeps the abort path a plain early-return, not a try/catch.\nfunction nextAnimationFrame(win: Window, signal: AbortSignal): Promise<number> {\n return new Promise((resolve) => {\n if (signal.aborted) {\n resolve(-1);\n return;\n }\n let id = 0;\n const onAbort = () => {\n win.cancelAnimationFrame?.(id);\n resolve(-1);\n };\n signal.addEventListener(\"abort\", onAbort, { once: true });\n // A host whose rAF calls back synchronously reaches onAbort in the\n // callback, so it must already be declared above.\n id = win.requestAnimationFrame((ts) => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve(ts);\n });\n });\n}\n\n/** Composition-agnostic readiness input: waits for a frame to paint, then\n * two consecutive quiet frame gaps — an early-out once the main thread is\n * free relative to its own frame cadence. See MAX_PAINT_WAIT_MS for\n * what this bound does and does not cover. Compute and media inputs own completion. */\nexport function paintAndIdleReadinessInput(\n doc: Document,\n signal: AbortSignal,\n): Promise<void> | null {\n const win = doc.defaultView;\n if (!win) return null;\n return (async () => {\n const firstTs = await nextAnimationFrame(win, signal);\n if (signal.aborted) return;\n let lastTs = await nextAnimationFrame(win, signal);\n let shortestGap = lastTs - firstTs;\n let quietStreak = 0;\n while (!signal.aborted && quietStreak < IDLE_FRAMES_REQUIRED) {\n if (lastTs - firstTs >= MAX_PAINT_WAIT_MS) return;\n const ts = await nextAnimationFrame(win, signal);\n if (signal.aborted) return;\n const gap = ts - lastTs;\n shortestGap = Math.min(shortestGap, gap);\n const quiet = gap < IDLE_FRAME_GAP_MS || gap <= shortestGap * IDLE_FRAME_GAP_RATIO;\n quietStreak = quiet ? quietStreak + 1 : 0;\n lastTs = ts;\n }\n })();\n}\n\nconst DEFAULT_TIMEOUT_MS = 8_000;\n\nexport interface CompositionReadinessResult {\n timedOut: boolean;\n}\n\n/** Invokes `onSettled` once every input settles, or the timeout elapses.\n * Calls back synchronously, before returning, when nothing is pending — a\n * caller that plays right after this call sees the decision already\n * applied, same as before this gate existed. With the default inputs this\n * only happens for a document with no `defaultView`, since\n * paintAndIdleReadinessInput always has a frame to wait on otherwise. */\nexport function settleCompositionReadiness(\n doc: Document,\n onSettled: (result: CompositionReadinessResult) => void,\n opts: CompositionReadinessOptions & {\n inputs?: CompositionReadinessInput[];\n timeoutMs?: number;\n } = {},\n): void {\n const inputs = opts.inputs ?? [\n (inputDoc, signal) => mediaReadinessInput(inputDoc, signal, { scope: opts.scope }),\n computeReadinessInput,\n paintAndIdleReadinessInput,\n ];\n const controller = new AbortController();\n const pending = inputs\n .map((input) => input(doc, controller.signal))\n .filter((p): p is Promise<void> => p !== null);\n if (pending.length === 0) {\n onSettled({ timedOut: false });\n return;\n }\n\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n let timeoutId: ReturnType<typeof setTimeout>;\n const timeout = new Promise<\"timed-out\">((resolve) => {\n timeoutId = setTimeout(() => resolve(\"timed-out\"), timeoutMs);\n });\n // A timeout win leaves the losing inputs' promises pending forever unless\n // told to stop: this abort is what lets computeReadinessInput clear its\n // poll timer and paintAndIdleReadinessInput cancel its rAF chain instead\n // of running for the life of the page.\n Promise.race([Promise.all(pending).then(() => \"done\" as const), timeout]).then((result) => {\n clearTimeout(timeoutId);\n controller.abort();\n onSettled({ timedOut: result === \"timed-out\" });\n });\n}\n\nexport function settleFirstFrameCompositionReadiness(\n doc: Document,\n onSettled: (result: CompositionReadinessResult) => void,\n opts: Omit<\n CompositionReadinessOptions & {\n inputs?: CompositionReadinessInput[];\n timeoutMs?: number;\n },\n \"scope\"\n > = {},\n): void {\n settleCompositionReadiness(doc, onSettled, {\n ...opts,\n scope: FIRST_FRAME_READINESS_SCOPE,\n });\n}\n","/**\n * Parent-frame media proxy subsystem.\n *\n * Maintains mirror copies of the iframe's timed `<audio>`/`<video>` elements\n * in the parent frame so that mobile browsers — which gate `el.play()` on user\n * activation in the *same* frame — can still produce audible output via proxies\n * the parent controls directly.\n *\n * See the class-level JSDoc on `HyperframesPlayer` for the full ownership model.\n */\n\nimport { selectMediaObserverTargets } from \"./mediaObserverScope.js\";\nimport { isRealmElement, isRealmHtmlMediaElement } from \"./media-element-guards.js\";\nimport { isInClipWindow } from \"@hyperframes/core/runtime/clip-window\";\nimport { readClipTiming } from \"@hyperframes/core/composition-contract\";\n\n/** Minimum absolute drift before a currentTime correction is attempted. */\nconst MIRROR_DRIFT_THRESHOLD_SECONDS = 0.05;\n\n/**\n * How many *consecutive* over-threshold samples are required before issuing a\n * `currentTime` write. Absorbs single-sample jitter (GC pause, slow bridge\n * tick) without thrashing. Forced calls bypass this gate.\n *\n * Worst-case correction latency ≈ this × bridgeMaxPostIntervalMs (80 ms in\n * core/runtime/state.ts) = 160 ms — well under human A/V re-sync tolerance.\n */\nconst MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES = 2;\n\nexport interface ProxyEntry {\n el: HTMLMediaElement;\n start: number;\n duration: number;\n /**\n * The iframe media element this proxy mirrors, when adopted from the DOM.\n * Its `data-start`/`data-duration` are re-read each tick so live timeline\n * edits (trim/move) bound the proxy correctly. Null for URL-driven proxies.\n */\n source?: HTMLMediaElement | null;\n /**\n * Count of consecutive steady-state samples in which the proxy's\n * `currentTime` was found drifted beyond `MIRROR_DRIFT_THRESHOLD_SECONDS`.\n * Reset on every in-threshold sample. A write is only issued once this\n * reaches `MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES`, absorbing\n * single-sample jitter without thrashing.\n */\n driftSamples: number;\n}\n\nexport class ParentMediaManager {\n private _entries: ProxyEntry[] = [];\n private _mediaObserver?: MutationObserver;\n private _playbackErrorPosted = false;\n private _audioOwner: \"runtime\" | \"parent\" = \"runtime\";\n /** The proxy created from the `audio-src` attribute, tracked so it can be\n * replaced or cleared instead of accumulating on every attribute change. */\n private _urlAudioEntry: ProxyEntry | null = null;\n private _urlAudioSrc: string | null = null;\n\n private readonly _dispatchEvent: (event: Event) => void;\n private readonly _getMuted: () => boolean;\n private readonly _getVolume: () => number;\n private readonly _getPlaybackRate: () => number;\n private readonly _getCurrentTime: () => number;\n private readonly _isPaused: () => boolean;\n\n constructor(opts: {\n dispatchEvent: (event: Event) => void;\n getMuted: () => boolean;\n getVolume: () => number;\n getPlaybackRate: () => number;\n getCurrentTime: () => number;\n isPaused: () => boolean;\n }) {\n this._dispatchEvent = opts.dispatchEvent;\n this._getMuted = opts.getMuted;\n this._getVolume = opts.getVolume;\n this._getPlaybackRate = opts.getPlaybackRate;\n this._getCurrentTime = opts.getCurrentTime;\n this._isPaused = opts.isPaused;\n }\n\n get audioOwner(): \"runtime\" | \"parent\" {\n return this._audioOwner;\n }\n\n /** Exposed for test instrumentation only — do not use in production code. */\n get entries(): ProxyEntry[] {\n return this._entries;\n }\n\n resetForIframeLoad(): void {\n this._playbackErrorPosted = false;\n const wasPromoted = this._audioOwner === \"parent\";\n this._audioOwner = \"runtime\";\n this.pauseAll();\n for (const m of this._entries) if (m !== this._urlAudioEntry) m.el.src = \"\";\n this._entries = this._urlAudioEntry ? [this._urlAudioEntry] : [];\n if (this._urlAudioSrc && !this._urlAudioEntry)\n this._urlAudioEntry = this._createEntry(this._urlAudioSrc, \"audio\", 0, Infinity);\n this.teardownObserver();\n if (wasPromoted) {\n this._dispatchEvent(\n new CustomEvent(\"audioownershipchange\", {\n detail: { owner: \"runtime\", reason: \"iframe-reload\" },\n }),\n );\n }\n }\n\n destroy(): void {\n this.teardownObserver();\n for (const m of this._entries) {\n m.el.pause();\n m.el.src = \"\";\n }\n this._entries = [];\n this._urlAudioEntry = null;\n this._urlAudioSrc = null;\n this._audioOwner = \"runtime\";\n this._playbackErrorPosted = false;\n }\n\n updateMuted(muted: boolean): void {\n for (const m of this._entries) m.el.muted = muted;\n }\n\n updateVolume(volume: number): void {\n for (const m of this._entries) m.el.volume = volume;\n }\n\n updatePlaybackRate(rate: number): void {\n for (const m of this._entries) m.el.playbackRate = rate;\n }\n\n private _playEntry(m: ProxyEntry): void {\n if (!m.el.src) return;\n m.el.play().catch((err: unknown) => this._reportPlaybackError(err));\n }\n\n // Play only if the current playhead is inside the clip's (live) window, so\n // bulk starts (playAll / adopt) don't blip audio for clips outside their\n // window until the next mirrorTime tick gates them off.\n private _playEntryIfActive(m: ProxyEntry): void {\n this._refreshEntryBounds(m);\n if (!this._inWindow(m, this._getCurrentTime())) return;\n this._playEntry(m);\n }\n\n // Re-read the source clip's live timing so trims/moves bound the proxy\n // (adopt-time values go stale when the timeline is edited).\n private _refreshEntryBounds(m: ProxyEntry): void {\n if (!m.source?.isConnected) return;\n // Guard against a malformed (non-numeric) attribute parsing to NaN: an NaN\n // duration makes every window check pass, so the\n // gate never closes and the proxy plays past its clip end.\n const timing = readClipTiming(m.source);\n m.start = timing.start ?? 0;\n m.duration =\n timing.duration != null && timing.duration > 0 ? timing.duration : Number.POSITIVE_INFINITY;\n }\n\n // Pause the proxy outside its clip window; resume it on re-entry during\n // parent-owned playback. Returns whether the proxy is within the window.\n private _inWindow(m: ProxyEntry, timeSeconds: number): boolean {\n return isInClipWindow(timeSeconds, m.start, m.start + m.duration);\n }\n\n private _gateEntryPlayback(m: ProxyEntry, timeSeconds: number): boolean {\n if (!this._inWindow(m, timeSeconds)) {\n if (!m.el.paused) m.el.pause();\n m.driftSamples = 0;\n return false;\n }\n if (this._audioOwner === \"parent\" && !this._isPaused() && m.el.paused) this._playEntry(m);\n return true;\n }\n\n playAll(): void {\n for (const m of this._entries) this._playEntryIfActive(m);\n }\n\n pauseAll(): void {\n for (const m of this._entries) m.el.pause();\n }\n\n stopAdoptedMedia(): void {\n for (const m of this._entries) {\n if (m.source) m.el.pause();\n }\n }\n\n seekAll(timeInSeconds: number): void {\n for (const m of this._entries) {\n // Re-read live bounds so a trim/move just before a paused scrub gates and\n // positions against the current clip window, not the adopt-time one.\n this._refreshEntryBounds(m);\n if (this._inWindow(m, timeInSeconds)) m.el.currentTime = timeInSeconds - m.start;\n }\n }\n\n // Audible scrub: position every proxy at `timeInSeconds` AND play the ones whose\n // clip window covers it, so the viewer hears the track under the playhead while\n // dragging the scrubber (vs seekAll, which positions silently). Each drag move\n // re-seeks to the new position, so playback restarts from the playhead and you\n // hear the audio you're scrubbing over. The caller settles back to silence on\n // scrub end (a normal pause+seekAll). Muted proxies stay silent (play() is a no-op\n // for output). Out-of-window proxies are paused.\n scrubAll(timeInSeconds: number): void {\n for (const m of this._entries) {\n this._refreshEntryBounds(m);\n if (this._inWindow(m, timeInSeconds)) {\n m.el.currentTime = timeInSeconds - m.start;\n this._playEntry(m);\n } else if (!m.el.paused) {\n m.el.pause();\n }\n }\n }\n\n /**\n * Mirror parent-proxy `currentTime` to the iframe timeline, with optional\n * jitter-coalescing. Pass `{ force: true }` for alignment moments (ownership\n * promotion, new proxy initialization) where drift must be corrected\n * immediately.\n */\n mirrorTime(timelineSeconds: number, options?: { force?: boolean }): void {\n const force = options?.force === true;\n for (const m of this._entries) {\n this._refreshEntryBounds(m);\n if (!this._gateEntryPlayback(m, timelineSeconds)) continue;\n const relTime = timelineSeconds - m.start;\n if (Math.abs(m.el.currentTime - relTime) > MIRROR_DRIFT_THRESHOLD_SECONDS) {\n m.driftSamples += 1;\n if (force || m.driftSamples >= MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES) {\n m.el.currentTime = relTime;\n m.driftSamples = 0;\n }\n } else {\n m.driftSamples = 0;\n }\n }\n }\n\n /**\n * Take ownership of audible playback in response to the runtime's\n * `media-autoplay-blocked` signal. Idempotent.\n *\n * The caller is responsible for muting the iframe's own media output via the\n * postMessage bridge (`set-media-output-muted`) after calling this.\n */\n /**\n * Take ownership of audible playback. Idempotent. The `onMirror` callback\n * is called with the current timeline time and `{ force: true }` so the\n * caller's mirror implementation runs (enabling test spies on the player\n * to fire). If omitted, `mirrorTime` is called directly.\n */\n promoteToParentProxy(\n iframeDoc: Document | null,\n onMirror?: (t: number, opts: { force: boolean }) => void,\n ): void {\n if (this._audioOwner === \"parent\") return;\n this._audioOwner = \"parent\";\n\n // Synchronously mute iframe media to close the race window.\n if (iframeDoc) {\n for (const el of iframeDoc.querySelectorAll(\"video, audio\")) {\n if (isRealmHtmlMediaElement(el)) el.muted = true;\n }\n }\n for (const m of [...this._entries]) this._repointToSource(m);\n\n // One-shot alignment — bypass jitter-coalescing gate.\n const t = this._getCurrentTime();\n if (onMirror) onMirror(t, { force: true });\n else this.mirrorTime(t, { force: true });\n if (!this._isPaused()) this.playAll();\n\n this._dispatchEvent(\n new CustomEvent(\"audioownershipchange\", {\n detail: { owner: \"parent\", reason: \"autoplay-blocked\" },\n }),\n );\n }\n\n /**\n * Set up proxies for all timed media currently in the iframe document, then\n * install a MutationObserver for media added later (sub-composition activation).\n */\n setupFromIframe(iframeDoc: Document): void {\n const mediaEls = iframeDoc.querySelectorAll(\"audio[data-start], video[data-start]\");\n for (const iframeEl of mediaEls) {\n if (isRealmHtmlMediaElement(iframeEl)) this._adoptIframeMedia(iframeEl);\n }\n this._observeDynamicMedia(iframeDoc);\n }\n\n /**\n * Set (or replace) the parent-frame audio proxy driven by the `audio-src`\n * attribute. Re-setting with a different URL tears down the previous proxy\n * first, so changing `audio-src` swaps the track instead of stacking a\n * second one that keeps preloading and plays in parallel.\n */\n setupFromUrl(audioSrc: string): void {\n if (this._urlAudioSrc === audioSrc && this._urlAudioEntry) return;\n this.teardownUrlAudio();\n const entry = this._createEntry(audioSrc, \"audio\", 0, Infinity);\n // Null when the composition already has a proxy for this URL: that one stays the\n // composition's, and a reset creates ours once that document's proxies are gone.\n this._urlAudioEntry = entry;\n this._urlAudioSrc = audioSrc;\n // If the parent already owns playback, bring the fresh proxy online so a\n // mid-playback swap is not silent until the next play tick.\n if (entry && this._audioOwner === \"parent\" && !this._isPaused()) {\n this.mirrorTime(this._getCurrentTime(), { force: true });\n this.playAll();\n }\n }\n\n /** Tear down the `audio-src` proxy (used when the attribute is removed). */\n teardownUrlAudio(): void {\n const entry = this._urlAudioEntry;\n this._urlAudioEntry = null;\n this._urlAudioSrc = null;\n if (!entry) return;\n entry.el.pause();\n entry.el.src = \"\";\n const idx = this._entries.indexOf(entry);\n if (idx !== -1) this._entries.splice(idx, 1);\n }\n\n teardownObserver(): void {\n this._mediaObserver?.disconnect();\n this._mediaObserver = undefined;\n }\n\n // ── Private ──────────────────────────────────────────────────────────────\n\n private _reportPlaybackError(err: unknown): void {\n if (this._playbackErrorPosted) return;\n this._playbackErrorPosted = true;\n this._dispatchEvent(\n new CustomEvent(\"playbackerror\", { detail: { source: \"parent-proxy\", error: err } }),\n );\n }\n\n /**\n * Create a parent-frame media element and start preloading it. Returns the\n * new entry, or `null` if a proxy for this src already exists (dedup).\n */\n private _createEntry(\n src: string,\n tag: \"audio\" | \"video\",\n start: number,\n duration: number,\n source?: HTMLMediaElement | null,\n ): ProxyEntry | null {\n if (this._urlTaken(src)) return null;\n\n const el = tag === \"video\" ? document.createElement(\"video\") : new Audio();\n el.preload = \"auto\";\n el.src = src;\n el.load();\n el.muted = this._getMuted();\n el.volume = this._getVolume();\n const rate = this._getPlaybackRate();\n if (rate !== 1) el.playbackRate = rate;\n\n const entry: ProxyEntry = { el, start, duration, driftSamples: 0, source };\n this._entries.push(entry);\n return entry;\n }\n\n /** Resolve an iframe media element's source to an absolute URL, or null. */\n private _resolveIframeMediaSrc(iframeEl: HTMLMediaElement): string | null {\n const rawSrc =\n iframeEl.getAttribute(\"src\") || iframeEl.querySelector(\"source\")?.getAttribute(\"src\");\n return rawSrc ? new URL(rawSrc, iframeEl.ownerDocument.baseURI).href : null;\n }\n\n // fallow-ignore-next-line complexity\n private _adoptIframeMedia(iframeEl: HTMLMediaElement): void {\n // Skip elements the preloader has demoted: the observer adopts one when its preload turns\n // \"auto\" and drops its proxy, which fetches the whole file, when it turns back.\n if (iframeEl.preload === \"metadata\" || iframeEl.preload === \"none\") return;\n\n const src = this._resolveIframeMediaSrc(iframeEl);\n if (!src) return;\n\n const timing = readClipTiming(iframeEl);\n const start = timing.start ?? 0;\n const duration = timing.duration ?? Number.POSITIVE_INFINITY;\n const tag = iframeEl.tagName === \"VIDEO\" ? (\"video\" as const) : (\"audio\" as const);\n\n const created = this._createEntry(src, tag, start, duration, iframeEl);\n\n // If already under parent ownership and playing, the new proxy must catch\n // up immediately — bypass the jitter-coalescing gate.\n if (created && this._audioOwner === \"parent\") {\n this.mirrorTime(this._getCurrentTime(), { force: true });\n if (!this._isPaused()) this._playEntryIfActive(created);\n }\n }\n\n // The runtime re-points a clip (its preview copy, a new bound file). A proxy that is not\n // playing keeps its file until the parent takes over, so the copy is not fetched twice.\n private _followIframeMediaSrc(iframeEl: HTMLMediaElement): void {\n const entry = this._entries.find((m) => m.source === iframeEl);\n if (!entry) return this._adoptIframeMedia(iframeEl);\n if (this._audioOwner !== \"parent\" || !this._repointToSource(entry)) return;\n this.mirrorTime(this._getCurrentTime(), { force: true });\n if (!this._isPaused()) this._playEntryIfActive(entry);\n }\n\n /** One proxy per file: a second clip on the same file is heard through the first one's proxy.\n * A proxy's file is its clip's current one (a clip that left the page owns none). */\n private _urlTaken(src: string, except?: ProxyEntry): boolean {\n return this._entries.some((m) => {\n if (m === except || (m.source && !m.source.isConnected)) return false;\n return ((m.source && this._resolveIframeMediaSrc(m.source)) || m.el.src) === src;\n });\n }\n\n private _repointToSource(entry: ProxyEntry): boolean {\n const src = entry.source ? this._resolveIframeMediaSrc(entry.source) : null;\n if (!src || entry.el.src === src) return false;\n if (this._urlTaken(src, entry)) {\n this._removeEntry(entry);\n return false;\n }\n entry.el.src = src;\n entry.el.load();\n return true;\n }\n\n private _detachIframeMedia(iframeEl: HTMLMediaElement): void {\n const entry = this._entries.find((m) => m.source === iframeEl);\n if (entry) this._removeEntry(entry);\n }\n\n private _removeEntry(entry: ProxyEntry): void {\n entry.el.pause();\n entry.el.src = \"\";\n this._entries.splice(this._entries.indexOf(entry), 1);\n }\n\n private _observeDynamicMedia(doc: Document): void {\n this.teardownObserver();\n if (typeof MutationObserver === \"undefined\" || !doc.body) return;\n\n // fallow-ignore-next-line complexity\n const obs = new MutationObserver((mutations) => {\n for (const m of mutations) {\n if (m.type === \"attributes\" && m.attributeName === \"src\") {\n const target = m.target;\n if (\n isRealmHtmlMediaElement(target) &&\n target.matches(\"audio[data-start], video[data-start]\")\n ) {\n this._followIframeMediaSrc(target);\n }\n continue;\n }\n if (m.type === \"attributes\" && m.attributeName === \"preload\") {\n const target = m.target;\n if (\n isRealmHtmlMediaElement(target) &&\n target.matches(\"audio[data-start], video[data-start]\")\n ) {\n if (target.preload === \"auto\") this._adoptIframeMedia(target);\n else this._detachIframeMedia(target);\n }\n continue;\n }\n\n for (const added of m.addedNodes) {\n if (!isRealmElement(added)) continue;\n const candidates: HTMLMediaElement[] = [];\n if (\n isRealmHtmlMediaElement(added) &&\n added.matches(\"audio[data-start], video[data-start]\")\n ) {\n candidates.push(added);\n }\n const inside = added.querySelectorAll(\"audio[data-start], video[data-start]\");\n for (const el of inside) {\n if (isRealmHtmlMediaElement(el)) candidates.push(el);\n }\n for (const el of candidates) this._adoptIframeMedia(el);\n }\n\n for (const removed of m.removedNodes) {\n if (!isRealmElement(removed)) continue;\n const dropped: HTMLMediaElement[] = [];\n if (\n isRealmHtmlMediaElement(removed) &&\n removed.matches(\"audio[data-start], video[data-start]\")\n ) {\n dropped.push(removed);\n }\n const inside = removed.querySelectorAll(\"audio[data-start], video[data-start]\");\n for (const el of inside) {\n if (isRealmHtmlMediaElement(el)) dropped.push(el);\n }\n for (const el of dropped) this._detachIframeMedia(el);\n }\n }\n });\n\n const observeOpts: MutationObserverInit = {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"preload\", \"src\"],\n };\n\n const targets = selectMediaObserverTargets(doc);\n for (const target of targets) {\n obs.observe(target, observeOpts);\n }\n this._mediaObserver = obs;\n }\n}\n","// Range playback: the `range-start` / `range-end` attributes, in film seconds.\n\nimport { playRangeHoldTime } from \"@hyperframes/core/runtime/protocol\";\n\nexport interface PlayRange {\n start: number;\n end: number | null;\n}\n\n/** The range the player applies for the attributes, and whether it differs from the one asked for:\n * an end past the film is cut to the film's end, and an empty or negative range applies no range. */\nexport function resolvePlayRange(\n start: number | null,\n end: number | null,\n duration: number,\n): { range: PlayRange | null; clamped: boolean } {\n if (start === null && end === null) return { range: null, clamped: false };\n const from = start ?? 0;\n const cut = end !== null && duration > 0 && end > duration;\n const to = cut ? duration : end;\n const limit = to ?? (duration > 0 ? duration : Infinity);\n if (!(from >= 0) || !(limit > from)) return { range: null, clamped: true };\n return { range: { start: from, end: to }, clamped: cut };\n}\n\nexport function playRangeStopTime(range: PlayRange, duration: number, fps: number): number {\n const end = range.end ?? duration;\n return end < duration ? playRangeHoldTime(range.start, end, fps) : end;\n}\n\nexport function isOutsidePlayRange(\n time: number,\n range: PlayRange,\n duration: number,\n fps: number,\n): boolean {\n return time < range.start || time >= playRangeStopTime(range, duration, fps);\n}\n","/**\n * Pure playback-state update logic for the `state` message from the runtime.\n *\n * Extracted from the web component so the state-transition rules — loop\n * handling, play/pause mirroring, completion detection — can be read and\n * exercised independently.\n */\n\nimport type { ParentMediaManager } from \"./parent-media.js\";\nimport { type PlayRange, playRangeStopTime } from \"./play-range.js\";\n\nconst UI_UPDATE_INTERVAL_MS = 100;\n\nexport interface PlaybackState {\n currentTime: number;\n duration: number;\n paused: boolean;\n lastUpdateMs: number;\n enteringRange?: boolean;\n}\n\nexport interface PlaybackStateCallbacks {\n updateControlsTime: (current: number, duration: number) => void;\n updateControlsPlaying: (playing: boolean) => void;\n dispatchEvent: (event: Event) => void;\n seek: (t: number) => void;\n play: () => void;\n getLoop: () => boolean;\n getPlayRange?: () => PlayRange | null;\n media: ParentMediaManager;\n}\n\ntype RuntimeStateData = {\n frame: number;\n isPlaying: boolean;\n currentTime?: number;\n ended?: boolean;\n ownsRange?: boolean;\n};\n\n/** The runtime's exact time when it sends one (older runtimes send only the whole frame). */\nfunction runtimeTime(data: RuntimeStateData, fps: number): number {\n return typeof data.currentTime === \"number\" && Number.isFinite(data.currentTime)\n ? data.currentTime\n : (data.frame ?? 0) / fps;\n}\n\n/** A stopped runtime's `ended: false` is a pause; otherwise reaching the length is the end. */\nfunction isAtEnd(data: RuntimeStateData, time: number, duration: number): boolean {\n if (duration <= 0) return false;\n if (data.ended === true) return true;\n if (data.ended === false && !data.isPlaying) return false;\n return time >= duration;\n}\n\ntype PlayBounds = {\n start: number;\n shownAtEnd: number;\n atEnd: boolean;\n stopRuntime: boolean;\n entering: boolean;\n};\n\n/** A `play-range` runtime says when a range ended. For an older one the player reads the time,\n * and after it jumps into the range only from a state inside it, never a stale one. */\nfunction playBounds(\n data: RuntimeStateData,\n time: number,\n current: PlaybackState,\n playing: boolean,\n range: PlayRange | null | undefined,\n fps: number,\n): PlayBounds {\n const { duration } = current;\n if (!range) {\n const atEnd = isAtEnd(data, time, duration);\n return { start: 0, shownAtEnd: duration, atEnd, stopRuntime: false, entering: false };\n }\n if (data.ownsRange) {\n const atEnd = playing && data.ended === true && !data.isPlaying;\n return { start: range.start, shownAtEnd: time, atEnd, stopRuntime: false, entering: false };\n }\n return olderRuntimeBounds(data, time, current, playing, range, fps);\n}\n\nfunction olderRuntimeBounds(\n data: RuntimeStateData,\n time: number,\n current: PlaybackState,\n playing: boolean,\n range: PlayRange,\n fps: number,\n): PlayBounds {\n const end = range.end ?? current.duration;\n const entering = current.enteringRange === true && (time < range.start || time >= end);\n return {\n start: range.start,\n shownAtEnd: playRangeStopTime(range, current.duration, fps),\n atEnd: playing && !entering && isAtEnd(data, time, end),\n stopRuntime: data.isPlaying,\n entering,\n };\n}\n\n/**\n * Process a `state` message from the runtime and return the next state.\n * Side effects (controls updates, events, media mirroring) are fired through\n * `callbacks`. The caller must commit the returned state object.\n */\nexport function applyRuntimeStateMessage(\n data: RuntimeStateData,\n fps: number,\n current: PlaybackState,\n callbacks: PlaybackStateCallbacks,\n): PlaybackState {\n const rawTime = runtimeTime(data, fps);\n const wasPlaying = !current.paused;\n const playing = wasPlaying || data.isPlaying;\n const range = callbacks.getPlayRange?.();\n const bounds = playBounds(data, rawTime, current, playing, range, fps);\n const { atEnd, start: loopStart } = bounds;\n const clampedTime = current.duration > 0 ? Math.min(rawTime, current.duration) : rawTime;\n const currentTime = atEnd ? bounds.shownAtEnd : clampedTime;\n const nextPaused = !data.isPlaying;\n const completedPlayback = atEnd && playing;\n\n if (completedPlayback && callbacks.getLoop()) {\n if (callbacks.media.audioOwner === \"parent\") callbacks.media.pauseAll();\n callbacks.seek(loopStart);\n callbacks.play();\n // play() sets paused=false; reflect that in the returned state so the\n // caller's destructure doesn't overwrite it with the stale nextPaused value.\n return { ...current, currentTime: loopStart, paused: false, enteringRange: true };\n }\n\n const next = { ...current, currentTime, paused: nextPaused, enteringRange: bounds.entering };\n\n if (callbacks.media.audioOwner === \"parent\") {\n if (wasPlaying && nextPaused) {\n callbacks.media.pauseAll();\n } else if (!wasPlaying && !nextPaused) {\n callbacks.media.playAll();\n }\n callbacks.media.mirrorTime(currentTime);\n }\n\n const now = performance.now();\n const playStateChanged = nextPaused !== current.paused;\n if (now - current.lastUpdateMs > UI_UPDATE_INTERVAL_MS || playStateChanged) {\n next.lastUpdateMs = now;\n callbacks.updateControlsTime(currentTime, current.duration);\n callbacks.updateControlsPlaying(!nextPaused);\n callbacks.dispatchEvent(new CustomEvent(\"timeupdate\", { detail: { currentTime } }));\n }\n\n if (completedPlayback) {\n // A runtime without play-range plays on past the range: stop it on the last frame inside.\n if (bounds.stopRuntime) callbacks.seek(currentTime);\n if (callbacks.media.audioOwner === \"parent\") callbacks.media.pauseAll();\n next.paused = true;\n callbacks.updateControlsPlaying(false);\n callbacks.dispatchEvent(new Event(\"ended\"));\n }\n\n return next;\n}\n","/**\n * Routes postMessages from the composition iframe to the appropriate handlers.\n *\n * Accepts the raw MessageEvent and delegates through typed callbacks so the\n * web component keeps its state fields private and this module stays stateless.\n */\n\nimport {\n applyRuntimeStateMessage,\n type PlaybackState,\n type PlaybackStateCallbacks,\n} from \"./playback-state.js\";\nimport type { ShaderLoaderState } from \"./shader-loader-state.js\";\nimport type { ShaderTransitionState } from \"./shader-options.js\";\nimport { inspectRuntimeProtocol } from \"@hyperframes/core/runtime/protocol\";\n\ntype SceneRecord = { id: string; start: number; duration: number };\n\nfunction extractScenes(raw: unknown): SceneRecord[] {\n if (!Array.isArray(raw)) return [];\n return raw.filter(\n (s): s is SceneRecord =>\n typeof s === \"object\" &&\n s !== null &&\n typeof (s as Record<string, unknown>)[\"id\"] === \"string\" &&\n typeof (s as Record<string, unknown>)[\"start\"] === \"number\" &&\n typeof (s as Record<string, unknown>)[\"duration\"] === \"number\",\n );\n}\n\nexport interface MessageHandlerCallbacks extends PlaybackStateCallbacks {\n getPlaybackState: () => PlaybackState;\n setPlaybackState: (next: PlaybackState) => void;\n getShaderLoadingMode: () => string;\n shaderLoader: ShaderLoaderState;\n setCompositionSize: (width: number, height: number) => void;\n sendControl: (action: string, extra?: Record<string, unknown>) => void;\n getIframeDoc: () => Document | null;\n /** Invoked when the iframe runtime posts `{type: \"ready\"}` — the player\n * uses it to replay current bridge state (mute, volume, playback rate) so\n * control messages sent before the iframe's listener registered aren't lost. */\n onRuntimeReady: () => void;\n /** Invoked when the runtime reports its composition assets settled. */\n onRuntimeAssetsReady?: (timedOut: boolean) => void;\n onRuntimeDataApplied?: (channel: unknown, requestId: unknown) => void;\n onRuntimeDataError?: (channel: unknown, requestId: unknown, message: unknown) => void;\n /** Invoked when the runtime posts a finite positive timeline duration. The\n * player uses this as the cross-origin readiness signal because the\n * same-origin composition probe cannot inspect CDN iframes. */\n onRuntimeTimelineReady: (duration: number, assetsReady: boolean | undefined) => void;\n setRuntimeFps?: (fps: number) => void;\n setRuntimeOwnsPlayRange?: (owns: boolean) => void;\n /** Called with the scene list whenever a \"timeline\" message is received. */\n setScenes: (scenes: SceneRecord[]) => void;\n /** Return false to ignore the iframe runtime's audible-media autoplay fallback.\n * Slideshow embeds keep iframe media under native element ownership because\n * presenter/audience sync mirrors those media events directly. */\n shouldPromoteMediaAutoplayFallback?: () => boolean;\n}\n\n// fallow-ignore-next-line complexity\nexport function handleRuntimeMessage(\n event: MessageEvent,\n frameWindow: Window | null,\n callbacks: MessageHandlerCallbacks,\n): void {\n if (event.source !== frameWindow) return;\n const data = event.data as Record<string, unknown> | undefined;\n if (!data || data[\"source\"] !== \"hf-preview\") return;\n const protocol = inspectRuntimeProtocol(data);\n if (protocol.status === \"unsupported\") {\n callbacks.dispatchEvent(\n new CustomEvent(\"runtimeprotocolerror\", {\n detail: { code: protocol.code, receivedVersion: protocol.receivedVersion },\n }),\n );\n return;\n }\n callbacks.setRuntimeFps?.(protocol.fps);\n const ownsPlayRange =\n protocol.status === \"supported\" && protocol.metadata.capabilities.includes(\"play-range\");\n callbacks.setRuntimeOwnsPlayRange?.(ownsPlayRange);\n\n if (data[\"type\"] === \"shader-transition-state\") {\n const state: ShaderTransitionState =\n data[\"state\"] && typeof data[\"state\"] === \"object\"\n ? (data[\"state\"] as ShaderTransitionState)\n : {};\n callbacks.shaderLoader.update(state, callbacks.getShaderLoadingMode());\n callbacks.dispatchEvent(\n new CustomEvent(\"shadertransitionstate\", {\n detail: { compositionId: data[\"compositionId\"], state },\n }),\n );\n return;\n }\n\n if (data[\"type\"] === \"ready\") {\n callbacks.onRuntimeReady();\n return;\n }\n\n if (data[\"type\"] === \"assets-ready\") {\n callbacks.onRuntimeAssetsReady?.(data[\"timedOut\"] === true);\n return;\n }\n\n if (data[\"type\"] === \"runtime-data-error\") {\n callbacks.onRuntimeDataError?.(data[\"channel\"], data[\"requestId\"], data[\"message\"]);\n return;\n }\n\n if (data[\"type\"] === \"runtime-data-applied\") {\n callbacks.onRuntimeDataApplied?.(data[\"channel\"], data[\"requestId\"]);\n return;\n }\n\n if (data[\"type\"] === \"state\") {\n callbacks.setPlaybackState(\n applyRuntimeStateMessage(\n {\n frame: (data[\"frame\"] as number) ?? 0,\n currentTime: data[\"currentTime\"] as number | undefined,\n ended: typeof data[\"ended\"] === \"boolean\" ? data[\"ended\"] : undefined,\n isPlaying: !!data[\"isPlaying\"],\n ownsRange: ownsPlayRange,\n },\n protocol.fps,\n callbacks.getPlaybackState(),\n callbacks,\n ),\n );\n return;\n }\n\n if (data[\"type\"] === \"media-autoplay-blocked\") {\n if (callbacks.shouldPromoteMediaAutoplayFallback?.() === false) return;\n let iframeDoc: Document | null = null;\n try {\n iframeDoc = callbacks.getIframeDoc();\n } catch {\n /* cross-origin */\n }\n callbacks.media.promoteToParentProxy(iframeDoc, (t, opts) =>\n callbacks.media.mirrorTime(t, opts),\n );\n callbacks.sendControl(\"set-media-output-muted\", { muted: true });\n return;\n }\n\n if (data[\"type\"] === \"timeline\" && (data[\"durationInFrames\"] as number) > 0) {\n const declaredDuration = Number(data[\"durationSeconds\"]);\n const frameDuration = Number(data[\"durationInFrames\"]);\n const duration =\n Number.isFinite(declaredDuration) && declaredDuration > 0\n ? declaredDuration\n : frameDuration / protocol.fps;\n if (\n Number.isFinite(data[\"compositionWidth\"]) &&\n (data[\"compositionWidth\"] as number) > 0 &&\n Number.isFinite(data[\"compositionHeight\"]) &&\n (data[\"compositionHeight\"] as number) > 0\n ) {\n callbacks.setCompositionSize(\n data[\"compositionWidth\"] as number,\n data[\"compositionHeight\"] as number,\n );\n }\n if (Number.isFinite(duration) && duration > 0) {\n const pb = callbacks.getPlaybackState();\n callbacks.setPlaybackState({ ...pb, duration });\n callbacks.updateControlsTime(pb.currentTime, duration);\n callbacks.onRuntimeTimelineReady(\n duration,\n typeof data[\"assetsReady\"] === \"boolean\" ? data[\"assetsReady\"] : undefined,\n );\n }\n callbacks.setScenes(extractScenes(data[\"scenes\"]));\n return;\n }\n\n if (\n data[\"type\"] === \"stage-size\" &&\n // Finite-check like the timeline branch above: `> 0` alone lets\n // Infinity through, which scales the iframe to 0 and blanks it.\n Number.isFinite(data[\"width\"]) &&\n (data[\"width\"] as number) > 0 &&\n Number.isFinite(data[\"height\"]) &&\n (data[\"height\"] as number) > 0\n ) {\n callbacks.setCompositionSize(data[\"width\"] as number, data[\"height\"] as number);\n }\n}\n","/**\n * Put the runtime in the document's head, before anything in the body runs.\n *\n * The probe injects the runtime by appending a `<script src>` to an already\n * loaded document, and only once it has a reason to: a nested composition, or\n * five polls with a timeline present. Both are far too late for a COMPONENT.\n *\n * A component is markup pasted into a composition, and it reads its values in\n * an inline IIFE that runs while the body is being parsed:\n *\n * var vars = window.__hyperframes && window.__hyperframes.getVariables\n * ? window.__hyperframes.getVariables()\n * : {};\n *\n * With the runtime arriving afterwards, that guard always took the empty\n * branch, so the component fell back to the defaults hardcoded in its own\n * script and every chosen value was ignored. On the catalog page that looked\n * like the customise panel doing nothing: badge-pop with count 10 and a green\n * accent rendered 3, in red.\n *\n * A classic external `<script src>` in `<head>` is parser-blocking, so moving\n * the same URL into the srcdoc fixes the ordering without changing what is\n * loaded. A CLI render never had this problem because the engine already puts\n * the runtime ahead of body scripts.\n */\n\n/** Already carrying the runtime: a CLI-rendered page, or a second preparation. */\nfunction alreadyHasRuntime(html: string, runtimeUrl: string): boolean {\n if (html.includes(runtimeUrl)) return true;\n // The engine inlines the runtime rather than linking it, and it defines this\n // global on the way in. Matching the source avoids a second, redundant copy.\n return /hyperframe\\.runtime\\.iife\\.js|__hyperframes\\s*=/.test(html);\n}\n\ntype OpeningTag = { index: number; end: number };\nconst OPENING_TAG_BOUNDARIES = new Set<string | undefined>([\">\", \" \", \"\\t\", \"\\n\", \"\\r\", \"\\f\"]);\n\n/** Find an opening tag in one linear pass, without a backtracking regex over caller-owned HTML. */\nfunction findOpeningTag(html: string, tagName: string): OpeningTag | null {\n const lower = html.toLowerCase();\n const prefix = `<${tagName}`;\n let from = 0;\n while (from < lower.length) {\n const index = lower.indexOf(prefix, from);\n if (index < 0) return null;\n const boundary = lower[index + prefix.length];\n if (OPENING_TAG_BOUNDARIES.has(boundary)) {\n const close = lower.indexOf(\">\", index + prefix.length);\n if (close < 0) return null;\n return { index, end: close + 1 };\n }\n from = index + prefix.length;\n }\n return null;\n}\n\nexport function ensureRuntimeBeforeBodyScripts(html: string, runtimeUrl: string): string {\n if (!html || alreadyHasRuntime(html, runtimeUrl)) return html;\n\n const tag = `<script src=\"${runtimeUrl}\"></script>`;\n const head = findOpeningTag(html, \"head\");\n if (head) {\n return html.slice(0, head.end) + tag + html.slice(head.end);\n }\n // No head: get in before <body> so body scripts still see the runtime. A\n // fragment with neither lands at the front, which is the same guarantee.\n const body = findOpeningTag(html, \"body\");\n if (body) return html.slice(0, body.index) + tag + html.slice(body.index);\n const htmlTag = findOpeningTag(html, \"html\");\n if (htmlTag) {\n return html.slice(0, htmlTag.end) + tag + html.slice(htmlTag.end);\n }\n return tag + html;\n}\n","/**\n * Shader transition option types, constants, and pure helper functions for\n * injecting shader capture scale and loading mode parameters into composition\n * URLs and srcdoc HTML.\n */\n\nimport { ensureRuntimeBeforeBodyScripts } from \"./runtime-in-srcdoc.js\";\nimport { RUNTIME_CDN_URL } from \"./runtime-url.js\";\n\nexport const SHADER_CAPTURE_SCALE_ATTR = \"shader-capture-scale\";\nexport const SHADER_LOADING_ATTR = \"shader-loading\";\nexport const RUNTIME_SRC_ATTR = \"runtime-src\";\nconst SHADER_CAPTURE_SCALE_PARAM = \"__hf_shader_capture_scale\";\nconst SHADER_LOADING_PARAM = \"__hf_shader_loading\";\n\nexport const SHADER_LOADING_PHRASES = [\n \"Preparing scene transitions\",\n \"Sampling outgoing scene motion\",\n \"Sampling incoming scene motion\",\n \"Caching transition frames\",\n \"Finalizing transition preview\",\n];\n\nexport type ShaderLoadingMode = \"composition\" | \"player\" | \"none\";\n\nexport interface ShaderTransitionState {\n ready?: boolean;\n progress?: number;\n total?: number;\n currentTransition?: number;\n transitionTotal?: number;\n transitionFrame?: number;\n transitionFrames?: number;\n phase?: \"cached\" | \"capturing\" | \"finalizing\";\n loading?: boolean;\n}\n\nfunction normalizeShaderCaptureScale(value: string | null): string | null {\n if (value === null) return null;\n const parsed = Number(value);\n if (!Number.isFinite(parsed) || parsed <= 0) return null;\n return String(Math.min(1, Math.max(0.25, parsed)));\n}\n\nfunction normalizeShaderLoadingMode(value: string | null): ShaderLoadingMode {\n if (value === null || value.trim() === \"\") return \"composition\";\n const normalized = value.trim().toLowerCase();\n if (\n normalized === \"none\" ||\n normalized === \"false\" ||\n normalized === \"0\" ||\n normalized === \"off\"\n ) {\n return \"none\";\n }\n if (\n normalized === \"player\" ||\n normalized === \"true\" ||\n normalized === \"1\" ||\n normalized === \"on\"\n ) {\n return \"player\";\n }\n return \"composition\";\n}\n\n/** Drop one of our own keys from raw `a=1&b=2` pairs, matched by name so that\n * nothing around it has to be decoded. */\nfunction withoutParam(pairs: string[], key: string): string[] {\n return pairs.filter((pair) => pair !== \"\" && pair.split(\"=\")[0] !== key);\n}\n\n/**\n * The player's own params, appended to the query the composition author wrote\n * rather than merged into a re-serialized copy of it.\n *\n * `new URLSearchParams(query).toString()` is a form-encoding round trip: it\n * re-encodes the *whole* query as application/x-www-form-urlencoded, which\n * writes every space as `+`. A composition reading its own query with\n * `decodeURIComponent` — percent-decoding, which leaves `+` alone — cannot undo\n * that, so a value of \"Ship it today\" arrived on the page as \"Ship+it+today\".\n * The two codecs are not inverses, and the player has no business picking one\n * for a query it is only passing along. The author's bytes now travel through\n * byte-identical; only our two keys are rewritten.\n */\nfunction withShaderQueryParams(\n src: string,\n scale: string | null,\n loadingMode: ShaderLoadingMode,\n): string {\n const hashIndex = src.indexOf(\"#\");\n const beforeHash = hashIndex >= 0 ? src.slice(0, hashIndex) : src;\n const hash = hashIndex >= 0 ? src.slice(hashIndex) : \"\";\n const queryIndex = beforeHash.indexOf(\"?\");\n const path = queryIndex >= 0 ? beforeHash.slice(0, queryIndex) : beforeHash;\n const query = queryIndex >= 0 ? beforeHash.slice(queryIndex + 1) : \"\";\n let pairs = withoutParam(query.split(\"&\"), SHADER_CAPTURE_SCALE_PARAM);\n pairs = withoutParam(pairs, SHADER_LOADING_PARAM);\n if (scale !== null) pairs.push(`${SHADER_CAPTURE_SCALE_PARAM}=${encodeURIComponent(scale)}`);\n if (loadingMode !== \"composition\") {\n pairs.push(`${SHADER_LOADING_PARAM}=${encodeURIComponent(loadingMode)}`);\n }\n const nextQuery = pairs.join(\"&\");\n return `${path}${nextQuery ? `?${nextQuery}` : \"\"}${hash}`;\n}\n\nfunction injectShaderOptionsIntoSrcdoc(\n html: string,\n scale: string | null,\n loadingMode: ShaderLoadingMode,\n): string {\n if (scale === null && loadingMode === \"composition\") return html;\n const lines: string[] = [];\n if (scale !== null) lines.push(`window.__HF_SHADER_CAPTURE_SCALE=${JSON.stringify(scale)};`);\n if (loadingMode !== \"composition\") {\n lines.push(`window.__HF_SHADER_LOADING=${JSON.stringify(loadingMode)};`);\n }\n const script = `<script data-hyperframes-player-shader-options>${lines.join(\"\")}</script>`;\n if (/<head\\b[^>]*>/i.test(html))\n return html.replace(/<head\\b[^>]*>/i, (match) => `${match}${script}`);\n if (/<html\\b[^>]*>/i.test(html))\n return html.replace(/<html\\b[^>]*>/i, (match) => `${match}${script}`);\n return `${script}${html}`;\n}\n\n/**\n * Convenience wrappers that read shader attributes directly from an element,\n * avoiding boilerplate in the web component class body.\n */\n\nexport function getShaderModeFromElement(el: Element): ShaderLoadingMode {\n return normalizeShaderLoadingMode(el.getAttribute(SHADER_LOADING_ATTR));\n}\n\nexport function getShaderCaptureScaleFromElement(el: Element): number {\n return Number(normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)) ?? \"1\");\n}\n\nexport function prepareSrcForElement(el: Element, src: string): string {\n return withShaderQueryParams(\n src,\n normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)),\n getShaderModeFromElement(el),\n );\n}\n\nexport function prepareSrcdocForElement(el: Element, srcdoc: string): string {\n // Runtime first, and in the head: a component's inline script reads its\n // variables while the body is parsing, long before the probe's own injection\n // could land. See runtime-in-srcdoc.ts.\n return ensureRuntimeBeforeBodyScripts(\n injectShaderOptionsIntoSrcdoc(\n srcdoc,\n normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)),\n getShaderModeFromElement(el),\n ),\n runtimeSrcFromElement(el),\n );\n}\n\nexport function runtimeSrcFromElement(el: Element): string {\n const configured = el.getAttribute(RUNTIME_SRC_ATTR)?.trim();\n if (!configured) return RUNTIME_CDN_URL;\n try {\n const url = new URL(configured, document.baseURI);\n // A srcdoc frame runs with `allow-same-origin` by default, so it inherits the embedder's\n // origin. An unrestricted host here would therefore be arbitrary script execution in the\n // embedding page, reachable through a prop bag since React spreads unknown props onto\n // custom elements. Loopback and same-origin cover the local rig this exists for; a foreign\n // origin has to be a deliberate, named opt-in rather than a scheme check falling through.\n const okScheme = url.protocol === \"http:\" || url.protocol === \"https:\";\n const loopback =\n url.hostname === \"127.0.0.1\" || url.hostname === \"localhost\" || url.hostname === \"[::1]\";\n return okScheme && (loopback || url.origin === location.origin) ? url.href : RUNTIME_CDN_URL;\n } catch {\n return RUNTIME_CDN_URL;\n }\n}\n","/**\n * Factory for the shader-transition loading overlay DOM tree.\n *\n * Kept in its own module so the ~100-line DOM construction stays out of the\n * web component class body. The returned `ShaderLoaderElements` bag gives the\n * component direct handles to the nodes it needs to update without querying\n * the shadow DOM on every state change.\n */\n\nimport { SHADER_LOADING_PHRASES } from \"./shader-options.js\";\n\nexport interface ShaderLoaderElements {\n root: HTMLDivElement;\n fill: HTMLDivElement;\n title: HTMLSpanElement;\n detail: HTMLDivElement;\n transitionValue: HTMLSpanElement;\n transitionRow: HTMLDivElement;\n frameLabel: HTMLSpanElement;\n frameValue: HTMLSpanElement;\n frameRow: HTMLDivElement;\n}\n\nexport function createShaderLoader(): ShaderLoaderElements {\n const root = document.createElement(\"div\");\n root.className = \"hfp-shader-loader\";\n root.setAttribute(\"role\", \"status\");\n root.setAttribute(\"aria-live\", \"polite\");\n root.setAttribute(\"aria-label\", \"Preparing scene transitions\");\n root.setAttribute(\"data-hyperframes-ignore\", \"\");\n root.draggable = false;\n\n const blockOverlayInteraction = (event: Event) => {\n event.preventDefault();\n event.stopPropagation();\n };\n for (const eventName of [\n \"selectstart\",\n \"dragstart\",\n \"pointerdown\",\n \"mousedown\",\n \"click\",\n \"dblclick\",\n \"contextmenu\",\n \"touchstart\",\n ]) {\n root.addEventListener(eventName, blockOverlayInteraction, { capture: true });\n }\n\n const panel = document.createElement(\"div\");\n panel.className = \"hfp-shader-loader-panel\";\n panel.draggable = false;\n\n const markFrame = document.createElement(\"div\");\n markFrame.className = \"hfp-shader-loader-mark\";\n markFrame.draggable = false;\n markFrame.innerHTML = [\n '<svg width=\"78\" height=\"78\" viewBox=\"0 0 100 100\" fill=\"none\" aria-hidden=\"true\" draggable=\"false\">',\n '<path d=\"M10.1851 57.8021L33.1145 73.8313C36.2202 75.9978 41.5173 73.5433 42.4816 69.4984L51.7611 30.4271C52.7253 26.3822 48.5802 23.9277 44.4602 26.0942L13.917 42.1235C6.96677 45.7676 4.97564 54.1579 10.1851 57.8021Z\" fill=\"url(#hfp-shader-loader-grad-left)\"/>',\n '<path d=\"M87.5129 57.5141L56.9696 73.5433C52.8371 75.7098 48.7046 73.2553 49.6688 69.2104L58.9483 30.1391C59.9125 26.0942 65.2097 23.6397 68.3154 25.8062L91.2447 41.8354C96.4668 45.4796 94.4631 53.8699 87.5129 57.5141Z\" fill=\"url(#hfp-shader-loader-grad-right)\"/>',\n \"<defs>\",\n '<linearGradient id=\"hfp-shader-loader-grad-left\" x1=\"48.5676\" y1=\"25\" x2=\"44.7804\" y2=\"71.9384\" gradientUnits=\"userSpaceOnUse\">',\n '<stop stop-color=\"#06E3FA\"/>',\n '<stop offset=\"1\" stop-color=\"#4FDB5E\"/>',\n \"</linearGradient>\",\n '<linearGradient id=\"hfp-shader-loader-grad-right\" x1=\"54.8282\" y1=\"73.8392\" x2=\"72.0989\" y2=\"32.8932\" gradientUnits=\"userSpaceOnUse\">',\n '<stop stop-color=\"#06E3FA\"/>',\n '<stop offset=\"1\" stop-color=\"#4FDB5E\"/>',\n \"</linearGradient>\",\n \"</defs>\",\n \"</svg>\",\n ].join(\"\");\n\n const titleContainer = document.createElement(\"div\");\n titleContainer.className = \"hfp-shader-loader-title\";\n const titleText = document.createElement(\"span\");\n titleText.className = \"hfp-shader-loader-title-text\";\n titleText.textContent = SHADER_LOADING_PHRASES[0] || \"Preparing scene transitions\";\n titleContainer.appendChild(titleText);\n\n const detail = document.createElement(\"div\");\n detail.className = \"hfp-shader-loader-detail\";\n detail.textContent = \"Rendering animated scene samples for shader transitions.\";\n\n const track = document.createElement(\"div\");\n track.className = \"hfp-shader-loader-track\";\n track.setAttribute(\"aria-hidden\", \"true\");\n const fill = document.createElement(\"div\");\n fill.className = \"hfp-shader-loader-fill\";\n track.appendChild(fill);\n\n const progress = document.createElement(\"div\");\n progress.className = \"hfp-shader-loader-progress\";\n const createProgressRow = (labelText: string) => {\n const row = document.createElement(\"div\");\n row.className = \"hfp-shader-loader-row\";\n const label = document.createElement(\"span\");\n label.className = \"hfp-shader-loader-label\";\n label.textContent = labelText;\n const value = document.createElement(\"span\");\n value.className = \"hfp-shader-loader-value\";\n row.appendChild(label);\n row.appendChild(value);\n progress.appendChild(row);\n return { row, label, value };\n };\n const transitionStatus = createProgressRow(\"transition\");\n const frameStatus = createProgressRow(\"transition frame\");\n\n panel.appendChild(markFrame);\n panel.appendChild(titleContainer);\n panel.appendChild(detail);\n panel.appendChild(track);\n panel.appendChild(progress);\n root.appendChild(panel);\n\n return {\n root,\n fill,\n title: titleText,\n detail,\n transitionValue: transitionStatus.value,\n transitionRow: transitionStatus.row,\n frameLabel: frameStatus.label,\n frameValue: frameStatus.value,\n frameRow: frameStatus.row,\n };\n}\n","/**\n * Runtime state controller for the shader-transition loading overlay.\n *\n * Manages show/hide transitions (with a CSS fade-out delay) and updates\n * the progress bar, phrase text, and detail rows from `ShaderTransitionState`\n * messages received from the iframe.\n *\n * Holds direct references to the DOM nodes created by `createShaderLoader`\n * so state updates never touch the shadow-DOM query API at runtime.\n */\n\nimport { SHADER_LOADING_PHRASES, type ShaderTransitionState } from \"./shader-options.js\";\nimport type { ShaderLoaderElements } from \"./shader-loader-element.js\";\n\nconst HIDE_TRANSITION_MS = 420;\n\n/** A progress row shows only while it has a value, so no bare label is left on the card. */\nfunction setRowValue(row: HTMLElement, value: HTMLElement, text: string): void {\n value.textContent = text;\n row.style.visibility = text ? \"visible\" : \"hidden\";\n}\n\nexport class ShaderLoaderState {\n private readonly _el: ShaderLoaderElements;\n private _hideTimeout: ReturnType<typeof setTimeout> | null = null;\n private _hiddenCallbacks: Array<() => void> = [];\n private _drawingAssets = false;\n\n constructor(elements: ShaderLoaderElements) {\n this._el = elements;\n }\n\n show(): void {\n if (this._hideTimeout) {\n clearTimeout(this._hideTimeout);\n this._hideTimeout = null;\n }\n this._el.root.classList.remove(\"hfp-hiding\");\n this._el.root.classList.add(\"hfp-visible\");\n }\n\n hide(): void {\n if (this._el.root.classList.contains(\"hfp-hiding\")) {\n if (!this._hideTimeout) this._scheduleCleanup();\n return;\n }\n if (!this._el.root.classList.contains(\"hfp-visible\")) return;\n this._el.root.classList.add(\"hfp-hiding\");\n this._el.root.classList.remove(\"hfp-visible\");\n this._scheduleCleanup();\n }\n\n /** Runs `cb` once the panel is fully gone: now if it is not raised or\n * fading, otherwise when the fade's cleanup fires. */\n whenHidden(cb: () => void): void {\n const cls = this._el.root.classList;\n if (cls.contains(\"hfp-visible\") || cls.contains(\"hfp-hiding\")) this._hiddenCallbacks.push(cb);\n else cb();\n }\n\n private _flushHidden(): void {\n const callbacks = this._hiddenCallbacks;\n this._hiddenCallbacks = [];\n for (const cb of callbacks) cb();\n }\n\n reset(): void {\n if (this._hideTimeout) {\n clearTimeout(this._hideTimeout);\n this._hideTimeout = null;\n }\n this._el.root.classList.remove(\"hfp-visible\", \"hfp-hiding\");\n this._drawingAssets = false;\n this._flushHidden();\n this._el.fill.style.transform = \"scaleX(0)\";\n setRowValue(this._el.transitionRow, this._el.transitionValue, \"\");\n setRowValue(this._el.frameRow, this._el.frameValue, \"\");\n }\n\n // fallow-ignore-next-line unused-class-member, complexity\n update(status: ShaderTransitionState, loadingMode: string): void {\n const draws = loadingMode === \"player\" && status.loading && !status.ready;\n // A shader message that draws nothing leaves the Loading assets card to its own owner.\n if (this._drawingAssets && !draws) return;\n if (loadingMode !== \"player\") {\n this.reset();\n return;\n }\n if (status.ready || !status.loading) {\n this.hide();\n return;\n }\n // showAssetsLoading() may have left \"Loading assets\" here for a prior\n // show; this path owns the label whenever it's the one drawing the panel.\n this._el.root.setAttribute(\"aria-label\", \"Preparing scene transitions\");\n this._drawingAssets = false;\n\n const progress =\n typeof status.progress === \"number\" && Number.isFinite(status.progress) ? status.progress : 0;\n const total =\n typeof status.total === \"number\" && Number.isFinite(status.total) ? status.total : 0;\n const ratio = total > 0 ? Math.min(1, Math.max(0, progress / total)) : 0;\n\n const phraseIndex = Math.min(\n SHADER_LOADING_PHRASES.length - 1,\n Math.floor(ratio * SHADER_LOADING_PHRASES.length),\n );\n this._el.title.textContent =\n SHADER_LOADING_PHRASES[phraseIndex] || \"Preparing scene transitions\";\n\n this._el.detail.textContent =\n status.phase === \"cached\"\n ? \"Loading cached transition frames before playback.\"\n : status.phase === \"finalizing\"\n ? \"Uploading transition textures for smooth playback.\"\n : \"Rendering animated scene samples for shader transitions.\";\n\n this._el.fill.style.transform = `scaleX(${ratio})`;\n\n // fallow-ignore-next-line code-duplication\n const transitionValue =\n status.currentTransition !== undefined && status.transitionTotal !== undefined\n ? `${status.currentTransition}/${status.transitionTotal}`\n : total > 0\n ? `${progress}/${total}`\n : \"\";\n\n const frameValue =\n status.transitionFrame !== undefined && status.transitionFrames !== undefined\n ? `${status.transitionFrame}/${status.transitionFrames}`\n : \"\";\n\n this._el.frameLabel.textContent =\n status.phase === \"cached\"\n ? \"cached transition frames\"\n : status.phase === \"finalizing\"\n ? \"finalizing transition frames\"\n : \"rendering transition frames\";\n\n setRowValue(this._el.transitionRow, this._el.transitionValue, transitionValue);\n setRowValue(this._el.frameRow, this._el.frameValue, frameValue);\n this._el.root.setAttribute(\"aria-valuenow\", String(Math.round(ratio * 100)));\n this.show();\n }\n\n /** Second reason to show the same overlay — reuses the shader-transition\n * panel's DOM rather than drawing a second one. */\n showAssetsLoading(): void {\n this.reset();\n this._el.title.textContent = \"Loading assets\";\n this._el.detail.textContent = \"Waiting for images, video and fonts to finish loading.\";\n this._el.root.setAttribute(\"aria-label\", \"Loading assets\");\n this._drawingAssets = true;\n this.show();\n }\n\n /** Hides the panel only while it is the Loading assets card, never a shader load drawn over it. */\n hideAssetsLoading(): void {\n if (this._drawingAssets) this.hide();\n }\n\n // fallow-ignore-next-line unused-class-member\n get hideTimeout(): ReturnType<typeof setTimeout> | null {\n return this._hideTimeout;\n }\n\n destroy(): void {\n if (this._hideTimeout) {\n clearTimeout(this._hideTimeout);\n this._hideTimeout = null;\n }\n this._hiddenCallbacks = [];\n }\n\n private _scheduleCleanup(): void {\n if (this._hideTimeout) clearTimeout(this._hideTimeout);\n this._hideTimeout = setTimeout(() => {\n this._el.root.classList.remove(\"hfp-hiding\");\n this._hideTimeout = null;\n this._flushHidden();\n }, HIDE_TRANSITION_MS);\n }\n}\n","// Video mode: a `<video playsinline>` behind the direct-timeline adapter a same-origin\n// `__timelines` composition uses, so play, seek, loop and `ended` share one code path.\n\nimport type { DirectTimelineAdapter } from \"./timeline-adapters.js\";\n\nexport function isVideoType(type: string | null): boolean {\n return type?.trim().toLowerCase().startsWith(\"video/\") ?? false;\n}\n\nexport interface VideoSourceCallbacks {\n onMetadata: (video: HTMLVideoElement) => void;\n onDurationChange: (video: HTMLVideoElement) => void;\n onResize: (video: HTMLVideoElement) => void;\n onPlay: (video: HTMLVideoElement) => void;\n onPause: (video: HTMLVideoElement) => void;\n onTimeUpdate: () => void;\n onError: (message: string, code: number | null) => void;\n onPlayRejected: (error: unknown) => void;\n}\n\nexport interface VideoSource {\n video: HTMLVideoElement;\n adapter: DirectTimelineAdapter;\n destroy: () => void;\n}\n\nexport function createVideoSource(callbacks: VideoSourceCallbacks): VideoSource {\n const video = document.createElement(\"video\");\n video.className = \"hfp-video\";\n video.playsInline = true;\n video.preload = \"metadata\";\n\n const listening = new AbortController();\n const { signal } = listening;\n video.addEventListener(\"loadedmetadata\", () => callbacks.onMetadata(video), { signal });\n video.addEventListener(\"durationchange\", () => callbacks.onDurationChange(video), { signal });\n video.addEventListener(\"resize\", () => callbacks.onResize(video), { signal });\n video.addEventListener(\"play\", () => callbacks.onPlay(video), { signal });\n video.addEventListener(\"pause\", () => callbacks.onPause(video), { signal });\n video.addEventListener(\"timeupdate\", () => callbacks.onTimeUpdate(), { signal });\n video.addEventListener(\n \"error\",\n () =>\n callbacks.onError(video.error?.message || \"Video failed to load\", video.error?.code ?? null),\n { signal },\n );\n\n const adapter: DirectTimelineAdapter = {\n duration: () => video.duration,\n time: () => video.currentTime,\n seek: (timeInSeconds) => {\n video.currentTime = timeInSeconds;\n },\n // A rejection that lands after destroy() belongs to a source that is gone.\n play: () =>\n video.play().catch((error: unknown) => {\n if (!signal.aborted) callbacks.onPlayRejected(error);\n }),\n pause: () => video.pause(),\n // The default rate survives a new src; the current rate alone would reset to it.\n timeScale: (rate) => {\n video.defaultPlaybackRate = rate;\n video.playbackRate = rate;\n },\n };\n\n return {\n video,\n adapter,\n destroy: () => {\n listening.abort();\n video.pause();\n video.removeAttribute(\"src\");\n video.load();\n video.remove();\n },\n };\n}\n"],"mappings":"ubAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,uBAAAE,GAAA,kBAAAC,GAAA,gBAAAC,EAAA,eAAAC,KAAA,eAAAC,GAAAN,ICsBA,IAAMO,GAAoBC,GACxBA,IAAU,MAAQ,OAAO,SAASA,CAAK,GAAKA,EAAQ,EAE/C,SAASC,GACdC,EAC2B,CAC3B,IAAMC,EAAeD,EAAM,gBAAgB,OAAQE,GAAQA,IAAQ,IAAI,EAAE,OACzE,GAAIL,GAAiBG,EAAM,uBAAuB,EAChD,MAAO,CAAE,QAASA,EAAM,wBAAyB,OAAQ,WAAY,aAAAC,CAAa,EAEpF,IAAME,EAAQH,EAAM,gBAAgB,OAAOH,EAAgB,EAC3D,OAAIM,EAAM,OAAS,EACV,CAAE,QAAS,KAAK,IAAI,GAAGA,CAAK,EAAG,OAAQ,UAAW,aAAAF,CAAa,EAEjE,CACL,QAAS,KACT,OAAQ,aACR,aAAAA,EACA,OACEA,EAAe,EACX,uCACA,gDACR,CACF,CCLA,IAAMG,GAAW,+BASX,SAAUC,GAAcC,EAAc,CAC1C,OAAOA,GAAS,MAASA,EAAmB,WAAa,CAC3D,CAOM,SAAUC,GAAcD,EAAc,CAC1C,OAAOD,GAAcC,CAAK,GAAMA,EAAmB,eAAiBF,EACtE,CAIA,SAASI,GAAUF,EAAgBG,EAAW,CAC5C,OAAOF,GAAcD,CAAK,GAAMA,EAAmB,YAAcG,CACnE,CAGM,SAAUC,GAAeJ,EAAc,CAC3C,OAAOE,GAAUF,EAAO,OAAO,CACjC,CAGM,SAAUK,GAAeL,EAAc,CAC3C,OAAOE,GAAUF,EAAO,OAAO,CACjC,CAGM,SAAUM,EAAeN,EAAc,CAC3C,OAAOI,GAAeJ,CAAK,GAAKK,GAAeL,CAAK,CACtD,CAGM,SAAUO,GAAeP,EAAc,CAC3C,OAAOE,GAAUF,EAAO,KAAK,CAC/B,CC5EM,SAAUQ,GAA0BC,EAAgC,CACxE,GAAIA,GAAS,MAAQA,EAAM,KAAI,IAAO,GAAI,OAAO,KACjD,IAAMC,EAAS,OAAO,SAASD,EAAO,EAAE,EACxC,OAAO,OAAO,SAASC,CAAM,GAAKA,EAAS,EAAIA,EAAS,IAC1D,CAEM,SAAUC,GAA2BC,EAAkB,SAAQ,CACnE,IAAMC,EAAeD,EAAI,cAAc,yCAAyC,EAChF,OAAIE,GAAcD,CAAY,EAAUA,EAEjCD,EAAI,cAA2B,uBAAuB,CAC/D,CCXO,IAAMG,EAAyB,OAAO,OAAO,CAClD,MAAO,aACP,SAAU,gBACV,WAAY,mBACZ,WAAY,WACZ,YAAa,YACf,CAAU,EAEGC,GAAuC,OAAO,OAAO,CAChED,EAAuB,MACvBA,EAAuB,SACvBA,EAAuB,UACzB,CAAU,EAEGE,GAA4B,OAAO,OAAO,CACrDF,EAAuB,UACzB,CAAU,EAEGG,GAA2B,OAAO,OAAO,CACpDH,EAAuB,WACvBA,EAAuB,WACzB,CAAU,EAoEH,SAASI,GAAaC,EAAiD,CAC5E,GAAIA,GAAS,MAAQA,EAAM,KAAK,IAAM,GAAI,OAAO,KACjD,IAAMC,EAAS,OAAOD,CAAK,EAC3B,OAAO,OAAO,SAASC,CAAM,EAAIA,EAAS,IAC5C,CC7FA,IAAMC,GAAeC,GAAkD,CACrE,IAAMH,EAAQD,GAAaI,CAAG,EAC9B,OAAOH,IAAU,MAAQA,GAAS,EAAIA,EAAQ,IAChD,EAKO,SAASI,GAAuBC,EAA6B,CAClE,OACEH,GAAYG,EAAQ,qBAAqB,CAAC,GAAKH,GAAYG,EAAQ,kBAAkB,CAAC,GAAK,CAE/F,CAGO,IAAMC,EAAoB,GACpBC,EAAoB,GAG1B,SAASC,GAAkBL,EAAqB,CACrD,OAAO,OAAO,SAASA,CAAG,GAAKA,EAAM,EACjC,KAAK,IAAIG,EAAmB,KAAK,IAAIC,EAAmBJ,CAAG,CAAC,EAC5D,CACN,CAIO,SAASM,GAAiBJ,EAAqBK,EAAW,EAAW,CAC1E,IAAMC,EAAW,OAAO,WAAWN,EAAQ,oBAAoB,GAAK,EAAE,EACtE,OAAOG,GAAkB,OAAO,SAASG,CAAQ,GAAKA,EAAW,EAAIA,EAAWD,CAAQ,CAC1F,CAGO,SAASE,GAAwBP,EAAoC,CAC1E,IAAMQ,EAAWd,GAAaM,EAAQ,eAAe,CAAC,EACtD,OAAOQ,IAAa,MAAQA,EAAW,EAAIA,EAAW,IACxD,CAIO,SAASC,GACdT,EACAU,EACe,CACf,IAAMF,EAAWD,GAAwBP,CAAO,EAChD,GAAIQ,IAAa,KAAM,OAAOA,EAC9B,IAAMG,EAAMjB,GAAaM,EAAQ,UAAU,CAAC,EAC5C,OAAOW,IAAQ,KAAOA,EAAMD,EAAe,IAC7C,CAIO,IAAME,GAA0C,EAGhD,SAASC,GACdC,EACAC,EACAC,EACe,CACf,OAAK,OAAO,SAASF,CAAqB,EACxB,KAAK,IAAI,EAAGA,EAAwBC,CAAiB,EACpDZ,GAAkBa,CAAY,EAFG,IAGtD,CAqBO,SAASC,GAAqBC,EAAuD,CAC1F,GAAM,CAAE,IAAAC,EAAK,wBAAAC,EAAyB,sBAAAN,EAAuB,kBAAAC,EAAmB,aAAAC,CAAa,EAC3FE,EACF,GAAIE,IAA4B,MAAQA,EAA0B,EAChE,MAAO,CAAE,QAASA,EAAyB,OAAQ,UAAW,EAEhE,GAAID,IAAQ,MACV,MAAO,CAAE,QAASP,GAAyC,OAAQ,SAAU,EAE/E,GAAIE,IAA0B,KAC5B,MAAO,CAAE,QAAS,KAAM,OAAQ,UAAW,OAAQ,gCAAiC,EAEtF,IAAMO,EAAUR,GACdC,EACAC,EACAC,CACF,EACA,OAAIK,IAAY,KACP,CAAE,QAAS,KAAM,OAAQ,UAAW,OAAQ,uCAAwC,EAEtF,CAAE,QAAAA,EAAS,OAAQ,OAAQ,CACpC,CCnGO,IAAMC,GAAmB,gBAUnBC,GAAuBD,GAAiB,MAAM,CAAc,EA4DzE,IAAME,EAAO,CACXC,EAAM,YACNC,EAAQ,YACRC,EAAM,IACNC,EAAM,GACNC,EAAM,OACoB,CAC1B,KAAM,SACN,IAAAJ,EACA,MAAAC,EACA,KAAM,KACN,IAAAE,EACA,IAAAC,EACA,KAAM,EACN,QAASF,EACT,MAAO,MACP,YAAa,KAGTG,GAAS,CAACH,EAAM,KAAOI,EAAO,0CAA6D,CAC/F,KAAM,SACN,IAAK,IACL,MAAO,IACP,KAAM,GACN,IAAK,GACL,IAAK,GACL,KAAM,IACN,QAASJ,EACT,MAAO,MACP,YAAa,GACb,KAAAI,IAGIC,GAAS,CAACJ,EAAM,IAAKC,EAAM,GAAIF,EAAM,KAA6B,CACtE,KAAM,SACN,IAAK,OACL,MAAO,OACP,KAAM,KACN,IAAAC,EACA,IAAAC,EACA,KAAM,GACN,QAASF,EACT,YAAa,KAGTM,GAA4B,CAChC,KAAM,OACN,IAAK,QACL,MAAO,QACP,QAAS,CACP,CAAE,MAAO,IAAK,MAAO,UAAU,EAC/B,CAAE,MAAO,IAAK,MAAO,WAAW,GAElC,QAAS,IACT,KAAM,uDAOKC,GAAuC,CAClD,CACE,GAAI,OACJ,MAAO,OACP,MAAO,WACP,YAAa,6EAGb,OAAQ,CAACF,GAAO,IAAK,GAAI,CAAC,CAAC,EAC3B,IAAK,aAEP,CACE,GAAI,UACJ,MAAO,aACP,MAAO,SACP,YAAa,6DACb,OAAQ,CAACR,EAAK,YAAa,YAAa,GAAI,EAAGQ,GAAO,IAAK,GAAI,CAAC,EAAGF,GAAO,CAAC,CAAC,EAC5E,IAAK,kBAEP,CACE,GAAI,WACJ,MAAO,YACP,MAAO,SACP,YAAa,sDAIb,OAAQ,CAACN,EAAK,YAAa,YAAa,IAAK,GAAI,GAAI,EAAGQ,GAAO,IAAK,GAAI,CAAC,CAAC,EAC1E,IAAK,mBAEP,CACE,GAAI,YACJ,MAAO,aACP,MAAO,SACP,YAAa,sDACb,OAAQ,CAACR,EAAK,YAAa,YAAa,IAAM,IAAK,GAAK,EAAGQ,GAAO,IAAK,GAAI,CAAC,CAAC,EAC7E,IAAK,oBAEP,CACE,GAAI,WACJ,MAAO,YACP,MAAO,SACP,YAAa,qEACb,OAAQ,CAACR,EAAK,YAAa,SAAU,IAAK,GAAI,GAAK,EAAGM,GAAO,IAAK,EAAGG,EAAK,EAC1E,IAAK,mBAEP,CACE,GAAI,UACJ,MAAO,WACP,MAAO,SACP,YAAa,6DACb,OAAQ,CAACT,EAAK,YAAa,SAAU,IAAM,IAAK,GAAK,EAAGM,GAAO,IAAK,EAAGG,EAAK,EAC5E,IAAK,kBAGP,CACE,GAAI,aACJ,MAAO,aACP,MAAO,WACP,YAAa,sDACb,OAAQ,CACN,CACE,KAAM,SACN,IAAK,YACL,MAAO,YACP,KAAM,KACN,IAAK,IACL,IAAK,EACL,KAAM,GACN,QAAS,IACT,KAAM,oDAER,CACE,KAAM,SACN,IAAK,QACL,MAAO,QACP,KAAM,KACN,IAAK,EACL,IAAK,GACL,KAAM,GACN,QAAS,GAEX,CACE,KAAM,SACN,IAAK,SACL,MAAO,SACP,KAAM,KACN,IAAK,IACL,IAAK,IACL,KAAM,GACN,QAAS,GACT,MAAO,OAET,CACE,KAAM,SACN,IAAK,UACL,MAAO,UACP,KAAM,KACN,IAAK,IACL,IAAK,IACL,KAAM,EACN,QAAS,IACT,MAAO,OAET,CACE,KAAM,SACN,IAAK,OACL,MAAO,OACP,KAAM,GACN,IAAK,EACL,IAAK,EACL,KAAM,IACN,QAAS,KACT,KAAM,6CAER,CACE,KAAM,SACN,IAAK,SACL,MAAO,SACP,KAAM,KACN,IAAK,EACL,IAAK,GACL,KAAM,GACN,QAAS,GAEX,CACE,KAAM,SACN,IAAK,MACL,MAAO,MACP,KAAM,GACN,IAAK,EACL,IAAK,EACL,KAAM,IACN,QAAS,EACT,KAAM,2CAGV,IAAK,sBAEP,CACE,GAAI,UACJ,MAAO,UACP,MAAO,WACP,YAAa,mDACb,OAAQ,CACN,CACE,KAAM,SACN,IAAK,QACL,MAAO,UACP,KAAM,KACN,IAAK,IACL,IAAK,EACL,KAAM,GACN,QAAS,IAEX,CACE,KAAM,SACN,IAAK,SACL,MAAO,SACP,KAAM,KACN,IAAK,GACL,IAAK,GACL,KAAM,GACN,QAAS,GAEX,CACE,KAAM,SACN,IAAK,UACL,MAAO,UACP,KAAM,KACN,IAAK,EACL,IAAK,IACL,KAAM,EACN,QAAS,GACT,MAAO,OAET,CACE,KAAM,SACN,IAAK,YACL,MAAO,SACP,KAAM,KACN,IAAK,IACL,IAAK,GACL,KAAM,GACN,QAAS,IAGb,IAAK,mBAEP,CACE,GAAI,OACJ,MAAO,aACP,MAAO,WACP,YAAa,uDACb,OAAQ,CACN,CACE,KAAM,SACN,IAAK,YACL,MAAO,YACP,KAAM,KACN,IAAK,IACL,IAAK,EACL,KAAM,GACN,QAAS,KAEX,CACE,KAAM,SACN,IAAK,QACL,MAAO,QACP,KAAM,KACN,IAAK,IACL,IAAK,EACL,KAAM,GACN,QAAS,IACT,KAAM,4CAER,CACE,KAAM,SACN,IAAK,QACL,MAAO,QACP,KAAM,KACN,IAAK,EACL,IAAK,GACL,KAAM,GACN,QAAS,IAEX,CACE,KAAM,SACN,IAAK,SACL,MAAO,SACP,KAAM,KACN,IAAK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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA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","names":["hyperframes_player_exports","__export","HyperframesPlayer","SPEED_PRESETS","formatSpeed","formatTime","__toCommonJS","isPositiveFinite","value","resolveCompositionDuration","input","pendingClips","end","known","XHTML_NS","isElementNode","value","isHtmlElement","isHtmlTag","tag","isVideoElement","isAudioElement","isMediaElement","isImageElement","parseCompositionDimension","value","parsed","findRootCompositionElement","doc","explicitRoot","isHtmlElement","COMPOSITION_ATTRIBUTES","CANONICAL_AUTHORED_TIMING_ATTRIBUTES","DERIVED_TIMING_ATTRIBUTES","LEGACY_TIMING_ATTRIBUTES","parseNumeric","value","parsed","nonNegative","raw","readMediaOffsetSeconds","getAttr","MIN_PLAYBACK_RATE","MAX_PLAYBACK_RATE","clampPlaybackRate","readPlaybackRate","fallback","authored","readDataDurationSeconds","duration","readAuthoredDurationSeconds","startSeconds","end","DEFAULT_IMAGE_TIMELINE_DURATION_SECONDS","resolveNaturalDurationSeconds","sourceDurationSeconds","mediaStartSeconds","playbackRate","resolveMediaDuration","input","tag","authoredDurationSeconds","seconds","HF_AUDIO_FX_ATTR","HF_AUDIO_FX_DATA_KEY","freq","key","label","def","min","max","qParam","hint","gainDb","poles","HF_AUDIO_FX","BY_ID","d","HF_AUDIO_FX_IDS","HF_AUDIO_FX","d","audioDbToGain","db","MAX_AUDIO_GAIN","audioDbToGain","HF_AUDIO_AUTOMATION_ATTR","HF_AUDIO_AUTOMATION_DATA_KEY","HF_AUDIO_AUTOMATION_VERSION","MAX_AUTOMATION_POINTS","AudioAutomationError","message","VOLUME_TARGET","RATE_TARGET","parseAutomationTarget","target","parts","PRESET_TARGET_KEY","presetId","nodeId","param","PRESET_TARGET_KEY","VOLUME_RANGE","MAX_AUDIO_GAIN","RATE_RANGE","MIN_PLAYBACK_RATE","MAX_PLAYBACK_RATE","numberOrNull","raw","n","clampCurve","v","clampVia","rawX","rawY","x","y","inside","cx","cy","shapeFields","p","curve","clampCurve","via","clampVia","cleanPoint","range","numberOrNull","v","clamped","normalizePoints","points","clean","a","b","out","MAX_AUTOMATION_POINTS","normalizeAutomation","automation","lanes","lane","parseAutomationTarget","VOLUME_TARGET","VOLUME_RANGE","RATE_TARGET","RATE_RANGE","HF_AUDIO_AUTOMATION_VERSION","parseAutomation","json","raw","err","AudioAutomationError","obj","HF_AUDIO_AUTOMATION_VERSION","lanes","l","i","lane","parseAutomationTarget","normalizeAutomation","applyCurve","x","curve","viaConic","viaX","viaY","dx","dy","edge","MAX_WEIGHT","needX","needY","w","shapeVia","cx","cy","spread","a","b","t","conicParam","rest","denominator","c","root","first","second","onArc","shapeProgress","point","via","clampVia","lerpValue","scale","sampleAutomationLane","lane","pts","last","lo","hi","mid","span","cache","CACHE_LIMIT","laneFromAttr","raw","target","VOLUME_TARGET","key","hit","lane","parseAutomation","l","CELLS_PER_SEGMENT","tables","buildTable","lane","pts","firstRate","lastRate","ts","ss","push","t","prevT","prevRate","sampleAutomationLane","first","i","a","b","k","tableFor","table","usable","spec","interpolate","xs","ys","x","lo","hi","mid","timeAtSourceTime","spec","source","usable","ts","ss","firstRate","lastRate","tableFor","last","interpolate","parseRateLane","raw","laneFromAttr","RATE_TARGET","resolveRateSpec","automationAttr","constant","SPEED_PRESETS","COMPOSITION_ATTRIBUTES","CANONICAL_AUTHORED_TIMING_ATTRIBUTES","DERIVED_TIMING_ATTRIBUTES","LEGACY_TIMING_ATTRIBUTES","parseNumeric","value","parsed","REFERENCE_ID_PATTERN","isAsciiDigitAt","index","code","skipDigitsLeft","start","cursor","skipWhitespaceLeft","findMagnitudeStart","last","parseReferenceOffset","magnitudeStart","operatorIndex","operator","refId","magnitude","parseStartExpression","raw","normalized","absolute","reference","pushDiagnostic","diagnostics","attribute","resolveStart","expression","rawStart","options","COMPOSITION_ATTRIBUTES","referencedEnd","DERIVED_END_EQUALITY_EPSILON_SECONDS","derivedEndsAreConsistent","parsedEnd","canonicalEnd","diagnoseDerivedEnd","rawEnd","readCanonicalDuration","rawDuration","duration","readLegacyEnd","end","readDuration","attributes","canonical","readTrackValue","rawValue","source","trackIndex","readTrack","rawTrack","rawLayer","parsedLayer","readClipTiming","startExpression","track","parseStrictFiniteTimingNumber","raw","parseNumeric","readElementPlaybackRate","el","readPlaybackRate","name","isMediaElement","readElementRateSpec","el","resolveRateSpec","readElementPlaybackRate","readMediaStart","readMediaOffsetSeconds","name","resolveNaturalMediaTimelineDuration","sourceDuration","resolveNaturalMediaTimelineDurationFromValues","resolveMediaElementDurationSeconds","resolved","resolveMediaDuration","readDataDurationSeconds","resolveTimedImageDurationSeconds","startSeconds","isImageElement","readAuthoredDurationSeconds","mediaStart","playbackRate","resolveNaturalDurationSeconds","timeAtSourceTime","getDebugSurface","swallow","label","error","w","getDebugSurface","handler","handlerError","AUTHORED_DURATION_ATTR","AUTHORED_END_ATTR","finiteNumber","value","parsed","resolveAuthoredTimingWindow","values","parsedStart","start","publicDuration","preservedDuration","duration","publicEnd","preservedEnd","end","MEDIA_START_BASIS_ATTR","readMediaStartBasis","value","resolveAbsoluteMediaStartSeconds","input","resolveMediaStartSeconds","hasLiteralNestedStart","isRootGlobalMediaStart","createRuntimeStartTimeResolver","params","timelineRegistry","includeAuthoredTimingAttrs","doc","startCache","durationCache","visiting","findReferenceTarget","refId","byId","resolveDurationForElement","element","cached","resolved","durationTiming","resolveAuthoredTimingWindow","start","resolveStartForElementInternal","endTiming","isMediaElement","resolveNaturalMediaTimelineDuration","resolveTimedImageDurationSeconds","compositionId","timeline","timelineDuration","err","swallow","resolveHostOffsetForElement","fallback","parentComposition","compositionRoot","expression","parseStartExpression","parent","parentStart","absolute","target","targetStart","targetDuration","unresolved","mediaStartInput","parseStrictFiniteTimingNumber","MEDIA_START_BASIS_ATTR","input","resolveMediaStartSeconds","isRootGlobalMediaStart","createRuntimeState","LOOP_INFLATED_TIMELINE_SECONDS","MIN_VALID_TIMELINE_DURATION_SECONDS","aboveOneFrame","seconds","readCompositionSize","root","parseCompositionDimension","resolveMediaWindowDurationSeconds","doc","media","clipEnds","node","start","duration","resolveCompositionDuration","resolveAuthoredCompositionFloorSeconds","startResolver","subCompositionEnds","declared","parseStrictFiniteTimingNumber","isCompositionHost","resolveContentDerivedDuration","unregisteredLottie","isMediaElement","result","resolveCompositionLengthSeconds","input","rawTimeline","floor","fallback","floorOrFallback","timeline","readStaticCompositionMeta","findRootCompositionElement","resolver","createRuntimeStartTimeResolver","durationSeconds","resolveMediaElementDurationSeconds","createRuntimeState","STUDIO_PREVIEW_MARK_META","STUDIO_PREVIEW_ERRORS","shouldInjectRuntime","state","isObjectRecord","value","isRuntimeDurationAdapter","isDirectTimelineAdapter","RUNTIME_CDN_URL","readPositiveDimension","value","parsed","readCompositionSizeFromDocument","doc","root","width","height","firstAuthorError","errors","CompositionProbe","_iframe","_callbacks","attempts","outcome","error","win","message","STUDIO_PREVIEW_ERRORS","timelines","hasRuntime","shouldInjectRuntime","isObjectRecord","result","adapter","duration","readStaticCompositionMeta","script","RUNTIME_CDN_URL","keys","rootId","key","timeline","isDirectTimelineAdapter","runtimePlayer","isRuntimeDurationAdapter","PLAYER_STYLES","PLAY_ICON","PAUSE_ICON","VOLUME_HIGH_ICON","VOLUME_LOW_ICON","VOLUME_MUTED_ICON","SPEED_PRESETS","formatSpeed","speed","formatTime","seconds","s","m","sec","createControls","parent","callbacks","options","presets","controls","e","playBtn","PLAY_ICON","PAUSE_ICON","scrubber","progress","time","speedWrap","speedBtn","speedMenu","preset","item","volumeWrap","muteBtn","VOLUME_HIGH_ICON","volumeSliderWrap","volumeSlider","volumeFill","isPlaying","isMuted","currentVolume","hideTimeout","speedIndex","getVolumeIcon","muted","volume","VOLUME_MUTED_ICON","VOLUME_LOW_ICON","volumeScrubbing","handleVolumeAt","clientX","rect","fraction","onVolumeMouseMove","onVolumeMouseUp","touch","onVolumeTouchMove","onVolumeTouchEnd","VOLUME_STEP","newVol","setActiveOption","opt","isOpen","target","newSpeed","onDocClick","handleScrubAt","scrubbing","onMouseMove","onMouseUp","onTouchMove","onTouchEnd","startHideTimer","host","onHostMouseMove","onHostMouseLeave","current","duration","clampedCurrent","pct","playing","idx","hidden","setupControls","parent","muted","volume","speedPresetsAttr","callbacks","audioLocked","speedPresets","n","options","api","createControls","setupPoster","posterUrl","existing","isControlsClick","event","t","_sharedSheet","adoptShadowStyles","shadow","cssText","style","createCompositionIframe","container","iframe","scaleIframeToFit","playerElement","compositionWidth","compositionHeight","w","h","scale","reachedStop","time","lookAhead","stop","early","DirectTimelineClock","_callbacks","timeline","getCurrentTime","getDuration","isPaused","getStop","lastTime","lastStep","tick","currentTime","duration","step","completedPlayback","now","selectMediaObserverTargets","doc","all","topLevel","el","hasCompositionAncestor","warnOnUnscopedTimedMedia","body","candidates","orphans","cursor","instantTolerance","time","sameInstant","a","b","hasClipStarted","time","start","sameInstant","isInClipWindow","end","TERMINAL_EPSILON_SECONDS","isClipVisibleAt","compositionDuration","HF_AUDIO_FADE_IN_ATTR","HF_AUDIO_FADE_OUT_ATTR","HF_AUDIO_FADE_IN_DATA_KEY","HF_AUDIO_FADE_OUT_DATA_KEY","NO_FADES","AUDIBLE_VIDEO_QUALIFIER","AUDIBLE_MEDIA_SELECTOR","NON_NONE_STACKING_CONTEXT_PROPERTIES","STACKING_CONTEXT_WILL_CHANGE_PROPERTIES","safeFps","fps","safeTime","timeSeconds","snapTimeToFrameBoundary","timeSeconds","fps","grid","safeFps","time","safeTime","framePosition","nearestFrame","exportClipWindow","start","end","RUNTIME_PROTOCOL_CAPABILITIES","greatestCommonDivisor","a","b","left","right","remainder","runtimeProtocolFpsFromNumber","value","safe","denominator","numerator","divisor","playRangeHoldTime","start","end","fps","runtimeProtocolFpsToNumber","runtimeProtocolMetadata","frameDisplayScale","frame","layoutWidth","shownWidth","hasDeclaredCapabilities","capability","inspectRuntimeProtocol","legacyFps","message","isRuntimeElementVisibleAt","rawNode","options","tag","start","duration","compId","compTimeline","liveDuration","AUTHORED_DURATION_ATTR","AUTHORED_END_ATTR","computedEnd","clipWindow","exportClipWindow","isClipVisibleAt","FIRST_FRAME_READINESS_SCOPE","HAVE_FUTURE_DATA","isRealmElement","node","view","isRealmHtmlMediaElement","isTimedElement","element","isActiveAtFirstFrame","resolver","timelineRegistry","current","isRuntimeElementVisibleAt","shouldIncludeAsset","scope","scanPendingCompositionAssets","doc","runtimeWindow","createRuntimeStartTimeResolver","defersMedia","STUDIO_PREVIEW_MARK_META","pendingMedia","el","pendingImages","img","fontsLoading","collectPendingCompositionAssets","signal","mediaReady","resolve","onSettled","imagesReady","fontsReady","mediaReadinessInput","scan","RENDER_READY_POLL_MS","computeReadinessInput","win","timeoutId","finish","poll","IDLE_FRAME_GAP_MS","IDLE_FRAME_GAP_RATIO","IDLE_FRAMES_REQUIRED","MAX_PAINT_WAIT_MS","nextAnimationFrame","id","onAbort","ts","paintAndIdleReadinessInput","firstTs","lastTs","shortestGap","quietStreak","gap","DEFAULT_TIMEOUT_MS","settleCompositionReadiness","opts","inputs","inputDoc","controller","pending","input","p","timeoutMs","timeout","result","settleFirstFrameCompositionReadiness","MIRROR_DRIFT_THRESHOLD_SECONDS","MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES","ParentMediaManager","opts","wasPromoted","m","muted","volume","rate","err","timing","readClipTiming","timeSeconds","isInClipWindow","timeInSeconds","timelineSeconds","options","force","relTime","iframeDoc","onMirror","el","isRealmHtmlMediaElement","t","mediaEls","iframeEl","audioSrc","entry","idx","src","tag","start","duration","source","rawSrc","created","except","doc","obs","mutations","target","added","isRealmElement","candidates","inside","removed","dropped","observeOpts","targets","selectMediaObserverTargets","resolvePlayRange","start","end","duration","from","cut","to","limit","playRangeStopTime","range","fps","playRangeHoldTime","isOutsidePlayRange","time","UI_UPDATE_INTERVAL_MS","runtimeTime","data","fps","isAtEnd","time","duration","playBounds","current","playing","range","atEnd","olderRuntimeBounds","end","entering","playRangeStopTime","applyRuntimeStateMessage","callbacks","rawTime","wasPlaying","bounds","loopStart","clampedTime","currentTime","nextPaused","completedPlayback","next","now","playStateChanged","extractScenes","raw","s","handleRuntimeMessage","event","frameWindow","callbacks","data","protocol","inspectRuntimeProtocol","ownsPlayRange","state","applyRuntimeStateMessage","iframeDoc","t","opts","declaredDuration","frameDuration","duration","pb","alreadyHasRuntime","html","runtimeUrl","OPENING_TAG_BOUNDARIES","findOpeningTag","tagName","lower","prefix","from","index","boundary","close","ensureRuntimeBeforeBodyScripts","tag","head","body","htmlTag","SHADER_CAPTURE_SCALE_ATTR","SHADER_LOADING_ATTR","RUNTIME_SRC_ATTR","SHADER_CAPTURE_SCALE_PARAM","SHADER_LOADING_PARAM","SHADER_LOADING_PHRASES","normalizeShaderCaptureScale","value","parsed","normalizeShaderLoadingMode","normalized","withoutParam","pairs","key","pair","withShaderQueryParams","src","scale","loadingMode","hashIndex","beforeHash","hash","queryIndex","path","query","nextQuery","injectShaderOptionsIntoSrcdoc","html","lines","script","match","getShaderModeFromElement","el","getShaderCaptureScaleFromElement","prepareSrcForElement","prepareSrcdocForElement","srcdoc","ensureRuntimeBeforeBodyScripts","runtimeSrcFromElement","configured","RUNTIME_CDN_URL","url","okScheme","loopback","createShaderLoader","root","blockOverlayInteraction","event","eventName","panel","markFrame","titleContainer","titleText","SHADER_LOADING_PHRASES","detail","track","fill","progress","createProgressRow","labelText","row","label","value","transitionStatus","frameStatus","HIDE_TRANSITION_MS","setRowValue","row","value","text","ShaderLoaderState","elements","cb","cls","callbacks","status","loadingMode","draws","progress","total","ratio","phraseIndex","SHADER_LOADING_PHRASES","transitionValue","frameValue","isVideoType","type","createVideoSource","callbacks","video","listening","signal","timeInSeconds","error","rate","MIN_PLAYBACK_RATE","MAX_PLAYBACK_RATE","SANDBOX_ORIGIN_ATTR","RUNTIME_DATA_DELIVERY_TIMEOUT_MS","ASSETS_READY_TIMEOUT_MS","ASSETS_LOADING_ATTR","ASSETS_LOADING_UI_ATTR","LOW_POWER_IDLE_ATTR","DISABLE_CLICK_TO_PLAY_ATTR","RANGE_START_ATTR","RANGE_END_ATTR","ASSETS_LOADING_SHOW_DELAY_MS","clampPlaybackRate","rate","HyperframesPlayer","RUNTIME_SRC_ATTR","SHADER_CAPTURE_SCALE_ATTR","SHADER_LOADING_ATTR","adoptShadowStyles","PLAYER_STYLES","createCompositionIframe","loaderElements","createShaderLoader","ShaderLoaderState","ParentMediaManager","e","DirectTimelineClock","currentTime","duration","CompositionProbe","runtimeSrcFromElement","result","message","event","isControlsClick","setupPoster","prepareSrcdocForElement","name","oldVal","val","src","readPositiveDimension","v","reloadActiveDocument","srcdoc","announce","range","isOutsidePlayRange","directTimelineStarted","queuedForReady","timeInSeconds","target","grading","compare","channel","payload","retained","t","r","getShaderCaptureScaleFromElement","scale","getShaderModeFromElement","mode","m","locked","ua","disabled","l","seconds","value","resolvePlayRange","endSeconds","park","clamped","clampKey","detail","time","end","hold","playRangeStopTime","action","extra","frameWindow","runtimeProtocolMetadata","error","requestId","bridge","previous","timeoutId","pending","muted","iframeDoc","el","isRealmHtmlMediaElement","frameDisplayScale","isVideoType","prepareSrcForElement","createVideoSource","video","code","adapter","player","fn","resolved","tl","tick","handleRuntimeMessage","paused","lastUpdateMs","enteringRange","w","h","assetsReady","fps","owns","scenes","d","p","ev","doc","compositionSize","runtimeAssetsReady","generation","settleFirstFrameCompositionReadiness","timedOut","pendingMedia","pendingImages","fontsLoading","scanPendingCompositionAssets","FIRST_FRAME_READINESS_SCOPE","win","img","reason","width","height","changed","apply","queue","errors","run","step","applied","scaleIframeToFit","setupControls","f","s","opts","o"]}