@hyperframes/player 0.7.101 → 0.7.103

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","../src/shouldInjectRuntime.ts","../src/timeline-adapters.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","../src/media-element-guards.ts","../../parsers/src/compositionContract.ts","../src/parent-media.ts","../src/playback-state.ts","../../core/src/runtime/protocol.ts","../src/runtime-message-handler.ts","../src/shader-options.ts","../src/shader-loader-element.ts","../src/shader-loader-state.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 SHADER_CAPTURE_SCALE_ATTR,\n SHADER_LOADING_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 { runtimeProtocolMetadata } from \"@hyperframes/core/runtime/protocol\";\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;\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\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 SHADER_CAPTURE_SCALE_ATTR,\n SHADER_LOADING_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 _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\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.dispatchEvent(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.dispatchEvent(new CustomEvent(\"timeupdate\", { detail: { currentTime } }));\n },\n getLoop: () => this.loop,\n restart: () => {\n this.seek(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.dispatchEvent(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.dispatchEvent(new CustomEvent(\"error\", { detail: { message } })),\n });\n\n this.addEventListener(\"click\", (event) => {\n if (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 }\n\n connectedCallback() {\n this.resizeObserver.observe(this);\n window.addEventListener(\"message\", this._onMessage);\n this.iframe.addEventListener(\"load\", this._onIframeLoad);\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\"))\n this.iframe.src = prepareSrcForElement(this, 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._sendControl(\"pause\");\n this._stopIframeMedia();\n this.resizeObserver.disconnect();\n window.removeEventListener(\"message\", this._onMessage);\n this.iframe.removeEventListener(\"load\", this._onIframeLoad);\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._ready = false;\n }\n\n // fallow-ignore-next-line complexity\n attributeChangedCallback(name: string, _old: string | null, val: string | null) {\n switch (name) {\n case \"src\":\n if (val) {\n this._ready = false;\n this.iframe.src = prepareSrcForElement(this, val);\n }\n break;\n case \"srcdoc\":\n this._ready = false;\n if (val !== null) this.iframe.srcdoc = prepareSrcdocForElement(this, val);\n else this.iframe.removeAttribute(\"srcdoc\");\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._compositionWidth = readPositiveDimension(val) ?? 1920;\n this._rescale();\n break;\n case \"height\":\n this._compositionHeight = readPositiveDimension(val) ?? 1080;\n this._rescale();\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.dispatchEvent(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._media.updateVolume(v);\n this._sendControl(\"set-volume\", { volume: v });\n this.controlsApi?.updateVolume(v);\n this.dispatchEvent(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 SHADER_CAPTURE_SCALE_ATTR:\n case SHADER_LOADING_ATTR:\n this._reloadShaderOptions();\n break;\n }\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.posterEl?.remove();\n this.posterEl = null;\n 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 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 // Guards against firing ticks into an uninitialized iframe when play()\n // is called before the probe has resolved.\n if (this._ready && !this._directTimelineAdapter) {\n this._startParentTickClock();\n }\n }\n if (this._media.audioOwner === \"parent\") this._media.playAll();\n this.controlsApi?.updatePlaying(true);\n this.dispatchEvent(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 );\n }\n }\n\n pause() {\n if (!this._tryDirectTimelinePause()) this._sendControl(\"pause\");\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.dispatchEvent(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 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 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 get paused() {\n return this._paused;\n }\n get ready() {\n return this._ready;\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 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._setIframeMediaMuted(val !== null);\n this._sendControl(\"set-muted\", { muted: val !== null });\n this.controlsApi?.updateMuted(val !== null);\n this.dispatchEvent(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 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 private _sendControl(action: string, extra: Record<string, unknown> = {}) {\n try {\n this.iframe.contentWindow?.postMessage(\n {\n ...extra,\n source: \"hf-parent\",\n type: \"control\",\n action,\n ...runtimeProtocolMetadata(this._runtimeFps),\n },\n \"*\",\n );\n } catch {\n /* cross-origin */\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 }\n\n private _reloadShaderOptions(): void {\n if (getShaderModeFromElement(this) !== \"player\") this.shaderLoader.reset();\n if (this.hasAttribute(\"srcdoc\")) {\n this.iframe.srcdoc = prepareSrcdocForElement(this, this.getAttribute(\"srcdoc\") || \"\");\n return;\n }\n if (this.hasAttribute(\"src\")) {\n this.iframe.src = prepareSrcForElement(this, this.getAttribute(\"src\") || \"\");\n }\n }\n\n private _trySyncSeek(timeInSeconds: number): boolean {\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 tl = this._directTimelineAdapter || this.probe.resolveDirectTimelineAdapter();\n if (!tl) return false;\n try {\n fn(tl);\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 /**\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's own rAF loop still runs — ticking GSAP twice per frame is\n * harmless because seekTimelineAndAdapters is idempotent.\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 handleRuntimeMessage(e, this.iframe.contentWindow, {\n getPlaybackState: () => ({\n currentTime: this._currentTime,\n duration: this._duration,\n paused: this._paused,\n lastUpdateMs: this._lastUpdateMs,\n }),\n setPlaybackState: ({ currentTime, duration, paused, lastUpdateMs }) => {\n this._currentTime = currentTime;\n this._duration = duration;\n this._paused = paused;\n this._lastUpdateMs = lastUpdateMs;\n },\n getShaderLoadingMode: () => getShaderModeFromElement(this),\n shaderLoader: this.shaderLoader,\n setCompositionSize: (w, h) => {\n this._compositionWidth = w;\n this._compositionHeight = h;\n this._rescale();\n },\n sendControl: (action, extra) => this._sendControl(action, extra),\n getIframeDoc: () => this.iframe.contentDocument,\n onRuntimeReady: () => this._replayBridgeState(),\n onRuntimeTimelineReady: (duration) => this._onRuntimeTimelineReady(duration),\n setRuntimeFps: (fps) => {\n this._runtimeFps = fps;\n },\n shouldPromoteMediaAutoplayFallback: () => !this._isSlideshowPlayer(),\n setScenes: (scenes) => {\n this._scenes = scenes;\n this.dispatchEvent(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.dispatchEvent(ev),\n seek: (t) => this.seek(t),\n play: () => this.play(),\n getLoop: () => this.loop,\n media: this._media,\n });\n }\n\n private _onRuntimeTimelineReady(duration: number) {\n if (this._ready) return;\n this.probe.stop();\n this._duration = duration;\n this._directTimelineAdapter = null;\n this._ready = true;\n this.controlsApi?.updateTime(this._currentTime, duration);\n this.dispatchEvent(new CustomEvent(\"ready\", { detail: { duration } }));\n // stage-size may not have arrived yet (race in the runtime's postTimeline\n // resolving the root's data-width/data-height on first paint) — rescale\n // here too so cross-origin compositions never stay unscaled/untransformed.\n this._rescale();\n\n const doc = this._getSameOriginIframeDocument();\n if (doc) this._media.setupFromIframe(doc);\n\n this._replayBridgeState();\n this._setIframeMediaMuted(this.muted);\n if (this.hasAttribute(\"autoplay\")) this.play();\n }\n\n private _onProbeReady({ duration, adapter, compositionSize }: ProbeResult) {\n this._duration = duration;\n this._directTimelineAdapter = adapter.kind === \"direct-timeline\" ? adapter.timeline : null;\n this._ready = true;\n this.controlsApi?.updateTime(0, duration);\n this.dispatchEvent(new CustomEvent(\"ready\", { detail: { duration } }));\n if (compositionSize) {\n this._compositionWidth = compositionSize.width;\n this._compositionHeight = compositionSize.height;\n this._rescale();\n }\n try {\n const doc = this.iframe.contentDocument;\n if (doc) this._media.setupFromIframe(doc);\n } catch {\n /* cross-origin */\n }\n this._setIframeMediaMuted(this.muted);\n if (this.hasAttribute(\"autoplay\")) this.play();\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._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 this._ready = false;\n this._directTimelineAdapter = null;\n this._directTimelineClock.stop();\n this._stopParentTickClock();\n this.shaderLoader.reset();\n this._media.resetForIframeLoad();\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","/**\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 two 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: \"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 * 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 * - A `PlaybackDurationAdapter` 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 { shouldInjectRuntime } from \"./shouldInjectRuntime.js\";\nimport {\n type DirectTimelineAdapter,\n type PlaybackDurationAdapter,\n isDirectTimelineAdapter,\n isObjectRecord,\n isRuntimeDurationAdapter,\n} from \"./timeline-adapters.js\";\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\nconst RUNTIME_CDN_URL =\n typeof __HYPERFRAMES_RUNTIME_CDN_URL__ === \"string\"\n ? __HYPERFRAMES_RUNTIME_CDN_URL__\n : runtimeCdnUrlForVersion(\"0.0.0-dev\");\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\nexport class CompositionProbe {\n private _interval: ReturnType<typeof setInterval> | null = null;\n private _runtimeInjected = false;\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. Stops any previously running probe first. */\n start(): void {\n this.stop();\n this._runtimeInjected = false;\n let attempts = 0;\n\n // fallow-ignore-next-line complexity\n this._interval = setInterval(() => {\n attempts++;\n try {\n const win = this._iframe.contentWindow as Window & {\n __player?: { getDuration: () => number };\n __timelines?: Record<string, { duration: () => number }>;\n __hf?: unknown;\n };\n if (!win) return;\n\n const hasRuntime = !!(win.__hf || win.__player);\n const hasTimelines = !!(win.__timelines && Object.keys(win.__timelines).length > 0);\n const hasNestedCompositions =\n !!this._iframe.contentDocument?.querySelector(\"[data-composition-src]\");\n\n if (\n shouldInjectRuntime({\n hasRuntime,\n hasTimelines,\n hasNestedCompositions,\n runtimeInjected: this._runtimeInjected,\n attempts,\n })\n ) {\n this._injectRuntime();\n return;\n }\n\n if (this._runtimeInjected && !hasRuntime) return;\n\n const adapter = this._resolvePlaybackDurationAdapter(win);\n if (adapter && adapter.getDuration() > 0) {\n this.stop();\n\n const compositionSize = readCompositionSizeFromDocument(this._iframe.contentDocument);\n\n this._callbacks.onReady({\n duration: adapter.getDuration(),\n adapter,\n compositionSize,\n });\n return;\n }\n } catch {\n /* cross-origin */\n }\n\n if (attempts >= 40) {\n this.stop();\n this._callbacks.onError(\"Composition timeline not found after 8s\");\n }\n }, 200);\n }\n\n stop(): void {\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-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-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;\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\nexport class DirectTimelineClock {\n private _raf: number | null = null;\n private _lastUpdateMs = 0;\n\n constructor(private readonly _callbacks: ClockCallbacks) {}\n\n start(\n timeline: DirectTimelineAdapter,\n getCurrentTime: () => number,\n getDuration: () => number,\n isPaused: () => boolean,\n ): void {\n this.stop();\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 if (duration > 0) currentTime = Math.min(currentTime, duration);\n\n const completedPlayback = duration > 0 && currentTime >= duration;\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 } 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._raf = requestAnimationFrame(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","export 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\n const view = node.ownerDocument?.defaultView;\n if (view && node instanceof view.HTMLMediaElement) return true;\n return node instanceof HTMLMediaElement;\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","/**\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 { 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 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 const relTime = this._getCurrentTime() - m.start;\n if (relTime < 0 || relTime >= m.duration) 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 `relTime >= m.duration` window check false, 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 _gateEntryPlayback(m: ProxyEntry, relTime: number): boolean {\n if (relTime < 0 || relTime >= m.duration) {\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 const relTime = timeInSeconds - m.start;\n if (relTime >= 0 && relTime < m.duration) m.el.currentTime = relTime;\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 const relTime = timeInSeconds - m.start;\n if (relTime >= 0 && relTime < m.duration) {\n m.el.currentTime = relTime;\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 const relTime = timelineSeconds - m.start;\n if (!this._gateEntryPlayback(m, relTime)) continue;\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\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 // `_createEntry` returns null when a proxy for this URL already exists\n // (e.g. the composition already adopted the same media). In that case we do\n // not own a proxy, so leave the tracking cleared rather than recording a\n // src with no entry — otherwise teardown would target nothing and the\n // no-op guard would never engage.\n this._urlAudioEntry = entry;\n this._urlAudioSrc = entry ? audioSrc : null;\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._entries.some((m) => m.el.src === 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 will re-trigger\n // when the preload attribute is promoted to \"auto\".\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 private _detachIframeMedia(iframeEl: HTMLMediaElement): void {\n const src = this._resolveIframeMediaSrc(iframeEl);\n if (!src) return;\n const idx = this._entries.findIndex((m) => m.el.src === src);\n if (idx === -1) return;\n const entry = this._entries[idx];\n entry.el.pause();\n entry.el.src = \"\";\n this._entries.splice(idx, 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 === \"preload\") {\n const target = m.target;\n if (\n isRealmHtmlMediaElement(target) &&\n target.matches(\"audio[data-start], video[data-start]\") &&\n target.preload === \"auto\"\n ) {\n this._adoptIframeMedia(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\"],\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","/**\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\";\n\nconst UI_UPDATE_INTERVAL_MS = 100;\n\nexport interface PlaybackState {\n currentTime: number;\n duration: number;\n paused: boolean;\n lastUpdateMs: number;\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 media: ParentMediaManager;\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: { frame: number; isPlaying: boolean },\n fps: number,\n current: PlaybackState,\n callbacks: PlaybackStateCallbacks,\n): PlaybackState {\n const rawTime = (data.frame ?? 0) / fps;\n const currentTime = current.duration > 0 ? Math.min(rawTime, current.duration) : rawTime;\n const wasPlaying = !current.paused;\n const nextPaused = !data.isPlaying;\n const completedPlayback =\n current.duration > 0 && currentTime >= current.duration && (wasPlaying || data.isPlaying);\n\n if (completedPlayback && callbacks.getLoop()) {\n if (callbacks.media.audioOwner === \"parent\") callbacks.media.pauseAll();\n callbacks.seek(0);\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, paused: false };\n }\n\n const next: PlaybackState = { ...current, currentTime, paused: nextPaused };\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 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","export const RUNTIME_PROTOCOL_VERSION = 1 as const;\n\nexport const RUNTIME_PROTOCOL_CAPABILITIES = [\n \"seconds-time\",\n \"rational-fps\",\n \"seek-keep-playing\",\n \"composition-manifest-v1\",\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 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\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","/**\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 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) => void;\n setRuntimeFps?: (fps: number) => 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\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\"] === \"state\") {\n callbacks.setPlaybackState(\n applyRuntimeStateMessage(\n { frame: (data[\"frame\"] as number) ?? 0, isPlaying: !!data[\"isPlaying\"] },\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 (Number.isFinite(duration) && duration > 0) {\n const pb = callbacks.getPlaybackState();\n callbacks.setPlaybackState({ ...pb, duration });\n callbacks.updateControlsTime(pb.currentTime, duration);\n callbacks.onRuntimeTimelineReady(duration);\n }\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 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 * 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\nexport const SHADER_CAPTURE_SCALE_ATTR = \"shader-capture-scale\";\nexport const SHADER_LOADING_ATTR = \"shader-loading\";\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\nfunction setQueryParam(params: URLSearchParams, key: string, value: string | null): void {\n if (value === null) params.delete(key);\n else params.set(key, value);\n}\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 const params = new URLSearchParams(query);\n setQueryParam(params, SHADER_CAPTURE_SCALE_PARAM, scale);\n setQueryParam(params, SHADER_LOADING_PARAM, loadingMode === \"composition\" ? null : loadingMode);\n const nextQuery = params.toString();\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 return injectShaderOptionsIntoSrcdoc(\n srcdoc,\n normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)),\n getShaderModeFromElement(el),\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 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 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\nexport class ShaderLoaderState {\n private readonly _el: ShaderLoaderElements;\n private _hideTimeout: ReturnType<typeof setTimeout> | null = null;\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 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._el.fill.style.transform = \"scaleX(0)\";\n this._el.transitionValue.textContent = \"\";\n this._el.frameValue.textContent = \"\";\n this._el.frameRow.style.visibility = \"hidden\";\n }\n\n update(status: ShaderTransitionState, loadingMode: string): void {\n if (loadingMode !== \"player\") {\n this.reset();\n return;\n }\n if (status.ready || !status.loading) {\n this.hide();\n return;\n }\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 this._el.transitionValue.textContent =\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 this._el.frameValue.textContent = frameValue;\n this._el.frameRow.style.visibility = frameValue ? \"visible\" : \"hidden\";\n this._el.root.setAttribute(\"aria-valuenow\", String(Math.round(ratio * 100)));\n this.show();\n }\n\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 }\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 }, HIDE_TRANSITION_MS);\n }\n}\n"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["hyperframes_player_exports","__export","HyperframesPlayer","SPEED_PRESETS","formatSpeed","formatTime","shouldInjectRuntime","state","isObjectRecord","value","isRuntimeDurationAdapter","isDirectTimelineAdapter","RUNTIME_CDN_URL","readPositiveDimension","value","parsed","readCompositionSizeFromDocument","doc","root","width","height","CompositionProbe","_iframe","_callbacks","attempts","win","hasRuntime","hasTimelines","hasNestedCompositions","shouldInjectRuntime","adapter","compositionSize","isObjectRecord","script","timelines","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","DirectTimelineClock","_callbacks","timeline","getCurrentTime","getDuration","isPaused","tick","currentTime","duration","completedPlayback","now","selectMediaObserverTargets","doc","all","topLevel","el","hasCompositionAncestor","warnOnUnscopedTimedMedia","body","candidates","orphans","cursor","isRealmElement","node","view","isRealmHtmlMediaElement","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","MIRROR_DRIFT_THRESHOLD_SECONDS","MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES","ParentMediaManager","opts","wasPromoted","m","muted","volume","rate","err","relTime","timing","readClipTiming","timeInSeconds","timelineSeconds","options","force","iframeDoc","onMirror","el","isRealmHtmlMediaElement","t","mediaEls","iframeEl","audioSrc","entry","idx","src","tag","start","duration","source","rawSrc","created","doc","obs","mutations","target","added","isRealmElement","candidates","inside","removed","dropped","observeOpts","targets","selectMediaObserverTargets","applyRuntimeStateMessage","data","fps","current","callbacks","rawTime","currentTime","wasPlaying","nextPaused","completedPlayback","next","now","playStateChanged","RUNTIME_PROTOCOL_CAPABILITIES","greatestCommonDivisor","a","b","left","right","remainder","runtimeProtocolFpsFromNumber","value","safe","denominator","numerator","divisor","runtimeProtocolFpsToNumber","fps","runtimeProtocolMetadata","hasDeclaredCapabilities","capability","inspectRuntimeProtocol","legacyFps","message","extractScenes","raw","s","handleRuntimeMessage","event","frameWindow","callbacks","data","protocol","inspectRuntimeProtocol","state","applyRuntimeStateMessage","iframeDoc","t","opts","declaredDuration","frameDuration","duration","pb","SHADER_CAPTURE_SCALE_ATTR","SHADER_LOADING_ATTR","SHADER_CAPTURE_SCALE_PARAM","SHADER_LOADING_PARAM","SHADER_LOADING_PHRASES","normalizeShaderCaptureScale","value","parsed","normalizeShaderLoadingMode","normalized","setQueryParam","params","key","withShaderQueryParams","src","scale","loadingMode","hashIndex","beforeHash","hash","queryIndex","path","query","nextQuery","injectShaderOptionsIntoSrcdoc","html","lines","script","match","getShaderModeFromElement","el","getShaderCaptureScaleFromElement","prepareSrcForElement","prepareSrcdocForElement","srcdoc","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","ShaderLoaderState","elements","status","loadingMode","progress","total","ratio","phraseIndex","SHADER_LOADING_PHRASES","frameValue","MIN_PLAYBACK_RATE","MAX_PLAYBACK_RATE","clampPlaybackRate","rate","HyperframesPlayer","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","prepareSrcForElement","name","_old","val","readPositiveDimension","v","directTimelineStarted","timeInSeconds","target","grading","compare","t","r","getShaderCaptureScaleFromElement","scale","getShaderModeFromElement","mode","m","locked","ua","l","action","extra","runtimeProtocolMetadata","muted","iframeDoc","el","isRealmHtmlMediaElement","player","fn","tl","tick","handleRuntimeMessage","paused","lastUpdateMs","w","h","fps","scenes","d","p","ev","doc","adapter","compositionSize","scaleIframeToFit","setupControls","f","s","opts","o"]}
1
+ {"version":3,"sources":["../src/hyperframes-player.ts","../src/shouldInjectRuntime.ts","../src/timeline-adapters.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","../src/media-element-guards.ts","../../parsers/src/compositionContract.ts","../src/parent-media.ts","../src/playback-state.ts","../../core/src/runtime/protocol.ts","../src/runtime-message-handler.ts","../src/shader-options.ts","../src/shader-loader-element.ts","../src/shader-loader-state.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 SHADER_CAPTURE_SCALE_ATTR,\n SHADER_LOADING_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 { runtimeProtocolMetadata } from \"@hyperframes/core/runtime/protocol\";\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;\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\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 SHADER_CAPTURE_SCALE_ATTR,\n SHADER_LOADING_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 _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\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.dispatchEvent(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.dispatchEvent(new CustomEvent(\"timeupdate\", { detail: { currentTime } }));\n },\n getLoop: () => this.loop,\n restart: () => {\n this.seek(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.dispatchEvent(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.dispatchEvent(new CustomEvent(\"error\", { detail: { message } })),\n });\n\n this.addEventListener(\"click\", (event) => {\n if (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 }\n\n connectedCallback() {\n this.resizeObserver.observe(this);\n window.addEventListener(\"message\", this._onMessage);\n this.iframe.addEventListener(\"load\", this._onIframeLoad);\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\"))\n this.iframe.src = prepareSrcForElement(this, 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._sendControl(\"pause\");\n this._stopIframeMedia();\n this.resizeObserver.disconnect();\n window.removeEventListener(\"message\", this._onMessage);\n this.iframe.removeEventListener(\"load\", this._onIframeLoad);\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._ready = false;\n }\n\n // fallow-ignore-next-line complexity\n attributeChangedCallback(name: string, _old: string | null, val: string | null) {\n switch (name) {\n case \"src\":\n if (val) {\n this._ready = false;\n this.iframe.src = prepareSrcForElement(this, val);\n }\n break;\n case \"srcdoc\":\n this._ready = false;\n if (val !== null) this.iframe.srcdoc = prepareSrcdocForElement(this, val);\n else this.iframe.removeAttribute(\"srcdoc\");\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._compositionWidth = readPositiveDimension(val) ?? 1920;\n this._rescale();\n break;\n case \"height\":\n this._compositionHeight = readPositiveDimension(val) ?? 1080;\n this._rescale();\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.dispatchEvent(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._media.updateVolume(v);\n this._sendControl(\"set-volume\", { volume: v });\n this.controlsApi?.updateVolume(v);\n this.dispatchEvent(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 SHADER_CAPTURE_SCALE_ATTR:\n case SHADER_LOADING_ATTR:\n this._reloadShaderOptions();\n break;\n }\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.posterEl?.remove();\n this.posterEl = null;\n 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 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 // Guards against firing ticks into an uninitialized iframe when play()\n // is called before the probe has resolved.\n if (this._ready && !this._directTimelineAdapter) {\n this._startParentTickClock();\n }\n }\n if (this._media.audioOwner === \"parent\") this._media.playAll();\n this.controlsApi?.updatePlaying(true);\n this.dispatchEvent(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 );\n }\n }\n\n pause() {\n if (!this._tryDirectTimelinePause()) this._sendControl(\"pause\");\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.dispatchEvent(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 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 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 get paused() {\n return this._paused;\n }\n get ready() {\n return this._ready;\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 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._setIframeMediaMuted(val !== null);\n this._sendControl(\"set-muted\", { muted: val !== null });\n this.controlsApi?.updateMuted(val !== null);\n this.dispatchEvent(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 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 private _sendControl(action: string, extra: Record<string, unknown> = {}) {\n try {\n this.iframe.contentWindow?.postMessage(\n {\n ...extra,\n source: \"hf-parent\",\n type: \"control\",\n action,\n ...runtimeProtocolMetadata(this._runtimeFps),\n },\n \"*\",\n );\n } catch {\n /* cross-origin */\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 }\n\n private _reloadShaderOptions(): void {\n if (getShaderModeFromElement(this) !== \"player\") this.shaderLoader.reset();\n if (this.hasAttribute(\"srcdoc\")) {\n this.iframe.srcdoc = prepareSrcdocForElement(this, this.getAttribute(\"srcdoc\") || \"\");\n return;\n }\n if (this.hasAttribute(\"src\")) {\n this.iframe.src = prepareSrcForElement(this, this.getAttribute(\"src\") || \"\");\n }\n }\n\n private _trySyncSeek(timeInSeconds: number): boolean {\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 tl = this._directTimelineAdapter || this.probe.resolveDirectTimelineAdapter();\n if (!tl) return false;\n try {\n fn(tl);\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 /**\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's own rAF loop still runs — ticking GSAP twice per frame is\n * harmless because seekTimelineAndAdapters is idempotent.\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 handleRuntimeMessage(e, this.iframe.contentWindow, {\n getPlaybackState: () => ({\n currentTime: this._currentTime,\n duration: this._duration,\n paused: this._paused,\n lastUpdateMs: this._lastUpdateMs,\n }),\n setPlaybackState: ({ currentTime, duration, paused, lastUpdateMs }) => {\n this._currentTime = currentTime;\n this._duration = duration;\n this._paused = paused;\n this._lastUpdateMs = lastUpdateMs;\n },\n getShaderLoadingMode: () => getShaderModeFromElement(this),\n shaderLoader: this.shaderLoader,\n setCompositionSize: (w, h) => {\n this._compositionWidth = w;\n this._compositionHeight = h;\n this._rescale();\n },\n sendControl: (action, extra) => this._sendControl(action, extra),\n getIframeDoc: () => this.iframe.contentDocument,\n onRuntimeReady: () => this._replayBridgeState(),\n onRuntimeTimelineReady: (duration) => this._onRuntimeTimelineReady(duration),\n setRuntimeFps: (fps) => {\n this._runtimeFps = fps;\n },\n shouldPromoteMediaAutoplayFallback: () => !this._isSlideshowPlayer(),\n setScenes: (scenes) => {\n this._scenes = scenes;\n this.dispatchEvent(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.dispatchEvent(ev),\n seek: (t) => this.seek(t),\n play: () => this.play(),\n getLoop: () => this.loop,\n media: this._media,\n });\n }\n\n private _onRuntimeTimelineReady(duration: number) {\n if (this._ready) return;\n this.probe.stop();\n this._duration = duration;\n this._directTimelineAdapter = null;\n this._ready = true;\n this.controlsApi?.updateTime(this._currentTime, duration);\n this.dispatchEvent(new CustomEvent(\"ready\", { detail: { duration } }));\n // stage-size may not have arrived yet (race in the runtime's postTimeline\n // resolving the root's data-width/data-height on first paint) — rescale\n // here too so cross-origin compositions never stay unscaled/untransformed.\n this._rescale();\n\n const doc = this._getSameOriginIframeDocument();\n if (doc) this._media.setupFromIframe(doc);\n\n this._replayBridgeState();\n this._setIframeMediaMuted(this.muted);\n if (this.hasAttribute(\"autoplay\")) this.play();\n }\n\n private _onProbeReady({ duration, adapter, compositionSize }: ProbeResult) {\n this._duration = duration;\n this._directTimelineAdapter = adapter.kind === \"direct-timeline\" ? adapter.timeline : null;\n this._ready = true;\n this.controlsApi?.updateTime(0, duration);\n this.dispatchEvent(new CustomEvent(\"ready\", { detail: { duration } }));\n if (compositionSize) {\n this._compositionWidth = compositionSize.width;\n this._compositionHeight = compositionSize.height;\n this._rescale();\n }\n try {\n const doc = this.iframe.contentDocument;\n if (doc) this._media.setupFromIframe(doc);\n } catch {\n /* cross-origin */\n }\n this._setIframeMediaMuted(this.muted);\n if (this.hasAttribute(\"autoplay\")) this.play();\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._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 this._ready = false;\n this._directTimelineAdapter = null;\n this._directTimelineClock.stop();\n this._stopParentTickClock();\n this.shaderLoader.reset();\n this._media.resetForIframeLoad();\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","/**\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 two 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: \"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 * 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 * - A `PlaybackDurationAdapter` 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 { shouldInjectRuntime } from \"./shouldInjectRuntime.js\";\nimport {\n type DirectTimelineAdapter,\n type PlaybackDurationAdapter,\n isDirectTimelineAdapter,\n isObjectRecord,\n isRuntimeDurationAdapter,\n} from \"./timeline-adapters.js\";\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\nconst RUNTIME_CDN_URL =\n typeof __HYPERFRAMES_RUNTIME_CDN_URL__ === \"string\"\n ? __HYPERFRAMES_RUNTIME_CDN_URL__\n : runtimeCdnUrlForVersion(\"0.0.0-dev\");\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\nexport class CompositionProbe {\n private _interval: ReturnType<typeof setInterval> | null = null;\n private _runtimeInjected = false;\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. Stops any previously running probe first. */\n start(): void {\n this.stop();\n this._runtimeInjected = false;\n let attempts = 0;\n\n // fallow-ignore-next-line complexity\n this._interval = setInterval(() => {\n attempts++;\n try {\n const win = this._iframe.contentWindow as Window & {\n __player?: { getDuration: () => number };\n __timelines?: Record<string, { duration: () => number }>;\n __hf?: unknown;\n };\n if (!win) return;\n\n const hasRuntime = !!(win.__hf || win.__player);\n const hasTimelines = !!(win.__timelines && Object.keys(win.__timelines).length > 0);\n const hasNestedCompositions =\n !!this._iframe.contentDocument?.querySelector(\"[data-composition-src]\");\n\n if (\n shouldInjectRuntime({\n hasRuntime,\n hasTimelines,\n hasNestedCompositions,\n runtimeInjected: this._runtimeInjected,\n attempts,\n })\n ) {\n this._injectRuntime();\n return;\n }\n\n if (this._runtimeInjected && !hasRuntime) return;\n\n const adapter = this._resolvePlaybackDurationAdapter(win);\n if (adapter && adapter.getDuration() > 0) {\n this.stop();\n\n const compositionSize = readCompositionSizeFromDocument(this._iframe.contentDocument);\n\n this._callbacks.onReady({\n duration: adapter.getDuration(),\n adapter,\n compositionSize,\n });\n return;\n }\n } catch {\n /* cross-origin */\n }\n\n if (attempts >= 40) {\n this.stop();\n this._callbacks.onError(\"Composition timeline not found after 8s\");\n }\n }, 200);\n }\n\n stop(): void {\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-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-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;\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\nexport class DirectTimelineClock {\n private _raf: number | null = null;\n private _lastUpdateMs = 0;\n\n constructor(private readonly _callbacks: ClockCallbacks) {}\n\n start(\n timeline: DirectTimelineAdapter,\n getCurrentTime: () => number,\n getDuration: () => number,\n isPaused: () => boolean,\n ): void {\n this.stop();\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 if (duration > 0) currentTime = Math.min(currentTime, duration);\n\n const completedPlayback = duration > 0 && currentTime >= duration;\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 } 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._raf = requestAnimationFrame(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","export 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\n const view = node.ownerDocument?.defaultView;\n if (view && node instanceof view.HTMLMediaElement) return true;\n return node instanceof HTMLMediaElement;\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","/**\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 { 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 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 const relTime = this._getCurrentTime() - m.start;\n if (relTime < 0 || relTime >= m.duration) 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 `relTime >= m.duration` window check false, 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 _gateEntryPlayback(m: ProxyEntry, relTime: number): boolean {\n if (relTime < 0 || relTime >= m.duration) {\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 const relTime = timeInSeconds - m.start;\n if (relTime >= 0 && relTime < m.duration) m.el.currentTime = relTime;\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 const relTime = timeInSeconds - m.start;\n if (relTime >= 0 && relTime < m.duration) {\n m.el.currentTime = relTime;\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 const relTime = timelineSeconds - m.start;\n if (!this._gateEntryPlayback(m, relTime)) continue;\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\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 // `_createEntry` returns null when a proxy for this URL already exists\n // (e.g. the composition already adopted the same media). In that case we do\n // not own a proxy, so leave the tracking cleared rather than recording a\n // src with no entry — otherwise teardown would target nothing and the\n // no-op guard would never engage.\n this._urlAudioEntry = entry;\n this._urlAudioSrc = entry ? audioSrc : null;\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._entries.some((m) => m.el.src === 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 will re-trigger\n // when the preload attribute is promoted to \"auto\".\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 private _detachIframeMedia(iframeEl: HTMLMediaElement): void {\n const src = this._resolveIframeMediaSrc(iframeEl);\n if (!src) return;\n const idx = this._entries.findIndex((m) => m.el.src === src);\n if (idx === -1) return;\n const entry = this._entries[idx];\n entry.el.pause();\n entry.el.src = \"\";\n this._entries.splice(idx, 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 === \"preload\") {\n const target = m.target;\n if (\n isRealmHtmlMediaElement(target) &&\n target.matches(\"audio[data-start], video[data-start]\") &&\n target.preload === \"auto\"\n ) {\n this._adoptIframeMedia(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\"],\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","/**\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\";\n\nconst UI_UPDATE_INTERVAL_MS = 100;\n\nexport interface PlaybackState {\n currentTime: number;\n duration: number;\n paused: boolean;\n lastUpdateMs: number;\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 media: ParentMediaManager;\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: { frame: number; isPlaying: boolean },\n fps: number,\n current: PlaybackState,\n callbacks: PlaybackStateCallbacks,\n): PlaybackState {\n const rawTime = (data.frame ?? 0) / fps;\n const currentTime = current.duration > 0 ? Math.min(rawTime, current.duration) : rawTime;\n const wasPlaying = !current.paused;\n const nextPaused = !data.isPlaying;\n const completedPlayback =\n current.duration > 0 && currentTime >= current.duration && (wasPlaying || data.isPlaying);\n\n if (completedPlayback && callbacks.getLoop()) {\n if (callbacks.media.audioOwner === \"parent\") callbacks.media.pauseAll();\n callbacks.seek(0);\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, paused: false };\n }\n\n const next: PlaybackState = { ...current, currentTime, paused: nextPaused };\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 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","export const RUNTIME_PROTOCOL_VERSION = 1 as const;\n\nexport const RUNTIME_PROTOCOL_CAPABILITIES = [\n \"seconds-time\",\n \"rational-fps\",\n \"seek-keep-playing\",\n \"composition-manifest-v1\",\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 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\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","/**\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 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) => void;\n setRuntimeFps?: (fps: number) => 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\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\"] === \"state\") {\n callbacks.setPlaybackState(\n applyRuntimeStateMessage(\n { frame: (data[\"frame\"] as number) ?? 0, isPlaying: !!data[\"isPlaying\"] },\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 (Number.isFinite(duration) && duration > 0) {\n const pb = callbacks.getPlaybackState();\n callbacks.setPlaybackState({ ...pb, duration });\n callbacks.updateControlsTime(pb.currentTime, duration);\n callbacks.onRuntimeTimelineReady(duration);\n }\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 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 * 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\nexport const SHADER_CAPTURE_SCALE_ATTR = \"shader-capture-scale\";\nexport const SHADER_LOADING_ATTR = \"shader-loading\";\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 return injectShaderOptionsIntoSrcdoc(\n srcdoc,\n normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)),\n getShaderModeFromElement(el),\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 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 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\nexport class ShaderLoaderState {\n private readonly _el: ShaderLoaderElements;\n private _hideTimeout: ReturnType<typeof setTimeout> | null = null;\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 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._el.fill.style.transform = \"scaleX(0)\";\n this._el.transitionValue.textContent = \"\";\n this._el.frameValue.textContent = \"\";\n this._el.frameRow.style.visibility = \"hidden\";\n }\n\n update(status: ShaderTransitionState, loadingMode: string): void {\n if (loadingMode !== \"player\") {\n this.reset();\n return;\n }\n if (status.ready || !status.loading) {\n this.hide();\n return;\n }\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 this._el.transitionValue.textContent =\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 this._el.frameValue.textContent = frameValue;\n this._el.frameRow.style.visibility = frameValue ? \"visible\" : \"hidden\";\n this._el.root.setAttribute(\"aria-valuenow\", String(Math.round(ratio * 100)));\n this.show();\n }\n\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 }\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 }, HIDE_TRANSITION_MS);\n }\n}\n"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["hyperframes_player_exports","__export","HyperframesPlayer","SPEED_PRESETS","formatSpeed","formatTime","shouldInjectRuntime","state","isObjectRecord","value","isRuntimeDurationAdapter","isDirectTimelineAdapter","RUNTIME_CDN_URL","readPositiveDimension","value","parsed","readCompositionSizeFromDocument","doc","root","width","height","CompositionProbe","_iframe","_callbacks","attempts","win","hasRuntime","hasTimelines","hasNestedCompositions","shouldInjectRuntime","adapter","compositionSize","isObjectRecord","script","timelines","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","DirectTimelineClock","_callbacks","timeline","getCurrentTime","getDuration","isPaused","tick","currentTime","duration","completedPlayback","now","selectMediaObserverTargets","doc","all","topLevel","el","hasCompositionAncestor","warnOnUnscopedTimedMedia","body","candidates","orphans","cursor","isRealmElement","node","view","isRealmHtmlMediaElement","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","MIRROR_DRIFT_THRESHOLD_SECONDS","MIRROR_REQUIRED_CONSECUTIVE_DRIFT_SAMPLES","ParentMediaManager","opts","wasPromoted","m","muted","volume","rate","err","relTime","timing","readClipTiming","timeInSeconds","timelineSeconds","options","force","iframeDoc","onMirror","el","isRealmHtmlMediaElement","t","mediaEls","iframeEl","audioSrc","entry","idx","src","tag","start","duration","source","rawSrc","created","doc","obs","mutations","target","added","isRealmElement","candidates","inside","removed","dropped","observeOpts","targets","selectMediaObserverTargets","applyRuntimeStateMessage","data","fps","current","callbacks","rawTime","currentTime","wasPlaying","nextPaused","completedPlayback","next","now","playStateChanged","RUNTIME_PROTOCOL_CAPABILITIES","greatestCommonDivisor","a","b","left","right","remainder","runtimeProtocolFpsFromNumber","value","safe","denominator","numerator","divisor","runtimeProtocolFpsToNumber","fps","runtimeProtocolMetadata","hasDeclaredCapabilities","capability","inspectRuntimeProtocol","legacyFps","message","extractScenes","raw","s","handleRuntimeMessage","event","frameWindow","callbacks","data","protocol","inspectRuntimeProtocol","state","applyRuntimeStateMessage","iframeDoc","t","opts","declaredDuration","frameDuration","duration","pb","SHADER_CAPTURE_SCALE_ATTR","SHADER_LOADING_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","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","ShaderLoaderState","elements","status","loadingMode","progress","total","ratio","phraseIndex","SHADER_LOADING_PHRASES","frameValue","MIN_PLAYBACK_RATE","MAX_PLAYBACK_RATE","clampPlaybackRate","rate","HyperframesPlayer","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","prepareSrcForElement","name","_old","val","readPositiveDimension","v","directTimelineStarted","timeInSeconds","target","grading","compare","t","r","getShaderCaptureScaleFromElement","scale","getShaderModeFromElement","mode","m","locked","ua","l","action","extra","runtimeProtocolMetadata","muted","iframeDoc","el","isRealmHtmlMediaElement","player","fn","tl","tick","handleRuntimeMessage","paused","lastUpdateMs","w","h","fps","scenes","d","p","ev","doc","adapter","compositionSize","scaleIframeToFit","setupControls","f","s","opts","o"]}