@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
@@ -14,7 +14,22 @@
14
14
  // it and layer picking cannot return it.
15
15
  //
16
16
  // It also touches no document. Everything it draws is a pure function of the
17
- // resolved rig, which is a pure function of (document, frame).
17
+ // resolved rig (or of the camera a component drove), which is a pure function
18
+ // of (document, frame).
19
+ //
20
+ // ── TWO BUFFERS: THE BODY AND THE REACH ─────────────────────────────────
21
+ //
22
+ // The BODY is the camera as an object: a short lens pyramid in front of the eye,
23
+ // a box behind it, the up-marker on the lens. Its size is fixed against the
24
+ // SCENE (frustumGeometry's `cameraDisplayLength`), clamped in screen px, so it
25
+ // reads as one more object in the world rather than a thing that grows as the
26
+ // author backs away. It is drawn for the rig and for a driven camera alike.
27
+ //
28
+ // The REACH is the document's model: the four long edges from the eye to the
29
+ // framed rectangle at the design plane, and that rectangle. It is what is in
30
+ // shot, from any angle, and the rig case alone has it (a driven camera has no
31
+ // plane to land on). Dimmer than the body, so the body reads as the camera and
32
+ // the reach as its sightline.
18
33
  //
19
34
  // ── WHY THE MATERIAL REFUSES THE OVERRIDE ───────────────────────────────
20
35
  //
@@ -25,47 +40,42 @@
25
40
  //
26
41
  // ── AND WHY THE COLOUR IS READ FROM CSS ─────────────────────────────────
27
42
  //
28
- // It is a MATERIAL colour, not a GL clear colour, and it is resolved once at
29
- // mount inside the stage document — which imports shotTokens.css. So `--frustum`
30
- // stays the single definition both documents share, and the overlay could draw
31
- // the same yellow tomorrow without a second literal. (SplitRenderer's clay is
32
- // hard-coded for the opposite reason: a clear colour is written before any
33
- // style has resolved.)
34
- import { useEffect, useMemo } from "react";
35
- import { useFrame } from "@react-three/fiber";
36
- import {
37
- BufferAttribute,
38
- BufferGeometry,
39
- LineSegments,
40
- Vector3,
41
- } from "three";
43
+ // It is a MATERIAL colour, not a GL clear colour, and it is resolved inside the
44
+ // stage document, which imports shotTokens.css, whenever appearance changes.
45
+ // So `--frustum` stays the single definition both documents share, and the
46
+ // overlay could draw the same yellow tomorrow without a second literal.
47
+ import { useEffect, useMemo, useRef } from "react";
48
+ import { useFrame, useThree } from "@react-three/fiber";
49
+ import { Box3, BufferAttribute, BufferGeometry, LineSegments, Vector3 } from "three";
50
+ import type { Quaternion } from "three";
42
51
  import { LineBasicNodeMaterial } from "three/webgpu";
43
52
  import { EDITOR_LAYER } from "./StageTools.js";
44
53
  import { tokenColor } from "./tokenColor.js";
54
+ import type { StageTheme } from "./stageTheme.js";
45
55
  import {
46
- FRUSTUM_SEGMENT_FLOATS,
56
+ BODY_GRAB_PX,
57
+ CAMERA_BODY_SEGMENT_FLOATS,
58
+ FRAME_SEGMENT_FLOATS,
47
59
  borderDistance,
48
- frustumSegments,
60
+ cameraBodySegments,
61
+ cameraBodyShape,
62
+ cameraDisplayLength,
63
+ frameSegments,
49
64
  frustumShape,
65
+ isDriven,
66
+ rigPose,
50
67
  segmentsMoved,
68
+ waypointCrossSize,
69
+ waypointSegmentFloats,
70
+ waypointSegments,
51
71
  } from "./frustumGeometry.js";
52
- import type { CameraRig } from "./frustumGeometry.js";
53
- import { toScreen } from "./project.js";
72
+ import type { CameraPose, CameraRig } from "./frustumGeometry.js";
73
+ import { toScreen, worldPerPixel } from "./project.js";
74
+ import { sceneExtent } from "./sceneExtent.js";
54
75
  import type { StageFeed } from "./feed.js";
55
76
  import type { PaneRect } from "./panes.js";
56
77
  import type { FrameSize } from "./view.js";
57
78
 
