@dailies-studio/shot-editor 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/dist/.vite/manifest.json +382 -0
  2. package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
  3. package/dist/assets/AgentPanel-DJEphah0.css +1 -0
  4. package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
  5. package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
  6. package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
  7. package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
  8. package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
  9. package/dist/assets/CurvePane-BjFVabKX.css +1 -0
  10. package/dist/assets/CurvePane-R22WzllJ.js +1 -0
  11. package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
  12. package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
  13. package/dist/assets/FontPicker-v0IOIArM.css +1 -0
  14. package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
  15. package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
  16. package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
  17. package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
  18. package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
  19. package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
  20. package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
  21. package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
  22. package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
  23. package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
  24. package/dist/assets/SoundMode-BmcZru3i.js +3 -0
  25. package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
  26. package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
  27. package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
  28. package/dist/assets/board-B88EPuLO.css +1 -0
  29. package/dist/assets/board-CL-he7SL.js +3 -0
  30. package/dist/assets/components-0oOSPN0l.js +1 -0
  31. package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
  32. package/dist/assets/googleFonts-BNon6M7X.js +1 -0
  33. package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
  34. package/dist/assets/index-4F5NQmI8.css +1 -0
  35. package/dist/assets/index-BGHQGV5z.js +117 -0
  36. package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  37. package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  38. package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  39. package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  40. package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  41. package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  42. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  43. package/dist/assets/libraryModel-CpIwfFha.js +1 -0
  44. package/dist/assets/lucide-6iK64tTB.js +9319 -0
  45. package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
  46. package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
  47. package/dist/assets/webmcp-BsB9gxWC.js +2 -0
  48. package/dist/index.html +34 -3
  49. package/package.json +12 -9
  50. package/src/addFamilies.ts +66 -0
  51. package/src/api.ts +1631 -55
  52. package/src/app/App.tsx +977 -198
  53. package/src/app/lazyPanel.tsx +137 -0
  54. package/src/app/lazySurfaces.tsx +203 -0
  55. package/src/audio/index.tsx +41 -10
  56. package/src/audio/meter.ts +485 -0
  57. package/src/audio/meterMath.ts +273 -0
  58. package/src/audio/meterStore.ts +155 -0
  59. package/src/audio/offlineMix.ts +289 -0
  60. package/src/audio/preview.ts +928 -118
  61. package/src/bridge.ts +15 -5
  62. package/src/bridgeTypes.ts +1611 -36
  63. package/src/channels.ts +17 -32
  64. package/src/fonts/googleFonts.ts +4 -0
  65. package/src/hostApi.ts +174 -0
  66. package/src/icons.tsx +770 -0
  67. package/src/layerWords.ts +61 -0
  68. package/src/main.tsx +56 -4
  69. package/src/panels/ResizeHandle.tsx +33 -43
  70. package/src/panels/agent/AgentDots.tsx +36 -0
  71. package/src/panels/agent/AgentPanel.tsx +119 -43
  72. package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
  73. package/src/panels/agent/agent.css +113 -97
  74. package/src/panels/agent/agentGate.ts +35 -0
  75. package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
  76. package/src/panels/code/CodeStudio.tsx +373 -0
  77. package/src/panels/code/FileTree.tsx +265 -0
  78. package/src/panels/code/codeBufferGuard.ts +52 -0
  79. package/src/panels/code/codeParts.tsx +321 -0
  80. package/src/panels/comments/CommentComposer.tsx +239 -0
  81. package/src/panels/comments/CommentThread.tsx +346 -0
  82. package/src/panels/comments/CommentsPanel.tsx +336 -0
  83. package/src/panels/comments/InboxButton.tsx +131 -0
  84. package/src/panels/comments/MentionMenu.tsx +98 -0
  85. package/src/panels/comments/parts.tsx +171 -0
  86. package/src/panels/export/ExportDialog.tsx +919 -96
  87. package/src/panels/export/ExportScrim.tsx +57 -0
  88. package/src/panels/files/AssetsTab.tsx +656 -0
  89. package/src/panels/files/FilePanel.tsx +1466 -216
  90. package/src/panels/files/LayerMarks.tsx +40 -0
  91. package/src/panels/files/LeftColumn.tsx +37 -0
  92. package/src/panels/files/McpSection.tsx +690 -47
  93. package/src/panels/files/MissingAssetsNotice.tsx +60 -0
  94. package/src/panels/files/assetDrag.ts +79 -0
  95. package/src/panels/files/resourceParts.tsx +255 -0
  96. package/src/panels/fonts/FontPicker.tsx +762 -0
  97. package/src/panels/fonts/faceCache.ts +207 -0
  98. package/src/panels/fonts/fontPicker.css +271 -0
  99. package/src/panels/fonts/pickerModel.ts +265 -0
  100. package/src/panels/fonts/pickerRequest.ts +46 -0
  101. package/src/panels/header/AccountAvatar.tsx +76 -0
  102. package/src/panels/header/AgentChip.tsx +77 -0
  103. package/src/panels/header/AppMenu.tsx +213 -0
  104. package/src/panels/header/Header.tsx +564 -84
  105. package/src/panels/header/HistoryControls.tsx +31 -5
  106. package/src/panels/header/PresenceStack.tsx +329 -0
  107. package/src/panels/header/ProjectTitle.tsx +232 -0
  108. package/src/panels/header/ShareButton.tsx +59 -0
  109. package/src/panels/header/StatusBadges.tsx +358 -27
  110. package/src/panels/header/SyncDeletedDialog.tsx +88 -0
  111. package/src/panels/header/ZoomControls.tsx +46 -15
  112. package/src/panels/header/agentHandoff.ts +52 -0
  113. package/src/panels/header/leave.ts +26 -0
  114. package/src/panels/help/HelpSection.tsx +189 -163
  115. package/src/panels/insert/InsertDrawer.tsx +723 -0
  116. package/src/panels/insert/iconSet.ts +109 -0
  117. package/src/panels/inspector/ExposeDot.tsx +117 -0
  118. package/src/panels/inspector/FramingRows.tsx +314 -44
  119. package/src/panels/inspector/InspectorFooter.tsx +91 -0
  120. package/src/panels/inspector/InspectorHead.tsx +425 -0
  121. package/src/panels/inspector/InspectorPanel.tsx +408 -124
  122. package/src/panels/inspector/KeyframeDot.tsx +373 -0
  123. package/src/panels/inspector/KeyframeSection.tsx +257 -0
  124. package/src/panels/inspector/MarkerSection.tsx +55 -11
  125. package/src/panels/inspector/ParamRow.tsx +84 -53
  126. package/src/panels/inspector/RightColumn.tsx +152 -0
  127. package/src/panels/inspector/TimeSection.tsx +866 -0
  128. package/src/panels/inspector/animate/EaseControl.tsx +244 -0
  129. package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
  130. package/src/panels/inspector/animate/ShotSection.tsx +562 -48
  131. package/src/panels/inspector/design/AlignRow.tsx +95 -31
  132. package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
  133. package/src/panels/inspector/design/ClonerSection.tsx +323 -62
  134. package/src/panels/inspector/design/ComponentSection.tsx +50 -43
  135. package/src/panels/inspector/design/CornerSection.tsx +45 -14
  136. package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
  137. package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
  138. package/src/panels/inspector/design/DeviceSection.tsx +210 -0
  139. package/src/panels/inspector/design/FillSection.tsx +83 -51
  140. package/src/panels/inspector/design/FontField.tsx +196 -0
  141. package/src/panels/inspector/design/HtmlSection.tsx +23 -12
  142. package/src/panels/inspector/design/InstanceSection.tsx +373 -154
  143. package/src/panels/inspector/design/MaskSection.tsx +84 -0
  144. package/src/panels/inspector/design/MaterialSection.tsx +177 -0
  145. package/src/panels/inspector/design/MediaSection.tsx +273 -120
  146. package/src/panels/inspector/design/MeshSection.tsx +910 -82
  147. package/src/panels/inspector/design/PaintRows.tsx +550 -0
  148. package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
  149. package/src/panels/inspector/design/ShadowSection.tsx +221 -48
  150. package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
  151. package/src/panels/inspector/design/StrokeSection.tsx +228 -43
  152. package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
  153. package/src/panels/inspector/design/TransformSection.tsx +244 -50
  154. package/src/panels/inspector/design/TypographySection.tsx +679 -110
  155. package/src/panels/inspector/layout.ts +295 -0
  156. package/src/panels/inspector/propValue.ts +199 -0
  157. package/src/panels/inspector/scene/CameraSection.tsx +232 -22
  158. package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
  159. package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
  160. package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
  161. package/src/panels/inspector/scene/SceneSection.tsx +219 -44
  162. package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
  163. package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
  164. package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
  165. package/src/panels/inspector/timeModel.ts +190 -0
  166. package/src/panels/inspector/useDocWrite.ts +438 -146
  167. package/src/panels/library/LibraryBrowser.tsx +435 -0
  168. package/src/panels/library/LibraryGrid.tsx +190 -0
  169. package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
  170. package/src/panels/library/LibraryRail.tsx +84 -0
  171. package/src/panels/library/LibrarySoundPanel.tsx +293 -0
  172. package/src/panels/library/SoundHistory.tsx +245 -0
  173. package/src/panels/library/StockSounds.tsx +390 -0
  174. package/src/panels/library/libraryModel.ts +147 -0
  175. package/src/panels/library/stockSearch.ts +77 -0
  176. package/src/panels/maskMarks.tsx +117 -0
  177. package/src/panels/material/MaterialCommentNode.tsx +54 -0
  178. package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
  179. package/src/panels/material/MaterialGraphMenu.tsx +45 -0
  180. package/src/panels/material/MaterialGroupNode.tsx +56 -0
  181. package/src/panels/material/MaterialLibrary.tsx +253 -0
  182. package/src/panels/material/MaterialNode.tsx +453 -0
  183. package/src/panels/material/MaterialNodePalette.tsx +349 -0
  184. package/src/panels/material/MaterialParameterControls.tsx +525 -0
  185. package/src/panels/material/MaterialRerouteNode.tsx +42 -0
  186. package/src/panels/material/MaterialWorkspace.tsx +501 -0
  187. package/src/panels/material/NodePreview.tsx +70 -0
  188. package/src/panels/material/flowModel.ts +441 -0
  189. package/src/panels/material/parameterValues.ts +231 -0
  190. package/src/panels/material/previewScheduler.ts +235 -0
  191. package/src/panels/material/reactFlow.css +6 -0
  192. package/src/panels/material/socketGlyph.ts +85 -0
  193. package/src/panels/material/viewportMemory.ts +58 -0
  194. package/src/panels/preferences/PreferencesDialog.tsx +215 -0
  195. package/src/panels/share/ShareComponentDialog.tsx +247 -0
  196. package/src/panels/stage/StageMenu.tsx +35 -5
  197. package/src/panels/stage/StagePanel.tsx +338 -23
  198. package/src/panels/stage/cellSlots.tsx +70 -0
  199. package/src/panels/storyboard/BoardCanvas.tsx +739 -0
  200. package/src/panels/storyboard/BoardCursors.tsx +158 -0
  201. package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
  202. package/src/panels/storyboard/BoardPlan.tsx +42 -0
  203. package/src/panels/storyboard/ComponentSheet.tsx +187 -0
  204. package/src/panels/storyboard/ComponentStage.tsx +235 -0
  205. package/src/panels/storyboard/MiniSketch.tsx +173 -0
  206. package/src/panels/storyboard/PartPicker.tsx +64 -0
  207. package/src/panels/storyboard/PlanPanel.tsx +617 -0
  208. package/src/panels/storyboard/ReferencePreview.tsx +217 -0
  209. package/src/panels/storyboard/ShotBrief.tsx +360 -0
  210. package/src/panels/storyboard/ShotCard.tsx +251 -0
  211. package/src/panels/storyboard/StagePreview.tsx +348 -0
  212. package/src/panels/storyboard/StillThumbs.tsx +162 -0
  213. package/src/panels/storyboard/StoryboardView.tsx +79 -0
  214. package/src/panels/storyboard/board.css +1065 -0
  215. package/src/panels/storyboard/board.ts +12 -0
  216. package/src/panels/storyboard/boardClipboard.ts +111 -0
  217. package/src/panels/storyboard/boardOrder.ts +68 -0
  218. package/src/panels/storyboard/boardRows.tsx +425 -0
  219. package/src/panels/storyboard/liveCap.ts +90 -0
  220. package/src/panels/storyboard/plan.css +295 -0
  221. package/src/panels/storyboard/posterCache.ts +133 -0
  222. package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
  223. package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
  224. package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
  225. package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
  226. package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
  227. package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
  228. package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
  229. package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
  230. package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
  231. package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
  232. package/src/panels/storyboard/shotboard/sketch.css +1392 -0
  233. package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
  234. package/src/panels/storyboard/storyboardDrop.ts +164 -0
  235. package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
  236. package/src/panels/studio/ComponentSidePanel.tsx +355 -0
  237. package/src/panels/studio/ComponentStudio.tsx +243 -0
  238. package/src/panels/studio/ComponentsHome.tsx +318 -0
  239. package/src/panels/studio/ComponentsPreview.tsx +179 -0
  240. package/src/panels/studio/EveryUse.tsx +244 -0
  241. package/src/panels/studio/ThisUsePanel.tsx +679 -0
  242. package/src/panels/studio/componentParts.tsx +341 -0
  243. package/src/panels/studio/components.ts +6 -0
  244. package/src/panels/studio/newCodeComponent.ts +66 -0
  245. package/src/panels/timeline/AudioFxButton.tsx +290 -0
  246. package/src/panels/timeline/AudioLane.tsx +674 -85
  247. package/src/panels/timeline/BlockRow.tsx +154 -85
  248. package/src/panels/timeline/ChannelRow.tsx +378 -129
  249. package/src/panels/timeline/CurvePane.tsx +508 -0
  250. package/src/panels/timeline/Filmstrip.tsx +361 -0
  251. package/src/panels/timeline/GenerateDrawer.tsx +407 -0
  252. package/src/panels/timeline/KeyStretch.tsx +115 -0
  253. package/src/panels/timeline/LifespanRow.tsx +730 -47
  254. package/src/panels/timeline/MarkerLane.tsx +46 -8
  255. package/src/panels/timeline/PlayButton.tsx +91 -0
  256. package/src/panels/timeline/PropertyPicker.tsx +254 -0
  257. package/src/panels/timeline/ShotRow.tsx +400 -0
  258. package/src/panels/timeline/SoundMode.tsx +1406 -0
  259. package/src/panels/timeline/TimelineDock.tsx +2338 -272
  260. package/src/panels/timeline/TransportVolume.tsx +163 -0
  261. package/src/panels/timeline/beatRuler.ts +74 -0
  262. package/src/panels/timeline/curveModel.ts +500 -0
  263. package/src/panels/timeline/duckUnderVoice.ts +68 -0
  264. package/src/panels/timeline/keyframeMenu.ts +138 -0
  265. package/src/panels/timeline/labelWidth.ts +49 -0
  266. package/src/panels/timeline/marquee.ts +155 -0
  267. package/src/panels/timeline/model.ts +1860 -199
  268. package/src/panels/timeline/snap.ts +125 -43
  269. package/src/panels/timeline/staggerPreview.tsx +92 -0
  270. package/src/panels/timeline/takeMeasure.ts +72 -0
  271. package/src/panels/timeline/useLive.ts +48 -0
  272. package/src/panels/timeline/viewport.ts +234 -0
  273. package/src/panels/toolbar/BackChip.tsx +116 -0
  274. package/src/panels/toolbar/CameraViewBar.tsx +118 -49
  275. package/src/panels/toolbar/CanvasBar.tsx +117 -0
  276. package/src/panels/toolbar/InsertButton.tsx +38 -0
  277. package/src/panels/toolbar/IsolationTrail.tsx +156 -0
  278. package/src/panels/toolbar/PanelToggles.tsx +86 -0
  279. package/src/panels/toolbar/ShadingBar.tsx +119 -26
  280. package/src/panels/toolbar/ToolDockBar.tsx +329 -80
  281. package/src/panels/toolbar/ToolFooter.tsx +102 -0
  282. package/src/panels/toolbar/ViewModeBar.tsx +247 -38
  283. package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
  284. package/src/panels/toolbar/useAddRows.tsx +326 -0
  285. package/src/panels/toolbar/useDismiss.ts +122 -0
  286. package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
  287. package/src/panels/versions/versionModel.ts +103 -0
  288. package/src/panels/virtualWindow.ts +271 -0
  289. package/src/shotTokens.css +151 -60
  290. package/src/stage/FrustumGizmo.tsx +504 -84
  291. package/src/stage/GridPlane.tsx +225 -16
  292. package/src/stage/NodePreviews.tsx +628 -0
  293. package/src/stage/PerfHud.tsx +437 -47
  294. package/src/stage/SplitRenderer.tsx +816 -169
  295. package/src/stage/StageApp.tsx +4274 -486
  296. package/src/stage/StageRootBoundary.tsx +114 -0
  297. package/src/stage/StageTools.tsx +651 -151
  298. package/src/stage/TransformGizmo.tsx +696 -0
  299. package/src/stage/adaptivePresentation.ts +145 -0
  300. package/src/stage/bridgeClient.ts +2522 -41
  301. package/src/stage/exportWorker.ts +9 -0
  302. package/src/stage/feed.ts +305 -14
  303. package/src/stage/fontPreview.ts +176 -0
  304. package/src/stage/frustumGeometry.ts +319 -1
  305. package/src/stage/furnitureTokens.ts +33 -0
  306. package/src/stage/gizmoMath.ts +508 -16
  307. package/src/stage/gpuTimer.ts +276 -0
  308. package/src/stage/instanceModules.ts +194 -33
  309. package/src/stage/interactions/move.ts +24 -5
  310. package/src/stage/isolation.ts +291 -33
  311. package/src/stage/jsSelfProfiler.ts +325 -0
  312. package/src/stage/main.tsx +116 -16
  313. package/src/stage/modalState.ts +222 -23
  314. package/src/stage/moduleEpoch.ts +60 -0
  315. package/src/stage/motionPathStage.ts +279 -0
  316. package/src/stage/motionPreview.ts +74 -0
  317. package/src/stage/overlays/Overlays.tsx +1112 -159
  318. package/src/stage/overlays/Rulers.tsx +495 -0
  319. package/src/stage/panes.ts +159 -10
  320. package/src/stage/project.ts +68 -3
  321. package/src/stage/renderScaleController.ts +730 -0
  322. package/src/stage/rulers.ts +129 -0
  323. package/src/stage/sceneExtent.ts +75 -0
  324. package/src/stage/shading.ts +20 -0
  325. package/src/stage/splineStage.ts +632 -0
  326. package/src/stage/stagePerformanceTelemetry.ts +717 -0
  327. package/src/stage/stageTheme.ts +36 -0
  328. package/src/stage/stageWarmup.ts +11 -0
  329. package/src/stage/textAutoSize.ts +124 -0
  330. package/src/stage/tier1Export.ts +641 -0
  331. package/src/stage/tokenColor.ts +14 -9
  332. package/src/stage/view.ts +56 -9
  333. package/src/stage/workingView.ts +277 -33
  334. package/src/state/accountPrefs.ts +261 -0
  335. package/src/state/agentActions.ts +173 -15
  336. package/src/state/agentActivity.ts +198 -0
  337. package/src/state/agentQuestion.ts +239 -0
  338. package/src/state/bootOpen.ts +42 -0
  339. package/src/state/cameraVerbs.ts +139 -0
  340. package/src/state/clientId.ts +6 -1
  341. package/src/state/comments.ts +1099 -0
  342. package/src/state/context.tsx +19 -1
  343. package/src/state/editorPrefs.ts +220 -0
  344. package/src/state/errorReports.ts +121 -0
  345. package/src/state/fieldWrite.ts +161 -0
  346. package/src/state/fontRecents.ts +70 -0
  347. package/src/state/fonts.ts +656 -0
  348. package/src/state/graphClipboard.ts +414 -0
  349. package/src/state/imageThumbs.ts +216 -0
  350. package/src/state/insert.ts +444 -67
  351. package/src/state/isolationTrail.ts +92 -0
  352. package/src/state/keyMoves.ts +134 -0
  353. package/src/state/keyframeClipboard.ts +177 -0
  354. package/src/state/keymap.ts +1153 -117
  355. package/src/state/layoutPrefs.ts +272 -0
  356. package/src/state/layouts.ts +205 -0
  357. package/src/state/measureMedia.ts +167 -0
  358. package/src/state/menu.ts +2341 -28
  359. package/src/state/motionStamp.ts +7 -0
  360. package/src/state/panelSize.ts +375 -5
  361. package/src/state/pendingChanges.ts +119 -0
  362. package/src/state/playHold.ts +78 -0
  363. package/src/state/playbackPreparation.ts +562 -0
  364. package/src/state/posterCapture.ts +242 -0
  365. package/src/state/presence.ts +254 -0
  366. package/src/state/projectComponents.ts +160 -0
  367. package/src/state/renderEvents.ts +298 -18
  368. package/src/state/runtimeErrors.ts +46 -0
  369. package/src/state/selection.ts +12 -0
  370. package/src/state/shortcuts.ts +1606 -0
  371. package/src/state/sketchGuide.ts +51 -0
  372. package/src/state/splineRefs.ts +87 -0
  373. package/src/state/stageHealth.ts +259 -0
  374. package/src/state/stageWatchdog.ts +47 -0
  375. package/src/state/stillStage.ts +330 -0
  376. package/src/state/stillThumbs.ts +474 -0
  377. package/src/state/store.ts +23591 -2982
  378. package/src/state/storyboardModel.ts +528 -0
  379. package/src/state/sync.ts +1257 -0
  380. package/src/state/syncCache.ts +159 -0
  381. package/src/state/triangles.ts +13 -0
  382. package/src/state/types.ts +1698 -55
  383. package/src/state/unsavedGuard.ts +49 -0
  384. package/src/state/viewMode.ts +200 -0
  385. package/src/state/webmcp.ts +1128 -0
  386. package/src/state/workspace.ts +1147 -0
  387. package/src/styles/assets-tab.css +304 -0
  388. package/src/styles/canvas.css +545 -0
  389. package/src/styles/comments.css +1435 -0
  390. package/src/styles/controls.css +162 -0
  391. package/src/styles/dialogs.css +390 -0
  392. package/src/styles/insert-drawer.css +586 -0
  393. package/src/styles/inspector-board.css +207 -0
  394. package/src/styles/inspector-object.css +486 -0
  395. package/src/styles/inspector-shot.css +252 -0
  396. package/src/styles/inspector.css +360 -0
  397. package/src/styles/left-column.css +1628 -0
  398. package/src/styles/library-preview.css +575 -0
  399. package/src/styles/library.css +512 -0
  400. package/src/styles/modes/code.css +569 -0
  401. package/src/styles/modes/component.css +1561 -0
  402. package/src/styles/modes/material.css +1201 -0
  403. package/src/styles/modes/storyboard.css +518 -0
  404. package/src/styles/share-component.css +49 -0
  405. package/src/styles/shell.css +653 -0
  406. package/src/styles/shot-brief.css +360 -0
  407. package/src/styles/states.css +76 -0
  408. package/src/styles/sync.css +380 -0
  409. package/src/styles/timeline-graph.css +384 -0
  410. package/src/styles/timeline-meters.css +89 -0
  411. package/src/styles/timeline-rows.css +1959 -0
  412. package/src/styles/timeline-sound.css +1003 -0
  413. package/src/styles/timeline.css +754 -0
  414. package/src/styles/tool-footer.css +525 -0
  415. package/src/styles/topbar.css +886 -0
  416. package/src/styles/versions.css +262 -0
  417. package/src/styles.css +72 -2921
  418. package/src/tools.ts +108 -23
  419. package/dist/assets/index-B7notge7.css +0 -1
  420. package/dist/assets/index-BBE6Ta9L.js +0 -52
  421. package/src/panels/files/icons.tsx +0 -118
  422. package/src/panels/header/DocSwitcher.tsx +0 -85
  423. package/src/panels/header/TransportBar.tsx +0 -86
  424. package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
  425. package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
  426. package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
  427. package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
  428. package/src/panels/inspector/animate/useBlocks.ts +0 -43
  429. package/src/panels/material/MaterialPicker.tsx +0 -172
  430. package/src/panels/timeline/SceneStrip.tsx +0 -145
  431. package/src/panels/toolbar/AddMenu.tsx +0 -230
  432. package/src/stage/MoveGizmo.tsx +0 -411
