@bycrux/editor 1.2.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/package.json +1 -1
  2. package/src/ControlsInfoModal.tsx +5 -5
  3. package/src/__tests__/no-opacity-modifier-on-editor-vars.test.ts +84 -0
  4. package/src/__tests__/node-shims.d.ts +19 -0
  5. package/src/__tests__/portal-roots-set-editor-text.test.ts +55 -0
  6. package/src/carousel/AddElementMenu.tsx +1 -1
  7. package/src/carousel/CarouselEditor.tsx +11 -11
  8. package/src/carousel/CarouselRenderModal.tsx +14 -14
  9. package/src/carousel/OverlayPicker.tsx +4 -4
  10. package/src/carousel/SlidePropertyPanel.tsx +6 -6
  11. package/src/carousel/__tests__/CarouselEditor.test.tsx +12 -0
  12. package/src/components/FilmstripScrubber.tsx +1 -1
  13. package/src/crop/VideoSourceCropModal.tsx +5 -5
  14. package/src/state/__tests__/use-project-sync.test.tsx +76 -0
  15. package/src/state/use-project-sync.ts +21 -1
  16. package/src/text/FontPicker.tsx +3 -3
  17. package/src/text/TextFormattingToolbar.tsx +3 -3
  18. package/src/types.ts +18 -0
  19. package/src/ui/Loader.tsx +1 -1
  20. package/src/ui/NumberField.tsx +3 -3
  21. package/src/ui/SwatchInput.tsx +1 -1
  22. package/src/ui/Tooltip.tsx +1 -1
  23. package/src/ui/input.tsx +1 -1
  24. package/src/ui/label.tsx +1 -1
  25. package/src/ui/textarea.tsx +1 -1
  26. package/src/video/AudioPolishModal.tsx +22 -22
  27. package/src/video/CaptionListPanel.test.tsx +8 -7
  28. package/src/video/CaptionListPanel.tsx +17 -17
  29. package/src/video/CaptionRegenModal.tsx +8 -8
  30. package/src/video/CaptionStyleGallery.tsx +1 -1
  31. package/src/video/CommandPalette.tsx +8 -8
  32. package/src/video/ImageToneMenu.tsx +13 -14
  33. package/src/video/OverlayInspector.tsx +7 -7
  34. package/src/video/RenderModal.tsx +76 -78
  35. package/src/video/VersionCompare.tsx +10 -10
  36. package/src/video/VersionPanel.tsx +2 -2
  37. package/src/video/VideoEditor.tsx +48 -23
  38. package/src/video/__tests__/RenderModal.hostTheme.test.tsx +85 -0
  39. package/src/video/__tests__/VideoEditor.context.test.tsx +6 -0
  40. package/src/video/__tests__/VideoEditor.pendingSurface.test.tsx +233 -0
  41. package/src/video/__tests__/VideoEditor.saveError.test.tsx +105 -0
  42. package/src/video/__tests__/export-limits.test.ts +27 -2
  43. package/src/video/__tests__/use-report-context.test.tsx +22 -1
  44. package/src/video/export-limits.ts +22 -1
  45. package/src/video/panels/ClipPropertiesPanel.tsx +4 -4
  46. package/src/video/panels/OverlayContentPanel.tsx +5 -5
  47. package/src/video/preview/OverlayItemsLayer.tsx +7 -2
  48. package/src/video/preview/PreviewPlayer.tsx +14 -0
  49. package/src/video/preview/SocialPreviewMenu.tsx +6 -6
  50. package/src/video/preview/__tests__/PreviewPlayer.playing.test.tsx +62 -0
  51. package/src/video/preview/__tests__/resolveOverlayPropPaths.test.ts +21 -0
  52. package/src/video/timeline/EditableSegment.tsx +1 -1
  53. package/src/video/timeline/Scrubber.tsx +2 -2
  54. package/src/video/timeline/SpeedControl.tsx +3 -3
  55. package/src/video/timeline/Timeline.tsx +3 -3
  56. package/src/video/timeline/TrackGutter.tsx +6 -6
  57. package/src/video/timeline/TrackSettingsPopover.tsx +4 -4
  58. package/src/video/timeline/VolumeControl.tsx +3 -3
  59. package/src/video/use-report-context.ts +10 -7
@@ -115,12 +115,10 @@ interface RenderModalProps<P extends Project = Project> {
115
115
  preRenderOptions?: PreRenderOptions
116
116
  /** Editor theme mode — light/dark. The export dialog and progress/error
117
117
  * panels follow `--editor-surface`, so warning/error hues need to darken
118
- * in light mode. The render-log terminal boxes stay dark by design and so
119
- * take no mode: they are painted an OPAQUE near-black (`#0a0e17`/`#0e131f`)
120
- * rather than a translucent `bg-black/40`, which would have composited to
121
- * mid-grey over a light surface instead of staying a terminal. Those two
122
- * values are exactly what the old translucent blacks composited to over the
123
- * dark `--editor-surface`, so dark mode is unchanged. Absent -> dark,
118
+ * in light mode. The render-log terminal boxes stay dark by design: they are
119
+ * painted an OPAQUE near-black (`#0a0e17`) with fixed white text, so they
120
+ * read the same under either theme. The phase stepper and footer buttons
121
+ * follow the theme tokens instead. Absent -> dark,
124
122
  * matching every existing caller. */
125
123
  mode?: 'light' | 'dark'
126
124
  }
