@editframe/elements 0.37.2-beta → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (321) 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 +4 -4
  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 +7 -10
  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 +4 -4
  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 +4 -4
  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 +4 -4
  107. package/dist/gui/EFFocusOverlay.js +3 -3
  108. package/dist/gui/EFFocusOverlay.js.map +1 -1
  109. package/dist/gui/EFOverlayItem.d.ts +4 -4
  110. package/dist/gui/EFOverlayLayer.d.ts +4 -4
  111. package/dist/gui/EFPause.d.ts +4 -4
  112. package/dist/gui/EFPause.js +1 -1
  113. package/dist/gui/EFPlay.d.ts +4 -4
  114. package/dist/gui/EFPlay.js +1 -1
  115. package/dist/gui/EFPreview.js +1 -1
  116. package/dist/gui/EFResizableBox.d.ts +4 -4
  117. package/dist/gui/EFResizableBox.js +5 -5
  118. package/dist/gui/EFResizableBox.js.map +1 -1
  119. package/dist/gui/EFScrubber.d.ts +4 -4
  120. package/dist/gui/EFScrubber.js +8 -13
  121. package/dist/gui/EFScrubber.js.map +1 -1
  122. package/dist/gui/EFTimeDisplay.d.ts +8 -4
  123. package/dist/gui/EFTimeDisplay.js +25 -7
  124. package/dist/gui/EFTimeDisplay.js.map +1 -1
  125. package/dist/gui/EFTimelineRuler.d.ts +4 -4
  126. package/dist/gui/EFTimelineRuler.js +3 -3
  127. package/dist/gui/EFTimelineRuler.js.map +1 -1
  128. package/dist/gui/EFToggleLoop.d.ts +4 -4
  129. package/dist/gui/EFToggleLoop.js +1 -1
  130. package/dist/gui/EFTogglePlay.d.ts +4 -4
  131. package/dist/gui/EFTogglePlay.js +1 -1
  132. package/dist/gui/EFTransformHandles.d.ts +4 -4
  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 +3 -1
  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 +5 -6
  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 +4 -4
  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 +4 -4
  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.js +267 -145
  238. package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
  239. package/dist/preview/renderTimegroupToCanvas.types.d.ts +30 -0
  240. package/dist/preview/renderTimegroupToVideo.js +85 -105
  241. package/dist/preview/renderTimegroupToVideo.js.map +1 -1
  242. package/dist/preview/{renderTimegroupToVideo.d.ts → renderTimegroupToVideo.types.d.ts} +9 -9
  243. package/dist/preview/renderVideoToVideo.js +286 -0
  244. package/dist/preview/renderVideoToVideo.js.map +1 -0
  245. package/dist/preview/renderers.js.map +1 -1
  246. package/dist/preview/rendering/ScaleConfig.js +74 -0
  247. package/dist/preview/rendering/ScaleConfig.js.map +1 -0
  248. package/dist/preview/rendering/inlineImages.js +1 -44
  249. package/dist/preview/rendering/inlineImages.js.map +1 -1
  250. package/dist/preview/rendering/loadImage.js +22 -0
  251. package/dist/preview/rendering/loadImage.js.map +1 -0
  252. package/dist/preview/rendering/renderToImageNative.js +3 -3
  253. package/dist/preview/rendering/renderToImageNative.js.map +1 -1
  254. package/dist/preview/rendering/serializeTimelineDirect.js +224 -68
  255. package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -1
  256. package/dist/preview/statsTrackingStrategy.js +1 -101
  257. package/dist/preview/statsTrackingStrategy.js.map +1 -1
  258. package/dist/preview/workers/WorkerPool.js +0 -1
  259. package/dist/preview/workers/WorkerPool.js.map +1 -1
  260. package/dist/preview/workers/encoderWorkerInline.js +21 -54
  261. package/dist/preview/workers/encoderWorkerInline.js.map +1 -1
  262. package/dist/render/EFRenderAPI.d.ts +2 -1
  263. package/dist/render/EFRenderAPI.js +12 -36
  264. package/dist/render/EFRenderAPI.js.map +1 -1
  265. package/dist/render/getRenderData.js +4 -4
  266. package/dist/render/getRenderData.js.map +1 -1
  267. package/dist/style.css +114 -163
  268. package/dist/transcoding/cache/RequestDeduplicator.js +1 -0
  269. package/dist/transcoding/cache/RequestDeduplicator.js.map +1 -1
  270. package/dist/transcoding/types/index.d.ts +1 -1
  271. package/dist/transcoding/utils/UrlGenerator.js +10 -3
  272. package/dist/transcoding/utils/UrlGenerator.js.map +1 -1
  273. package/dist/utils/LRUCache.js +1 -0
  274. package/dist/utils/LRUCache.js.map +1 -1
  275. package/dist/utils/frameTime.js +23 -1
  276. package/dist/utils/frameTime.js.map +1 -1
  277. package/package.json +21 -8
  278. package/scripts/build-css.js +8 -1
  279. package/test/setup.ts +0 -1
  280. package/test/useAssetMSW.ts +50 -0
  281. package/test/visualRegressionUtils.ts +23 -9
  282. package/dist/_virtual/rolldown_runtime.js +0 -27
  283. package/dist/elements/EFMedia/AssetIdMediaEngine.js.map +0 -1
  284. package/dist/elements/EFThumbnailStrip.d.ts +0 -167
  285. package/dist/elements/EFThumbnailStrip.js +0 -731
  286. package/dist/elements/EFThumbnailStrip.js.map +0 -1
  287. package/dist/elements/SessionThumbnailCache.js +0 -154
  288. package/dist/elements/SessionThumbnailCache.js.map +0 -1
  289. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +0 -688
  290. package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +0 -1
  291. package/dist/node_modules/react/cjs/react.development.js +0 -1521
  292. package/dist/node_modules/react/cjs/react.development.js.map +0 -1
  293. package/dist/node_modules/react/index.js +0 -13
  294. package/dist/node_modules/react/index.js.map +0 -1
  295. package/dist/node_modules/react/jsx-runtime.js +0 -13
  296. package/dist/node_modules/react/jsx-runtime.js.map +0 -1
  297. package/dist/preview/encoding/types.d.ts +0 -1
  298. package/dist/preview/renderTimegroupPreview.js +0 -686
  299. package/dist/preview/renderTimegroupPreview.js.map +0 -1
  300. package/dist/preview/renderTimegroupToCanvas.d.ts +0 -42
  301. package/dist/preview/rendering/renderToImage.d.ts +0 -2
  302. package/dist/preview/rendering/renderToImage.js +0 -95
  303. package/dist/preview/rendering/renderToImage.js.map +0 -1
  304. package/dist/preview/rendering/renderToImageForeignObject.js +0 -163
  305. package/dist/preview/rendering/renderToImageForeignObject.js.map +0 -1
  306. package/dist/preview/rendering/renderToImageNative.d.ts +0 -1
  307. package/dist/preview/rendering/svgSerializer.js +0 -43
  308. package/dist/preview/rendering/svgSerializer.js.map +0 -1
  309. package/dist/preview/rendering/types.d.ts +0 -2
  310. package/dist/preview/thumbnailCacheSettings.js +0 -52
  311. package/dist/preview/thumbnailCacheSettings.js.map +0 -1
  312. package/dist/sandbox/PlaybackControls.d.ts +0 -1
  313. package/dist/sandbox/PlaybackControls.js +0 -10
  314. package/dist/sandbox/PlaybackControls.js.map +0 -1
  315. package/dist/sandbox/ScenarioRunner.d.ts +0 -1
  316. package/dist/sandbox/ScenarioRunner.js +0 -1
  317. package/dist/sandbox/defineSandbox.d.ts +0 -1
  318. package/dist/sandbox/index.d.ts +0 -3
  319. package/dist/sandbox/index.js +0 -2
  320. package/test/EFVideo.framegen.browsertest.ts +0 -80
  321. package/test/thumbnail-performance-test.html +0 -116
@@ -1,13 +1,12 @@
1
1
  import { TrackItem } from "./TrackItem.js";
2
2
  import { Caption } from "../../../elements/EFCaptions.js";
3
- import * as lit42 from "lit";
3
+ import * as lit43 from "lit";
4
4
  import { TemplateResult, nothing } from "lit";
5
5
 
6
6
  //#region src/gui/timeline/tracks/CaptionsTrack.d.ts
7
7
  declare class EFCaptionsTrack extends TrackItem {
8
- static styles: lit42.CSSResult[];
8
+ static styles: lit43.CSSResult[];
9
9
  contextCurrentTimeMs: number;
10
- private _timeController;
11
10
  private lastPixelsPerMs;
12
11
  protected updated(changedProperties: Map<string | number | symbol, unknown>): void;
13
12
  render(): TemplateResult<1>;
@@ -1,6 +1,7 @@
1
1
  import { currentTimeContext } from "../../currentTimeContext.js";
2
2
  import { __decorate } from "../../../_virtual/_@oxc-project_runtime@0.95.0/helpers/decorate.js";
3
3
  import { ICONS, phosphorIcon } from "../../icons.js";
4
+ import { getElementTypeColor } from "../../theme.js";
4
5
  import { TrackItem } from "./TrackItem.js";
5
6
  import { consume } from "@lit/context";
6
7
  import { css, html, nothing } from "lit";
@@ -37,7 +38,7 @@ function measureTextWidth(text, fontSize, fontWeight = 500) {
37
38
  }
38
39
  /**
39
40
  * Check if words can fit individually within a segment when positioned by time
40
- *
41
+ *
41
42
  * Strategy: Allow overlaps as long as all words can be rendered within the container.
42
43
  * Only use compact mode when words are so cramped they can't be displayed at all.
43
44
  */
@@ -73,69 +74,10 @@ function canWordsFitIndividually(words, segmentStart, segmentWidthPx, pixelsPerM
73
74
  reason: `words exceed segment (total text: ${totalTextWidth.toFixed(1)}px, segment: ${segmentWidthPx.toFixed(1)}px)`
74
75
  };
75
76
  }
