@editframe/elements 0.37.3-beta → 0.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/dist/EF_FRAMEGEN.js +17 -14
  2. package/dist/EF_FRAMEGEN.js.map +1 -1
  3. package/dist/EF_RENDERING.js.map +1 -1
  4. package/dist/canvas/EFCanvas.d.ts +9 -2
  5. package/dist/canvas/EFCanvas.js +14 -4
  6. package/dist/canvas/EFCanvas.js.map +1 -1
  7. package/dist/canvas/EFCanvasItem.d.ts +2 -2
  8. package/dist/canvas/overlays/SelectionOverlay.d.ts +10 -2
  9. package/dist/canvas/overlays/SelectionOverlay.js +5 -12
  10. package/dist/canvas/overlays/SelectionOverlay.js.map +1 -1
  11. package/dist/canvas/overlays/overlayState.js.map +1 -1
  12. package/dist/canvas/selection/SelectionController.js.map +1 -1
  13. package/dist/elements/EFAudio.d.ts +1 -11
  14. package/dist/elements/EFAudio.js +2 -10
  15. package/dist/elements/EFAudio.js.map +1 -1
  16. package/dist/elements/EFCaptions.d.ts +5 -9
  17. package/dist/elements/EFCaptions.js +34 -11
  18. package/dist/elements/EFCaptions.js.map +1 -1
  19. package/dist/elements/EFImage.d.ts +10 -8
  20. package/dist/elements/EFImage.js +117 -32
  21. package/dist/elements/EFImage.js.map +1 -1
  22. package/dist/elements/EFMedia/AssetMediaEngine.js +2 -2
  23. package/dist/elements/EFMedia/AssetMediaEngine.js.map +1 -1
  24. package/dist/elements/EFMedia/BaseMediaEngine.js +15 -92
  25. package/dist/elements/EFMedia/BaseMediaEngine.js.map +1 -1
  26. package/dist/elements/EFMedia/BufferedSeekingInput.js +10 -11
  27. package/dist/elements/EFMedia/BufferedSeekingInput.js.map +1 -1
  28. package/dist/elements/EFMedia/{AssetIdMediaEngine.js → FileMediaEngine.js} +44 -24
  29. package/dist/elements/EFMedia/FileMediaEngine.js.map +1 -0
  30. package/dist/elements/EFMedia/JitMediaEngine.js +14 -13
  31. package/dist/elements/EFMedia/JitMediaEngine.js.map +1 -1
  32. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +3 -3
  33. package/dist/elements/EFMedia/shared/AudioSpanUtils.js.map +1 -1
  34. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +12 -7
  35. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js.map +1 -1
  36. package/dist/elements/EFMedia/shared/timeoutUtils.js +44 -0
  37. package/dist/elements/EFMedia/shared/timeoutUtils.js.map +1 -0
  38. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +1 -1
  39. package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js.map +1 -1
  40. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +4 -4
  41. package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js.map +1 -1
  42. package/dist/elements/EFMedia.d.ts +14 -8
  43. package/dist/elements/EFMedia.js +52 -19
  44. package/dist/elements/EFMedia.js.map +1 -1
  45. package/dist/elements/EFPanZoom.d.ts +2 -2
  46. package/dist/elements/EFPanZoom.js +1 -1
  47. package/dist/elements/EFPanZoom.js.map +1 -1
  48. package/dist/elements/EFSourceMixin.js +16 -8
  49. package/dist/elements/EFSourceMixin.js.map +1 -1
  50. package/dist/elements/EFSurface.d.ts +5 -8
  51. package/dist/elements/EFSurface.js +4 -43
  52. package/dist/elements/EFSurface.js.map +1 -1
  53. package/dist/elements/EFTemporal.d.ts +33 -8
  54. package/dist/elements/EFTemporal.js +92 -40
  55. package/dist/elements/EFTemporal.js.map +1 -1
  56. package/dist/elements/EFText.d.ts +3 -0
  57. package/dist/elements/EFText.js +54 -21
  58. package/dist/elements/EFText.js.map +1 -1
  59. package/dist/elements/EFTextSegment.js +8 -4
  60. package/dist/elements/EFTextSegment.js.map +1 -1
  61. package/dist/elements/EFTimegroup.d.ts +26 -43
  62. package/dist/elements/EFTimegroup.js +295 -314
  63. package/dist/elements/EFTimegroup.js.map +1 -1
  64. package/dist/elements/EFVideo.d.ts +44 -42
  65. package/dist/elements/EFVideo.js +259 -172
  66. package/dist/elements/EFVideo.js.map +1 -1
  67. package/dist/elements/EFWaveform.d.ts +3 -8
  68. package/dist/elements/EFWaveform.js +18 -13
  69. package/dist/elements/EFWaveform.js.map +1 -1
  70. package/dist/elements/ElementPositionInfo.js.map +1 -1
  71. package/dist/elements/FetchMixin.js.map +1 -1
  72. package/dist/elements/TargetController.d.ts +0 -3
  73. package/dist/elements/TargetController.js +12 -35
  74. package/dist/elements/TargetController.js.map +1 -1
  75. package/dist/elements/TimegroupController.js.map +1 -1
  76. package/dist/elements/cloneFactoryRegistry.d.ts +14 -0
  77. package/dist/elements/cloneFactoryRegistry.js +15 -0
  78. package/dist/elements/cloneFactoryRegistry.js.map +1 -0
  79. package/dist/elements/renderTemporalAudio.js +8 -6
  80. package/dist/elements/renderTemporalAudio.js.map +1 -1
  81. package/dist/elements/setupTemporalHierarchy.js +62 -0
  82. package/dist/elements/setupTemporalHierarchy.js.map +1 -0
  83. package/dist/elements/updateAnimations.js +62 -87
  84. package/dist/elements/updateAnimations.js.map +1 -1
  85. package/dist/getRenderInfo.d.ts +3 -2
  86. package/dist/getRenderInfo.js +20 -4
  87. package/dist/getRenderInfo.js.map +1 -1
  88. package/dist/gui/ContextMixin.js +68 -12
  89. package/dist/gui/ContextMixin.js.map +1 -1
  90. package/dist/gui/Controllable.js +1 -1
  91. package/dist/gui/Controllable.js.map +1 -1
  92. package/dist/gui/EFActiveRootTemporal.d.ts +2 -2
  93. package/dist/gui/EFActiveRootTemporal.js.map +1 -1
  94. package/dist/gui/EFControls.d.ts +2 -2
  95. package/dist/gui/EFControls.js +2 -2
  96. package/dist/gui/EFControls.js.map +1 -1
  97. package/dist/gui/EFDial.d.ts +2 -2
  98. package/dist/gui/EFDial.js +12 -9
  99. package/dist/gui/EFDial.js.map +1 -1
  100. package/dist/gui/EFFilmstrip.d.ts +2 -0
  101. package/dist/gui/EFFilmstrip.js +18 -10
  102. package/dist/gui/EFFilmstrip.js.map +1 -1
  103. package/dist/gui/EFFitScale.d.ts +28 -4
  104. package/dist/gui/EFFitScale.js +88 -26
  105. package/dist/gui/EFFitScale.js.map +1 -1
  106. package/dist/gui/EFFocusOverlay.d.ts +2 -2
  107. package/dist/gui/EFFocusOverlay.js +3 -3
  108. package/dist/gui/EFFocusOverlay.js.map +1 -1
  109. package/dist/gui/EFOverlayItem.d.ts +2 -2
  110. package/dist/gui/EFOverlayLayer.d.ts +2 -2
  111. package/dist/gui/EFPause.d.ts +2 -2
  112. package/dist/gui/EFPause.js +1 -1
  113. package/dist/gui/EFPlay.d.ts +2 -2
  114. package/dist/gui/EFPlay.js +1 -1
  115. package/dist/gui/EFPreview.js +1 -1
  116. package/dist/gui/EFResizableBox.d.ts +2 -2
  117. package/dist/gui/EFResizableBox.js +5 -5
  118. package/dist/gui/EFResizableBox.js.map +1 -1
  119. package/dist/gui/EFScrubber.d.ts +2 -2
  120. package/dist/gui/EFScrubber.js +8 -13
  121. package/dist/gui/EFScrubber.js.map +1 -1
  122. package/dist/gui/EFTimeDisplay.d.ts +6 -2
  123. package/dist/gui/EFTimeDisplay.js +25 -7
  124. package/dist/gui/EFTimeDisplay.js.map +1 -1
  125. package/dist/gui/EFTimelineRuler.d.ts +2 -2
  126. package/dist/gui/EFTimelineRuler.js +3 -3
  127. package/dist/gui/EFTimelineRuler.js.map +1 -1
  128. package/dist/gui/EFToggleLoop.d.ts +2 -2
  129. package/dist/gui/EFToggleLoop.js +1 -1
  130. package/dist/gui/EFTogglePlay.d.ts +2 -2
  131. package/dist/gui/EFTogglePlay.js +1 -1
  132. package/dist/gui/EFTransformHandles.d.ts +2 -2
  133. package/dist/gui/EFTransformHandles.js +6 -6
  134. package/dist/gui/EFTransformHandles.js.map +1 -1
  135. package/dist/gui/EFWorkbench.d.ts +40 -36
  136. package/dist/gui/EFWorkbench.js +436 -822
  137. package/dist/gui/EFWorkbench.js.map +1 -1
  138. package/dist/gui/FitScaleHelpers.js.map +1 -1
  139. package/dist/gui/PlaybackController.d.ts +3 -8
  140. package/dist/gui/PlaybackController.js +59 -56
  141. package/dist/gui/PlaybackController.js.map +1 -1
  142. package/dist/gui/TWMixin.js +1 -1
  143. package/dist/gui/TWMixin.js.map +1 -1
  144. package/dist/gui/TargetOrContextMixin.js +43 -6
  145. package/dist/gui/TargetOrContextMixin.js.map +1 -1
  146. package/dist/gui/ef-theme.css +136 -0
  147. package/dist/gui/hierarchy/EFHierarchy.d.ts +2 -2
  148. package/dist/gui/hierarchy/EFHierarchy.js +14 -24
  149. package/dist/gui/hierarchy/EFHierarchy.js.map +1 -1
  150. package/dist/gui/hierarchy/EFHierarchyItem.d.ts +3 -3
  151. package/dist/gui/hierarchy/EFHierarchyItem.js +22 -10
  152. package/dist/gui/hierarchy/EFHierarchyItem.js.map +1 -1
  153. package/dist/gui/icons.js.map +1 -1
  154. package/dist/gui/previewSettingsContext.d.ts +18 -0
  155. package/dist/gui/previewSettingsContext.js.map +1 -1
  156. package/dist/gui/theme.js +34 -0
  157. package/dist/gui/theme.js.map +1 -0
  158. package/dist/gui/timeline/EFTimeline.d.ts +2 -2
  159. package/dist/gui/timeline/EFTimeline.js +70 -52
  160. package/dist/gui/timeline/EFTimeline.js.map +1 -1
  161. package/dist/gui/timeline/EFTimelineRow.d.ts +5 -3
  162. package/dist/gui/timeline/EFTimelineRow.js +55 -32
  163. package/dist/gui/timeline/EFTimelineRow.js.map +1 -1
  164. package/dist/gui/timeline/TrimHandles.d.ts +23 -9
  165. package/dist/gui/timeline/TrimHandles.js +224 -51
  166. package/dist/gui/timeline/TrimHandles.js.map +1 -1
  167. package/dist/gui/timeline/flattenHierarchy.js.map +1 -1
  168. package/dist/gui/timeline/timelineEditingContext.d.ts +34 -0
  169. package/dist/gui/timeline/timelineEditingContext.js +24 -0
  170. package/dist/gui/timeline/timelineEditingContext.js.map +1 -0
  171. package/dist/gui/timeline/timelineStateContext.js.map +1 -1
  172. package/dist/gui/timeline/tracks/AudioTrack.js +1 -1
  173. package/dist/gui/timeline/tracks/AudioTrack.js.map +1 -1
  174. package/dist/gui/timeline/tracks/CaptionsTrack.d.ts +2 -3
  175. package/dist/gui/timeline/tracks/CaptionsTrack.js +17 -75
  176. package/dist/gui/timeline/tracks/CaptionsTrack.js.map +1 -1
  177. package/dist/gui/timeline/tracks/EFThumbnailStrip.d.ts +52 -0
  178. package/dist/gui/timeline/tracks/EFThumbnailStrip.js +596 -0
  179. package/dist/gui/timeline/tracks/EFThumbnailStrip.js.map +1 -0
  180. package/dist/gui/timeline/tracks/HTMLTrack.js.map +1 -1
  181. package/dist/gui/timeline/tracks/ImageTrack.js.map +1 -1
  182. package/dist/gui/timeline/tracks/TextTrack.d.ts +3 -2
  183. package/dist/gui/timeline/tracks/TextTrack.js +17 -43
  184. package/dist/gui/timeline/tracks/TextTrack.js.map +1 -1
  185. package/dist/gui/timeline/tracks/TimegroupTrack.d.ts +3 -4
  186. package/dist/gui/timeline/tracks/TimegroupTrack.js +33 -23
  187. package/dist/gui/timeline/tracks/TimegroupTrack.js.map +1 -1
  188. package/dist/gui/timeline/tracks/TrackItem.d.ts +7 -9
  189. package/dist/gui/timeline/tracks/TrackItem.js +18 -17
  190. package/dist/gui/timeline/tracks/TrackItem.js.map +1 -1
  191. package/dist/gui/timeline/tracks/VideoTrack.d.ts +3 -3
  192. package/dist/gui/timeline/tracks/VideoTrack.js +11 -14
  193. package/dist/gui/timeline/tracks/VideoTrack.js.map +1 -1
  194. package/dist/gui/timeline/tracks/WaveformTrack.js.map +1 -1
  195. package/dist/gui/timeline/tracks/renderTrackChildren.js.map +1 -1
  196. package/dist/gui/timeline/tracks/waveformUtils.js +1 -1
  197. package/dist/gui/timeline/tracks/waveformUtils.js.map +1 -1
  198. package/dist/gui/tree/EFTree.d.ts +2 -2
  199. package/dist/gui/tree/EFTree.js +8 -14
  200. package/dist/gui/tree/EFTree.js.map +1 -1
  201. package/dist/gui/tree/EFTreeItem.d.ts +2 -2
  202. package/dist/gui/tree/EFTreeItem.js +3 -3
  203. package/dist/gui/tree/EFTreeItem.js.map +1 -1
  204. package/dist/gui/tree/treeContext.js.map +1 -1
  205. package/dist/index.d.ts +10 -8
  206. package/dist/index.js +6 -5
  207. package/dist/index.js.map +1 -1
  208. package/dist/node.d.ts +2 -2
  209. package/dist/node.js +2 -2
  210. package/dist/preview/AdaptiveResolutionTracker.js +3 -3
  211. package/dist/preview/AdaptiveResolutionTracker.js.map +1 -1
  212. package/dist/preview/FrameController.d.ts +2 -17
  213. package/dist/preview/FrameController.js +40 -63
  214. package/dist/preview/FrameController.js.map +1 -1
  215. package/dist/preview/QualityUpgradeScheduler.d.ts +76 -0
  216. package/dist/preview/QualityUpgradeScheduler.js +158 -0
  217. package/dist/preview/QualityUpgradeScheduler.js.map +1 -0
  218. package/dist/preview/RenderContext.d.ts +119 -1
  219. package/dist/preview/RenderContext.js +21 -3
  220. package/dist/preview/RenderContext.js.map +1 -1
  221. package/dist/preview/RenderProfiler.js.map +1 -1
  222. package/dist/preview/RenderStats.js +85 -0
  223. package/dist/preview/RenderStats.js.map +1 -0
  224. package/dist/preview/encoding/canvasEncoder.js +2 -52
  225. package/dist/preview/encoding/canvasEncoder.js.map +1 -1
  226. package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
  227. package/dist/preview/encoding/workerEncoder.js.map +1 -1
  228. package/dist/preview/logger.js.map +1 -1
  229. package/dist/preview/previewSettings.d.ts +34 -0
  230. package/dist/preview/previewSettings.js +29 -17
  231. package/dist/preview/previewSettings.js.map +1 -1
  232. package/dist/preview/previewTypes.js +4 -4
  233. package/dist/preview/previewTypes.js.map +1 -1
  234. package/dist/preview/renderElementToCanvas.d.ts +44 -0
  235. package/dist/preview/renderElementToCanvas.js +72 -0
  236. package/dist/preview/renderElementToCanvas.js.map +1 -0
  237. package/dist/preview/renderTimegroupToCanvas.d.ts +134 -32
  238. package/dist/preview/renderTimegroupToCanvas.js +321 -146
  239. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  240. package/dist/preview/renderTimegroupToCanvas.types.d.ts +51 -0
  241. package/dist/preview/renderTimegroupToVideo.d.ts +20 -35
  242. package/dist/preview/renderTimegroupToVideo.js +94 -106
  243. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  244. package/dist/preview/renderTimegroupToVideo.types.d.ts +42 -0
  245. package/dist/preview/renderVideoToVideo.js +286 -0
  246. package/dist/preview/renderVideoToVideo.js.map +1 -0
  247. package/dist/preview/renderers.d.ts +56 -0
  248. package/dist/preview/renderers.js +13 -1
  249. package/dist/preview/renderers.js.map +1 -1
  250. package/dist/preview/rendering/ScaleConfig.js +74 -0
  251. package/dist/preview/rendering/ScaleConfig.js.map +1 -0
  252. package/dist/preview/rendering/inlineImages.d.ts +13 -0
  253. package/dist/preview/rendering/inlineImages.js +7 -44
  254. package/dist/preview/rendering/inlineImages.js.map +1 -1
  255. package/dist/preview/rendering/loadImage.d.ts +8 -0
  256. package/dist/preview/rendering/loadImage.js +22 -0
  257. package/dist/preview/rendering/loadImage.js.map +1 -0
  258. package/dist/preview/rendering/renderToImageNative.js +3 -3
  259. package/dist/preview/rendering/renderToImageNative.js.map +1 -1
  260. package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
  261. package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
  262. package/dist/preview/statsTrackingStrategy.js +1 -101
  263. package/dist/preview/statsTrackingStrategy.js.map +1 -1
  264. package/dist/preview/workers/WorkerPool.js +0 -1
  265. package/dist/preview/workers/WorkerPool.js.map +1 -1
  266. package/dist/preview/workers/encoderWorkerInline.js +21 -54
  267. package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
  268. package/dist/render/EFRenderAPI.d.ts +2 -1
  269. package/dist/render/EFRenderAPI.js +12 -36
  270. package/dist/render/EFRenderAPI.js.map +1 -1
  271. package/dist/render/getRenderData.js +4 -4
  272. package/dist/render/getRenderData.js.map +1 -1
  273. package/dist/style.css +114 -163
  274. package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
  275. package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
  276. package/dist/transcoding/types/index.d.ts +1 -1
  277. package/dist/transcoding/utils/UrlGenerator.js +10 -3
  278. package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
  279. package/dist/utils/LRUCache.js +1 -0
  280. package/dist/utils/LRUCache.js.map +1 -1
  281. package/dist/utils/frameTime.js +23 -1
  282. package/dist/utils/frameTime.js.map +1 -1
  283. package/package.json +45 -8
  284. package/scripts/build-css.js +8 -1
  285. package/test/setup.ts +0 -1
  286. package/test/useAssetMSW.ts +50 -0
  287. package/test/visualRegressionUtils.ts +23 -9
  288. package/tsdown.config.ts +6 -1
  289. package/dist/_virtual/rolldown_runtime.js +0 -27
  290. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
  291. package/dist/elements/EFThumbnailStrip.d.ts +0 -167
  292. package/dist/elements/EFThumbnailStrip.js +0 -731
  293. package/dist/elements/EFThumbnailStrip.js.map +0 -1
  294. package/dist/elements/SessionThumbnailCache.js +0 -154
  295. package/dist/elements/SessionThumbnailCache.js.map +0 -1
  296. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
  297. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
  298. package/dist/node_modules/react/cjs/react.development.js +0 -1521
  299. package/dist/node_modules/react/cjs/react.development.js.map +0 -1
  300. package/dist/node_modules/react/index.js +0 -13
  301. package/dist/node_modules/react/index.js.map +0 -1
  302. package/dist/node_modules/react/jsx-runtime.js +0 -13
  303. package/dist/node_modules/react/jsx-runtime.js.map +0 -1
  304. package/dist/preview/encoding/types.d.ts +0 -1
  305. package/dist/preview/renderTimegroupPreview.js +0 -686
  306. package/dist/preview/renderTimegroupPreview.js.map +0 -1
  307. package/dist/preview/rendering/renderToImage.d.ts +0 -2
  308. package/dist/preview/rendering/renderToImage.js +0 -95
  309. package/dist/preview/rendering/renderToImage.js.map +0 -1
  310. package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
  311. package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
  312. package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
  313. package/dist/preview/rendering/svgSerializer.js +0 -43
  314. package/dist/preview/rendering/svgSerializer.js.map +0 -1
  315. package/dist/preview/rendering/types.d.ts +0 -2
  316. package/dist/preview/thumbnailCacheSettings.js +0 -52
  317. package/dist/preview/thumbnailCacheSettings.js.map +0 -1
  318. package/dist/sandbox/PlaybackControls.d.ts +0 -1
  319. package/dist/sandbox/PlaybackControls.js +0 -10
  320. package/dist/sandbox/PlaybackControls.js.map +0 -1
  321. package/dist/sandbox/ScenarioRunner.d.ts +0 -1
  322. package/dist/sandbox/ScenarioRunner.js +0 -1
  323. package/dist/sandbox/defineSandbox.d.ts +0 -1
  324. package/dist/sandbox/index.d.ts +0 -3
  325. package/dist/sandbox/index.js +0 -2
  326. package/test/EFVideo.framegen.browsertest.ts +0 -80
  327. package/test/thumbnail-performance-test.html +0 -116
