@hyperframes/studio 0.8.50 → 0.8.51

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 (215) hide show
  1. package/dist/assets/{hyperframes-player-CxxDxHTW.js → hyperframes-player-Cn_PpHqP.js} +3 -3
  2. package/dist/assets/index-C05SNrvY.js +360 -0
  3. package/dist/assets/{index-DUAIWxkI.js → index-CPS3Cvj8.js} +1 -1
  4. package/dist/assets/{index-Dt3qBI-9.js → index-Clrb11lj.js} +1 -1
  5. package/dist/assets/index-DUR9qzQM.css +1 -0
  6. package/dist/index.d.ts +18 -13
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +1867 -3335
  9. package/dist/index.js.map +1 -1
  10. package/dist/styles/tailwind-preset.js.map +1 -1
  11. package/package.json +11 -12
  12. package/src/App.tsx +128 -141
  13. package/src/captions/components/CaptionAnimationPanel.tsx +4 -4
  14. package/src/captions/components/CaptionOverlayUtils.ts +2 -3
  15. package/src/captions/components/CaptionPropertyPanel.tsx +3 -3
  16. package/src/captions/components/CaptionTimeline.tsx +1 -1
  17. package/src/captions/components/shared.tsx +2 -2
  18. package/src/components/AskAgentModal.tsx +5 -5
  19. package/src/components/EditorShell.tsx +4 -7
  20. package/src/components/ExternalFileConflictBanner.tsx +16 -16
  21. package/src/components/LintModal.tsx +6 -16
  22. package/src/components/MediaPreview.tsx +2 -2
  23. package/src/components/SaveQueuePausedBanner.tsx +2 -2
  24. package/src/components/StudioHeader.tsx +2 -62
  25. package/src/components/StudioLeftSidebar.tsx +7 -4
  26. package/src/components/StudioOverlays.tsx +1 -1
  27. package/src/components/StudioRightPanel.tsx +4 -4
  28. package/src/components/StudioToast.tsx +2 -2
  29. package/src/components/TimelineToolbar.tsx +7 -7
  30. package/src/components/dock/Dock.test.tsx +192 -0
  31. package/src/components/dock/Dock.tsx +264 -0
  32. package/src/components/dock/dock.css +23 -0
  33. package/src/components/dock/dockLayout.ts +58 -0
  34. package/src/components/dock/dockLayoutSchema.ts +66 -0
  35. package/src/components/dock/dockLayoutStore.ts +91 -0
  36. package/src/components/dock/panelRegistry.ts +67 -0
  37. package/src/components/editor/AnimationCard.tsx +4 -4
  38. package/src/components/editor/AnimationCardParts.tsx +6 -6
  39. package/src/components/editor/BlockParamsPanel.tsx +4 -4
  40. package/src/components/editor/BorderRadiusEditor.tsx +1 -1
  41. package/src/components/editor/CanvasContextMenu.tsx +1 -1
  42. package/src/components/editor/ComputedTweenNotice.tsx +1 -1
  43. package/src/components/editor/DomEditOverlay.tsx +1 -1
  44. package/src/components/editor/EaseCurveSection.tsx +1 -1
  45. package/src/components/editor/EaseParamFields.tsx +5 -5
  46. package/src/components/editor/FileTree.tsx +4 -4
  47. package/src/components/editor/FileTreeIcons.tsx +1 -1
  48. package/src/components/editor/FileTreeNodes.tsx +13 -13
  49. package/src/components/editor/InlineTextToolbar.tsx +1 -1
  50. package/src/components/editor/InspectorHeaderActions.tsx +3 -3
  51. package/src/components/editor/KeyframeDiamond.tsx +1 -1
  52. package/src/components/editor/KeyframeEaseList.tsx +1 -1
  53. package/src/components/editor/LayersPanel.tsx +3 -3
  54. package/src/components/editor/MarqueeOverlay.tsx +1 -1
  55. package/src/components/editor/OffCanvasIndicators.tsx +1 -1
  56. package/src/components/editor/PromotableControl.tsx +2 -2
  57. package/src/components/editor/PropertyPanelEmptyState.tsx +7 -7
  58. package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
  59. package/src/components/editor/SnapToolbar.tsx +2 -2
  60. package/src/components/editor/TimelineFxPopover.tsx +1 -1
  61. package/src/components/editor/TopologyLens.tsx +4 -4
  62. package/src/components/editor/Transform3DCube.tsx +2 -2
  63. package/src/components/editor/audioFxRevealTarget.ts +1 -0
  64. package/src/components/editor/domEditing.test.ts +1 -1
  65. package/src/components/editor/domEditingAgentPrompt.test.ts +8 -6
  66. package/src/components/editor/domEditingAgentPrompt.ts +4 -3
  67. package/src/components/editor/propertyPanelColor.tsx +11 -11
  68. package/src/components/editor/propertyPanelColorCurveGraph.tsx +1 -1
  69. package/src/components/editor/propertyPanelColorGradingControls.tsx +7 -9
  70. package/src/components/editor/propertyPanelColorGradingSection.tsx +6 -6
  71. package/src/components/editor/propertyPanelColorGradingSlider.tsx +5 -5
  72. package/src/components/editor/propertyPanelColorWheels.tsx +1 -1
  73. package/src/components/editor/propertyPanelCommitField.tsx +1 -1
  74. package/src/components/editor/propertyPanelFill.tsx +4 -4
  75. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
  76. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +7 -7
  77. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
  78. package/src/components/editor/propertyPanelFlatMediaSection.tsx +3 -3
  79. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +1 -1
  80. package/src/components/editor/propertyPanelFlatPrimitives.tsx +10 -10
  81. package/src/components/editor/propertyPanelFlatSelectRow.tsx +3 -3
  82. package/src/components/editor/propertyPanelFlatTextSection.test.tsx +8 -9
  83. package/src/components/editor/propertyPanelFlatTextSection.tsx +5 -5
  84. package/src/components/editor/propertyPanelFlatToggle.tsx +1 -1
  85. package/src/components/editor/propertyPanelFont.tsx +5 -5
  86. package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
  87. package/src/components/editor/propertyPanelFxControls.test.tsx +1 -1
  88. package/src/components/editor/propertyPanelFxControls.tsx +6 -6
  89. package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
  90. package/src/components/editor/propertyPanelFxPresetStyle.ts +2 -2
  91. package/src/components/editor/propertyPanelGradingNumberField.tsx +1 -1
  92. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  93. package/src/components/editor/propertyPanelPrimitives.tsx +7 -7
  94. package/src/components/editor/propertyPanelSections.tsx +8 -8
  95. package/src/components/feedback/StudioFeedbackCard.tsx +7 -7
  96. package/src/components/feedback/feedbackTrigger.ts +0 -1
  97. package/src/components/nle/AssetPreviewOverlay.tsx +6 -2
  98. package/src/components/nle/CompositionBreadcrumb.tsx +3 -3
  99. package/src/components/nle/NLEPreview.tsx +3 -3
  100. package/src/components/nle/PreviewOverlays.tsx +6 -6
  101. package/src/components/nle/PreviewPane.tsx +2 -2
  102. package/src/components/nle/TimelinePane.tsx +3 -3
  103. package/src/components/nle/TimelineResizeDivider.tsx +2 -2
  104. package/src/components/panels/SlideshowPanel.tsx +2 -2
  105. package/src/components/panels/SlideshowSubPanels.tsx +20 -17
  106. package/src/components/panels/VariablesBindElement.tsx +2 -2
  107. package/src/components/panels/VariablesDeclarationForm.tsx +2 -2
  108. package/src/components/panels/VariablesOtherCompositions.tsx +1 -1
  109. package/src/components/panels/VariablesPanel.tsx +3 -3
  110. package/src/components/panels/VariablesValueControls.tsx +2 -2
  111. package/src/components/renders/FfmpegRequiredNotice.tsx +5 -5
  112. package/src/components/renders/RenderQueue.test.tsx +7 -0
  113. package/src/components/renders/RenderQueue.tsx +6 -6
  114. package/src/components/renders/RenderQueueItem.tsx +6 -6
  115. package/src/components/sidebar/AssetCard.tsx +8 -8
  116. package/src/components/sidebar/AssetContextMenu.tsx +8 -8
  117. package/src/components/sidebar/AssetsTab.tsx +2 -2
  118. package/src/components/sidebar/AudioRow.tsx +6 -6
  119. package/src/components/sidebar/BlocksTab.tsx +6 -6
  120. package/src/components/sidebar/CompositionsTab.tsx +5 -5
  121. package/src/components/sidebar/GlobalAssetsView.tsx +1 -1
  122. package/src/components/sidebar/LeftSidebar.lintBadge.test.tsx +58 -0
  123. package/src/components/sidebar/LeftSidebar.tsx +19 -5
  124. package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
  125. package/src/components/ui/Button.test.tsx +60 -0
  126. package/src/components/ui/Button.tsx +4 -4
  127. package/src/components/ui/SearchInput.tsx +2 -2
  128. package/src/components/ui/Tooltip.tsx +1 -1
  129. package/src/hooks/useAppHotkeys.ts +1 -1
  130. package/src/hooks/useLintModal.test.tsx +65 -0
  131. package/src/hooks/useLintModal.ts +3 -0
  132. package/src/hooks/useSdkSession.lifecycle.test.tsx +49 -4
  133. package/src/hooks/useSdkSession.ts +43 -10
  134. package/src/hooks/useTimelineAssetDropOps.test.tsx +41 -1
  135. package/src/hooks/useTimelineAssetDropOps.ts +52 -11
  136. package/src/main.tsx +9 -3
  137. package/src/player/components/AudioWaveform.tsx +1 -1
  138. package/src/player/components/AutomationSelectionMenu.tsx +2 -2
  139. package/src/player/components/ClipContextMenu.tsx +3 -3
  140. package/src/player/components/CompositionThumbnail.test.ts +0 -3
  141. package/src/player/components/CompositionThumbnail.tsx +3 -4
  142. package/src/player/components/EditModal.tsx +1 -1
  143. package/src/player/components/ImageThumbnail.tsx +1 -1
  144. package/src/player/components/KeyframeDiamondContextMenu.tsx +4 -4
  145. package/src/player/components/LayerDisclosureRow.tsx +2 -2
  146. package/src/player/components/PlayerControls.tsx +1 -1
  147. package/src/player/components/ShortcutsPanel.tsx +9 -9
  148. package/src/player/components/SpeedMenu.tsx +3 -5
  149. package/src/player/components/Timeline.tsx +2 -1
  150. package/src/player/components/TimelineCanvas.tsx +17 -10
  151. package/src/player/components/TimelineDiamondConnectors.tsx +1 -1
  152. package/src/player/components/TimelineEmptyState.tsx +5 -5
  153. package/src/player/components/TimelineFxButton.tsx +6 -6
  154. package/src/player/components/TimelineGroupHeader.tsx +5 -5
  155. package/src/player/components/TimelineGroupLaneLabels.tsx +1 -1
  156. package/src/player/components/TimelineGroupRow.tsx +1 -1
  157. package/src/player/components/TimelineLanes.test.tsx +32 -0
  158. package/src/player/components/TimelineLanes.tsx +2 -5
  159. package/src/player/components/TimelineLayerGroupHeader.tsx +2 -2
  160. package/src/player/components/TimelineRuler.tsx +1 -1
  161. package/src/player/components/TimelineShortcutHint.tsx +1 -1
  162. package/src/player/components/TimelineTrackHeader.tsx +1 -1
  163. package/src/player/components/TimelineTrackPlainHeader.tsx +3 -3
  164. package/src/player/components/TimelineTypes.ts +2 -1
  165. package/src/player/components/TrackGapContextMenu.tsx +1 -1
  166. package/src/player/components/VideoThumbnail.tsx +4 -2
  167. package/src/player/components/VolumeControl.tsx +2 -2
  168. package/src/player/components/timelineCallbacks.ts +8 -8
  169. package/src/player/components/timelineClipDragPreview.ts +51 -27
  170. package/src/player/components/timelineClipDragTypes.ts +3 -0
  171. package/src/player/components/timelineDragDrop.test.tsx +61 -1
  172. package/src/player/components/timelineDragDrop.ts +57 -15
  173. package/src/player/components/timelineLaneProps.ts +3 -0
  174. package/src/player/components/timelineSnapping.test.ts +27 -0
  175. package/src/player/components/timelineSnapping.ts +14 -1
  176. package/src/player/components/trackHeaderLabelRows.tsx +4 -4
  177. package/src/player/components/useTimelineClipDrag.resize.test.tsx +136 -2
  178. package/src/player/components/useTimelineClipDrag.ts +30 -3
  179. package/src/player/lib/describeClips.ts +3 -54
  180. package/src/styles/iconTokens.ts +20 -0
  181. package/src/styles/palette-reset.css +6 -0
  182. package/src/styles/studio.css +24 -21
  183. package/src/styles/tailwind-preset.shared.js +3 -0
  184. package/src/styles/theme.css +213 -0
  185. package/src/styles/theme.test.ts +196 -0
  186. package/src/utils/studioUiPreferences.ts +7 -0
  187. package/src/utils/timelineDropTrackInsert.test.ts +134 -0
  188. package/src/utils/timelineDropTrackInsert.ts +83 -0
  189. package/dist/assets/index-n2ml4tUn.js +0 -442
  190. package/dist/assets/index-pO6p0H27.css +0 -1
  191. package/src/components/storyboard/AgentChatMessageButton.test.tsx +0 -47
  192. package/src/components/storyboard/AgentChatMessageButton.tsx +0 -45
  193. package/src/components/storyboard/FramePoster.test.tsx +0 -61
  194. package/src/components/storyboard/FramePoster.tsx +0 -86
  195. package/src/components/storyboard/StoryboardDirection.tsx +0 -45
  196. package/src/components/storyboard/StoryboardFrameFocus.tsx +0 -412
  197. package/src/components/storyboard/StoryboardFrameTile.tsx +0 -117
  198. package/src/components/storyboard/StoryboardGrid.tsx +0 -55
  199. package/src/components/storyboard/StoryboardLoaded.tsx +0 -307
  200. package/src/components/storyboard/StoryboardReviewGuide.tsx +0 -300
  201. package/src/components/storyboard/StoryboardScriptPanel.tsx +0 -26
  202. package/src/components/storyboard/StoryboardSourceEditor.tsx +0 -231
  203. package/src/components/storyboard/StoryboardStatusLegend.tsx +0 -21
  204. package/src/components/storyboard/StoryboardView.tsx +0 -169
  205. package/src/components/storyboard/StoryboardViewModeGuard.test.tsx +0 -187
  206. package/src/components/storyboard/frameComments.test.ts +0 -95
  207. package/src/components/storyboard/frameComments.ts +0 -115
  208. package/src/components/storyboard/frameStatus.ts +0 -36
  209. package/src/components/storyboard/storyboardReviewStage.test.ts +0 -40
  210. package/src/components/storyboard/storyboardReviewStage.ts +0 -45
  211. package/src/components/storyboard/useFrameComments.ts +0 -98
  212. package/src/contexts/ViewModeContext.tsx +0 -142
  213. package/src/hooks/useProjectSignaturePoll.test.tsx +0 -135
  214. package/src/hooks/useProjectSignaturePoll.ts +0 -68
  215. package/src/hooks/useStoryboard.ts +0 -99
