{"version":3,"sources":["jsdelivr-header.js","/npm/@hyperframes/player@0.8.8/src/shouldInjectRuntime.ts","/npm/@hyperframes/player@0.8.8/src/timeline-adapters.ts","/npm/@hyperframes/player@0.8.8/src/runtime-url.ts","/npm/@hyperframes/player@0.8.8/src/composition-probe.ts","/npm/@hyperframes/player@0.8.8/src/styles.ts","/npm/@hyperframes/player@0.8.8/src/controls.ts","/npm/@hyperframes/player@0.8.8/src/controls-setup.ts","/npm/@hyperframes/player@0.8.8/src/iframe-dom.ts","/npm/@hyperframes/player@0.8.8/src/direct-timeline-clock.ts","/npm/@hyperframes/player@0.8.8/src/mediaObserverScope.ts","/npm/@hyperframes/player@0.8.8/src/media-element-guards.ts","/npm/@hyperframes/parsers/src/compositionContract.ts","/npm/@hyperframes/player@0.8.8/src/parent-media.ts","/npm/@hyperframes/player@0.8.8/src/playback-state.ts","/npm/@hyperframes/core/src/runtime/protocol.ts","/npm/@hyperframes/player@0.8.8/src/runtime-message-handler.ts","/npm/@hyperframes/player@0.8.8/src/runtime-in-srcdoc.ts","/npm/@hyperframes/player@0.8.8/src/shader-options.ts","/npm/@hyperframes/player@0.8.8/src/shader-loader-element.ts","/npm/@hyperframes/player@0.8.8/src/shader-loader-state.ts","/npm/@hyperframes/player@0.8.8/src/hyperframes-player.ts"],"names":[],"mappings":"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","file":"/npm/@hyperframes/player@0.8.8/dist/hyperframes-player.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.48.0.\n * Original file: /npm/@hyperframes/player@0.8.8/dist/hyperframes-player.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\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 * Where the runtime comes from.\n *\n * Split out of composition-probe so the probe's late injection and the srcdoc's\n * parse-time injection cannot drift onto different URLs.\n */\ndeclare const __HYPERFRAMES_RUNTIME_CDN_URL__: string;\n\nexport function runtimeCdnUrlForVersion(version: string): string {\n  if (!/^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/.test(version)) {\n    throw new Error(`Invalid HyperFrames runtime version: ${version}`);\n  }\n  return `https://cdn.jsdelivr.net/npm/@hyperframes/core@${version}/dist/hyperframe.runtime.iife.js`;\n}\n\nexport const RUNTIME_CDN_URL =\n  typeof __HYPERFRAMES_RUNTIME_CDN_URL__ === \"string\"\n    ? __HYPERFRAMES_RUNTIME_CDN_URL__\n    : runtimeCdnUrlForVersion(\"0.0.0-dev\");\n","/**\n * Probes an iframe document to discover the composition's playback adapter\n * and detect whether the HyperFrames runtime needs to be injected.\n *\n * The probe interval polls every 200 ms until one of:\n *   - 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\nimport { RUNTIME_CDN_URL, runtimeCdnUrlForVersion } from \"./runtime-url.js\";\n\nexport { runtimeCdnUrlForVersion };\n\nexport interface ProbeResult {\n  duration: number;\n  adapter: PlaybackDurationAdapter;\n  /** Resolved composition dimensions, if present in the document. */\n  compositionSize: { width: number; height: number } | null;\n}\n\nexport interface ProbeCallbacks {\n  onReady: (result: ProbeResult) => void;\n  onError: (message: string) => void;\n  /** Called when runtime is successfully injected (informational). */\n  onRuntimeInjected?: () => void;\n}\n\n/**\n * Parse a composition dimension, rejecting anything that isn't a positive\n * finite number. Exported because the `width`/`height` attribute handlers in\n * hyperframes-player.ts need the same guard: dimensions feed\n * scaleIframeToFit's `w / compositionWidth` division, where NaN produces an\n * invalid `scale(NaN)` transform and zero a division by zero — both render\n * the player blank with no signal.\n */\nexport function readPositiveDimension(value: string | null): number | null {\n  if (value === null) return null;\n  const parsed = Number.parseInt(value, 10);\n  return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n}\n\nexport function readCompositionSizeFromDocument(\n  doc: Document | null | undefined,\n): { width: number; height: number } | null {\n  const root =\n    doc?.querySelector(\"[data-composition-id][data-width][data-height]\") ??\n    doc?.querySelector(\"[data-width][data-height]\");\n  if (!root) return null;\n  const width = readPositiveDimension(root.getAttribute(\"data-width\"));\n  const height = readPositiveDimension(root.getAttribute(\"data-height\"));\n  return width !== null && height !== null ? { width, height } : null;\n}\n\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 * Put the runtime in the document's head, before anything in the body runs.\n *\n * The probe injects the runtime by appending a `<script src>` to an already\n * loaded document, and only once it has a reason to: a nested composition, or\n * five polls with a timeline present. Both are far too late for a COMPONENT.\n *\n * A component is markup pasted into a composition, and it reads its values in\n * an inline IIFE that runs while the body is being parsed:\n *\n *     var vars = window.__hyperframes && window.__hyperframes.getVariables\n *       ? window.__hyperframes.getVariables()\n *       : {};\n *\n * With the runtime arriving afterwards, that guard always took the empty\n * branch, so the component fell back to the defaults hardcoded in its own\n * script and every chosen value was ignored. On the catalog page that looked\n * like the customise panel doing nothing: badge-pop with count 10 and a green\n * accent rendered 3, in red.\n *\n * A classic external `<script src>` in `<head>` is parser-blocking, so moving\n * the same URL into the srcdoc fixes the ordering without changing what is\n * loaded. A CLI render never had this problem because the engine already puts\n * the runtime ahead of body scripts.\n */\n\n/** Already carrying the runtime: a CLI-rendered page, or a second preparation. */\nfunction alreadyHasRuntime(html: string, runtimeUrl: string): boolean {\n  if (html.includes(runtimeUrl)) return true;\n  // The engine inlines the runtime rather than linking it, and it defines this\n  // global on the way in. Matching the source avoids a second, redundant copy.\n  return /hyperframe\\.runtime\\.iife\\.js|__hyperframes\\s*=/.test(html);\n}\n\nexport function ensureRuntimeBeforeBodyScripts(html: string, runtimeUrl: string): string {\n  if (!html || alreadyHasRuntime(html, runtimeUrl)) return html;\n\n  const tag = `<script src=\"${runtimeUrl}\"></script>`;\n  const head = /<head[^>]*>/i.exec(html);\n  if (head) {\n    const at = head.index + head[0].length;\n    return html.slice(0, at) + tag + html.slice(at);\n  }\n  // No head: get in before <body> so body scripts still see the runtime. A\n  // fragment with neither lands at the front, which is the same guarantee.\n  const body = /<body[^>]*>/i.exec(html);\n  if (body) return html.slice(0, body.index) + tag + html.slice(body.index);\n  const htmlTag = /<html[^>]*>/i.exec(html);\n  if (htmlTag) {\n    const at = htmlTag.index + htmlTag[0].length;\n    return html.slice(0, at) + tag + html.slice(at);\n  }\n  return tag + html;\n}\n","/**\n * Shader transition option types, constants, and pure helper functions for\n * injecting shader capture scale and loading mode parameters into composition\n * URLs and srcdoc HTML.\n */\n\nimport { ensureRuntimeBeforeBodyScripts } from \"./runtime-in-srcdoc.js\";\nimport { RUNTIME_CDN_URL } from \"./runtime-url.js\";\n\nexport const SHADER_CAPTURE_SCALE_ATTR = \"shader-capture-scale\";\nexport const SHADER_LOADING_ATTR = \"shader-loading\";\nconst SHADER_CAPTURE_SCALE_PARAM = \"__hf_shader_capture_scale\";\nconst SHADER_LOADING_PARAM = \"__hf_shader_loading\";\n\nexport const SHADER_LOADING_PHRASES = [\n  \"Preparing scene transitions\",\n  \"Sampling outgoing scene motion\",\n  \"Sampling incoming scene motion\",\n  \"Caching transition frames\",\n  \"Finalizing transition preview\",\n];\n\nexport type ShaderLoadingMode = \"composition\" | \"player\" | \"none\";\n\nexport interface ShaderTransitionState {\n  ready?: boolean;\n  progress?: number;\n  total?: number;\n  currentTransition?: number;\n  transitionTotal?: number;\n  transitionFrame?: number;\n  transitionFrames?: number;\n  phase?: \"cached\" | \"capturing\" | \"finalizing\";\n  loading?: boolean;\n}\n\nfunction normalizeShaderCaptureScale(value: string | null): string | null {\n  if (value === null) return null;\n  const parsed = Number(value);\n  if (!Number.isFinite(parsed) || parsed <= 0) return null;\n  return String(Math.min(1, Math.max(0.25, parsed)));\n}\n\nfunction normalizeShaderLoadingMode(value: string | null): ShaderLoadingMode {\n  if (value === null || value.trim() === \"\") return \"composition\";\n  const normalized = value.trim().toLowerCase();\n  if (\n    normalized === \"none\" ||\n    normalized === \"false\" ||\n    normalized === \"0\" ||\n    normalized === \"off\"\n  ) {\n    return \"none\";\n  }\n  if (\n    normalized === \"player\" ||\n    normalized === \"true\" ||\n    normalized === \"1\" ||\n    normalized === \"on\"\n  ) {\n    return \"player\";\n  }\n  return \"composition\";\n}\n\n/** Drop one of our own keys from raw `a=1&b=2` pairs, matched by name so that\n *  nothing around it has to be decoded. */\nfunction withoutParam(pairs: string[], key: string): string[] {\n  return pairs.filter((pair) => pair !== \"\" && pair.split(\"=\")[0] !== key);\n}\n\n/**\n * The player's own params, appended to the query the composition author wrote\n * rather than merged into a re-serialized copy of it.\n *\n * `new URLSearchParams(query).toString()` is a form-encoding round trip: it\n * re-encodes the *whole* query as application/x-www-form-urlencoded, which\n * writes every space as `+`. A composition reading its own query with\n * `decodeURIComponent` — percent-decoding, which leaves `+` alone — cannot undo\n * that, so a value of \"Ship it today\" arrived on the page as \"Ship+it+today\".\n * The two codecs are not inverses, and the player has no business picking one\n * for a query it is only passing along. The author's bytes now travel through\n * byte-identical; only our two keys are rewritten.\n */\nfunction withShaderQueryParams(\n  src: string,\n  scale: string | null,\n  loadingMode: ShaderLoadingMode,\n): string {\n  const hashIndex = src.indexOf(\"#\");\n  const beforeHash = hashIndex >= 0 ? src.slice(0, hashIndex) : src;\n  const hash = hashIndex >= 0 ? src.slice(hashIndex) : \"\";\n  const queryIndex = beforeHash.indexOf(\"?\");\n  const path = queryIndex >= 0 ? beforeHash.slice(0, queryIndex) : beforeHash;\n  const query = queryIndex >= 0 ? beforeHash.slice(queryIndex + 1) : \"\";\n  let pairs = withoutParam(query.split(\"&\"), SHADER_CAPTURE_SCALE_PARAM);\n  pairs = withoutParam(pairs, SHADER_LOADING_PARAM);\n  if (scale !== null) pairs.push(`${SHADER_CAPTURE_SCALE_PARAM}=${encodeURIComponent(scale)}`);\n  if (loadingMode !== \"composition\") {\n    pairs.push(`${SHADER_LOADING_PARAM}=${encodeURIComponent(loadingMode)}`);\n  }\n  const nextQuery = pairs.join(\"&\");\n  return `${path}${nextQuery ? `?${nextQuery}` : \"\"}${hash}`;\n}\n\nfunction injectShaderOptionsIntoSrcdoc(\n  html: string,\n  scale: string | null,\n  loadingMode: ShaderLoadingMode,\n): string {\n  if (scale === null && loadingMode === \"composition\") return html;\n  const lines: string[] = [];\n  if (scale !== null) lines.push(`window.__HF_SHADER_CAPTURE_SCALE=${JSON.stringify(scale)};`);\n  if (loadingMode !== \"composition\") {\n    lines.push(`window.__HF_SHADER_LOADING=${JSON.stringify(loadingMode)};`);\n  }\n  const script = `<script data-hyperframes-player-shader-options>${lines.join(\"\")}</script>`;\n  if (/<head\\b[^>]*>/i.test(html))\n    return html.replace(/<head\\b[^>]*>/i, (match) => `${match}${script}`);\n  if (/<html\\b[^>]*>/i.test(html))\n    return html.replace(/<html\\b[^>]*>/i, (match) => `${match}${script}`);\n  return `${script}${html}`;\n}\n\n/**\n * Convenience wrappers that read shader attributes directly from an element,\n * avoiding boilerplate in the web component class body.\n */\n\nexport function getShaderModeFromElement(el: Element): ShaderLoadingMode {\n  return normalizeShaderLoadingMode(el.getAttribute(SHADER_LOADING_ATTR));\n}\n\nexport function getShaderCaptureScaleFromElement(el: Element): number {\n  return Number(normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)) ?? \"1\");\n}\n\nexport function prepareSrcForElement(el: Element, src: string): string {\n  return withShaderQueryParams(\n    src,\n    normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)),\n    getShaderModeFromElement(el),\n  );\n}\n\nexport function prepareSrcdocForElement(el: Element, srcdoc: string): string {\n  // Runtime first, and in the head: a component's inline script reads its\n  // variables while the body is parsing, long before the probe's own injection\n  // could land. See runtime-in-srcdoc.ts.\n  return ensureRuntimeBeforeBodyScripts(\n    injectShaderOptionsIntoSrcdoc(\n      srcdoc,\n      normalizeShaderCaptureScale(el.getAttribute(SHADER_CAPTURE_SCALE_ATTR)),\n      getShaderModeFromElement(el),\n    ),\n    RUNTIME_CDN_URL,\n  );\n}\n","/**\n * Factory for the shader-transition loading overlay DOM tree.\n *\n * Kept in its own module so the ~100-line DOM construction stays out of the\n * web component class body. The returned `ShaderLoaderElements` bag gives the\n * component direct handles to the nodes it needs to update without querying\n * the shadow DOM on every state change.\n */\n\nimport { SHADER_LOADING_PHRASES } from \"./shader-options.js\";\n\nexport interface ShaderLoaderElements {\n  root: HTMLDivElement;\n  fill: HTMLDivElement;\n  title: HTMLSpanElement;\n  detail: HTMLDivElement;\n  transitionValue: HTMLSpanElement;\n  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","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"]}