@@ -1,17 +1,14 @@
1
+ import { TWMixin } from "./TWMixin2.js";
2
+ import { updateAnimations } from "../elements/updateAnimations.js";
1
3
  import { __decorate } from "../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
2
4
  import { ContextMixin } from "./ContextMixin.js";
3
- import { TWMixin } from "./TWMixin2.js";
4
- import { getPreviewPresentationMode, getPreviewResolutionScale, getRenderMode, getShowStats, isNativeCanvasApiAvailable, setPreviewPresentationMode, setPreviewResolutionScale, setRenderMode, setShowStats } from "../preview/previewSettings.js";
5
- import { renderTimegroupToCanvas } from "../preview/renderTimegroupToCanvas.js";
6
- import { RenderCancelledError, renderTimegroupToVideo } from "../preview/renderTimegroupToVideo.js";
7
5
  import { findRootTemporal } from "../elements/findRootTemporal.js";
8
6
  import { ICONS, phosphorIcon } from "./icons.js";
9
- import { sessionThumbnailCache } from "../elements/SessionThumbnailCache.js";
10
- import "../elements/EFThumbnailStrip.js";
11
- import { createStatsTrackingStrategy } from "../preview/statsTrackingStrategy.js";
12
- import { getThumbnailCacheMaxSize, onThumbnailCacheSettingsChanged, setThumbnailCacheMaxSize } from "../preview/thumbnailCacheSettings.js";
13
- import { AdaptiveResolutionTracker } from "../preview/AdaptiveResolutionTracker.js";
7
+ import { getPreviewPresentationMode, getPreviewResolutionScale, getRenderMode, getShowStats, getShowThumbnailTimestamps, isNativeCanvasApiAvailable, setPreviewPresentationMode, setRenderMode, setShowStats, setShowThumbnailTimestamps } from "../preview/previewSettings.js";
14
8
  import { previewSettingsContext } from "./previewSettingsContext.js";
9
+ import { AdaptiveResolutionTracker } from "../preview/AdaptiveResolutionTracker.js";
10
+ import { RenderStats } from "../preview/RenderStats.js";
11
+ import { DomStatsStrategy } from "../preview/statsTrackingStrategy.js";
15
12
  import "./EFFitScale.js";
16
13
  import { EFTimegroup } from "../elements/EFTimegroup.js";
17
14
  import { provide } from "@lit/context";
@@ -39,15 +36,11 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
39
36
  renderMode: getRenderMode(),
40
37
  resolutionScale: getPreviewResolutionScale(),
41
38
  showStats: getShowStats(),
42
- thumbnailCacheMaxSize: getThumbnailCacheMaxSize()
39
+ showThumbnailTimestamps: getShowThumbnailTimestamps()
43
40
  };
44
41
  this.renderMode = this.previewSettings.renderMode;
45
42
  this.presentationMode = this.previewSettings.presentationMode;
46
43
  this.previewResolutionScale = this.previewSettings.resolutionScale;