@@ -243,7 +243,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
243
243
  if (req) trackStudioFeedbackInterviewClick({ reason: req.reason });
244
244
  close("sent");
245
245
  }}
246
- className="h-6 flex-shrink-0 whitespace-nowrap rounded-full border border-white/20 bg-white/[0.08] px-2.5 text-[11px] leading-6 text-neutral-100 transition-[background-color,border-color,transform] duration-150 ease-out hover:border-white/35 hover:bg-white/[0.16] active:scale-[0.97] motion-reduce:transition-none"
246
+ className="h-6 shrink-0 whitespace-nowrap rounded-full border border-white/20 bg-white/8 px-2.5 text-[11px] leading-6 text-neutral-100 transition-[background-color,border-color,transform] duration-150 ease-out hover:border-white/35 hover:bg-white/16 active:scale-[0.97] motion-reduce:transition-none"
247
247
  >
248
248
  Talk to us, 30 min
249
249
  </a>
@@ -251,7 +251,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
251
251
  <button
252
252
  type="button"
253
253
  onClick={() => close("close")}
254
- className="-mr-1 -mt-0.5 flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-md text-neutral-500 transition-colors duration-150 hover:bg-white/10 hover:text-neutral-200"
254
+ className="-mr-1 -mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-md text-neutral-500 transition-colors duration-150 hover:bg-white/10 hover:text-neutral-200"
255
255
  aria-label="Dismiss"
