@dailies-studio/shot-editor 0.1.0 → 0.2.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 (432) hide show
  1. package/dist/.vite/manifest.json +382 -0
  2. package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
  3. package/dist/assets/AgentPanel-DJEphah0.css +1 -0
  4. package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
  5. package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
  6. package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
  7. package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
  8. package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
  9. package/dist/assets/CurvePane-BjFVabKX.css +1 -0
  10. package/dist/assets/CurvePane-R22WzllJ.js +1 -0
  11. package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
  12. package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
  13. package/dist/assets/FontPicker-v0IOIArM.css +1 -0
  14. package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
  15. package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
  16. package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
  17. package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
  18. package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
  19. package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
  20. package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
  21. package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
  22. package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
  23. package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
  24. package/dist/assets/SoundMode-BmcZru3i.js +3 -0
  25. package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
  26. package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
  27. package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
  28. package/dist/assets/board-B88EPuLO.css +1 -0
  29. package/dist/assets/board-CL-he7SL.js +3 -0
  30. package/dist/assets/components-0oOSPN0l.js +1 -0
  31. package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
  32. package/dist/assets/googleFonts-BNon6M7X.js +1 -0
  33. package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
  34. package/dist/assets/index-4F5NQmI8.css +1 -0
  35. package/dist/assets/index-BGHQGV5z.js +117 -0
  36. package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  37. package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  38. package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  39. package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  40. package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  41. package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  42. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  43. package/dist/assets/libraryModel-CpIwfFha.js +1 -0
  44. package/dist/assets/lucide-6iK64tTB.js +9319 -0
  45. package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
  46. package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
  47. package/dist/assets/webmcp-BsB9gxWC.js +2 -0
  48. package/dist/index.html +34 -3
  49. package/package.json +12 -9
  50. package/src/addFamilies.ts +66 -0
  51. package/src/api.ts +1631 -55
  52. package/src/app/App.tsx +977 -198
  53. package/src/app/lazyPanel.tsx +137 -0
  54. package/src/app/lazySurfaces.tsx +203 -0
  55. package/src/audio/index.tsx +41 -10
  56. package/src/audio/meter.ts +485 -0
  57. package/src/audio/meterMath.ts +273 -0
  58. package/src/audio/meterStore.ts +155 -0
  59. package/src/audio/offlineMix.ts +289 -0
  60. package/src/audio/preview.ts +928 -118
  61. package/src/bridge.ts +15 -5
  62. package/src/bridgeTypes.ts +1611 -36
  63. package/src/channels.ts +17 -32
  64. package/src/fonts/googleFonts.ts +4 -0
  65. package/src/hostApi.ts +174 -0
  66. package/src/icons.tsx +770 -0
  67. package/src/layerWords.ts +61 -0
  68. package/src/main.tsx +56 -4
  69. package/src/panels/ResizeHandle.tsx +33 -43
  70. package/src/panels/agent/AgentDots.tsx +36 -0
  71. package/src/panels/agent/AgentPanel.tsx +119 -43
  72. package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
  73. package/src/panels/agent/agent.css +113 -97
  74. package/src/panels/agent/agentGate.ts +35 -0
  75. package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
  76. package/src/panels/code/CodeStudio.tsx +373 -0
  77. package/src/panels/code/FileTree.tsx +265 -0
  78. package/src/panels/code/codeBufferGuard.ts +52 -0
  79. package/src/panels/code/codeParts.tsx +321 -0
  80. package/src/panels/comments/CommentComposer.tsx +239 -0
  81. package/src/panels/comments/CommentThread.tsx +346 -0
  82. package/src/panels/comments/CommentsPanel.tsx +336 -0
  83. package/src/panels/comments/InboxButton.tsx +131 -0
  84. package/src/panels/comments/MentionMenu.tsx +98 -0
  85. package/src/panels/comments/parts.tsx +171 -0
  86. package/src/panels/export/ExportDialog.tsx +919 -96
  87. package/src/panels/export/ExportScrim.tsx +57 -0
  88. package/src/panels/files/AssetsTab.tsx +656 -0
  89. package/src/panels/files/FilePanel.tsx +1466 -216
  90. package/src/panels/files/LayerMarks.tsx +40 -0
  91. package/src/panels/files/LeftColumn.tsx +37 -0
  92. package/src/panels/files/McpSection.tsx +690 -47
  93. package/src/panels/files/MissingAssetsNotice.tsx +60 -0
  94. package/src/panels/files/assetDrag.ts +79 -0
  95. package/src/panels/files/resourceParts.tsx +255 -0
  96. package/src/panels/fonts/FontPicker.tsx +762 -0
  97. package/src/panels/fonts/faceCache.ts +207 -0
  98. package/src/panels/fonts/fontPicker.css +271 -0
  99. package/src/panels/fonts/pickerModel.ts +265 -0
  100. package/src/panels/fonts/pickerRequest.ts +46 -0
  101. package/src/panels/header/AccountAvatar.tsx +76 -0
  102. package/src/panels/header/AgentChip.tsx +77 -0
  103. package/src/panels/header/AppMenu.tsx +213 -0
  104. package/src/panels/header/Header.tsx +564 -84
  105. package/src/panels/header/HistoryControls.tsx +31 -5
  106. package/src/panels/header/PresenceStack.tsx +329 -0
  107. package/src/panels/header/ProjectTitle.tsx +232 -0
  108. package/src/panels/header/ShareButton.tsx +59 -0
  109. package/src/panels/header/StatusBadges.tsx +358 -27
  110. package/src/panels/header/SyncDeletedDialog.tsx +88 -0
  111. package/src/panels/header/ZoomControls.tsx +46 -15
  112. package/src/panels/header/agentHandoff.ts +52 -0
  113. package/src/panels/header/leave.ts +26 -0
  114. package/src/panels/help/HelpSection.tsx +189 -163
  115. package/src/panels/insert/InsertDrawer.tsx +723 -0
  116. package/src/panels/insert/iconSet.ts +109 -0
  117. package/src/panels/inspector/ExposeDot.tsx +117 -0
  118. package/src/panels/inspector/FramingRows.tsx +314 -44
  119. package/src/panels/inspector/InspectorFooter.tsx +91 -0
  120. package/src/panels/inspector/InspectorHead.tsx +425 -0
  121. package/src/panels/inspector/InspectorPanel.tsx +408 -124
  122. package/src/panels/inspector/KeyframeDot.tsx +373 -0
  123. package/src/panels/inspector/KeyframeSection.tsx +257 -0
  124. package/src/panels/inspector/MarkerSection.tsx +55 -11
  125. package/src/panels/inspector/ParamRow.tsx +84 -53
  126. package/src/panels/inspector/RightColumn.tsx +152 -0
  127. package/src/panels/inspector/TimeSection.tsx +866 -0
  128. package/src/panels/inspector/animate/EaseControl.tsx +244 -0
  129. package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
  130. package/src/panels/inspector/animate/ShotSection.tsx +562 -48
  131. package/src/panels/inspector/design/AlignRow.tsx +95 -31
  132. package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
  133. package/src/panels/inspector/design/ClonerSection.tsx +323 -62
  134. package/src/panels/inspector/design/ComponentSection.tsx +50 -43
  135. package/src/panels/inspector/design/CornerSection.tsx +45 -14
  136. package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
  137. package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
  138. package/src/panels/inspector/design/DeviceSection.tsx +210 -0
  139. package/src/panels/inspector/design/FillSection.tsx +83 -51
  140. package/src/panels/inspector/design/FontField.tsx +196 -0
  141. package/src/panels/inspector/design/HtmlSection.tsx +23 -12
  142. package/src/panels/inspector/design/InstanceSection.tsx +373 -154
  143. package/src/panels/inspector/design/MaskSection.tsx +84 -0
  144. package/src/panels/inspector/design/MaterialSection.tsx +177 -0
  145. package/src/panels/inspector/design/MediaSection.tsx +273 -120
  146. package/src/panels/inspector/design/MeshSection.tsx +910 -82
  147. package/src/panels/inspector/design/PaintRows.tsx +550 -0
  148. package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
  149. package/src/panels/inspector/design/ShadowSection.tsx +221 -48
  150. package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
  151. package/src/panels/inspector/design/StrokeSection.tsx +228 -43
  152. package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
  153. package/src/panels/inspector/design/TransformSection.tsx +244 -50
  154. package/src/panels/inspector/design/TypographySection.tsx +679 -110
  155. package/src/panels/inspector/layout.ts +295 -0
  156. package/src/panels/inspector/propValue.ts +199 -0
  157. package/src/panels/inspector/scene/CameraSection.tsx +232 -22
  158. package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
  159. package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
  160. package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
  161. package/src/panels/inspector/scene/SceneSection.tsx +219 -44
  162. package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
  163. package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
  164. package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
  165. package/src/panels/inspector/timeModel.ts +190 -0
  166. package/src/panels/inspector/useDocWrite.ts +438 -146
  167. package/src/panels/library/LibraryBrowser.tsx +435 -0
  168. package/src/panels/library/LibraryGrid.tsx +190 -0
  169. package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
  170. package/src/panels/library/LibraryRail.tsx +84 -0
  171. package/src/panels/library/LibrarySoundPanel.tsx +293 -0
  172. package/src/panels/library/SoundHistory.tsx +245 -0
  173. package/src/panels/library/StockSounds.tsx +390 -0
  174. package/src/panels/library/libraryModel.ts +147 -0
  175. package/src/panels/library/stockSearch.ts +77 -0
  176. package/src/panels/maskMarks.tsx +117 -0
  177. package/src/panels/material/MaterialCommentNode.tsx +54 -0
  178. package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
  179. package/src/panels/material/MaterialGraphMenu.tsx +45 -0
  180. package/src/panels/material/MaterialGroupNode.tsx +56 -0
  181. package/src/panels/material/MaterialLibrary.tsx +253 -0
  182. package/src/panels/material/MaterialNode.tsx +453 -0
  183. package/src/panels/material/MaterialNodePalette.tsx +349 -0
  184. package/src/panels/material/MaterialParameterControls.tsx +525 -0
  185. package/src/panels/material/MaterialRerouteNode.tsx +42 -0
  186. package/src/panels/material/MaterialWorkspace.tsx +501 -0
  187. package/src/panels/material/NodePreview.tsx +70 -0
  188. package/src/panels/material/flowModel.ts +441 -0
  189. package/src/panels/material/parameterValues.ts +231 -0
  190. package/src/panels/material/previewScheduler.ts +235 -0
  191. package/src/panels/material/reactFlow.css +6 -0
  192. package/src/panels/material/socketGlyph.ts +85 -0
  193. package/src/panels/material/viewportMemory.ts +58 -0
  194. package/src/panels/preferences/PreferencesDialog.tsx +215 -0
  195. package/src/panels/share/ShareComponentDialog.tsx +247 -0
  196. package/src/panels/stage/StageMenu.tsx +35 -5
  197. package/src/panels/stage/StagePanel.tsx +338 -23
  198. package/src/panels/stage/cellSlots.tsx +70 -0
  199. package/src/panels/storyboard/BoardCanvas.tsx +739 -0
  200. package/src/panels/storyboard/BoardCursors.tsx +158 -0
  201. package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
  202. package/src/panels/storyboard/BoardPlan.tsx +42 -0
  203. package/src/panels/storyboard/ComponentSheet.tsx +187 -0
  204. package/src/panels/storyboard/ComponentStage.tsx +235 -0
  205. package/src/panels/storyboard/MiniSketch.tsx +173 -0
  206. package/src/panels/storyboard/PartPicker.tsx +64 -0
  207. package/src/panels/storyboard/PlanPanel.tsx +617 -0
  208. package/src/panels/storyboard/ReferencePreview.tsx +217 -0
  209. package/src/panels/storyboard/ShotBrief.tsx +360 -0
  210. package/src/panels/storyboard/ShotCard.tsx +251 -0
  211. package/src/panels/storyboard/StagePreview.tsx +348 -0
  212. package/src/panels/storyboard/StillThumbs.tsx +162 -0
  213. package/src/panels/storyboard/StoryboardView.tsx +79 -0
  214. package/src/panels/storyboard/board.css +1065 -0
  215. package/src/panels/storyboard/board.ts +12 -0
  216. package/src/panels/storyboard/boardClipboard.ts +111 -0
  217. package/src/panels/storyboard/boardOrder.ts +68 -0
  218. package/src/panels/storyboard/boardRows.tsx +425 -0
  219. package/src/panels/storyboard/liveCap.ts +90 -0
  220. package/src/panels/storyboard/plan.css +295 -0
  221. package/src/panels/storyboard/posterCache.ts +133 -0
  222. package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
  223. package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
  224. package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
  225. package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
  226. package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
  227. package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
  228. package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
  229. package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
  230. package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
  231. package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
  232. package/src/panels/storyboard/shotboard/sketch.css +1392 -0
  233. package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
  234. package/src/panels/storyboard/storyboardDrop.ts +164 -0
  235. package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
  236. package/src/panels/studio/ComponentSidePanel.tsx +355 -0
  237. package/src/panels/studio/ComponentStudio.tsx +243 -0
  238. package/src/panels/studio/ComponentsHome.tsx +318 -0
  239. package/src/panels/studio/ComponentsPreview.tsx +179 -0
  240. package/src/panels/studio/EveryUse.tsx +244 -0
  241. package/src/panels/studio/ThisUsePanel.tsx +679 -0
  242. package/src/panels/studio/componentParts.tsx +341 -0
  243. package/src/panels/studio/components.ts +6 -0
  244. package/src/panels/studio/newCodeComponent.ts +66 -0
  245. package/src/panels/timeline/AudioFxButton.tsx +290 -0
  246. package/src/panels/timeline/AudioLane.tsx +674 -85
  247. package/src/panels/timeline/BlockRow.tsx +154 -85
  248. package/src/panels/timeline/ChannelRow.tsx +378 -129
  249. package/src/panels/timeline/CurvePane.tsx +508 -0
  250. package/src/panels/timeline/Filmstrip.tsx +361 -0
  251. package/src/panels/timeline/GenerateDrawer.tsx +407 -0
  252. package/src/panels/timeline/KeyStretch.tsx +115 -0
  253. package/src/panels/timeline/LifespanRow.tsx +730 -47
  254. package/src/panels/timeline/MarkerLane.tsx +46 -8
  255. package/src/panels/timeline/PlayButton.tsx +91 -0
  256. package/src/panels/timeline/PropertyPicker.tsx +254 -0
  257. package/src/panels/timeline/ShotRow.tsx +400 -0
  258. package/src/panels/timeline/SoundMode.tsx +1406 -0
  259. package/src/panels/timeline/TimelineDock.tsx +2338 -272
  260. package/src/panels/timeline/TransportVolume.tsx +163 -0
  261. package/src/panels/timeline/beatRuler.ts +74 -0
  262. package/src/panels/timeline/curveModel.ts +500 -0
  263. package/src/panels/timeline/duckUnderVoice.ts +68 -0
  264. package/src/panels/timeline/keyframeMenu.ts +138 -0
  265. package/src/panels/timeline/labelWidth.ts +49 -0
  266. package/src/panels/timeline/marquee.ts +155 -0
  267. package/src/panels/timeline/model.ts +1860 -199
  268. package/src/panels/timeline/snap.ts +125 -43
  269. package/src/panels/timeline/staggerPreview.tsx +92 -0
  270. package/src/panels/timeline/takeMeasure.ts +72 -0
  271. package/src/panels/timeline/useLive.ts +48 -0
  272. package/src/panels/timeline/viewport.ts +234 -0
  273. package/src/panels/toolbar/BackChip.tsx +116 -0
  274. package/src/panels/toolbar/CameraViewBar.tsx +118 -49
  275. package/src/panels/toolbar/CanvasBar.tsx +117 -0
  276. package/src/panels/toolbar/InsertButton.tsx +38 -0
  277. package/src/panels/toolbar/IsolationTrail.tsx +156 -0
  278. package/src/panels/toolbar/PanelToggles.tsx +86 -0
  279. package/src/panels/toolbar/ShadingBar.tsx +119 -26
  280. package/src/panels/toolbar/ToolDockBar.tsx +329 -80
  281. package/src/panels/toolbar/ToolFooter.tsx +102 -0
  282. package/src/panels/toolbar/ViewModeBar.tsx +247 -38
  283. package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
  284. package/src/panels/toolbar/useAddRows.tsx +326 -0
  285. package/src/panels/toolbar/useDismiss.ts +122 -0
  286. package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
  287. package/src/panels/versions/versionModel.ts +103 -0
  288. package/src/panels/virtualWindow.ts +271 -0
  289. package/src/shotTokens.css +151 -60
  290. package/src/stage/FrustumGizmo.tsx +504 -84
  291. package/src/stage/GridPlane.tsx +225 -16
  292. package/src/stage/NodePreviews.tsx +628 -0
  293. package/src/stage/PerfHud.tsx +437 -47
  294. package/src/stage/SplitRenderer.tsx +816 -169
  295. package/src/stage/StageApp.tsx +4274 -486
  296. package/src/stage/StageRootBoundary.tsx +114 -0
  297. package/src/stage/StageTools.tsx +651 -151
  298. package/src/stage/TransformGizmo.tsx +696 -0
  299. package/src/stage/adaptivePresentation.ts +145 -0
  300. package/src/stage/bridgeClient.ts +2522 -41
  301. package/src/stage/exportWorker.ts +9 -0
  302. package/src/stage/feed.ts +305 -14
  303. package/src/stage/fontPreview.ts +176 -0
  304. package/src/stage/frustumGeometry.ts +319 -1
  305. package/src/stage/furnitureTokens.ts +33 -0
  306. package/src/stage/gizmoMath.ts +508 -16
  307. package/src/stage/gpuTimer.ts +276 -0
  308. package/src/stage/instanceModules.ts +194 -33
  309. package/src/stage/interactions/move.ts +24 -5
  310. package/src/stage/isolation.ts +291 -33
  311. package/src/stage/jsSelfProfiler.ts +325 -0
  312. package/src/stage/main.tsx +116 -16
  313. package/src/stage/modalState.ts +222 -23
  314. package/src/stage/moduleEpoch.ts +60 -0
  315. package/src/stage/motionPathStage.ts +279 -0
  316. package/src/stage/motionPreview.ts +74 -0
  317. package/src/stage/overlays/Overlays.tsx +1112 -159
  318. package/src/stage/overlays/Rulers.tsx +495 -0
  319. package/src/stage/panes.ts +159 -10
  320. package/src/stage/project.ts +68 -3
  321. package/src/stage/renderScaleController.ts +730 -0
  322. package/src/stage/rulers.ts +129 -0
  323. package/src/stage/sceneExtent.ts +75 -0
  324. package/src/stage/shading.ts +20 -0
  325. package/src/stage/splineStage.ts +632 -0
  326. package/src/stage/stagePerformanceTelemetry.ts +717 -0
  327. package/src/stage/stageTheme.ts +36 -0
  328. package/src/stage/stageWarmup.ts +11 -0
  329. package/src/stage/textAutoSize.ts +124 -0
  330. package/src/stage/tier1Export.ts +641 -0
  331. package/src/stage/tokenColor.ts +14 -9
  332. package/src/stage/view.ts +56 -9
  333. package/src/stage/workingView.ts +277 -33
  334. package/src/state/accountPrefs.ts +261 -0
  335. package/src/state/agentActions.ts +173 -15
  336. package/src/state/agentActivity.ts +198 -0
  337. package/src/state/agentQuestion.ts +239 -0
  338. package/src/state/bootOpen.ts +42 -0
  339. package/src/state/cameraVerbs.ts +139 -0
  340. package/src/state/clientId.ts +6 -1
  341. package/src/state/comments.ts +1099 -0
  342. package/src/state/context.tsx +19 -1
  343. package/src/state/editorPrefs.ts +220 -0
  344. package/src/state/errorReports.ts +121 -0
  345. package/src/state/fieldWrite.ts +161 -0
  346. package/src/state/fontRecents.ts +70 -0
  347. package/src/state/fonts.ts +656 -0
  348. package/src/state/graphClipboard.ts +414 -0
  349. package/src/state/imageThumbs.ts +216 -0
  350. package/src/state/insert.ts +444 -67
  351. package/src/state/isolationTrail.ts +92 -0
  352. package/src/state/keyMoves.ts +134 -0
  353. package/src/state/keyframeClipboard.ts +177 -0
  354. package/src/state/keymap.ts +1153 -117
  355. package/src/state/layoutPrefs.ts +272 -0
  356. package/src/state/layouts.ts +205 -0
  357. package/src/state/measureMedia.ts +167 -0
  358. package/src/state/menu.ts +2341 -28
  359. package/src/state/motionStamp.ts +7 -0
  360. package/src/state/panelSize.ts +375 -5
  361. package/src/state/pendingChanges.ts +119 -0
  362. package/src/state/playHold.ts +78 -0
  363. package/src/state/playbackPreparation.ts +562 -0
  364. package/src/state/posterCapture.ts +242 -0
  365. package/src/state/presence.ts +254 -0
  366. package/src/state/projectComponents.ts +160 -0
  367. package/src/state/renderEvents.ts +298 -18
  368. package/src/state/runtimeErrors.ts +46 -0
  369. package/src/state/selection.ts +12 -0
  370. package/src/state/shortcuts.ts +1606 -0
  371. package/src/state/sketchGuide.ts +51 -0
  372. package/src/state/splineRefs.ts +87 -0
  373. package/src/state/stageHealth.ts +259 -0
  374. package/src/state/stageWatchdog.ts +47 -0
  375. package/src/state/stillStage.ts +330 -0
  376. package/src/state/stillThumbs.ts +474 -0
  377. package/src/state/store.ts +23591 -2982
  378. package/src/state/storyboardModel.ts +528 -0
  379. package/src/state/sync.ts +1257 -0
  380. package/src/state/syncCache.ts +159 -0
  381. package/src/state/triangles.ts +13 -0
  382. package/src/state/types.ts +1698 -55
  383. package/src/state/unsavedGuard.ts +49 -0
  384. package/src/state/viewMode.ts +200 -0
  385. package/src/state/webmcp.ts +1128 -0
  386. package/src/state/workspace.ts +1147 -0
  387. package/src/styles/assets-tab.css +304 -0
  388. package/src/styles/canvas.css +545 -0
  389. package/src/styles/comments.css +1435 -0
  390. package/src/styles/controls.css +162 -0
  391. package/src/styles/dialogs.css +390 -0
  392. package/src/styles/insert-drawer.css +586 -0
  393. package/src/styles/inspector-board.css +207 -0
  394. package/src/styles/inspector-object.css +486 -0
  395. package/src/styles/inspector-shot.css +252 -0
  396. package/src/styles/inspector.css +360 -0
  397. package/src/styles/left-column.css +1628 -0
  398. package/src/styles/library-preview.css +575 -0
  399. package/src/styles/library.css +512 -0
  400. package/src/styles/modes/code.css +569 -0
  401. package/src/styles/modes/component.css +1561 -0
  402. package/src/styles/modes/material.css +1201 -0
  403. package/src/styles/modes/storyboard.css +518 -0
  404. package/src/styles/share-component.css +49 -0
  405. package/src/styles/shell.css +653 -0
  406. package/src/styles/shot-brief.css +360 -0
  407. package/src/styles/states.css +76 -0
  408. package/src/styles/sync.css +380 -0
  409. package/src/styles/timeline-graph.css +384 -0
  410. package/src/styles/timeline-meters.css +89 -0
  411. package/src/styles/timeline-rows.css +1959 -0
  412. package/src/styles/timeline-sound.css +1003 -0
  413. package/src/styles/timeline.css +754 -0
  414. package/src/styles/tool-footer.css +525 -0
  415. package/src/styles/topbar.css +886 -0
  416. package/src/styles/versions.css +262 -0
  417. package/src/styles.css +72 -2921
  418. package/src/tools.ts +108 -23
  419. package/dist/assets/index-B7notge7.css +0 -1
  420. package/dist/assets/index-BBE6Ta9L.js +0 -52
  421. package/src/panels/files/icons.tsx +0 -118
  422. package/src/panels/header/DocSwitcher.tsx +0 -85
  423. package/src/panels/header/TransportBar.tsx +0 -86
  424. package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
  425. package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
  426. package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
  427. package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
  428. package/src/panels/inspector/animate/useBlocks.ts +0 -43
  429. package/src/panels/material/MaterialPicker.tsx +0 -172
  430. package/src/panels/timeline/SceneStrip.tsx +0 -145
  431. package/src/panels/toolbar/AddMenu.tsx +0 -230
  432. package/src/stage/MoveGizmo.tsx +0 -411