47
- this.debugThumbnailTimestamps = false;
48
- this.thumbnailCacheMaxSize = this.previewSettings.thumbnailCacheMaxSize;
49
- this.thumbnailCacheStats = null;
50
- this.cacheStatsUpdateInterval = null;
51
44
  this.exportOptions = {
52
45
  includeAudio: true,
53
46
  scale: 1,
@@ -61,20 +54,16 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
61
54
  this.isAtRest = true;
62
55
  this.currentAdaptiveScale = 1;
63
56
  this.showStats = this.previewSettings.showStats;
64
- this.statsStrategy = null;
57
+ this.themeMode = this.getInitialTheme();
58
+ this.renderStats = null;
59
+ this.systemThemeMediaQuery = null;
60
+ this.systemThemeListener = null;
61
+ this.domStatsStrategy = null;
65
62
  this.isScrubbingRef = { current: false };
66
63
  this.restDebounceTimer = null;
67
64
  this.playingCheckInterval = null;
68
65
  this.adaptiveTracker = null;
69
66
  this.savePanZoomDebounceTimer = null;
70
- this.cloneOverlayRef = createRef();
71
- this.cloneRefresh = null;
72
- this.cloneAnimationFrame = null;
73
- this.cloneRootElement = null;
74
- this.cloneTimegroup = null;
75
- this.structureObserver = null;
76
- this.rebuildPending = false;
77
- this.canvasRefresh = null;
78
67
  this.canvasPreviewRef = createRef();
79
68
  this.canvasPreviewResult = null;
80
69
  this.canvasAnimationFrame = null;
@@ -101,29 +90,20 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
101
90
  static {
102
91
  this.styles = [css`
103
92
  :host {
104
- display: flex;
105
- flex-direction: column;
93
+ display: grid;
94
+ grid-template-rows: auto 1fr 280px;
95
+ grid-template-columns: 280px 1fr;
106
96
  width: 100%;
107
97
  height: 100%;
108
- min-width: 0;
109
- min-height: 0;
110
98
  overflow: hidden;
99
+ background-color: var(--ef-color-bg);
111
100
 
112
- /* Light mode colors */
113
- --workbench-bg: rgb(30 41 59); /* slate-800 */
114
- --workbench-overlay-border: rgb(59 130 246); /* blue-500 */
115
- --workbench-overlay-bg: rgb(191 219 254); /* blue-200 */
116
- --toolbar-bg: rgb(15 23 42); /* slate-900 */
117
- --toolbar-border: rgba(148, 163, 184, 0.2);
118
- }
119
-
120
- :host(.dark), :host-context(.dark) {
121
- /* Dark mode colors */
122
- --workbench-bg: rgb(2 6 23); /* slate-950 */
123
- --workbench-overlay-border: rgb(96 165 250); /* blue-400 */
124
- --workbench-overlay-bg: rgb(30 58 138); /* blue-900 */
125
- --toolbar-bg: rgb(2 6 23);
126
- --toolbar-border: rgba(148, 163, 184, 0.15);
101
+ /* Component tokens (reference globals from ef-theme.css) */
102
+ --workbench-bg: var(--ef-color-bg);
103
+ --workbench-overlay-border: var(--ef-color-primary);
104
+ --workbench-overlay-bg: var(--ef-color-primary-subtle);
105
+ --toolbar-bg: var(--ef-color-bg-elevated);
106
+ --toolbar-border: var(--ef-color-border-subtle);
127
107
  }
128
108
 
129
109
  /* Utility classes (not relying on external Tailwind) */
@@ -186,36 +166,36 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
186
166
  justify-content: center;
187
167
  gap: 6px;
188
168
  padding: 6px 12px;
189
- background: rgba(51, 65, 85, 0.6);
190
- border: 1px solid rgba(148, 163, 184, 0.2);
169
+ background: var(--ef-color-bg-inset);
170
+ border: 1px solid var(--ef-color-border-subtle);
191
171
  border-radius: 6px;
192
- color: #e2e8f0;
172
+ color: var(--ef-color-text);
193
173
  font-size: 12px;
194
- font-weight: 500;
174
+ font-weight: 600;
195
175
  cursor: pointer;
196
176
  transition: all 0.15s ease;
197
177
  }
198
178
 
199
179
  .toolbar-btn:hover {
200
- background: rgba(51, 65, 85, 0.9);
201
- border-color: rgba(148, 163, 184, 0.3);
180
+ background: var(--ef-color-hover);
181
+ border-color: var(--ef-color-border);
202
182
  }
203
183
 
204
184
  .toolbar-btn.active {
205
- background: rgba(59, 130, 246, 0.2);
206
- border-color: rgba(59, 130, 246, 0.4);
207
- color: #60a5fa;
185
+ background: var(--ef-color-primary-subtle);
186
+ border-color: var(--ef-color-primary);
187
+ color: var(--ef-color-primary-hover);
208
188
  }
209
189
 
210
190
  .toolbar-btn.primary {
211
- background: linear-gradient(135deg, #3b82f6, #2563eb);
191
+ background: var(--ef-color-primary);
212
192
  border-color: transparent;
213
193
  color: white;
214
194
  font-weight: 600;
215
195
  }
216
196
 
217
197
  .toolbar-btn.primary:hover {
218
- background: linear-gradient(135deg, #60a5fa, #3b82f6);
198
+ background: var(--ef-color-primary-hover);
219
199
  }
220
200
 
221
201
  .toolbar-icon-btn {
@@ -225,23 +205,23 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
225
205
  width: 32px;
226
206
  height: 32px;
227
207
  padding: 0;
228
- background: rgba(51, 65, 85, 0.6);
229
- border: 1px solid rgba(148, 163, 184, 0.2);
208
+ background: var(--ef-color-bg-inset);
209
+ border: 1px solid var(--ef-color-border-subtle);
230
210
  border-radius: 6px;
231
- color: #e2e8f0;
211
+ color: var(--ef-color-text);
232
212
  cursor: pointer;
233
213
  transition: all 0.15s ease;
234
214
  }
235
215
 
236
216
  .toolbar-icon-btn:hover {
237
- background: rgba(51, 65, 85, 0.9);
238
- border-color: rgba(148, 163, 184, 0.3);
217
+ background: var(--ef-color-hover);
218
+ border-color: var(--ef-color-border);
239
219
  }
240
220
 
241
221
  .toolbar-icon-btn.active {
242
- background: rgba(59, 130, 246, 0.2);
243
- border-color: rgba(59, 130, 246, 0.4);
244
- color: #60a5fa;
222
+ background: var(--ef-color-primary-subtle);
223
+ border-color: var(--ef-color-primary);
224
+ color: var(--ef-color-primary-hover);
245
225
  }
246
226
 
247
227
  .mode-indicator {
@@ -258,15 +238,15 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
258
238
  }
259
239
 
260
240
  .mode-indicator.dom {
261
- background: rgba(34, 197, 94, 0.2);
262
- color: #4ade80;
263
- border: 1px solid rgba(34, 197, 94, 0.3);
241
+ background: color-mix(in srgb, var(--ef-color-success) 15%, transparent);
242
+ color: var(--ef-color-success);
243
+ border: 1px solid color-mix(in srgb, var(--ef-color-success) 30%, transparent);
264
244
  }
265
245
 
266
246
  .mode-indicator.canvas {
267
- background: rgba(168, 85, 247, 0.2);
268
- color: #c084fc;
269
- border: 1px solid rgba(168, 85, 247, 0.3);
247
+ background: color-mix(in srgb, var(--ef-color-type-image) 15%, transparent);
248
+ color: var(--ef-color-type-image);
249
+ border: 1px solid color-mix(in srgb, var(--ef-color-type-image) 30%, transparent);
270
250
  }
271
251
 
272
252
  .canvas-container {
@@ -277,6 +257,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
277
257
  grid-template-columns: 100%;
278
258
  grid-template-rows: 100%;
279
259
  min-height: 0;
260
+ background: var(--ef-color-bg);
280
261
  }
281
262
 
282
263
  .canvas-container ::slotted(*) {
@@ -286,7 +267,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
286
267
  grid-row: 1;
287
268
  }
288
269
 
289
- .clone-overlay {
270
+ .canvas-overlay {
290
271
  position: absolute;
291
272
  inset: 0;
292
273
  pointer-events: none;
@@ -303,13 +284,13 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
303
284
  top: 8px;
304
285
  left: 8px;
305
286
  width: 200px;
306
- background: rgba(0, 0, 0, 0.75);
287
+ background: color-mix(in srgb, var(--ef-color-bg-elevated) 90%, transparent);
307
288
  backdrop-filter: blur(4px);
308
289
  border-radius: 6px;
309
290
  padding: 8px 12px;
310
291
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
311
292
  font-size: 11px;
312
- color: #e2e8f0;
293
+ color: var(--ef-color-text);
313
294
  z-index: 10;
314
295
  pointer-events: none;
315
296
  line-height: 1.5;
@@ -322,7 +303,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
322
303
  }
323
304
 
324
305
  .playback-stats .stat-label {
325
- color: #94a3b8;
306
+ color: var(--ef-color-text-muted);
326
307
  flex-shrink: 0;
327
308
  width: 85px;
328
309
  }
@@ -335,15 +316,15 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
335
316
  }
336
317
 
337
318
  .playback-stats .stat-value.good {
338
- color: #4ade80;
319
+ color: var(--ef-color-success);
339
320
  }
340
321
 
341
322
  .playback-stats .stat-value.warning {
342
- color: #fbbf24;
323
+ color: var(--ef-color-warning);
343
324
  }
344
325
 
345
326
  .playback-stats .stat-value.bad {
346
- color: #f87171;
327
+ color: var(--ef-color-danger);
347
328
  }
348
329
 
349
330
  .pressure-histogram {
@@ -353,7 +334,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
353
334
  height: 24px;
354
335
  margin-top: 8px;
355
336
  padding-top: 8px;
356
- border-top: 1px solid rgba(148, 163, 184, 0.2);
337
+ border-top: 1px solid var(--ef-color-border-subtle);
357
338
  }
358
339
 
359
340
  .pressure-histogram .bar {
@@ -365,22 +346,22 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
365
346
  }
366
347
 
367
348
  .pressure-histogram .bar.nominal {
368
- background: #4ade80;
349
+ background: var(--ef-color-success);
369
350
  height: 25%;
370
351
  }
371
352
 
372
353
  .pressure-histogram .bar.fair {
373
- background: #a3e635;
354
+ background: var(--ef-color-warning);
374
355
  height: 50%;
375
356
  }
376
357
 
377
358
  .pressure-histogram .bar.serious {
378
- background: #fbbf24;
359
+ background: var(--ef-color-warning);
379
360
  height: 75%;
380
361
  }
381
362
 
382
363
  .pressure-histogram .bar.critical {
383
- background: #f87171;
364
+ background: var(--ef-color-danger);
384
365
  height: 100%;
385
366
  }
386
367
 
@@ -389,7 +370,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
389
370
  justify-content: space-between;
390
371
  margin-top: 4px;
391
372
  font-size: 9px;
392
- color: #64748b;
373
+ color: var(--ef-color-text-subtle);
393
374
  }
394
375
 
395
376
  .dropdown-panel {
@@ -398,11 +379,11 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
398
379
  padding: 14px 16px;
399
380
  min-width: 260px;
400
381
  max-width: calc(100vw - 32px);
401
- background: linear-gradient(135deg, rgba(15, 23, 42, 0.98), rgba(30, 41, 59, 0.98));
402
- border: 1px solid rgba(148, 163, 184, 0.3);
382
+ background: var(--ef-color-bg-elevated);
383
+ border: 1px solid var(--ef-color-border);
403
384
  border-radius: 10px;
404
385
  backdrop-filter: blur(12px);
405
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
386
+ box-shadow: 0 8px 32px color-mix(in srgb, var(--ef-color-bg) 50%, transparent);
406
387
  }
407
388
 
408
389
  .dropdown-panel::backdrop {
@@ -431,11 +412,11 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
431
412
  justify-content: space-between;
432
413
  margin-bottom: 12px;
433
414
  padding-bottom: 10px;
434
- border-bottom: 1px solid rgba(148, 163, 184, 0.15);
415
+ border-bottom: 1px solid var(--ef-color-border-subtle);
435
416
  }
436
417
 
437
418
  .dropdown-title {
438
- color: #e2e8f0;
419
+ color: var(--ef-color-text);
439
420
  font-size: 13px;
440
421
  font-weight: 600;
441
422
  }
@@ -443,8 +424,100 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
443
424
  .dropdown-close {
444
425
  background: transparent;
445
426
  border: none;
446
- color: #64748b;
427
+ color: var(--ef-color-text-subtle);
447
428
  cursor: pointer;
429
+ }
430
+
431
+ .dropdown-section {
432
+ background: var(--ef-color-bg-inset);
433
+ border-radius: 8px;
434
+ padding: 12px;
435
+ margin-top: 10px;
436
+ }
437
+
438
+ .dropdown-label {
439
+ color: var(--ef-color-text);
440
+ font-size: 11px;
441
+ font-weight: 600;
442
+ margin-bottom: 6px;
443
+ }
444
+
445
+ .dropdown-description {
446
+ margin-top: 8px;
447
+ color: var(--ef-color-text-subtle);
448
+ font-size: 10px;
449
+ line-height: 1.4;
450
+ }
451
+
452
+ .button-group {
453
+ display: flex;
454
+ gap: 6px;
455
+ margin-top: 6px;
456
+ }
457
+
458
+ .button-group-btn {
459
+ flex: 1;
460
+ padding: 6px 8px;
461
+ border: 1px solid transparent;
462
+ border-radius: 4px;
463
+ font-size: 10px;
464
+ font-weight: 500;
465
+ cursor: pointer;
466
+ transition: all 0.15s ease;
467
+ background: transparent;
468
+ color: var(--ef-color-text-muted);
469
+ }
470
+
471
+ .button-group-btn.active {
472
+ background: var(--ef-color-selected);
473
+ color: var(--ef-color-primary);
474
+ border-color: var(--ef-color-primary-subtle);
475
+ }
476
+
477
+ .checkbox-label {
478
+ display: flex;
479
+ align-items: center;
480
+ gap: 8px;
481
+ cursor: pointer;
482
+ }
483
+
484
+ .checkbox-label input[type="checkbox"] {
485
+ width: 14px;
486
+ height: 14px;
487
+ accent-color: var(--ef-color-primary);
488
+ cursor: pointer;
489
+ }
490
+
491
+ .checkbox-label span {
492
+ color: var(--ef-color-text);
493
+ font-size: 12px;
494
+ font-weight: 500;
495
+ }
496
+
497
+ .dropdown-select {
498
+ width: 100%;
499
+ padding: 6px 8px;
500
+ background: var(--ef-color-bg-inset);
501
+ border: 1px solid var(--ef-color-border);
502
+ border-radius: 6px;
503
+ color: var(--ef-color-text);
504
+ font-size: 11px;
505
+ cursor: pointer;
506
+ }
507
+
508
+ .dropdown-input {
509
+ width: 100%;
510
+ padding: 5px 7px;
511
+ background: var(--ef-color-bg-inset);
512
+ border: 1px solid var(--ef-color-border);
513
+ border-radius: 4px;
514
+ color: var(--ef-color-text);
515
+ font-size: 11px;
516
+ }
517
+
518
+ .dropdown-input:disabled {
519
+ opacity: 0.5;
520
+ cursor: not-allowed;
448
521
  padding: 2px;
449
522
  line-height: 1;
450
523
  font-size: 14px;
@@ -452,7 +525,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
452
525
  }
453
526
 
454
527
  .dropdown-close:hover {
455
- color: #94a3b8;
528
+ color: var(--ef-color-text-muted);
456
529
  }
457
530
  `];
458
531
  }
@@ -461,6 +534,8 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
461
534
  }
462
535
  connectedCallback() {
463
536
  super.connectedCallback();
537
+ this.applyTheme();
538
+ if (!this.hasAttribute("rendering") && typeof window !== "undefined" && "FRAMEGEN_BRIDGE" in window) this.rendering = true;
464
539
  this.addEventListener("transform-changed", this.boundHandleTransformChanged);
465
540
  this.startMotionStateTracking();
466
541
  this.adaptiveTracker = new AdaptiveResolutionTracker({ onScaleChange: (scale) => {
@@ -471,29 +546,20 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
471
546
  this.canvasPreviewResult.setResolutionScale(scale);
472
547
  console.log(`[EFWorkbench] Resolution changed ${(oldScale * 100).toFixed(0)}% → ${(scale * 100).toFixed(0)}% (instant)`);
473
548
  }
474
- } else console.log(`[EFWorkbench] Adaptive scale updated to ${(scale * 100).toFixed(0)}% (no change: atRest=${this.isAtRest}, mode=${this.presentationMode})`);
549
+ }
475
550
  } });
476
- this.updateThumbnailCacheStats();
477
- this.startCacheStatsUpdates();
478
- onThumbnailCacheSettingsChanged(() => {
479
- const newSize = getThumbnailCacheMaxSize();
480
- this.thumbnailCacheMaxSize = newSize;
481
- this.previewSettings = {
482
- ...this.previewSettings,
483
- thumbnailCacheMaxSize: newSize
551
+ this.renderStats = new RenderStats(this.adaptiveTracker);
552
+ if (typeof window !== "undefined") {
553
+ this.systemThemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
554
+ this.systemThemeListener = () => {
555
+ if (this.themeMode === "system") this.applyTheme();
484
556
  };
485
- sessionThumbnailCache.setMaxSize(newSize);
486
- this.updateThumbnailCacheStats();
487
- });
557
+ this.systemThemeMediaQuery.addEventListener("change", this.systemThemeListener);
558
+ }
488
559
  }