256
256
  >
257
257
  <svg
@@ -283,7 +283,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
283
283
  key={n}
284
284
  // A resting fill, so the row reads as eleven controls rather
285
285
  // than as a line of text that happens to be clickable.
286
- className="flex h-7 cursor-pointer items-center justify-center rounded-md bg-white/[0.04] text-[11px] tabular-nums text-neutral-400 transition-[background-color,color,transform] duration-150 ease-out hover:bg-white/[0.14] hover:text-neutral-100 active:scale-[0.97] has-[:checked]:bg-white/90 has-[:checked]:text-neutral-900 has-[:focus-visible]:ring-1 has-[:focus-visible]:ring-white/50 motion-reduce:transition-none"
286
+ className="flex h-7 cursor-pointer items-center justify-center rounded-md bg-white/4 text-[11px] tabular-nums text-neutral-400 transition-[background-color,color,transform] duration-150 ease-out hover:bg-white/[0.14] hover:text-neutral-100 active:scale-[0.97] has-checked:bg-white/90 has-checked:text-neutral-900 has-focus-visible:ring-1 has-focus-visible:ring-white/50 motion-reduce:transition-none"
287
287
  >
288
288
  <input
289
289
  type="radio"
@@ -328,8 +328,8 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
328
328
  // attached to nothing.
329
329
  className={`h-6 rounded-full border px-2.5 text-[11px] transition-[background-color,border-color,color,transform] duration-150 ease-out active:scale-[0.97] motion-reduce:transition-none ${
330
330
  failed
331
- ? "border-red-300/20 bg-red-100/[0.06] text-red-100/80 hover:border-red-200/70 hover:bg-red-100/25 hover:text-white"
332
- : "border-white/10 bg-white/[0.04] text-neutral-300 hover:border-white/60 hover:bg-white/25 hover:text-white"
331
+ ? "border-red-300/20 bg-red-100/6 text-red-100/80 hover:border-red-200/70 hover:bg-red-100/25 hover:text-white"
332
+ : "border-white/10 bg-white/4 text-neutral-300 hover:border-white/60 hover:bg-white/25 hover:text-white"
333
333
  }`}
334
334
  >
335
335
  {preset.label}
@@ -355,12 +355,12 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
355
355
  placeholder={followUp.placeholder}
356
356
  aria-label={followUp.prompt}
357
357
  maxLength={500}
358
- className="h-7 min-w-0 flex-1 rounded-md border border-white/10 bg-black/20 px-2 text-[11px] text-neutral-100 outline-none transition-colors duration-150 placeholder:text-neutral-500 focus:border-white/25"
358
+ className="h-7 min-w-0 flex-1 rounded-md border border-white/10 bg-black/20 px-2 text-[11px] text-neutral-100 outline-hidden transition-colors duration-150 placeholder:text-neutral-500 focus:border-white/25"
359
359
  />
360
360
  <button
361
361
  type="button"
362
362
  onClick={() => submit()}
363
- className="h-7 flex-shrink-0 rounded-md bg-white/10 px-2.5 text-[11px] text-neutral-100 transition-[background-color,transform] duration-150 ease-out hover:bg-white/[0.16] active:scale-[0.97] motion-reduce:transition-none"
363
+ className="h-7 shrink-0 rounded-md bg-white/10 px-2.5 text-[11px] text-neutral-100 transition-[background-color,transform] duration-150 ease-out hover:bg-white/16 active:scale-[0.97] motion-reduce:transition-none"
364
364
  >
365
365
  Send
366
366
  </button>