@@ -0,0 +1,1959 @@
1
+ /* The timeline's ROWS mode: the sticky label column, every row kind (shots, lifespans, camera moves,
2
+ camera poses, channels, + property and its picker, audio, markers), the cut markers, the
3
+ rubber band and the group separators. Imported right after timeline.css (the dock frame), in
4
+ the same layer. A row rule and a frame rule never style one element at equal specificity, so
5
+ which of the two files comes first decides nothing; keep it that way. */
6
+
7
+ /* ── THE DOCK'S LABEL COLUMN IS STICKY ────────────────────────────────────
8
+ Every row in the timeline is `[label cell | time area]` inside ONE
9
+ horizontally scrolling box, so the names used to scroll away with the bars.
10
+ That was survivable while the only way to scroll was a deliberate
11
+ shift+wheel pan. It is not survivable now that the lane FOLLOWS THE PLAYHEAD:
12
+ every playback slides the whole column off the left edge and leaves the
13
+ author watching a wall of anonymous bars.
14
+
15
+ Six cells, one rule, because they are one column — a per-component copy is
16
+ six places to forget the z-index.
17
+
18
+ OPAQUE, and that is the load-bearing part: a transparent sticky cell has bars
19
+ sliding visibly through the layer names. `--surface-panel` is the dock's own
20
+ surface (`.timeline-dock`, timeline.css), so the column reads as part of
21
+ the dock rather than as a card on top of it.
22
+
23
+ Sticky and NOT a separate fixed column beside the scroller: the rows already
24
+ own their own labels, and splitting them would mean two scroll containers
25
+ kept in vertical sync by hand — one more thing that can drift, for a layout
26
+ the browser does for free. */
27
+ .shot-label-cell,
28
+ .block-row-label-cell,
29
+ .channel-label-cell,
30
+ .lifespan-label-cell,
31
+ .marker-label-cell,
32
+ .audio-label-cell {
33
+ position: sticky;
34
+ left: 0;
35
+ /* Above the bars in the same row (a scene cut is 3) AND above
36
+ `.dock-overlay` (3: the playhead, the boundaries and the snap guide). The
37
+ overlay is a sibling of the rows in content coordinates, so once the lanes
38
+ scroll left its lines slide under this column; painted over it, a playhead
39
+ whose frame had scrolled out of view crossed the shot names instead. In
40
+ view, every line sits right of `labelWidth` and nothing is covered. */
41
+ z-index: 4;
42
+ background-color: var(--surface-panel);
43
+ }
44
+
45
+ /* THE LABEL COLUMN CONTINUES THE LEFT COLUMN'S SEAM. It is exactly the rail
46
+ track's width (labelWidth.ts), and the rail draws its border on its last
47
+ pixel, so the same 1px line runs from the top bar down through the ruler
48
+ corner and every row. A rule of its own because `.scene-audio-label` takes
49
+ the seam but not the sticky recipe above. */
50
+ .shot-label-cell,
51
+ .block-row-label-cell,
52
+ .channel-label-cell,
53
+ .lifespan-label-cell,
54
+ .marker-label-cell,
55
+ .audio-label-cell,
56
+ .scene-audio-label {
57
+ border-right: 1px solid var(--line);
58
+ }
59
+
60
+ /* NO ROW TINTS. A selected or current row is said by its CLIP (the outline,
61
+ the solid live fill, `states.css`) and by its label's weight, never by an
62
+ `--accent-quiet` wash across the row: the wash fought the clip for the one
63
+ accent the lane has, and on a three-row dock it lit a third of the panel. */
64
+
65
+ /* THE CLIP, option A, the one recipe every lane clip reads from: a top-down
66
+ accent wash 18% to 6% in oklab under a 1.5px accent edge at 45%, radius 6,
67
+ 20 tall at 4 from the row's top, 11/400 accent ink inside. The shot clip
68
+ is the same at 22%/8% with a 60% edge. Tokens so `states.css` can name the
69
+ rest look when it lays the selected edge over it. */
70
+ .timeline-dock {
71
+ --clip-rest: linear-gradient(
72
+ in oklab 180deg,
73
+ color-mix(in oklab, var(--accent) 18%, transparent),
74
+ color-mix(in oklab, var(--accent) 6%, transparent)
75
+ );
76
+ --clip-edge: color-mix(in oklab, var(--accent) 45%, transparent);
77
+ --clip-shot-rest: linear-gradient(
78
+ in oklab 180deg,
79
+ color-mix(in oklab, var(--accent) 22%, transparent),
80
+ color-mix(in oklab, var(--accent) 8%, transparent)
81
+ );
82
+ --clip-shot-edge: color-mix(in oklab, var(--accent) 60%, transparent);
83
+ }
84
+
85
+ /* The cut marker: a small diamond ON the seam, hollow for a hard cut and
86
+ filled when the shot before it blends. Centred by a half-width translate so
87
+ it sits on the boundary rather than after it. Below the playhead (z 4) so the
88
+ frame you are on always wins. */
89
+ .scene-cut {
90
+ position: absolute;
91
+ top: 50%;
92
+ width: 9px;
93
+ height: 9px;
94
+ padding: 0;
95
+ transform: translate(-50%, -50%) rotate(45deg);
96
+ border: 1px solid var(--ink-3);
97
+ border-radius: var(--radius-2xs);
98
+ background: var(--surface-panel);
99
+ cursor: pointer;
100
+ z-index: 3;
101
+ }
102
+
103
+ .scene-cut:hover {
104
+ border-color: var(--ink-1);
105
+ }
106
+
107
+ .scene-cut.is-blend {
108
+ border-color: var(--accent);
109
+ background: var(--accent);
110
+ }
111
+
112
+ /* The blend's length, drawn forward from the cut into the incoming shot's
113
+ space — which is where `transitionAt` puts the window.
114
+
115
+ Accent at low alpha rather than a fill: the ribbon overhangs the row beneath
116
+ it, and a solid bar there would read as a shot that is not one. It sits
117
+ BELOW the diamond (z-index 2 against its 3) so the seam's own control stays
118
+ the top target, and it is pointer-transparent so only the handle inside it
119
+ takes a drag. */
120
+ .scene-blend {
121
+ position: absolute;
122
+ top: 50%;
123
+ height: 9px;
124
+ transform: translateY(-50%);
125
+ border: 1px solid var(--accent);
126
+ border-left: none;
127
+ border-radius: 0 var(--radius-2xs) var(--radius-2xs) 0;
128
+ background: color-mix(in oklab, var(--accent) 22%, transparent);
129
+ pointer-events: none;
130
+ z-index: 2;
131
+ }
132
+
133
+ /* The one thing in the ribbon that is a target. `.scene-trim` supplies the
134
+ 7px column and the col-resize cursor; this only re-enables the pointer the
135
+ ribbon turned off. */
136
+ .scene-blend .scene-trim {
137
+ pointer-events: auto;
138
+ }
139
+
140
+ /* THE RUBBER BAND, over the rows it is selecting.
141
+
142
+ AN EDGE AND A WASH, never a surface step. `--panel-raised` IS `--panel` in
143
+ the light appearance, so a band drawn as a lighter fill over the lane would
144
+ be invisible in exactly one of the two themes and perfect in the other. The
145
+ 1px accent edge is what reads in both, and the 12% wash is there only to say
146
+ which side of the line is inside: it is the same pair the stage's own marquee
147
+ draws with (`Overlays.tsx`), so the two surfaces spell one gesture one way.
148
+
149
+ `--marquee` and not `--accent` directly: the token exists for this, and the
150
+ stage reads it from the same file (shotTokens.css).
151
+
152
+ Pointer-transparent, and level with the sticky label cells (4) but later in
153
+ the DOM, so it is drawn THROUGH them: a band that vanished behind the column
154
+ would stop halfway across its own box. Below the sticky ruler (5). */
155
+ .dock-marquee {
156
+ position: absolute;
157
+ border: 1px solid var(--marquee);
158
+ background: color-mix(in srgb, var(--marquee) 12%, transparent);
159
+ border-radius: var(--radius-2xs);
160
+ pointer-events: none;
161
+ z-index: 4;
162
+ }
163
+
164
+ /* Groups are separated by the boundary line above; this is the horizontal half
165
+ of the same statement, for a reader scanning the label column.
166
+ A CLASS AND NOT AN ADJACENT SIBLING: past the virtual window's threshold a
167
+ spacer can sit between two groups, so `+` lost the line at a window edge
168
+ while `groupHeights` still counted it. The class follows the group's index,
169
+ which is what the prefix sum counts. */
170
+ .dock-scene-group.has-separator {
171
+ border-top: 1px solid var(--line);
172
+ }
173
+
174
+ /* ---- shot rows ---------------------------------------------------------- */
175
+ /* One row per shot, at the head of that shot's group. Taller than a block row
176
+ and quieter than a lifespan bar, because it is the thing all of them sit
177
+ INSIDE — and its bar and the boundary lines through the track area are the
178
+ same prefix sum, so nothing can move one without the other. */
179
+ .shot-row {
180
+ display: flex;
181
+ align-items: stretch;
182
+ /* SHOT_ROW_H in model.ts, which the vertical follow and the group sums read.
183
+ A px literal so the row-metrics test can compare the two. */
184
+ height: 28px;
185
+ border-bottom: 1px solid var(--line-soft);
186
+ /* Same isolation boundary as .lifespan-row, same legality note. */
187
+ contain: layout paint;
188
+ }
189
+
190
+ /* THE SHOT LABEL: an accent 8px swatch, the name at 12/600 ink, and at the
191
+ end the fold: "N layers ›", which opens the shot's layer rows under it. */
192
+ .shot-label-cell {
193
+ display: flex;
194
+ align-items: center;
195
+ gap: var(--space-4);
196
+ padding: 0 var(--space-7) 0 var(--space-7);
197
+ flex: none;
198
+ overflow: hidden;
199
+ }
200
+
201
+ .shot-swatch {
202
+ flex: none;
203
+ width: 8px;
204
+ height: 8px;
205
+ border-radius: var(--radius-2xs);
206
+ background: var(--accent);
207
+ }
208
+
209
+ .shot-disclosure {
210
+ flex: none;
211
+ display: inline-flex;
212
+ align-items: center;
213
+ gap: var(--space-2);
214
+ margin-left: auto;
215
+ font: inherit;
216
+ font-size: var(--type-sm);
217
+ line-height: var(--leading-none);
218
+ color: var(--ink-3);
219
+ background: transparent;
220
+ border: 0;
221
+ padding: var(--space-2) 0;
222
+ white-space: nowrap;
223
+ cursor: pointer;
224
+ }
225
+
226
+ .shot-disclosure:hover {
227
+ color: var(--ink-1);
228
+ }
229
+
230
+ .shot-fold-chevron {
231
+ flex: none;
232
+ width: 12px;
233
+ height: 12px;
234
+ transition: transform var(--dur-fast) var(--ease);
235
+ }
236
+
237
+ .shot-fold-chevron.is-open {
238
+ transform: rotate(90deg);
239
+ }
240
+
241
+ .shot-name {
242
+ font: inherit;
243
+ font-size: var(--type-md);
244
+ font-weight: var(--weight-semibold);
245
+ color: var(--ink-1);
246
+ background: transparent;
247
+ border: 0;
248
+ padding: var(--space-1) 0;
249
+ min-width: 0;
250
+ overflow: hidden;
251
+ text-overflow: ellipsis;
252
+ white-space: nowrap;
253
+ cursor: pointer;
254
+ }
255
+
256
+ .shot-rename {
257
+ font: inherit;
258
+ font-weight: 600;
259
+ color: var(--ink-1);
260
+ background: var(--fill-rest);
261
+ border: 1px solid var(--accent);
262
+ border-radius: var(--radius-xs);
263
+ padding: 1px var(--space-2);
264
+ min-width: 0;
265
+ flex: 1;
266
+ }
267
+
268
+ .shot-lane {
269
+ position: relative;
270
+ flex: 1;
271
+ }
272
+
273
+ /* THE SHOT CLIP, option A at its heavier step: the accent wash 22% to 8%
274
+ under a 1.5px accent edge at 60%, radius 6, 20 tall at 4 from the row's top
275
+ (the row is 28 with a 1px bottom border, so 3 stay clear under it). The name
276
+ at 11/600 accent ink, the length at the far end in mono 10. Solid accent
277
+ while the playhead is in it (`states.css`). */
278
+ .shot-bar {
279
+ position: absolute;
280
+ top: 4px;
281
+ height: 20px;
282
+ box-sizing: border-box;
283
+ display: flex;
284
+ align-items: center;
285
+ gap: var(--space-4);
286
+ padding: 0 var(--space-4);
287
+ border-radius: var(--radius-sm);
288
+ background: var(--clip-shot-rest);
289
+ border: 1.5px solid var(--clip-shot-edge);
290
+ color: var(--accent-ink);
291
+ font-size: var(--type-sm);
292
+ line-height: var(--leading-none);
293
+ cursor: pointer;
294
+ overflow: hidden;
295
+ }
296
+
297
+ .shot-bar-name {
298
+ min-width: 0;
299
+ white-space: nowrap;
300
+ overflow: hidden;
301
+ text-overflow: ellipsis;
302
+ font-weight: var(--weight-semibold);
303
+ }
304
+
305
+ /* The shot's length ("18.00s", `formatDuration`), pushed to the clip's far
306
+ end. Mono, so a column of lengths reads as numbers. */
307
+ .shot-frames {
308
+ flex: none;
309
+ margin-left: auto;
310
+ font-family: var(--font-mono);
311
+ font-size: var(--type-micro);
312
+ font-variant-numeric: var(--numeric);
313
+ white-space: nowrap;
314
+ }
315
+
316
+ /* Reorder feedback: the line on the side the dragged shot lands, the outline's
317
+ `.layer-row.is-drop-*` recipe turned on its side. */
318
+ .shot-bar.is-drop-before {
319
+ box-shadow: inset 2px 0 0 var(--ink-1);
320
+ }
321
+
322
+ .shot-bar.is-drop-after {
323
+ box-shadow: inset -2px 0 0 var(--ink-1);
324
+ }
325
+
326
+ /* THE WAY IN. Sits inside the clip after the length, left of the trim handle,
327
+ and is the only control on the row that changes what the whole chrome is
328
+ showing: an aimed target beside the double-click. The trim handle's width
329
+ stays reserved so the two never overlap. */
330
+ .scene-open {
331
+ flex: none;
332
+ margin-right: var(--dock-trim-w);
333
+ font: inherit;
334
+ font-size: var(--type-lg);
335
+ line-height: var(--leading-none);
336
+ color: inherit;
337
+ opacity: 0.7;
338
+ background: transparent;
339
+ border: 0;
340
+ padding: 0 var(--space-1);
341
+ border-radius: var(--radius-2xs);
342
+ cursor: pointer;
343
+ }
344
+
345
+ .scene-open:hover {
346
+ opacity: 1;
347
+ background: color-mix(in srgb, currentColor 18%, transparent);
348
+ }
349
+
350
+ .scene-trim {
351
+ position: absolute;
352
+ right: 0;
353
+ top: 0;
354
+ bottom: 0;
355
+ width: var(--dock-trim-w);
356
+ cursor: col-resize;
357
+ }
358
+
359
+ /* The grip, drawn only on the edge that really drags. The head of a shot is
360
+ not a handle (moving it is a reorder, not a trim), so it carries none: a grip
361
+ there would promise a drag that does nothing. A 2x12 grip in the clip's own
362
+ ink (accent at rest, white on the live fill): heavier than a lifespan bar's
363
+ grip because this edge moves every shot after it. */
364
+ .scene-trim::after {
365
+ content: "";
366
+ position: absolute;
367
+ left: 50%;
368
+ top: 50%;
369
+ width: 2px;
370
+ height: 12px;
371
+ /* The ladder's first step: on a 2px-wide grip it clamps to fully round ends. */
372
+ border-radius: var(--radius-2xs);
373
+ transform: translate(-50%, -50%);
374
+ background: currentColor;
375
+ pointer-events: none;
376
+ }
377
+
378
+
379
+ /* ---- lifespan rows ------------------------------------------------------ */
380
+ /* The layer's own bar: taller and quieter than a block bar, because it is the
381
+ thing a block sits INSIDE. Solid-ish fill rather than the block's outline,
382
+ so a row reads as one object with intervals on it rather than as a pile of
383
+ equal-weight rectangles. */
384
+ .lifespan-row {
385
+ display: flex;
386
+ align-items: stretch;
387
+ height: 28px;
388
+ border-bottom: 1px solid var(--line-soft);
389
+ /* Fixed-height rows are isolation boundaries: a style change inside one
390
+ (a bar drag, a hover) must not invalidate layout or repaint outside it.
391
+ Legal here — the containment ban (styles.css:5) covers only ancestors of
392
+ the stage iframe, and the dock is its sibling. */
393
+ contain: layout paint;
394
+ }
395
+
396
+ /* A PICK ELSEWHERE REVEALED THIS ROW (Paper page 23, "6 · Reveal on select"):
397
+ one wash of the accent that settles back to the row's own ground, selected
398
+ or not, so the eye lands on it after the scroll. No fill mode: when it ends
399
+ the row is exactly what it was, and the dock takes the class back. */
400
+ @keyframes row-revealed {
401
+ from { background-color: var(--accent-soft); }
402
+ }
403
+
404
+ @media (prefers-reduced-motion: no-preference) {
405
+ .lifespan-row.is-revealed { animation: row-revealed 900ms var(--ease); }
406
+ }
407
+
408
+ .lifespan-label-cell {
409
+ /* `--mask-inset` makes room for the row's mask brackets (maskMarks.tsx),
410
+ which sit against this box like the outline's sit against its row. */
411
+ position: relative;
412
+ /* 32 in from the seam and 16 short of it, the boards' layer label, plus the
413
+ tree's indent and the mask brackets' room. */
414
+ padding-left: calc(var(--space-7) * 2 + var(--depth, 0) * var(--tree-indent) + var(--mask-inset, 0px));
415
+ padding-right: var(--space-7);
416
+ display: flex;
417
+ align-items: center;
418
+ flex: none;
419
+ overflow: hidden;
420
+ }
421
+
422
+ .lifespan-disclosure {
423
+ font: inherit;
424
+ font-size: var(--type-micro);
425
+ line-height: var(--leading-none);
426
+ color: var(--ink-3);
427
+ background: transparent;
428
+ border: 0;
429
+ padding: var(--space-2) var(--space-2);
430
+ cursor: pointer;
431
+ flex: none;
432
+ }
433
+
434
+ .lifespan-disclosure:hover {
435
+ color: var(--ink-1);
436
+ }
437
+
438
+ /* 12px secondary at rest. The row whose clip the playhead is in lifts its
439
+ name to 600 ink (`states.css`); selection does not, the clip says it. */
440
+ .lifespan-name {
441
+ font: inherit;
442
+ font-size: var(--type-md);
443
+ color: var(--ink-2);
444
+ background: transparent;
445
+ border: 0;
446
+ text-align: left;
447
+ padding: 0 var(--space-3);
448
+ cursor: pointer;
449
+ flex: 1;
450
+ min-width: 0;
451
+ white-space: nowrap;
452
+ overflow: hidden;
453
+ text-overflow: ellipsis;
454
+ }
455
+
456
+ .lifespan-lane {
457
+ position: relative;
458
+ flex: 1;
459
+ }
460
+
461
+ /* CLIP OPTION A AT REST: the accent wash under its 45% edge, radius 6, 20
462
+ tall at 4 under the row's top (3 clear of its 1px bottom border), 8 in, the
463
+ name at 11/400 accent ink. The tokens are on `.timeline-dock` above. */
464
+ .lifespan-bar {
465
+ position: absolute;
466
+ top: 4px;
467
+ bottom: 3px;
468
+ display: flex;
469
+ align-items: center;
470
+ padding: 0 var(--space-4);
471
+ border-radius: var(--radius-sm);
472
+ background: var(--clip-rest);
473
+ border: 1.5px solid var(--clip-edge);
474
+ color: var(--accent-ink);
475
+ font-size: var(--type-sm);
476
+ font-weight: var(--weight-regular);
477
+ cursor: grab;
478
+ overflow: hidden;
479
+ white-space: nowrap;
480
+ }
481
+
482
+ .lifespan-bar:hover {
483
+ border-color: color-mix(in oklab, var(--accent) 70%, transparent);
484
+ }
485
+
486
+ /* A VIDEO CLIP keeps the hueless ground: its picture and its waveform fill the
487
+ box (the inline style drops the border and the padding), and an accent wash
488
+ under footage would tint the frames. */
489
+ .lifespan-bar.is-video {
490
+ background: var(--bar-ground);
491
+ color: var(--ink-1);
492
+ }
493
+
494
+ /* SELECTED IS AN EDGE, never a fill: the fill says whether the clip is playing
495
+ now (states.css), and an author routinely has one clip selected and another
496
+ under the playhead. The full accent edge at 1.5px over the rest wash, the
497
+ name at 600, and white trim handles 3x12 at each end. Selected AND live is
498
+ the solid fill with the same handles. The video clip has no border of its
499
+ own, so its selection stays an outline. */
500
+ .lifespan-bar.is-selected {
501
+ border-color: var(--accent);
502
+ padding-inline: calc(var(--space-3) / 2);
503
+ }
504
+
505
+ .lifespan-bar.is-selected .lifespan-bar-name {
506
+ font-weight: var(--weight-semibold);
507
+ }
508
+
509
+ .lifespan-bar.is-video.is-selected {
510
+ outline: 1.5px solid var(--accent);
511
+ outline-offset: -1.5px;
512
+ }
513
+
514
+ /* THE STAGGER (Paper page 23, "8 · Stagger"). A dashed ghost where each moving
515
+ bar started, an accent rule above it as long as the bar's shift, and the step
516
+ readout beside the grabbed bar. All three are drawn only while ⌥ drags a
517
+ selection, and none takes a pointer. The ghost is the bar's own box. */
518
+ .stagger-ghost {
519
+ position: absolute;
520
+ top: 4px;
521
+ bottom: 5px;
522
+ border: 1px dashed var(--ink-3);
523
+ border-radius: var(--radius-2xs);
524
+ background: color-mix(in srgb, var(--wash-ink) 3%, transparent);
525
+ pointer-events: none;
526
+ }
527
+
528
+ .stagger-shift {
529
+ position: absolute;
530
+ top: calc(-1 * var(--space-3));
531
+ height: 1px;
532
+ background: var(--accent-ink);
533
+ }
534
+
535
+ .stagger-chip {
536
+ position: absolute;
537
+ top: 50%;
538
+ transform: translateY(-50%);
539
+ margin-left: var(--space-4);
540
+ display: inline-flex;
541
+ align-items: center;
542
+ gap: var(--space-3);
543
+ padding: var(--space-1) var(--space-4);
544
+ border: 1px solid var(--line-strong);
545
+ border-radius: var(--radius-sm);
546
+ background: var(--surface-panel);
547
+ box-shadow: var(--shadow-control);
548
+ font-size: var(--type-sm);
549
+ white-space: nowrap;
550
+ pointer-events: none;
551
+ }
552
+
553
+ .stagger-chip-step {
554
+ color: var(--ink-1);
555
+ font-weight: var(--weight-semibold);
556
+ font-variant-numeric: var(--numeric);
557
+ }
558
+
559
+ .stagger-chip-key {
560
+ color: var(--ink-3);
561
+ }
562
+
563
+ /* ---- the stretch bracket (KeyStretch.tsx) --------------------------------
564
+ The selected keys' span: a guide through the rows at each end (in the
565
+ pointer-transparent overlay), in the key colour (never the accent), and in
566
+ the sticky ruler a band over the span with a grab tab just inside each end,
567
+ so it never scrolls away and survives the ruler's clip at frame 0. The
568
+ readout sits in the ruler beside the tab being dragged. */
569
+ .dock-key-stretch-guide {
570
+ position: absolute;
571
+ top: 0;
572
+ bottom: 0;
573
+ width: 1px;
574
+ background: var(--kf-soft);
575
+ pointer-events: none;
576
+ }
577
+
578
+ .dock-key-stretch-handle {
579
+ position: absolute;
580
+ top: var(--space-1);
581
+ bottom: var(--space-1);
582
+ width: var(--space-4);
583
+ border: 1px solid var(--kf);
584
+ background: var(--surface-panel);
585
+ border-radius: var(--radius-2xs);
586
+ cursor: ew-resize;
587
+ pointer-events: auto;
588
+ }
589
+
590
+ .dock-key-stretch-band {
591
+ position: absolute;
592
+ top: var(--space-1);
593
+ bottom: var(--space-1);
594
+ background: var(--kf-soft);
595
+ border-radius: var(--radius-2xs);
596
+ pointer-events: none;
597
+ }
598
+
599
+ .dock-key-stretch-handle.is-right {
600
+ transform: translateX(-100%);
601
+ }
602
+
603
+ .dock-key-stretch-handle:hover {
604
+ background: var(--kf-soft);
605
+ }
606
+
607
+ .dock-key-stretch-chip {
608
+ z-index: 1;
609
+ }
610
+
611
+ .lifespan-bar-name {
612
+ min-width: 0;
613
+ overflow: hidden;
614
+ text-overflow: ellipsis;
615
+ pointer-events: none;
616
+ }
617
+
618
+ /* THE FULL NAME, OUTSIDE. A name that does not fit its clip is never cut: it
619
+ sits in the lane after the clip, or before it when the clip ends at the
620
+ track's end (`clipNamePlace`), at the inside name's size and ink. The 6px
621
+ gap and the side come from the row; this is only the type. */
622
+ .lifespan-bar-name.is-outside {
623
+ position: absolute;
624
+ top: 7px;
625
+ width: max-content;
626
+ overflow: visible;
627
+ white-space: pre;
628
+ font-size: var(--type-sm);
629
+ line-height: var(--leading-tight);
630
+ color: var(--accent-ink);
631
+ }
632
+
633
+ /* A MASK'S BAR (gap 7, Paper page 25). Dashed in the accent ink, a pill, with
634
+ the mode beside the name: a mask draws nothing of its own, so its bar must
635
+ not read as a layer that paints. Its edge survives the live fill, see
636
+ states.css. */
637
+ .lifespan-bar.is-mask {
638
+ gap: var(--space-3);
639
+ padding: 0 var(--space-4);
640
+ border: 1px dashed var(--accent-ink);
641
+ border-radius: var(--radius-full);
642
+ }
643
+
644
+ .lifespan-bar.is-mask .lifespan-bar-name {
645
+ color: var(--accent-ink);
646
+ font-size: var(--type-sm);
647
+ }
648
+
649
+ .lifespan-bar-mask {
650
+ flex: none;
651
+ font-family: var(--font-mono);
652
+ font-size: var(--type-micro);
653
+ color: var(--ink-2);
654
+ pointer-events: none;
655
+ }
656
+
657
+ .lifespan-handle {
658
+ position: absolute;
659
+ top: 0;
660
+ bottom: 0;
661
+ width: 8px;
662
+ cursor: col-resize;
663
+ }
664
+
665
+ /* THE TRIM HANDLES, shown on a selected clip only: white 3x12 tabs, radius
666
+ 2, centred in each 8px hit target. A hairline accent ring keeps the white
667
+ readable on the light wash; on the solid live fill it disappears into it.
668
+ The hit target is there at rest too, unmarked. Not on a video clip: its edges
669
+ are trims with their own picture under them, and a tab over the frames is
670
+ noise. */
671
+ .lifespan-bar.is-selected:not(.is-video) .lifespan-handle::after {
672
+ content: "";
673
+ position: absolute;
674
+ left: 50%;
675
+ top: 50%;
676
+ width: 3px;
677
+ height: 12px;
678
+ border-radius: var(--radius-2xs);
679
+ transform: translate(-50%, -50%);
680
+ background: var(--on-accent);
681
+ box-shadow: 0 0 0 1px var(--accent);
682
+ pointer-events: none;
683
+ }
684
+
685
+ .lifespan-handle-in {
686
+ left: 0;
687
+ }
688
+
689
+ .lifespan-handle-out {
690
+ right: 0;
691
+ }
692
+
693
+ /* ---- the camera row ----------------------------------------------------- */
694
+ /* 28, the row ruling, and `BLOCK_ROW_H` in model.ts: the camera row is counted
695
+ by the dock's height sums at that number. */
696
+ .block-row {
697
+ display: flex;
698
+ align-items: stretch;
699
+ height: 28px;
700
+ border-bottom: 1px solid var(--line-soft);
701
+ /* Same isolation boundary as .lifespan-row, same legality note. */
702
+ contain: layout paint;
703
+ }
704
+
705
+ /* The label column: the name, and on the camera row a create button beside it.
706
+ Fixed width from the dock's label width, so the lanes of every row still
707
+ start on the same x as the ruler. */
708
+ .block-row-label-cell {
709
+ display: flex;
710
+ align-items: stretch;
711
+ flex: none;
712
+ overflow: hidden;
713
+ }
714
+
715
+ /* 12px secondary, the lifespan label's recipe, so the camera row reads as part
716
+ of the same column. */
717
+ .block-row-label {
718
+ align-self: stretch;
719
+ font: inherit;
720
+ font-size: var(--type-md);
721
+ color: var(--ink-2);
722
+ background: transparent;
723
+ border: 0;
724
+ text-align: left;
725
+ padding: 0;
726
+ cursor: pointer;
727
+ flex: 1;
728
+ min-width: 0;
729
+ white-space: nowrap;
730
+ overflow: hidden;
731
+ text-overflow: ellipsis;
732
+ }
733
+
734
+ /* THE CAMERA ROW'S LABEL: a hollow 8px square (the shot's own eye, not one of
735
+ its contents), "Camera" in the secondary ink, then at the end how many marks
736
+ it holds in mono accent and the add icon. 16 in from the seam, like the shot
737
+ and the sound rows it sits between. */
738
+ .block-row.is-camera .block-row-label-cell {
739
+ align-items: center;
740
+ gap: var(--space-4);
741
+ padding: 0 var(--space-5) 0 var(--space-7);
742
+ }
743
+
744
+ .camera-swatch {
745
+ flex: none;
746
+ width: 8px;
747
+ height: 8px;
748
+ border: 1.5px solid var(--ink-2);
749
+ border-radius: var(--radius-2xs);
750
+ }
751
+
752
+ .camera-keyed {
753
+ flex: none;
754
+ font-family: var(--font-mono);
755
+ font-size: var(--type-micro);
756
+ font-variant-numeric: var(--numeric);
757
+ color: var(--accent-ink);
758
+ white-space: nowrap;
759
+ }
760
+
761
+ /* VISIBLE AT REST: the one place a camera move is created, and every dimmed
762
+ version of it read as a capability the editor does not have ("quiet is not
763
+ hidden"). A compact house icon button, 20 square with a 12px glyph. */
764
+ .block-row-add {
765
+ flex: none;
766
+ width: 20px;
767
+ height: 20px;
768
+ padding: 0;
769
+ color: var(--ink-2);
770
+ }
771
+
772
+ .block-row-add svg {
773
+ width: 12px;
774
+ height: 12px;
775
+ }
776
+
777
+ .block-row-lane {
778
+ position: relative;
779
+ flex: 1;
780
+ }
781
+
782
+ /* THE CAMERA LANE IS A LINE WITH MARKS ON IT: a 1px hairline 13 from the top,
783
+ from 8 in, and the poses as diamonds on it. The moves are still bars (they
784
+ take the drag, the trim and the menu) but they draw nothing at rest, so the
785
+ lane reads as the boards' keyframe line; hovering or selecting a move shows
786
+ its extent as a clip edge, and its name. */
787
+ .block-row.is-camera .block-row-lane::before {
788
+ content: "";
789
+ position: absolute;
790
+ top: 13px;
791
+ left: 8px;
792
+ right: 0;
793
+ height: 1px;
794
+ background: var(--line);
795
+ pointer-events: none;
796
+ }
797
+
798
+ .block-row.is-camera .block-bar {
799
+ background: transparent;
800
+ border-color: transparent;
801
+ }
802
+
803
+ .block-row.is-camera .block-bar .block-label,
804
+ .block-row.is-camera .block-handle::after {
805
+ visibility: hidden;
806
+ }
807
+
808
+ .block-row.is-camera .block-bar:hover,
809
+ .block-row.is-camera .block-bar.is-selected {
810
+ border-color: var(--clip-edge);
811
+ }
812
+
813
+ .block-row.is-camera .block-bar.is-selected {
814
+ border-color: var(--accent);
815
+ outline: 0;
816
+ }
817
+
818
+ .block-row.is-camera .block-bar:hover .block-label,
819
+ .block-row.is-camera .block-bar.is-selected .block-label {
820
+ visibility: visible;
821
+ }
822
+
823
+ /* Dark fill, bright border, Raylight's clip. A solid bar per move turns a
824
+ busy row into a colour field where nothing is readable; an outline keeps
825
+ forty of them distinct and lets overlaps show through. */
826
+ /* ── THE INSET LIVES ON THE LABEL, NOT ON THE BAR ─────────────────────────
827
+
828
+ `padding: 0 8px` here, under the global `box-sizing: border-box`, put a hard
829
+ FLOOR of 16px plus the border under every bar: a box cannot render narrower
830
+ than its own padding. So a bar the code sized at 9.9px for a one-frame move
831
+ drew 18px, nearly twice its length and most of it over the top of the next
832
+ block. The camera row this was found on has twenty-two moves, eight of them
833
+ one frame long, so eight bars overdrew their neighbour: both a lie about the
834
+ move's length and a theft of the neighbour's trim handle and click target.
835
+
836
+ Moving the same 8px onto `.block-label` keeps every wide bar looking exactly
837
+ as it did, and lets a narrow one be as narrow as the block really is. The
838
+ `Math.max(4, ...)` in BlockBarView stays the only floor, which is what it
839
+ was always meant to be. */
840
+ .block-bar {
841
+ position: absolute;
842
+ top: 4px;
843
+ bottom: 4px;
844
+ display: flex;
845
+ align-items: center;
846
+ border-radius: var(--radius-sm);
847
+ /* THE FILL SAYS WHEN, THE BORDER SAYS WHAT. At rest the fill is the house
848
+ control ground, the same as a lifetime bar's, and the live rule at the
849
+ overrides layer (states.css) paints it while the playhead is inside the bar.
850
+ The border and the label keep the move hue at all times: that hue is the one
851
+ fact a move has that a lifetime bar does not, so it takes the place of the
852
+ control edge here. */
853
+ background: var(--bar-ground);
854
+ border: 1px solid color-mix(in srgb, var(--block-during) 60%, transparent);
855
+ color: color-mix(in srgb, var(--block-during) 40%, var(--timeline-ink));
856
+ cursor: grab;
857
+ overflow: visible;
858
+ white-space: nowrap;
859
+ transition: background var(--dur-fast) var(--ease);
860
+ }
861
+
862
+ .block-bar:hover {
863
+ background: var(--bar-ground-hover);
864
+ }
865
+
866
+ /* Selected is the accent edge and the accent label, the lifetime bar's own
867
+ selection. An inset outline, so it sits on the border instead of a pixel
868
+ outside it, and survives the live rule repainting that border. */
869
+ .block-bar.is-selected {
870
+ outline: 1px solid var(--accent);
871
+ outline-offset: -1px;
872
+ }
873
+
874
+ .block-bar.is-selected .block-label {
875
+ color: var(--accent-ink);
876
+ }
877
+
878
+ .block-label {
879
+ padding: 0 var(--space-4);
880
+ overflow: hidden;
881
+ /* A BACKSTOP, not the mechanism. `fitsBlockLabel` decides whether the label
882
+ is rendered at all, and it estimates the text's width rather than
883
+ measuring it, so this catches the case where the estimate runs a pixel
884
+ short instead of letting a word spill out of its bar. */
885
+ text-overflow: ellipsis;
886
+ pointer-events: none;
887
+ font-weight: 500;
888
+ }
889
+
890
+ /* ── A SHARE OF THE BAR, NOT A FIXED STRIP ───────────────────────────────
891
+
892
+ 7px flat was safe only while `.block-bar` carried padding, because that put
893
+ an 18px floor under every bar and two 7px handles could never meet. With the
894
+ padding moved to the label a bar is as narrow as its block is short, and on a
895
+ one-frame move the two handles covered it end to end: the out handle is later
896
+ in DOM order, so it won every hit test and the body drag became unreachable.
897
+ On a narrow SPRING block it was worse, because that handle swallows the press
898
+ and does nothing, which left the bar completely inert.
899
+
900
+ `min(7px, 35%)` keeps the 7px target on any bar wide enough for it (20px and
901
+ up) and leaves at least 30% of a narrow bar for the body. Below about 8px the
902
+ handles are smaller than the pointer anyway; the bar can still be selected,
903
+ and trimming a block that thin is a zoom-in job either way. */
904
+ .block-handle {
905
+ position: absolute;
906
+ top: 0;
907
+ bottom: 0;
908
+ width: min(7px, 35%);
909
+ cursor: col-resize;
910
+ }
911
+
912
+ .block-handle-in {
913
+ left: -1px;
914
+ }
915
+
916
+ .block-handle-out {
917
+ right: -1px;
918
+ }
919
+
920
+ /* A 1x8 grip centred in each live handle, the lifetime bar's grip. */
921
+ .block-handle:not(.is-disabled)::after {
922
+ content: "";
923
+ position: absolute;
924
+ left: 50%;
925
+ top: 50%;
926
+ width: 1px;
927
+ height: 8px;
928
+ transform: translate(-50%, -50%);
929
+ background: var(--bar-grip);
930
+ pointer-events: none;
931
+ }
932
+
933
+ /* The dead variant has to stop promising a resize on an edge that cannot
934
+ resize. That promise IS the defect, so it loses both the cursor and the
935
+ grip above. */
936
+ .block-handle.is-disabled {
937
+ cursor: default;
938
+ }
939
+
940
+ /* A spring keeps moving past its own window. The tail says so, and the OUT
941
+ handle is drawn dead, with the reason in its title. */
942
+ .block-spring-tail {
943
+ position: absolute;
944
+ right: -18px;
945
+ top: 50%;
946
+ width: 18px;
947
+ border-top: 1px dashed currentColor;
948
+ opacity: 0.6;
949
+ }
950
+
951
+ /* ---- camera poses on the camera row (P4) --------------------------------
952
+ THE CAMERA IS ALREADY A KEYFRAME SYSTEM, and this is its picture. What it
953
+ is NOT is a property lane: a move carries ONE ease for its whole window and
954
+ the gap after it is a hold, so these marks get their own glyph and their own
955
+ state rather than borrowing `.channel-key`'s. Drawing them alike would teach
956
+ an author that every diamond behaves alike, and the first right-click
957
+ expecting an ease menu would prove it wrong.
958
+
959
+ HOLLOW is the distinction, and it carries the meaning: a camera pose is
960
+ read-only here (it lives in `shot.from`/`shot.to`, which the inspector owns)
961
+ while a property keyframe is filled and draggable. */
962
+ .camera-key {
963
+ position: absolute;
964
+ /* A 9x9 square turned 45deg, centred on its frame horizontally and on the
965
+ hairline (13.5) vertically. */
966
+ top: 9px;
967
+ width: 9px;
968
+ height: 9px;
969
+ box-sizing: border-box;
970
+ transform: translateX(-50%) rotate(45deg);
971
+ background: var(--accent);
972
+ border-radius: 1px;
973
+ /* A READING, not a control. Without this the mark would eat the press on a
974
+ bar's own trim handle, and every pose sits exactly on one. */
975
+ pointer-events: none;
976
+ }
977
+
978
+ /* CHAINED: the next move opens on the pose this one closed on, so the camera
979
+ passes through it. The other kind of mark: hollow, the dock's ground inside
980
+ a 1.5px accent edge. A filled diamond is a pose the camera stops on. */
981
+ .camera-key.is-chained {
982
+ background: var(--surface-panel);
983
+ border: 1.5px solid var(--accent);
984
+ }
985
+
986
+ /* A PATH's interior waypoint: smaller, and quieter, because it is the one mark
987
+ here that is not an authorable endpoint at all. Still on the hairline. */
988
+ .camera-key.is-waypoint {
989
+ top: 11px;
990
+ width: 5px;
991
+ height: 5px;
992
+ background: var(--surface-panel);
993
+ border: 1px solid var(--accent-ink);
994
+ }
995
+
996
+ /* THE RIG BREAK. The end of a zoom move that changes the zoom: `cameraRigAt`
997
+ pins the distance only INSIDE that window, so one frame later the distance
998
+ and the fov both jump and every layer at a non-zero depth pops.
999
+ Drawn SPLIT, a warm half-mark with a gap where the other half would be,
1000
+ because an unbroken diamond would promise a continuity that is not there.
1001
+ The warm hue is `--guide`'s family, this lane's "look at this" colour, and
1002
+ deliberately not `--danger`: the document is legal and the render is
1003
+ correct, it is the MOTION that has a step in it. */
1004
+ .camera-key.is-break {
1005
+ background: linear-gradient(to right, var(--accent-warm) 0 45%, transparent 45%);
1006
+ border: 1.5px dashed var(--accent-warm);
1007
+ }
1008
+
1009
+ /* ---- channel rows (one animated property per row — the Lottie read) ------ */
1010
+ /* 28, the row ruling: `CHANNEL_ROW_H` in model.ts, the unit the dock's height
1011
+ sums count channel rows in. */
1012
+ .channel-row {
1013
+ display: flex;
1014
+ align-items: stretch;
1015
+ height: 28px;
1016
+ border-bottom: 1px solid var(--line-soft);
1017
+ }
1018
+
1019
+ .channel-label-cell {
1020
+ padding-left: calc(var(--space-4) + var(--depth, 0) * var(--tree-indent));
1021
+ display: flex;
1022
+ align-items: center;
1023
+ gap: var(--space-3);
1024
+ flex: none;
1025
+ overflow: hidden;
1026
+ }
1027
+
1028
+ /* THE NAME TAKES THE SLACK, rather than the value claiming it with
1029
+ `margin-left: auto`. A row can now have a value and a clear button, a clear
1030
+ button alone (a tracked path with no block channel), or neither — and one
1031
+ auto margin cannot right-pack all three cases, while a growing first child
1032
+ can. */
1033
+ .channel-name {
1034
+ flex: 1;
1035
+ min-width: 0;
1036
+ color: var(--ink-3);
1037
+ font-size: var(--type-micro);
1038
+ white-space: nowrap;
1039
+ overflow: hidden;
1040
+ text-overflow: ellipsis;
1041
+ }
1042
+
1043
+ /* The resolved value at the playhead. Tabular, so playback reads as a counter
1044
+ rather than a word that keeps changing width. */
1045
+ .channel-value {
1046
+ padding-right: var(--space-4);
1047
+ color: var(--ink-2);
1048
+ font-size: var(--type-micro);
1049
+ font-variant-numeric: var(--numeric);
1050
+ white-space: nowrap;
1051
+ overflow: hidden;
1052
+ text-overflow: ellipsis;
1053
+ }
1054
+
1055
+ .channel-lane {
1056
+ position: relative;
1057
+ flex: 1;
1058
+ }
1059
+
1060
+ /* SOLID diamonds, the Lottie/Figma mark, and this shape means a TRACK
1061
+ KEYFRAME and nothing else.
1062
+ `--kf` is the house token for exactly this and is not the accent: a keyframe
1063
+ is a fact about the document, while the accent means selection and playhead.
1064
+ An 8px diamond with a 1px corner, the boards' key. The radius is a literal
1065
+ because the house radius scale starts at 2px, which rounds an 8px diamond
1066
+ into a dot.
1067
+
1068
+ SELECTED KEEPS ITS HUE and grows: 12px with a 1px ink-1 edge. Whitening the
1069
+ fill threw away the one thing the mark says (this is a keyframe) at the
1070
+ moment the author is looking hardest at it; size and an edge say "this one"
1071
+ without spending it. 12px turned 45deg is 17px across, inside the 20px row. */
1072
+ .channel-key {
1073
+ position: absolute;
1074
+ top: 50%;
1075
+ width: 8px;
1076
+ height: 8px;
1077
+ transform: translate(-50%, -50%) rotate(45deg);
1078
+ background: var(--kf);
1079
+ border: 1px solid var(--surface-app);
1080
+ border-radius: 1px;
1081
+ cursor: col-resize;
1082
+ }
1083
+
1084
+ .channel-key:hover {
1085
+ background: color-mix(in srgb, var(--kf) 70%, var(--timeline-ink));
1086
+ }
1087
+
1088
+ .channel-key.is-selected {
1089
+ width: 12px;
1090
+ height: 12px;
1091
+ border-color: var(--ink-1);
1092
+ }
1093
+
1094
+ /* A HOLD does not interpolate, so it is drawn hollow: the segment leaving it is
1095
+ a step, and a filled mark would promise a ramp. A SPRING carries physics past
1096
+ its own segment, and the ring says the motion does not stop at the next key.
1097
+ Both are shape statements on the one mark, never a second hue. */
1098
+ .channel-key.ease-hold {
1099
+ background: transparent;
1100
+ border-color: var(--kf);
1101
+ }
1102
+
1103
+ .channel-key.ease-hold:hover {
1104
+ background: color-mix(in srgb, var(--kf) 30%, transparent);
1105
+ }
1106
+
1107
+ .channel-key.ease-spring {
1108
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--spring) 45%, transparent);
1109
+ }
1110
+
1111
+ /* Remove-animation, on the row that has a track. Visible at rest (a control
1112
+ hidden until hover is one a touch user never finds), but quiet: --ink-3, and
1113
+ red only under the pointer. */
1114
+ .channel-clear {
1115
+ flex: none;
1116
+ margin-right: var(--space-2);
1117
+ padding: 0 var(--space-2);
1118
+ border: 0;
1119
+ background: none;
1120
+ color: var(--ink-3);
1121
+ font: inherit;
1122
+ line-height: var(--leading-none);
1123
+ cursor: pointer;
1124
+ }
1125
+
1126
+ .channel-clear:hover {
1127
+ color: var(--danger-ink);
1128
+ }
1129
+
1130
+ /* The expanded-but-empty layer: say where the first keyframe comes from. The
1131
+ same quiet lane text carries the selected-key status beside "+ property",
1132
+ in mono there, because it is a readout of numbers. */
1133
+ .channel-empty-hint,
1134
+ .channel-add-status {
1135
+ display: flex;
1136
+ align-items: center;
1137
+ padding-left: var(--space-5);
1138
+ color: var(--ink-3);
1139
+ font-size: var(--type-micro);
1140
+ white-space: nowrap;
1141
+ overflow: hidden;
1142
+ text-overflow: ellipsis;
1143
+ }
1144
+
1145
+ /* ---- + property --------------------------------------------------------- */
1146
+ /* The last row of a layer's expansion, and the timeline's own way into
1147
+ keyframing. A BORDERED BUTTON, the boards' recipe: the strong edge, the
1148
+ small radius, 12/18 500 in ink-1. Docked, so flat (no control shadow).
1149
+
1150
+ THE CLIP'S PLACE IN THE ROW: 20 tall at 4 under the row's top, 3 clear of
1151
+ its 1px bottom border, where `.lifespan-bar` sits in the lane beside it.
1152
+ At the row's own 28 (the 28 includes the border) its edge lay on both row
1153
+ lines. 12/18 and the 1px edge make the 20 exactly. The ROW stays
1154
+ `CHANNEL_ROW_H`, the unit `expandedLayerHeight` and `groupHeights` reserve
1155
+ for it. Never a row override of its own: an 18px one once put every row
1156
+ below an expanded layer 2px off its spacer. */
1157
+ .channel-add {
1158
+ display: inline-flex;
1159
+ align-items: center;
1160
+ flex: none;
1161
+ align-self: flex-start;
1162
+ height: 20px;
1163
+ margin-top: var(--space-2);
1164
+ font: inherit;
1165
+ font-size: var(--type-md);
1166
+ line-height: var(--leading-sm);
1167
+ font-weight: 500;
1168
+ color: var(--ink-1);
1169
+ background: transparent;
1170
+ border: 1px solid var(--line-strong);
1171
+ padding: 0 var(--space-4);
1172
+ border-radius: var(--radius-sm);
1173
+ cursor: pointer;
1174
+ }
1175
+
1176
+ .channel-add:hover {
1177
+ background: var(--fill-rest);
1178
+ }
1179
+
1180
+ /* OPEN, while its menu is up: the accent-quiet ground and the accent edge, so
1181
+ the control the menu belongs to is the one that says so. */
1182
+ .channel-add[aria-expanded="true"] {
1183
+ background: var(--accent-quiet);
1184
+ border-color: var(--accent);
1185
+ }
1186
+
1187
+ /* The status beside it is a readout, not a label. */
1188
+ .channel-add-status {
1189
+ font-family: var(--font-mono);
1190
+ font-variant-numeric: var(--numeric);
1191
+ }
1192
+
1193
+ /* THE HOUSE MENU, restated rather than borrowed. `Popover` already draws the
1194
+ panel ground, the strong edge, the radius and the popover shadow; the rows
1195
+ below take `.menu-item`'s metrics (30 tall, 12/20, the small radius, the
1196
+ accent-quiet hover) on their own class, because styling one element from two
1197
+ sheets at equal specificity is the thing this codebase forbids. 320 wide, so
1198
+ a name and its note sit on one line. */
1199
+ .property-pop {
1200
+ width: 320px;
1201
+ max-height: 340px;
1202
+ overflow-y: auto;
1203
+ padding: var(--space-3);
1204
+ }
1205
+
1206
+ /* Sentence case, the house's group label: the caps-and-tracking version read as
1207
+ a second heading level the menu does not have. */
1208
+ .property-group-label {
1209
+ display: flex;
1210
+ align-items: center;
1211
+ min-height: 24px;
1212
+ padding: 0 var(--space-4);
1213
+ font-size: var(--type-md);
1214
+ line-height: var(--leading-md);
1215
+ font-weight: 500;
1216
+ color: var(--ink-3);
1217
+ }
1218
+
1219
+ .property-item {
1220
+ display: flex;
1221
+ align-items: center;
1222
+ gap: var(--space-4);
1223
+ width: 100%;
1224
+ min-height: 30px;
1225
+ font: inherit;
1226
+ font-size: var(--type-md);
1227
+ line-height: var(--leading-md);
1228
+ text-align: left;
1229
+ color: var(--ink-1);
1230
+ background: transparent;
1231
+ border: 0;
1232
+ padding: 0 var(--space-4);
1233
+ border-radius: var(--radius-sm);
1234
+ cursor: pointer;
1235
+ }
1236
+
1237
+ .property-item:hover:not(:disabled) {
1238
+ background: var(--accent-quiet);
1239
+ }
1240
+
1241
+ /* REFUSED, NOT HIDDEN. The reason rides beside the name — "why can I not
1242
+ animate the stroke width" is answered where it is asked. */
1243
+ .property-item:disabled {
1244
+ color: var(--ink-disabled);
1245
+ cursor: default;
1246
+ }
1247
+
1248
+ .property-item-note {
1249
+ margin-left: auto;
1250
+ /* The house menu's detail size (`.menu-detail`, `.menu-shortcut`). */
1251
+ font-size: var(--type-sm);
1252
+ color: var(--ink-3);
1253
+ white-space: nowrap;
1254
+ overflow: hidden;
1255
+ text-overflow: ellipsis;
1256
+ max-width: 55%;
1257
+ }
1258
+
1259
+ /* A tracked property's name stays ink-1: the "animated" note beside it is the
1260
+ mark. Tinting the name too said one fact twice, and spent the accent ink in a
1261
+ menu where the accent means "you are on this row". */
1262
+
1263
+ /* ---- audio lane --------------------------------------------------------- */
1264
+ /* A DISTINCT COLOURED BAND under the video rows. Taller than a block row,
1265
+ because the waveform inside it is the thing the author is reading. 32 with
1266
+ its seam, the boards' sound row: AUDIO_ROW_H in model.ts (AUDIO_LANE_H in
1267
+ AudioLane.tsx is the same number), which the fitted dock height counts. */
1268
+ .audio-lane {
1269
+ display: flex;
1270
+ align-items: stretch;
1271
+ position: relative;
1272
+ height: 32px;
1273
+ border-top: 1px solid var(--line);
1274
+ background: color-mix(in srgb, var(--audio-clip) 4%, transparent);
1275
+ }
1276
+
1277
+ /* A file dragged over the lane. The whole lane lights, not the row under the
1278
+ pointer: the clip lands at the PLAYHEAD (see AudioLane), so highlighting a
1279
+ position would promise a drop point the import does not honour. */
1280
+ .audio-lane.is-over {
1281
+ background: color-mix(in srgb, var(--audio-clip) 14%, transparent);
1282
+ outline: 1px dashed var(--audio-clip);
1283
+ outline-offset: -1px;
1284
+ }
1285
+
1286
+ /* Inset 16, the shot and camera rows' inset, so the three swatches stand in
1287
+ one column. */
1288
+ .audio-label-cell {
1289
+ display: flex;
1290
+ align-items: center;
1291
+ gap: var(--space-4);
1292
+ flex: none;
1293
+ padding-left: var(--space-7);
1294
+ overflow: hidden;
1295
+ }
1296
+
1297
+ /* The sound row's mark, in both audio rows: a teal 8px square, the lane's own
1298
+ hue, where the shot row wears the accent one. */
1299
+ .audio-swatch {
1300
+ flex: none;
1301
+ width: 8px;
1302
+ height: 8px;
1303
+ border-radius: var(--radius-2xs);
1304
+ background: var(--audio-clip);
1305
+ }
1306
+
1307
+ /* A SHOT'S OWN AUDIO, inside that shot's group. The master lane's height and
1308
+ tint (`SHOT_AUDIO_ROW_H` is `AUDIO_LANE_H`), so the two read as one medium in
1309
+ two places rather than two features. Inside a shot it is the only audio row,
1310
+ 32 tall. Its seam is an inset line rather than a border, so the row's box is
1311
+ the whole 32 the group sums count. Its empty state is the same drop box as
1312
+ the master lane's, below. */
1313
+ .scene-audio-row {
1314
+ display: flex;
1315
+ align-items: stretch;
1316
+ position: relative;
1317
+ height: 32px;
1318
+ box-shadow: inset 0 1px 0 var(--line);
1319
+ background: color-mix(in srgb, var(--audio-clip) 4%, transparent);
1320
+ }
1321
+
1322
+ .scene-audio-row.is-over {
1323
+ background: color-mix(in srgb, var(--audio-clip) 14%, transparent);
1324
+ outline: 1px dashed var(--audio-clip);
1325
+ outline-offset: -1px;
1326
+ }
1327
+
1328
+ .scene-audio-label {
1329
+ position: sticky;
1330
+ left: 0;
1331
+ z-index: 4; /* the sticky label column's layer (see the shared rule) */
1332
+ display: flex;
1333
+ align-items: center;
1334
+ gap: var(--space-4);
1335
+ flex: none;
1336
+ /* 16 in, the swatch column the shot and camera rows share. */
1337
+ padding-left: var(--space-7);
1338
+ overflow: hidden;
1339
+ background-color: var(--surface-panel);
1340
+ }
1341
+
1342
+ /* "Sound", the row's name: 12/600 ink, a heading row like the shot's. */
1343
+ .scene-audio-name,
1344
+ .audio-lane-name {
1345
+ font-size: var(--type-md);
1346
+ font-weight: var(--weight-semibold);
1347
+ color: var(--ink-1);
1348
+ flex: 1;
1349
+ min-width: 0;
1350
+ white-space: nowrap;
1351
+ overflow: hidden;
1352
+ text-overflow: ellipsis;
1353
+ }
1354
+
1355
+ .audio-add {
1356
+ font: inherit;
1357
+ color: var(--ink-2);
1358
+ background: var(--fill-rest);
1359
+ border: 0;
1360
+ border-radius: var(--radius-xs);
1361
+ padding: 1px var(--space-3);
1362
+ margin-right: var(--space-3);
1363
+ cursor: pointer;
1364
+ flex: none;
1365
+ }
1366
+
1367
+ .audio-add:hover:not(:disabled) {
1368
+ color: var(--ink-1);
1369
+ background: var(--fill-hover);
1370
+ }
1371
+
1372
+ .audio-track {
1373
+ position: relative;
1374
+ flex: 1;
1375
+ /* THE SHOT CONTEXT'S LEFT EDGE. This lane is master-timed and the shot view
1376
+ re-zeros it on the shot, so a bed that started earlier is drawn from a
1377
+ NEGATIVE x — which without this lands on top of the label cell. `clip` and
1378
+ not `hidden`: hidden on one axis forces `auto` on the other, and the fade
1379
+ corners and the gain band overflow vertically by design. The margin keeps
1380
+ a bar that starts exactly at the shot's zero from losing its rounded end. */
1381
+ overflow-x: clip;
1382
+ overflow-clip-margin: 8px;
1383
+ }
1384
+
1385
+ /* THE DROP BOX, the boards' empty audio row in both the master lane and a
1386
+ shot's own row: a dashed line edge on the sunken ground, so it reads as a
1387
+ place a file goes rather than as one more clip. The copy says the two ways
1388
+ in, and the mono hint after it lists the formats the import accepts, built
1389
+ from the same list the file input's `accept` is (AudioLane.tsx), so the box
1390
+ cannot advertise a format the import refuses. Inert to the pointer: the
1391
+ drop target is the whole row, and the box is only its label. */
1392
+ .audio-empty {
1393
+ position: absolute;
1394
+ left: var(--space-2);
1395
+ right: var(--space-2);
1396
+ max-width: 420px;
1397
+ top: var(--space-2);
1398
+ bottom: var(--space-2);
1399
+ display: flex;
1400
+ align-items: center;
1401
+ gap: var(--space-4);
1402
+ padding: 0 var(--space-5);
1403
+ border: 1px dashed var(--line);
1404
+ border-radius: var(--radius-xs);
1405
+ background: var(--surface-sunken);
1406
+ color: var(--ink-3);
1407
+ font-size: var(--type-sm);
1408
+ pointer-events: none;
1409
+ white-space: nowrap;
1410
+ overflow: hidden;
1411
+ }
1412
+
1413
+ .audio-empty-copy {
1414
+ min-width: 0;
1415
+ overflow: hidden;
1416
+ text-overflow: ellipsis;
1417
+ }
1418
+
1419
+ .audio-empty-formats {
1420
+ flex: none;
1421
+ font-family: var(--font-mono);
1422
+ font-size: var(--type-sm);
1423
+ color: var(--ink-3);
1424
+ }
1425
+
1426
+ /* Same quiet-fill/bright-border grammar as a block bar, in the lane's own hue.
1427
+ The 4px inset is AUDIO_CLIP_INSET in AudioLane.tsx, which sizes the wave. */
1428
+ .audio-clip {
1429
+ position: absolute;
1430
+ top: 4px;
1431
+ bottom: 4px;
1432
+ border-radius: var(--radius-sm);
1433
+ background: color-mix(in srgb, var(--audio-clip) 16%, transparent);
1434
+ border: 1px solid color-mix(in srgb, var(--audio-clip) 65%, transparent);
1435
+ color: color-mix(in srgb, var(--audio-clip) 35%, var(--timeline-ink));
1436
+ cursor: grab;
1437
+ overflow: hidden;
1438
+ }
1439
+
1440
+ .audio-clip:hover {
1441
+ background: color-mix(in srgb, var(--audio-clip) 26%, transparent);
1442
+ }
1443
+
1444
+ /* A muted clip is KEPT and dimmed, never hidden: mute is a mix decision the
1445
+ author has to be able to see in order to undo. */
1446
+ .audio-clip.is-muted {
1447
+ opacity: 0.45;
1448
+ border-style: dashed;
1449
+ }
1450
+
1451
+ .audio-wave {
1452
+ position: absolute;
1453
+ inset: 0;
1454
+ width: 100%;
1455
+ height: 100%;
1456
+ pointer-events: none;
1457
+ }
1458
+
1459
+ .audio-fade-curve {
1460
+ position: absolute;
1461
+ inset: 0;
1462
+ width: 100%;
1463
+ height: 100%;
1464
+ pointer-events: none;
1465
+ }
1466
+
1467
+ .audio-fade-curve polyline {
1468
+ stroke: var(--audio-line);
1469
+ stroke-width: 1;
1470
+ vector-effect: non-scaling-stroke;
1471
+ }
1472
+
1473
+ .audio-clip-name {
1474
+ position: absolute;
1475
+ left: 8px;
1476
+ top: 1px;
1477
+ pointer-events: none;
1478
+ font-weight: 500;
1479
+ max-width: calc(100% - 46px);
1480
+ overflow: hidden;
1481
+ text-overflow: ellipsis;
1482
+ white-space: nowrap;
1483
+ }
1484
+
1485
+ /* THE SOUND ROW'S CLIP, in the dock's two audio rows (Sound mode draws its
1486
+ own, timeline-sound.css): 24 tall at 4 from the top (the base rule's inset),
1487
+ the teal wash 18% to 6% under a 55% teal edge, radius 6. The waveform is
1488
+ drawn in the same teal (`--audio-waveform` is `--audio-clip` in shotTokens.css;
1489
+ the canvas reads it off itself) at 0.75. */
1490
+ .audio-lane .audio-clip,
1491
+ .scene-audio-row .audio-clip {
1492
+ border-radius: var(--radius-sm);
1493
+ background: linear-gradient(
1494
+ in oklab 180deg,
1495
+ color-mix(in oklab, var(--audio-clip) 18%, transparent),
1496
+ color-mix(in oklab, var(--audio-clip) 6%, transparent)
1497
+ );
1498
+ border: 1px solid color-mix(in oklab, var(--audio-clip) 55%, transparent);
1499
+ }
1500
+
1501
+ .audio-lane .audio-clip:hover,
1502
+ .scene-audio-row .audio-clip:hover {
1503
+ border-color: color-mix(in oklab, var(--audio-clip) 80%, transparent);
1504
+ }
1505
+
1506
+ .audio-lane .audio-wave,
1507
+ .scene-audio-row .audio-wave {
1508
+ opacity: 0.75;
1509
+ }
1510
+
1511
+ /* The clip's name as a chip on the wave: a dark teal tile with light teal
1512
+ type, 11/600, radius 4, 16 tall, so the name reads over any waveform. */
1513
+ .audio-lane .audio-clip-name,
1514
+ .scene-audio-row .audio-clip-name {
1515
+ top: 50%;
1516
+ left: 4px;
1517
+ transform: translateY(-50%);
1518
+ /* Block, so the base rule's ellipsis works; the 16px line is the chip. */
1519
+ display: block;
1520
+ padding: 0 calc(var(--space-5) / 2);
1521
+ border-radius: var(--radius-xs);
1522
+ background: var(--audio-chip-bg);
1523
+ color: var(--audio-chip-ink);
1524
+ font-size: var(--type-sm);
1525
+ font-weight: var(--weight-semibold);
1526
+ line-height: 16px;
1527
+ }
1528
+
1529
+ /* The rubber band. A hairline across the whole clip with a fat invisible grab
1530
+ area, because a 1px drag target is a control nobody can hit. */
1531
+ .audio-gain-band {
1532
+ position: absolute;
1533
+ left: 0;
1534
+ right: 0;
1535
+ height: 9px;
1536
+ margin-bottom: calc(-1 * var(--space-2));
1537
+ cursor: ns-resize;
1538
+ }
1539
+
1540
+ .audio-gain-band::after {
1541
+ content: "";
1542
+ position: absolute;
1543
+ left: 0;
1544
+ right: 0;
1545
+ top: 4px;
1546
+ height: 1px;
1547
+ background: var(--audio-line);
1548
+ opacity: 0.8;
1549
+ }
1550
+
1551
+ /* AUTOMATED: the flat line is gone (the envelope svg draws the level now) and
1552
+ the band becomes an invisible full-height grab area for the key under the
1553
+ playhead. Bottom 0 / top 0 rather than a level, because there is no single
1554
+ level left to sit at. */
1555
+ .audio-gain-band.is-automated {
1556
+ top: 0;
1557
+ bottom: 0;
1558
+ height: auto;
1559
+ margin-bottom: 0;
1560
+ }
1561
+
1562
+ .audio-gain-band.is-automated::after {
1563
+ content: none;
1564
+ }
1565
+
1566
+ /* The sampled envelope, through the SAME engine function the mixer uses. */
1567
+ .audio-gain-curve {
1568
+ position: absolute;
1569
+ inset: 0;
1570
+ width: 100%;
1571
+ height: 100%;
1572
+ pointer-events: none;
1573
+ }
1574
+
1575
+ .audio-gain-curve polyline {
1576
+ stroke: var(--audio-line);
1577
+ stroke-width: 1;
1578
+ vector-effect: non-scaling-stroke;
1579
+ opacity: 0.9;
1580
+ }
1581
+
1582
+ /* The tail of an owned clip that runs past its shot: kept in the document,
1583
+ absent from the mix. Hatched so it reads as "not this" rather than as a
1584
+ different clip, and inert so every gesture still belongs to the bar. */
1585
+ .audio-overrun {
1586
+ position: absolute;
1587
+ top: 0;
1588
+ bottom: 0;
1589
+ right: 0;
1590
+ pointer-events: none;
1591
+ background: repeating-linear-gradient(
1592
+ -45deg,
1593
+ color-mix(in srgb, var(--surface-app) 70%, transparent) 0 4px,
1594
+ transparent 4px 8px
1595
+ );
1596
+ border-left: 1px solid var(--audio-line);
1597
+ }
1598
+
1599
+ /* Centred on its point by `translate`, which composes with the rotation
1600
+ rather than replacing it: `left` and `bottom` name the key's frame and gain
1601
+ exactly. */
1602
+ .audio-gain-key {
1603
+ position: absolute;
1604
+ width: 5px;
1605
+ height: 5px;
1606
+ translate: -50% 50%;
1607
+ background: var(--audio-line);
1608
+ transform: rotate(45deg);
1609
+ pointer-events: none;
1610
+ }
1611
+
1612
+ .audio-handle {
1613
+ position: absolute;
1614
+ top: 0;
1615
+ bottom: 0;
1616
+ width: 7px;
1617
+ cursor: col-resize;
1618
+ }
1619
+
1620
+ .audio-handle-in {
1621
+ left: -1px;
1622
+ }
1623
+
1624
+ .audio-handle-out {
1625
+ right: -1px;
1626
+ }
1627
+
1628
+ /* The fade corners ride ON the ramp's top end, so the handle is where the
1629
+ author sees the fade finish rather than at the edge of the clip. Centred on
1630
+ that x by `translate`. */
1631
+ .audio-fade {
1632
+ position: absolute;
1633
+ top: -1px;
1634
+ width: 11px;
1635
+ height: 11px;
1636
+ translate: -50% 0;
1637
+ border-radius: var(--radius-full);
1638
+ cursor: ew-resize;
1639
+ background: color-mix(in srgb, var(--audio-line) 70%, transparent);
1640
+ }
1641
+
1642
+ .audio-clip-buttons {
1643
+ position: absolute;
1644
+ right: 2px;
1645
+ top: 1px;
1646
+ display: flex;
1647
+ gap: var(--space-1);
1648
+ }
1649
+
1650
+ .audio-clip-btn {
1651
+ font: inherit;
1652
+ font-size: var(--type-micro);
1653
+ line-height: var(--leading-none);
1654
+ color: var(--ink-2);
1655
+ background: transparent;
1656
+ border: 0;
1657
+ padding: var(--space-1);
1658
+ border-radius: var(--radius-xs);
1659
+ cursor: pointer;
1660
+ }
1661
+
1662
+ .audio-clip-btn:hover {
1663
+ color: var(--ink-1);
1664
+ background: var(--fill-rest);
1665
+ }
1666
+
1667
+ /* DUCKING. The button is present whether or not the clip ducks (a
1668
+ capability's only affordance stays visible at rest); armed is what changes,
1669
+ and it uses the accent the rest of the chrome uses for an active tool. */
1670
+ .audio-duck {
1671
+ position: relative;
1672
+ }
1673
+
1674
+ .audio-clip-btn[aria-pressed="true"] {
1675
+ color: var(--accent-ink);
1676
+ }
1677
+
1678
+ .audio-duck-pop {
1679
+ position: absolute;
1680
+ right: 0;
1681
+ top: 100%;
1682
+ /* Local to the dock (a stacking context): above the ruler (5) and the
1683
+ resize edge (6). */
1684
+ z-index: 7;
1685
+ min-width: 190px;
1686
+ margin-top: var(--space-2);
1687
+ padding: var(--space-4);
1688
+ display: flex;
1689
+ flex-direction: column;
1690
+ gap: var(--space-2);
1691
+ background: var(--surface-raised);
1692
+ border: 1px solid var(--line);
1693
+ border-radius: var(--radius-lg);
1694
+ box-shadow: 0 8px 24px rgb(0 0 0 / 0.35);
1695
+ }
1696
+
1697
+ .audio-duck-title,
1698
+ .audio-duck-empty {
1699
+ margin: 0 0 var(--space-1);
1700
+ font-size: var(--type-sm);
1701
+ color: var(--ink-2);
1702
+ }
1703
+
1704
+ .audio-duck-row {
1705
+ display: flex;
1706
+ align-items: center;
1707
+ gap: var(--space-3);
1708
+ font-size: var(--type-sm);
1709
+ color: var(--ink-1);
1710
+ }
1711
+
1712
+ .audio-duck-strength {
1713
+ margin-top: var(--space-2);
1714
+ padding-top: var(--space-3);
1715
+ border-top: 1px solid var(--line);
1716
+ }
1717
+
1718
+ .audio-duck-strength input[type="range"] {
1719
+ flex: 1;
1720
+ min-width: 0;
1721
+ }
1722
+
1723
+ .audio-duck-db {
1724
+ min-width: 40px;
1725
+ text-align: right;
1726
+ font-variant-numeric: var(--numeric);
1727
+ color: var(--ink-2);
1728
+ }
1729
+
1730
+ /* CLIP EFFECTS: the duck's popover, a stage per block. */
1731
+ .audio-fx-btn {
1732
+ font: var(--weight-medium) var(--type-micro)/1 var(--font-sans);
1733
+ letter-spacing: var(--tracking-caps);
1734
+ }
1735
+
1736
+ .audio-fx-pop {
1737
+ min-width: 260px;
1738
+ }
1739
+
1740
+ .audio-fx-stage {
1741
+ margin-top: var(--space-2);
1742
+ padding-top: var(--space-3);
1743
+ border-top: 1px solid var(--line);
1744
+ }
1745
+
1746
+ .audio-fx-label {
1747
+ min-width: 64px;
1748
+ }
1749
+
1750
+ .audio-fx-slider input[type="range"] {
1751
+ flex: 1;
1752
+ min-width: 0;
1753
+ }
1754
+
1755
+ .audio-fx-room {
1756
+ margin-left: auto;
1757
+ }
1758
+
1759
+ .audio-fx-foot {
1760
+ margin-top: var(--space-2);
1761
+ display: flex;
1762
+ align-items: center;
1763
+ justify-content: space-between;
1764
+ gap: var(--space-3);
1765
+ }
1766
+
1767
+ .audio-error {
1768
+ position: absolute;
1769
+ left: 8px;
1770
+ bottom: 2px;
1771
+ margin: 0;
1772
+ color: var(--danger-ink);
1773
+ pointer-events: none;
1774
+ }
1775
+
1776
+ /* ---- marker lane -------------------------------------------------------- */
1777
+ /* A THIN ORANGE STRIP over the audio. Short on purpose: a marker carries no
1778
+ duration, so there is nothing here that gets more readable with height —
1779
+ every pixel it takes is a pixel off the tracks, which do. */
1780
+ .marker-lane {
1781
+ display: flex;
1782
+ align-items: stretch;
1783
+ position: relative;
1784
+ height: 20px;
1785
+ border-top: 1px solid var(--line);
1786
+ background: color-mix(in srgb, var(--marker) 10%, transparent);
1787
+ }
1788
+
1789
+ .marker-label-cell {
1790
+ display: flex;
1791
+ align-items: center;
1792
+ gap: var(--space-3);
1793
+ flex: none;
1794
+ padding-left: var(--space-4);
1795
+ overflow: hidden;
1796
+ }
1797
+
1798
+ .marker-lane-name {
1799
+ color: var(--ink-2);
1800
+ flex: 1;
1801
+ min-width: 0;
1802
+ white-space: nowrap;
1803
+ overflow: hidden;
1804
+ text-overflow: ellipsis;
1805
+ }
1806
+
1807
+ .marker-add {
1808
+ font: inherit;
1809
+ line-height: var(--leading-none);
1810
+ color: var(--ink-2);
1811
+ background: var(--fill-rest);
1812
+ border: 0;
1813
+ border-radius: var(--radius-xs);
1814
+ padding: 1px var(--space-3);
1815
+ margin-right: var(--space-3);
1816
+ cursor: pointer;
1817
+ flex: none;
1818
+ }
1819
+
1820
+ .marker-add:hover {
1821
+ color: var(--ink-1);
1822
+ background: var(--fill-hover);
1823
+ }
1824
+
1825
+ .marker-track {
1826
+ position: relative;
1827
+ flex: 1;
1828
+ /* The audio track's rule, for the same reason: this lane is master-timed and
1829
+ the shot view re-zeroes it, so a mark before the shot's start is drawn at a
1830
+ NEGATIVE x and would land on the label cell. Clipping here rather than
1831
+ filtering the list is what keeps a pin MOUNTED while it is being dragged
1832
+ across that edge — the drag holds the pointer capture on the node. `clip`
1833
+ and not `hidden`, so the hover label may still overflow vertically. */
1834
+ overflow-x: clip;
1835
+ overflow-clip-margin: 8px;
1836
+ }
1837
+
1838
+ .marker-empty {
1839
+ position: absolute;
1840
+ left: 10px;
1841
+ top: 50%;
1842
+ transform: translateY(-50%);
1843
+ color: var(--ink-3);
1844
+ pointer-events: none;
1845
+ white-space: nowrap;
1846
+ }
1847
+
1848
+ /* CENTRED ON ITS FRAME, which is why the pin is a zero-width anchor with the
1849
+ diamond translated out of it: `left` is the frame's exact x, so a diamond
1850
+ given a width would sit a few px late and drift further as the author zoomed
1851
+ in — the one thing a marker must never do. */
1852
+ .marker-pin {
1853
+ position: absolute;
1854
+ top: 0;
1855
+ bottom: 0;
1856
+ width: 0;
1857
+ display: flex;
1858
+ align-items: center;
1859
+ cursor: grab;
1860
+ }
1861
+
1862
+ .marker-diamond {
1863
+ width: 8px;
1864
+ height: 8px;
1865
+ margin-left: calc(-1 * var(--space-2));
1866
+ background: var(--marker-pin);
1867
+ border: 1px solid color-mix(in srgb, var(--marker) 70%, #000000);
1868
+ transform: rotate(45deg);
1869
+ flex: none;
1870
+ }
1871
+
1872
+ .marker-pin.is-selected .marker-diamond {
1873
+ background: var(--marker);
1874
+ border-color: var(--marker-pin);
1875
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--marker) 45%, transparent);
1876
+ }
1877
+
1878
+ .marker-pin.is-at-playhead .marker-diamond {
1879
+ border-color: var(--playhead);
1880
+ }
1881
+
1882
+ /* HOVER (and selection) only. Always-on labels turn a strip of beats into a
1883
+ wall of overlapping text at any zoom the author actually works at. */
1884
+ .marker-pin-label {
1885
+ display: none;
1886
+ position: absolute;
1887
+ left: 8px;
1888
+ white-space: nowrap;
1889
+ padding: 0 var(--space-2);
1890
+ border-radius: var(--radius-2xs);
1891
+ color: var(--ink-1);
1892
+ background: var(--surface-panel);
1893
+ border: 1px solid var(--line);
1894
+ pointer-events: none;
1895
+ z-index: 1;
1896
+ }
1897
+
1898
+ .marker-pin:hover .marker-pin-label,
1899
+ .marker-pin.is-selected .marker-pin-label {
1900
+ display: block;
1901
+ }
1902
+
1903
+ /* ---- three kinds of row, told apart ------------------------------------- */
1904
+ /* Every row carried the same hairline, the same label ink and (for layers) the
1905
+ same hueless bar, so a shot header, its camera and its elements read as one
1906
+ undifferentiated stack. The hierarchy is real; it just was not drawn.
1907
+
1908
+ Heights are UNTOUCHED. `SHOT_ROW_H`, `expandedLayerHeight` and
1909
+ `groupHeights` mirror them in JS for virtualisation, and `dockFollow`'s
1910
+ scroll maths asserts them — so this is colour and weight only. */
1911
+ /* NO GROUND STEP ON THE SHOT ROW. Every row sits on the dock's one ground,
1912
+ split by the house divider `--line-soft`; a shot header is told from its
1913
+ element rows by its accent swatch, its 12/600 name and its own clip, never
1914
+ by a tint. */
1915
+ .shot-row {
1916
+ position: relative;
1917
+ }
1918
+
1919
+ /* THE LAST ROW HAS NO LINE. The last shot's last row sits on the marker or
1920
+ Sound lane's own `--line` top edge, and two rules stacked read as a
1921
+ double seam. The border goes TRANSPARENT rather than away, so the row keeps
1922
+ its 1px and `groupHeights` still adds up. */
1923
+ .dock-scene-group:not(:has(~ .dock-scene-group))
1924
+ > :is(.shot-row, .lifespan-row, .block-row, .channel-row):last-child {
1925
+ border-bottom-color: transparent;
1926
+ }
1927
+
1928
+ /* The element rows get the outliner's own kind glyph, so a row means the same
1929
+ thing in both panels. `.layer-kind` is reused deliberately and NOT
1930
+ re-coloured at rest: its neutral-with-an-accent-for-components rule is the
1931
+ taxonomy, and any dock-scoped rest colour would outrank
1932
+ `.layer-kind.is-component` on specificity and flatten it. */
1933
+ .lifespan-label-cell .layer-kind {
1934
+ width: 16px;
1935
+ }
1936
+
1937
+ .lifespan-label-cell .layer-kind svg {
1938
+ width: 14px;
1939
+ height: 14px;
1940
+ }
1941
+
1942
+ /* SELECTED, the glyph alone takes the accent ink, and the name stays ink: the
1943
+ boards mark the selected layer with its kind icon and its bar label, not by
1944
+ tinting a column of names. This flattens nothing, because a component's
1945
+ glyph is already the accent ink. */
1946
+ .lifespan-row.is-selected .layer-kind {
1947
+ color: var(--accent-ink);
1948
+ }
1949
+
1950
+ /* The two row disclosures hold an inline <svg> where a text glyph used to
1951
+ sit, so their line box still reserved the font's descender under it and the
1952
+ icon rode ~1.5px high. `inline-flex` drops the baseline and centres the box
1953
+ instead. (The head's chips are `.btn`, which is already this.) */
1954
+ .shot-disclosure,
1955
+ .lifespan-disclosure {
1956
+ display: inline-flex;
1957
+ align-items: center;
1958
+ justify-content: center;
1959
+ }