489
560
  disconnectedCallback() {
490
561
  super.disconnectedCallback();
491
- if (this.statsStrategy) {
492
- this.statsStrategy.stop();
493
- this.statsStrategy = null;
494
- }
495
- if (this.presentationMode === "clone") this.stopCloneOverlay();
496
- else if (this.presentationMode === "dom") this.stopDomMode();
562
+ if (this.presentationMode === "dom") this.stopDomMode();
497
563
  else if (this.presentationMode === "canvas") this.stopCanvasMode();
498
564
  const timegroup = this.getTimegroup();
499
565
  if (timegroup) {
@@ -501,8 +567,9 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
501
567
  timegroup.style.pointerEvents = "";
502
568
  }
503
569
  this.removeEventListener("transform-changed", this.boundHandleTransformChanged);
570
+ if (this.systemThemeMediaQuery && this.systemThemeListener) this.systemThemeMediaQuery.removeEventListener("change", this.systemThemeListener);
504
571
  this.stopMotionStateTracking();
505
- this.stopCacheStatsUpdates();
572
+ if (typeof this.stopCacheStatsUpdates === "function") this.stopCacheStatsUpdates();
506
573
  if (this.adaptiveTracker) {
507
574
  this.adaptiveTracker.dispose();
508
575
  this.adaptiveTracker = null;
@@ -517,25 +584,29 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
517
584
  requestAnimationFrame(() => {
518
585
  this.restorePreviewPanZoom();
519
586
  });
520
- if (this.presentationMode === "clone") this.initCloneOverlay();
521
- else if (this.presentationMode === "dom") this.initDomMode();
522
- else if (this.presentationMode === "canvas") this.initCanvasMode();
587
+ this.updateComplete.then(() => {
588
+ if (this.presentationMode === "dom") this.initDomMode();
589
+ else if (this.presentationMode === "canvas") this.initCanvasMode();
590
+ });
523
591
  }
524
592
  handleTransformChanged(e) {
525
593
  this.panZoomTransform = e.detail;
526
594
  this.debouncedSavePreviewPanZoom();
527
- if (this.presentationMode === "clone") this.updateCloneTransform();
528
- else if (this.presentationMode === "canvas") {
595
+ if (this.presentationMode === "canvas") {
529
596
  this.updateCanvasTransform();
530
597
  const zoomRatio = e.detail.scale / this.lastCanvasZoom;
531
598
  if (zoomRatio < .75 || zoomRatio > 1.33) {
532
599
  if (this.zoomReinitTimeout !== null) clearTimeout(this.zoomReinitTimeout);
533
600
  this.zoomReinitTimeout = window.setTimeout(() => {
534
601
  this.zoomReinitTimeout = null;
535
- if (this.presentationMode === "canvas") {
602
+ if (this.presentationMode === "canvas" && this.canvasPreviewResult) {
536
603
  this.lastCanvasZoom = this.panZoomTransform.scale;
537
- this.stopCanvasMode();
538
- this.initCanvasMode();
604
+ const timegroup = this.getTimegroup();
605
+ const canvasContainer = this.canvasPreviewRef.value;
606
+ if (timegroup && canvasContainer) {
607
+ const newScale = this.previewResolutionScale === "auto" ? this.getEffectiveResolutionScale(timegroup, canvasContainer) : this.getResolutionScale(timegroup, canvasContainer);
608
+ this.canvasPreviewResult.setResolutionScale(newScale);
609
+ }
539
610
  }
540
611
  }, 500);
541
612
  }
@@ -605,8 +676,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
605
676
  panZoomElement.y = this.panZoomTransform.y;
606
677
  panZoomElement.scale = this.panZoomTransform.scale;
607
678
  }
608
- if (this.presentationMode === "clone") this.updateCloneTransform();
609
- else if (this.presentationMode === "canvas") this.updateCanvasTransform();
679
+ if (this.presentationMode === "canvas") this.updateCanvasTransform();
610
680
  });
611
681
  }
612
682
  } catch (error) {
@@ -660,6 +730,18 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
660
730
  else if (!isInMotion && wasInMotion) this.handleMotionStop();
661
731
  }
662
732
  /**
733
+ * Pause or unpause the canvas overlay components (EFCanvas, SelectionOverlay)
734
+ * to avoid layout-thrashing during playback.
735
+ */
736
+ setOverlaysPaused(paused) {
737
+ const canvasSlot = this.querySelector("[slot='canvas']");
738
+ if (!canvasSlot) return;
739
+ const efCanvas = canvasSlot.querySelector("ef-canvas");
740
+ if (efCanvas && "paused" in efCanvas) efCanvas.paused = paused;
741
+ const overlay = canvasSlot.querySelector("ef-canvas-selection-overlay") ?? this.querySelector("ef-canvas-selection-overlay");
742
+ if (overlay && "paused" in overlay) overlay.paused = paused;
743
+ }
744
+ /**
663
745
  * Called when motion starts (playing or scrubbing began).
664
746
  */
665
747
  handleMotionStart() {
@@ -668,6 +750,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
668
750
  this.restDebounceTimer = null;
669
751
  }
670
752
  this.isAtRest = false;
753
+ this.setOverlaysPaused(true);
671
754
  if (this.previewResolutionScale === "auto" && this.adaptiveTracker) {
672
755
  const timegroup = this.getTimegroup();
673
756
  if (timegroup) {
@@ -678,10 +761,8 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
678
761
  this.adaptiveTracker.initializeAtScale(displayScale);
679
762
  this.currentAdaptiveScale = this.adaptiveTracker.getRecommendedScale();
680
763
  if (this.canvasPreviewResult) this.canvasPreviewResult.setResolutionScale(this.currentAdaptiveScale);
681
- console.log(`[EFWorkbench] Motion started, set resolution to ${(this.currentAdaptiveScale * 100).toFixed(0)}% (displayScale=${(displayScale * 100).toFixed(0)}%)`);
682
764
  }
683
765
  }
684
- console.log(`[EFWorkbench] Motion started (playing=${this.isPlaying}, scrubbing=${this.isScrubbing})`);
685
766
  }
686
767
  /**
687
768
  * Called when motion stops (not playing and not scrubbing).
@@ -699,14 +780,11 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
699
780
  */
700
781
  transitionToRest() {
701
782
  this.isAtRest = true;
702
- console.log("[EFWorkbench] Transitioned to rest state");
783
+ this.setOverlaysPaused(false);
703
784
  if (this.previewResolutionScale === "auto" && this.presentationMode === "canvas") {
704
785
  this.adaptiveTracker?.reset();
705
786
  this.currentAdaptiveScale = 1;
706
- if (this.canvasPreviewResult) {
707
- this.canvasPreviewResult.setResolutionScale(1);
708
- console.log("[EFWorkbench] Set full resolution for rest state (instant)");
709
- }
787
+ if (this.canvasPreviewResult) this.canvasPreviewResult.setResolutionScale(1);
710
788
  }
711
789
  }
712
790
  /**
@@ -742,173 +820,11 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
742
820
  this.renderMode = this.previewSettings.renderMode;
743
821
  this.previewResolutionScale = this.previewSettings.resolutionScale;
744
822
  this.showStats = this.previewSettings.showStats;
745
- this.thumbnailCacheMaxSize = this.previewSettings.thumbnailCacheMaxSize;
746
- this.updateStatsStrategy();
747
- }
748
- /**
749
- * Update or create stats tracking strategy based on current mode and settings.
750
- */
751
- updateStatsStrategy() {
752
- if (this.statsStrategy) {
753
- this.statsStrategy.stop();
754
- this.statsStrategy = null;
755
- }
756
- if (!this.showStats) return;
757
- const timegroup = this.getTimegroup();
758
- if (!timegroup || !this.adaptiveTracker) return;
759
- const compositionWidth = timegroup.offsetWidth || 1920;
760
- const compositionHeight = timegroup.offsetHeight || 1080;
761
- const strategy = createStatsTrackingStrategy(this.presentationMode, {
762
- timegroup,
763
- adaptiveTracker: this.adaptiveTracker,
764
- canvasPreviewResult: this.canvasPreviewResult ?? void 0,
765
- compositionWidth,
766
- compositionHeight,
767
- getResolutionScale: this.canvasPreviewResult?.getResolutionScale,
768
- isAtRest: () => this.isAtRest,
769
- isExporting: () => this.isExporting
770
- });
771
- if (strategy) {
772
- this.statsStrategy = strategy;
773
- strategy.start();
774
- }
775
- }
776
- initCloneOverlay() {
777
- if (this.presentationMode !== "clone") return;
778
- const timegroup = this.getTimegroup();
779
- const cloneContainer = this.cloneOverlayRef.value;
780
- if (!timegroup || !cloneContainer) {
781
- setTimeout(() => this.initCloneOverlay(), 100);
782
- return;
783
- }
784
- this.cloneTimegroup = timegroup;
785
- timegroup.proxyMode = false;
786
- timegroup.updateComplete.then(() => {
787
- if (this.presentationMode !== "clone") return;
788
- this.finishCloneSetup(timegroup, cloneContainer);
789
- });
790
- }
791
- finishCloneSetup(timegroup, cloneContainer) {
792
- timegroup.style.clipPath = "inset(100%)";
793
- timegroup.style.pointerEvents = "none";
794
- cloneContainer.style.display = "block";
795
- this.rebuildClone(timegroup);
796
- this.setupStructureObserver(timegroup);
797
- }
798
- rebuildClone(timegroup) {
799
- if (this.presentationMode !== "clone") return;
800
- const container = this.cloneOverlayRef.value;
801
- if (!container) return;
802
- try {
803
- const { container: previewContainer, refresh } = renderTimegroupPreview(timegroup);
804
- container.innerHTML = "";
805
- previewContainer.classList.add("clone-content");
806
- container.appendChild(previewContainer);
807
- this.cloneRefresh = refresh;
808
- this.cloneRootElement = previewContainer.firstElementChild ?? null;
809
- if (this.cloneRootElement) {
810
- this.cloneRootElement.style.opacity = "1";
811
- this.cloneRootElement.style.clipPath = "none";
812
- this.cloneRootElement.style.position = "relative";
813
- this.cloneRootElement.style.inset = "auto";
814
- this.cloneRootElement.style.top = "0";
815
- this.cloneRootElement.style.right = "auto";
816
- this.cloneRootElement.style.bottom = "auto";
817
- this.cloneRootElement.style.left = "0";
818
- }
819
- this.updateCloneTransform();
820
- this.observeShadowRoots(timegroup);
821
- if (this.cloneAnimationFrame === null) this.startCloneLoop();
822
- } catch (e) {
823
- console.error("Failed to build clone:", e);
824
- }
825
- }
826
- setupStructureObserver(timegroup) {
827
- if (this.structureObserver) this.structureObserver.disconnect();
828
- this.structureObserver = new MutationObserver((mutations) => {
829
- if (this.presentationMode !== "clone") return;
830
- if (mutations.some((m) => m.type === "childList" && (m.addedNodes.length > 0 || m.removedNodes.length > 0)) && !this.rebuildPending) {
831
- this.rebuildPending = true;
832
- requestAnimationFrame(() => {
833
- this.rebuildPending = false;
834
- if (this.presentationMode === "clone") this.rebuildClone(timegroup);
835
- });
836
- }
837
- });
838
- this.structureObserver.observe(timegroup, {
839
- childList: true,
840
- subtree: true
841
- });
842
- this.observeShadowRoots(timegroup);
843
- }
844
- observeShadowRoots(root) {
845
- if (!this.structureObserver) return;
846
- const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);
847
- let node = root;
848
- while (node) {
849
- if (node.shadowRoot) this.structureObserver.observe(node.shadowRoot, {
850
- childList: true,
851
- subtree: true
852
- });
853
- node = walker.nextNode();
854
- }
855
- }
856
- updateCloneTransform() {
857
- if (this.presentationMode !== "clone") return;
858
- const container = this.cloneOverlayRef.value;
859
- if (!container) return;
860
- const cloneContent = container.querySelector(".clone-content");
861
- if (!cloneContent) return;
862
- const { x, y, scale } = this.panZoomTransform;
863
- cloneContent.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;
864
- }
865
- startCloneLoop() {
866
- const loop = () => {
867
- if (this.presentationMode !== "clone" || !this.cloneRefresh) {
868
- this.cloneAnimationFrame = null;
869
- return;
870
- }
871
- if (!this.isExporting) {
872
- this.cloneRefresh();
873
- if (this.cloneRootElement) {
874
- this.cloneRootElement.style.clipPath = "none";
875
- this.cloneRootElement.style.opacity = "1";
876
- this.cloneRootElement.style.position = "relative";
877
- this.cloneRootElement.style.inset = "auto";
878
- this.cloneRootElement.style.top = "0";
879
- this.cloneRootElement.style.right = "auto";
880
- this.cloneRootElement.style.bottom = "auto";
881
- this.cloneRootElement.style.left = "0";
882
- }
883
- }
884
- this.cloneAnimationFrame = requestAnimationFrame(loop);
885
- };
886
- this.cloneAnimationFrame = requestAnimationFrame(loop);
887
- }
888
- stopCloneOverlay() {
889
- if (this.cloneAnimationFrame !== null) {
890
- cancelAnimationFrame(this.cloneAnimationFrame);
891
- this.cloneAnimationFrame = null;
892
- }
893
- if (this.structureObserver) {
894
- this.structureObserver.disconnect();
895
- this.structureObserver = null;
896
- }
897
- this.cloneRefresh = null;
898
- this.cloneRootElement = null;
899
- this.cloneTimegroup = null;
900
- this.rebuildPending = false;
901
- const container = this.cloneOverlayRef.value;
902
- if (container) {
903
- container.innerHTML = "";
904
- container.style.display = "none";
905
- }
906
823
  }
907
824
  async handlePresentationModeChange(mode) {
908
825
  if (mode === this.presentationMode) return;
909
826
  const previousMode = this.presentationMode;
910
- if (previousMode === "clone") this.stopCloneOverlay();
911
- else if (previousMode === "dom") this.stopDomMode();
827
+ if (previousMode === "dom") this.stopDomMode();
912
828
  else if (previousMode === "canvas") this.stopCanvasMode();
913
829
  setPreviewPresentationMode(mode);
914
830
  this.previewSettings = {
@@ -916,8 +832,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
916
832
  presentationMode: mode
917
833
  };
918
834
  await this.updateComplete;
919
- if (mode === "clone") this.initCloneOverlay();
920
- else if (mode === "dom") this.initDomMode();
835
+ if (mode === "dom") this.initDomMode();
921
836
  else if (mode === "canvas") this.initCanvasMode();
922
837
  }
