@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
@@ -16,17 +16,36 @@
16
16
  // +y DOWN (see the header of @dailies-studio/shot's types.ts). The stage did the
17
17
  // conversion because it is the half that knows the pane's pixel size. Nothing
18
18
  // here divides by a zoom, and nothing here knows what a screen is.
19
- import { LAYER_KINDS, clampSize, pathBounds, pathToD, translatePath } from "@dailies-studio/shot/doc";
19
+ import {
20
+ DEFAULT_ARROW_HEAD,
21
+ DEFAULT_HTML_CSS,
22
+ DEFAULT_HTML_MARKUP,
23
+ DEFAULT_PRIMITIVE_COLOR,
24
+ DEFAULT_TEXT_CONTENT,
25
+ DEFAULT_TEXT_STYLE,
26
+ SPLINE_POINT_ID_PREFIX,
27
+ LAYER_KINDS,
28
+ clampSize,
29
+ iconNodesToPathD,
30
+ pathBounds,
31
+ pathToD,
32
+ translatePath,
33
+ } from "@dailies-studio/shot/doc";
20
34
  import type {
35
+ IconNode,
21
36
  InstanceContractRef,
22
37
  Layer3D,
23
38
  Layer3DKind,
39
+ MeshPrimitiveKind,
40
+ MeshPrimitive,
24
41
  Paint,
25
42
  PathData,
26
43
  Stroke,
27
- TextStyle,
44
+ SplinePoint3D,
28
45
  } from "@dailies-studio/shot/doc";
29
- import type { Vec2, Vec3 } from "@dailies-studio/engine";
46
+ import type { SplineCreateDraft } from "../bridgeTypes.js";
47
+ import { parseHex } from "@dailies-studio/engine";
48
+ import type { Value, Vec2, Vec3 } from "@dailies-studio/engine";
30
49
  import type { InsertTool } from "../tools.js";
31
50
 
32
51
  /**
@@ -70,35 +89,35 @@ export const DEFAULT_DEVICE_SIZE: Vec2 = [400, 800];
70
89
  * resized to something a card actually wants. */
71
90
  export const DEFAULT_HTML_SIZE: Vec2 = [720, 360];
72
91
 
73
- /** An instance's box. Square, like a model's, and for the same reason: the
74
- * component decides its own extent (the box is a placement and a `CompSize`,
75
- * not a crop), so a rectangle here would make a claim about which way an
76
- * arbitrary module is long. */
77
- export const DEFAULT_INSTANCE_SIZE: Vec2 = [480, 480];
92
+ /** The colour a fresh paint opens on, for BOTH doors into one: the draw tools
93
+ * here and the inspector's own "+ Fill" (`FillSection`). White on the
94
+ * near-black a fresh scene opens on, because a default that draws nothing looks
95
+ * exactly like a tool that did nothing. */
96
+ export const DEFAULT_PAINT_COLOR = "#ffffff";
78
97
 
79
- /** What a drawn shape is filled with. White on the near-black a fresh scene
80
- * opens on: a default that draws nothing looks exactly like a tool that did
81
- * nothing. */
82
- export const DRAW_FILL: Paint = { type: "solid", color: "#ffffff" };
98
+ /** What a drawn shape is filled with. */
99
+ export const DRAW_FILL: Paint = { type: "solid", color: DEFAULT_PAINT_COLOR };
83
100
 
