@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
@@ -1,45 +1,198 @@
1
- // How this shot hands over to the next one.
1
+ // How a shot hands over to the next one.
2
2
  //
3
- // A transition is stored on the OUTGOING shot — the one selected here — and
4
- // plays over the opening frames of the one after it. That is why the section is
5
- // titled "Into next shot" and why the last shot's controls are inert: there is
6
- // nothing after it to blend into. It stays EDITABLE there rather than hidden,
3
+ // A transition is stored on the OUTGOING shot and plays over the opening
4
+ // frames of the one after it. The last shot's controls are inert: there is
5
+ // nothing after it to blend into. They stay EDITABLE rather than hidden,
7
6
  // because reordering can make any shot last and then not last again, and a
8
7
  // setting that vanishes when you drag a bar looks like data loss.
9
8
  //
10
- // The preset list comes from the ROOT of @dailies-studio/transitions, never from
11
- // its ./gl entry: that entry's first import is `three/tsl`, and the chrome
12
- // bundle must not carry a shader graph layer to draw a dropdown. The root's
13
- // `glPresetSpecs` is the same data gl.ts builds against, asserted in lockstep
14
- // at import.
9
+ // ── TWO DOORS, ONE SET OF ROWS ───────────────────────────────────────────
10
+ //
11
+ // Studio's project context mounts `ShotTransitionSection`: the selected shot's
12
+ // own rows, then these. The board mounts `StoryboardCutSection` when a cut is
13
+ // its subject. Both render `TransitionRows` with an EXPLICIT shot id. The rows
14
+ // used to read `state.sceneId`, which every seek re-pins, so a scrub across
15
+ // the seam would have switched them to the incoming shot halfway through.
16
+ //
17
+ // ── PRESETS AS FOUR BUTTONS, THE REST BEHIND "Browse all" ────────────────
18
+ //
19
+ // Cut, Fade, Directional Wipe and Circle Open are plain buttons with
20
+ // `aria-pressed`, NOT a `SegmentedControl`. A radio group commits on an arrow
21
+ // key, and an arrow step over Cut deletes the record: the next step rebuilds
22
+ // it at the default length with its ease, params and Outgoing gone. A stored
23
+ // kind outside the four takes the fourth slot, so the lit button always names
24
+ // what is stored. The full registry stays one press away in the Combobox.
25
+ //
26
+ // The preset list comes from the ROOT of @dailies-studio/transitions, never
27
+ // from its ./gl entry: that entry's first import is `three/tsl`, and the
28
+ // chrome bundle must not carry a shader graph layer to draw a dropdown. The
29
+ // root's `glPresetSpecs` is the same data gl.ts builds against, asserted in
30
+ // lockstep at import.
31
+ import { Fragment, useId, useState } from "react";
15
32
  import {
33
+ Combobox,
34
+ FieldBox,
35
+ FieldLabels,
16
36
  InspectorRow,
17
37
  InspectorSection,
18
38
  NumberField,
39
+ SegmentedControl,
40
+ useLocked,
19
41
  } from "@dailies-studio/ui";
42
+ import type { ComboboxOption, ScrubHandlers } from "@dailies-studio/ui";
20
43
  import { glPresetSpecs, resolveGlPresetSpec } from "@dailies-studio/transitions";
21
- import { MIN_TRANSITION_FRAMES } from "@dailies-studio/shot/doc";
22
- import type { Value } from "@dailies-studio/engine";
44
+ import { DEFAULT_TRANSITION_FRAMES, MIN_TRANSITION_FRAMES, partOfScene, seamWindow, sceneStart } from "@dailies-studio/shot/doc";
45
+ import type { Doc3D, SceneTransition } from "@dailies-studio/shot/doc";
46
+ import { EASE_PRESETS } from "@dailies-studio/timeline";
47
+ import { framesToSeconds } from "@dailies-studio/engine";
48
+ import type { Ease, Value } from "@dailies-studio/engine";
23
49
  import { useShotEditor, useStore } from "../../../state/context.js";