58
- /** How close a press has to land to the apex to grab it, in CSS px. Larger
59
- * than the layer handles' 10: the apex is a single point with no box around
60
- * it, and it is the only way to reach the zoom. */
61
- export const APEX_GRAB_PX = 14;
62
-
63
- /** How close a press has to land to the base rectangle's DRAWN border to grab
64
- * the body. The interior is deliberately not a grab target — see
65
- * `borderDistance` for why an interior claim takes the whole pane away — so
66
- * the border is a line, and it gets the same slop the apex point does. */
67
- const BODY_GRAB_PX = 14;
68
-
69
79
  /** Over everything. Every material in this lane draws with `depthTest: false`,
70
80
  * so paint order is `renderOrder` alone (ShotRuntime's header) — a gizmo that
71
81
  * shared the layers' range would be hidden by whichever layer sorted after it. */
@@ -73,112 +83,522 @@ const FRUSTUM_RENDER_ORDER = 1_000_000;
73
83
 
74
84
  const FRUSTUM_FALLBACK = "#eab308";
75
85
 
86
+ /** How far the reach sits below the body: the sightline reads as belonging to
87
+ * the camera without competing with it. */
88
+ const REACH_OPACITY = 0.55;
89
+
90
+ /** How far a hovered gizmo blends towards the theme's contrast ink — the move
91
+ * arms' own tint, so the two widgets in this pane light up by the same amount
92
+ * and neither reads as the more active one. */
93
+ const HOVER_TINT = 0.45;
94
+
95
+ /** How many committed frames a driven scene's bounds are trusted for before
96
+ * the walk runs again. A flythrough moves objects, but the ground and the
97
+ * architecture bound the world; a change between refreshes is a sub-pixel
98
+ * pop in the body's size, not a wrong drawing. */
99
+ const EXTENT_REFRESH_FRAMES = 60;
100
+
101
+ /** The lens rectangle stays in front of the design plane: this far along the
102
+ * rig's own distance at most. */
103
+ const REACH_CAP_FRACTION = 0.9;
104
+
76
105
  /** Scratch for the border test's behind-camera check. */
77
106
  const _view = new Vector3();
78
107
 