@@ -0,0 +1,177 @@
1
+ // One-click plate looks: the named treatments every tool in this class ships,
2
+ // written into the fields this lane already has.
3
+ //
4
+ // ── WHY PRESETS AT ALL, WHEN EVERY FIELD IS ALREADY THERE ───────────────────
5
+ //
6
+ // `paint.radius`, `paint.stroke` and `paint.shadows` have all been honoured on a
7
+ // media plate since unify Phase 1: the corner is a uv1 alphaMap stencil, the
8
+ // stroke is a separate plate raster hung in front with the silhouette punched
9
+ // out, and the shadows are drawn outside the box with the pad baked into the
10
+ // mesh (raster.ts). What was missing is that assembling the LOOK meant setting
11
+ // nine numbers across three sections, in an order nobody is told, and being
12
+ // wrong about any one of them reads as "this tool cannot do the thing every
13
+ // other tool does in one click".
14
+ //
15
+ // shots.so ships Default / Glass Light / Glass Dark / Liquid Glass / Inset Light
16
+ // / Inset Dark / Outline / Border, with shadow modes None / Spread / Hug /
17
+ // Adaptive. Screen Studio and Tella ship the same idea under their own names.
18
+ // This is that shelf, in this lane's vocabulary.
19
+ //
20
+ // ── WHAT IT DELIBERATELY IS NOT ────────────────────────────────────────────
21
+ //
22
+ // NOT A DOCUMENT FIELD. Nothing records which preset was used, and there is no
23
+ // "preset" key on a layer. A preset is a WRITE of ordinary fields, so the
24
+ // author owns the result immediately and every knob stays theirs to nudge: the
25
+ // row is a starting point, not a mode they are now in. That also means no
26
+ // schema bump, nothing an older studio could misread, and no resurrection of
27
+ // the deleted template lane (AGENTS.md) — there is no shipped content here,
28
+ // only numbers this chrome could already type.
29
+ //
30
+ // NOT GLASS, HONESTLY. There is no backdrop blur: a true frosted plate needs
31
+ // the composited scene behind it, which the plate raster (hung in FRONT at
32
+ // plateOrder) structurally cannot sample, and the correct implementation is a
33
+ // two-group split render that doubles the pass count. So the glass rows here
34
+ // are a translucent stroke plus a soft shadow, which is what reads as glass
35
+ // over a busy background.
36
+ import { InspectorRow, InspectorSection, useLocked } from "@dailies-studio/ui";
37
+ import type { CornerRadius, Layer3D, Shadow, Stroke } from "@dailies-studio/shot/doc";
38
+ import { useSelectedLayers, useSelectionWrite } from "../useDocWrite.js";
39
+
40
+ /** What a preset writes. Every field is optional and an ABSENT one is DELETED
41
+ * rather than left alone: a look is a complete statement, so applying "Plain"
42
+ * after "Outline" has to remove the stroke, not leave it behind. */
43
+ interface PlateStyle {
44
+ key: string;
45
+ label: string;
46
+ title: string;
47
+ radius?: CornerRadius;
48
+ stroke?: Stroke;
49
+ shadows?: Shadow[];
50
+ }
51
+
52
+ const r = (n: number): CornerRadius => ({ tl: n, tr: n, br: n, bl: n });
53
+
54
+ /**
55
+ * The shelf.
56
+ *
57
+ * The numbers are the ones this class of tool converged on, in design px
58
+ * against a 1920-wide frame: a plate corner around 12-24px, a hairline at 1px,
59
+ * and a shadow whose blur is several times its own y offset (a tight, dark
60
+ * shadow reads as a sticker; a wide soft one reads as depth).
61
+ */
62
+ const STYLES: readonly PlateStyle[] = [
63
+ {
64
+ key: "plain",
65
+ label: "Plain",
66
+ title: "Square corners, no border, no shadow",
67
+ },
68
+ {
69
+ key: "soft",
70
+ label: "Soft",
71
+ title: "Rounded corners and a wide, soft drop shadow",
72
+ radius: r(16),
73
+ shadows: [{ xPx: 0, yPx: 18, blurPx: 48, spreadPx: -8, color: "#00000059" }],
74
+ },
75
+ {
76
+ key: "lifted",
77
+ label: "Lifted",
78
+ title: "A deeper shadow, for a plate that sits well off its background",
79
+ radius: r(20),
80
+ shadows: [
81
+ // TWO entries, near and far. One shadow at this size goes muddy: the
82
+ // near one keeps the plate's edge defined and the far one carries the
83
+ // height. This is what "Deep" is in the tools that ship it.
84
+ { xPx: 0, yPx: 4, blurPx: 10, spreadPx: -2, color: "#00000040" },
85
+ { xPx: 0, yPx: 40, blurPx: 90, spreadPx: -16, color: "#00000073" },
86
+ ],
87
+ },
88
+ {
89
+ key: "outline",
90
+ label: "Outline",
91
+ title: "A hairline border, no shadow",
92
+ radius: r(12),
93
+ // INSIDE align: an outside stroke grows the plate's silhouette, so a
94
+ // recording placed to fill an exact box would no longer fill it.
95
+ stroke: { color: "#ffffff26", widthPx: 1, align: "inside" },
96
+ },
97
+ {
98
+ key: "glass",
99
+ label: "Glass",
100
+ title: "A bright hairline and a soft glow, the look that reads as glass over a busy background",
101
+ radius: r(20),
102
+ stroke: { color: "#ffffff40", widthPx: 1, align: "inside" },
103
+ shadows: [
104
+ // A zero-offset bright shadow IS a glow in this schema (types.ts), which
105
+ // is why there is one field and not two.
106
+ { xPx: 0, yPx: 0, blurPx: 32, spreadPx: 0, color: "#ffffff1f" },
107
+ { xPx: 0, yPx: 24, blurPx: 64, spreadPx: -12, color: "#00000066" },
108
+ ],
109
+ },
110
+ {
111
+ key: "frame",
112
+ label: "Frame",
113
+ title: "A thick light border, the way a screenshot sits inside a mount",
114
+ radius: r(8),
115
+ stroke: { color: "#ffffff", widthPx: 10, align: "inside" },
116
+ shadows: [{ xPx: 0, yPx: 12, blurPx: 32, spreadPx: -6, color: "#0000004d" }],
117
+ },
118
+ ];
119
+
120
+ export function PlateStyleSection() {
121
+ const write = useSelectionWrite();
122
+ const layers = useSelectedLayers();
123
+ // Each chip writes paint, so a viewer's panel disables the shelf.
124
+ const locked = useLocked();
125
+
126
+ // MEDIA ONLY, and that is a scoping choice rather than a technical one: every
127
+ // field below is honoured on a rect and a path too. But "plate" names the
128
+ // thing a screenshot or a recording sits on, and offering the shelf on every
129
+ // layer would put a Glass row on a line and an arrow.
130
+ if (layers.length === 0 || !layers.every(isMedia)) return null;
131
+
132
+ const apply = (style: PlateStyle): void =>
133
+ write((layer) => ({
134
+ ...layer,
135
+ paint: {
136
+ // Everything the preset does NOT name is dropped, including keys it has
137
+ // no opinion on, so a look is repeatable: applying the same row twice
138
+ // must give the same picture whatever was there first. `fill` is
139
+ // carried, because it is inert on media and the author may have set it
140
+ // for a non-media sibling.
141
+ ...(layer.paint.fill === undefined ? {} : { fill: layer.paint.fill }),
142
+ ...(style.radius === undefined ? {} : { radius: style.radius }),
143
+ ...(style.stroke === undefined ? {} : { stroke: style.stroke }),
144
+ ...(style.shadows === undefined ? {} : { shadows: style.shadows.map((s) => ({ ...s })) }),
145
+ },
146
+ }));
147
+
148
+ return (
149
+ <InspectorSection className="insp-object" title="Plate style" persistKey="shot:plate-style">
150
+ <InspectorRow>
151
+ <div className="plate-style-grid">
152
+ {STYLES.map((style) => (
153
+ <button
154
+ key={style.key}
155
+ type="button"
156
+ className="plate-style-chip"
157
+ title={style.title}
158
+ disabled={locked}
159
+ onClick={() => apply(style)}
160
+ >
161
+ {style.label}
162
+ </button>
163
+ ))}
164
+ </div>
165
+ </InspectorRow>
166
+ </InspectorSection>
167
+ );
168
+ }
169
+
170
+ function isMedia(layer: Layer3D): boolean {
171
+ return layer.kind === "image" || layer.kind === "video";
172
+ }
173
+
174
+ /** Exported for the test, which asserts the shelf's own invariants rather than
175
+ * its numbers: pinning nine colours would make a design tweak a test failure
176
+ * and teach nobody anything. */
177
+ export { STYLES as PLATE_STYLES };
@@ -2,72 +2,245 @@
2
2
  // that can disagree. A glow IS a shadow at zero offset with a bright colour,