@@ -290,10 +288,10 @@ function PhaseStepper({ current, phases }: { current: RenderPhase; phases: Rende
290
288
  <span
291
289
  className={
292
290
  complete
293
- ? 'text-sm text-[var(--editor-text)]/70'
291
+ ? 'text-sm text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)]'
294
292
  : active
295
293
  ? 'text-sm font-semibold text-[var(--editor-text)]'
296
- : 'text-sm text-[var(--editor-text)]/35'
294
+ : 'text-sm text-[color-mix(in_srgb,var(--editor-text)_35%,transparent)]'
297
295
  }
298
296
  >
299
297
  {phaseLabel(phase)}
@@ -309,15 +307,15 @@ function PhaseStepper({ current, phases }: { current: RenderPhase; phases: Rende
309
307
 
310
308
  function LogLine({ text }: { text: string }) {
311
309
  const t = text.replace(/^\[montaj render\]\s*/, '')
312
- let color = 'text-[var(--editor-text)]/60'
310
+ let color = 'text-white/60'
313
311
  if (/ready|complete|done|encoded|assembled/i.test(t)) color = 'text-green-400'
314
312
  else if (/rendering|bundling|launching|browsers/i.test(t)) color = 'text-sky-400'
315
313
  else if (/trimming|building|composing/i.test(t)) color = 'text-amber-400'
316
- else if (/frame\s+\d+\/\d+/i.test(t)) color = 'text-[var(--editor-text)]/55'
314
+ else if (/frame\s+\d+\/\d+/i.test(t)) color = 'text-white/55'
317
315
  else if (/error|fail|warn/i.test(t)) color = 'text-red-400'
318
316
 
319
317
  const prefix = text.startsWith('[montaj render]')
320
- ? <span className="text-[var(--editor-text)]/40">[render] </span>
318
+ ? <span className="text-white/40">[render] </span>
321
319
  : null
322
320
 
323
321
  return (
@@ -385,7 +383,7 @@ function ProgressBar({ value }: { value: number | null }) {
385
383
  return (
386
384
  <div className="w-full max-w-xs" role="progressbar" aria-label="Render progress"
387
385
  aria-valuenow={pct === null ? undefined : Math.round(pct)} aria-valuemin={0} aria-valuemax={100}>
388
- <div className="h-1.5 w-full overflow-hidden rounded-full bg-[var(--editor-text)]/10">
386
+ <div className="h-1.5 w-full overflow-hidden rounded-full bg-[color-mix(in_srgb,var(--editor-text)_10%,transparent)]">
389
387
  {pct === null ? (
390
388
  <>
391
389
  <style>{'@keyframes montajRenderIndet{0%{transform:translateX(-100%)}100%{transform:translateX(320%)}}'}</style>
@@ -792,16 +790,16 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
792
790
  }
793
791
  return (<>
794
792
  {createPortal(
795
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-md p-4">
796
- <div className="w-full max-w-3xl bg-[var(--editor-surface)] border border-white/10 rounded-2xl shadow-2xl flex flex-col overflow-hidden">
793
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-md p-4 text-[var(--editor-text)]">
794
+ <div className="w-full max-w-3xl bg-[var(--editor-surface)] border border-[var(--editor-border)] rounded-2xl shadow-2xl flex flex-col overflow-hidden">
797
795
 
798
796
  {/* Header */}
799
- <div className="flex items-center justify-between px-5 py-4 border-b border-white/10">
797
+ <div className="flex items-center justify-between px-5 py-4 border-b border-[var(--editor-border)]">
800
798
  <h2 className="text-sm font-semibold text-[var(--editor-text)]">Export</h2>
801
799
  <button
802
800
  onClick={handleCancel}
803
801
  aria-label="Close"
804
- className="text-[var(--editor-text)]/55 hover:text-[var(--editor-text)] transition-colors text-lg leading-none"
802
+ className="text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] hover:text-[var(--editor-text)] transition-colors text-lg leading-none"
805
803
  >
806
804
  ×
807
805
  </button>
@@ -813,7 +811,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
813
811
  {/* Left — cover preview + editor */}
814
812
  <div className="md:w-64 shrink-0 flex flex-col gap-2">
815
813
  <div
816
- className="relative mx-auto max-w-full overflow-hidden rounded-lg border border-white/10 bg-black/40 flex items-center justify-center"
814
+ className="relative mx-auto max-w-full overflow-hidden rounded-lg border border-[var(--editor-border)] bg-black/40 flex items-center justify-center"
817
815
  style={coverPortrait
818
816
  ? { aspectRatio: coverAspect, height: 'min(46vh, 22rem)' }
819
817
  : { aspectRatio: coverAspect, width: '100%' }}
@@ -827,7 +825,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
827
825
  ) : coverLoading ? (
828
826
  <Loader size="md" label="Loading cover" />
829
827
  ) : (
830
- <span className="text-[11px] text-[var(--editor-text)]/40">No preview</span>
828
+ <span className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">No preview</span>
831
829
  )}
832
830
  </div>
833
831
  {adapter.getSampleFrame && (
@@ -835,7 +833,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
835
833
  <button
836
834
  onClick={() => setCoverEditing(o => !o)}
837
835
  aria-expanded={coverEditing}
838
- className="self-start text-xs text-[var(--editor-text)]/70 hover:text-[var(--editor-text)] transition-colors"
836
+ className="self-start text-xs text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)] hover:text-[var(--editor-text)] transition-colors"
839
837
  >
840
838
  {coverEditing ? 'Done' : 'Edit cover'}
841
839
  </button>
@@ -857,7 +855,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
857
855
  className={`relative shrink-0 overflow-hidden rounded border transition-colors ${
858
856
  active
859
857
  ? 'border-[var(--editor-accent)] ring-1 ring-[var(--editor-accent)]'
860
- : 'border-white/10 hover:border-white/40'
858
+ : 'border-[var(--editor-border)] hover:border-[color-mix(in_srgb,var(--editor-text)_40%,transparent)]'
861
859
  }`}
862
860
  style={{ aspectRatio: coverAspect, height: 72 }}
863
861
  >
@@ -869,7 +867,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
869
867
  })}
870
868
  </div>
871
869
  ) : (
872
- <p className="text-[11px] text-[var(--editor-text)]/40">No frames to pick from.</p>
870
+ <p className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">No frames to pick from.</p>
873
871
  )
874
872
  )}
875
873
  </>
@@ -880,7 +878,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
880
878
  <div className="flex-1 min-w-0 flex flex-col gap-4">
881
879
  {/* Name */}
882
880
  <label className="flex flex-col gap-1.5">
883
- <span className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]/50">Name</span>
881
+ <span className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">Name</span>
884
882
  <input
885
883
  type="text"
886
884
  value={name}
@@ -892,9 +890,9 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
892
890
 
893
891
  {/* Save to */}
894
892
  <div className="flex flex-col gap-1">
895
- <span className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]/50">Save to</span>
896
- <span className="text-xs font-mono text-[var(--editor-text)]/70 truncate">output/{outputName}.mp4</span>
897
- <span className="text-[11px] text-[var(--editor-text)]/45">Download after export.</span>
893
+ <span className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">Save to</span>
894
+ <span className="text-xs font-mono text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)] truncate">output/{outputName}.mp4</span>
895
+ <span className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">Download after export.</span>
898
896
  </div>
899
897
 
900
898
  {/* Resolution — source-capped tier picker. Only rendered when the
@@ -908,7 +906,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
908
906
  const currentShort = Math.min(...res.value)
909
907
  return (
910
908
  <div className="flex flex-col gap-2">
911
- <p className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]/50">Resolution</p>
909
+ <p className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">Resolution</p>
912
910
  <div role="radiogroup" aria-label="Resolution" className="grid grid-cols-2 sm:grid-cols-4 gap-2">
913
911
  {res.available.map(([w, h]) => {
914
912
  const short = Math.min(w, h)
@@ -921,12 +919,12 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
921
919
  onClick={() => res.set([w, h])}
922
920
  className={`flex flex-col gap-1 rounded-lg border p-2.5 text-left transition-colors ${
923
921
  active
924
- ? 'border-violet-400/60 bg-violet-400/10'
925
- : 'border-[var(--editor-border)] hover:bg-[var(--editor-text)]/5'
922
+ ? 'border-[var(--editor-accent)] bg-[color-mix(in_srgb,var(--editor-accent)_12%,transparent)]'
923
+ : 'border-[var(--editor-border)] hover:bg-[color-mix(in_srgb,var(--editor-text)_5%,transparent)]'
926
924
  }`}
927
925
  >
928
926
  <span className="text-xs font-semibold text-[var(--editor-text)]">{resLabel([w, h])}</span>
929
- <span className="text-[10px] leading-snug text-[var(--editor-text)]/55">{w} × {h}</span>
927
+ <span className="text-[10px] leading-snug text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">{w} × {h}</span>
930
928
  </button>
931
929
  )
932
930
  })}
@@ -940,7 +938,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
940
938
  const fps = preRenderOptions.fps
941
939
  return (
942
940
  <div className="flex flex-col gap-2">
943
- <p className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]/50">Frame rate</p>
941
+ <p className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">Frame rate</p>
944
942
  <div role="radiogroup" aria-label="Frame rate" className="grid grid-cols-3 gap-2">
945
943
  {fps.available.map(f => {
946
944
  const active = f === fps.value
@@ -953,12 +951,12 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
953
951
  onClick={() => fps.set(f)}
954
952
  className={`flex flex-col gap-1 rounded-lg border p-2.5 text-left transition-colors ${
955
953
  active
956
- ? 'border-violet-400/60 bg-violet-400/10'
957
- : 'border-[var(--editor-border)] hover:bg-[var(--editor-text)]/5'
954
+ ? 'border-[var(--editor-accent)] bg-[color-mix(in_srgb,var(--editor-accent)_12%,transparent)]'
955
+ : 'border-[var(--editor-border)] hover:bg-[color-mix(in_srgb,var(--editor-text)_5%,transparent)]'
958
956
  }`}
959
957
  >
960
958
  <span className="text-xs font-semibold text-[var(--editor-text)]">{f} fps</span>
961
- <span className="text-[10px] leading-snug text-[var(--editor-text)]/55">{hint}</span>
959
+ <span className="text-[10px] leading-snug text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">{hint}</span>
962
960
  </button>
963
961
  )
964
962
  })}
@@ -972,7 +970,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
972
970
  <>
973
971
  {/* Format */}
974
972
  <div className="flex flex-col gap-2">
975
- <p className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]/50">Format</p>
973
+ <p className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">Format</p>
976
974
  <div role="radiogroup" aria-label="Format" className="grid grid-cols-3 gap-2">
977
975
  {EXPORT_CHOICES.map(choice => {
978
976
  const active = choice.id === exportChoice
@@ -984,12 +982,12 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
984
982
  onClick={() => setExport(choice.id)}
985
983
  className={`flex flex-col gap-1 rounded-lg border p-2.5 text-left transition-colors ${
986
984
  active
987
- ? 'border-violet-400/60 bg-violet-400/10'
988
- : 'border-[var(--editor-border)] hover:bg-[var(--editor-text)]/5'
985
+ ? 'border-[var(--editor-accent)] bg-[color-mix(in_srgb,var(--editor-accent)_12%,transparent)]'
986
+ : 'border-[var(--editor-border)] hover:bg-[color-mix(in_srgb,var(--editor-text)_5%,transparent)]'
989
987
  }`}
990
988
  >
991
989
  <span className="text-xs font-semibold text-[var(--editor-text)]">{choice.label}</span>
992
- <span className="text-[10px] leading-snug text-[var(--editor-text)]/55">{choice.blurb}</span>
990
+ <span className="text-[10px] leading-snug text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">{choice.blurb}</span>
993
991
  </button>
994
992
  )
995
993
  })}
@@ -1010,7 +1008,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1010
1008
  <button
1011
1009
  onClick={() => setImageColor(o => !o)}
1012
1010
  aria-expanded={imageColorOpen}
1013
- className="flex items-center gap-1.5 self-start text-xs text-[var(--editor-text)]/70 hover:text-[var(--editor-text)] transition-colors"
1011
+ className="flex items-center gap-1.5 self-start text-xs text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)] hover:text-[var(--editor-text)] transition-colors"
1014
1012
  >
1015
1013
  {imageColorOpen ? <ChevronDown size={13} /> : <ChevronRight size={13} />}
1016
1014
  Image color
@@ -1018,7 +1016,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1018
1016
 
1019
1017
  {imageColorOpen ? (
1020
1018
  <div className="flex flex-col gap-2">
1021
- <p className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]/50">
1019
+ <p className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">
1022
1020
  How photos and logos convert for HDR
1023
1021
  </p>
1024
1022
  <div role="radiogroup" aria-label="Image color" className="grid grid-cols-2 gap-2">
@@ -1032,8 +1030,8 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1032
1030
  onClick={() => it.set(t.id)}
1033
1031
  className={`flex flex-col gap-1.5 rounded-lg border p-2 text-left transition-colors ${
1034
1032
  active
1035
- ? 'border-violet-400/60 bg-violet-400/10'
1036
- : 'border-[var(--editor-border)] hover:bg-[var(--editor-text)]/5'
1033
+ ? 'border-[var(--editor-accent)] bg-[color-mix(in_srgb,var(--editor-accent)_12%,transparent)]'
1034
+ : 'border-[var(--editor-border)] hover:bg-[color-mix(in_srgb,var(--editor-text)_5%,transparent)]'
1037
1035
  }`}
1038
1036
  >
1039
1037
  <img
@@ -1044,17 +1042,17 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1044
1042
  <span className="text-xs font-semibold text-[var(--editor-text)] flex items-center gap-1.5">
1045
1043
  {t.label}
1046
1044
  {t.id === DEFAULT_IMAGE_TONE && (
1047
- <span className="text-[9px] font-normal px-1 py-px rounded bg-[var(--editor-text)]/10 text-[var(--editor-text)]/55">default</span>
1045
+ <span className="text-[9px] font-normal px-1 py-px rounded bg-[color-mix(in_srgb,var(--editor-text)_10%,transparent)] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">default</span>
1048
1046
  )}
1049
1047
  </span>
1050
- <span className="text-[10px] leading-snug text-[var(--editor-text)]/60">{t.summary}</span>
1048
+ <span className="text-[10px] leading-snug text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">{t.summary}</span>
1051
1049
  </button>
1052
1050
  )
1053
1051
  })}