50
+ import { boardSubjectOf } from "../../../state/storyboardModel.js";
51
+ import { SECTION_ICONS } from "../../../icons.js";
52
+ import { formatDuration, timecode } from "../../timeline/model.js";
53
+ import { ShotRows } from "./SceneSection.js";
24
54
  import { ParamRow } from "../ParamRow.js";
55
+ import { EaseControl } from "../animate/EaseControl.js";
25
56
 
26
- /** "no transition", as a select value. Not a preset id, and cannot be one. */
57
+ /** "no transition", as a picker value. Not a preset id, and cannot be one. */
27
58
  const CUT = "";
28
59
 
29
- /** What a transition looks like when first chosen: a third of a second at 30fps,
30
- * which reads as a transition rather than as a glitch. */
31
- const DEFAULT_FRAMES = 10;
60
+ /** The three presets with a button of their own, after Cut. */
61
+ const TILE_KINDS = ["gl/fade", "gl/directional-wipe", "gl/circle-open"] as const;
62
+
63
+ /** Each tile's picture of its blend, 28x14: A is the outgoing frame (the
64
+ * outline), B the incoming one (the fill). A kind outside the four gets the
65
+ * plain A-to-B glyph. */
66
+ function TileGlyph({ kind }: { kind: string }) {
67
+ // Per mount: a fixed id repeats in every tile set on the page (the board and
68
+ // the inspector), and `url(#id)` then paints from whichever came first.
69
+ const fade = useId();
70
+ return (
71
+ <svg className="insp-tile-glyph" viewBox="0 0 28 14" aria-hidden="true">
72
+ {kind === CUT ? (
73
+ <>
74
+ <rect x="1" y="1" width="11" height="12" rx="1.5" />
75
+ <rect x="16" y="1" width="11" height="12" rx="1.5" className="is-b" />
76
+ </>
77
+ ) : kind === "gl/fade" ? (
78
+ <>
79
+ <defs>
80
+ <linearGradient id={fade}>
81
+ <stop offset="0" stopColor="currentColor" stopOpacity="0" />
82
+ <stop offset="1" stopColor="currentColor" stopOpacity="0.9" />
83
+ </linearGradient>
84
+ </defs>
85
+ <rect x="1" y="1" width="26" height="12" rx="1.5" fill={`url(#${fade})`} />
86
+ </>
87
+ ) : kind === "gl/directional-wipe" ? (
88
+ <>
89
+ <rect x="1" y="1" width="26" height="12" rx="1.5" />
90
+ <path d="M16 1 L27 1 L27 13 L10 13 Z" className="is-b" />
91
+ </>
92
+ ) : kind === "gl/circle-open" ? (
93
+ <>
94
+ <rect x="1" y="1" width="26" height="12" rx="1.5" />
95
+ <circle cx="14" cy="7" r="4" className="is-b" />
96
+ </>
97
+ ) : (
98
+ <>
99
+ <rect x="1" y="1" width="26" height="12" rx="1.5" />
100
+ <path d="M8 7 H20 M16 4 L20 7 L16 10" />
101
+ </>
102
+ )}
103
+ </svg>
104
+ );
105
+ }
106
+
107
+ /** What an absent ease PLAYS: the sampler resolves a stored transition with no
108
+ * ease as linear. Every write below passes this rather than nothing, because
109
+ * `setSceneTransition` fills an absent ease with ease-in-out, and a Length
110
+ * edit must not quietly re-time a legacy linear blend. */
111
+ const LINEAR: Ease = EASE_PRESETS.find((p) => p.id === "linear")!.ease;
32
112
 