3
3
  // which is exactly how the rasterizer draws it (raster.ts), so modelling them
4
4
  // separately would be two schema fields for one Canvas2D call.
5
- import { ColorPicker, InspectorRow, InspectorSection, NumberField } from "@dailies-studio/ui";
6
- import type { Shadow } from "@dailies-studio/shot/doc";
7
- import { NO_SCRUB, useSelectionValue, useSelectionWrite } from "../useDocWrite.js";
5
+ //
6
+ // ── WHICH ONLY WORKS IF THE PANEL SAYS BOTH WORDS ──────────────────────
7
+ //
8
+ // This section printed "Shadow" over everything, so an author who could see a
9
+ // glow on the stage had no row to look at: the word appeared in ZERO pixels of
10
+ // the chrome, and the question it produced was "where does a glow get its
11
+ // colour". Each entry is now named from its OWN value, GLOW at zero offset and
12
+ // SHADOW otherwise. Derived rather than stored, so dragging Y off zero renames
13
+ // the row it is in and the label can never disagree with the paint.
14
+ //
15
+ // ── AND EVERY ENTRY, NOT JUST THE FIRST ────────────────────────────────
16
+ //
17
+ // `paint.shadows` is a LIST and the pair this lane exists for is a glow plus a
18
+ // drop shadow. Editing `shadows[0]` alone left the second one invisible AND
19
+ // uneditable on exactly the document the schema was shaped for. Entries are
20
+ // listed in ARRAY order, which is PAINT order (raster.ts walks the list and
21
+ // each entry is its own pass), so Add appends and the new entry paints last.
22
+ //
23
+ // NO INSET TOGGLE. `Shadow.inset` exists in the shared type but this lane's
24
+ // raster skips an inset entry outright (it needs a clip pass v1 does not do),
25
+ // so the control would write a field and paint nothing.
26
+ import {
27
+ ColorField,
28
+ IconMinus,
29
+ IconPlus,
30
+ InspectorRow,
31
+ InspectorSection,
32
+ NumberField,
33
+ PropertyGrid,
34
+ useLocked,
35
+ } from "@dailies-studio/ui";
36
+ import type { ScrubHandlers } from "@dailies-studio/ui";
37
+ import { removeArrayElementTracks } from "@dailies-studio/shot/doc";
38
+ import { KeyframeDot } from "../KeyframeDot.js";
39
+ import type { Layer3D, Shadow } from "@dailies-studio/shot/doc";
40
+ import { useSelectedLayers, useSelectionScrub, useSelectionValue, useSelectionWrite } from "../useDocWrite.js";
8
41
 