923
838
  initDomMode() {
@@ -928,15 +843,26 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
928
843
  return;
929
844
  }
930
845
  const fitScale = this.querySelector("[slot='canvas']");
931
- if (fitScale?.removeScale && fitScale?.paused !== void 0) {
846
+ if (!!(fitScale?.removeScale && fitScale?.paused !== void 0)) {
932
847
  fitScale.paused = true;
933
848
  fitScale.removeScale();
934
849
  }
935
850
  timegroup.proxyMode = false;
936
851
  timegroup.style.clipPath = "";
937
852
  timegroup.style.pointerEvents = "";
853
+ if (this.showStats && this.adaptiveTracker) {
854
+ this.domStatsStrategy = new DomStatsStrategy({
855
+ timegroup,
856
+ adaptiveTracker: this.adaptiveTracker
857
+ });
858
+ this.domStatsStrategy.start();
859
+ }
938
860
  }
939
861
  stopDomMode() {
862
+ if (this.domStatsStrategy) {
863
+ this.domStatsStrategy.stop();
864
+ this.domStatsStrategy = null;
865
+ }
940
866
  const timegroup = this.getTimegroup();
941
867
  if (timegroup) {
942
868
  timegroup.style.clipPath = "inset(100%)";
@@ -944,20 +870,16 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
944
870
  }
945
871
  const fitScale = this.querySelector("[slot='canvas']");
946
872
  if (fitScale?.paused !== void 0) fitScale.paused = false;
947
- if (this.statsStrategy) {
948
- this.statsStrategy.stop();
949
- this.statsStrategy = null;
950
- }
951
873
  }
952
874
  /**
953
875
  * Get the resolution scale for canvas rendering (for fixed scale modes).
954
- *
876
+ *
955
877
  * Logic:
956
878
  * - Get actual displayed size from getBoundingClientRect()
957
879
  * - For "Full": render at displayed size (1:1 pixel mapping)
958
880
  * - For other settings: render at that % of displayed size
959
881
  * - Never exceed composition size (100%)
960
- *
882
+ *
961
883
  * Note: For "auto" mode, use getEffectiveResolutionScale() instead.
962
884
  */
963
885
  getResolutionScale(timegroup, _canvasContainer) {
@@ -969,18 +891,9 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
969
891
  const displayedHeight = rect.height;
970
892
  const displayScale = Math.min(displayedWidth / compositionWidth, displayedHeight / compositionHeight);
971
893
  const targetScale = this.previewResolutionScale === 1 ? displayScale : Math.min(displayScale, this.previewResolutionScale);
972
- const finalScale = Math.max(.1, Math.min(1, targetScale));
973
- const renderWidth = Math.floor(compositionWidth * finalScale);
974
- const renderHeight = Math.floor(compositionHeight * finalScale);
975
- console.log(`[EFWorkbench] Resolution scale:
976
- Composition (offsetWidth×offsetHeight): ${compositionWidth}×${compositionHeight}
977
- Displayed (boundingRect): ${Math.round(displayedWidth)}×${Math.round(displayedHeight)}
978
- Display scale: ${(displayScale * 100).toFixed(1)}%
979
- Setting: ${this.previewResolutionScale === 1 ? "Full" : `${Math.round(this.previewResolutionScale * 100)}%`}
980
- Final: ${(finalScale * 100).toFixed(1)}% → ${renderWidth}×${renderHeight}`);
981
- return finalScale;
894
+ return Math.max(.1, Math.min(1, targetScale));
982
895
  }
