@hyperframes/studio 0.8.104 → 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 (169) hide show
  1. package/dist/assets/{hyperframes-player-DAoaajuM.js → hyperframes-player-tnfCiVdR.js} +1 -1
  2. package/dist/assets/{index-BDK4rTMb.js → index-Ca6LZlVb.js} +1 -1
  3. package/dist/assets/index-Cre9X4RS.css +1 -0
  4. package/dist/assets/{index-De2aO4XM.js → index-DEDhPmPD.js} +205 -205
  5. package/dist/assets/{index-WV9LroAD.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.html +2 -2
  14. package/dist/index.js +614 -638
  15. package/dist/index.js.map +1 -1
  16. package/dist/player.js +2 -2
  17. package/dist/styles/tailwind-preset.js.map +1 -1
  18. package/dist/styles.css +1 -1
  19. package/dist/ui.js +2 -2
  20. package/package.json +8 -8
  21. package/src/captions/components/CaptionAnimationPanel.tsx +2 -2
  22. package/src/captions/components/CaptionPropertyPanel.tsx +2 -2
  23. package/src/captions/components/CaptionTimeline.tsx +3 -3
  24. package/src/components/AskAgentModal.tsx +1 -1
  25. package/src/components/LintModal.tsx +35 -35
  26. package/src/components/StudioErrorBoundary.tsx +1 -1
  27. package/src/components/StudioHeader.dom.test.tsx +4 -4
  28. package/src/components/StudioHeader.tsx +2 -1
  29. package/src/components/StudioToast.tsx +7 -16
  30. package/src/components/TimelineToolPicker.tsx +1 -1
  31. package/src/components/TimelineToolbar.tsx +11 -11
  32. package/src/components/dock/Dock.tsx +1 -1
  33. package/src/components/dock/dock.css +9 -1
  34. package/src/components/editor/AnimationCard.tsx +5 -5
  35. package/src/components/editor/AnimationCardParts.tsx +1 -1
  36. package/src/components/editor/BlockParamsPanel.tsx +3 -3
  37. package/src/components/editor/BorderRadiusEditor.tsx +8 -15
  38. package/src/components/editor/CanvasContextMenu.tsx +1 -1
  39. package/src/components/editor/ComputedTweenNotice.tsx +1 -1
  40. package/src/components/editor/DomEditRotateHandle.tsx +1 -1
  41. package/src/components/editor/EaseCurveSection.tsx +14 -19
  42. package/src/components/editor/EaseModeControls.tsx +2 -2
  43. package/src/components/editor/EaseParamFields.tsx +8 -8
  44. package/src/components/editor/FileTree.tsx +1 -1
  45. package/src/components/editor/FileTreeIcons.tsx +47 -26
  46. package/src/components/editor/FileTreeNodes.tsx +11 -8
  47. package/src/components/editor/GestureRecordControl.tsx +3 -3
  48. package/src/components/editor/GsapAddAnimationControl.tsx +1 -1
  49. package/src/components/editor/GsapAnimationSection.tsx +2 -2
  50. package/src/components/editor/InlineTextToolbar.tsx +4 -4
  51. package/src/components/editor/InspectorHeaderActions.tsx +1 -1
  52. package/src/components/editor/KeyframeDiamond.tsx +6 -4
  53. package/src/components/editor/KeyframeNavigation.tsx +4 -4
  54. package/src/components/editor/LayersPanel.tsx +3 -3
  55. package/src/components/editor/PromotableControl.tsx +2 -2
  56. package/src/components/editor/PropertyPanelEmptyState.tsx +4 -4
  57. package/src/components/editor/PropertyPanelFlatFooter.tsx +2 -2
  58. package/src/components/editor/PropertyPanelFlatHeader.test.tsx +1 -1
  59. package/src/components/editor/PropertyPanelFlatHeader.tsx +2 -2
  60. package/src/components/editor/SnapToolbar.tsx +5 -5
  61. package/src/components/editor/TimelineFxPopover.tsx +4 -4
  62. package/src/components/editor/Transform3DCube.tsx +1 -1
  63. package/src/components/editor/anchoredResizeReleaseShift.test.ts +0 -84
  64. package/src/components/editor/easeCurveSvg.tsx +1 -1
  65. package/src/components/editor/manualEditsDom.ts +3 -3
  66. package/src/components/editor/manualEditsDomPatches.test.ts +2 -86
  67. package/src/components/editor/manualEditsDomPatches.ts +0 -30
  68. package/src/components/editor/manualEditsSnapshot.ts +1 -0
  69. package/src/components/editor/persistSeam.integration.test.ts +0 -18
  70. package/src/components/editor/plainTranslate.ts +34 -3
  71. package/src/components/editor/propertyPanelColorCurveGraph.tsx +2 -2
  72. package/src/components/editor/propertyPanelColorGradingControls.tsx +1 -1
  73. package/src/components/editor/propertyPanelColorGradingSection.tsx +8 -8
  74. package/src/components/editor/propertyPanelColorGradingSlider.tsx +1 -1
  75. package/src/components/editor/propertyPanelColorSecondary.tsx +4 -4
  76. package/src/components/editor/propertyPanelFill.tsx +6 -6
  77. package/src/components/editor/propertyPanelFlatColorGradingAccessory.tsx +3 -3
  78. package/src/components/editor/propertyPanelFlatColorGradingSection.test.tsx +1 -1
  79. package/src/components/editor/propertyPanelFlatColorGradingSection.tsx +6 -9
  80. package/src/components/editor/propertyPanelFlatEffectsSection.tsx +4 -4
  81. package/src/components/editor/propertyPanelFlatMediaSection.tsx +2 -2
  82. package/src/components/editor/propertyPanelFlatMotionSection.tsx +2 -2
  83. package/src/components/editor/propertyPanelFlatOverlaysSection.tsx +2 -2
  84. package/src/components/editor/propertyPanelFlatPrimitives.test.tsx +4 -3
  85. package/src/components/editor/propertyPanelFlatPrimitives.tsx +1 -1
  86. package/src/components/editor/propertyPanelFlatSliderReadout.tsx +1 -1
  87. package/src/components/editor/propertyPanelFont.tsx +2 -2
  88. package/src/components/editor/propertyPanelFxCarveModule.tsx +1 -1
  89. package/src/components/editor/propertyPanelFxControls.tsx +2 -2
  90. package/src/components/editor/propertyPanelFxEqModule.tsx +2 -2
  91. package/src/components/editor/propertyPanelFxNodeRow.tsx +1 -1
  92. package/src/components/editor/propertyPanelFxPresetRun.tsx +1 -1
  93. package/src/components/editor/propertyPanelMediaSection.tsx +1 -1
  94. package/src/components/editor/propertyPanelPrimitives.tsx +1 -1
  95. package/src/components/editor/propertyPanelSections.tsx +2 -2
  96. package/src/components/editor/propertyPanelValueTier.test.ts +1 -1
  97. package/src/components/editor/propertyPanelValueTier.ts +1 -1
  98. package/src/components/feedback/StudioFeedbackCard.tsx +11 -23
  99. package/src/components/nle/AssetPreviewOverlay.tsx +1 -1
  100. package/src/components/nle/AudioMeterStrip.tsx +2 -2
  101. package/src/components/nle/NLEPreview.test.ts +8 -4
  102. package/src/components/nle/PreviewOverlays.tsx +1 -1
  103. package/src/components/nle/PreviewPane.tsx +3 -3
  104. package/src/components/nle/PreviewPoster.tsx +29 -1
  105. package/src/components/nle/PreviewZoomOverlay.tsx +1 -1
  106. package/src/components/panels/SlideshowPanel.tsx +4 -4
  107. package/src/components/panels/SlideshowSubPanels.tsx +19 -21
  108. package/src/components/panels/VariablesBindElement.tsx +1 -1
  109. package/src/components/panels/VariablesDeclarationForm.tsx +1 -1
  110. package/src/components/panels/VariablesPanel.tsx +4 -6
  111. package/src/components/panels/VariablesRowAction.tsx +1 -1
  112. package/src/components/renders/FfmpegRequiredNotice.tsx +4 -4
  113. package/src/components/renders/RenderQueue.tsx +2 -2
  114. package/src/components/renders/RenderQueueItem.tsx +3 -3
  115. package/src/components/sidebar/AssetCard.tsx +3 -3
  116. package/src/components/sidebar/AssetContextMenu.tsx +7 -7
  117. package/src/components/sidebar/AssetsTab.tsx +6 -6
  118. package/src/components/sidebar/AudioRow.tsx +5 -3
  119. package/src/components/sidebar/BlocksTab.tsx +6 -6
  120. package/src/components/sidebar/CompositionsTab.drag.test.tsx +3 -69
  121. package/src/components/sidebar/CompositionsTab.rootBadge.test.tsx +4 -50
  122. package/src/components/sidebar/CompositionsTab.thumbnails.test.tsx +256 -0
  123. package/src/components/sidebar/CompositionsTab.tsx +65 -12
  124. package/src/components/sidebar/PromptPreviewModal.tsx +3 -3
  125. package/src/components/sidebar/SidebarLintButton.tsx +3 -3
  126. package/src/components/sidebar/compositionsTabTestUtils.tsx +39 -0
  127. package/src/components/timelineToolbarStyles.ts +3 -3
  128. package/src/components/ui/Button.tsx +4 -4
  129. package/src/components/ui/HyperframesLogo.tsx +19 -18
  130. package/src/components/ui/Input.tsx +1 -1
  131. package/src/components/ui/Menu.tsx +4 -2
  132. package/src/hooks/elementOffsetStager.ts +8 -7
  133. package/src/hooks/useClipboard.source.test.ts +42 -4
  134. package/src/hooks/useClipboard.ts +13 -3
  135. package/src/hooks/useDomEditCommits.ts +0 -3
  136. package/src/hooks/useDomGeometryCommit.test.tsx +4 -1
  137. package/src/hooks/useDomGeometryCommit.ts +0 -1
  138. package/src/hooks/useDomGeometryCommits.test.tsx +177 -32
  139. package/src/hooks/useDomGeometryCommits.ts +4 -49
  140. package/src/player/components/ClipContextMenu.tsx +1 -1
  141. package/src/player/components/CompositionThumbnail.tsx +23 -11
  142. package/src/player/components/EditModal.tsx +6 -6
  143. package/src/player/components/KeyframeDiamondContextMenu.tsx +2 -2
  144. package/src/player/components/PlayerControls.tsx +3 -3
  145. package/src/player/components/ShortcutsPanel.tsx +8 -20
  146. package/src/player/components/TimelineCanvas.tsx +1 -1
  147. package/src/player/components/TimelineClip.tsx +2 -2
  148. package/src/player/components/TimelineEmptyState.tsx +2 -2
  149. package/src/player/components/TimelineLanes.tsx +0 -1
  150. package/src/player/components/TrackClipCount.tsx +1 -1
  151. package/src/player/components/VolumeControl.tsx +1 -1
  152. package/src/player/components/timelineClipChildren.tsx +1 -1
  153. package/src/player/components/timelineMotionStyles.test.ts +6 -22
  154. package/src/player/components/trackHeaderLabelRows.tsx +16 -16
  155. package/src/player/lib/thumbnailScheduler.ts +4 -5
  156. package/src/player/lib/timelineViewportBudgets.test.ts +1 -1
  157. package/src/player/lib/timelineViewportBudgets.ts +1 -1
  158. package/src/styles/hex-baseline.json +7 -19
  159. package/src/styles/studio.css +0 -9
  160. package/src/styles/tailwind-preset.shared.js +2 -1
  161. package/src/styles/theme.css +204 -147
  162. package/src/styles/theme.test.ts +13 -9
  163. package/src/utils/blockCategories.ts +41 -9
  164. package/src/utils/clipboardPayload.ts +2 -0
  165. package/dist/assets/index-CQN3M56N.css +0 -1
  166. package/dist/chunk-62DDWOSH.js.map +0 -1
  167. package/dist/chunk-6GAO75IU.js.map +0 -1
  168. package/src/hooks/gsapTargetCache.ts +0 -62
  169. /package/dist/{chunk-HIGHVWYD.js.map → chunk-QADHJFDF.js.map} +0 -0
@@ -131,7 +131,7 @@ export function ImageFillField({
131
131
  className={`inline-flex h-7 max-w-full items-center gap-1.5 rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors ${
132
132
  disabled || uploading
133
133
  ? "cursor-not-allowed text-neutral-600"
134
- : "cursor-pointer hover:border-neutral-600 hover:text-white"
134
+ : "cursor-pointer hover:border-neutral-600 hover:text-text-0"
135
135
  }`}
136
136
  >
137
137
  <Plus size={12} className="shrink-0" />
@@ -151,7 +151,7 @@ export function ImageFillField({
151
151
  />
152
152
  </div>
153
153
  {uploadError && (
154
- <div className="text-[10px] text-red-400" role="alert">
154
+ <div className="text-[10px] text-danger-ink" role="alert">
155
155
  {uploadError}
156
156
  </div>
157
157
  )}
@@ -340,7 +340,7 @@ export function GradientField({
340
340
  track("toggle", "Repeat gradient");
341
341
  patch({ repeating: e.target.checked });
342
342
  }}
343
- className="h-4 w-4 rounded-sm border-neutral-700 bg-neutral-950 text-panel-accent focus:ring-panel-accent"
343
+ className="h-4 w-4 rounded-sm border-neutral-700 bg-neutral-950 text-accent-ink focus:ring-panel-accent"
344
344
  />
345
345
  Repeat
346
346
  </label>
@@ -357,7 +357,7 @@ export function GradientField({
357
357
  })),
358
358
  });
359
359
  }}