1054
1052
  </div>
1055
1053
  </div>
1056
1054
  ) : (
1057
- <p className="text-[11px] text-[var(--editor-text)]/45">
1055
+ <p className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">
1058
1056
  Photos and logos use the {toneInfo.label} conversion. Open to change.
1059
1057
  </p>
1060
1058
  )}
@@ -1067,7 +1065,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1067
1065
  <button
1068
1066
  onClick={() => setAdvanced(o => !o)}
1069
1067
  aria-expanded={advancedOpen}
1070
- className="flex items-center gap-1.5 self-start text-xs text-[var(--editor-text)]/70 hover:text-[var(--editor-text)] transition-colors"
1068
+ className="flex items-center gap-1.5 self-start text-xs text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)] hover:text-[var(--editor-text)] transition-colors"
1071
1069
  >
1072
1070
  {advancedOpen ? <ChevronDown size={13} /> : <ChevronRight size={13} />}
1073
1071
  Advanced
@@ -1075,7 +1073,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1075
1073
 
1076
1074
  {advancedOpen ? (
1077
1075
  <div className="flex flex-col gap-2">
1078
- <p className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]/50">
1076
+ <p className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">
1079
1077
  Tone curve for SDR
1080
1078
  </p>
1081
1079
  <div role="radiogroup" aria-label="Tone curve for SDR" className="grid grid-cols-2 gap-2">
@@ -1090,8 +1088,8 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1090
1088
  onClick={() => setSdrCurve(curve.id)}
1091
1089
  className={`flex flex-col gap-1.5 rounded-lg border p-2 text-left transition-colors ${
1092
1090
  active
1093
- ? 'border-violet-400/60 bg-violet-400/10'
1094
- : 'border-[var(--editor-border)] hover:bg-[var(--editor-text)]/5'
1091
+ ? 'border-[var(--editor-accent)] bg-[color-mix(in_srgb,var(--editor-accent)_12%,transparent)]'
1092
+ : 'border-[var(--editor-border)] hover:bg-[color-mix(in_srgb,var(--editor-text)_5%,transparent)]'
1095
1093
  }`}
1096
1094
  >
1097
1095
  {thumb ? (
@@ -1120,17 +1118,17 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1120
1118
  </span>
1121
1119
  </span>
1122
1120
  ) : thumbsPending ? (
1123
- <span style={{ aspectRatio: coverAspect }} className="w-full rounded border border-[var(--editor-border)] bg-[var(--editor-text)]/5 flex items-center justify-center">
1121
+ <span style={{ aspectRatio: coverAspect }} className="w-full rounded border border-[var(--editor-border)] bg-[color-mix(in_srgb,var(--editor-text)_5%,transparent)] flex items-center justify-center">
1124
1122
  <Loader size="sm" />
1125
1123
  </span>
1126
1124
  ) : null}
1127
1125
  <span className="text-xs font-semibold text-[var(--editor-text)] flex items-center gap-1.5">
1128
1126
  {curve.label}
1129
1127
  {curve.id === DEFAULT_SDR_CURVE && (
1130
- <span className="text-[9px] font-normal px-1 py-px rounded bg-[var(--editor-text)]/10 text-[var(--editor-text)]/55">default</span>
1128
+ <span className="text-[9px] font-normal px-1 py-px rounded bg-[color-mix(in_srgb,var(--editor-text)_10%,transparent)] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">default</span>
1131
1129
  )}
1132
1130
  </span>
1133
- <span className="text-[10px] leading-snug text-[var(--editor-text)]/60">{curve.blurb}</span>
1131
+ <span className="text-[10px] leading-snug text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">{curve.blurb}</span>
1134
1132
  </button>
1135
1133
  )
