@editframe/elements 0.38.1 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/elements/EFCaptions.js +1 -1
- package/dist/elements/EFCaptions.js.map +1 -1
- package/dist/elements/EFImage.js +3 -4
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFMedia/BufferedSeekingInput.js +1 -1
- package/dist/elements/EFMedia/CachedFetcher.js +99 -0
- package/dist/elements/EFMedia/CachedFetcher.js.map +1 -0
- package/dist/elements/EFMedia/MediaEngine.d.ts +19 -0
- package/dist/elements/EFMedia/MediaEngine.js +129 -0
- package/dist/elements/EFMedia/MediaEngine.js.map +1 -0
- package/dist/elements/EFMedia/SegmentIndex.d.ts +32 -0
- package/dist/elements/EFMedia/SegmentIndex.js +185 -0
- package/dist/elements/EFMedia/SegmentIndex.js.map +1 -0
- package/dist/elements/EFMedia/SegmentTransport.d.ts +12 -0
- package/dist/elements/EFMedia/SegmentTransport.js +69 -0
- package/dist/elements/EFMedia/SegmentTransport.js.map +1 -0
- package/dist/elements/EFMedia/TimingModel.d.ts +10 -0
- package/dist/elements/EFMedia/TimingModel.js +28 -0
- package/dist/elements/EFMedia/TimingModel.js.map +1 -0
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +7 -6
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +13 -34
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
- package/dist/elements/EFMedia.d.ts +2 -1
- package/dist/elements/EFMedia.js +14 -31
- package/dist/elements/EFMedia.js.map +1 -1
- package/dist/elements/EFSourceMixin.js +1 -1
- package/dist/elements/EFSourceMixin.js.map +1 -1
- package/dist/elements/EFTemporal.js +2 -1
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFTimegroup.js +2 -1
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/EFVideo.js +204 -187
- package/dist/elements/EFVideo.js.map +1 -1
- package/dist/gui/EFConfiguration.d.ts +0 -7
- package/dist/gui/EFConfiguration.js +0 -5
- package/dist/gui/EFConfiguration.js.map +1 -1
- package/dist/gui/EFWorkbench.d.ts +2 -0
- package/dist/gui/EFWorkbench.js +68 -1
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/PlaybackController.d.ts +2 -0
- package/dist/gui/PlaybackController.js +11 -1
- package/dist/gui/PlaybackController.js.map +1 -1
- package/dist/gui/ef-theme.css +11 -0
- package/dist/gui/timeline/tracks/AudioTrack.js +28 -30
- package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +1 -0
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js +41 -8
- package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -1
- package/dist/gui/timeline/tracks/VideoTrack.js +2 -2
- package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
- package/dist/gui/timeline/tracks/waveformUtils.js +19 -19
- package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
- package/dist/preview/QualityUpgradeScheduler.d.ts +8 -0
- package/dist/preview/QualityUpgradeScheduler.js +13 -1
- package/dist/preview/QualityUpgradeScheduler.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.js +3 -3
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/renderVideoToVideo.js +5 -6
- package/dist/preview/renderVideoToVideo.js.map +1 -1
- package/dist/transcoding/types/index.d.ts +6 -94
- package/dist/transcoding/utils/UrlGenerator.d.ts +3 -12
- package/dist/transcoding/utils/UrlGenerator.js +3 -29
- package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
- package/package.json +2 -2
- package/test/setup.ts +1 -1
- package/test/useAssetMSW.ts +0 -100
- package/dist/elements/EFMedia/AssetMediaEngine.js +0 -284
- package/dist/elements/EFMedia/AssetMediaEngine.js.map +0 -1
- package/dist/elements/EFMedia/BaseMediaEngine.js +0 -200
- package/dist/elements/EFMedia/BaseMediaEngine.js.map +0 -1
- package/dist/elements/EFMedia/FileMediaEngine.js +0 -122
- package/dist/elements/EFMedia/FileMediaEngine.js.map +0 -1
- package/dist/elements/EFMedia/JitMediaEngine.js +0 -157
- package/dist/elements/EFMedia/JitMediaEngine.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EFWorkbench.js","names":["EFWorkbench","state","statusColor: string","statusText: string","stats: PlaybackStats | null"],"sources":["../../src/gui/EFWorkbench.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport {\n customElement,\n eventOptions,\n property,\n state,\n} from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\n\nimport { ContextMixin } from \"./ContextMixin.js\";\nimport { TWMixin } from \"./TWMixin.js\";\nimport { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport { findRootTemporal } from \"../elements/findRootTemporal.js\";\n// Import only types - actual functions loaded dynamically to avoid SSR issues\nimport type { CanvasPreviewResult } from \"../preview/renderTimegroupToCanvas.types.js\";\nimport type {\n RenderToVideoOptions,\n RenderProgress,\n} from \"../preview/renderTimegroupToVideo.types.js\";\nimport { updateAnimations } from \"../elements/updateAnimations.js\";\nimport {\n isNativeCanvasApiAvailable,\n getPreviewPresentationMode,\n setPreviewPresentationMode,\n type PreviewPresentationMode,\n getRenderMode,\n setRenderMode,\n type RenderMode,\n getPreviewResolutionScale,\n type PreviewResolutionScale,\n getShowStats,\n getShowThumbnailTimestamps,\n setShowThumbnailTimestamps,\n} from \"../preview/previewSettings.js\";\nimport { setShowStats } from \"../preview/previewSettings.js\";\nimport { AdaptiveResolutionTracker } from \"../preview/AdaptiveResolutionTracker.js\";\nimport { RenderStats, type PlaybackStats } from \"../preview/RenderStats.js\";\nimport { DomStatsStrategy } from \"../preview/statsTrackingStrategy.js\";\nimport { provide } from \"@lit/context\";\nimport {\n previewSettingsContext,\n type PreviewSettings,\n} from \"./previewSettingsContext.js\";\nimport { phosphorIcon, ICONS } from \"./icons.js\";\n\n// Side-effect import for template usage (pan-zoom is created in light DOM by wrapWithWorkbench)\nimport \"./EFFitScale.js\";\n\n/** Debounce delay before considering the preview \"at rest\" after motion stops */\nconst REST_DEBOUNCE_MS = 200;\n\n@customElement(\"ef-workbench\")\nexport class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {\n static styles = [\n css`\n :host {\n display: grid;\n grid-template-rows: auto 1fr 280px;\n grid-template-columns: 280px 1fr;\n width: 100%;\n height: 100%;\n overflow: hidden;\n background-color: var(--ef-color-bg);\n \n /* Component tokens (reference globals from ef-theme.css) */\n --workbench-bg: var(--ef-color-bg);\n --workbench-overlay-border: var(--ef-color-primary);\n --workbench-overlay-bg: var(--ef-color-primary-subtle);\n --toolbar-bg: var(--ef-color-bg-elevated);\n --toolbar-border: var(--ef-color-border-subtle);\n }\n \n /* Utility classes (not relying on external Tailwind) */\n .grid {\n display: grid;\n }\n \n .overflow-hidden {\n overflow: hidden;\n }\n \n .fixed {\n position: fixed;\n }\n \n .inset-0 {\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n \n .h-full {\n height: 100%;\n }\n \n .w-full {\n width: 100%;\n }\n \n .toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 8px 12px;\n background: var(--toolbar-bg);\n border-bottom: 1px solid var(--toolbar-border);\n flex-shrink: 0;\n font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;\n position: relative;\n z-index: 20;\n }\n \n .toolbar-left {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n \n .toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n \n .toolbar-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 6px 12px;\n background: var(--ef-color-bg-inset);\n border: 1px solid var(--ef-color-border-subtle);\n border-radius: 6px;\n color: var(--ef-color-text);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n \n .toolbar-btn:hover {\n background: var(--ef-color-hover);\n border-color: var(--ef-color-border);\n }\n \n .toolbar-btn.active {\n background: var(--ef-color-primary-subtle);\n border-color: var(--ef-color-primary);\n color: var(--ef-color-primary-hover);\n }\n \n .toolbar-btn.primary {\n background: var(--ef-color-primary);\n border-color: transparent;\n color: white;\n font-weight: 600;\n }\n \n .toolbar-btn.primary:hover {\n background: var(--ef-color-primary-hover);\n }\n \n .toolbar-icon-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n background: var(--ef-color-bg-inset);\n border: 1px solid var(--ef-color-border-subtle);\n border-radius: 6px;\n color: var(--ef-color-text);\n cursor: pointer;\n transition: all 0.15s ease;\n }\n \n .toolbar-icon-btn:hover {\n background: var(--ef-color-hover);\n border-color: var(--ef-color-border);\n }\n \n .toolbar-icon-btn.active {\n background: var(--ef-color-primary-subtle);\n border-color: var(--ef-color-primary);\n color: var(--ef-color-primary-hover);\n }\n \n .mode-indicator {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n white-space: nowrap;\n }\n \n .mode-indicator.dom {\n background: color-mix(in srgb, var(--ef-color-success) 15%, transparent);\n color: var(--ef-color-success);\n border: 1px solid color-mix(in srgb, var(--ef-color-success) 30%, transparent);\n }\n \n .mode-indicator.canvas {\n background: color-mix(in srgb, var(--ef-color-type-image) 15%, transparent);\n color: var(--ef-color-type-image);\n border: 1px solid color-mix(in srgb, var(--ef-color-type-image) 30%, transparent);\n }\n \n .canvas-container {\n position: relative;\n overflow: hidden;\n flex: 1;\n display: grid;\n grid-template-columns: 100%;\n grid-template-rows: 100%;\n min-height: 0;\n background: var(--ef-color-bg);\n }\n \n .canvas-container ::slotted(*) {\n width: 100%;\n height: 100%;\n grid-column: 1;\n grid-row: 1;\n }\n \n .canvas-overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 1;\n }\n \n .clone-content {\n position: absolute;\n transform-origin: 0 0;\n }\n \n .playback-stats {\n position: absolute;\n top: 8px;\n left: 8px;\n width: 200px;\n background: color-mix(in srgb, var(--ef-color-bg-elevated) 90%, transparent);\n backdrop-filter: blur(4px);\n border-radius: 6px;\n padding: 8px 12px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, monospace;\n font-size: 11px;\n color: var(--ef-color-text);\n z-index: 10;\n pointer-events: none;\n line-height: 1.5;\n }\n \n .playback-stats .stat-row {\n display: flex;\n justify-content: space-between;\n gap: 8px;\n }\n \n .playback-stats .stat-label {\n color: var(--ef-color-text-muted);\n flex-shrink: 0;\n width: 85px;\n }\n \n .playback-stats .stat-value {\n font-weight: 600;\n text-align: right;\n flex: 1;\n font-variant-numeric: tabular-nums;\n }\n \n .playback-stats .stat-value.good {\n color: var(--ef-color-success);\n }\n \n .playback-stats .stat-value.warning {\n color: var(--ef-color-warning);\n }\n \n .playback-stats .stat-value.bad {\n color: var(--ef-color-danger);\n }\n \n .pressure-histogram {\n display: flex;\n align-items: flex-end;\n gap: 1px;\n height: 24px;\n margin-top: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--ef-color-border-subtle);\n }\n \n .pressure-histogram .bar {\n flex: 1;\n min-width: 2px;\n max-width: 4px;\n border-radius: 1px 1px 0 0;\n transition: height 0.1s ease-out;\n }\n \n .pressure-histogram .bar.nominal {\n background: var(--ef-color-success);\n height: 25%;\n }\n \n .pressure-histogram .bar.fair {\n background: var(--ef-color-warning);\n height: 50%;\n }\n \n .pressure-histogram .bar.serious {\n background: var(--ef-color-warning);\n height: 75%;\n }\n \n .pressure-histogram .bar.critical {\n background: var(--ef-color-danger);\n height: 100%;\n }\n \n .pressure-histogram-label {\n display: flex;\n justify-content: space-between;\n margin-top: 4px;\n font-size: 9px;\n color: var(--ef-color-text-subtle);\n }\n \n .dropdown-panel {\n position: fixed;\n margin: 0;\n padding: 14px 16px;\n min-width: 260px;\n max-width: calc(100vw - 32px);\n background: var(--ef-color-bg-elevated);\n border: 1px solid var(--ef-color-border);\n border-radius: 10px;\n backdrop-filter: blur(12px);\n box-shadow: 0 8px 32px color-mix(in srgb, var(--ef-color-bg) 50%, transparent);\n }\n \n .dropdown-panel::backdrop {\n background: transparent;\n }\n \n .dropdown-panel:popover-open {\n /* Animation for opening */\n animation: popover-fade-in 0.15s ease-out;\n }\n \n @keyframes popover-fade-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n \n .dropdown-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 12px;\n padding-bottom: 10px;\n border-bottom: 1px solid var(--ef-color-border-subtle);\n }\n \n .dropdown-title {\n color: var(--ef-color-text);\n font-size: 13px;\n font-weight: 600;\n }\n \n .dropdown-close {\n background: transparent;\n border: none;\n color: var(--ef-color-text-subtle);\n cursor: pointer;\n }\n \n .dropdown-section {\n background: var(--ef-color-bg-inset);\n border-radius: 8px;\n padding: 12px;\n margin-top: 10px;\n }\n \n .dropdown-label {\n color: var(--ef-color-text);\n font-size: 11px;\n font-weight: 600;\n margin-bottom: 6px;\n }\n \n .dropdown-description {\n margin-top: 8px;\n color: var(--ef-color-text-subtle);\n font-size: 10px;\n line-height: 1.4;\n }\n \n .button-group {\n display: flex;\n gap: 6px;\n margin-top: 6px;\n }\n \n .button-group-btn {\n flex: 1;\n padding: 6px 8px;\n border: 1px solid transparent;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n background: transparent;\n color: var(--ef-color-text-muted);\n }\n \n .button-group-btn.active {\n background: var(--ef-color-selected);\n color: var(--ef-color-primary);\n border-color: var(--ef-color-primary-subtle);\n }\n \n .checkbox-label {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n }\n \n .checkbox-label input[type=\"checkbox\"] {\n width: 14px;\n height: 14px;\n accent-color: var(--ef-color-primary);\n cursor: pointer;\n }\n \n .checkbox-label span {\n color: var(--ef-color-text);\n font-size: 12px;\n font-weight: 500;\n }\n \n .dropdown-select {\n width: 100%;\n padding: 6px 8px;\n background: var(--ef-color-bg-inset);\n border: 1px solid var(--ef-color-border);\n border-radius: 6px;\n color: var(--ef-color-text);\n font-size: 11px;\n cursor: pointer;\n }\n \n .dropdown-input {\n width: 100%;\n padding: 5px 7px;\n background: var(--ef-color-bg-inset);\n border: 1px solid var(--ef-color-border);\n border-radius: 4px;\n color: var(--ef-color-text);\n font-size: 11px;\n }\n \n .dropdown-input:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n padding: 2px;\n line-height: 1;\n font-size: 14px;\n transition: color 0.15s;\n }\n \n .dropdown-close:hover {\n color: var(--ef-color-text-muted);\n }\n `,\n ];\n\n @property({ type: Boolean })\n rendering = false;\n\n @state()\n private panZoomTransform = { x: 0, y: 0, scale: 1 };\n\n @state()\n private isExporting = false;\n\n @state()\n private exportProgress: RenderProgress | null = null;\n\n @state()\n private exportStatus:\n | \"idle\"\n | \"rendering\"\n | \"complete\"\n | \"error\"\n | \"cancelled\" = \"idle\";\n\n @provide({ context: previewSettingsContext })\n @state()\n private previewSettings: PreviewSettings = {\n presentationMode: getPreviewPresentationMode(),\n renderMode: getRenderMode(),\n resolutionScale: getPreviewResolutionScale(),\n showStats: getShowStats(),\n showThumbnailTimestamps: getShowThumbnailTimestamps(),\n };\n\n // Local state mirrors for direct access (context is primary source of truth)\n @state()\n private renderMode: RenderMode = this.previewSettings.renderMode;\n\n @state()\n private presentationMode: PreviewPresentationMode =\n this.previewSettings.presentationMode;\n\n @state()\n private previewResolutionScale: PreviewResolutionScale =\n this.previewSettings.resolutionScale;\n\n @state()\n private exportOptions = {\n includeAudio: true,\n scale: 1,\n useInOut: false,\n inMs: 0,\n outMs: 0,\n };\n\n private exportAbortController: AbortController | null = null;\n\n // Motion state tracking for adaptive resolution\n @state()\n private isPlaying = false;\n\n @state()\n private isScrubbing = false;\n\n @state()\n private isAtRest = true;\n\n /**\n * Current adaptive resolution scale (only used when previewResolutionScale === \"auto\")\n */\n @state()\n private currentAdaptiveScale: number = 1;\n\n /**\n * Playback stats for display (FPS, dropped frames, etc.)\n * Mirrors previewSettings.showStats for direct access\n */\n @state()\n private showStats: boolean = this.previewSettings.showStats;\n\n /**\n * Theme mode: 'light', 'dark', or 'system'\n */\n @state()\n private themeMode: \"light\" | \"dark\" | \"system\" = this.getInitialTheme();\n\n /**\n * Always-on render statistics collection for canvas mode.\n * Collects data regardless of whether stats are visible.\n */\n private renderStats: RenderStats | null = null;\n\n /**\n * Media query for system theme preference\n */\n private systemThemeMediaQuery: MediaQueryList | null = null;\n private systemThemeListener: (() => void) | null = null;\n\n /**\n * DOM mode stats strategy (has its own animation loop).\n * Only active in DOM mode.\n */\n private domStatsStrategy: DomStatsStrategy | null = null;\n\n /**\n * Reference for tracking scrubbing state from EFScrubber.\n * Pass this to <ef-scrubber isScrubbingRef={...}> to enable motion detection.\n */\n readonly isScrubbingRef = { current: false };\n\n private restDebounceTimer: number | null = null;\n private playingCheckInterval: number | null = null;\n private adaptiveTracker: AdaptiveResolutionTracker | null = null;\n private savePanZoomDebounceTimer: number | null = null;\n\n // Canvas preview mode state\n private canvasPreviewRef = createRef<HTMLDivElement>();\n private canvasPreviewResult: CanvasPreviewResult | null = null;\n private canvasAnimationFrame: number | null = null;\n\n private boundHandleTransformChanged = this.handleTransformChanged.bind(this);\n\n focusOverlay = createRef<HTMLDivElement>();\n\n @eventOptions({ passive: false, capture: true })\n handleStageWheel(event: WheelEvent) {\n event.preventDefault();\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n\n // Apply initial theme\n this.applyTheme();\n\n // Check if we're in rendering mode and set rendering=true before first render\n if (\n !this.hasAttribute(\"rendering\") &&\n typeof window !== \"undefined\" &&\n \"FRAMEGEN_BRIDGE\" in window\n ) {\n this.rendering = true;\n }\n\n // Listen for pan-zoom transform changes\n this.addEventListener(\n \"transform-changed\",\n this.boundHandleTransformChanged as EventListener,\n );\n\n // Start motion state polling (checks playing state and scrubbing ref)\n this.startMotionStateTracking();\n\n // Initialize adaptive tracker\n // Scale changes directly update the canvas resolution - no expensive reinit needed\n this.adaptiveTracker = new AdaptiveResolutionTracker({\n onScaleChange: (scale) => {\n const oldScale = this.currentAdaptiveScale;\n this.currentAdaptiveScale = scale;\n\n // Directly update resolution if in auto mode, canvas mode, and in motion\n if (\n this.previewResolutionScale === \"auto\" &&\n this.presentationMode === \"canvas\" &&\n !this.isAtRest\n ) {\n // Use the new dynamic setResolutionScale - instant, no DOM rebuild\n if (this.canvasPreviewResult) {\n this.canvasPreviewResult.setResolutionScale(scale);\n console.log(\n `[EFWorkbench] Resolution changed ${(oldScale * 100).toFixed(0)}% → ${(scale * 100).toFixed(0)}% (instant)`,\n );\n }\n }\n },\n });\n\n // Initialize render stats (always-on collection)\n this.renderStats = new RenderStats(this.adaptiveTracker);\n\n // Listen for system theme changes when in system mode\n if (typeof window !== \"undefined\") {\n this.systemThemeMediaQuery = window.matchMedia(\n \"(prefers-color-scheme: dark)\",\n );\n this.systemThemeListener = () => {\n if (this.themeMode === \"system\") {\n this.applyTheme();\n }\n };\n this.systemThemeMediaQuery.addEventListener(\n \"change\",\n this.systemThemeListener,\n );\n }\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n\n // Clean up current mode\n if (this.presentationMode === \"dom\") {\n this.stopDomMode();\n } else if (this.presentationMode === \"canvas\") {\n this.stopCanvasMode();\n }\n\n // Restore timegroup visibility\n const timegroup = this.getTimegroup();\n if (timegroup) {\n timegroup.style.clipPath = \"\";\n timegroup.style.pointerEvents = \"\";\n }\n\n this.removeEventListener(\n \"transform-changed\",\n this.boundHandleTransformChanged as EventListener,\n );\n\n // Clean up theme listener\n if (this.systemThemeMediaQuery && this.systemThemeListener) {\n this.systemThemeMediaQuery.removeEventListener(\n \"change\",\n this.systemThemeListener,\n );\n }\n\n // Clean up motion state tracking\n this.stopMotionStateTracking();\n\n // Clean up cache stats updates\n if (typeof (this as any).stopCacheStatsUpdates === \"function\") {\n (this as any).stopCacheStatsUpdates();\n }\n\n // Clean up adaptive tracker\n if (this.adaptiveTracker) {\n this.adaptiveTracker.dispose();\n this.adaptiveTracker = null;\n }\n\n // Save pan/zoom state before disconnecting\n if (this.savePanZoomDebounceTimer !== null) {\n clearTimeout(this.savePanZoomDebounceTimer);\n this.savePanZoomDebounceTimer = null;\n }\n this.savePreviewPanZoom();\n }\n\n protected firstUpdated(): void {\n // Restore preview pan/zoom from localStorage\n // Wait for timegroup to be available\n requestAnimationFrame(() => {\n this.restorePreviewPanZoom();\n });\n\n // Wait for Lit to complete initial render and layout before initializing preview\n // This ensures workbench dimensions are available for resolution calculation\n this.updateComplete.then(() => {\n // Initialize based on current presentation mode\n if (this.presentationMode === \"dom\") {\n this.initDomMode();\n } else if (this.presentationMode === \"canvas\") {\n this.initCanvasMode();\n }\n });\n }\n\n // Track zoom for detecting changes that need canvas reinit\n private lastCanvasZoom = 1;\n private zoomReinitTimeout: number | null = null;\n\n private handleTransformChanged(\n e: CustomEvent<{ x: number; y: number; scale: number }>,\n ) {\n this.panZoomTransform = e.detail;\n\n // Save pan/zoom state to localStorage\n this.debouncedSavePreviewPanZoom();\n\n // Update overlay transform based on current mode\n if (this.presentationMode === \"canvas\") {\n this.updateCanvasTransform();\n\n // Check if zoom changed enough to warrant re-rendering at new resolution\n // Only update if zoom changed by >25% from last init\n const zoomRatio = e.detail.scale / this.lastCanvasZoom;\n if (zoomRatio < 0.75 || zoomRatio > 1.33) {\n // Debounce to avoid thrashing during zoom gestures\n if (this.zoomReinitTimeout !== null) {\n clearTimeout(this.zoomReinitTimeout);\n }\n this.zoomReinitTimeout = window.setTimeout(() => {\n this.zoomReinitTimeout = null;\n if (this.presentationMode === \"canvas\" && this.canvasPreviewResult) {\n this.lastCanvasZoom = this.panZoomTransform.scale;\n\n // Dynamically update resolution scale without recreating canvas\n const timegroup = this.getTimegroup();\n const canvasContainer = this.canvasPreviewRef.value;\n if (timegroup && canvasContainer) {\n const newScale =\n this.previewResolutionScale === \"auto\"\n ? this.getEffectiveResolutionScale(timegroup, canvasContainer)\n : this.getResolutionScale(timegroup, canvasContainer);\n\n this.canvasPreviewResult.setResolutionScale(newScale);\n }\n }\n }, 500); // Wait 500ms after zoom stops\n }\n }\n }\n\n private getTimegroup(): EFTimegroup | null {\n // Find the timegroup in our canvas slot\n const canvas = this.querySelector(\"[slot='canvas']\");\n if (!canvas) return null;\n return canvas.querySelector(\"ef-timegroup\") as EFTimegroup | null;\n }\n\n /**\n * Get the root timegroup ID for localStorage key generation.\n * Returns null if no root timegroup is found or it has no ID.\n */\n private getRootTimegroupId(): string | null {\n const timegroup = this.getTimegroup();\n if (!timegroup) return null;\n\n const rootTemporal = findRootTemporal(timegroup);\n if (rootTemporal instanceof EFTimegroup && rootTemporal.id) {\n return rootTemporal.id;\n }\n\n return null;\n }\n\n /**\n * Get localStorage key for preview pan/zoom state.\n */\n private getPreviewPanZoomStorageKey(): string | null {\n const rootId = this.getRootTimegroupId();\n return rootId ? `ef-workbench-panzoom-${rootId}` : null;\n }\n\n /**\n * Save preview pan/zoom to localStorage.\n */\n private savePreviewPanZoom(): void {\n const storageKey = this.getPreviewPanZoomStorageKey();\n if (!storageKey) return;\n\n try {\n const state = {\n x: this.panZoomTransform.x,\n y: this.panZoomTransform.y,\n scale: this.panZoomTransform.scale,\n };\n localStorage.setItem(storageKey, JSON.stringify(state));\n } catch (error) {\n console.warn(\"Failed to save preview pan/zoom to localStorage\", error);\n }\n }\n\n /**\n * Restore preview pan/zoom from localStorage.\n */\n private restorePreviewPanZoom(): void {\n const storageKey = this.getPreviewPanZoomStorageKey();\n if (!storageKey) return;\n\n try {\n const stored = localStorage.getItem(storageKey);\n if (!stored) return;\n\n const state = JSON.parse(stored);\n if (\n typeof state.x === \"number\" &&\n typeof state.y === \"number\" &&\n typeof state.scale === \"number\" &&\n state.scale > 0\n ) {\n // Clamp scale to valid range [0.1, 5]\n const clampedScale = Math.max(0.1, Math.min(5, state.scale));\n this.panZoomTransform = {\n x: state.x,\n y: state.y,\n scale: clampedScale,\n };\n\n // Apply transform to pan-zoom element if it exists\n requestAnimationFrame(() => {\n const panZoomElement = this.querySelector(\"ef-pan-zoom\");\n if (panZoomElement) {\n (panZoomElement as any).x = this.panZoomTransform.x;\n (panZoomElement as any).y = this.panZoomTransform.y;\n (panZoomElement as any).scale = this.panZoomTransform.scale;\n }\n\n // Update transforms based on current mode\n if (this.presentationMode === \"canvas\") {\n this.updateCanvasTransform();\n }\n });\n }\n } catch (error) {\n console.warn(\n \"Failed to restore preview pan/zoom from localStorage\",\n error,\n );\n }\n }\n\n /**\n * Debounced save of preview pan/zoom to avoid excessive localStorage writes.\n */\n private debouncedSavePreviewPanZoom(): void {\n if (this.savePanZoomDebounceTimer !== null) {\n clearTimeout(this.savePanZoomDebounceTimer);\n }\n this.savePanZoomDebounceTimer = window.setTimeout(() => {\n this.savePanZoomDebounceTimer = null;\n this.savePreviewPanZoom();\n }, 200);\n }\n\n // ==================== Motion State Detection ====================\n\n /**\n * Start polling for motion state (playing/scrubbing).\n * We use polling because:\n * - Playing state comes from timegroup's playbackController\n * - Scrubbing state comes from isScrubbingRef (set by EFScrubber)\n */\n private startMotionStateTracking(): void {\n if (this.playingCheckInterval !== null) return;\n\n this.playingCheckInterval = window.setInterval(() => {\n this.updateMotionState();\n }, 50); // Check every 50ms for responsive motion detection\n }\n\n private stopMotionStateTracking(): void {\n if (this.playingCheckInterval !== null) {\n clearInterval(this.playingCheckInterval);\n this.playingCheckInterval = null;\n }\n if (this.restDebounceTimer !== null) {\n clearTimeout(this.restDebounceTimer);\n this.restDebounceTimer = null;\n }\n }\n\n /**\n * Update motion state by checking timegroup and scrubbing ref.\n */\n private updateMotionState(): void {\n const timegroup = this.getTimegroup();\n const wasPlaying = this.isPlaying;\n const wasScrubbing = this.isScrubbing;\n\n // Check playing state from timegroup\n this.isPlaying = timegroup?.playing ?? false;\n\n // Check scrubbing state from ref\n this.isScrubbing = this.isScrubbingRef.current;\n\n const wasInMotion = wasPlaying || wasScrubbing;\n const isInMotion = this.isPlaying || this.isScrubbing;\n\n // Handle motion state transitions\n if (isInMotion && !wasInMotion) {\n // Started moving - immediately mark as not at rest\n this.handleMotionStart();\n } else if (!isInMotion && wasInMotion) {\n // Stopped moving - start debounce timer for rest\n this.handleMotionStop();\n }\n }\n\n /**\n * Pause or unpause the canvas overlay components (EFCanvas, SelectionOverlay)\n * to avoid layout-thrashing during playback.\n */\n private setOverlaysPaused(paused: boolean): void {\n const canvasSlot = this.querySelector(\"[slot='canvas']\");\n if (!canvasSlot) return;\n const efCanvas = canvasSlot.querySelector(\n \"ef-canvas\",\n ) as HTMLElement | null;\n if (efCanvas && \"paused\" in efCanvas) {\n (efCanvas as any).paused = paused;\n }\n // SelectionOverlay is a sibling of ef-pan-zoom inside the workbench shadow DOM,\n // or a child of the canvas slot container. Search both locations.\n const overlay =\n (canvasSlot.querySelector(\n \"ef-canvas-selection-overlay\",\n ) as HTMLElement | null) ??\n (this.querySelector(\"ef-canvas-selection-overlay\") as HTMLElement | null);\n if (overlay && \"paused\" in overlay) {\n (overlay as any).paused = paused;\n }\n }\n\n /**\n * Called when motion starts (playing or scrubbing began).\n */\n private handleMotionStart(): void {\n // Cancel any pending rest transition\n if (this.restDebounceTimer !== null) {\n clearTimeout(this.restDebounceTimer);\n this.restDebounceTimer = null;\n }\n\n // Mark as in motion immediately\n this.isAtRest = false;\n\n // Pause overlay RAF loops to free CPU for the render pipeline\n this.setOverlaysPaused(true);\n\n // For auto mode, initialize the tracker at the current display scale\n // so it doesn't have to step down from 100% to reach it.\n if (this.previewResolutionScale === \"auto\" && this.adaptiveTracker) {\n const timegroup = this.getTimegroup();\n if (timegroup) {\n const compositionWidth = timegroup.offsetWidth || 1920;\n const compositionHeight = timegroup.offsetHeight || 1080;\n const rect = timegroup.getBoundingClientRect();\n const displayScale = Math.min(\n rect.width / compositionWidth,\n rect.height / compositionHeight,\n );\n\n // Initialize tracker at display scale so it can immediately start\n // scaling down if there are performance issues\n this.adaptiveTracker.initializeAtScale(displayScale);\n this.currentAdaptiveScale = this.adaptiveTracker.getRecommendedScale();\n\n // Set canvas to the initial adaptive scale (instant - no rebuild)\n if (this.canvasPreviewResult) {\n this.canvasPreviewResult.setResolutionScale(\n this.currentAdaptiveScale,\n );\n }\n }\n }\n }\n\n /**\n * Called when motion stops (not playing and not scrubbing).\n * Starts a debounce timer before transitioning to rest state.\n */\n private handleMotionStop(): void {\n // Start debounce timer\n if (this.restDebounceTimer !== null) {\n clearTimeout(this.restDebounceTimer);\n }\n\n this.restDebounceTimer = window.setTimeout(() => {\n this.restDebounceTimer = null;\n this.transitionToRest();\n }, REST_DEBOUNCE_MS);\n }\n\n /**\n * Called after debounce period when we're confirmed to be at rest.\n */\n private transitionToRest(): void {\n this.isAtRest = true;\n\n // Resume overlay RAF loops now that playback has stopped\n this.setOverlaysPaused(false);\n\n // If in auto mode, set full resolution (instant - no rebuild needed)\n if (\n this.previewResolutionScale === \"auto\" &&\n this.presentationMode === \"canvas\"\n ) {\n // Reset tracker and set full resolution\n this.adaptiveTracker?.reset();\n this.currentAdaptiveScale = 1;\n\n // Use instant resolution change - no DOM rebuild\n if (this.canvasPreviewResult) {\n this.canvasPreviewResult.setResolutionScale(1);\n }\n }\n }\n\n /**\n * Get the effective resolution scale based on current mode and motion state.\n * For \"auto\" mode, returns full resolution at rest, adaptive scale in motion.\n */\n private getEffectiveResolutionScale(\n timegroup: EFTimegroup,\n canvasContainer: HTMLElement,\n ): number {\n // For non-auto modes, use the existing logic\n if (this.previewResolutionScale !== \"auto\") {\n return this.getResolutionScale(timegroup, canvasContainer);\n }\n\n // Auto mode: full resolution at rest, adaptive in motion\n const compositionWidth = timegroup.offsetWidth || 1920;\n const compositionHeight = timegroup.offsetHeight || 1080;\n const rect = timegroup.getBoundingClientRect();\n const displayedWidth = rect.width;\n const displayedHeight = rect.height;\n const displayScale = Math.min(\n displayedWidth / compositionWidth,\n displayedHeight / compositionHeight,\n );\n\n if (this.isAtRest) {\n // At rest: use display scale (full resolution for current display size)\n const scale = Math.max(0.1, Math.min(1, displayScale));\n console.log(\n `[EFWorkbench] Auto mode (at rest): using display scale ${(scale * 100).toFixed(1)}%`,\n );\n return scale;\n } else {\n // In motion: use adaptive scale (may be reduced to prevent dropped frames)\n const adaptiveScale = this.currentAdaptiveScale;\n const targetScale = Math.min(displayScale, adaptiveScale);\n const scale = Math.max(0.1, Math.min(1, targetScale));\n console.log(\n `[EFWorkbench] Auto mode (in motion): adaptive=${adaptiveScale}, display=${displayScale.toFixed(2)}, final=${(scale * 100).toFixed(1)}%`,\n );\n return scale;\n }\n }\n\n /**\n * Apply settings when dependencies are ready.\n * Called from updated() hook when settings change or dependencies become available.\n */\n private applySettings(): void {\n // Sync local state from context (for direct property access)\n this.presentationMode = this.previewSettings.presentationMode;\n this.renderMode = this.previewSettings.renderMode;\n this.previewResolutionScale = this.previewSettings.resolutionScale;\n this.showStats = this.previewSettings.showStats;\n }\n\n // ==================== End Motion State Detection ====================\n\n private async handlePresentationModeChange(mode: PreviewPresentationMode) {\n if (mode === this.presentationMode) return;\n\n const previousMode = this.presentationMode;\n\n // Stop previous mode (this will stop stats strategy)\n if (previousMode === \"dom\") {\n this.stopDomMode();\n } else if (previousMode === \"canvas\") {\n this.stopCanvasMode();\n }\n\n // Update context and persist\n setPreviewPresentationMode(mode);\n this.previewSettings = { ...this.previewSettings, presentationMode: mode };\n\n // Wait for Lit to re-render (removes old overlay, adds new one if needed)\n await this.updateComplete;\n\n // Start new mode after DOM is updated\n if (mode === \"dom\") {\n this.initDomMode();\n } else if (mode === \"canvas\") {\n this.initCanvasMode();\n }\n }\n\n private initDomMode() {\n // Don't initialize if we're no longer in dom mode\n if (this.presentationMode !== \"dom\") {\n return;\n }\n\n const timegroup = this.getTimegroup();\n if (!timegroup) {\n setTimeout(() => this.initDomMode(), 100);\n return;\n }\n\n // Pause the ef-fit-scale to prevent it from applying transforms\n const fitScale = this.querySelector(\"[slot='canvas']\") as any;\n const hasFitScaleMethods = !!(\n fitScale?.removeScale && fitScale?.paused !== undefined\n );\n if (hasFitScaleMethods) {\n fitScale.paused = true;\n fitScale.removeScale();\n }\n\n // Disable the timegroup's own proxy mode (it may have been enabled by thumbnail strip)\n (timegroup as any).proxyMode = false;\n\n // Show the original timegroup directly\n timegroup.style.clipPath = \"\";\n timegroup.style.pointerEvents = \"\";\n\n // Create DOM stats strategy if stats are enabled\n if (this.showStats && this.adaptiveTracker) {\n this.domStatsStrategy = new DomStatsStrategy({\n timegroup,\n adaptiveTracker: this.adaptiveTracker,\n });\n this.domStatsStrategy.start();\n }\n }\n\n private stopDomMode() {\n // Stop DOM stats strategy\n if (this.domStatsStrategy) {\n this.domStatsStrategy.stop();\n this.domStatsStrategy = null;\n }\n\n const timegroup = this.getTimegroup();\n if (timegroup) {\n // Hide the original again\n timegroup.style.clipPath = \"inset(100%)\";\n timegroup.style.pointerEvents = \"none\";\n }\n\n // Resume the ef-fit-scale\n const fitScale = this.querySelector(\"[slot='canvas']\") as any;\n if (fitScale?.paused !== undefined) {\n fitScale.paused = false;\n }\n }\n\n /**\n * Get the resolution scale for canvas rendering (for fixed scale modes).\n *\n * Logic:\n * - Get actual displayed size from getBoundingClientRect()\n * - For \"Full\": render at displayed size (1:1 pixel mapping)\n * - For other settings: render at that % of displayed size\n * - Never exceed composition size (100%)\n *\n * Note: For \"auto\" mode, use getEffectiveResolutionScale() instead.\n */\n private getResolutionScale(\n timegroup: EFTimegroup,\n _canvasContainer: HTMLElement,\n ): number {\n // For \"auto\" mode, delegate to getEffectiveResolutionScale\n if (this.previewResolutionScale === \"auto\") {\n return this.getEffectiveResolutionScale(timegroup, _canvasContainer);\n }\n\n // Composition size = the native resolution (offsetWidth/Height gives CSS layout size)\n const compositionWidth = timegroup.offsetWidth || 1920;\n const compositionHeight = timegroup.offsetHeight || 1080;\n\n // Displayed size = actual screen pixels after all transforms\n const rect = timegroup.getBoundingClientRect();\n const displayedWidth = rect.width;\n const displayedHeight = rect.height;\n\n // Display scale = displayed / composition (how much the composition is scaled down for display)\n const displayScale = Math.min(\n displayedWidth / compositionWidth,\n displayedHeight / compositionHeight,\n );\n\n // For \"Full\", we want to render at displayed size (displayScale of composition)\n // For other settings, we want min(displayScale, setting) of composition\n // But we should never exceed 100% of composition\n const targetScale =\n this.previewResolutionScale === 1\n ? displayScale // Full = match display\n : Math.min(displayScale, this.previewResolutionScale); // Others = min of display and setting\n\n // Clamp to reasonable bounds [10%, 100%]\n const finalScale = Math.max(0.1, Math.min(1, targetScale));\n\n return finalScale;\n }\n\n private async initCanvasMode() {\n // Don't initialize if we're no longer in canvas mode\n if (this.presentationMode !== \"canvas\") return;\n\n const timegroup = this.getTimegroup();\n const canvasContainer = this.canvasPreviewRef.value;\n\n // Wait for both timegroup and container to be available\n if (!timegroup || !canvasContainer) {\n setTimeout(() => this.initCanvasMode(), 100);\n return;\n }\n\n // Don't wait for timegroup initialization here - it can cause deadlocks when\n // localStorage restoration triggers seeks that wait for waitForMediaDurations().\n // The canvas refresh loop already handles this by checking if timegroup is ready:\n // - refresh() checks if sourceTimeMs and userTimeMs are synchronized\n // - If they're not synchronized (seek in progress), refresh() returns early\n // - Once the seek completes and times are synchronized, refresh() will render\n // This avoids blocking the main thread while still ensuring correct rendering.\n\n // Disable the timegroup's own proxy mode - workbench handles canvas rendering\n (timegroup as any).proxyMode = false;\n\n // Signal that canvas preview owns the render-capture cycle.\n // PlaybackController will skip runThrottledFrameTask during playback\n // to avoid DOM mutations (Layerize steps) between captures.\n (timegroup as any).canvasPreviewActive = true;\n\n // Ensure the timegroup is visible for native drawElementImage capture.\n // The canvas overlay (position:absolute, inset:0) covers it visually.\n // Clear any clipPath that stopDomMode() may have set.\n timegroup.style.clipPath = \"\";\n timegroup.style.pointerEvents = \"none\";\n\n // Show the canvas container\n canvasContainer.style.display = \"block\";\n\n // Get initial resolution scale based on display size, user setting, and motion state\n const initialResolutionScale =\n this.previewResolutionScale === \"auto\"\n ? this.getEffectiveResolutionScale(timegroup, canvasContainer)\n : this.getResolutionScale(timegroup, canvasContainer);\n\n // Track zoom level for detecting significant changes\n this.lastCanvasZoom = this.panZoomTransform.scale;\n\n try {\n // CRITICAL: Wait for any in-progress seek to complete AND let playback controller initialize\n // The playback controller may be restoring time from localStorage\n await timegroup.seekTask.taskComplete;\n\n // If there's a playback controller, wait for it to complete initial seek\n // This prevents rendering at 0ms before restoring to saved time\n if (timegroup.playbackController) {\n // The playback controller's seek is async, give it time to start and coordinate animations\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n\n // CRITICAL: Ensure timegroup has correct display states before first render\n // Text elements have default display:flex until updateAnimations runs\n updateAnimations(timegroup);\n\n // Create canvas preview - this builds the clone structure ONCE\n // Dynamic import to avoid loading render utilities during SSR\n const { renderTimegroupToCanvas } =\n await import(\"../preview/renderTimegroupToCanvas.js\");\n const result = renderTimegroupToCanvas(timegroup, {\n scale: 1,\n resolutionScale: initialResolutionScale,\n });\n\n // Store the full result for dynamic resolution changes\n this.canvasPreviewResult = result;\n\n const { container, canvas, refresh } = result;\n\n canvas.classList.add(\"clone-content\");\n\n canvasContainer.innerHTML = \"\";\n canvasContainer.appendChild(container);\n\n // Apply current transform\n this.updateCanvasTransform();\n\n // Start the canvas render loop\n const loop = async () => {\n if (this.presentationMode !== \"canvas\") return;\n\n // Skip refresh during export to avoid wasting CPU\n if (!this.isExporting) {\n try {\n // Measure render time for stats tracking\n const renderStart = performance.now();\n await refresh();\n const renderTime = performance.now() - renderStart;\n\n // Always record render stats (regardless of display visibility)\n if (this.renderStats) {\n this.renderStats.recordFrame(\n renderTime,\n performance.now(),\n this.isAtRest,\n );\n }\n\n this.updateCanvasTransform();\n } catch (e) {\n console.error(\"Canvas refresh failed:\", e);\n }\n }\n\n this.canvasAnimationFrame = requestAnimationFrame(loop);\n };\n this.canvasAnimationFrame = requestAnimationFrame(loop);\n } catch (e) {\n console.error(\"Failed to init canvas mode:\", e);\n }\n }\n\n private stopCanvasMode() {\n if (this.canvasAnimationFrame !== null) {\n cancelAnimationFrame(this.canvasAnimationFrame);\n this.canvasAnimationFrame = null;\n }\n if (this.zoomReinitTimeout !== null) {\n clearTimeout(this.zoomReinitTimeout);\n this.zoomReinitTimeout = null;\n }\n\n this.canvasPreviewResult?.dispose();\n this.canvasPreviewResult = null;\n\n const timegroup = this.getTimegroup();\n if (timegroup) {\n (timegroup as any).canvasPreviewActive = false;\n }\n\n // Note: renderStats persists across mode changes - no cleanup needed\n\n // Clear and hide the canvas container\n const container = this.canvasPreviewRef.value;\n if (container) {\n container.innerHTML = \"\";\n container.style.display = \"none\";\n }\n }\n\n private updateCanvasTransform() {\n if (this.presentationMode !== \"canvas\") return;\n\n const container = this.canvasPreviewRef.value;\n if (!container) return;\n\n const canvas = container.querySelector(\"canvas\") as HTMLElement;\n if (!canvas) return;\n\n const { x, y, scale } = this.panZoomTransform;\n canvas.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;\n }\n\n // Canvas renderer - kept for thumbnail generation\n async initCanvasRenderer(): Promise<{\n canvas: HTMLCanvasElement;\n refresh: () => Promise<void>;\n } | null> {\n const timegroup = this.getTimegroup();\n if (!timegroup) return null;\n\n try {\n // Dynamic import to avoid loading render utilities during SSR\n const { renderTimegroupToCanvas } =\n await import(\"../preview/renderTimegroupToCanvas.js\");\n const { canvas, refresh } = renderTimegroupToCanvas(timegroup, 1);\n return { canvas, refresh };\n } catch (e) {\n console.error(\"Failed to init canvas renderer:\", e);\n return null;\n }\n }\n\n /** Start video export with progress tracking */\n async startExport(options: RenderToVideoOptions = {}): Promise<void> {\n const timegroup = this.getTimegroup();\n if (!timegroup) {\n console.error(\"No timegroup found for export\");\n return;\n }\n\n if (this.isExporting) {\n console.warn(\"Export already in progress\");\n return;\n }\n\n this.exportAbortController = new AbortController();\n this.isExporting = true;\n this.exportProgress = null;\n this.exportStatus = \"rendering\";\n\n try {\n // Dynamic import to avoid loading render utilities during SSR\n const { renderTimegroupToVideo } =\n await import(\"../preview/renderTimegroupToVideo.js\");\n await renderTimegroupToVideo(timegroup, {\n ...options,\n signal: this.exportAbortController.signal,\n onProgress: (progress) => {\n this.exportProgress = progress;\n },\n });\n\n this.exportStatus = \"complete\";\n setTimeout(() => {\n this.isExporting = false;\n this.exportProgress = null;\n this.exportStatus = \"idle\";\n this.exportAbortController = null;\n }, 2000);\n } catch (e) {\n // Need to re-import to check error type\n const { RenderCancelledError } =\n await import(\"../preview/renderTimegroupToVideo.js\");\n if (e instanceof RenderCancelledError) {\n console.log(\"Export cancelled by user\");\n this.exportStatus = \"cancelled\";\n setTimeout(() => {\n this.isExporting = false;\n this.exportProgress = null;\n this.exportStatus = \"idle\";\n this.exportAbortController = null;\n }, 1500);\n } else {\n console.error(\"Export failed:\", e);\n this.exportStatus = \"error\";\n setTimeout(() => {\n this.isExporting = false;\n this.exportProgress = null;\n this.exportStatus = \"idle\";\n this.exportAbortController = null;\n }, 3000);\n }\n }\n }\n\n /** Cancel the current export */\n cancelExport(): void {\n if (this.exportAbortController) {\n this.exportAbortController.abort();\n }\n }\n\n private positionPopover(popover: HTMLElement, anchorId: string) {\n const anchor = this.shadowRoot?.getElementById(anchorId);\n if (!anchor) return;\n\n const anchorRect = anchor.getBoundingClientRect();\n const popoverRect = popover.getBoundingClientRect();\n const padding = 8;\n\n // Position below the anchor\n let top = anchorRect.bottom + padding;\n // Align right edge of popover with right edge of anchor\n let left = anchorRect.right - popoverRect.width;\n\n // Keep within viewport bounds\n if (left < padding) {\n left = padding;\n }\n if (left + popoverRect.width > window.innerWidth - padding) {\n left = window.innerWidth - popoverRect.width - padding;\n }\n if (top + popoverRect.height > window.innerHeight - padding) {\n // Flip to above if doesn't fit below\n top = anchorRect.top - popoverRect.height - padding;\n }\n\n popover.style.top = `${top}px`;\n popover.style.left = `${left}px`;\n }\n\n private handleSettingsPopoverToggle(e: Event) {\n const popover = e.target as HTMLElement;\n if ((e as ToggleEvent).newState === \"open\") {\n // Position after the popover is shown so we can measure it\n requestAnimationFrame(() => {\n this.positionPopover(popover, \"settings-btn\");\n });\n }\n }\n\n private handleExportPopoverToggle(e: Event) {\n const popover = e.target as HTMLElement;\n if ((e as ToggleEvent).newState === \"open\") {\n // Initialize export options when popover opens\n if (this.exportOptions.outMs === 0) {\n const timegroup = this.getTimegroup();\n if (timegroup) {\n this.exportOptions = {\n ...this.exportOptions,\n outMs: timegroup.durationMs,\n };\n }\n }\n // Position after the popover is shown\n requestAnimationFrame(() => {\n this.positionPopover(popover, \"export-btn\");\n });\n }\n }\n\n private handleStartExport() {\n this.startExport({\n includeAudio: this.exportOptions.includeAudio,\n scale: this.exportOptions.scale,\n fromMs: this.exportOptions.useInOut ? this.exportOptions.inMs : undefined,\n toMs: this.exportOptions.useInOut ? this.exportOptions.outMs : undefined,\n });\n }\n\n private updateExportOption<K extends keyof typeof this.exportOptions>(\n key: K,\n value: (typeof this.exportOptions)[K],\n ) {\n this.exportOptions = { ...this.exportOptions, [key]: value };\n }\n\n private formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n if (minutes > 0) {\n return `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n }\n return `${seconds}s`;\n }\n\n private handleCancelClick() {\n this.cancelExport();\n }\n\n private handleRenderModeChange(mode: RenderMode) {\n setRenderMode(mode);\n this.previewSettings = { ...this.previewSettings, renderMode: mode };\n }\n\n private handleShowStatsToggle(enabled: boolean) {\n setShowStats(enabled);\n this.previewSettings = { ...this.previewSettings, showStats: enabled };\n // applySettings() will be called automatically via updated() hook when context changes\n }\n\n private handleShowThumbnailTimestampsToggle(enabled: boolean) {\n setShowThumbnailTimestamps(enabled);\n this.previewSettings = {\n ...this.previewSettings,\n showThumbnailTimestamps: enabled,\n };\n }\n\n /**\n * Get initial theme from localStorage or default to 'dark'\n */\n private getInitialTheme(): \"light\" | \"dark\" | \"system\" {\n if (typeof window === \"undefined\") return \"dark\";\n const stored = localStorage.getItem(\"ef-theme\");\n if (stored === \"light\" || stored === \"dark\" || stored === \"system\") {\n return stored;\n }\n return \"dark\";\n }\n\n /**\n * Cycle through theme modes: light → dark → system → light\n */\n private handleThemeToggle(): void {\n const nextTheme =\n this.themeMode === \"light\"\n ? \"dark\"\n : this.themeMode === \"dark\"\n ? \"system\"\n : \"light\";\n this.themeMode = nextTheme;\n localStorage.setItem(\"ef-theme\", nextTheme);\n this.applyTheme();\n }\n\n /**\n * Apply the current theme to the document and host element\n */\n private applyTheme(): void {\n const root = document.documentElement;\n let shouldBeDark = false;\n\n if (this.themeMode === \"light\") {\n shouldBeDark = false;\n } else if (this.themeMode === \"dark\") {\n shouldBeDark = true;\n } else {\n // System mode - check system preference\n shouldBeDark = window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n }\n\n // Apply to document root (for light DOM elements)\n if (shouldBeDark) {\n root.classList.add(\"dark\");\n root.classList.remove(\"light\");\n } else {\n root.classList.add(\"light\");\n root.classList.remove(\"dark\");\n }\n\n // Apply to host element (for shadow DOM and :host-context)\n if (shouldBeDark) {\n this.classList.add(\"dark\");\n this.classList.remove(\"light\");\n } else {\n this.classList.add(\"light\");\n this.classList.remove(\"dark\");\n }\n }\n\n /**\n * Reset and fit the preview to show all content centered.\n * Finds the pan-zoom element and calls fitToContent() on it.\n */\n private handleFitToContent(): void {\n const panZoomElement = this.querySelector(\"ef-pan-zoom\") as any;\n if (panZoomElement && typeof panZoomElement.fitToContent === \"function\") {\n panZoomElement.fitToContent();\n }\n }\n\n private renderSettingsPopover() {\n const isAvailable = isNativeCanvasApiAvailable();\n\n return html`\n <div \n id=\"settings-popover\" \n popover=\"auto\"\n class=\"dropdown-panel\"\n @toggle=${this.handleSettingsPopoverToggle}\n >\n <div class=\"dropdown-header\">\n <span class=\"dropdown-title\">Preview Settings</span>\n <button class=\"dropdown-close\" popovertarget=\"settings-popover\" popovertargetaction=\"hide\">✕</button>\n </div>\n \n <!-- Presentation Mode Setting -->\n <div style=\"\n background: var(--ef-color-bg-inset);\n border-radius: 8px;\n padding: 12px;\n margin-bottom: 10px;\n \">\n <div class=\"dropdown-label\" style=\"margin-bottom: 10px;\">Presentation Mode</div>\n \n <div class=\"button-group\">\n <button\n @click=${() => this.handlePresentationModeChange(\"dom\")}\n class=\"button-group-btn ${this.presentationMode === \"dom\" ? \"active\" : \"\"}\"\n >DOM</button>\n <button\n @click=${() => this.handlePresentationModeChange(\"canvas\")}\n class=\"button-group-btn ${this.presentationMode === \"canvas\" ? \"active\" : \"\"}\"\n >Canvas</button>\n </div>\n \n <div class=\"dropdown-description\">\n ${\n this.presentationMode === \"dom\"\n ? \"Default. Shows the real timegroup DOM directly.\"\n : \"Renders to canvas each frame.\"\n }\n </div>\n </div>\n \n <!-- Render Mode Setting -->\n <div class=\"dropdown-section\">\n <div style=\"display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;\">\n <span class=\"dropdown-label\">Render Mode</span>\n ${\n isAvailable\n ? html`\n <div style=\"display: flex; align-items: center; gap: 5px;\">\n <span style=\"\n display: inline-block;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--ef-color-success);\n \"></span>\n <span style=\"color: var(--ef-color-success); font-size: 10px; font-weight: 500;\">\n Native Available\n </span>\n </div>\n `\n : \"\"\n }\n </div>\n \n <div class=\"button-group\">\n <button\n @click=${() => this.handleRenderModeChange(\"foreignObject\")}\n class=\"button-group-btn ${this.renderMode === \"foreignObject\" ? \"active\" : \"\"}\"\n >foreignObject</button>\n <button\n @click=${() => this.handleRenderModeChange(\"native\")}\n ?disabled=${!isAvailable}\n class=\"button-group-btn ${this.renderMode === \"native\" ? \"active\" : \"\"}\"\n style=\"\n cursor: ${isAvailable ? \"pointer\" : \"not-allowed\"};\n opacity: ${isAvailable ? \"1\" : \"0.5\"};\n \"\n >native</button>\n </div>\n \n <div class=\"dropdown-description\">\n ${\n this.renderMode === \"foreignObject\"\n ? \"SVG foreignObject serialization. Works everywhere but slower.\"\n : \"Chrome's drawElementImage API. Fastest, requires chrome://flags/#canvas-draw-element.\"\n }\n </div>\n </div>\n \n <!-- This section was already updated earlier in the dropdown-section refactor -->\n \n <!-- Show Performance Stats Setting -->\n <div class=\"dropdown-section\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n ?checked=${this.showStats}\n @change=${(e: Event) => this.handleShowStatsToggle((e.target as HTMLInputElement).checked)}\n />\n <span>Show Performance Stats</span>\n </label>\n \n <div class=\"dropdown-description\">\n Display FPS, CPU pressure, and performance metrics overlay.\n </div>\n </div>\n \n <!-- Thumbnail Timestamps -->\n <div class=\"dropdown-section\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n ?checked=${this.previewSettings.showThumbnailTimestamps}\n @change=${(e: Event) => this.handleShowThumbnailTimestampsToggle((e.target as HTMLInputElement).checked)}\n />\n <span>Show Thumbnail Timestamps</span>\n </label>\n \n <div class=\"dropdown-description\">\n Display timestamp overlay on timeline thumbnails for debugging.\n </div>\n </div>\n </div>\n `;\n }\n\n private renderExportPopover() {\n const timegroup = this.getTimegroup();\n const durationMs = timegroup?.durationMs ?? 0;\n\n return html`\n <div \n id=\"export-popover\" \n popover=\"auto\"\n class=\"dropdown-panel\"\n @toggle=${this.handleExportPopoverToggle}\n >\n <div class=\"dropdown-header\">\n <span class=\"dropdown-title\">Export Settings</span>\n <button class=\"dropdown-close\" popovertarget=\"export-popover\" popovertargetaction=\"hide\">✕</button>\n </div>\n \n <!-- Scale -->\n <div style=\"margin-bottom: 10px;\">\n <label class=\"dropdown-label\" style=\"display: block; margin-bottom: 4px;\">Scale</label>\n <select\n class=\"dropdown-select\"\n .value=${String(this.exportOptions.scale)}\n @change=${(e: Event) => this.updateExportOption(\"scale\", Number((e.target as HTMLSelectElement).value))}\n >\n <option value=\"1\">100% (Full)</option>\n <option value=\"0.75\">75%</option>\n <option value=\"0.5\">50%</option>\n <option value=\"0.25\">25%</option>\n </select>\n </div>\n \n <!-- Audio -->\n <div style=\"margin-bottom: 10px;\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n ?checked=${this.exportOptions.includeAudio}\n @change=${(e: Event) => this.updateExportOption(\"includeAudio\", (e.target as HTMLInputElement).checked)}\n />\n <span>Include Audio</span>\n </label>\n </div>\n \n <!-- In/Out Range -->\n <div style=\"margin-bottom: 12px;\">\n <label class=\"checkbox-label\" style=\"margin-bottom: 6px;\">\n <input\n type=\"checkbox\"\n ?checked=${this.exportOptions.useInOut}\n @change=${(e: Event) => this.updateExportOption(\"useInOut\", (e.target as HTMLInputElement).checked)}\n />\n <span>Custom Range</span>\n </label>\n \n ${\n this.exportOptions.useInOut\n ? html`\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 6px;\">\n <div>\n <label class=\"dropdown-label\" style=\"display: block; margin-bottom: 2px; font-size: 10px;\">In (ms)</label>\n <input\n type=\"number\"\n class=\"dropdown-input\"\n style=\"font-family: ui-monospace, monospace;\"\n min=\"0\"\n max=${durationMs}\n .value=${String(this.exportOptions.inMs)}\n @change=${(e: Event) => this.updateExportOption(\"inMs\", Number((e.target as HTMLInputElement).value))}\n />\n </div>\n <div>\n <label class=\"dropdown-label\" style=\"display: block; margin-bottom: 2px; font-size: 10px;\">Out (ms)</label>\n <input\n type=\"number\"\n class=\"dropdown-input\"\n style=\"font-family: ui-monospace, monospace;\"\n min=\"0\"\n max=${durationMs}\n .value=${String(this.exportOptions.outMs)}\n @change=${(e: Event) => this.updateExportOption(\"outMs\", Number((e.target as HTMLInputElement).value))}\n />\n </div>\n </div>\n <div class=\"dropdown-description\" style=\"margin-top: 4px;\">\n Duration: ${this.formatTime(this.exportOptions.outMs - this.exportOptions.inMs)} / ${this.formatTime(durationMs)}\n </div>\n `\n : html`\n <div class=\"dropdown-description\">\n Full duration: ${this.formatTime(durationMs)}\n </div>\n `\n }\n </div>\n \n <!-- Start Export button -->\n <button\n class=\"toolbar-btn primary\"\n style=\"width: 100%; justify-content: center;\"\n @click=${this.handleStartExport}\n popovertarget=\"export-popover\"\n popovertargetaction=\"hide\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"23 7 16 12 23 17 23 7\"></polygon>\n <rect x=\"1\" y=\"5\" width=\"15\" height=\"14\" rx=\"2\" ry=\"2\"></rect>\n </svg>\n Start Export\n </button>\n </div>\n `;\n }\n\n private renderExportProgressPopover() {\n const p = this.exportProgress;\n const progressPercent = p ? Math.round(p.progress * 100) : 0;\n const isComplete = this.exportStatus === \"complete\";\n const isError = this.exportStatus === \"error\";\n const isCancelled = this.exportStatus === \"cancelled\";\n const isRendering = this.exportStatus === \"rendering\";\n\n let statusColor: string;\n let statusText: string;\n\n if (isComplete) {\n statusColor = \"var(--ef-color-success)\";\n statusText = \"Complete!\";\n } else if (isError) {\n statusColor = \"var(--ef-color-danger)\";\n statusText = \"Failed\";\n } else if (isCancelled) {\n statusColor = \"var(--ef-color-warning)\";\n statusText = \"Cancelled\";\n } else {\n statusColor = \"var(--ef-color-primary)\";\n statusText = `${progressPercent}%`;\n }\n\n return html`\n <div \n id=\"export-progress-popover\" \n popover=\"manual\"\n class=\"dropdown-panel\" \n style=\"min-width: 240px;\"\n >\n <div class=\"dropdown-header\">\n <span class=\"dropdown-title\">Exporting</span>\n ${\n isRendering\n ? html`\n <button \n class=\"dropdown-close\" \n style=\"color: var(--ef-color-danger);\"\n @click=${this.handleCancelClick}\n >Cancel</button>\n `\n : null\n }\n </div>\n \n ${\n isRendering && p !== null\n ? html`\n ${\n p.framePreviewCanvas\n ? html`\n <div style=\"margin-bottom: 10px; display: flex; justify-content: center;\">\n ${p.framePreviewCanvas}\n </div>\n <style>\n ef-workbench canvas {\n border-radius: 4px;\n border: 1px solid var(--ef-color-border);\n max-width: 100%;\n height: auto;\n }\n </style>\n `\n : null\n }\n \n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; margin-bottom: 10px; font-family: ui-monospace, monospace; font-size: 10px;\">\n <div>\n <div style=\"color: var(--ef-color-text-subtle);\">Frames</div>\n <div style=\"color: var(--ef-color-text);\">${p.currentFrame} / ${p.totalFrames}</div>\n </div>\n <div>\n <div style=\"color: var(--ef-color-text-subtle);\">Time</div>\n <div style=\"color: var(--ef-color-text);\">${this.formatTime(p.renderedMs)} / ${this.formatTime(p.totalDurationMs)}</div>\n </div>\n <div>\n <div style=\"color: var(--ef-color-text-subtle);\">Speed</div>\n <div style=\"color: ${p.speedMultiplier >= 1 ? \"var(--ef-color-success)\" : \"var(--ef-color-warning)\"};\">${p.speedMultiplier.toFixed(2)}x</div>\n </div>\n <div>\n <div style=\"color: var(--ef-color-text-subtle);\">ETA</div>\n <div style=\"color: var(--ef-color-text);\">${this.formatTime(p.estimatedRemainingMs)}</div>\n </div>\n </div>\n `\n : null\n }\n \n <div style=\"height: 4px; background: var(--ef-color-bg-inset); border-radius: 2px; overflow: hidden;\">\n <div style=\"\n height: 100%;\n width: ${progressPercent}%;\n background: ${statusColor};\n border-radius: 2px;\n transition: width 0.15s ease-out;\n \"></div>\n </div>\n \n <div style=\"text-align: center; margin-top: 6px; font-size: 11px; font-weight: 600; color: ${statusColor};\">\n ${statusText}\n </div>\n </div>\n \n <style>\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n </style>\n `;\n }\n\n private renderToolbar() {\n return html`\n <div class=\"toolbar\" part=\"toolbar\">\n <div class=\"toolbar-left\">\n <!-- Fit to content button -->\n <button \n class=\"toolbar-icon-btn\"\n @click=${this.handleFitToContent}\n title=\"Fit to Content (Reset Zoom & Center)\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"></rect>\n <path d=\"M8 12h8M12 8v8\"></path>\n </svg>\n </button>\n </div>\n \n <div class=\"toolbar-right\">\n <!-- Mode indicator -->\n <span class=\"mode-indicator ${this.presentationMode}\">\n ${\n this.presentationMode === \"dom\"\n ? \"DOM\"\n : html`\n Canvas ${\n getRenderMode() === \"native\"\n ? phosphorIcon(ICONS.lightning, 12)\n : phosphorIcon(ICONS.code, 12)\n }\n `\n }\n </span>\n \n <!-- Theme toggle button -->\n <button \n class=\"toolbar-icon-btn\"\n @click=${this.handleThemeToggle}\n title=\"${this.themeMode === \"light\" ? \"Light mode\" : this.themeMode === \"dark\" ? \"Dark mode\" : \"System preference\"}\"\n >\n ${\n this.themeMode === \"light\"\n ? html`\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"></line>\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"></line>\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"></line>\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"></line>\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"></line>\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"></line>\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"></line>\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"></line>\n </svg>\n `\n : this.themeMode === \"dark\"\n ? html`\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"></path>\n </svg>\n `\n : html`\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\" ry=\"2\"></rect>\n <line x1=\"8\" y1=\"21\" x2=\"16\" y2=\"21\"></line>\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"21\"></line>\n </svg>\n `\n }\n </button>\n \n <!-- Settings button -->\n <button \n id=\"settings-btn\"\n class=\"toolbar-icon-btn\"\n popovertarget=\"settings-popover\"\n title=\"Preview Settings\"\n >\n ${phosphorIcon(ICONS.gear, 16)}\n </button>\n \n <!-- Export button -->\n ${\n this.isExporting\n ? html`\n <button \n id=\"export-btn\"\n class=\"toolbar-btn active\"\n style=\"min-width: 100px;\"\n popovertarget=\"export-progress-popover\"\n >\n <div style=\"width: 12px; height: 12px; border: 2px solid var(--ef-color-primary-subtle); border-top-color: var(--ef-color-primary); border-radius: 50%; animation: spin 1s linear infinite;\"></div>\n Exporting...\n </button>\n `\n : html`\n <button \n id=\"export-btn\"\n class=\"toolbar-btn primary\"\n popovertarget=\"export-popover\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"23 7 16 12 23 17 23 7\"></polygon>\n <rect x=\"1\" y=\"5\" width=\"15\" height=\"14\" rx=\"2\" ry=\"2\"></rect>\n </svg>\n Export\n </button>\n `\n }\n </div>\n </div>\n \n <!-- Popovers (rendered into top-layer) -->\n ${this.renderSettingsPopover()}\n ${this.renderExportPopover()}\n ${this.renderExportProgressPopover()}\n `;\n }\n\n update(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.update(changedProperties);\n\n if (changedProperties.has(\"focusedElement\")) {\n this.drawOverlays();\n }\n }\n\n updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Restore preview pan/zoom when timegroup becomes available\n // Check if timegroup is now available (slot content changed)\n const timegroup = this.getTimegroup();\n if (timegroup && !changedProperties.has(\"panZoomTransform\")) {\n // Only restore if we haven't already restored (avoid overwriting user changes)\n // Check if panZoomTransform is still at default values\n if (\n this.panZoomTransform.x === 0 &&\n this.panZoomTransform.y === 0 &&\n this.panZoomTransform.scale === 1\n ) {\n requestAnimationFrame(() => {\n this.restorePreviewPanZoom();\n });\n }\n }\n\n // Apply settings when dependencies become available or settings change\n if (\n changedProperties.has(\"previewSettings\") ||\n changedProperties.has(\"presentationMode\") ||\n changedProperties.has(\"showStats\")\n ) {\n this.applySettings();\n }\n\n // Show/hide export progress popover based on isExporting state\n if (changedProperties.has(\"isExporting\")) {\n const popover = this.shadowRoot?.getElementById(\n \"export-progress-popover\",\n ) as HTMLElement | null;\n if (popover) {\n if (this.isExporting) {\n popover.showPopover();\n // Position after showing\n requestAnimationFrame(() => {\n this.positionPopover(popover, \"export-btn\");\n });\n } else {\n popover.hidePopover();\n }\n }\n }\n }\n\n drawOverlays = () => {\n const focusOverlay = this.focusOverlay.value;\n if (focusOverlay) {\n if (this.focusedElement) {\n focusOverlay.style.display = \"block\";\n const rect = this.focusedElement.getBoundingClientRect();\n Object.assign(focusOverlay.style, {\n position: \"fixed\",\n top: `${rect.top}px`,\n left: `${rect.left}px`,\n width: `${rect.width}px`,\n height: `${rect.height}px`,\n });\n requestAnimationFrame(this.drawOverlays);\n } else {\n focusOverlay.style.display = \"none\";\n }\n }\n };\n\n private renderPlaybackStats() {\n // Only show stats if enabled\n if (!this.showStats) {\n return null;\n }\n\n // Get stats based on current mode\n let stats: PlaybackStats | null = null;\n\n if (this.presentationMode === \"canvas\" && this.renderStats) {\n // Canvas mode: use RenderStats (always-on collection)\n const timegroup = this.getTimegroup();\n if (!timegroup) return null;\n\n const compositionWidth = timegroup.offsetWidth || 1920;\n const compositionHeight = timegroup.offsetHeight || 1080;\n\n const resolutionScale = this.canvasPreviewResult\n ? this.canvasPreviewResult.getResolutionScale()\n : 1;\n\n const renderWidth = Math.floor(compositionWidth * resolutionScale);\n const renderHeight = Math.floor(compositionHeight * resolutionScale);\n\n stats = this.renderStats.getStats(\n renderWidth,\n renderHeight,\n resolutionScale,\n );\n } else if (this.presentationMode === \"dom\" && this.domStatsStrategy) {\n // DOM mode: use DomStatsStrategy (has its own loop)\n stats = this.domStatsStrategy.getStats();\n }\n\n if (!stats) {\n return null;\n }\n\n // Determine FPS color (based on frame interval, not render time)\n const fpsClass =\n stats.fps >= 55 ? \"good\" : stats.fps >= 25 ? \"warning\" : \"bad\";\n\n // Determine render time color (target is 33ms for 30fps)\n const renderClass =\n stats.avgRenderTime !== null\n ? stats.avgRenderTime <= 20\n ? \"good\"\n : stats.avgRenderTime <= 30\n ? \"warning\"\n : \"bad\"\n : \"\";\n\n // Determine headroom color (positive = good, negative = bad)\n const headroomClass =\n stats.headroom !== null\n ? stats.headroom >= 10\n ? \"good\"\n : stats.headroom >= 0\n ? \"warning\"\n : \"bad\"\n : \"\";\n\n // Determine pressure color\n const pressureClass =\n stats.pressureState === \"nominal\"\n ? \"good\"\n : stats.pressureState === \"fair\"\n ? \"good\"\n : stats.pressureState === \"serious\"\n ? \"warning\"\n : \"bad\";\n\n // Resolution scale color (only for canvas mode)\n const scaleClass =\n stats.resolutionScale !== null\n ? stats.resolutionScale >= 0.75\n ? \"good\"\n : stats.resolutionScale >= 0.5\n ? \"warning\"\n : \"bad\"\n : \"\";\n\n // Determine which stats to show based on mode\n const isCanvasMode = this.presentationMode === \"canvas\";\n const showRenderTime = isCanvasMode;\n const showHeadroom = isCanvasMode;\n const showResolutionScale = isCanvasMode;\n const showAdaptiveResolution =\n isCanvasMode && this.previewResolutionScale === \"auto\";\n\n // Motion state\n const motionState = this.isAtRest\n ? \"At Rest\"\n : this.isPlaying\n ? \"Playing\"\n : this.isScrubbing\n ? \"Scrubbing\"\n : \"Idle\";\n\n // Render pressure histogram bars\n const renderPressureHistogram = () => {\n if (stats.pressureHistory.length === 0) {\n return html`<div style=\"color: var(--ef-color-text-subtle); font-size: 9px;\">No pressure data (API not available)</div>`;\n }\n\n return html`\n <div class=\"pressure-histogram\">\n ${stats.pressureHistory.map(\n (state) => html`\n <div class=\"bar ${state}\"></div>\n `,\n )}\n </div>\n <div class=\"pressure-histogram-label\">\n <span>30s ago</span>\n <span>now</span>\n </div>\n `;\n };\n\n // Helper to pad numbers for consistent width\n const padNum = (n: number, decimals: number, width: number) => {\n const str = n.toFixed(decimals);\n return str.padStart(width, \"\\u2007\"); // Use figure space for padding\n };\n\n return html`\n <div class=\"playback-stats\">\n <div class=\"stat-row\">\n <span class=\"stat-label\">FPS</span>\n <span class=\"stat-value ${fpsClass}\">${padNum(stats.fps, 1, 5)}</span>\n </div>\n ${\n showRenderTime && stats.avgRenderTime !== null\n ? html`\n <div class=\"stat-row\">\n <span class=\"stat-label\">Render</span>\n <span class=\"stat-value ${renderClass}\">${padNum(stats.avgRenderTime, 1, 5)}ms</span>\n </div>\n `\n : null\n }\n ${\n showHeadroom && stats.headroom !== null\n ? html`\n <div class=\"stat-row\">\n <span class=\"stat-label\">Headroom</span>\n <span class=\"stat-value ${headroomClass}\">${stats.headroom >= 0 ? \"+\" : \"\"}${padNum(stats.headroom, 1, 4)}ms</span>\n </div>\n `\n : null\n }\n <div class=\"stat-row\">\n <span class=\"stat-label\">Resolution</span>\n <span class=\"stat-value\">${stats.renderWidth}×${stats.renderHeight}</span>\n </div>\n ${\n showResolutionScale && stats.resolutionScale !== null\n ? html`\n <div class=\"stat-row\">\n <span class=\"stat-label\">Scale</span>\n <span class=\"stat-value ${scaleClass}\">${String(Math.round(stats.resolutionScale * 100)).padStart(3, \"\\u2007\")}%</span>\n </div>\n `\n : null\n }\n <div class=\"stat-row\">\n <span class=\"stat-label\">CPU</span>\n <span class=\"stat-value ${pressureClass}\">${stats.pressureState}</span>\n </div>\n <div class=\"stat-row\">\n <span class=\"stat-label\">State</span>\n <span class=\"stat-value\">${motionState}</span>\n </div>\n ${\n showAdaptiveResolution && stats.samplesAtCurrentScale !== undefined\n ? html`\n <div style=\"margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--ef-color-border-subtle);\">\n <div class=\"stat-row\">\n <span class=\"stat-label\">Mode</span>\n <span class=\"stat-value good\">Auto</span>\n </div>\n <div class=\"stat-row\">\n <span class=\"stat-label\">Samples</span>\n <span class=\"stat-value\">${String(stats.samplesAtCurrentScale).padStart(3, \"\\u2007\")}/60</span>\n </div>\n <div class=\"stat-row\">\n <span class=\"stat-label\">Scale Up</span>\n <span class=\"stat-value ${stats.canScaleUp ? \"good\" : \"\"}\">${stats.canScaleUp ? \"Ready\" : \"Waiting\"}</span>\n </div>\n <div class=\"stat-row\">\n <span class=\"stat-label\">Scale Down</span>\n <span class=\"stat-value ${stats.canScaleDown ? \"\" : \"warning\"}\">${stats.canScaleDown ? \"Ready\" : \"Min\"}</span>\n </div>\n </div>\n `\n : null\n }\n \n <!-- CPU Pressure Histogram -->\n <div style=\"margin-top: 8px;\">\n <div style=\"color: #94a3b8; font-size: 10px; margin-bottom: 4px;\">CPU Pressure History</div>\n ${renderPressureHistogram()}\n </div>\n </div>\n `;\n }\n\n render() {\n if (this.rendering) {\n return html`\n <slot class=\"fixed inset-0 h-full w-full\" name=\"canvas\"></slot>\n `;\n }\n return html`\n <!-- Top: Full-width Toolbar -->\n <div style=\"grid-row: 1 / 2; grid-column: 1 / -1;\">\n ${this.renderToolbar()}\n </div>\n \n <!-- Left: Hierarchy Panel -->\n <div\n style=\"grid-row: 2 / 3; grid-column: 1 / 2; background: var(--ef-color-bg-panel); border-right: 1px solid var(--ef-color-border); min-height: 0; overflow: hidden;\"\n >\n <slot name=\"hierarchy\"></slot>\n </div>\n\n <!-- Center: Canvas area -->\n <div\n class=\"canvas-container\"\n part=\"canvas\"\n style=\"grid-row: 2 / 3; grid-column: 2 / 3; min-height: 0; overflow: hidden;\"\n @wheel=${this.handleStageWheel}\n >\n <!-- Original timegroup (hidden in clone/canvas mode, visible in dom mode) -->\n <slot name=\"canvas\"></slot>\n \n <!-- Canvas preview (visible in canvas mode only) -->\n <div \n class=\"canvas-overlay\" \n ${ref(this.canvasPreviewRef)}\n style=\"display: ${this.presentationMode === \"canvas\" ? \"block\" : \"none\"}\"\n ></div>\n \n <!-- Playback stats overlay (visible in canvas mode only) -->\n ${this.renderPlaybackStats()}\n </div>\n\n <!-- Bottom: Timeline -->\n <div\n class=\"overflow-hidden\"\n style=\"grid-row: 3 / 4; grid-column: 1 / -1; height: 100%; border-top: 1px solid var(--ef-color-border);\"\n >\n <slot name=\"timeline\"></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-workbench\": EFWorkbench;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EFWorkbench.js","names":["EFWorkbench","#trackedTimegroup","#readyStateHandler","#syncTimegroupListener","state","statusColor: string","statusText: string","stats: PlaybackStats | null"],"sources":["../../src/gui/EFWorkbench.ts"],"sourcesContent":["import { css, html, LitElement, type PropertyValueMap } from \"lit\";\nimport {\n customElement,\n eventOptions,\n property,\n state,\n} from \"lit/decorators.js\";\nimport { createRef, ref } from \"lit/directives/ref.js\";\n\nimport { ContextMixin } from \"./ContextMixin.js\";\nimport { TWMixin } from \"./TWMixin.js\";\nimport { EFTimegroup } from \"../elements/EFTimegroup.js\";\nimport { findRootTemporal } from \"../elements/findRootTemporal.js\";\n// Import only types - actual functions loaded dynamically to avoid SSR issues\nimport type { CanvasPreviewResult } from \"../preview/renderTimegroupToCanvas.types.js\";\nimport type {\n RenderToVideoOptions,\n RenderProgress,\n} from \"../preview/renderTimegroupToVideo.types.js\";\nimport { updateAnimations } from \"../elements/updateAnimations.js\";\nimport {\n isNativeCanvasApiAvailable,\n getPreviewPresentationMode,\n setPreviewPresentationMode,\n type PreviewPresentationMode,\n getRenderMode,\n setRenderMode,\n type RenderMode,\n getPreviewResolutionScale,\n type PreviewResolutionScale,\n getShowStats,\n getShowThumbnailTimestamps,\n setShowThumbnailTimestamps,\n} from \"../preview/previewSettings.js\";\nimport { setShowStats } from \"../preview/previewSettings.js\";\nimport { AdaptiveResolutionTracker } from \"../preview/AdaptiveResolutionTracker.js\";\nimport { RenderStats, type PlaybackStats } from \"../preview/RenderStats.js\";\nimport { DomStatsStrategy } from \"../preview/statsTrackingStrategy.js\";\nimport { provide } from \"@lit/context\";\nimport {\n previewSettingsContext,\n type PreviewSettings,\n} from \"./previewSettingsContext.js\";\nimport { phosphorIcon, ICONS } from \"./icons.js\";\n\n// Side-effect import for template usage (pan-zoom is created in light DOM by wrapWithWorkbench)\nimport \"./EFFitScale.js\";\n\n/** Debounce delay before considering the preview \"at rest\" after motion stops */\nconst REST_DEBOUNCE_MS = 200;\n\n@customElement(\"ef-workbench\")\nexport class EFWorkbench extends ContextMixin(TWMixin(LitElement)) {\n static styles = [\n css`\n :host {\n display: grid;\n grid-template-rows: auto 1fr 280px;\n grid-template-columns: 280px 1fr;\n width: 100%;\n height: 100%;\n overflow: hidden;\n background-color: var(--ef-color-bg);\n \n /* Component tokens (reference globals from ef-theme.css) */\n --workbench-bg: var(--ef-color-bg);\n --workbench-overlay-border: var(--ef-color-primary);\n --workbench-overlay-bg: var(--ef-color-primary-subtle);\n --toolbar-bg: var(--ef-color-bg-elevated);\n --toolbar-border: var(--ef-color-border-subtle);\n }\n \n /* Utility classes (not relying on external Tailwind) */\n .grid {\n display: grid;\n }\n \n .overflow-hidden {\n overflow: hidden;\n }\n \n .fixed {\n position: fixed;\n }\n \n .inset-0 {\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n }\n \n .h-full {\n height: 100%;\n }\n \n .w-full {\n width: 100%;\n }\n \n .toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n padding: 8px 12px;\n background: var(--toolbar-bg);\n border-bottom: 1px solid var(--toolbar-border);\n flex-shrink: 0;\n font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;\n position: relative;\n z-index: 20;\n }\n \n .toolbar-left {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n \n .toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n \n .toolbar-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 6px 12px;\n background: var(--ef-color-bg-inset);\n border: 1px solid var(--ef-color-border-subtle);\n border-radius: 6px;\n color: var(--ef-color-text);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n }\n \n .toolbar-btn:hover {\n background: var(--ef-color-hover);\n border-color: var(--ef-color-border);\n }\n \n .toolbar-btn.active {\n background: var(--ef-color-primary-subtle);\n border-color: var(--ef-color-primary);\n color: var(--ef-color-primary-hover);\n }\n \n .toolbar-btn.primary {\n background: var(--ef-color-primary);\n border-color: transparent;\n color: white;\n font-weight: 600;\n }\n \n .toolbar-btn.primary:hover {\n background: var(--ef-color-primary-hover);\n }\n \n .toolbar-icon-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n background: var(--ef-color-bg-inset);\n border: 1px solid var(--ef-color-border-subtle);\n border-radius: 6px;\n color: var(--ef-color-text);\n cursor: pointer;\n transition: all 0.15s ease;\n }\n \n .toolbar-icon-btn:hover {\n background: var(--ef-color-hover);\n border-color: var(--ef-color-border);\n }\n \n .toolbar-icon-btn.active {\n background: var(--ef-color-primary-subtle);\n border-color: var(--ef-color-primary);\n color: var(--ef-color-primary-hover);\n }\n \n .mode-indicator {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n white-space: nowrap;\n }\n \n .mode-indicator.dom {\n background: color-mix(in srgb, var(--ef-color-success) 15%, transparent);\n color: var(--ef-color-success);\n border: 1px solid color-mix(in srgb, var(--ef-color-success) 30%, transparent);\n }\n \n .mode-indicator.canvas {\n background: color-mix(in srgb, var(--ef-color-type-image) 15%, transparent);\n color: var(--ef-color-type-image);\n border: 1px solid color-mix(in srgb, var(--ef-color-type-image) 30%, transparent);\n }\n \n .canvas-container {\n position: relative;\n overflow: hidden;\n flex: 1;\n display: grid;\n grid-template-columns: 100%;\n grid-template-rows: 100%;\n min-height: 0;\n background: var(--ef-color-bg);\n }\n \n .canvas-container ::slotted(*) {\n width: 100%;\n height: 100%;\n grid-column: 1;\n grid-row: 1;\n }\n \n .canvas-overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 1;\n }\n\n .preview-loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 2px;\n overflow: hidden;\n z-index: 8;\n pointer-events: none;\n background: var(--ef-color-loading-spinner-track, rgba(255, 255, 255, 0.1));\n }\n\n .preview-loading-bar {\n position: absolute;\n top: 0;\n height: 100%;\n width: 40%;\n background: var(--ef-color-loading-spinner-fill, rgba(255, 255, 255, 0.8));\n animation: preview-sweep 1.4s ease-in-out infinite;\n }\n\n @keyframes preview-sweep {\n 0% { left: -40%; }\n 100% { left: 140%; }\n }\n \n .clone-content {\n position: absolute;\n transform-origin: 0 0;\n }\n \n .playback-stats {\n position: absolute;\n top: 8px;\n left: 8px;\n width: 200px;\n background: color-mix(in srgb, var(--ef-color-bg-elevated) 90%, transparent);\n backdrop-filter: blur(4px);\n border-radius: 6px;\n padding: 8px 12px;\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, monospace;\n font-size: 11px;\n color: var(--ef-color-text);\n z-index: 10;\n pointer-events: none;\n line-height: 1.5;\n }\n \n .playback-stats .stat-row {\n display: flex;\n justify-content: space-between;\n gap: 8px;\n }\n \n .playback-stats .stat-label {\n color: var(--ef-color-text-muted);\n flex-shrink: 0;\n width: 85px;\n }\n \n .playback-stats .stat-value {\n font-weight: 600;\n text-align: right;\n flex: 1;\n font-variant-numeric: tabular-nums;\n }\n \n .playback-stats .stat-value.good {\n color: var(--ef-color-success);\n }\n \n .playback-stats .stat-value.warning {\n color: var(--ef-color-warning);\n }\n \n .playback-stats .stat-value.bad {\n color: var(--ef-color-danger);\n }\n \n .pressure-histogram {\n display: flex;\n align-items: flex-end;\n gap: 1px;\n height: 24px;\n margin-top: 8px;\n padding-top: 8px;\n border-top: 1px solid var(--ef-color-border-subtle);\n }\n \n .pressure-histogram .bar {\n flex: 1;\n min-width: 2px;\n max-width: 4px;\n border-radius: 1px 1px 0 0;\n transition: height 0.1s ease-out;\n }\n \n .pressure-histogram .bar.nominal {\n background: var(--ef-color-success);\n height: 25%;\n }\n \n .pressure-histogram .bar.fair {\n background: var(--ef-color-warning);\n height: 50%;\n }\n \n .pressure-histogram .bar.serious {\n background: var(--ef-color-warning);\n height: 75%;\n }\n \n .pressure-histogram .bar.critical {\n background: var(--ef-color-danger);\n height: 100%;\n }\n \n .pressure-histogram-label {\n display: flex;\n justify-content: space-between;\n margin-top: 4px;\n font-size: 9px;\n color: var(--ef-color-text-subtle);\n }\n \n .dropdown-panel {\n position: fixed;\n margin: 0;\n padding: 14px 16px;\n min-width: 260px;\n max-width: calc(100vw - 32px);\n background: var(--ef-color-bg-elevated);\n border: 1px solid var(--ef-color-border);\n border-radius: 10px;\n backdrop-filter: blur(12px);\n box-shadow: 0 8px 32px color-mix(in srgb, var(--ef-color-bg) 50%, transparent);\n }\n \n .dropdown-panel::backdrop {\n background: transparent;\n }\n \n .dropdown-panel:popover-open {\n /* Animation for opening */\n animation: popover-fade-in 0.15s ease-out;\n }\n \n @keyframes popover-fade-in {\n from {\n opacity: 0;\n transform: translateY(-4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n \n .dropdown-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 12px;\n padding-bottom: 10px;\n border-bottom: 1px solid var(--ef-color-border-subtle);\n }\n \n .dropdown-title {\n color: var(--ef-color-text);\n font-size: 13px;\n font-weight: 600;\n }\n \n .dropdown-close {\n background: transparent;\n border: none;\n color: var(--ef-color-text-subtle);\n cursor: pointer;\n }\n \n .dropdown-section {\n background: var(--ef-color-bg-inset);\n border-radius: 8px;\n padding: 12px;\n margin-top: 10px;\n }\n \n .dropdown-label {\n color: var(--ef-color-text);\n font-size: 11px;\n font-weight: 600;\n margin-bottom: 6px;\n }\n \n .dropdown-description {\n margin-top: 8px;\n color: var(--ef-color-text-subtle);\n font-size: 10px;\n line-height: 1.4;\n }\n \n .button-group {\n display: flex;\n gap: 6px;\n margin-top: 6px;\n }\n \n .button-group-btn {\n flex: 1;\n padding: 6px 8px;\n border: 1px solid transparent;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.15s ease;\n background: transparent;\n color: var(--ef-color-text-muted);\n }\n \n .button-group-btn.active {\n background: var(--ef-color-selected);\n color: var(--ef-color-primary);\n border-color: var(--ef-color-primary-subtle);\n }\n \n .checkbox-label {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n }\n \n .checkbox-label input[type=\"checkbox\"] {\n width: 14px;\n height: 14px;\n accent-color: var(--ef-color-primary);\n cursor: pointer;\n }\n \n .checkbox-label span {\n color: var(--ef-color-text);\n font-size: 12px;\n font-weight: 500;\n }\n \n .dropdown-select {\n width: 100%;\n padding: 6px 8px;\n background: var(--ef-color-bg-inset);\n border: 1px solid var(--ef-color-border);\n border-radius: 6px;\n color: var(--ef-color-text);\n font-size: 11px;\n cursor: pointer;\n }\n \n .dropdown-input {\n width: 100%;\n padding: 5px 7px;\n background: var(--ef-color-bg-inset);\n border: 1px solid var(--ef-color-border);\n border-radius: 4px;\n color: var(--ef-color-text);\n font-size: 11px;\n }\n \n .dropdown-input:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n padding: 2px;\n line-height: 1;\n font-size: 14px;\n transition: color 0.15s;\n }\n \n .dropdown-close:hover {\n color: var(--ef-color-text-muted);\n }\n `,\n ];\n\n @property({ type: Boolean })\n rendering = false;\n\n @state()\n private panZoomTransform = { x: 0, y: 0, scale: 1 };\n\n @state()\n private isExporting = false;\n\n @state()\n private exportProgress: RenderProgress | null = null;\n\n @state()\n private exportStatus:\n | \"idle\"\n | \"rendering\"\n | \"complete\"\n | \"error\"\n | \"cancelled\" = \"idle\";\n\n @provide({ context: previewSettingsContext })\n @state()\n private previewSettings: PreviewSettings = {\n presentationMode: getPreviewPresentationMode(),\n renderMode: getRenderMode(),\n resolutionScale: getPreviewResolutionScale(),\n showStats: getShowStats(),\n showThumbnailTimestamps: getShowThumbnailTimestamps(),\n };\n\n // Local state mirrors for direct access (context is primary source of truth)\n @state()\n private renderMode: RenderMode = this.previewSettings.renderMode;\n\n @state()\n private presentationMode: PreviewPresentationMode =\n this.previewSettings.presentationMode;\n\n @state()\n private previewResolutionScale: PreviewResolutionScale =\n this.previewSettings.resolutionScale;\n\n @state()\n private exportOptions = {\n includeAudio: true,\n scale: 1,\n useInOut: false,\n inMs: 0,\n outMs: 0,\n };\n\n private exportAbortController: AbortController | null = null;\n\n @state()\n private previewIsLoading = false;\n\n #trackedTimegroup: EFTimegroup | null = null;\n #readyStateHandler: (() => void) | null = null;\n\n // Motion state tracking for adaptive resolution\n @state()\n private isPlaying = false;\n\n @state()\n private isScrubbing = false;\n\n @state()\n private isAtRest = true;\n\n /**\n * Current adaptive resolution scale (only used when previewResolutionScale === \"auto\")\n */\n @state()\n private currentAdaptiveScale: number = 1;\n\n /**\n * Playback stats for display (FPS, dropped frames, etc.)\n * Mirrors previewSettings.showStats for direct access\n */\n @state()\n private showStats: boolean = this.previewSettings.showStats;\n\n /**\n * Theme mode: 'light', 'dark', or 'system'\n */\n @state()\n private themeMode: \"light\" | \"dark\" | \"system\" = this.getInitialTheme();\n\n /**\n * Always-on render statistics collection for canvas mode.\n * Collects data regardless of whether stats are visible.\n */\n private renderStats: RenderStats | null = null;\n\n /**\n * Media query for system theme preference\n */\n private systemThemeMediaQuery: MediaQueryList | null = null;\n private systemThemeListener: (() => void) | null = null;\n\n /**\n * DOM mode stats strategy (has its own animation loop).\n * Only active in DOM mode.\n */\n private domStatsStrategy: DomStatsStrategy | null = null;\n\n /**\n * Reference for tracking scrubbing state from EFScrubber.\n * Pass this to <ef-scrubber isScrubbingRef={...}> to enable motion detection.\n */\n readonly isScrubbingRef = { current: false };\n\n private restDebounceTimer: number | null = null;\n private playingCheckInterval: number | null = null;\n private adaptiveTracker: AdaptiveResolutionTracker | null = null;\n private savePanZoomDebounceTimer: number | null = null;\n\n // Canvas preview mode state\n private canvasPreviewRef = createRef<HTMLDivElement>();\n private canvasPreviewResult: CanvasPreviewResult | null = null;\n private canvasAnimationFrame: number | null = null;\n\n private boundHandleTransformChanged = this.handleTransformChanged.bind(this);\n\n focusOverlay = createRef<HTMLDivElement>();\n\n @eventOptions({ passive: false, capture: true })\n handleStageWheel(event: WheelEvent) {\n event.preventDefault();\n }\n\n connectedCallback(): void {\n super.connectedCallback();\n\n // Apply initial theme\n this.applyTheme();\n\n // Check if we're in rendering mode and set rendering=true before first render\n if (\n !this.hasAttribute(\"rendering\") &&\n typeof window !== \"undefined\" &&\n \"FRAMEGEN_BRIDGE\" in window\n ) {\n this.rendering = true;\n }\n\n // Listen for pan-zoom transform changes\n this.addEventListener(\n \"transform-changed\",\n this.boundHandleTransformChanged as EventListener,\n );\n\n // Start motion state polling (checks playing state and scrubbing ref)\n this.startMotionStateTracking();\n\n // Initialize adaptive tracker\n // Scale changes directly update the canvas resolution - no expensive reinit needed\n this.adaptiveTracker = new AdaptiveResolutionTracker({\n onScaleChange: (scale) => {\n const oldScale = this.currentAdaptiveScale;\n this.currentAdaptiveScale = scale;\n\n // Directly update resolution if in auto mode, canvas mode, and in motion\n if (\n this.previewResolutionScale === \"auto\" &&\n this.presentationMode === \"canvas\" &&\n !this.isAtRest\n ) {\n // Use the new dynamic setResolutionScale - instant, no DOM rebuild\n if (this.canvasPreviewResult) {\n this.canvasPreviewResult.setResolutionScale(scale);\n console.log(\n `[EFWorkbench] Resolution changed ${(oldScale * 100).toFixed(0)}% → ${(scale * 100).toFixed(0)}% (instant)`,\n );\n }\n }\n },\n });\n\n // Initialize render stats (always-on collection)\n this.renderStats = new RenderStats(this.adaptiveTracker);\n\n // Listen for system theme changes when in system mode\n if (typeof window !== \"undefined\") {\n this.systemThemeMediaQuery = window.matchMedia(\n \"(prefers-color-scheme: dark)\",\n );\n this.systemThemeListener = () => {\n if (this.themeMode === \"system\") {\n this.applyTheme();\n }\n };\n this.systemThemeMediaQuery.addEventListener(\n \"change\",\n this.systemThemeListener,\n );\n }\n }\n\n disconnectedCallback(): void {\n super.disconnectedCallback();\n\n // Clean up current mode\n if (this.presentationMode === \"dom\") {\n this.stopDomMode();\n } else if (this.presentationMode === \"canvas\") {\n this.stopCanvasMode();\n }\n\n // Restore timegroup visibility\n const timegroup = this.getTimegroup();\n if (timegroup) {\n timegroup.style.clipPath = \"\";\n timegroup.style.pointerEvents = \"\";\n }\n\n this.removeEventListener(\n \"transform-changed\",\n this.boundHandleTransformChanged as EventListener,\n );\n\n // Clean up theme listener\n if (this.systemThemeMediaQuery && this.systemThemeListener) {\n this.systemThemeMediaQuery.removeEventListener(\n \"change\",\n this.systemThemeListener,\n );\n }\n\n // Clean up motion state tracking\n this.stopMotionStateTracking();\n\n // Clean up cache stats updates\n if (typeof (this as any).stopCacheStatsUpdates === \"function\") {\n (this as any).stopCacheStatsUpdates();\n }\n\n // Clean up adaptive tracker\n if (this.adaptiveTracker) {\n this.adaptiveTracker.dispose();\n this.adaptiveTracker = null;\n }\n\n // Save pan/zoom state before disconnecting\n if (this.savePanZoomDebounceTimer !== null) {\n clearTimeout(this.savePanZoomDebounceTimer);\n this.savePanZoomDebounceTimer = null;\n }\n this.savePreviewPanZoom();\n\n // Clean up timegroup ready state listener\n if (this.#trackedTimegroup && this.#readyStateHandler) {\n this.#trackedTimegroup.removeEventListener(\n \"readystatechange\",\n this.#readyStateHandler,\n );\n this.#trackedTimegroup = null;\n this.#readyStateHandler = null;\n }\n }\n\n protected firstUpdated(): void {\n // Restore preview pan/zoom from localStorage\n // Wait for timegroup to be available\n requestAnimationFrame(() => {\n this.restorePreviewPanZoom();\n });\n\n // Wait for Lit to complete initial render and layout before initializing preview\n // This ensures workbench dimensions are available for resolution calculation\n this.updateComplete.then(() => {\n // Initialize based on current presentation mode\n if (this.presentationMode === \"dom\") {\n this.initDomMode();\n } else if (this.presentationMode === \"canvas\") {\n this.initCanvasMode();\n }\n });\n\n // Set up preview loading state listener on the canvas slot's timegroup\n this.#syncTimegroupListener();\n const canvasSlot = this.shadowRoot?.querySelector('slot[name=\"canvas\"]');\n if (canvasSlot) {\n canvasSlot.addEventListener(\"slotchange\", () => {\n this.#syncTimegroupListener();\n });\n }\n }\n\n // Track zoom for detecting changes that need canvas reinit\n private lastCanvasZoom = 1;\n private zoomReinitTimeout: number | null = null;\n\n private handleTransformChanged(\n e: CustomEvent<{ x: number; y: number; scale: number }>,\n ) {\n this.panZoomTransform = e.detail;\n\n // Save pan/zoom state to localStorage\n this.debouncedSavePreviewPanZoom();\n\n // Update overlay transform based on current mode\n if (this.presentationMode === \"canvas\") {\n this.updateCanvasTransform();\n\n // Check if zoom changed enough to warrant re-rendering at new resolution\n // Only update if zoom changed by >25% from last init\n const zoomRatio = e.detail.scale / this.lastCanvasZoom;\n if (zoomRatio < 0.75 || zoomRatio > 1.33) {\n // Debounce to avoid thrashing during zoom gestures\n if (this.zoomReinitTimeout !== null) {\n clearTimeout(this.zoomReinitTimeout);\n }\n this.zoomReinitTimeout = window.setTimeout(() => {\n this.zoomReinitTimeout = null;\n if (this.presentationMode === \"canvas\" && this.canvasPreviewResult) {\n this.lastCanvasZoom = this.panZoomTransform.scale;\n\n // Dynamically update resolution scale without recreating canvas\n const timegroup = this.getTimegroup();\n const canvasContainer = this.canvasPreviewRef.value;\n if (timegroup && canvasContainer) {\n const newScale =\n this.previewResolutionScale === \"auto\"\n ? this.getEffectiveResolutionScale(timegroup, canvasContainer)\n : this.getResolutionScale(timegroup, canvasContainer);\n\n this.canvasPreviewResult.setResolutionScale(newScale);\n }\n }\n }, 500); // Wait 500ms after zoom stops\n }\n }\n }\n\n private getTimegroup(): EFTimegroup | null {\n // Find the timegroup in our canvas slot\n const canvas = this.querySelector(\"[slot='canvas']\");\n if (!canvas) return null;\n if (canvas instanceof EFTimegroup) return canvas;\n return canvas.querySelector(\"ef-timegroup\") as EFTimegroup | null;\n }\n\n #syncTimegroupListener(): void {\n const timegroup = this.getTimegroup();\n if (timegroup === this.#trackedTimegroup) return;\n\n if (this.#trackedTimegroup && this.#readyStateHandler) {\n this.#trackedTimegroup.removeEventListener(\n \"readystatechange\",\n this.#readyStateHandler,\n );\n }\n\n this.#trackedTimegroup = timegroup;\n\n if (!timegroup) {\n this.previewIsLoading = false;\n this.#readyStateHandler = null;\n return;\n }\n\n this.#readyStateHandler = () => {\n this.previewIsLoading =\n (timegroup as any).contentReadyState === \"loading\";\n };\n timegroup.addEventListener(\"readystatechange\", this.#readyStateHandler);\n this.previewIsLoading = (timegroup as any).contentReadyState === \"loading\";\n }\n\n /**\n * Get the root timegroup ID for localStorage key generation.\n * Returns null if no root timegroup is found or it has no ID.\n */\n private getRootTimegroupId(): string | null {\n const timegroup = this.getTimegroup();\n if (!timegroup) return null;\n\n const rootTemporal = findRootTemporal(timegroup);\n if (rootTemporal instanceof EFTimegroup && rootTemporal.id) {\n return rootTemporal.id;\n }\n\n return null;\n }\n\n /**\n * Get localStorage key for preview pan/zoom state.\n */\n private getPreviewPanZoomStorageKey(): string | null {\n const rootId = this.getRootTimegroupId();\n return rootId ? `ef-workbench-panzoom-${rootId}` : null;\n }\n\n /**\n * Save preview pan/zoom to localStorage.\n */\n private savePreviewPanZoom(): void {\n const storageKey = this.getPreviewPanZoomStorageKey();\n if (!storageKey) return;\n\n try {\n const state = {\n x: this.panZoomTransform.x,\n y: this.panZoomTransform.y,\n scale: this.panZoomTransform.scale,\n };\n localStorage.setItem(storageKey, JSON.stringify(state));\n } catch (error) {\n console.warn(\"Failed to save preview pan/zoom to localStorage\", error);\n }\n }\n\n /**\n * Restore preview pan/zoom from localStorage.\n */\n private restorePreviewPanZoom(): void {\n const storageKey = this.getPreviewPanZoomStorageKey();\n if (!storageKey) return;\n\n try {\n const stored = localStorage.getItem(storageKey);\n if (!stored) return;\n\n const state = JSON.parse(stored);\n if (\n typeof state.x === \"number\" &&\n typeof state.y === \"number\" &&\n typeof state.scale === \"number\" &&\n state.scale > 0\n ) {\n // Clamp scale to valid range [0.1, 5]\n const clampedScale = Math.max(0.1, Math.min(5, state.scale));\n this.panZoomTransform = {\n x: state.x,\n y: state.y,\n scale: clampedScale,\n };\n\n // Apply transform to pan-zoom element if it exists\n requestAnimationFrame(() => {\n const panZoomElement = this.querySelector(\"ef-pan-zoom\");\n if (panZoomElement) {\n (panZoomElement as any).x = this.panZoomTransform.x;\n (panZoomElement as any).y = this.panZoomTransform.y;\n (panZoomElement as any).scale = this.panZoomTransform.scale;\n }\n\n // Update transforms based on current mode\n if (this.presentationMode === \"canvas\") {\n this.updateCanvasTransform();\n }\n });\n }\n } catch (error) {\n console.warn(\n \"Failed to restore preview pan/zoom from localStorage\",\n error,\n );\n }\n }\n\n /**\n * Debounced save of preview pan/zoom to avoid excessive localStorage writes.\n */\n private debouncedSavePreviewPanZoom(): void {\n if (this.savePanZoomDebounceTimer !== null) {\n clearTimeout(this.savePanZoomDebounceTimer);\n }\n this.savePanZoomDebounceTimer = window.setTimeout(() => {\n this.savePanZoomDebounceTimer = null;\n this.savePreviewPanZoom();\n }, 200);\n }\n\n // ==================== Motion State Detection ====================\n\n /**\n * Start polling for motion state (playing/scrubbing).\n * We use polling because:\n * - Playing state comes from timegroup's playbackController\n * - Scrubbing state comes from isScrubbingRef (set by EFScrubber)\n */\n private startMotionStateTracking(): void {\n if (this.playingCheckInterval !== null) return;\n\n this.playingCheckInterval = window.setInterval(() => {\n this.updateMotionState();\n }, 50); // Check every 50ms for responsive motion detection\n }\n\n private stopMotionStateTracking(): void {\n if (this.playingCheckInterval !== null) {\n clearInterval(this.playingCheckInterval);\n this.playingCheckInterval = null;\n }\n if (this.restDebounceTimer !== null) {\n clearTimeout(this.restDebounceTimer);\n this.restDebounceTimer = null;\n }\n }\n\n /**\n * Update motion state by checking timegroup and scrubbing ref.\n */\n private updateMotionState(): void {\n const timegroup = this.getTimegroup();\n const wasPlaying = this.isPlaying;\n const wasScrubbing = this.isScrubbing;\n\n // Check playing state from timegroup\n this.isPlaying = timegroup?.playing ?? false;\n\n // Check scrubbing state from ref\n this.isScrubbing = this.isScrubbingRef.current;\n\n const wasInMotion = wasPlaying || wasScrubbing;\n const isInMotion = this.isPlaying || this.isScrubbing;\n\n // Handle motion state transitions\n if (isInMotion && !wasInMotion) {\n // Started moving - immediately mark as not at rest\n this.handleMotionStart();\n } else if (!isInMotion && wasInMotion) {\n // Stopped moving - start debounce timer for rest\n this.handleMotionStop();\n }\n }\n\n /**\n * Pause or unpause the canvas overlay components (EFCanvas, SelectionOverlay)\n * to avoid layout-thrashing during playback.\n */\n private setOverlaysPaused(paused: boolean): void {\n const canvasSlot = this.querySelector(\"[slot='canvas']\");\n if (!canvasSlot) return;\n const efCanvas = canvasSlot.querySelector(\n \"ef-canvas\",\n ) as HTMLElement | null;\n if (efCanvas && \"paused\" in efCanvas) {\n (efCanvas as any).paused = paused;\n }\n // SelectionOverlay is a sibling of ef-pan-zoom inside the workbench shadow DOM,\n // or a child of the canvas slot container. Search both locations.\n const overlay =\n (canvasSlot.querySelector(\n \"ef-canvas-selection-overlay\",\n ) as HTMLElement | null) ??\n (this.querySelector(\"ef-canvas-selection-overlay\") as HTMLElement | null);\n if (overlay && \"paused\" in overlay) {\n (overlay as any).paused = paused;\n }\n }\n\n /**\n * Called when motion starts (playing or scrubbing began).\n */\n private handleMotionStart(): void {\n // Cancel any pending rest transition\n if (this.restDebounceTimer !== null) {\n clearTimeout(this.restDebounceTimer);\n this.restDebounceTimer = null;\n }\n\n // Mark as in motion immediately\n this.isAtRest = false;\n\n // Pause overlay RAF loops to free CPU for the render pipeline\n this.setOverlaysPaused(true);\n\n // For auto mode, initialize the tracker at the current display scale\n // so it doesn't have to step down from 100% to reach it.\n if (this.previewResolutionScale === \"auto\" && this.adaptiveTracker) {\n const timegroup = this.getTimegroup();\n if (timegroup) {\n const compositionWidth = timegroup.offsetWidth || 1920;\n const compositionHeight = timegroup.offsetHeight || 1080;\n const rect = timegroup.getBoundingClientRect();\n const displayScale = Math.min(\n rect.width / compositionWidth,\n rect.height / compositionHeight,\n );\n\n // Initialize tracker at display scale so it can immediately start\n // scaling down if there are performance issues\n this.adaptiveTracker.initializeAtScale(displayScale);\n this.currentAdaptiveScale = this.adaptiveTracker.getRecommendedScale();\n\n // Set canvas to the initial adaptive scale (instant - no rebuild)\n if (this.canvasPreviewResult) {\n this.canvasPreviewResult.setResolutionScale(\n this.currentAdaptiveScale,\n );\n }\n }\n }\n }\n\n /**\n * Called when motion stops (not playing and not scrubbing).\n * Starts a debounce timer before transitioning to rest state.\n */\n private handleMotionStop(): void {\n // Start debounce timer\n if (this.restDebounceTimer !== null) {\n clearTimeout(this.restDebounceTimer);\n }\n\n this.restDebounceTimer = window.setTimeout(() => {\n this.restDebounceTimer = null;\n this.transitionToRest();\n }, REST_DEBOUNCE_MS);\n }\n\n /**\n * Called after debounce period when we're confirmed to be at rest.\n */\n private transitionToRest(): void {\n this.isAtRest = true;\n\n // Resume overlay RAF loops now that playback has stopped\n this.setOverlaysPaused(false);\n\n // If in auto mode, set full resolution (instant - no rebuild needed)\n if (\n this.previewResolutionScale === \"auto\" &&\n this.presentationMode === \"canvas\"\n ) {\n // Reset tracker and set full resolution\n this.adaptiveTracker?.reset();\n this.currentAdaptiveScale = 1;\n\n // Use instant resolution change - no DOM rebuild\n if (this.canvasPreviewResult) {\n this.canvasPreviewResult.setResolutionScale(1);\n }\n }\n }\n\n /**\n * Get the effective resolution scale based on current mode and motion state.\n * For \"auto\" mode, returns full resolution at rest, adaptive scale in motion.\n */\n private getEffectiveResolutionScale(\n timegroup: EFTimegroup,\n canvasContainer: HTMLElement,\n ): number {\n // For non-auto modes, use the existing logic\n if (this.previewResolutionScale !== \"auto\") {\n return this.getResolutionScale(timegroup, canvasContainer);\n }\n\n // Auto mode: full resolution at rest, adaptive in motion\n const compositionWidth = timegroup.offsetWidth || 1920;\n const compositionHeight = timegroup.offsetHeight || 1080;\n const rect = timegroup.getBoundingClientRect();\n const displayedWidth = rect.width;\n const displayedHeight = rect.height;\n const displayScale = Math.min(\n displayedWidth / compositionWidth,\n displayedHeight / compositionHeight,\n );\n\n if (this.isAtRest) {\n // At rest: use display scale (full resolution for current display size)\n const scale = Math.max(0.1, Math.min(1, displayScale));\n console.log(\n `[EFWorkbench] Auto mode (at rest): using display scale ${(scale * 100).toFixed(1)}%`,\n );\n return scale;\n } else {\n // In motion: use adaptive scale (may be reduced to prevent dropped frames)\n const adaptiveScale = this.currentAdaptiveScale;\n const targetScale = Math.min(displayScale, adaptiveScale);\n const scale = Math.max(0.1, Math.min(1, targetScale));\n console.log(\n `[EFWorkbench] Auto mode (in motion): adaptive=${adaptiveScale}, display=${displayScale.toFixed(2)}, final=${(scale * 100).toFixed(1)}%`,\n );\n return scale;\n }\n }\n\n /**\n * Apply settings when dependencies are ready.\n * Called from updated() hook when settings change or dependencies become available.\n */\n private applySettings(): void {\n // Sync local state from context (for direct property access)\n this.presentationMode = this.previewSettings.presentationMode;\n this.renderMode = this.previewSettings.renderMode;\n this.previewResolutionScale = this.previewSettings.resolutionScale;\n this.showStats = this.previewSettings.showStats;\n }\n\n // ==================== End Motion State Detection ====================\n\n private async handlePresentationModeChange(mode: PreviewPresentationMode) {\n if (mode === this.presentationMode) return;\n\n const previousMode = this.presentationMode;\n\n // Stop previous mode (this will stop stats strategy)\n if (previousMode === \"dom\") {\n this.stopDomMode();\n } else if (previousMode === \"canvas\") {\n this.stopCanvasMode();\n }\n\n // Update context and persist\n setPreviewPresentationMode(mode);\n this.previewSettings = { ...this.previewSettings, presentationMode: mode };\n\n // Wait for Lit to re-render (removes old overlay, adds new one if needed)\n await this.updateComplete;\n\n // Start new mode after DOM is updated\n if (mode === \"dom\") {\n this.initDomMode();\n } else if (mode === \"canvas\") {\n this.initCanvasMode();\n }\n }\n\n private initDomMode() {\n // Don't initialize if we're no longer in dom mode\n if (this.presentationMode !== \"dom\") {\n return;\n }\n\n const timegroup = this.getTimegroup();\n if (!timegroup) {\n setTimeout(() => this.initDomMode(), 100);\n return;\n }\n\n // Pause the ef-fit-scale to prevent it from applying transforms\n const fitScale = this.querySelector(\"[slot='canvas']\") as any;\n const hasFitScaleMethods = !!(\n fitScale?.removeScale && fitScale?.paused !== undefined\n );\n if (hasFitScaleMethods) {\n fitScale.paused = true;\n fitScale.removeScale();\n }\n\n // Disable the timegroup's own proxy mode (it may have been enabled by thumbnail strip)\n (timegroup as any).proxyMode = false;\n\n // Show the original timegroup directly\n timegroup.style.clipPath = \"\";\n timegroup.style.pointerEvents = \"\";\n\n // Create DOM stats strategy if stats are enabled\n if (this.showStats && this.adaptiveTracker) {\n this.domStatsStrategy = new DomStatsStrategy({\n timegroup,\n adaptiveTracker: this.adaptiveTracker,\n });\n this.domStatsStrategy.start();\n }\n }\n\n private stopDomMode() {\n // Stop DOM stats strategy\n if (this.domStatsStrategy) {\n this.domStatsStrategy.stop();\n this.domStatsStrategy = null;\n }\n\n const timegroup = this.getTimegroup();\n if (timegroup) {\n // Hide the original again\n timegroup.style.clipPath = \"inset(100%)\";\n timegroup.style.pointerEvents = \"none\";\n }\n\n // Resume the ef-fit-scale\n const fitScale = this.querySelector(\"[slot='canvas']\") as any;\n if (fitScale?.paused !== undefined) {\n fitScale.paused = false;\n }\n }\n\n /**\n * Get the resolution scale for canvas rendering (for fixed scale modes).\n *\n * Logic:\n * - Get actual displayed size from getBoundingClientRect()\n * - For \"Full\": render at displayed size (1:1 pixel mapping)\n * - For other settings: render at that % of displayed size\n * - Never exceed composition size (100%)\n *\n * Note: For \"auto\" mode, use getEffectiveResolutionScale() instead.\n */\n private getResolutionScale(\n timegroup: EFTimegroup,\n _canvasContainer: HTMLElement,\n ): number {\n // For \"auto\" mode, delegate to getEffectiveResolutionScale\n if (this.previewResolutionScale === \"auto\") {\n return this.getEffectiveResolutionScale(timegroup, _canvasContainer);\n }\n\n // Composition size = the native resolution (offsetWidth/Height gives CSS layout size)\n const compositionWidth = timegroup.offsetWidth || 1920;\n const compositionHeight = timegroup.offsetHeight || 1080;\n\n // Displayed size = actual screen pixels after all transforms\n const rect = timegroup.getBoundingClientRect();\n const displayedWidth = rect.width;\n const displayedHeight = rect.height;\n\n // Display scale = displayed / composition (how much the composition is scaled down for display)\n const displayScale = Math.min(\n displayedWidth / compositionWidth,\n displayedHeight / compositionHeight,\n );\n\n // For \"Full\", we want to render at displayed size (displayScale of composition)\n // For other settings, we want min(displayScale, setting) of composition\n // But we should never exceed 100% of composition\n const targetScale =\n this.previewResolutionScale === 1\n ? displayScale // Full = match display\n : Math.min(displayScale, this.previewResolutionScale); // Others = min of display and setting\n\n // Clamp to reasonable bounds [10%, 100%]\n const finalScale = Math.max(0.1, Math.min(1, targetScale));\n\n return finalScale;\n }\n\n private async initCanvasMode() {\n // Don't initialize if we're no longer in canvas mode\n if (this.presentationMode !== \"canvas\") return;\n\n const timegroup = this.getTimegroup();\n const canvasContainer = this.canvasPreviewRef.value;\n\n // Wait for both timegroup and container to be available\n if (!timegroup || !canvasContainer) {\n setTimeout(() => this.initCanvasMode(), 100);\n return;\n }\n\n // Don't wait for timegroup initialization here - it can cause deadlocks when\n // localStorage restoration triggers seeks that wait for waitForMediaDurations().\n // The canvas refresh loop already handles this by checking if timegroup is ready:\n // - refresh() checks if sourceTimeMs and userTimeMs are synchronized\n // - If they're not synchronized (seek in progress), refresh() returns early\n // - Once the seek completes and times are synchronized, refresh() will render\n // This avoids blocking the main thread while still ensuring correct rendering.\n\n // Disable the timegroup's own proxy mode - workbench handles canvas rendering\n (timegroup as any).proxyMode = false;\n\n // Signal that canvas preview owns the render-capture cycle.\n // PlaybackController will skip runThrottledFrameTask during playback\n // to avoid DOM mutations (Layerize steps) between captures.\n (timegroup as any).canvasPreviewActive = true;\n\n // Ensure the timegroup is visible for native drawElementImage capture.\n // The canvas overlay (position:absolute, inset:0) covers it visually.\n // Clear any clipPath that stopDomMode() may have set.\n timegroup.style.clipPath = \"\";\n timegroup.style.pointerEvents = \"none\";\n\n // Show the canvas container\n canvasContainer.style.display = \"block\";\n\n // Get initial resolution scale based on display size, user setting, and motion state\n const initialResolutionScale =\n this.previewResolutionScale === \"auto\"\n ? this.getEffectiveResolutionScale(timegroup, canvasContainer)\n : this.getResolutionScale(timegroup, canvasContainer);\n\n // Track zoom level for detecting significant changes\n this.lastCanvasZoom = this.panZoomTransform.scale;\n\n try {\n // CRITICAL: Wait for any in-progress seek to complete AND let playback controller initialize\n // The playback controller may be restoring time from localStorage\n await timegroup.seekTask.taskComplete;\n\n // If there's a playback controller, wait for it to complete initial seek\n // This prevents rendering at 0ms before restoring to saved time\n if (timegroup.playbackController) {\n // The playback controller's seek is async, give it time to start and coordinate animations\n await new Promise((resolve) => setTimeout(resolve, 0));\n }\n\n // CRITICAL: Ensure timegroup has correct display states before first render\n // Text elements have default display:flex until updateAnimations runs\n updateAnimations(timegroup);\n\n // Create canvas preview - this builds the clone structure ONCE\n // Dynamic import to avoid loading render utilities during SSR\n const { renderTimegroupToCanvas } =\n await import(\"../preview/renderTimegroupToCanvas.js\");\n const result = renderTimegroupToCanvas(timegroup, {\n scale: 1,\n resolutionScale: initialResolutionScale,\n });\n\n // Store the full result for dynamic resolution changes\n this.canvasPreviewResult = result;\n\n const { container, canvas, refresh } = result;\n\n canvas.classList.add(\"clone-content\");\n\n canvasContainer.innerHTML = \"\";\n canvasContainer.appendChild(container);\n\n // Apply current transform\n this.updateCanvasTransform();\n\n // Start the canvas render loop\n const loop = async () => {\n if (this.presentationMode !== \"canvas\") return;\n\n // Skip refresh during export to avoid wasting CPU\n if (!this.isExporting) {\n try {\n // Measure render time for stats tracking\n const renderStart = performance.now();\n await refresh();\n const renderTime = performance.now() - renderStart;\n\n // Always record render stats (regardless of display visibility)\n if (this.renderStats) {\n this.renderStats.recordFrame(\n renderTime,\n performance.now(),\n this.isAtRest,\n );\n }\n\n this.updateCanvasTransform();\n } catch (e) {\n console.error(\"Canvas refresh failed:\", e);\n }\n }\n\n this.canvasAnimationFrame = requestAnimationFrame(loop);\n };\n this.canvasAnimationFrame = requestAnimationFrame(loop);\n } catch (e) {\n console.error(\"Failed to init canvas mode:\", e);\n }\n }\n\n private stopCanvasMode() {\n if (this.canvasAnimationFrame !== null) {\n cancelAnimationFrame(this.canvasAnimationFrame);\n this.canvasAnimationFrame = null;\n }\n if (this.zoomReinitTimeout !== null) {\n clearTimeout(this.zoomReinitTimeout);\n this.zoomReinitTimeout = null;\n }\n\n this.canvasPreviewResult?.dispose();\n this.canvasPreviewResult = null;\n\n const timegroup = this.getTimegroup();\n if (timegroup) {\n (timegroup as any).canvasPreviewActive = false;\n }\n\n // Note: renderStats persists across mode changes - no cleanup needed\n\n // Clear and hide the canvas container\n const container = this.canvasPreviewRef.value;\n if (container) {\n container.innerHTML = \"\";\n container.style.display = \"none\";\n }\n }\n\n private updateCanvasTransform() {\n if (this.presentationMode !== \"canvas\") return;\n\n const container = this.canvasPreviewRef.value;\n if (!container) return;\n\n const canvas = container.querySelector(\"canvas\") as HTMLElement;\n if (!canvas) return;\n\n const { x, y, scale } = this.panZoomTransform;\n canvas.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;\n }\n\n // Canvas renderer - kept for thumbnail generation\n async initCanvasRenderer(): Promise<{\n canvas: HTMLCanvasElement;\n refresh: () => Promise<void>;\n } | null> {\n const timegroup = this.getTimegroup();\n if (!timegroup) return null;\n\n try {\n // Dynamic import to avoid loading render utilities during SSR\n const { renderTimegroupToCanvas } =\n await import(\"../preview/renderTimegroupToCanvas.js\");\n const { canvas, refresh } = renderTimegroupToCanvas(timegroup, 1);\n return { canvas, refresh };\n } catch (e) {\n console.error(\"Failed to init canvas renderer:\", e);\n return null;\n }\n }\n\n /** Start video export with progress tracking */\n async startExport(options: RenderToVideoOptions = {}): Promise<void> {\n const timegroup = this.getTimegroup();\n if (!timegroup) {\n console.error(\"No timegroup found for export\");\n return;\n }\n\n if (this.isExporting) {\n console.warn(\"Export already in progress\");\n return;\n }\n\n this.exportAbortController = new AbortController();\n this.isExporting = true;\n this.exportProgress = null;\n this.exportStatus = \"rendering\";\n\n try {\n // Dynamic import to avoid loading render utilities during SSR\n const { renderTimegroupToVideo } =\n await import(\"../preview/renderTimegroupToVideo.js\");\n await renderTimegroupToVideo(timegroup, {\n ...options,\n signal: this.exportAbortController.signal,\n onProgress: (progress) => {\n this.exportProgress = progress;\n },\n });\n\n this.exportStatus = \"complete\";\n setTimeout(() => {\n this.isExporting = false;\n this.exportProgress = null;\n this.exportStatus = \"idle\";\n this.exportAbortController = null;\n }, 2000);\n } catch (e) {\n // Need to re-import to check error type\n const { RenderCancelledError } =\n await import(\"../preview/renderTimegroupToVideo.js\");\n if (e instanceof RenderCancelledError) {\n console.log(\"Export cancelled by user\");\n this.exportStatus = \"cancelled\";\n setTimeout(() => {\n this.isExporting = false;\n this.exportProgress = null;\n this.exportStatus = \"idle\";\n this.exportAbortController = null;\n }, 1500);\n } else {\n console.error(\"Export failed:\", e);\n this.exportStatus = \"error\";\n setTimeout(() => {\n this.isExporting = false;\n this.exportProgress = null;\n this.exportStatus = \"idle\";\n this.exportAbortController = null;\n }, 3000);\n }\n }\n }\n\n /** Cancel the current export */\n cancelExport(): void {\n if (this.exportAbortController) {\n this.exportAbortController.abort();\n }\n }\n\n private positionPopover(popover: HTMLElement, anchorId: string) {\n const anchor = this.shadowRoot?.getElementById(anchorId);\n if (!anchor) return;\n\n const anchorRect = anchor.getBoundingClientRect();\n const popoverRect = popover.getBoundingClientRect();\n const padding = 8;\n\n // Position below the anchor\n let top = anchorRect.bottom + padding;\n // Align right edge of popover with right edge of anchor\n let left = anchorRect.right - popoverRect.width;\n\n // Keep within viewport bounds\n if (left < padding) {\n left = padding;\n }\n if (left + popoverRect.width > window.innerWidth - padding) {\n left = window.innerWidth - popoverRect.width - padding;\n }\n if (top + popoverRect.height > window.innerHeight - padding) {\n // Flip to above if doesn't fit below\n top = anchorRect.top - popoverRect.height - padding;\n }\n\n popover.style.top = `${top}px`;\n popover.style.left = `${left}px`;\n }\n\n private handleSettingsPopoverToggle(e: Event) {\n const popover = e.target as HTMLElement;\n if ((e as ToggleEvent).newState === \"open\") {\n // Position after the popover is shown so we can measure it\n requestAnimationFrame(() => {\n this.positionPopover(popover, \"settings-btn\");\n });\n }\n }\n\n private handleExportPopoverToggle(e: Event) {\n const popover = e.target as HTMLElement;\n if ((e as ToggleEvent).newState === \"open\") {\n // Initialize export options when popover opens\n if (this.exportOptions.outMs === 0) {\n const timegroup = this.getTimegroup();\n if (timegroup) {\n this.exportOptions = {\n ...this.exportOptions,\n outMs: timegroup.durationMs,\n };\n }\n }\n // Position after the popover is shown\n requestAnimationFrame(() => {\n this.positionPopover(popover, \"export-btn\");\n });\n }\n }\n\n private handleStartExport() {\n this.startExport({\n includeAudio: this.exportOptions.includeAudio,\n scale: this.exportOptions.scale,\n fromMs: this.exportOptions.useInOut ? this.exportOptions.inMs : undefined,\n toMs: this.exportOptions.useInOut ? this.exportOptions.outMs : undefined,\n });\n }\n\n private updateExportOption<K extends keyof typeof this.exportOptions>(\n key: K,\n value: (typeof this.exportOptions)[K],\n ) {\n this.exportOptions = { ...this.exportOptions, [key]: value };\n }\n\n private formatTime(ms: number): string {\n const totalSeconds = Math.floor(ms / 1000);\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = totalSeconds % 60;\n if (minutes > 0) {\n return `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n }\n return `${seconds}s`;\n }\n\n private handleCancelClick() {\n this.cancelExport();\n }\n\n private handleRenderModeChange(mode: RenderMode) {\n setRenderMode(mode);\n this.previewSettings = { ...this.previewSettings, renderMode: mode };\n }\n\n private handleShowStatsToggle(enabled: boolean) {\n setShowStats(enabled);\n this.previewSettings = { ...this.previewSettings, showStats: enabled };\n // applySettings() will be called automatically via updated() hook when context changes\n }\n\n private handleShowThumbnailTimestampsToggle(enabled: boolean) {\n setShowThumbnailTimestamps(enabled);\n this.previewSettings = {\n ...this.previewSettings,\n showThumbnailTimestamps: enabled,\n };\n }\n\n /**\n * Get initial theme from localStorage or default to 'dark'\n */\n private getInitialTheme(): \"light\" | \"dark\" | \"system\" {\n if (typeof window === \"undefined\") return \"dark\";\n const stored = localStorage.getItem(\"ef-theme\");\n if (stored === \"light\" || stored === \"dark\" || stored === \"system\") {\n return stored;\n }\n return \"dark\";\n }\n\n /**\n * Cycle through theme modes: light → dark → system → light\n */\n private handleThemeToggle(): void {\n const nextTheme =\n this.themeMode === \"light\"\n ? \"dark\"\n : this.themeMode === \"dark\"\n ? \"system\"\n : \"light\";\n this.themeMode = nextTheme;\n localStorage.setItem(\"ef-theme\", nextTheme);\n this.applyTheme();\n }\n\n /**\n * Apply the current theme to the document and host element\n */\n private applyTheme(): void {\n const root = document.documentElement;\n let shouldBeDark = false;\n\n if (this.themeMode === \"light\") {\n shouldBeDark = false;\n } else if (this.themeMode === \"dark\") {\n shouldBeDark = true;\n } else {\n // System mode - check system preference\n shouldBeDark = window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n }\n\n // Apply to document root (for light DOM elements)\n if (shouldBeDark) {\n root.classList.add(\"dark\");\n root.classList.remove(\"light\");\n } else {\n root.classList.add(\"light\");\n root.classList.remove(\"dark\");\n }\n\n // Apply to host element (for shadow DOM and :host-context)\n if (shouldBeDark) {\n this.classList.add(\"dark\");\n this.classList.remove(\"light\");\n } else {\n this.classList.add(\"light\");\n this.classList.remove(\"dark\");\n }\n }\n\n /**\n * Reset and fit the preview to show all content centered.\n * Finds the pan-zoom element and calls fitToContent() on it.\n */\n private handleFitToContent(): void {\n const panZoomElement = this.querySelector(\"ef-pan-zoom\") as any;\n if (panZoomElement && typeof panZoomElement.fitToContent === \"function\") {\n panZoomElement.fitToContent();\n }\n }\n\n private renderSettingsPopover() {\n const isAvailable = isNativeCanvasApiAvailable();\n\n return html`\n <div \n id=\"settings-popover\" \n popover=\"auto\"\n class=\"dropdown-panel\"\n @toggle=${this.handleSettingsPopoverToggle}\n >\n <div class=\"dropdown-header\">\n <span class=\"dropdown-title\">Preview Settings</span>\n <button class=\"dropdown-close\" popovertarget=\"settings-popover\" popovertargetaction=\"hide\">✕</button>\n </div>\n \n <!-- Presentation Mode Setting -->\n <div style=\"\n background: var(--ef-color-bg-inset);\n border-radius: 8px;\n padding: 12px;\n margin-bottom: 10px;\n \">\n <div class=\"dropdown-label\" style=\"margin-bottom: 10px;\">Presentation Mode</div>\n \n <div class=\"button-group\">\n <button\n @click=${() => this.handlePresentationModeChange(\"dom\")}\n class=\"button-group-btn ${this.presentationMode === \"dom\" ? \"active\" : \"\"}\"\n >DOM</button>\n <button\n @click=${() => this.handlePresentationModeChange(\"canvas\")}\n class=\"button-group-btn ${this.presentationMode === \"canvas\" ? \"active\" : \"\"}\"\n >Canvas</button>\n </div>\n \n <div class=\"dropdown-description\">\n ${\n this.presentationMode === \"dom\"\n ? \"Default. Shows the real timegroup DOM directly.\"\n : \"Renders to canvas each frame.\"\n }\n </div>\n </div>\n \n <!-- Render Mode Setting -->\n <div class=\"dropdown-section\">\n <div style=\"display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;\">\n <span class=\"dropdown-label\">Render Mode</span>\n ${\n isAvailable\n ? html`\n <div style=\"display: flex; align-items: center; gap: 5px;\">\n <span style=\"\n display: inline-block;\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--ef-color-success);\n \"></span>\n <span style=\"color: var(--ef-color-success); font-size: 10px; font-weight: 500;\">\n Native Available\n </span>\n </div>\n `\n : \"\"\n }\n </div>\n \n <div class=\"button-group\">\n <button\n @click=${() => this.handleRenderModeChange(\"foreignObject\")}\n class=\"button-group-btn ${this.renderMode === \"foreignObject\" ? \"active\" : \"\"}\"\n >foreignObject</button>\n <button\n @click=${() => this.handleRenderModeChange(\"native\")}\n ?disabled=${!isAvailable}\n class=\"button-group-btn ${this.renderMode === \"native\" ? \"active\" : \"\"}\"\n style=\"\n cursor: ${isAvailable ? \"pointer\" : \"not-allowed\"};\n opacity: ${isAvailable ? \"1\" : \"0.5\"};\n \"\n >native</button>\n </div>\n \n <div class=\"dropdown-description\">\n ${\n this.renderMode === \"foreignObject\"\n ? \"SVG foreignObject serialization. Works everywhere but slower.\"\n : \"Chrome's drawElementImage API. Fastest, requires chrome://flags/#canvas-draw-element.\"\n }\n </div>\n </div>\n \n <!-- This section was already updated earlier in the dropdown-section refactor -->\n \n <!-- Show Performance Stats Setting -->\n <div class=\"dropdown-section\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n ?checked=${this.showStats}\n @change=${(e: Event) => this.handleShowStatsToggle((e.target as HTMLInputElement).checked)}\n />\n <span>Show Performance Stats</span>\n </label>\n \n <div class=\"dropdown-description\">\n Display FPS, CPU pressure, and performance metrics overlay.\n </div>\n </div>\n \n <!-- Thumbnail Timestamps -->\n <div class=\"dropdown-section\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n ?checked=${this.previewSettings.showThumbnailTimestamps}\n @change=${(e: Event) => this.handleShowThumbnailTimestampsToggle((e.target as HTMLInputElement).checked)}\n />\n <span>Show Thumbnail Timestamps</span>\n </label>\n \n <div class=\"dropdown-description\">\n Display timestamp overlay on timeline thumbnails for debugging.\n </div>\n </div>\n </div>\n `;\n }\n\n private renderExportPopover() {\n const timegroup = this.getTimegroup();\n const durationMs = timegroup?.durationMs ?? 0;\n\n return html`\n <div \n id=\"export-popover\" \n popover=\"auto\"\n class=\"dropdown-panel\"\n @toggle=${this.handleExportPopoverToggle}\n >\n <div class=\"dropdown-header\">\n <span class=\"dropdown-title\">Export Settings</span>\n <button class=\"dropdown-close\" popovertarget=\"export-popover\" popovertargetaction=\"hide\">✕</button>\n </div>\n \n <!-- Scale -->\n <div style=\"margin-bottom: 10px;\">\n <label class=\"dropdown-label\" style=\"display: block; margin-bottom: 4px;\">Scale</label>\n <select\n class=\"dropdown-select\"\n .value=${String(this.exportOptions.scale)}\n @change=${(e: Event) => this.updateExportOption(\"scale\", Number((e.target as HTMLSelectElement).value))}\n >\n <option value=\"1\">100% (Full)</option>\n <option value=\"0.75\">75%</option>\n <option value=\"0.5\">50%</option>\n <option value=\"0.25\">25%</option>\n </select>\n </div>\n \n <!-- Audio -->\n <div style=\"margin-bottom: 10px;\">\n <label class=\"checkbox-label\">\n <input\n type=\"checkbox\"\n ?checked=${this.exportOptions.includeAudio}\n @change=${(e: Event) => this.updateExportOption(\"includeAudio\", (e.target as HTMLInputElement).checked)}\n />\n <span>Include Audio</span>\n </label>\n </div>\n \n <!-- In/Out Range -->\n <div style=\"margin-bottom: 12px;\">\n <label class=\"checkbox-label\" style=\"margin-bottom: 6px;\">\n <input\n type=\"checkbox\"\n ?checked=${this.exportOptions.useInOut}\n @change=${(e: Event) => this.updateExportOption(\"useInOut\", (e.target as HTMLInputElement).checked)}\n />\n <span>Custom Range</span>\n </label>\n \n ${\n this.exportOptions.useInOut\n ? html`\n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 6px;\">\n <div>\n <label class=\"dropdown-label\" style=\"display: block; margin-bottom: 2px; font-size: 10px;\">In (ms)</label>\n <input\n type=\"number\"\n class=\"dropdown-input\"\n style=\"font-family: ui-monospace, monospace;\"\n min=\"0\"\n max=${durationMs}\n .value=${String(this.exportOptions.inMs)}\n @change=${(e: Event) => this.updateExportOption(\"inMs\", Number((e.target as HTMLInputElement).value))}\n />\n </div>\n <div>\n <label class=\"dropdown-label\" style=\"display: block; margin-bottom: 2px; font-size: 10px;\">Out (ms)</label>\n <input\n type=\"number\"\n class=\"dropdown-input\"\n style=\"font-family: ui-monospace, monospace;\"\n min=\"0\"\n max=${durationMs}\n .value=${String(this.exportOptions.outMs)}\n @change=${(e: Event) => this.updateExportOption(\"outMs\", Number((e.target as HTMLInputElement).value))}\n />\n </div>\n </div>\n <div class=\"dropdown-description\" style=\"margin-top: 4px;\">\n Duration: ${this.formatTime(this.exportOptions.outMs - this.exportOptions.inMs)} / ${this.formatTime(durationMs)}\n </div>\n `\n : html`\n <div class=\"dropdown-description\">\n Full duration: ${this.formatTime(durationMs)}\n </div>\n `\n }\n </div>\n \n <!-- Start Export button -->\n <button\n class=\"toolbar-btn primary\"\n style=\"width: 100%; justify-content: center;\"\n @click=${this.handleStartExport}\n popovertarget=\"export-popover\"\n popovertargetaction=\"hide\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"23 7 16 12 23 17 23 7\"></polygon>\n <rect x=\"1\" y=\"5\" width=\"15\" height=\"14\" rx=\"2\" ry=\"2\"></rect>\n </svg>\n Start Export\n </button>\n </div>\n `;\n }\n\n private renderExportProgressPopover() {\n const p = this.exportProgress;\n const progressPercent = p ? Math.round(p.progress * 100) : 0;\n const isComplete = this.exportStatus === \"complete\";\n const isError = this.exportStatus === \"error\";\n const isCancelled = this.exportStatus === \"cancelled\";\n const isRendering = this.exportStatus === \"rendering\";\n\n let statusColor: string;\n let statusText: string;\n\n if (isComplete) {\n statusColor = \"var(--ef-color-success)\";\n statusText = \"Complete!\";\n } else if (isError) {\n statusColor = \"var(--ef-color-danger)\";\n statusText = \"Failed\";\n } else if (isCancelled) {\n statusColor = \"var(--ef-color-warning)\";\n statusText = \"Cancelled\";\n } else {\n statusColor = \"var(--ef-color-primary)\";\n statusText = `${progressPercent}%`;\n }\n\n return html`\n <div \n id=\"export-progress-popover\" \n popover=\"manual\"\n class=\"dropdown-panel\" \n style=\"min-width: 240px;\"\n >\n <div class=\"dropdown-header\">\n <span class=\"dropdown-title\">Exporting</span>\n ${\n isRendering\n ? html`\n <button \n class=\"dropdown-close\" \n style=\"color: var(--ef-color-danger);\"\n @click=${this.handleCancelClick}\n >Cancel</button>\n `\n : null\n }\n </div>\n \n ${\n isRendering && p !== null\n ? html`\n ${\n p.framePreviewCanvas\n ? html`\n <div style=\"margin-bottom: 10px; display: flex; justify-content: center;\">\n ${p.framePreviewCanvas}\n </div>\n <style>\n ef-workbench canvas {\n border-radius: 4px;\n border: 1px solid var(--ef-color-border);\n max-width: 100%;\n height: auto;\n }\n </style>\n `\n : null\n }\n \n <div style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; margin-bottom: 10px; font-family: ui-monospace, monospace; font-size: 10px;\">\n <div>\n <div style=\"color: var(--ef-color-text-subtle);\">Frames</div>\n <div style=\"color: var(--ef-color-text);\">${p.currentFrame} / ${p.totalFrames}</div>\n </div>\n <div>\n <div style=\"color: var(--ef-color-text-subtle);\">Time</div>\n <div style=\"color: var(--ef-color-text);\">${this.formatTime(p.renderedMs)} / ${this.formatTime(p.totalDurationMs)}</div>\n </div>\n <div>\n <div style=\"color: var(--ef-color-text-subtle);\">Speed</div>\n <div style=\"color: ${p.speedMultiplier >= 1 ? \"var(--ef-color-success)\" : \"var(--ef-color-warning)\"};\">${p.speedMultiplier.toFixed(2)}x</div>\n </div>\n <div>\n <div style=\"color: var(--ef-color-text-subtle);\">ETA</div>\n <div style=\"color: var(--ef-color-text);\">${this.formatTime(p.estimatedRemainingMs)}</div>\n </div>\n </div>\n `\n : null\n }\n \n <div style=\"height: 4px; background: var(--ef-color-bg-inset); border-radius: 2px; overflow: hidden;\">\n <div style=\"\n height: 100%;\n width: ${progressPercent}%;\n background: ${statusColor};\n border-radius: 2px;\n transition: width 0.15s ease-out;\n \"></div>\n </div>\n \n <div style=\"text-align: center; margin-top: 6px; font-size: 11px; font-weight: 600; color: ${statusColor};\">\n ${statusText}\n </div>\n </div>\n \n <style>\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n </style>\n `;\n }\n\n private renderToolbar() {\n return html`\n <div class=\"toolbar\" part=\"toolbar\">\n <div class=\"toolbar-left\">\n <!-- Fit to content button -->\n <button \n class=\"toolbar-icon-btn\"\n @click=${this.handleFitToContent}\n title=\"Fit to Content (Reset Zoom & Center)\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"></rect>\n <path d=\"M8 12h8M12 8v8\"></path>\n </svg>\n </button>\n </div>\n \n <div class=\"toolbar-right\">\n <!-- Mode indicator -->\n <span class=\"mode-indicator ${this.presentationMode}\">\n ${\n this.presentationMode === \"dom\"\n ? \"DOM\"\n : html`\n Canvas ${\n getRenderMode() === \"native\"\n ? phosphorIcon(ICONS.lightning, 12)\n : phosphorIcon(ICONS.code, 12)\n }\n `\n }\n </span>\n \n <!-- Theme toggle button -->\n <button \n class=\"toolbar-icon-btn\"\n @click=${this.handleThemeToggle}\n title=\"${this.themeMode === \"light\" ? \"Light mode\" : this.themeMode === \"dark\" ? \"Dark mode\" : \"System preference\"}\"\n >\n ${\n this.themeMode === \"light\"\n ? html`\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"5\"></circle>\n <line x1=\"12\" y1=\"1\" x2=\"12\" y2=\"3\"></line>\n <line x1=\"12\" y1=\"21\" x2=\"12\" y2=\"23\"></line>\n <line x1=\"4.22\" y1=\"4.22\" x2=\"5.64\" y2=\"5.64\"></line>\n <line x1=\"18.36\" y1=\"18.36\" x2=\"19.78\" y2=\"19.78\"></line>\n <line x1=\"1\" y1=\"12\" x2=\"3\" y2=\"12\"></line>\n <line x1=\"21\" y1=\"12\" x2=\"23\" y2=\"12\"></line>\n <line x1=\"4.22\" y1=\"19.78\" x2=\"5.64\" y2=\"18.36\"></line>\n <line x1=\"18.36\" y1=\"5.64\" x2=\"19.78\" y2=\"4.22\"></line>\n </svg>\n `\n : this.themeMode === \"dark\"\n ? html`\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z\"></path>\n </svg>\n `\n : html`\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\" ry=\"2\"></rect>\n <line x1=\"8\" y1=\"21\" x2=\"16\" y2=\"21\"></line>\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"21\"></line>\n </svg>\n `\n }\n </button>\n \n <!-- Settings button -->\n <button \n id=\"settings-btn\"\n class=\"toolbar-icon-btn\"\n popovertarget=\"settings-popover\"\n title=\"Preview Settings\"\n >\n ${phosphorIcon(ICONS.gear, 16)}\n </button>\n \n <!-- Export button -->\n ${\n this.isExporting\n ? html`\n <button \n id=\"export-btn\"\n class=\"toolbar-btn active\"\n style=\"min-width: 100px;\"\n popovertarget=\"export-progress-popover\"\n >\n <div style=\"width: 12px; height: 12px; border: 2px solid var(--ef-color-primary-subtle); border-top-color: var(--ef-color-primary); border-radius: 50%; animation: spin 1s linear infinite;\"></div>\n Exporting...\n </button>\n `\n : html`\n <button \n id=\"export-btn\"\n class=\"toolbar-btn primary\"\n popovertarget=\"export-popover\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polygon points=\"23 7 16 12 23 17 23 7\"></polygon>\n <rect x=\"1\" y=\"5\" width=\"15\" height=\"14\" rx=\"2\" ry=\"2\"></rect>\n </svg>\n Export\n </button>\n `\n }\n </div>\n </div>\n \n <!-- Popovers (rendered into top-layer) -->\n ${this.renderSettingsPopover()}\n ${this.renderExportPopover()}\n ${this.renderExportProgressPopover()}\n `;\n }\n\n update(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.update(changedProperties);\n\n if (changedProperties.has(\"focusedElement\")) {\n this.drawOverlays();\n }\n }\n\n updated(\n changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Restore preview pan/zoom when timegroup becomes available\n // Check if timegroup is now available (slot content changed)\n const timegroup = this.getTimegroup();\n if (timegroup && !changedProperties.has(\"panZoomTransform\")) {\n // Only restore if we haven't already restored (avoid overwriting user changes)\n // Check if panZoomTransform is still at default values\n if (\n this.panZoomTransform.x === 0 &&\n this.panZoomTransform.y === 0 &&\n this.panZoomTransform.scale === 1\n ) {\n requestAnimationFrame(() => {\n this.restorePreviewPanZoom();\n });\n }\n }\n\n // Apply settings when dependencies become available or settings change\n if (\n changedProperties.has(\"previewSettings\") ||\n changedProperties.has(\"presentationMode\") ||\n changedProperties.has(\"showStats\")\n ) {\n this.applySettings();\n }\n\n // Show/hide export progress popover based on isExporting state\n if (changedProperties.has(\"isExporting\")) {\n const popover = this.shadowRoot?.getElementById(\n \"export-progress-popover\",\n ) as HTMLElement | null;\n if (popover) {\n if (this.isExporting) {\n popover.showPopover();\n // Position after showing\n requestAnimationFrame(() => {\n this.positionPopover(popover, \"export-btn\");\n });\n } else {\n popover.hidePopover();\n }\n }\n }\n }\n\n drawOverlays = () => {\n const focusOverlay = this.focusOverlay.value;\n if (focusOverlay) {\n if (this.focusedElement) {\n focusOverlay.style.display = \"block\";\n const rect = this.focusedElement.getBoundingClientRect();\n Object.assign(focusOverlay.style, {\n position: \"fixed\",\n top: `${rect.top}px`,\n left: `${rect.left}px`,\n width: `${rect.width}px`,\n height: `${rect.height}px`,\n });\n requestAnimationFrame(this.drawOverlays);\n } else {\n focusOverlay.style.display = \"none\";\n }\n }\n };\n\n private renderPlaybackStats() {\n // Only show stats if enabled\n if (!this.showStats) {\n return null;\n }\n\n // Get stats based on current mode\n let stats: PlaybackStats | null = null;\n\n if (this.presentationMode === \"canvas\" && this.renderStats) {\n // Canvas mode: use RenderStats (always-on collection)\n const timegroup = this.getTimegroup();\n if (!timegroup) return null;\n\n const compositionWidth = timegroup.offsetWidth || 1920;\n const compositionHeight = timegroup.offsetHeight || 1080;\n\n const resolutionScale = this.canvasPreviewResult\n ? this.canvasPreviewResult.getResolutionScale()\n : 1;\n\n const renderWidth = Math.floor(compositionWidth * resolutionScale);\n const renderHeight = Math.floor(compositionHeight * resolutionScale);\n\n stats = this.renderStats.getStats(\n renderWidth,\n renderHeight,\n resolutionScale,\n );\n } else if (this.presentationMode === \"dom\" && this.domStatsStrategy) {\n // DOM mode: use DomStatsStrategy (has its own loop)\n stats = this.domStatsStrategy.getStats();\n }\n\n if (!stats) {\n return null;\n }\n\n // Determine FPS color (based on frame interval, not render time)\n const fpsClass =\n stats.fps >= 55 ? \"good\" : stats.fps >= 25 ? \"warning\" : \"bad\";\n\n // Determine render time color (target is 33ms for 30fps)\n const renderClass =\n stats.avgRenderTime !== null\n ? stats.avgRenderTime <= 20\n ? \"good\"\n : stats.avgRenderTime <= 30\n ? \"warning\"\n : \"bad\"\n : \"\";\n\n // Determine headroom color (positive = good, negative = bad)\n const headroomClass =\n stats.headroom !== null\n ? stats.headroom >= 10\n ? \"good\"\n : stats.headroom >= 0\n ? \"warning\"\n : \"bad\"\n : \"\";\n\n // Determine pressure color\n const pressureClass =\n stats.pressureState === \"nominal\"\n ? \"good\"\n : stats.pressureState === \"fair\"\n ? \"good\"\n : stats.pressureState === \"serious\"\n ? \"warning\"\n : \"bad\";\n\n // Resolution scale color (only for canvas mode)\n const scaleClass =\n stats.resolutionScale !== null\n ? stats.resolutionScale >= 0.75\n ? \"good\"\n : stats.resolutionScale >= 0.5\n ? \"warning\"\n : \"bad\"\n : \"\";\n\n // Determine which stats to show based on mode\n const isCanvasMode = this.presentationMode === \"canvas\";\n const showRenderTime = isCanvasMode;\n const showHeadroom = isCanvasMode;\n const showResolutionScale = isCanvasMode;\n const showAdaptiveResolution =\n isCanvasMode && this.previewResolutionScale === \"auto\";\n\n // Motion state\n const motionState = this.isAtRest\n ? \"At Rest\"\n : this.isPlaying\n ? \"Playing\"\n : this.isScrubbing\n ? \"Scrubbing\"\n : \"Idle\";\n\n // Render pressure histogram bars\n const renderPressureHistogram = () => {\n if (stats.pressureHistory.length === 0) {\n return html`<div style=\"color: var(--ef-color-text-subtle); font-size: 9px;\">No pressure data (API not available)</div>`;\n }\n\n return html`\n <div class=\"pressure-histogram\">\n ${stats.pressureHistory.map(\n (state) => html`\n <div class=\"bar ${state}\"></div>\n `,\n )}\n </div>\n <div class=\"pressure-histogram-label\">\n <span>30s ago</span>\n <span>now</span>\n </div>\n `;\n };\n\n // Helper to pad numbers for consistent width\n const padNum = (n: number, decimals: number, width: number) => {\n const str = n.toFixed(decimals);\n return str.padStart(width, \"\\u2007\"); // Use figure space for padding\n };\n\n return html`\n <div class=\"playback-stats\">\n <div class=\"stat-row\">\n <span class=\"stat-label\">FPS</span>\n <span class=\"stat-value ${fpsClass}\">${padNum(stats.fps, 1, 5)}</span>\n </div>\n ${\n showRenderTime && stats.avgRenderTime !== null\n ? html`\n <div class=\"stat-row\">\n <span class=\"stat-label\">Render</span>\n <span class=\"stat-value ${renderClass}\">${padNum(stats.avgRenderTime, 1, 5)}ms</span>\n </div>\n `\n : null\n }\n ${\n showHeadroom && stats.headroom !== null\n ? html`\n <div class=\"stat-row\">\n <span class=\"stat-label\">Headroom</span>\n <span class=\"stat-value ${headroomClass}\">${stats.headroom >= 0 ? \"+\" : \"\"}${padNum(stats.headroom, 1, 4)}ms</span>\n </div>\n `\n : null\n }\n <div class=\"stat-row\">\n <span class=\"stat-label\">Resolution</span>\n <span class=\"stat-value\">${stats.renderWidth}×${stats.renderHeight}</span>\n </div>\n ${\n showResolutionScale && stats.resolutionScale !== null\n ? html`\n <div class=\"stat-row\">\n <span class=\"stat-label\">Scale</span>\n <span class=\"stat-value ${scaleClass}\">${String(Math.round(stats.resolutionScale * 100)).padStart(3, \"\\u2007\")}%</span>\n </div>\n `\n : null\n }\n <div class=\"stat-row\">\n <span class=\"stat-label\">CPU</span>\n <span class=\"stat-value ${pressureClass}\">${stats.pressureState}</span>\n </div>\n <div class=\"stat-row\">\n <span class=\"stat-label\">State</span>\n <span class=\"stat-value\">${motionState}</span>\n </div>\n ${\n showAdaptiveResolution && stats.samplesAtCurrentScale !== undefined\n ? html`\n <div style=\"margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--ef-color-border-subtle);\">\n <div class=\"stat-row\">\n <span class=\"stat-label\">Mode</span>\n <span class=\"stat-value good\">Auto</span>\n </div>\n <div class=\"stat-row\">\n <span class=\"stat-label\">Samples</span>\n <span class=\"stat-value\">${String(stats.samplesAtCurrentScale).padStart(3, \"\\u2007\")}/60</span>\n </div>\n <div class=\"stat-row\">\n <span class=\"stat-label\">Scale Up</span>\n <span class=\"stat-value ${stats.canScaleUp ? \"good\" : \"\"}\">${stats.canScaleUp ? \"Ready\" : \"Waiting\"}</span>\n </div>\n <div class=\"stat-row\">\n <span class=\"stat-label\">Scale Down</span>\n <span class=\"stat-value ${stats.canScaleDown ? \"\" : \"warning\"}\">${stats.canScaleDown ? \"Ready\" : \"Min\"}</span>\n </div>\n </div>\n `\n : null\n }\n \n <!-- CPU Pressure Histogram -->\n <div style=\"margin-top: 8px;\">\n <div style=\"color: #94a3b8; font-size: 10px; margin-bottom: 4px;\">CPU Pressure History</div>\n ${renderPressureHistogram()}\n </div>\n </div>\n `;\n }\n\n render() {\n if (this.rendering) {\n return html`\n <slot class=\"fixed inset-0 h-full w-full\" name=\"canvas\"></slot>\n `;\n }\n return html`\n <!-- Top: Full-width Toolbar -->\n <div style=\"grid-row: 1 / 2; grid-column: 1 / -1;\">\n ${this.renderToolbar()}\n </div>\n \n <!-- Left: Hierarchy Panel -->\n <div\n style=\"grid-row: 2 / 3; grid-column: 1 / 2; background: var(--ef-color-bg-panel); border-right: 1px solid var(--ef-color-border); min-height: 0; overflow: hidden;\"\n >\n <slot name=\"hierarchy\"></slot>\n </div>\n\n <!-- Center: Canvas area -->\n <div\n class=\"canvas-container\"\n part=\"canvas\"\n style=\"grid-row: 2 / 3; grid-column: 2 / 3; min-height: 0; overflow: hidden;\"\n @wheel=${this.handleStageWheel}\n >\n <!-- Original timegroup (hidden in clone/canvas mode, visible in dom mode) -->\n <slot\n name=\"canvas\"\n @slotchange=${() => this.#syncTimegroupListener()}\n ></slot>\n \n <!-- Canvas preview (visible in canvas mode only) -->\n <div \n class=\"canvas-overlay\" \n ${ref(this.canvasPreviewRef)}\n style=\"display: ${this.presentationMode === \"canvas\" ? \"block\" : \"none\"}\"\n ></div>\n\n <!-- Preview loading overlay (shown when timegroup content is loading) -->\n ${\n this.previewIsLoading\n ? html`\n <div class=\"preview-loading-overlay\">\n <div class=\"preview-loading-bar\"></div>\n </div>\n `\n : \"\"\n }\n \n <!-- Playback stats overlay (visible in canvas mode only) -->\n ${this.renderPlaybackStats()}\n </div>\n\n <!-- Bottom: Timeline -->\n <div\n class=\"overflow-hidden\"\n style=\"grid-row: 3 / 4; grid-column: 1 / -1; height: 100%; border-top: 1px solid var(--ef-color-border);\"\n >\n <slot name=\"timeline\"></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-workbench\": EFWorkbench;\n }\n}\n"],"mappings":"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"}
|
|
@@ -58,6 +58,8 @@ declare class PlaybackController implements ReactiveController {
|
|
|
58
58
|
hostConnected(): void;
|
|
59
59
|
hostDisconnected(): void;
|
|
60
60
|
hostUpdated(): void;
|
|
61
|
+
suspendSelfRender(): void;
|
|
62
|
+
resumeSelfRender(): void;
|
|
61
63
|
/**
|
|
62
64
|
* Run frame rendering via FrameController, or directly on the host if it
|
|
63
65
|
* implements FrameRenderable (standalone media element without a Timegroup).
|
|
@@ -213,6 +213,15 @@ var PlaybackController = class {
|
|
|
213
213
|
#selfRenderAbortController;
|
|
214
214
|
#selfRenderPromise;
|
|
215
215
|
#selfRenderDirty = false;
|
|
216
|
+
#selfRenderSuspended = false;
|
|
217
|
+
suspendSelfRender() {
|
|
218
|
+
this.#selfRenderSuspended = true;
|
|
219
|
+
this.#selfRenderAbortController?.abort();
|
|
220
|
+
this.#selfRenderAbortController = void 0;
|
|
221
|
+
}
|
|
222
|
+
resumeSelfRender() {
|
|
223
|
+
this.#selfRenderSuspended = false;
|
|
224
|
+
}
|
|
216
225
|
/**
|
|
217
226
|
* Run frame rendering via FrameController, or directly on the host if it
|
|
218
227
|
* implements FrameRenderable (standalone media element without a Timegroup).
|
|
@@ -232,6 +241,7 @@ var PlaybackController = class {
|
|
|
232
241
|
}
|
|
233
242
|
const host = this.#host;
|
|
234
243
|
if (!host.prepareFrame || !host.renderFrame) return;
|
|
244
|
+
if (this.#selfRenderSuspended) return;
|
|
235
245
|
if (this.#selfRenderPromise) {
|
|
236
246
|
this.#selfRenderDirty = true;
|
|
237
247
|
return this.#selfRenderPromise;
|
|
@@ -255,7 +265,7 @@ var PlaybackController = class {
|
|
|
255
265
|
console.error("Standalone frame render error:", error);
|
|
256
266
|
} finally {
|
|
257
267
|
this.#selfRenderPromise = void 0;
|
|
258
|
-
if (this.#selfRenderDirty) this.#startSelfRender(host, this.currentTimeMs);
|
|
268
|
+
if (this.#selfRenderDirty && !this.#selfRenderSuspended) this.#startSelfRender(host, this.currentTimeMs);
|
|
259
269
|
}
|
|
260
270
|
})();
|
|
261
271
|
return this.#selfRenderPromise;
|