@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,550 @@
1
+ // The ONE paint control: solid, linear gradient or radial gradient, writing the
2
+ // shot `Paint` union the rasterizer already reads.
3
+ //
4
+ // ── WHY THIS EXISTS ─────────────────────────────────────────────────────────
5
+ //
6
+ // `BackgroundPlane` has rendered linear and radial gradients for real since the
7
+ // unify overhaul, and `paintStyle` (raster.ts) draws them for a layer fill too.
8
+ // The chrome could not write either. Worse, both surfaces DESTROYED one: the
9
+ // scene's Background row and the layer's Fill row were solid-only pickers whose
10
+ // scrub and commit each wrote `{type: "solid", color}`, so a gradient authored
11
+ // by MCP or by an agent died the first time an author touched the swatch, with
12
+ // no undo affordance naming what was lost.
13
+ //
14
+ // ── WHY NOT `GradientValueEditor` FROM @dailies-studio/ui ───────────────────
15
+ //
16
+ // It exists, it is exported, and it is the wrong shape. It takes contract's
17
+ // `GradientValue` (`{kind: "gradient", stops, space?, hueFixup?}`) and its
18
+ // validator refuses anything whose `kind` is not `"gradient"` AND refuses any
19
+ // extra key, so a shot `Paint` (`{type: "linear-gradient", angleDeg, stops}`)
20
+ // handed to it renders an invalid badge and never opens. Adapting both ways is
21
+ // more code than the rows below, and it would import two knobs that are DEAD
22
+ // here: `space` and `hueFixup` describe an interpolation the shot lane does not
23
+ // perform. `paintStyle` uses canvas `addColorStop`, which is plain sRGB, and
24
+ // nothing in packages/shot reads either field. It also has no angle control, so
25
+ // the one thing a linear gradient most needs would have to be written anyway.
26
+ //
27
+ // So: the stops list is authored directly in the shape the renderer reads, and
28
+ // each stop keeps its own ColorField and At field.
29
+ //
30
+ // ── THE GRADIENT BAR, AND WHY ITS RAMP IS REPAINTED HERE ───────────────────
31
+ //
32
+ // Over the stop rows sits `GradientBar` from @dailies-studio/ui: the design's
33
+ // 28px ramp with one draggable marker per stop. It draws the stops along one
34
+ // axis from 0 to 1, NOT the paint's geometry, so the angle-convention drift
35
+ // that kept a CSS preview of the whole fill out of this file does not arise:
36
+ // there is no angle in it to get wrong.
37
+ //
38
+ // Two things about it are adapted rather than taken whole:
39
+ //
40
+ // ITS RAMP IS OKLCH, THE CANVAS IS sRGB. The bar previews through contract's
41
+ // `gradientPreviewCss`, which interpolates in OKLCH, while `paintStyle`'s
42
+ // `addColorStop` blends plain sRGB. A ramp that brightens through the
43
+ // middle where the picture darkens is a preview lying about the picture,
44
+ // so the wrapper sets `--paint-ramp` to a plain CSS `linear-gradient` (sRGB
45
+ // by default, the canvas's space) and inspector-object.css paints it over
46
+ // the bar's own fill. The bar's markers, hit area and keyboard are its own.
47
+ // ITS VALUES MUST BE CONTRACT-VALID. Its algebra (composite.ts) refuses a
48
+ // stop that is not hex or a list that is not strictly ascending, and a shot
49
+ // `Paint` accepts both (style.ts `paintProblem` asks a stop for an `at` in
50
+ // 0..1 and a colour `colourProblem` passes: any non-empty string but a CSS
51
+ // image, so a named colour is legal). The bar renders only over stops it can edit;
52
+ // the rows below it edit everything, so no paint loses a control.
53
+ //
54
+ // A bar drag is ONE scrub. The bar reports `onChange` per move and `onCommit`
55
+ // at release, and a cancel restores the press stops through `onChange` with no
56
+ // commit at all, so the scrub opens on the first `onChange`, closes with a
57
+ // commit on `onCommit`, and closes WITHOUT one on the gesture's own abort
58
+ // events (Escape, pointercancel, lost capture). A discrete bar edit (a press on
59
+ // the empty ramp, a Delete, an arrow nudge) is `onChange` then `onCommit` in
60
+ // one tick, which is the same bracket, and one undo entry either way.
61
+ //
62
+ // ── STORE-FREE, LIKE EVERY REUSABLE WIDGET IN THIS CHROME ──────────────────
63
+ //
64
+ // A scene background writes through `patchScene`; a layer fill writes through
65
+ // `useSelectionWrite`. Neither is reachable from a shared component, and both
66
+ // have to route through their own edit path so `routeSceneEdit` /
67
+ // `routeLayerEdit` can turn the write into a keyframe when a track is armed.
68
+ // So this takes handlers and holds no document state. What it does keep is view
69
+ // state no document field records: which stop the bar has selected, and the
70
+ // bar's open scrub between its first move and its release.
71
+ import { useEffect, useRef, useState } from "react";
72
+ import type { CSSProperties } from "react";
73
+ import {
74
+ ColorField,
75
+ FieldLabels,
76
+ GradientBar,
77
+ IconMinus,
78
+ IconPlus,
79
+ InspectorRow,
80
+ MIN_GRADIENT_STOPS,
81
+ NumberField,
82
+ SegmentedControl,
83
+ tryParseHex,
84
+ useLocked,
85
+ } from "@dailies-studio/ui";
86
+ import { validateGradient } from "@dailies-studio/contract";
87
+ import type { GradientStop } from "@dailies-studio/contract";
88
+ import { formatHex } from "@dailies-studio/engine";
89
+ import type { Paint } from "@dailies-studio/shot/doc";
90
+
91
+ /** The three paint kinds as data, pinned to the schema's own union so a variant
92
+ * added or renamed there is a compile error here rather than a segment that
93
+ * writes a `type` the rasterizer falls through on. */
94
+ const PAINT_TYPES = ["solid", "linear-gradient", "radial-gradient"] as const satisfies readonly Paint["type"][];
95
+
96
+ const PAINT_LABELS: Record<Paint["type"], string> = {
97
+ solid: "Solid",
98
+ "linear-gradient": "Linear",
99
+ "radial-gradient": "Radial",
100
+ };
101
+
102
+ /** The events that end a bar drag WITHOUT a commit. Escape is read on the
103
+ * window's capture phase because `startDrag` stops its propagation there
104
+ * (after cancelling, so the restore has already arrived); the pointer events
105
+ * in the bubble phase, so the marker's own handler has run first. */
106
+ const ABORT_BUBBLE = ["pointerup", "pointercancel", "lostpointercapture"] as const;
107
+
108
+ /** One control's edit, applied by the caller to EACH paint it writes. A whole
109
+ * paint built from the lead would copy the lead's colour, stops and angle onto
110
+ * every selected layer when the author changed one field. */
111
+ export type PaintEdit = (prev: Paint) => Paint;
112
+
113
+ export interface PaintRowsProps {
114
+ /** The lead paint, for display. */
115
+ value: Paint;
116
+ /** Every paint the rows write to, for the Mixed reads. Defaults to `value`. */
117
+ all?: readonly Paint[];
118
+ /** Every write, as an edit. The caller decides whether that lands on a scene
119
+ * or on a selection, and whether it becomes a keyframe. */
120
+ onCommit: (edit: PaintEdit) => void;
121
+ /** The DRAG half. A ColorPicker's SV square, hue bar and alpha bar never call
122
+ * `commit` while the pointer is down (ui/ColorPicker.tsx), so without these
123
+ * the thumb moves and the document does not. */
124
+ onScrubStart: () => void;
125
+ onScrub: (edit: PaintEdit) => void;
126
+ onScrubEnd: (commit: boolean) => void;
127
+ /** Prefixes every control's accessible name, because two of these can be on
128
+ * screen at once (a scene background and a layer fill). ACCESSIBLE NAMES
129
+ * ONLY: what the type row READS is `label` below. */
130
+ ariaPrefix: string;
131
+ /**
132
+ * The VISIBLE label of the type row. "Type" unless the caller says otherwise,
133
+ * and `null` for none at all.
134
+ *
135
+ * The callers want different words because the row sits under different
136
+ * headings. The shot's rows sit under a section called "Shot" beside a Lens
137
+ * and a Duration, where a bare "Type" offering Solid/Linear/Radial names
138
+ * nothing it is the type OF, so `SceneSection` passes "Background".
139
+ * `FillSection`'s heading already says Fill, and the design draws its type
140
+ * row as a bare chip track with no label, so it passes `null`: labelling it
141
+ * read Fill / Type / Color straight down the rail, three lines for one control.
142
+ */
143
+ label?: string | null;
144
+ /** The keyframe diamond for the SOLID colour, when the caller has one, in
145
+ * the colour's box. A gradient has no single colour and therefore no
146
+ * diamond: `background.color` and `paint.fill.color` are colour tracks, and
147
+ * the track table refuses them over a gradient for exactly that reason
148
+ * (tracks.ts). */
149
+ solidTrailing?: React.ReactNode;
150
+ /**
151
+ * A solid's OPACITY row, editing the colour's alpha byte. Opt-in, because a
152
+ * scene background is the frame's ground and has nothing behind it for an
153
+ * alpha to show; a layer fill does. It writes the same colour the picker's
154
+ * alpha bar does, so it is one more door onto `color`, not a second field.
155
+ */
156
+ showOpacity?: boolean;
157
+ /** The Opacity box's accessories: the colour's diamond again (the alpha
158
+ * byte of the same colour, keyed by the same track), without the expose
159
+ * pin the colour's box already carries, because a pin publishes the whole
160
+ * track and one per track is the rule (KeyframeDot). */
161
+ opacityTrailing?: React.ReactNode;
162
+ /** The paint holds no alpha (a scene background is opaque by contract): the
163
+ * pickers hide their alpha bar and every colour they commit is `#rrggbb`. */
164
+ opaque?: boolean;
165
+ }
166
+
167
+ /**
168
+ * A colour committed over a selection whose ALPHAS differ, onto one layer's
169
+ * own colour (`lead` is the colour the field shows, the lead layer's).
170
+ *
171
+ * Whether the author meant to set the alpha is read off the alpha byte the
172
+ * field was showing. A commit that left it where it was (a hue or SV drag, a
173
+ * typed six-digit hex, which ColorField fills from that alpha) changed the
174
+ * RGB only, so each layer keeps its own alpha. A commit that moved it (a typed
175
+ * eight-digit hex, the picker's alpha bar or its A field) sets that alpha on
176
+ * every layer: keeping each layer's own there meant typing eight digits, the
177
+ * ColorField's one way to set both, could not set the alpha at all.
178
+ *
179
+ * ponytail: the commit carries a colour and not how it was made, so an
180
+ * eight-digit hex whose alpha equals the lead's reads as untouched and each
181
+ * layer keeps its own. The Opacity (or Alpha) field beside the colour reads
182
+ * Mixed and sets one alpha on all; a commit that says whether its alpha was
183
+ * typed is a ColorField change.
184
+ */
185
+ export function colorOverMixedAlpha(committed: string, lead: string, own: string): string {
186
+ const next = tryParseHex(committed);
187
+ const mine = tryParseHex(own);
188
+ if (next === null || mine === null) return committed;
189
+ // An unreadable lead is alpha 1, ColorField's own fallback for six digits.
190
+ const shown = tryParseHex(lead)?.alpha ?? 1;
191
+ const byte = (alpha: number): number => Math.round(alpha * 255);
192
+ return byte(next.alpha) === byte(shown) ? formatHex({ ...next, alpha: mine.alpha }) : committed;
193
+ }
194
+
195
+ /** Can the bar edit these stops? Its algebra is contract's, so contract says. */
196
+ function barCanEdit(stops: readonly GradientStop[]): boolean {
197
+ return stops.length >= MIN_GRADIENT_STOPS && validateGradient({ kind: "gradient", stops }) === null;
198
+ }
199
+
200
+ /** The ramp as the CANVAS blends it: plain sRGB, which is a CSS gradient's
201
+ * default space. See the header for why the bar's own OKLCH fill is covered. */
202
+ function srgbRamp(stops: readonly GradientStop[]): string {
203
+ return `linear-gradient(to right, ${stops.map((s) => `${s.color} ${s.at * 100}%`).join(", ")})`;
204
+ }
205
+
206
+ /**
207
+ * Where "+ Stop" puts a stop: the middle of the WIDEST gap, coloured as the
208
+ * canvas already paints that point, so adding one moves no pixel and the new
209
+ * marker lands where it can be seen and grabbed. Null when the stops are not
210
+ * hex (nothing to blend) or have no gap to split.
211
+ */
212
+ function splitWidestGap(stops: readonly GradientStop[]): GradientStop[] | null {
213
+ let best = -1;
214
+ for (let i = 0; i + 1 < stops.length; i++) {
215
+ const gap = stops[i + 1]!.at - stops[i]!.at;
216
+ if (gap > 0 && (best < 0 || gap > stops[best + 1]!.at - stops[best]!.at)) best = i;
217
+ }
218
+ if (best < 0) return null;
219
+ const a = tryParseHex(stops[best]!.color);
220
+ const b = tryParseHex(stops[best + 1]!.color);
221
+ if (a === null || b === null) return null;
222
+ const mix = (x: number, y: number): number => (x + y) / 2;
223
+ const stop = {
224
+ at: mix(stops[best]!.at, stops[best + 1]!.at),
225
+ color: formatHex({ r: mix(a.r, b.r), g: mix(a.g, b.g), b: mix(a.b, b.b), alpha: mix(a.alpha, b.alpha) }),
226
+ };
227
+ return [...stops.slice(0, best + 1), stop, ...stops.slice(best + 1)];
228
+ }
229
+
230
+ export function PaintRows(props: PaintRowsProps) {
231
+ const { value, onCommit, onScrub, onScrubStart, onScrubEnd, ariaPrefix, label = "Type" } = props;
232
+ const all = props.all ?? [value];
233
+ const stops = value.type === "solid" ? [] : value.stops;
234
+ const [picked, setPicked] = useState(0);
235
+ const selected = Math.min(picked, Math.max(0, stops.length - 1));
236
+ // A VIEWER'S PANEL LOCKS EVERY VERB THAT WRITES. The widgets read the lock
237
+ // themselves; the stop verbs here are plain buttons, so they read it here.
238
+ const locked = useLocked();
239
+
240
+ // MIXED, per field. A selection whose TYPES differ shows the type row alone:
241
+ // no other row means the same thing on all of them.
242
+ const typeMixed = all.some((p) => p.type !== value.type);
243
+ const solidRgb = value.type === "solid" ? tryParseHex(value.color) : null;
244
+ const rgbOf = (p: Paint): string | null => {
245
+ const rgb = p.type === "solid" ? tryParseHex(p.color) : null;
246
+ return rgb === null ? null : `${rgb.r},${rgb.g},${rgb.b}`;
247
+ };
248
+ const colorMixed = all.some((p) => rgbOf(p) !== rgbOf(value));
249
+ const alphaMixed = all.some((p) => (p.type === "solid" ? tryParseHex(p.color)?.alpha : null) !== solidRgb?.alpha);
250
+ const angleMixed = all.some((p) => p.type !== "linear-gradient" || value.type !== "linear-gradient" || p.angleDeg !== value.angleDeg);
251
+ const stopsKey = JSON.stringify(stops);
252
+ const stopsMixed = all.some((p) => p.type === "solid" || JSON.stringify(p.stops) !== stopsKey);
253
+
254
+ /** A stops edit over each paint's OWN stops. The whole array is written: the
255
+ * renderer reads the array, so a merged single stop would be left behind. */
256
+ const onStops = (edit: (stops: GradientStop[]) => GradientStop[]): PaintEdit =>
257
+ (prev) => (prev.type === "solid" ? prev : { ...prev, stops: edit(prev.stops) });
258
+
259
+ const setStop = (index: number, patch: Partial<GradientStop>): PaintEdit =>
260
+ onStops((own) => own.map((stop, i) => (i === index ? { ...stop, ...patch } : stop)));
261
+
262
+ // THE BAR'S SCRUB, open between its first `onChange` and its commit or
263
+ // abort. See the header. A ref, because the bracket spans pointer events
264
+ // and must not wait on a render.
265
+ const barScrub = useRef<((commit: boolean) => void) | null>(null);
266
+ const live = useRef(props);
267
+ live.current = props;
268
+ useEffect(() => () => barScrub.current?.(false), []);
269
+
270
+ const barChange = (next: GradientStop[]): void => {
271
+ if (barScrub.current === null) {
272
+ const onEscape = (e: KeyboardEvent): void => {
273
+ if (e.key === "Escape") end(false);
274
+ };
275
+ const onAbort = (): void => end(false);
276
+ const end = (commit: boolean): void => {
277
+ window.removeEventListener("keydown", onEscape, true);
278
+ for (const type of ABORT_BUBBLE) window.removeEventListener(type, onAbort);
279
+ barScrub.current = null;
280
+ live.current.onScrubEnd(commit);
281
+ };
282
+ window.addEventListener("keydown", onEscape, true);
283
+ for (const type of ABORT_BUBBLE) window.addEventListener(type, onAbort);
284
+ barScrub.current = end;
285
+ onScrubStart();
286
+ }
287
+ onScrub(onStops(() => next));
288
+ };
289
+
290
+ const barCommit = (next: GradientStop[]): void => {
291
+ if (barScrub.current !== null) barScrub.current(true);
292
+ else onCommit(onStops(() => next));
293
+ };
294
+
295
+ /** The alpha byte on each paint's OWN colour. */
296
+ const withAlpha = (pct: number): PaintEdit => (prev) => {
297
+ const rgb = prev.type === "solid" ? tryParseHex(prev.color) : null;
298
+ return rgb === null
299
+ ? prev
300
+ : { type: "solid", color: formatHex({ ...rgb, alpha: Math.min(1, Math.max(0, pct / 100)) }) };
301
+ };
302
+ /** A colour pick; where opacities differ, each paint keeps its own alpha
303
+ * unless the pick moved the alpha the field shows (`colorOverMixedAlpha`). */
304
+ const solidColor = (color: string): PaintEdit => (prev) =>
305
+ prev.type !== "solid"
306
+ ? prev
307
+ : {
308
+ type: "solid",
309
+ color: alphaMixed && value.type === "solid" ? colorOverMixedAlpha(color, value.color, prev.color) : color,
310
+ };
311
+ const withAngle = (angleDeg: number): PaintEdit => (prev) =>
312
+ prev.type === "linear-gradient" ? { ...prev, angleDeg } : prev;
313
+
314
+ // The house segmented control, the one look every switch in the chrome
315
+ // shares (widgets.css): the chosen type is the flat tile, not an accent.
316
+ const typeRow = (
317
+ <SegmentedControl
318
+ ariaLabel={`${ariaPrefix} paint type`}
319
+ // An unmatched value is how a segmented control says Mixed.
320
+ value={typeMixed ? "" : value.type}
321
+ options={PAINT_TYPES.map((type) => ({ value: type, label: PAINT_LABELS[type] }))}
322
+ onChange={(next) => onCommit((prev) => convert(prev, next as Paint["type"]))}
323
+ />
324
+ );
325
+ const opaque = props.opaque === true;
326
+
327
+ return (
328
+ <>
329
+ {/* THE LABEL IS THE CALLER'S. "Background" over Solid/Linear/Radial in
330
+ the shot section, none at all under a heading that already names the
331
+ paint. The accessible name keeps its own longer spelling either way,
332
+ which is why it is a second prop and not this one. */}
333
+ {label === null ? <InspectorRow>{typeRow}</InspectorRow> : <InspectorRow label={label}>{typeRow}</InspectorRow>}
334
+
335
+ {typeMixed ? null : (
336
+ <>
337
+ {value.type === "solid" ? (
338
+ <InspectorRow>
339
+ <ColorField
340
+ value={value.color}
341
+ mixed={colorMixed}
342
+ ariaLabel={`${ariaPrefix} colour`}
343
+ {...(opaque ? { opaque: true } : {})}
344
+ trailing={props.solidTrailing}
345
+ scrub={{
346
+ start: onScrubStart,
347
+ move: (color) => onScrub(solidColor(String(color))),
348
+ end: onScrubEnd,
349
+ }}
350
+ commit={(color) => onCommit(solidColor(color))}
351
+ />
352
+ </InspectorRow>
353
+ ) : null}
354
+
355
+ {/* OPACITY IS THE ALPHA BYTE. Only over a colour the engine can read:
356
+ an unparseable string has no byte to set, and a field that wrote one
357
+ would replace the author's colour with a guess. It is keyed by the
358
+ colour's track, so its box carries the colour's diamond. */}
359
+ {value.type === "solid" && props.showOpacity === true && solidRgb !== null ? (
360
+ <InspectorRow>
361
+ <NumberField
362
+ value={Math.round(solidRgb.alpha * 100)}
363
+ mixed={alphaMixed}
364
+ label="Opacity"
365
+ ariaLabel={`${ariaPrefix} opacity`}
366
+ unit="%"
367
+ min={0}
368
+ max={100}
369
+ step={1}
370
+ trailing={props.opacityTrailing}
371
+ onScrubStart={onScrubStart}
372
+ onScrub={(pct) => onScrub(withAlpha(pct))}
373
+ onScrubEnd={onScrubEnd}
374
+ onCommit={(pct) => onCommit(withAlpha(pct))}
375
+ />
376
+ </InspectorRow>
377
+ ) : null}
378
+
379
+ {/* ANGLE IS LINEAR-ONLY. `paintStyle` builds a radial gradient from the
380
+ box centre outward, so there is no direction for an angle to name and
381
+ a field here would write a number nothing reads. */}
382
+ {value.type === "linear-gradient" ? (
383
+ <InspectorRow>
384
+ <NumberField
385
+ value={value.angleDeg}
386
+ mixed={angleMixed}
387
+ label="Angle"
388
+ ariaLabel={`${ariaPrefix} gradient angle`}
389
+ unit="°"
390
+ step={1}
391
+ // NO min/max, and no wrap. The axis is `cos`/`sin` of the angle, so
392
+ // 370 and 10 draw the same picture and clamping would only stop a
393
+ // drag from crossing the seam mid-gesture.
394
+ onScrubStart={onScrubStart}
395
+ onScrub={(angleDeg) => onScrub(withAngle(angleDeg))}
396
+ onScrubEnd={onScrubEnd}
397
+ onCommit={(angleDeg) => onCommit(withAngle(angleDeg))}
398
+ />
399
+ </InspectorRow>
400
+ ) : null}
401
+
402
+ {/* STOPS THAT DIFFER have no one list to show, so the stop controls
403
+ wait until the selection agrees. */}
404
+ {value.type !== "solid" && stopsMixed ? (
405
+ <p className="insp-note">The selected gradients have different stops.</p>
406
+ ) : null}
407
+
408
+ {value.type !== "solid" && !stopsMixed && barCanEdit(stops) ? (
409
+ <div className="paint-gradient" style={{ "--paint-ramp": srgbRamp(stops) } as CSSProperties}>
410
+ <GradientBar
411
+ stops={stops}
412
+ selected={selected}
413
+ onSelect={setPicked}
414
+ onChange={barChange}
415
+ onCommit={barCommit}
416
+ ariaLabel={`${ariaPrefix} gradient stops`}
417
+ />
418
+ </div>
419
+ ) : null}
420
+
421
+ {value.type !== "solid" && !stopsMixed
422
+ ? stops.map((stop, index) => (
423
+ <InspectorRow
424
+ // Index and not the stop's colour: two stops may share a colour,
425
+ // and a key that changes as the author drags a hue remounts the
426
+ // picker mid-gesture and drops the pointer capture.
427
+ key={index}
428
+ className={`paint-stop${index === selected ? " is-selected" : ""}`}
429
+ label={value.type === "radial-gradient" ? (index === 0 ? "Centre" : "Stop") : "Stop"}
430
+ trailing={
431
+ // Never below the minimum. One stop is a solid written the
432
+ // long way, and `paintStyle` on an EMPTY stops array draws
433
+ // nothing at all, which is a scene that renders as the clear
434
+ // colour with no control on screen explaining why.
435
+ //
436
+ // A VERB IN THE BOX, not a fourth diamond: the house accessory
437
+ // (`.insp-key`) at rest, the danger ink on hover (`is-danger`),
438
+ // and inside the stop's own box, so the row is as wide with it
439
+ // as without it.
440
+ stops.length > MIN_GRADIENT_STOPS ? (
441
+ <button
442
+ type="button"
443
+ className="insp-key is-danger"
444
+ title="Remove stop"
445
+ aria-label={`Remove ${ariaPrefix} stop ${index + 1}`}
446
+ disabled={locked}
447
+ onClick={() => onCommit(onStops((own) => own.filter((_, i) => i !== index)))}
448
+ >
449
+ <IconMinus />
450
+ </button>
451
+ ) : undefined
452
+ }
453
+ >
454
+ {/* A FIXED 28px BOX holding two values: the At field keeps its
455
+ in-box prefix in every field-label mode, since a name above
456
+ it would land inside the stop's own box. */}
457
+ <FieldLabels value="compact">
458
+ <ColorField
459
+ value={stop.color}
460
+ ariaLabel={`${ariaPrefix} stop ${index + 1} colour`}
461
+ {...(opaque ? { opaque: true } : {})}
462
+ scrub={{
463
+ start: onScrubStart,
464
+ move: (color) => onScrub(setStop(index, { color: String(color) })),
465
+ end: onScrubEnd,
466
+ }}
467
+ commit={(color) => onCommit(setStop(index, { color }))}
468
+ />
469
+ <NumberField
470
+ value={stop.at}
471
+ label="At"
472
+ ariaLabel={`${ariaPrefix} stop ${index + 1} position`}
473
+ min={0}
474
+ max={1}
475
+ // A SNAP, not a speed: the drag and the arrow keys land on
476
+ // twenty positions, a ramp an author can put where they
477
+ // meant; 0.01 would be a hundred and land on none of them.
478
+ // It is also the quantum this chrome already picked for its
479
+ // other bounded 0..1 fields, the media crop's and
480
+ // EffectsSection's amounts. The bar above is the fine
481
+ // control.
482
+ step={0.05}
483
+ onScrubStart={onScrubStart}
484
+ onScrub={(at) => onScrub(setStop(index, { at }))}
485
+ onScrubEnd={onScrubEnd}
486
+ onCommit={(at) => onCommit(setStop(index, { at }))}
487
+ />
488
+ </FieldLabels>
489
+ </InspectorRow>
490
+ ))
491
+ : null}
492
+
493
+ {value.type !== "solid" && !stopsMixed ? (
494
+ <InspectorRow className="paint-add-stop">
495
+ <button
496
+ type="button"
497
+ className="btn"
498
+ aria-label={`Add ${ariaPrefix} stop`}
499
+ disabled={locked}
500
+ // Adding a stop moves NO pixel: the author is opening a control,
501
+ // not asking for a change. It splits the widest gap at the colour
502
+ // the canvas already paints there, so the new marker is visible
503
+ // and grabbable on the bar. Over stops the engine cannot blend it
504
+ // falls back to appending at 1 in the last stop's colour, which
505
+ // is equally invisible, and dragging it left is the edit.
506
+ onClick={() => {
507
+ const split = splitWidestGap(stops);
508
+ if (split !== null) {
509
+ setPicked(split.findIndex((s, i) => s !== stops[i]));
510
+ onCommit(onStops(() => split));
511
+ return;
512
+ }
513
+ onCommit(onStops((own) => [...own, { at: 1, color: own[own.length - 1]?.color ?? "#ffffff" }]));
514
+ }}
515
+ >
516
+ <IconPlus />
517
+ Stop
518
+ </button>
519
+ </InspectorRow>
520
+ ) : null}
521
+ </>
522
+ )}
523
+ </>
524
+ );
525
+ }
526
+
527
+ /**
528
+ * The same paint as another type, keeping whatever survives the change.
529
+ *
530
+ * A type switch must not be a data-loss event the author cannot see coming, so
531
+ * a solid becomes a ramp that STARTS at its own colour, and a gradient becomes
532
+ * the solid of its first stop (which is what the old solid-only swatch already
533
+ * displayed for one, so the picture the author was looking at is the picture
534
+ * they get). The stops survive a linear/radial switch untouched, because both
535
+ * arms carry the identical array and only the geometry differs.
536
+ */
537
+ export function convert(paint: Paint, type: Paint["type"]): Paint {
538
+ if (paint.type === type) return paint;
539
+ if (type === "solid") {
540
+ return { type: "solid", color: paint.type === "solid" ? paint.color : (paint.stops[0]?.color ?? "#000000") };
541
+ }
542
+ const stops =
543
+ paint.type === "solid"
544
+ ? [{ at: 0, color: paint.color }, { at: 1, color: "#000000" }]
545
+ : paint.stops.map((stop) => ({ ...stop }));
546
+ // `angleDeg` is not preserved across a round trip through radial, because a
547
+ // radial gradient has nowhere to keep it. 90 is a top-to-bottom ramp, which
548
+ // is the one every background in this lane has ever wanted.
549
+ return type === "linear-gradient" ? { type, angleDeg: 90, stops } : { type, stops };
550
+ }