76
- /**
77
- * Controller to ensure captions track updates reactively during playback.
78
- *
79
- * Performance optimization: Only requests updates when the visual state actually
80
- * needs to change (active word/segment changed), not on every frame.
81
- */
82
- var CaptionsTimeController = class CaptionsTimeController {
83
- static {
84
- this.MIN_TIME_CHANGE_MS = 100;
85
- }
86
- constructor(host) {
87
- this.host = host;
88
- this.lastTimeMs = -1;
89
- this.lastActiveWordIndex = -1;
90
- this.lastActiveSegmentIndex = -1;
91
- this.host.addController(this);
92
- }
93
- hostConnected() {
94
- this.startTimeUpdate();
95
- }
96
- hostDisconnected() {
97
- this.stopTimeUpdate();
98
- }
99
- startTimeUpdate() {
100
- const update = () => {
101
- const captions = this.host.element;
102
- const currentTimeMs = captions.rootTimegroup?.currentTimeMs || 0;
103
- const captionsData = captions?.unifiedCaptionsDataTask?.value;
104
- let shouldUpdate = false;
105
- if (captionsData) {
106
- const captionsLocalTimeSec = (currentTimeMs - captions.startTimeMs) / 1e3;
107
- const activeWordIndex = captionsData.word_segments.findIndex((word) => captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end);
108
- const activeSegmentIndex = captionsData.segments.findIndex((seg) => captionsLocalTimeSec >= seg.start && captionsLocalTimeSec < seg.end);
109
- if (activeWordIndex !== this.lastActiveWordIndex) {
110
- this.lastActiveWordIndex = activeWordIndex;
111
- shouldUpdate = true;
112
- }
113
- if (activeSegmentIndex !== this.lastActiveSegmentIndex) {
114
- this.lastActiveSegmentIndex = activeSegmentIndex;
115
- shouldUpdate = true;
116
- }
117
- }
118
- if (Math.abs(currentTimeMs - this.lastTimeMs) >= CaptionsTimeController.MIN_TIME_CHANGE_MS) shouldUpdate = true;
119
- if (shouldUpdate) {
120
- this.lastTimeMs = currentTimeMs;
121
- this.host.requestUpdate();
122
- }
123
- this.animationFrameId = requestAnimationFrame(update);
124
- };
125
- update();
126
- }
127
- stopTimeUpdate() {
128
- if (this.animationFrameId) {
129
- cancelAnimationFrame(this.animationFrameId);
130
- this.animationFrameId = void 0;
131
- }
132
- }
133
- };
134
77
  let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
135
78
  constructor(..._args) {
136
79
  super(..._args);
137
80
  this.contextCurrentTimeMs = 0;
138
- this._timeController = new CaptionsTimeController(this);
139
81
  this.lastPixelsPerMs = 0;
140
82
  }
141
83
  static {
@@ -164,13 +106,13 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
164
106
  padding: 2px 4px;
165
107
  border-radius: 2px;
166
108
  transition: all 0.1s ease;
167
- background: rgba(30, 41, 59, 0.9);
168
- color: rgb(226, 232, 240);
109
+ background: var(--ef-color-bg-elevated);
110
+ color: var(--ef-color-text);
169
111
  z-index: 1;
170
112
  }
171
113
 
172
114
  .word-element.active {
173
- background: rgb(74, 222, 128);
115
+ background: var(--ef-color-success);
174
116
  color: rgb(20, 30, 20);
175
117
  font-weight: 700;
176
118
  font-size: 10px;
@@ -179,14 +121,14 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
179
121
  }
180
122
 
181
123
  .word-element.future {
182
- background: rgba(51, 65, 85, 0.8);
183
- color: rgb(226, 232, 240);
124
+ background: var(--ef-color-bg-inset);
125
+ color: var(--ef-color-text);
184
126
  z-index: 5;
185
127
  }
186
128
 
187
129
  .segment-block.active .word-element:not(.active):not(.future) {
188
- color: rgb(203, 213, 225);
189
- background: rgba(30, 41, 59, 0.9);
130
+ color: var(--ef-color-text-muted);
131
+ background: var(--ef-color-bg-elevated);
190
132
  }
191
133
 
192
134
  /* Compact text mode - when words are too small to position individually */
@@ -205,7 +147,7 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
205
147
  /* Expand to fit content on hover */
206
148
  width: max-content !important;
207
149
  min-width: max-content;
208
- background: rgba(34, 60, 40, 0.95) !important;
150
+ background: var(--ef-color-bg-elevated) !important;
209
151
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
210
152
  }
211
153
 
@@ -215,7 +157,7 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
215
157
  white-space: nowrap;
216
158
  overflow: hidden;
217
159
  text-overflow: ellipsis;
218
- color: rgb(226, 232, 240);
160
+ color: var(--ef-color-text);
219
161
  width: 100%;
220
162
  }
221
163
 
@@ -226,7 +168,7 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
226
168
  }
227
169
 
228
170
  .segment-block.compact-text.active .segment-text-compact {
229
- color: rgb(203, 213, 225);
171
+ color: var(--ef-color-text-muted);
230
172
  font-weight: 500;
231
173
  }
232
174
 
@@ -251,12 +193,12 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
251
193
  bottom: 0;
252
194
  width: 1px;
253
195
  height: 30%;
254
- background: rgba(255, 255, 255, 0.3);
196
+ background: var(--ef-color-border-subtle);
255
197
  pointer-events: none;
256
198
  }
257
199
 
258
200
  .word-marker.active {
259
- background: rgba(255, 255, 255, 0.8);
201
+ background: var(--ef-color-text);
260
202
  height: 50%;
261
203
  width: 2px;
262
204
  }
@@ -318,7 +260,7 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
318
260
  const captions = this.element;
319
261
  const rootTimegroup = captions.rootTimegroup;
320
262
  const captionsLocalTimeSec = ((this.contextCurrentTimeMs || rootTimegroup?.currentTimeMs || 0) - captions.startTimeMs) / 1e3;
321
- const captionColor = "rgb(34, 197, 94)";
263
+ const captionColor = getElementTypeColor("captions", this);
322
264
  const activeWord = captionsData.word_segments.find((word) => captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end);