108
+ /**
109
+ * AFTER EVERY CAMERA WRITER. `ShotCamera` puts the render camera on the rig at
110
+ * priority 0, and an instance component that drives the camera itself writes
111
+ * below that (fiber sorts a phase's priorities DESCENDING, so lower runs
112
+ * later). The gizmo reads the camera after all of them, so it draws the
113
+ * camera the pane is about to render with and not the one the rig proposed.
114
+ */
115
+ const AFTER_CAMERA_WRITERS = -1000;
116
+
117
+ /** Scratch for reading the render camera's basis. */
118
+ const _eye = new Vector3();
119
+ const _right = new Vector3();
120
+ const _up = new Vector3();
121
+ const _forward = new Vector3();
122
+ const _pose: CameraPose = { eye: [0, 0, 0], right: [1, 0, 0], up: [0, 1, 0], forward: [0, 0, -1], fovDeg: 35 };
123
+ /** Scratch for the bounds walk, and its centre. */
124
+ const _bounds = new Box3();
125
+ const _centre = new Vector3();
126
+
127
+ /** The render camera's pose, or null when it is not a perspective camera. */
128
+ function readPose(camera: object): CameraPose | null {
129
+ const cam = camera as {
130
+ isPerspectiveCamera?: boolean;
131
+ fov: number;
132
+ position: Vector3;
133
+ quaternion: Quaternion;
134
+ };
135
+ if (cam.isPerspectiveCamera !== true) return null;
136
+ _eye.copy(cam.position);
137
+ _right.set(1, 0, 0).applyQuaternion(cam.quaternion);
138
+ _up.set(0, 1, 0).applyQuaternion(cam.quaternion);
139
+ _forward.set(0, 0, -1).applyQuaternion(cam.quaternion);
140
+ _pose.eye = [_eye.x, _eye.y, _eye.z];
141
+ _pose.right = [_right.x, _right.y, _right.z];
142
+ _pose.up = [_up.x, _up.y, _up.z];
143
+ _pose.forward = [_forward.x, _forward.y, _forward.z];
144
+ _pose.fovDeg = cam.fov;
145
+ return _pose;
146
+ }
147
+
148
+ /** How close a press has to land to a waypoint's centre to grab it, in CSS px.
149
+ * A point with no box around it gets the body's own slop. */
150
+ export const WAYPOINT_GRAB_PX = 14;
151
+
152
+ /** The eyes of the path move under the playhead, if there is one. */
153
+ export interface WaypointMarks {
154
+ shotId: string;
155
+ eyes: readonly [number, number, number][];
156
+ }
157
+
79
158
  export interface FrustumGizmoProps {
159
+ /** Resolved editor theme. It rebuilds only the token-backed GL material. */
160
+ theme: StageTheme;
80
161
  feed: StageFeed;
81
162
  panes: { working: PaneRect | null; camera: PaneRect | null };
82
- /** The camera as resolved at the playhead. Null when no scene is on screen. */
83
- rig: CameraRig | null;
163
+ /** The camera rig at the clock's frame, read at FRAME time (StageApp's
164
+ * `playhead`), or null when nothing is on screen. */
165
+ rigAt: () => CameraRig | null;
84
166
  frame: FrameSize;
167
+ /**
168
+ * The waypoints to draw and to make grabbable, or null.
169
+ *
170
+ * Null whenever the playhead is not inside a PATH move — the same rule the
171
+ * frustum follows about which camera it draws. Computed by the host because
172
+ * it holds the document; this component only ever draws what it is handed.
173
+ */
174
+ waypointsAt: () => WaypointMarks | null;
175
+ /**
176
+ * The component that has CLAIMED the render camera (the runtime's
177
+ * `useClaimStageCamera`), read at frame time, or null. A claim beats the
178
+ * float comparison `offTheRig` makes, and it brings the component's own
179
+ * extent for the body's size.
180
+ */
181
+ driverAt: () => { name: string; extent?: number } | null;
182
+ /**
183
+ * Who drives the camera, pushed ON CHANGE: the claim's name, "a component"
184
+ * for an unclaimed writer the float comparison caught, or null for the rig.
185
+ * The stage relays it to the chrome (`onCameraDriver`).
186
+ */
187
+ onDriver: (name: string | null) => void;
188
+ }
189
+
190
+ /** One line buffer: allocated once, rewritten in place. `rest` is its opacity
191
+ * when nothing holds the camera; a held camera draws at a fraction of it. */
192
+ interface Lines {
193
+ lines: LineSegments;
194
+ geometry: BufferGeometry;
195
+ material: LineBasicNodeMaterial;
196
+ tint: number;
197
+ rest: number;
198
+ opacity: number;
199
+ }
200
+
201
+ /** How far a HELD camera fades: the fork would refuse a drag on it (a finished
202
+ * move with Animate off, a path), and the dimming says so before the press. */
203
+ const HELD_OPACITY = 0.4;
204
+
205
+ function newLines(floats: number, opacity: number): Lines {
206
+ const geometry = new BufferGeometry();
207
+ geometry.setAttribute("position", new BufferAttribute(new Float32Array(floats), 3));
208
+ const material = new LineBasicNodeMaterial();
209
+ material.color.copy(tokenColor("--frustum", FRUSTUM_FALLBACK));
210
+ material.depthTest = false;
211
+ material.depthWrite = false;
212
+ material.toneMapped = false;
213
+ // See the header: the working pane renders under a clay override.
214
+ material.allowOverride = false;
215
+ // Always transparent, so the held fade is one `opacity` write and not a
216
+ // material recompile the first time the playhead crosses into a move.
217
+ material.transparent = true;
218
+ material.opacity = opacity;
219
+ const lines = new LineSegments(geometry, material);
220
+ lines.layers.set(EDITOR_LAYER);
221
+ lines.renderOrder = FRUSTUM_RENDER_ORDER;
222
+ // The vertices are WORLD positions rewritten in place, so the object stays
223
+ // at the origin and the bounding sphere the culler would test is never the
224
+ // one the buffer holds. Culling it against a stale sphere makes the gizmo
225
+ // vanish at some orbits and not others.
226
+ lines.frustumCulled = false;
227
+ lines.matrixAutoUpdate = false;
228
+ return { lines, geometry, material, tint: 0, rest: opacity, opacity };
229
+ }
230
+
231
+ function fadeTo(target: Lines, held: boolean): void {
232
+ const want = held ? target.rest * HELD_OPACITY : target.rest;
233
+ if (target.opacity === want) return;
234
+ target.opacity = want;
235
+ target.material.opacity = want;
236
+ }
237
+
238
+ /** Rewrite a buffer only when a vertex moved by more than `epsilon`: this runs
239
+ * on every committed frame, and a parked playhead must not re-upload. */
240
+ function write(target: Lines, next: number[], epsilon: number): void {
241
+ const attribute = target.geometry.getAttribute("position") as BufferAttribute;
242
+ const buffer = attribute.array as Float32Array;
243
+ if (segmentsMoved(buffer, next, epsilon)) {
244
+ buffer.set(next);
245
+ attribute.needsUpdate = true;
246
+ }
247
+ }
248
+
249
+ function tintTo(target: Lines, want: number, hot: ReturnType<typeof tokenColor>): void {
250
+ if (target.tint === want) return;
251
+ target.tint = want;
252
+ target.material.color.copy(tokenColor("--frustum", FRUSTUM_FALLBACK)).lerp(hot, want);
253
+ }
254
+
255
+ function newPoints(n: number): Vector3[] {
256
+ return Array.from({ length: n }, () => new Vector3());
85
257
  }