1136
1134
  })}
@@ -1139,7 +1137,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1139
1137
  data-testid="sdr-honesty-line"
1140
1138
  className={`text-[11px] leading-snug ${
1141
1139
  sdrCurve === DEFAULT_SDR_CURVE
1142
- ? 'text-[var(--editor-text)]/55'
1140
+ ? 'text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]'
1143
1141
  : (mode === 'light' ? 'text-amber-700' : 'text-amber-400/90')
1144
1142
  }`}
1145
1143
  >
@@ -1147,7 +1145,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1147
1145
  </p>
1148
1146
  </div>
1149
1147
  ) : (
1150
- <p className="text-[11px] text-[var(--editor-text)]/45">
1148
+ <p className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">
1151
1149
  SDR files use the {curveInfo.label} look. Open Advanced to compare curves.
1152
1150
  </p>
1153
1151
  )}
@@ -1159,13 +1157,13 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1159
1157
 
1160
1158
  {/* Footer — duration on the left, actions on the right. */}
1161
1159
  <div className="flex items-center justify-between gap-2 px-5 py-3 border-t border-[var(--editor-border)]">
1162
- <span className="text-xs text-[var(--editor-text)]/50 tabular-nums">
1160
+ <span className="text-xs text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] tabular-nums">
1163
1161
  {durationSec > 0 ? formatTimecode(durationSec) : ''}
