@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,43 +1,128 @@
1
1
  // One camera move: what kind it is, how it eases, and where it starts and ends.
2
- // The Animate tab's subject whenever the focused bar is on the CAMERA row.
2
+ // The inspector's focused-object slot whenever a bar on the CAMERA row is
3
+ // focused.
3
4
  //
4
- // ── WHY THIS PANEL IS PLAIN FIELDS AND THE SCENE'S CAMERA IS NOT ────────
5
+ // ── WHY THIS PANEL WRITES STRAIGHT THROUGH AND THE SCENE'S CAMERA DOES NOT ─
5
6
  //
6
7
  // `scene.camera` is invisible from the first shot's start onwards, which is why
7
8
  // CameraSection has to read effective, write base and refuse. A SHOT has no
8
9
  // such hazard: `from` and `to` are exactly what `cameraAt` interpolates between
9
10
  // at this shot's own frames, so writing either is visible the moment the
10
11
  // playhead is inside the move. Two truths, one of them safe, and the difference
11
- // is worth stating because the two panels look alike and are not.
12
+ // is worth stating because the two panels draw the same rows (`FramingRows`)
13
+ // and are not the same write.
12
14
  //
13
15
  // The "set from current framing" buttons are the bridge back. They take what is
14
16
  // on screen at the playhead (`cameraAt`, the same read every other surface
15
17
  // makes) and write it into one end, so an author who framed a pose by dragging
16
18
  // the frustum keeps it without retyping five numbers.
17
- import { InspectorRow, InspectorSection, SegmentedControl } from "@dailies-studio/ui";
18
- import { cameraAt } from "@dailies-studio/shot/doc";
19
- import type { Framing, MoveShot } from "@dailies-studio/shot/doc";
19
+ import { useMemo } from "react";
20
+ import {
21
+ Combobox,
22
+ IconMinus,
23
+ IconPlus,
24
+ InspectorRow,
25
+ InspectorSection,
26
+ NumberField,
27
+ SegmentedControl,
28
+ Toggle,
29
+ useLocked,
30
+ wordOptions,
31
+ } from "@dailies-studio/ui";
32
+ import { DEFAULT_SHOT_FPS, MAX_SHAKE_PX, MAX_SHAKE_ROLL_DEG, MAX_SPLINE_ANGLE_DEG, MIN_PATH_WAYPOINTS, MAX_TRACK_MARGIN, MAX_TRACK_SMOOTH_FRAMES, MIN_SHAKE_FREQ_HZ, SPLINE_DOMAINS, TRACK_FITS, containerFrame, isZoomRigBreak, steadyCameraAt, maxShakeFreqHz, meshPrimitiveOf } from "@dailies-studio/shot/doc";
33
+ import type { Framing, MoveShot, Scene3D, Shot, SplineDomain, SplineShot, TrackShot } from "@dailies-studio/shot/doc";
34
+ import type { ShotEditorStore } from "../../../state/store.js";
20
35
  import { useShotEditor, useStore } from "../../../state/context.js";
21
- import { CAMERA_ROW_ID } from "../../timeline/model.js";
22
36
  import { FramingRows } from "../FramingRows.js";
23
- import { sceneLocalFrame, useInspectorFrame } from "../useDocWrite.js";
24
- import { EaseControl } from "./BlockTiming.js";
37
+ import type { FramingScrub } from "../FramingRows.js";
38
+ import { useInspectorFrame } from "../useDocWrite.js";
39
+ import { EaseControl } from "./EaseControl.js";
40
+ import { cameraSplineSourceIsSafe } from "../../../state/splineRefs.js";
25
41
 