86
258
 
87
- export function FrustumGizmo({ feed, panes, rig, frame }: FrustumGizmoProps) {
88
- const gizmo = useMemo(() => {
259
+ export function FrustumGizmo({
260
+ theme,
261
+ feed,
262
+ panes,
263
+ rigAt,
264
+ frame,
265
+ waypointsAt,
266
+ driverAt,
267
+ onDriver,
268
+ }: FrustumGizmoProps) {
269
+ /** The last driver name pushed, so the relay fires on change only. */
270
+ const pushedDriver = useRef<string | null>(null);
271
+ // The camera the camera pane renders with. On the rig it is where `rigAt`
272
+ // says; a component that drives it moves it off the rig, and then THIS is
273
+ // the camera the picture came from.
274
+ const renderCamera = useThree((s) => s.camera);
275
+ const scene = useThree((s) => s.scene);
276
+ const hotColor = useMemo(
277
+ () => tokenColor("--gizmo-hot", theme === "light" ? "#1c1c1c" : "#ffffff"),
278
+ [theme],
279
+ );
280
+ const body = useMemo(() => newLines(CAMERA_BODY_SEGMENT_FLOATS, 1), [theme]);
281
+ const reach = useMemo(() => newLines(FRAME_SEGMENT_FLOATS, REACH_OPACITY), [theme]);
282
+ // The hit-test geometry, kept as three vectors so the screen test allocates
283
+ // nothing: the twelve body points (lens rectangle, then the box) and the
284
+ // framed rectangle's four corners.
285
+ const hit = useMemo(() => ({ body: newPoints(12), quad: newPoints(4) }), []);
286
+
287
+ // ── THE WAYPOINT MARKS ────────────────────────────────────────────────────
288
+ //
289
+ // A SECOND object rather than more segments in the body's buffer, and the
290
+ // reason is the count: the body has 23 segments always, so its buffer is
291
+ // allocated once and rewritten in place. A path has two to a dozen
292
+ // waypoints, and folding them in would mean reallocating the body's buffer
293
+ // whenever an author inserted a pose. Separate objects, one allocation each.
294
+ const marks = useMemo(() => {
89
295
  const geometry = new BufferGeometry();
90
- geometry.setAttribute(
91
- "position",
92
- new BufferAttribute(new Float32Array(FRUSTUM_SEGMENT_FLOATS), 3),
93
- );
94
296
  const material = new LineBasicNodeMaterial();
95
297
  material.color.copy(tokenColor("--frustum", FRUSTUM_FALLBACK));
96
298
  material.depthTest = false;
97
299
  material.depthWrite = false;
98
300
  material.toneMapped = false;
99
- // See the header: the working pane renders under a clay override.
100
301
  material.allowOverride = false;
101
-
102
302
  const lines = new LineSegments(geometry, material);
103
303
  lines.layers.set(EDITOR_LAYER);
104
304
  lines.renderOrder = FRUSTUM_RENDER_ORDER;
105
- // The vertices are WORLD positions rewritten in place, so the object stays
106
- // at the origin and the bounding sphere the culler would test is never the
107
- // one the buffer holds. Culling it against a stale sphere makes the gizmo
108
- // vanish at some orbits and not others.
109
305
  lines.frustumCulled = false;
110
306
  lines.matrixAutoUpdate = false;
111
- return { lines, geometry, material, apex: new Vector3(), quad: newQuad() };
112
- }, []);
307
+ lines.visible = false;
308
+ return { lines, geometry, material, eyes: [] as Vector3[], sizes: [] as number[], count: -1, tint: 0 };
309
+ }, [theme]);
113
310
 
114
311
  useEffect(
115
312
  () => () => {
116
- gizmo.geometry.dispose();
117
- gizmo.material.dispose();
313
+ body.geometry.dispose();
314
+ body.material.dispose();
315
+ reach.geometry.dispose();
316
+ reach.material.dispose();
317
+ marks.geometry.dispose();
318
+ marks.material.dispose();
118
319
  },
119
- [gizmo],
320
+ [body, reach, marks],
120
321
  );
121
322
 
323
+ /**
324
+ * The extent of a DRIVEN camera's world: the largest axis of the live
325
+ * content's bounds, re-walked when the graph's membership changes
326
+ * (`feed.sceneEpoch`) or every `EXTENT_REFRESH_FRAMES`, whichever comes
327
+ * first. NaN when the walk found nothing, and the caller falls back to the
328
+ * frame.
329
+ *
330
+ * ponytail: a full traverse per refresh. A component that streams thousands
331
+ * of separate meshes pays it every 60 frames; a declared extent from the
332
+ * component (the camera claim) is the upgrade and skips the walk entirely.
333
+ */
334
+ const extentCache = useRef({ epoch: -1, frames: 0, value: Number.NaN });
335
+ const drivenExtent = (): number => {
336
+ const cache = extentCache.current;
337
+ if (cache.epoch !== feed.sceneEpoch || cache.frames >= EXTENT_REFRESH_FRAMES) {
338
+ cache.epoch = feed.sceneEpoch;
339
+ cache.frames = 0;
340
+ cache.value = sceneExtent(scene, _bounds);
341
+ }
342
+ cache.frames += 1;
343
+ return cache.value;
344
+ };
345
+ const rigExtent = Math.max(frame.width, frame.height);
346
+
122
347
  useFrame(() => {
348
+ const list = waypointsAt()?.eyes ?? [];
349
+ if (list.length === 0) {
350
+ marks.lines.visible = false;
351
+ feed.waypointHit = null;
352
+ return;
353
+ }
354
+ marks.lines.visible = true;
355
+ const want = feed.hoverWaypoint === null ? 0 : HOVER_TINT;
356
+ if (marks.tint !== want) {
357
+ marks.tint = want;
358
+ marks.material.color.copy(tokenColor("--frustum", FRUSTUM_FALLBACK)).lerp(hotColor, want);
359
+ }
360
+ // REALLOCATE ONLY WHEN THE COUNT CHANGES, which is when a waypoint is
361
+ // inserted or removed. A drag moves vertices inside a buffer that is
362
+ // already the right size.
363
+ if (marks.count !== list.length) {
364
+ marks.count = list.length;
365
+ // ── DISPOSE, THEN REBUILD ──────────────────────────────────────────────
366
+ //
367
+ // `setAttribute` alone ORPHANS the outgoing attribute's GPU buffer: three
368
+ // frees an attribute only from the geometry's own dispose listener, which
369
+ // walks the attributes still ATTACHED at that moment. Replacing one in
370
+ // place leaves the old buffer alive with nothing pointing at it, and
371
+ // nothing draws wrong — the only symptom is `renderer.info.memory
372
+ // .attributes` climbing every time the playhead crosses between two path
373
+ // moves with different waypoint counts.
374
+ marks.geometry.dispose();
375
+ marks.geometry = new BufferGeometry();
376
+ marks.geometry.setAttribute(
377
+ "position",
378
+ new BufferAttribute(new Float32Array(waypointSegmentFloats(list.length)), 3),
379
+ );
380
+ marks.lines.geometry = marks.geometry;
381
+ marks.eyes = list.map(() => new Vector3());
382
+ marks.sizes = list.map(() => 0);
383
+ }
384
+ // The hit-test points follow every frame regardless of the redraw gate,
385
+ // the same rule the body follows and for the same reason — and each cross
386
+ // is sized against its OWN screen depth, so a path that runs towards the
387
+ // author does not end in a mark too small to grab.
388
+ const camera = feed.workingCamera;
123
389
  const working = panes.working;
390
+ let epsilon = 0.5;
391
+ for (let i = 0; i < list.length; i++) {
392
+ const eye = marks.eyes[i]!.set(list[i]![0], list[i]![1], list[i]![2]);
393
+ const wpp = camera === null || working === null ? 0 : worldPerPixel(camera, eye, working.height);
394
+ marks.sizes[i] = waypointCrossSize(rigExtent, wpp);
395
+ if (i === 0 && wpp > 0) epsilon = 0.5 * wpp;
396
+ }
397
+ const next = waypointSegments(list, marks.sizes);
398
+ const attribute = marks.geometry.getAttribute("position") as BufferAttribute;
399
+ const buffer = attribute.array as Float32Array;
400
+ if (segmentsMoved(buffer, next, epsilon)) {
401
+ buffer.set(next);
402
+ attribute.needsUpdate = true;
403
+ }
404
+
405
+ feed.waypointHit = (x, y) => {
406
+ if (camera === null || working === null) return null;
407
+ // NEAREST WINS, and it has to: a path that doubles back puts two eyes
408
+ // within a grab radius of each other, and answering with whichever came
409
+ // first in the list would move the one behind the pointer.
410
+ let best: number | null = null;
411
+ let bestDistance = WAYPOINT_GRAB_PX;
412
+ for (let i = 0; i < marks.eyes.length; i++) {
413
+ const eye = marks.eyes[i]!;
414
+ // A waypoint BEHIND the working camera projects to garbage, so it is
415
+ // not on screen to be grabbed.
416
+ _view.copy(eye).applyMatrix4(camera.matrixWorldInverse);
417
+ if (_view.z >= 0) continue;
418
+ const [px, py] = toScreen(eye, camera, working.width, working.height);
419
+ const distance = Math.hypot(px - x, py - y);
420
+ if (distance <= bestDistance) {
421
+ best = i;
422
+ bestDistance = distance;
423
+ }
424
+ }
425
+ return best;
426
+ };
427
+ }, 0);
428
+
429
+ useEffect(
430
+ () => () => {
431
+ feed.waypointHit = null;
432
+ feed.hoverWaypoint = null;
433
+ },
434
+ [feed],
435
+ );
436
+
437
+ useFrame(() => {
438
+ const working = panes.working;
439
+ const rig = rigAt();
124
440
  if (rig === null) {
125
- gizmo.lines.visible = false;
441
+ body.lines.visible = false;
442
+ reach.lines.visible = false;
126
443
  feed.frustumHit = null;
444
+ feed.cameraDriven = false;
445
+ feed.cameraWorld = null;
446
+ if (pushedDriver.current !== null) {
447
+ pushedDriver.current = null;
448
+ onDriver(null);
449
+ }
127
450
  return;
128
451
  }
129
- gizmo.lines.visible = true;
452
+ const camera = feed.workingCamera;
130
453
 
131
- const shape = frustumShape(rig, frame);
132
- const next = frustumSegments(shape);
133
- const attribute = gizmo.geometry.getAttribute("position") as BufferAttribute;
134
- const buffer = attribute.array as Float32Array;
135
- // The 0.5px gate `quadMoved` uses, for the same reason: this runs on every
136
- // committed frame, and a parked playhead must not re-upload a buffer.
137
- if (segmentsMoved(buffer, next)) {
138
- buffer.set(next);
139
- attribute.needsUpdate = true;
454
+ // ── IT LIGHTS UP UNDER THE CURSOR, LIKE THE MOVE ARMS ─────────────────
455
+ //
456
+ // Off a plain field the pointer lane wrote (`feed.hoverFrustum`), read
457
+ // here in the frame loop: one uniform write per change and zero React
458
+ // renders, the same trade `feed.hoverArm` makes. Both grips pan, so both
459
+ // buffers light together: one tint is the honest one.
460
+ const want = feed.hoverFrustum === null ? 0 : HOVER_TINT;
461
+ tintTo(body, want, hotColor);
462
+ tintTo(reach, want, hotColor);
463
+
464
+ // ── WHOSE CAMERA ───────────────────────────────────────────────────────
465
+ //
466
+ // The rig's, unless a component drives the render camera: one that has
467
+ // CLAIMED it (by name, with its world's extent), or one the float
468
+ // comparison catches writing it off the rig this frame (a flythrough
469
+ // writes eye, target and lens itself). Then the body follows the camera
470
+ // the pane renders with, sized against the component's own world, and
471
+ // nothing is grabbable: dragging the rig would move nothing the picture
472
+ // shows.
473
+ const live = readPose(renderCamera);
474
+ const driver = driverAt();
475
+ const driven = isDriven(driver, rig, live);
476
+ feed.cameraDriven = driven;
477
+ const driverName = driver?.name ?? (driven ? "a component" : null);
478
+ if (driverName !== pushedDriver.current) {
479
+ pushedDriver.current = driverName;
480
+ onDriver(driverName);
481
+ }
482
+ const pose = driven && live !== null ? live : rigPose(rig);
483
+ // A HELD camera fades: the fork would refuse a drag on it, and the fade is
484
+ // the cue that says so before the press (the cursor and the pill say why).
485
+ const held = !driven && feed.cameraGate !== null && feed.cameraGate.kind !== "free";
486
+ fadeTo(body, held);
487
+ fadeTo(reach, held);
488
+
489
+ // The component's world: its declared extent, or the one the bounds walk
490
+ // measures; the walk runs either way for the CENTRE, which Home fits to.
491
+ let extent = rigExtent;
492
+ if (driven) {
493
+ const walked = drivenExtent();
494
+ const declared = driver?.extent;
495
+ const chosen = declared !== undefined && declared > 0 ? declared : walked;
496
+ extent = Number.isFinite(chosen) && chosen > 0 ? chosen : rigExtent;
497
+ if (!_bounds.isEmpty()) {
498
+ _bounds.getCenter(_centre);
499
+ feed.cameraWorld = { centre: [_centre.x, _centre.y, _centre.z], extent };
500
+ } else {
501
+ feed.cameraWorld = { centre: [pose.eye[0], pose.eye[1], pose.eye[2]], extent };
502
+ }
503
+ } else {
504
+ feed.cameraWorld = null;
140
505
  }
506
+ _eye.set(pose.eye[0], pose.eye[1], pose.eye[2]);
507
+ const wpp = camera === null || working === null ? 0 : worldPerPixel(camera, _eye, working.height);
508
+ const length = cameraDisplayLength(
509
+ extent,
510
+ wpp,
511
+ working === null ? 0 : working.height,
512
+ driven ? Number.POSITIVE_INFINITY : REACH_CAP_FRACTION * rig.distance,
513
+ );
514
+ // Half a SCREEN pixel at the eye, both cases: the old half design px gate
515
+ // was a whole city block for a metre-scale component.
516
+ const epsilon = wpp > 0 ? 0.5 * wpp : 0.5;
141
517
 
142
- // The hit-test geometry follows every frame regardless of that gate: half a
518
+ const shape = cameraBodyShape(pose, length, frame);
519
+ write(body, cameraBodySegments(shape), epsilon);
520
+ body.lines.visible = true;
521
+ // The hit-test geometry follows every frame regardless of the gate: half a
143
522
  // pixel of drift is invisible in the drawing and is still the difference
144
- // between grabbing the apex and missing it.
145
- gizmo.apex.set(shape.apex[0], shape.apex[1], shape.apex[2]);
146
- for (let i = 0; i < 4; i++) {
147
- gizmo.quad[i]!.set(shape.corners[i]![0], shape.corners[i]![1], shape.corners[i]![2]);
523
+ // between grabbing the body and missing it.
524
+ for (let i = 0; i < 4; i++) hit.body[i]!.set(shape.near[i]![0], shape.near[i]![1], shape.near[i]![2]);
525
+ for (let i = 0; i < 8; i++) hit.body[4 + i]!.set(shape.box[i]![0], shape.box[i]![1], shape.box[i]![2]);
526
+
527
+ if (driven) {
528
+ reach.lines.visible = false;
529
+ feed.frustumHit = null;
530
+ return;
148
531
  }
532
+ const far = frustumShape(rig, frame);
533
+ write(reach, frameSegments(far), epsilon);
534
+ reach.lines.visible = true;
535
+ for (let i = 0; i < 4; i++) hit.quad[i]!.set(far.corners[i]![0], far.corners[i]![1], far.corners[i]![2]);
149
536
 
150
537
  feed.frustumHit = (x, y) => {
151
- const camera = feed.workingCamera;
152
538
  if (camera === null || working === null) return null;
153
- // THE APEX FIRST, and in SCREEN px: it is a point, so a world-space
154
- // radius would be a grab target that shrinks as the author dollies out
155
- // until it cannot be hit at all.
156
- const [ax, ay] = toScreen(gizmo.apex, camera, working.width, working.height);
157
- if (Math.hypot(ax - x, ay - y) <= APEX_GRAB_PX) return "apex";
158
- // THE BODY BY ITS BORDER, in SCREEN px like the apex and for the same
159
- // reason. A corner behind the working camera projects to garbage, so a
160
- // quad that is not wholly in front answers null — its border is not on
161
- // screen to be grabbed, and the press falls through to the orbit.
539
+ // THE BODY FIRST, by the screen box its twelve points project to,
540
+ // inflated by the slop. A small object drawn over the layers wins over
541
+ // them (the pointer lane keeps its layer veto for the reach alone). A
542
+ // point behind the working camera projects to garbage, so a body that is
543
+ // not wholly in front is not on screen to be grabbed.
544
+ let minX = Number.POSITIVE_INFINITY;
545
+ let minY = Number.POSITIVE_INFINITY;
546
+ let maxX = Number.NEGATIVE_INFINITY;
547
+ let maxY = Number.NEGATIVE_INFINITY;
548
+ let inFront = true;
549
+ for (const point of hit.body) {
550
+ _view.copy(point).applyMatrix4(camera.matrixWorldInverse);
551
+ if (_view.z >= 0) {
552
+ inFront = false;
553
+ break;
554
+ }
555
+ const [sx, sy] = toScreen(point, camera, working.width, working.height);
556
+ if (sx < minX) minX = sx;
557
+ if (sx > maxX) maxX = sx;
558
+ if (sy < minY) minY = sy;
559
+ if (sy > maxY) maxY = sy;
560
+ }
561
+ if (
562
+ inFront &&
563
+ x >= minX - BODY_GRAB_PX &&
564
+ x <= maxX + BODY_GRAB_PX &&
565
+ y >= minY - BODY_GRAB_PX &&
566
+ y <= maxY + BODY_GRAB_PX
567
+ ) {
568
+ return "body";
569
+ }
570
+ // THE FRAMED RECTANGLE BY ITS BORDER, in SCREEN px like the body and for
571
+ // the same reason. Its interior is deliberately not a target — see
572
+ // `borderDistance` for why an interior claim takes the whole pane away.
162
573
  const corners: [number, number][] = [];
163
- for (const corner of gizmo.quad) {
574
+ for (const corner of hit.quad) {
164
575
  _view.copy(corner).applyMatrix4(camera.matrixWorldInverse);
165
576
  if (_view.z >= 0) return null;
166
577
  corners.push(toScreen(corner, camera, working.width, working.height));
167
578
  }
168
- return borderDistance(x, y, corners) <= BODY_GRAB_PX ? "body" : null;
579
+ return borderDistance(x, y, corners) <= BODY_GRAB_PX ? "frame" : null;
169
580
  };
170
- }, 0);
581
+ }, AFTER_CAMERA_WRITERS);
171
582
 
172
583
  useEffect(
173
584
  () => () => {
174
585
  feed.frustumHit = null;
586
+ feed.hoverFrustum = null;
587
+ feed.cameraDriven = false;
588
+ feed.cameraWorld = null;
589
+ if (pushedDriver.current !== null) {
590
+ pushedDriver.current = null;
591
+ onDriver(null);
592
+ }
175
593
  },
176
- [feed],
594
+ [feed, onDriver],
177
595
  );
178
596
 
179
- return <primitive object={gizmo.lines} />;
180
- }
181
-
182
- function newQuad(): [Vector3, Vector3, Vector3, Vector3] {
183
- return [new Vector3(), new Vector3(), new Vector3(), new Vector3()];
597
+ return (
598
+ <>
599
+ <primitive object={body.lines} />
600
+ <primitive object={reach.lines} />
601
+ <primitive object={marks.lines} />
602
+ </>
603
+ );
184
604
  }