33
- export function TransitionSection() {
113
+ /** The eight directions the stepper walks, counter-clockwise in the presets'
114
+ * y-up space from +x. Not normalised: the registry's default is [1, -1], and
115
+ * a walk that cannot land on it again never gives the default back. */
116
+ const DIRECTIONS: ReadonlyArray<readonly [number, number]> = [
117
+ [1, 0],
118
+ [1, 1],
119
+ [0, 1],
120
+ [-1, 1],
121
+ [-1, 0],
122
+ [-1, -1],
123
+ [0, -1],
124
+ [1, -1],
125
+ ];
126
+
127
+ /** The next direction after `value`: the nearest of the eight by angle, then
128
+ * one step on. A hand-typed vector steps from wherever it points. */
129
+ export function nextDirection(value: Value | undefined): [number, number] {
130
+ const [x, y] = Array.isArray(value) && value.length >= 2 ? [Number(value[0]), Number(value[1])] : [1, -1];
131
+ const angle = Math.atan2(Number.isFinite(y) ? y : 0, Number.isFinite(x) ? x : 1);
132
+ const step = (((Math.round(angle / (Math.PI / 4)) % 8) + 8) % 8 + 1) % 8;
133
+ const [nx, ny] = DIRECTIONS[step]!;
134
+ return [nx, ny];
135
+ }
136
+
137
+ /** The closed head's line: the preset's label, "Cut" without one. A STRING
138
+ * selector, so it re-renders on the label and never allocates. */
139
+ function useTransitionSummary(): string | null {
140
+ return useShotEditor((s) => {
141
+ if (s.doc === null || s.sceneId === null) return null;
142
+ const scene = s.doc.scenesById[s.sceneId];
143
+ if (scene === undefined) return null;
144
+ const kind = scene.transition?.kind;
145
+ if (kind === undefined) return "Cut";
146
+ return resolveGlPresetSpec(kind)?.label ?? `${kind} (unknown)`;
147
+ });
148
+ }
149
+
150
+ export function ShotTransitionSection() {
151
+ const summary = useTransitionSummary();
152
+ const sceneId = useShotEditor((s) => s.sceneId);
153
+ if (summary === null || sceneId === null) return null;
154
+ return (
155
+ // A NEW collapse bit. Neither old one ("shot:scene", "shot:transition")
156
+ // describes this section, and inheriting either would open or close it on
157
+ // a choice the author made about something else. `ShotRows` look here
158
+ // exactly as they do in the Shot section, and the length's readout has its
159
+ // face, because both are the house's (widgets.css), with no section scope.
160
+ <InspectorSection
161
+ title="Shot & transition"
162
+ icon={SECTION_ICONS.shot}
163
+ summary={summary}
164
+ persistKey="shot:shot-transition"
165
+ >
166
+ <ShotRows />
167
+ <TransitionRows sceneId={sceneId} lengthLabel="Duration" heading />
168
+ </InspectorSection>
169
+ );
170
+ }
171
+
172
+ /** How shot `sceneId` hands over to the next one. `heading` draws the
173
+ * "Transition · into Shot 02" line over the rows: Studio's section holds the
174
+ * shot's rows too, while the board's section is the transition alone and its
175
+ * head already names both shots.
176
+ *
177
+ * `lengthLabel` is the word on the length's box. Studio says "Duration",
178
+ * the word its section's other frame count (the shot's) already uses, for
179
+ * the reason `ShotRows` gives; the board's Between has no other length
180
+ * beside it and says "Length". */
181
+ export function TransitionRows(props: { sceneId: string; lengthLabel: string; heading?: boolean }) {
182
+ const { sceneId, lengthLabel } = props;
34
183
  const store = useStore();
35
184
  const doc = useShotEditor((s) => s.doc);
36
- const sceneId = useShotEditor((s) => s.sceneId);
37
- if (doc === null || sceneId === null) return null;
185
+ const [browsing, setBrowsing] = useState(false);
186
+ // The preset buttons and the direction step are plain buttons, so they read
187
+ // a viewer's lock themselves; every widget below reads it on its own.
188
+ const locked = useLocked();
189
+ if (doc === null) return null;
38
190
  const scene = doc.scenesById[sceneId];
39
191
  if (scene === undefined) return null;
40
192
 
41
193
  const transition = scene.transition;
42
194
  const spec = transition === undefined ? undefined : resolveGlPresetSpec(transition.kind);
195
+ const ease = transition?.ease ?? LINEAR;
43
196
  const isLast = doc.scenes[doc.scenes.length - 1] === sceneId;
44
197
  const next = isLast ? null : (doc.scenesById[doc.scenes[doc.scenes.indexOf(sceneId) + 1]!] ?? null);
45
198
 
@@ -48,89 +201,387 @@ export function TransitionSection() {
48
201
  store.setSceneTransition(sceneId, null);
49
202
  return;
50
203
  }
204
+ if (kind === transition?.kind) return;
51
205
  // Params are PRESET-SCOPED, so switching preset starts from that preset's
52
- // own defaults rather than carrying the previous one's names across — the
53
- // same reasoning as setBlockPreset.
206
+ // own defaults rather than carrying the previous one's names across: the
207
+ // same reasoning as setBlockPreset. The ease is not passed either, so a new
208
+ // blend is minted with the house ease-in-out.
54
209
  store.setSceneTransition(sceneId, {
55
210
  kind,
56
- durationFrames: transition?.durationFrames ?? DEFAULT_FRAMES,
211
+ durationFrames: transition?.durationFrames ?? DEFAULT_TRANSITION_FRAMES,
57
212
  });
58
213
  };
59
214
 
215
+ /** Every edit of a stored transition: the record with `patch` over it, and
216
+ * the ease it plays, never absent. */
217
+ const write = (patch: Partial<SceneTransition>): void => {
218
+ if (transition === undefined) return;
219
+ store.setSceneTransition(sceneId, { ...transition, ease, ...patch });
220
+ };
221
+
60
222
  const setParam = (name: string, value: Value): void => {
61
223
  if (transition === undefined) return;
62
- store.setSceneTransition(sceneId, {
63
- ...transition,
64
- params: { ...transition.params, [name]: value },
65
- });
224
+ write({ params: { ...transition.params, [name]: value } });
66
225
  };
67
226
 
227
+ /** A DRAG on the length or a param: the panel scrub around a write, which
228
+ * `setSceneTransition` captures into while it is open (store.ts), so one
229
+ * drag is one undo entry and the stage blends under the pointer instead of
230
+ * waiting for the release. */
231
+ const drag = (move: (raw: unknown) => void): ScrubHandlers => ({
232
+ start: () => store.beginScrub(),
233
+ move,
234
+ end: (commit) => store.endScrub(commit),
235
+ });
236
+ const lengthDrag = drag((raw) => write({ durationFrames: Math.round(Number(raw)) }));
237
+
238
+ const options: ComboboxOption[] = [
239
+ { value: CUT, label: "Cut" },
240
+ ...Object.values(glPresetSpecs).map((p) => ({ value: p.id, label: p.label })),
241
+ // A document may name a preset this build does not have. It renders as a
242
+ // cut, and the picker says so instead of silently showing "Cut" and
243
+ // rewriting it on the next edit.
244
+ ...(transition !== undefined && spec === undefined
245
+ ? [{ value: transition.kind, label: `${transition.kind} (unknown)` }]
246
+ : []),
247
+ ];
248
+ const labelOf = (kind: string): string =>
249
+ options.find((o) => o.value === kind)?.label ?? kind;
250
+ const stored = transition?.kind ?? CUT;
251
+ // The fourth slot shows a stored kind outside the first three.
252
+ const tiles = [CUT, TILE_KINDS[0], TILE_KINDS[1], (TILE_KINDS as readonly string[]).includes(stored) || stored === CUT ? TILE_KINDS[2] : stored];
253
+ const presetCount = Object.keys(glPresetSpecs).length;
254
+ const outgoing = transition?.outgoing ?? "hold";
255
+
68
256
  return (
69
- <InspectorSection title="Into next shot" persistKey="shot:transition">
70
- <InspectorRow label="Transition">
71
- <select
72
- className="select"
73
- aria-label="Transition preset"
74
- value={transition?.kind ?? CUT}
75
- onChange={(e) => setKind(e.target.value)}
76
- >
77
- <option value={CUT}>Cut</option>
78
- {Object.values(glPresetSpecs).map((p) => (
79
- <option key={p.id} value={p.id}>
80
- {p.label}
81
- </option>
257
+ <>
258
+ {/* WHICH SEAM, in the heading. These are the only rows in the rail
259
+ about a JOIN, and naming the shot on the far side is what stops them
260
+ reading as a property of one shot alone. */}
261
+ {props.heading === true ? (
262
+ <h4 className="insp-transition-head">
263
+ Transition
264
+ {next === null ? null : (
265
+ <span className="insp-transition-into">
266
+ {" · into "}
267
+ <span data-testid="transition-into">{next.name}</span>
268
+ </span>
269
+ )}
270
+ </h4>
271
+ ) : null}
272
+ <InspectorRow>
273
+ <div className="insp-tiles" role="group" aria-label="Transition presets">
274
+ {tiles.map((kind) => (
275
+ <button
276
+ key={kind === CUT ? "cut" : kind}
277
+ type="button"
278
+ className="btn insp-tile"
279
+ aria-pressed={stored === kind}
280
+ disabled={locked}
281
+ onClick={() => setKind(kind)}
282
+ >
283
+ <TileGlyph kind={kind} />
284
+ <span>{labelOf(kind)}</span>
285
+ </button>
82
286
  ))}
83
- {/* A document may name a preset this build does not have — it renders
84
- as a cut, and the picker says so instead of silently showing
85
- "Cut" and rewriting it on the next edit. */}
86
- {transition !== undefined && spec === undefined ? (
87
- <option value={transition.kind}>{transition.kind} (unknown)</option>
88
- ) : null}
89
- </select>
287
+ </div>
90
288
  </InspectorRow>
289
+ {/* A viewer's panel has no list to open (a locked Combobox will not), and
290
+ the pressed tile already names what is stored, so the door is not
291
+ drawn there at all. */}
292
+ {locked ? null : (
293
+ <InspectorRow>
294
+ {browsing ? (
295
+ <Combobox
296
+ ariaLabel="Transition preset"
297
+ options={options}
298
+ value={stored}
299
+ // The trigger shows the PLACEHOLDER for an empty value, so "Cut"
300
+ // is spelled twice: once as the option, once as the closed face.
301
+ placeholder="Cut"
302
+ autoOpen
303
+ onChange={(kind) => {
304
+ setBrowsing(false);
305
+ setKind(kind);
306
+ }}
307
+ onClose={() => setBrowsing(false)}
308
+ />
309
+ ) : (
310
+ <button type="button" className="link-button" onClick={() => setBrowsing(true)}>
311
+ Browse all {presetCount}
312
+ </button>
313
+ )}
314
+ </InspectorRow>
315
+ )}
316
+
317
+ {/* THE LENGTH, a standard field: its name inside, the frames, and the
318
+ seconds they last beside them in the same box. A cut has no length
319
+ and says so in the same place, in the house readout, rather than
320
+ leaving a hole where the field will be. */}
321
+ {transition === undefined ? (
322
+ <InspectorRow label={lengthLabel} className="insp-readout">
323
+ <span className="insp-dim" data-testid="transition-instant">
324
+ 0 f · instant
325
+ </span>
326
+ </InspectorRow>
327
+ ) : (
328
+ <InspectorRow>
329
+ <FieldBox>
330
+ {/* The name starts with the word in the box, then says what the
331
+ number is, so a screen reader and a speech command use the
332
+ word the author reads, in both chromes. No ceiling: the stored
333
+ number is the author's, and the resolver clamps it to what the
334
+ next shot can hold (see the note below).
335
+
336
+ The word stays IN the box whatever the field-label preference:
337
+ it is a word already, and named mode would print the whole
338
+ accessible name ("Duration, transition frames") above it. */}
339
+ <FieldLabels value="compact">
340
+ <NumberField
341
+ value={transition.durationFrames}
342
+ label={lengthLabel}
343
+ ariaLabel={`${lengthLabel}, transition frames`}
344
+ unit="f"
345
+ min={MIN_TRANSITION_FRAMES}
346
+ step={1}
347
+ onScrubStart={lengthDrag.start}
348
+ onScrub={lengthDrag.move}
349
+ onScrubEnd={lengthDrag.end}
350
+ onCommit={(v) => write({ durationFrames: Math.round(v) })}
351
+ />
352
+ </FieldLabels>
353
+ <span className="insp-length-secs" data-testid="transition-seconds">
354
+ {formatDuration(framesToSeconds(transition.durationFrames, doc.fps))}
355
+ </span>
356
+ </FieldBox>
357
+ </InspectorRow>
358
+ )}
91
359
 
92
360
  {transition !== undefined ? (
93
361
  <>
94
- <InspectorRow label="Frames">
95
- <NumberField
96
- value={transition.durationFrames}
97
- ariaLabel="Transition frames"
98
- min={MIN_TRANSITION_FRAMES}
99
- step={1}
100
- onCommit={(v) =>
101
- store.setSceneTransition(sceneId, {
102
- ...transition,
103
- durationFrames: Math.max(MIN_TRANSITION_FRAMES, Math.round(v)),
104
- })
105
- }
362
+ {/* The timing curve over the blend, THE SAME `Ease` a camera move
363
+ carries, through the same `EaseControl`. An absent ease
364
+ shows as Linear because Linear is what it plays. The bezier pad's
365
+ handle drag goes through `onScrub` inside the pad's own scrub
366
+ bracket, so one drag is one undo entry. */}
367
+ <EaseControl
368
+ ease={ease}
369
+ ariaLabel="Transition ease"
370
+ onEase={(next) => write({ ease: next })}
371
+ onScrub={(next) => write({ ease: next })}
372
+ />
373
+ <InspectorRow label="Outgoing">
374
+ <SegmentedControl
375
+ ariaLabel="Outgoing"
376
+ options={[
377
+ { value: "hold", label: "Hold" },
378
+ { value: "run", label: "Run" },
379
+ ]}
380
+ value={outgoing}
381
+ onChange={(v) => write({ outgoing: v === "run" ? "run" : "hold" })}
106
382
  />
107
383
  </InspectorRow>
384
+ <p className="insp-note">
385
+ Hold freezes {scene.name} on its last frame. Run keeps it playing.
386
+ </p>
387
+ <p className="insp-note" data-testid="transition-held">
388
+ {outgoing === "hold"
389
+ ? `Held at ${timecode(scene.durationFrames, doc.fps)}`
390
+ : `${scene.name} keeps playing under the blend.`}
391
+ </p>
108
392
  {/* The stored number is the AUTHOR's, and the resolver clamps it to
109
- whatever the next shot can hold — so trimming that shot short and
110
- back again does not quietly rewrite what was typed. Said here
111
- because the discrepancy is otherwise invisible. */}
393
+ whatever the next shot can hold, so trimming that shot short and
394
+ back again does not quietly rewrite what was typed. */}
112
395
  {next !== null && transition.durationFrames > next.durationFrames ? (
113
- <p className="inspector-note">
114
- Longer than {next.name} ({next.durationFrames}f) — it plays over
396
+ <p className="insp-note">
397
+ Longer than {next.name} ({next.durationFrames}f), so it plays over
115
398
  that whole shot.
116
399
  </p>
117
400
  ) : null}
118
401
  {isLast ? (
119
- <p className="inspector-note">
402
+ <p className="insp-note">
120
403
  This is the last shot, so nothing plays. Kept for when one follows
121
404
  it.
122
405
  </p>
123
406
  ) : null}
124
- {spec?.params.map((p) => (
125
- <ParamRow
126
- key={p.name}
127
- spec={p}
128
- value={transition.params?.[p.name] ?? p.default}
129
- onCommit={(v) => setParam(p.name, v)}
130
- />
131
- ))}
407
+ {spec?.params.map((p) => {
408
+ const value = transition.params?.[p.name] ?? p.default;
409
+ return (
410
+ <Fragment key={p.name}>
411
+ <ParamRow
412
+ spec={p}
413
+ value={value}
414
+ onCommit={(v) => setParam(p.name, v)}
415
+ // ParamRow coerces a drag's value through the lane's one
416
+ // param coercion before it gets here.
417
+ scrub={drag((v) => setParam(p.name, v as Value))}
418
+ />
419
+ {p.kind === "vec2" && p.name === "direction" ? (
420
+ <DirectionStepper value={value} onStep={(v) => setParam(p.name, v)} />
421
+ ) : null}
422
+ </Fragment>
423
+ );
424
+ })}
132
425
  </>
133
426
  ) : null}
427
+ </>
428
+ );
429
+ }
430
+
431
+ /** One press turns the direction an eighth of a circle. The arrow is drawn
432
+ * in the presets' y-up space, so screen rotation is the negated angle.
433
+ *
434
+ * Its name starts with the words it shows ("x 1, y -1, step direction"), the
435
+ * house "visible words, then what" pattern: a name of its verb alone did not
436
+ * contain the vector the author reads on it, so a speech command saying what
437
+ * is on screen found no button. */
438
+ function DirectionStepper(props: { value: Value; onStep(next: [number, number]): void }) {
439
+ const locked = useLocked();
440
+ const v = Array.isArray(props.value) ? props.value : [1, -1];
441
+ const x = Number(v[0] ?? 1);
442
+ const y = Number(v[1] ?? -1);
443
+ const degrees = (Math.atan2(y, x) * 180) / Math.PI;
444
+ const shown = `x ${x}, y ${y}`;
445
+ return (
446
+ <InspectorRow>
447
+ <div className="insp-buttons">
448
+ <button
449
+ type="button"
450
+ className="btn insp-direction"
451
+ aria-label={`${shown}, step direction`}
452
+ title="Turn the direction an eighth of a circle"
453
+ disabled={locked}
454
+ onClick={() => props.onStep(nextDirection(props.value))}
455
+ >
456
+ <span className="insp-direction-arrow" style={{ transform: `rotate(${-degrees}deg)` }} aria-hidden>
457
+ {"→"}
458
+ </span>
459
+ <span data-testid="direction-readout">{shown}</span>
460
+ </button>
461
+ </div>
462
+ </InspectorRow>
463
+ );
464
+ }
465
+
466
+ // ── THE BOARD'S CUT ──────────────────────────────────────────────────────────
467
+
468
+ /**
469
+ * THE CUT ON THE BOARD: the seam's rows for the shot it is saved on, how it
470
+ * hands over, where the blend sits, and the way into Studio. The board does
471
+ * not play, so the seam is drawn, not looped: Studio plays it. Self-gates on
472
+ * the board's subject being a cut, like the other storyboard sections.
473
+ */
474
+ export function StoryboardCutSection() {
475
+ const store = useStore();
476
+ const open = useShotEditor((s) => s.storyboardOpen);
477
+ const selection = useShotEditor((s) => s.storyboardSelection);
478
+ const doc = useShotEditor((s) => s.doc);
479
+ const subject = open ? boardSubjectOf(doc, selection) : null;
480
+ if (doc === null || subject === null || subject.kind !== "cut") return null;
481
+ const sceneId = subject.sceneId;
482
+ const scene = doc.scenesById[sceneId];
483
+ if (scene === undefined) return null;
484
+ const window = seamWindow(doc, sceneId);
485
+
486
+ const fineTune = (): void => {
487
+ store.setStoryboardOpen(false);
488
+ store.leaveScene();
489
+ store.setScene(sceneId);
490
+ // The frame the cut diamond parks on: the last one the blend starts from.
491
+ // Without the seek, the next one re-pins the shot to wherever the playhead was.
492
+ const start = sceneStart(doc, sceneId) ?? 0;
493
+ store.seek(Math.max(start, start + scene.durationFrames - 1));
494
+ };
495
+
496
+ return (
497
+ <InspectorSection title="Transition" persistKey="shot:storyboard-cut" collapsible={false}>
498
+ <TransitionRows sceneId={sceneId} lengthLabel="Length" />
499
+ <Handover doc={doc} sceneId={sceneId} />
500
+ {/* A READOUT, not a field: the seam is where the blend sits, drawn and
501
+ read, and nothing in it takes input. So it wears the house readout
502
+ (no hover, the reading in the numeral face), never the pill an
503
+ author would try to type into. */}
504
+ {window === null ? null : (
505
+ <InspectorRow label="Seam" className="insp-readout">
506
+ <div className="insp-seam">
507
+ <SeamTrack start={window.start} end={window.end} cut={window.cut} d={window.durationFrames} fps={doc.fps} />
508
+ </div>
509
+ </InspectorRow>
510
+ )}
511
+ <InspectorRow>
512
+ <button type="button" className="link-button" onClick={fineTune}>
513
+ Fine-tune in Studio
514
+ </button>
515
+ </InspectorRow>
134
516
  </InspectorSection>
135
517
  );
136
518
  }
519
+
520
+ /** HOW IT HANDS OVER: the notes stored on the outgoing planned shot, or,
521
+ * without any, what the transition itself says. */
522
+ function Handover(props: { doc: Doc3D; sceneId: string }) {
523
+ const { doc, sceneId } = props;
524
+ const scene = doc.scenesById[sceneId]!;
525
+ const handover = partOfScene(doc, sceneId)?.handover;
526
+ const notes = handover?.notes?.trim() ?? "";
527
+ const beats = handover?.motion ?? [];
528
+ const transition = scene.transition;
529
+ const derived =
530
+ transition === undefined
531
+ ? ["A hard cut."]
532
+ : [
533
+ `${resolveGlPresetSpec(transition.kind)?.label ?? transition.kind} over ${formatDuration(framesToSeconds(transition.durationFrames, doc.fps))}.`,
534
+ transition.outgoing === "run"
535
+ ? `${scene.name} keeps playing under it.`
536
+ : `${scene.name} holds on its last frame.`,
537
+ ];
538
+ return (
539
+ <section className="notes-block-section" data-testid="cut-handover">
540
+ <h4 className="notes-block-heading">How it hands over</h4>
541
+ {notes === "" && beats.length === 0 ? (
542
+ derived.map((line) => (
543
+ <p key={line} className="notes-block-text">
544
+ {line}
545
+ </p>
546
+ ))
547
+ ) : (
548
+ <>
549
+ {notes === "" ? null : <p className="notes-block-text">{notes}</p>}
550
+ {beats.length === 0 ? null : (
551
+ <ol className="notes-block-beats">
552
+ {beats.map((beat, at) => (
553
+ <li key={at} className="notes-block-beat">
554
+ <span className="notes-block-number">{at + 1}</span>
555
+ <span className="notes-block-beat-text">{beat.text}</span>
556
+ </li>
557
+ ))}
558
+ </ol>
559
+ )}
560
+ </>
561
+ )}
562
+ </section>
563
+ );
564
+ }
565
+
566
+ /** The seam's three spans (the lead-in, the blend, the run-out) and where the
567
+ * playhead is inside the blend. Its own component: it reads the playhead,
568
+ * which moves with every scrub. */
569
+ function SeamTrack(props: { start: number; end: number; cut: number; d: number; fps: Doc3D["fps"] }) {
570
+ const { start, end, cut, d, fps } = props;
571
+ const frame = useShotEditor((s) => s.frame);
572
+ const total = Math.max(1, end - start);
573
+ const pct = (n: number): string => `${(Math.max(0, n) / total) * 100}%`;
574
+ const into = Math.min(Math.max(frame - cut, 0), d);
575
+ return (
576
+ <>
577
+ <div className="insp-seam-track" aria-hidden>
578
+ <span className="insp-seam-span" style={{ width: pct(cut - start) }} />
579
+ <span className="insp-seam-span is-blend" style={{ width: pct(d) }} />
580
+ <span className="insp-seam-span" style={{ width: pct(end - cut - d) }} />
581
+ </div>
582
+ <span className="insp-dim" data-testid="seam-readout">
583
+ {`${timecode(into, fps)} / ${formatDuration(framesToSeconds(d, fps))}`}
584
+ </span>
585
+ </>
586
+ );
587
+ }