26
- const KINDS = [
27
- { value: "dolly", label: "Dolly" },
28
- { value: "zoom", label: "Zoom" },
29
- ];
42
+ /**
43
+ * A DRAGGED field of this panel, as one undo entry: the panel scrub around
44
+ * the move verb it writes through. Every verb here (`setShotFraming`,
45
+ * `setShotWaypoint`, `setTrackShot`, `setShotShake`, `setSplineShot`) captures
46
+ * into an open panel scrub and edits once otherwise (store.ts), so a drag
47
+ * moves the camera on the stage under the pointer and a typed value is one
48
+ * plain edit, through the same write.
49
+ */
50
+ function dragThrough<T>(store: ShotEditorStore, write: (value: T) => void) {
51
+ return {
52
+ onScrubStart: () => store.beginScrub(),
53
+ onScrub: write,
54
+ onScrubEnd: (commit: boolean) => store.endScrub(commit),
55
+ };
56
+ }
57
+
58
+ /** A pose's fields as a `FramingScrub` over one move verb, `dragThrough`'s
59
+ * bracket in the shape `FramingRows` drives. */
60
+ function poseScrub(store: ShotEditorStore, write: (patch: Partial<Framing>) => void): FramingScrub {
61
+ return {
62
+ start: () => store.beginScrub(),
63
+ move: write,
64
+ end: (commit) => store.endScrub(commit),
65
+ commit: write,
66
+ };
67
+ }
68
+
69
+ // PATH IS IN THE CONTROL NOW. It used to be absent with the reason stated in
70
+ // the panel — "authored by an agent" — because converting a two-pose move into
71
+ // a path would have produced something this chrome had no editor for. It has
72
+ // one below, so the refusal was retired with the gap that justified it.
73
+ //
74
+ // SPLINE stays out: its poses are sampled off a scene layer's geometry, so
75
+ // there are no authored framings to carry into it. Its door is the explicit
76
+ // "Camera rail" combobox, which names the layer.
77
+ //
78
+ // `satisfies readonly Shot["kind"][]` is what keeps this a DELIBERATE subset
79
+ // rather than a stale one: it cannot list a kind the schema does not have, so
80
+ // renaming one in `types.ts` fails here instead of leaving a segment that
81
+ // writes a value nothing accepts. It deliberately does NOT check the other
82
+ // direction, because the two absences above are the point.
83
+ const KINDS = ["dolly", "zoom", "path"] as const satisfies readonly Shot["kind"][];
84
+
85
+ /** The segments, labelled by capitalising the kind: the schema's kind names are
86
+ * already the words the control shows, so a fourth offered kind needs no
87
+ * second edit to get a label. */
88
+ const KIND_OPTIONS = wordOptions(KINDS);
89
+
90
+ /** The track fits, capitalised the same way and off the schema's own list, so
91
+ * a third fit reaches this control without a second edit. */
92
+ const FIT_OPTIONS = wordOptions(TRACK_FITS);
30
93
 