983
- initCanvasMode() {
896
+ async initCanvasMode() {
984
897
  if (this.presentationMode !== "canvas") return;
985
898
  const timegroup = this.getTimegroup();
986
899
  const canvasContainer = this.canvasPreviewRef.value;
@@ -989,20 +902,23 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
989
902
  return;
990
903
  }
991
904
  timegroup.proxyMode = false;
992
- timegroup.style.clipPath = "inset(100%)";
905
+ timegroup.canvasPreviewActive = true;
906
+ timegroup.style.clipPath = "";
993
907
  timegroup.style.pointerEvents = "none";
994
908
  canvasContainer.style.display = "block";
995
909
  const initialResolutionScale = this.previewResolutionScale === "auto" ? this.getEffectiveResolutionScale(timegroup, canvasContainer) : this.getResolutionScale(timegroup, canvasContainer);
996
910
  this.lastCanvasZoom = this.panZoomTransform.scale;
997
- timegroup.offsetWidth;
998
- timegroup.offsetHeight;
999
911
  try {
912
+ await timegroup.seekTask.taskComplete;
913
+ if (timegroup.playbackController) await new Promise((resolve) => setTimeout(resolve, 0));
914
+ updateAnimations(timegroup);
915
+ const { renderTimegroupToCanvas } = await import("../preview/renderTimegroupToCanvas.js");
1000
916
  const result = renderTimegroupToCanvas(timegroup, {
1001
917
  scale: 1,
1002
918
  resolutionScale: initialResolutionScale
1003
919
  });
1004
920
  this.canvasPreviewResult = result;
1005
- const { container, canvas, refresh, getResolutionScale } = result;
921
+ const { container, canvas, refresh } = result;
1006
922
  canvas.classList.add("clone-content");
1007
923
  canvasContainer.innerHTML = "";
1008
924
  canvasContainer.appendChild(container);
@@ -1010,7 +926,10 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1010
926
  const loop = async () => {
1011
927
  if (this.presentationMode !== "canvas") return;
1012
928
  if (!this.isExporting) try {
929
+ const renderStart = performance.now();
1013
930
  await refresh();
931
+ const renderTime = performance.now() - renderStart;
932
+ if (this.renderStats) this.renderStats.recordFrame(renderTime, performance.now(), this.isAtRest);
1014
933
  this.updateCanvasTransform();
1015
934
  } catch (e) {
1016
935
  console.error("Canvas refresh failed:", e);
@@ -1031,11 +950,10 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1031
950
  clearTimeout(this.zoomReinitTimeout);
1032
951
  this.zoomReinitTimeout = null;
1033
952
  }
953
+ this.canvasPreviewResult?.dispose();
1034
954
  this.canvasPreviewResult = null;
1035
- if (this.statsStrategy) {
1036
- this.statsStrategy.stop();
1037
- this.statsStrategy = null;
1038
- }
955
+ const timegroup = this.getTimegroup();
956
+ if (timegroup) timegroup.canvasPreviewActive = false;
1039
957
  const container = this.canvasPreviewRef.value;
1040
958
  if (container) {
1041
959
  container.innerHTML = "";
@@ -1051,12 +969,12 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1051
969
  const { x, y, scale } = this.panZoomTransform;
1052
970
  canvas.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;
1053
971
  }
1054
- initCanvasRenderer() {
972
+ async initCanvasRenderer() {
1055
973
  const timegroup = this.getTimegroup();
1056
974
  if (!timegroup) return null;
1057
975
  try {
976
+ const { renderTimegroupToCanvas } = await import("../preview/renderTimegroupToCanvas.js");
1058
977
  const { canvas, refresh } = renderTimegroupToCanvas(timegroup, 1);
1059
- this.canvasRefresh = refresh;
1060
978
  return {
1061
979
  canvas,
1062
980
  refresh
@@ -1082,6 +1000,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1082
1000
  this.exportProgress = null;
1083
1001
  this.exportStatus = "rendering";
1084
1002
  try {
1003
+ const { renderTimegroupToVideo } = await import("../preview/renderTimegroupToVideo.js");
1085
1004
  await renderTimegroupToVideo(timegroup, {
1086
1005
  ...options,
1087
1006
  signal: this.exportAbortController.signal,
@@ -1097,6 +1016,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1097
1016
  this.exportAbortController = null;
1098
1017
  }, 2e3);
1099
1018
  } catch (e) {
1019
+ const { RenderCancelledError } = await import("../preview/renderTimegroupToVideo.js");
1100
1020
  if (e instanceof RenderCancelledError) {
1101
1021
  console.log("Export cancelled by user");
1102
1022
  this.exportStatus = "cancelled";
@@ -1188,73 +1108,61 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1188
1108
  renderMode: mode
1189
1109
  };
1190
1110
  }
1191
- handleResolutionScaleChange(scale) {
1192
- console.log(`[EFWorkbench] Resolution scale changed to ${scale}, presentationMode=${this.presentationMode}`);
1193
- setPreviewResolutionScale(scale);
1111
+ handleShowStatsToggle(enabled) {
1112
+ setShowStats(enabled);
1194
1113
  this.previewSettings = {
1195
1114
  ...this.previewSettings,
1196
- resolutionScale: scale
1115
+ showStats: enabled
1197
1116
  };
1198
- if (scale === "auto") {
1199
- this.adaptiveTracker?.reset();
1200
- this.currentAdaptiveScale = 1;
1201
- }
1202
- if (this.presentationMode === "canvas") {
1203
- console.log("[EFWorkbench] Reinitializing canvas mode with new resolution scale");
1204
- this.stopCanvasMode();
1205
- this.initCanvasMode();
1206
- }
1207
- }
1208
- async updateThumbnailCacheStats() {
1209
- try {
1210
- this.thumbnailCacheStats = await sessionThumbnailCache.getStats();
1211
- } catch (error) {
1212
- console.warn("Failed to update thumbnail cache stats:", error);
1213
- }
1214
1117
  }
1215
- startCacheStatsUpdates() {
1216
- this.cacheStatsUpdateInterval = window.setInterval(() => {
1217
- this.updateThumbnailCacheStats();
1218
- }, 2e3);
1219
- }
1220
- stopCacheStatsUpdates() {
1221
- if (this.cacheStatsUpdateInterval !== null) {
1222
- clearInterval(this.cacheStatsUpdateInterval);
1223
- this.cacheStatsUpdateInterval = null;
1224
- }
1225
- }
1226
- handleThumbnailCacheMaxSizeChange(size) {
1227
- setThumbnailCacheMaxSize(size);
1118
+ handleShowThumbnailTimestampsToggle(enabled) {
1119
+ setShowThumbnailTimestamps(enabled);
1228
1120
  this.previewSettings = {
1229
1121
  ...this.previewSettings,
1230
- thumbnailCacheMaxSize: size
1122
+ showThumbnailTimestamps: enabled
1231
1123
  };
1232
- sessionThumbnailCache.setMaxSize(size);
1233
- this.updateThumbnailCacheStats();
1234
1124
  }
1235
- async handleClearThumbnailCache() {
1236
- await sessionThumbnailCache.clear();
1237
- await this.updateThumbnailCacheStats();
1238
- }
1239
- formatBytes(bytes) {
1240
- if (bytes < 1024) return `${bytes} B`;
1241
- else if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1242
- else return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1125
+ /**
1126
+ * Get initial theme from localStorage or default to 'dark'
1127
+ */
1128
+ getInitialTheme() {
1129
+ if (typeof window === "undefined") return "dark";
1130
+ const stored = localStorage.getItem("ef-theme");
1131
+ if (stored === "light" || stored === "dark" || stored === "system") return stored;
1132
+ return "dark";
1243
1133
  }
1244
- handleDebugThumbnailTimestampsToggle(enabled) {
1245
- this.debugThumbnailTimestamps = enabled;
1246
- this.dispatchEvent(new CustomEvent("ef-debug-thumbnail-timestamps-changed", {
1247
- detail: { enabled },
1248
- bubbles: true,
1249
- composed: true
1250
- }));
1134
+ /**
1135
+ * Cycle through theme modes: light → dark → system → light
1136
+ */
1137
+ handleThemeToggle() {
1138
+ const nextTheme = this.themeMode === "light" ? "dark" : this.themeMode === "dark" ? "system" : "light";
1139
+ this.themeMode = nextTheme;
1140
+ localStorage.setItem("ef-theme", nextTheme);
1141
+ this.applyTheme();
1251
1142
  }
1252
- handleShowStatsToggle(enabled) {
1253
- setShowStats(enabled);
1254
- this.previewSettings = {
1255
- ...this.previewSettings,
1256
- showStats: enabled
1257
- };
1143
+ /**
1144
+ * Apply the current theme to the document and host element
1145
+ */
1146
+ applyTheme() {
1147
+ const root = document.documentElement;
1148
+ let shouldBeDark = false;
1149
+ if (this.themeMode === "light") shouldBeDark = false;
1150
+ else if (this.themeMode === "dark") shouldBeDark = true;
1151
+ else shouldBeDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
1152
+ if (shouldBeDark) {
1153
+ root.classList.add("dark");
1154
+ root.classList.remove("light");
1155
+ } else {
1156
+ root.classList.add("light");
1157
+ root.classList.remove("dark");
1158
+ }
1159
+ if (shouldBeDark) {
1160
+ this.classList.add("dark");
1161
+ this.classList.remove("light");
1162
+ } else {
1163
+ this.classList.add("light");
1164
+ this.classList.remove("dark");
1165
+ }
1258
1166
  }
1259
1167
  /**
1260
1168
  * Reset and fit the preview to show all content centered.
@@ -1280,77 +1188,33 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1280
1188
 
1281
1189
  <!-- Presentation Mode Setting -->
1282
1190
  <div style="
1283
- background: rgba(51, 65, 85, 0.4);
1191
+ background: var(--ef-color-bg-inset);
1284
1192
  border-radius: 8px;
1285
1193
  padding: 12px;
1286
1194
  margin-bottom: 10px;
1287
1195
  ">
1288
- <div style="color: #e2e8f0; font-size: 12px; font-weight: 500; margin-bottom: 10px;">Presentation Mode</div>
1196
+ <div class="dropdown-label" style="margin-bottom: 10px;">Presentation Mode</div>
1289
1197
 
1290
- <div style="display: flex; gap: 4px; background: rgba(30, 41, 59, 0.6); border-radius: 6px; padding: 3px;">
1291
- <button
1292
- @click=${() => this.handlePresentationModeChange("clone")}
1293
- style="
1294
- flex: 1;
1295
- padding: 6px 10px;
1296
- border: none;
1297
- border-radius: 4px;
1298
- font-size: 11px;
1299
- font-weight: 500;
1300
- cursor: pointer;
1301
- transition: all 0.15s ease;
1302
- background: ${this.presentationMode === "clone" ? "rgba(59, 130, 246, 0.3)" : "transparent"};
1303
- color: ${this.presentationMode === "clone" ? "#60a5fa" : "#94a3b8"};
1304
- border: 1px solid ${this.presentationMode === "clone" ? "rgba(59, 130, 246, 0.4)" : "transparent"};
1305
- "
1306
- >Clone</button>
1198
+ <div class="button-group">
1307
1199
  <button
1308
1200
  @click=${() => this.handlePresentationModeChange("dom")}
1309
- style="
1310
- flex: 1;
1311
- padding: 6px 10px;
1312
- border: none;
1313
- border-radius: 4px;
1314
- font-size: 11px;
1315
- font-weight: 500;
1316
- cursor: pointer;
1317
- transition: all 0.15s ease;
1318
- background: ${this.presentationMode === "dom" ? "rgba(34, 197, 94, 0.3)" : "transparent"};
1319
- color: ${this.presentationMode === "dom" ? "#4ade80" : "#94a3b8"};
1320
- border: 1px solid ${this.presentationMode === "dom" ? "rgba(34, 197, 94, 0.4)" : "transparent"};
1321
- "
1201
+ class="button-group-btn ${this.presentationMode === "dom" ? "active" : ""}"
1322
1202
  >DOM</button>
1323
1203
  <button
1324
1204
  @click=${() => this.handlePresentationModeChange("canvas")}
1325
- style="
1326
- flex: 1;
1327
- padding: 6px 10px;
1328
- border: none;
1329
- border-radius: 4px;
1330
- font-size: 11px;
1331
- font-weight: 500;
1332
- cursor: pointer;
1333
- transition: all 0.15s ease;
1334
- background: ${this.presentationMode === "canvas" ? "rgba(168, 85, 247, 0.3)" : "transparent"};
1335
- color: ${this.presentationMode === "canvas" ? "#c084fc" : "#94a3b8"};
1336
- border: 1px solid ${this.presentationMode === "canvas" ? "rgba(168, 85, 247, 0.4)" : "transparent"};
1337
- "
1205
+ class="button-group-btn ${this.presentationMode === "canvas" ? "active" : ""}"
1338
1206
  >Canvas</button>
1339
1207
  </div>
1340
1208
 
1341
- <div style="margin-top: 8px; color: #64748b; font-size: 10px; line-height: 1.4;">
1342
- ${this.presentationMode === "clone" ? "Default. Shows a styled clone synced from the hidden original." : this.presentationMode === "dom" ? "Shows the real timegroup DOM directly." : "Renders to canvas each frame (experimental)."}
1209
+ <div class="dropdown-description">
1210
+ ${this.presentationMode === "dom" ? "Default. Shows the real timegroup DOM directly." : "Renders to canvas each frame."}
1343
1211
  </div>
1344
1212
  </div>
1345
1213
 
1346
1214
  <!-- Render Mode Setting -->
1347
- <div style="
1348
- background: rgba(51, 65, 85, 0.4);
1349
- border-radius: 8px;
1350
- padding: 12px;
1351
- ">
1215
+ <div class="dropdown-section">
1352
1216
  <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;">
1353
- <span style="color: #e2e8f0; font-size: 12px; font-weight: 500;">Render Mode</span>
1217
+ <span class="dropdown-label">Render Mode</span>
1354
1218
  ${isAvailable ? html`
1355
1219
  <div style="display: flex; align-items: center; gap: 5px;">
1356
1220
  <span style="
@@ -1358,321 +1222,67 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1358
1222
  width: 7px;
1359
1223
  height: 7px;
1360
1224
  border-radius: 50%;
1361
- background: #4ade80;
1225
+ background: var(--ef-color-success);
1362
1226
  "></span>
1363
- <span style="color: #4ade80; font-size: 10px; font-weight: 500;">
1227
+ <span style="color: var(--ef-color-success); font-size: 10px; font-weight: 500;">
1364
1228
  Native Available
1365
1229
  </span>
1366
1230
  </div>
1367
1231
  ` : ""}
1368
1232
  </div>
1369
1233
 
1370
- <div style="display: flex; gap: 4px; background: rgba(30, 41, 59, 0.6); border-radius: 6px; padding: 3px;">
1234
+ <div class="button-group">
1371
1235
  <button
1372
1236
  @click=${() => this.handleRenderModeChange("foreignObject")}
1373
- style="
1374
- flex: 1;
1375
- padding: 6px 8px;
1376
- border: none;
1377
- border-radius: 4px;
1378
- font-size: 10px;
1379
- font-weight: 500;
1380
- cursor: pointer;
1381
- transition: all 0.15s ease;
1382
- background: ${this.renderMode === "foreignObject" ? "rgba(59, 130, 246, 0.3)" : "transparent"};
1383
- color: ${this.renderMode === "foreignObject" ? "#60a5fa" : "#94a3b8"};
1384
- border: 1px solid ${this.renderMode === "foreignObject" ? "rgba(59, 130, 246, 0.4)" : "transparent"};
1385
- "
1237
+ class="button-group-btn ${this.renderMode === "foreignObject" ? "active" : ""}"
1386
1238
  >foreignObject</button>
1387
1239
  <button
1388
1240
  @click=${() => this.handleRenderModeChange("native")}
1389
1241
  ?disabled=${!isAvailable}
1242
+ class="button-group-btn ${this.renderMode === "native" ? "active" : ""}"
1390
1243
  style="
1391
- flex: 1;
1392
- padding: 6px 8px;
1393
- border: none;
1394
- border-radius: 4px;
1395
- font-size: 10px;
1396
- font-weight: 500;
1397
1244
  cursor: ${isAvailable ? "pointer" : "not-allowed"};
1398
- transition: all 0.15s ease;
1399
- background: ${this.renderMode === "native" ? "rgba(34, 197, 94, 0.3)" : "transparent"};
1400
- color: ${this.renderMode === "native" ? "#4ade80" : isAvailable ? "#94a3b8" : "#64748b"};
1401
- border: 1px solid ${this.renderMode === "native" ? "rgba(34, 197, 94, 0.4)" : "transparent"};
1402
1245
  opacity: ${isAvailable ? "1" : "0.5"};
1403
1246
  "
1404
1247
  >native</button>
1405
1248
  </div>
1406
1249
 
1407
- <div style="margin-top: 8px; color: #64748b; font-size: 10px; line-height: 1.4;">
1250
+ <div class="dropdown-description">
1408
1251
  ${this.renderMode === "foreignObject" ? "SVG foreignObject serialization. Works everywhere but slower." : "Chrome's drawElementImage API. Fastest, requires chrome://flags/#canvas-draw-element."}
1409
1252
  </div>
1410
1253
  </div>
1411
1254
 
1412
- <!-- Preview Resolution Setting -->
1413
- <div style="
1414
- background: rgba(51, 65, 85, 0.4);
1415
- border-radius: 8px;
1416
- padding: 12px;
1417
- margin-top: 10px;
1418
- ">
1419
- <div style="color: #e2e8f0; font-size: 12px; font-weight: 500; margin-bottom: 10px;">Preview Resolution</div>
1420
-
1421
- <div style="display: flex; gap: 4px; background: rgba(30, 41, 59, 0.6); border-radius: 6px; padding: 3px;">
1422
- <button
1423
- @click=${() => this.handleResolutionScaleChange("auto")}
1424
- style="
1425
- flex: 1;
1426
- padding: 6px 8px;
1427
- border: none;
1428
- border-radius: 4px;
1429
- font-size: 10px;
1430
- font-weight: 500;
1431
- cursor: pointer;
1432
- transition: all 0.15s ease;
1433
- background: ${this.previewResolutionScale === "auto" ? "rgba(34, 197, 94, 0.3)" : "transparent"};
1434
- color: ${this.previewResolutionScale === "auto" ? "#4ade80" : "#94a3b8"};
1435
- border: 1px solid ${this.previewResolutionScale === "auto" ? "rgba(34, 197, 94, 0.4)" : "transparent"};
1436
- "
1437
- >Auto</button>
1438
- <button
1439
- @click=${() => this.handleResolutionScaleChange(1)}
1440
- style="
1441
- flex: 1;
1442
- padding: 6px 8px;
1443
- border: none;
1444
- border-radius: 4px;
1445
- font-size: 10px;
1446
- font-weight: 500;
1447
- cursor: pointer;
1448
- transition: all 0.15s ease;
1449
- background: ${this.previewResolutionScale === 1 ? "rgba(59, 130, 246, 0.3)" : "transparent"};
1450
- color: ${this.previewResolutionScale === 1 ? "#60a5fa" : "#94a3b8"};
1451
- border: 1px solid ${this.previewResolutionScale === 1 ? "rgba(59, 130, 246, 0.4)" : "transparent"};
1452
- "
1453
- >Full</button>
1454
- <button
1455
- @click=${() => this.handleResolutionScaleChange(.75)}
1456
- style="
1457
- flex: 1;
1458
- padding: 6px 8px;
1459
- border: none;
1460
- border-radius: 4px;
1461
- font-size: 10px;
1462
- font-weight: 500;
1463
- cursor: pointer;
1464
- transition: all 0.15s ease;
1465
- background: ${this.previewResolutionScale === .75 ? "rgba(59, 130, 246, 0.3)" : "transparent"};
1466
- color: ${this.previewResolutionScale === .75 ? "#60a5fa" : "#94a3b8"};
1467
- border: 1px solid ${this.previewResolutionScale === .75 ? "rgba(59, 130, 246, 0.4)" : "transparent"};
1468
- "
1469
- >3/4</button>
1470
- <button
1471
- @click=${() => this.handleResolutionScaleChange(.5)}
1472
- style="
1473
- flex: 1;
1474
- padding: 6px 8px;
1475
- border: none;
1476
- border-radius: 4px;
1477
- font-size: 10px;
1478
- font-weight: 500;
1479
- cursor: pointer;
1480
- transition: all 0.15s ease;
1481
- background: ${this.previewResolutionScale === .5 ? "rgba(59, 130, 246, 0.3)" : "transparent"};
1482
- color: ${this.previewResolutionScale === .5 ? "#60a5fa" : "#94a3b8"};
1483
- border: 1px solid ${this.previewResolutionScale === .5 ? "rgba(59, 130, 246, 0.4)" : "transparent"};
1484
- "
1485
- >1/2</button>
1486
- <button
1487
- @click=${() => this.handleResolutionScaleChange(.25)}
1488
- style="
1489
- flex: 1;
1490
- padding: 6px 8px;
1491
- border: none;
1492
- border-radius: 4px;
1493
- font-size: 10px;
1494
- font-weight: 500;
1495
- cursor: pointer;
1496
- transition: all 0.15s ease;
1497
- background: ${this.previewResolutionScale === .25 ? "rgba(59, 130, 246, 0.3)" : "transparent"};
1498
- color: ${this.previewResolutionScale === .25 ? "#60a5fa" : "#94a3b8"};
1499
- border: 1px solid ${this.previewResolutionScale === .25 ? "rgba(59, 130, 246, 0.4)" : "transparent"};
1500
- "
1501
- >1/4</button>
1502
- </div>
1503
-
1504
- <div style="margin-top: 8px; color: #64748b; font-size: 10px; line-height: 1.4;">
1505
- ${this.previewResolutionScale === "auto" ? `Auto: Full resolution at rest, adaptive during playback/scrub.${!this.isAtRest ? ` Currently: ${Math.round(this.currentAdaptiveScale * 100)}%` : ""}` : this.previewResolutionScale === 1 ? "Full: Matches display resolution (1:1 pixels, adapts to zoom)." : `${Math.round(this.previewResolutionScale * 100)}%: Reduced quality for faster rendering.`}
1506
- Canvas mode only.
1507
- </div>
1508
- </div>
1255
+ <!-- This section was already updated earlier in the dropdown-section refactor -->
1509
1256
 
1510
1257
  <!-- Show Performance Stats Setting -->
1511
- <div style="
1512
- background: rgba(51, 65, 85, 0.4);
1513
- border-radius: 8px;
1514
- padding: 12px;
1515
- margin-top: 10px;
1516
- ">
1517
- <label style="
1518
- display: flex;
1519
- align-items: center;
1520
- gap: 8px;
1521
- cursor: pointer;
1522
- ">
1258
+ <div class="dropdown-section">
1259
+ <label class="checkbox-label">
1523
1260
  <input
1524
1261
  type="checkbox"
1525
1262
  ?checked=${this.showStats}
1526
1263
  @change=${(e) => this.handleShowStatsToggle(e.target.checked)}
1527
- style="
1528
- width: 14px;
1529
- height: 14px;
1530
- accent-color: #3b82f6;
1531
- cursor: pointer;
1532
- "
1533
1264
  />
1534
- <span style="color: #e2e8f0; font-size: 12px; font-weight: 500;">Show Performance Stats</span>
1265
+ <span>Show Performance Stats</span>
1535
1266
  </label>
1536
1267
 
1537
- <div style="
1538
- margin-top: 8px;
1539
- color: #64748b;
1540
- font-size: 10px;
1541
- line-height: 1.4;
1542
- ">
1268
+ <div class="dropdown-description">
1543
1269
  Display FPS, CPU pressure, and performance metrics overlay.
1544
1270
  </div>
1545
1271
  </div>
1546
1272
 
1547
- <!-- Thumbnail Cache Setting -->
1548
- <div
1549
- data-testid="thumbnail-cache-section"
1550
- style="
1551
- background: rgba(51, 65, 85, 0.4);
1552
- border-radius: 8px;
1553
- padding: 12px;
1554
- margin-top: 10px;
1555
- "
1556
- >
1557
- <div style="color: #e2e8f0; font-size: 12px; font-weight: 500; margin-bottom: 10px;">Thumbnail Cache</div>
1558
-
1559
- <!-- Cache Size Input -->
1560
- <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
1561
- <label style="color: #94a3b8; font-size: 11px; min-width: 80px;">Max Size:</label>
1562
- <input
1563
- data-testid="thumbnail-cache-size"
1564
- type="number"
1565
- min="100"
1566
- max="5000"
1567
- step="100"
1568
- .value=${String(this.thumbnailCacheMaxSize)}
1569
- @change=${(e) => {
1570
- const value = parseInt(e.target.value, 10);
1571
- if (!isNaN(value) && value >= 100 && value <= 5e3) this.handleThumbnailCacheMaxSizeChange(value);
1572
- }}
1573
- style="
1574
- flex: 1;
1575
- padding: 4px 8px;
1576
- background: rgba(30, 41, 59, 0.6);
1577
- border: 1px solid rgba(148, 163, 184, 0.2);
1578
- border-radius: 4px;
1579
- color: #e2e8f0;
1580
- font-size: 11px;
1581
- "
1582
- />
1583
- <span style="color: #64748b; font-size: 10px;">items</span>
1584
- </div>
1585
-
1586
- <!-- Cache Statistics -->
1587
- ${this.thumbnailCacheStats ? html`
1588
- <div
1589
- data-testid="thumbnail-cache-stats"
1590
- style="
1591
- background: rgba(30, 41, 59, 0.6);
1592
- border-radius: 6px;
1593
- padding: 8px;
1594
- margin-bottom: 10px;
1595
- font-size: 10px;
1596
- color: #94a3b8;
1597
- "
1598
- >
1599
- <div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
1600
- <span>Items:</span>
1601
- <span style="color: #e2e8f0; font-weight: 500;">${this.thumbnailCacheStats.itemCount} / ${this.thumbnailCacheStats.maxSize}</span>
1602
- </div>
1603
- <div style="display: flex; justify-content: space-between;">
1604
- <span>Size:</span>
1605
- <span style="color: #e2e8f0; font-weight: 500;">${this.formatBytes(this.thumbnailCacheStats.totalSizeBytes)}</span>
1606
- </div>
1607
- </div>
1608
- ` : ""}
1609
-
1610
- <!-- Clear Cache Button -->
1611
- <button
1612
- data-testid="thumbnail-cache-clear"
1613
- @click=${() => this.handleClearThumbnailCache()}
1614
- style="
1615
- width: 100%;
1616
- padding: 6px 10px;
1617
- background: rgba(239, 68, 68, 0.2);
1618
- border: 1px solid rgba(239, 68, 68, 0.4);
1619
- border-radius: 4px;
1620
- color: #f87171;
1621
- font-size: 11px;
1622
- font-weight: 500;
1623
- cursor: pointer;
1624
- transition: all 0.15s ease;
1625
- "
1626
- onmouseover="this.style.background='rgba(239, 68, 68, 0.3)'"
1627
- onmouseout="this.style.background='rgba(239, 68, 68, 0.2)'"
1628
- >
1629
- Clear Cache
1630
- </button>
1631
-
1632
- <div style="
1633
- margin-top: 8px;
1634
- color: #64748b;
1635
- font-size: 10px;
1636
- line-height: 1.4;
1637
- ">
1638
- Persistent cache survives page reloads. Stored in IndexedDB.
1639
- </div>
1640
- </div>
1641
-
1642
- <!-- Debug Thumbnails Setting -->
1643
- <div style="
1644
- background: rgba(51, 65, 85, 0.4);
1645
- border-radius: 8px;
1646
- padding: 12px;
1647
- margin-top: 10px;
1648
- ">
1649
- <label style="
1650
- display: flex;
1651
- align-items: center;
1652
- gap: 8px;
1653
- cursor: pointer;
1654
- ">
1273
+ <!-- Thumbnail Timestamps -->
1274
+ <div class="dropdown-section">
1275
+ <label class="checkbox-label">
1655
1276
  <input
1656
1277
  type="checkbox"
1657
- ?checked=${this.debugThumbnailTimestamps}
1658
- @change=${(e) => this.handleDebugThumbnailTimestampsToggle(e.target.checked)}
1659
- style="
1660
- width: 14px;
1661
- height: 14px;
1662
- accent-color: #f59e0b;
1663
- cursor: pointer;
1664
- "
1278
+ ?checked=${this.previewSettings.showThumbnailTimestamps}
1279
+ @change=${(e) => this.handleShowThumbnailTimestampsToggle(e.target.checked)}
1665
1280
  />
1666
- <span style="color: #e2e8f0; font-size: 12px; font-weight: 500;">Show Thumbnail Timestamps</span>
1281
+ <span>Show Thumbnail Timestamps</span>
1667
1282
  </label>
1668
1283
 
1669
- <div style="
1670
- margin-top: 8px;
1671
- color: #64748b;
1672
- font-size: 10px;
1673
- line-height: 1.4;
1674
- ">
1675
- Overlays capture timestamps on timeline thumbnails for debugging.
1284
+ <div class="dropdown-description">
1285
+ Display timestamp overlay on timeline thumbnails for debugging.
1676
1286
  </div>
1677
1287
  </div>
1678
1288
  </div>
@@ -1694,18 +1304,9 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1694
1304
 
1695
1305
  <!-- Scale -->
1696
1306
  <div style="margin-bottom: 10px;">
1697
- <label style="display: block; color: #94a3b8; font-size: 11px; margin-bottom: 4px;">Scale</label>
1307
+ <label class="dropdown-label" style="display: block; margin-bottom: 4px;">Scale</label>
1698
1308
  <select
1699
- style="
1700
- width: 100%;
1701
- padding: 6px 10px;
1702
- background: rgba(51, 65, 85, 0.8);
1703
- border: 1px solid rgba(148, 163, 184, 0.2);
1704
- border-radius: 5px;
1705
- color: #e2e8f0;
1706
- font-size: 12px;
1707
- cursor: pointer;
1708
- "
1309
+ class="dropdown-select"
1709
1310
  .value=${String(this.exportOptions.scale)}
1710
1311
  @change=${(e) => this.updateExportOption("scale", Number(e.target.value))}
1711
1312
  >
@@ -1718,77 +1319,59 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1718
1319
 
1719
1320
  <!-- Audio -->
1720
1321
  <div style="margin-bottom: 10px;">
1721
- <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
1322
+ <label class="checkbox-label">
1722
1323
  <input
1723
1324
  type="checkbox"
1724
1325
  ?checked=${this.exportOptions.includeAudio}
1725
1326
  @change=${(e) => this.updateExportOption("includeAudio", e.target.checked)}
1726
- style="width: 14px; height: 14px; accent-color: #3b82f6;"
1727
1327
  />
1728
- <span style="color: #e2e8f0; font-size: 12px;">Include Audio</span>
1328
+ <span>Include Audio</span>
1729
1329
  </label>
1730
1330
  </div>
1731
1331
 
1732
1332
  <!-- In/Out Range -->
1733
1333
  <div style="margin-bottom: 12px;">
1734
- <label style="display: flex; align-items: center; gap: 8px; cursor: pointer; margin-bottom: 6px;">
1334
+ <label class="checkbox-label" style="margin-bottom: 6px;">
1735
1335
  <input
1736
1336
  type="checkbox"
1737
1337
  ?checked=${this.exportOptions.useInOut}
1738
1338
  @change=${(e) => this.updateExportOption("useInOut", e.target.checked)}
1739
- style="width: 14px; height: 14px; accent-color: #3b82f6;"
1740
1339
  />
1741
- <span style="color: #e2e8f0; font-size: 12px;">Custom Range</span>
1340
+ <span>Custom Range</span>
1742
1341
  </label>
1743
1342
 
1744
1343
  ${this.exportOptions.useInOut ? html`
1745
1344
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 6px;">
1746
1345
  <div>
1747
- <label style="display: block; color: #94a3b8; font-size: 10px; margin-bottom: 2px;">In (ms)</label>
1346
+ <label class="dropdown-label" style="display: block; margin-bottom: 2px; font-size: 10px;">In (ms)</label>
1748
1347
  <input
1749
1348
  type="number"
1349
+ class="dropdown-input"
1350
+ style="font-family: ui-monospace, monospace;"
1750
1351
  min="0"
1751
1352
  max=${durationMs}
1752
1353
  .value=${String(this.exportOptions.inMs)}
1753
1354
  @change=${(e) => this.updateExportOption("inMs", Number(e.target.value))}
1754
- style="
1755
- width: 100%;
1756
- padding: 5px 7px;
1757
- background: rgba(51, 65, 85, 0.8);
1758
- border: 1px solid rgba(148, 163, 184, 0.2);
1759
- border-radius: 4px;
1760
- color: #e2e8f0;
1761
- font-size: 11px;
1762
- font-family: ui-monospace, monospace;
1763
- "
1764
1355
  />
1765
1356
  </div>
1766
1357
  <div>
1767
- <label style="display: block; color: #94a3b8; font-size: 10px; margin-bottom: 2px;">Out (ms)</label>
1358
+ <label class="dropdown-label" style="display: block; margin-bottom: 2px; font-size: 10px;">Out (ms)</label>
1768
1359
  <input
1769
1360
  type="number"
1361
+ class="dropdown-input"
1362
+ style="font-family: ui-monospace, monospace;"
1770
1363
  min="0"
1771
1364
  max=${durationMs}
1772
1365
  .value=${String(this.exportOptions.outMs)}
1773
1366
  @change=${(e) => this.updateExportOption("outMs", Number(e.target.value))}
1774
- style="
1775
- width: 100%;
1776
- padding: 5px 7px;
1777
- background: rgba(51, 65, 85, 0.8);
1778
- border: 1px solid rgba(148, 163, 184, 0.2);
1779
- border-radius: 4px;
1780
- color: #e2e8f0;
1781
- font-size: 11px;
1782
- font-family: ui-monospace, monospace;
1783
- "
1784
1367
  />
1785
1368
  </div>
1786
1369
  </div>
1787
- <div style="color: #64748b; font-size: 10px; margin-top: 4px;">
1370
+ <div class="dropdown-description" style="margin-top: 4px;">
1788
1371
  Duration: ${this.formatTime(this.exportOptions.outMs - this.exportOptions.inMs)} / ${this.formatTime(durationMs)}
1789
1372
  </div>
1790
1373
  ` : html`
1791
- <div style="color: #64748b; font-size: 10px;">
1374
+ <div class="dropdown-description">
1792
1375
  Full duration: ${this.formatTime(durationMs)}
1793
1376
  </div>
1794
1377
  `}
@@ -1821,16 +1404,16 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1821
1404
  let statusColor;
1822
1405
  let statusText;
1823
1406
  if (isComplete) {
1824
- statusColor = "#4ade80";
1407
+ statusColor = "var(--ef-color-success)";
1825
1408
  statusText = "Complete!";
1826
1409
  } else if (isError) {
1827
- statusColor = "#f87171";
1410
+ statusColor = "var(--ef-color-danger)";
1828
1411
  statusText = "Failed";
1829
1412
  } else if (isCancelled) {
1830
- statusColor = "#fbbf24";
1413
+ statusColor = "var(--ef-color-warning)";
1831
1414
  statusText = "Cancelled";
1832
1415
  } else {
1833
- statusColor = "#60a5fa";
1416
+ statusColor = "var(--ef-color-primary)";
1834
1417
  statusText = `${progressPercent}%`;
1835
1418
  }
1836
1419
  return html`
@@ -1845,7 +1428,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1845
1428
  ${isRendering ? html`
1846
1429
  <button
1847
1430
  class="dropdown-close"
1848
- style="color: #f87171;"
1431
+ style="color: var(--ef-color-danger);"
1849
1432
  @click=${this.handleCancelClick}
1850
1433
  >Cancel</button>
1851
1434
  ` : null}
@@ -1859,7 +1442,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1859
1442
  <style>
1860
1443
  ef-workbench canvas {
1861
1444
  border-radius: 4px;
1862
- border: 1px solid rgba(148, 163, 184, 0.2);
1445
+ border: 1px solid var(--ef-color-border);
1863
1446
  max-width: 100%;
1864
1447
  height: auto;
1865
1448
  }
@@ -1868,25 +1451,25 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1868
1451
 
1869
1452
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; margin-bottom: 10px; font-family: ui-monospace, monospace; font-size: 10px;">
1870
1453
  <div>
1871
- <div style="color: #64748b;">Frames</div>
1872
- <div style="color: #e2e8f0;">${p.currentFrame} / ${p.totalFrames}</div>
1454
+ <div style="color: var(--ef-color-text-subtle);">Frames</div>
1455
+ <div style="color: var(--ef-color-text);">${p.currentFrame} / ${p.totalFrames}</div>
1873
1456
  </div>
1874
1457
  <div>
1875
- <div style="color: #64748b;">Time</div>
1876
- <div style="color: #e2e8f0;">${this.formatTime(p.renderedMs)} / ${this.formatTime(p.totalDurationMs)}</div>
1458
+ <div style="color: var(--ef-color-text-subtle);">Time</div>
1459
+ <div style="color: var(--ef-color-text);">${this.formatTime(p.renderedMs)} / ${this.formatTime(p.totalDurationMs)}</div>
1877
1460
  </div>
1878
1461
  <div>
1879
- <div style="color: #64748b;">Speed</div>
1880
- <div style="color: ${p.speedMultiplier >= 1 ? "#4ade80" : "#fbbf24"};">${p.speedMultiplier.toFixed(2)}x</div>
1462
+ <div style="color: var(--ef-color-text-subtle);">Speed</div>
1463
+ <div style="color: ${p.speedMultiplier >= 1 ? "var(--ef-color-success)" : "var(--ef-color-warning)"};">${p.speedMultiplier.toFixed(2)}x</div>
1881
1464
  </div>
1882
1465
  <div>
1883
- <div style="color: #64748b;">ETA</div>
1884
- <div style="color: #e2e8f0;">${this.formatTime(p.estimatedRemainingMs)}</div>
1466
+ <div style="color: var(--ef-color-text-subtle);">ETA</div>
1467
+ <div style="color: var(--ef-color-text);">${this.formatTime(p.estimatedRemainingMs)}</div>
1885
1468
  </div>
1886
1469
  </div>
1887
1470
  ` : null}
1888
1471
 
1889
- <div style="height: 4px; background: rgba(51, 65, 85, 0.8); border-radius: 2px; overflow: hidden;">
1472
+ <div style="height: 4px; background: var(--ef-color-bg-inset); border-radius: 2px; overflow: hidden;">
1890
1473
  <div style="
1891
1474
  height: 100%;
1892
1475
  width: ${progressPercent}%;
@@ -1910,7 +1493,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1910
1493
  }
1911
1494
  renderToolbar() {
1912
1495
  return html`
1913
- <div class="toolbar">
1496
+ <div class="toolbar" part="toolbar">
1914
1497
  <div class="toolbar-left">
1915
1498
  <!-- Fit to content button -->
1916
1499
  <button
@@ -1926,14 +1509,43 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1926
1509
  </div>
1927
1510
 
1928
1511
  <div class="toolbar-right">
1929
- <!-- Mode indicator (shown when not in default clone mode) -->
1930
- ${this.presentationMode !== "clone" ? html`
1931
- <span class="mode-indicator ${this.presentationMode}">
1932
- ${this.presentationMode === "dom" ? "DOM" : html`
1933
- Canvas ${getRenderMode() === "native" ? phosphorIcon(ICONS.lightning, 12) : phosphorIcon(ICONS.code, 12)}
1934
- `}
1935
- </span>
1936
- ` : null}
1512
+ <!-- Mode indicator -->
1513
+ <span class="mode-indicator ${this.presentationMode}">
1514
+ ${this.presentationMode === "dom" ? "DOM" : html`
1515
+ Canvas ${getRenderMode() === "native" ? phosphorIcon(ICONS.lightning, 12) : phosphorIcon(ICONS.code, 12)}
1516
+ `}
1517
+ </span>
1518
+
1519
+ <!-- Theme toggle button -->
1520
+ <button
1521
+ class="toolbar-icon-btn"
1522
+ @click=${this.handleThemeToggle}
1523
+ title="${this.themeMode === "light" ? "Light mode" : this.themeMode === "dark" ? "Dark mode" : "System preference"}"
1524
+ >
1525
+ ${this.themeMode === "light" ? html`
1526
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1527
+ <circle cx="12" cy="12" r="5"></circle>
1528
+ <line x1="12" y1="1" x2="12" y2="3"></line>
1529
+ <line x1="12" y1="21" x2="12" y2="23"></line>
1530
+ <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
1531
+ <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
1532
+ <line x1="1" y1="12" x2="3" y2="12"></line>
1533
+ <line x1="21" y1="12" x2="23" y2="12"></line>
1534
+ <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
1535
+ <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
1536
+ </svg>
1537
+ ` : this.themeMode === "dark" ? html`
1538
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1539
+ <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
1540
+ </svg>
1541
+ ` : html`
1542
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
1543
+ <rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect>
1544
+ <line x1="8" y1="21" x2="16" y2="21"></line>
1545
+ <line x1="12" y1="17" x2="12" y2="21"></line>
1546
+ </svg>
1547
+ `}
1548
+ </button>
1937
1549
 
1938
1550
  <!-- Settings button -->
1939
1551
  <button
@@ -1953,7 +1565,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
1953
1565
  style="min-width: 100px;"
1954
1566
  popovertarget="export-progress-popover"
1955
1567
  >
1956
- <div style="width: 12px; height: 12px; border: 2px solid rgba(96, 165, 250, 0.3); border-top-color: #60a5fa; border-radius: 50%; animation: spin 1s linear infinite;"></div>
1568
+ <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>
1957
1569
  Exporting...
1958
1570
  </button>
1959
1571
  ` : html`
@@ -2001,17 +1613,32 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
2001
1613
  }
2002
1614
  }
2003
1615
  renderPlaybackStats() {
2004
- if (!this.showStats || !this.statsStrategy) return null;
2005
- const stats = this.statsStrategy.getStats();
1616
+ if (!this.showStats) return null;
1617
+ let stats = null;
1618
+ if (this.presentationMode === "canvas" && this.renderStats) {
1619
+ const timegroup = this.getTimegroup();
1620
+ if (!timegroup) return null;
1621
+ const compositionWidth = timegroup.offsetWidth || 1920;
1622
+ const compositionHeight = timegroup.offsetHeight || 1080;
1623
+ const resolutionScale = this.canvasPreviewResult ? this.canvasPreviewResult.getResolutionScale() : 1;
1624
+ const renderWidth = Math.floor(compositionWidth * resolutionScale);
1625
+ const renderHeight = Math.floor(compositionHeight * resolutionScale);
1626
+ stats = this.renderStats.getStats(renderWidth, renderHeight, resolutionScale);
1627
+ } else if (this.presentationMode === "dom" && this.domStatsStrategy) stats = this.domStatsStrategy.getStats();
2006
1628
  if (!stats) return null;
2007
1629
  const fpsClass = stats.fps >= 55 ? "good" : stats.fps >= 25 ? "warning" : "bad";
2008
1630
  const renderClass = stats.avgRenderTime !== null ? stats.avgRenderTime <= 20 ? "good" : stats.avgRenderTime <= 30 ? "warning" : "bad" : "";
2009
1631
  const headroomClass = stats.headroom !== null ? stats.headroom >= 10 ? "good" : stats.headroom >= 0 ? "warning" : "bad" : "";
2010
1632
  const pressureClass = stats.pressureState === "nominal" ? "good" : stats.pressureState === "fair" ? "good" : stats.pressureState === "serious" ? "warning" : "bad";
2011
1633
  const scaleClass = stats.resolutionScale !== null ? stats.resolutionScale >= .75 ? "good" : stats.resolutionScale >= .5 ? "warning" : "bad" : "";
1634
+ const isCanvasMode = this.presentationMode === "canvas";
1635
+ const showRenderTime = isCanvasMode;
1636
+ const showHeadroom = isCanvasMode;
1637
+ const showResolutionScale = isCanvasMode;
1638
+ const showAdaptiveResolution = isCanvasMode && this.previewResolutionScale === "auto";
2012
1639
  const motionState = this.isAtRest ? "At Rest" : this.isPlaying ? "Playing" : this.isScrubbing ? "Scrubbing" : "Idle";
2013
1640
  const renderPressureHistogram = () => {
2014
- if (stats.pressureHistory.length === 0) return html`<div style="color: #64748b; font-size: 9px;">No pressure data (API not available)</div>`;
1641
+ if (stats.pressureHistory.length === 0) return html`<div style="color: var(--ef-color-text-subtle); font-size: 9px;">No pressure data (API not available)</div>`;
2015
1642
  return html`
2016
1643
  <div class="pressure-histogram">
2017
1644
  ${stats.pressureHistory.map((state$1) => html`
@@ -2033,13 +1660,13 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
2033
1660
  <span class="stat-label">FPS</span>
2034
1661
  <span class="stat-value ${fpsClass}">${padNum(stats.fps, 1, 5)}</span>
2035
1662
  </div>
2036
- ${this.statsStrategy.supportsStat("renderTime") && stats.avgRenderTime !== null ? html`
1663
+ ${showRenderTime && stats.avgRenderTime !== null ? html`
2037
1664
  <div class="stat-row">
2038
1665
  <span class="stat-label">Render</span>
2039
1666
  <span class="stat-value ${renderClass}">${padNum(stats.avgRenderTime, 1, 5)}ms</span>
2040
1667
  </div>
2041
1668
  ` : null}
2042
- ${this.statsStrategy.supportsStat("headroom") && stats.headroom !== null ? html`
1669
+ ${showHeadroom && stats.headroom !== null ? html`
2043
1670
  <div class="stat-row">
2044
1671
  <span class="stat-label">Headroom</span>
2045
1672
  <span class="stat-value ${headroomClass}">${stats.headroom >= 0 ? "+" : ""}${padNum(stats.headroom, 1, 4)}ms</span>
@@ -2049,7 +1676,7 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
2049
1676
  <span class="stat-label">Resolution</span>
2050
1677
  <span class="stat-value">${stats.renderWidth}×${stats.renderHeight}</span>
2051
1678
  </div>
2052
- ${this.statsStrategy.supportsStat("resolutionScale") && stats.resolutionScale !== null ? html`
1679
+ ${showResolutionScale && stats.resolutionScale !== null ? html`
2053
1680
  <div class="stat-row">
2054
1681
  <span class="stat-label">Scale</span>
2055
1682
  <span class="stat-value ${scaleClass}">${String(Math.round(stats.resolutionScale * 100)).padStart(3, " ")}%</span>
@@ -2063,8 +1690,8 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
2063
1690
  <span class="stat-label">State</span>
2064
1691
  <span class="stat-value">${motionState}</span>
2065
1692
  </div>
2066
- ${this.statsStrategy.supportsStat("adaptiveResolution") && this.previewResolutionScale === "auto" && stats.samplesAtCurrentScale !== void 0 ? html`
2067
- <div style="margin-top: 4px; padding-top: 4px; border-top: 1px solid rgba(148, 163, 184, 0.2);">
1693
+ ${showAdaptiveResolution && stats.samplesAtCurrentScale !== void 0 ? html`
1694
+ <div style="margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--ef-color-border-subtle);">
2068
1695
  <div class="stat-row">
2069
1696
  <span class="stat-label">Mode</span>
2070
1697
  <span class="stat-value good">Auto</span>
@@ -2097,56 +1724,45 @@ let EFWorkbench = class EFWorkbench$1 extends ContextMixin(TWMixin(LitElement))
2097
1724
  <slot class="fixed inset-0 h-full w-full" name="canvas"></slot>
2098
1725
  `;
2099
1726
  return html`
1727
+ <!-- Top: Full-width Toolbar -->
1728
+ <div style="grid-row: 1 / 2; grid-column: 1 / -1;">
1729
+ ${this.renderToolbar()}
1730
+ </div>
1731
+
1732
+ <!-- Left: Hierarchy Panel -->
2100
1733
  <div
2101
- class="grid overflow-hidden"
2102
- style="flex: 1; min-height: 0; width: 100%; grid-template-rows: auto 1fr 280px; grid-template-columns: 280px 1fr; background-color: var(--workbench-bg);"
1734
+ 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;"
2103
1735
  >
2104
- <!-- Top: Full-width Toolbar -->
2105
- <div style="grid-row: 1 / 2; grid-column: 1 / -1;">
2106
- ${this.renderToolbar()}
2107
- </div>
2108
-
2109
- <!-- Left: Hierarchy Panel -->
2110
- <div
2111
- style="grid-row: 2 / 3; grid-column: 1 / 2; background: rgb(30 41 59); border-right: 1px solid rgba(148, 163, 184, 0.2); min-height: 0; max-height: 100%; display: flex; flex-direction: column; overflow: hidden;"
2112
- >
2113
- <slot name="hierarchy"></slot>
2114
- </div>
1736
+ <slot name="hierarchy"></slot>
1737
+ </div>
2115
1738
 
2116
- <!-- Center: Canvas area -->
2117
- <div
2118
- class="canvas-container"
2119
- style="grid-row: 2 / 3; grid-column: 2 / 3; min-height: 0;"
2120
- @wheel=${this.handleStageWheel}
2121
- >
2122
- <!-- Original timegroup (hidden in clone/canvas mode, visible in dom mode) -->
2123
- <slot name="canvas"></slot>
2124
-
2125
- <!-- Clone overlay (visible in clone mode only) -->
2126
- <div
2127
- class="clone-overlay"
2128
- ${ref(this.cloneOverlayRef)}
2129
- style="display: ${this.presentationMode === "clone" ? "block" : "none"}"
2130
- ></div>
2131
-
2132
- <!-- Canvas preview (visible in canvas mode only) -->
2133
- <div
2134
- class="clone-overlay"
2135
- ${ref(this.canvasPreviewRef)}
2136
- style="display: ${this.presentationMode === "canvas" ? "block" : "none"}"
2137
- ></div>
2138
-
2139
- <!-- Playback stats overlay (visible in canvas mode only) -->
2140
- ${this.renderPlaybackStats()}
2141
- </div>
1739
+ <!-- Center: Canvas area -->
1740
+ <div
1741
+ class="canvas-container"
1742
+ part="canvas"
1743
+ style="grid-row: 2 / 3; grid-column: 2 / 3; min-height: 0; overflow: hidden;"
1744
+ @wheel=${this.handleStageWheel}
1745
+ >
1746
+ <!-- Original timegroup (hidden in clone/canvas mode, visible in dom mode) -->
1747
+ <slot name="canvas"></slot>
1748
+
1749
+ <!-- Canvas preview (visible in canvas mode only) -->
1750
+ <div
1751
+ class="canvas-overlay"
1752
+ ${ref(this.canvasPreviewRef)}
1753
+ style="display: ${this.presentationMode === "canvas" ? "block" : "none"}"
1754
+ ></div>
1755
+
1756
+ <!-- Playback stats overlay (visible in canvas mode only) -->
1757
+ ${this.renderPlaybackStats()}
1758
+ </div>
2142
1759
 
2143
- <!-- Bottom: Timeline -->
2144
- <div
2145
- class="overflow-hidden"
2146
- style="grid-row: 3 / 4; grid-column: 1 / -1; width: 100%; border-top: 1px solid rgba(148, 163, 184, 0.2);"
2147
- >
2148
- <slot name="timeline"></slot>
2149
- </div>
1760
+ <!-- Bottom: Timeline -->
1761
+ <div
1762
+ class="overflow-hidden"
1763
+ style="grid-row: 3 / 4; grid-column: 1 / -1; height: 100%; border-top: 1px solid var(--ef-color-border);"
1764
+ >
1765
+ <slot name="timeline"></slot>
2150
1766
  </div>
2151
1767
  `;
2152
1768
  }
@@ -2160,15 +1776,13 @@ __decorate([provide({ context: previewSettingsContext }), state()], EFWorkbench.
2160
1776
  __decorate([state()], EFWorkbench.prototype, "renderMode", void 0);
2161
1777
  __decorate([state()], EFWorkbench.prototype, "presentationMode", void 0);
2162
1778
  __decorate([state()], EFWorkbench.prototype, "previewResolutionScale", void 0);
2163
- __decorate([state()], EFWorkbench.prototype, "debugThumbnailTimestamps", void 0);
2164
- __decorate([state()], EFWorkbench.prototype, "thumbnailCacheMaxSize", void 0);
2165
- __decorate([state()], EFWorkbench.prototype, "thumbnailCacheStats", void 0);
2166
1779
  __decorate([state()], EFWorkbench.prototype, "exportOptions", void 0);
2167
1780
  __decorate([state()], EFWorkbench.prototype, "isPlaying", void 0);
2168
1781
  __decorate([state()], EFWorkbench.prototype, "isScrubbing", void 0);
2169
1782
  __decorate([state()], EFWorkbench.prototype, "isAtRest", void 0);
2170
1783
  __decorate([state()], EFWorkbench.prototype, "currentAdaptiveScale", void 0);
2171
1784
  __decorate([state()], EFWorkbench.prototype, "showStats", void 0);
1785
+ __decorate([state()], EFWorkbench.prototype, "themeMode", void 0);
2172
1786
  __decorate([eventOptions({
2173
1787
  passive: false,
2174
1788
  capture: true