323
265
  return html`
324
266
  ${captionsData.word_segments.map((word) => {
@@ -399,8 +341,8 @@ let EFCaptionsTrack = class EFCaptionsTrack$1 extends TrackItem {
399
341
  width: `${Math.max(segmentWidth, 4)}px`,
400
342
  height: "100%",
401
343
  top: "0px",
402
- backgroundColor: isActiveSegment ? `rgba(34, 197, 94, ${.3 + density * .2})` : `rgba(34, 197, 94, ${.1 + density * .1})`,
403
- borderColor: isActiveSegment ? captionColor : `rgba(34, 197, 94, 0.4)`,
344
+ backgroundColor: isActiveSegment ? `color-mix(in srgb, var(--ef-color-type-captions) ${30 + density * 20}%, transparent)` : `color-mix(in srgb, var(--ef-color-type-captions) ${10 + density * 10}%, transparent)`,
345
+ borderColor: isActiveSegment ? captionColor : `color-mix(in srgb, var(--ef-color-type-captions) 40%, transparent)`,
404
346
  minWidth: segmentWidth < 20 ? "20px" : "auto"
405
347
  })}
406
348
  title=${useCompactText ? `Caption: '${segment.text}'\nDuration: ${this.formatDuration(segmentDuration)}\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s` : `Caption: '${segment.text}'\nDuration: ${this.formatDuration(segmentDuration)}\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s\nWords: ${wordsInSegment.length}`}
@@ -1 +1 @@
1
- {"version":3,"file":"CaptionsTrack.js","names":["measurementCanvas: HTMLCanvasElement | null","measurementContext: CanvasRenderingContext2D | null","wordWidths: Array<{ textWidth: number; timeWidth: number; startPx: number; endPx: number }>","host: EFCaptionsTrack","EFCaptionsTrack","EFCaptionsActiveWordTrack","EFCaptionsSegmentTrack","EFCaptionsBeforeWordTrack","EFCaptionsAfterWordTrack"],"sources":["../../../../src/gui/timeline/tracks/CaptionsTrack.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, nothing, type TemplateResult } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport type { ReactiveController } from \"lit\";\nimport {\n type Caption,\n EFCaptions,\n type WordSegment,\n} from \"../../../elements/EFCaptions.js\";\nimport { phosphorIcon, ICONS } from \"../../icons.js\";\nimport { currentTimeContext } from \"../../currentTimeContext.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\n\n// Shared canvas context for text measurement (avoids creating new canvas each time)\nlet measurementCanvas: HTMLCanvasElement | null = null;\nlet measurementContext: CanvasRenderingContext2D | null = null;\n// Cache for text measurements: key is \"text:fontSize:fontWeight\"\nconst textMeasurementCache = new Map<string, number>();\nconst MAX_CACHE_SIZE = 500;\n\n/**\n * Measure text width accurately using canvas.\n * Matches the actual font used in word elements (font-weight: 500).\n * Results are cached to avoid repeated measurements of the same text.\n */\nfunction measureTextWidth(text: string, fontSize: number, fontWeight: number = 500): number {\n // Check cache first\n const cacheKey = `${text}:${fontSize}:${fontWeight}`;\n const cached = textMeasurementCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n \n // Initialize shared canvas context if needed\n if (!measurementCanvas || !measurementContext) {\n measurementCanvas = document.createElement(\"canvas\");\n measurementContext = measurementCanvas.getContext(\"2d\");\n }\n \n if (!measurementContext) {\n return text.length * fontSize * 0.6; // Fallback estimate\n }\n \n // Match the actual font used in word elements\n const fontFamily = getComputedStyle(document.body).fontFamily || \"system-ui, sans-serif\";\n measurementContext.font = `${fontWeight} ${fontSize}px ${fontFamily}`;\n const width = measurementContext.measureText(text).width;\n \n // Cache the result (with size limit to prevent memory leaks)\n if (textMeasurementCache.size >= MAX_CACHE_SIZE) {\n // Clear oldest entries (simple strategy: clear half the cache)\n const keysToDelete = Array.from(textMeasurementCache.keys()).slice(0, MAX_CACHE_SIZE / 2);\n for (const key of keysToDelete) {\n textMeasurementCache.delete(key);\n }\n }\n textMeasurementCache.set(cacheKey, width);\n \n return width;\n}\n\n/**\n * Check if words can fit individually within a segment when positioned by time\n * \n * Strategy: Allow overlaps as long as all words can be rendered within the container.\n * Only use compact mode when words are so cramped they can't be displayed at all.\n */\nfunction canWordsFitIndividually(\n words: WordSegment[],\n segmentStart: number,\n segmentWidthPx: number,\n pixelsPerMs: number,\n): { fits: boolean; reason?: string } {\n if (words.length === 0) {\n return { fits: false, reason: \"no words\" };\n }\n \n // Measure total text width of all words (as if rendered sequentially)\n let totalTextWidth = 0;\n const wordWidths: Array<{ textWidth: number; timeWidth: number; startPx: number; endPx: number }> = [];\n \n for (const word of words) {\n if (!word) continue;\n \n // Measure actual text width (with padding: 2px left + 2px right = 4px total)\n const textWidth = measureTextWidth(word.text.trim(), 9, 500) + 4;\n \n // Calculate time-based position and width\n const startPx = pixelsPerMs * (word.start - segmentStart) * 1000;\n const endPx = pixelsPerMs * (word.end - segmentStart) * 1000;\n const timeWidth = endPx - startPx;\n \n wordWidths.push({ textWidth, timeWidth, startPx, endPx });\n totalTextWidth += textWidth;\n }\n \n // Key insight: If total text width fits in segment, we can render words individually\n // even if they overlap based on their time positions\n // Use 90% threshold to account for some spacing/overlap\n if (totalTextWidth <= segmentWidthPx * 0.9) {\n // All words can fit - use positioned mode (overlaps are okay)\n return { fits: true };\n }\n \n // If total text doesn't fit, check if individual words are too narrow to be readable\n // If any word's time-based width is less than 30% of its text width, it's unreadable\n for (const { textWidth, timeWidth } of wordWidths) {\n if (timeWidth < textWidth * 0.3) {\n return { fits: false, reason: `word too narrow (${timeWidth.toFixed(1)}px < ${(textWidth * 0.3).toFixed(1)}px)` };\n }\n }\n \n // If words are readable individually but total text is too wide,\n // check if they can still fit with overlaps\n // Find the maximum right edge of all words\n const maxEndPx = Math.max(...wordWidths.map(w => w.endPx));\n \n // If the rightmost word fits within the segment, allow overlaps\n if (maxEndPx <= segmentWidthPx * 1.1) {\n return { fits: true };\n }\n \n // Words don't fit - use compact mode\n return { fits: false, reason: `words exceed segment (total text: ${totalTextWidth.toFixed(1)}px, segment: ${segmentWidthPx.toFixed(1)}px)` };\n}\n\n/**\n * Controller to ensure captions track updates reactively during playback.\n * \n * Performance optimization: Only requests updates when the visual state actually\n * needs to change (active word/segment changed), not on every frame.\n */\nclass CaptionsTimeController implements ReactiveController {\n private animationFrameId?: number;\n private lastTimeMs = -1;\n private lastActiveWordIndex = -1;\n private lastActiveSegmentIndex = -1;\n // Minimum time change to trigger update when no word change (for segment boundaries)\n private static readonly MIN_TIME_CHANGE_MS = 100;\n \n constructor(private host: EFCaptionsTrack) {\n this.host.addController(this);\n }\n \n hostConnected(): void {\n this.startTimeUpdate();\n }\n \n hostDisconnected(): void {\n this.stopTimeUpdate();\n }\n \n private startTimeUpdate(): void {\n const update = () => {\n // Read current time from root timegroup\n const captions = this.host.element as EFCaptions;\n const rootTimegroup = captions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsData = captions?.unifiedCaptionsDataTask?.value;\n \n // Check if we actually need to update\n let shouldUpdate = false;\n \n if (captionsData) {\n const captionsLocalTimeMs = currentTimeMs - captions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n \n // Find current active word and segment indices\n const activeWordIndex = captionsData.word_segments.findIndex(\n (word) => captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end\n );\n const activeSegmentIndex = captionsData.segments.findIndex(\n (seg) => captionsLocalTimeSec >= seg.start && captionsLocalTimeSec < seg.end\n );\n \n // Update if active word or segment changed\n if (activeWordIndex !== this.lastActiveWordIndex) {\n this.lastActiveWordIndex = activeWordIndex;\n shouldUpdate = true;\n }\n if (activeSegmentIndex !== this.lastActiveSegmentIndex) {\n this.lastActiveSegmentIndex = activeSegmentIndex;\n shouldUpdate = true;\n }\n }\n \n // Also update if time changed significantly (for visual feedback during seek)\n const timeDelta = Math.abs(currentTimeMs - this.lastTimeMs);\n if (timeDelta >= CaptionsTimeController.MIN_TIME_CHANGE_MS) {\n shouldUpdate = true;\n }\n \n if (shouldUpdate) {\n this.lastTimeMs = currentTimeMs;\n this.host.requestUpdate();\n }\n \n this.animationFrameId = requestAnimationFrame(update);\n };\n update();\n }\n \n private stopTimeUpdate(): void {\n if (this.animationFrameId) {\n cancelAnimationFrame(this.animationFrameId);\n this.animationFrameId = undefined;\n }\n }\n}\n\n@customElement(\"ef-captions-track\")\nexport class EFCaptionsTrack extends TrackItem {\n static styles = [\n ...TrackItem.styles,\n css`\n .segment-block {\n position: absolute;\n border-radius: 3px;\n transition: box-shadow 0.15s ease, z-index 0.15s ease;\n cursor: pointer;\n overflow: visible;\n }\n \n .segment-block:hover {\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);\n z-index: 5;\n }\n \n .word-element {\n position: absolute;\n font-size: 9px;\n line-height: 1.2;\n white-space: nowrap;\n font-weight: 500;\n top: 50%;\n transform: translateY(-50%);\n padding: 2px 4px;\n border-radius: 2px;\n transition: all 0.1s ease;\n background: rgba(30, 41, 59, 0.9);\n color: rgb(226, 232, 240);\n z-index: 1;\n }\n \n .word-element.active {\n background: rgb(74, 222, 128);\n color: rgb(20, 30, 20);\n font-weight: 700;\n font-size: 10px;\n z-index: 10;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n }\n \n .word-element.future {\n background: rgba(51, 65, 85, 0.8);\n color: rgb(226, 232, 240);\n z-index: 5;\n }\n \n .segment-block.active .word-element:not(.active):not(.future) {\n color: rgb(203, 213, 225);\n background: rgba(30, 41, 59, 0.9);\n }\n \n /* Compact text mode - when words are too small to position individually */\n .segment-block.compact-text {\n display: flex;\n align-items: center;\n padding: 0 4px;\n overflow: hidden;\n /* Keep position: absolute from .segment-block for correct time-based positioning */\n }\n \n /* Allow overflow on hover for compact text */\n .segment-block.compact-text:hover {\n overflow: visible;\n z-index: 100;\n /* Expand to fit content on hover */\n width: max-content !important;\n min-width: max-content;\n background: rgba(34, 60, 40, 0.95) !important;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n \n .segment-text-compact {\n font-size: 10px;\n line-height: 1.2;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: rgb(226, 232, 240);\n width: 100%;\n }\n \n /* On hover, show full text */\n .segment-block.compact-text:hover .segment-text-compact {\n overflow: visible;\n text-overflow: clip;\n }\n \n .segment-block.compact-text.active .segment-text-compact {\n color: rgb(203, 213, 225);\n font-weight: 500;\n }\n \n .segment-duration-indicator {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 2px;\n background: currentColor;\n opacity: 0.2;\n border-radius: 0 0 3px 3px;\n }\n \n .segment-block.active .segment-duration-indicator {\n opacity: 0.4;\n height: 2px;\n }\n \n .word-marker {\n position: absolute;\n bottom: 0;\n width: 1px;\n height: 30%;\n background: rgba(255, 255, 255, 0.3);\n pointer-events: none;\n }\n \n .word-marker.active {\n background: rgba(255, 255, 255, 0.8);\n height: 50%;\n width: 2px;\n }\n `,\n ];\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = 0;\n \n // Controller ensures real-time updates during playback\n // The controller manages its own lifecycle via ReactiveController interface\n private _timeController = new CaptionsTimeController(this);\n \n private lastPixelsPerMs = 0;\n \n protected updated(changedProperties: Map<string | number | symbol, unknown>): void {\n super.updated(changedProperties);\n \n // Re-render when pixelsPerMs changes (zoom level changes)\n if (changedProperties.has(\"pixelsPerMs\")) {\n const currentPixelsPerMs = this.pixelsPerMs;\n if (currentPixelsPerMs !== this.lastPixelsPerMs) {\n this.lastPixelsPerMs = currentPixelsPerMs;\n // Force update to recalculate layout mode\n this.requestUpdate();\n }\n }\n }\n\n render() {\n const captions = this.element as EFCaptions;\n const captionsData = captions.unifiedCaptionsDataTask.value;\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n class=\"relative\"\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"trim-container relative mb-0 block text-nowrap border text-sm overflow-visible\"\n style=${styleMap({\n ...this.trimPortionStyles,\n height: \"var(--timeline-track-height, 22px)\",\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: \"var(--filmstrip-border)\",\n borderLeftColor: this.getElementTypeColor(),\n borderLeftWidth: \"3px\",\n minHeight: \"22px\",\n })}\n >\n ${this.renderCaptionsData(captionsData)}\n ${\n this.enableTrim\n ? html`<ef-trim-handles\n element-id=${(this.element as HTMLElement).id || \"\"}\n pixels-per-ms=${this.pixelsPerMs}\n trim-start-ms=${this.element.trimStartMs ?? 0}\n trim-end-ms=${this.element.trimEndMs ?? 0}\n intrinsic-duration-ms=${this.element.intrinsicDurationMs ?? this.element.durationMs}\n @trim-change=${this.handleTrimChange}\n ></ef-trim-handles>`\n : nothing\n }\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n \n\n renderCaptionsData(captionsData: Caption | null | undefined) {\n if (!captionsData) {\n return html``;\n }\n\n const captions = this.element as EFCaptions;\n const rootTimegroup = captions.rootTimegroup;\n // Use context current time for reactivity, fallback to rootTimegroup\n const currentTimeMs = this.contextCurrentTimeMs || rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - captions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n // Get element type color for captions\n const captionColor = \"rgb(34, 197, 94)\"; // Green for captions\n \n // Find active word for highlighting\n const activeWord = captionsData.word_segments.find(\n (word) =>\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end\n );\n \n // Render word markers for visual density indication (subtle)\n const wordMarkers = captionsData.word_segments.map((word) => {\n const wordStartPx = this.pixelsPerMs * word.start * 1000;\n const wordWidth = this.pixelsPerMs * (word.end - word.start) * 1000;\n const isActive = word === activeWord;\n \n // Only show markers if they're wide enough to be visible\n if (wordWidth < 1.5) return nothing;\n \n return html`<div\n class=\"word-marker ${isActive ? \"active\" : \"\"}\"\n style=${styleMap({\n left: `${wordStartPx}px`,\n })}\n ></div>`;\n });\n\n // Render semantic segment blocks with words positioned by their actual timing\n const segmentElements = captionsData.segments.map((segment) => {\n const isActiveSegment =\n captionsLocalTimeSec >= segment.start &&\n captionsLocalTimeSec < segment.end;\n \n const segmentStartPx = this.pixelsPerMs * segment.start * 1000;\n const segmentWidth = this.pixelsPerMs * (segment.end - segment.start) * 1000;\n const segmentDuration = (segment.end - segment.start) * 1000;\n \n // Get words in this segment, sorted by start time\n const wordsInSegment = captionsData.word_segments\n .filter(\n (word) =>\n word.start >= segment.start && word.end <= segment.end\n )\n .sort((a, b) => a.start - b.start);\n \n // Calculate visual density based on word count\n const density = Math.min(wordsInSegment.length / 10, 1);\n \n // Use actual measurement to determine if words can fit individually\n // Allow overlaps - only use compact mode when words can't be rendered at all\n const measurementResult = canWordsFitIndividually(\n wordsInSegment,\n segment.start,\n segmentWidth,\n this.pixelsPerMs,\n );\n \n const useCompactText = !measurementResult.fits;\n let avgSpacing = 0;\n \n // Calculate average spacing for font scaling (only if using positioned mode)\n if (!useCompactText && wordsInSegment.length > 1) {\n let totalSpacing = 0;\n let spacingCount = 0;\n \n for (let i = 0; i < wordsInSegment.length - 1; i++) {\n const word1 = wordsInSegment[i];\n const word2 = wordsInSegment[i + 1];\n if (!word1 || !word2) continue;\n \n const word1EndPx = this.pixelsPerMs * (word1.end - segment.start) * 1000;\n const word2StartPx = this.pixelsPerMs * (word2.start - segment.start) * 1000;\n const spacing = word2StartPx - word1EndPx;\n \n if (spacing > 0) {\n totalSpacing += spacing;\n spacingCount++;\n }\n }\n \n avgSpacing = spacingCount > 0 ? totalSpacing / spacingCount : 0;\n }\n \n // Calculate optimal font size for positioned words (if not using compact mode)\n const MIN_READABLE_FONT_SIZE = 6; // Minimum readable font size in pixels\n const baseFontSize = 9;\n const activeFontSize = 10;\n let scaledFontSize = baseFontSize;\n let scaledActiveFontSize = activeFontSize;\n \n if (!useCompactText && wordsInSegment.length > 1 && avgSpacing < 8) {\n // Scale down font size proportionally, but don't go below minimum\n const scaleFactor = Math.max(MIN_READABLE_FONT_SIZE / baseFontSize, avgSpacing / 8);\n scaledFontSize = Math.max(MIN_READABLE_FONT_SIZE, baseFontSize * scaleFactor);\n scaledActiveFontSize = Math.max(MIN_READABLE_FONT_SIZE, activeFontSize * scaleFactor);\n }\n \n // Render words positioned by their actual timing within the segment\n const renderWords = () => {\n if (useCompactText) {\n // Compact mode: show text that can overflow on hover\n return html`\n <span class=\"segment-text-compact\">${segment.text}</span>\n `;\n }\n \n // Positioned mode: render words at their time positions\n return wordsInSegment.map((word) => {\n // Position relative to segment start\n const wordOffsetFromSegmentStart = (word.start - segment.start) * 1000;\n const wordLeftPx = this.pixelsPerMs * wordOffsetFromSegmentStart;\n const wordWidthPx = this.pixelsPerMs * (word.end - word.start) * 1000;\n const isActive = word === activeWord;\n \n // Determine if word is in the future (after active word)\n const isFuture = activeWord && word.start > activeWord.end;\n \n return html`\n <span\n class=\"word-element ${isActive ? \"active\" : \"\"} ${isFuture ? \"future\" : \"\"}\"\n style=${styleMap({\n left: `${wordLeftPx}px`,\n minWidth: `${Math.max(wordWidthPx, 8)}px`,\n fontSize: isActive ? `${scaledActiveFontSize}px` : `${scaledFontSize}px`,\n top: \"50%\",\n })}\n title=\"Word: '${word.text}' (${word.start.toFixed(2)}s - ${word.end.toFixed(2)}s)\"\n >\n ${word.text.trim()}\n </span>\n `;\n });\n };\n \n return html`<div\n class=\"segment-block ${isActiveSegment ? \"active\" : \"\"} ${useCompactText ? \"compact-text\" : \"\"}\"\n style=${styleMap({\n left: `${segmentStartPx}px`,\n width: `${Math.max(segmentWidth, 4)}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isActiveSegment\n ? `rgba(34, 197, 94, ${0.3 + density * 0.2})`\n : `rgba(34, 197, 94, ${0.1 + density * 0.1})`,\n borderColor: isActiveSegment\n ? captionColor\n : `rgba(34, 197, 94, 0.4)`,\n minWidth: segmentWidth < 20 ? \"20px\" : \"auto\",\n })}\n title=${useCompactText \n ? `Caption: '${segment.text}'\\nDuration: ${this.formatDuration(segmentDuration)}\\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s`\n : `Caption: '${segment.text}'\\nDuration: ${this.formatDuration(segmentDuration)}\\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s\\nWords: ${wordsInSegment.length}`}\n @click=${(e: MouseEvent) => {\n e.stopPropagation();\n // Affordance: Click to seek to segment start\n if (rootTimegroup) {\n const absoluteStartTime = captions.startTimeMs + segment.start * 1000;\n rootTimegroup.currentTimeMs = absoluteStartTime;\n }\n }}\n >\n ${renderWords()}\n ${!useCompactText ? html`<div class=\"segment-duration-indicator\"></div>` : nothing}\n </div>`;\n });\n\n return html`\n ${wordMarkers}\n ${segmentElements}\n `;\n }\n\n renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {\n // Don't render child tracks - captions are consolidated into a single track\n // Child elements (active-word, segment, before-word, after-word) are handled\n // inline within the main captions track visualization\n return nothing;\n }\n}\n\n@customElement(\"ef-captions-active-word-track\")\nexport class EFCaptionsActiveWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.microphone)} Active Word\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-caption-bg);\">${word.text.trim()}</span>` : \"\"}\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-segment-track\")\nexport class EFCaptionsSegmentTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.textT)} Segment\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.segments.map((segment) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= segment.start &&\n captionsLocalTimeSec < segment.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * segment.start * 1000}px`,\n width: `${this.pixelsPerMs * (segment.end - segment.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-segment-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-segment-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-segment-bg);\">${segment.text}</span>` : \"\"}\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-before-word-track\")\nexport class EFCaptionsBeforeWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.arrowLeft)} Before\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-waveform-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-waveform-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-after-word-track\")\nexport class EFCaptionsAfterWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.arrowRight)} After\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-waveform-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-waveform-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-captions-track\": EFCaptionsTrack;\n \"ef-captions-active-word-track\": EFCaptionsActiveWordTrack;\n \"ef-captions-segment-track\": EFCaptionsSegmentTrack;\n \"ef-captions-before-word-track\": EFCaptionsBeforeWordTrack;\n \"ef-captions-after-word-track\": EFCaptionsAfterWordTrack;\n }\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"CaptionsTrack.js","names":["measurementCanvas: HTMLCanvasElement | null","measurementContext: CanvasRenderingContext2D | null","wordWidths: Array<{\n textWidth: number;\n timeWidth: number;\n startPx: number;\n endPx: number;\n }>","EFCaptionsTrack","EFCaptionsActiveWordTrack","EFCaptionsSegmentTrack","EFCaptionsBeforeWordTrack","EFCaptionsAfterWordTrack"],"sources":["../../../../src/gui/timeline/tracks/CaptionsTrack.ts"],"sourcesContent":["import { consume } from \"@lit/context\";\nimport { css, html, nothing, type TemplateResult } from \"lit\";\nimport { customElement } from \"lit/decorators.js\";\nimport { styleMap } from \"lit/directives/style-map.js\";\nimport {\n type Caption,\n EFCaptions,\n type WordSegment,\n} from \"../../../elements/EFCaptions.js\";\nimport { phosphorIcon, ICONS } from \"../../icons.js\";\nimport { currentTimeContext } from \"../../currentTimeContext.js\";\n// TrackItem must be pre-loaded before this module is imported\n// See preloadTracks.ts for the initialization sequence\nimport { TrackItem } from \"./TrackItem.js\";\nimport { getElementTypeColor } from \"../../theme.js\";\n\n// Shared canvas context for text measurement (avoids creating new canvas each time)\nlet measurementCanvas: HTMLCanvasElement | null = null;\nlet measurementContext: CanvasRenderingContext2D | null = null;\n// Cache for text measurements: key is \"text:fontSize:fontWeight\"\nconst textMeasurementCache = new Map<string, number>();\nconst MAX_CACHE_SIZE = 500;\n\n/**\n * Measure text width accurately using canvas.\n * Matches the actual font used in word elements (font-weight: 500).\n * Results are cached to avoid repeated measurements of the same text.\n */\nfunction measureTextWidth(\n text: string,\n fontSize: number,\n fontWeight: number = 500,\n): number {\n // Check cache first\n const cacheKey = `${text}:${fontSize}:${fontWeight}`;\n const cached = textMeasurementCache.get(cacheKey);\n if (cached !== undefined) {\n return cached;\n }\n\n // Initialize shared canvas context if needed\n if (!measurementCanvas || !measurementContext) {\n measurementCanvas = document.createElement(\"canvas\");\n measurementContext = measurementCanvas.getContext(\"2d\");\n }\n\n if (!measurementContext) {\n return text.length * fontSize * 0.6; // Fallback estimate\n }\n\n // Match the actual font used in word elements\n const fontFamily =\n getComputedStyle(document.body).fontFamily || \"system-ui, sans-serif\";\n measurementContext.font = `${fontWeight} ${fontSize}px ${fontFamily}`;\n const width = measurementContext.measureText(text).width;\n\n // Cache the result (with size limit to prevent memory leaks)\n if (textMeasurementCache.size >= MAX_CACHE_SIZE) {\n // Clear oldest entries (simple strategy: clear half the cache)\n const keysToDelete = Array.from(textMeasurementCache.keys()).slice(\n 0,\n MAX_CACHE_SIZE / 2,\n );\n for (const key of keysToDelete) {\n textMeasurementCache.delete(key);\n }\n }\n textMeasurementCache.set(cacheKey, width);\n\n return width;\n}\n\n/**\n * Check if words can fit individually within a segment when positioned by time\n *\n * Strategy: Allow overlaps as long as all words can be rendered within the container.\n * Only use compact mode when words are so cramped they can't be displayed at all.\n */\nfunction canWordsFitIndividually(\n words: WordSegment[],\n segmentStart: number,\n segmentWidthPx: number,\n pixelsPerMs: number,\n): { fits: boolean; reason?: string } {\n if (words.length === 0) {\n return { fits: false, reason: \"no words\" };\n }\n\n // Measure total text width of all words (as if rendered sequentially)\n let totalTextWidth = 0;\n const wordWidths: Array<{\n textWidth: number;\n timeWidth: number;\n startPx: number;\n endPx: number;\n }> = [];\n\n for (const word of words) {\n if (!word) continue;\n\n // Measure actual text width (with padding: 2px left + 2px right = 4px total)\n const textWidth = measureTextWidth(word.text.trim(), 9, 500) + 4;\n\n // Calculate time-based position and width\n const startPx = pixelsPerMs * (word.start - segmentStart) * 1000;\n const endPx = pixelsPerMs * (word.end - segmentStart) * 1000;\n const timeWidth = endPx - startPx;\n\n wordWidths.push({ textWidth, timeWidth, startPx, endPx });\n totalTextWidth += textWidth;\n }\n\n // Key insight: If total text width fits in segment, we can render words individually\n // even if they overlap based on their time positions\n // Use 90% threshold to account for some spacing/overlap\n if (totalTextWidth <= segmentWidthPx * 0.9) {\n // All words can fit - use positioned mode (overlaps are okay)\n return { fits: true };\n }\n\n // If total text doesn't fit, check if individual words are too narrow to be readable\n // If any word's time-based width is less than 30% of its text width, it's unreadable\n for (const { textWidth, timeWidth } of wordWidths) {\n if (timeWidth < textWidth * 0.3) {\n return {\n fits: false,\n reason: `word too narrow (${timeWidth.toFixed(1)}px < ${(textWidth * 0.3).toFixed(1)}px)`,\n };\n }\n }\n\n // If words are readable individually but total text is too wide,\n // check if they can still fit with overlaps\n // Find the maximum right edge of all words\n const maxEndPx = Math.max(...wordWidths.map((w) => w.endPx));\n\n // If the rightmost word fits within the segment, allow overlaps\n if (maxEndPx <= segmentWidthPx * 1.1) {\n return { fits: true };\n }\n\n // Words don't fit - use compact mode\n return {\n fits: false,\n reason: `words exceed segment (total text: ${totalTextWidth.toFixed(1)}px, segment: ${segmentWidthPx.toFixed(1)}px)`,\n };\n}\n\n@customElement(\"ef-captions-track\")\nexport class EFCaptionsTrack extends TrackItem {\n static styles = [\n ...TrackItem.styles,\n css`\n .segment-block {\n position: absolute;\n border-radius: 3px;\n transition: box-shadow 0.15s ease, z-index 0.15s ease;\n cursor: pointer;\n overflow: visible;\n }\n \n .segment-block:hover {\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);\n z-index: 5;\n }\n \n .word-element {\n position: absolute;\n font-size: 9px;\n line-height: 1.2;\n white-space: nowrap;\n font-weight: 500;\n top: 50%;\n transform: translateY(-50%);\n padding: 2px 4px;\n border-radius: 2px;\n transition: all 0.1s ease;\n background: var(--ef-color-bg-elevated);\n color: var(--ef-color-text);\n z-index: 1;\n }\n \n .word-element.active {\n background: var(--ef-color-success);\n color: rgb(20, 30, 20);\n font-weight: 700;\n font-size: 10px;\n z-index: 10;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n }\n \n .word-element.future {\n background: var(--ef-color-bg-inset);\n color: var(--ef-color-text);\n z-index: 5;\n }\n \n .segment-block.active .word-element:not(.active):not(.future) {\n color: var(--ef-color-text-muted);\n background: var(--ef-color-bg-elevated);\n }\n \n /* Compact text mode - when words are too small to position individually */\n .segment-block.compact-text {\n display: flex;\n align-items: center;\n padding: 0 4px;\n overflow: hidden;\n /* Keep position: absolute from .segment-block for correct time-based positioning */\n }\n \n /* Allow overflow on hover for compact text */\n .segment-block.compact-text:hover {\n overflow: visible;\n z-index: 100;\n /* Expand to fit content on hover */\n width: max-content !important;\n min-width: max-content;\n background: var(--ef-color-bg-elevated) !important;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n \n .segment-text-compact {\n font-size: 10px;\n line-height: 1.2;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--ef-color-text);\n width: 100%;\n }\n \n /* On hover, show full text */\n .segment-block.compact-text:hover .segment-text-compact {\n overflow: visible;\n text-overflow: clip;\n }\n \n .segment-block.compact-text.active .segment-text-compact {\n color: var(--ef-color-text-muted);\n font-weight: 500;\n }\n \n .segment-duration-indicator {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 2px;\n background: currentColor;\n opacity: 0.2;\n border-radius: 0 0 3px 3px;\n }\n \n .segment-block.active .segment-duration-indicator {\n opacity: 0.4;\n height: 2px;\n }\n \n .word-marker {\n position: absolute;\n bottom: 0;\n width: 1px;\n height: 30%;\n background: var(--ef-color-border-subtle);\n pointer-events: none;\n }\n \n .word-marker.active {\n background: var(--ef-color-text);\n height: 50%;\n width: 2px;\n }\n `,\n ];\n\n @consume({ context: currentTimeContext, subscribe: true })\n contextCurrentTimeMs = 0;\n\n private lastPixelsPerMs = 0;\n\n protected updated(\n changedProperties: Map<string | number | symbol, unknown>,\n ): void {\n super.updated(changedProperties);\n\n // Re-render when pixelsPerMs changes (zoom level changes)\n if (changedProperties.has(\"pixelsPerMs\")) {\n const currentPixelsPerMs = this.pixelsPerMs;\n if (currentPixelsPerMs !== this.lastPixelsPerMs) {\n this.lastPixelsPerMs = currentPixelsPerMs;\n // Force update to recalculate layout mode\n this.requestUpdate();\n }\n }\n }\n\n render() {\n const captions = this.element as EFCaptions;\n const captionsData = captions.unifiedCaptionsDataTask.value;\n\n return html`<div style=${styleMap(this.gutterStyles)}>\n <div\n class=\"relative\"\n style=\"background-color: var(--filmstrip-bg);\"\n ?data-focused=${this.isFocused}\n @mouseenter=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = this.element;\n }\n }}\n @mouseleave=${() => {\n if (this.focusContext) {\n this.focusContext.focusedElement = null;\n }\n }}\n >\n <div\n ?data-focused=${this.isFocused}\n class=\"trim-container relative mb-0 block text-nowrap border text-sm overflow-visible\"\n style=${styleMap({\n ...this.trimPortionStyles,\n height: \"var(--timeline-track-height, 22px)\",\n backgroundColor: this.isFocused\n ? \"var(--filmstrip-item-focused)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: \"var(--filmstrip-border)\",\n borderLeftColor: this.getElementTypeColor(),\n borderLeftWidth: \"3px\",\n minHeight: \"22px\",\n })}\n >\n ${this.renderCaptionsData(captionsData)}\n ${\n this.enableTrim\n ? html`<ef-trim-handles\n element-id=${(this.element as HTMLElement).id || \"\"}\n pixels-per-ms=${this.pixelsPerMs}\n trim-start-ms=${this.element.trimStartMs ?? 0}\n trim-end-ms=${this.element.trimEndMs ?? 0}\n intrinsic-duration-ms=${this.element.intrinsicDurationMs ?? this.element.durationMs}\n @trim-change=${this.handleTrimChange}\n ></ef-trim-handles>`\n : nothing\n }\n </div>\n </div>\n ${this.renderChildren()}\n </div>`;\n }\n\n renderCaptionsData(captionsData: Caption | null | undefined) {\n if (!captionsData) {\n return html``;\n }\n\n const captions = this.element as EFCaptions;\n const rootTimegroup = captions.rootTimegroup;\n // Use context current time for reactivity, fallback to rootTimegroup\n const currentTimeMs =\n this.contextCurrentTimeMs || rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - captions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n // Get element type color for captions using shared theme utility\n const captionColor = getElementTypeColor(\"captions\", this);\n\n // Find active word for highlighting\n const activeWord = captionsData.word_segments.find(\n (word) =>\n captionsLocalTimeSec >= word.start && captionsLocalTimeSec < word.end,\n );\n\n // Render word markers for visual density indication (subtle)\n const wordMarkers = captionsData.word_segments.map((word) => {\n const wordStartPx = this.pixelsPerMs * word.start * 1000;\n const wordWidth = this.pixelsPerMs * (word.end - word.start) * 1000;\n const isActive = word === activeWord;\n\n // Only show markers if they're wide enough to be visible\n if (wordWidth < 1.5) return nothing;\n\n return html`<div\n class=\"word-marker ${isActive ? \"active\" : \"\"}\"\n style=${styleMap({\n left: `${wordStartPx}px`,\n })}\n ></div>`;\n });\n\n // Render semantic segment blocks with words positioned by their actual timing\n const segmentElements = captionsData.segments.map((segment) => {\n const isActiveSegment =\n captionsLocalTimeSec >= segment.start &&\n captionsLocalTimeSec < segment.end;\n\n const segmentStartPx = this.pixelsPerMs * segment.start * 1000;\n const segmentWidth =\n this.pixelsPerMs * (segment.end - segment.start) * 1000;\n const segmentDuration = (segment.end - segment.start) * 1000;\n\n // Get words in this segment, sorted by start time\n const wordsInSegment = captionsData.word_segments\n .filter(\n (word) => word.start >= segment.start && word.end <= segment.end,\n )\n .sort((a, b) => a.start - b.start);\n\n // Calculate visual density based on word count\n const density = Math.min(wordsInSegment.length / 10, 1);\n\n // Use actual measurement to determine if words can fit individually\n // Allow overlaps - only use compact mode when words can't be rendered at all\n const measurementResult = canWordsFitIndividually(\n wordsInSegment,\n segment.start,\n segmentWidth,\n this.pixelsPerMs,\n );\n\n const useCompactText = !measurementResult.fits;\n let avgSpacing = 0;\n\n // Calculate average spacing for font scaling (only if using positioned mode)\n if (!useCompactText && wordsInSegment.length > 1) {\n let totalSpacing = 0;\n let spacingCount = 0;\n\n for (let i = 0; i < wordsInSegment.length - 1; i++) {\n const word1 = wordsInSegment[i];\n const word2 = wordsInSegment[i + 1];\n if (!word1 || !word2) continue;\n\n const word1EndPx =\n this.pixelsPerMs * (word1.end - segment.start) * 1000;\n const word2StartPx =\n this.pixelsPerMs * (word2.start - segment.start) * 1000;\n const spacing = word2StartPx - word1EndPx;\n\n if (spacing > 0) {\n totalSpacing += spacing;\n spacingCount++;\n }\n }\n\n avgSpacing = spacingCount > 0 ? totalSpacing / spacingCount : 0;\n }\n\n // Calculate optimal font size for positioned words (if not using compact mode)\n const MIN_READABLE_FONT_SIZE = 6; // Minimum readable font size in pixels\n const baseFontSize = 9;\n const activeFontSize = 10;\n let scaledFontSize = baseFontSize;\n let scaledActiveFontSize = activeFontSize;\n\n if (!useCompactText && wordsInSegment.length > 1 && avgSpacing < 8) {\n // Scale down font size proportionally, but don't go below minimum\n const scaleFactor = Math.max(\n MIN_READABLE_FONT_SIZE / baseFontSize,\n avgSpacing / 8,\n );\n scaledFontSize = Math.max(\n MIN_READABLE_FONT_SIZE,\n baseFontSize * scaleFactor,\n );\n scaledActiveFontSize = Math.max(\n MIN_READABLE_FONT_SIZE,\n activeFontSize * scaleFactor,\n );\n }\n\n // Render words positioned by their actual timing within the segment\n const renderWords = () => {\n if (useCompactText) {\n // Compact mode: show text that can overflow on hover\n return html`\n <span class=\"segment-text-compact\">${segment.text}</span>\n `;\n }\n\n // Positioned mode: render words at their time positions\n return wordsInSegment.map((word) => {\n // Position relative to segment start\n const wordOffsetFromSegmentStart =\n (word.start - segment.start) * 1000;\n const wordLeftPx = this.pixelsPerMs * wordOffsetFromSegmentStart;\n const wordWidthPx = this.pixelsPerMs * (word.end - word.start) * 1000;\n const isActive = word === activeWord;\n\n // Determine if word is in the future (after active word)\n const isFuture = activeWord && word.start > activeWord.end;\n\n return html`\n <span\n class=\"word-element ${isActive ? \"active\" : \"\"} ${isFuture ? \"future\" : \"\"}\"\n style=${styleMap({\n left: `${wordLeftPx}px`,\n minWidth: `${Math.max(wordWidthPx, 8)}px`,\n fontSize: isActive\n ? `${scaledActiveFontSize}px`\n : `${scaledFontSize}px`,\n top: \"50%\",\n })}\n title=\"Word: '${word.text}' (${word.start.toFixed(2)}s - ${word.end.toFixed(2)}s)\"\n >\n ${word.text.trim()}\n </span>\n `;\n });\n };\n\n return html`<div\n class=\"segment-block ${isActiveSegment ? \"active\" : \"\"} ${useCompactText ? \"compact-text\" : \"\"}\"\n style=${styleMap({\n left: `${segmentStartPx}px`,\n width: `${Math.max(segmentWidth, 4)}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isActiveSegment\n ? `color-mix(in srgb, var(--ef-color-type-captions) ${30 + density * 20}%, transparent)`\n : `color-mix(in srgb, var(--ef-color-type-captions) ${10 + density * 10}%, transparent)`,\n borderColor: isActiveSegment\n ? captionColor\n : `color-mix(in srgb, var(--ef-color-type-captions) 40%, transparent)`,\n minWidth: segmentWidth < 20 ? \"20px\" : \"auto\",\n })}\n title=${\n useCompactText\n ? `Caption: '${segment.text}'\\nDuration: ${this.formatDuration(segmentDuration)}\\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s`\n : `Caption: '${segment.text}'\\nDuration: ${this.formatDuration(segmentDuration)}\\nTime: ${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s\\nWords: ${wordsInSegment.length}`\n }\n @click=${(e: MouseEvent) => {\n e.stopPropagation();\n // Affordance: Click to seek to segment start\n if (rootTimegroup) {\n const absoluteStartTime =\n captions.startTimeMs + segment.start * 1000;\n rootTimegroup.currentTimeMs = absoluteStartTime;\n }\n }}\n >\n ${renderWords()}\n ${!useCompactText ? html`<div class=\"segment-duration-indicator\"></div>` : nothing}\n </div>`;\n });\n\n return html`\n ${wordMarkers}\n ${segmentElements}\n `;\n }\n\n renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {\n // Don't render child tracks - captions are consolidated into a single track\n // Child elements (active-word, segment, before-word, after-word) are handled\n // inline within the main captions track visualization\n return nothing;\n }\n}\n\n@customElement(\"ef-captions-active-word-track\")\nexport class EFCaptionsActiveWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.microphone)} Active Word\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-caption-bg);\">${word.text.trim()}</span>` : \"\"}\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-segment-track\")\nexport class EFCaptionsSegmentTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.textT)} Segment\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.segments.map((segment) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= segment.start &&\n captionsLocalTimeSec < segment.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * segment.start * 1000}px`,\n width: `${this.pixelsPerMs * (segment.end - segment.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-segment-bg)\"\n : \"var(--filmstrip-item-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-segment-border)\"\n : \"var(--filmstrip-border)\",\n })}\n title=\"Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)\"\n >\n ${isCurrentlyActive ? html`<span class=\"px-0.5 text-[8px] font-bold whitespace-nowrap\" style=\"background-color: var(--filmstrip-segment-bg);\">${segment.text}</span>` : \"\"}\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-before-word-track\")\nexport class EFCaptionsBeforeWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.arrowLeft)} Before\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-waveform-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-waveform-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\n@customElement(\"ef-captions-after-word-track\")\nexport class EFCaptionsAfterWordTrack extends TrackItem {\n get captionsTrackStyles() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n return {\n position: \"relative\",\n left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,\n width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,\n };\n }\n\n render() {\n const parentCaptions = this.element.closest(\"ef-captions\") as EFCaptions;\n const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;\n\n if (!captionsData) {\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"border h-[1.1rem] mb-[1px] text-xs\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${phosphorIcon(ICONS.arrowRight)} After\n </div>\n </div>`;\n }\n\n const rootTimegroup = parentCaptions.rootTimegroup;\n const currentTimeMs = rootTimegroup?.currentTimeMs || 0;\n const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;\n const captionsLocalTimeSec = captionsLocalTimeMs / 1000;\n\n return html`<div style=${styleMap(this.captionsTrackStyles)}>\n <div class=\"relative border h-[1.1rem] mb-[1px] w-full\" style=\"background-color: var(--filmstrip-bg); border-color: var(--filmstrip-border);\">\n ${captionsData.word_segments.map((word) => {\n const isCurrentlyActive =\n captionsLocalTimeSec >= word.start &&\n captionsLocalTimeSec < word.end;\n\n return html`<div\n class=\"absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? \"font-bold z-[5]\" : \"\"}\"\n style=${styleMap({\n left: `${this.pixelsPerMs * word.start * 1000}px`,\n width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,\n height: \"100%\",\n top: \"0px\",\n backgroundColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-bg)\"\n : \"var(--filmstrip-waveform-bg)\",\n borderColor: isCurrentlyActive\n ? \"var(--filmstrip-caption-border)\"\n : \"var(--filmstrip-waveform-border)\",\n })}\n title=\"Word: '${word.text}' (${word.start}s - ${word.end}s)\"\n >\n </div>`;\n })}\n </div>\n </div>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ef-captions-track\": EFCaptionsTrack;\n \"ef-captions-active-word-track\": EFCaptionsActiveWordTrack;\n \"ef-captions-segment-track\": EFCaptionsSegmentTrack;\n \"ef-captions-before-word-track\": EFCaptionsBeforeWordTrack;\n \"ef-captions-after-word-track\": EFCaptionsAfterWordTrack;\n }\n}\n"],"mappings":";;;;;;;;;;;AAiBA,IAAIA,oBAA8C;AAClD,IAAIC,qBAAsD;AAE1D,MAAM,uCAAuB,IAAI,KAAqB;AACtD,MAAM,iBAAiB;;;;;;AAOvB,SAAS,iBACP,MACA,UACA,aAAqB,KACb;CAER,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,GAAG;CACxC,MAAM,SAAS,qBAAqB,IAAI,SAAS;AACjD,KAAI,WAAW,OACb,QAAO;AAIT,KAAI,CAAC,qBAAqB,CAAC,oBAAoB;AAC7C,sBAAoB,SAAS,cAAc,SAAS;AACpD,uBAAqB,kBAAkB,WAAW,KAAK;;AAGzD,KAAI,CAAC,mBACH,QAAO,KAAK,SAAS,WAAW;AAMlC,oBAAmB,OAAO,GAAG,WAAW,GAAG,SAAS,KADlD,iBAAiB,SAAS,KAAK,CAAC,cAAc;CAEhD,MAAM,QAAQ,mBAAmB,YAAY,KAAK,CAAC;AAGnD,KAAI,qBAAqB,QAAQ,gBAAgB;EAE/C,MAAM,eAAe,MAAM,KAAK,qBAAqB,MAAM,CAAC,CAAC,MAC3D,GACA,iBAAiB,EAClB;AACD,OAAK,MAAM,OAAO,aAChB,sBAAqB,OAAO,IAAI;;AAGpC,sBAAqB,IAAI,UAAU,MAAM;AAEzC,QAAO;;;;;;;;AAST,SAAS,wBACP,OACA,cACA,gBACA,aACoC;AACpC,KAAI,MAAM,WAAW,EACnB,QAAO;EAAE,MAAM;EAAO,QAAQ;EAAY;CAI5C,IAAI,iBAAiB;CACrB,MAAMC,aAKD,EAAE;AAEP,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,CAAC,KAAM;EAGX,MAAM,YAAY,iBAAiB,KAAK,KAAK,MAAM,EAAE,GAAG,IAAI,GAAG;EAG/D,MAAM,UAAU,eAAe,KAAK,QAAQ,gBAAgB;EAC5D,MAAM,QAAQ,eAAe,KAAK,MAAM,gBAAgB;EACxD,MAAM,YAAY,QAAQ;AAE1B,aAAW,KAAK;GAAE;GAAW;GAAW;GAAS;GAAO,CAAC;AACzD,oBAAkB;;AAMpB,KAAI,kBAAkB,iBAAiB,GAErC,QAAO,EAAE,MAAM,MAAM;AAKvB,MAAK,MAAM,EAAE,WAAW,eAAe,WACrC,KAAI,YAAY,YAAY,GAC1B,QAAO;EACL,MAAM;EACN,QAAQ,oBAAoB,UAAU,QAAQ,EAAE,CAAC,QAAQ,YAAY,IAAK,QAAQ,EAAE,CAAC;EACtF;AAUL,KAHiB,KAAK,IAAI,GAAG,WAAW,KAAK,MAAM,EAAE,MAAM,CAAC,IAG5C,iBAAiB,IAC/B,QAAO,EAAE,MAAM,MAAM;AAIvB,QAAO;EACL,MAAM;EACN,QAAQ,qCAAqC,eAAe,QAAQ,EAAE,CAAC,eAAe,eAAe,QAAQ,EAAE,CAAC;EACjH;;AAII,4BAAMC,0BAAwB,UAAU;;;8BAgItB;yBAEG;;;gBAjIV,CACd,GAAG,UAAU,QACb,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MA0HJ;;CAOD,AAAU,QACR,mBACM;AACN,QAAM,QAAQ,kBAAkB;AAGhC,MAAI,kBAAkB,IAAI,cAAc,EAAE;GACxC,MAAM,qBAAqB,KAAK;AAChC,OAAI,uBAAuB,KAAK,iBAAiB;AAC/C,SAAK,kBAAkB;AAEvB,SAAK,eAAe;;;;CAK1B,SAAS;EAEP,MAAM,eADW,KAAK,QACQ,wBAAwB;AAEtD,SAAO,IAAI,cAAc,SAAS,KAAK,aAAa,CAAC;;;;wBAIjC,KAAK,UAAU;4BACX;AAClB,OAAI,KAAK,aACP,MAAK,aAAa,iBAAiB,KAAK;IAE1C;4BACkB;AAClB,OAAI,KAAK,aACP,MAAK,aAAa,iBAAiB;IAErC;;;0BAGgB,KAAK,UAAU;;kBAEvB,SAAS;GACf,GAAG,KAAK;GACR,QAAQ;GACR,iBAAiB,KAAK,YAClB,kCACA;GACJ,aAAa;GACb,iBAAiB,KAAK,qBAAqB;GAC3C,iBAAiB;GACjB,WAAW;GACZ,CAAC,CAAC;;YAED,KAAK,mBAAmB,aAAa,CAAC;YAEtC,KAAK,aACD,IAAI;6BACU,KAAK,QAAwB,MAAM,GAAG;gCACpC,KAAK,YAAY;gCACjB,KAAK,QAAQ,eAAe,EAAE;8BAChC,KAAK,QAAQ,aAAa,EAAE;wCAClB,KAAK,QAAQ,uBAAuB,KAAK,QAAQ,WAAW;+BACrE,KAAK,iBAAiB;qCAErC,QACL;;;QAGH,KAAK,gBAAgB,CAAC;;;CAI5B,mBAAmB,cAA0C;AAC3D,MAAI,CAAC,aACH,QAAO,IAAI;EAGb,MAAM,WAAW,KAAK;EACtB,MAAM,gBAAgB,SAAS;EAK/B,MAAM,yBAFJ,KAAK,wBAAwB,eAAe,iBAAiB,KACnB,SAAS,eACF;EAGnD,MAAM,eAAe,oBAAoB,YAAY,KAAK;EAG1D,MAAM,aAAa,aAAa,cAAc,MAC3C,SACC,wBAAwB,KAAK,SAAS,uBAAuB,KAAK,IACrE;AA+KD,SAAO,IAAI;QA5KS,aAAa,cAAc,KAAK,SAAS;GAC3D,MAAM,cAAc,KAAK,cAAc,KAAK,QAAQ;GACpD,MAAM,YAAY,KAAK,eAAe,KAAK,MAAM,KAAK,SAAS;GAC/D,MAAM,WAAW,SAAS;AAG1B,OAAI,YAAY,IAAK,QAAO;AAE5B,UAAO,IAAI;6BACY,WAAW,WAAW,GAAG;gBACtC,SAAS,EACf,MAAM,GAAG,YAAY,KACtB,CAAC,CAAC;;IAEL,CA+Jc;QA5JQ,aAAa,SAAS,KAAK,YAAY;GAC7D,MAAM,kBACJ,wBAAwB,QAAQ,SAChC,uBAAuB,QAAQ;GAEjC,MAAM,iBAAiB,KAAK,cAAc,QAAQ,QAAQ;GAC1D,MAAM,eACJ,KAAK,eAAe,QAAQ,MAAM,QAAQ,SAAS;GACrD,MAAM,mBAAmB,QAAQ,MAAM,QAAQ,SAAS;GAGxD,MAAM,iBAAiB,aAAa,cACjC,QACE,SAAS,KAAK,SAAS,QAAQ,SAAS,KAAK,OAAO,QAAQ,IAC9D,CACA,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM;GAGpC,MAAM,UAAU,KAAK,IAAI,eAAe,SAAS,IAAI,EAAE;GAWvD,MAAM,iBAAiB,CAPG,wBACxB,gBACA,QAAQ,OACR,cACA,KAAK,YACN,CAEyC;GAC1C,IAAI,aAAa;AAGjB,OAAI,CAAC,kBAAkB,eAAe,SAAS,GAAG;IAChD,IAAI,eAAe;IACnB,IAAI,eAAe;AAEnB,SAAK,IAAI,IAAI,GAAG,IAAI,eAAe,SAAS,GAAG,KAAK;KAClD,MAAM,QAAQ,eAAe;KAC7B,MAAM,QAAQ,eAAe,IAAI;AACjC,SAAI,CAAC,SAAS,CAAC,MAAO;KAEtB,MAAM,aACJ,KAAK,eAAe,MAAM,MAAM,QAAQ,SAAS;KAGnD,MAAM,UADJ,KAAK,eAAe,MAAM,QAAQ,QAAQ,SAAS,MACtB;AAE/B,SAAI,UAAU,GAAG;AACf,sBAAgB;AAChB;;;AAIJ,iBAAa,eAAe,IAAI,eAAe,eAAe;;GAIhE,MAAM,yBAAyB;GAC/B,MAAM,eAAe;GACrB,MAAM,iBAAiB;GACvB,IAAI,iBAAiB;GACrB,IAAI,uBAAuB;AAE3B,OAAI,CAAC,kBAAkB,eAAe,SAAS,KAAK,aAAa,GAAG;IAElE,MAAM,cAAc,KAAK,IACvB,yBAAyB,cACzB,aAAa,EACd;AACD,qBAAiB,KAAK,IACpB,wBACA,eAAe,YAChB;AACD,2BAAuB,KAAK,IAC1B,wBACA,iBAAiB,YAClB;;GAIH,MAAM,oBAAoB;AACxB,QAAI,eAEF,QAAO,IAAI;iDAC4B,QAAQ,KAAK;;AAKtD,WAAO,eAAe,KAAK,SAAS;KAElC,MAAM,8BACH,KAAK,QAAQ,QAAQ,SAAS;KACjC,MAAM,aAAa,KAAK,cAAc;KACtC,MAAM,cAAc,KAAK,eAAe,KAAK,MAAM,KAAK,SAAS;KACjE,MAAM,WAAW,SAAS;KAG1B,MAAM,WAAW,cAAc,KAAK,QAAQ,WAAW;AAEvD,YAAO,IAAI;;oCAEe,WAAW,WAAW,GAAG,GAAG,WAAW,WAAW,GAAG;sBACnE,SAAS;MACf,MAAM,GAAG,WAAW;MACpB,UAAU,GAAG,KAAK,IAAI,aAAa,EAAE,CAAC;MACtC,UAAU,WACN,GAAG,qBAAqB,MACxB,GAAG,eAAe;MACtB,KAAK;MACN,CAAC,CAAC;8BACa,KAAK,KAAK,KAAK,KAAK,MAAM,QAAQ,EAAE,CAAC,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;;gBAE7E,KAAK,KAAK,MAAM,CAAC;;;MAGvB;;AAGJ,UAAO,IAAI;+BACc,kBAAkB,WAAW,GAAG,GAAG,iBAAiB,iBAAiB,GAAG;gBACvF,SAAS;IACf,MAAM,GAAG,eAAe;IACxB,OAAO,GAAG,KAAK,IAAI,cAAc,EAAE,CAAC;IACpC,QAAQ;IACR,KAAK;IACL,iBAAiB,kBACb,oDAAoD,KAAK,UAAU,GAAG,mBACtE,oDAAoD,KAAK,UAAU,GAAG;IAC1E,aAAa,kBACT,eACA;IACJ,UAAU,eAAe,KAAK,SAAS;IACxC,CAAC,CAAC;gBAED,iBACI,aAAa,QAAQ,KAAK,eAAe,KAAK,eAAe,gBAAgB,CAAC,UAAU,QAAQ,MAAM,QAAQ,EAAE,CAAC,MAAM,QAAQ,IAAI,QAAQ,EAAE,CAAC,KAC9I,aAAa,QAAQ,KAAK,eAAe,KAAK,eAAe,gBAAgB,CAAC,UAAU,QAAQ,MAAM,QAAQ,EAAE,CAAC,MAAM,QAAQ,IAAI,QAAQ,EAAE,CAAC,YAAY,eAAe,SAC9K;kBACS,MAAkB;AAC1B,MAAE,iBAAiB;AAEnB,QAAI,cAGF,eAAc,gBADZ,SAAS,cAAc,QAAQ,QAAQ;KAG3C;;UAEA,aAAa,CAAC;UACd,CAAC,iBAAiB,IAAI,mDAAmD,QAAQ;;IAErF,CAIkB;;;CAItB,iBAA6E;AAI3E,SAAO;;;YAxRR,QAAQ;CAAE,SAAS;CAAoB,WAAW;CAAM,CAAC;8BAhI3D,cAAc,oBAAoB;AA6Z5B,sCAAMC,oCAAkC,UAAU;CACvD,IAAI,sBAAsB;EACxB,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,cAAc;AAC1D,SAAO;GACL,UAAU;GACV,MAAM,GAAG,KAAK,eAAe,gBAAgB,2BAA2B,GAAG;GAC3E,OAAO,GAAG,KAAK,eAAe,gBAAgB,cAAc,GAAG;GAChE;;CAGH,SAAS;EACP,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,cAAc;EAC1D,MAAM,eAAe,gBAAgB,wBAAwB;AAE7D,MAAI,CAAC,aACH,QAAO,IAAI,cAAc,SAAS,KAAK,oBAAoB,CAAC;;YAEtD,aAAa,MAAM,WAAW,CAAC;;;EAQvC,MAAM,yBAHgB,eAAe,eACA,iBAAiB,KACV,eAAe,eACR;AAEnD,SAAO,IAAI,cAAc,SAAS,KAAK,oBAAoB,CAAC;;UAEtD,aAAa,cAAc,KAAK,SAAS;GACzC,MAAM,oBACJ,wBAAwB,KAAK,SAC7B,uBAAuB,KAAK;AAE9B,UAAO,IAAI;gFAC2D,oBAAoB,oBAAoB,GAAG;oBACvG,SAAS;IACf,MAAM,GAAG,KAAK,cAAc,KAAK,QAAQ,IAAK;IAC9C,OAAO,GAAG,KAAK,eAAe,KAAK,MAAM,KAAK,SAAS,IAAK;IAC5D,QAAQ;IACR,KAAK;IACL,iBAAiB,oBACb,gCACA;IACJ,aAAa,oBACT,oCACA;IACL,CAAC,CAAC;4BACa,KAAK,KAAK,KAAK,KAAK,MAAM,MAAM,KAAK,IAAI;;cAEvD,oBAAoB,IAAI,sHAAsH,KAAK,KAAK,MAAM,CAAC,WAAW,GAAG;;IAEjL,CAAC;;;;;wCArDV,cAAc,gCAAgC;AA4DxC,mCAAMC,iCAA+B,UAAU;CACpD,IAAI,sBAAsB;EACxB,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,cAAc;AAC1D,SAAO;GACL,UAAU;GACV,MAAM,GAAG,KAAK,eAAe,gBAAgB,2BAA2B,GAAG;GAC3E,OAAO,GAAG,KAAK,eAAe,gBAAgB,cAAc,GAAG;GAChE;;CAGH,SAAS;EACP,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,cAAc;EAC1D,MAAM,eAAe,gBAAgB,wBAAwB;AAE7D,MAAI,CAAC,aACH,QAAO,IAAI,cAAc,SAAS,KAAK,oBAAoB,CAAC;;YAEtD,aAAa,MAAM,MAAM,CAAC;;;EAQlC,MAAM,yBAHgB,eAAe,eACA,iBAAiB,KACV,eAAe,eACR;AAEnD,SAAO,IAAI,cAAc,SAAS,KAAK,oBAAoB,CAAC;;UAEtD,aAAa,SAAS,KAAK,YAAY;GACvC,MAAM,oBACJ,wBAAwB,QAAQ,SAChC,uBAAuB,QAAQ;AAEjC,UAAO,IAAI;gFAC2D,oBAAoB,oBAAoB,GAAG;oBACvG,SAAS;IACf,MAAM,GAAG,KAAK,cAAc,QAAQ,QAAQ,IAAK;IACjD,OAAO,GAAG,KAAK,eAAe,QAAQ,MAAM,QAAQ,SAAS,IAAK;IAClE,QAAQ;IACR,KAAK;IACL,iBAAiB,oBACb,gCACA;IACJ,aAAa,oBACT,oCACA;IACL,CAAC,CAAC;+BACgB,QAAQ,KAAK,KAAK,QAAQ,MAAM,MAAM,QAAQ,IAAI;;cAEnE,oBAAoB,IAAI,sHAAsH,QAAQ,KAAK,WAAW,GAAG;;IAE7K,CAAC;;;;;qCArDV,cAAc,4BAA4B;AA4DpC,sCAAMC,oCAAkC,UAAU;CACvD,IAAI,sBAAsB;EACxB,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,cAAc;AAC1D,SAAO;GACL,UAAU;GACV,MAAM,GAAG,KAAK,eAAe,gBAAgB,2BAA2B,GAAG;GAC3E,OAAO,GAAG,KAAK,eAAe,gBAAgB,cAAc,GAAG;GAChE;;CAGH,SAAS;EACP,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,cAAc;EAC1D,MAAM,eAAe,gBAAgB,wBAAwB;AAE7D,MAAI,CAAC,aACH,QAAO,IAAI,cAAc,SAAS,KAAK,oBAAoB,CAAC;;YAEtD,aAAa,MAAM,UAAU,CAAC;;;EAQtC,MAAM,yBAHgB,eAAe,eACA,iBAAiB,KACV,eAAe,eACR;AAEnD,SAAO,IAAI,cAAc,SAAS,KAAK,oBAAoB,CAAC;;UAEtD,aAAa,cAAc,KAAK,SAAS;GACzC,MAAM,oBACJ,wBAAwB,KAAK,SAC7B,uBAAuB,KAAK;AAE9B,UAAO,IAAI;gFAC2D,oBAAoB,oBAAoB,GAAG;oBACvG,SAAS;IACf,MAAM,GAAG,KAAK,cAAc,KAAK,QAAQ,IAAK;IAC9C,OAAO,GAAG,KAAK,eAAe,KAAK,MAAM,KAAK,SAAS,IAAK;IAC5D,QAAQ;IACR,KAAK;IACL,iBAAiB,oBACb,gCACA;IACJ,aAAa,oBACT,oCACA;IACL,CAAC,CAAC;4BACa,KAAK,KAAK,KAAK,KAAK,MAAM,MAAM,KAAK,IAAI;;;IAG3D,CAAC;;;;;wCApDV,cAAc,gCAAgC;AA2DxC,qCAAMC,mCAAiC,UAAU;CACtD,IAAI,sBAAsB;EACxB,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,cAAc;AAC1D,SAAO;GACL,UAAU;GACV,MAAM,GAAG,KAAK,eAAe,gBAAgB,2BAA2B,GAAG;GAC3E,OAAO,GAAG,KAAK,eAAe,gBAAgB,cAAc,GAAG;GAChE;;CAGH,SAAS;EACP,MAAM,iBAAiB,KAAK,QAAQ,QAAQ,cAAc;EAC1D,MAAM,eAAe,gBAAgB,wBAAwB;AAE7D,MAAI,CAAC,aACH,QAAO,IAAI,cAAc,SAAS,KAAK,oBAAoB,CAAC;;YAEtD,aAAa,MAAM,WAAW,CAAC;;;EAQvC,MAAM,yBAHgB,eAAe,eACA,iBAAiB,KACV,eAAe,eACR;AAEnD,SAAO,IAAI,cAAc,SAAS,KAAK,oBAAoB,CAAC;;UAEtD,aAAa,cAAc,KAAK,SAAS;GACzC,MAAM,oBACJ,wBAAwB,KAAK,SAC7B,uBAAuB,KAAK;AAE9B,UAAO,IAAI;gFAC2D,oBAAoB,oBAAoB,GAAG;oBACvG,SAAS;IACf,MAAM,GAAG,KAAK,cAAc,KAAK,QAAQ,IAAK;IAC9C,OAAO,GAAG,KAAK,eAAe,KAAK,MAAM,KAAK,SAAS,IAAK;IAC5D,QAAQ;IACR,KAAK;IACL,iBAAiB,oBACb,gCACA;IACJ,aAAa,oBACT,oCACA;IACL,CAAC,CAAC;4BACa,KAAK,KAAK,KAAK,KAAK,MAAM,MAAM,KAAK,IAAI;;;IAG3D,CAAC;;;;;uCApDV,cAAc,+BAA+B"}
@@ -0,0 +1,52 @@
1
+ import { TimelineState } from "../timelineStateContext.js";
2
+ import { PreviewSettings } from "../../previewSettingsContext.js";
3
+ import * as lit36 from "lit";
4
+ import { LitElement } from "lit";
5
+ import * as lit_html33 from "lit-html";
6
+
7
+ //#region src/gui/timeline/tracks/EFThumbnailStrip.d.ts
8
+ declare const EFThumbnailStrip_base: typeof LitElement;
9
+ /**
10
+ * Thumbnail strip component that renders thumbnails for video or timegroup elements.
11
+ *
12
+ * Features:
13
+ * - Targets ef-video or root ef-timegroup via target attribute
14
+ * - Batch video thumbnail extraction via ThumbnailExtractor
15
+ * - Canvas rendering for timegroups at low resolution
16
+ * - Viewport-based lazy loading with scroll calculation
17
+ * - Fixed visual spacing (consistent at all zoom levels)
18
+ * - Error indicators for failed thumbnails
19
+ */
20
+ declare class EFThumbnailStrip extends EFThumbnailStrip_base {
21
+ #private;
22
+ static styles: lit36.CSSResult[];
23
+ target: string;
24
+ targetElement: Element | null;
25
+ thumbnailHeight: number;
26
+ thumbnailSpacingPx: number;
27
+ pixelsPerMs: number | null;
28
+ useIntrinsicDuration: boolean;
29
+ timelineState?: TimelineState;
30
+ previewSettings?: PreviewSettings;
31
+ thumbnailDimensions: {
32
+ width: number;
33
+ height: number;
34
+ };
35
+ /**
36
+ * Check if target is valid (EFVideo or root EFTimegroup)
37
+ */
38
+ get isValidTarget(): boolean;
39
+ connectedCallback(): void;
40
+ disconnectedCallback(): void;
41
+ protected willUpdate(changedProperties: Map<string | number | symbol, unknown>): void;
42
+ updated(changedProperties: Map<string | number | symbol, unknown>): void;
43
+ render(): lit_html33.TemplateResult<1>;
44
+ }
45
+ declare global {
46
+ interface HTMLElementTagNameMap {
47
+ "ef-thumbnail-strip": EFThumbnailStrip;
48
+ }
49
+ }
50
+ //#endregion
51
+ export { EFThumbnailStrip };
52
+ //# sourceMappingURL=EFThumbnailStrip.d.ts.map