@@ -76,7 +76,6 @@ const FOLLOW_UPS: readonly FollowUpQuestion[] = [
76
76
  { label: "Layers panel", hint: "The layer tree on the right" },
77
77
  { label: "Variables panel", hint: "Composition variables on the right" },
78
78
  { label: "Blocks browser", hint: "The block library in the sidebar" },
79
- { label: "Storyboard mode", hint: "The storyboard view next to Preview" },
80
79
  { label: "Caption editing", hint: "Editing caption words and presets in Studio" },
81
80
  { label: "Render history", hint: "The list of past exports" },
82
81
  { label: "Nothing", hint: "It all earns its place" },
@@ -42,7 +42,11 @@ function AssetPreviewMedia({
42
42
  }) {
43
43
  if (kind === "image") {
44
44
  return (
45
- <img src={serveUrl} alt={name} className="max-w-full max-h-[40vh] rounded object-contain" />
45
+ <img
46
+ src={serveUrl}
47
+ alt={name}
48
+ className="max-w-full max-h-[40vh] rounded-sm object-contain"
49
+ />
46
50
  );
47
51
  }
48
52
  if (kind === "video") {
@@ -53,7 +57,7 @@ function AssetPreviewMedia({
53
57
  autoPlay
54
58
  muted
55
59
  playsInline
56
- className="max-w-full max-h-[40vh] rounded"
60
+ className="max-w-full max-h-[40vh] rounded-sm"
57
61
  />
58
62
  );
59
63
  }
@@ -21,7 +21,7 @@ export function CompositionBreadcrumb({ stack, onNavigate }: CompositionBreadcru
21
21
  return (
22
22
  <nav
23
23
  aria-label="Composition navigation"
24
- className="flex items-center gap-1 px-2 h-8 border-b border-neutral-800/50 bg-neutral-900/50 flex-shrink-0"
24
+ className="flex items-center gap-1 px-2 h-8 border-b border-neutral-800/50 bg-neutral-900/50 shrink-0"
25
25
  >
26
26
  {/* Back button — always goes to parent */}
27
27
  <button
@@ -33,7 +33,7 @@ export function CompositionBreadcrumb({ stack, onNavigate }: CompositionBreadcru
33
33
  });
34
34
  onNavigate(stack.length - 2);
35
35
  }}
36
- className="flex items-center gap-1 px-1.5 py-0.5 rounded text-xs text-neutral-400 hover:text-white hover:bg-neutral-800 active:scale-[0.98] transition-colors"
36
+ className="flex items-center gap-1 px-1.5 py-0.5 rounded-sm text-xs text-neutral-400 hover:text-white hover:bg-neutral-800 active:scale-[0.98] transition-colors"
37
37
  title="Back (Esc, or double-click empty timeline)"
38
38
  aria-label="Back to parent composition"
39
39
  >
@@ -45,7 +45,7 @@ export function CompositionBreadcrumb({ stack, onNavigate }: CompositionBreadcru
45
45
  const isLast = i === stack.length - 1;
46
46
  return (
47
47
  <span key={level.id} className="flex items-center gap-1">
48
- {i > 0 && <CaretRight size={10} className="text-neutral-600 flex-shrink-0" />}
48
+ {i > 0 && <CaretRight size={10} className="text-neutral-600 shrink-0" />}
49
49
  {isLast ? (
50
50
  <span className="text-xs text-neutral-200 font-medium">{level.label}</span>
51
51
  ) : (
@@ -442,7 +442,7 @@ export const NLEPreview = memo(function NLEPreview({
442
442
  <div className="flex flex-col h-full min-h-0">
443
443
  <div
444
444
  ref={viewportRef}
445
- className="relative flex-1 flex items-center justify-center p-2 overflow-hidden min-h-0 outline-none focus:ring-1 focus:ring-studio-accent/40 bg-neutral-950"
445
+ className="relative flex-1 flex items-center justify-center p-2 overflow-hidden min-h-0 outline-hidden focus:ring-1 focus:ring-studio-accent/40 bg-neutral-950"
446
446
  tabIndex={0}
447
447
  aria-label="Composition preview"
448
448
  >
@@ -492,14 +492,14 @@ export const NLEPreview = memo(function NLEPreview({
492
492
  </div>
493
493
  <div
494
494
  ref={hudRef}
495
- className="pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 rounded-lg px-4 py-2 text-sm font-mono tabular-nums text-white/90 bg-black/60 backdrop-blur-sm shadow-lg"
495
+ className="pointer-events-none absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 rounded-lg px-4 py-2 text-sm font-mono tabular-nums text-white/90 bg-black/60 backdrop-blur-xs shadow-lg"
496
496
  style={{ opacity: 0, transition: "opacity 200ms ease-in" }}
497
497
  aria-live="polite"
498
498
  />
499
499
  {!isPreviewAtFit(settledZoom) && (
500
500
  <button
501
501
  type="button"
502
- className="absolute bottom-3 right-3 z-50 rounded-md px-2.5 py-1 text-xs font-medium text-white/80 bg-black/50 backdrop-blur-sm hover:bg-black/70 hover:text-white transition-colors"
502
+ className="absolute bottom-3 right-3 z-50 rounded-md px-2.5 py-1 text-xs font-medium text-white/80 bg-black/50 backdrop-blur-xs hover:bg-black/70 hover:text-white transition-colors"
503
503
  onClick={() => applyZoom(DEFAULT_PREVIEW_ZOOM)}
504
504
  aria-label="Reset zoom to fit"
505
505
  data-testid="preview-reset-zoom"
@@ -222,13 +222,13 @@ export function PreviewOverlays({
222
222
  <TopologyLens iframeRef={previewIframeRef} activeCompositionPath={activeCompPath} />
223
223
  <CaptionOverlay iframeRef={previewIframeRef} />
224
224
  {/* Mode indicator + explicit exit */}
225
- <div className="absolute top-2 left-1/2 -translate-x-1/2 z-[60] flex items-center gap-2 rounded-full border border-studio-accent/40 bg-black/70 px-2.5 py-1">
225
+ <div className="absolute top-2 left-1/2 -translate-x-1/2 z-60 flex items-center gap-2 rounded-full border border-studio-accent/40 bg-black/70 px-2.5 py-1">
226
226
  <span className="h-1.5 w-1.5 rounded-full bg-studio-accent" aria-hidden="true" />
227
227
  <span className="text-2xs text-neutral-200">Editing captions</span>
228
228
  <button
229
229
  type="button"
230
230
  onClick={exitCaptionMode}
231
- className="rounded text-2xs text-neutral-400 underline underline-offset-2 hover:text-neutral-100 focus-visible:outline focus-visible:outline-1 focus-visible:outline-studio-accent"
231
+ className="rounded-sm text-2xs text-neutral-400 underline underline-offset-2 hover:text-neutral-100 focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-studio-accent"
232
232
  >
233
233
  Exit
234
234
  </button>
@@ -236,13 +236,13 @@ export function PreviewOverlays({
236
236
  {captionSyncError && (
237
237
  <div
238
238
  role="alert"
239
- className="absolute top-10 left-1/2 -translate-x-1/2 z-[60] flex items-center gap-2 rounded-full border border-red-500/50 bg-red-950/90 px-2.5 py-1"
239
+ className="absolute top-10 left-1/2 -translate-x-1/2 z-60 flex items-center gap-2 rounded-full border border-red-500/50 bg-red-950/90 px-2.5 py-1"
240
240
  >
241
241
  <span className="text-2xs text-red-200">{captionSyncError}</span>
242
242
  <button
243
243
  type="button"
244
244
  onClick={() => useCaptionStore.getState().retrySave?.()}
245
- className="rounded text-2xs text-red-100 underline underline-offset-2 hover:text-white"
245
+ className="rounded-sm text-2xs text-red-100 underline underline-offset-2 hover:text-white"
246
246
  >
247
247
  Retry
248
248
  </button>
@@ -250,7 +250,7 @@ export function PreviewOverlays({
250
250
  type="button"
251
251
  onClick={() => useCaptionStore.getState().setSyncError(null)}
252
252
  aria-label="Dismiss"
253
- className="rounded px-0.5 text-2xs text-red-300/70 hover:text-red-100"
253
+ className="rounded-sm px-0.5 text-2xs text-red-300/70 hover:text-red-100"
254
254
  >
255
255
  ✕
256
256
  </button>
@@ -338,7 +338,7 @@ export function PreviewOverlays({
338
338
  <button
339
339
  type="button"
340
340
  onClick={enterCaptionMode}
341
- className="absolute top-2 left-1/2 -translate-x-1/2 z-[60] rounded-full border border-neutral-700 bg-black/60 px-2.5 py-1 text-2xs text-neutral-300 transition-colors hover:border-studio-accent/50 hover:text-studio-accent focus-visible:outline focus-visible:outline-1 focus-visible:outline-studio-accent"
341
+ className="absolute top-2 left-1/2 -translate-x-1/2 z-60 rounded-full border border-neutral-700 bg-black/60 px-2.5 py-1 text-2xs text-neutral-300 transition-colors hover:border-studio-accent/50 hover:text-studio-accent focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-studio-accent"
342
342
  >
343
343
  Edit captions
344
344
  </button>
@@ -139,7 +139,7 @@ export function PreviewPane({
139
139
  onCompositionSizeChange={setPreviewCompositionSize}
140
140
  />
141
141
  {previewDragOver && (
142
- <div className="absolute inset-2 z-40 rounded-lg border-2 border-dashed border-studio-accent/50 bg-studio-accent/[0.04] pointer-events-none" />
142
+ <div className="absolute inset-2 z-40 rounded-lg border-2 border-dashed border-studio-accent/50 bg-studio-accent/4 pointer-events-none" />
143
143
  )}
144
144
  <AssetPreviewOverlay />
145
145
  </div>
@@ -148,7 +148,7 @@ export function PreviewPane({
148
148
  </div>
149
149
  {/* Transport row: no own background or border — the controls sit flat on
150
150
  the preview panel's surface (CapCut-style). */}
151
- <div className="flex-shrink-0">
151
+ <div className="shrink-0">
152
152
  {!isFullscreen && compositionStack.length > 1 && (
153
153
  <CompositionBreadcrumb stack={compositionStack} onNavigate={handleNavigateComposition} />
154
154
  )}
@@ -266,7 +266,7 @@ export function TimelinePane({
266
266
  {/* Timeline section — inner padding (not margin) keeps the divider's
267
267
  height math exact while giving the panel a gap from the shell edges. */}
268
268
  <div
269
- className="relative flex flex-col flex-shrink-0 px-px pb-px"
269
+ className="relative flex flex-col shrink-0 px-px pb-px"
270
270
  style={{ height: timelineH }}
271
271
  aria-disabled={timelineDisabled || undefined}
272
272
  >
@@ -280,7 +280,7 @@ export function TimelinePane({
280
280
  }
281
281
  }}
282
282
  >
283
- <div className="flex-shrink-0">{timelineToolbar}</div>
283
+ <div className="shrink-0">{timelineToolbar}</div>
284
284
  <Timeline
285
285
  sessionEpoch={timelineSessionEpoch}
286
286
  onSeek={seek}
@@ -304,7 +304,7 @@ export function TimelinePane({
304
304
  canPasteClip={canPasteClip}
305
305
  />
306
306
  </div>
307
- {timelineFooter && <div className="flex-shrink-0">{timelineFooter}</div>}
307
+ {timelineFooter && <div className="shrink-0">{timelineFooter}</div>}
308
308
  {timelineDisabled && (
309
309
  <div
310
310
  className="absolute inset-0 z-30 cursor-not-allowed bg-black/18 flex items-center justify-center"
@@ -79,7 +79,7 @@ export function TimelineResizeDivider({
79
79
  (containerRef.current?.getBoundingClientRect().height ?? 600) - MIN_PREVIEW_H,
80
80
  )}
81
81
  tabIndex={0}
82
- className="group relative h-[3px] flex-shrink-0 cursor-row-resize z-10 outline-none focus-visible:bg-studio-accent/20"
82
+ className="group relative h-[3px] shrink-0 cursor-row-resize z-10 outline-hidden focus-visible:bg-studio-accent/20"
83
83
  style={{ touchAction: "none" }}
84
84
  onPointerDown={handlePointerDown}
85
85
  onPointerMove={handlePointerMove}
@@ -93,7 +93,7 @@ export function TimelineResizeDivider({
93
93
  the 24px WCAG 2.2 (2.5.8) target because that is all the dead space
94
94
  there is: 28px transport buttons sit above and 28px toolbar buttons
95
95
  below, and silently stealing their clicks is the worse bug. */}
96
- <div className="absolute inset-x-0 -top-[4px] h-[10px]" />
96
+ <div className="absolute inset-x-0 top-[-4px] h-[10px]" />
97
97
  {/* Visible hairline — invisible at rest, subtle wash on hover/drag/focus */}
98
98
  <div className="h-[3px] w-full bg-transparent transition-colors group-hover:bg-white/12 group-active:bg-white/18 group-focus-visible:bg-studio-accent/60" />
99
99
  </div>
@@ -435,7 +435,7 @@ export function SlideshowPanel({ scenes, onPersist, onPersistNotes }: SlideshowP
435
435
  type="button"
436
436
  disabled={retrying}
437
437
  onClick={handleRetryPersist}
438
- className="px-2 py-0.5 text-[10px] rounded bg-red-600 text-white enabled:hover:bg-red-500 enabled:active:scale-[0.97] disabled:opacity-50 transition-colors"
438
+ className="px-2 py-0.5 text-[10px] rounded-sm bg-red-600 text-white enabled:hover:bg-red-500 enabled:active:scale-[0.97] disabled:opacity-50 transition-colors"
439
439
  >
440
440
  {retrying ? "Retrying…" : "Retry"}
441
441
  </button>
@@ -447,7 +447,7 @@ export function SlideshowPanel({ scenes, onPersist, onPersistNotes }: SlideshowP
447
447
  type="button"
448
448
  onClick={handleUndo}
449
449
  title="Undo last slideshow edit (⌘Z)"
450
- className="px-2 py-0.5 text-[10px] rounded text-neutral-400 hover:text-neutral-200 hover:bg-neutral-800 active:scale-[0.97] transition-colors"
450
+ className="px-2 py-0.5 text-[10px] rounded-sm text-neutral-400 hover:text-neutral-200 hover:bg-neutral-800 active:scale-[0.97] transition-colors"
451
451
  >
452
452
  Undo ({undoDepth})
453
453
  </button>
@@ -90,11 +90,11 @@ export function SlideList({
90
90
  checked={isSlide}
91
91
  onChange={() => onToggle(scene.id)}
92
92
  onClick={(e) => e.stopPropagation()}
93
- className="accent-studio-accent flex-shrink-0"
93
+ className="accent-studio-accent shrink-0"
94
94
  />
95
95
  <span className="flex-1 truncate">{scene.label || scene.id}</span>
96
96
  {isSlide && (
97
- <span className="flex gap-0.5 flex-shrink-0">
97
+ <span className="flex gap-0.5 shrink-0">
98
98
  <button
99
99
  type="button"
100
100
  aria-label="Move slide up"
@@ -162,7 +162,7 @@ export function SlideInspector({
162
162
  <div className="flex flex-col gap-1">
163
163
  <label className="text-[11px] text-neutral-400">Notes</label>
164
164
  <textarea
165
- className="bg-neutral-800 border border-neutral-700 rounded px-2 py-1.5 text-[11px] text-white resize-none placeholder-neutral-600 focus:border-studio-accent/60 focus:outline-none"
165
+ className="bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1.5 text-[11px] text-white resize-none placeholder-neutral-600 focus:border-studio-accent/60 focus:outline-hidden"
166
166
  rows={3}
167
167
  placeholder="Speaker notes or script..."
168
168
  value={slide?.notes ?? ""}
@@ -174,7 +174,7 @@ export function SlideInspector({
174
174
  <span className="text-[11px] text-neutral-400">Fragment hold-points</span>
175
175
  <button
176
176
  type="button"
177
- className="text-[10px] px-2 py-0.5 rounded bg-neutral-700 hover:bg-neutral-600 text-neutral-200 transition-colors"
177
+ className="text-[10px] px-2 py-0.5 rounded-sm bg-neutral-700 hover:bg-neutral-600 text-neutral-200 transition-colors"
178
178
  onClick={onMarkFragment}
179
179
  title={`Mark ${currentTime.toFixed(2)}s as hold-point`}
180
180
  >
@@ -186,7 +186,7 @@ export function SlideInspector({
186
186
  {fragments.map((t, i) => (
187
187
  <span
188
188
  key={`frag-${i}`}
189
- className="inline-flex items-center gap-1 bg-neutral-700 rounded px-1.5 py-0.5 text-[10px] text-neutral-200"
189
+ className="inline-flex items-center gap-1 bg-neutral-700 rounded-sm px-1.5 py-0.5 text-[10px] text-neutral-200"
190
190
  >
191
191
  {t.toFixed(2)}s
192
192
  <button
@@ -249,7 +249,7 @@ export function BranchTree({
249
249
  <input
250
250
  id={inputId}
251
251
  type="text"
252
- className="flex-1 bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-[11px] text-white placeholder-neutral-600 focus:border-studio-accent/60 focus:outline-none"
252
+ className="flex-1 bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1 text-[11px] text-white placeholder-neutral-600 focus:border-studio-accent/60 focus:outline-hidden"
253
253
  placeholder="New branch name..."
254
254
  value={newLabel}
255
255
  onChange={(e) => setNewLabel(e.target.value)}
@@ -260,7 +260,7 @@ export function BranchTree({
260
260
  />
261
261
  <button
262
262
  type="button"
263
- className="px-2 py-1 rounded bg-neutral-700 hover:bg-neutral-600 text-[11px] text-neutral-200 transition-colors flex-shrink-0"
263
+ className="px-2 py-1 rounded-sm bg-neutral-700 hover:bg-neutral-600 text-[11px] text-neutral-200 transition-colors shrink-0"
264
264
  onClick={handleCreate}
265
265
  >
266
266
  Add
@@ -322,11 +322,11 @@ function BranchItem({
322
322
  }, [draft, onRename, seq.id, seq.label]);
323
323
 
324
324
  return (
325
- <div className="border border-neutral-700/60 rounded p-2 flex flex-col gap-2">
325
+ <div className="border border-neutral-700/60 rounded-sm p-2 flex flex-col gap-2">
326
326
  <div className="flex items-center gap-1">
327
327
  {editing ? (
328
328
  <input
329
- className="flex-1 bg-neutral-800 border border-neutral-600 rounded px-1.5 py-0.5 text-[11px] text-white focus:border-studio-accent/60 focus:outline-none"
329
+ className="flex-1 bg-neutral-800 border border-neutral-600 rounded-sm px-1.5 py-0.5 text-[11px] text-white focus:border-studio-accent/60 focus:outline-hidden"
330
330
  value={draft}
331
331
  autoFocus
332
332
  onChange={(e) => setDraft(e.target.value)}
@@ -364,7 +364,7 @@ function BranchItem({
364
364
  </button>
365
365
  </div>
366
366
  {confirmingDelete && (
367
- <div className="px-2 py-1.5 bg-red-950/30 border-l-2 border-red-500 flex flex-col gap-1 rounded-sm">
367
+ <div className="px-2 py-1.5 bg-red-950/30 border-l-2 border-red-500 flex flex-col gap-1 rounded-xs">
368
368
  <span className="text-[10px] text-red-400">
369
369
  Delete branch &ldquo;{seq.label}&rdquo;
370
370
  {seq.slides.length > 0
@@ -379,14 +379,14 @@ function BranchItem({
379
379
  setConfirmingDelete(false);
380
380
  onDelete(seq.id);
381
381
  }}
382
- className="px-2 py-0.5 text-[10px] rounded bg-red-600 text-white hover:bg-red-500 active:bg-red-700 transition-colors"
382
+ className="px-2 py-0.5 text-[10px] rounded-sm bg-red-600 text-white hover:bg-red-500 active:bg-red-700 transition-colors"
383
383
  >
384
384
  Delete
385
385
  </button>
386
386
  <button
387
387
  type="button"
388
388
  onClick={() => setConfirmingDelete(false)}
389
- className="px-2 py-0.5 text-[10px] rounded text-neutral-400 hover:text-neutral-200 transition-colors"
389
+ className="px-2 py-0.5 text-[10px] rounded-sm text-neutral-400 hover:text-neutral-200 transition-colors"
390
390
  >
391
391
  Cancel
392
392
  </button>
@@ -407,7 +407,7 @@ function BranchItem({
407
407
  aria-label={`Assign ${scene.label || scene.id} to branch ${seq.label}`}
408
408
  checked={assigned}
409
409
  onChange={(e) => onAssign(seq.id, scene.id, e.target.checked)}
410
- className="accent-studio-accent flex-shrink-0"
410
+ className="accent-studio-accent shrink-0"
411
411
  />
412
412
  {assigned ? (
413
413
  <button
@@ -497,7 +497,7 @@ export function HotspotTool({
497
497
  <label className="text-[11px] text-neutral-400">Hotspot label</label>
498
498
  <input
499
499
  type="text"
500
- className="bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-[11px] text-white placeholder-neutral-600 focus:border-studio-accent/60 focus:outline-none"
500
+ className="bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1 text-[11px] text-white placeholder-neutral-600 focus:border-studio-accent/60 focus:outline-hidden"
501
501
  placeholder="Button label..."
502
502
  value={hotspotLabel}
503
503
  onChange={(e) => setHotspotLabel(e.target.value)}
@@ -505,7 +505,7 @@ export function HotspotTool({
505
505
  />
506
506
  <label className="text-[11px] text-neutral-400">Target branch</label>
507
507
  <select
508
- className="bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-[11px] text-white focus:border-studio-accent/60 focus:outline-none"
508
+ className="bg-neutral-800 border border-neutral-700 rounded-sm px-2 py-1 text-[11px] text-white focus:border-studio-accent/60 focus:outline-hidden"
509
509
  value={targetSequenceId}
510
510
  onChange={(e) => setTargetSequenceId(e.target.value)}
511
511
  aria-label="Target branch sequence"
@@ -527,7 +527,7 @@ export function HotspotTool({
527
527
  ? "Choose a target branch first"
528
528
  : undefined
529
529
  }
530
- className="px-3 py-1.5 rounded bg-studio-accent/80 enabled:hover:bg-studio-accent enabled:active:scale-[0.98] text-white text-[11px] font-medium transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
530
+ className="px-3 py-1.5 rounded-sm bg-studio-accent/80 enabled:hover:bg-studio-accent enabled:active:scale-[0.98] text-white text-[11px] font-medium transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
531
531
  onClick={handleMakeHotspot}
532
532
  >
533
533
  Make hotspot
@@ -540,7 +540,10 @@ export function HotspotTool({
540
540
  {hotspots.map((h) => {
541
541
  const seqLabel = sequences.find((s) => s.id === h.target)?.label ?? h.target;
542
542
  return (
543
- <div key={h.id} className="flex items-center gap-2 bg-neutral-800 rounded px-2 py-1">
543
+ <div
544
+ key={h.id}
545
+ className="flex items-center gap-2 bg-neutral-800 rounded-sm px-2 py-1"
546
+ >
544
547
  <span className="flex-1 text-[11px] text-neutral-200 truncate">
545
548
  {h.label} → <span className="text-neutral-400">{seqLabel}</span>
546
549
  </span>
@@ -227,7 +227,7 @@ export function VariablesBindElement({
227
227
  <button
228
228
  type="button"
229
229
  onClick={() => setActiveKey(null)}
230
- className="h-6 rounded px-2 text-[10px] text-neutral-500 hover:text-neutral-300"
230
+ className="h-6 rounded-sm px-2 text-[10px] text-neutral-500 hover:text-neutral-300"
231
231
  >
232
232
  Cancel
233
233
  </button>
@@ -238,7 +238,7 @@ export function VariablesBindElement({
238
238
  setActiveKey(null);
239
239
  onBind(active, trimmedId);
240
240
  }}
241
- className="h-6 rounded bg-neutral-800 px-2 text-[10px] font-medium text-neutral-200 hover:bg-neutral-700 disabled:cursor-not-allowed disabled:opacity-40"
241
+ className="h-6 rounded-sm bg-neutral-800 px-2 text-[10px] font-medium text-neutral-200 hover:bg-neutral-700 disabled:cursor-not-allowed disabled:opacity-40"
242
242
  >
243
243
  {existingDecl ? "Bind anyway" : "Bind"}
244
244
  </button>
@@ -304,14 +304,14 @@ export function DeclarationForm({
304
304
  <button
305
305
  type="button"
306
306
  onClick={onCancel}
307
- className="h-6 rounded px-2 text-[10px] text-neutral-500 hover:text-neutral-300"
307
+ className="h-6 rounded-sm px-2 text-[10px] text-neutral-500 hover:text-neutral-300"
308
308
  >
309
309
  Cancel
310
310
  </button>
311
311
  <button
312
312
  type="button"
313
313
  onClick={submit}
314
- className="h-6 rounded bg-neutral-800 px-2 text-[10px] font-medium text-neutral-200 hover:bg-neutral-700"
314
+ className="h-6 rounded-sm bg-neutral-800 px-2 text-[10px] font-medium text-neutral-200 hover:bg-neutral-700"
315
315
  >
316
316
  {submitLabel}
317
317
  </button>
@@ -51,7 +51,7 @@ function CompositionSection({
51
51
  <span className="truncate text-[10px] font-medium text-neutral-300">
52
52
  {decl.label}
53
53
  </span>
54
- <span className="rounded bg-neutral-800 px-1 py-px font-mono text-[8px] text-neutral-500">
54
+ <span className="rounded-sm bg-neutral-800 px-1 py-px font-mono text-[8px] text-neutral-500">
55
55
  {decl.type}
56
56
  </span>
57
57
  <span className="ml-auto flex items-center gap-1">
@@ -96,12 +96,12 @@ function VariableRow({
96
96
  <div className="space-y-1.5 rounded-lg border border-neutral-800/70 p-2">
97
97
  <div className="flex items-center gap-1.5">
98
98
  <span className="truncate text-[10px] font-medium text-neutral-300">{decl.label}</span>
99
- <span className="rounded bg-neutral-800 px-1 py-px font-mono text-[8px] text-neutral-500">
99
+ <span className="rounded-sm bg-neutral-800 px-1 py-px font-mono text-[8px] text-neutral-500">
100
100
  {decl.type}
101
101
  </span>
102
102
  {unused && (
103
103
  <span
104
- className="rounded bg-amber-900/40 px-1 py-px text-[8px] text-amber-400"
104
+ className="rounded-sm bg-amber-900/40 px-1 py-px text-[8px] text-amber-400"
105
105
  title="No script reads this variable"
106
106
  >
107
107
  unused
@@ -189,7 +189,7 @@ function PreviewModeHeader({
189
189
  <button
190
190
  type="button"
191
191
  onClick={onReset}
192
- className="h-6 rounded px-2 text-[10px] text-neutral-400 hover:text-neutral-200"
192
+ className="h-6 rounded-sm px-2 text-[10px] text-neutral-400 hover:text-neutral-200"
193
193
  >
194
194
  Reset
195
195
  </button>
@@ -14,7 +14,7 @@ import type {
14
14
  } from "@hyperframes/sdk";
15
15
 
16
16
  export const VARIABLES_INPUT_CLASS =
17
- "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";
17
+ "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";
18
18
 
19
19
  /** Text input that drafts locally and commits on blur/Enter. */
20
20
  function DraftTextInput({
@@ -106,7 +106,7 @@ function ColorControl({
106
106
  if (draft !== null && draft !== colorValue) onCommit(draft);
107
107
  setDraft(null);
108
108
  }}
109
- className="h-6 w-6 cursor-pointer rounded border border-neutral-700 bg-transparent"
109
+ className="h-6 w-6 cursor-pointer rounded-sm border border-neutral-700 bg-transparent"
110
110
  />
111
111
  <DraftTextInput
112
112
  value={colorValue}
@@ -8,7 +8,7 @@ const CUE_MS = 1600;
8
8
  // Matches the focus treatment every other button in this panel uses. A control
9
9
  // the keyboard can reach but not see focus on is unusable without a mouse.
10
10
  const FOCUS_RING =
11
- "outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-studio-accent";
11
+ "outline-hidden focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-studio-accent";
12
12
 
13
13
  /**
14
14
  * Shown above Export when the dev server reports no usable FFmpeg.
@@ -81,7 +81,7 @@ export const FfmpegRequiredNotice = memo(function FfmpegRequiredNotice({
81
81
 
82
82
  {status.command ? (
83
83
  <div className="flex items-center gap-1.5">
84
- <code className="flex-1 overflow-x-auto whitespace-nowrap rounded-sm bg-black/40 px-2 py-1 text-[10px] text-panel-text-2">
84
+ <code className="flex-1 overflow-x-auto whitespace-nowrap rounded-xs bg-black/40 px-2 py-1 text-[10px] text-panel-text-2">
85
85
  {status.command}
86
86
  </code>
87
87
  {/* Fixed width so swapping the label to "Copied" cannot shift the
@@ -89,7 +89,7 @@ export const FfmpegRequiredNotice = memo(function FfmpegRequiredNotice({
89
89
  <button
90
90
  type="button"
91
91
  onClick={() => void copy(status.command ?? "")}
92
- className={`h-6 w-14 flex-shrink-0 rounded-sm border border-amber-500/30 text-[10px] font-medium text-amber-200 transition-colors hover:bg-amber-500/20 active:scale-[0.98] ${FOCUS_RING}`}
92
+ className={`h-6 w-14 shrink-0 rounded-xs border border-amber-500/30 text-[10px] font-medium text-amber-200 transition-colors hover:bg-amber-500/20 active:scale-[0.98] ${FOCUS_RING}`}
93
93
  >
94
94
  {copied ? "Copied" : "Copy"}
95
95
  </button>
@@ -107,7 +107,7 @@ export const FfmpegRequiredNotice = memo(function FfmpegRequiredNotice({
107
107
  type="button"
108
108
  onClick={onRecheck}
109
109
  disabled={checking}
110
- className={`rounded-sm py-0.5 text-[10px] font-medium text-amber-200 underline-offset-2 transition-colors hover:underline disabled:opacity-50 ${FOCUS_RING}`}
110
+ className={`rounded-xs py-0.5 text-[10px] font-medium text-amber-200 underline-offset-2 transition-colors hover:underline disabled:opacity-50 ${FOCUS_RING}`}
111
111
  >
112
112
  {checking ? "Checking…" : "Recheck"}
113
113
  </button>
@@ -115,7 +115,7 @@ export const FfmpegRequiredNotice = memo(function FfmpegRequiredNotice({
115
115
  href={DOWNLOAD_URL}
116
116
  target="_blank"
117
117
  rel="noreferrer"
118
- className={`rounded-sm py-0.5 text-[10px] text-panel-text-2 underline-offset-2 transition-colors hover:text-panel-text-0 hover:underline ${FOCUS_RING}`}
118
+ className={`rounded-xs py-0.5 text-[10px] text-panel-text-2 underline-offset-2 transition-colors hover:text-panel-text-0 hover:underline ${FOCUS_RING}`}
119
119
  >
120
120
  Other install options
121
121
  </a>
@@ -81,6 +81,13 @@ function exportButtonIn(host: HTMLElement): HTMLButtonElement {
81
81
  return button;
82
82
  }
83
83
 
84
+ describe("RenderQueue Export button", () => {
85
+ it("leaves the font size to the shared Button", () => {
86
+ const host = mountRenderQueue(vi.fn());
87
+ expect(exportButtonIn(host).className).not.toMatch(/text-\[/);
88
+ });
89
+ });
90
+
84
91
  describe("RenderQueue FFmpeg gate", () => {
85
92
  it("refuses Export and shows the install command when the server reports no FFmpeg", () => {
86
93
  ffmpegStatus = {
@@ -207,7 +207,7 @@ function FormatInfoTooltip({ format }: { format: "mp4" | "webm" | "mov" }) {
207
207
  onFocus={show}
208
208
  onBlur={hide}
209
209
  onClick={() => setOpen((prev) => !prev)}
210
- className="flex items-center justify-center p-0.5 -m-0.5 rounded text-panel-text-5 hover:text-panel-text-3 transition-colors cursor-help outline-none focus-visible:outline focus-visible:outline-2 focus-visible:outline-studio-accent"
210
+ className="flex items-center justify-center p-0.5 -m-0.5 rounded-sm text-panel-text-5 hover:text-panel-text-3 transition-colors cursor-help outline-hidden focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-studio-accent"
211
211
  >
212
212
  <svg
213
213
  width="12"
@@ -232,7 +232,7 @@ function FormatInfoTooltip({ format }: { format: "mp4" | "webm" | "mov" }) {
232
232
  role="tooltip"
233
233
  onPointerEnter={show}
234
234
  onPointerLeave={hide}
235
- className="fixed w-52 p-2 rounded bg-panel-input border border-neutral-700 shadow-lg z-[200]"
235
+ className="fixed w-52 p-2 rounded-sm bg-panel-input border border-neutral-700 shadow-lg z-200"
236
236
  style={{ left: position?.left ?? -9999, top: position?.top ?? -9999 }}
237
237
  >
238
238
  <p className="text-[10px] font-semibold text-panel-text-1 mb-0.5">{info.label}</p>
@@ -303,7 +303,7 @@ function FormatExportButton({
303
303
  const showQuality = format !== "mov";
304
304
 
305
305
  const selectCls =
306
- "h-7 w-full px-2 text-[11px] bg-panel-input rounded-md text-panel-text-1 outline-none cursor-pointer disabled:opacity-50 hover:bg-panel-hover transition-colors";
306
+ "h-7 w-full px-2 text-[11px] bg-panel-input rounded-md text-panel-text-1 outline-hidden cursor-pointer disabled:opacity-50 hover:bg-panel-hover transition-colors";
307
307
 
308
308
  return (
309
309
  <div className="flex flex-col gap-3">
@@ -407,7 +407,7 @@ function FormatExportButton({
407
407
  trackStudioEvent("render_start", { format, quality, resolution: outputResolution, fps });
408
408
  void onStartRender(format, quality, outputResolution, fps);
409
409
  }}
410
- className="w-full text-[11px] font-semibold"
410
+ className="w-full font-semibold"
411
411
  >
412
412
  {isRendering ? "Rendering…" : "Export"}
413
413
  </Button>
@@ -454,7 +454,7 @@ export const RenderQueue = memo(function RenderQueue({
454
454
 
455
455
  return (
456
456
  <div className="flex flex-col h-full">
457
- <div className="px-3 py-3 border-b border-panel-border flex-shrink-0">
457
+ <div className="px-3 py-3 border-b border-panel-border shrink-0">
458
458
  <FormatExportButton
459
459
  onStartRender={onStartRender}
460
460
  isRendering={isRendering}
@@ -476,7 +476,7 @@ export const RenderQueue = memo(function RenderQueue({
476
476
  <button
477
477
  onClick={onDismissActionError}
478
478
  aria-label="Dismiss error"
479
- className="text-[10px] text-panel-text-4 hover:text-panel-text-2 flex-shrink-0"
479
+ className="text-[10px] text-panel-text-4 hover:text-panel-text-2 shrink-0"
480
480
  >
481
481
  ✕
482
482
  </button>