1164
1162
  </span>
1165
1163
  <div className="flex items-center gap-2">
1166
1164
  <button
1167
1165
  onClick={handleCancel}
1168
- className="text-sm px-4 py-1.5 rounded-md bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[var(--editor-text)]/80 hover:opacity-90 transition-colors"
1166
+ className="text-sm px-4 py-1.5 rounded-md bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)] hover:opacity-90 transition-colors"
1169
1167
  >
1170
1168
  Cancel
1171
1169
  </button>
@@ -1183,7 +1181,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1183
1181
  )}
1184
1182
  {expandedCurve && expandedUrl && createPortal(
1185
1183
  <div
1186
- className="fixed inset-0 z-[60] flex flex-col items-center justify-center gap-3 bg-black/90 p-6"
1184
+ className="fixed inset-0 z-[60] flex flex-col items-center justify-center gap-3 bg-black/90 p-6 text-white"
1187
1185
  onClick={() => setExpandedCurveId(null)}
1188
1186
  role="dialog"
1189
1187
  aria-label={`${expandedCurve.label} preview`}
@@ -1243,7 +1241,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1243
1241
  // block for this `fixed` overlay, sizing it to the scrolled page height and
1244
1242
  // centering the panel off-screen below the fold.
1245
1243
  return createPortal(
1246
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black">
1244
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black text-[var(--editor-text)]">
1247
1245
  <div className="w-[96vw] h-[96vh] bg-[var(--editor-surface)] border border-[var(--editor-border)] rounded-2xl shadow-2xl flex overflow-hidden">
1248
1246
 
1249
1247
  {/* Left — video */}
@@ -1257,7 +1255,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1257
1255
  className="h-full w-full object-contain"
1258
1256
  />
1259
1257
  ) : (
1260
- <p className="text-sm text-[var(--editor-text)]/60">Render complete.</p>
1258
+ <p className="text-sm text-white/60">Render complete.</p>
1261
1259
  )}
1262
1260
  </div>
1263
1261
 
@@ -1268,10 +1266,10 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1268
1266
  <span className="w-2 h-2 rounded-full bg-green-400" />
1269
1267
  <div>
1270
1268
  <p className="text-sm font-semibold text-[var(--editor-text)]">Render complete</p>
1271
- <p className="text-xs text-[var(--editor-text)]/60">Saved to your library.</p>
1269
+ <p className="text-xs text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">Saved to your library.</p>
1272
1270
  </div>
1273
1271
  </div>
1274
- <button onClick={onClose} className="text-[var(--editor-text)]/55 hover:text-[var(--editor-text)] transition-colors text-lg leading-none">×</button>
1272
+ <button onClick={onClose} className="text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] hover:text-[var(--editor-text)] transition-colors text-lg leading-none">×</button>
1275
1273
  </div>
1276
1274
 
1277
1275
  <div className="flex flex-col gap-3 p-5 flex-1">
@@ -1281,7 +1279,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1281
1279
  <a
1282
1280
  href={videoUrl}
1283
1281
  download={downloadName}
1284
- className="w-full text-center text-sm px-4 py-2.5 rounded-lg bg-green-800/60 border border-green-700 text-green-200 hover:bg-green-700/60 transition-colors font-medium"
1282
+ className={`w-full text-center text-sm px-4 py-2.5 rounded-lg border transition-colors font-medium ${mode === 'light' ? 'bg-green-600 border-green-700 text-white hover:bg-green-700' : 'bg-green-800/60 border-green-700 text-green-200 hover:bg-green-700/60'}`}
1285
1283
  >
1286
1284
  {extraOutputs.length > 0 ? 'Download HDR master' : 'Download'}
1287
1285
  </a>
@@ -1292,14 +1290,14 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1292
1290
  key={p}
1293
1291
  href={adapter.fileUrl(p)}
1294
1292
  download={basename(p)}
1295
- className="w-full text-center text-sm px-4 py-2.5 rounded-lg bg-[var(--editor-surface)] border border-green-800 text-green-200/90 hover:bg-green-900/40 transition-colors font-medium"
1293
+ className={`w-full text-center text-sm px-4 py-2.5 rounded-lg bg-[var(--editor-surface)] border transition-colors font-medium ${mode === 'light' ? 'border-green-600 text-green-700 hover:bg-green-50' : 'border-green-800 text-green-200/90 hover:bg-green-900/40'}`}
1296
1294
  >
1297
1295
  Download SDR version
1298
1296
  </a>
1299
1297
  ))}
