@hyperframes/studio 0.8.50 → 0.8.53-desktop.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 (330) hide show
  1. package/dist/assets/{hyperframes-player-CxxDxHTW.js → hyperframes-player-PTEnAHhg.js} +3 -3
  2. package/dist/assets/{index-DUAIWxkI.js → index--myXp8ui.js} +1 -1
  3. package/dist/assets/{index-Dt3qBI-9.js → index-BjslOS9g.js} +1 -1
  4. package/dist/assets/index-BnXZuIJD.css +1 -0
  5. package/dist/assets/index-CNyviK_l.js +390 -0
  6. package/dist/index.css +29 -0
  7. package/dist/index.css.map +1 -0
  8. package/dist/index.d.ts +825 -24
  9. package/dist/index.html +2 -2
  10. package/dist/index.js +8911 -9853
  11. package/dist/index.js.map +1 -1
  12. package/dist/styles/tailwind-preset.js.map +1 -1
  13. package/package.json +13 -12
  14. package/src/App.tsx +145 -163
  15. package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
  16. package/src/captions/components/CaptionOverlay.sender.test.tsx +52 -0
  17. package/src/captions/components/CaptionOverlay.tsx +8 -4
  18. package/src/captions/components/CaptionOverlayUtils.ts +2 -3
  19. package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
  20. package/src/captions/components/CaptionTimeline.tsx +1 -1
  21. package/src/captions/components/shared.tsx +2 -2
  22. package/src/components/AskAgentModal.tsx +5 -5
  23. package/src/components/EditorShell.selectionSync.test.tsx +9 -4
  24. package/src/components/EditorShell.tsx +59 -62
  25. package/src/components/ExternalFileConflictBanner.tsx +16 -16
  26. package/src/components/LintModal.tsx +6 -16
  27. package/src/components/MediaPreview.tsx +2 -2
  28. package/src/components/RightPanelTabs.test.tsx +151 -0
  29. package/src/components/RightPanelTabs.tsx +59 -0
  30. package/src/components/SaveQueuePausedBanner.tsx +2 -2
  31. package/src/components/StudioHeader.dom.test.tsx +181 -0
  32. package/src/components/StudioHeader.test.ts +0 -12
  33. package/src/components/StudioHeader.tsx +118 -221
  34. package/src/components/StudioLeftPanels.tsx +202 -0
  35. package/src/components/StudioOverlays.tsx +1 -1
  36. package/src/components/{StudioRightPanel.tsx → StudioRightPanels.tsx} +57 -194
  37. package/src/components/{StudioRightPanel.types.ts → StudioRightPanels.types.ts} +4 -3
  38. package/src/components/StudioToast.tsx +2 -2
  39. package/src/components/TimelineHistoryButtons.dom.test.tsx +117 -0
  40. package/src/components/TimelineHistoryButtons.tsx +54 -0
  41. package/src/components/TimelineToolbar.test.tsx +8 -0
  42. package/src/components/TimelineToolbar.tsx +8 -12
  43. package/src/components/dock/Dock.test.tsx +274 -0
  44. package/src/components/dock/Dock.tsx +291 -0
  45. package/src/components/dock/dock.css +28 -0
  46. package/src/components/dock/dockAccessibility.test.ts +166 -0
  47. package/src/components/dock/dockAccessibility.ts +98 -0
  48. package/src/components/dock/dockLayout.test.ts +134 -0
  49. package/src/components/dock/dockLayout.ts +105 -0
  50. package/src/components/dock/dockLayoutSchema.ts +66 -0
  51. package/src/components/dock/dockLayoutStore.test.ts +28 -0
  52. package/src/components/dock/dockLayoutStore.ts +108 -0
  53. package/src/components/dock/dockTestHarness.ts +11 -0
  54. package/src/components/dock/panelRegistry.ts +67 -0
  55. package/src/components/editor/AnimationCard.tsx +4 -4
  56. package/src/components/editor/AnimationCardParts.tsx +6 -6
  57. package/src/components/editor/BlockParamsPanel.tsx +4 -4
  58. package/src/components/editor/BorderRadiusEditor.tsx +1 -1
  59. package/src/components/editor/CanvasContextMenu.tsx +1 -1
  60. package/src/components/editor/ComputedTweenNotice.tsx +1 -1
  61. package/src/components/editor/DomEditOverlay.tsx +1 -1
  62. package/src/components/editor/EaseCurveSection.tsx +1 -1
  63. package/src/components/editor/EaseParamFields.tsx +5 -5
  64. package/src/components/editor/FileTree.tsx +4 -4
  65. package/src/components/editor/FileTreeIcons.tsx +1 -1
  66. package/src/components/editor/FileTreeNodes.tsx +13 -13
  67. package/src/components/editor/InlineTextToolbar.tsx +1 -1
  68. package/src/components/editor/InspectorHeaderActions.tsx +3 -3
  69. package/src/components/editor/KeyframeDiamond.tsx +1 -1
  70. package/src/components/editor/KeyframeEaseList.tsx +1 -1
  71. package/src/components/editor/LayersPanel.test.ts +97 -1
  72. package/src/components/editor/LayersPanel.tsx +7 -5
  73. package/src/components/editor/MarqueeOverlay.tsx +1 -1
  74. package/src/components/editor/OffCanvasIndicators.tsx +1 -1
  75. package/src/components/editor/PreviewGuides.tsx +1 -1
  76. package/src/components/editor/PromotableControl.tsx +2 -2
  77. package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
  78. package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
  79. package/src/components/editor/SnapToolbar.test.tsx +0 -3
  80. package/src/components/editor/SnapToolbar.tsx +2 -2
  81. package/src/components/editor/TimelineFxPopover.tsx +1 -1
  82. package/src/components/editor/TopologyLens.test.tsx +28 -0
  83. package/src/components/editor/TopologyLens.tsx +7 -5
  84. package/src/components/editor/Transform3DCube.tsx +2 -2
  85. package/src/components/editor/audioFxRevealTarget.ts +1 -0
  86. package/src/components/editor/domEditing.test.ts +1 -1
  87. package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
  88. package/src/components/editor/domEditingAgentPrompt.ts +4 -3
  89. package/src/components/editor/propertyPanelColor.tsx +11 -11
  90. package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
  91. package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
  92. package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
  93. package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
  94. package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
  95. package/src/components/editor/propertyPanelCommitField.tsx +1 -1
  96. package/src/components/editor/propertyPanelFill.tsx +4 -4
  97. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
  98. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
  99. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
  100. package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
  101. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
  102. package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
  103. package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
  104. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
  105. package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
  106. package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
  107. package/src/components/editor/propertyPanelFont.tsx +5 -5
  108. package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
  109. package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
  110. package/src/components/editor/propertyPanelFxControls.tsx +6 -6
  111. package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
  112. package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
  113. package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
  114. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  115. package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
  116. package/src/components/editor/propertyPanelSections.tsx +8 -8
  117. package/src/components/editor/useColorGradingController.test.ts +38 -0
  118. package/src/components/editor/useColorGradingController.ts +9 -1
  119. package/src/components/editor/useColorGradingPreviews.test.ts +76 -0
  120. package/src/components/editor/useColorGradingPreviews.ts +11 -1
  121. package/src/components/editor/usePreviewCompositionRect.test.tsx +95 -0
  122. package/src/components/editor/usePreviewCompositionRect.ts +21 -0
  123. package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
  124. package/src/components/feedback/feedbackTrigger.ts +0 -1
  125. package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
  126. package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
  127. package/src/components/nle/NLEContext.tsx +0 -43
  128. package/src/components/nle/NLEPreview.tsx +3 -3
  129. package/src/components/nle/PreviewOverlays.tsx +6 -6
  130. package/src/components/nle/PreviewPane.tsx +3 -3
  131. package/src/components/nle/TimelinePane.tsx +53 -67
  132. package/src/components/panels/SlideshowPanel.tsx +3 -3
  133. package/src/components/panels/SlideshowSubPanels.tsx +20 -17
  134. package/src/components/panels/VariablesBindElement.tsx +2 -2
  135. package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
  136. package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
  137. package/src/components/panels/VariablesPanel.tsx +4 -4
  138. package/src/components/panels/VariablesValueControls.tsx +2 -2
  139. package/src/components/pointerTargetSize.test.tsx +8 -0
  140. package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
  141. package/src/components/renders/RenderQueue.test.tsx +7 -0
  142. package/src/components/renders/RenderQueue.tsx +7 -7
  143. package/src/components/renders/RenderQueueItem.tsx +6 -6
  144. package/src/components/renders/RenderQueuePanel.tsx +1 -1
  145. package/src/components/sidebar/AssetCard.tsx +8 -8
  146. package/src/components/sidebar/AssetContextMenu.tsx +8 -8
  147. package/src/components/sidebar/AssetsTab.tsx +2 -2
  148. package/src/components/sidebar/AudioRow.tsx +6 -6
  149. package/src/components/sidebar/BlocksTab.tsx +6 -6
  150. package/src/components/sidebar/{LeftSidebar.masterComposition.test.tsx → CompositionsPanel.masterComposition.test.tsx} +6 -9
  151. package/src/components/sidebar/CompositionsPanel.tsx +15 -0
  152. package/src/components/sidebar/CompositionsTab.tsx +5 -5
  153. package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
  154. package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
  155. package/src/components/sidebar/SidebarLintButton.test.tsx +53 -0
  156. package/src/components/sidebar/SidebarLintButton.tsx +50 -0
  157. package/src/components/timelineToolbarStyles.ts +5 -0
  158. package/src/components/ui/Button.test.tsx +252 -0
  159. package/src/components/ui/Button.tsx +89 -126
  160. package/src/components/ui/IconButton.tsx +38 -0
  161. package/src/components/ui/Input.tsx +115 -0
  162. package/src/components/ui/Menu.test.tsx +374 -0
  163. package/src/components/ui/Menu.tsx +213 -0
  164. package/src/components/ui/NumberField.tsx +122 -0
  165. package/src/components/ui/Popover.tsx +74 -0
  166. package/src/components/ui/SearchInput.tsx +2 -2
  167. package/src/components/ui/Select.tsx +100 -0
  168. package/src/components/ui/Slider.tsx +153 -0
  169. package/src/components/ui/Tabs.test.tsx +98 -0
  170. package/src/components/ui/Tabs.tsx +71 -0
  171. package/src/components/ui/Toggle.tsx +63 -0
  172. package/src/components/ui/Tooltip.test.tsx +110 -0
  173. package/src/components/ui/Tooltip.tsx +38 -112
  174. package/src/components/ui/cn.test.ts +30 -0
  175. package/src/components/ui/cn.ts +25 -0
  176. package/src/components/ui/index.ts +29 -2
  177. package/src/components/ui/mountHost.testHelpers.ts +24 -0
  178. package/src/components/ui/valueControls.test.tsx +524 -0
  179. package/src/contexts/PanelLayoutContext.tsx +3 -57
  180. package/src/hooks/appHotkeysDispatch.ts +3 -4
  181. package/src/hooks/timelineEditPermission.ts +31 -0
  182. package/src/hooks/useAppHotkeys.previewForwarding.test.tsx +0 -2
  183. package/src/hooks/useAppHotkeys.test.ts +0 -1
  184. package/src/hooks/useAppHotkeys.textEditing.test.tsx +0 -3
  185. package/src/hooks/useAppHotkeys.ts +21 -84
  186. package/src/hooks/useCaptionDetection.sender.test.tsx +46 -0
  187. package/src/hooks/useCaptionDetection.ts +3 -1
  188. package/src/hooks/useDomEditPreviewSync.ts +4 -6
  189. package/src/hooks/useDomEditSession.ts +0 -7
  190. package/src/hooks/useDomEditWiring.ts +4 -9
  191. package/src/hooks/useEditHistoryActions.test.tsx +90 -0
  192. package/src/hooks/useEditHistoryActions.ts +106 -0
  193. package/src/hooks/useFileManager.ts +21 -123
  194. package/src/hooks/useLintModal.test.tsx +65 -0
  195. package/src/hooks/useLintModal.ts +3 -0
  196. package/src/hooks/usePanelLayout.test.ts +108 -296
  197. package/src/hooks/usePanelLayout.ts +50 -255
  198. package/src/hooks/usePersistentEditHistory.ts +1 -1
  199. package/src/hooks/useProjectFileWriter.ts +150 -0
  200. package/src/hooks/useRightPanelIntents.test.ts +181 -0
  201. package/src/hooks/useRightPanelIntents.ts +63 -0
  202. package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
  203. package/src/hooks/useSdkSession.ts +43 -10
  204. package/src/hooks/useServerConnection.hashProject.test.tsx +94 -0
  205. package/src/hooks/useServerConnection.ts +43 -10
  206. package/src/hooks/useSlideshowTabState.test.ts +9 -31
  207. package/src/hooks/useSlideshowTabState.ts +6 -19
  208. package/src/hooks/useStudioContextValue.test.ts +6 -18
  209. package/src/hooks/useStudioContextValue.ts +5 -26
  210. package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
  211. package/src/hooks/useTimelineAssetDropOps.ts +52 -11
  212. package/src/hooks/useTimelineDeleteOps.test.tsx +111 -0
  213. package/src/hooks/useTimelineEditing.test.tsx +322 -3
  214. package/src/hooks/useTimelineEditing.ts +103 -16
  215. package/src/hooks/useTimelineEditingTypes.ts +3 -0
  216. package/src/hooks/useTrackPendingTimelineEdit.test.tsx +62 -0
  217. package/src/hooks/useTrackPendingTimelineEdit.ts +22 -0
  218. package/src/hostMount.test.tsx +222 -0
  219. package/src/index.ts +87 -0
  220. package/src/main.tsx +9 -3
  221. package/src/player/components/AudioWaveform.tsx +1 -1
  222. package/src/player/components/AutomationSelectionMenu.tsx +3 -3
  223. package/src/player/components/ClipContextMenu.tsx +3 -3
  224. package/src/player/components/CompositionThumbnail.test.ts +0 -3
  225. package/src/player/components/CompositionThumbnail.tsx +3 -4
  226. package/src/player/components/EditModal.tsx +1 -1
  227. package/src/player/components/ImageThumbnail.tsx +1 -1
  228. package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
  229. package/src/player/components/LayerDisclosureRow.tsx +2 -2
  230. package/src/player/components/PlayerControls.tsx +1 -1
  231. package/src/player/components/ShortcutsPanel.tsx +9 -9
  232. package/src/player/components/SpeedMenu.tsx +3 -5
  233. package/src/player/components/Timeline.tsx +3 -2
  234. package/src/player/components/TimelineCanvas.tsx +21 -14
  235. package/src/player/components/TimelineClip.tsx +0 -1
  236. package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
  237. package/src/player/components/TimelineEmptyState.tsx +5 -5
  238. package/src/player/components/TimelineFxButton.tsx +6 -6
  239. package/src/player/components/TimelineGroupHeader.tsx +5 -5
  240. package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
  241. package/src/player/components/TimelineGroupRow.tsx +1 -1
  242. package/src/player/components/TimelineLanes.test.tsx +32 -0
  243. package/src/player/components/TimelineLanes.tsx +2 -5
  244. package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
  245. package/src/player/components/TimelineRuler.tsx +1 -1
  246. package/src/player/components/TimelineShortcutHint.tsx +1 -1
  247. package/src/player/components/TimelineTrackHeader.test.tsx +26 -5
  248. package/src/player/components/TimelineTrackHeader.tsx +2 -2
  249. package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
  250. package/src/player/components/TimelineTypes.ts +2 -1
  251. package/src/player/components/TrackGapContextMenu.tsx +1 -1
  252. package/src/player/components/VideoThumbnail.tsx +4 -2
  253. package/src/player/components/VolumeControl.tsx +2 -2
  254. package/src/player/components/timelineCallbacks.ts +8 -8
  255. package/src/player/components/timelineClipDragPreview.ts +51 -27
  256. package/src/player/components/timelineClipDragTypes.ts +3 -0
  257. package/src/player/components/timelineDragDrop.test.tsx +61 -1
  258. package/src/player/components/timelineDragDrop.ts +57 -15
  259. package/src/player/components/timelineLaneProps.ts +3 -0
  260. package/src/player/components/timelineSnapping.test.ts +27 -0
  261. package/src/player/components/timelineSnapping.ts +14 -1
  262. package/src/player/components/timelineTheme.test.ts +4 -4
  263. package/src/player/components/timelineTheme.ts +30 -30
  264. package/src/player/components/trackHeaderLabelRows.tsx +4 -4
  265. package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
  266. package/src/player/components/useTimelineClipDrag.ts +30 -3
  267. package/src/player/lib/describeClips.ts +3 -54
  268. package/src/player/lib/playbackShortcuts.ts +2 -0
  269. package/src/player/lib/timelinePerformanceDiagnostics.test.ts +3 -2
  270. package/src/player/lib/timelinePerformanceDiagnostics.ts +1 -1
  271. package/src/player/store/previewIframeStore.notify.test.tsx +75 -0
  272. package/src/player/store/previewIframeStore.test.tsx +29 -0
  273. package/src/player/store/previewIframeStore.ts +25 -0
  274. package/src/renderQueueExports.test.tsx +58 -0
  275. package/src/styles/classCandidates.test.ts +105 -0
  276. package/src/styles/classCandidates.ts +281 -0
  277. package/src/styles/hex-baseline.json +84 -0
  278. package/src/styles/hexRatchet.test.ts +201 -0
  279. package/src/styles/iconTokens.ts +20 -0
  280. package/src/styles/palette-reset.css +6 -0
  281. package/src/styles/studio.css +24 -21
  282. package/src/styles/styleSources.test.ts +20 -0
  283. package/src/styles/styleSources.ts +47 -0
  284. package/src/styles/tailwind-preset.shared.js +3 -0
  285. package/src/styles/theme.css +270 -0
  286. package/src/styles/theme.test.ts +183 -0
  287. package/src/styles/tokenGate.test.ts +109 -0
  288. package/src/utils/sourcePatcher.ts +1 -1
  289. package/src/utils/studioHelpers.test.ts +18 -0
  290. package/src/utils/studioHelpers.ts +11 -8
  291. package/src/utils/studioUiPreferences.test.ts +10 -16
  292. package/src/utils/studioUiPreferences.ts +7 -16
  293. package/src/utils/timelineDropTrackInsert.test.ts +134 -0
  294. package/src/utils/timelineDropTrackInsert.ts +83 -0
  295. package/src/utils/typingTarget.ts +4 -0
  296. package/dist/assets/index-n2ml4tUn.js +0 -442
  297. package/dist/assets/index-pO6p0H27.css +0 -1
  298. package/src/components/PanelTabButton.tsx +0 -31
  299. package/src/components/StudioLeftSidebar.tsx +0 -204
  300. package/src/components/nle/TimelineResizeDivider.tsx +0 -101
  301. package/src/components/sidebar/LeftSidebar.storage.test.ts +0 -28
  302. package/src/components/sidebar/LeftSidebar.tsx +0 -334
  303. package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
  304. package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
  305. package/src/components/storyboard/FramePoster.test.tsx +0 -61
  306. package/src/components/storyboard/FramePoster.tsx +0 -86
  307. package/src/components/storyboard/StoryboardDirection.tsx +0 -45
  308. package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
  309. package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
  310. package/src/components/storyboard/StoryboardGrid.tsx +0 -55
  311. package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
  312. package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
  313. package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
  314. package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
  315. package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
  316. package/src/components/storyboard/StoryboardView.tsx +0 -169
  317. package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
  318. package/src/components/storyboard/frameComments.test.ts +0 -95
  319. package/src/components/storyboard/frameComments.ts +0 -115
  320. package/src/components/storyboard/frameStatus.ts +0 -36
  321. package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
  322. package/src/components/storyboard/storyboardReviewStage.ts +0 -45
  323. package/src/components/storyboard/useFrameComments.ts +0 -98
  324. package/src/contexts/ViewModeContext.tsx +0 -142
  325. package/src/hooks/useInspectorSplitResize.ts +0 -51
  326. package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
  327. package/src/hooks/useProjectSignaturePoll.ts +0 -68
  328. package/src/hooks/useStoryboard.ts +0 -99
  329. package/src/utils/fitPanels.test.ts +0 -164
  330. package/src/utils/fitPanels.ts +0 -151
