@editframe/elements 0.35.0-beta → 0.36.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/elements/EFImage.js +11 -2
  2. package/dist/elements/EFImage.js.map +1 -1
  3. package/dist/elements/EFTemporal.js +1 -0
  4. package/dist/elements/EFTemporal.js.map +1 -1
  5. package/dist/elements/EFTimegroup.d.ts +40 -6
  6. package/dist/elements/EFTimegroup.js +127 -8
  7. package/dist/elements/EFTimegroup.js.map +1 -1
  8. package/dist/elements/updateAnimations.js +38 -15
  9. package/dist/elements/updateAnimations.js.map +1 -1
  10. package/dist/gui/EFWorkbench.js +10 -12
  11. package/dist/gui/EFWorkbench.js.map +1 -1
  12. package/dist/gui/TWMixin.js +1 -1
  13. package/dist/gui/TWMixin.js.map +1 -1
  14. package/dist/preview/FrameController.js +6 -1
  15. package/dist/preview/FrameController.js.map +1 -1
  16. package/dist/preview/encoding/canvasEncoder.js.map +1 -1
  17. package/dist/preview/encoding/mainThreadEncoder.js +3 -0
  18. package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
  19. package/dist/preview/renderTimegroupPreview.js +57 -55
  20. package/dist/preview/renderTimegroupPreview.js.map +1 -1
  21. package/dist/preview/renderTimegroupToCanvas.js +22 -23
  22. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  23. package/dist/preview/renderTimegroupToVideo.d.ts +2 -1
  24. package/dist/preview/renderTimegroupToVideo.js +77 -40
  25. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  26. package/dist/preview/rendering/renderToImage.d.ts +1 -0
  27. package/dist/preview/rendering/renderToImage.js +1 -26
  28. package/dist/preview/rendering/renderToImage.js.map +1 -1
  29. package/dist/preview/rendering/renderToImageForeignObject.js +34 -6
  30. package/dist/preview/rendering/renderToImageForeignObject.js.map +1 -1
  31. package/dist/preview/rendering/serializeTimelineDirect.js +379 -0
  32. package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -0
  33. package/dist/render/EFRenderAPI.js +45 -0
  34. package/dist/render/EFRenderAPI.js.map +1 -1
  35. package/dist/style.css +12 -0
  36. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"EFTimegroup.js","names":["durationCache: WeakMap<EFTimegroup, number>","ancestor: Node | null","EFTimegroup","taskObj: { run(): void | Promise<void>; taskComplete: Promise<void> }","#frameTaskAbortController","#frameTaskPromise","#runFrameTask","taskObj: { run(): void | Promise<number | undefined>; taskComplete: Promise<number | undefined> }","#seekTaskAbortController","#pendingSeekTime","#currentTime","#seekTaskPromise","#runSeekTask","#restoringFromLocalStorage","#frameController","#contentEpoch","#runThrottledFrameTask","#userTimeMs","#processingPendingSeek","#seekInProgress","#evaluateVisibleElementsForFrame","#getAllLitElementDescendants","#executeCustomFrameTasks","result: LitElement[]","#onFrameCallback","#onFrameCleanup","#customFrameTasks","#handleSlotChange","#setupPlaybackListener","#playbackListener","#previousDurationMs","#resizeObserver","#removePlaybackListener","canvases: HTMLCanvasElement[]","result: unknown","#waitForCaptionsData","waitPromises: Promise<unknown>[]","#copyCaptionsData","#copyTextContent","#copyTextSegmentData","#runInitializer","container","#mediaDurationsPromise","#waitForMediaDurations","rafId1: number","rafId2: number","timeoutId: ReturnType<typeof setTimeout>","#contextProvider","#efElements","#testPlayAudio","#loadMd5Sums","loaderTasks: Promise<any>[]","#timegroupFrameTaskLastReset","#timegroupFrameTaskCount"],"sources":["../../src/elements/EFTimegroup.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport debug from \"debug\";\nimport { css, html, LitElement, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nimport { quantizeToFrameTimeS } from \"../utils/frameTime.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { isTracingEnabled, withSpan } from \"../otel/tracingHelpers.js\";\nimport { FrameController } from \"../preview/FrameController.js\";\nimport { deepGetMediaElements, type EFMedia } from \"./EFMedia.js\";\nimport {\n deepGetElementsWithFrameTasks,\n EFTemporal,\n flushStartTimeMsCache,\n resetTemporalCache,\n shallowGetTemporalElements,\n timegroupContext,\n type TemporalMixinInterface,\n} from \"./EFTemporal.js\";\nimport { parseTimeToMs } from \"./parseTimeToMs.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.js\";\nimport { TimegroupController } from \"./TimegroupController.js\";\nimport {\n evaluateAnimationVisibilityState,\n updateAnimations,\n} from \"./updateAnimations.js\";\nimport {\n type ContainerInfo,\n getContainerInfoFromElement,\n} from \"./ContainerInfo.js\";\nimport {\n type ElementPositionInfo,\n getPositionInfoFromElement,\n} from \"./ElementPositionInfo.js\";\nimport {\n captureTimegroupAtTime,\n captureFromClone,\n type CaptureOptions,\n type CaptureBatchOptions,\n} from \"../preview/renderTimegroupToCanvas.js\";\nimport {\n renderTimegroupToVideo,\n type RenderToVideoOptions,\n} from \"../preview/renderTimegroupToVideo.js\";\nimport type { PlaybackControllerUpdateEvent } from \"../gui/PlaybackController.js\";\n\n// Side-effect imports for workbench wrapping\nimport \"../canvas/EFCanvas.js\";\nimport \"../gui/hierarchy/EFHierarchy.js\";\nimport \"../gui/EFFilmstrip.js\";\nimport \"../gui/EFWorkbench.js\";\nimport \"../gui/EFFitScale.js\";\nimport \"./EFPanZoom.js\";\n\n\nconst log = debug(\"ef:elements:EFTimegroup\");\n\n// Custom frame task callback type\nexport type FrameTaskCallback = (info: {\n ownCurrentTimeMs: number;\n currentTimeMs: number;\n durationMs: number;\n percentComplete: number;\n element: EFTimegroup;\n}) => void | Promise<void>;\n\n/**\n * Result of createRenderClone() - contains the clone, its container, and cleanup function.\n */\nexport interface RenderCloneResult {\n /** The cloned timegroup, fully functional with its own time state */\n clone: EFTimegroup;\n /** The offscreen container holding the clone */\n container: HTMLElement;\n /** Call this to remove the clone from DOM and clean up */\n cleanup: () => void;\n}\n\n/**\n * Initializer function type for setting up JavaScript behavior on timegroup instances.\n * This function is called on both the prime timeline and each render clone.\n * \n * CONSTRAINTS:\n * - MUST be synchronous (no async/await, no Promise return)\n * - MUST complete in <100ms (error) or <10ms (warning)\n * - Should only register callbacks and set up behavior, not do expensive work\n */\nexport type TimegroupInitializer = (timegroup: EFTimegroup) => void;\n\n// Constants for initializer time budget enforcement\nconst INITIALIZER_ERROR_THRESHOLD_MS = 100;\nconst INITIALIZER_WARN_THRESHOLD_MS = 10;\n\n// ============================================================================\n// Purpose 1: Composition Rules - How Duration is Determined\n// ============================================================================\n//\n// A timegroup's duration is determined by its mode:\n// - \"fixed\": Uses explicit duration attribute (base case)\n// - \"sequence\": Sum of child durations minus overlaps\n// - \"contain\": Maximum of child durations\n// - \"fit\": Inherits duration from parent timegroup\n//\n// Core invariant: Every timegroup has exactly one duration value at any moment,\n// computed from either explicit specification (fixed mode) or child relationships\n// (sequence/contain/fit modes).\n//\n// ============================================================================\n\n/**\n * The four timegroup modes define how duration is calculated:\n * - \"fit\": Inherits duration from parent timegroup\n * - \"fixed\": Uses explicit duration attribute\n * - \"sequence\": Sum of child durations minus overlaps\n * - \"contain\": Maximum of child durations\n */\nexport type TimeMode = \"fit\" | \"fixed\" | \"sequence\" | \"contain\";\n\n// Cache for duration calculations to avoid O(n) recalculation on every access\n// Used by all modes (sequence, contain) to avoid repeated iteration through children\nlet durationCache: WeakMap<EFTimegroup, number> = new WeakMap();\n\nexport const flushDurationCache = () => {\n durationCache = new WeakMap();\n};\n\n// Keep alias for backwards compatibility\nexport const flushSequenceDurationCache = flushDurationCache;\n\n// Track timegroups currently calculating duration to prevent infinite loops\nconst durationCalculationInProgress = new WeakSet<EFTimegroup>();\n\n// Export function to check if a timegroup is currently calculating duration\n// This is used by EFTemporal to prevent calling parent.durationMs during calculation\nexport const isTimegroupCalculatingDuration = (\n timegroup: EFTimegroup | undefined,\n): boolean => {\n return (\n timegroup !== undefined && durationCalculationInProgress.has(timegroup)\n );\n};\n\n// Register this function with EFTemporal to break circular dependency\n// EFTemporal needs this function but can't import it directly due to circular dependency\nimport { registerIsTimegroupCalculatingDuration } from \"./EFTemporal.js\";\nregisterIsTimegroupCalculatingDuration(isTimegroupCalculatingDuration);\n\n/**\n * Determines if a timegroup has its own duration based on its mode.\n * This is the semantic rule: which modes produce independent durations.\n */\nfunction hasOwnDurationForMode(\n mode: TimeMode,\n hasExplicitDuration: boolean,\n): boolean {\n return (\n mode === \"contain\" ||\n mode === \"sequence\" ||\n (mode === \"fixed\" && hasExplicitDuration)\n );\n}\n\n/**\n * Determines if a child temporal element should participate in parent duration calculation.\n *\n * Semantic rule: Fit-mode children inherit from parent, so they don't contribute to parent's\n * duration calculation (to avoid circular dependencies). Children without own duration\n * also don't contribute.\n */\nfunction shouldParticipateInDurationCalculation(\n child: TemporalMixinInterface & HTMLElement,\n): boolean {\n // Fit timegroups look \"up\" to their parent for duration, so skip to avoid infinite loop\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n return false;\n }\n // Only children with their own duration contribute\n if (!child.hasOwnDuration) {\n return false;\n }\n return true;\n}\n\n/**\n * Evaluates duration for \"fit\" mode: inherits from parent.\n * Semantic rule: fit mode always matches parent duration, or 0 if no parent.\n */\nfunction evaluateFitDuration(parentTimegroup: EFTimegroup | undefined): number {\n if (!parentTimegroup) {\n return 0;\n }\n return parentTimegroup.durationMs;\n}\n\n/**\n * Evaluates duration for \"sequence\" mode: sum of children minus overlaps.\n * Semantic rule: sequence mode sums child durations, subtracting overlap between consecutive items.\n * Fit-mode children are excluded to avoid circular dependencies.\n */\nfunction evaluateSequenceDuration(\n timegroup: EFTimegroup,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n overlapMs: number,\n): number {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = durationCache.get(timegroup);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let duration = 0;\n let participatingIndex = 0;\n childTemporals.forEach((child) => {\n if (!shouldParticipateInDurationCalculation(child)) {\n return;\n }\n // Prevent infinite loops: skip children that are already calculating their duration\n if (\n child instanceof EFTimegroup &&\n durationCalculationInProgress.has(child)\n ) {\n return;\n }\n\n // Additional safety: if child is a timegroup, check if any of its ancestors\n // (EXCLUDING the current timegroup) are calculating.\n // This prevents cycles where a child's descendant eventually calls back to an ancestor,\n // but allows direct children of the current timegroup to participate.\n if (child instanceof EFTimegroup) {\n let ancestor: Node | null = child.parentNode;\n let shouldSkip = false;\n while (ancestor) {\n // Stop FIRST if we've reached the current timegroup - direct children are allowed\n if (ancestor === timegroup) {\n break;\n }\n if (\n ancestor instanceof EFTimegroup &&\n durationCalculationInProgress.has(ancestor)\n ) {\n // Found a calculating ancestor (not the current timegroup) - skip this child to prevent cycle\n shouldSkip = true;\n break;\n }\n ancestor = ancestor.parentNode;\n }\n if (shouldSkip) {\n return;\n }\n }\n\n // Subtract overlap for all items after the first\n if (participatingIndex > 0) {\n duration -= overlapMs;\n }\n duration += child.durationMs;\n participatingIndex++;\n });\n\n // Ensure non-negative duration (invariant)\n duration = Math.max(0, duration);\n\n // Cache the calculated duration\n durationCache.set(timegroup, duration);\n return duration;\n}\n\n/**\n * Evaluates duration for \"contain\" mode: maximum of children.\n * Semantic rule: contain mode takes the maximum child duration.\n * Fit-mode children and children without own duration are excluded.\n */\nfunction evaluateContainDuration(\n timegroup: EFTimegroup,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n): number {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = durationCache.get(timegroup);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let maxDuration = 0;\n for (const child of childTemporals) {\n if (!shouldParticipateInDurationCalculation(child)) {\n continue;\n }\n // Prevent infinite loops: skip children that are already calculating their duration\n // This check applies to all timegroup children, not just contain mode, because\n // a sequence-mode child could contain a contain-mode grandchild that\n // eventually references back to the parent through the parent chain\n if (\n child instanceof EFTimegroup &&\n durationCalculationInProgress.has(child)\n ) {\n continue;\n }\n\n // Additional safety: if child is a timegroup, check if any of its ancestors\n // (EXCLUDING the current timegroup) are calculating.\n // This prevents cycles where a child's descendant eventually calls back to an ancestor,\n // but allows direct children of the current timegroup to participate.\n if (child instanceof EFTimegroup) {\n let ancestor: Node | null = child.parentNode;\n let shouldSkip = false;\n while (ancestor) {\n // Stop FIRST if we've reached the current timegroup - direct children are allowed\n if (ancestor === timegroup) {\n break;\n }\n if (\n ancestor instanceof EFTimegroup &&\n durationCalculationInProgress.has(ancestor)\n ) {\n // Found a calculating ancestor (not the current timegroup) - skip this child to prevent cycle\n shouldSkip = true;\n break;\n }\n ancestor = ancestor.parentNode;\n }\n if (shouldSkip) {\n continue;\n }\n }\n\n maxDuration = Math.max(maxDuration, child.durationMs);\n }\n // Ensure non-negative duration (invariant)\n const duration = Math.max(0, maxDuration);\n\n // Cache the calculated duration\n durationCache.set(timegroup, duration);\n return duration;\n}\n\n/**\n * Evaluates duration based on timegroup mode.\n * This is the semantic evaluation function - it determines what duration should be.\n *\n * Note: Fixed mode is handled inline in the getter because it needs to call super.durationMs\n * which requires the class context. The other modes are extracted for clarity.\n */\nfunction evaluateDurationForMode(\n timegroup: EFTimegroup,\n mode: TimeMode,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n): number {\n switch (mode) {\n case \"fit\":\n return evaluateFitDuration(timegroup.parentTimegroup);\n case \"sequence\": {\n // Mark this timegroup as calculating duration to prevent infinite loops\n durationCalculationInProgress.add(timegroup);\n try {\n return evaluateSequenceDuration(\n timegroup,\n childTemporals,\n timegroup.overlapMs,\n );\n } finally {\n // Always remove the marker, even if an error occurs\n durationCalculationInProgress.delete(timegroup);\n }\n }\n case \"contain\": {\n // Mark this timegroup as calculating duration to prevent infinite loops\n durationCalculationInProgress.add(timegroup);\n try {\n return evaluateContainDuration(timegroup, childTemporals);\n } finally {\n // Always remove the marker, even if an error occurs\n durationCalculationInProgress.delete(timegroup);\n }\n }\n default:\n throw new Error(`Invalid time mode: ${mode}`);\n }\n}\n\nexport const shallowGetTimegroups = (\n element: Element,\n groups: EFTimegroup[] = [],\n) => {\n for (const child of Array.from(element.children)) {\n if (child instanceof EFTimegroup) {\n groups.push(child);\n } else {\n shallowGetTimegroups(child, groups);\n }\n }\n return groups;\n};\n\n// ============================================================================\n// Purpose 2: Time Propagation - How currentTime Flows Root to Children\n// ============================================================================\n//\n// Time propagation determines how the root timegroup's currentTime flows to child\n// temporal elements, computing each child's ownCurrentTime based on:\n// - The root's currentTime (global coordinate)\n// - The child's startTimeMs (determined by parent's composition mode)\n// - The parent's mode (sequence/contain/fit/fixed)\n//\n// Propagation rules by mode:\n// - Sequence: Each child's ownCurrentTime progresses within its time-shifted window\n// - Contain: All children share the same ownCurrentTime as parent\n// - Fit: Child ownCurrentTime = parent ownCurrentTime (identity mapping)\n//\n// Core invariant: Only root timegroup's currentTime should be written.\n// Child times are computed from parent state via ownCurrentTimeMs.\n//\n// Note: Time propagation logic is primarily implemented in EFTemporal.ts\n// (ownCurrentTimeMs getter and startTimeMs calculation). The timegroup's\n// currentTime setter triggers propagation by updating root time.\n//\n// ============================================================================\n\n// ============================================================================\n// Purpose 3: Seeking - Moving to a Specific Time\n// ============================================================================\n//\n// Seeking moves the timeline to a specific time position. This involves:\n// 1. Quantizing the requested time to frame boundaries (based on fps)\n// 2. Clamping to valid range [0, duration]\n// 3. Updating root timegroup's currentTime (which triggers time propagation)\n// 4. Waiting for all media and frame tasks to complete\n//\n// Core invariant: All time values snap to frame boundaries when FPS is set.\n// This ensures consistent seek/render behavior.\n//\n// ============================================================================\n\n/**\n * Evaluates the target time for a seek operation.\n * Applies quantization and clamping to determine the valid seek target.\n */\nfunction evaluateSeekTarget(\n requestedTime: number,\n durationMs: number,\n fps: number,\n): number {\n // Quantize to frame boundaries\n const quantizedTime = quantizeToFrameTimeS(requestedTime, fps);\n // Clamp to valid range [0, duration]\n return Math.max(0, Math.min(quantizedTime, durationMs / 1000));\n}\n\n@customElement(\"ef-timegroup\")\nexport class EFTimegroup extends EFTargetable(EFTemporal(TWMixin(LitElement))) {\n static get observedAttributes(): string[] {\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mode\",\n \"overlap\",\n \"currenttime\",\n \"fit\",\n \"fps\",\n \"auto-init\",\n \"workbench\",\n ];\n }\n\n static styles = css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n\n ::slotted(ef-timegroup) {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n overflow: initial;\n }\n `;\n\n /** @internal */\n @provide({ context: timegroupContext })\n _timeGroupContext = this;\n\n /** @internal */\n @provide({ context: efContext })\n efContext = this;\n\n /** @public */\n mode: TimeMode = \"contain\";\n /** @public */\n overlapMs = 0;\n\n /**\n * Initializer function for setting up JavaScript behavior on this timegroup.\n * This function is called on both the prime timeline and each render clone.\n * \n * REQUIRED for render operations (captureBatch, renderToVideo, createRenderClone).\n * \n * CONSTRAINTS:\n * - MUST be synchronous (no async/await, no Promise return)\n * - MUST complete in <100ms (error thrown) or <10ms (warning logged)\n * - Should only register callbacks and set up behavior, not do expensive work\n * \n * @example\n * ```javascript\n * const tg = document.querySelector('ef-timegroup');\n * tg.initializer = (instance) => {\n * instance.addFrameCallback((time) => {\n * // Update content based on time\n * });\n * };\n * ```\n * @public\n */\n initializer?: TimegroupInitializer;\n\n /** @public */\n @property({ type: Number })\n fps = 30;\n\n /**\n * When true, automatically seeks to frame 0 after media durations are loaded.\n * Only applies to root timegroups (timegroups that are not nested inside another timegroup).\n * This ensures the first frame is rendered immediately on initialization.\n */\n @property({ type: Boolean, attribute: \"auto-init\" })\n autoInit = false;\n\n /**\n * When true, automatically wraps this root timegroup with an ef-workbench element.\n * The workbench provides development UI including hierarchy panel, timeline, and playback controls.\n * Only applies to root timegroups.\n * @public\n */\n @property({ type: Boolean, reflect: true })\n workbench = false;\n\n attributeChangedCallback(\n name: string,\n old: string | null,\n value: string | null,\n ): void {\n if (name === \"mode\" && value) {\n this.mode = value as typeof this.mode;\n }\n if (name === \"overlap\" && value) {\n this.overlapMs = parseTimeToMs(value);\n }\n if (name === \"auto-init\") {\n this.autoInit = value !== null;\n }\n if (name === \"fps\" && value) {\n this.fps = Number.parseFloat(value);\n }\n if (name === \"workbench\") {\n this.workbench = value !== null;\n }\n super.attributeChangedCallback(name, old, value);\n }\n\n /** @public */\n @property({ type: String })\n fit: \"none\" | \"contain\" | \"cover\" = \"none\";\n\n #resizeObserver?: ResizeObserver;\n\n /** Content epoch - increments when visual content changes (used by thumbnail cache) */\n #contentEpoch = 0;\n\n #currentTime: number | undefined = undefined;\n #userTimeMs: number = 0; // What the user last requested (for preview display)\n #seekInProgress = false;\n #pendingSeekTime: number | undefined;\n #processingPendingSeek = false;\n #restoringFromLocalStorage = false; // Guard to prevent recursive seeks during localStorage restoration\n \n /** @internal */\n isRestoringFromLocalStorage(): boolean {\n return this.#restoringFromLocalStorage;\n }\n \n /** @internal - Used by PlaybackController to set restoration state */\n setRestoringFromLocalStorage(value: boolean): void {\n this.#restoringFromLocalStorage = value;\n }\n #customFrameTasks: Set<FrameTaskCallback> = new Set();\n #onFrameCallback: FrameTaskCallback | null = null;\n #onFrameCleanup: (() => void) | null = null;\n #playbackListener: ((event: PlaybackControllerUpdateEvent) => void) | null = null;\n \n /**\n * Centralized frame controller for coordinating element rendering.\n * Replaces the distributed Lit Task hierarchy with a single control point.\n */\n #frameController: FrameController = new FrameController(this);\n \n /**\n * Get the frame controller for centralized rendering coordination.\n * @public\n */\n get frameController(): FrameController {\n return this.#frameController;\n }\n\n /**\n * Get the effective FPS for this timegroup.\n * During rendering, uses the render options FPS if available.\n * Otherwise uses the configured fps property.\n * @public\n */\n get effectiveFps(): number {\n // During rendering, prefer the render options FPS\n if (typeof window !== \"undefined\" && window.EF_FRAMEGEN?.renderOptions) {\n return window.EF_FRAMEGEN.renderOptions.encoderOptions.video.framerate;\n }\n return this.fps;\n }\n\n /**\n * Get the current content epoch (used by thumbnail cache).\n * The epoch increments whenever visual content changes.\n * @public\n */\n get contentEpoch(): number {\n return this.#contentEpoch;\n }\n\n /**\n * Increment content epoch (called when visual content changes).\n * This invalidates cached thumbnails by changing their cache keys.\n * @public\n */\n incrementContentEpoch(): void {\n this.#contentEpoch++;\n }\n\n async #runThrottledFrameTask(): Promise<void> {\n if (this.playbackController) {\n return this.playbackController.runThrottledFrameTask();\n }\n await this.frameTask.run();\n }\n\n // ============================================================================\n // Purpose 3: Seeking Implementation\n // ============================================================================\n\n /** @public */\n @property({ type: Number, attribute: \"currenttime\" })\n set currentTime(time: number) {\n // Evaluate seek target (quantization and clamping)\n const seekTarget = evaluateSeekTarget(\n time,\n this.durationMs,\n this.effectiveFps,\n );\n\n // Delegate to playbackController if available\n if (this.playbackController) {\n this.playbackController.currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n return;\n }\n\n // Only root timegroups can have their currentTime set directly\n if (!this.isRootTimegroup) {\n return;\n }\n\n // Validate seek target\n if (Number.isNaN(seekTarget)) {\n return;\n }\n\n // Skip if already at target time (unless processing pending seek or restoring from localStorage)\n if (seekTarget === this.#currentTime && !this.#processingPendingSeek && !this.#restoringFromLocalStorage) {\n return;\n }\n\n // Skip if this is the same as pending seek\n if (this.#pendingSeekTime === seekTarget) {\n return;\n }\n \n // Prevent recursive seeks during localStorage restoration\n if (this.#restoringFromLocalStorage && seekTarget !== this.#currentTime) {\n // Allow the restoration seek to proceed, but prevent subsequent seeks\n // The flag will be cleared after the seek completes\n }\n\n // Handle concurrent seeks by queuing pending seek\n // This ensures we only have ONE seek in flight at a time, avoiding wasted work.\n // When scrubbing quickly, intermediate positions are skipped entirely - we don't\n // start work we know will be thrown away.\n if (this.#seekInProgress) {\n this.#pendingSeekTime = seekTarget;\n this.#currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n return;\n }\n\n // Execute seek - update both source time and user time\n this.#currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n this.#seekInProgress = true;\n\n // Attach .catch() to prevent unhandled rejection warning - errors are handled by seekTask.onError\n this.seekTask.run().catch(() => {}).finally(() => {\n this.#seekInProgress = false;\n \n // Process pending seek if it differs from completed seek\n // This jumps directly to wherever the user ended up, skipping intermediates\n if (\n this.#pendingSeekTime !== undefined &&\n this.#pendingSeekTime !== seekTarget\n ) {\n const pendingTime = this.#pendingSeekTime;\n this.#pendingSeekTime = undefined;\n this.#processingPendingSeek = true;\n try {\n this.currentTime = pendingTime;\n } finally {\n this.#processingPendingSeek = false;\n }\n } else {\n this.#pendingSeekTime = undefined;\n }\n });\n }\n\n /** @public */\n get currentTime() {\n if (this.playbackController) {\n return this.playbackController.currentTime;\n }\n return this.#currentTime ?? 0;\n }\n\n /** @public */\n set currentTimeMs(ms: number) {\n this.currentTime = ms / 1000;\n }\n\n /** @public */\n get currentTimeMs() {\n return this.currentTime * 1000;\n }\n\n /**\n * The time the user last requested via seek/scrub.\n * Preview systems should use this instead of currentTimeMs to avoid\n * seeing intermediate times during batch operations (thumbnails, export).\n * @public\n */\n get userTimeMs(): number {\n return this.#userTimeMs;\n }\n\n /**\n * Seek to a specific time and wait for all frames to be ready.\n * This is the recommended way to seek in tests and programmatic control.\n *\n * Combines seeking (Purpose 3) with frame rendering (Purpose 4) to ensure\n * all visible elements are ready after the seek completes.\n * \n * Updates both the source time AND userTimeMs (what the preview displays).\n *\n * @param timeMs - Time in milliseconds to seek to\n * @returns Promise that resolves when the seek is complete and all visible children are ready\n * @public\n */\n async seek(timeMs: number): Promise<void> {\n // Update user time - this is what the preview should display\n this.#userTimeMs = timeMs;\n \n // Execute seek (Purpose 3)\n this.currentTimeMs = timeMs;\n await this.seekTask.taskComplete;\n\n // Handle localStorage when playbackController delegates seek\n if (this.playbackController) {\n this.saveTimeToLocalStorage(this.currentTime);\n }\n\n // Wait for frame rendering (Purpose 4)\n await this.frameTask.taskComplete;\n\n // Ensure all visible elements have completed their reactive update cycles AND frame rendering\n // waitForFrameTasks() calls frameTask.run() on children, but this may happen before child\n // elements have processed property changes from requestUpdate(). To ensure frame data is\n // accurate, we wait for updateComplete first, then ensure the frameTask has run with the\n // updated properties. Elements like EFVideo provide waitForFrameReady() for this pattern.\n const visibleElements = this.#evaluateVisibleElementsForFrame();\n\n await Promise.all(\n visibleElements.map(async (element) => {\n if (\n \"waitForFrameReady\" in element &&\n typeof element.waitForFrameReady === \"function\"\n ) {\n await (element as any).waitForFrameReady();\n } else {\n await element.updateComplete;\n }\n }),\n );\n }\n\n /**\n * Optimized seek for render loops.\n * Unlike `seek()`, this:\n * - Skips waitForMediaDurations (already loaded at render setup)\n * - Skips localStorage persistence\n * - Uses FrameController for centralized element coordination\n * \n * Still waits for all content to be ready (Lit updates, element preparation, rendering).\n * \n * @param timeMs - Time in milliseconds to seek to\n * @internal\n */\n async seekForRender(timeMs: number): Promise<void> {\n // Set time directly (skip seekTask overhead)\n const newTime = timeMs / 1000;\n this.#userTimeMs = timeMs;\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n \n // First await: let Lit propagate time to children\n await this.updateComplete;\n \n // Collect all LitElement descendants (not just those with frameTask)\n // This ensures ef-text, ef-captions, and other reactive elements update\n const allLitElements = this.#getAllLitElementDescendants();\n \n // Wait for ALL LitElement descendants to complete their reactive updates\n // This is critical for elements like ef-text and ef-captions that don't have frameTask\n await Promise.all(allLitElements.map((el) => el.updateComplete));\n \n // Wait for ef-text elements to have their segments ready\n // ef-text creates segments asynchronously via requestAnimationFrame\n const textElements = allLitElements.filter((el) => el.tagName === \"EF-TEXT\");\n if (textElements.length > 0) {\n await Promise.all(\n textElements.map((el) => {\n if (\"whenSegmentsReady\" in el && typeof el.whenSegmentsReady === \"function\") {\n return (el as any).whenSegmentsReady();\n }\n return Promise.resolve();\n }),\n );\n }\n \n // Use FrameController for centralized element coordination\n // This replaces the old distributed frameTask system\n // Animation updates are handled via the onAnimationsUpdate callback\n await this.#frameController.renderFrame(timeMs, { \n waitForLitUpdate: false,\n onAnimationsUpdate: (root) => {\n updateAnimations(root as typeof this);\n // CRITICAL: Force style recalculation after updateAnimations sets animation.currentTime\n // Without this, getComputedStyle may return stale values (e.g., opacity: 0 instead of 1)\n // Accessing offsetWidth triggers synchronous style recalc\n void (root as HTMLElement).offsetWidth;\n },\n });\n \n // Execute custom frame tasks registered via addFrameTask()\n await this.#executeCustomFrameTasks();\n }\n \n /**\n * Collects all LitElement descendants recursively.\n * Used by seekForRender to ensure all reactive elements have updated.\n * Optimized to filter by temporal visibility and skip hidden subtrees.\n */\n #getAllLitElementDescendants(): LitElement[] {\n const result: LitElement[] = [];\n const currentTimeMs = this.currentTimeMs;\n let hiddenSubtreesSkipped = 0;\n let temporallyFilteredOut = 0;\n let totalLitElements = 0;\n \n const walk = (el: Element) => {\n // OPTIMIZATION: Check inline style.display first (no getComputedStyle call)\n if (el instanceof HTMLElement && el.style.display === 'none') {\n hiddenSubtreesSkipped++;\n return; // Fast path - skip without expensive getComputedStyle\n }\n \n // Slow path: only if inline style doesn't indicate hidden\n if (el instanceof HTMLElement) {\n const style = getComputedStyle(el);\n if (style.display === \"none\" || style.visibility === \"hidden\") {\n hiddenSubtreesSkipped++;\n return; // Don't walk children\n }\n }\n \n for (const child of el.children) {\n if (child instanceof LitElement) {\n totalLitElements++;\n // Check temporal visibility for temporal elements\n if (\"startTimeMs\" in child && \"endTimeMs\" in child) {\n const startMs = (child as any).startTimeMs ?? -Infinity;\n const endMs = (child as any).endTimeMs ?? Infinity;\n if (currentTimeMs >= startMs && currentTimeMs <= endMs) {\n result.push(child);\n } else {\n temporallyFilteredOut++;\n }\n } else {\n // Non-temporal elements always included\n result.push(child);\n }\n }\n walk(child);\n }\n };\n walk(this);\n \n return result;\n }\n\n /**\n * Determines if this is a root timegroup (no parent timegroups)\n * @public\n */\n get isRootTimegroup(): boolean {\n return !this.parentTimegroup;\n }\n\n /**\n * Property-based frame task callback for React integration.\n * When set, automatically registers the callback as a frame task.\n * Setting a new value automatically cleans up the previous callback.\n * Set to null or undefined to remove the callback.\n *\n * @example\n * // React usage:\n * <Timegroup onFrame={({ ownCurrentTimeMs, percentComplete }) => {\n * // Per-frame updates\n * }} />\n *\n * @public\n */\n get onFrame(): FrameTaskCallback | null {\n return this.#onFrameCallback;\n }\n\n set onFrame(callback: FrameTaskCallback | null | undefined) {\n // Clean up previous callback if exists\n if (this.#onFrameCleanup) {\n this.#onFrameCleanup();\n this.#onFrameCleanup = null;\n }\n this.#onFrameCallback = callback ?? null;\n\n // Register new callback if provided\n if (callback) {\n this.#onFrameCleanup = this.addFrameTask(callback);\n }\n }\n\n /**\n * Register a custom frame task callback that will be executed during frame rendering.\n * The callback receives timing information and can be async or sync.\n * Multiple callbacks can be registered and will execute in parallel.\n *\n * @param callback - Function to execute on each frame\n * @returns A cleanup function that removes the callback when called\n * @public\n */\n addFrameTask(callback: FrameTaskCallback): () => void {\n if (typeof callback !== \"function\") {\n throw new Error(\"Frame task callback must be a function\");\n }\n this.#customFrameTasks.add(callback);\n return () => {\n this.#customFrameTasks.delete(callback);\n };\n }\n\n /**\n * Remove a previously registered custom frame task callback.\n *\n * @param callback - The callback function to remove\n * @public\n */\n removeFrameTask(callback: FrameTaskCallback): void {\n this.#customFrameTasks.delete(callback);\n }\n\n /** @internal */\n saveTimeToLocalStorage(time: number) {\n try {\n if (this.id && this.isConnected && !Number.isNaN(time)) {\n localStorage.setItem(this.storageKey, time.toString());\n }\n } catch (error) {\n log(\"Failed to save time to localStorage\", error);\n }\n }\n\n render() {\n return html`<slot @slotchange=${this.#handleSlotChange}></slot> `;\n }\n\n #handleSlotChange = () => {\n // Invalidate caches when slot content changes\n resetTemporalCache();\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n\n // Request update to trigger recalculation of dependent properties\n this.requestUpdate();\n };\n\n /** @internal */\n loadTimeFromLocalStorage(): number | undefined {\n if (this.id) {\n try {\n const storedValue = localStorage.getItem(this.storageKey);\n if (storedValue === null) {\n return undefined;\n }\n const parsedValue = Number.parseFloat(storedValue);\n // Guard against NaN and Infinity which could cause issues\n if (Number.isNaN(parsedValue) || !Number.isFinite(parsedValue)) {\n return undefined;\n }\n return parsedValue;\n } catch (error) {\n log(\"Failed to load time from localStorage\", error);\n }\n }\n return undefined;\n }\n\n connectedCallback() {\n \n // CRITICAL: super.connectedCallback() MUST be synchronous for Lit lifecycle to work correctly.\n // Deferring it breaks render clones because updateComplete resolves before Lit initializes.\n // \n // EFTemporal.connectedCallback() handles root detection after Lit Context propagates:\n // - Schedules updateComplete.then(didBecomeRoot check)\n // - Only true roots (no parent after context) create PlaybackController\n // \n // PlaybackController.hostConnected() owns ALL root initialization:\n // - waitForMediaDurations\n // - localStorage time restoration \n // - initial seek\n //\n // This avoids the previous race conditions where both EFTimegroup.connectedCallback\n // and PlaybackController.hostConnected tried to initialize, causing concurrent seeks.\n super.connectedCallback();\n\n // Defer TimegroupController creation and workbench wrapping to next frame\n // These operations involve DOM queries (closest, getBoundingClientRect) which\n // can be expensive when many elements initialize simultaneously\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (this.parentTimegroup) {\n new TimegroupController(this.parentTimegroup, this);\n }\n\n\n if (this.shouldWrapWithWorkbench()) {\n this.wrapWithWorkbench();\n }\n });\n });\n }\n \n /**\n * Called when this timegroup becomes a root (no parent timegroup).\n * Sets up the playback listener after PlaybackController is created.\n * @internal\n */\n didBecomeRoot() {\n super.didBecomeRoot();\n this.#setupPlaybackListener();\n }\n\n /**\n * Setup listener on playbackController to sync userTimeMs during playback.\n */\n #setupPlaybackListener(): void {\n // Already setup or no controller\n if (this.#playbackListener || !this.playbackController) return;\n \n this.#playbackListener = (event: PlaybackControllerUpdateEvent) => {\n // Only update userTimeMs during playback time changes\n // Clone-timeline: captures use separate clones, so Prime-timeline updates freely\n if (event.property === \"currentTimeMs\" && typeof event.value === \"number\") {\n if (this.playing) {\n this.#userTimeMs = event.value;\n }\n }\n };\n \n this.playbackController.addListener(this.#playbackListener);\n }\n \n /**\n * Remove playback listener on disconnect.\n */\n #removePlaybackListener(): void {\n if (this.#playbackListener && this.playbackController) {\n this.playbackController.removeListener(this.#playbackListener);\n }\n this.#playbackListener = null;\n }\n\n #previousDurationMs = 0;\n\n protected updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n if (changedProperties.has(\"mode\") || changedProperties.has(\"overlapMs\")) {\n durationCache.delete(this);\n }\n\n if (this.#previousDurationMs !== this.durationMs) {\n this.#previousDurationMs = this.durationMs;\n this.#runThrottledFrameTask();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#resizeObserver?.disconnect();\n this.#removePlaybackListener();\n }\n\n /**\n * Capture the timegroup at a specific timestamp as a canvas.\n * Does NOT modify currentTimeMs - captures are rendered independently.\n * \n * @param options - Capture options including timeMs, scale, contentReadyMode\n * @returns Promise resolving to an HTMLCanvasElement with the captured frame\n * @public\n */\n async captureAtTime(options: CaptureOptions): Promise<HTMLCanvasElement> {\n return captureTimegroupAtTime(this, options);\n }\n\n /**\n * Capture multiple timestamps as canvas thumbnails in a single batch.\n * \n * CLONE-TIMELINE ARCHITECTURE:\n * Creates a single render clone and reuses it across all captures.\n * Prime-timeline is NEVER seeked - user can continue previewing/editing during capture.\n * \n * @param timestamps - Array of timestamps (in milliseconds) to capture\n * @param options - Capture options (scale, contentReadyMode, blockingTimeoutMs)\n * @returns Promise resolving to array of HTMLCanvasElements\n * @public\n */\n async captureBatch(\n timestamps: number[],\n options: CaptureBatchOptions = {},\n ): Promise<HTMLCanvasElement[]> {\n if (timestamps.length === 0) return [];\n\n const {\n scale = 0.25,\n contentReadyMode = \"immediate\",\n blockingTimeoutMs = 5000,\n } = options;\n\n const batchStartTime = performance.now();\n\n // CLONE-TIMELINE: Create ONE clone and reuse across all captures\n const cloneStartTime = performance.now();\n const { clone: renderClone, container: renderContainer, cleanup: cleanupRenderClone } =\n await this.createRenderClone();\n const cloneTime = performance.now() - cloneStartTime;\n\n // Pre-fetch scrub segments for all video elements to ensure fast seeks\n const prefetchStartTime = performance.now();\n const videoElements = renderClone.querySelectorAll(\"ef-video\");\n if (videoElements.length > 0) {\n await Promise.all(\n Array.from(videoElements).map((video) =>\n (video as import(\"./EFVideo.js\").EFVideo).prefetchScrubSegments(timestamps),\n ),\n );\n }\n const prefetchTime = performance.now() - prefetchStartTime;\n\n const canvases: HTMLCanvasElement[] = [];\n let totalSeekTime = 0;\n let totalCaptureTime = 0;\n\n try {\n for (let i = 0; i < timestamps.length; i++) {\n const timeMs = timestamps[i]!\n \n // Seek clone to target time using optimized seekForRender\n // (skips waitForMediaDurations, localStorage, consolidates awaits)\n const seekStart = performance.now();\n await renderClone.seekForRender(timeMs);\n totalSeekTime += performance.now() - seekStart;\n \n // Capture from the seeked clone\n const captureStart = performance.now();\n const canvas = await captureFromClone(renderClone, renderContainer, {\n scale,\n contentReadyMode,\n blockingTimeoutMs,\n originalTimegroup: this,\n });\n totalCaptureTime += performance.now() - captureStart;\n canvases.push(canvas);\n }\n \n return canvases;\n } finally {\n // Log timing and clean up the render clone\n const totalTime = performance.now() - batchStartTime;\n console.log(`[captureBatch] ${timestamps.length} frames: clone=${cloneTime.toFixed(0)}ms, prefetch=${prefetchTime.toFixed(0)}ms, seek=${totalSeekTime.toFixed(0)}ms, capture=${totalCaptureTime.toFixed(0)}ms, total=${totalTime.toFixed(0)}ms`);\n cleanupRenderClone();\n }\n }\n\n /**\n * Render the timegroup to an MP4 video file and trigger download.\n * Captures each frame at the specified fps, encodes using WebCodecs via\n * MediaBunny, and downloads the resulting video.\n * \n * @param options - Rendering options (fps, codec, bitrate, filename, etc.)\n * @returns Promise that resolves when video is downloaded\n * @public\n */\n async renderToVideo(options?: RenderToVideoOptions): Promise<Uint8Array | undefined> {\n return renderTimegroupToVideo(this, options);\n }\n\n /**\n * Runs the initializer function with validation for synchronous execution and time budget.\n * @throws Error if no initializer is set\n * @throws Error if initializer returns a Promise (async not allowed)\n * @throws Error if initializer takes more than INITIALIZER_ERROR_THRESHOLD_MS\n * @internal\n */\n #runInitializer(cloneEl: EFTimegroup): void {\n if (!this.initializer) {\n return;\n }\n \n const startTime = performance.now();\n const result: unknown = this.initializer(cloneEl);\n const elapsed = performance.now() - startTime;\n \n // Check for async (Promise return) - initializers MUST be synchronous\n if (result !== undefined && result !== null && typeof (result as any).then === 'function') {\n throw new Error(\n 'Timeline initializer must be synchronous. ' +\n 'Do not return a Promise from the initializer function.'\n );\n }\n \n // Time budget enforcement - initializers run for EVERY clone\n if (elapsed > INITIALIZER_ERROR_THRESHOLD_MS) {\n throw new Error(\n `Timeline initializer took ${elapsed.toFixed(1)}ms, exceeding the ${INITIALIZER_ERROR_THRESHOLD_MS}ms limit. ` +\n 'Initializers must be fast - move expensive work outside the initializer.'\n );\n }\n \n if (elapsed > INITIALIZER_WARN_THRESHOLD_MS) {\n console.warn(\n `[ef-timegroup] Initializer took ${elapsed.toFixed(1)}ms, exceeding ${INITIALIZER_WARN_THRESHOLD_MS}ms. ` +\n 'Consider optimizing for better render performance.'\n );\n }\n }\n\n /**\n * Copy captionsData property from original to clone.\n * cloneNode() only copies attributes, not JavaScript properties.\n * captionsData is often set via JS (e.g., captionsEl.captionsData = {...}),\n * so we must manually copy it to the cloned elements.\n * @internal\n */\n #copyCaptionsData(original: Element, clone: Element): void {\n // Find matching caption elements by position (querySelectorAll returns in document order)\n const originalCaptions = original.querySelectorAll('ef-captions');\n const cloneCaptions = clone.querySelectorAll('ef-captions');\n \n for (let i = 0; i < originalCaptions.length && i < cloneCaptions.length; i++) {\n const origCap = originalCaptions[i] as any;\n const cloneCap = cloneCaptions[i] as any;\n \n // Copy captionsData if set via JS property\n if (origCap.captionsData) {\n cloneCap.captionsData = origCap.captionsData;\n }\n }\n }\n \n /**\n * Copy ef-text _textContent property from original to cloned elements.\n * This MUST be called BEFORE elements upgrade (before updateComplete)\n * because splitText() runs in connectedCallback and will clear segments\n * if _textContent is null/empty.\n * @internal\n */\n #copyTextContent(original: Element, clone: Element): void {\n const originalTexts = original.querySelectorAll('ef-text');\n const cloneTexts = clone.querySelectorAll('ef-text');\n \n for (let i = 0; i < originalTexts.length && i < cloneTexts.length; i++) {\n const origText = originalTexts[i] as any;\n const cloneText = cloneTexts[i] as any;\n \n // Copy _textContent if it exists\n // This is a private property, so we access it via any\n if (origText._textContent !== undefined) {\n cloneText._textContent = origText._textContent;\n }\n // Also copy the segments getter to ensure we can read them\n if (origText._templateElement !== undefined) {\n cloneText._templateElement = origText._templateElement;\n }\n }\n }\n \n /**\n * Copy ef-text-segment properties from original to cloned elements.\n * segmentText and other properties are set via JS, not attributes,\n * so we must manually copy them to the cloned elements.\n * @internal\n */\n #copyTextSegmentData(original: Element, clone: Element): void {\n // Find matching text segment elements by position\n const originalSegments = original.querySelectorAll('ef-text-segment');\n const cloneSegments = clone.querySelectorAll('ef-text-segment');\n \n for (let i = 0; i < originalSegments.length && i < cloneSegments.length; i++) {\n const origSeg = originalSegments[i] as any;\n const cloneSeg = cloneSegments[i] as any;\n \n // Copy all segment properties\n if (origSeg.segmentText !== undefined) {\n cloneSeg.segmentText = origSeg.segmentText;\n }\n if (origSeg.segmentIndex !== undefined) {\n cloneSeg.segmentIndex = origSeg.segmentIndex;\n }\n if (origSeg.staggerOffsetMs !== undefined) {\n cloneSeg.staggerOffsetMs = origSeg.staggerOffsetMs;\n }\n if (origSeg.segmentStartMs !== undefined) {\n cloneSeg.segmentStartMs = origSeg.segmentStartMs;\n }\n if (origSeg.segmentEndMs !== undefined) {\n cloneSeg.segmentEndMs = origSeg.segmentEndMs;\n }\n }\n }\n\n /**\n * Wait for all ef-captions elements to have their data loaded.\n * This is needed because EFCaptions is not an EFMedia, so waitForMediaDurations doesn't cover it.\n * Used by createRenderClone to ensure captions are ready before rendering.\n * @internal\n */\n async #waitForCaptionsData(root: Element): Promise<void> {\n // Find all ef-captions elements (including nested in timegroups)\n const captionsElements = root.querySelectorAll('ef-captions');\n if (captionsElements.length === 0) return;\n \n // Wait for each caption element's data to load\n // Use duck-typing to check for loadCaptionsData method\n const waitPromises: Promise<unknown>[] = [];\n for (const el of captionsElements) {\n const captions = el as any;\n // Try new async method first\n if (typeof captions.loadCaptionsData === 'function') {\n waitPromises.push(captions.loadCaptionsData().catch(() => {}));\n }\n // Fallback to task if present\n else if (captions.unifiedCaptionsDataTask?.taskComplete) {\n waitPromises.push(captions.unifiedCaptionsDataTask.taskComplete.catch(() => {}));\n }\n }\n \n if (waitPromises.length > 0) {\n await Promise.all(waitPromises);\n }\n }\n\n /**\n * Create an independent clone of this timegroup for rendering.\n * The clone is a fully functional ef-timegroup with its own animations\n * and time state, isolated from the original (Prime-timeline).\n * \n * OPTIONAL: An initializer can be set via `timegroup.initializer = (tg) => { ... }`\n * to re-run JavaScript setup (frame callbacks, React components) on each clone.\n * \n * This enables:\n * - Rendering without affecting user's preview position\n * - Concurrent renders with different clones\n * - Re-running JavaScript setup on each clone (if initializer is provided)\n * \n * @returns Promise resolving to clone, container, and cleanup function\n * @throws Error if initializer is async or takes too long\n * @public\n */\n async createRenderClone(): Promise<RenderCloneResult> {\n // 1. Create offscreen container positioned off-screen but in the DOM\n // The clone needs to be in the DOM for:\n // - Custom elements to upgrade (connectedCallback)\n // - CSS to compute correctly\n // - Animations to work\n const container = document.createElement(\"div\");\n container.className = \"ef-render-clone-container\";\n container.style.cssText = `\n position: fixed;\n left: -9999px;\n top: 0;\n width: ${this.offsetWidth || 1920}px;\n height: ${this.offsetHeight || 1080}px;\n pointer-events: none;\n overflow: hidden;\n `;\n \n // 2. Deep clone the DOM - this clones the entire subtree\n const cloneEl = this.cloneNode(true) as EFTimegroup;\n \n // CRITICAL: Clear the clone's id to prevent localStorage conflicts\n // The original timegroup and clone would share the same localStorage key otherwise,\n // which causes time to be loaded from storage during connectedCallback.\n cloneEl.removeAttribute(\"id\");\n \n // 2b. Copy JavaScript properties that aren't cloned by cloneNode()\n this.#copyCaptionsData(this, cloneEl);\n \n // 2c. Copy ef-text _textContent BEFORE elements upgrade\n // This is critical because splitText() runs in connectedCallback and will\n // clear segments if _textContent is empty\n this.#copyTextContent(this, cloneEl);\n \n // 3. Preserve ef-configuration context for the clone\n // Media elements use closest(\"ef-configuration\") to determine settings like media-engine.\n // Without this, clones would lose configuration and use wrong media engine types.\n const originalConfig = this.closest(\"ef-configuration\");\n if (originalConfig) {\n // Shallow clone the configuration element (just the element, not children)\n const configClone = originalConfig.cloneNode(false) as HTMLElement;\n configClone.appendChild(cloneEl);\n container.appendChild(configClone);\n } else {\n container.appendChild(cloneEl);\n }\n \n document.body.appendChild(container);\n \n // 3. Wait for custom elements to upgrade\n await cloneEl.updateComplete;\n \n // 3b. Copy ef-text-segment properties AFTER elements have upgraded\n // segmentText is a JS property, so we need to wait for custom elements to define it\n this.#copyTextSegmentData(this, cloneEl);\n \n // 4. Run initializer to set up JavaScript behavior on the clone\n // This re-registers frame callbacks, React components, etc.\n // MUST be synchronous and fast (enforced by #runInitializer)\n // NOTE: For React, the initializer may REPLACE cloneEl with a fresh React-rendered tree\n this.#runInitializer(cloneEl);\n \n // 5. Find the actual timegroup after initializer runs\n // React initializers replace the cloned DOM with a fresh render, so we need to find\n // the actual ef-timegroup in the container (may be different from cloneEl)\n let actualClone = container.querySelector('ef-timegroup') as EFTimegroup;\n if (!actualClone) {\n throw new Error(\n 'No ef-timegroup found after initializer. ' +\n 'Ensure your initializer renders a Timegroup (React) or does not remove the cloned element (vanilla JS).'\n );\n }\n \n // 6. Wait for custom elements to upgrade\n // React renders DOM synchronously via flushSync, but custom elements upgrade asynchronously.\n // We need to ensure the element has been upgraded to its class before accessing Lit properties.\n await customElements.whenDefined('ef-timegroup');\n \n // Force upgrade of all custom elements in the container (in case they haven't upgraded yet)\n customElements.upgrade(container);\n \n // Re-query in case the element reference changed during upgrade\n actualClone = container.querySelector('ef-timegroup') as EFTimegroup;\n if (!actualClone) {\n throw new Error('ef-timegroup element lost after upgrade');\n }\n \n // 7. Wait for LitElement updates and media durations\n await actualClone.updateComplete;\n \n // 7a. CRITICAL: Manually set up parent-child relationships for cloned elements\n // Lit Context doesn't automatically propagate to cloned children because the\n // context consumer decorator runs before the element is in the DOM tree.\n // We need to explicitly walk the tree and set parentTimegroup/rootTimegroup on each element.\n const setupParentChildRelationships = (parent: EFTimegroup, root: EFTimegroup) => {\n for (const child of parent.children) {\n // Handle nested timegroups\n if (child.tagName === 'EF-TIMEGROUP') {\n const childTG = child as EFTimegroup;\n // Use the public setter to trigger proper initialization\n childTG.parentTimegroup = parent;\n childTG.rootTimegroup = root;\n // Lock to prevent Lit Context from overriding our manually set root\n (childTG as any).lockRootTimegroup();\n // Recursively process children\n setupParentChildRelationships(childTG, root);\n }\n // Handle temporal elements (videos, audio, captions, etc.)\n else if ('parentTimegroup' in child && 'rootTimegroup' in child) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n temporal.parentTimegroup = parent;\n temporal.rootTimegroup = root;\n // Lock to prevent Lit Context from overriding our manually set root\n if ('lockRootTimegroup' in temporal && typeof temporal.lockRootTimegroup === 'function') {\n temporal.lockRootTimegroup();\n }\n }\n // Recursively check non-timegroup containers (divs, etc.)\n else if (child instanceof Element) {\n setupParentChildRelationshipsInContainer(child, parent, root);\n }\n }\n };\n \n const setupParentChildRelationshipsInContainer = (container: Element, nearestParentTG: EFTimegroup, root: EFTimegroup) => {\n for (const child of container.children) {\n if (child.tagName === 'EF-TIMEGROUP') {\n const childTG = child as EFTimegroup;\n childTG.parentTimegroup = nearestParentTG;\n childTG.rootTimegroup = root;\n (childTG as any).lockRootTimegroup();\n setupParentChildRelationships(childTG, root);\n } else if ('parentTimegroup' in child && 'rootTimegroup' in child) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n temporal.parentTimegroup = nearestParentTG;\n temporal.rootTimegroup = root;\n if ('lockRootTimegroup' in temporal && typeof temporal.lockRootTimegroup === 'function') {\n temporal.lockRootTimegroup();\n }\n } else if (child instanceof Element) {\n setupParentChildRelationshipsInContainer(child, nearestParentTG, root);\n }\n }\n };\n \n // Set up the root clone itself (it's its own root, no parent)\n // Note: This must happen BEFORE lockRootTimegroup to set the correct value\n actualClone.rootTimegroup = actualClone;\n setupParentChildRelationships(actualClone, actualClone);\n \n // Wait for updates to propagate\n await actualClone.updateComplete;\n \n // CRITICAL: Lock AFTER all updates are complete to prevent further context changes\n // Also re-set rootTimegroup in case Lit Context overwrote it during updates\n actualClone.rootTimegroup = actualClone;\n (actualClone as any).lockRootTimegroup();\n const finalizeRootTimegroup = (el: Element) => {\n if ('rootTimegroup' in el && 'lockRootTimegroup' in el) {\n (el as any).rootTimegroup = actualClone;\n (el as any).lockRootTimegroup();\n }\n for (const child of el.children) {\n finalizeRootTimegroup(child);\n }\n };\n finalizeRootTimegroup(actualClone);\n \n await actualClone.waitForMediaDurations();\n \n // 7b. Wait for captions data to load (ef-captions elements need to fetch their data)\n // This is separate from waitForMediaDurations because EFCaptions is not an EFMedia.\n await this.#waitForCaptionsData(actualClone);\n \n // 8. CRITICAL: Remove PlaybackController from clone\n // Clones get a PlaybackController when they become root (in didBecomeRoot callback).\n // But render clones need direct seeking without the UI/context machinery.\n // Remove it to enable direct seekTask execution.\n if (actualClone.playbackController) {\n actualClone.playbackController.remove();\n actualClone.playbackController = undefined;\n }\n \n // 9. Initial seek to frame 0 to ensure animations are at correct state\n await actualClone.seek(0);\n \n return {\n clone: actualClone,\n container,\n cleanup: () => {\n // Remove container from DOM immediately\n container.remove();\n \n // Unmount React root if present (set by TimelineRoot component)\n // Defer to next microtask to avoid \"unmount during render\" warning\n // when cleanup is called rapidly during batch operations\n const reactRoot = (actualClone as any)._reactRoot;\n if (reactRoot) {\n queueMicrotask(() => {\n reactRoot.unmount();\n });\n }\n },\n };\n }\n\n /** @internal */\n get storageKey() {\n if (!this.id) {\n throw new Error(\"Timegroup must have an id to use localStorage.\");\n }\n return `ef-timegroup-${this.id}`;\n }\n\n /** @internal */\n get intrinsicDurationMs() {\n if (this.hasExplicitDuration) {\n return this.explicitDurationMs;\n }\n return undefined;\n }\n\n /** @internal */\n get hasOwnDuration() {\n return hasOwnDurationForMode(this.mode, this.hasExplicitDuration);\n }\n\n // ============================================================================\n // Purpose 1: Composition Rules Implementation\n // ============================================================================\n\n /** @public */\n get durationMs(): number {\n // Fixed mode delegates to parent class durationMs which handles trimming, source in/out, etc.\n if (this.mode === \"fixed\") {\n return super.durationMs;\n }\n\n // Evaluate duration semantics based on mode (Purpose 1)\n // childTemporals returns TemporalMixinInterface[], but we need HTMLElement intersection\n const childTemporalsAsElements = this.childTemporals as Array<\n TemporalMixinInterface & HTMLElement\n >;\n return evaluateDurationForMode(this, this.mode, childTemporalsAsElements);\n }\n\n // ============================================================================\n // Purpose 4: Frame Rendering - What Happens Each Frame\n // ============================================================================\n\n /**\n * Evaluates which elements should be rendered in the current frame.\n * Filters to only include temporally visible elements for frame processing.\n * Uses animation-friendly visibility to prevent animation jumps at exact boundaries.\n */\n #evaluateVisibleElementsForFrame(): Array<\n TemporalMixinInterface & HTMLElement\n > {\n const temporalElements = deepGetElementsWithFrameTasks(this);\n return temporalElements.filter((element) => {\n const animationState = evaluateAnimationVisibilityState(element);\n return animationState.isVisible;\n });\n }\n\n /** @internal */\n async waitForFrameTasks(signal?: AbortSignal) {\n const result = await withSpan(\n \"timegroup.waitForFrameTasks\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // Check abort before starting\n signal?.throwIfAborted();\n \n const innerStart = performance.now();\n\n const temporalElements = deepGetElementsWithFrameTasks(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"temporalElementsCount\", temporalElements.length);\n }\n\n // Check abort after getting elements\n signal?.throwIfAborted();\n\n // Evaluate which elements should be rendered\n const visibleElements = this.#evaluateVisibleElementsForFrame();\n if (isTracingEnabled()) {\n span.setAttribute(\"visibleElementsCount\", visibleElements.length);\n }\n\n const promiseStart = performance.now();\n\n // Execute frame tasks for all visible elements\n // CRITICAL: Must wait for updateComplete before running frameTask so that\n // property changes (like desiredSeekTimeMs) have propagated to the element.\n // Elements with waitForFrameReady() handle this; others need explicit waiting.\n await Promise.all(\n visibleElements.map(async (element) => {\n // Check abort before each element\n signal?.throwIfAborted();\n \n try {\n if (\n \"waitForFrameReady\" in element &&\n typeof element.waitForFrameReady === \"function\"\n ) {\n await (element as any).waitForFrameReady();\n } else {\n await element.updateComplete;\n await element.frameTask.run();\n }\n } catch (error) {\n // AbortErrors are expected when elements are disconnected or tasks cancelled\n const isAbortError = \n error instanceof DOMException && error.name === \"AbortError\" ||\n error instanceof Error && (\n error.name === \"AbortError\" ||\n (error as any).message?.includes(\"signal is aborted\") ||\n (error as any).message?.includes(\"The user aborted a request\")\n );\n \n if (isAbortError) {\n // Re-throw if our signal is also aborted (propagate cancellation)\n signal?.throwIfAborted();\n return; // Otherwise silently ignore - element was disconnected\n }\n throw error;\n }\n }),\n );\n const promiseEnd = performance.now();\n\n const innerEnd = performance.now();\n if (isTracingEnabled()) {\n span.setAttribute(\"actualInnerMs\", innerEnd - innerStart);\n span.setAttribute(\"promiseAwaitMs\", promiseEnd - promiseStart);\n }\n },\n );\n\n return result;\n }\n\n #mediaDurationsPromise: Promise<void> | undefined = undefined;\n\n /** @internal */\n async waitForMediaDurations(signal?: AbortSignal) {\n // Check abort before starting\n signal?.throwIfAborted();\n \n // Start loading media durations in background, but don't block if already in progress\n // This prevents multiple concurrent calls from creating redundant work\n if (!this.#mediaDurationsPromise) {\n this.#mediaDurationsPromise = this.#waitForMediaDurations(signal).catch(err => {\n // Re-throw AbortError to propagate cancellation\n if (err instanceof DOMException && err.name === \"AbortError\") {\n this.#mediaDurationsPromise = undefined;\n throw err;\n }\n console.error(`[EFTimegroup] waitForMediaDurations failed for ${this.id || 'unnamed'}:`, err);\n // Clear promise on error so it can be retried\n this.#mediaDurationsPromise = undefined;\n throw err;\n });\n }\n \n // If signal is provided and aborted, throw immediately\n if (signal?.aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n \n return this.#mediaDurationsPromise;\n }\n\n /**\n * Wait for all media elements to load their initial segments.\n * Ideally we would only need the extracted index json data, but\n * that caused issues with constructing audio data. We had negative durations\n * in calculations and it was not clear why.\n */\n async #waitForMediaDurations(signal?: AbortSignal) {\n return withSpan(\n \"timegroup.waitForMediaDurations\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // Check abort before starting\n signal?.throwIfAborted();\n \n // Don't wait for updateComplete during initialization - it causes deadlocks with nested timegroups\n // Instead, use a short delay to let elements connect, then scan for media elements\n // If elements aren't ready yet, we'll retry or they'll be picked up on the next update cycle\n await new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n \n const abortHandler = () => {\n clearTimeout(timeoutId);\n cancelAnimationFrame(rafId2);\n cancelAnimationFrame(rafId1);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener('abort', abortHandler, { once: true });\n \n let rafId1: number;\n let rafId2: number;\n let timeoutId: ReturnType<typeof setTimeout>;\n \n // Use multiple animation frames to ensure DOM is ready, but don't wait for all children\n rafId1 = requestAnimationFrame(() => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n rafId2 = requestAnimationFrame(() => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n // Small additional delay to let custom elements upgrade\n timeoutId = setTimeout(() => {\n signal?.removeEventListener('abort', abortHandler);\n resolve();\n }, 10);\n });\n });\n });\n \n // Check abort after delay\n signal?.throwIfAborted();\n \n const mediaElements = deepGetMediaElements(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"mediaElementsCount\", mediaElements.length);\n }\n\n // Check abort after getting elements\n signal?.throwIfAborted();\n\n // Then, we must await the fragmentIndexTask to ensure all media elements have their\n // fragment index loaded, which is where their duration is parsed from.\n // Use Promise.allSettled with timeout to avoid blocking if asset server is slow\n const mediaLoadStart = Date.now();\n const MEDIA_LOAD_TIMEOUT_MS = 30000; // 30 second timeout per element\n \n const loadPromises = mediaElements.map(async (m, index) => {\n // Check abort before each element\n signal?.throwIfAborted();\n \n const elementStart = Date.now();\n try {\n // Use getMediaEngine async method if available\n if (typeof m.getMediaEngine === 'function') {\n // Add timeout to prevent indefinite blocking\n const timeoutPromise = new Promise<undefined>((_, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(() => reject(new Error(`Media element ${index} load timeout after ${MEDIA_LOAD_TIMEOUT_MS}ms`)), MEDIA_LOAD_TIMEOUT_MS);\n signal?.addEventListener('abort', () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }, { once: true });\n });\n \n await Promise.race([m.getMediaEngine(signal), timeoutPromise]);\n } \n // Fallback: check status and use taskComplete\n else if (m.mediaEngineTask) {\n // Status: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n const status = m.mediaEngineTask.status;\n \n // Already complete or errored - no need to wait\n if (status === 2 || status === 3) {\n return;\n }\n \n // Attach .catch() to taskComplete to prevent unhandled rejection\n const taskPromise = m.mediaEngineTask.taskComplete;\n taskPromise?.catch(() => {});\n \n if (taskPromise) {\n const timeoutPromise = new Promise<undefined>((_, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(() => reject(new Error(`Media element ${index} load timeout after ${MEDIA_LOAD_TIMEOUT_MS}ms`)), MEDIA_LOAD_TIMEOUT_MS);\n signal?.addEventListener('abort', () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }, { once: true });\n });\n \n await Promise.race([taskPromise, timeoutPromise]);\n }\n }\n } catch (error) {\n // Re-throw AbortError to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Log only if tracing is enabled to reduce console noise\n if (isTracingEnabled()) {\n const elementElapsed = Date.now() - elementStart;\n console.error(`[EFTimegroup] Media element ${index} failed after ${elementElapsed}ms:`, error);\n }\n // Don't throw - continue with other elements\n }\n });\n \n const results = await Promise.allSettled(loadPromises);\n \n // Check if any were aborted\n const aborted = results.some(r => \n r.status === 'rejected' && \n r.reason instanceof DOMException && \n r.reason.name === \"AbortError\"\n );\n if (aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n \n // Log any failures but don't throw - we want to continue even if some media fails\n const failures = results.filter(r => r.status === 'rejected');\n if (failures.length > 0 && isTracingEnabled()) {\n const mediaLoadElapsed = Date.now() - mediaLoadStart;\n console.warn(`[EFTimegroup] ${failures.length} media elements failed to load in ${mediaLoadElapsed}ms:`, failures.map(r => r.status === 'rejected' ? r.reason : null));\n }\n\n // After waiting for durations, we must force some updates to cascade and ensure all temporal elements\n // have correct durations and start times. It is not ideal that we have to do this inside here,\n // but it is the best current way to ensure that all temporal elements have correct durations and start times.\n\n // Next, we must flush the startTimeMs cache to ensure all media elements have their\n // startTimeMs parsed fresh, otherwise the startTimeMs is cached per animation frame.\n flushStartTimeMsCache();\n\n // Flush duration cache since child durations may have changed\n flushSequenceDurationCache();\n\n // Request an update to the currentTime of this group, ensuring that time updates will cascade\n // down to children, forcing sequence groups to arrange correctly.\n // This also makes the filmstrip update correctly.\n // Defer using setTimeout(0) to avoid Lit warning about scheduling updates after update completed.\n // This method can be called during a task or after an update cycle completes, and using\n // setTimeout ensures we're completely outside any Lit update cycle.\n setTimeout(() => this.requestUpdate(\"currentTime\"), 0);\n // Note: We don't await updateComplete here during initialization to avoid deadlocks.\n // The update will complete asynchronously, and sequence groups will arrange correctly\n // once all timegroups have finished initializing. During normal operation (seeks, etc.),\n // the caller will wait for updateComplete explicitly if needed.\n },\n );\n }\n\n /** @internal */\n get childTemporals() {\n return shallowGetTemporalElements(this);\n }\n\n get #contextProvider() {\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n return parent;\n }\n parent = parent.parentNode;\n }\n return null;\n }\n\n /**\n * Returns true if the timegroup should be wrapped with a workbench.\n *\n * A timegroup should be wrapped with a workbench if:\n * - It's being rendered (EF_RENDERING), OR\n * - The workbench property is set to true\n *\n * If the timegroup is already wrapped in a context provider like ef-preview,\n * it should NOT be wrapped in a workbench.\n * @internal\n */\n shouldWrapWithWorkbench() {\n // Only root timegroups should wrap with workbench\n if (!this.isRootTimegroup) {\n return false;\n }\n\n // Never wrap with workbench when inside a canvas\n // Canvas manages its own layout and coordinate system\n if (this.closest(\"ef-canvas\") !== null) {\n return false;\n }\n\n // Never wrap if already inside preview, workbench, or preview context\n if (\n this.closest(\"ef-preview\") !== null ||\n this.closest(\"ef-workbench\") !== null ||\n this.closest(\"ef-preview-context\") !== null\n ) {\n return false;\n }\n\n // Skip wrapping in test contexts\n if (this.closest(\"test-context\") !== null) {\n return false;\n }\n\n // Never wrap render clones (they're in an offscreen container for capture operations)\n if (this.closest(\".ef-render-clone-container\") !== null) {\n return false;\n }\n\n // During rendering, always wrap with workbench (needed by EF_FRAMEGEN)\n const isRendering = EF_RENDERING?.() === true;\n if (isRendering) {\n return true;\n }\n\n // Respect the explicit workbench property\n return this.workbench;\n }\n\n /** @internal */\n wrapWithWorkbench() {\n const workbench = document.createElement(\"ef-workbench\");\n const parent = this.parentElement;\n \n // Only apply viewport sizing when the workbench will be a direct child of body.\n // This ensures the workbench fills the screen regardless of the document's CSS\n // setup (e.g., missing `html, body { height: 100% }`).\n // When embedded in another container, use default sizing and let the container\n // control the workbench dimensions.\n if (parent === document.body) {\n workbench.style.position = \"fixed\";\n workbench.style.top = \"0\";\n workbench.style.left = \"0\";\n workbench.style.width = \"100vw\";\n workbench.style.height = \"100vh\";\n workbench.style.zIndex = \"0\";\n }\n \n parent?.append(workbench);\n if (!this.hasAttribute(\"id\")) {\n this.setAttribute(\"id\", \"root-timegroup\");\n }\n\n // Create pan-zoom for selection overlay support\n // Must be in light DOM so canvas can find it via closest()\n const panZoom = document.createElement(\"ef-pan-zoom\");\n panZoom.id = \"workbench-panzoom\";\n panZoom.setAttribute(\"slot\", \"canvas\");\n panZoom.setAttribute(\"auto-fit\", \"\"); // Fit content to view on first render\n panZoom.style.width = \"100%\";\n panZoom.style.height = \"100%\";\n\n // Create canvas wrapper for selection/highlighting support\n // Get dimensions from the timegroup for explicit canvas sizing\n const rect = this.getBoundingClientRect();\n const canvas = document.createElement(\"ef-canvas\");\n canvas.id = \"workbench-canvas\";\n // Use explicit dimensions (required for selection bounds calculation)\n canvas.style.width = `${Math.max(rect.width, 1920)}px`;\n canvas.style.height = `${Math.max(rect.height, 1080)}px`;\n canvas.style.display = \"block\";\n\n // Move timegroup into canvas, canvas into pan-zoom\n canvas.append(this as unknown as Element);\n panZoom.append(canvas);\n workbench.append(panZoom);\n\n // Add hierarchy panel - targets canvas for selection support\n const hierarchy = document.createElement(\"ef-hierarchy\");\n hierarchy.setAttribute(\"slot\", \"hierarchy\");\n hierarchy.setAttribute(\"target\", \"workbench-canvas\");\n hierarchy.setAttribute(\"header\", \"Scenes\");\n workbench.append(hierarchy);\n\n // Add filmstrip/timeline - targets timegroup for playback\n const filmstrip = document.createElement(\"ef-filmstrip\");\n filmstrip.setAttribute(\"slot\", \"timeline\");\n filmstrip.setAttribute(\"target\", this.id);\n workbench.append(filmstrip);\n }\n\n get #efElements() {\n return Array.from(\n this.querySelectorAll(\n \"ef-audio, ef-video, ef-image, ef-captions, ef-waveform\",\n ),\n );\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n * @internal\n */\n getMediaElements(): EFMedia[] {\n return deepGetMediaElements(this);\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n * @internal\n */\n async renderAudio(fromMs: number, toMs: number, signal?: AbortSignal): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs, signal);\n }\n\n /**\n * TEMPORARY TEST METHOD: Renders audio and immediately plays it back\n * Usage: timegroup.testPlayAudio(0, 5000) // Play first 5 seconds\n */\n async #testPlayAudio(fromMs: number, toMs: number) {\n // Render the audio using the existing renderAudio method\n const renderedBuffer = await this.renderAudio(fromMs, toMs);\n\n // Create a regular AudioContext for playback\n const playbackContext = new AudioContext();\n\n // Create a buffer source and connect it\n const bufferSource = playbackContext.createBufferSource();\n bufferSource.buffer = renderedBuffer;\n bufferSource.connect(playbackContext.destination);\n\n // Start playback immediately\n bufferSource.start(0);\n\n // Return a promise that resolves when playback ends\n return new Promise<void>((resolve) => {\n bufferSource.onended = () => {\n playbackContext.close();\n resolve();\n };\n });\n }\n\n async #loadMd5Sums() {\n const efElements = this.#efElements;\n const loaderTasks: Promise<any>[] = [];\n for (const el of efElements) {\n const md5SumLoader = (el as any).md5SumLoader;\n // Check for any object with run() and taskComplete\n if (md5SumLoader && typeof md5SumLoader.run === 'function') {\n // Attach .catch() to prevent unhandled rejection warning - errors handled via taskComplete\n md5SumLoader.run().catch(() => {});\n if (md5SumLoader.taskComplete) {\n loaderTasks.push(md5SumLoader.taskComplete);\n }\n }\n }\n\n await Promise.all(loaderTasks);\n\n efElements.forEach((el) => {\n if (\"productionSrc\" in el && el.productionSrc instanceof Function) {\n el.setAttribute(\"src\", el.productionSrc());\n }\n });\n }\n\n // Track frameTask execution count to detect runaway loops\n #timegroupFrameTaskCount = 0;\n #timegroupFrameTaskLastReset = Date.now();\n static readonly TIMEGROUP_FRAME_TASK_THRESHOLD = 100;\n static readonly TIMEGROUP_FRAME_TASK_RESET_MS = 1000;\n\n /** @internal */\n #frameTaskPromise: Promise<void> = Promise.resolve();\n #frameTaskAbortController: AbortController | null = null;\n \n frameTask = (() => {\n const self = this;\n const taskObj: { run(): void | Promise<void>; taskComplete: Promise<void> } = {\n run: () => {\n // Abort any in-flight task\n self.#frameTaskAbortController?.abort();\n self.#frameTaskAbortController = new AbortController();\n const signal = self.#frameTaskAbortController.signal;\n \n self.#frameTaskPromise = self.#runFrameTask(signal);\n taskObj.taskComplete = self.#frameTaskPromise;\n return self.#frameTaskPromise;\n },\n taskComplete: Promise.resolve(),\n };\n return taskObj;\n })();\n\n async #runFrameTask(signal: AbortSignal): Promise<void> {\n // Check for runaway loop\n const now = Date.now();\n if (now - this.#timegroupFrameTaskLastReset > EFTimegroup.TIMEGROUP_FRAME_TASK_RESET_MS) {\n this.#timegroupFrameTaskCount = 0;\n this.#timegroupFrameTaskLastReset = now;\n }\n this.#timegroupFrameTaskCount++;\n \n if (this.#timegroupFrameTaskCount > EFTimegroup.TIMEGROUP_FRAME_TASK_THRESHOLD) {\n // Safety break to prevent infinite loops\n return;\n }\n \n try {\n signal.throwIfAborted();\n \n if (this.isRootTimegroup) {\n // Root timegroup orchestrates frame rendering for entire tree\n // Use FrameController for centralized, unified rendering\n await withSpan(\n \"timegroup.frameTask\",\n {\n timegroupId: this.id || \"unknown\",\n ownCurrentTimeMs: this.ownCurrentTimeMs,\n currentTimeMs: this.currentTimeMs,\n },\n undefined,\n async () => {\n // Use FrameController for centralized element coordination\n // This replaces waitForFrameTasks() with the new unified rendering path\n await this.#frameController.renderFrame(this.currentTimeMs, {\n waitForLitUpdate: false, // Already in an update cycle\n onAnimationsUpdate: (root) => {\n updateAnimations(root as typeof this);\n },\n });\n signal.throwIfAborted();\n // Execute custom frame tasks registered on this timegroup\n await this.#executeCustomFrameTasks();\n },\n );\n } else {\n // Non-root timegroups execute their custom frame tasks when called\n await this.#executeCustomFrameTasks();\n }\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n return;\n }\n console.error(\"EFTimegroup frameTask error\", error);\n }\n }\n\n async #executeCustomFrameTasks() {\n if (this.#customFrameTasks.size > 0) {\n const percentComplete =\n this.durationMs > 0 ? this.ownCurrentTimeMs / this.durationMs : 0;\n const frameInfo = {\n ownCurrentTimeMs: this.ownCurrentTimeMs,\n currentTimeMs: this.currentTimeMs,\n durationMs: this.durationMs,\n percentComplete,\n element: this,\n };\n\n await Promise.all(\n Array.from(this.#customFrameTasks).map((callback) =>\n Promise.resolve(callback(frameInfo)),\n ),\n );\n }\n }\n\n /** @internal */\n #seekTaskPromise: Promise<number | undefined> = Promise.resolve(undefined);\n #seekTaskAbortController: AbortController | null = null;\n \n seekTask = (() => {\n const self = this;\n const taskObj: { run(): void | Promise<number | undefined>; taskComplete: Promise<number | undefined> } = {\n run: () => {\n // Abort any in-flight task\n self.#seekTaskAbortController?.abort();\n self.#seekTaskAbortController = new AbortController();\n const signal = self.#seekTaskAbortController.signal;\n \n const targetTime = self.#pendingSeekTime ?? self.#currentTime;\n self.#seekTaskPromise = self.#runSeekTask(targetTime, signal);\n taskObj.taskComplete = self.#seekTaskPromise;\n return self.#seekTaskPromise;\n },\n taskComplete: Promise.resolve(undefined),\n };\n return taskObj;\n })();\n\n async #runSeekTask(targetTime: number | undefined, signal: AbortSignal): Promise<number | undefined> {\n try {\n signal.throwIfAborted();\n \n // Delegate to playbackController if available\n if (this.playbackController) {\n // Wait for playbackController's seek to complete\n await this.playbackController.currentTime; // Trigger seek\n signal.throwIfAborted();\n return this.currentTime;\n }\n\n // Only root timegroups execute seek tasks\n if (!this.isRootTimegroup) {\n return undefined;\n }\n\n return await withSpan(\n \"timegroup.seekTask\",\n {\n timegroupId: this.id || \"unknown\",\n targetTime: targetTime ?? 0,\n durationMs: this.durationMs,\n },\n undefined,\n async (span) => {\n // Wait for media durations to be loaded\n try {\n await Promise.race([\n this.waitForMediaDurations(signal),\n new Promise<void>((_, reject) => {\n if (signal.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(() => reject(new Error('waitForMediaDurations timeout')), 10000);\n signal.addEventListener('abort', () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }, { once: true });\n })\n ]);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Continue with seek even if durations aren't loaded yet\n }\n\n signal.throwIfAborted();\n\n // Evaluate and apply seek target\n const newTime = evaluateSeekTarget(\n targetTime ?? 0,\n this.durationMs,\n this.effectiveFps,\n );\n if (isTracingEnabled()) {\n span.setAttribute(\"newTime\", newTime);\n }\n\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n \n await this.updateComplete;\n signal.throwIfAborted();\n\n await this.#runThrottledFrameTask();\n signal.throwIfAborted();\n\n if (!this.#restoringFromLocalStorage) {\n this.saveTimeToLocalStorage(this.#currentTime);\n }\n this.#seekInProgress = false;\n if (this.#restoringFromLocalStorage) {\n this.#restoringFromLocalStorage = false;\n }\n return newTime;\n },\n );\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n return undefined;\n }\n console.error(\"EFTimegroup seekTask error\", error);\n return undefined;\n }\n }\n\n /**\n * Get container information for this timegroup.\n * Timegroups are always containers and can contain children.\n * Display mode is determined from computed styles.\n *\n * @public\n */\n getContainerInfo(): ContainerInfo {\n const info = getContainerInfoFromElement(this);\n // Timegroups are always containers and can contain children\n return {\n ...info,\n isContainer: true,\n canContainChildren: true,\n };\n }\n\n /**\n * Get position information for this timegroup.\n * Returns computed bounds, transform, and rotation.\n *\n * @public\n */\n getPositionInfo(): ElementPositionInfo | null {\n return getPositionInfoFromElement(this);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timegroup\": EFTimegroup & Element;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2DA,MAAM,MAAM,MAAM,0BAA0B;AAmC5C,MAAM,iCAAiC;AACvC,MAAM,gCAAgC;AA6BtC,IAAIA,gCAA8C,IAAI,SAAS;AAE/D,MAAa,2BAA2B;AACtC,iCAAgB,IAAI,SAAS;;AAI/B,MAAa,6BAA6B;AAG1C,MAAM,gDAAgC,IAAI,SAAsB;AAIhE,MAAa,kCACX,cACY;AACZ,QACE,cAAc,UAAa,8BAA8B,IAAI,UAAU;;AAO3E,uCAAuC,+BAA+B;;;;;AAMtE,SAAS,sBACP,MACA,qBACS;AACT,QACE,SAAS,aACT,SAAS,cACR,SAAS,WAAW;;;;;;;;;AAWzB,SAAS,uCACP,OACS;AAET,KAAI,iBAAiB,eAAe,MAAM,SAAS,MACjD,QAAO;AAGT,KAAI,CAAC,MAAM,eACT,QAAO;AAET,QAAO;;;;;;AAOT,SAAS,oBAAoB,iBAAkD;AAC7E,KAAI,CAAC,gBACH,QAAO;AAET,QAAO,gBAAgB;;;;;;;AAQzB,SAAS,yBACP,WACA,gBACA,WACQ;CAER,MAAM,iBAAiB,cAAc,IAAI,UAAU;AACnD,KAAI,mBAAmB,OACrB,QAAO;CAGT,IAAI,WAAW;CACf,IAAI,qBAAqB;AACzB,gBAAe,SAAS,UAAU;AAChC,MAAI,CAAC,uCAAuC,MAAM,CAChD;AAGF,MACE,iBAAiB,eACjB,8BAA8B,IAAI,MAAM,CAExC;AAOF,MAAI,iBAAiB,aAAa;GAChC,IAAIC,WAAwB,MAAM;GAClC,IAAI,aAAa;AACjB,UAAO,UAAU;AAEf,QAAI,aAAa,UACf;AAEF,QACE,oBAAoB,eACpB,8BAA8B,IAAI,SAAS,EAC3C;AAEA,kBAAa;AACb;;AAEF,eAAW,SAAS;;AAEtB,OAAI,WACF;;AAKJ,MAAI,qBAAqB,EACvB,aAAY;AAEd,cAAY,MAAM;AAClB;GACA;AAGF,YAAW,KAAK,IAAI,GAAG,SAAS;AAGhC,eAAc,IAAI,WAAW,SAAS;AACtC,QAAO;;;;;;;AAQT,SAAS,wBACP,WACA,gBACQ;CAER,MAAM,iBAAiB,cAAc,IAAI,UAAU;AACnD,KAAI,mBAAmB,OACrB,QAAO;CAGT,IAAI,cAAc;AAClB,MAAK,MAAM,SAAS,gBAAgB;AAClC,MAAI,CAAC,uCAAuC,MAAM,CAChD;AAMF,MACE,iBAAiB,eACjB,8BAA8B,IAAI,MAAM,CAExC;AAOF,MAAI,iBAAiB,aAAa;GAChC,IAAIA,WAAwB,MAAM;GAClC,IAAI,aAAa;AACjB,UAAO,UAAU;AAEf,QAAI,aAAa,UACf;AAEF,QACE,oBAAoB,eACpB,8BAA8B,IAAI,SAAS,EAC3C;AAEA,kBAAa;AACb;;AAEF,eAAW,SAAS;;AAEtB,OAAI,WACF;;AAIJ,gBAAc,KAAK,IAAI,aAAa,MAAM,WAAW;;CAGvD,MAAM,WAAW,KAAK,IAAI,GAAG,YAAY;AAGzC,eAAc,IAAI,WAAW,SAAS;AACtC,QAAO;;;;;;;;;AAUT,SAAS,wBACP,WACA,MACA,gBACQ;AACR,SAAQ,MAAR;EACE,KAAK,MACH,QAAO,oBAAoB,UAAU,gBAAgB;EACvD,KAAK;AAEH,iCAA8B,IAAI,UAAU;AAC5C,OAAI;AACF,WAAO,yBACL,WACA,gBACA,UAAU,UACX;aACO;AAER,kCAA8B,OAAO,UAAU;;EAGnD,KAAK;AAEH,iCAA8B,IAAI,UAAU;AAC5C,OAAI;AACF,WAAO,wBAAwB,WAAW,eAAe;aACjD;AAER,kCAA8B,OAAO,UAAU;;EAGnD,QACE,OAAM,IAAI,MAAM,sBAAsB,OAAO;;;AAInD,MAAa,wBACX,SACA,SAAwB,EAAE,KACvB;AACH,MAAK,MAAM,SAAS,MAAM,KAAK,QAAQ,SAAS,CAC9C,KAAI,iBAAiB,YACnB,QAAO,KAAK,MAAM;KAElB,sBAAqB,OAAO,OAAO;AAGvC,QAAO;;;;;;AA8CT,SAAS,mBACP,eACA,YACA,KACQ;CAER,MAAM,gBAAgB,qBAAqB,eAAe,IAAI;AAE9D,QAAO,KAAK,IAAI,GAAG,KAAK,IAAI,eAAe,aAAa,IAAK,CAAC;;AAIzD,wBAAMC,sBAAoB,aAAa,WAAW,QAAQ,WAAW,CAAC,CAAC,CAAC;;;;;;2BAkCzD;mBAIR;cAGK;mBAEL;aA4BN;kBAQK;mBASC;aA2BwB;0BAkmDjB;GACjB,MAAM,OAAO;GACb,MAAMC,UAAwE;IAC5E,WAAW;AAET,WAAKC,0BAA2B,OAAO;AACvC,WAAKA,2BAA4B,IAAI,iBAAiB;KACtD,MAAM,SAAS,MAAKA,yBAA0B;AAE9C,WAAKC,mBAAoB,MAAKC,aAAc,OAAO;AACnD,aAAQ,eAAe,MAAKD;AAC5B,YAAO,MAAKA;;IAEd,cAAc,QAAQ,SAAS;IAChC;AACD,UAAO;MACL;yBAgFc;GAChB,MAAM,OAAO;GACb,MAAME,UAAoG;IACxG,WAAW;AAET,WAAKC,yBAA0B,OAAO;AACtC,WAAKA,0BAA2B,IAAI,iBAAiB;KACrD,MAAM,SAAS,MAAKA,wBAAyB;KAE7C,MAAM,aAAa,MAAKC,mBAAoB,MAAKC;AACjD,WAAKC,kBAAmB,MAAKC,YAAa,YAAY,OAAO;AAC7D,aAAQ,eAAe,MAAKD;AAC5B,YAAO,MAAKA;;IAEd,cAAc,QAAQ,QAAQ,OAAU;IACzC;AACD,UAAO;MACL;;CAr0DJ,WAAW,qBAA+B;AAExC,SAAO;GACL,GAFuB,MAAM,sBAAsB,EAAE;GAGrD;GACA;GACA;GACA;GACA;GACA;GACA;GACD;;;gBAGa,GAAG;;;;;;;;;;;;;;;;;CA2EnB,yBACE,MACA,KACA,OACM;AACN,MAAI,SAAS,UAAU,MACrB,MAAK,OAAO;AAEd,MAAI,SAAS,aAAa,MACxB,MAAK,YAAY,cAAc,MAAM;AAEvC,MAAI,SAAS,YACX,MAAK,WAAW,UAAU;AAE5B,MAAI,SAAS,SAAS,MACpB,MAAK,MAAM,OAAO,WAAW,MAAM;AAErC,MAAI,SAAS,YACX,MAAK,YAAY,UAAU;AAE7B,QAAM,yBAAyB,MAAM,KAAK,MAAM;;CAOlD;;CAGA,gBAAgB;CAEhB,eAAmC;CACnC,cAAsB;CACtB,kBAAkB;CAClB;CACA,yBAAyB;CACzB,6BAA6B;;CAG7B,8BAAuC;AACrC,SAAO,MAAKE;;;CAId,6BAA6B,OAAsB;AACjD,QAAKA,4BAA6B;;CAEpC,oCAA4C,IAAI,KAAK;CACrD,mBAA6C;CAC7C,kBAAuC;CACvC,oBAA6E;;;;;CAM7E,mBAAoC,IAAI,gBAAgB,KAAK;;;;;CAM7D,IAAI,kBAAmC;AACrC,SAAO,MAAKC;;;;;;;;CASd,IAAI,eAAuB;AAEzB,MAAI,OAAO,WAAW,eAAe,OAAO,aAAa,cACvD,QAAO,OAAO,YAAY,cAAc,eAAe,MAAM;AAE/D,SAAO,KAAK;;;;;;;CAQd,IAAI,eAAuB;AACzB,SAAO,MAAKC;;;;;;;CAQd,wBAA8B;AAC5B,QAAKA;;CAGP,OAAMC,wBAAwC;AAC5C,MAAI,KAAK,mBACP,QAAO,KAAK,mBAAmB,uBAAuB;AAExD,QAAM,KAAK,UAAU,KAAK;;;CAQ5B,IACI,YAAY,MAAc;EAE5B,MAAM,aAAa,mBACjB,MACA,KAAK,YACL,KAAK,aACN;AAGD,MAAI,KAAK,oBAAoB;AAC3B,QAAK,mBAAmB,cAAc;AACtC,SAAKC,aAAc,aAAa;AAChC;;AAIF,MAAI,CAAC,KAAK,gBACR;AAIF,MAAI,OAAO,MAAM,WAAW,CAC1B;AAIF,MAAI,eAAe,MAAKP,eAAgB,CAAC,MAAKQ,yBAA0B,CAAC,MAAKL,0BAC5E;AAIF,MAAI,MAAKJ,oBAAqB,WAC5B;AAIF,MAAI,MAAKI,6BAA8B,eAAe,MAAKH,aAAc;AASzE,MAAI,MAAKS,gBAAiB;AACxB,SAAKV,kBAAmB;AACxB,SAAKC,cAAe;AACpB,SAAKO,aAAc,aAAa;AAChC;;AAIF,QAAKP,cAAe;AACpB,QAAKO,aAAc,aAAa;AAChC,QAAKE,iBAAkB;AAGvB,OAAK,SAAS,KAAK,CAAC,YAAY,GAAG,CAAC,cAAc;AAChD,SAAKA,iBAAkB;AAIvB,OACE,MAAKV,oBAAqB,UAC1B,MAAKA,oBAAqB,YAC1B;IACA,MAAM,cAAc,MAAKA;AACzB,UAAKA,kBAAmB;AACxB,UAAKS,wBAAyB;AAC9B,QAAI;AACF,UAAK,cAAc;cACX;AACR,WAAKA,wBAAyB;;SAGhC,OAAKT,kBAAmB;IAE1B;;;CAIJ,IAAI,cAAc;AAChB,MAAI,KAAK,mBACP,QAAO,KAAK,mBAAmB;AAEjC,SAAO,MAAKC,eAAgB;;;CAI9B,IAAI,cAAc,IAAY;AAC5B,OAAK,cAAc,KAAK;;;CAI1B,IAAI,gBAAgB;AAClB,SAAO,KAAK,cAAc;;;;;;;;CAS5B,IAAI,aAAqB;AACvB,SAAO,MAAKO;;;;;;;;;;;;;;;CAgBd,MAAM,KAAK,QAA+B;AAExC,QAAKA,aAAc;AAGnB,OAAK,gBAAgB;AACrB,QAAM,KAAK,SAAS;AAGpB,MAAI,KAAK,mBACP,MAAK,uBAAuB,KAAK,YAAY;AAI/C,QAAM,KAAK,UAAU;EAOrB,MAAM,kBAAkB,MAAKG,iCAAkC;AAE/D,QAAM,QAAQ,IACZ,gBAAgB,IAAI,OAAO,YAAY;AACrC,OACE,uBAAuB,WACvB,OAAO,QAAQ,sBAAsB,WAErC,OAAO,QAAgB,mBAAmB;OAE1C,OAAM,QAAQ;IAEhB,CACH;;;;;;;;;;;;;;CAeH,MAAM,cAAc,QAA+B;EAEjD,MAAM,UAAU,SAAS;AACzB,QAAKH,aAAc;AACnB,QAAKP,cAAe;AACpB,OAAK,cAAc,cAAc;AAGjC,QAAM,KAAK;EAIX,MAAM,iBAAiB,MAAKW,6BAA8B;AAI1D,QAAM,QAAQ,IAAI,eAAe,KAAK,OAAO,GAAG,eAAe,CAAC;EAIhE,MAAM,eAAe,eAAe,QAAQ,OAAO,GAAG,YAAY,UAAU;AAC5E,MAAI,aAAa,SAAS,EACxB,OAAM,QAAQ,IACZ,aAAa,KAAK,OAAO;AACvB,OAAI,uBAAuB,MAAM,OAAO,GAAG,sBAAsB,WAC/D,QAAQ,GAAW,mBAAmB;AAExC,UAAO,QAAQ,SAAS;IACxB,CACH;AAMH,QAAM,MAAKP,gBAAiB,YAAY,QAAQ;GAC9C,kBAAkB;GAClB,qBAAqB,SAAS;AAC5B,qBAAiB,KAAoB;AAIrC,IAAM,KAAqB;;GAE9B,CAAC;AAGF,QAAM,MAAKQ,yBAA0B;;;;;;;CAQvC,+BAA6C;EAC3C,MAAMC,SAAuB,EAAE;EAC/B,MAAM,gBAAgB,KAAK;EAC3B,IAAI,wBAAwB;EAC5B,IAAI,wBAAwB;EAC5B,IAAI,mBAAmB;EAEvB,MAAM,QAAQ,OAAgB;AAE5B,OAAI,cAAc,eAAe,GAAG,MAAM,YAAY,QAAQ;AAC5D;AACA;;AAIF,OAAI,cAAc,aAAa;IAC7B,MAAM,QAAQ,iBAAiB,GAAG;AAClC,QAAI,MAAM,YAAY,UAAU,MAAM,eAAe,UAAU;AAC7D;AACA;;;AAIJ,QAAK,MAAM,SAAS,GAAG,UAAU;AAC/B,QAAI,iBAAiB,YAAY;AAC/B;AAEA,SAAI,iBAAiB,SAAS,eAAe,OAAO;MAClD,MAAM,UAAW,MAAc,eAAe;MAC9C,MAAM,QAAS,MAAc,aAAa;AAC1C,UAAI,iBAAiB,WAAW,iBAAiB,MAC/C,QAAO,KAAK,MAAM;UAElB;WAIF,QAAO,KAAK,MAAM;;AAGtB,SAAK,MAAM;;;AAGf,OAAK,KAAK;AAEV,SAAO;;;;;;CAOT,IAAI,kBAA2B;AAC7B,SAAO,CAAC,KAAK;;;;;;;;;;;;;;;;CAiBf,IAAI,UAAoC;AACtC,SAAO,MAAKC;;CAGd,IAAI,QAAQ,UAAgD;AAE1D,MAAI,MAAKC,gBAAiB;AACxB,SAAKA,gBAAiB;AACtB,SAAKA,iBAAkB;;AAEzB,QAAKD,kBAAmB,YAAY;AAGpC,MAAI,SACF,OAAKC,iBAAkB,KAAK,aAAa,SAAS;;;;;;;;;;;CAatD,aAAa,UAAyC;AACpD,MAAI,OAAO,aAAa,WACtB,OAAM,IAAI,MAAM,yCAAyC;AAE3D,QAAKC,iBAAkB,IAAI,SAAS;AACpC,eAAa;AACX,SAAKA,iBAAkB,OAAO,SAAS;;;;;;;;;CAU3C,gBAAgB,UAAmC;AACjD,QAAKA,iBAAkB,OAAO,SAAS;;;CAIzC,uBAAuB,MAAc;AACnC,MAAI;AACF,OAAI,KAAK,MAAM,KAAK,eAAe,CAAC,OAAO,MAAM,KAAK,CACpD,cAAa,QAAQ,KAAK,YAAY,KAAK,UAAU,CAAC;WAEjD,OAAO;AACd,OAAI,uCAAuC,MAAM;;;CAIrD,SAAS;AACP,SAAO,IAAI,qBAAqB,MAAKC,iBAAkB;;CAGzD,0BAA0B;AAExB,sBAAoB;AACpB,8BAA4B;AAC5B,yBAAuB;AAGvB,OAAK,eAAe;;;CAItB,2BAA+C;AAC7C,MAAI,KAAK,GACP,KAAI;GACF,MAAM,cAAc,aAAa,QAAQ,KAAK,WAAW;AACzD,OAAI,gBAAgB,KAClB;GAEF,MAAM,cAAc,OAAO,WAAW,YAAY;AAElD,OAAI,OAAO,MAAM,YAAY,IAAI,CAAC,OAAO,SAAS,YAAY,CAC5D;AAEF,UAAO;WACA,OAAO;AACd,OAAI,yCAAyC,MAAM;;;CAMzD,oBAAoB;AAgBlB,QAAM,mBAAmB;AAKzB,8BAA4B;AAC1B,+BAA4B;AAC1B,QAAI,KAAK,gBACP,KAAI,oBAAoB,KAAK,iBAAiB,KAAK;AAIrD,QAAI,KAAK,yBAAyB,CAChC,MAAK,mBAAmB;KAE1B;IACF;;;;;;;CAQJ,gBAAgB;AACd,QAAM,eAAe;AACrB,QAAKC,uBAAwB;;;;;CAM/B,yBAA+B;AAE7B,MAAI,MAAKC,oBAAqB,CAAC,KAAK,mBAAoB;AAExD,QAAKA,oBAAqB,UAAyC;AAGjE,OAAI,MAAM,aAAa,mBAAmB,OAAO,MAAM,UAAU,UAC/D;QAAI,KAAK,QACP,OAAKZ,aAAc,MAAM;;;AAK/B,OAAK,mBAAmB,YAAY,MAAKY,iBAAkB;;;;;CAM7D,0BAAgC;AAC9B,MAAI,MAAKA,oBAAqB,KAAK,mBACjC,MAAK,mBAAmB,eAAe,MAAKA,iBAAkB;AAEhE,QAAKA,mBAAoB;;CAG3B,sBAAsB;CAEtB,AAAU,QAAQ,mBAAyC;AACzD,QAAM,QAAQ,kBAAkB;AAEhC,MAAI,kBAAkB,IAAI,OAAO,IAAI,kBAAkB,IAAI,YAAY,CACrE,eAAc,OAAO,KAAK;AAG5B,MAAI,MAAKC,uBAAwB,KAAK,YAAY;AAChD,SAAKA,qBAAsB,KAAK;AAChC,SAAKd,uBAAwB;;;CAIjC,uBAAuB;AACrB,QAAM,sBAAsB;AAC5B,QAAKe,gBAAiB,YAAY;AAClC,QAAKC,wBAAyB;;;;;;;;;;CAWhC,MAAM,cAAc,SAAqD;AACvE,SAAO,uBAAuB,MAAM,QAAQ;;;;;;;;;;;;;;CAe9C,MAAM,aACJ,YACA,UAA+B,EAAE,EACH;AAC9B,MAAI,WAAW,WAAW,EAAG,QAAO,EAAE;EAEtC,MAAM,EACJ,QAAQ,KACR,mBAAmB,aACnB,oBAAoB,QAClB;EAEJ,MAAM,iBAAiB,YAAY,KAAK;EAGxC,MAAM,iBAAiB,YAAY,KAAK;EACxC,MAAM,EAAE,OAAO,aAAa,WAAW,iBAAiB,SAAS,uBAC/D,MAAM,KAAK,mBAAmB;EAChC,MAAM,YAAY,YAAY,KAAK,GAAG;EAGtC,MAAM,oBAAoB,YAAY,KAAK;EAC3C,MAAM,gBAAgB,YAAY,iBAAiB,WAAW;AAC9D,MAAI,cAAc,SAAS,EACzB,OAAM,QAAQ,IACZ,MAAM,KAAK,cAAc,CAAC,KAAK,UAC5B,MAAyC,sBAAsB,WAAW,CAC5E,CACF;EAEH,MAAM,eAAe,YAAY,KAAK,GAAG;EAEzC,MAAMC,WAAgC,EAAE;EACxC,IAAI,gBAAgB;EACpB,IAAI,mBAAmB;AAEvB,MAAI;AACF,QAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;IAC1C,MAAM,SAAS,WAAW;IAI1B,MAAM,YAAY,YAAY,KAAK;AACnC,UAAM,YAAY,cAAc,OAAO;AACvC,qBAAiB,YAAY,KAAK,GAAG;IAGrC,MAAM,eAAe,YAAY,KAAK;IACtC,MAAM,SAAS,MAAM,iBAAiB,aAAa,iBAAiB;KAClE;KACA;KACA;KACA,mBAAmB;KACpB,CAAC;AACF,wBAAoB,YAAY,KAAK,GAAG;AACxC,aAAS,KAAK,OAAO;;AAGvB,UAAO;YACC;GAER,MAAM,YAAY,YAAY,KAAK,GAAG;AACtC,WAAQ,IAAI,kBAAkB,WAAW,OAAO,iBAAiB,UAAU,QAAQ,EAAE,CAAC,eAAe,aAAa,QAAQ,EAAE,CAAC,WAAW,cAAc,QAAQ,EAAE,CAAC,cAAc,iBAAiB,QAAQ,EAAE,CAAC,YAAY,UAAU,QAAQ,EAAE,CAAC,IAAI;AAChP,uBAAoB;;;;;;;;;;;;CAaxB,MAAM,cAAc,SAAiE;AACnF,SAAO,uBAAuB,MAAM,QAAQ;;;;;;;;;CAU9C,gBAAgB,SAA4B;AAC1C,MAAI,CAAC,KAAK,YACR;EAGF,MAAM,YAAY,YAAY,KAAK;EACnC,MAAMC,SAAkB,KAAK,YAAY,QAAQ;EACjD,MAAM,UAAU,YAAY,KAAK,GAAG;AAGpC,MAAI,WAAW,UAAa,WAAW,QAAQ,OAAQ,OAAe,SAAS,WAC7E,OAAM,IAAI,MACR,mGAED;AAIH,MAAI,UAAU,+BACZ,OAAM,IAAI,MACR,6BAA6B,QAAQ,QAAQ,EAAE,CAAC,oBAAoB,+BAA+B,oFAEpG;AAGH,MAAI,UAAU,8BACZ,SAAQ,KACN,mCAAmC,QAAQ,QAAQ,EAAE,CAAC,gBAAgB,8BAA8B,wDAErG;;;;;;;;;CAWL,kBAAkB,UAAmB,OAAsB;EAEzD,MAAM,mBAAmB,SAAS,iBAAiB,cAAc;EACjE,MAAM,gBAAgB,MAAM,iBAAiB,cAAc;AAE3D,OAAK,IAAI,IAAI,GAAG,IAAI,iBAAiB,UAAU,IAAI,cAAc,QAAQ,KAAK;GAC5E,MAAM,UAAU,iBAAiB;GACjC,MAAM,WAAW,cAAc;AAG/B,OAAI,QAAQ,aACV,UAAS,eAAe,QAAQ;;;;;;;;;;CAYtC,iBAAiB,UAAmB,OAAsB;EACxD,MAAM,gBAAgB,SAAS,iBAAiB,UAAU;EAC1D,MAAM,aAAa,MAAM,iBAAiB,UAAU;AAEpD,OAAK,IAAI,IAAI,GAAG,IAAI,cAAc,UAAU,IAAI,WAAW,QAAQ,KAAK;GACtE,MAAM,WAAW,cAAc;GAC/B,MAAM,YAAY,WAAW;AAI7B,OAAI,SAAS,iBAAiB,OAC5B,WAAU,eAAe,SAAS;AAGpC,OAAI,SAAS,qBAAqB,OAChC,WAAU,mBAAmB,SAAS;;;;;;;;;CAW5C,qBAAqB,UAAmB,OAAsB;EAE5D,MAAM,mBAAmB,SAAS,iBAAiB,kBAAkB;EACrE,MAAM,gBAAgB,MAAM,iBAAiB,kBAAkB;AAE/D,OAAK,IAAI,IAAI,GAAG,IAAI,iBAAiB,UAAU,IAAI,cAAc,QAAQ,KAAK;GAC5E,MAAM,UAAU,iBAAiB;GACjC,MAAM,WAAW,cAAc;AAG/B,OAAI,QAAQ,gBAAgB,OAC1B,UAAS,cAAc,QAAQ;AAEjC,OAAI,QAAQ,iBAAiB,OAC3B,UAAS,eAAe,QAAQ;AAElC,OAAI,QAAQ,oBAAoB,OAC9B,UAAS,kBAAkB,QAAQ;AAErC,OAAI,QAAQ,mBAAmB,OAC7B,UAAS,iBAAiB,QAAQ;AAEpC,OAAI,QAAQ,iBAAiB,OAC3B,UAAS,eAAe,QAAQ;;;;;;;;;CAWtC,OAAMC,oBAAqB,MAA8B;EAEvD,MAAM,mBAAmB,KAAK,iBAAiB,cAAc;AAC7D,MAAI,iBAAiB,WAAW,EAAG;EAInC,MAAMC,eAAmC,EAAE;AAC3C,OAAK,MAAM,MAAM,kBAAkB;GACjC,MAAM,WAAW;AAEjB,OAAI,OAAO,SAAS,qBAAqB,WACvC,cAAa,KAAK,SAAS,kBAAkB,CAAC,YAAY,GAAG,CAAC;YAGvD,SAAS,yBAAyB,aACzC,cAAa,KAAK,SAAS,wBAAwB,aAAa,YAAY,GAAG,CAAC;;AAIpF,MAAI,aAAa,SAAS,EACxB,OAAM,QAAQ,IAAI,aAAa;;;;;;;;;;;;;;;;;;;CAqBnC,MAAM,oBAAgD;EAMpD,MAAM,YAAY,SAAS,cAAc,MAAM;AAC/C,YAAU,YAAY;AACtB,YAAU,MAAM,UAAU;;;;eAIf,KAAK,eAAe,KAAK;gBACxB,KAAK,gBAAgB,KAAK;;;;EAMtC,MAAM,UAAU,KAAK,UAAU,KAAK;AAKpC,UAAQ,gBAAgB,KAAK;AAG7B,QAAKC,iBAAkB,MAAM,QAAQ;AAKrC,QAAKC,gBAAiB,MAAM,QAAQ;EAKpC,MAAM,iBAAiB,KAAK,QAAQ,mBAAmB;AACvD,MAAI,gBAAgB;GAElB,MAAM,cAAc,eAAe,UAAU,MAAM;AACnD,eAAY,YAAY,QAAQ;AAChC,aAAU,YAAY,YAAY;QAElC,WAAU,YAAY,QAAQ;AAGhC,WAAS,KAAK,YAAY,UAAU;AAGpC,QAAM,QAAQ;AAId,QAAKC,oBAAqB,MAAM,QAAQ;AAMxC,QAAKC,eAAgB,QAAQ;EAK7B,IAAI,cAAc,UAAU,cAAc,eAAe;AACzD,MAAI,CAAC,YACH,OAAM,IAAI,MACR,mJAED;AAMH,QAAM,eAAe,YAAY,eAAe;AAGhD,iBAAe,QAAQ,UAAU;AAGjC,gBAAc,UAAU,cAAc,eAAe;AACrD,MAAI,CAAC,YACH,OAAM,IAAI,MAAM,0CAA0C;AAI5D,QAAM,YAAY;EAMlB,MAAM,iCAAiC,QAAqB,SAAsB;AAChF,QAAK,MAAM,SAAS,OAAO,SAEzB,KAAI,MAAM,YAAY,gBAAgB;IACpC,MAAM,UAAU;AAEhB,YAAQ,kBAAkB;AAC1B,YAAQ,gBAAgB;AAExB,IAAC,QAAgB,mBAAmB;AAEpC,kCAA8B,SAAS,KAAK;cAGrC,qBAAqB,SAAS,mBAAmB,OAAO;IAC/D,MAAM,WAAW;AACjB,aAAS,kBAAkB;AAC3B,aAAS,gBAAgB;AAEzB,QAAI,uBAAuB,YAAY,OAAO,SAAS,sBAAsB,WAC3E,UAAS,mBAAmB;cAIvB,iBAAiB,QACxB,0CAAyC,OAAO,QAAQ,KAAK;;EAKnE,MAAM,4CAA4C,aAAoB,iBAA8B,SAAsB;AACxH,QAAK,MAAM,SAASC,YAAU,SAC5B,KAAI,MAAM,YAAY,gBAAgB;IACpC,MAAM,UAAU;AAChB,YAAQ,kBAAkB;AAC1B,YAAQ,gBAAgB;AACxB,IAAC,QAAgB,mBAAmB;AACpC,kCAA8B,SAAS,KAAK;cACnC,qBAAqB,SAAS,mBAAmB,OAAO;IACjE,MAAM,WAAW;AACjB,aAAS,kBAAkB;AAC3B,aAAS,gBAAgB;AACzB,QAAI,uBAAuB,YAAY,OAAO,SAAS,sBAAsB,WAC3E,UAAS,mBAAmB;cAErB,iBAAiB,QAC1B,0CAAyC,OAAO,iBAAiB,KAAK;;AAO5E,cAAY,gBAAgB;AAC5B,gCAA8B,aAAa,YAAY;AAGvD,QAAM,YAAY;AAIlB,cAAY,gBAAgB;AAC5B,EAAC,YAAoB,mBAAmB;EACxC,MAAM,yBAAyB,OAAgB;AAC7C,OAAI,mBAAmB,MAAM,uBAAuB,IAAI;AACtD,IAAC,GAAW,gBAAgB;AAC5B,IAAC,GAAW,mBAAmB;;AAEjC,QAAK,MAAM,SAAS,GAAG,SACrB,uBAAsB,MAAM;;AAGhC,wBAAsB,YAAY;AAElC,QAAM,YAAY,uBAAuB;AAIzC,QAAM,MAAKN,oBAAqB,YAAY;AAM5C,MAAI,YAAY,oBAAoB;AAClC,eAAY,mBAAmB,QAAQ;AACvC,eAAY,qBAAqB;;AAInC,QAAM,YAAY,KAAK,EAAE;AAEzB,SAAO;GACL,OAAO;GACP;GACA,eAAe;AAEb,cAAU,QAAQ;IAKlB,MAAM,YAAa,YAAoB;AACvC,QAAI,UACF,sBAAqB;AACnB,eAAU,SAAS;MACnB;;GAGP;;;CAIH,IAAI,aAAa;AACf,MAAI,CAAC,KAAK,GACR,OAAM,IAAI,MAAM,iDAAiD;AAEnE,SAAO,gBAAgB,KAAK;;;CAI9B,IAAI,sBAAsB;AACxB,MAAI,KAAK,oBACP,QAAO,KAAK;;;CAMhB,IAAI,iBAAiB;AACnB,SAAO,sBAAsB,KAAK,MAAM,KAAK,oBAAoB;;;CAQnE,IAAI,aAAqB;AAEvB,MAAI,KAAK,SAAS,QAChB,QAAO,MAAM;EAKf,MAAM,2BAA2B,KAAK;AAGtC,SAAO,wBAAwB,MAAM,KAAK,MAAM,yBAAyB;;;;;;;CAY3E,mCAEE;AAEA,SADyB,8BAA8B,KAAK,CACpC,QAAQ,YAAY;AAE1C,UADuB,iCAAiC,QAAQ,CAC1C;IACtB;;;CAIJ,MAAM,kBAAkB,QAAsB;AA8E5C,SA7Ee,MAAM,SACnB,+BACA;GACE,aAAa,KAAK,MAAM;GACxB,MAAM,KAAK;GACZ,EACD,QACA,OAAO,SAAS;AAEd,WAAQ,gBAAgB;GAExB,MAAM,aAAa,YAAY,KAAK;GAEpC,MAAM,mBAAmB,8BAA8B,KAAK;AAC5D,OAAI,kBAAkB,CACpB,MAAK,aAAa,yBAAyB,iBAAiB,OAAO;AAIrE,WAAQ,gBAAgB;GAGxB,MAAM,kBAAkB,MAAKf,iCAAkC;AAC/D,OAAI,kBAAkB,CACpB,MAAK,aAAa,wBAAwB,gBAAgB,OAAO;GAGnE,MAAM,eAAe,YAAY,KAAK;AAMtC,SAAM,QAAQ,IACZ,gBAAgB,IAAI,OAAO,YAAY;AAErC,YAAQ,gBAAgB;AAExB,QAAI;AACF,SACE,uBAAuB,WACvB,OAAO,QAAQ,sBAAsB,WAErC,OAAO,QAAgB,mBAAmB;UACrC;AACL,YAAM,QAAQ;AACd,YAAM,QAAQ,UAAU,KAAK;;aAExB,OAAO;AAUd,SAPE,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,UACf,MAAM,SAAS,gBACd,MAAc,SAAS,SAAS,oBAAoB,IACpD,MAAc,SAAS,SAAS,6BAA6B,GAGhD;AAEhB,cAAQ,gBAAgB;AACxB;;AAEF,WAAM;;KAER,CACH;GACD,MAAM,aAAa,YAAY,KAAK;GAEpC,MAAM,WAAW,YAAY,KAAK;AAClC,OAAI,kBAAkB,EAAE;AACtB,SAAK,aAAa,iBAAiB,WAAW,WAAW;AACzD,SAAK,aAAa,kBAAkB,aAAa,aAAa;;IAGnE;;CAKH,yBAAoD;;CAGpD,MAAM,sBAAsB,QAAsB;AAEhD,UAAQ,gBAAgB;AAIxB,MAAI,CAAC,MAAKsB,sBACR,OAAKA,wBAAyB,MAAKC,sBAAuB,OAAO,CAAC,OAAM,QAAO;AAE7E,OAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;AAC5D,UAAKD,wBAAyB;AAC9B,UAAM;;AAER,WAAQ,MAAM,kDAAkD,KAAK,MAAM,UAAU,IAAI,IAAI;AAE7F,SAAKA,wBAAyB;AAC9B,SAAM;IACN;AAIJ,MAAI,QAAQ,QACV,OAAM,IAAI,aAAa,WAAW,aAAa;AAGjD,SAAO,MAAKA;;;;;;;;CASd,OAAMC,sBAAuB,QAAsB;AACjD,SAAO,SACL,mCACA;GACE,aAAa,KAAK,MAAM;GACxB,MAAM,KAAK;GACZ,EACD,QACA,OAAO,SAAS;AAEd,WAAQ,gBAAgB;AAKxB,SAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,QAAI,QAAQ,SAAS;AACnB,YAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;IAGF,MAAM,qBAAqB;AACzB,kBAAa,UAAU;AACvB,0BAAqB,OAAO;AAC5B,0BAAqB,OAAO;AAC5B,YAAO,IAAI,aAAa,WAAW,aAAa,CAAC;;AAEnD,YAAQ,iBAAiB,SAAS,cAAc,EAAE,MAAM,MAAM,CAAC;IAE/D,IAAIC;IACJ,IAAIC;IACJ,IAAIC;AAGJ,aAAS,4BAA4B;AACnC,SAAI,QAAQ,SAAS;AACnB,aAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;AAEF,cAAS,4BAA4B;AACnC,UAAI,QAAQ,SAAS;AACnB,cAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;AAGF,kBAAY,iBAAiB;AAC3B,eAAQ,oBAAoB,SAAS,aAAa;AAClD,gBAAS;SACR,GAAG;OACN;MACF;KACF;AAGF,WAAQ,gBAAgB;GAExB,MAAM,gBAAgB,qBAAqB,KAAK;AAChD,OAAI,kBAAkB,CACpB,MAAK,aAAa,sBAAsB,cAAc,OAAO;AAI/D,WAAQ,gBAAgB;GAKxB,MAAM,iBAAiB,KAAK,KAAK;GACjC,MAAM,wBAAwB;GAE9B,MAAM,eAAe,cAAc,IAAI,OAAO,GAAG,UAAU;AAEzD,YAAQ,gBAAgB;IAExB,MAAM,eAAe,KAAK,KAAK;AAC/B,QAAI;AAEF,SAAI,OAAO,EAAE,mBAAmB,YAAY;MAE1C,MAAM,iBAAiB,IAAI,SAAoB,GAAG,WAAW;AAC3D,WAAI,QAAQ,SAAS;AACnB,eAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;OAEF,MAAM,YAAY,iBAAiB,uBAAO,IAAI,MAAM,iBAAiB,MAAM,sBAAsB,sBAAsB,IAAI,CAAC,EAAE,sBAAsB;AACpJ,eAAQ,iBAAiB,eAAe;AACtC,qBAAa,UAAU;AACvB,eAAO,IAAI,aAAa,WAAW,aAAa,CAAC;UAChD,EAAE,MAAM,MAAM,CAAC;QAClB;AAEF,YAAM,QAAQ,KAAK,CAAC,EAAE,eAAe,OAAO,EAAE,eAAe,CAAC;gBAGvD,EAAE,iBAAiB;MAE1B,MAAM,SAAS,EAAE,gBAAgB;AAGjC,UAAI,WAAW,KAAK,WAAW,EAC7B;MAIF,MAAM,cAAc,EAAE,gBAAgB;AACtC,mBAAa,YAAY,GAAG;AAE5B,UAAI,aAAa;OACf,MAAM,iBAAiB,IAAI,SAAoB,GAAG,WAAW;AAC3D,YAAI,QAAQ,SAAS;AACnB,gBAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;QAEF,MAAM,YAAY,iBAAiB,uBAAO,IAAI,MAAM,iBAAiB,MAAM,sBAAsB,sBAAsB,IAAI,CAAC,EAAE,sBAAsB;AACpJ,gBAAQ,iBAAiB,eAAe;AACtC,sBAAa,UAAU;AACvB,gBAAO,IAAI,aAAa,WAAW,aAAa,CAAC;WAChD,EAAE,MAAM,MAAM,CAAC;SAClB;AAEF,aAAM,QAAQ,KAAK,CAAC,aAAa,eAAe,CAAC;;;aAG9C,OAAO;AAEd,SAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAGR,SAAI,kBAAkB,EAAE;MACtB,MAAM,iBAAiB,KAAK,KAAK,GAAG;AACpC,cAAQ,MAAM,+BAA+B,MAAM,gBAAgB,eAAe,MAAM,MAAM;;;KAIlG;GAEF,MAAM,UAAU,MAAM,QAAQ,WAAW,aAAa;AAQtD,OALgB,QAAQ,MAAK,MAC3B,EAAE,WAAW,cACb,EAAE,kBAAkB,gBACpB,EAAE,OAAO,SAAS,aACnB,CAEC,OAAM,IAAI,aAAa,WAAW,aAAa;GAIjD,MAAM,WAAW,QAAQ,QAAO,MAAK,EAAE,WAAW,WAAW;AAC7D,OAAI,SAAS,SAAS,KAAK,kBAAkB,EAAE;IAC7C,MAAM,mBAAmB,KAAK,KAAK,GAAG;AACtC,YAAQ,KAAK,iBAAiB,SAAS,OAAO,oCAAoC,iBAAiB,MAAM,SAAS,KAAI,MAAK,EAAE,WAAW,aAAa,EAAE,SAAS,KAAK,CAAC;;AASxK,0BAAuB;AAGvB,+BAA4B;AAQ5B,oBAAiB,KAAK,cAAc,cAAc,EAAE,EAAE;IAMzD;;;CAIH,IAAI,iBAAiB;AACnB,SAAO,2BAA2B,KAAK;;CAGzC,KAAIC,kBAAmB;EACrB,IAAI,SAAS,KAAK;AAClB,SAAO,QAAQ;AACb,OAAI,eAAe,OAAO,CACxB,QAAO;AAET,YAAS,OAAO;;AAElB,SAAO;;;;;;;;;;;;;CAcT,0BAA0B;AAExB,MAAI,CAAC,KAAK,gBACR,QAAO;AAKT,MAAI,KAAK,QAAQ,YAAY,KAAK,KAChC,QAAO;AAIT,MACE,KAAK,QAAQ,aAAa,KAAK,QAC/B,KAAK,QAAQ,eAAe,KAAK,QACjC,KAAK,QAAQ,qBAAqB,KAAK,KAEvC,QAAO;AAIT,MAAI,KAAK,QAAQ,eAAe,KAAK,KACnC,QAAO;AAIT,MAAI,KAAK,QAAQ,6BAA6B,KAAK,KACjD,QAAO;AAKT,MADoB,gBAAgB,KAAK,KAEvC,QAAO;AAIT,SAAO,KAAK;;;CAId,oBAAoB;EAClB,MAAM,YAAY,SAAS,cAAc,eAAe;EACxD,MAAM,SAAS,KAAK;AAOpB,MAAI,WAAW,SAAS,MAAM;AAC5B,aAAU,MAAM,WAAW;AAC3B,aAAU,MAAM,MAAM;AACtB,aAAU,MAAM,OAAO;AACvB,aAAU,MAAM,QAAQ;AACxB,aAAU,MAAM,SAAS;AACzB,aAAU,MAAM,SAAS;;AAG3B,UAAQ,OAAO,UAAU;AACzB,MAAI,CAAC,KAAK,aAAa,KAAK,CAC1B,MAAK,aAAa,MAAM,iBAAiB;EAK3C,MAAM,UAAU,SAAS,cAAc,cAAc;AACrD,UAAQ,KAAK;AACb,UAAQ,aAAa,QAAQ,SAAS;AACtC,UAAQ,aAAa,YAAY,GAAG;AACpC,UAAQ,MAAM,QAAQ;AACtB,UAAQ,MAAM,SAAS;EAIvB,MAAM,OAAO,KAAK,uBAAuB;EACzC,MAAM,SAAS,SAAS,cAAc,YAAY;AAClD,SAAO,KAAK;AAEZ,SAAO,MAAM,QAAQ,GAAG,KAAK,IAAI,KAAK,OAAO,KAAK,CAAC;AACnD,SAAO,MAAM,SAAS,GAAG,KAAK,IAAI,KAAK,QAAQ,KAAK,CAAC;AACrD,SAAO,MAAM,UAAU;AAGvB,SAAO,OAAO,KAA2B;AACzC,UAAQ,OAAO,OAAO;AACtB,YAAU,OAAO,QAAQ;EAGzB,MAAM,YAAY,SAAS,cAAc,eAAe;AACxD,YAAU,aAAa,QAAQ,YAAY;AAC3C,YAAU,aAAa,UAAU,mBAAmB;AACpD,YAAU,aAAa,UAAU,SAAS;AAC1C,YAAU,OAAO,UAAU;EAG3B,MAAM,YAAY,SAAS,cAAc,eAAe;AACxD,YAAU,aAAa,QAAQ,WAAW;AAC1C,YAAU,aAAa,UAAU,KAAK,GAAG;AACzC,YAAU,OAAO,UAAU;;CAG7B,KAAIC,aAAc;AAChB,SAAO,MAAM,KACX,KAAK,iBACH,yDACD,CACF;;;;;;;;CASH,mBAA8B;AAC5B,SAAO,qBAAqB,KAAK;;;;;;;;CASnC,MAAM,YAAY,QAAgB,MAAc,QAA4C;AAC1F,SAAO,oBAAoB,MAAM,QAAQ,MAAM,OAAO;;;;;;CAOxD,OAAMC,cAAe,QAAgB,MAAc;EAEjD,MAAM,iBAAiB,MAAM,KAAK,YAAY,QAAQ,KAAK;EAG3D,MAAM,kBAAkB,IAAI,cAAc;EAG1C,MAAM,eAAe,gBAAgB,oBAAoB;AACzD,eAAa,SAAS;AACtB,eAAa,QAAQ,gBAAgB,YAAY;AAGjD,eAAa,MAAM,EAAE;AAGrB,SAAO,IAAI,SAAe,YAAY;AACpC,gBAAa,gBAAgB;AAC3B,oBAAgB,OAAO;AACvB,aAAS;;IAEX;;CAGJ,OAAMC,cAAe;EACnB,MAAM,aAAa,MAAKF;EACxB,MAAMG,cAA8B,EAAE;AACtC,OAAK,MAAM,MAAM,YAAY;GAC3B,MAAM,eAAgB,GAAW;AAEjC,OAAI,gBAAgB,OAAO,aAAa,QAAQ,YAAY;AAE1D,iBAAa,KAAK,CAAC,YAAY,GAAG;AAClC,QAAI,aAAa,aACf,aAAY,KAAK,aAAa,aAAa;;;AAKjD,QAAM,QAAQ,IAAI,YAAY;AAE9B,aAAW,SAAS,OAAO;AACzB,OAAI,mBAAmB,MAAM,GAAG,yBAAyB,SACvD,IAAG,aAAa,OAAO,GAAG,eAAe,CAAC;IAE5C;;CAIJ,2BAA2B;CAC3B,+BAA+B,KAAK,KAAK;;wCACQ;;;uCACD;;;CAGhD,oBAAmC,QAAQ,SAAS;CACpD,4BAAoD;CAoBpD,OAAM7C,aAAc,QAAoC;EAEtD,MAAM,MAAM,KAAK,KAAK;AACtB,MAAI,MAAM,MAAK8C,2CAA2C,+BAA+B;AACvF,SAAKC,0BAA2B;AAChC,SAAKD,8BAA+B;;AAEtC,QAAKC;AAEL,MAAI,MAAKA,uCAAuC,+BAE9C;AAGF,MAAI;AACF,UAAO,gBAAgB;AAEvB,OAAI,KAAK,gBAGP,OAAM,SACJ,uBACA;IACE,aAAa,KAAK,MAAM;IACxB,kBAAkB,KAAK;IACvB,eAAe,KAAK;IACrB,EACD,QACA,YAAY;AAGV,UAAM,MAAKvC,gBAAiB,YAAY,KAAK,eAAe;KAC1D,kBAAkB;KAClB,qBAAqB,SAAS;AAC5B,uBAAiB,KAAoB;;KAExC,CAAC;AACF,WAAO,gBAAgB;AAEvB,UAAM,MAAKQ,yBAA0B;KAExC;OAGD,OAAM,MAAKA,yBAA0B;WAEhC,OAAO;AACd,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD;AAEF,WAAQ,MAAM,+BAA+B,MAAM;;;CAIvD,OAAMA,0BAA2B;AAC/B,MAAI,MAAKI,iBAAkB,OAAO,GAAG;GACnC,MAAM,kBACJ,KAAK,aAAa,IAAI,KAAK,mBAAmB,KAAK,aAAa;GAClE,MAAM,YAAY;IAChB,kBAAkB,KAAK;IACvB,eAAe,KAAK;IACpB,YAAY,KAAK;IACjB;IACA,SAAS;IACV;AAED,SAAM,QAAQ,IACZ,MAAM,KAAK,MAAKA,iBAAkB,CAAC,KAAK,aACtC,QAAQ,QAAQ,SAAS,UAAU,CAAC,CACrC,CACF;;;;CAKL,mBAAgD,QAAQ,QAAQ,OAAU;CAC1E,2BAAmD;CAqBnD,OAAMd,YAAa,YAAgC,QAAkD;AACnG,MAAI;AACF,UAAO,gBAAgB;AAGvB,OAAI,KAAK,oBAAoB;AAE3B,UAAM,KAAK,mBAAmB;AAC9B,WAAO,gBAAgB;AACvB,WAAO,KAAK;;AAId,OAAI,CAAC,KAAK,gBACR;AAGF,UAAO,MAAM,SACX,sBACA;IACE,aAAa,KAAK,MAAM;IACxB,YAAY,cAAc;IAC1B,YAAY,KAAK;IAClB,EACD,QACA,OAAO,SAAS;AAEd,QAAI;AACF,WAAM,QAAQ,KAAK,CACjB,KAAK,sBAAsB,OAAO,EAClC,IAAI,SAAe,GAAG,WAAW;AAC/B,UAAI,OAAO,SAAS;AAClB,cAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;MAEF,MAAM,YAAY,iBAAiB,uBAAO,IAAI,MAAM,gCAAgC,CAAC,EAAE,IAAM;AAC7F,aAAO,iBAAiB,eAAe;AACrC,oBAAa,UAAU;AACvB,cAAO,IAAI,aAAa,WAAW,aAAa,CAAC;SAChD,EAAE,MAAM,MAAM,CAAC;OAClB,CACH,CAAC;aACK,OAAO;AACd,SAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;;AAKV,WAAO,gBAAgB;IAGvB,MAAM,UAAU,mBACd,cAAc,GACd,KAAK,YACL,KAAK,aACN;AACD,QAAI,kBAAkB,CACpB,MAAK,aAAa,WAAW,QAAQ;AAGvC,UAAKF,cAAe;AACpB,SAAK,cAAc,cAAc;AAEjC,UAAM,KAAK;AACX,WAAO,gBAAgB;AAEvB,UAAM,MAAKM,uBAAwB;AACnC,WAAO,gBAAgB;AAEvB,QAAI,CAAC,MAAKH,0BACR,MAAK,uBAAuB,MAAKH,YAAa;AAEhD,UAAKS,iBAAkB;AACvB,QAAI,MAAKN,0BACP,OAAKA,4BAA6B;AAEpC,WAAO;KAEV;WACM,OAAO;AACd,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD;AAEF,WAAQ,MAAM,8BAA8B,MAAM;AAClD;;;;;;;;;;CAWJ,mBAAkC;AAGhC,SAAO;GACL,GAHW,4BAA4B,KAAK;GAI5C,aAAa;GACb,oBAAoB;GACrB;;;;;;;;CASH,kBAA8C;AAC5C,SAAO,2BAA2B,KAAK;;;YAx5DxC,QAAQ,EAAE,SAAS,kBAAkB,CAAC;YAItC,QAAQ,EAAE,SAAS,WAAW,CAAC;YAiC/B,SAAS,EAAE,MAAM,QAAQ,CAAC;YAQ1B,SAAS;CAAE,MAAM;CAAS,WAAW;CAAa,CAAC;YASnD,SAAS;CAAE,MAAM;CAAS,SAAS;CAAM,CAAC;YA2B1C,SAAS,EAAE,MAAM,QAAQ,CAAC;YAuF1B,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAe,CAAC;yCA1MtD,cAAc,eAAe"}
