@hyperframes/studio 0.8.103 → 0.8.105

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 (200) hide show
  1. package/dist/assets/{hyperframes-player-DG795Hwu.js → hyperframes-player-tnfCiVdR.js} +1 -1
  2. package/dist/assets/{index-BiEtDkae.js → index-Ca6LZlVb.js} +1 -1
  3. package/dist/assets/index-Cre9X4RS.css +1 -0
  4. package/dist/assets/{index-BPJRf2X9.js → index-DEDhPmPD.js} +215 -215
  5. package/dist/assets/{index-DuCZ3pQw.js → index-zpe332hb.js} +1 -1
  6. package/dist/{chunk-62DDWOSH.js → chunk-EEXSYY5F.js} +19 -18
  7. package/dist/chunk-EEXSYY5F.js.map +1 -0
  8. package/dist/{chunk-6GAO75IU.js → chunk-NCUBAVGK.js} +10 -8
  9. package/dist/chunk-NCUBAVGK.js.map +1 -0
  10. package/dist/{chunk-HIGHVWYD.js → chunk-QADHJFDF.js} +2 -2
  11. package/dist/index.css +6 -1
  12. package/dist/index.css.map +1 -1
  13. package/dist/index.d.ts +181 -168
  14. package/dist/index.html +2 -2
  15. package/dist/index.js +1018 -919
  16. package/dist/index.js.map +1 -1
  17. package/dist/player.js +2 -2
  18. package/dist/styles/tailwind-preset.js.map +1 -1
  19. package/dist/styles.css +1 -1
  20. package/dist/ui.js +2 -2
  21. package/package.json +8 -8
  22. package/src/captions/components/CaptionAnimationPanel.tsx +2 -2
  23. package/src/captions/components/CaptionPropertyPanel.tsx +2 -2
  24. package/src/captions/components/CaptionTimeline.tsx +3 -3
  25. package/src/components/AskAgentModal.tsx +1 -1
  26. package/src/components/LintModal.tsx +35 -35
  27. package/src/components/StudioErrorBoundary.tsx +1 -1
  28. package/src/components/StudioHeader.dom.test.tsx +4 -4
  29. package/src/components/StudioHeader.tsx +2 -1
  30. package/src/components/StudioToast.tsx +7 -16
  31. package/src/components/TimelineToolPicker.tsx +1 -1
  32. package/src/components/TimelineToolbar.tsx +11 -11
  33. package/src/components/dock/Dock.tsx +1 -1
  34. package/src/components/dock/dock.css +9 -1
  35. package/src/components/editor/AnimationCard.tsx +5 -5
  36. package/src/components/editor/AnimationCardParts.tsx +1 -1
  37. package/src/components/editor/BlockParamsPanel.tsx +3 -3
  38. package/src/components/editor/BorderRadiusEditor.tsx +8 -15
  39. package/src/components/editor/CanvasContextMenu.tsx +1 -1
  40. package/src/components/editor/ComputedTweenNotice.tsx +1 -1
  41. package/src/components/editor/DomEditOverlay.tsx +5 -6
  42. package/src/components/editor/DomEditRotateHandle.tsx +1 -1
  43. package/src/components/editor/EaseCurveSection.tsx +14 -19
  44. package/src/components/editor/EaseModeControls.tsx +2 -2
  45. package/src/components/editor/EaseParamFields.tsx +8 -8
  46. package/src/components/editor/FileTree.tsx +1 -1
  47. package/src/components/editor/FileTreeIcons.tsx +47 -26
  48. package/src/components/editor/FileTreeNodes.tsx +11 -8
  49. package/src/components/editor/GestureRecordControl.tsx +3 -3
  50. package/src/components/editor/GsapAddAnimationControl.tsx +1 -1
  51. package/src/components/editor/GsapAnimationSection.tsx +2 -2
  52. package/src/components/editor/InlineTextToolbar.tsx +4 -4
  53. package/src/components/editor/InspectorHeaderActions.tsx +1 -1
  54. package/src/components/editor/KeyframeDiamond.tsx +6 -4
  55. package/src/components/editor/KeyframeNavigation.tsx +4 -4
  56. package/src/components/editor/LayersPanel.tsx +3 -3
  57. package/src/components/editor/PromotableControl.tsx +2 -2
  58. package/src/components/editor/PropertyPanel.tsx +3 -3
  59. package/src/components/editor/PropertyPanelEmptyState.tsx +4 -4
  60. package/src/components/editor/PropertyPanelFlatFooter.tsx +2 -2
  61. package/src/components/editor/PropertyPanelFlatHeader.test.tsx +1 -1
  62. package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
  63. package/src/components/editor/SnapToolbar.tsx +5 -5
  64. package/src/components/editor/TimelineFxPopover.tsx +4 -4
  65. package/src/components/editor/Transform3DCube.tsx +1 -1
  66. package/src/components/editor/anchoredResizeCommitFeedsOffset.test.ts +46 -6
  67. package/src/components/editor/anchoredResizeReleaseShift.test.ts +0 -84
  68. package/src/components/editor/domEditOverlayGestures.ts +13 -19
  69. package/src/components/editor/domEditOverlayStartGesture.test.ts +170 -13
  70. package/src/components/editor/domEditOverlayStartGesture.ts +15 -25
  71. package/src/components/editor/easeCurveSvg.tsx +1 -1
  72. package/src/components/editor/manualEdits.test.ts +3 -9
  73. package/src/components/editor/manualEdits.ts +0 -1
  74. package/src/components/editor/manualEditsDom.ts +3 -13
  75. package/src/components/editor/manualEditsDomPatches.test.ts +3 -131
  76. package/src/components/editor/manualEditsDomPatches.ts +1 -53
  77. package/src/components/editor/manualEditsSnapshot.ts +3 -0
  78. package/src/components/editor/manualEditsTypes.ts +2 -0
  79. package/src/components/editor/manualOffsetDrag.test.ts +41 -0
  80. package/src/components/editor/manualOffsetDrag.ts +6 -27
  81. package/src/components/editor/persistSeam.integration.test.ts +0 -18
  82. package/src/components/editor/plainTranslate.ts +42 -6
  83. package/src/components/editor/propertyPanelColorCurveGraph.tsx +2 -2
  84. package/src/components/editor/propertyPanelColorGradingControls.tsx +1 -1
  85. package/src/components/editor/propertyPanelColorGradingSection.tsx +8 -8
  86. package/src/components/editor/propertyPanelColorGradingSlider.tsx +1 -1
  87. package/src/components/editor/propertyPanelColorSecondary.tsx +4 -4
  88. package/src/components/editor/propertyPanelFill.tsx +6 -6
  89. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
  90. package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +1 -1
  91. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +6 -9
  92. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
  93. package/src/components/editor/propertyPanelFlatMediaSection.tsx +2 -2
  94. package/src/components/editor/propertyPanelFlatMotionSection.tsx +2 -2
  95. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +2 -2
  96. package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +4 -3
  97. package/src/components/editor/propertyPanelFlatPrimitives.tsx +1 -1
  98. package/src/components/editor/propertyPanelFlatSliderReadout.tsx +1 -1
  99. package/src/components/editor/propertyPanelFont.tsx +2 -2
  100. package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
  101. package/src/components/editor/propertyPanelFxControls.tsx +2 -2
  102. package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
  103. package/src/components/editor/propertyPanelFxNodeRow.tsx +1 -1
  104. package/src/components/editor/propertyPanelFxPresetRun.tsx +1 -1
  105. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  106. package/src/components/editor/propertyPanelPrimitives.tsx +1 -1
  107. package/src/components/editor/propertyPanelSections.tsx +2 -2
  108. package/src/components/editor/propertyPanelTransformCommit.test.ts +29 -0
  109. package/src/components/editor/propertyPanelTransformCommit.ts +8 -5
  110. package/src/components/editor/propertyPanelTypes.ts +1 -0
  111. package/src/components/editor/propertyPanelValueTier.test.ts +1 -1
  112. package/src/components/editor/propertyPanelValueTier.ts +1 -1
  113. package/src/components/editor/rotateInlineSave.test.ts +111 -0
  114. package/src/components/editor/rotationDraft.test.ts +111 -0
  115. package/src/components/editor/rotationDraft.ts +145 -0
  116. package/src/components/editor/useDomEditNudge.test.tsx +33 -0
  117. package/src/components/editor/useDomEditNudge.ts +6 -2
  118. package/src/components/editor/useDomEditOverlayGestures.ts +22 -24
  119. package/src/components/feedback/StudioFeedbackCard.tsx +11 -23
  120. package/src/components/nle/AssetPreviewOverlay.tsx +1 -1
  121. package/src/components/nle/AudioMeterStrip.tsx +2 -2
  122. package/src/components/nle/NLEPreview.test.ts +8 -4
  123. package/src/components/nle/PreviewOverlays.tsx +1 -1
  124. package/src/components/nle/PreviewPane.tsx +3 -3
  125. package/src/components/nle/PreviewPoster.tsx +29 -1
  126. package/src/components/nle/PreviewZoomOverlay.tsx +1 -1
  127. package/src/components/panels/SlideshowPanel.tsx +4 -4
  128. package/src/components/panels/SlideshowSubPanels.tsx +19 -21
  129. package/src/components/panels/VariablesBindElement.tsx +1 -1
  130. package/src/components/panels/VariablesDeclarationForm.tsx +1 -1
  131. package/src/components/panels/VariablesPanel.tsx +4 -6
  132. package/src/components/panels/VariablesRowAction.tsx +1 -1
  133. package/src/components/renders/FfmpegRequiredNotice.tsx +4 -4
  134. package/src/components/renders/RenderQueue.tsx +2 -2
  135. package/src/components/renders/RenderQueueItem.tsx +3 -3
  136. package/src/components/sidebar/AssetCard.tsx +3 -3
  137. package/src/components/sidebar/AssetContextMenu.tsx +7 -7
  138. package/src/components/sidebar/AssetsTab.tsx +6 -6
  139. package/src/components/sidebar/AudioRow.tsx +5 -3
  140. package/src/components/sidebar/BlocksTab.tsx +6 -6
  141. package/src/components/sidebar/CompositionsTab.drag.test.tsx +3 -69
  142. package/src/components/sidebar/CompositionsTab.rootBadge.test.tsx +4 -50
  143. package/src/components/sidebar/CompositionsTab.thumbnails.test.tsx +256 -0
  144. package/src/components/sidebar/CompositionsTab.tsx +65 -12
  145. package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
  146. package/src/components/sidebar/SidebarLintButton.tsx +3 -3
  147. package/src/components/sidebar/compositionsTabTestUtils.tsx +39 -0
  148. package/src/components/timelineToolbarStyles.ts +3 -3
  149. package/src/components/ui/Button.tsx +4 -4
  150. package/src/components/ui/HyperframesLogo.tsx +19 -18
  151. package/src/components/ui/Input.tsx +1 -1
  152. package/src/components/ui/Menu.tsx +4 -2
  153. package/src/hooks/draggedGsapPosition.ts +28 -0
  154. package/src/hooks/elementOffsetStager.ts +34 -13
  155. package/src/hooks/gsapRuntimeBridge.ts +5 -3
  156. package/src/hooks/gsapRuntimeKeyframes.ts +36 -2
  157. package/src/hooks/plainRotation.test.ts +122 -0
  158. package/src/hooks/plainRotation.ts +42 -0
  159. package/src/hooks/useClipboard.source.test.ts +42 -4
  160. package/src/hooks/useClipboard.ts +13 -3
  161. package/src/hooks/useDomEditCommits.ts +0 -3
  162. package/src/hooks/useDomEditSession.ts +5 -5
  163. package/src/hooks/useDomGeometryCommit.test.tsx +79 -1
  164. package/src/hooks/useDomGeometryCommit.ts +16 -17
  165. package/src/hooks/useDomGeometryCommits.test.tsx +233 -33
  166. package/src/hooks/useDomGeometryCommits.ts +27 -80
  167. package/src/hooks/useGsapAwareEditing.test.tsx +194 -11
  168. package/src/hooks/useGsapAwareEditing.ts +43 -42
  169. package/src/hooks/useInlineTextEdit.test.tsx +33 -3
  170. package/src/hooks/useInlineTextEdit.ts +2 -2
  171. package/src/player/components/ClipContextMenu.tsx +1 -1
  172. package/src/player/components/CompositionThumbnail.tsx +23 -11
  173. package/src/player/components/EditModal.tsx +6 -6
  174. package/src/player/components/KeyframeDiamondContextMenu.tsx +2 -2
  175. package/src/player/components/PlayerControls.tsx +3 -3
  176. package/src/player/components/ShortcutsPanel.tsx +8 -20
  177. package/src/player/components/TimelineCanvas.tsx +1 -1
  178. package/src/player/components/TimelineClip.tsx +2 -2
  179. package/src/player/components/TimelineEmptyState.tsx +2 -2
  180. package/src/player/components/TimelineLanes.tsx +0 -1
  181. package/src/player/components/TrackClipCount.tsx +1 -1
  182. package/src/player/components/VolumeControl.tsx +1 -1
  183. package/src/player/components/timelineClipChildren.tsx +1 -1
  184. package/src/player/components/timelineMotionStyles.test.ts +6 -22
  185. package/src/player/components/trackHeaderLabelRows.tsx +16 -16
  186. package/src/player/lib/thumbnailScheduler.ts +4 -5
  187. package/src/player/lib/timelineViewportBudgets.test.ts +1 -1
  188. package/src/player/lib/timelineViewportBudgets.ts +1 -1
  189. package/src/styles/hex-baseline.json +7 -19
  190. package/src/styles/studio.css +0 -9
  191. package/src/styles/tailwind-preset.shared.js +2 -1
  192. package/src/styles/theme.css +204 -147
  193. package/src/styles/theme.test.ts +13 -9
  194. package/src/utils/blockCategories.ts +41 -9
  195. package/src/utils/clipboardPayload.ts +2 -0
  196. package/dist/assets/index-CQN3M56N.css +0 -1
  197. package/dist/chunk-62DDWOSH.js.map +0 -1
  198. package/dist/chunk-6GAO75IU.js.map +0 -1
  199. package/src/hooks/gsapTargetCache.ts +0 -62
  200. /package/dist/{chunk-HIGHVWYD.js.map → chunk-QADHJFDF.js.map} +0 -0