360
- className="inline-flex h-7 items-center gap-1.5 rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-white disabled:cursor-not-allowed disabled:text-neutral-600"
360
+ className="inline-flex h-7 items-center gap-1.5 rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-text-0 disabled:cursor-not-allowed disabled:text-neutral-600"
361
361
  >
362
362
  <ReverseGradientIcon size={16} />
363
363
  Reverse
@@ -442,7 +442,7 @@ export function GradientField({
442
442
  disabled={disabled || parsed.stops.length >= 6}
443
443
  onClick={() => addStop()}
444
444
  title={parsed.stops.length >= 6 ? "Maximum 6 stops" : "Add a gradient stop"}
445
- className="inline-flex h-7 items-center gap-1.5 rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-white active:scale-[0.98] disabled:cursor-not-allowed disabled:text-neutral-600"
445
+ className="inline-flex h-7 items-center gap-1.5 rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-text-0 active:scale-[0.98] disabled:cursor-not-allowed disabled:text-neutral-600"
446
446
  >
447
447
  <Plus size={12} />
448
448
  Add stop
@@ -474,7 +474,7 @@ export function GradientField({
474
474
  type="button"
475
475
  disabled={disabled || parsed.stops.length <= 2}
476
476
  onClick={() => removeStop(index)}
477
- className="mt-[22px] flex h-10 items-center justify-center rounded-lg border border-neutral-700 bg-neutral-950 text-neutral-400 transition-colors hover:border-neutral-600 hover:text-white disabled:cursor-not-allowed disabled:text-neutral-700"
477
+ className="mt-[22px] flex h-10 items-center justify-center rounded-lg border border-neutral-700 bg-neutral-950 text-neutral-400 transition-colors hover:border-neutral-600 hover:text-text-0 disabled:cursor-not-allowed disabled:text-text-off"
478
478
  aria-label={`Remove stop ${index + 1}`}
479
479
  >
480
480
  <X size={12} />
@@ -5,9 +5,9 @@ import { Compare, RotateCcw } from "../../icons/SystemIcons";
5
5
  import type { ColorGradingControllerState } from "./useColorGradingController";
6
6
 
7
7
  const STATUS_DOT_CLASS: Record<ColorGradingControllerState["runtimeStatus"]["state"], string> = {
8
- active: "bg-emerald-400",
9
- pending: "bg-amber-300",
10
- unavailable: "bg-red-400",
8
+ active: "bg-accent",
9
+ pending: "bg-warning-ink",
10
+ unavailable: "bg-danger-ink",
11
11
  missing: "bg-panel-text-5",
12
12
  inactive: "bg-panel-text-5",
13
13
  };
@@ -81,7 +81,7 @@ describe("FlatColorGradingAccessory", () => {
81
81
  const dot = host.querySelector('[data-flat-grade-status-dot="true"]');
82
82
  expect(dot).not.toBeNull();
83
83
  expect(dot?.getAttribute("title")).toBe("Shader active");
84
- expect(dot?.className).toContain("bg-emerald-400");
84
+ expect(dot?.className).toContain("bg-accent");
85
85
  act(() => root.unmount());
86
86
  });
87
87
 
@@ -70,23 +70,20 @@ function HdrBanner({ metadata }: { metadata: MediaMetadata | null }) {
70
70
  return (
71
71
  <div
72
72
  data-flat-grade-hdr-banner="true"
73
- className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2.5 py-1.5 text-[10px] leading-4 text-amber-100"
73
+ className="rounded-md border border-amber-500/30 bg-amber-500/10 px-2.5 py-1.5 text-[10px] leading-4 text-warning-ink"
74
74
  >
75
75
  <div className="mb-0.5 flex items-center justify-between gap-2">
76
76
  <span className="font-semibold">{metadata.color.label} source</span>
77
- <span className="rounded-sm bg-amber-400/20 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-amber-100">
77
+ <span className="rounded-sm bg-amber-500/15 px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-wide text-warning-ink">
78
78
  SDR preview
79
79
  </span>
80
80
  </div>
81
- <p className="text-amber-100/80">
81
+ <p className="text-text-2">
82
82
  These controls use the current SDR shader preview path. Render may stay HDR-tagged, but this
83
83
  is not true HDR color grading yet.
84
84
  </p>
85
85
  {details && (
86
- <p
87
- data-flat-grade-hdr-detail="true"
88
- className="mt-0.5 truncate text-[9px] text-amber-100/55"
89
- >
86
+ <p data-flat-grade-hdr-detail="true" className="mt-0.5 truncate text-[9px] text-text-muted">
90
87
  {details}
91
88
  </p>
92
89
  )}
@@ -214,7 +211,7 @@ export function FlatColorGradingSection({
214
211
  <button
215
212
  type="button"
216
213
  onClick={onRequestPresetPreviews}
217
- className="text-[10px] font-medium text-panel-accent hover:text-panel-accent/80"
214
+ className="text-[10px] font-medium text-accent-ink hover:text-accent-ink/80"
218
215
  >
219
216
  Retry look previews
220
217
  </button>
@@ -519,7 +516,7 @@ export function FlatColorGradingSection({
519
516
  track("button", "Apply grade to scope");
520
517
  onApplyToScope();
521
518
  }}
522
- className="text-[11px] font-medium text-panel-accent hover:text-panel-accent/80 disabled:cursor-not-allowed disabled:opacity-50"
519
+ className="text-[11px] font-medium text-accent-ink hover:text-accent-ink/80 disabled:cursor-not-allowed disabled:opacity-50"
523
520
  >
524
521
  {applyBusy ? "Applying" : "Apply"}
525
522
  </button>
@@ -186,7 +186,7 @@ export function FlatEffectsSection({
186
186
  track("button", "Customize effect palette");
187
187
  onCommitColorGrading({ ...grading, palette: fallback });
188
188
  }}
189
- className="flex min-h-[28px] items-center gap-1 text-[10px] font-medium text-panel-accent hover:text-panel-accent/80"
189
+ className="flex min-h-[28px] items-center gap-1 text-[10px] font-medium text-accent-ink hover:text-accent-ink/80"
190
190
  >
191
191
  <Plus size={11} /> Custom palette
192
192
  </button>
@@ -307,7 +307,7 @@ export function FlatEffectsSection({
307
307
  type="button"
308
308
  title={`Remove ${selectedEffect.label}`}
309
309
  onClick={() => removeEffect(selectedEffect)}
310
- className="text-panel-text-4 hover:text-red-300"
310
+ className="text-panel-text-4 hover:text-danger-ink"
311
311
  >
312
312
  <X size={11} />
313
313
  </button>
@@ -352,7 +352,7 @@ export function FlatEffectsSection({
352
352
  data-flat-effects-add-toggle="true"
353
353
  aria-expanded={catalogOpen}
354
354
  onClick={() => setCatalogOpen((open) => !open)}
355
- className="flex min-h-[30px] items-center gap-1 text-[10px] font-medium text-panel-accent hover:text-panel-accent/80"
355
+ className="flex min-h-[30px] items-center gap-1 text-[10px] font-medium text-accent-ink hover:text-accent-ink/80"
356
356
  >
357
357
  <Plus size={11} /> Add effect
358
358
  </button>
@@ -374,7 +374,7 @@ export function FlatEffectsSection({
374
374
  onClick={() => setCatalogGroup(group.label)}
375
375
  className={`min-h-[25px] min-w-0 truncate bg-panel-bg px-2 text-[9px] ${
376
376
  catalogGroup === group.label
377
- ? "font-medium text-panel-accent"
377
+ ? "font-medium text-accent-ink"
378
378
  : "text-panel-text-4 hover:text-panel-text-1"
379
379
  }`}
380
380
  >
@@ -200,7 +200,7 @@ export function FlatMediaSection({
200
200
  data-flat-media-remove-bg="true"
201
201
  disabled={!canRemoveBackground || removeBusy}
202
202
  onClick={() => void runBackgroundRemoval()}
203
- className="flex items-center gap-1 text-[10px] font-medium text-panel-accent disabled:cursor-not-allowed disabled:opacity-50"
203
+ className="flex items-center gap-1 text-[10px] font-medium text-accent-ink disabled:cursor-not-allowed disabled:opacity-50"
204
204
  title={
205
205
  canRemoveBackground
206
206
  ? "Remove background and save a transparent asset"
@@ -231,7 +231,7 @@ export function FlatMediaSection({
231
231
  <div className="h-1 overflow-hidden rounded-full bg-panel-hover">
232
232
  <div
233
233
  className={`h-full rounded-full ${
234
- removeProgress.status === "failed" ? "bg-red-400" : "bg-panel-accent"
234
+ removeProgress.status === "failed" ? "bg-danger-ink" : "bg-panel-accent"
235
235
  }`}
236
236
  style={{ width: `${Math.max(0, Math.min(100, removeProgress.progress))}%` }}
237
237
  />
@@ -148,13 +148,13 @@ export function FlatMotionSection({
148
148
  {showEffects && (
149
149
  <>
150
150
  {multipleTimelines && (
151
- <p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-amber-400">
151
+ <p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink">
152
152
  This file has multiple GSAP timelines. Animation editing is disabled to prevent data
153
153
  loss — consolidate into a single timeline to enable editing.
154
154
  </p>
155
155
  )}
156
156
  {unsupportedTimelinePattern && (
157
- <p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-amber-400">
157
+ <p className="rounded-lg bg-amber-500/10 px-3 py-2 text-[11px] leading-relaxed text-warning-ink">
158
158
  This timeline uses a computed key the editor can&apos;t resolve statically.
159
159
  </p>
160
160
  )}
@@ -45,7 +45,7 @@ export function FlatOverlaysSection({
45
45
  return <div className="py-4 text-center text-[10px] text-panel-text-4">Loading overlays…</div>;
46
46
  }
47
47
  if (error) {
48
- return <div className="py-4 text-center text-[10px] text-red-300">{error}</div>;
48
+ return <div className="py-4 text-center text-[10px] text-danger-ink">{error}</div>;
49
49
  }
50
50
 
51
51
  const busy = adding !== null;
@@ -95,7 +95,7 @@ export function FlatOverlaysSection({
95
95
  )}
96
96
  <Plus
97
97
  size={12}
98
- className="absolute right-1.5 top-1.5 text-white opacity-70 drop-shadow-sm group-hover:text-panel-accent group-hover:opacity-100"
98
+ className="absolute right-1.5 top-1.5 text-white opacity-70 drop-shadow-sm group-hover:text-accent-ink group-hover:opacity-100"
99
99
  />
100
100
  </span>
101
101
  <span className="block truncate px-2 py-1.5 text-[10px] text-panel-text-2">
@@ -60,7 +60,7 @@ describe("FlatRow", () => {
60
60
  />,
61
61
  );
62
62
  const value = host.querySelector('[data-flat-row-value="true"]');
63
- expect(value?.className).toContain("text-panel-accent");
63
+ expect(value?.className).toContain("text-accent-ink");
64
64
  const reset = host.querySelector<HTMLButtonElement>('[data-flat-row-reset="true"]');
65
65
  expect(reset).not.toBeNull();
66
66
  act(() => reset?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
@@ -356,7 +356,7 @@ describe("FlatSlider", () => {
356
356
  const fill = host.querySelector<HTMLElement>('[data-flat-slider-fill="true"]');
357
357
  expect(fill?.style.width).toBe("100%");
358
358
  const knob = host.querySelector<HTMLElement>('[data-flat-slider-knob="true"]');
359
- expect(knob?.className).toContain("bg-white");
359
+ expect(knob?.className).toContain("bg-text-0");
360
360
  act(() => root.unmount());
361
361
  });
362
362
 
@@ -1172,7 +1172,7 @@ describe("FlatSelectRow", () => {
1172
1172
  />,
1173
1173
  );
1174
1174
  const select = host.querySelector<HTMLSelectElement>("select");
1175
- expect(select?.className).toContain("text-panel-accent");
1175
+ expect(select?.className).toContain("text-accent-ink");
1176
1176
  const reset = host.querySelector<HTMLButtonElement>('[data-flat-select-reset="true"]');
1177
1177
  act(() => reset?.dispatchEvent(new MouseEvent("click", { bubbles: true })));
1178
1178
  expect(onReset).toHaveBeenCalledTimes(1);
@@ -1364,6 +1364,7 @@ describe("FlatSlider — typed value", () => {
1364
1364
  const still = host.querySelector<HTMLInputElement>('[data-flat-slider-input="true"]');
1365
1365
  expect(still).not.toBeNull();
1366
1366
  expect(still?.getAttribute("aria-invalid")).toBe("true");
1367
+ expect(still?.className).toContain("border-dashed");
1367
1368
  act(() => still?.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true })));
1368
1369
  expect(host.querySelector('[data-flat-slider-input="true"]')).toBeNull();
1369
1370
  expect(host.querySelector('[data-flat-slider-value="true"]')?.textContent).toBe("1x");
@@ -537,7 +537,7 @@ export function FlatSlider({
537
537
  <div
538
538
  data-flat-slider-knob="true"
539
539
  className={`absolute top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full ${
540
- tier === "explicitCustom" ? "h-2 w-2 bg-white" : "h-[7px] w-[7px] bg-panel-text-4"
540
+ tier === "explicitCustom" ? "h-2 w-2 bg-text-0" : "h-[7px] w-[7px] bg-panel-text-4"
541
541
  }`}
542
542
  style={{ left: `${clampedPct}%` }}
543
543
  />
@@ -76,7 +76,7 @@ export function FlatSliderReadout({
76
76
  }}
77
77
  onBlur={() => commit(false)}
78
78
  className={`w-11 shrink-0 rounded-[3px] border bg-panel-surface px-1 text-right font-mono text-[10px] text-panel-text-0 outline-none ${
79
- invalid ? "border-red-400" : "border-panel-accent"
79
+ invalid ? "border-dashed border-danger-ink" : "border-panel-accent"
80
80
  }`}
81
81
  />
82
82
  );
@@ -433,7 +433,7 @@ export function FontFamilyField({
433
433
  type="button"
434
434
  disabled={disabled || loadingLocalFonts}
435
435
  onClick={loadBrowserLocalFonts}
436
- className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-neutral-700"
436
+ className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-text-off"
437
437
  >
438
438
  {loadingLocalFonts ? "..." : "Local"}
439
439
  </button>
@@ -442,7 +442,7 @@ export function FontFamilyField({
442
442
  type="button"
443
443
  disabled={disabled || importingFonts || !onImportFonts}
444
444
  onClick={() => fontInputRef.current?.click()}
445
- className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-neutral-700"
445
+ className="rounded-lg border border-neutral-700 bg-neutral-900 px-2.5 text-[10px] font-medium text-neutral-400 transition-colors hover:border-neutral-600 hover:text-neutral-100 disabled:cursor-not-allowed disabled:text-text-off"
446
446
  >
447
447
  {importingFonts ? "..." : "Import"}
448
448
  </button>
@@ -111,7 +111,7 @@ function FxCarveMember({
111
111
  {/* The lane is where an automated value comes from, and where it is
112
112
  edited — saying so is the difference between a stale readout and
113
113
  a pointer to the thing that owns it. */}
114
- {automated ? <span className="text-[#3CE6AC]">A</span> : null}
114
+ {automated ? <span className="text-accent-ink">A</span> : null}
115
115
  </span>
116
116
  );
117
117
  })}
@@ -90,7 +90,7 @@ export function AutomationToggle({
90
90
  type="button"
91
91
  className={`hf-fx-automate w-[16px] shrink-0 rounded-[3px] border font-mono text-[9px] leading-none ${
92
92
  automated
93
- ? "border-panel-accent text-panel-accent"
93
+ ? "border-panel-accent text-accent-ink"
94
94
  : "border-panel-border-input text-panel-text-2 hover:text-panel-text-0"
95
95
  }`}
96
96
  aria-pressed={automated}
@@ -237,7 +237,7 @@ export function FxParamRow({
237
237
  {/* See the enum row above for why the name wraps instead of truncating. */}
238
238
  <span
239
239
  className={`hf-fx-label w-[86px] shrink-0 wrap-break-word text-[10px] leading-tight ${
240
- automated ? "text-panel-accent" : "text-panel-text-2"
240
+ automated ? "text-accent-ink" : "text-panel-text-2"
241
241
  }`}
242
242
  >
243
243
  {param.label}
@@ -115,7 +115,7 @@ function Fader({
115
115
  </span>
116
116
  <span
117
117
  className={`hf-fx-eq-value font-mono text-[9px] tabular-nums ${
118
- moved ? "text-panel-accent" : "text-panel-text-2"
118
+ moved ? "text-accent-ink" : "text-panel-text-2"
119
119
  }`}
120
120
  >
121
121
  {moved ? shown(value) : "0"}
@@ -160,7 +160,7 @@ export function FxEqModule({
160
160
  <span className="font-mono text-[9px] text-panel-text-2">{bands.length}-band</span>
161
161
  <button
162
162
  type="button"
163
- className="hf-fx-remove px-1 text-[11px] text-panel-text-2 hover:text-panel-danger"
163
+ className="hf-fx-remove px-1 text-[11px] text-panel-text-2 hover:text-danger-ink"
164
164
  aria-label="Remove Tone"
165
165
  disabled={disabled}
166
166
  onClick={onRemove}
@@ -216,7 +216,7 @@ function FxNodeHeader({
216
216
  />
217
217
  <button
218
218
  type="button"
219
- className="hf-fx-remove px-1 font-mono text-[11px] text-panel-text-2 hover:text-red-400 disabled:opacity-40"
219
+ className="hf-fx-remove px-1 font-mono text-[11px] text-panel-text-2 hover:text-danger-ink disabled:opacity-40"
220
220
  title="Remove"
221
221
  disabled={disabled}
222
222
  onClick={onRemove}
@@ -189,7 +189,7 @@ export function FxPresetRun({
189
189
  </button>
190
190
  <button
191
191
  type="button"
192
- className="hf-fx-preset-run-remove px-1 font-mono text-[11px] text-panel-text-2 hover:text-red-400 disabled:opacity-40"
192
+ className="hf-fx-preset-run-remove px-1 font-mono text-[11px] text-panel-text-2 hover:text-danger-ink disabled:opacity-40"
193
193
  title={`Remove ${preset.label}`}
194
194
  disabled={disabled}
195
195
  onClick={onRemoveRun}
@@ -231,7 +231,7 @@ export function MediaSection({
231
231
  <div className="h-1 overflow-hidden rounded-full bg-panel-border">
232
232
  <div
233
233
  className={`h-full rounded-full ${
234
- removeProgress.status === "failed" ? "bg-red-400" : "bg-studio-accent"
234
+ removeProgress.status === "failed" ? "bg-danger-ink" : "bg-studio-accent"
235
235
  }`}
236
236
  style={{ width: `${Math.max(0, Math.min(100, removeProgress.progress))}%` }}
237
237
  />
@@ -234,7 +234,7 @@ export function SegmentedControl({
234
234
  aria-pressed={option.value === value}
235
235
  className={`min-w-0 truncate rounded px-2 py-[5px] text-[11px] font-medium transition-colors disabled:cursor-not-allowed ${
236
236
  option.value === value
237
- ? "bg-panel-hover text-white"
237
+ ? "bg-panel-hover text-text-0"
238
238
  : "text-panel-text-4 hover:text-panel-text-2"
239
239
  }`}
240
240
  >
@@ -323,7 +323,7 @@ function TextFieldEditor({
323
323
  track("button", "Remove text field");
324
324
  onRemoveTextField(field.key);
325
325
  }}
326
- className="inline-flex h-7 shrink-0 items-center rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-white"
326
+ className="inline-flex h-7 shrink-0 items-center rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-text-0"
327
327
  >
328
328
  Remove
329
329
  </button>
@@ -474,7 +474,7 @@ export function TextSection({
474
474
  if (nextKey) setActiveTextFieldKey(nextKey);
475
475
  });
476
476
  }}
477
- className="inline-flex h-7 max-w-full items-center gap-1.5 rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-white"
477
+ className="inline-flex h-7 max-w-full items-center gap-1.5 rounded-lg border border-neutral-700 bg-neutral-950 px-2.5 text-[11px] font-medium text-neutral-300 transition-colors hover:border-neutral-600 hover:text-text-0"
478
478
  >
479
479
  <Plus size={12} className="shrink-0" />
480
480
  <span className="truncate">Add text</span>
@@ -27,6 +27,6 @@ describe("value tier class maps", () => {
27
27
  expect(VALUE_TIER_LABEL_CLASS[tier]).toBeTruthy();
28
28
  expect(VALUE_TIER_VALUE_CLASS[tier]).toBeTruthy();
29
29
  }
30
- expect(VALUE_TIER_VALUE_CLASS.explicitCustom).toBe("text-panel-accent");
30
+ expect(VALUE_TIER_VALUE_CLASS.explicitCustom).toBe("text-accent-ink");
31
31
  });
32
32
  });
@@ -24,5 +24,5 @@ export const VALUE_TIER_LABEL_CLASS: Record<PropertyValueTier, string> = {
24
24
  export const VALUE_TIER_VALUE_CLASS: Record<PropertyValueTier, string> = {
25
25
  default: "text-panel-text-3",
26
26
  explicitDefault: "text-panel-text-0",
27
- explicitCustom: "text-panel-accent",
27
+ explicitCustom: "text-accent-ink",
28
28
  };
@@ -1,4 +1,5 @@
1
1
  import { memo, useState, useCallback, useRef, useEffect } from "react";
2
+ import { toastSurface } from "../StudioToast";
2
3
  import {
3
4
  trackStudioFeedback,
4
5
  trackStudioFeedbackShown,
@@ -212,24 +213,11 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
212
213
  }}
213
214
  >
214
215
  <div
215
- className="flex w-[min(340px,calc(100vw-48px))] flex-col gap-2.5 overflow-hidden rounded-2xl px-3.5 py-3"
216
- style={{
217
- background: failed
218
- ? "linear-gradient(135deg, rgba(127,29,29,0.55), rgba(80,10,10,0.45))"
219
- : "linear-gradient(135deg, rgba(38,38,38,0.55), rgba(23,23,23,0.45))",
220
- backdropFilter: "blur(16px) saturate(1.6)",
221
- WebkitBackdropFilter: "blur(16px) saturate(1.6)",
222
- border: `1px solid ${failed ? "rgba(239,68,68,0.18)" : "rgba(255,255,255,0.08)"}`,
223
- boxShadow: [
224
- "0 8px 32px rgba(0,0,0,0.35)",
225
- `inset 0 1px 0 ${failed ? "rgba(239,68,68,0.12)" : "rgba(255,255,255,0.06)"}`,
226
- "inset 0 -1px 0 rgba(0,0,0,0.15)",
227
- ].join(", "),
228
- }}
216
+ className={`flex w-[min(340px,calc(100vw-48px))] flex-col gap-2.5 overflow-hidden rounded-2xl px-3.5 py-3 ${toastSurface(failed)}`}
229
217
  >
230
218
  <div className="flex items-start gap-3">
231
219
  <p
232
- className={`min-w-0 flex-1 text-[12px] leading-5 ${failed ? "text-red-100" : "text-neutral-100"}`}
220
+ className={`min-w-0 flex-1 text-[12px] leading-5 ${failed ? "text-danger-ink" : "text-text-0"}`}
233
221
  >
234
222
  {step === "thanks" ? "Thanks, that helps." : title}
235
223
  </p>
@@ -243,7 +231,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
243
231
  if (req) trackStudioFeedbackInterviewClick({ reason: req.reason });
244
232
  close("sent");
245
233
  }}
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"
234
+ className="h-6 shrink-0 whitespace-nowrap rounded-full border border-border-strong bg-hover px-2.5 text-[11px] leading-6 text-neutral-100 transition-[background-color,border-color,transform] duration-150 ease-out hover:border-text-muted hover:bg-press active:scale-[0.97] motion-reduce:transition-none"
247
235
  >
248
236
  Talk to us, 30 min
249
237
  </a>
@@ -251,7 +239,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
251
239
  <button
252
240
  type="button"
253
241
  onClick={() => close("close")}
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"
242
+ 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-press hover:text-neutral-200"
255
243
  aria-label="Dismiss"
256
244
  >
257
245
  <svg
@@ -270,7 +258,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
270
258
  {/* The failure text the user already saw, quoted back so the report
271
259
  carries it without asking them to retype the error. */}
272
260
  {failed && request.detail && step !== "thanks" && (
273
- <p className="line-clamp-2 text-[11px] leading-4 text-red-200/70">{request.detail}</p>
261
+ <p className="line-clamp-2 text-[11px] leading-4 text-danger-ink">{request.detail}</p>
274
262
  )}
275
263
 
276
264
  {step === "rating" && (
@@ -283,7 +271,7 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
283
271
  key={n}
284
272
  // A resting fill, so the row reads as eleven controls rather
285
273
  // 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/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"
274
+ className="flex h-7 cursor-pointer items-center justify-center rounded-md bg-hover text-[11px] tabular-nums text-neutral-400 transition-[background-color,color,transform] duration-150 ease-out hover:bg-press hover:text-neutral-100 active:scale-[0.97] has-checked:bg-text-0 has-checked:text-bg-0 has-focus-visible:ring-1 has-focus-visible:ring-ring motion-reduce:transition-none"
287
275
  >
288
276
  <input
289
277
  type="radio"
@@ -328,8 +316,8 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
328
316
  // attached to nothing.
329
317
  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
318
  failed
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"
319
+ ? "border-danger/30 bg-danger/10 text-danger-ink hover:border-danger/60 hover:bg-danger/20"
320
+ : "border-border bg-hover text-neutral-300 hover:border-text-muted hover:bg-press hover:text-text-0"
333
321
  }`}
334
322
  >
335
323
  {preset.label}
@@ -355,12 +343,12 @@ export const StudioFeedbackCard = memo(function StudioFeedbackCard() {
355
343
  placeholder={followUp.placeholder}
356
344
  aria-label={followUp.prompt}
357
345
  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-hidden transition-colors duration-150 placeholder:text-neutral-500 focus:border-white/25"
346
+ className="h-7 min-w-0 flex-1 rounded-md border border-border bg-input px-2 text-[11px] text-neutral-100 outline-hidden transition-colors duration-150 placeholder:text-neutral-500 focus:border-ring"
359
347
  />
360
348
  <button
361
349
  type="button"
362
350
  onClick={() => submit()}
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"
351
+ className="h-7 shrink-0 rounded-md bg-press px-2.5 text-[11px] text-neutral-100 transition-[background-color,transform] duration-150 ease-out hover:bg-on active:scale-[0.97] motion-reduce:transition-none"
364
352
  >
365
353
  Send
366
354
  </button>
@@ -142,7 +142,7 @@ export function AssetPreviewOverlay() {
142
142
  >
143
143
  {/* Close button */}
144
144
  <button
145
- className="absolute top-2 right-2 w-6 h-6 rounded-full bg-neutral-800 hover:bg-neutral-700 text-neutral-300 hover:text-white flex items-center justify-center transition-colors z-10"
145
+ className="absolute top-2 right-2 w-6 h-6 rounded-full bg-raised hover:bg-press text-text-2 hover:text-text-0 flex items-center justify-center transition-colors z-10"
146
146
  onClick={(e) => {
147
147
  e.stopPropagation();
148
148
  clearPreviewAsset();
@@ -215,7 +215,7 @@ function useMeterLoop(
215
215
 
216
216
  function Bar({ maskRef, peakRef }: { maskRef: Ref<HTMLDivElement>; peakRef: Ref<HTMLDivElement> }) {
217
217
  return (
218
- <div className="relative h-full w-[18px] overflow-hidden rounded-[2px] bg-neutral-900">
218
+ <div className="scheme-dark relative h-full w-[18px] overflow-hidden rounded-[2px] bg-neutral-900">
219
219
  <div
220
220
  className="absolute inset-x-0 bottom-0 bg-green-500"
221
221
  style={{ height: `${AMBER_AT * 100}%` }}
@@ -237,7 +237,7 @@ function Bar({ maskRef, peakRef }: { maskRef: Ref<HTMLDivElement>; peakRef: Ref<
237
237
  <div
238
238
  ref={peakRef}
239
239
  data-testid="meter-peak"
240
- className="absolute inset-x-0 bottom-0 h-px bg-white"
240
+ className="absolute inset-x-0 bottom-0 h-px bg-text-0"
241
241
  />
242
242
  </div>
243
243
  );
@@ -413,13 +413,14 @@ describe("NLEPreview", () => {
413
413
  describe("a missing poster", () => {
414
414
  const renderUrl =
415
415
  "/api/projects/timeline-edit-playground/thumbnail/index.html?t=0&output=source";
416
- const fetchSpy = vi.fn(() => Promise.resolve(new Response()));
416
+ const fetchSpy = vi.fn((_url: string, _init?: RequestInit) => Promise.resolve(new Response()));
417
417
  beforeEach(() => {
418
418
  fetchSpy.mockClear();
419
419
  vi.stubGlobal("fetch", fetchSpy);
420
420
  });
421
421
  afterEach(() => vi.unstubAllGlobals());
422
422
 
423
+ const renders = () => fetchSpy.mock.calls.map(([url]) => url);
423
424
  const settle = (
424
425
  view: ReturnType<typeof renderPreview>,
425
426
  steps: Array<"ready" | "missing" | "loaded">,
@@ -437,7 +438,10 @@ describe("NLEPreview", () => {
437
438
  it("is rendered for the next open when the live frame is ready first", () => {
438
439
  const view = renderPreview();
439
440
  settle(view, ["ready", "missing"]);
440
- expect(fetchSpy.mock.calls).toEqual([[renderUrl]]);
441
+ expect(renders()).toEqual([renderUrl]);
442
+ expect(fetchSpy.mock.calls[0]?.[1]?.signal, "rendered as scheduler work").toBeInstanceOf(
443
+ AbortSignal,
444
+ );
441
445
  expect(view.stage.querySelector('[data-testid="preview-poster"]')).toBeNull();
442
446
  view.cleanup();
443
447
  });
@@ -447,7 +451,7 @@ describe("NLEPreview", () => {
447
451
  settle(view, ["missing"]);
448
452
  expect(fetchSpy).not.toHaveBeenCalled();
449
453
  settle(view, ["ready", "ready"]);
450
- expect(fetchSpy.mock.calls).toEqual([[renderUrl]]);
454
+ expect(renders()).toEqual([renderUrl]);
451
455
  view.cleanup();
452
456
  });
453
457
 
@@ -458,7 +462,7 @@ describe("NLEPreview", () => {
458
462
  settle(view, ["ready"]);
459
463
  view.render();
460
464
  settle(view, ["ready", "missing"]);
461
- expect(fetchSpy.mock.calls).toEqual([[renderUrl]]);
465
+ expect(renders()).toEqual([renderUrl]);
462
466
  view.cleanup();
463
467
  });
464
468
  });
@@ -152,7 +152,7 @@ export function PreviewOverlays({
152
152
  <button
153
153
  type="button"
154
154
  onClick={enterCaptionMode}
155
- className="pointer-events-auto 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"
155
+ className="pointer-events-auto 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-accent-ink focus-visible:outline-solid focus-visible:outline-1 focus-visible:outline-studio-accent"
156
156
  >
157
157
  Edit captions
158
158
  </button>
@@ -116,9 +116,9 @@ export function PreviewPane({
116
116
  <div
117
117
  ref={containerRef}
118
118
  // A recessed well inset in its dock card; dropped in fullscreen so the
119
- // preview fills the screen edge-to-edge.
120
- className={`flex-1 min-h-0 flex flex-col overflow-hidden bg-[var(--studio-preview-bg,var(--color-neutral-950))] ${
121
- isFullscreen ? "" : "m-3 rounded-md border border-neutral-800/50"
119
+ // preview fills the screen edge-to-edge. The player stays dark in both themes.
120
+ className={`scheme-dark flex-1 min-h-0 flex flex-col overflow-hidden bg-[var(--studio-preview-bg,var(--color-neutral-950))] ${
121
+ isFullscreen ? "" : "m-3 rounded-md border border-border"
122
122
  }`}
123
123
  data-studio-fullscreen-target=""
124
124
  >