@@ -0,0 +1,108 @@
1
+ import { create } from "zustand";
2
+ import {
3
+ PANEL_DEFINITIONS,
4
+ PANEL_IDS,
5
+ panelsInZone,
6
+ type PanelId,
7
+ type PanelZone,
8
+ } from "./panelRegistry";
9
+
10
+ /** The narrow surface Dock.Root exposes; dockview types never leave the dock folder. */
11
+ export interface DockController {
12
+ /** Adds the panel at its default place if it is closed; never moves focus. */
13
+ open: (id: PanelId) => void;
14
+ /** Focuses the panel, reopening it at its default place if it was closed. */
15
+ activate: (id: PanelId) => void;
16
+ setTitle: (id: PanelId, title: string) => void;
17
+ close: (id: PanelId) => void;
18
+ /** Hides or shows the panel's whole tab group without closing it. */
19
+ setGroupVisible: (id: PanelId, visible: boolean) => void;
20
+ reset: () => void;
21
+ }
22
+
23
+ export interface DockSnapshot {
24
+ openPanels: ReadonlySet<PanelId>;
25
+ /** Open panels whose tab is showing and whose group is not hidden. */
26
+ visiblePanels: ReadonlySet<PanelId>;
27
+ activePanel: PanelId | null;
28
+ /** The tab each group shows, so a restored layout still records what every side column last showed. */
29
+ groupActivePanels?: readonly PanelId[];
30
+ }
31
+
32
+ type LastActive = Partial<Record<PanelZone, PanelId>>;
33
+
34
+ interface DockLayoutState extends DockSnapshot {
35
+ controller: DockController | null;
36
+ lastActive: LastActive;
37
+ /** An activation requested before the dock mounted; Dock.Root applies it on ready. */
38
+ pendingActivation: PanelId | null;
39
+ attach: (controller: DockController) => void;
40
+ detach: () => void;
41
+ sync: (snapshot: DockSnapshot) => void;
42
+ takePendingActivation: () => PanelId | null;
43
+ activatePanel: (id: PanelId) => void;
44
+ closePanel: (id: PanelId) => void;
45
+ togglePanel: (id: PanelId) => void;
46
+ setZoneVisible: (zone: PanelZone, visible: boolean) => void;
47
+ resetLayout: () => void;
48
+ }
49
+
50
+ export const useDockLayoutStore = create<DockLayoutState>((set, get) => ({
51
+ controller: null,
52
+ openPanels: new Set(PANEL_IDS),
53
+ visiblePanels: new Set(PANEL_IDS),
54
+ activePanel: null,
55
+ lastActive: {},
56
+ pendingActivation: null,
57
+ attach: (controller) => set({ controller }),
58
+ detach: () => set({ controller: null, lastActive: {} }),
59
+ sync: (snapshot) =>
60
+ set((state) => {
61
+ const lastActive = { ...state.lastActive };
62
+ for (const id of snapshot.groupActivePanels ?? []) {
63
+ const zone = PANEL_DEFINITIONS[id].zone;
64
+ if (zone !== "center") lastActive[zone] = id;
65
+ }
66
+ const { activePanel } = snapshot;
67
+ if (activePanel) lastActive[PANEL_DEFINITIONS[activePanel].zone] = activePanel;
68
+ return { ...snapshot, lastActive };
69
+ }),
70
+ takePendingActivation: () => {
71
+ const { pendingActivation } = get();
72
+ set({ pendingActivation: null });
73
+ return pendingActivation;
74
+ },
75
+ activatePanel: (id) => {
76
+ const { controller } = get();
77
+ if (!controller) {
78
+ set({ pendingActivation: id });
79
+ return;
80
+ }
81
+ controller.setGroupVisible(id, true);
82
+ controller.activate(id);
83
+ },
84
+ closePanel: (id) => get().controller?.close(id),
85
+ togglePanel: (id) => {
86
+ const { openPanels, activatePanel, closePanel } = get();
87
+ if (openPanels.has(id)) closePanel(id);
88
+ else activatePanel(id);
89
+ },
90
+ setZoneVisible: (zone, visible) => {
91
+ const { controller, openPanels } = get();
92
+ for (const id of panelsInZone(zone)) {
93
+ if (openPanels.has(id)) controller?.setGroupVisible(id, visible);
94
+ }
95
+ },
96
+ resetLayout: () => get().controller?.reset(),
97
+ }));
98
+
99
+ /** The panel of `zone` the user is looking at: the last one focused if still showing, else any showing. */
100
+ export function visiblePanelInZone(
101
+ zone: PanelZone,
102
+ lastActive: LastActive,
103
+ visiblePanels: ReadonlySet<PanelId>,
104
+ ): PanelId | null {
105
+ const last = lastActive[zone];
106
+ if (last && visiblePanels.has(last)) return last;
107
+ return panelsInZone(zone).find((id) => visiblePanels.has(id)) ?? null;
108
+ }
@@ -0,0 +1,11 @@
1
+ import { createDockview, type DockviewApi } from "dockview-react";
2
+
3
+ /** A real dockview mounted on a fresh host element, with empty panel bodies. */
4
+ export function mountBareDockview(): { host: HTMLElement; api: DockviewApi } {
5
+ const host = document.createElement("div");
6
+ document.body.append(host);
7
+ const api = createDockview(host, {
8
+ createComponent: () => ({ element: document.createElement("div"), init() {}, dispose() {} }),
9
+ });
10
+ return { host, api };
11
+ }
@@ -0,0 +1,67 @@
1
+ import type { Direction } from "dockview-react";
2
+
3
+ export const PANEL_IDS = [
4
+ "preview",
5
+ "timeline",
6
+ "compositions",
7
+ "assets",
8
+ "code",
9
+ "catalog",
10
+ "design",
11
+ "layers",
12
+ "renders",
13
+ "variables",
14
+ "slideshow",
15
+ ] as const;
16
+
17
+ export type PanelId = (typeof PANEL_IDS)[number];
18
+ export type PanelZone = "left" | "center" | "right";
19
+
20
+ export interface PanelDefinition {
21
+ title: string;
22
+ zone: PanelZone;
23
+ /** Where Window > <panel> puts it when it has no saved place: next to `near`. */
24
+ reopen: { near: PanelId; direction: Direction };
25
+ /** Content stays mounted while its tab is hidden (the preview iframe must not reload). */
26
+ keepMounted?: true;
27
+ }
28
+
29
+ export const PANEL_DEFINITIONS = {
30
+ preview: {
31
+ title: "Preview",
32
+ zone: "center",
33
+ reopen: { near: "timeline", direction: "above" },
34
+ keepMounted: true,
35
+ },
36
+ timeline: {
37
+ title: "Timeline",
38
+ zone: "center",
39
+ reopen: { near: "preview", direction: "below" },
40
+ keepMounted: true,
41
+ },
42
+ compositions: {
43
+ title: "Compositions",
44
+ zone: "left",
45
+ reopen: { near: "preview", direction: "left" },
46
+ },
47
+ assets: { title: "Assets", zone: "left", reopen: { near: "compositions", direction: "within" } },
48
+ code: { title: "Code", zone: "left", reopen: { near: "compositions", direction: "within" } },
49
+ catalog: {
50
+ title: "Catalog",
51
+ zone: "left",
52
+ reopen: { near: "compositions", direction: "within" },
53
+ },
54
+ design: { title: "Design", zone: "right", reopen: { near: "preview", direction: "right" } },
55
+ layers: { title: "Layers", zone: "right", reopen: { near: "design", direction: "within" } },
56
+ renders: { title: "Renders", zone: "right", reopen: { near: "design", direction: "within" } },
57
+ variables: { title: "Variables", zone: "right", reopen: { near: "design", direction: "within" } },
58
+ slideshow: { title: "Slideshow", zone: "right", reopen: { near: "design", direction: "within" } },
59
+ } as const satisfies Record<PanelId, PanelDefinition>;
60
+
61
+ export function isPanelId(value: unknown): value is PanelId {
62
+ return typeof value === "string" && (PANEL_IDS as readonly string[]).includes(value);
63
+ }
64
+
65
+ export function panelsInZone(zone: PanelZone): PanelId[] {
66
+ return PANEL_IDS.filter((id) => PANEL_DEFINITIONS[id].zone === zone);
67
+ }
@@ -172,7 +172,7 @@ export const AnimationCard = memo(function AnimationCard({
172
172
  return (
173
173
  <div className="border-b border-neutral-800 pb-2">
174
174
  <div className="flex items-center gap-2 py-1.5">
175
- <span className="rounded bg-neutral-800 px-1.5 py-0.5 text-[10px] font-medium text-neutral-400">
175
+ <span className="rounded-sm bg-neutral-800 px-1.5 py-0.5 text-[10px] font-medium text-neutral-400">
176
176
  Position
177
177
  </span>
178
178
  <span className="text-[11px] text-neutral-500">
@@ -201,7 +201,7 @@ export const AnimationCard = memo(function AnimationCard({
201
201
  className="flex w-full items-center gap-2 py-1.5 active:scale-[0.99]"
202
202
  >
203
203
  <span
204
- className="rounded bg-panel-accent/10 px-1.5 py-0.5 text-[10px] font-semibold text-panel-accent"
204
+ className="rounded-sm bg-panel-accent/10 px-1.5 py-0.5 text-[10px] font-semibold text-panel-accent"
205
205
  title={METHOD_TOOLTIPS[animation.method]}
206
206
  >
207
207
  {methodLabel}
@@ -226,7 +226,7 @@ export const AnimationCard = memo(function AnimationCard({
226
226
  height="10"
227
227
  viewBox="0 0 10 10"
228
228
  fill="currentColor"
229
- className={`flex-shrink-0 transition-transform ${flat ? "text-panel-text-5" : "text-neutral-500"} ${expanded ? "" : "-rotate-90"}`}
229
+ className={`shrink-0 transition-transform ${flat ? "text-panel-text-5" : "text-neutral-500"} ${expanded ? "" : "-rotate-90"}`}
230
230
  >
231
231
  <path d="M2 3l3 4 3-4z" />
232
232
  </svg>
@@ -262,7 +262,7 @@ export const AnimationCard = memo(function AnimationCard({
262
262
  setCopied(true);
263
263
  setTimeout(() => setCopied(false), 1500);
264
264
  }}
265
- className="flex-shrink-0 rounded px-1.5 py-0.5 text-[9px] font-medium text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
265
+ className="shrink-0 rounded-sm px-1.5 py-0.5 text-[9px] font-medium text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
266
266
  title="Copy description to clipboard — paste into agent prompts"
267
267
  >
268
268
  {copied ? "Copied" : "Copy"}
@@ -46,7 +46,7 @@ function RemoveButton({ onClick, title }: { onClick: () => void; title: string }
46
46
  <button
47
47
  type="button"
48
48
  onClick={onClick}
49
- className="relative flex-shrink-0 rounded p-1.5 text-neutral-600 transition-colors hover:bg-neutral-800 hover:text-red-400 active:scale-[0.95]"
49
+ className="relative shrink-0 rounded-sm p-1.5 text-neutral-600 transition-colors hover:bg-neutral-800 hover:text-red-400 active:scale-[0.95]"
50
50
  title={title}
51
51
  aria-label={title}
52
52
  >
@@ -91,7 +91,7 @@ export function PropertyRow({
91
91
  role="switch"
92
92
  aria-checked={isVisible}
93
93
  onClick={() => onCommit(isVisible ? "hidden" : "visible")}
94
- className="flex-shrink-0 rounded-full transition-colors duration-200 relative"
94
+ className="shrink-0 rounded-full transition-colors duration-200 relative"
95
95
  style={{ width: 28, height: 16, background: isVisible ? P.accent : P.borderInput }}
96
96
  title={isVisible ? "Visible — click to hide" : "Hidden — click to show"}
97
97
  >
@@ -117,13 +117,13 @@ export function PropertyRow({
117
117
  <div className="flex flex-col gap-1">
118
118
  <div className="flex items-center gap-1">
119
119
  <div className="min-w-0 flex-1 flex items-center gap-2 px-2 py-1 rounded-lg bg-neutral-900 border border-neutral-800">
120
- <span className="flex-shrink-0 text-[11px] font-medium text-neutral-500">
120
+ <span className="shrink-0 text-[11px] font-medium text-neutral-500">
121
121
  {PROP_LABELS[prop] ?? prop}
122
122
  </span>
123
123
  <input
124
124
  type="text"
125
125
  defaultValue={String(val)}
126
- className="flex-1 bg-transparent text-[11px] text-neutral-200 outline-none"
126
+ className="flex-1 bg-transparent text-[11px] text-neutral-200 outline-hidden"
127
127
  onBlur={(e) => onCommit(e.currentTarget.value)}
128
128
  onKeyDown={(e) => {
129
129
  if (e.key === "Enter") e.currentTarget.blur();
@@ -139,7 +139,7 @@ export function PropertyRow({
139
139
  key={p.value}
140
140
  type="button"
141
141
  onClick={() => onCommit(p.value)}
142
- className="px-1.5 py-0.5 rounded text-[9px] font-medium text-neutral-500 bg-neutral-800/50 hover:bg-neutral-800 hover:text-neutral-300 transition-colors"
142
+ className="px-1.5 py-0.5 rounded-sm text-[9px] font-medium text-neutral-500 bg-neutral-800/50 hover:bg-neutral-800 hover:text-neutral-300 transition-colors"
143
143
  >
144
144
  {p.label}
145
145
  </button>
@@ -190,7 +190,7 @@ export function AddPropertyTrigger({
190
190
  return (
191
191
  <select
192
192
  autoFocus
193
- className="min-w-0 rounded-lg border border-neutral-700 bg-neutral-900 px-2 py-1 text-[11px] text-neutral-100 outline-none"
193
+ className="min-w-0 rounded-lg border border-neutral-700 bg-neutral-900 px-2 py-1 text-[11px] text-neutral-100 outline-hidden"
194
194
  defaultValue=""
195
195
  onChange={(e) => {
196
196
  if (e.target.value) onAdd(e.target.value);
@@ -222,7 +222,7 @@ function ParamControl({
222
222
  disabled={disabled}
223
223
  aria-label={`${param.label} color`}
224
224
  onChange={(e) => onChange(e.target.value)}
225
- className="w-7 h-7 rounded border border-neutral-700 bg-transparent cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
225
+ className="w-7 h-7 rounded-sm border border-neutral-700 bg-transparent cursor-pointer disabled:cursor-not-allowed disabled:opacity-50"
226
226
  />
227
227
  <input
228
228
  type="text"
@@ -230,7 +230,7 @@ function ParamControl({
230
230
  disabled={disabled}
231
231
  aria-label={`${param.label} value`}
232
232
  onChange={(e) => onChange(e.target.value)}
233
- className="flex-1 bg-neutral-900 border border-neutral-800 rounded px-2 py-1 text-[10px] text-neutral-200 font-mono focus:outline-none focus:border-neutral-700 disabled:cursor-not-allowed disabled:opacity-50"
233
+ className="flex-1 bg-neutral-900 border border-neutral-800 rounded-sm px-2 py-1 text-[10px] text-neutral-200 font-mono focus:outline-hidden focus:border-neutral-700 disabled:cursor-not-allowed disabled:opacity-50"
234
234
  />
235
235
  </div>
236
236
  )}
@@ -259,7 +259,7 @@ function ParamControl({
259
259
  disabled={disabled}
260
260
  aria-label={param.label}
261
261
  onChange={(e) => onChange(e.target.value)}
262
- className="w-full bg-neutral-900 border border-neutral-800 rounded px-2 py-1 text-[10px] text-neutral-200 focus:outline-none focus:border-neutral-700 disabled:cursor-not-allowed disabled:opacity-50"
262
+ className="w-full bg-neutral-900 border border-neutral-800 rounded-sm px-2 py-1 text-[10px] text-neutral-200 focus:outline-hidden focus:border-neutral-700 disabled:cursor-not-allowed disabled:opacity-50"
263
263
  />
264
264
  )}
265
265
 
@@ -269,7 +269,7 @@ function ParamControl({
269
269
  disabled={disabled}
270
270
  aria-label={param.label}
271
271
  onChange={(e) => onChange(e.target.value)}
272
- className="w-full bg-neutral-900 border border-neutral-800 rounded px-2 py-1 text-[10px] text-neutral-200 focus:outline-none focus:border-neutral-700 disabled:cursor-not-allowed disabled:opacity-50"
272
+ className="w-full bg-neutral-900 border border-neutral-800 rounded-sm px-2 py-1 text-[10px] text-neutral-200 focus:outline-hidden focus:border-neutral-700 disabled:cursor-not-allowed disabled:opacity-50"
273
273
  >
274
274
  {param.options.map((opt) => (
275
275
  <option key={opt.value} value={opt.value}>
@@ -71,7 +71,7 @@ export function BorderRadiusEditor({
71
71
  width={PREVIEW_W}
72
72
  height={PREVIEW_H}
73
73
  viewBox={`0 0 ${PREVIEW_W} ${PREVIEW_H}`}
74
- className="flex-shrink-0"
74
+ className="shrink-0"
75
75
  >
76
76
  <path
77
77
  d={path}
@@ -212,7 +212,7 @@ export const CanvasContextMenu = memo(function CanvasContextMenu({
212
212
  return createPortal(
213
213
  <div
214
214
  ref={menuRef}
215
- className="fixed z-[200] bg-neutral-900 border border-neutral-700 rounded-md shadow-lg py-1 min-w-[180px]"
215
+ className="fixed z-200 bg-neutral-900 border border-neutral-700 rounded-md shadow-lg py-1 min-w-[180px]"
216
216
  style={{ left: adjustedX, top: adjustedY }}
217
217
  onPointerDown={stopBubble}
218
218
  onMouseDown={stopBubble}
@@ -29,7 +29,7 @@ export function ComputedTweenNotice({
29
29
  <button
30
30
  type="button"
31
31
  onClick={onUnroll}
32
- className="flex-shrink-0 rounded px-1.5 py-0.5 text-[9px] font-medium text-panel-accent hover:bg-neutral-800"
32
+ className="shrink-0 rounded-sm px-1.5 py-0.5 text-[9px] font-medium text-panel-accent hover:bg-neutral-800"
33
33
  title="Rewrite the helper/loop into explicit tweens so this keyframe edits directly"
34
34
  >
35
35
  Unroll to edit
@@ -465,7 +465,7 @@ export const DomEditOverlay = memo(function DomEditOverlay({
465
465
  ref={overlayRef}
466
466
  // Standing aside is the only way the caret below can be reached, and is
467
467
  // what keeps selection, drag and marquee from firing mid-edit.
468
- className={`absolute inset-0 z-10 outline-none ${
468
+ className={`absolute inset-0 z-10 outline-hidden ${
469
469
  inlineText.editing ? "pointer-events-none" : "pointer-events-auto"
470
470
  }`}
471
471
  data-editing-text={inlineText.editing ? "true" : undefined}
@@ -518,7 +518,7 @@ export function EaseCurveSection({
518
518
  aria-valuemax={1}
519
519
  aria-valuenow={key === "p1" ? x1 : x2}
520
520
  aria-valuetext={`x ${key === "p1" ? x1 : x2}, y ${key === "p1" ? y1 : y2}`}
521
- className="cursor-grab stroke-transparent outline-none active:cursor-grabbing focus-visible:stroke-white focus-visible:stroke-[2px]"
521
+ className="cursor-grab stroke-transparent outline-hidden active:cursor-grabbing focus-visible:stroke-white focus-visible:stroke-[2px]"
522
522
  onPointerDown={(e) => handlePointerDown(key, e)}
523
523
  onKeyDown={(event) => handleKeyDown(key, event)}
524
524
  onPointerEnter={() => setHover(key)}
@@ -93,7 +93,7 @@ export function EaseBezierField({
93
93
  }
94
94
  }}
95
95
  onBlur={(event) => commit(event.currentTarget.value)}
96
- className={`w-full rounded border bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-none ${
96
+ className={`w-full rounded border bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-hidden ${
97
97
  error
98
98
  ? "border-red-500/70 focus:border-red-400"
99
99
  : "border-white/10 focus:border-panel-accent/50"
@@ -183,7 +183,7 @@ export function SpringBounceField({
183
183
  const bounce = parseSpringBounce(`spring(${value})`);
184
184
  if (bounce !== null) onCommit(`spring(${round2(bounce)})`);
185
185
  }}
186
- className="w-16 rounded border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-none focus:border-panel-accent/50"
186
+ className="w-16 rounded-sm border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-hidden focus:border-panel-accent/50"
187
187
  />
188
188
  </div>
189
189
  );
@@ -207,7 +207,7 @@ export function WiggleField({
207
207
  min={1}
208
208
  step={1}
209
209
  onCommit={(value) => commitWiggle(onCommit, value, config.type, amplitude)}
210
- className="w-14 rounded border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-none focus:border-panel-accent/50"
210
+ className="w-14 rounded-sm border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-hidden focus:border-panel-accent/50"
211
211
  />
212
212
  </div>
213
213
  <div className="flex items-center gap-1">
@@ -220,7 +220,7 @@ export function WiggleField({
220
220
  commitWiggle(onCommit, config.wiggles, event.currentTarget.value, amplitude);
221
221
  }
222
222
  }}
223
- className="rounded border border-white/10 bg-black/20 px-1.5 py-1 text-[10px] text-neutral-300 outline-none focus:border-panel-accent/50"
223
+ className="rounded-sm border border-white/10 bg-black/20 px-1.5 py-1 text-[10px] text-neutral-300 outline-hidden focus:border-panel-accent/50"
224
224
  >
225
225
  {WIGGLE_TYPES.map((type) => (
226
226
  <option key={type} value={type}>
@@ -238,7 +238,7 @@ export function WiggleField({
238
238
  max={1}
239
239
  step={0.01}
240
240
  onCommit={(value) => commitWiggle(onCommit, config.wiggles, config.type, value)}
241
- className="w-16 rounded border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-none focus:border-panel-accent/50"
241
+ className="w-16 rounded-sm border border-white/10 bg-black/20 px-1.5 py-1 font-mono text-[10px] text-neutral-300 outline-hidden focus:border-panel-accent/50"
242
242
  />
243
243
  </div>
244
244
  </div>
@@ -235,14 +235,14 @@ export const FileTree = memo(function FileTree({
235
235
  <div className="flex flex-col h-full min-h-0">
236
236
  {/* FILES header with action buttons */}
237
237
  {hasFileOps && (
238
- <div className="flex items-center justify-between px-2.5 py-1.5 border-b border-neutral-800/50 flex-shrink-0">
238
+ <div className="flex items-center justify-between px-2.5 py-1.5 border-b border-neutral-800/50 shrink-0">
239
239
  <span className="text-[10px] font-semibold tracking-wider text-neutral-600 uppercase">
240
240
  Files
241
241
  </span>
242
242
  <div className="flex items-center gap-0.5">
243
243
  <button
244
244
  onClick={() => handleNewFile("")}
245
- className="p-1.5 rounded hover:bg-neutral-800 text-neutral-600 hover:text-neutral-400 active:scale-[0.97] transition-colors"
245
+ className="p-1.5 rounded-sm hover:bg-neutral-800 text-neutral-600 hover:text-neutral-400 active:scale-[0.97] transition-colors"
246
246
  title="New File"
247
247
  aria-label="New File"
248
248
  >
@@ -250,7 +250,7 @@ export const FileTree = memo(function FileTree({
250
250
  </button>
251
251
  <button
252
252
  onClick={() => handleNewFolder("")}
253
- className="p-1.5 rounded hover:bg-neutral-800 text-neutral-600 hover:text-neutral-400 active:scale-[0.97] transition-colors"
253
+ className="p-1.5 rounded-sm hover:bg-neutral-800 text-neutral-600 hover:text-neutral-400 active:scale-[0.97] transition-colors"
254
254
  title="New Folder"
255
255
  aria-label="New Folder"
256
256
  >
@@ -263,7 +263,7 @@ export const FileTree = memo(function FileTree({
263
263
  <div
264
264
  className={`flex-1 overflow-y-auto py-1 transition-colors ${
265
265
  dragOverFolder === ""
266
- ? "bg-[#3CE6AC]/5 outline outline-1 outline-[#3CE6AC]/30 -outline-offset-1"
266
+ ? "bg-[#3CE6AC]/5 outline-solid outline-1 outline-[#3CE6AC]/30 -outline-offset-1"
267
267
  : ""
268
268
  }`}
269
269
  onContextMenu={handleRootContextMenu}
@@ -23,7 +23,7 @@ const W = "duotone" as const;
23
23
 
24
24
  export function FileIcon({ path }: { path: string }) {
25
25
  const ext = path.split(".").pop()?.toLowerCase() ?? "";
26
- const c = "flex-shrink-0";
26
+ const c = "shrink-0";
27
27
  if (ext === "html") return <FileHtml size={SZ} weight={W} color="#E44D26" className={c} />;
28
28
  if (ext === "css") return <FileCss size={SZ} weight={W} color="#264DE4" className={c} />;
29
29
  if (ext === "js" || ext === "mjs" || ext === "cjs")
@@ -107,7 +107,7 @@ export function ContextMenu({
107
107
  <>
108
108
  <button
109
109
  role="menuitem"
110
- className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-none cursor-pointer text-left"
110
+ className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-hidden cursor-pointer text-left"
111
111
  onClick={() => {
112
112
  onNewFile(state.targetPath);
113
113
  onClose();
@@ -118,7 +118,7 @@ export function ContextMenu({
118
118
  </button>
119
119
  <button
120
120
  role="menuitem"
121
- className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-none cursor-pointer text-left"
121
+ className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-hidden cursor-pointer text-left"
122
122
  onClick={() => {
123
123
  onNewFolder(state.targetPath);
124
124
  onClose();
@@ -134,7 +134,7 @@ export function ContextMenu({
134
134
  <>
135
135
  <button
136
136
  role="menuitem"
137
- className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-none cursor-pointer text-left"
137
+ className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-hidden cursor-pointer text-left"
138
138
  onClick={() => {
139
139
  onNewFile(parentPath);
140
140
  onClose();
@@ -148,7 +148,7 @@ export function ContextMenu({
148
148
  )}
149
149
  <button
150
150
  role="menuitem"
151
- className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-none cursor-pointer text-left"
151
+ className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-hidden cursor-pointer text-left"
152
152
  onClick={() => {
153
153
  onRename(state.targetPath);
154
154
  onClose();
@@ -160,7 +160,7 @@ export function ContextMenu({
160
160
  {!state.targetIsFolder && (
161
161
  <button
162
162
  role="menuitem"
163
- className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-none cursor-pointer text-left"
163
+ className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-300 hover:bg-neutral-800 focus-visible:bg-neutral-800 active:bg-neutral-700 outline-hidden cursor-pointer text-left"
164
164
  onClick={() => {
165
165
  onDuplicate(state.targetPath);
166
166
  onClose();
@@ -173,7 +173,7 @@ export function ContextMenu({
173
173
  <div className="border-t border-neutral-700 my-1" />
174
174
  <button
175
175
  role="menuitem"
176
- className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-red-400 hover:bg-red-900/30 focus-visible:bg-red-900/30 active:bg-red-900/50 outline-none cursor-pointer text-left"
176
+ className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-red-400 hover:bg-red-900/30 focus-visible:bg-red-900/30 active:bg-red-900/50 outline-hidden cursor-pointer text-left"
177
177
  onClick={() => {
178
178
  onDelete(state.targetPath);
179
179
  onClose();
@@ -263,7 +263,7 @@ export function InlineInput({
263
263
  style={{ paddingLeft: `${8 + depth * 12 + (isFolder ? 0 : 14)}px` }}
264
264
  >
265
265
  {isFolder ? (
266
- <FolderSimple size={SZ_ICON} weight="duotone" color="#6B7280" className="flex-shrink-0" />
266
+ <FolderSimple size={SZ_ICON} weight="duotone" color="#6B7280" className="shrink-0" />
267
267
  ) : (
268
268
  <FileIcon path={value} />
269
269
  )}
@@ -278,7 +278,7 @@ export function InlineInput({
278
278
  onKeyDown={handleKeyDown}
279
279
  onBlur={handleBlur}
280
280
  aria-invalid={error ? true : undefined}
281
- className={`w-full min-w-0 bg-neutral-800 text-neutral-200 text-xs px-1.5 py-0.5 rounded border outline-none ${
281
+ className={`w-full min-w-0 bg-neutral-800 text-neutral-200 text-xs px-1.5 py-0.5 rounded border outline-hidden ${
282
282
  error ? "border-red-500/70" : "border-neutral-600 focus:border-[#3CE6AC]"
283
283
  }`}
284
284
  spellCheck={false}
@@ -344,13 +344,13 @@ export function DeleteConfirm({
344
344
  <div className="flex gap-1.5">
345
345
  <button
346
346
  onClick={onCancel}
347
- className="flex-1 px-2 py-1 rounded bg-neutral-700 text-neutral-300 hover:bg-neutral-600 transition-colors"
347
+ className="flex-1 px-2 py-1 rounded-sm bg-neutral-700 text-neutral-300 hover:bg-neutral-600 transition-colors"
348
348
  >
349
349
  Cancel
350
350
  </button>
351
351
  <button
352
352
  onClick={onConfirm}
353
- className="flex-1 px-2 py-1 rounded bg-red-900/60 text-red-300 hover:bg-red-800/60 transition-colors"
353
+ className="flex-1 px-2 py-1 rounded-sm bg-red-900/60 text-red-300 hover:bg-red-800/60 transition-colors"
354
354
  >
355
355
  Delete
356
356
  </button>
@@ -419,7 +419,7 @@ export const TreeFile = memo(function TreeFile({
419
419
  <span className="truncate flex-1">{node.name}</span>
420
420
  {lintInfo && lintInfo.count > 0 && (
421
421
  <span
422
- className="flex-shrink-0 min-w-[16px] rounded-full bg-amber-500/20 px-1 text-[8px] font-bold text-amber-400 text-center mr-1"
422
+ className="shrink-0 min-w-[16px] rounded-full bg-amber-500/20 px-1 text-[8px] font-bold text-amber-400 text-center mr-1"
423
423
  title={lintInfo.messages.join("\n")}
424
424
  >
425
425
  {lintInfo.count}
@@ -505,11 +505,11 @@ export const TreeFolder = memo(function TreeFolder({
505
505
  }}
506
506
  onDragLeave={onDragLeave}
507
507
  className={`w-full flex items-center gap-1.5 px-2.5 py-1 min-h-7 text-left text-xs text-neutral-400 hover:bg-neutral-800/30 hover:text-neutral-300 transition-colors ${
508
- isDragOver ? "bg-[#3CE6AC]/10 outline outline-1 outline-[#3CE6AC]/40" : ""
508
+ isDragOver ? "bg-[#3CE6AC]/10 outline-solid outline-1 outline-[#3CE6AC]/40" : ""
509
509
  }`}
510
510
  style={{ paddingLeft: `${8 + depth * 12}px` }}
511
511
  >
512
- <Chevron size={10} className="flex-shrink-0 text-neutral-600" />
512
+ <Chevron size={10} className="shrink-0 text-neutral-600" />
513
513
  <span className="truncate font-medium">{node.name}</span>
514
514
  </button>
515
515
  {isOpen && (
@@ -77,7 +77,7 @@ export function InlineTextToolbar({
77
77
  data-inline-text-toolbar="true"
78
78
  role="toolbar"
79
79
  aria-label="Text formatting"
80
- className="pointer-events-auto fixed z-[200] flex items-center gap-1 rounded-lg border border-white/10 bg-[#15171c] p-1 shadow-[0_8px_24px_rgba(0,0,0,0.55)]"
80
+ className="pointer-events-auto fixed z-200 flex items-center gap-1 rounded-lg border border-white/10 bg-[#15171c] p-1 shadow-[0_8px_24px_rgba(0,0,0,0.55)]"
81
81
  style={{
82
82
  left: placement.left,
83
83
  top: placement.top,
@@ -37,7 +37,7 @@ export function InspectorHeaderActions({
37
37
  track("toggle", "Element visibility");
38
38
  void onToggleHidden(selectedElementId, !selectedElementHidden);
39
39
  }}
40
- className="flex h-6 w-6 items-center justify-center rounded text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
40
+ className="flex h-6 w-6 items-center justify-center rounded-sm text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
41
41
  >
42
42
  {selectedElementHidden ? (
43
43
  <EyeSlash size={13} weight="bold" aria-hidden="true" />
@@ -54,7 +54,7 @@ export function InspectorHeaderActions({
54
54
  onUngroup();
55
55
  }}
56
56
  title="Ungroup (⌘⇧G)"
57
- className="flex h-6 items-center rounded px-2 text-[11px] font-medium text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-neutral-200"
57
+ className="flex h-6 items-center rounded-sm px-2 text-[11px] font-medium text-neutral-400 transition-colors hover:bg-neutral-800 hover:text-neutral-200"
58
58
  >
59
59
  Ungroup
60
60
  </button>
@@ -91,7 +91,7 @@ export function InspectorHeaderActions({
91
91
  track("button", "Clear selection");
92
92
  onClear();
93
93
  }}
94
- className="flex h-6 w-6 items-center justify-center rounded text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
94
+ className="flex h-6 w-6 items-center justify-center rounded-sm text-neutral-500 transition-colors hover:bg-neutral-800 hover:text-neutral-300"
95
95
  >
96
96
  <X size={13} />
97
97
  </button>
@@ -29,7 +29,7 @@ export const KeyframeDiamond = memo(function KeyframeDiamond({
29
29
  e.stopPropagation();
30
30
  onClick();
31
31
  }}
32
- className="relative flex-shrink-0 p-0.5 transition-opacity hover:opacity-100 before:absolute before:-inset-1.5 before:content-['']"
32
+ className="relative shrink-0 p-0.5 transition-opacity hover:opacity-100 before:absolute before:-inset-1.5 before:content-['']"
33
33
  style={{ color, opacity }}
34
34
  title={title}
35
35
  aria-label={title}
@@ -74,7 +74,7 @@ export function KeyframeEaseList({
74
74
  const next = e.target.value;
75
75
  if (next) onApplyAll(next);
76
76
  }}
77
- className="ml-auto cursor-pointer rounded bg-neutral-800 px-1.5 py-0.5 text-[9px] text-neutral-300 outline-none hover:bg-neutral-700 focus:ring-1 focus:ring-panel-accent/40"
77
+ className="ml-auto cursor-pointer rounded-sm bg-neutral-800 px-1.5 py-0.5 text-[9px] text-neutral-300 outline-hidden hover:bg-neutral-700 focus:ring-1 focus:ring-panel-accent/40"
78
78
  >
79
79
  <option value="" disabled>
80
80
  Set all…