42
+ /** What Add creates: the neutral soft drop. A zero-everything entry paints
43
+ * nothing, and an author who clicks Add and sees no change cannot tell whether
44
+ * it worked. */
9
45
  const DEFAULT_SHADOW: Shadow = { xPx: 0, yPx: 12, blurPx: 32, spreadPx: 0, color: "#00000080" };
10
46
 
47
+ /** The four numbers, in the order they read as a sentence: offset, then
48
+ * softness, then growth. */
49
+ const SHADOW_NUMBERS = [
50
+ { field: "xPx", label: "X", title: "Horizontal offset. Zero on both axes is a glow." },
51
+ { field: "yPx", label: "Y", title: "Vertical offset. Zero on both axes is a glow." },
52
+ { field: "blurPx", label: "Blur", title: "Blur radius" },
53
+ { field: "spreadPx", label: "Spread", title: "Spread (signed: negative shrinks)" },
54
+ ] as const;
55
+ type ShadowNumber = (typeof SHADOW_NUMBERS)[number]["field"];
56
+
11
57
  export function ShadowSection() {
12
58
  const write = useSelectionWrite();
13
- const present = useSelectionValue((layer) => (layer.paint.shadows ?? []).length > 0);
14
- const first = useSelectionValue((layer) => layer.paint.shadows?.[0]);
15
-
16
- /** The FIRST shadow only. A layer may hold several (the schema is a list, and
17
- * a glow plus a drop shadow is the common pair), but editing the second one
18
- * needs a per-shadow list UI — which is a section of its own. */
19
- const patch = (next: Partial<Shadow>): void =>
20
- write((layer) => {
21
- const shadows = [...(layer.paint.shadows ?? [DEFAULT_SHADOW])];
22
- shadows[0] = { ...DEFAULT_SHADOW, ...shadows[0], ...next };
23
- return { ...layer, paint: { ...layer.paint, shadows } };
24
- });
25
-
26
- const shadow = first.value ?? DEFAULT_SHADOW;
59
+ // Add and each entry's remove are plain buttons, so they read a viewer's
60
+ // lock here; every field is a widget that reads it itself.
61
+ const locked = useLocked();
62
+ // The LARGEST count in the selection, not the first layer's: with two
63
+ // layers holding two and one entries, every entry must have a row. A layer
64
+ // without an index reads undefined there, so that row shows Mixed, and
65
+ // `withShadow`/`withoutShadow` skip it.
66
+ const entries = Math.max(0, ...useSelectedLayers().map((layer) => layer.paint.shadows?.length ?? 0));
27
67
 
28
68
  return (
29
69
  <InspectorSection
70
+ className="insp-object"
30
71
  title="Shadow"
31
72
  persistKey="shot:shadow"
32
73
  action={
33
74
  <button
34
75
  type="button"
35
- className="section-action"
36
- title={present.value === true ? "Remove shadow" : "Add shadow"}
37
- onClick={() =>
38
- present.value === true
39
- ? write((layer) => ({ ...layer, paint: { ...layer.paint, shadows: [] } }))
40
- : patch({})
41
- }
76
+ className="btn is-icon insp-section-action"
77
+ title="Add shadow or glow"
78
+ aria-label="Add shadow or glow"
79
+ disabled={locked}
80
+ onClick={() => write(appendShadow)}
42
81
  >
43
- {present.value === true ? "−" : "+"}
82
+ <IconPlus />
44
83
  </button>
45
84
  }
46
85
  >
47
- {present.value === true ? (
48
- <>
49
- <InspectorRow label="Color">
50
- <ColorPicker
51
- value={shadow.color}
52
- ariaLabel="Shadow colour"
53
- scrub={NO_SCRUB}
54
- commit={(hex) => patch({ color: hex })}
86
+ {entries === 0 ? <p className="insp-empty">No shadow or glow</p> : null}
87
+ {Array.from({ length: entries }, (_, index) => (
88
+ <ShadowEntry key={index} index={index} locked={locked} />
89
+ ))}
90
+ </InspectorSection>
91
+ );
92
+ }
93
+
94
+ /**
95
+ * ONE entry. A component of its own because the hooks are per index and the
96
+ * list length changes under the author's fingers, which is the same reason the
97
+ * 2D lane's shadow rows are components.
98
+ */
99
+ function ShadowEntry(props: { index: number; locked: boolean }) {
100
+ const { index } = props;
101
+ const write = useSelectionWrite();
102
+ const color = useSelectionValue((layer) => layer.paint.shadows?.[index]?.color);
103
+ const colorScrub = useSelectionScrub((layer, raw) => withShadow(layer, index, { color: String(raw) }));
104
+ const x = useShadowNumber(index, "xPx");
105
+ const y = useShadowNumber(index, "yPx");
106
+ const blur = useShadowNumber(index, "blurPx");
107
+ const spread = useShadowNumber(index, "spreadPx");
108
+ const fields: Record<ShadowNumber, ShadowNumberField> = {
109
+ xPx: x,
110
+ yPx: y,
111
+ blurPx: blur,
112
+ spreadPx: spread,
113
+ };
114
+
115
+ // THE NAME THE AUTHOR IS LOOKING FOR, derived from the live offsets so it
116
+ // follows a drag rather than describing what the entry was when it was added.
117
+ // The number is the paint slot, which is the only thing that distinguishes
118
+ // two entries of the same kind.
119
+ const name = `${x.value === 0 && y.value === 0 ? "Glow" : "Shadow"} ${index + 1}`;
120
+
121
+ return (
122
+ <>
123
+ <InspectorRow
124
+ label={name}
125
+ trailing={
126
+ <>
127
+ {/* A GLOW IS A ZERO-OFFSET SHADOW, so this diamond is what a glow
128
+ pulse is authored with. */}
129
+ <KeyframeDot path={`paint.shadows.${index}.color`} label={`${name} colour`} />
130
+ {/* The diamond's own button, in the same lane inside the box: two
131
+ verbs read as one kind of control only if they rest at one size
132
+ and strength, and the remove says it destroys on hover. */}
133
+ <button
134
+ type="button"
135
+ className="insp-key is-danger"
136
+ title={`Remove ${name}`}
137
+ aria-label={`Remove ${name}`}
138
+ disabled={props.locked}
139
+ onClick={() => write((layer) => withoutShadow(layer, index))}
140
+ >
141
+ <IconMinus />
142
+ </button>
143
+ </>
144
+ }
145
+ >
146
+ <ColorField
147
+ value={color.value ?? DEFAULT_SHADOW.color}
148
+ mixed={color.mixed}
149
+ ariaLabel={`${name} colour`}
150
+ scrub={colorScrub}
151
+ commit={(hex) => write((layer) => withShadow(layer, index, { color: hex }))}
152
+ />
153
+ </InspectorRow>
154
+ <PropertyGrid>
155
+ {SHADOW_NUMBERS.map((f) => (
156
+ <InspectorRow key={f.field}>
157
+ <NumberField
158
+ value={fields[f.field].value ?? 0}
159
+ mixed={fields[f.field].mixed}
160
+ label={f.label}
161
+ ariaLabel={`${f.label}, ${name}`}
162
+ unit="px"
163
+ title={f.title}
164
+ step={1}
165
+ // Blur alone has a floor (a negative blur is not a blur). X, Y
166
+ // and Spread are SIGNED and unbounded.
167
+ {...(f.field === "blurPx" ? { min: 0 } : {})}
168
+ trailing={<KeyframeDot path={`paint.shadows.${index}.${f.field}`} label={`${f.label}, ${name}`} />}
169
+ onCommit={fields[f.field].commit}
170
+ onScrubStart={fields[f.field].scrub.start}
171
+ onScrub={fields[f.field].scrub.move}
172
+ onScrubEnd={fields[f.field].scrub.end}
55
173
  />
56
174
  </InspectorRow>
57
- <InspectorRow>
58
- <NumberField value={shadow.xPx} label="X" ariaLabel="Shadow X" unit="px" step={1} onCommit={(v) => patch({ xPx: v })} />
59
- </InspectorRow>
60
- <InspectorRow>
61
- <NumberField value={shadow.yPx} label="Y" ariaLabel="Shadow Y" unit="px" step={1} onCommit={(v) => patch({ yPx: v })} />
62
- </InspectorRow>
63
- <InspectorRow>
64
- <NumberField value={shadow.blurPx} label="Blur" ariaLabel="Shadow blur" unit="px" min={0} step={1} onCommit={(v) => patch({ blurPx: v })} />
65
- </InspectorRow>
66
- <InspectorRow>
67
- <NumberField value={shadow.spreadPx} label="Spread" ariaLabel="Shadow spread" unit="px" step={1} onCommit={(v) => patch({ spreadPx: v })} />
68
- </InspectorRow>
69
- </>
70
- ) : null}
71
- </InspectorSection>
175
+ ))}
176
+ </PropertyGrid>
177
+ </>
72
178
  );
73
179
  }