31
94
  export function ShotSection() {
32
95
  const store = useStore();
33
96
  const doc = useShotEditor((s) => s.doc);
34
- const focused = useShotEditor((s) => s.selectedBlock);
97
+ const focused = useShotEditor((s) => s.selectedMove);
35
98
  const { sceneId, sceneFrame } = useInspectorFrame();
36
- if (doc === null || sceneId === null || focused === null || focused.layerId !== CAMERA_ROW_ID) {
99
+ // A VIEWER'S PANEL LOCKS EVERY VERB THAT WRITES. `LockedControls` reaches
100
+ // the widgets; this panel's buttons are plain ones, so each reads the lock.
101
+ const locked = useLocked();
102
+ const scene = doc === null || sceneId === null ? undefined : doc.scenesById[sceneId];
103
+ const frameHeight = doc?.size.height;
104
+ // Memoised on the scene: the section repaints on every document identity
105
+ // change (once per rAF frame of any drag), and this walked every layer of the
106
+ // scene through `cameraSplineSourceIsSafe` each time. Above the early return
107
+ // because it is a hook.
108
+ const splineOptions = useMemo(
109
+ () =>
110
+ scene === undefined || frameHeight === undefined
111
+ ? []
112
+ : Object.values(scene.layersById)
113
+ .filter(
114
+ (layer) =>
115
+ layer.kind === "mesh" &&
116
+ meshPrimitiveOf(layer.mesh)?.kind === "spline" &&
117
+ cameraSplineSourceIsSafe(scene, layer.id, frameHeight),
118
+ )
119
+ .map((layer) => ({ value: layer.id, label: layer.label })),
120
+ [scene, frameHeight],
121
+ );
122
+ if (doc === null || sceneId === null || focused === null || focused.sceneId !== sceneId) {
37
123
  return null;
38
124
  }
39
- const scene = doc.scenesById[sceneId];
40
- const shot = scene?.shots.find((s) => s.id === focused.blockId);
125
+ const shot = scene?.shots.find((s) => s.id === focused.shotId);
41
126
  if (scene === undefined || shot === undefined) return null;
42
127
 
43
128
  // LIVE state at the click, never the hook's coalesced frame: the hook holds
@@ -45,43 +130,115 @@ export function ShotSection() {
45
130
  // framing" means the framing on SCREEN — writing the held frame's pose puts
46
131
  // a camera the author never saw into the document. Same rule as
47
132
  // useTransformWrite.
133
+ //
134
+ // STEADY, never the shaken `cameraAt`: this feeds the "set from current
135
+ // framing" buttons, which WRITE the pose into the document. Through the shaken
136
+ // read they would store one arbitrary sample of a sine sum as an authored
137
+ // pose, after which the shake applies on top of itself.
48
138
  const onScreen = (): Framing => {
49
139
  const s = store.getState();
50
- if (s.doc === null || s.sceneId === null) return cameraAt(scene, sceneFrame, doc.fps);
140
+ if (s.doc === null || s.sceneId === null) return steadyCameraAt(scene, sceneFrame, doc.fps, doc.size);
51
141
  const liveScene = s.doc.scenesById[s.sceneId] ?? scene;
52
- return cameraAt(liveScene, sceneLocalFrame(s.doc, s.sceneId, s.frame), s.doc.fps);
142
+ return steadyCameraAt(liveScene, containerFrame(s.doc, s.sceneId, s.frame), s.doc.fps, s.doc.size);
53
143
  };
54
-
55
144
  return (
56
- <InspectorSection title="Camera move" persistKey="shot:animate:shot" collapsible={false}>
145
+ <InspectorSection className="insp-object" title="Camera move" persistKey="shot:animate:shot" collapsible={false}>
57
146
  {shot.kind === "path" ? (
58
- // A path is authored by an agent, through `waypoints`. Offering it in
59
- // the segmented control would mean converting a two-pose move into one
60
- // with nothing to edit it with, so this chrome READS a path and refuses
61
- // to half-edit it. The count is the one number that says which move
62
- // this is.
63
- <span className="insp-dim">
64
- {`A camera path through ${shot.waypoints.length} waypoints, curved with a centripetal spline. ` +
65
- `Its ends are the first and last waypoint. Edit it through the agent.`}
66
- </span>
147
+ <>
148
+ <InspectorRow label="Kind">
149
+ <SegmentedControl
150
+ ariaLabel="Move kind"
151
+ value={shot.kind}
152
+ options={KIND_OPTIONS}
153
+ onChange={(next) => store.setShotKind(shot.id, next as MoveShot["kind"])}
154
+ />
155
+ </InspectorRow>
156
+ <p className="insp-note">
157
+ {`A camera path through ${shot.waypoints.length} waypoints, curved with a centripetal spline. ` +
158
+ `Its ends are the first and last waypoint.`}
159
+ </p>
160
+ {/* ── ONE GROUP PER WAYPOINT ────────────────────────────────────
161
+ The same `FramingRows` the two-pose ends use, so a pose is
162
+ edited the same way wherever it lives, plus the same "set from
163
+ current framing" button — which is what makes a path authorable
164
+ without a stage grip: park the camera, then press it. */}
165
+ {shot.waypoints.map((framing, index) => (
166
+ <WaypointGroup
167
+ key={index}
168
+ shotId={shot.id}
169
+ index={index}
170
+ framing={framing}
171
+ count={shot.waypoints.length}
172
+ read={onScreen}
173
+ />
174
+ ))}
175
+ </>
176
+ ) : shot.kind === "track" ? (
177
+ <TrackShotRows shot={shot} scene={scene} />
178
+ ) : shot.kind === "spline" ? (
179
+ <SplineShotRows shot={shot} options={splineOptions} />
67
180
  ) : (
68
181
  <>
69
182
  <InspectorRow label="Kind">
70
183
  <SegmentedControl
71
184
  ariaLabel="Move kind"
72
185
  value={shot.kind}
73
- options={KINDS}
186
+ options={KIND_OPTIONS}
74
187
  onChange={(next) => store.setShotKind(shot.id, next as MoveShot["kind"])}
75
188
  />
76
189
  </InspectorRow>
190
+ <InspectorRow label="Camera rail">
191
+ <Combobox
192
+ options={splineOptions}
193
+ value=""
194
+ ariaLabel="Convert move to spline"
195
+ placeholder={splineOptions.length === 0 ? "No valid scene spline" : "Choose spline"}
196
+ onChange={(spline) => store.convertShotToSpline(shot.id, spline)}
197
+ />
198
+ </InspectorRow>
199
+ {splineOptions.length === 0 ? (
200
+ <p className="insp-note">A camera rail cannot be below a cloner, and its authored base centreline must stay inside the safe camera distance range.</p>
201
+ ) : (
202
+ <p className="insp-note">Choosing a rail explicitly replaces this move&apos;s x, y and zoom with spline progress from 0 to 1.</p>
203
+ )}
77
204
  {/* The one line that makes the choice a choice. Both read as "get
78
205
  closer"; only one of them moves the camera through the scene, and
79
206
  a layer at a depth is the only place the difference shows. */}
80
- <span className="insp-dim">
207
+ <p className="insp-note">
81
208
  {shot.kind === "dolly"
82
209
  ? "Dolly derives the distance from the live zoom, so the camera moves and layers at different depths shift against each other."
83
210
  : "Zoom pins the distance at the move's start and narrows the lens instead, so nothing shifts."}
84
- </span>
211
+ </p>
212
+ {/* ── AND WHAT THE PIN COSTS ON THE WAY OUT ─────────────────────
213
+ FIXED IN v18 — the fov became a held channel that carries the
214
+ pinned distance across the exit — so this only appears on a scene
215
+ still carrying `legacyZoomRig`, which every document authored
216
+ before the fix does until someone presses Upgrade. The advice is
217
+ no longer "work around it" but "take the fix", because there now
218
+ is one. The fix is a 28px button row of its own under the warning,
219
+ like every verb in this panel: inside the notice it was the 32px
220
+ house button in a section whose every other control is 28. */}
221
+ {scene.legacyZoomRig === true && isZoomRigBreak(shot) ? (
222
+ <>
223
+ <div className="insp-notice is-warn" data-testid="legacy-rig-notice">
224
+ {`This scene is on the old camera rig, where the distance pin ends WITH the move: at frame ` +
225
+ `${shot.frame + Math.max(1, Math.trunc(shot.durationFrames))} the distance and lens snap back, ` +
226
+ `so anything off the z=0 plane pops one frame.`}
227
+ </div>
228
+ <InspectorRow>
229
+ <div className="insp-buttons">
230
+ <button
231
+ type="button"
232
+ className="btn"
233
+ disabled={locked}
234
+ onClick={() => store.upgradeCameraRig(sceneId)}
235
+ >
236
+ Upgrade this shot&apos;s camera
237
+ </button>
238
+ </div>
239
+ </InspectorRow>
240
+ </>
241
+ ) : null}
85
242
  </>
86
243
  )}
87
244
 
@@ -92,35 +249,392 @@ export function ShotSection() {
92
249
  onScrub={(ease) => store.setShotEase(shot.id, ease)}
93
250
  />
94
251
 
95
- {shot.kind === "path" ? null : (
252
+ <ShakeRows shot={shot} />
253
+
254
+ {shot.kind === "dolly" || shot.kind === "zoom" ? (
96
255
  <>
97
256
  <FramingGroup shot={shot} end="from" title="From" read={onScreen} />
98
257
  <FramingGroup shot={shot} end="to" title="To" read={onScreen} />
99
258
  </>
100
- )}
259
+ ) : null}
101
260
  </InspectorSection>
102
261
  );
103
262
  }
104
263
 
264
+ /**
265
+ * A FOLLOW: which layers, how tight, and how much it smooths.
266
+ *
267
+ * No `from`/`to` rows, because there are none — the framing is derived from the
268
+ * targets on every frame. What this panel offers instead is the two things the
269
+ * derivation reads (targets and margin), the two that shape it (fit and
270
+ * smoothing), and the one way out (bake).
271
+ */
272
+ function TrackShotRows({ shot, scene }: { shot: TrackShot; scene: Scene3D }) {
273
+ const store = useStore();
274
+ const selection = useShotEditor((s) => s.selection);
275
+ const locked = useLocked();
276
+ const labels = shot.targets.map((id) => scene.layersById[id]?.label ?? id);
277
+ const followed = labels.length === 0 ? "nothing" : labels.join(", ");
278
+ return (
279
+ <>
280
+ {/* A READOUT of names: the house readout's box (no hover, nothing to
281
+ type into), the names as words (`.is-text`) in the row's sans face
282
+ rather than the numeral face a number takes, on one line. The whole
283
+ list is the tooltip, for when it ellipsizes. */}
284
+ <InspectorRow label="Following" className="insp-readout">
285
+ <span className="insp-dim is-text" title={followed}>
286
+ {followed}
287
+ </span>
288
+ </InspectorRow>
289
+ <InspectorRow>
290
+ {/* THE SELECTION IS THE EDITOR. A layer picker here would be a second
291
+ outline in a 280px column; the outline is already on screen and
292
+ already the way this chrome names layers. */}
293
+ <div className="insp-buttons">
294
+ <button
295
+ type="button"
296
+ className="btn"
297
+ data-testid="track-set-targets"
298
+ disabled={locked || selection.length === 0}
299
+ title={
300
+ selection.length === 0
301
+ ? "Select the layers in the outline first"
302
+ : "Follow the selected layers instead"
303
+ }
304
+ onClick={() => store.setTrackShot(shot.id, { targets: [...selection] })}
305
+ >
306
+ Follow selection
307
+ </button>
308
+ <button
309
+ type="button"
310
+ className="btn"
311
+ data-testid="track-bake"
312
+ title="Write the framings this follow derived into a plain dolly. The camera stops following."
313
+ disabled={locked}
314
+ onClick={() => store.bakeTrackShot(shot.id)}
315
+ >
316
+ Bake to dolly
317
+ </button>
318
+ </div>
319
+ </InspectorRow>
320
+ <InspectorRow label="Fit">
321
+ <SegmentedControl
322
+ ariaLabel="Track fit"
323
+ value={shot.fit}
324
+ options={FIT_OPTIONS}
325
+ onChange={(fit) => store.setTrackShot(shot.id, { fit: fit as TrackShot["fit"] })}
326
+ />
327
+ </InspectorRow>
328
+ <p className="insp-note">
329
+ {shot.fit === "frame"
330
+ ? "Frame derives x, y and zoom, so the targets keep filling the frame as they move or resize."
331
+ : "Position derives x and y only and keeps the zoom the camera arrived with, so a follow does not pump."}
332
+ </p>
333
+ {/* AN UNLABELLED PAIR, each box naming itself: a "Margin" pill holding
334
+ fields named Margin and Smooth printed Margin twice and nested two
335
+ boxes in a third. */}
336
+ <InspectorRow>
337
+ <div className="insp-vec">
338
+ <NumberField
339
+ value={shot.margin}
340
+ label="Margin"
341
+ ariaLabel="Track margin"
342
+ min={0}
343
+ max={MAX_TRACK_MARGIN}
344
+ step={0.01}
345
+ {...dragThrough(store, (margin: number) => store.setTrackShot(shot.id, { margin }))}
346
+ onCommit={(margin) => store.setTrackShot(shot.id, { margin })}
347
+ />
348
+ <NumberField
349
+ value={shot.smoothFrames ?? 0}
350
+ label="Smooth"
351
+ ariaLabel="Track smoothing"
352
+ unit="f"
353
+ min={0}
354
+ max={MAX_TRACK_SMOOTH_FRAMES}
355
+ step={1}
356
+ {...dragThrough(store, (smoothFrames: number) => store.setTrackShot(shot.id, { smoothFrames }))}
357
+ onCommit={(smoothFrames) => store.setTrackShot(shot.id, { smoothFrames })}
358
+ />
359
+ </div>
360
+ </InspectorRow>
361
+ {/* SAID, NOT HIDDEN. The fit is 2D, so a target off the design plane is
362
+ framed by its authored size rather than its apparent one. `validate`
363
+ carries the same sentence as a warning. */}
364
+ {shot.targets.some((id) => (scene.layersById[id]?.transform.position[2] ?? 0) !== 0) ? (
365
+ <div className="insp-notice">
366
+ A target sits off the design plane, and the fit uses each layer&apos;s authored size rather
367
+ than its apparent one, so the framing is close but not exact for it.
368
+ </div>
369
+ ) : null}
370
+ </>
371
+ );
372
+ }
373
+
374
+ /**
375
+ * HANDHELD SHAKE.
376
+ *
377
+ * One switch row and three numbers, and the switch is the whole of "is there
378
+ * shake": the document says so by carrying the field or not, and there is no
379
+ * second way (a zeroed amplitude) to mean the same thing. See `CameraShake`.
380
+ * It is the house `Toggle`, like every other boolean in the inspector; it was
381
+ * a bare native checkbox.
382
+ *
383
+ * A REROLL rather than a seed field. The seed is an integer whose only job is
384
+ * to be different, so a control that asks the author to pick one is asking a
385
+ * question with no good answer; the button steps it and the number rides along
386
+ * for anyone who wants to write it down. Stepping rather than randomising, so
387
+ * pressing it twice from the same document lands on the same place.
388
+ *
389
+ * Every number is a box of its own in an unlabelled row: an "Amount" pill
390
+ * around Amp and Roll, and a "Rate" pill around Freq and a 32px button, nested
391
+ * boxes in boxes and made those rows 34px.
392
+ */
393
+ function ShakeRows({ shot }: { shot: Shot }) {
394
+ const store = useStore();
395
+ // `DEFAULT_SHOT_FPS` and not a literal: the fallback is the timebase a fresh
396
+ // document is CREATED at, so a rehome of that value must move this with it.
397
+ // A module constant is also Object.is-stable, which a fallback minted inside
398
+ // the selector is not.
399
+ const fps = useShotEditor((s) => s.doc?.fps ?? DEFAULT_SHOT_FPS);
400
+ const locked = useLocked();
401
+ const shake = shot.shake;
402
+ const ceiling = maxShakeFreqHz(fps);
403
+ const patch = (next: Partial<NonNullable<Shot["shake"]>>): void => store.setShotShake(shot.id, next);
404
+ return (
405
+ <>
406
+ <InspectorRow label="Shake">
407
+ <Toggle
408
+ checked={shake !== undefined}
409
+ ariaLabel="Camera shake"
410
+ onChange={(on) => store.setShotShake(shot.id, on ? {} : null)}
411
+ />
412
+ </InspectorRow>
413
+ {shake === undefined ? null : (
414
+ <>
415
+ <InspectorRow>
416
+ <div className="insp-vec">
417
+ <NumberField
418
+ value={shake.ampPx}
419
+ label="Amp"
420
+ ariaLabel="Amp, shake amplitude"
421
+ unit="px"
422
+ min={0}
423
+ max={MAX_SHAKE_PX}
424
+ step={1}
425
+ {...dragThrough(store, (ampPx: number) => patch({ ampPx }))}
426
+ onCommit={(ampPx) => patch({ ampPx })}
427
+ />
428
+ <NumberField
429
+ value={shake.rollDeg ?? 0}
430
+ label="Roll"
431
+ ariaLabel="Roll, shake"
432
+ unit="°"
433
+ min={0}
434
+ max={MAX_SHAKE_ROLL_DEG}
435
+ step={0.5}
436
+ {...dragThrough(store, (rollDeg: number) => patch({ rollDeg }))}
437
+ onCommit={(rollDeg) => patch({ rollDeg })}
438
+ />
439
+ </div>
440
+ </InspectorRow>
441
+ <InspectorRow>
442
+ <NumberField
443
+ value={shake.freqHz}
444
+ label="Freq"
445
+ ariaLabel="Freq, shake frequency"
446
+ unit="Hz"
447
+ min={MIN_SHAKE_FREQ_HZ}
448
+ max={ceiling}
449
+ step={0.1}
450
+ {...dragThrough(store, (freqHz: number) => patch({ freqHz }))}
451
+ onCommit={(freqHz) => patch({ freqHz })}
452
+ />
453
+ </InspectorRow>
454
+ <InspectorRow>
455
+ <div className="insp-buttons">
456
+ <button
457
+ type="button"
458
+ className="btn"
459
+ data-testid="shake-reroll"
460
+ title={`Try another shake. The top octave runs at ${(ceiling * 4).toFixed(1)} Hz, which is this document's frame rate halved. Above it the shake aliases.`}
461
+ disabled={locked}
462
+ onClick={() => patch({ seed: shake.seed + 1 })}
463
+ >
464
+ Reroll {shake.seed}
465
+ </button>
466
+ </div>
467
+ </InspectorRow>
468
+ </>
469
+ )}
470
+ </>
471
+ );
472
+ }
473
+
474
+ function SplineShotRows({ shot, options }: { shot: SplineShot; options: Array<{ value: string; label: string }> }) {
475
+ const store = useStore();
476
+ const choices = options.some((option) => option.value === shot.spline)
477
+ ? options
478
+ : [{ value: shot.spline, label: shot.spline }, ...options];
479
+ return (
480
+ <>
481
+ <InspectorRow label="Spline">
482
+ <Combobox options={choices} value={shot.spline} ariaLabel="Camera spline" placeholder="Choose spline" onChange={(spline) => store.setSplineShot(shot.id, { spline })} />
483
+ </InspectorRow>
484
+ <InspectorRow label="Domain">
485
+ <SegmentedControl ariaLabel="Camera spline domain" value={shot.domain ?? "full"} options={wordOptions(SPLINE_DOMAINS)} onChange={(domain) => store.setSplineShot(shot.id, { domain: domain as SplineDomain })} />
486
+ </InspectorRow>
487
+ <SplineEndpointRows shot={shot} end="from" label="From" />
488
+ <SplineEndpointRows shot={shot} end="to" label="To" />
489
+ </>
490
+ );
491
+ }
492
+
493
+ /** One end of a camera rail: where along the spline it sits, and how it is
494
+ * held there. Under its own heading, like a two-pose move's From and To, so
495
+ * each box names its field with one word ("Progress", "Roll", "Focus") and
496
+ * its accessible name says which end after that word ("Progress, From"),
497
+ * as a two-pose move's fields do (FramingRows). */
498
+ function SplineEndpointRows({ shot, end, label }: { shot: SplineShot; end: "from" | "to"; label: string }) {
499
+ const store = useStore();
500
+ const endpoint = shot[end];
501
+ // The endpoint as drawn, with the one field a box moves. A drag keeps the
502
+ // other two as they are, and each move names its absolute value, so the
503
+ // scrub's capture never compounds.
504
+ const patch = (next: Partial<SplineShot[typeof end]>): void =>
505
+ store.setSplineShot(shot.id, { [end]: { ...endpoint, ...next } });
506
+ return (
507
+ <>
508
+ <div className="insp-heading">{label}</div>
509
+ <InspectorRow>
510
+ <NumberField
511
+ value={endpoint.progress}
512
+ label="Progress"
513
+ ariaLabel={`Progress, ${label}`}
514
+ step={0.01}
515
+ {...dragThrough(store, (progress: number) => patch({ progress }))}
516
+ onCommit={(progress) => patch({ progress })}
517
+ />
518
+ </InspectorRow>
519
+ <InspectorRow>
520
+ <div className="insp-vec">
521
+ <NumberField
522
+ value={endpoint.rollDeg ?? 0}
523
+ label="Roll"
524
+ ariaLabel={`Roll, ${label}`}
525
+ unit="°"
526
+ min={-MAX_SPLINE_ANGLE_DEG}
527
+ max={MAX_SPLINE_ANGLE_DEG}
528
+ step={1}
529
+ {...dragThrough(store, (rollDeg: number) => patch({ rollDeg }))}
530
+ onCommit={(rollDeg) => patch({ rollDeg })}
531
+ />
532
+ <NumberField
533
+ value={endpoint.focusZ ?? 0}
534
+ label="Focus"
535
+ ariaLabel={`Focus, ${label}`}
536
+ unit="px"
537
+ step={1}
538
+ {...dragThrough(store, (focusZ: number) => patch({ focusZ }))}
539
+ onCommit={(focusZ) => patch({ focusZ })}
540
+ />
541
+ </div>
542
+ </InspectorRow>
543
+ </>
544
+ );
545
+ }
546
+
547
+ /** One waypoint of a path: its pose, a capture button, and the two structural
548
+ * verbs. `+` inserts AFTER this one so the list reads top to bottom in the
549
+ * order the camera travels; `−` is absent on the last two, because a path
550
+ * needs two and a button that always refuses is worse than no button.
551
+ *
552
+ * A POSE IS A GROUP, so its name is the group's heading and its buttons are
553
+ * one row of 28px buttons under it, like every inspector control: as a
554
+ * labelled pill holding a 32px button it was a 34px row with a box inside a
555
+ * box. */
556
+ function WaypointGroup(props: {
557
+ shotId: string;
558
+ index: number;
559
+ framing: Framing;
560
+ count: number;
561
+ read(): Framing;
562
+ }) {
563
+ const store = useStore();
564
+ const locked = useLocked();
565
+ const { shotId, index, framing, count } = props;
566
+ const title = `Waypoint ${index + 1}`;
567
+ const write = (patch: Partial<Framing>): void => store.setShotWaypoint(shotId, index, patch);
568
+ return (
569
+ <>
570
+ <div className="insp-heading">{title}</div>
571
+ <InspectorRow>
572
+ <div className="insp-buttons">
573
+ <CaptureButton pose={title} disabled={locked} onCapture={() => write(props.read())} />
574
+ <button
575
+ type="button"
576
+ className="btn is-icon"
577
+ title="Add a waypoint after this one"
578
+ aria-label={`Insert waypoint after ${title}`}
579
+ disabled={locked}
580
+ onClick={() => store.insertShotWaypoint(shotId, index)}
581
+ >
582
+ <IconPlus />
583
+ </button>
584
+ {count > MIN_PATH_WAYPOINTS ? (
585
+ <button
586
+ type="button"
587
+ className="btn is-icon"
588
+ title="Remove this waypoint"
589
+ aria-label={`Remove ${title}`}
590
+ disabled={locked}
591
+ onClick={() => store.removeShotWaypoint(shotId, index)}
592
+ >
593
+ <IconMinus />
594
+ </button>
595
+ ) : null}
596
+ </div>
597
+ </InspectorRow>
598
+ <FramingRows framing={framing} name={title} scrub={poseScrub(store, write)} />
599
+ </>
600
+ );
601
+ }
602
+
603
+ /** One end of a two-pose move, under its own heading: the capture button,
604
+ * then the pose, which drags live like every other field. */
105
605
  function FramingGroup(props: { shot: MoveShot; end: "from" | "to"; title: string; read(): Framing }) {
106
606
  const store = useStore();
607
+ const locked = useLocked();
107
608
  const { shot, end, title } = props;
609
+ const write = (patch: Partial<Framing>): void => store.setShotFraming(shot.id, end, patch);
108
610
  return (
109
611
  <>
110
- <InspectorRow label={title}>
111
- <button
112
- type="button"
113
- className="btn"
114
- onClick={() => store.setShotFraming(shot.id, end, props.read())}
115
- >
116
- Set from current framing
117
- </button>
612
+ <div className="insp-heading">{title}</div>
613
+ <InspectorRow>
614
+ <div className="insp-buttons">
615
+ <CaptureButton pose={title} disabled={locked} onCapture={() => write(props.read())} />
616
+ </div>
118
617
  </InspectorRow>
119
- <FramingRows
120
- framing={shot[end]}
121
- name={title}
122
- onCommit={(patch) => store.setShotFraming(shot.id, end, patch)}
123
- />
618
+ <FramingRows framing={shot[end]} name={title} scrub={poseScrub(store, write)} />
124
619
  </>
125
620
  );
126
621
  }
622
+
623
+ /** "Set from current framing" for one pose. Its NAME carries the pose after
624
+ * the visible words ("Set from current framing, From"), as the pose's fields
625
+ * ("X, From") and the waypoint verbs ("Remove Waypoint 2") carry it: the
626
+ * heading above is a plain line, so a screen reader moving by controls heard
627
+ * the same button once per pose with nothing saying which one it writes. */
628
+ function CaptureButton(props: { pose: string; disabled: boolean; onCapture(): void }) {
629
+ return (
630
+ <button
631
+ type="button"
632
+ className="btn"
633
+ aria-label={`Set from current framing, ${props.pose}`}
634
+ disabled={props.disabled}
635
+ onClick={props.onCapture}
636
+ >
637
+ Set from current framing
638
+ </button>
639
+ );
640
+ }