@@ -147,7 +147,7 @@ export function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }:
147
147
  <div className="px-4 py-2 border-b border-neutral-800/40 max-h-24 overflow-y-auto">
148
148
  {elementsInRange.map((el) => (
149
149
  <div key={el.id} className="flex items-center justify-between py-0.5">
150
- <span className="text-[10px] font-mono text-studio-accent/80">#{el.id}</span>
150
+ <span className="text-[10px] font-mono text-accent-ink/80">#{el.id}</span>
151
151
  <span className="text-[10px] text-neutral-600">{el.tag}</span>
152
152
  </div>
153
153
  ))}
@@ -174,7 +174,7 @@ export function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }:
174
174
 
175
175
  {/* Action */}
176
176
  {copyError && (
177
- <p className="px-3 pb-2 text-[10px] text-red-400" role="alert">
177
+ <p className="px-3 pb-2 text-[10px] text-danger-ink" role="alert">
178
178
  Copy failed — check clipboard permissions and try again.
179
179
  </p>
180
180
  )}
@@ -184,7 +184,7 @@ export function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }:
184
184
  disabled={!buildPromptCopyText(prompt)}
185
185
  className={`py-1.5 text-[11px] font-medium rounded-lg transition-all border ${
186
186
  copiedPromptOnly
187
- ? "bg-green-500/20 text-green-400 border-green-500/30"
187
+ ? "bg-accent/15 text-accent-ink border-accent/30"
188
188
  : "bg-neutral-800/70 text-neutral-200 border-neutral-700/50 hover:bg-neutral-800"
189
189
  } disabled:opacity-50 disabled:cursor-not-allowed`}