1300
1298
  <button
1301
1299
  onClick={onClose}
1302
- className="w-full text-center text-sm px-4 py-2.5 rounded-lg bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[var(--editor-text)]/80 hover:opacity-90 transition-colors"
1300
+ className="w-full text-center text-sm px-4 py-2.5 rounded-lg bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)] hover:opacity-90 transition-colors"
1303
1301
  >
1304
1302
  Close
1305
1303
  </button>
@@ -1312,11 +1310,11 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1312
1310
  }
1313
1311
 
1314
1312
  return createPortal(
1315
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-md p-4">
1316
- <div className="w-full max-w-md bg-[var(--editor-surface)] border border-white/10 rounded-2xl shadow-2xl flex flex-col overflow-hidden">
1313
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-md p-4 text-[var(--editor-text)]">
1314
+ <div className="w-full max-w-md bg-[var(--editor-surface)] border border-[var(--editor-border)] rounded-2xl shadow-2xl flex flex-col overflow-hidden">
1317
1315
 
1318
1316
  {/* Header */}
1319
- <div className="flex items-center justify-between px-5 py-4 border-b border-white/10">
1317
+ <div className="flex items-center justify-between px-5 py-4 border-b border-[var(--editor-border)]">
1320
1318
  <div className="flex items-center gap-2.5">
1321
1319
  {status === 'running' && <span className="w-2 h-2 rounded-full bg-[var(--editor-accent)] animate-pulse" />}
1322
1320
  {status === 'error' && <span className="w-2 h-2 rounded-full bg-red-400" />}
@@ -1325,7 +1323,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1325
1323
  </h2>
1326
1324
  </div>
1327
1325
  {status !== 'running' && (
1328
- <button onClick={onClose} className="text-[var(--editor-text)]/55 hover:text-[var(--editor-text)] transition-colors text-lg leading-none">×</button>
1326
+ <button onClick={onClose} className="text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] hover:text-[var(--editor-text)] transition-colors text-lg leading-none">×</button>
1329
1327
  )}
1330
1328
  </div>
1331
1329
 
@@ -1336,7 +1334,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1336
1334
 
1337
1335
  <div className="flex flex-col items-center gap-1 text-center">
1338
1336
  <h3 className="text-base font-semibold text-[var(--editor-text)]">Exporting your video</h3>
1339
- <p className="text-xs text-[var(--editor-text)]/50">This can take a few minutes for longer videos.</p>
1337
+ <p className="text-xs text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">This can take a few minutes for longer videos.</p>
1340
1338
  </div>
1341
1339
 
1342
1340
  {/* Progress bar — always visible while rendering, above the stepper
@@ -1355,7 +1353,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1355
1353
 
1356
1354
  {/* Granular phase progress (poll hosts advance through phases). */}
1357
1355
  {view !== 'logs' && (
1358
- <div className="w-full max-w-xs rounded-xl border border-white/10 bg-[#0e131f] px-4 py-3">
1356
+ <div className="w-full max-w-xs rounded-xl border border-[var(--editor-border)] bg-[color-mix(in_srgb,var(--editor-text)_4%,var(--editor-surface))] px-4 py-3">
1359
1357
  <PhaseStepper current={phase} phases={stepperPhases(renderOpts)} />
1360
1358
  </div>
1361
1359
  )}
@@ -1366,7 +1364,7 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1366
1364
  <button
1367
1365
  onClick={() => setShowLog(o => !o)}
1368
1366
  aria-expanded={showLog}
1369
- className="mx-auto flex items-center gap-1 text-[11px] text-[var(--editor-text)]/50 hover:text-[var(--editor-text)]/80 transition-colors"
1367
+ className="mx-auto flex items-center gap-1 text-[11px] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] hover:text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)] transition-colors"
1370
1368
  >
1371
1369
  {showLog ? <ChevronDown size={13} /> : <ChevronRight size={13} />}
1372
1370
  {showLog ? 'Hide render log' : 'Show render log'}
@@ -1375,17 +1373,17 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1375
1373
  <div className="relative mt-2">
1376
1374
  <button
1377
1375
  onClick={() => navigator.clipboard.writeText(logs.join('\n'))}
1378
- className="absolute top-2 right-2 z-10 rounded border border-white/10 bg-[#0a0e17] px-2 py-0.5 text-[10px] text-[var(--editor-text)]/60 hover:text-[var(--editor-text)] transition-colors"
1376
+ className="absolute top-2 right-2 z-10 rounded border border-white/10 bg-[#0a0e17] px-2 py-0.5 text-[10px] text-white/60 hover:text-white transition-colors"
1379
1377
  title="Copy logs"
1380
1378
  >
1381
1379
  Copy
1382
1380
  </button>
1383
1381
  <div
1384
1382
  ref={logRef}
1385
- className="flex h-52 flex-col gap-0.5 overflow-y-auto rounded-lg border border-white/10 bg-[#0a0e17] px-3 py-2 font-mono text-[11px] text-[var(--editor-text)]/80"
1383
+ className="flex h-52 flex-col gap-0.5 overflow-y-auto rounded-lg border border-white/10 bg-[#0a0e17] px-3 py-2 font-mono text-[11px] text-white/80"
1386
1384
  >
1387
1385
  {logs.length === 0
1388
- ? <span className="italic text-[var(--editor-text)]/40">Starting render engine…</span>
1386
+ ? <span className="italic text-white/40">Starting render engine…</span>
1389
1387
  : logs.map((line, i) => <LogLine key={i} text={line} />)}
1390
1388
  </div>
1391
1389
  </div>
@@ -1400,14 +1398,14 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1400
1398
  <div className="relative">
1401
1399
  <button
1402
1400
  onClick={() => navigator.clipboard.writeText(logs.join('\n') + (errorMsg ? '\n' + errorMsg : ''))}
1403
- className="absolute top-2 right-2 z-10 rounded border border-white/10 bg-[#0a0e17] px-2 py-0.5 text-[10px] text-[var(--editor-text)]/60 hover:text-[var(--editor-text)] transition-colors"
1401
+ className="absolute top-2 right-2 z-10 rounded border border-white/10 bg-[#0a0e17] px-2 py-0.5 text-[10px] text-white/60 hover:text-white transition-colors"
1404
1402
  title="Copy logs"
1405
1403
  >
1406
1404
  Copy
1407
1405
  </button>
1408
1406
  <div
1409
1407
  ref={logRef}
1410
- className="flex h-72 flex-col gap-0.5 overflow-y-auto rounded-lg border border-white/10 bg-[#0a0e17] px-3 py-2 font-mono text-[11px] text-[var(--editor-text)]/80"
1408
+ className="flex h-72 flex-col gap-0.5 overflow-y-auto rounded-lg border border-white/10 bg-[#0a0e17] px-3 py-2 font-mono text-[11px] text-white/80"
1411
1409
  >
1412
1410
  {logs.map((line, i) => <LogLine key={i} text={line} />)}
1413
1411
  {errorMsg && <span className="mt-1 text-red-400">{errorMsg}</span>}
@@ -1422,18 +1420,18 @@ export default function RenderModal<P extends Project = Project>({ projectId, ad
1422
1420
  )}
1423
1421
 
1424
1422
  {/* Footer */}
1425
- <div className="flex items-center justify-end gap-2 px-5 py-3 border-t border-white/10">
1423
+ <div className="flex items-center justify-end gap-2 px-5 py-3 border-t border-[var(--editor-border)]">
1426
1424
  {status === 'running' ? (
1427
1425
  <button
1428
1426
  onClick={handleCancel}
1429
- className={`text-sm px-4 py-1.5 rounded-md bg-[#0e131f] border border-white/10 text-[var(--editor-text)]/80 transition-colors ${mode === 'light' ? 'hover:bg-red-50 hover:border-red-300 hover:text-red-700' : 'hover:bg-red-900/40 hover:border-red-700 hover:text-red-300'}`}
1427
+ className={`text-sm px-4 py-1.5 rounded-md bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)] transition-colors ${mode === 'light' ? 'hover:bg-red-50 hover:border-red-300 hover:text-red-700' : 'hover:bg-red-900/40 hover:border-red-700 hover:text-red-300'}`}
1430
1428
  >
1431
1429
  Cancel
1432
1430
  </button>
1433
1431
  ) : (
1434
1432
  <button
1435
1433
  onClick={onClose}
1436
- className="text-sm px-4 py-1.5 rounded-md bg-[#0e131f] border border-white/10 text-[var(--editor-text)] hover:opacity-90 transition-colors"
1434
+ className="text-sm px-4 py-1.5 rounded-md bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[var(--editor-text)] hover:opacity-90 transition-colors"
1437
1435
  >
1438
1436
  Close
1439
1437
  </button>
@@ -86,7 +86,7 @@ function VersionFramePane({
86
86
 
87
87
  return (
88
88
  <div className="flex-1 min-w-0 flex flex-col gap-1.5">
89
- <span className="text-[11px] font-medium text-[var(--editor-text)]/60 truncate" title={label}>
89
+ <span className="text-[11px] font-medium text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] truncate" title={label}>
90
90
  {label}
91
91
  </span>
92
92
  <div className="relative aspect-video w-full overflow-hidden rounded-lg border border-[var(--editor-border)] bg-[var(--editor-bg)] flex items-center justify-center">
@@ -101,7 +101,7 @@ function VersionFramePane({
101
101
  style={error ? { display: 'none' } : undefined}
102
102
  />
103
103
  {loading && !error && (
104
- <span className="absolute inset-0 flex items-center justify-center text-xs text-[var(--editor-text)]/50">
104
+ <span className="absolute inset-0 flex items-center justify-center text-xs text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">
105
105
  Loading…
106
106
  </span>
107
107
  )}
@@ -169,16 +169,16 @@ export default function VersionCompare({
169
169
  }, [onClose])
170
170
 
171
171
  return createPortal(
172
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-md p-4">
173
- <div className="w-full max-w-5xl bg-[var(--editor-surface)] border border-white/10 rounded-2xl shadow-2xl flex flex-col overflow-hidden">
172
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-md p-4 text-[var(--editor-text)]">
173
+ <div className="w-full max-w-5xl bg-[var(--editor-surface)] border border-[var(--editor-border)] rounded-2xl shadow-2xl flex flex-col overflow-hidden">
174
174
 
175
175
  {/* Header */}
176
- <div className="flex items-center justify-between px-5 py-4 border-b border-white/10">
176
+ <div className="flex items-center justify-between px-5 py-4 border-b border-[var(--editor-border)]">
177
177
  <h2 className="text-sm font-semibold text-[var(--editor-text)]">Compare versions</h2>
178
178
  <button
179
179
  onClick={onClose}
180
180
  aria-label="Close"
181
- className="text-[var(--editor-text)]/55 hover:text-[var(--editor-text)] transition-colors text-lg leading-none"
181
+ className="text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)] hover:text-[var(--editor-text)] transition-colors text-lg leading-none"
182
182
  >
183
183
  ×
184
184
  </button>
@@ -190,7 +190,7 @@ export default function VersionCompare({
190
190
  {/* Picker row */}
191
191
  <div className="flex flex-col sm:flex-row gap-3">
192
192
  <label className="flex-1 min-w-0 flex flex-col gap-1.5">
193
- <span className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]/50">Left</span>
193
+ <span className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">Left</span>
194
194
  <select
195
195
  value={leftHash}
196
196
  onChange={(e) => setLeftHash(e.target.value)}
@@ -202,7 +202,7 @@ export default function VersionCompare({
202
202
  </select>
203
203
  </label>
204
204
  <label className="flex-1 min-w-0 flex flex-col gap-1.5">
205
- <span className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]/50">Right</span>
205
+ <span className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">Right</span>
206
206
  <select
207
207
  value={rightHash}
208
208
  onChange={(e) => setRightHash(e.target.value)}
@@ -238,8 +238,8 @@ export default function VersionCompare({
238
238
  {/* Time-scrub slider */}
239
239
  <div className="flex flex-col gap-1.5">
240
240
  <div className="flex items-center justify-between">
241
- <span className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]/50">Time</span>
242
- <span className="text-xs text-[var(--editor-text)]/60 tabular-nums">{formatT(t)}</span>
241
+ <span className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">Time</span>
242
+ <span className="text-xs text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] tabular-nums">{formatT(t)}</span>
243
243
  </div>
244
244
  <Slider
245
245
  min={0}
@@ -87,7 +87,7 @@ export default function VersionPanel({ versions, restoring, onRestore, onSaveVer
87
87
  IS the list. The count sits on the right as an explicit "N versions"
88
88
  label so it reads as a quantity rather than part of the title. */}
89
89
  <div className="shrink-0 flex items-center justify-between px-3 py-2 border-b border-[var(--editor-border)]">
90
- <span className="text-xs font-medium text-[var(--editor-text)]/60 uppercase tracking-wide">Versions</span>
90
+ <span className="text-xs font-medium text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] uppercase tracking-wide">Versions</span>
91
91
  {rows.length > 0 && (
92
92
  <span className="text-[10px] text-[var(--editor-text)] opacity-50">
93
93
  {rows.length} {rows.length === 1 ? 'version' : 'versions'}
@@ -106,7 +106,7 @@ export default function VersionPanel({ versions, restoring, onRestore, onSaveVer
106
106
  onChange={e => setNameInput(e.target.value)}
107
107
  placeholder="Name (optional)"
108
108
  disabled={saving || !onSaveVersion}
109
- className="min-w-0 flex-1 h-8 text-[11px] bg-[var(--editor-bg)] border border-[var(--editor-border)] rounded-md px-2 text-[var(--editor-text)] placeholder:text-[var(--editor-text)]/40 focus:outline-none focus:border-[var(--editor-accent)] disabled:opacity-40"
109
+ className="min-w-0 flex-1 h-8 text-[11px] bg-[var(--editor-bg)] border border-[var(--editor-border)] rounded-md px-2 text-[var(--editor-text)] placeholder:text-[color-mix(in_srgb,var(--editor-text)_40%,transparent)] focus:outline-none focus:border-[var(--editor-accent)] disabled:opacity-40"
110
110
  />
111
111
  <button
112
112
  onClick={handleSaveClick}