1
+ {"version":3,"file":"EFTimegroup.js","names":["durationCache: WeakMap<EFTimegroup, number>","ancestor: Node | null","EFTimegroup","taskObj: { run(): void | Promise<void>; taskComplete: Promise<void> }","#frameTaskAbortController","#frameTaskPromise","#runFrameTask","taskObj: { run(): void | Promise<number | undefined>; taskComplete: Promise<number | undefined> }","#seekTaskAbortController","#pendingSeekTime","#currentTime","#seekTaskPromise","#runSeekTask","#initializer","#initializerHasRun","#initializerComplete","#runInitializer","#restoringFromLocalStorage","#frameController","#customFrameTasks","#executeCustomFrameTasks","#contentEpoch","#runThrottledFrameTask","#userTimeMs","#processingPendingSeek","#seekInProgress","#evaluateVisibleElementsForFrame","#getAllLitElementDescendants","result: LitElement[]","#onFrameCallback","#onFrameCleanup","#handleSlotChange","#setupPlaybackListener","#playbackListener","#previousDurationMs","#resizeObserver","#removePlaybackListener","canvases: HTMLCanvasElement[]","result: unknown","#copyTextSegmentData","updatePromises: Promise<any>[]","#waitForCaptionsData","waitPromises: Promise<unknown>[]","#copyInitializersToClone","initializerPromises: Promise<void>[]","#copyCaptionsData","#copyTextContent","container","#mediaDurationsPromise","#waitForMediaDurations","rafId1: number","rafId2: number","timeoutId: ReturnType<typeof setTimeout>","#contextProvider","#efElements","#testPlayAudio","#loadMd5Sums","loaderTasks: Promise<any>[]","#timegroupFrameTaskLastReset","#timegroupFrameTaskCount"],"sources":["../../src/elements/EFTimegroup.ts"],"sourcesContent":["import { provide } from \"@lit/context\";\nimport debug from \"debug\";\nimport { css, html, LitElement, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nimport { quantizeToFrameTimeS } from \"../utils/frameTime.js\";\nimport { EF_RENDERING } from \"../EF_RENDERING.js\";\nimport { isContextMixin } from \"../gui/ContextMixin.js\";\nimport { efContext } from \"../gui/efContext.js\";\nimport { TWMixin } from \"../gui/TWMixin.js\";\nimport { isTracingEnabled, withSpan } from \"../otel/tracingHelpers.js\";\nimport {\n FrameController,\n type FrameRenderable,\n type FrameState,\n PRIORITY_DEFAULT,\n} from \"../preview/FrameController.js\";\nimport { deepGetMediaElements, type EFMedia } from \"./EFMedia.js\";\nimport {\n deepGetElementsWithFrameTasks,\n EFTemporal,\n flushStartTimeMsCache,\n resetTemporalCache,\n shallowGetTemporalElements,\n timegroupContext,\n type TemporalMixinInterface,\n} from \"./EFTemporal.js\";\nimport { parseTimeToMs } from \"./parseTimeToMs.js\";\nimport { renderTemporalAudio } from \"./renderTemporalAudio.js\";\nimport { EFTargetable } from \"./TargetController.js\";\nimport { TimegroupController } from \"./TimegroupController.js\";\nimport {\n evaluateAnimationVisibilityState,\n updateAnimations,\n} from \"./updateAnimations.js\";\nimport {\n type ContainerInfo,\n getContainerInfoFromElement,\n} from \"./ContainerInfo.js\";\nimport {\n type ElementPositionInfo,\n getPositionInfoFromElement,\n} from \"./ElementPositionInfo.js\";\nimport {\n captureTimegroupAtTime,\n captureFromClone,\n type CaptureOptions,\n type CaptureBatchOptions,\n} from \"../preview/renderTimegroupToCanvas.js\";\nimport {\n renderTimegroupToVideo,\n type RenderToVideoOptions,\n} from \"../preview/renderTimegroupToVideo.js\";\nimport type { PlaybackControllerUpdateEvent } from \"../gui/PlaybackController.js\";\n\n// Side-effect imports for workbench wrapping\nimport \"../canvas/EFCanvas.js\";\nimport \"../gui/hierarchy/EFHierarchy.js\";\nimport \"../gui/EFFilmstrip.js\";\nimport \"../gui/EFWorkbench.js\";\nimport \"../gui/EFFitScale.js\";\nimport \"./EFPanZoom.js\";\n\n\nconst log = debug(\"ef:elements:EFTimegroup\");\n\n// Custom frame task callback type\nexport type FrameTaskCallback = (info: {\n ownCurrentTimeMs: number;\n currentTimeMs: number;\n durationMs: number;\n percentComplete: number;\n element: EFTimegroup;\n}) => void | Promise<void>;\n\n/**\n * Result of createRenderClone() - contains the clone, its container, and cleanup function.\n */\nexport interface RenderCloneResult {\n /** The cloned timegroup, fully functional with its own time state */\n clone: EFTimegroup;\n /** The offscreen container holding the clone */\n container: HTMLElement;\n /** Call this to remove the clone from DOM and clean up */\n cleanup: () => void;\n}\n\n/**\n * Initializer function type for setting up JavaScript behavior on timegroup instances.\n * This function is called on both the prime timeline and each render clone.\n * \n * CONSTRAINTS:\n * - MUST be synchronous (no async/await, no Promise return)\n * - MUST complete in <100ms (error) or <10ms (warning)\n * - Should only register callbacks and set up behavior, not do expensive work\n */\nexport type TimegroupInitializer = (timegroup: EFTimegroup) => void;\n\n// Constants for initializer time budget enforcement\nconst INITIALIZER_ERROR_THRESHOLD_MS = 100;\nconst INITIALIZER_WARN_THRESHOLD_MS = 10;\n\n// ============================================================================\n// Purpose 1: Composition Rules - How Duration is Determined\n// ============================================================================\n//\n// A timegroup's duration is determined by its mode:\n// - \"fixed\": Uses explicit duration attribute (base case)\n// - \"sequence\": Sum of child durations minus overlaps\n// - \"contain\": Maximum of child durations\n// - \"fit\": Inherits duration from parent timegroup\n//\n// Core invariant: Every timegroup has exactly one duration value at any moment,\n// computed from either explicit specification (fixed mode) or child relationships\n// (sequence/contain/fit modes).\n//\n// ============================================================================\n\n/**\n * The four timegroup modes define how duration is calculated:\n * - \"fit\": Inherits duration from parent timegroup\n * - \"fixed\": Uses explicit duration attribute\n * - \"sequence\": Sum of child durations minus overlaps\n * - \"contain\": Maximum of child durations\n */\nexport type TimeMode = \"fit\" | \"fixed\" | \"sequence\" | \"contain\";\n\n// Cache for duration calculations to avoid O(n) recalculation on every access\n// Used by all modes (sequence, contain) to avoid repeated iteration through children\nlet durationCache: WeakMap<EFTimegroup, number> = new WeakMap();\n\nexport const flushDurationCache = () => {\n durationCache = new WeakMap();\n};\n\n// Keep alias for backwards compatibility\nexport const flushSequenceDurationCache = flushDurationCache;\n\n// Track timegroups currently calculating duration to prevent infinite loops\nconst durationCalculationInProgress = new WeakSet<EFTimegroup>();\n\n// Export function to check if a timegroup is currently calculating duration\n// This is used by EFTemporal to prevent calling parent.durationMs during calculation\nexport const isTimegroupCalculatingDuration = (\n timegroup: EFTimegroup | undefined,\n): boolean => {\n return (\n timegroup !== undefined && durationCalculationInProgress.has(timegroup)\n );\n};\n\n// Register this function with EFTemporal to break circular dependency\n// EFTemporal needs this function but can't import it directly due to circular dependency\nimport { registerIsTimegroupCalculatingDuration } from \"./EFTemporal.js\";\nregisterIsTimegroupCalculatingDuration(isTimegroupCalculatingDuration);\n\n/**\n * Determines if a timegroup has its own duration based on its mode.\n * This is the semantic rule: which modes produce independent durations.\n */\nfunction hasOwnDurationForMode(\n mode: TimeMode,\n hasExplicitDuration: boolean,\n): boolean {\n return (\n mode === \"contain\" ||\n mode === \"sequence\" ||\n (mode === \"fixed\" && hasExplicitDuration)\n );\n}\n\n/**\n * Determines if a child temporal element should participate in parent duration calculation.\n *\n * Semantic rule: Fit-mode children inherit from parent, so they don't contribute to parent's\n * duration calculation (to avoid circular dependencies). Children without own duration\n * also don't contribute.\n */\nfunction shouldParticipateInDurationCalculation(\n child: TemporalMixinInterface & HTMLElement,\n): boolean {\n // Fit timegroups look \"up\" to their parent for duration, so skip to avoid infinite loop\n if (child instanceof EFTimegroup && child.mode === \"fit\") {\n return false;\n }\n // Only children with their own duration contribute\n if (!child.hasOwnDuration) {\n return false;\n }\n return true;\n}\n\n/**\n * Evaluates duration for \"fit\" mode: inherits from parent.\n * Semantic rule: fit mode always matches parent duration, or 0 if no parent.\n */\nfunction evaluateFitDuration(parentTimegroup: EFTimegroup | undefined): number {\n if (!parentTimegroup) {\n return 0;\n }\n return parentTimegroup.durationMs;\n}\n\n/**\n * Evaluates duration for \"sequence\" mode: sum of children minus overlaps.\n * Semantic rule: sequence mode sums child durations, subtracting overlap between consecutive items.\n * Fit-mode children are excluded to avoid circular dependencies.\n */\nfunction evaluateSequenceDuration(\n timegroup: EFTimegroup,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n overlapMs: number,\n): number {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = durationCache.get(timegroup);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let duration = 0;\n let participatingIndex = 0;\n childTemporals.forEach((child) => {\n if (!shouldParticipateInDurationCalculation(child)) {\n return;\n }\n // Prevent infinite loops: skip children that are already calculating their duration\n if (\n child instanceof EFTimegroup &&\n durationCalculationInProgress.has(child)\n ) {\n return;\n }\n\n // Additional safety: if child is a timegroup, check if any of its ancestors\n // (EXCLUDING the current timegroup) are calculating.\n // This prevents cycles where a child's descendant eventually calls back to an ancestor,\n // but allows direct children of the current timegroup to participate.\n if (child instanceof EFTimegroup) {\n let ancestor: Node | null = child.parentNode;\n let shouldSkip = false;\n while (ancestor) {\n // Stop FIRST if we've reached the current timegroup - direct children are allowed\n if (ancestor === timegroup) {\n break;\n }\n if (\n ancestor instanceof EFTimegroup &&\n durationCalculationInProgress.has(ancestor)\n ) {\n // Found a calculating ancestor (not the current timegroup) - skip this child to prevent cycle\n shouldSkip = true;\n break;\n }\n ancestor = ancestor.parentNode;\n }\n if (shouldSkip) {\n return;\n }\n }\n\n // Subtract overlap for all items after the first\n if (participatingIndex > 0) {\n duration -= overlapMs;\n }\n duration += child.durationMs;\n participatingIndex++;\n });\n\n // Ensure non-negative duration (invariant)\n duration = Math.max(0, duration);\n\n // Cache the calculated duration\n durationCache.set(timegroup, duration);\n return duration;\n}\n\n/**\n * Evaluates duration for \"contain\" mode: maximum of children.\n * Semantic rule: contain mode takes the maximum child duration.\n * Fit-mode children and children without own duration are excluded.\n */\nfunction evaluateContainDuration(\n timegroup: EFTimegroup,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n): number {\n // Check cache first to avoid expensive O(n) recalculation\n const cachedDuration = durationCache.get(timegroup);\n if (cachedDuration !== undefined) {\n return cachedDuration;\n }\n\n let maxDuration = 0;\n for (const child of childTemporals) {\n if (!shouldParticipateInDurationCalculation(child)) {\n continue;\n }\n // Prevent infinite loops: skip children that are already calculating their duration\n // This check applies to all timegroup children, not just contain mode, because\n // a sequence-mode child could contain a contain-mode grandchild that\n // eventually references back to the parent through the parent chain\n if (\n child instanceof EFTimegroup &&\n durationCalculationInProgress.has(child)\n ) {\n continue;\n }\n\n // Additional safety: if child is a timegroup, check if any of its ancestors\n // (EXCLUDING the current timegroup) are calculating.\n // This prevents cycles where a child's descendant eventually calls back to an ancestor,\n // but allows direct children of the current timegroup to participate.\n if (child instanceof EFTimegroup) {\n let ancestor: Node | null = child.parentNode;\n let shouldSkip = false;\n while (ancestor) {\n // Stop FIRST if we've reached the current timegroup - direct children are allowed\n if (ancestor === timegroup) {\n break;\n }\n if (\n ancestor instanceof EFTimegroup &&\n durationCalculationInProgress.has(ancestor)\n ) {\n // Found a calculating ancestor (not the current timegroup) - skip this child to prevent cycle\n shouldSkip = true;\n break;\n }\n ancestor = ancestor.parentNode;\n }\n if (shouldSkip) {\n continue;\n }\n }\n\n maxDuration = Math.max(maxDuration, child.durationMs);\n }\n // Ensure non-negative duration (invariant)\n const duration = Math.max(0, maxDuration);\n\n // Cache the calculated duration\n durationCache.set(timegroup, duration);\n return duration;\n}\n\n/**\n * Evaluates duration based on timegroup mode.\n * This is the semantic evaluation function - it determines what duration should be.\n *\n * Note: Fixed mode is handled inline in the getter because it needs to call super.durationMs\n * which requires the class context. The other modes are extracted for clarity.\n */\nfunction evaluateDurationForMode(\n timegroup: EFTimegroup,\n mode: TimeMode,\n childTemporals: Array<TemporalMixinInterface & HTMLElement>,\n): number {\n switch (mode) {\n case \"fit\":\n return evaluateFitDuration(timegroup.parentTimegroup);\n case \"sequence\": {\n // Mark this timegroup as calculating duration to prevent infinite loops\n durationCalculationInProgress.add(timegroup);\n try {\n return evaluateSequenceDuration(\n timegroup,\n childTemporals,\n timegroup.overlapMs,\n );\n } finally {\n // Always remove the marker, even if an error occurs\n durationCalculationInProgress.delete(timegroup);\n }\n }\n case \"contain\": {\n // Mark this timegroup as calculating duration to prevent infinite loops\n durationCalculationInProgress.add(timegroup);\n try {\n return evaluateContainDuration(timegroup, childTemporals);\n } finally {\n // Always remove the marker, even if an error occurs\n durationCalculationInProgress.delete(timegroup);\n }\n }\n default:\n throw new Error(`Invalid time mode: ${mode}`);\n }\n}\n\nexport const shallowGetTimegroups = (\n element: Element,\n groups: EFTimegroup[] = [],\n) => {\n for (const child of Array.from(element.children)) {\n if (child instanceof EFTimegroup) {\n groups.push(child);\n } else {\n shallowGetTimegroups(child, groups);\n }\n }\n return groups;\n};\n\n// ============================================================================\n// Purpose 2: Time Propagation - How currentTime Flows Root to Children\n// ============================================================================\n//\n// Time propagation determines how the root timegroup's currentTime flows to child\n// temporal elements, computing each child's ownCurrentTime based on:\n// - The root's currentTime (global coordinate)\n// - The child's startTimeMs (determined by parent's composition mode)\n// - The parent's mode (sequence/contain/fit/fixed)\n//\n// Propagation rules by mode:\n// - Sequence: Each child's ownCurrentTime progresses within its time-shifted window\n// - Contain: All children share the same ownCurrentTime as parent\n// - Fit: Child ownCurrentTime = parent ownCurrentTime (identity mapping)\n//\n// Core invariant: Only root timegroup's currentTime should be written.\n// Child times are computed from parent state via ownCurrentTimeMs.\n//\n// Note: Time propagation logic is primarily implemented in EFTemporal.ts\n// (ownCurrentTimeMs getter and startTimeMs calculation). The timegroup's\n// currentTime setter triggers propagation by updating root time.\n//\n// ============================================================================\n\n// ============================================================================\n// Purpose 3: Seeking - Moving to a Specific Time\n// ============================================================================\n//\n// Seeking moves the timeline to a specific time position. This involves:\n// 1. Quantizing the requested time to frame boundaries (based on fps)\n// 2. Clamping to valid range [0, duration]\n// 3. Updating root timegroup's currentTime (which triggers time propagation)\n// 4. Waiting for all media and frame tasks to complete\n//\n// Core invariant: All time values snap to frame boundaries when FPS is set.\n// This ensures consistent seek/render behavior.\n//\n// ============================================================================\n\n/**\n * Evaluates the target time for a seek operation.\n * Applies quantization and clamping to determine the valid seek target.\n */\nfunction evaluateSeekTarget(\n requestedTime: number,\n durationMs: number,\n fps: number,\n): number {\n // Quantize to frame boundaries\n const quantizedTime = quantizeToFrameTimeS(requestedTime, fps);\n // Clamp to valid range [0, duration]\n return Math.max(0, Math.min(quantizedTime, durationMs / 1000));\n}\n\n@customElement(\"ef-timegroup\")\nexport class EFTimegroup extends EFTargetable(EFTemporal(TWMixin(LitElement))) implements FrameRenderable {\n static get observedAttributes(): string[] {\n const parentAttributes = super.observedAttributes || [];\n return [\n ...parentAttributes,\n \"mode\",\n \"overlap\",\n \"currenttime\",\n \"fit\",\n \"fps\",\n \"auto-init\",\n \"workbench\",\n ];\n }\n\n static styles = css`\n :host {\n display: block;\n position: relative;\n overflow: hidden;\n }\n\n ::slotted(ef-timegroup) {\n position: absolute;\n width: 100%;\n height: 100%;\n top: 0;\n left: 0;\n overflow: initial;\n }\n `;\n\n /** @internal */\n @provide({ context: timegroupContext })\n _timeGroupContext = this;\n\n /** @internal */\n @provide({ context: efContext })\n efContext = this;\n\n /** @public */\n mode: TimeMode = \"contain\";\n /** @public */\n overlapMs = 0;\n\n #initializer?: TimegroupInitializer;\n \n /**\n * Initializer function for setting up JavaScript behavior on this timegroup.\n * This function is called ONCE per instance - on the prime timeline when first connected,\n * and on each render clone when created.\n * \n * Use this to register frame callbacks, set up event listeners, or initialize state.\n * The same initializer code runs on both prime and clones, eliminating duplication.\n * \n * CONSTRAINTS:\n * - MUST be synchronous (no async/await, no Promise return)\n * - MUST complete in <100ms (error thrown) or <10ms (warning logged)\n * - Should only register callbacks and set up behavior, not do expensive work\n * \n * TIMING:\n * - If set before element connects to DOM: runs automatically after connectedCallback\n * - If set after element is connected: runs immediately\n * - Clones automatically copy and run the initializer when created\n * \n * @example\n * ```javascript\n * const tg = document.querySelector('ef-timegroup');\n * tg.initializer = (instance) => {\n * // Runs once on prime timeline, once on each clone\n * instance.addFrameTask((info) => {\n * // Update content based on time\n * });\n * };\n * ```\n * @public\n */\n get initializer(): TimegroupInitializer | undefined {\n return this.#initializer;\n }\n \n set initializer(fn: TimegroupInitializer | undefined) {\n this.#initializer = fn;\n \n // If element is already connected and initializer hasn't run, run it now\n // This handles the case where initializer is set after connectedCallback\n if (fn && this.isConnected && !this.#initializerHasRun) {\n // Create promise that resolves when initializer completes\n this.#initializerComplete = this.updateComplete.then(() => {\n this.#runInitializer();\n });\n }\n }\n \n /**\n * Track if initializer has run on this instance to prevent double execution.\n * @internal\n */\n #initializerHasRun = false;\n \n /**\n * Promise that resolves when initializer completes.\n * Used by createRenderClone to ensure frame tasks are registered before rendering.\n * @internal\n */\n #initializerComplete?: Promise<void>;\n \n /**\n * Public accessor for initializer completion promise.\n * Allows createRenderClone to wait for initializer before rendering.\n * @internal\n */\n get initializerComplete(): Promise<void> | undefined {\n return this.#initializerComplete;\n }\n\n /** @public */\n @property({ type: Number })\n fps = 30;\n\n /**\n * When true, automatically seeks to frame 0 after media durations are loaded.\n * Only applies to root timegroups (timegroups that are not nested inside another timegroup).\n * This ensures the first frame is rendered immediately on initialization.\n */\n @property({ type: Boolean, attribute: \"auto-init\" })\n autoInit = false;\n\n /**\n * When true, automatically wraps this root timegroup with an ef-workbench element.\n * The workbench provides development UI including hierarchy panel, timeline, and playback controls.\n * Only applies to root timegroups.\n * @public\n */\n @property({ type: Boolean, reflect: true })\n workbench = false;\n\n attributeChangedCallback(\n name: string,\n old: string | null,\n value: string | null,\n ): void {\n if (name === \"mode\" && value) {\n this.mode = value as typeof this.mode;\n }\n if (name === \"overlap\" && value) {\n this.overlapMs = parseTimeToMs(value);\n }\n if (name === \"auto-init\") {\n this.autoInit = value !== null;\n }\n if (name === \"fps\" && value) {\n this.fps = Number.parseFloat(value);\n }\n if (name === \"workbench\") {\n this.workbench = value !== null;\n }\n super.attributeChangedCallback(name, old, value);\n }\n\n /** @public */\n @property({ type: String })\n fit: \"none\" | \"contain\" | \"cover\" = \"none\";\n\n #resizeObserver?: ResizeObserver;\n\n /** Content epoch - increments when visual content changes (used by thumbnail cache) */\n #contentEpoch = 0;\n\n #currentTime: number | undefined = undefined;\n #userTimeMs: number = 0; // What the user last requested (for preview display)\n #seekInProgress = false;\n #pendingSeekTime: number | undefined;\n #processingPendingSeek = false;\n #restoringFromLocalStorage = false; // Guard to prevent recursive seeks during localStorage restoration\n \n /** @internal */\n isRestoringFromLocalStorage(): boolean {\n return this.#restoringFromLocalStorage;\n }\n \n /** @internal - Used by PlaybackController to set restoration state */\n setRestoringFromLocalStorage(value: boolean): void {\n this.#restoringFromLocalStorage = value;\n }\n #customFrameTasks: Set<FrameTaskCallback> = new Set();\n #onFrameCallback: FrameTaskCallback | null = null;\n #onFrameCleanup: (() => void) | null = null;\n #playbackListener: ((event: PlaybackControllerUpdateEvent) => void) | null = null;\n \n /**\n * Centralized frame controller for coordinating element rendering.\n * Replaces the distributed Lit Task hierarchy with a single control point.\n */\n #frameController: FrameController = new FrameController(this);\n \n /**\n * Get the frame controller for centralized rendering coordination.\n * @public\n */\n get frameController(): FrameController {\n return this.#frameController;\n }\n\n // ============================================================================\n // FrameRenderable Interface Implementation\n // ============================================================================\n // Allows FrameController to discover and coordinate nested timegroups.\n // This ensures frame callbacks registered on nested timegroups are executed.\n // ============================================================================\n\n /**\n * Query timegroup's readiness state for a given time.\n * Timegroups are always ready (no async preparation needed).\n * @public\n */\n getFrameState(_timeMs: number): FrameState {\n return {\n needsPreparation: false,\n isReady: true,\n priority: PRIORITY_DEFAULT,\n };\n }\n\n /**\n * Async preparation phase (no-op for timegroups).\n * Timegroups don't need preparation - they just coordinate child rendering.\n * @public\n */\n async prepareFrame(_timeMs: number, _signal: AbortSignal): Promise<void> {\n // No preparation needed for timegroups\n }\n\n /**\n * Synchronous render phase - executes custom frame callbacks.\n * Called by FrameController after all preparation is complete.\n * Kicks off async frame callbacks without blocking (they run in background).\n * @public\n */\n renderFrame(_timeMs: number): void {\n // Execute custom frame tasks registered via addFrameTask()\n // Fire and forget - callbacks can be async but we don't block here\n // The frameTask.taskComplete promise tracks completion if needed\n if (this.#customFrameTasks.size > 0) {\n this.#executeCustomFrameTasks().catch((error) => {\n console.error(\"EFTimegroup custom frame task error:\", error);\n });\n }\n }\n\n /**\n * Get the effective FPS for this timegroup.\n * During rendering, uses the render options FPS if available.\n * Otherwise uses the configured fps property.\n * @public\n */\n get effectiveFps(): number {\n // During rendering, prefer the render options FPS\n if (typeof window !== \"undefined\" && window.EF_FRAMEGEN?.renderOptions) {\n return window.EF_FRAMEGEN.renderOptions.encoderOptions.video.framerate;\n }\n return this.fps;\n }\n\n /**\n * Get the current content epoch (used by thumbnail cache).\n * The epoch increments whenever visual content changes.\n * @public\n */\n get contentEpoch(): number {\n return this.#contentEpoch;\n }\n\n /**\n * Increment content epoch (called when visual content changes).\n * This invalidates cached thumbnails by changing their cache keys.\n * @public\n */\n incrementContentEpoch(): void {\n this.#contentEpoch++;\n }\n\n async #runThrottledFrameTask(): Promise<void> {\n if (this.playbackController) {\n return this.playbackController.runThrottledFrameTask();\n }\n await this.frameTask.run();\n }\n\n // ============================================================================\n // Purpose 3: Seeking Implementation\n // ============================================================================\n\n /** @public */\n @property({ type: Number, attribute: \"currenttime\" })\n set currentTime(time: number) {\n // Evaluate seek target (quantization and clamping)\n const seekTarget = evaluateSeekTarget(\n time,\n this.durationMs,\n this.effectiveFps,\n );\n\n // Delegate to playbackController if available\n if (this.playbackController) {\n this.playbackController.currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n return;\n }\n\n // Only root timegroups can have their currentTime set directly\n if (!this.isRootTimegroup) {\n return;\n }\n\n // Validate seek target\n if (Number.isNaN(seekTarget)) {\n return;\n }\n\n // Skip if already at target time (unless processing pending seek or restoring from localStorage)\n if (seekTarget === this.#currentTime && !this.#processingPendingSeek && !this.#restoringFromLocalStorage) {\n return;\n }\n\n // Skip if this is the same as pending seek\n if (this.#pendingSeekTime === seekTarget) {\n return;\n }\n \n // Prevent recursive seeks during localStorage restoration\n if (this.#restoringFromLocalStorage && seekTarget !== this.#currentTime) {\n // Allow the restoration seek to proceed, but prevent subsequent seeks\n // The flag will be cleared after the seek completes\n }\n\n // Handle concurrent seeks by queuing pending seek\n // This ensures we only have ONE seek in flight at a time, avoiding wasted work.\n // When scrubbing quickly, intermediate positions are skipped entirely - we don't\n // start work we know will be thrown away.\n if (this.#seekInProgress) {\n this.#pendingSeekTime = seekTarget;\n this.#currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n return;\n }\n\n // Execute seek - update both source time and user time\n this.#currentTime = seekTarget;\n this.#userTimeMs = seekTarget * 1000; // User-initiated time change\n this.#seekInProgress = true;\n\n // Attach .catch() to prevent unhandled rejection warning - errors are handled by seekTask.onError\n this.seekTask.run().catch(() => {}).finally(() => {\n this.#seekInProgress = false;\n \n // Process pending seek if it differs from completed seek\n // This jumps directly to wherever the user ended up, skipping intermediates\n if (\n this.#pendingSeekTime !== undefined &&\n this.#pendingSeekTime !== seekTarget\n ) {\n const pendingTime = this.#pendingSeekTime;\n this.#pendingSeekTime = undefined;\n this.#processingPendingSeek = true;\n try {\n this.currentTime = pendingTime;\n } finally {\n this.#processingPendingSeek = false;\n }\n } else {\n this.#pendingSeekTime = undefined;\n }\n });\n }\n\n /** @public */\n get currentTime() {\n if (this.playbackController) {\n return this.playbackController.currentTime;\n }\n return this.#currentTime ?? 0;\n }\n\n /** @public */\n set currentTimeMs(ms: number) {\n this.currentTime = ms / 1000;\n }\n\n /** @public */\n get currentTimeMs() {\n return this.currentTime * 1000;\n }\n\n /**\n * The time the user last requested via seek/scrub.\n * Preview systems should use this instead of currentTimeMs to avoid\n * seeing intermediate times during batch operations (thumbnails, export).\n * @public\n */\n get userTimeMs(): number {\n return this.#userTimeMs;\n }\n\n /**\n * Seek to a specific time and wait for all frames to be ready.\n * This is the recommended way to seek in tests and programmatic control.\n *\n * Combines seeking (Purpose 3) with frame rendering (Purpose 4) to ensure\n * all visible elements are ready after the seek completes.\n * \n * Updates both the source time AND userTimeMs (what the preview displays).\n *\n * @param timeMs - Time in milliseconds to seek to\n * @returns Promise that resolves when the seek is complete and all visible children are ready\n * @public\n */\n async seek(timeMs: number): Promise<void> {\n // Update user time - this is what the preview should display\n this.#userTimeMs = timeMs;\n \n // Execute seek (Purpose 3)\n this.currentTimeMs = timeMs;\n await this.seekTask.taskComplete;\n\n // Handle localStorage when playbackController delegates seek\n if (this.playbackController) {\n this.saveTimeToLocalStorage(this.currentTime);\n }\n\n // Wait for frame rendering (Purpose 4)\n await this.frameTask.taskComplete;\n\n // Ensure all visible elements have completed their reactive update cycles AND frame rendering\n // waitForFrameTasks() calls frameTask.run() on children, but this may happen before child\n // elements have processed property changes from requestUpdate(). To ensure frame data is\n // accurate, we wait for updateComplete first, then ensure the frameTask has run with the\n // updated properties. Elements like EFVideo provide waitForFrameReady() for this pattern.\n const visibleElements = this.#evaluateVisibleElementsForFrame();\n\n await Promise.all(\n visibleElements.map(async (element) => {\n if (\n \"waitForFrameReady\" in element &&\n typeof element.waitForFrameReady === \"function\"\n ) {\n await (element as any).waitForFrameReady();\n } else {\n await element.updateComplete;\n }\n }),\n );\n }\n\n /**\n * Optimized seek for render loops.\n * Unlike `seek()`, this:\n * - Skips waitForMediaDurations (already loaded at render setup)\n * - Skips localStorage persistence\n * - Uses FrameController for centralized element coordination\n * \n * Still waits for all content to be ready (Lit updates, element preparation, rendering).\n * \n * @param timeMs - Time in milliseconds to seek to\n * @internal\n */\n async seekForRender(timeMs: number): Promise<void> {\n // Set time directly (skip seekTask overhead)\n const newTime = timeMs / 1000;\n this.#userTimeMs = timeMs;\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n \n // First await: let Lit propagate time to children\n await this.updateComplete;\n \n // Collect all LitElement descendants (not just those with frameTask)\n // This ensures ef-text, ef-captions, and other reactive elements update\n const allLitElements = this.#getAllLitElementDescendants();\n \n // Wait for ALL LitElement descendants to complete their reactive updates\n // This is critical for elements like ef-text and ef-captions that don't have frameTask\n await Promise.all(allLitElements.map((el) => el.updateComplete));\n \n // Wait for ef-text elements to have their segments ready\n // ef-text creates segments asynchronously via requestAnimationFrame\n const textElements = allLitElements.filter((el) => el.tagName === \"EF-TEXT\");\n if (textElements.length > 0) {\n await Promise.all(\n textElements.map((el) => {\n if (\"whenSegmentsReady\" in el && typeof el.whenSegmentsReady === \"function\") {\n return (el as any).whenSegmentsReady();\n }\n return Promise.resolve();\n }),\n );\n }\n \n // Use FrameController for centralized element coordination\n // This replaces the old distributed frameTask system\n // Animation updates are handled via the onAnimationsUpdate callback\n await this.#frameController.renderFrame(timeMs, { \n waitForLitUpdate: false,\n onAnimationsUpdate: (root) => {\n updateAnimations(root as typeof this);\n // CRITICAL: Force style recalculation after updateAnimations sets animation.currentTime\n // Without this, getComputedStyle may return stale values (e.g., opacity: 0 instead of 1)\n // Accessing offsetWidth triggers synchronous style recalc\n void (root as HTMLElement).offsetWidth;\n },\n });\n \n // Execute custom frame tasks registered via addFrameTask()\n await this.#executeCustomFrameTasks();\n }\n \n /**\n * Collects all LitElement descendants recursively.\n * Used by seekForRender to ensure all reactive elements have updated.\n * Optimized to filter by temporal visibility and skip hidden subtrees.\n */\n #getAllLitElementDescendants(): LitElement[] {\n const result: LitElement[] = [];\n const currentTimeMs = this.currentTimeMs;\n let hiddenSubtreesSkipped = 0;\n let temporallyFilteredOut = 0;\n let totalLitElements = 0;\n \n const walk = (el: Element) => {\n // OPTIMIZATION: Check inline style.display first (no getComputedStyle call)\n if (el instanceof HTMLElement && el.style.display === 'none') {\n hiddenSubtreesSkipped++;\n return; // Fast path - skip without expensive getComputedStyle\n }\n \n // Slow path: only if inline style doesn't indicate hidden\n if (el instanceof HTMLElement) {\n const style = getComputedStyle(el);\n if (style.display === \"none\" || style.visibility === \"hidden\") {\n hiddenSubtreesSkipped++;\n return; // Don't walk children\n }\n }\n \n for (const child of el.children) {\n if (child instanceof LitElement) {\n totalLitElements++;\n // Check temporal visibility for temporal elements\n if (\"startTimeMs\" in child && \"endTimeMs\" in child) {\n const startMs = (child as any).startTimeMs ?? -Infinity;\n const endMs = (child as any).endTimeMs ?? Infinity;\n if (currentTimeMs >= startMs && currentTimeMs <= endMs) {\n result.push(child);\n } else {\n temporallyFilteredOut++;\n }\n } else {\n // Non-temporal elements always included\n result.push(child);\n }\n }\n walk(child);\n }\n };\n walk(this);\n \n return result;\n }\n\n /**\n * Determines if this is a root timegroup (no parent timegroups)\n * @public\n */\n get isRootTimegroup(): boolean {\n return !this.parentTimegroup;\n }\n\n /**\n * Property-based frame task callback for React integration.\n * When set, automatically registers the callback as a frame task.\n * Setting a new value automatically cleans up the previous callback.\n * Set to null or undefined to remove the callback.\n *\n * @example\n * // React usage:\n * <Timegroup onFrame={({ ownCurrentTimeMs, percentComplete }) => {\n * // Per-frame updates\n * }} />\n *\n * @public\n */\n get onFrame(): FrameTaskCallback | null {\n return this.#onFrameCallback;\n }\n\n set onFrame(callback: FrameTaskCallback | null | undefined) {\n // Clean up previous callback if exists\n if (this.#onFrameCleanup) {\n this.#onFrameCleanup();\n this.#onFrameCleanup = null;\n }\n this.#onFrameCallback = callback ?? null;\n\n // Register new callback if provided\n if (callback) {\n this.#onFrameCleanup = this.addFrameTask(callback);\n }\n }\n\n /**\n * Register a custom frame task callback that will be executed during frame rendering.\n * The callback receives timing information and can be async or sync.\n * Multiple callbacks can be registered and will execute in parallel.\n *\n * @param callback - Function to execute on each frame\n * @returns A cleanup function that removes the callback when called\n * @public\n */\n addFrameTask(callback: FrameTaskCallback): () => void {\n if (typeof callback !== \"function\") {\n throw new Error(\"Frame task callback must be a function\");\n }\n this.#customFrameTasks.add(callback);\n return () => {\n this.#customFrameTasks.delete(callback);\n };\n }\n\n /**\n * Remove a previously registered custom frame task callback.\n *\n * @param callback - The callback function to remove\n * @public\n */\n removeFrameTask(callback: FrameTaskCallback): void {\n this.#customFrameTasks.delete(callback);\n }\n\n /** @internal */\n saveTimeToLocalStorage(time: number) {\n try {\n if (this.id && this.isConnected && !Number.isNaN(time)) {\n localStorage.setItem(this.storageKey, time.toString());\n }\n } catch (error) {\n log(\"Failed to save time to localStorage\", error);\n }\n }\n\n render() {\n return html`<slot @slotchange=${this.#handleSlotChange}></slot> `;\n }\n\n #handleSlotChange = () => {\n // Invalidate caches when slot content changes\n resetTemporalCache();\n flushSequenceDurationCache();\n flushStartTimeMsCache();\n\n // Request update to trigger recalculation of dependent properties\n this.requestUpdate();\n };\n\n /** @internal */\n loadTimeFromLocalStorage(): number | undefined {\n if (this.id) {\n try {\n const storedValue = localStorage.getItem(this.storageKey);\n if (storedValue === null) {\n return undefined;\n }\n const parsedValue = Number.parseFloat(storedValue);\n // Guard against NaN and Infinity which could cause issues\n if (Number.isNaN(parsedValue) || !Number.isFinite(parsedValue)) {\n return undefined;\n }\n return parsedValue;\n } catch (error) {\n log(\"Failed to load time from localStorage\", error);\n }\n }\n return undefined;\n }\n\n connectedCallback() {\n \n // CRITICAL: super.connectedCallback() MUST be synchronous for Lit lifecycle to work correctly.\n // Deferring it breaks render clones because updateComplete resolves before Lit initializes.\n // \n // EFTemporal.connectedCallback() handles root detection after Lit Context propagates:\n // - Schedules updateComplete.then(didBecomeRoot check)\n // - Only true roots (no parent after context) create PlaybackController\n // \n // PlaybackController.hostConnected() owns ALL root initialization:\n // - waitForMediaDurations\n // - localStorage time restoration \n // - initial seek\n //\n // This avoids the previous race conditions where both EFTimegroup.connectedCallback\n // and PlaybackController.hostConnected tried to initialize, causing concurrent seeks.\n super.connectedCallback();\n\n // Run initializer after element is fully connected and Lit has updated\n // This ensures the element is in a stable state before user code runs\n this.updateComplete.then(() => {\n this.#runInitializer();\n });\n\n // Defer TimegroupController creation and workbench wrapping to next frame\n // These operations involve DOM queries (closest, getBoundingClientRect) which\n // can be expensive when many elements initialize simultaneously\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n if (this.parentTimegroup) {\n new TimegroupController(this.parentTimegroup, this);\n }\n\n\n if (this.shouldWrapWithWorkbench()) {\n this.wrapWithWorkbench();\n }\n });\n });\n }\n \n /**\n * Called when this timegroup becomes a root (no parent timegroup).\n * Sets up the playback listener after PlaybackController is created.\n * @internal\n */\n didBecomeRoot() {\n super.didBecomeRoot();\n this.#setupPlaybackListener();\n }\n\n /**\n * Setup listener on playbackController to sync userTimeMs during playback.\n */\n #setupPlaybackListener(): void {\n // Already setup or no controller\n if (this.#playbackListener || !this.playbackController) return;\n \n this.#playbackListener = (event: PlaybackControllerUpdateEvent) => {\n // Only update userTimeMs during playback time changes\n // Clone-timeline: captures use separate clones, so Prime-timeline updates freely\n if (event.property === \"currentTimeMs\" && typeof event.value === \"number\") {\n if (this.playing) {\n this.#userTimeMs = event.value;\n }\n }\n };\n \n this.playbackController.addListener(this.#playbackListener);\n }\n \n /**\n * Remove playback listener on disconnect.\n */\n #removePlaybackListener(): void {\n if (this.#playbackListener && this.playbackController) {\n this.playbackController.removeListener(this.#playbackListener);\n }\n this.#playbackListener = null;\n }\n\n #previousDurationMs = 0;\n\n protected updated(changedProperties: PropertyValues): void {\n super.updated(changedProperties);\n\n if (changedProperties.has(\"mode\") || changedProperties.has(\"overlapMs\")) {\n durationCache.delete(this);\n }\n\n if (this.#previousDurationMs !== this.durationMs) {\n this.#previousDurationMs = this.durationMs;\n this.#runThrottledFrameTask();\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.#resizeObserver?.disconnect();\n this.#removePlaybackListener();\n }\n\n /**\n * Capture the timegroup at a specific timestamp as a canvas.\n * Does NOT modify currentTimeMs - captures are rendered independently.\n * \n * @param options - Capture options including timeMs, scale, contentReadyMode\n * @returns Promise resolving to an HTMLCanvasElement with the captured frame\n * @public\n */\n async captureAtTime(options: CaptureOptions): Promise<HTMLCanvasElement> {\n return captureTimegroupAtTime(this, options);\n }\n\n /**\n * Capture multiple timestamps as canvas thumbnails in a single batch.\n * \n * CLONE-TIMELINE ARCHITECTURE:\n * Creates a single render clone and reuses it across all captures.\n * Prime-timeline is NEVER seeked - user can continue previewing/editing during capture.\n * \n * @param timestamps - Array of timestamps (in milliseconds) to capture\n * @param options - Capture options (scale, contentReadyMode, blockingTimeoutMs)\n * @returns Promise resolving to array of HTMLCanvasElements\n * @public\n */\n async captureBatch(\n timestamps: number[],\n options: CaptureBatchOptions = {},\n ): Promise<HTMLCanvasElement[]> {\n if (timestamps.length === 0) return [];\n\n const {\n scale = 0.25,\n contentReadyMode = \"immediate\",\n blockingTimeoutMs = 5000,\n } = options;\n\n const batchStartTime = performance.now();\n\n // CLONE-TIMELINE: Create ONE clone and reuse across all captures\n const cloneStartTime = performance.now();\n const { clone: renderClone, container: renderContainer, cleanup: cleanupRenderClone } =\n await this.createRenderClone();\n const cloneTime = performance.now() - cloneStartTime;\n\n // Pre-fetch scrub segments for all video elements to ensure fast seeks\n const prefetchStartTime = performance.now();\n const videoElements = renderClone.querySelectorAll(\"ef-video\");\n if (videoElements.length > 0) {\n await Promise.all(\n Array.from(videoElements).map((video) =>\n (video as import(\"./EFVideo.js\").EFVideo).prefetchScrubSegments(timestamps),\n ),\n );\n }\n const prefetchTime = performance.now() - prefetchStartTime;\n\n const canvases: HTMLCanvasElement[] = [];\n let totalSeekTime = 0;\n let totalCaptureTime = 0;\n\n try {\n for (let i = 0; i < timestamps.length; i++) {\n const timeMs = timestamps[i]!\n \n // Seek clone to target time using optimized seekForRender\n // (skips waitForMediaDurations, localStorage, consolidates awaits)\n const seekStart = performance.now();\n await renderClone.seekForRender(timeMs);\n totalSeekTime += performance.now() - seekStart;\n \n // Capture from the seeked clone\n const captureStart = performance.now();\n const canvas = await captureFromClone(renderClone, renderContainer, {\n scale,\n contentReadyMode,\n blockingTimeoutMs,\n originalTimegroup: this,\n });\n totalCaptureTime += performance.now() - captureStart;\n canvases.push(canvas);\n }\n \n return canvases;\n } finally {\n // Log timing and clean up the render clone\n const totalTime = performance.now() - batchStartTime;\n console.log(`[captureBatch] ${timestamps.length} frames: clone=${cloneTime.toFixed(0)}ms, prefetch=${prefetchTime.toFixed(0)}ms, seek=${totalSeekTime.toFixed(0)}ms, capture=${totalCaptureTime.toFixed(0)}ms, total=${totalTime.toFixed(0)}ms`);\n cleanupRenderClone();\n }\n }\n\n /**\n * Render the timegroup to an MP4 video file and trigger download.\n * Captures each frame at the specified fps, encodes using WebCodecs via\n * MediaBunny, and downloads the resulting video.\n * \n * @param options - Rendering options (fps, codec, bitrate, filename, etc.)\n * @returns Promise that resolves when video is downloaded\n * @public\n */\n async renderToVideo(options?: RenderToVideoOptions): Promise<Uint8Array | undefined> {\n return renderTimegroupToVideo(this, options);\n }\n\n /**\n * Runs the initializer function with validation for synchronous execution and time budget.\n * Only runs once per instance. Safe to call multiple times - will skip if already run.\n * @throws Error if initializer returns a Promise (async not allowed)\n * @throws Error if initializer takes more than INITIALIZER_ERROR_THRESHOLD_MS\n * @internal\n */\n #runInitializer(): void {\n // Skip if no initializer or already run\n if (!this.initializer || this.#initializerHasRun) {\n return;\n }\n \n // Mark as run before executing to prevent recursion\n this.#initializerHasRun = true;\n \n const startTime = performance.now();\n const result: unknown = this.initializer(this);\n const elapsed = performance.now() - startTime;\n \n // Check for async (Promise return) - initializers MUST be synchronous\n if (result !== undefined && result !== null && typeof (result as any).then === 'function') {\n throw new Error(\n 'Timeline initializer must be synchronous. ' +\n 'Do not return a Promise from the initializer function.'\n );\n }\n \n // Time budget enforcement - initializers run for EVERY instance\n if (elapsed > INITIALIZER_ERROR_THRESHOLD_MS) {\n throw new Error(\n `Timeline initializer took ${elapsed.toFixed(1)}ms, exceeding the ${INITIALIZER_ERROR_THRESHOLD_MS}ms limit. ` +\n 'Initializers must be fast - move expensive work outside the initializer.'\n );\n }\n \n if (elapsed > INITIALIZER_WARN_THRESHOLD_MS) {\n console.warn(\n `[ef-timegroup] Initializer took ${elapsed.toFixed(1)}ms, exceeding ${INITIALIZER_WARN_THRESHOLD_MS}ms. ` +\n 'Consider optimizing for better render performance.'\n );\n }\n }\n\n /**\n * Copy captionsData property from original to clone.\n * cloneNode() only copies attributes, not JavaScript properties.\n * captionsData is often set via JS (e.g., captionsEl.captionsData = {...}),\n * so we must manually copy it to the cloned elements.\n * @internal\n */\n #copyCaptionsData(original: Element, clone: Element): void {\n // Find matching caption elements by position (querySelectorAll returns in document order)\n const originalCaptions = original.querySelectorAll('ef-captions');\n const cloneCaptions = clone.querySelectorAll('ef-captions');\n \n for (let i = 0; i < originalCaptions.length && i < cloneCaptions.length; i++) {\n const origCap = originalCaptions[i] as any;\n const cloneCap = cloneCaptions[i] as any;\n \n // Copy captionsData if set via JS property\n if (origCap.captionsData) {\n cloneCap.captionsData = origCap.captionsData;\n }\n }\n }\n \n /**\n * Copy ef-text _textContent property from original to cloned elements.\n * This MUST be called BEFORE elements upgrade (before updateComplete)\n * because splitText() runs in connectedCallback and will clear segments\n * if _textContent is null/empty.\n * @internal\n */\n #copyTextContent(original: Element, clone: Element): void {\n const originalTexts = original.querySelectorAll('ef-text');\n const cloneTexts = clone.querySelectorAll('ef-text');\n \n for (let i = 0; i < originalTexts.length && i < cloneTexts.length; i++) {\n const origText = originalTexts[i] as any;\n const cloneText = cloneTexts[i] as any;\n \n // Copy _textContent if it exists\n // This is a private property, so we access it via any\n if (origText._textContent !== undefined) {\n cloneText._textContent = origText._textContent;\n }\n // Also copy the segments getter to ensure we can read them\n if (origText._templateElement !== undefined) {\n cloneText._templateElement = origText._templateElement;\n }\n }\n }\n \n /**\n * Copy ef-text-segment properties from original to cloned elements.\n * segmentText and other properties are set via JS, not attributes,\n * so we must manually copy them to the cloned elements.\n * @internal\n */\n async #copyTextSegmentData(original: Element, clone: Element): Promise<void> {\n // Find matching text segment elements by position\n const originalSegments = original.querySelectorAll('ef-text-segment');\n const cloneSegments = clone.querySelectorAll('ef-text-segment');\n \n const updatePromises: Promise<any>[] = [];\n \n for (let i = 0; i < originalSegments.length && i < cloneSegments.length; i++) {\n const origSeg = originalSegments[i] as any;\n const cloneSeg = cloneSegments[i] as any;\n \n // Copy all segment properties\n if (origSeg.segmentText !== undefined) {\n cloneSeg.segmentText = origSeg.segmentText;\n }\n if (origSeg.segmentIndex !== undefined) {\n cloneSeg.segmentIndex = origSeg.segmentIndex;\n }\n if (origSeg.staggerOffsetMs !== undefined) {\n cloneSeg.staggerOffsetMs = origSeg.staggerOffsetMs;\n }\n if (origSeg.segmentStartMs !== undefined) {\n cloneSeg.segmentStartMs = origSeg.segmentStartMs;\n }\n if (origSeg.segmentEndMs !== undefined) {\n cloneSeg.segmentEndMs = origSeg.segmentEndMs;\n }\n \n // Wait for Lit to render the updated segmentText to shadow DOM\n if (cloneSeg.updateComplete) {\n updatePromises.push(cloneSeg.updateComplete);\n }\n }\n \n // Wait for all segment updates to complete\n await Promise.all(updatePromises);\n }\n\n /**\n * Wait for all ef-captions elements to have their data loaded.\n * This is needed because EFCaptions is not an EFMedia, so waitForMediaDurations doesn't cover it.\n * Used by createRenderClone to ensure captions are ready before rendering.\n * @internal\n */\n async #waitForCaptionsData(root: Element): Promise<void> {\n // Find all ef-captions elements (including nested in timegroups)\n const captionsElements = root.querySelectorAll('ef-captions');\n if (captionsElements.length === 0) return;\n \n // Wait for each caption element's data to load\n // Use duck-typing to check for loadCaptionsData method\n const waitPromises: Promise<unknown>[] = [];\n for (const el of captionsElements) {\n const captions = el as any;\n // Try new async method first\n if (typeof captions.loadCaptionsData === 'function') {\n waitPromises.push(captions.loadCaptionsData().catch(() => {}));\n }\n // Fallback to task if present\n else if (captions.unifiedCaptionsDataTask?.taskComplete) {\n waitPromises.push(captions.unifiedCaptionsDataTask.taskComplete.catch(() => {}));\n }\n }\n \n if (waitPromises.length > 0) {\n await Promise.all(waitPromises);\n }\n }\n\n /**\n * Copies initializers from original timegroup tree to cloned timegroup tree.\n * Handles both the root timegroup and all nested timegroups recursively.\n * @internal\n */\n async #copyInitializersToClone(original: EFTimegroup, clone: EFTimegroup): Promise<void> {\n const initializerPromises: Promise<void>[] = [];\n \n // Copy initializer from this level\n if (original.initializer) {\n clone.initializer = original.initializer;\n if (clone.initializerComplete) {\n initializerPromises.push(clone.initializerComplete);\n }\n }\n \n // Find all nested timegroups in both original and clone\n const originalNested = Array.from(original.querySelectorAll('ef-timegroup')) as EFTimegroup[];\n const cloneNested = Array.from(clone.querySelectorAll('ef-timegroup')) as EFTimegroup[];\n \n // Match up nested timegroups by index (they should correspond 1:1)\n for (let i = 0; i < originalNested.length && i < cloneNested.length; i++) {\n const origNested = originalNested[i];\n const cloneNestedItem = cloneNested[i];\n \n if (origNested.initializer) {\n cloneNestedItem.initializer = origNested.initializer;\n if (cloneNestedItem.initializerComplete) {\n initializerPromises.push(cloneNestedItem.initializerComplete);\n }\n }\n }\n \n // Wait for all initializers to complete\n if (initializerPromises.length > 0) {\n await Promise.all(initializerPromises);\n }\n }\n\n /**\n * Create an independent clone of this timegroup for rendering.\n * The clone is a fully functional ef-timegroup with its own animations\n * and time state, isolated from the original (Prime-timeline).\n * \n * OPTIONAL: An initializer can be set via `timegroup.initializer = (tg) => { ... }`\n * to re-run JavaScript setup (frame callbacks, React components) on each clone.\n * \n * This enables:\n * - Rendering without affecting user's preview position\n * - Concurrent renders with different clones\n * - Re-running JavaScript setup on each clone (if initializer is provided)\n * \n * @returns Promise resolving to clone, container, and cleanup function\n * @throws Error if initializer is async or takes too long\n * @public\n */\n async createRenderClone(): Promise<RenderCloneResult> {\n // 1. Create offscreen container positioned off-screen but in the DOM\n // The clone needs to be in the DOM for:\n // - Custom elements to upgrade (connectedCallback)\n // - CSS to compute correctly\n // - Animations to work\n const container = document.createElement(\"div\");\n container.className = \"ef-render-clone-container\";\n container.style.cssText = `\n position: fixed;\n left: -9999px;\n top: 0;\n width: ${this.offsetWidth || 1920}px;\n height: ${this.offsetHeight || 1080}px;\n pointer-events: none;\n overflow: hidden;\n `;\n \n // 2. Deep clone the DOM - this clones the entire subtree\n const cloneEl = this.cloneNode(true) as EFTimegroup;\n \n // CRITICAL: Clear the clone's id to prevent localStorage conflicts\n // The original timegroup and clone would share the same localStorage key otherwise,\n // which causes time to be loaded from storage during connectedCallback.\n cloneEl.removeAttribute(\"id\");\n \n // 2b. Copy JavaScript properties that aren't cloned by cloneNode()\n this.#copyCaptionsData(this, cloneEl);\n \n // 2c. Copy ef-text _textContent BEFORE elements upgrade\n // This is critical because splitText() runs in connectedCallback and will\n // clear segments if _textContent is empty\n this.#copyTextContent(this, cloneEl);\n \n // Note: We'll copy the initializer AFTER the clone is connected to DOM\n // so that the setter's isConnected check passes and schedules execution\n \n // 3. Preserve ef-configuration context for the clone\n // Media elements use closest(\"ef-configuration\") to determine settings like media-engine.\n // Without this, clones would lose configuration and use wrong media engine types.\n const originalConfig = this.closest(\"ef-configuration\");\n if (originalConfig) {\n // Shallow clone the configuration element (just the element, not children)\n const configClone = originalConfig.cloneNode(false) as HTMLElement;\n configClone.appendChild(cloneEl);\n container.appendChild(configClone);\n } else {\n container.appendChild(cloneEl);\n }\n \n document.body.appendChild(container);\n \n // 3. Wait for custom elements to upgrade\n await cloneEl.updateComplete;\n \n // 3a. Copy initializers from this timegroup and all nested timegroups\n // The initializer setter only schedules execution if this.isConnected is true\n // Setting it before connection would skip the automatic scheduling\n await this.#copyInitializersToClone(this, cloneEl);\n \n // 3b. Copy ef-text-segment properties AFTER elements have upgraded\n // segmentText is a JS property, so we need to wait for custom elements to define it\n // Wait for segments to update their shadow DOM with the copied text\n await this.#copyTextSegmentData(this, cloneEl);\n \n // 4. Initializer has already run via connectedCallback (scheduled in updateComplete)\n // No need to call it manually - it runs automatically when the clone was appended to DOM.\n // NOTE: For React, the initializer may REPLACE cloneEl with a fresh React-rendered tree,\n // so we need to find the actual timegroup element in the container after this point.\n \n // 5. Find the actual timegroup after initializer runs\n // React initializers replace the cloned DOM with a fresh render, so we need to find\n // the actual ef-timegroup in the container (may be different from cloneEl)\n let actualClone = container.querySelector('ef-timegroup') as EFTimegroup;\n if (!actualClone) {\n throw new Error(\n 'No ef-timegroup found after initializer. ' +\n 'Ensure your initializer renders a Timegroup (React) or does not remove the cloned element (vanilla JS).'\n );\n }\n \n // 6. Wait for custom elements to upgrade\n // React renders DOM synchronously via flushSync, but custom elements upgrade asynchronously.\n // We need to ensure the element has been upgraded to its class before accessing Lit properties.\n await customElements.whenDefined('ef-timegroup');\n \n // Force upgrade of all custom elements in the container (in case they haven't upgraded yet)\n customElements.upgrade(container);\n \n // Re-query in case the element reference changed during upgrade\n actualClone = container.querySelector('ef-timegroup') as EFTimegroup;\n if (!actualClone) {\n throw new Error('ef-timegroup element lost after upgrade');\n }\n \n // 7. Wait for LitElement updates and media durations\n await actualClone.updateComplete;\n \n // 7a. Copy ef-text-segment properties from original to actualClone\n // IMPORTANT: This must happen AFTER the initializer runs (which may replace the DOM for React)\n // segmentText is a JS property that needs to be copied, then we wait for shadow DOM updates\n await this.#copyTextSegmentData(this, actualClone);\n \n // 7b. CRITICAL: Manually set up parent-child relationships for cloned elements\n // Lit Context doesn't automatically propagate to cloned children because the\n // context consumer decorator runs before the element is in the DOM tree.\n // We need to explicitly walk the tree and set parentTimegroup/rootTimegroup on each element.\n const setupParentChildRelationships = (parent: EFTimegroup, root: EFTimegroup) => {\n for (const child of parent.children) {\n // Handle nested timegroups\n if (child.tagName === 'EF-TIMEGROUP') {\n const childTG = child as EFTimegroup;\n // Use the public setter to trigger proper initialization\n childTG.parentTimegroup = parent;\n childTG.rootTimegroup = root;\n // Lock to prevent Lit Context from overriding our manually set root\n (childTG as any).lockRootTimegroup();\n // Recursively process children\n setupParentChildRelationships(childTG, root);\n }\n // Handle temporal elements (videos, audio, captions, etc.)\n else if ('parentTimegroup' in child && 'rootTimegroup' in child) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n temporal.parentTimegroup = parent;\n temporal.rootTimegroup = root;\n // Lock to prevent Lit Context from overriding our manually set root\n if ('lockRootTimegroup' in temporal && typeof temporal.lockRootTimegroup === 'function') {\n temporal.lockRootTimegroup();\n }\n }\n // Recursively check non-timegroup containers (divs, etc.)\n else if (child instanceof Element) {\n setupParentChildRelationshipsInContainer(child, parent, root);\n }\n }\n };\n \n const setupParentChildRelationshipsInContainer = (container: Element, nearestParentTG: EFTimegroup, root: EFTimegroup) => {\n for (const child of container.children) {\n if (child.tagName === 'EF-TIMEGROUP') {\n const childTG = child as EFTimegroup;\n childTG.parentTimegroup = nearestParentTG;\n childTG.rootTimegroup = root;\n (childTG as any).lockRootTimegroup();\n setupParentChildRelationships(childTG, root);\n } else if ('parentTimegroup' in child && 'rootTimegroup' in child) {\n const temporal = child as TemporalMixinInterface & HTMLElement;\n temporal.parentTimegroup = nearestParentTG;\n temporal.rootTimegroup = root;\n if ('lockRootTimegroup' in temporal && typeof temporal.lockRootTimegroup === 'function') {\n temporal.lockRootTimegroup();\n }\n } else if (child instanceof Element) {\n setupParentChildRelationshipsInContainer(child, nearestParentTG, root);\n }\n }\n };\n \n // Set up the root clone itself (it's its own root, no parent)\n // Note: This must happen BEFORE lockRootTimegroup to set the correct value\n actualClone.rootTimegroup = actualClone;\n setupParentChildRelationships(actualClone, actualClone);\n \n // Wait for updates to propagate\n await actualClone.updateComplete;\n \n // CRITICAL: Lock AFTER all updates are complete to prevent further context changes\n // Also re-set rootTimegroup in case Lit Context overwrote it during updates\n actualClone.rootTimegroup = actualClone;\n (actualClone as any).lockRootTimegroup();\n const finalizeRootTimegroup = (el: Element) => {\n if ('rootTimegroup' in el && 'lockRootTimegroup' in el) {\n (el as any).rootTimegroup = actualClone;\n (el as any).lockRootTimegroup();\n }\n for (const child of el.children) {\n finalizeRootTimegroup(child);\n }\n };\n finalizeRootTimegroup(actualClone);\n \n await actualClone.waitForMediaDurations();\n \n // 7b. Wait for captions data to load (ef-captions elements need to fetch their data)\n // This is separate from waitForMediaDurations because EFCaptions is not an EFMedia.\n await this.#waitForCaptionsData(actualClone);\n \n // 8. CRITICAL: Remove PlaybackController from clone\n // Clones get a PlaybackController when they become root (in didBecomeRoot callback).\n // But render clones need direct seeking without the UI/context machinery.\n // Remove it to enable direct seekTask execution.\n if (actualClone.playbackController) {\n actualClone.playbackController.remove();\n actualClone.playbackController = undefined;\n }\n \n // 9. Initial seek to frame 0 to ensure animations are at correct state\n await actualClone.seek(0);\n \n return {\n clone: actualClone,\n container,\n cleanup: () => {\n // Remove container from DOM immediately\n container.remove();\n \n // Unmount React root if present (set by TimelineRoot component)\n // Defer to next microtask to avoid \"unmount during render\" warning\n // when cleanup is called rapidly during batch operations\n const reactRoot = (actualClone as any)._reactRoot;\n if (reactRoot) {\n queueMicrotask(() => {\n reactRoot.unmount();\n });\n }\n },\n };\n }\n\n /** @internal */\n get storageKey() {\n if (!this.id) {\n throw new Error(\"Timegroup must have an id to use localStorage.\");\n }\n return `ef-timegroup-${this.id}`;\n }\n\n /** @internal */\n get intrinsicDurationMs() {\n if (this.hasExplicitDuration) {\n return this.explicitDurationMs;\n }\n return undefined;\n }\n\n /** @internal */\n get hasOwnDuration() {\n return hasOwnDurationForMode(this.mode, this.hasExplicitDuration);\n }\n\n // ============================================================================\n // Purpose 1: Composition Rules Implementation\n // ============================================================================\n\n /** @public */\n get durationMs(): number {\n // Fixed mode delegates to parent class durationMs which handles trimming, source in/out, etc.\n if (this.mode === \"fixed\") {\n return super.durationMs;\n }\n\n // Evaluate duration semantics based on mode (Purpose 1)\n // childTemporals returns TemporalMixinInterface[], but we need HTMLElement intersection\n const childTemporalsAsElements = this.childTemporals as Array<\n TemporalMixinInterface & HTMLElement\n >;\n return evaluateDurationForMode(this, this.mode, childTemporalsAsElements);\n }\n\n // ============================================================================\n // Purpose 4: Frame Rendering - What Happens Each Frame\n // ============================================================================\n\n /**\n * Evaluates which elements should be rendered in the current frame.\n * Filters to only include temporally visible elements for frame processing.\n * Uses animation-friendly visibility to prevent animation jumps at exact boundaries.\n */\n #evaluateVisibleElementsForFrame(): Array<\n TemporalMixinInterface & HTMLElement\n > {\n const temporalElements = deepGetElementsWithFrameTasks(this);\n return temporalElements.filter((element) => {\n const animationState = evaluateAnimationVisibilityState(element);\n return animationState.isVisible;\n });\n }\n\n /** @internal */\n async waitForFrameTasks(signal?: AbortSignal) {\n const result = await withSpan(\n \"timegroup.waitForFrameTasks\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // Check abort before starting\n signal?.throwIfAborted();\n \n const innerStart = performance.now();\n\n const temporalElements = deepGetElementsWithFrameTasks(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"temporalElementsCount\", temporalElements.length);\n }\n\n // Check abort after getting elements\n signal?.throwIfAborted();\n\n // Evaluate which elements should be rendered\n const visibleElements = this.#evaluateVisibleElementsForFrame();\n if (isTracingEnabled()) {\n span.setAttribute(\"visibleElementsCount\", visibleElements.length);\n }\n\n const promiseStart = performance.now();\n\n // Execute frame tasks for all visible elements\n // CRITICAL: Must wait for updateComplete before running frameTask so that\n // property changes (like desiredSeekTimeMs) have propagated to the element.\n // Elements with waitForFrameReady() handle this; others need explicit waiting.\n await Promise.all(\n visibleElements.map(async (element) => {\n // Check abort before each element\n signal?.throwIfAborted();\n \n try {\n if (\n \"waitForFrameReady\" in element &&\n typeof element.waitForFrameReady === \"function\"\n ) {\n await (element as any).waitForFrameReady();\n } else {\n await element.updateComplete;\n await element.frameTask.run();\n }\n } catch (error) {\n // AbortErrors are expected when elements are disconnected or tasks cancelled\n const isAbortError = \n error instanceof DOMException && error.name === \"AbortError\" ||\n error instanceof Error && (\n error.name === \"AbortError\" ||\n (error as any).message?.includes(\"signal is aborted\") ||\n (error as any).message?.includes(\"The user aborted a request\")\n );\n \n if (isAbortError) {\n // Re-throw if our signal is also aborted (propagate cancellation)\n signal?.throwIfAborted();\n return; // Otherwise silently ignore - element was disconnected\n }\n throw error;\n }\n }),\n );\n const promiseEnd = performance.now();\n\n const innerEnd = performance.now();\n if (isTracingEnabled()) {\n span.setAttribute(\"actualInnerMs\", innerEnd - innerStart);\n span.setAttribute(\"promiseAwaitMs\", promiseEnd - promiseStart);\n }\n },\n );\n\n return result;\n }\n\n #mediaDurationsPromise: Promise<void> | undefined = undefined;\n\n /** @internal */\n async waitForMediaDurations(signal?: AbortSignal) {\n // Check abort before starting\n signal?.throwIfAborted();\n \n // Start loading media durations in background, but don't block if already in progress\n // This prevents multiple concurrent calls from creating redundant work\n if (!this.#mediaDurationsPromise) {\n this.#mediaDurationsPromise = this.#waitForMediaDurations(signal).catch(err => {\n // Re-throw AbortError to propagate cancellation\n if (err instanceof DOMException && err.name === \"AbortError\") {\n this.#mediaDurationsPromise = undefined;\n throw err;\n }\n console.error(`[EFTimegroup] waitForMediaDurations failed for ${this.id || 'unnamed'}:`, err);\n // Clear promise on error so it can be retried\n this.#mediaDurationsPromise = undefined;\n throw err;\n });\n }\n \n // If signal is provided and aborted, throw immediately\n if (signal?.aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n \n return this.#mediaDurationsPromise;\n }\n\n /**\n * Wait for all media elements to load their initial segments.\n * Ideally we would only need the extracted index json data, but\n * that caused issues with constructing audio data. We had negative durations\n * in calculations and it was not clear why.\n */\n async #waitForMediaDurations(signal?: AbortSignal) {\n return withSpan(\n \"timegroup.waitForMediaDurations\",\n {\n timegroupId: this.id || \"unknown\",\n mode: this.mode,\n },\n undefined,\n async (span) => {\n // Check abort before starting\n signal?.throwIfAborted();\n \n // Don't wait for updateComplete during initialization - it causes deadlocks with nested timegroups\n // Instead, use a short delay to let elements connect, then scan for media elements\n // If elements aren't ready yet, we'll retry or they'll be picked up on the next update cycle\n await new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n \n const abortHandler = () => {\n clearTimeout(timeoutId);\n cancelAnimationFrame(rafId2);\n cancelAnimationFrame(rafId1);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n };\n signal?.addEventListener('abort', abortHandler, { once: true });\n \n let rafId1: number;\n let rafId2: number;\n let timeoutId: ReturnType<typeof setTimeout>;\n \n // Use multiple animation frames to ensure DOM is ready, but don't wait for all children\n rafId1 = requestAnimationFrame(() => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n rafId2 = requestAnimationFrame(() => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n // Small additional delay to let custom elements upgrade\n timeoutId = setTimeout(() => {\n signal?.removeEventListener('abort', abortHandler);\n resolve();\n }, 10);\n });\n });\n });\n \n // Check abort after delay\n signal?.throwIfAborted();\n \n const mediaElements = deepGetMediaElements(this);\n if (isTracingEnabled()) {\n span.setAttribute(\"mediaElementsCount\", mediaElements.length);\n }\n\n // Check abort after getting elements\n signal?.throwIfAborted();\n\n // Then, we must await the fragmentIndexTask to ensure all media elements have their\n // fragment index loaded, which is where their duration is parsed from.\n // Use Promise.allSettled with timeout to avoid blocking if asset server is slow\n const mediaLoadStart = Date.now();\n const MEDIA_LOAD_TIMEOUT_MS = 30000; // 30 second timeout per element\n \n const loadPromises = mediaElements.map(async (m, index) => {\n // Check abort before each element\n signal?.throwIfAborted();\n \n const elementStart = Date.now();\n try {\n // Use getMediaEngine async method if available\n if (typeof m.getMediaEngine === 'function') {\n // Add timeout to prevent indefinite blocking\n const timeoutPromise = new Promise<undefined>((_, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(() => reject(new Error(`Media element ${index} load timeout after ${MEDIA_LOAD_TIMEOUT_MS}ms`)), MEDIA_LOAD_TIMEOUT_MS);\n signal?.addEventListener('abort', () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }, { once: true });\n });\n \n await Promise.race([m.getMediaEngine(signal), timeoutPromise]);\n } \n // Fallback: check status and use taskComplete\n else if (m.mediaEngineTask) {\n // Status: INITIAL=0, PENDING=1, COMPLETE=2, ERROR=3\n const status = m.mediaEngineTask.status;\n \n // Already complete or errored - no need to wait\n if (status === 2 || status === 3) {\n return;\n }\n \n // Attach .catch() to taskComplete to prevent unhandled rejection\n const taskPromise = m.mediaEngineTask.taskComplete;\n taskPromise?.catch(() => {});\n \n if (taskPromise) {\n const timeoutPromise = new Promise<undefined>((_, reject) => {\n if (signal?.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(() => reject(new Error(`Media element ${index} load timeout after ${MEDIA_LOAD_TIMEOUT_MS}ms`)), MEDIA_LOAD_TIMEOUT_MS);\n signal?.addEventListener('abort', () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }, { once: true });\n });\n \n await Promise.race([taskPromise, timeoutPromise]);\n }\n }\n } catch (error) {\n // Re-throw AbortError to propagate cancellation\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Log only if tracing is enabled to reduce console noise\n if (isTracingEnabled()) {\n const elementElapsed = Date.now() - elementStart;\n console.error(`[EFTimegroup] Media element ${index} failed after ${elementElapsed}ms:`, error);\n }\n // Don't throw - continue with other elements\n }\n });\n \n const results = await Promise.allSettled(loadPromises);\n \n // Check if any were aborted\n const aborted = results.some(r => \n r.status === 'rejected' && \n r.reason instanceof DOMException && \n r.reason.name === \"AbortError\"\n );\n if (aborted) {\n throw new DOMException(\"Aborted\", \"AbortError\");\n }\n \n // Log any failures but don't throw - we want to continue even if some media fails\n const failures = results.filter(r => r.status === 'rejected');\n if (failures.length > 0 && isTracingEnabled()) {\n const mediaLoadElapsed = Date.now() - mediaLoadStart;\n console.warn(`[EFTimegroup] ${failures.length} media elements failed to load in ${mediaLoadElapsed}ms:`, failures.map(r => r.status === 'rejected' ? r.reason : null));\n }\n\n // After waiting for durations, we must force some updates to cascade and ensure all temporal elements\n // have correct durations and start times. It is not ideal that we have to do this inside here,\n // but it is the best current way to ensure that all temporal elements have correct durations and start times.\n\n // Next, we must flush the startTimeMs cache to ensure all media elements have their\n // startTimeMs parsed fresh, otherwise the startTimeMs is cached per animation frame.\n flushStartTimeMsCache();\n\n // Flush duration cache since child durations may have changed\n flushSequenceDurationCache();\n\n // Request an update to the currentTime of this group, ensuring that time updates will cascade\n // down to children, forcing sequence groups to arrange correctly.\n // This also makes the filmstrip update correctly.\n // Defer using setTimeout(0) to avoid Lit warning about scheduling updates after update completed.\n // This method can be called during a task or after an update cycle completes, and using\n // setTimeout ensures we're completely outside any Lit update cycle.\n setTimeout(() => this.requestUpdate(\"currentTime\"), 0);\n // Note: We don't await updateComplete here during initialization to avoid deadlocks.\n // The update will complete asynchronously, and sequence groups will arrange correctly\n // once all timegroups have finished initializing. During normal operation (seeks, etc.),\n // the caller will wait for updateComplete explicitly if needed.\n },\n );\n }\n\n /** @internal */\n get childTemporals() {\n return shallowGetTemporalElements(this);\n }\n\n get #contextProvider() {\n let parent = this.parentNode;\n while (parent) {\n if (isContextMixin(parent)) {\n return parent;\n }\n parent = parent.parentNode;\n }\n return null;\n }\n\n /**\n * Returns true if the timegroup should be wrapped with a workbench.\n *\n * A timegroup should be wrapped with a workbench if:\n * - It's being rendered (EF_RENDERING), OR\n * - The workbench property is set to true\n *\n * If the timegroup is already wrapped in a context provider like ef-preview,\n * it should NOT be wrapped in a workbench.\n * @internal\n */\n shouldWrapWithWorkbench() {\n // Only root timegroups should wrap with workbench\n if (!this.isRootTimegroup) {\n return false;\n }\n\n // Never wrap with workbench when inside a canvas\n // Canvas manages its own layout and coordinate system\n if (this.closest(\"ef-canvas\") !== null) {\n return false;\n }\n\n // Never wrap if already inside preview, workbench, or preview context\n if (\n this.closest(\"ef-preview\") !== null ||\n this.closest(\"ef-workbench\") !== null ||\n this.closest(\"ef-preview-context\") !== null\n ) {\n return false;\n }\n\n // Skip wrapping in test contexts\n if (this.closest(\"test-context\") !== null) {\n return false;\n }\n\n // Never wrap render clones (they're in an offscreen container for capture operations)\n if (this.closest(\".ef-render-clone-container\") !== null) {\n return false;\n }\n\n // During rendering, always wrap with workbench (needed by EF_FRAMEGEN)\n const isRendering = EF_RENDERING?.() === true;\n if (isRendering) {\n return true;\n }\n\n // Respect the explicit workbench property\n return this.workbench;\n }\n\n /** @internal */\n wrapWithWorkbench() {\n const workbench = document.createElement(\"ef-workbench\");\n const parent = this.parentElement;\n \n // Only apply viewport sizing when the workbench will be a direct child of body.\n // This ensures the workbench fills the screen regardless of the document's CSS\n // setup (e.g., missing `html, body { height: 100% }`).\n // When embedded in another container, use default sizing and let the container\n // control the workbench dimensions.\n if (parent === document.body) {\n workbench.style.position = \"fixed\";\n workbench.style.top = \"0\";\n workbench.style.left = \"0\";\n workbench.style.width = \"100vw\";\n workbench.style.height = \"100vh\";\n workbench.style.zIndex = \"0\";\n }\n \n parent?.append(workbench);\n if (!this.hasAttribute(\"id\")) {\n this.setAttribute(\"id\", \"root-timegroup\");\n }\n\n // Create pan-zoom for selection overlay support\n // Must be in light DOM so canvas can find it via closest()\n const panZoom = document.createElement(\"ef-pan-zoom\");\n panZoom.id = \"workbench-panzoom\";\n panZoom.setAttribute(\"slot\", \"canvas\");\n panZoom.setAttribute(\"auto-fit\", \"\"); // Fit content to view on first render\n panZoom.style.width = \"100%\";\n panZoom.style.height = \"100%\";\n\n // Create canvas wrapper for selection/highlighting support\n // Get dimensions from the timegroup for explicit canvas sizing\n const rect = this.getBoundingClientRect();\n const canvas = document.createElement(\"ef-canvas\");\n canvas.id = \"workbench-canvas\";\n // Use explicit dimensions (required for selection bounds calculation)\n canvas.style.width = `${Math.max(rect.width, 1920)}px`;\n canvas.style.height = `${Math.max(rect.height, 1080)}px`;\n canvas.style.display = \"block\";\n\n // Move timegroup into canvas, canvas into pan-zoom\n canvas.append(this as unknown as Element);\n panZoom.append(canvas);\n workbench.append(panZoom);\n\n // Add hierarchy panel - targets canvas for selection support\n const hierarchy = document.createElement(\"ef-hierarchy\");\n hierarchy.setAttribute(\"slot\", \"hierarchy\");\n hierarchy.setAttribute(\"target\", \"workbench-canvas\");\n hierarchy.setAttribute(\"header\", \"Scenes\");\n workbench.append(hierarchy);\n\n // Add filmstrip/timeline - targets timegroup for playback\n const filmstrip = document.createElement(\"ef-filmstrip\");\n filmstrip.setAttribute(\"slot\", \"timeline\");\n filmstrip.setAttribute(\"target\", this.id);\n workbench.append(filmstrip);\n }\n\n get #efElements() {\n return Array.from(\n this.querySelectorAll(\n \"ef-audio, ef-video, ef-image, ef-captions, ef-waveform\",\n ),\n );\n }\n\n /**\n * Returns media elements for playback audio rendering\n * For standalone media, returns [this]; for timegroups, returns all descendants\n * Used by PlaybackController for audio-driven playback\n * @internal\n */\n getMediaElements(): EFMedia[] {\n return deepGetMediaElements(this);\n }\n\n /**\n * Render audio buffer for playback\n * Called by PlaybackController during live playback\n * Delegates to shared renderTemporalAudio utility for consistent behavior\n * @internal\n */\n async renderAudio(fromMs: number, toMs: number, signal?: AbortSignal): Promise<AudioBuffer> {\n return renderTemporalAudio(this, fromMs, toMs, signal);\n }\n\n /**\n * TEMPORARY TEST METHOD: Renders audio and immediately plays it back\n * Usage: timegroup.testPlayAudio(0, 5000) // Play first 5 seconds\n */\n async #testPlayAudio(fromMs: number, toMs: number) {\n // Render the audio using the existing renderAudio method\n const renderedBuffer = await this.renderAudio(fromMs, toMs);\n\n // Create a regular AudioContext for playback\n const playbackContext = new AudioContext();\n\n // Create a buffer source and connect it\n const bufferSource = playbackContext.createBufferSource();\n bufferSource.buffer = renderedBuffer;\n bufferSource.connect(playbackContext.destination);\n\n // Start playback immediately\n bufferSource.start(0);\n\n // Return a promise that resolves when playback ends\n return new Promise<void>((resolve) => {\n bufferSource.onended = () => {\n playbackContext.close();\n resolve();\n };\n });\n }\n\n async #loadMd5Sums() {\n const efElements = this.#efElements;\n const loaderTasks: Promise<any>[] = [];\n for (const el of efElements) {\n const md5SumLoader = (el as any).md5SumLoader;\n // Check for any object with run() and taskComplete\n if (md5SumLoader && typeof md5SumLoader.run === 'function') {\n // Attach .catch() to prevent unhandled rejection warning - errors handled via taskComplete\n md5SumLoader.run().catch(() => {});\n if (md5SumLoader.taskComplete) {\n loaderTasks.push(md5SumLoader.taskComplete);\n }\n }\n }\n\n await Promise.all(loaderTasks);\n\n efElements.forEach((el) => {\n if (\"productionSrc\" in el && el.productionSrc instanceof Function) {\n el.setAttribute(\"src\", el.productionSrc());\n }\n });\n }\n\n // Track frameTask execution count to detect runaway loops\n #timegroupFrameTaskCount = 0;\n #timegroupFrameTaskLastReset = Date.now();\n static readonly TIMEGROUP_FRAME_TASK_THRESHOLD = 100;\n static readonly TIMEGROUP_FRAME_TASK_RESET_MS = 1000;\n\n /** @internal */\n #frameTaskPromise: Promise<void> = Promise.resolve();\n #frameTaskAbortController: AbortController | null = null;\n \n frameTask = (() => {\n const self = this;\n const taskObj: { run(): void | Promise<void>; taskComplete: Promise<void> } = {\n run: () => {\n // Abort any in-flight task\n self.#frameTaskAbortController?.abort();\n self.#frameTaskAbortController = new AbortController();\n const signal = self.#frameTaskAbortController.signal;\n \n self.#frameTaskPromise = self.#runFrameTask(signal);\n taskObj.taskComplete = self.#frameTaskPromise;\n return self.#frameTaskPromise;\n },\n taskComplete: Promise.resolve(),\n };\n return taskObj;\n })();\n\n async #runFrameTask(signal: AbortSignal): Promise<void> {\n // Check for runaway loop\n const now = Date.now();\n if (now - this.#timegroupFrameTaskLastReset > EFTimegroup.TIMEGROUP_FRAME_TASK_RESET_MS) {\n this.#timegroupFrameTaskCount = 0;\n this.#timegroupFrameTaskLastReset = now;\n }\n this.#timegroupFrameTaskCount++;\n \n if (this.#timegroupFrameTaskCount > EFTimegroup.TIMEGROUP_FRAME_TASK_THRESHOLD) {\n // Safety break to prevent infinite loops\n return;\n }\n \n try {\n signal.throwIfAborted();\n \n if (this.isRootTimegroup) {\n // Root timegroup orchestrates frame rendering for entire tree\n // Use FrameController for centralized, unified rendering\n await withSpan(\n \"timegroup.frameTask\",\n {\n timegroupId: this.id || \"unknown\",\n ownCurrentTimeMs: this.ownCurrentTimeMs,\n currentTimeMs: this.currentTimeMs,\n },\n undefined,\n async () => {\n // Use FrameController for centralized element coordination\n // This replaces waitForFrameTasks() with the new unified rendering path\n await this.#frameController.renderFrame(this.currentTimeMs, {\n waitForLitUpdate: false, // Already in an update cycle\n onAnimationsUpdate: (root) => {\n updateAnimations(root as typeof this);\n },\n });\n signal.throwIfAborted();\n // Execute custom frame tasks registered on this timegroup\n await this.#executeCustomFrameTasks();\n },\n );\n } else {\n // Non-root timegroups execute their custom frame tasks when called\n await this.#executeCustomFrameTasks();\n }\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n return;\n }\n console.error(\"EFTimegroup frameTask error\", error);\n }\n }\n\n async #executeCustomFrameTasks() {\n if (this.#customFrameTasks.size > 0) {\n const percentComplete =\n this.durationMs > 0 ? this.ownCurrentTimeMs / this.durationMs : 0;\n const frameInfo = {\n ownCurrentTimeMs: this.ownCurrentTimeMs,\n currentTimeMs: this.currentTimeMs,\n durationMs: this.durationMs,\n percentComplete,\n element: this,\n };\n\n await Promise.all(\n Array.from(this.#customFrameTasks).map((callback) =>\n Promise.resolve(callback(frameInfo)),\n ),\n );\n }\n }\n\n /** @internal */\n #seekTaskPromise: Promise<number | undefined> = Promise.resolve(undefined);\n #seekTaskAbortController: AbortController | null = null;\n \n seekTask = (() => {\n const self = this;\n const taskObj: { run(): void | Promise<number | undefined>; taskComplete: Promise<number | undefined> } = {\n run: () => {\n // Abort any in-flight task\n self.#seekTaskAbortController?.abort();\n self.#seekTaskAbortController = new AbortController();\n const signal = self.#seekTaskAbortController.signal;\n \n const targetTime = self.#pendingSeekTime ?? self.#currentTime;\n self.#seekTaskPromise = self.#runSeekTask(targetTime, signal);\n taskObj.taskComplete = self.#seekTaskPromise;\n return self.#seekTaskPromise;\n },\n taskComplete: Promise.resolve(undefined),\n };\n return taskObj;\n })();\n\n async #runSeekTask(targetTime: number | undefined, signal: AbortSignal): Promise<number | undefined> {\n try {\n signal.throwIfAborted();\n \n // Delegate to playbackController if available\n if (this.playbackController) {\n // Wait for playbackController's seek to complete\n await this.playbackController.currentTime; // Trigger seek\n signal.throwIfAborted();\n return this.currentTime;\n }\n\n // Only root timegroups execute seek tasks\n if (!this.isRootTimegroup) {\n return undefined;\n }\n\n return await withSpan(\n \"timegroup.seekTask\",\n {\n timegroupId: this.id || \"unknown\",\n targetTime: targetTime ?? 0,\n durationMs: this.durationMs,\n },\n undefined,\n async (span) => {\n // Wait for media durations to be loaded\n try {\n await Promise.race([\n this.waitForMediaDurations(signal),\n new Promise<void>((_, reject) => {\n if (signal.aborted) {\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n return;\n }\n const timeoutId = setTimeout(() => reject(new Error('waitForMediaDurations timeout')), 10000);\n signal.addEventListener('abort', () => {\n clearTimeout(timeoutId);\n reject(new DOMException(\"Aborted\", \"AbortError\"));\n }, { once: true });\n })\n ]);\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n throw error;\n }\n // Continue with seek even if durations aren't loaded yet\n }\n\n signal.throwIfAborted();\n\n // Evaluate and apply seek target\n const newTime = evaluateSeekTarget(\n targetTime ?? 0,\n this.durationMs,\n this.effectiveFps,\n );\n if (isTracingEnabled()) {\n span.setAttribute(\"newTime\", newTime);\n }\n\n this.#currentTime = newTime;\n this.requestUpdate(\"currentTime\");\n \n await this.updateComplete;\n signal.throwIfAborted();\n\n await this.#runThrottledFrameTask();\n signal.throwIfAborted();\n\n if (!this.#restoringFromLocalStorage) {\n this.saveTimeToLocalStorage(this.#currentTime);\n }\n this.#seekInProgress = false;\n if (this.#restoringFromLocalStorage) {\n this.#restoringFromLocalStorage = false;\n }\n return newTime;\n },\n );\n } catch (error) {\n if (error instanceof DOMException && error.name === \"AbortError\") {\n return undefined;\n }\n console.error(\"EFTimegroup seekTask error\", error);\n return undefined;\n }\n }\n\n /**\n * Get container information for this timegroup.\n * Timegroups are always containers and can contain children.\n * Display mode is determined from computed styles.\n *\n * @public\n */\n getContainerInfo(): ContainerInfo {\n const info = getContainerInfoFromElement(this);\n // Timegroups are always containers and can contain children\n return {\n ...info,\n isContainer: true,\n canContainChildren: true,\n };\n }\n\n /**\n * Get position information for this timegroup.\n * Returns computed bounds, transform, and rotation.\n *\n * @public\n */\n getPositionInfo(): ElementPositionInfo | null {\n return getPositionInfoFromElement(this);\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-timegroup\": EFTimegroup & Element;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,MAAM,MAAM,MAAM,0BAA0B;AAmC5C,MAAM,iCAAiC;AACvC,MAAM,gCAAgC;AA6BtC,IAAIA,gCAA8C,IAAI,SAAS;AAE/D,MAAa,2BAA2B;AACtC,iCAAgB,IAAI,SAAS;;AAI/B,MAAa,6BAA6B;AAG1C,MAAM,gDAAgC,IAAI,SAAsB;AAIhE,MAAa,kCACX,cACY;AACZ,QACE,cAAc,UAAa,8BAA8B,IAAI,UAAU;;AAO3E,uCAAuC,+BAA+B;;;;;AAMtE,SAAS,sBACP,MACA,qBACS;AACT,QACE,SAAS,aACT,SAAS,cACR,SAAS,WAAW;;;;;;;;;AAWzB,SAAS,uCACP,OACS;AAET,KAAI,iBAAiB,eAAe,MAAM,SAAS,MACjD,QAAO;AAGT,KAAI,CAAC,MAAM,eACT,QAAO;AAET,QAAO;;;;;;AAOT,SAAS,oBAAoB,iBAAkD;AAC7E,KAAI,CAAC,gBACH,QAAO;AAET,QAAO,gBAAgB;;;;;;;AAQzB,SAAS,yBACP,WACA,gBACA,WACQ;CAER,MAAM,iBAAiB,cAAc,IAAI,UAAU;AACnD,KAAI,mBAAmB,OACrB,QAAO;CAGT,IAAI,WAAW;CACf,IAAI,qBAAqB;AACzB,gBAAe,SAAS,UAAU;AAChC,MAAI,CAAC,uCAAuC,MAAM,CAChD;AAGF,MACE,iBAAiB,eACjB,8BAA8B,IAAI,MAAM,CAExC;AAOF,MAAI,iBAAiB,aAAa;GAChC,IAAIC,WAAwB,MAAM;GAClC,IAAI,aAAa;AACjB,UAAO,UAAU;AAEf,QAAI,aAAa,UACf;AAEF,QACE,oBAAoB,eACpB,8BAA8B,IAAI,SAAS,EAC3C;AAEA,kBAAa;AACb;;AAEF,eAAW,SAAS;;AAEtB,OAAI,WACF;;AAKJ,MAAI,qBAAqB,EACvB,aAAY;AAEd,cAAY,MAAM;AAClB;GACA;AAGF,YAAW,KAAK,IAAI,GAAG,SAAS;AAGhC,eAAc,IAAI,WAAW,SAAS;AACtC,QAAO;;;;;;;AAQT,SAAS,wBACP,WACA,gBACQ;CAER,MAAM,iBAAiB,cAAc,IAAI,UAAU;AACnD,KAAI,mBAAmB,OACrB,QAAO;CAGT,IAAI,cAAc;AAClB,MAAK,MAAM,SAAS,gBAAgB;AAClC,MAAI,CAAC,uCAAuC,MAAM,CAChD;AAMF,MACE,iBAAiB,eACjB,8BAA8B,IAAI,MAAM,CAExC;AAOF,MAAI,iBAAiB,aAAa;GAChC,IAAIA,WAAwB,MAAM;GAClC,IAAI,aAAa;AACjB,UAAO,UAAU;AAEf,QAAI,aAAa,UACf;AAEF,QACE,oBAAoB,eACpB,8BAA8B,IAAI,SAAS,EAC3C;AAEA,kBAAa;AACb;;AAEF,eAAW,SAAS;;AAEtB,OAAI,WACF;;AAIJ,gBAAc,KAAK,IAAI,aAAa,MAAM,WAAW;;CAGvD,MAAM,WAAW,KAAK,IAAI,GAAG,YAAY;AAGzC,eAAc,IAAI,WAAW,SAAS;AACtC,QAAO;;;;;;;;;AAUT,SAAS,wBACP,WACA,MACA,gBACQ;AACR,SAAQ,MAAR;EACE,KAAK,MACH,QAAO,oBAAoB,UAAU,gBAAgB;EACvD,KAAK;AAEH,iCAA8B,IAAI,UAAU;AAC5C,OAAI;AACF,WAAO,yBACL,WACA,gBACA,UAAU,UACX;aACO;AAER,kCAA8B,OAAO,UAAU;;EAGnD,KAAK;AAEH,iCAA8B,IAAI,UAAU;AAC5C,OAAI;AACF,WAAO,wBAAwB,WAAW,eAAe;aACjD;AAER,kCAA8B,OAAO,UAAU;;EAGnD,QACE,OAAM,IAAI,MAAM,sBAAsB,OAAO;;;AAInD,MAAa,wBACX,SACA,SAAwB,EAAE,KACvB;AACH,MAAK,MAAM,SAAS,MAAM,KAAK,QAAQ,SAAS,CAC9C,KAAI,iBAAiB,YACnB,QAAO,KAAK,MAAM;KAElB,sBAAqB,OAAO,OAAO;AAGvC,QAAO;;;;;;AA8CT,SAAS,mBACP,eACA,YACA,KACQ;CAER,MAAM,gBAAgB,qBAAqB,eAAe,IAAI;AAE9D,QAAO,KAAK,IAAI,GAAG,KAAK,IAAI,eAAe,aAAa,IAAK,CAAC;;AAIzD,wBAAMC,sBAAoB,aAAa,WAAW,QAAQ,WAAW,CAAC,CAAC,CAA4B;;;;;;2BAkCpF;mBAIR;cAGK;mBAEL;aA2EN;kBAQK;mBASC;aA2BwB;0BAwtDjB;GACjB,MAAM,OAAO;GACb,MAAMC,UAAwE;IAC5E,WAAW;AAET,WAAKC,0BAA2B,OAAO;AACvC,WAAKA,2BAA4B,IAAI,iBAAiB;KACtD,MAAM,SAAS,MAAKA,yBAA0B;AAE9C,WAAKC,mBAAoB,MAAKC,aAAc,OAAO;AACnD,aAAQ,eAAe,MAAKD;AAC5B,YAAO,MAAKA;;IAEd,cAAc,QAAQ,SAAS;IAChC;AACD,UAAO;MACL;yBAgFc;GAChB,MAAM,OAAO;GACb,MAAME,UAAoG;IACxG,WAAW;AAET,WAAKC,yBAA0B,OAAO;AACtC,WAAKA,0BAA2B,IAAI,iBAAiB;KACrD,MAAM,SAAS,MAAKA,wBAAyB;KAE7C,MAAM,aAAa,MAAKC,mBAAoB,MAAKC;AACjD,WAAKC,kBAAmB,MAAKC,YAAa,YAAY,OAAO;AAC7D,aAAQ,eAAe,MAAKD;AAC5B,YAAO,MAAKA;;IAEd,cAAc,QAAQ,QAAQ,OAAU;IACzC;AACD,UAAO;MACL;;CA1+DJ,WAAW,qBAA+B;AAExC,SAAO;GACL,GAFuB,MAAM,sBAAsB,EAAE;GAGrD;GACA;GACA;GACA;GACA;GACA;GACA;GACD;;;gBAGa,GAAG;;;;;;;;;;;;;;;;;CA8BnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgCA,IAAI,cAAgD;AAClD,SAAO,MAAKE;;CAGd,IAAI,YAAY,IAAsC;AACpD,QAAKA,cAAe;AAIpB,MAAI,MAAM,KAAK,eAAe,CAAC,MAAKC,kBAElC,OAAKC,sBAAuB,KAAK,eAAe,WAAW;AACzD,SAAKC,gBAAiB;IACtB;;;;;;CAQN,qBAAqB;;;;;;CAOrB;;;;;;CAOA,IAAI,sBAAiD;AACnD,SAAO,MAAKD;;CAwBd,yBACE,MACA,KACA,OACM;AACN,MAAI,SAAS,UAAU,MACrB,MAAK,OAAO;AAEd,MAAI,SAAS,aAAa,MACxB,MAAK,YAAY,cAAc,MAAM;AAEvC,MAAI,SAAS,YACX,MAAK,WAAW,UAAU;AAE5B,MAAI,SAAS,SAAS,MACpB,MAAK,MAAM,OAAO,WAAW,MAAM;AAErC,MAAI,SAAS,YACX,MAAK,YAAY,UAAU;AAE7B,QAAM,yBAAyB,MAAM,KAAK,MAAM;;CAOlD;;CAGA,gBAAgB;CAEhB,eAAmC;CACnC,cAAsB;CACtB,kBAAkB;CAClB;CACA,yBAAyB;CACzB,6BAA6B;;CAG7B,8BAAuC;AACrC,SAAO,MAAKE;;;CAId,6BAA6B,OAAsB;AACjD,QAAKA,4BAA6B;;CAEpC,oCAA4C,IAAI,KAAK;CACrD,mBAA6C;CAC7C,kBAAuC;CACvC,oBAA6E;;;;;CAM7E,mBAAoC,IAAI,gBAAgB,KAAK;;;;;CAM7D,IAAI,kBAAmC;AACrC,SAAO,MAAKC;;;;;;;CAed,cAAc,SAA6B;AACzC,SAAO;GACL,kBAAkB;GAClB,SAAS;GACT,UAAU;GACX;;;;;;;CAQH,MAAM,aAAa,SAAiB,SAAqC;;;;;;;CAUzE,YAAY,SAAuB;AAIjC,MAAI,MAAKC,iBAAkB,OAAO,EAChC,OAAKC,yBAA0B,CAAC,OAAO,UAAU;AAC/C,WAAQ,MAAM,wCAAwC,MAAM;IAC5D;;;;;;;;CAUN,IAAI,eAAuB;AAEzB,MAAI,OAAO,WAAW,eAAe,OAAO,aAAa,cACvD,QAAO,OAAO,YAAY,cAAc,eAAe,MAAM;AAE/D,SAAO,KAAK;;;;;;;CAQd,IAAI,eAAuB;AACzB,SAAO,MAAKC;;;;;;;CAQd,wBAA8B;AAC5B,QAAKA;;CAGP,OAAMC,wBAAwC;AAC5C,MAAI,KAAK,mBACP,QAAO,KAAK,mBAAmB,uBAAuB;AAExD,QAAM,KAAK,UAAU,KAAK;;;CAQ5B,IACI,YAAY,MAAc;EAE5B,MAAM,aAAa,mBACjB,MACA,KAAK,YACL,KAAK,aACN;AAGD,MAAI,KAAK,oBAAoB;AAC3B,QAAK,mBAAmB,cAAc;AACtC,SAAKC,aAAc,aAAa;AAChC;;AAIF,MAAI,CAAC,KAAK,gBACR;AAIF,MAAI,OAAO,MAAM,WAAW,CAC1B;AAIF,MAAI,eAAe,MAAKb,eAAgB,CAAC,MAAKc,yBAA0B,CAAC,MAAKP,0BAC5E;AAIF,MAAI,MAAKR,oBAAqB,WAC5B;AAIF,MAAI,MAAKQ,6BAA8B,eAAe,MAAKP,aAAc;AASzE,MAAI,MAAKe,gBAAiB;AACxB,SAAKhB,kBAAmB;AACxB,SAAKC,cAAe;AACpB,SAAKa,aAAc,aAAa;AAChC;;AAIF,QAAKb,cAAe;AACpB,QAAKa,aAAc,aAAa;AAChC,QAAKE,iBAAkB;AAGvB,OAAK,SAAS,KAAK,CAAC,YAAY,GAAG,CAAC,cAAc;AAChD,SAAKA,iBAAkB;AAIvB,OACE,MAAKhB,oBAAqB,UAC1B,MAAKA,oBAAqB,YAC1B;IACA,MAAM,cAAc,MAAKA;AACzB,UAAKA,kBAAmB;AACxB,UAAKe,wBAAyB;AAC9B,QAAI;AACF,UAAK,cAAc;cACX;AACR,WAAKA,wBAAyB;;SAGhC,OAAKf,kBAAmB;IAE1B;;;CAIJ,IAAI,cAAc;AAChB,MAAI,KAAK,mBACP,QAAO,KAAK,mBAAmB;AAEjC,SAAO,MAAKC,eAAgB;;;CAI9B,IAAI,cAAc,IAAY;AAC5B,OAAK,cAAc,KAAK;;;CAI1B,IAAI,gBAAgB;AAClB,SAAO,KAAK,cAAc;;;;;;;;CAS5B,IAAI,aAAqB;AACvB,SAAO,MAAKa;;;;;;;;;;;;;;;CAgBd,MAAM,KAAK,QAA+B;AAExC,QAAKA,aAAc;AAGnB,OAAK,gBAAgB;AACrB,QAAM,KAAK,SAAS;AAGpB,MAAI,KAAK,mBACP,MAAK,uBAAuB,KAAK,YAAY;AAI/C,QAAM,KAAK,UAAU;EAOrB,MAAM,kBAAkB,MAAKG,iCAAkC;AAE/D,QAAM,QAAQ,IACZ,gBAAgB,IAAI,OAAO,YAAY;AACrC,OACE,uBAAuB,WACvB,OAAO,QAAQ,sBAAsB,WAErC,OAAO,QAAgB,mBAAmB;OAE1C,OAAM,QAAQ;IAEhB,CACH;;;;;;;;;;;;;;CAeH,MAAM,cAAc,QAA+B;EAEjD,MAAM,UAAU,SAAS;AACzB,QAAKH,aAAc;AACnB,QAAKb,cAAe;AACpB,OAAK,cAAc,cAAc;AAGjC,QAAM,KAAK;EAIX,MAAM,iBAAiB,MAAKiB,6BAA8B;AAI1D,QAAM,QAAQ,IAAI,eAAe,KAAK,OAAO,GAAG,eAAe,CAAC;EAIhE,MAAM,eAAe,eAAe,QAAQ,OAAO,GAAG,YAAY,UAAU;AAC5E,MAAI,aAAa,SAAS,EACxB,OAAM,QAAQ,IACZ,aAAa,KAAK,OAAO;AACvB,OAAI,uBAAuB,MAAM,OAAO,GAAG,sBAAsB,WAC/D,QAAQ,GAAW,mBAAmB;AAExC,UAAO,QAAQ,SAAS;IACxB,CACH;AAMH,QAAM,MAAKT,gBAAiB,YAAY,QAAQ;GAC9C,kBAAkB;GAClB,qBAAqB,SAAS;AAC5B,qBAAiB,KAAoB;AAIrC,IAAM,KAAqB;;GAE9B,CAAC;AAGF,QAAM,MAAKE,yBAA0B;;;;;;;CAQvC,+BAA6C;EAC3C,MAAMQ,SAAuB,EAAE;EAC/B,MAAM,gBAAgB,KAAK;EAC3B,IAAI,wBAAwB;EAC5B,IAAI,wBAAwB;EAC5B,IAAI,mBAAmB;EAEvB,MAAM,QAAQ,OAAgB;AAE5B,OAAI,cAAc,eAAe,GAAG,MAAM,YAAY,QAAQ;AAC5D;AACA;;AAIF,OAAI,cAAc,aAAa;IAC7B,MAAM,QAAQ,iBAAiB,GAAG;AAClC,QAAI,MAAM,YAAY,UAAU,MAAM,eAAe,UAAU;AAC7D;AACA;;;AAIJ,QAAK,MAAM,SAAS,GAAG,UAAU;AAC/B,QAAI,iBAAiB,YAAY;AAC/B;AAEA,SAAI,iBAAiB,SAAS,eAAe,OAAO;MAClD,MAAM,UAAW,MAAc,eAAe;MAC9C,MAAM,QAAS,MAAc,aAAa;AAC1C,UAAI,iBAAiB,WAAW,iBAAiB,MAC/C,QAAO,KAAK,MAAM;UAElB;WAIF,QAAO,KAAK,MAAM;;AAGtB,SAAK,MAAM;;;AAGf,OAAK,KAAK;AAEV,SAAO;;;;;;CAOT,IAAI,kBAA2B;AAC7B,SAAO,CAAC,KAAK;;;;;;;;;;;;;;;;CAiBf,IAAI,UAAoC;AACtC,SAAO,MAAKC;;CAGd,IAAI,QAAQ,UAAgD;AAE1D,MAAI,MAAKC,gBAAiB;AACxB,SAAKA,gBAAiB;AACtB,SAAKA,iBAAkB;;AAEzB,QAAKD,kBAAmB,YAAY;AAGpC,MAAI,SACF,OAAKC,iBAAkB,KAAK,aAAa,SAAS;;;;;;;;;;;CAatD,aAAa,UAAyC;AACpD,MAAI,OAAO,aAAa,WACtB,OAAM,IAAI,MAAM,yCAAyC;AAE3D,QAAKX,iBAAkB,IAAI,SAAS;AACpC,eAAa;AACX,SAAKA,iBAAkB,OAAO,SAAS;;;;;;;;;CAU3C,gBAAgB,UAAmC;AACjD,QAAKA,iBAAkB,OAAO,SAAS;;;CAIzC,uBAAuB,MAAc;AACnC,MAAI;AACF,OAAI,KAAK,MAAM,KAAK,eAAe,CAAC,OAAO,MAAM,KAAK,CACpD,cAAa,QAAQ,KAAK,YAAY,KAAK,UAAU,CAAC;WAEjD,OAAO;AACd,OAAI,uCAAuC,MAAM;;;CAIrD,SAAS;AACP,SAAO,IAAI,qBAAqB,MAAKY,iBAAkB;;CAGzD,0BAA0B;AAExB,sBAAoB;AACpB,8BAA4B;AAC5B,yBAAuB;AAGvB,OAAK,eAAe;;;CAItB,2BAA+C;AAC7C,MAAI,KAAK,GACP,KAAI;GACF,MAAM,cAAc,aAAa,QAAQ,KAAK,WAAW;AACzD,OAAI,gBAAgB,KAClB;GAEF,MAAM,cAAc,OAAO,WAAW,YAAY;AAElD,OAAI,OAAO,MAAM,YAAY,IAAI,CAAC,OAAO,SAAS,YAAY,CAC5D;AAEF,UAAO;WACA,OAAO;AACd,OAAI,yCAAyC,MAAM;;;CAMzD,oBAAoB;AAgBlB,QAAM,mBAAmB;AAIzB,OAAK,eAAe,WAAW;AAC7B,SAAKf,gBAAiB;IACtB;AAKF,8BAA4B;AAC1B,+BAA4B;AAC1B,QAAI,KAAK,gBACP,KAAI,oBAAoB,KAAK,iBAAiB,KAAK;AAIrD,QAAI,KAAK,yBAAyB,CAChC,MAAK,mBAAmB;KAE1B;IACF;;;;;;;CAQJ,gBAAgB;AACd,QAAM,eAAe;AACrB,QAAKgB,uBAAwB;;;;;CAM/B,yBAA+B;AAE7B,MAAI,MAAKC,oBAAqB,CAAC,KAAK,mBAAoB;AAExD,QAAKA,oBAAqB,UAAyC;AAGjE,OAAI,MAAM,aAAa,mBAAmB,OAAO,MAAM,UAAU,UAC/D;QAAI,KAAK,QACP,OAAKV,aAAc,MAAM;;;AAK/B,OAAK,mBAAmB,YAAY,MAAKU,iBAAkB;;;;;CAM7D,0BAAgC;AAC9B,MAAI,MAAKA,oBAAqB,KAAK,mBACjC,MAAK,mBAAmB,eAAe,MAAKA,iBAAkB;AAEhE,QAAKA,mBAAoB;;CAG3B,sBAAsB;CAEtB,AAAU,QAAQ,mBAAyC;AACzD,QAAM,QAAQ,kBAAkB;AAEhC,MAAI,kBAAkB,IAAI,OAAO,IAAI,kBAAkB,IAAI,YAAY,CACrE,eAAc,OAAO,KAAK;AAG5B,MAAI,MAAKC,uBAAwB,KAAK,YAAY;AAChD,SAAKA,qBAAsB,KAAK;AAChC,SAAKZ,uBAAwB;;;CAIjC,uBAAuB;AACrB,QAAM,sBAAsB;AAC5B,QAAKa,gBAAiB,YAAY;AAClC,QAAKC,wBAAyB;;;;;;;;;;CAWhC,MAAM,cAAc,SAAqD;AACvE,SAAO,uBAAuB,MAAM,QAAQ;;;;;;;;;;;;;;CAe9C,MAAM,aACJ,YACA,UAA+B,EAAE,EACH;AAC9B,MAAI,WAAW,WAAW,EAAG,QAAO,EAAE;EAEtC,MAAM,EACJ,QAAQ,KACR,mBAAmB,aACnB,oBAAoB,QAClB;EAEJ,MAAM,iBAAiB,YAAY,KAAK;EAGxC,MAAM,iBAAiB,YAAY,KAAK;EACxC,MAAM,EAAE,OAAO,aAAa,WAAW,iBAAiB,SAAS,uBAC/D,MAAM,KAAK,mBAAmB;EAChC,MAAM,YAAY,YAAY,KAAK,GAAG;EAGtC,MAAM,oBAAoB,YAAY,KAAK;EAC3C,MAAM,gBAAgB,YAAY,iBAAiB,WAAW;AAC9D,MAAI,cAAc,SAAS,EACzB,OAAM,QAAQ,IACZ,MAAM,KAAK,cAAc,CAAC,KAAK,UAC5B,MAAyC,sBAAsB,WAAW,CAC5E,CACF;EAEH,MAAM,eAAe,YAAY,KAAK,GAAG;EAEzC,MAAMC,WAAgC,EAAE;EACxC,IAAI,gBAAgB;EACpB,IAAI,mBAAmB;AAEvB,MAAI;AACF,QAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;IAC1C,MAAM,SAAS,WAAW;IAI1B,MAAM,YAAY,YAAY,KAAK;AACnC,UAAM,YAAY,cAAc,OAAO;AACvC,qBAAiB,YAAY,KAAK,GAAG;IAGrC,MAAM,eAAe,YAAY,KAAK;IACtC,MAAM,SAAS,MAAM,iBAAiB,aAAa,iBAAiB;KAClE;KACA;KACA;KACA,mBAAmB;KACpB,CAAC;AACF,wBAAoB,YAAY,KAAK,GAAG;AACxC,aAAS,KAAK,OAAO;;AAGvB,UAAO;YACC;GAER,MAAM,YAAY,YAAY,KAAK,GAAG;AACtC,WAAQ,IAAI,kBAAkB,WAAW,OAAO,iBAAiB,UAAU,QAAQ,EAAE,CAAC,eAAe,aAAa,QAAQ,EAAE,CAAC,WAAW,cAAc,QAAQ,EAAE,CAAC,cAAc,iBAAiB,QAAQ,EAAE,CAAC,YAAY,UAAU,QAAQ,EAAE,CAAC,IAAI;AAChP,uBAAoB;;;;;;;;;;;;CAaxB,MAAM,cAAc,SAAiE;AACnF,SAAO,uBAAuB,MAAM,QAAQ;;;;;;;;;CAU9C,kBAAwB;AAEtB,MAAI,CAAC,KAAK,eAAe,MAAKvB,kBAC5B;AAIF,QAAKA,oBAAqB;EAE1B,MAAM,YAAY,YAAY,KAAK;EACnC,MAAMwB,SAAkB,KAAK,YAAY,KAAK;EAC9C,MAAM,UAAU,YAAY,KAAK,GAAG;AAGpC,MAAI,WAAW,UAAa,WAAW,QAAQ,OAAQ,OAAe,SAAS,WAC7E,OAAM,IAAI,MACR,mGAED;AAIH,MAAI,UAAU,+BACZ,OAAM,IAAI,MACR,6BAA6B,QAAQ,QAAQ,EAAE,CAAC,oBAAoB,+BAA+B,oFAEpG;AAGH,MAAI,UAAU,8BACZ,SAAQ,KACN,mCAAmC,QAAQ,QAAQ,EAAE,CAAC,gBAAgB,8BAA8B,wDAErG;;;;;;;;;CAWL,kBAAkB,UAAmB,OAAsB;EAEzD,MAAM,mBAAmB,SAAS,iBAAiB,cAAc;EACjE,MAAM,gBAAgB,MAAM,iBAAiB,cAAc;AAE3D,OAAK,IAAI,IAAI,GAAG,IAAI,iBAAiB,UAAU,IAAI,cAAc,QAAQ,KAAK;GAC5E,MAAM,UAAU,iBAAiB;GACjC,MAAM,WAAW,cAAc;AAG/B,OAAI,QAAQ,aACV,UAAS,eAAe,QAAQ;;;;;;;;;;CAYtC,iBAAiB,UAAmB,OAAsB;EACxD,MAAM,gBAAgB,SAAS,iBAAiB,UAAU;EAC1D,MAAM,aAAa,MAAM,iBAAiB,UAAU;AAEpD,OAAK,IAAI,IAAI,GAAG,IAAI,cAAc,UAAU,IAAI,WAAW,QAAQ,KAAK;GACtE,MAAM,WAAW,cAAc;GAC/B,MAAM,YAAY,WAAW;AAI7B,OAAI,SAAS,iBAAiB,OAC5B,WAAU,eAAe,SAAS;AAGpC,OAAI,SAAS,qBAAqB,OAChC,WAAU,mBAAmB,SAAS;;;;;;;;;CAW5C,OAAMC,oBAAqB,UAAmB,OAA+B;EAE3E,MAAM,mBAAmB,SAAS,iBAAiB,kBAAkB;EACrE,MAAM,gBAAgB,MAAM,iBAAiB,kBAAkB;EAE/D,MAAMC,iBAAiC,EAAE;AAEzC,OAAK,IAAI,IAAI,GAAG,IAAI,iBAAiB,UAAU,IAAI,cAAc,QAAQ,KAAK;GAC5E,MAAM,UAAU,iBAAiB;GACjC,MAAM,WAAW,cAAc;AAG/B,OAAI,QAAQ,gBAAgB,OAC1B,UAAS,cAAc,QAAQ;AAEjC,OAAI,QAAQ,iBAAiB,OAC3B,UAAS,eAAe,QAAQ;AAElC,OAAI,QAAQ,oBAAoB,OAC9B,UAAS,kBAAkB,QAAQ;AAErC,OAAI,QAAQ,mBAAmB,OAC7B,UAAS,iBAAiB,QAAQ;AAEpC,OAAI,QAAQ,iBAAiB,OAC3B,UAAS,eAAe,QAAQ;AAIlC,OAAI,SAAS,eACX,gBAAe,KAAK,SAAS,eAAe;;AAKhD,QAAM,QAAQ,IAAI,eAAe;;;;;;;;CASnC,OAAMC,oBAAqB,MAA8B;EAEvD,MAAM,mBAAmB,KAAK,iBAAiB,cAAc;AAC7D,MAAI,iBAAiB,WAAW,EAAG;EAInC,MAAMC,eAAmC,EAAE;AAC3C,OAAK,MAAM,MAAM,kBAAkB;GACjC,MAAM,WAAW;AAEjB,OAAI,OAAO,SAAS,qBAAqB,WACvC,cAAa,KAAK,SAAS,kBAAkB,CAAC,YAAY,GAAG,CAAC;YAGvD,SAAS,yBAAyB,aACzC,cAAa,KAAK,SAAS,wBAAwB,aAAa,YAAY,GAAG,CAAC;;AAIpF,MAAI,aAAa,SAAS,EACxB,OAAM,QAAQ,IAAI,aAAa;;;;;;;CASnC,OAAMC,wBAAyB,UAAuB,OAAmC;EACvF,MAAMC,sBAAuC,EAAE;AAG/C,MAAI,SAAS,aAAa;AACxB,SAAM,cAAc,SAAS;AAC7B,OAAI,MAAM,oBACR,qBAAoB,KAAK,MAAM,oBAAoB;;EAKvD,MAAM,iBAAiB,MAAM,KAAK,SAAS,iBAAiB,eAAe,CAAC;EAC5E,MAAM,cAAc,MAAM,KAAK,MAAM,iBAAiB,eAAe,CAAC;AAGtE,OAAK,IAAI,IAAI,GAAG,IAAI,eAAe,UAAU,IAAI,YAAY,QAAQ,KAAK;GACxE,MAAM,aAAa,eAAe;GAClC,MAAM,kBAAkB,YAAY;AAEpC,OAAI,WAAW,aAAa;AAC1B,oBAAgB,cAAc,WAAW;AACzC,QAAI,gBAAgB,oBAClB,qBAAoB,KAAK,gBAAgB,oBAAoB;;;AAMnE,MAAI,oBAAoB,SAAS,EAC/B,OAAM,QAAQ,IAAI,oBAAoB;;;;;;;;;;;;;;;;;;;CAqB1C,MAAM,oBAAgD;EAMpD,MAAM,YAAY,SAAS,cAAc,MAAM;AAC/C,YAAU,YAAY;AACtB,YAAU,MAAM,UAAU;;;;eAIf,KAAK,eAAe,KAAK;gBACxB,KAAK,gBAAgB,KAAK;;;;EAMtC,MAAM,UAAU,KAAK,UAAU,KAAK;AAKpC,UAAQ,gBAAgB,KAAK;AAG7B,QAAKC,iBAAkB,MAAM,QAAQ;AAKrC,QAAKC,gBAAiB,MAAM,QAAQ;EAQpC,MAAM,iBAAiB,KAAK,QAAQ,mBAAmB;AACvD,MAAI,gBAAgB;GAElB,MAAM,cAAc,eAAe,UAAU,MAAM;AACnD,eAAY,YAAY,QAAQ;AAChC,aAAU,YAAY,YAAY;QAElC,WAAU,YAAY,QAAQ;AAGhC,WAAS,KAAK,YAAY,UAAU;AAGpC,QAAM,QAAQ;AAKd,QAAM,MAAKH,wBAAyB,MAAM,QAAQ;AAKlD,QAAM,MAAKJ,oBAAqB,MAAM,QAAQ;EAU9C,IAAI,cAAc,UAAU,cAAc,eAAe;AACzD,MAAI,CAAC,YACH,OAAM,IAAI,MACR,mJAED;AAMH,QAAM,eAAe,YAAY,eAAe;AAGhD,iBAAe,QAAQ,UAAU;AAGjC,gBAAc,UAAU,cAAc,eAAe;AACrD,MAAI,CAAC,YACH,OAAM,IAAI,MAAM,0CAA0C;AAI5D,QAAM,YAAY;AAKlB,QAAM,MAAKA,oBAAqB,MAAM,YAAY;EAMlD,MAAM,iCAAiC,QAAqB,SAAsB;AAChF,QAAK,MAAM,SAAS,OAAO,SAEzB,KAAI,MAAM,YAAY,gBAAgB;IACpC,MAAM,UAAU;AAEhB,YAAQ,kBAAkB;AAC1B,YAAQ,gBAAgB;AAExB,IAAC,QAAgB,mBAAmB;AAEpC,kCAA8B,SAAS,KAAK;cAGrC,qBAAqB,SAAS,mBAAmB,OAAO;IAC/D,MAAM,WAAW;AACjB,aAAS,kBAAkB;AAC3B,aAAS,gBAAgB;AAEzB,QAAI,uBAAuB,YAAY,OAAO,SAAS,sBAAsB,WAC3E,UAAS,mBAAmB;cAIvB,iBAAiB,QACxB,0CAAyC,OAAO,QAAQ,KAAK;;EAKnE,MAAM,4CAA4C,aAAoB,iBAA8B,SAAsB;AACxH,QAAK,MAAM,SAASQ,YAAU,SAC5B,KAAI,MAAM,YAAY,gBAAgB;IACpC,MAAM,UAAU;AAChB,YAAQ,kBAAkB;AAC1B,YAAQ,gBAAgB;AACxB,IAAC,QAAgB,mBAAmB;AACpC,kCAA8B,SAAS,KAAK;cACnC,qBAAqB,SAAS,mBAAmB,OAAO;IACjE,MAAM,WAAW;AACjB,aAAS,kBAAkB;AAC3B,aAAS,gBAAgB;AACzB,QAAI,uBAAuB,YAAY,OAAO,SAAS,sBAAsB,WAC3E,UAAS,mBAAmB;cAErB,iBAAiB,QAC1B,0CAAyC,OAAO,iBAAiB,KAAK;;AAO5E,cAAY,gBAAgB;AAC5B,gCAA8B,aAAa,YAAY;AAGvD,QAAM,YAAY;AAIlB,cAAY,gBAAgB;AAC5B,EAAC,YAAoB,mBAAmB;EACxC,MAAM,yBAAyB,OAAgB;AAC7C,OAAI,mBAAmB,MAAM,uBAAuB,IAAI;AACtD,IAAC,GAAW,gBAAgB;AAC5B,IAAC,GAAW,mBAAmB;;AAEjC,QAAK,MAAM,SAAS,GAAG,SACrB,uBAAsB,MAAM;;AAGhC,wBAAsB,YAAY;AAElC,QAAM,YAAY,uBAAuB;AAIzC,QAAM,MAAKN,oBAAqB,YAAY;AAM5C,MAAI,YAAY,oBAAoB;AAClC,eAAY,mBAAmB,QAAQ;AACvC,eAAY,qBAAqB;;AAInC,QAAM,YAAY,KAAK,EAAE;AAEzB,SAAO;GACL,OAAO;GACP;GACA,eAAe;AAEb,cAAU,QAAQ;IAKlB,MAAM,YAAa,YAAoB;AACvC,QAAI,UACF,sBAAqB;AACnB,eAAU,SAAS;MACnB;;GAGP;;;CAIH,IAAI,aAAa;AACf,MAAI,CAAC,KAAK,GACR,OAAM,IAAI,MAAM,iDAAiD;AAEnE,SAAO,gBAAgB,KAAK;;;CAI9B,IAAI,sBAAsB;AACxB,MAAI,KAAK,oBACP,QAAO,KAAK;;;CAMhB,IAAI,iBAAiB;AACnB,SAAO,sBAAsB,KAAK,MAAM,KAAK,oBAAoB;;;CAQnE,IAAI,aAAqB;AAEvB,MAAI,KAAK,SAAS,QAChB,QAAO,MAAM;EAKf,MAAM,2BAA2B,KAAK;AAGtC,SAAO,wBAAwB,MAAM,KAAK,MAAM,yBAAyB;;;;;;;CAY3E,mCAEE;AAEA,SADyB,8BAA8B,KAAK,CACpC,QAAQ,YAAY;AAE1C,UADuB,iCAAiC,QAAQ,CAC1C;IACtB;;;CAIJ,MAAM,kBAAkB,QAAsB;AA8E5C,SA7Ee,MAAM,SACnB,+BACA;GACE,aAAa,KAAK,MAAM;GACxB,MAAM,KAAK;GACZ,EACD,QACA,OAAO,SAAS;AAEd,WAAQ,gBAAgB;GAExB,MAAM,aAAa,YAAY,KAAK;GAEpC,MAAM,mBAAmB,8BAA8B,KAAK;AAC5D,OAAI,kBAAkB,CACpB,MAAK,aAAa,yBAAyB,iBAAiB,OAAO;AAIrE,WAAQ,gBAAgB;GAGxB,MAAM,kBAAkB,MAAKf,iCAAkC;AAC/D,OAAI,kBAAkB,CACpB,MAAK,aAAa,wBAAwB,gBAAgB,OAAO;GAGnE,MAAM,eAAe,YAAY,KAAK;AAMtC,SAAM,QAAQ,IACZ,gBAAgB,IAAI,OAAO,YAAY;AAErC,YAAQ,gBAAgB;AAExB,QAAI;AACF,SACE,uBAAuB,WACvB,OAAO,QAAQ,sBAAsB,WAErC,OAAO,QAAgB,mBAAmB;UACrC;AACL,YAAM,QAAQ;AACd,YAAM,QAAQ,UAAU,KAAK;;aAExB,OAAO;AAUd,SAPE,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,UACf,MAAM,SAAS,gBACd,MAAc,SAAS,SAAS,oBAAoB,IACpD,MAAc,SAAS,SAAS,6BAA6B,GAGhD;AAEhB,cAAQ,gBAAgB;AACxB;;AAEF,WAAM;;KAER,CACH;GACD,MAAM,aAAa,YAAY,KAAK;GAEpC,MAAM,WAAW,YAAY,KAAK;AAClC,OAAI,kBAAkB,EAAE;AACtB,SAAK,aAAa,iBAAiB,WAAW,WAAW;AACzD,SAAK,aAAa,kBAAkB,aAAa,aAAa;;IAGnE;;CAKH,yBAAoD;;CAGpD,MAAM,sBAAsB,QAAsB;AAEhD,UAAQ,gBAAgB;AAIxB,MAAI,CAAC,MAAKsB,sBACR,OAAKA,wBAAyB,MAAKC,sBAAuB,OAAO,CAAC,OAAM,QAAO;AAE7E,OAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;AAC5D,UAAKD,wBAAyB;AAC9B,UAAM;;AAER,WAAQ,MAAM,kDAAkD,KAAK,MAAM,UAAU,IAAI,IAAI;AAE7F,SAAKA,wBAAyB;AAC9B,SAAM;IACN;AAIJ,MAAI,QAAQ,QACV,OAAM,IAAI,aAAa,WAAW,aAAa;AAGjD,SAAO,MAAKA;;;;;;;;CASd,OAAMC,sBAAuB,QAAsB;AACjD,SAAO,SACL,mCACA;GACE,aAAa,KAAK,MAAM;GACxB,MAAM,KAAK;GACZ,EACD,QACA,OAAO,SAAS;AAEd,WAAQ,gBAAgB;AAKxB,SAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,QAAI,QAAQ,SAAS;AACnB,YAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;IAGF,MAAM,qBAAqB;AACzB,kBAAa,UAAU;AACvB,0BAAqB,OAAO;AAC5B,0BAAqB,OAAO;AAC5B,YAAO,IAAI,aAAa,WAAW,aAAa,CAAC;;AAEnD,YAAQ,iBAAiB,SAAS,cAAc,EAAE,MAAM,MAAM,CAAC;IAE/D,IAAIC;IACJ,IAAIC;IACJ,IAAIC;AAGJ,aAAS,4BAA4B;AACnC,SAAI,QAAQ,SAAS;AACnB,aAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;AAEF,cAAS,4BAA4B;AACnC,UAAI,QAAQ,SAAS;AACnB,cAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;AAGF,kBAAY,iBAAiB;AAC3B,eAAQ,oBAAoB,SAAS,aAAa;AAClD,gBAAS;SACR,GAAG;OACN;MACF;KACF;AAGF,WAAQ,gBAAgB;GAExB,MAAM,gBAAgB,qBAAqB,KAAK;AAChD,OAAI,kBAAkB,CACpB,MAAK,aAAa,sBAAsB,cAAc,OAAO;AAI/D,WAAQ,gBAAgB;GAKxB,MAAM,iBAAiB,KAAK,KAAK;GACjC,MAAM,wBAAwB;GAE9B,MAAM,eAAe,cAAc,IAAI,OAAO,GAAG,UAAU;AAEzD,YAAQ,gBAAgB;IAExB,MAAM,eAAe,KAAK,KAAK;AAC/B,QAAI;AAEF,SAAI,OAAO,EAAE,mBAAmB,YAAY;MAE1C,MAAM,iBAAiB,IAAI,SAAoB,GAAG,WAAW;AAC3D,WAAI,QAAQ,SAAS;AACnB,eAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;OAEF,MAAM,YAAY,iBAAiB,uBAAO,IAAI,MAAM,iBAAiB,MAAM,sBAAsB,sBAAsB,IAAI,CAAC,EAAE,sBAAsB;AACpJ,eAAQ,iBAAiB,eAAe;AACtC,qBAAa,UAAU;AACvB,eAAO,IAAI,aAAa,WAAW,aAAa,CAAC;UAChD,EAAE,MAAM,MAAM,CAAC;QAClB;AAEF,YAAM,QAAQ,KAAK,CAAC,EAAE,eAAe,OAAO,EAAE,eAAe,CAAC;gBAGvD,EAAE,iBAAiB;MAE1B,MAAM,SAAS,EAAE,gBAAgB;AAGjC,UAAI,WAAW,KAAK,WAAW,EAC7B;MAIF,MAAM,cAAc,EAAE,gBAAgB;AACtC,mBAAa,YAAY,GAAG;AAE5B,UAAI,aAAa;OACf,MAAM,iBAAiB,IAAI,SAAoB,GAAG,WAAW;AAC3D,YAAI,QAAQ,SAAS;AACnB,gBAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;QAEF,MAAM,YAAY,iBAAiB,uBAAO,IAAI,MAAM,iBAAiB,MAAM,sBAAsB,sBAAsB,IAAI,CAAC,EAAE,sBAAsB;AACpJ,gBAAQ,iBAAiB,eAAe;AACtC,sBAAa,UAAU;AACvB,gBAAO,IAAI,aAAa,WAAW,aAAa,CAAC;WAChD,EAAE,MAAM,MAAM,CAAC;SAClB;AAEF,aAAM,QAAQ,KAAK,CAAC,aAAa,eAAe,CAAC;;;aAG9C,OAAO;AAEd,SAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;AAGR,SAAI,kBAAkB,EAAE;MACtB,MAAM,iBAAiB,KAAK,KAAK,GAAG;AACpC,cAAQ,MAAM,+BAA+B,MAAM,gBAAgB,eAAe,MAAM,MAAM;;;KAIlG;GAEF,MAAM,UAAU,MAAM,QAAQ,WAAW,aAAa;AAQtD,OALgB,QAAQ,MAAK,MAC3B,EAAE,WAAW,cACb,EAAE,kBAAkB,gBACpB,EAAE,OAAO,SAAS,aACnB,CAEC,OAAM,IAAI,aAAa,WAAW,aAAa;GAIjD,MAAM,WAAW,QAAQ,QAAO,MAAK,EAAE,WAAW,WAAW;AAC7D,OAAI,SAAS,SAAS,KAAK,kBAAkB,EAAE;IAC7C,MAAM,mBAAmB,KAAK,KAAK,GAAG;AACtC,YAAQ,KAAK,iBAAiB,SAAS,OAAO,oCAAoC,iBAAiB,MAAM,SAAS,KAAI,MAAK,EAAE,WAAW,aAAa,EAAE,SAAS,KAAK,CAAC;;AASxK,0BAAuB;AAGvB,+BAA4B;AAQ5B,oBAAiB,KAAK,cAAc,cAAc,EAAE,EAAE;IAMzD;;;CAIH,IAAI,iBAAiB;AACnB,SAAO,2BAA2B,KAAK;;CAGzC,KAAIC,kBAAmB;EACrB,IAAI,SAAS,KAAK;AAClB,SAAO,QAAQ;AACb,OAAI,eAAe,OAAO,CACxB,QAAO;AAET,YAAS,OAAO;;AAElB,SAAO;;;;;;;;;;;;;CAcT,0BAA0B;AAExB,MAAI,CAAC,KAAK,gBACR,QAAO;AAKT,MAAI,KAAK,QAAQ,YAAY,KAAK,KAChC,QAAO;AAIT,MACE,KAAK,QAAQ,aAAa,KAAK,QAC/B,KAAK,QAAQ,eAAe,KAAK,QACjC,KAAK,QAAQ,qBAAqB,KAAK,KAEvC,QAAO;AAIT,MAAI,KAAK,QAAQ,eAAe,KAAK,KACnC,QAAO;AAIT,MAAI,KAAK,QAAQ,6BAA6B,KAAK,KACjD,QAAO;AAKT,MADoB,gBAAgB,KAAK,KAEvC,QAAO;AAIT,SAAO,KAAK;;;CAId,oBAAoB;EAClB,MAAM,YAAY,SAAS,cAAc,eAAe;EACxD,MAAM,SAAS,KAAK;AAOpB,MAAI,WAAW,SAAS,MAAM;AAC5B,aAAU,MAAM,WAAW;AAC3B,aAAU,MAAM,MAAM;AACtB,aAAU,MAAM,OAAO;AACvB,aAAU,MAAM,QAAQ;AACxB,aAAU,MAAM,SAAS;AACzB,aAAU,MAAM,SAAS;;AAG3B,UAAQ,OAAO,UAAU;AACzB,MAAI,CAAC,KAAK,aAAa,KAAK,CAC1B,MAAK,aAAa,MAAM,iBAAiB;EAK3C,MAAM,UAAU,SAAS,cAAc,cAAc;AACrD,UAAQ,KAAK;AACb,UAAQ,aAAa,QAAQ,SAAS;AACtC,UAAQ,aAAa,YAAY,GAAG;AACpC,UAAQ,MAAM,QAAQ;AACtB,UAAQ,MAAM,SAAS;EAIvB,MAAM,OAAO,KAAK,uBAAuB;EACzC,MAAM,SAAS,SAAS,cAAc,YAAY;AAClD,SAAO,KAAK;AAEZ,SAAO,MAAM,QAAQ,GAAG,KAAK,IAAI,KAAK,OAAO,KAAK,CAAC;AACnD,SAAO,MAAM,SAAS,GAAG,KAAK,IAAI,KAAK,QAAQ,KAAK,CAAC;AACrD,SAAO,MAAM,UAAU;AAGvB,SAAO,OAAO,KAA2B;AACzC,UAAQ,OAAO,OAAO;AACtB,YAAU,OAAO,QAAQ;EAGzB,MAAM,YAAY,SAAS,cAAc,eAAe;AACxD,YAAU,aAAa,QAAQ,YAAY;AAC3C,YAAU,aAAa,UAAU,mBAAmB;AACpD,YAAU,aAAa,UAAU,SAAS;AAC1C,YAAU,OAAO,UAAU;EAG3B,MAAM,YAAY,SAAS,cAAc,eAAe;AACxD,YAAU,aAAa,QAAQ,WAAW;AAC1C,YAAU,aAAa,UAAU,KAAK,GAAG;AACzC,YAAU,OAAO,UAAU;;CAG7B,KAAIC,aAAc;AAChB,SAAO,MAAM,KACX,KAAK,iBACH,yDACD,CACF;;;;;;;;CASH,mBAA8B;AAC5B,SAAO,qBAAqB,KAAK;;;;;;;;CASnC,MAAM,YAAY,QAAgB,MAAc,QAA4C;AAC1F,SAAO,oBAAoB,MAAM,QAAQ,MAAM,OAAO;;;;;;CAOxD,OAAMC,cAAe,QAAgB,MAAc;EAEjD,MAAM,iBAAiB,MAAM,KAAK,YAAY,QAAQ,KAAK;EAG3D,MAAM,kBAAkB,IAAI,cAAc;EAG1C,MAAM,eAAe,gBAAgB,oBAAoB;AACzD,eAAa,SAAS;AACtB,eAAa,QAAQ,gBAAgB,YAAY;AAGjD,eAAa,MAAM,EAAE;AAGrB,SAAO,IAAI,SAAe,YAAY;AACpC,gBAAa,gBAAgB;AAC3B,oBAAgB,OAAO;AACvB,aAAS;;IAEX;;CAGJ,OAAMC,cAAe;EACnB,MAAM,aAAa,MAAKF;EACxB,MAAMG,cAA8B,EAAE;AACtC,OAAK,MAAM,MAAM,YAAY;GAC3B,MAAM,eAAgB,GAAW;AAEjC,OAAI,gBAAgB,OAAO,aAAa,QAAQ,YAAY;AAE1D,iBAAa,KAAK,CAAC,YAAY,GAAG;AAClC,QAAI,aAAa,aACf,aAAY,KAAK,aAAa,aAAa;;;AAKjD,QAAM,QAAQ,IAAI,YAAY;AAE9B,aAAW,SAAS,OAAO;AACzB,OAAI,mBAAmB,MAAM,GAAG,yBAAyB,SACvD,IAAG,aAAa,OAAO,GAAG,eAAe,CAAC;IAE5C;;CAIJ,2BAA2B;CAC3B,+BAA+B,KAAK,KAAK;;wCACQ;;;uCACD;;;CAGhD,oBAAmC,QAAQ,SAAS;CACpD,4BAAoD;CAoBpD,OAAMnD,aAAc,QAAoC;EAEtD,MAAM,MAAM,KAAK,KAAK;AACtB,MAAI,MAAM,MAAKoD,2CAA2C,+BAA+B;AACvF,SAAKC,0BAA2B;AAChC,SAAKD,8BAA+B;;AAEtC,QAAKC;AAEL,MAAI,MAAKA,uCAAuC,+BAE9C;AAGF,MAAI;AACF,UAAO,gBAAgB;AAEvB,OAAI,KAAK,gBAGP,OAAM,SACJ,uBACA;IACE,aAAa,KAAK,MAAM;IACxB,kBAAkB,KAAK;IACvB,eAAe,KAAK;IACrB,EACD,QACA,YAAY;AAGV,UAAM,MAAKzC,gBAAiB,YAAY,KAAK,eAAe;KAC1D,kBAAkB;KAClB,qBAAqB,SAAS;AAC5B,uBAAiB,KAAoB;;KAExC,CAAC;AACF,WAAO,gBAAgB;AAEvB,UAAM,MAAKE,yBAA0B;KAExC;OAGD,OAAM,MAAKA,yBAA0B;WAEhC,OAAO;AACd,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD;AAEF,WAAQ,MAAM,+BAA+B,MAAM;;;CAIvD,OAAMA,0BAA2B;AAC/B,MAAI,MAAKD,iBAAkB,OAAO,GAAG;GACnC,MAAM,kBACJ,KAAK,aAAa,IAAI,KAAK,mBAAmB,KAAK,aAAa;GAClE,MAAM,YAAY;IAChB,kBAAkB,KAAK;IACvB,eAAe,KAAK;IACpB,YAAY,KAAK;IACjB;IACA,SAAS;IACV;AAED,SAAM,QAAQ,IACZ,MAAM,KAAK,MAAKA,iBAAkB,CAAC,KAAK,aACtC,QAAQ,QAAQ,SAAS,UAAU,CAAC,CACrC,CACF;;;;CAKL,mBAAgD,QAAQ,QAAQ,OAAU;CAC1E,2BAAmD;CAqBnD,OAAMP,YAAa,YAAgC,QAAkD;AACnG,MAAI;AACF,UAAO,gBAAgB;AAGvB,OAAI,KAAK,oBAAoB;AAE3B,UAAM,KAAK,mBAAmB;AAC9B,WAAO,gBAAgB;AACvB,WAAO,KAAK;;AAId,OAAI,CAAC,KAAK,gBACR;AAGF,UAAO,MAAM,SACX,sBACA;IACE,aAAa,KAAK,MAAM;IACxB,YAAY,cAAc;IAC1B,YAAY,KAAK;IAClB,EACD,QACA,OAAO,SAAS;AAEd,QAAI;AACF,WAAM,QAAQ,KAAK,CACjB,KAAK,sBAAsB,OAAO,EAClC,IAAI,SAAe,GAAG,WAAW;AAC/B,UAAI,OAAO,SAAS;AAClB,cAAO,IAAI,aAAa,WAAW,aAAa,CAAC;AACjD;;MAEF,MAAM,YAAY,iBAAiB,uBAAO,IAAI,MAAM,gCAAgC,CAAC,EAAE,IAAM;AAC7F,aAAO,iBAAiB,eAAe;AACrC,oBAAa,UAAU;AACvB,cAAO,IAAI,aAAa,WAAW,aAAa,CAAC;SAChD,EAAE,MAAM,MAAM,CAAC;OAClB,CACH,CAAC;aACK,OAAO;AACd,SAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD,OAAM;;AAKV,WAAO,gBAAgB;IAGvB,MAAM,UAAU,mBACd,cAAc,GACd,KAAK,YACL,KAAK,aACN;AACD,QAAI,kBAAkB,CACpB,MAAK,aAAa,WAAW,QAAQ;AAGvC,UAAKF,cAAe;AACpB,SAAK,cAAc,cAAc;AAEjC,UAAM,KAAK;AACX,WAAO,gBAAgB;AAEvB,UAAM,MAAKY,uBAAwB;AACnC,WAAO,gBAAgB;AAEvB,QAAI,CAAC,MAAKL,0BACR,MAAK,uBAAuB,MAAKP,YAAa;AAEhD,UAAKe,iBAAkB;AACvB,QAAI,MAAKR,0BACP,OAAKA,4BAA6B;AAEpC,WAAO;KAEV;WACM,OAAO;AACd,OAAI,iBAAiB,gBAAgB,MAAM,SAAS,aAClD;AAEF,WAAQ,MAAM,8BAA8B,MAAM;AAClD;;;;;;;;;;CAWJ,mBAAkC;AAGhC,SAAO;GACL,GAHW,4BAA4B,KAAK;GAI5C,aAAa;GACb,oBAAoB;GACrB;;;;;;;;CASH,kBAA8C;AAC5C,SAAO,2BAA2B,KAAK;;;YA7jExC,QAAQ,EAAE,SAAS,kBAAkB,CAAC;YAItC,QAAQ,EAAE,SAAS,WAAW,CAAC;YAgF/B,SAAS,EAAE,MAAM,QAAQ,CAAC;YAQ1B,SAAS;CAAE,MAAM;CAAS,WAAW;CAAa,CAAC;YASnD,SAAS;CAAE,MAAM;CAAS,SAAS;CAAM,CAAC;YA2B1C,SAAS,EAAE,MAAM,QAAQ,CAAC;YAqI1B,SAAS;CAAE,MAAM;CAAQ,WAAW;CAAe,CAAC;yCAvStD,cAAc,eAAe"}