180
+
181
+ interface ShadowNumberField {
182
+ value: number | undefined;
183
+ mixed: boolean;
184
+ commit: (value: number) => void;
185
+ scrub: ScrubHandlers;
186
+ }
187
+
188
+ /** One number of one entry: what it READS across the selection, what a typed
189
+ * value COMMITS, and what a label drag SCRUBS. Four identical calls rather
190
+ * than four sets of three, and the read is the SCALAR the row displays. */
191
+ function useShadowNumber(index: number, field: ShadowNumber): ShadowNumberField {
192
+ const write = useSelectionWrite();
193
+ const { value, mixed } = useSelectionValue((layer) => layer.paint.shadows?.[index]?.[field]);
194
+ const scrub = useSelectionScrub((layer, raw) => withShadow(layer, index, { [field]: Number(raw) }));
195
+ return {
196
+ value,
197
+ mixed,
198
+ commit: (next) => write((layer) => withShadow(layer, index, { [field]: next })),
199
+ scrub,
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Patch ONE EXISTING entry.
205
+ *
206
+ * A layer whose list is shorter than `index` is LEFT ALONE rather than grown to
207
+ * fit: the index is an array slot, so growing would materialise a shadow on a
208
+ * layer the author has no row for, and a sparse list would reach the
209
+ * rasterizer. Returning the layer by reference is also what lets
210
+ * `writeSelection` leave the document identical when nothing changed.
211
+ */
212
+ function withShadow(layer: Layer3D, index: number, next: Partial<Shadow>): Layer3D {
213
+ const shadows = layer.paint.shadows ?? [];
214
+ const current = shadows[index];
215
+ if (current === undefined) return layer;
216
+ const out = [...shadows];
217
+ out[index] = { ...current, ...next };
218
+ return { ...layer, paint: { ...layer.paint, shadows: out } };
219
+ }
220
+
221
+ function appendShadow(layer: Layer3D): Layer3D {
222
+ return {
223
+ ...layer,
224
+ paint: { ...layer.paint, shadows: [...(layer.paint.shadows ?? []), DEFAULT_SHADOW] },
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Remove one entry — AND RENUMBER ITS TRACKS.
230
+ *
231
+ * A shadow's tracks are addressed by array INDEX (`paint.shadows.1.color`), so
232
+ * removing entry 0 from a three-shadow layer leaves the track that animated
233
+ * shadow 1 pointing at what is now shadow 1 — a different shadow. Nothing
234
+ * errors: the path still resolves and validate is satisfied, so the wrong glow
235
+ * pulses and there is no signal at all.
236
+ *
237
+ * `routeLayerEdit`'s orphan sweep cannot catch this, because both paths still
238
+ * exist. This op is the only place that knows an element was removed, so it is
239
+ * the only place the renumber can live.
240
+ */
241
+ function withoutShadow(layer: Layer3D, index: number): Layer3D {
242
+ const shadows = layer.paint.shadows ?? [];
243
+ if (shadows[index] === undefined) return layer;
244
+ const next = { ...layer, paint: { ...layer.paint, shadows: shadows.filter((_, i) => i !== index) } };
245
+ return removeArrayElementTracks(next, "paint.shadows", index);
246
+ }