190
190
  >
@@ -194,13 +194,13 @@ export function EditPopover({ rangeStart, rangeEnd, anchorX, anchorY, onClose }:
194
194
  onClick={handleCopy}
195
195
  className={`py-1.5 text-[11px] font-medium rounded-lg transition-all ${
196
196
  copiedAgentPrompt
197
- ? "bg-green-500/20 text-green-400 border border-green-500/30"
198
- : "bg-studio-accent/15 text-studio-accent border border-studio-accent/25 hover:bg-studio-accent/25"
197
+ ? "bg-accent/15 text-accent-ink border border-accent/30"
198
+ : "bg-studio-accent/15 text-accent-ink border border-studio-accent/25 hover:bg-studio-accent/25"
199
199
  }`}
200
200
  >
201
201
  {copiedAgentPrompt ? "Copied!" : "Copy to Agent"}
202
202
  {!copiedAgentPrompt && (
203
- <span className="text-[9px] text-studio-accent/50 ml-1.5">Cmd+Enter</span>
203
+ <span className="text-[9px] text-text-muted ml-1.5">Cmd+Enter</span>
204
204
  )}
205
205
  </button>
206
206
  </div>
@@ -42,7 +42,7 @@ interface KeyframeDiamondContextMenuProps {
42
42
  const ITEM_CLS =
43
43
  "w-full flex items-center gap-2 px-3 py-1.5 text-xs text-neutral-200 hover:bg-neutral-800 focus-visible:bg-neutral-800 outline-hidden cursor-pointer text-left";
44
44
  const DESTRUCTIVE_ITEM_CLS =
45
- "w-full flex items-center gap-2 px-3 py-1.5 text-xs text-red-400 hover:bg-neutral-800 focus-visible:bg-neutral-800 outline-hidden cursor-pointer text-left";
45
+ "w-full flex items-center gap-2 px-3 py-1.5 text-xs text-danger-ink hover:bg-neutral-800 focus-visible:bg-neutral-800 outline-hidden cursor-pointer text-left";
46
46
 
47
47
  export function KeyframeDiamondContextMenu({
48
48
  state,
@@ -172,7 +172,7 @@ export function KeyframeDiamondContextMenu({
172
172
  <button
173
173
  type="button"
174
174
  role="menuitem"
175
- className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-red-400 hover:bg-red-950/40 focus-visible:bg-red-950/40 outline-hidden cursor-pointer text-left"
175
+ className="w-full flex items-center gap-2 px-3 py-1.5 text-xs text-danger-ink hover:bg-danger/25 focus-visible:bg-danger/25 outline-hidden cursor-pointer text-left"
176
176
  onClick={() => {
177
177
  onDeleteAll(state.element, state.animationId);
178
178
  onClose();
@@ -76,7 +76,7 @@ const LoopButton = memo(function LoopButton({
76
76
  }}
77
77
  disabled={disabled}
78
78
  className={`flex h-7 w-7 items-center justify-center rounded-md transition-colors disabled:opacity-30 ${
79
- loopEnabled ? "text-studio-accent" : "text-neutral-500 hover:text-neutral-200"
79
+ loopEnabled ? "text-accent-ink" : "text-neutral-500 hover:text-neutral-200"
80
80
  }`}
81
81
  aria-label={loopEnabled ? "Disable loop playback" : "Enable loop playback"}
82
82
  aria-pressed={loopEnabled}
@@ -118,7 +118,7 @@ const FullscreenButton = memo(function FullscreenButton({
118
118
  onToggleFullscreen();
119
119
  }}
120
120
  className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors ${
121
- isFullscreen ? "text-studio-accent" : "text-neutral-500 hover:text-neutral-200"
121
+ isFullscreen ? "text-accent-ink" : "text-neutral-500 hover:text-neutral-200"
122
122
  }`}
123
123
  aria-label={isFullscreen ? "Exit fullscreen" : "Enter fullscreen"}
124
124
  >
@@ -241,7 +241,7 @@ export const PlayerControls = memo(function PlayerControls({
241
241
  <span ref={timeDisplayRef}>{formatTime(0)}</span>
242
242
  {timeDisplayMode === "time" ? (
243
243
  <>
244
- <span className="mx-0.5 text-neutral-700">/</span>
244
+ <span className="mx-0.5 text-text-off">/</span>
245
245
  <span className="text-neutral-600">{formatTime(duration)}</span>
246
246
  </>
247
247
  ) : null}
@@ -1,6 +1,7 @@
1
1
  import { useState, useCallback, useEffect, useId, useRef, memo } from "react";
2
2
  import { formatTime, frameToSeconds } from "../lib/time";
3
3
  import { Tooltip } from "../../components/ui";
4
+ import { flatActive, flatIdle } from "../../components/timelineToolbarStyles";
4
5
  import { useContextMenuDismiss } from "../../hooks/useContextMenuDismiss";
5
6
  import { usePlayerStore } from "../store/playerStore";
6
7
  import { DEFAULT_SHORTCUT_SECTIONS, type ShortcutSection } from "./studioShortcuts";
@@ -77,9 +78,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
77
78
  ref={triggerRef}
78
79
  type="button"
79
80
  onClick={() => setShowShortcuts((v) => !v)}
80
- className={`flex h-7 w-7 items-center justify-center rounded-md transition-colors ${
81
- showShortcuts ? "text-neutral-200" : "text-neutral-600 hover:text-neutral-300"
82
- }`}
81
+ className={showShortcuts ? flatActive : flatIdle}
83
82
  aria-label="Shortcuts and tools"
84
83
  aria-expanded={showShortcuts}
85
84
  aria-controls={shortcutsPanelId}
@@ -110,10 +109,8 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
110
109
  // Deliberately NOT aria-modal. This is a non-modal disclosure: focus is
111
110
  // not trapped and the rest of the editor stays operable, so claiming
112
111
  // modality would make assistive tech treat the whole app as inert.
113
- className="absolute bottom-full right-0 mb-2 z-50 rounded-lg shadow-xl min-w-[220px] overflow-y-auto outline-hidden"
112
+ className="absolute bottom-full right-0 mb-2 z-50 rounded-lg border border-border bg-raised shadow-popover min-w-[220px] overflow-y-auto outline-hidden"
114
113
  style={{
115
- background: "#161618",
116
- border: "1px solid rgba(255,255,255,0.08)",
117
114
  maxHeight: "min(280px, calc(100vh - 80px))",
118
115
  }}
119
116
  >
@@ -145,7 +142,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
145
142
  </Tooltip>
146
143
  </form>
147
144
  </div>
148
- <div style={{ borderTop: "1px solid rgba(255,255,255,0.06)" }} />
145
+ <div className="border-t border-border" />
149
146
  <div className="px-3 pt-2.5 pb-2">
150
147
  <p className="text-[9px] font-medium text-neutral-500 uppercase tracking-wider mb-1.5">
151
148
  Work area
@@ -153,10 +150,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
153
150
  <div className="flex flex-col gap-1">
154
151
  <div className="flex items-center justify-between gap-2">
155
152
  <div className="flex items-center gap-2">
156
- <span
157
- className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[20px] text-center"
158
- style={{ background: "rgba(255,255,255,0.05)" }}
159
- >
153
+ <span className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[20px] text-center bg-hover">
160
154
  I
161
155
  </span>
162
156
  <span className="text-[10px] text-neutral-400">In-point</span>
@@ -194,10 +188,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
194
188
  </div>
195
189
  <div className="flex items-center justify-between gap-2">
196
190
  <div className="flex items-center gap-2">
197
- <span
198
- className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[20px] text-center"
199
- style={{ background: "rgba(255,255,255,0.05)" }}
200
- >
191
+ <span className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[20px] text-center bg-hover">
201
192
  O
202
193
  </span>
203
194
  <span className="text-[10px] text-neutral-400">Out-point</span>
@@ -235,7 +226,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
235
226
  </div>
236
227
  </div>
237
228
  </div>
238
- <div style={{ borderTop: "1px solid rgba(255,255,255,0.06)" }} />
229
+ <div className="border-t border-border" />
239
230
  <div className="px-3 pt-2.5 pb-3 flex flex-col gap-3">
240
231
  {sections.map((section, sectionIndex) => (
241
232
  <div key={sectionIndex}>
@@ -245,10 +236,7 @@ export const ShortcutsPanel = memo(function ShortcutsPanel({
245
236
  <div className="flex flex-col gap-1">
246
237
  {section.hints.map((hint, hintIndex) => (
247
238
  <div key={hintIndex} className="flex items-center gap-3">
248
- <span
249
- className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[36px] text-center"
250
- style={{ background: "rgba(255,255,255,0.05)" }}
251
- >
239
+ <span className="font-mono text-[10px] rounded-sm border border-neutral-700 px-1.5 py-0.5 text-neutral-300 min-w-[36px] text-center bg-hover">
252
240
  {hint.key}
253
241
  </span>
254
242
  <span className="text-[10px] text-neutral-400">{hint.label}</span>
@@ -83,7 +83,7 @@ export const TimelineCanvas = memo(function TimelineCanvas() {
83
83
  data-active={ghostLaneActive || undefined}
84
84
  className={`pointer-events-none absolute flex items-center justify-center rounded-lg border border-dashed text-[11px] transition-colors duration-150 ${
85
85
  ghostLaneActive
86
- ? "border-studio-accent/60 bg-studio-accent/6 text-studio-accent"
86
+ ? "border-studio-accent/60 bg-studio-accent/6 text-accent-ink"
87
87
  : "border-neutral-700/50 text-neutral-500"
88
88
  }`}
89
89
  style={{
@@ -165,7 +165,7 @@ export const TimelineClip = memo(function TimelineClip({
165
165
  width: 2,
166
166
  borderRadius: 1,
167
167
  background: "var(--clip-handle)",
168
- opacity: handleOpacity * 0.6,
168
+ opacity: handleOpacity,
169
169
  }}
170
170
  />
171
171
  </div>
@@ -195,7 +195,7 @@ export const TimelineClip = memo(function TimelineClip({
195
195
  width: 2,
196
196
  borderRadius: 1,
197
197
  background: "var(--clip-handle)",
198
- opacity: handleOpacity * 0.6,
198
+ opacity: handleOpacity,
199
199
  }}
200
200
  />
201
201
  </div>
@@ -63,13 +63,13 @@ export function TimelineEmptyState({
63
63
  strokeWidth="1.5"
64
64
  strokeLinecap="round"
65
65
  strokeLinejoin="round"
66
- className="text-studio-accent shrink-0"
66
+ className="text-accent-ink shrink-0"
67
67
  >
68
68
  <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
69
69
  <polyline points="7 10 12 15 17 10" />
70
70
  <line x1="12" y1="15" x2="12" y2="3" />
71
71
  </svg>
72
- <span className="text-[13px] text-studio-accent">Drop media files to import</span>
72
+ <span className="text-[13px] text-accent-ink">Drop media files to import</span>
73
73
  </>
74
74
  ) : (
75
75
  <>
@@ -353,7 +353,6 @@ export function TimelineLanes({
353
353
  fontSize: 11,
354
354
  letterSpacing: "0.06em",
355
355
  textTransform: "uppercase",
356
- opacity: 0.5,
357
356
  }}
358
357
  >
359
358
  New track
@@ -9,7 +9,7 @@ export function TrackClipCount({ clipCount }: { clipCount: number }) {
9
9
  <span
10
10
  aria-label={`${clipCount} clips`}
11
11
  title={`${clipCount} clips`}
12
- className="shrink-0 rounded-full bg-white/10 px-1 text-[9px] leading-[14px] tabular-nums text-white/55"
12
+ className="shrink-0 rounded-full bg-hover px-1 text-[9px] leading-[14px] tabular-nums text-text-muted"
13
13
  >
14
14
  {clipCount}
15
15
  </span>
@@ -93,7 +93,7 @@ export const VolumeControl = memo(function VolumeControl({
93
93
  aria-label={muteLabel}
94
94
  aria-pressed={silent}
95
95
  className={`flex h-7 w-7 shrink-0 items-center justify-center rounded-md transition-colors disabled:pointer-events-none disabled:opacity-30 ${
96
- silent ? "text-studio-accent" : "text-neutral-500 hover:text-neutral-200"
96
+ silent ? "text-accent-ink" : "text-neutral-500 hover:text-neutral-200"
97
97
  }`}
98
98
  >
99
99
  <VolumeIcon muted={audioMuted} volume={audioVolume} />
@@ -22,7 +22,7 @@ function ClipLintDot({ element }: { element: TimelineElement }) {
22
22
  if (!lint || lint.count === 0) return null;
23
23
  return (
24
24
  <span
25
- className="absolute w-1.5 h-1.5 rounded-full bg-amber-400"
25
+ className="absolute w-1.5 h-1.5 rounded-full bg-warning-ink"
26
26
  style={{ top: 7, right: 7 }}
27
27
  title={lint.messages.join("\n")}
28
28
  />
@@ -39,18 +39,15 @@ function themeTokenValue(token: string): string {
39
39
 
40
40
  describe("timeline motion styles", () => {
41
41
  it.each([
42
- ["--timeline-track-label", "rgba(255, 255, 255, 0.5)"],
43
- ["--timeline-tick-text", "rgba(255, 255, 255, 0.34)"],
44
- ["--timeline-border-strong", "rgba(255, 255, 255, 0.2)"],
45
- ["--timeline-group-member-tint", "rgba(255, 255, 255, 0.035)"],
46
- ["--timeline-overlay-text", "rgba(255, 255, 255, 0.8)"],
47
- ["--timeline-clip-shadow-dragging", "rgba(0, 0, 0, 0.4)"],
48
- ["--timeline-playhead-glow", "rgba(60, 230, 172, 0.14)"],
49
- ["--timeline-playhead-shadow", "rgba(0, 0, 0, 0.55)"],
42
+ ["--timeline-track-label", "var(--color-text-muted)"],
43
+ ["--timeline-tick-text", "var(--color-text-2)"],
44
+ ["--timeline-border-strong", "color-mix(in oklab, var(--color-text-0) 20%, transparent)"],
45
+ ["--timeline-group-member-tint", "color-mix(in oklab, var(--color-text-0) 3.5%, transparent)"],
46
+ ["--timeline-overlay-text", "var(--color-text-2)"],
47
+ ["--timeline-playhead-glow", "color-mix(in oklab, var(--color-accent) 14%, transparent)"],
50
48
  ])("keeps the migrated default for %s", (token, expected) => {
51
49
  expect(themeTokenValue(token)).toBe(expected);
52
50
  });
53
-
54
51
  it("keeps clip motion reduced-motion gated and layout safe", () => {
55
52
  const mediaStart = studioCss.indexOf("@media (prefers-reduced-motion: no-preference)");
56
53
  expect(mediaStart).toBeGreaterThanOrEqual(0);
@@ -93,13 +90,7 @@ describe("timeline motion styles", () => {
93
90
  expect(timelineClipLabelRule).toContain("text-overflow: ellipsis");
94
91
  expect(timelineClipLabelRule).toContain("max-width: calc(100% - 22px)");
95
92
  expect(timelineClipTimecodeRule).toContain("color: var(--timeline-tick-text)");
96
- expect(themeCss).toContain("--timeline-clip-bg: rgba(255, 255, 255, 0.12)");
97
- expect(themeCss).toContain("--timeline-clip-border: rgba(255, 255, 255, 0.22)");
98
- expect(themeCss).toContain("--timeline-track-label: rgba(255, 255, 255, 0.5)");
99
- expect(themeCss).toContain("--timeline-tick-text: rgba(255, 255, 255, 0.34)");
100
- expect(themeCss).toContain("--timeline-clip-label-active: rgba(232, 255, 247, 0.95)");
101
93
  expect(themeCss).toContain("--timeline-clip-label-shadow: 0 1px 2px rgba(0, 0, 0, 0.85)");
102
- expect(themeCss).toContain("--timeline-clip-selection: rgba(255, 255, 255, 0.85)");
103
94
  expect(themeCss).toContain("--timeline-clip-selection-highlight: rgb(255 255 255 / 0.55)");
104
95
  expect(themeCss).toContain("--timeline-clip-radius: 8px");
105
96
  expect(themeCss).toContain("--timeline-clip-audio-radius: 21px");
@@ -108,18 +99,11 @@ describe("timeline motion styles", () => {
108
99
  expect(themeCss).toContain("--timeline-clip-chip-bg: rgba(15, 22, 18, 0.73)");
109
100
  expect(themeCss).toContain("--timeline-clip-chip-audio-bg: rgba(25, 16, 38, 0.7)");
110
101
  expect(themeCss).toContain("--timeline-clip-chip-text: #ffffff");
111
- expect(themeCss).toContain("--timeline-row-bg: #edf0ed");
112
102
  expect(themeCss).toContain("--timeline-clip-shadow: 0 2px 6px rgb(0 0 0 / 0.12)");
113
- expect(themeCss).toContain("--timeline-clip-selection: #0f766e");
114
- expect(themeCss).toContain("--timeline-clip-audio-bg: rgba(167, 139, 250, 0.16)");
115
- expect(themeCss).toContain("--timeline-clip-audio-border: rgba(167, 139, 250, 0.4)");
116
- expect(themeCss).toContain("--timeline-clip-audio-bg-hover: rgba(167, 139, 250, 0.24)");
117
- expect(themeCss).toContain("--timeline-clip-audio-bg-dragging: rgba(60, 52, 84, 0.96)");
118
103
  expect(audioClipRule).toContain("background-color: var(--timeline-clip-audio-bg)");
119
104
  expect(audioClipRule).toContain("border-color: var(--timeline-clip-audio-border)");
120
105
  expect(audioClipHoverRule).toContain("background-color: var(--timeline-clip-audio-bg-hover)");
121
106
  expect(audioClipDraggingRule).toContain("var(--timeline-clip-audio-bg-dragging)");
122
- expect(themeCss).toContain("--timeline-clip-selection: rgba(255, 255, 255, 0.85)");
123
107
  expect(selectedTimelineClipRule).toContain(
124
108
  "outline: 1.5px solid var(--timeline-clip-selection)",
125
109
  );
@@ -38,8 +38,8 @@ function PropertyGroupNavigation({
38
38
  // target is met with a centered transparent ::before overlay instead of a
39
39
  // bigger box; focus-visible matches every other control in this header.
40
40
  const CHEVRON_BUTTON_CLASS =
41
- "relative h-5 w-3 border-0 bg-transparent p-0 text-white/55 hover:text-white disabled:text-white/15 " +
42
- "focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-[#3CE6AC] " +
41
+ "relative h-5 w-3 border-0 bg-transparent p-0 text-text-2 hover:text-text-0 disabled:text-text-off " +
42
+ "focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-ring " +
43
43
  "before:absolute before:left-1/2 before:top-1/2 before:h-6 before:w-6 " +
44
44
  "before:-translate-x-1/2 before:-translate-y-1/2 before:content-['']";
45
45
  const seekTo = (keyframe: { percentage: number } | null) => {
@@ -120,7 +120,7 @@ export function PropertyGroupHeaderRow({
120
120
  tabIndex={rovingTargetId === timelinePropertyRowId(elementId, lane.group) ? 0 : -1}
121
121
  data-property-group={lane.group}
122
122
  data-timeline-lane-top={getTimelineLaneTop(laneIndex)}
123
- className="absolute left-0 flex items-center gap-1 overflow-hidden px-1.5 text-[10px] text-white/65"
123
+ className="absolute left-0 flex items-center gap-1 overflow-hidden px-1.5 text-[10px] text-text-2"
124
124
  style={{
125
125
  top: getTimelineLaneTop(laneIndex),
126
126
  // The header column narrows to contentOrigin whenever that is under
@@ -134,12 +134,12 @@ export function PropertyGroupHeaderRow({
134
134
  {/* Tree connector: vertical spine (top-half on the last lane) + branch tick. */}
135
135
  <span className="relative h-full w-3 shrink-0" aria-hidden="true">
136
136
  <span
137
- className="absolute left-1.5 top-0 w-px bg-white/15"
137
+ className="absolute left-1.5 top-0 w-px bg-text-0/15"
138
138
  style={{ height: isLastLane ? "50%" : "100%" }}
139
139
  />
140
- <span className="absolute left-1.5 top-1/2 h-px w-1.5 bg-white/15" />
140
+ <span className="absolute left-1.5 top-1/2 h-px w-1.5 bg-text-0/15" />
141
141
  </span>
142
- <span className="w-[46px] shrink-0 truncate text-white" title={label}>
142
+ <span className="w-[46px] shrink-0 truncate text-text-0" title={label}>
143
143
  {label}
144
144
  </span>
145
145
  <PropertyGroupNavigation
@@ -154,7 +154,7 @@ export function PropertyGroupHeaderRow({
154
154
  aria-label={`${navigation.currentKeyframe ? "Remove" : "Add"} ${label} keyframe`}
155
155
  title={`${navigation.currentKeyframe ? "Remove" : "Add"} ${label} keyframe`}
156
156
  // h-6 w-6 = the 24x24 WCAG 2.2 minimum target; the ◆ glyph stays 11px.
157
- className="flex h-6 w-6 shrink-0 items-center justify-center border-0 bg-transparent p-0 text-[11px] text-[#3CE6AC] focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-[#3CE6AC]"
157
+ className="flex h-6 w-6 shrink-0 items-center justify-center border-0 bg-transparent p-0 text-[11px] text-accent-ink focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-ring"
158
158
  onClick={(event) => {
159
159
  // Same as the disclosure caret and the eye: a control in the label
160
160
  // column owns its click, it does not also hit the track row behind it.
@@ -168,7 +168,7 @@ export function PropertyGroupHeaderRow({
168
168
  </button>
169
169
  </PropertyGroupNavigation>
170
170
  <span
171
- className="min-w-0 flex-1 truncate text-right tabular-nums text-white/45"
171
+ className="min-w-0 flex-1 truncate text-right tabular-nums text-text-muted"
172
172
  title={valueReadout(lane.group, values)}
173
173
  >
174
174
  {valueReadout(lane.group, values)}
@@ -231,7 +231,7 @@ export function AutomationLaneHeaderRow({
231
231
  <div
232
232
  data-automation-lane-label={label}
233
233
  data-timeline-lane-top={top}
234
- className="absolute left-0 flex items-center gap-1 overflow-hidden px-1.5 text-[10px] text-white/65"
234
+ className="absolute left-0 flex items-center gap-1 overflow-hidden px-1.5 text-[10px] text-text-2"
235
235
  style={{
236
236
  top,
237
237
  width: columnWidth,
@@ -243,10 +243,10 @@ export function AutomationLaneHeaderRow({
243
243
  tick at the name's own height. */}
244
244
  <span className="relative h-full w-3 shrink-0" aria-hidden="true">
245
245
  <span
246
- className="absolute left-1.5 top-0 w-px bg-white/15"
246
+ className="absolute left-1.5 top-0 w-px bg-text-0/15"
247
247
  style={{ height: isLastLane ? "50%" : "100%" }}
248
248
  />
249
- <span className="absolute left-1.5 top-1/2 h-px w-1.5 bg-white/15" />
249
+ <span className="absolute left-1.5 top-1/2 h-px w-1.5 bg-text-0/15" />
250
250
  </span>
251
251
  {/* Two lines: what the effect is, then which knob the envelope drives. On
252
252
  one line a band's own name was the first thing truncated in a column this
@@ -262,7 +262,7 @@ export function AutomationLaneHeaderRow({
262
262
  aria-label={onReveal ? `Show ${label} in the effect rack` : undefined}
263
263
  title={onReveal ? `Show ${label} in the effect rack` : label}
264
264
  disabled={!onReveal}
265
- className="flex min-w-0 flex-1 flex-col justify-center rounded-sm border-0 bg-transparent p-0 text-left leading-tight enabled:hover:text-white focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-[#3CE6AC]"
265
+ className="flex min-w-0 flex-1 flex-col justify-center rounded-sm border-0 bg-transparent p-0 text-left leading-tight enabled:hover:text-text-0 focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-ring"
266
266
  onPointerDown={(event) => event.stopPropagation()}
267
267
  onClick={(event) => {
268
268
  // The label column owns its click; it does not also fall through to
@@ -271,13 +271,13 @@ export function AutomationLaneHeaderRow({
271
271
  onReveal?.();
272
272
  }}
273
273
  >
274
- <span data-automation-lane-name="" className="truncate font-mono text-[9px] text-white/70">
274
+ <span data-automation-lane-name="" className="truncate font-mono text-[9px] text-text-2">
275
275
  {name}
276
276
  </span>
277
277
  {param ? (
278
278
  <span
279
279
  data-automation-lane-param=""
280
- className="truncate font-mono text-[9px] text-white/40"
280
+ className="truncate font-mono text-[9px] text-text-muted"
281
281
  >
282
282
  {param}
283
283
  </span>
@@ -285,7 +285,7 @@ export function AutomationLaneHeaderRow({
285
285
  {alsoAutomatedBy ? (
286
286
  <span
287
287
  data-automation-lane-also=""
288
- className="truncate text-[9px] text-[#F5C542]/80"
288
+ className="truncate text-[9px] text-warning-ink"
289
289
  title={`${alsoAutomatedBy} is also fading this — the two multiply.`}
290
290
  >
291
291
  {alsoAutomatedBy} is also fading this.
@@ -311,7 +311,7 @@ export function AutomationLaneHeaderRow({
311
311
  aria-label={`Remove ${label} automation`}
312
312
  title={`Remove ${label} automation`}
313
313
  // h-6 w-6 is the 24x24 WCAG 2.2 target; the glyph stays small.
314
- className="flex h-6 w-6 shrink-0 items-center justify-center border-0 bg-transparent p-0 text-[11px] text-white/35 hover:text-white focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-[#3CE6AC]"
314
+ className="flex h-6 w-6 shrink-0 items-center justify-center border-0 bg-transparent p-0 text-[11px] text-text-muted hover:text-text-0 focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-ring"
315
315
  onPointerDown={(event) => event.stopPropagation()}
316
316
  onClick={(event) => {
317
317
  // A control in the label column owns its click; it does not also hit
@@ -23,6 +23,8 @@ export interface ThumbnailRequest {
23
23
  priority: ThumbnailPriority;
24
24
  /** Rich work is paused while the timeline is fast-scrolling. */
25
25
  rich?: boolean;
26
+ /** For work whose result nobody reads: it is dropped, not cached, once its last lease ends. */
27
+ discardWhenReleased?: boolean;
26
28
  load: (signal: AbortSignal) => Promise<ThumbnailLoadedResult>;
27
29
  }
28
30
 
@@ -214,10 +216,7 @@ export class ThumbnailScheduler {
214
216
  if (!scrolling) this.pump();
215
217
  }
216
218
 
217
- /**
218
- * Hold server-rendered composition thumbnails while the preview loads a new document: both
219
- * are served by the same Studio server, and the preview is what the person is waiting for.
220
- */
219
+ /** Holds composition renders while the preview loads: same server, and the preview is what the person awaits. */
221
220
  setPreviewReloading(reloading: boolean): void {
222
221
  if (this.previewReloading === reloading) return;
223
222
  this.previewReloading = reloading;
@@ -362,7 +361,7 @@ export class ThumbnailScheduler {
362
361
  const byteBudget = isWaveform
363
362
  ? this.budgets.waveformCacheBytes
364
363
  : this.budgets.thumbnailCacheBytes;
365
- entry.cached = entry.weight <= byteBudget;
364
+ entry.cached = !entry.request.discardWhenReleased && entry.weight <= byteBudget;
366
365
  if (!entry.cached) return;
367
366
  if (isWaveform) this.waveformCacheBytes += entry.weight;
368
367
  else this.cacheBytes += entry.weight;
@@ -23,7 +23,7 @@ describe("timeline viewport budgets", () => {
23
23
  constrainedLongTaskLimitMs: 300,
24
24
  posterCoverageRatio: 0.9,
25
25
  supportedFixtureFallbackRatio: 0.02,
26
- scrollSamplesPerRun: 21,
26
+ scrollSamplesPerRun: 63,
27
27
  warmupRuns: 3,
28
28
  measuredRuns: 5,
29
29
  requiredPassingRuns: 4,
@@ -99,7 +99,7 @@ export const TIMELINE_VIEWPORT_BUDGETS: Readonly<TimelineViewportBudgets> = Obje
99
99
  richPreviewP95Ms: 750,
100
100
  constrainedRichPreviewP95Ms: 1_200,
101
101
  supportedFixtureFallbackRatio: 0.02,
102
- scrollSamplesPerRun: 21,
102
+ scrollSamplesPerRun: 63,
103
103
  warmupRuns: 3,
104
104
  measuredRuns: 5,
105
105
  requiredPassingRuns: 4,
@@ -1,41 +1,32 @@
1
1
  {
2
- "total": 297,
2
+ "total": 226,
3
3
  "files": {
4
4
  "packages/studio/src/captions/components/CaptionOverlay.tsx": 5,
5
5
  "packages/studio/src/captions/components/CaptionTimeline.tsx": 11,
6
6
  "packages/studio/src/captions/generator.ts": 1,
7
7
  "packages/studio/src/captions/parser.ts": 1,
8
8
  "packages/studio/src/captions/types.ts": 4,
9
- "packages/studio/src/components/StudioToast.tsx": 10,
10
- "packages/studio/src/components/TimelineToolbar.tsx": 2,
9
+ "packages/studio/src/components/TimelineToolbar.tsx": 1,
11
10
  "packages/studio/src/components/editor/ArcPathControls.tsx": 1,
12
- "packages/studio/src/components/editor/BorderRadiusEditor.tsx": 10,
11
+ "packages/studio/src/components/editor/BorderRadiusEditor.tsx": 2,
13
12
  "packages/studio/src/components/editor/DomEditCropHandles.tsx": 1,
14
- "packages/studio/src/components/editor/EaseCurveSection.tsx": 2,
15
- "packages/studio/src/components/editor/FileTree.tsx": 2,
16
- "packages/studio/src/components/editor/FileTreeIcons.tsx": 17,
17
- "packages/studio/src/components/editor/FileTreeNodes.tsx": 4,
13
+ "packages/studio/src/components/editor/FileTreeIcons.tsx": 7,
18
14
  "packages/studio/src/components/editor/GestureTrailOverlay.tsx": 1,
19
15
  "packages/studio/src/components/editor/GridOverlay.tsx": 4,
20
- "packages/studio/src/components/editor/InlineTextToolbar.tsx": 2,
21
- "packages/studio/src/components/editor/KeyframeDiamond.tsx": 2,
22
- "packages/studio/src/components/editor/KeyframeNavigation.tsx": 2,
16
+ "packages/studio/src/components/editor/InlineTextToolbar.tsx": 1,
23
17
  "packages/studio/src/components/editor/MotionPathNode.tsx": 5,
24
18
  "packages/studio/src/components/editor/SnapGuideOverlay.tsx": 3,
25
- "packages/studio/src/components/editor/TimelineFxPopover.tsx": 1,
26
- "packages/studio/src/components/editor/Transform3DCube.tsx": 6,
19
+ "packages/studio/src/components/editor/Transform3DCube.tsx": 5,
27
20
  "packages/studio/src/components/editor/colorValue.ts": 3,
28
21
  "packages/studio/src/components/editor/domEditingElement.ts": 1,
29
- "packages/studio/src/components/editor/easeCurveSvg.tsx": 2,
30
22
  "packages/studio/src/components/editor/gradientValue.ts": 6,
31
23
  "packages/studio/src/components/editor/panelTokens.ts": 4,
32
24
  "packages/studio/src/components/editor/propertyPanelColor.tsx": 7,
33
- "packages/studio/src/components/editor/propertyPanelColorCurveGraph.tsx": 17,
25
+ "packages/studio/src/components/editor/propertyPanelColorCurveGraph.tsx": 16,
34
26
  "packages/studio/src/components/editor/propertyPanelColorScopes.tsx": 8,
35
27
  "packages/studio/src/components/editor/propertyPanelColorWheels.tsx": 8,
36
28
  "packages/studio/src/components/editor/propertyPanelFlatEffectsSection.tsx": 5,
37
29
  "packages/studio/src/components/editor/propertyPanelFlatStyleSections.tsx": 2,
38
- "packages/studio/src/components/editor/propertyPanelFxCarveModule.tsx": 1,
39
30
  "packages/studio/src/components/editor/propertyPanelFxFamily.ts": 1,
40
31
  "packages/studio/src/components/editor/propertyPanelFxPresetStyle.ts": 24,
41
32
  "packages/studio/src/components/editor/propertyPanelHelpers.ts": 4,
@@ -43,7 +34,6 @@
43
34
  "packages/studio/src/components/editor/propertyPanelSections.tsx": 1,
44
35
  "packages/studio/src/components/editor/propertyPanelStyleSections.tsx": 3,
45
36
  "packages/studio/src/components/editor/transform3dProjection.ts": 3,
46
- "packages/studio/src/components/feedback/StudioFeedbackCard.tsx": 10,
47
37
  "packages/studio/src/components/panels/VariablesBindElement.tsx": 2,
48
38
  "packages/studio/src/components/panels/VariablesValueControls.tsx": 2,
49
39
  "packages/studio/src/components/sidebar/AudioRow.tsx": 2,
@@ -54,9 +44,7 @@
54
44
  "packages/studio/src/player/components/CompositionThumbnail.tsx": 2,
55
45
  "packages/studio/src/player/components/Player.tsx": 2,
56
46
  "packages/studio/src/player/components/PlayerControls.tsx": 1,
57
- "packages/studio/src/player/components/ShortcutsPanel.tsx": 7,
58
47
  "packages/studio/src/player/components/SpeedMenu.tsx": 2,
59
- "packages/studio/src/player/components/trackHeaderLabelRows.tsx": 6,
60
48
  "packages/studio/src/styles/components.css": 48,
61
49
  "packages/studio/src/telemetry/client.ts": 1,
62
50
  "packages/studio/src/webmcp/webmcpTestUtils.ts": 1
@@ -16,15 +16,6 @@
16
16
  }
17
17
  }
18
18
 
19
- /*
20
- * Studio is a dark-only UI — pin the user-agent color scheme to dark so that
21
- * browser-native chrome (scrollbars, form controls, focus rings) picks the
22
- * matching palette instead of defaulting to light against our #0a0a0a body.
23
- */
24
- :root {
25
- color-scheme: dark;
26
- }
27
-
28
19
  body {
29
20
  margin: 0;
30
21
  padding: 0;
@@ -1,6 +1,7 @@
1
1
  /** @deprecated Superseded by `src/styles/theme.css` (published as
2
2
  * `@hyperframes/studio/theme.css`); kept one more major for Tailwind v3
3
- * consumers. Values are byte-identical; theme.test.ts fails if they drift. */
3
+ * consumers, frozen at the colours before Graphite (theme.css is light-dark() and colour-mix
4
+ * now, which v3 cannot read). */
4
5
  const studioPreset = {
5
6
  theme: {
6
7
  extend: {