84
101
  /** …and what a drawn line, arrow or path is stroked with. A line layer with no
85
102
  * stroke is not drawn at all (`drawStroke` is the only thing that draws one),
86
- * so this is not a nicety. */
87
- export const DRAW_STROKE: Stroke = { color: "#ffffff", widthPx: 4, align: "center" };
88
-
89
- export const DRAW_TEXT_STYLE: TextStyle = {
90
- fontFamily: "Inter, system-ui, sans-serif",
91
- weight: 600,
92
- sizePx: 64,
93
- lineHeight: 1.2,
94
- letterSpacingPx: 0,
95
- align: "left",
103
+ * so this is not a nicety.
104
+ *
105
+ * 4px, and deliberately NOT `StrokeSection`'s `DEFAULT_STROKE` (1px): this
106
+ * stroke IS the drawn mark, while that one is a hairline added around a shape
107
+ * that already draws. Two gestures, two widths. Do not merge them. */
108
+ export const DRAW_STROKE: Stroke = {
96
109
  color: "#ffffff",
110
+ widthPx: 4,
111
+ align: "center",
97
112
  };
98
113
 
99
- /** The arrowhead, as a fraction of the box's larger side — the unit the schema
100
- * uses, so it scales with the arrow instead of needing a second edit. */
101
- const ARROW_HEAD = 0.12;
114
+ // The text style, the text content, the html snippet and the arrowhead ratio
115
+ // USED to be declared here too, and the agent's `add_layer` had a byte-identical
116
+ // copy of each. They now ship from `@dailies-studio/shot/doc` (`layerOps.ts`),
117
+ // which is the only place either door should read them from. The fill and the
118
+ // stroke above stay LOCAL on purpose: a drawn mark takes the author's own drag
119
+ // and the agent has no drag to take, so those two genuinely differ per lane.
120
+ // See layerOps.ts for which defaults were hoisted and which were not.
102
121
 
103
122
  /** Where the playhead is and how long its scene is: everything a new layer
104
123
  * needs to land in TIME. */
@@ -107,6 +126,13 @@ export interface InsertContext {
107
126
  sceneFrame: number;
108
127
  /** The scene's own length, which is where a lifetime that starts late ends. */
109
128
  durationFrames: number;
129
+ /**
130
+ * The design point (scene px) a default-placed layer centres on: the centre
131
+ * of what the frame shows, so an Add lands in view under a panned or pushed
132
+ * camera (F54). Absent = the design origin, which is also the answer inside
133
+ * a definition, whose interior has no camera.
134
+ */
135
+ origin?: DrawPoint;
110
136
  /** For the kinds that name one. Absent for everything else. */
111
137
  asset?: string;
112
138
  /**
@@ -122,6 +148,27 @@ export interface InsertContext {
122
148
  * Absent = an image, which is what every caller before video existed meant.
123
149
  */
124
150
  assetKind?: "image" | "video";
151
+ /**
152
+ * width / height of the source the browser decoded, for the media arm.
153
+ *
154
+ * ── WHY THE GEOMETRY NEEDS IT ─────────────────────────────────────────
155
+ *
156
+ * Without it every dropped recording landed in the literal `DEFAULT_DRAW_SIZE`
157
+ * box at `fit: "fill"`, so a 16:9 screen capture arrived cropped to 3:2 with
158
+ * no affordance for getting its aspect back but typing numbers. A mockup tool
159
+ * whose first act is to crop the thing you gave it is not one.
160
+ *
161
+ * A NUMBER AND NOT THE PIXELS, because the box is design px and the source's
162
+ * own resolution is not: a 3840x2160 capture and a 1280x720 one want the same
163
+ * plate. Absent (or non-finite, or non-positive) reproduces the old geometry
164
+ * exactly, which is what a host with nothing to measure has to fall back to —
165
+ * jsdom decodes no media, and a real browser can still fail to.
166
+ *
167
+ * MEASURED IN THE STORE, never here: this file's contract is no document, no
168
+ * history, no asynchrony, no DOM, and that is what keeps `boxOf` provable by
169
+ * calling it.
170
+ */
171
+ sourceAspect?: number;
125
172
  }
126
173
 
127
174
  /**
@@ -158,7 +205,10 @@ interface DrawBox {
158
205
  * saved. The ceiling comes along for free, which is what a drag across a
159
206
  * zoomed-way-out pane needs.
160
207
  */
161
- function boxOf(points: readonly DrawPoint[]): DrawBox | null {
208
+ function boxOf(
209
+ points: readonly DrawPoint[],
210
+ sourceAspect?: number,
211
+ ): DrawBox | null {
162
212
  const first = points[0];
163
213
  const last = points[points.length - 1];
164
214
  if (first === undefined || last === undefined) return null;
@@ -178,7 +228,10 @@ function boxOf(points: readonly DrawPoint[]): DrawBox | null {
178
228
  const rawW = maxX - minX;
179
229
  const rawH = maxY - minY;
180
230
  if (rawW <= 0 && rawH <= 0) {
181
- const [w, h] = DEFAULT_DRAW_SIZE;
231
+ // A CLICK. The default box, re-shaped to the source's aspect at the SAME
232
+ // AREA, so a portrait capture and a landscape one land equally prominent
233
+ // rather than one of them dwarfing the other.
234
+ const [w, h] = defaultBoxFor(sourceAspect);
182
235
  return boxOf([
183
236
  [minX - w / 2, minY - h / 2],
184
237
  [minX + w / 2, minY + h / 2],
@@ -193,15 +246,64 @@ function boxOf(points: readonly DrawPoint[]): DrawBox | null {
193
246
  rawH > 0 ? (p[1] - minY) / rawH : 0.5,
194
247
  ];
195
248
 
249
+ // A REAL DRAG. The aspect is fitted INSIDE what the author drew and never
250
+ // grows past it: they expressed an extent, and honouring the source's shape
251
+ // must not be a licence to spill outside the rectangle they were looking at.
252
+ const [fitW, fitH] = fitAspect(rawW, rawH, sourceAspect);
253
+
196
254
  return {
197
255
  min: [minX, minY],
198
256
  position: [(minX + maxX) / 2, (minY + maxY) / 2, 0],
199
- size: [clampSize(rawW), clampSize(rawH)],
257
+ size: [clampSize(fitW), clampSize(fitH)],
200
258
  from: frac(first),
201
259
  to: frac(last),
202
260
  };
203
261
  }
204
262
 
263
+ /** A usable width/height ratio, or null. One gate for every caller, so "no
264
+ * aspect", "NaN because the decode failed" and "0 because the element reported
265
+ * nothing yet" all take the identical fallback path. */
266
+ function usableAspect(sourceAspect: number | undefined): number | null {
267
+ return sourceAspect !== undefined &&
268
+ Number.isFinite(sourceAspect) &&
269
+ sourceAspect > 0
270
+ ? sourceAspect
271
+ : null;
272
+ }
273
+
274
+ /** The click box, at `DEFAULT_DRAW_SIZE`'s AREA and the source's shape. Without
275
+ * an aspect it is `DEFAULT_DRAW_SIZE` itself, to the byte. */
276
+ function defaultBoxFor(sourceAspect: number | undefined): Vec2 {
277
+ const aspect = usableAspect(sourceAspect);
278
+ const [w, h] = DEFAULT_DRAW_SIZE;
279
+ if (aspect === null) return [w, h];
280
+ const area = w * h;
281
+ const height = Math.sqrt(area / aspect);
282
+ return [height * aspect, height];
283
+ }
284
+
285
+ /** The largest box of `sourceAspect` that fits inside `w` x `h`. Without an
286
+ * aspect the drawn box passes through untouched. */
287
+ function fitAspect(
288
+ w: number,
289
+ h: number,
290
+ sourceAspect: number | undefined,
291
+ ): Vec2 {
292
+ const aspect = usableAspect(sourceAspect);
293
+ // A degenerate axis has no shape to honour: a horizontal line drag is floored
294
+ // by `clampSize` into a box the author never framed, and fitting an aspect
295
+ // into it would invent a height out of the floor.
296
+ if (aspect === null || w <= 0 || h <= 0) return [w, h];
297
+ return w / h > aspect ? [h * aspect, h] : [w, w / aspect];
298
+ }
299
+
300
+ /** Only the media arm has a source to honour. Every other tool draws exactly
301
+ * the box it was given, and a stray `sourceAspect` on the context must not
302
+ * quietly re-shape a rectangle or a device. */
303
+ function aspectFor(tool: InsertTool, ctx: InsertContext): number | undefined {
304
+ return tool === "image" ? ctx.sourceAspect : undefined;
305
+ }
306
+
205
307
  /**
206
308
  * THE INSERT MAP. Keyed on `InsertTool`, so a tool added to the table without
207
309
  * an answer here is a compile error rather than a button that does nothing.
@@ -211,17 +313,42 @@ function boxOf(points: readonly DrawPoint[]): DrawBox | null {
211
313
  */
212
314
  const INSERT: Record<
213
315
  InsertTool,
214
- (box: DrawBox, points: readonly DrawPoint[], ctx: InsertContext) => NewLayer3D | null
316
+ (
317
+ box: DrawBox,
318
+ points: readonly DrawPoint[],
319
+ ctx: InsertContext,
320
+ ) => NewLayer3D | null
215
321
  > = {
216
- rect: (box) => ({ kind: "rect", label: "Rect", ...common(box), paint: { fill: DRAW_FILL } }),
217
- ellipse: (box) => ({ kind: "ellipse", label: "Ellipse", ...common(box), paint: { fill: DRAW_FILL } }),
218
- text: (box) => ({
219
- kind: "text",
220
- label: "Text",
322
+ rect: (box) => ({
323
+ kind: "rect",
324
+ label: "Rect",
325
+ ...common(box),
326
+ paint: { fill: DRAW_FILL },
327
+ }),
328
+ ellipse: (box) => ({
329
+ kind: "ellipse",
330
+ label: "Ellipse",
221
331
  ...common(box),
222
- paint: {},
223
- text: { content: "Text", style: DRAW_TEXT_STYLE },
332
+ paint: { fill: DRAW_FILL },
224
333
  }),
334
+ // A CLICK makes an auto-width box (`TextBox`), as in Figma: its left edge on
335
+ // the click, centred on it vertically, at a size estimated here and measured
336
+ // exactly by the stage once it draws. A DRAG makes a fixed box that wraps
337
+ // inside the dragged width.
338
+ text: (box, points) => {
339
+ const [x, y] = points[0]!;
340
+ const click = points.every((p) => p[0] === x && p[1] === y);
341
+ const style = DEFAULT_TEXT_STYLE;
342
+ const w = Math.ceil(DEFAULT_TEXT_CONTENT.length * style.sizePx * 0.6);
343
+ const h = style.sizePx * style.lineHeight;
344
+ return {
345
+ kind: "text",
346
+ label: "Text",
347
+ ...common(click ? { ...box, position: [x + w / 2, y, 0], size: [w, h] } : box),
348
+ paint: {},
349
+ text: { content: DEFAULT_TEXT_CONTENT, style, ...(click ? { box: "auto" as const } : {}) },
350
+ };
351
+ },
225
352
  // ONE ARM, TWO KINDS. `image` and `video` are one pipeline everywhere it
226
353
  // matters — the runtime collapses both to {asset, fit, crop} and draws them
227
354
  // through one plane — so the Frame tool draws one box and the file the author
@@ -232,7 +359,13 @@ const INSERT: Record<
232
359
  // "fill" and not "fit", for both: the author drew the box they want
233
360
  // covered, and letterboxing it would leave the drawn rectangle half empty.
234
361
  if (ctx.assetKind !== "video") {
235
- return { kind: "image", label: "Image", ...common(box), paint: {}, image: { asset: ctx.asset, fit: "fill" } };
362
+ return {
363
+ kind: "image",
364
+ label: "Image",
365
+ ...common(box),
366
+ paint: {},
367
+ image: { asset: ctx.asset, fit: "fill" },
368
+ };
236
369
  }
237
370
  // THE SOURCE STARTS WHERE THE LAYER DOES. `withLifetime` puts the layer's
238
371
  // `in` at the playhead, and a video whose source clock still began at scene
@@ -247,7 +380,11 @@ const INSERT: Record<
247
380
  label: "Video",
248
381
  ...common(box),
249
382
  paint: {},
250
- video: { asset: ctx.asset, fit: "fill", ...(start > 0 ? { offsetFrames: start } : {}) },
383
+ video: {
384
+ asset: ctx.asset,
385
+ fit: "fill",
386
+ ...(start > 0 ? { offsetFrames: start } : {}),
387
+ },
251
388
  };
252
389
  },
253
390
  line: (box) => ({
@@ -262,7 +399,7 @@ const INSERT: Record<
262
399
  label: "Arrow",
263
400
  ...common(box),
264
401
  paint: { stroke: DRAW_STROKE },
265
- arrow: { from: box.from, to: box.to, head: ARROW_HEAD },
402
+ arrow: { from: box.from, to: box.to, head: DEFAULT_ARROW_HEAD },
266
403
  }),
267
404
  path: (box, points) => ({
268
405
  kind: "path",
@@ -278,7 +415,7 @@ const INSERT: Record<
278
415
  };
279
416
 
280
417
  /** The fields every drawn layer fills in the same way. */
281
- function common(box: DrawBox): { transform: Layer3D["transform"]; blocks: [] } {
418
+ function common(box: DrawBox): { transform: Layer3D["transform"] } {
282
419
  return {
283
420
  transform: {
284
421
  position: [box.position[0], box.position[1], box.position[2]],
@@ -287,7 +424,6 @@ function common(box: DrawBox): { transform: Layer3D["transform"]; blocks: [] } {
287
424
  rollDeg: 0,
288
425
  opacity: 1,
289
426
  },
290
- blocks: [],
291
427
  };
292
428
  }
293
429
 
@@ -295,7 +431,12 @@ function common(box: DrawBox): { transform: Layer3D["transform"]; blocks: [] } {
295
431
  * a document written twice from the same drag is byte-identical. */
296
432
  function polyline(points: readonly DrawPoint[], min: Vec2): string {
297
433
  const n = (v: number): string => String(Math.round(v * 100) / 100);
298
- return points.map((p, i) => `${i === 0 ? "M" : "L"} ${n(p[0] - min[0])} ${n(p[1] - min[1])}`).join(" ");
434
+ return points
435
+ .map(
436
+ (p, i) =>
437
+ `${i === 0 ? "M" : "L"} ${n(p[0] - min[0])} ${n(p[1] - min[1])}`,
438
+ )
439
+ .join(" ");
299
440
  }
300
441
 
301
442
  /**
@@ -315,7 +456,10 @@ export function layerFromDraw(
315
456
  // already two identical ones, so this is the only arity worth stating.
316
457
  if (tool === "path" && dedupe(points).length < 2) return null;
317
458
  const cleaned = tool === "path" ? dedupe(points) : points;
318
- const box = boxOf(cleaned);
459
+ // The aspect reaches `boxOf` and nothing else: it is a GEOMETRY input, so it
460
+ // belongs where the box is decided rather than in the media arm, which would
461
+ // then be re-deriving a position and a size the box already computed.
462
+ const box = boxOf(cleaned, aspectFor(tool, ctx));
319
463
  if (box === null) return null;
320
464
  const layer = INSERT[tool](box, cleaned, ctx);
321
465
  return layer === null ? null : withLifetime(layer, ctx);
@@ -337,7 +481,10 @@ export function layerFromDraw(
337
481
  * origin into `d` and break `pathToD(points) === d` the moment the document was
338
482
  * opened.
339
483
  */
340
- export function penLayer(data: PathData, ctx: InsertContext): NewLayer3D | null {
484
+ export function penLayer(
485
+ data: PathData,
486
+ ctx: InsertContext,
487
+ ): NewLayer3D | null {
341
488
  if (data.points.length < 2) return null;
342
489
  const bounds = pathBounds(data);
343
490
  const box = boxOf([
@@ -355,7 +502,11 @@ export function penLayer(data: PathData, ctx: InsertContext): NewLayer3D | null
355
502
  // author was drawing, and a filled shape appearing on close would be a
356
503
  // second edit they did not make. The inspector adds one in a click.
357
504
  paint: { stroke: DRAW_STROKE },
358
- path: { d: pathToD(local), viewBox: [box.size[0], box.size[1]], points: local },
505
+ path: {
506
+ d: pathToD(local),
507
+ viewBox: [box.size[0], box.size[1]],
508
+ points: local,
509
+ },
359
510
  },
360
511
  ctx,
361
512
  );
@@ -367,25 +518,153 @@ export function meshLayer(asset: string, ctx: InsertContext): NewLayer3D {
367
518
  {
368
519
  kind: "mesh",
369
520
  label: "Model",
370
- transform: centred(DEFAULT_MESH_SIZE),
521
+ transform: centred(DEFAULT_MESH_SIZE, ctx),
371
522
  // Inert on a mesh — the look is `mesh.shading`, not a Canvas2D fill.
372
523
  paint: {},
373
- blocks: [],
374
524
  mesh: { asset },
375
525
  },
376
526
  ctx,
377
527
  );
378
528
  }
379
529
 
530
+ /** A shape's box. Square like a model's: every primitive derives its radii
531
+ * from the box, and a square makes no claim about which way a shape is long. */
532
+ export const DEFAULT_SHAPE_SIZE: Vec2 = [300, 300];
533
+
534
+ /**
535
+ * A parametric shape (schema v11), from the ADD menu.
536
+ *
537
+ * TOON BY DEFAULT, which is this factory's one real decision: an unlit
538
+ * flat-colour solid has no visible form — the silhouette is all there is — so
539
+ * a fresh box would read as a rounded rectangle until the author found the
540
+ * shading row. The default colour is authored INTO the document rather than
541
+ * left to the runtime's fallback, so the inspector's colour well shows the
542
+ * value the stage is actually drawing.
543
+ */
544
+ export function shapeLayer(
545
+ kind: MeshPrimitiveKind,
546
+ ctx: InsertContext,
547
+ ): NewLayer3D {
548
+ return withLifetime(
549
+ {
550
+ kind: "mesh",
551
+ label: kind.charAt(0).toUpperCase() + kind.slice(1),
552
+ transform: centred(DEFAULT_SHAPE_SIZE, ctx),
553
+ // Inert on a mesh — the look is colour + shading, not a Canvas2D fill.
554
+ paint: {},
555
+ mesh: {
556
+ primitive: primitiveForKind(kind),
557
+ color: DEFAULT_PRIMITIVE_COLOR,
558
+ shading: { mode: "toon" },
559
+ },
560
+ },
561
+ ctx,
562
+ );
563
+ }
564
+
565
+ /** A complete valid primitive for an Add-menu insert or an inspector kind
566
+ * switch. Structural spline defaults cannot be represented by `{kind}`. */
567
+ export function primitiveForKind(kind: MeshPrimitiveKind): MeshPrimitive {
568
+ if (kind === "spline") {
569
+ return {
570
+ kind,
571
+ frameSize: [300, 300],
572
+ points: [
573
+ {
574
+ id: `${SPLINE_POINT_ID_PREFIX}0`,
575
+ p: [-150, 60, 0],
576
+ out: [-50, -60, 0],
577
+ },
578
+ { id: `${SPLINE_POINT_ID_PREFIX}1`, p: [150, -60, 0], in: [50, 60, 0] },
579
+ ],
580
+ basis: "bezier",
581
+ profile: { kind: "tube", radiusPx: 8 },
582
+ caps: "round",
583
+ };
584
+ }
585
+ return { kind } as MeshPrimitive;
586
+ }
587
+
588
+ /** Normalize a stage-authored world-space draft into one local spline layer. */
589
+ export function splineLayer(
590
+ draft: SplineCreateDraft,
591
+ ctx: InsertContext,
592
+ ): NewLayer3D | null {
593
+ if (draft.points.length < (draft.closed ? 3 : 2)) return null;
594
+ const values = draft.points.flatMap((point) =>
595
+ [point.p, point.in, point.out].filter(isVec3),
596
+ );
597
+ if (values.length === 0) return null;
598
+ const min: Vec3 = [Infinity, Infinity, Infinity];
599
+ const max: Vec3 = [-Infinity, -Infinity, -Infinity];
600
+ for (const value of values) {
601
+ for (let axis = 0; axis < 3; axis += 1) {
602
+ min[axis] = Math.min(min[axis]!, value[axis]!);
603
+ max[axis] = Math.max(max[axis]!, value[axis]!);
604
+ }
605
+ }
606
+ if (![...min, ...max].every(Number.isFinite)) return null;
607
+ const centre: Vec3 = [
608
+ (min[0] + max[0]) / 2,
609
+ (min[1] + max[1]) / 2,
610
+ (min[2] + max[2]) / 2,
611
+ ];
612
+ const size: Vec2 = [clampSize(max[0] - min[0]), clampSize(max[1] - min[1])];
613
+ const local = (value: readonly [number, number, number]): Vec3 => [
614
+ value[0] - centre[0],
615
+ value[1] - centre[1],
616
+ value[2] - centre[2],
617
+ ];
618
+ const points: SplinePoint3D[] = draft.points.map((point, index) => ({
619
+ id: `${SPLINE_POINT_ID_PREFIX}${index}`,
620
+ p: local(point.p),
621
+ ...(point.in === undefined ? {} : { in: local(point.in) }),
622
+ ...(point.out === undefined ? {} : { out: local(point.out) }),
623
+ }));
624
+ return withLifetime(
625
+ {
626
+ kind: "mesh",
627
+ label: "Spline",
628
+ transform: {
629
+ position: centre,
630
+ size,
631
+ tilt: [0, 0],
632
+ rollDeg: 0,
633
+ opacity: 1,
634
+ },
635
+ paint: {},
636
+ mesh: {
637
+ primitive: {
638
+ kind: "spline",
639
+ frameSize: [size[0], size[1]],
640
+ points,
641
+ basis: draft.basis,
642
+ ...(draft.closed ? { closed: true } : {}),
643
+ profile: { kind: "tube", radiusPx: 8 },
644
+ caps: "round",
645
+ },
646
+ color: DEFAULT_PRIMITIVE_COLOR,
647
+ shading: { mode: "toon" },
648
+ },
649
+ },
650
+ ctx,
651
+ );
652
+ }
653
+
654
+ function isVec3(value: Vec3 | undefined): value is Vec3 {
655
+ return (
656
+ value !== undefined && value.length === 3 && value.every(Number.isFinite)
657
+ );
658
+ }
659
+
380
660
  /** A device slab, from the ADD menu. */
381
661
  export function deviceLayer(preset: string, ctx: InsertContext): NewLayer3D {
382
662
  return withLifetime(
383
663
  {
384
664
  kind: "device",
385
665
  label: preset.charAt(0).toUpperCase() + preset.slice(1),
386
- transform: centred(DEFAULT_DEVICE_SIZE),
666
+ transform: centred(DEFAULT_DEVICE_SIZE, ctx),
387
667
  paint: {},
388
- blocks: [],
389
668
  device: { preset, children: [] },
390
669
  },
391
670
  ctx,
@@ -408,15 +687,9 @@ export function htmlLayer(ctx: InsertContext): NewLayer3D {
408
687
  {
409
688
  kind: "html",
410
689
  label: "Html",
411
- transform: centred(DEFAULT_HTML_SIZE),
690
+ transform: centred(DEFAULT_HTML_SIZE, ctx),
412
691
  paint: {},
413
- blocks: [],
414
- html: {
415
- markup: `<div class="root"><span>Html</span></div>`,
416
- css:
417
- ".root{width:100%;height:100%;display:flex;align-items:center;justify-content:center;" +
418
- "font:600 64px Inter,system-ui,sans-serif;color:#fff}",
419
- },
692
+ html: { markup: DEFAULT_HTML_MARKUP, css: DEFAULT_HTML_CSS },
420
693
  },
421
694
  ctx,
422
695
  );
@@ -429,32 +702,113 @@ export function htmlLayer(ctx: InsertContext): NewLayer3D {
429
702
  * hash it was bound at, and the contract itself when the registry says a
430
703
  * generated file could not import it. That is what makes a later source edit
431
704
  * show up as DRIFT the author resolves, instead of silently re-typing props
432
- * authored against the old shape. `props` starts EMPTY — every unset key takes
433
- * the contract's own default through `buildProps`, and baking defaults in at
434
- * insert time would freeze them against later edits to the component itself.
705
+ * authored against the old shape. `props` seeds the instance's own values;
706
+ * every unset key takes the contract's default through `buildProps`, and
707
+ * baking defaults in at insert time would freeze them against later edits to
708
+ * the component itself.
709
+ *
710
+ * ONE SIZE, `frame`: the whole frame. A shipped component composes against its
711
+ * box and its card thumbnail shows it on a whole frame, so what is clicked is
712
+ * what lands, from every door (F51, F58).
435
713
  */
436
714
  export function instanceLayer(
437
715
  component: string,
438
716
  label: string,
439
717
  binding: { contractHash?: string; contract?: InstanceContractRef },
718
+ props: Readonly<Record<string, Value>>,
719
+ frame: Vec2,
440
720
  ctx: InsertContext,
441
721
  ): NewLayer3D {
442
722
  return withLifetime(
443
723
  {
444
724
  kind: "instance",
445
725
  label,
446
- transform: centred(DEFAULT_INSTANCE_SIZE),
726
+ transform: centred(frame, ctx),
447
727
  // Inert on an instance, as on a mesh: the component paints itself.
448
728
  paint: {},
449
- blocks: [],
450
- instance: { component, props: {}, ...binding },
729
+ instance: { component, props: { ...props }, ...binding },
730
+ },
731
+ ctx,
732
+ );
733
+ }
734
+
735
+ /** An icon's box. Square, like the 24×24 grid it is drawn on. */
736
+ export const ICON_SIZE = 96;
737
+
738
+ /** The two inks an icon is drawn in: light on a dark Shot, dark on a light
739
+ * one. */
740
+ export const ICON_INK = { light: "#f5f5f5", dark: "#111111" } as const;
741
+
742
+ /**
743
+ * The ink that reads on `background`: its mean relative luminance (sRGB
744
+ * weights, linearised) against 0.18, where black and white ink contrast
745
+ * equally. A gradient averages its stops.
746
+ * A colour that does not parse counts as dark, the scene's own default.
747
+ */
748
+ export function inkFor(background: Paint): string {
749
+ const colors = background.type === "solid" ? [background.color] : background.stops.map((s) => s.color);
750
+ const lin = (c: number): number => (c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
751
+ let sum = 0;
752
+ for (const color of colors) {
753
+ try {
754
+ const { r, g, b } = parseHex(color);
755
+ sum += 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);
756
+ } catch {
757
+ /* not a hex: counted as black */
758
+ }
759
+ }
760
+ return colors.length > 0 && sum / colors.length > 0.18 ? ICON_INK.dark : ICON_INK.light;
761
+ }
762
+
763
+ /** The name "Use as background" gives the ground it places. */
764
+ export const BACKGROUND_LABEL = "Background";
765
+
766
+ /**
767
+ * The ground "Use as background" placed in this Shot, or null: the back root
768
+ * layer, locked and named Background. Locked layers skip stage picks, so the
769
+ * Shot inspector's door selects it by this.
770
+ */
771
+ export function backgroundLayerOf(scene: { layers: readonly string[]; layersById: Readonly<Record<string, Layer3D>> } | undefined): string | null {
772
+ const id = scene?.layers[0];
773
+ const layer = id === undefined ? undefined : scene?.layersById[id];
774
+ return layer !== undefined && layer.locked === true && layer.label === BACKGROUND_LABEL ? layer.id : null;
775
+ }
776
+
777
+ /**
778
+ * A Lucide icon as a PATH layer: editable, recolourable and keyframable, never
779
+ * an image. Lucide draws a 2px stroke on a 24px grid with round ends and no
780
+ * fill, and the stroke width here is that 2px scaled to the box (8px on 96),
781
+ * so the layer looks like the tile the author clicked.
782
+ */
783
+ export function iconLayer(
784
+ name: string,
785
+ nodes: readonly IconNode[],
786
+ ink: string,
787
+ ctx: InsertContext,
788
+ ): NewLayer3D {
789
+ return withLifetime(
790
+ {
791
+ kind: "path",
792
+ label: name,
793
+ transform: centred([ICON_SIZE, ICON_SIZE], ctx),
794
+ paint: {
795
+ stroke: { color: ink, widthPx: (2 * ICON_SIZE) / 24, align: "center", cap: "round", join: "round" },
796
+ },
797
+ path: { d: iconNodesToPathD(nodes), viewBox: [24, 24] },
451
798
  },
452
799
  ctx,
453
800
  );
454
801
  }
455
802
 
456
- function centred(size: Vec2): Layer3D["transform"] {
457
- return { position: [0, 0, 0], size: [size[0], size[1]], tilt: [0, 0], rollDeg: 0, opacity: 1 };
803
+ function centred(size: Vec2, ctx: InsertContext): Layer3D["transform"] {
804
+ const [x, y] = ctx.origin ?? [0, 0];
805
+ return {
806
+ position: [x, y, 0],
807
+ size: [size[0], size[1]],
808
+ tilt: [0, 0],
809
+ rollDeg: 0,
810
+ opacity: 1,
811
+ };
458
812
  }
459
813
 
460
814
  /**
@@ -470,10 +824,33 @@ function centred(size: Vec2): Layer3D["transform"] {
470
824
  * Same placement, one fewer thing to maintain.
471
825
  */
472
826
  function withLifetime(layer: NewLayer3D, ctx: InsertContext): NewLayer3D {
827
+ const life = lifetimeFor(ctx);
828
+ return life === undefined
829
+ ? layer
830
+ : ({ ...layer, lifetime: life } as NewLayer3D);
831
+ }
832
+
833
+ /**
834
+ * The rule above, as a value, for the one create that does not build its
835
+ * layer here.
836
+ *
837
+ * `placeComponent` goes through `componentOps.addComponentInstance`, which
838
+ * assembles its own instance, so it could not wrap itself in `withLifetime`
839
+ * and for a long time did not land at the playhead at all: the rail's `+` on a
840
+ * definition made a whole-scene instance while the `+` one list below it, on a
841
+ * code component, landed under the clock. Two adjacent buttons, two answers to
842
+ * "when does this start". Exported so there is one.
843
+ *
844
+ * `undefined` at frame 0 for the reason above: absent already means the whole
845
+ * scene, and it keeps meaning that when the scene is later trimmed.
846
+ */
847
+ export function lifetimeFor(
848
+ ctx: InsertContext,
849
+ ): { in: number; out: number } | undefined {
473
850
  const start = startFrameOf(ctx);
474
851
  const end = Math.trunc(ctx.durationFrames);
475
- if (start <= 0 || end <= start) return layer;
476
- return { ...layer, lifetime: { in: start, out: end } } as NewLayer3D;
852
+ if (start <= 0 || end <= start) return undefined;
853
+ return { in: start, out: end };
477
854
  }
478
855
 
479
856
  /** The scene frame a new layer lands on. ONE function because TWO things read