@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.
- package/package.json +1 -1
- package/src/ControlsInfoModal.tsx +5 -5
- package/src/__tests__/no-opacity-modifier-on-editor-vars.test.ts +84 -0
- package/src/__tests__/node-shims.d.ts +19 -0
- package/src/__tests__/portal-roots-set-editor-text.test.ts +55 -0
- package/src/carousel/AddElementMenu.tsx +1 -1
- package/src/carousel/CarouselEditor.tsx +11 -11
- package/src/carousel/CarouselRenderModal.tsx +14 -14
- package/src/carousel/OverlayPicker.tsx +4 -4
- package/src/carousel/SlidePropertyPanel.tsx +6 -6
- package/src/carousel/__tests__/CarouselEditor.test.tsx +12 -0
- package/src/components/FilmstripScrubber.tsx +1 -1
- package/src/crop/VideoSourceCropModal.tsx +5 -5
- package/src/state/__tests__/use-project-sync.test.tsx +76 -0
- package/src/state/use-project-sync.ts +21 -1
- package/src/text/FontPicker.tsx +3 -3
- package/src/text/TextFormattingToolbar.tsx +3 -3
- package/src/types.ts +18 -0
- package/src/ui/Loader.tsx +1 -1
- package/src/ui/NumberField.tsx +3 -3
- package/src/ui/SwatchInput.tsx +1 -1
- package/src/ui/Tooltip.tsx +1 -1
- package/src/ui/input.tsx +1 -1
- package/src/ui/label.tsx +1 -1
- package/src/ui/textarea.tsx +1 -1
- package/src/video/AudioPolishModal.tsx +22 -22
- package/src/video/CaptionListPanel.test.tsx +8 -7
- package/src/video/CaptionListPanel.tsx +17 -17
- package/src/video/CaptionRegenModal.tsx +8 -8
- package/src/video/CaptionStyleGallery.tsx +1 -1
- package/src/video/CommandPalette.tsx +8 -8
- package/src/video/ImageToneMenu.tsx +13 -14
- package/src/video/OverlayInspector.tsx +7 -7
- package/src/video/RenderModal.tsx +76 -78
- package/src/video/VersionCompare.tsx +10 -10
- package/src/video/VersionPanel.tsx +2 -2
- package/src/video/VideoEditor.tsx +48 -23
- package/src/video/__tests__/RenderModal.hostTheme.test.tsx +85 -0
- package/src/video/__tests__/VideoEditor.context.test.tsx +6 -0
- package/src/video/__tests__/VideoEditor.pendingSurface.test.tsx +233 -0
- package/src/video/__tests__/VideoEditor.saveError.test.tsx +105 -0
- package/src/video/__tests__/export-limits.test.ts +27 -2
- package/src/video/__tests__/use-report-context.test.tsx +22 -1
- package/src/video/export-limits.ts +22 -1
- package/src/video/panels/ClipPropertiesPanel.tsx +4 -4
- package/src/video/panels/OverlayContentPanel.tsx +5 -5
- package/src/video/preview/OverlayItemsLayer.tsx +7 -2
- package/src/video/preview/PreviewPlayer.tsx +14 -0
- package/src/video/preview/SocialPreviewMenu.tsx +6 -6
- package/src/video/preview/__tests__/PreviewPlayer.playing.test.tsx +62 -0
- package/src/video/preview/__tests__/resolveOverlayPropPaths.test.ts +21 -0
- package/src/video/timeline/EditableSegment.tsx +1 -1
- package/src/video/timeline/Scrubber.tsx +2 -2
- package/src/video/timeline/SpeedControl.tsx +3 -3
- package/src/video/timeline/Timeline.tsx +3 -3
- package/src/video/timeline/TrackGutter.tsx +6 -6
- package/src/video/timeline/TrackSettingsPopover.tsx +4 -4
- package/src/video/timeline/VolumeControl.tsx +3 -3
- package/src/video/use-report-context.ts +10 -7
|
@@ -88,7 +88,7 @@ const ALIGNMENTS = [
|
|
|
88
88
|
/** The panel's one chip look, worn by every toggle on the Format tab (Bold,
|
|
89
89
|
* case, alignment). The inactive foreground is
|
|
90
90
|
* `text-[var(--editor-text)] opacity-60` rather than
|
|
91
|
-
* `text-[var(--editor-text)]
|
|
91
|
+
* `text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]` — Tailwind cannot generate an opacity
|
|
92
92
|
* modifier on an arbitrary var() color, so that class is a silent no-op (see
|
|
93
93
|
* the longer note on the row index span at the bottom of this file). The
|
|
94
94
|
* row-filter chips on the Captions tab hand-roll the same look with the older
|
|
@@ -96,7 +96,7 @@ const ALIGNMENTS = [
|
|
|
96
96
|
const chipClass = (active: boolean): string =>
|
|
97
97
|
`text-[10px] rounded px-2 py-0.5 transition-all border ${
|
|
98
98
|
active
|
|
99
|
-
? 'bg-[var(--editor-accent)]
|
|
99
|
+
? 'bg-[color-mix(in_srgb,var(--editor-accent)_20%,transparent)] border-[color-mix(in_srgb,var(--editor-accent)_60%,transparent)] text-[var(--editor-accent)]'
|
|
100
100
|
: 'bg-[var(--editor-surface)] border-[var(--editor-border)] text-[var(--editor-text)] opacity-60 hover:opacity-100'
|
|
101
101
|
}`
|
|
102
102
|
|
|
@@ -111,7 +111,7 @@ const rowClass = 'flex items-center gap-1.5'
|
|
|
111
111
|
* ("Alignment") at this size and still leaves the ~300px rail enough room for
|
|
112
112
|
* the widest control (the font picker + Bold).
|
|
113
113
|
*
|
|
114
|
-
* `opacity-60`, NOT `text-[var(--editor-text)]
|
|
114
|
+
* `opacity-60`, NOT `text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]` — an opacity modifier on an
|
|
115
115
|
* arbitrary var() color is a silent no-op; see the note on `chipClass` above.
|
|
116
116
|
*
|
|
117
117
|
* These labels are PRESENTATIONAL: plain sibling spans, never a
|
|
@@ -559,10 +559,10 @@ function CaptionListPanelBody({
|
|
|
559
559
|
control — so the left rail's panels share one tab language. ── */}
|
|
560
560
|
<div className="shrink-0 border-b border-[var(--editor-border)] flex flex-col">
|
|
561
561
|
<div className="px-3 pt-2 pb-1 flex items-center justify-between gap-2">
|
|
562
|
-
<span className="text-xs font-medium text-[var(--editor-text)]
|
|
562
|
+
<span className="text-xs font-medium text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] uppercase tracking-wide">
|
|
563
563
|
Captions
|
|
564
564
|
{segs.length > 0 && (
|
|
565
|
-
<span className="ml-1.5 text-[var(--editor-text)]
|
|
565
|
+
<span className="ml-1.5 text-[color-mix(in_srgb,var(--editor-text)_40%,transparent)] normal-case tracking-normal">{segs.length}</span>
|
|
566
566
|
)}
|
|
567
567
|
</span>
|
|
568
568
|
</div>
|
|
@@ -603,7 +603,7 @@ function CaptionListPanelBody({
|
|
|
603
603
|
</div>
|
|
604
604
|
) : (
|
|
605
605
|
<div className="flex flex-col items-center gap-2 text-center mt-4 px-2">
|
|
606
|
-
<p className="text-xs text-[var(--editor-text)]
|
|
606
|
+
<p className="text-xs text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)] leading-relaxed">
|
|
607
607
|
{onRegenerateCaptions
|
|
608
608
|
? "Captions are generated from the timeline's audio."
|
|
609
609
|
: 'No captions yet. Captions are generated during transcription.'}
|
|
@@ -920,14 +920,14 @@ function CaptionListPanelBody({
|
|
|
920
920
|
<div className="shrink-0 border-b border-[var(--editor-border)] px-3 py-2 flex flex-col gap-2">
|
|
921
921
|
{/* ── Search ── */}
|
|
922
922
|
<div className="relative">
|
|
923
|
-
<Search size={11} className="absolute left-2 top-1/2 -translate-y-1/2 text-[var(--editor-text)]
|
|
923
|
+
<Search size={11} className="absolute left-2 top-1/2 -translate-y-1/2 text-[color-mix(in_srgb,var(--editor-text)_40%,transparent)] pointer-events-none" />
|
|
924
924
|
<input
|
|
925
925
|
type="text"
|
|
926
926
|
value={search}
|
|
927
927
|
onChange={e => setSearch(e.target.value)}
|
|
928
928
|
placeholder="Search captions…"
|
|
929
929
|
aria-label="Search captions"
|
|
930
|
-
className="w-full h-7 rounded-md border border-[var(--editor-border)] bg-[var(--editor-surface)] pl-6 pr-2 text-xs text-[var(--editor-text)] placeholder-[var(--editor-text)]
|
|
930
|
+
className="w-full h-7 rounded-md border border-[var(--editor-border)] bg-[var(--editor-surface)] pl-6 pr-2 text-xs text-[var(--editor-text)] placeholder-[color-mix(in_srgb,var(--editor-text)_40%,transparent)] focus:outline-none focus:border-[var(--editor-accent)]"
|
|
931
931
|
/>
|
|
932
932
|
</div>
|
|
933
933
|
|
|
@@ -946,8 +946,8 @@ function CaptionListPanelBody({
|
|
|
946
946
|
onClick={() => setRowFilter(null)}
|
|
947
947
|
className={`text-[10px] rounded px-2 py-0.5 transition-all border ${
|
|
948
948
|
rowFilter === null
|
|
949
|
-
? 'bg-[var(--editor-accent)]
|
|
950
|
-
: 'bg-[var(--editor-surface)] border-[var(--editor-border)] text-[var(--editor-text)]
|
|
949
|
+
? 'bg-[color-mix(in_srgb,var(--editor-accent)_20%,transparent)] border-[color-mix(in_srgb,var(--editor-accent)_60%,transparent)] text-[var(--editor-accent)]'
|
|
950
|
+
: 'bg-[var(--editor-surface)] border-[var(--editor-border)] text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)] hover:text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)] hover:border-[color-mix(in_srgb,var(--editor-text)_30%,transparent)]'
|
|
951
951
|
}`}
|
|
952
952
|
>
|
|
953
953
|
All
|
|
@@ -960,8 +960,8 @@ function CaptionListPanelBody({
|
|
|
960
960
|
onClick={() => setRowFilter(g.lane)}
|
|
961
961
|
className={`text-[10px] rounded px-2 py-0.5 transition-all border ${
|
|
962
962
|
rowFilter === g.lane
|
|
963
|
-
? 'bg-[var(--editor-accent)]
|
|
964
|
-
: 'bg-[var(--editor-surface)] border-[var(--editor-border)] text-[var(--editor-text)]
|
|
963
|
+
? 'bg-[color-mix(in_srgb,var(--editor-accent)_20%,transparent)] border-[color-mix(in_srgb,var(--editor-accent)_60%,transparent)] text-[var(--editor-accent)]'
|
|
964
|
+
: 'bg-[var(--editor-surface)] border-[var(--editor-border)] text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)] hover:text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)] hover:border-[color-mix(in_srgb,var(--editor-text)_30%,transparent)]'
|
|
965
965
|
}`}
|
|
966
966
|
>
|
|
967
967
|
{`Row ${g.lane + 1}`}
|
|
@@ -974,7 +974,7 @@ function CaptionListPanelBody({
|
|
|
974
974
|
{/* ── Scrollable numbered list ── */}
|
|
975
975
|
<div role="list" aria-label="Caption segments" className="flex-1 min-h-0 overflow-y-auto px-2 py-2 flex flex-col gap-1">
|
|
976
976
|
{totalVisible === 0 ? (
|
|
977
|
-
<p className="text-xs text-[var(--editor-text)]
|
|
977
|
+
<p className="text-xs text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)] italic text-center mt-4 px-2">No matching captions.</p>
|
|
978
978
|
) : (
|
|
979
979
|
visibleGroups.map(group => (
|
|
980
980
|
// A single-lane project has exactly one entry here, so this
|
|
@@ -989,7 +989,7 @@ function CaptionListPanelBody({
|
|
|
989
989
|
// resolveDropLane), and a sidebar dropdown doing the same
|
|
990
990
|
// move by a different mechanism invites the two to disagree
|
|
991
991
|
// about what a drag means. Follow-up, not YAGNI-scope here.
|
|
992
|
-
<div className="sticky top-0 z-10 -mx-2 px-2 py-1 bg-[var(--editor-surface)] border-b border-[var(--editor-border)] text-[10px] font-medium text-[var(--editor-text)]
|
|
992
|
+
<div className="sticky top-0 z-10 -mx-2 px-2 py-1 bg-[var(--editor-surface)] border-b border-[var(--editor-border)] text-[10px] font-medium text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)] uppercase tracking-wide">
|
|
993
993
|
{`Row ${group.lane + 1}`}
|
|
994
994
|
</div>
|
|
995
995
|
)}
|
|
@@ -1050,7 +1050,7 @@ function CaptionListPanelBody({
|
|
|
1050
1050
|
/>
|
|
1051
1051
|
</span>
|
|
1052
1052
|
<button
|
|
1053
|
-
className={`shrink-0 opacity-0 group-hover:opacity-100 text-[var(--editor-text)]
|
|
1053
|
+
className={`shrink-0 opacity-0 group-hover:opacity-100 text-[color-mix(in_srgb,var(--editor-text)_40%,transparent)] transition-opacity ${mode === 'light' ? 'hover:text-red-600' : 'hover:text-red-400'}`}
|
|
1054
1054
|
onClick={e => { e.stopPropagation(); handleDelete(seg.id) }}
|
|
1055
1055
|
title="Delete caption"
|
|
1056
1056
|
aria-label="Delete caption"
|
|
@@ -1090,8 +1090,8 @@ function CaptionListPanelBody({
|
|
|
1090
1090
|
confirmRemove
|
|
1091
1091
|
? (mode === 'light' ? 'bg-red-50 border-red-400 text-red-700' : 'bg-red-500/15 border-red-500/60 text-red-400')
|
|
1092
1092
|
: (mode === 'light'
|
|
1093
|
-
? 'border-[var(--editor-border)] bg-[var(--editor-surface)] text-[var(--editor-text)]
|
|
1094
|
-
: 'border-[var(--editor-border)] bg-[var(--editor-surface)] text-[var(--editor-text)]
|
|
1093
|
+
? 'border-[var(--editor-border)] bg-[var(--editor-surface)] text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)] hover:border-red-400 hover:text-red-700'
|
|
1094
|
+
: 'border-[var(--editor-border)] bg-[var(--editor-surface)] text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)] hover:border-red-500/50 hover:text-red-400')
|
|
1095
1095
|
}`}
|
|
1096
1096
|
>
|
|
1097
1097
|
<Trash2 size={13} />
|
|
@@ -39,7 +39,7 @@ interface CaptionRegenModalProps<P extends Project = Project> {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function LogLine({ text, mode = 'dark' }: { text: string; mode?: 'light' | 'dark' }) {
|
|
42
|
-
let color = 'text-[var(--editor-text)]
|
|
42
|
+
let color = 'text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]'
|
|
43
43
|
if (/ready|complete|done|transcribed/i.test(text)) color = mode === 'light' ? 'text-green-700' : 'text-green-400'
|
|
44
44
|
else if (/transcrib|detecting|loading|model/i.test(text)) color = mode === 'light' ? 'text-sky-700' : 'text-sky-400'
|
|
45
45
|
else if (/extract|building|composing|mixing/i.test(text)) color = mode === 'light' ? 'text-amber-700' : 'text-amber-400'
|
|
@@ -144,7 +144,7 @@ export default function CaptionRegenModal<P extends Project = Project>({ project
|
|
|
144
144
|
// Portal to document.body so a transformed host ancestor can't trap this
|
|
145
145
|
// `fixed` overlay and push the panel off-screen (see RenderModal).
|
|
146
146
|
return createPortal(
|
|
147
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black">
|
|
147
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black text-[var(--editor-text)]">
|
|
148
148
|
<div className="w-full max-w-3xl bg-[var(--editor-surface)] border border-[var(--editor-border)] rounded-xl shadow-2xl flex flex-col overflow-hidden">
|
|
149
149
|
|
|
150
150
|
{/* Header */}
|
|
@@ -165,13 +165,13 @@ export default function CaptionRegenModal<P extends Project = Project>({ project
|
|
|
165
165
|
question about why a clip's audio showed up in the
|
|
166
166
|
captions. Read at generation time only: unmuting later just
|
|
167
167
|
means the next run sees it. */}
|
|
168
|
-
<p className="text-[11px] text-[var(--editor-text)]
|
|
168
|
+
<p className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">
|
|
169
169
|
Transcribes everything audible on the timeline. Muted clips and tracks are skipped.
|
|
170
170
|
</p>
|
|
171
171
|
</div>
|
|
172
172
|
</div>
|
|
173
173
|
{status !== 'running' && (
|
|
174
|
-
<button onClick={onClose} className="text-[var(--editor-text)]
|
|
174
|
+
<button onClick={onClose} className="text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)] hover:text-[var(--editor-text)] transition-colors text-lg leading-none">×</button>
|
|
175
175
|
)}
|
|
176
176
|
</div>
|
|
177
177
|
|
|
@@ -189,17 +189,17 @@ export default function CaptionRegenModal<P extends Project = Project>({ project
|
|
|
189
189
|
<div className="relative">
|
|
190
190
|
<button
|
|
191
191
|
onClick={() => navigator.clipboard.writeText(logs.join('\n') + (errorMsg ? '\n' + errorMsg : ''))}
|
|
192
|
-
className="absolute top-2 right-2 z-10 text-[10px] px-2 py-0.5 rounded bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[var(--editor-text)]
|
|
192
|
+
className="absolute top-2 right-2 z-10 text-[10px] px-2 py-0.5 rounded bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] hover:text-[var(--editor-text)] hover:border-[var(--editor-border)] transition-colors"
|
|
193
193
|
title="Copy logs"
|
|
194
194
|
>
|
|
195
195
|
Copy
|
|
196
196
|
</button>
|
|
197
197
|
<div
|
|
198
198
|
ref={logRef}
|
|
199
|
-
className="h-96 overflow-y-auto px-4 py-3 font-mono text-[11px] text-[var(--editor-text)]
|
|
199
|
+
className="h-96 overflow-y-auto px-4 py-3 font-mono text-[11px] text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)] bg-[var(--editor-surface)] flex flex-col gap-0.5"
|
|
200
200
|
>
|
|
201
201
|
{logs.length === 0 && status === 'running' && (
|
|
202
|
-
<span className="text-[var(--editor-text)]
|
|
202
|
+
<span className="text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] italic">Starting transcription…</span>
|
|
203
203
|
)}
|
|
204
204
|
{logs.map((line, i) => (
|
|
205
205
|
<LogLine key={i} text={line} mode={mode} />
|
|
@@ -215,7 +215,7 @@ export default function CaptionRegenModal<P extends Project = Project>({ project
|
|
|
215
215
|
{status === 'running' ? (
|
|
216
216
|
<button
|
|
217
217
|
onClick={handleCancel}
|
|
218
|
-
className={`text-sm px-4 py-1.5 rounded-md bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[var(--editor-text)]
|
|
218
|
+
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'}`}
|
|
219
219
|
>
|
|
220
220
|
Cancel
|
|
221
221
|
</button>
|
|
@@ -449,7 +449,7 @@ export function CaptionStyleCard({
|
|
|
449
449
|
</div>
|
|
450
450
|
)}
|
|
451
451
|
|
|
452
|
-
{/* NOT `text-[var(--editor-text)]
|
|
452
|
+
{/* NOT `text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]` — Tailwind cannot generate an
|
|
453
453
|
opacity modifier on an arbitrary var() colour, so that class is a
|
|
454
454
|
silent no-op. See the long note in CaptionListPanel.tsx. */}
|
|
455
455
|
<span
|
|
@@ -94,7 +94,7 @@ export default function CommandPalette({ commands, initialMode = 'list', onGoToT
|
|
|
94
94
|
|
|
95
95
|
return createPortal(
|
|
96
96
|
<div
|
|
97
|
-
className="fixed inset-0 z-50 flex items-start justify-center pt-[15vh] bg-black/70 backdrop-blur-sm"
|
|
97
|
+
className="fixed inset-0 z-50 flex items-start justify-center pt-[15vh] bg-black/70 backdrop-blur-sm text-[var(--editor-text)]"
|
|
98
98
|
onClick={onClose}
|
|
99
99
|
>
|
|
100
100
|
<div
|
|
@@ -103,7 +103,7 @@ export default function CommandPalette({ commands, initialMode = 'list', onGoToT
|
|
|
103
103
|
>
|
|
104
104
|
{mode === 'goto' ? (
|
|
105
105
|
<div className="flex flex-col gap-2 px-4 py-3">
|
|
106
|
-
<span className="text-xs font-medium text-[var(--editor-text)]
|
|
106
|
+
<span className="text-xs font-medium text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">Go to time</span>
|
|
107
107
|
<input
|
|
108
108
|
ref={inputRef}
|
|
109
109
|
type="text"
|
|
@@ -111,14 +111,14 @@ export default function CommandPalette({ commands, initialMode = 'list', onGoToT
|
|
|
111
111
|
onChange={(e) => { setGotoValue(e.target.value); setGotoError(false) }}
|
|
112
112
|
onKeyDown={handleGotoKeyDown}
|
|
113
113
|
placeholder="mm:ss, hh:mm:ss, or seconds"
|
|
114
|
-
className="w-full bg-transparent text-sm text-[var(--editor-text)] placeholder-[var(--editor-text)]
|
|
114
|
+
className="w-full bg-transparent text-sm text-[var(--editor-text)] placeholder-[color-mix(in_srgb,var(--editor-text)_25%,transparent)] outline-none border border-[var(--editor-border)] rounded px-2 py-1.5"
|
|
115
115
|
/>
|
|
116
116
|
{gotoError && <span className={`text-xs ${themeMode === 'light' ? 'text-red-600' : 'text-red-400'}`}>Couldn’t parse that timecode</span>}
|
|
117
117
|
</div>
|
|
118
118
|
) : (
|
|
119
119
|
<>
|
|
120
120
|
<div className="flex items-center gap-2 px-4 py-3 border-b border-[var(--editor-border)]">
|
|
121
|
-
<Search size={14} className="text-[var(--editor-text)]
|
|
121
|
+
<Search size={14} className="text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] shrink-0" />
|
|
122
122
|
<input
|
|
123
123
|
ref={inputRef}
|
|
124
124
|
type="text"
|
|
@@ -126,12 +126,12 @@ export default function CommandPalette({ commands, initialMode = 'list', onGoToT
|
|
|
126
126
|
onChange={(e) => setQuery(e.target.value)}
|
|
127
127
|
onKeyDown={handleListKeyDown}
|
|
128
128
|
placeholder="Type a command…"
|
|
129
|
-
className="w-full bg-transparent text-sm text-[var(--editor-text)] placeholder-[var(--editor-text)]
|
|
129
|
+
className="w-full bg-transparent text-sm text-[var(--editor-text)] placeholder-[color-mix(in_srgb,var(--editor-text)_25%,transparent)] outline-none"
|
|
130
130
|
/>
|
|
131
131
|
</div>
|
|
132
132
|
<div className="max-h-80 overflow-y-auto py-1">
|
|
133
133
|
{filtered.length === 0 && (
|
|
134
|
-
<div className="px-4 py-3 text-xs text-[var(--editor-text)]
|
|
134
|
+
<div className="px-4 py-3 text-xs text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">No matching commands</div>
|
|
135
135
|
)}
|
|
136
136
|
{filtered.map((cmd, i) => (
|
|
137
137
|
<button
|
|
@@ -140,14 +140,14 @@ export default function CommandPalette({ commands, initialMode = 'list', onGoToT
|
|
|
140
140
|
onMouseEnter={() => setHighlight(i)}
|
|
141
141
|
onClick={() => { cmd.run(); onClose() }}
|
|
142
142
|
className={`w-full flex items-center justify-between gap-3 px-4 py-2 text-left text-sm transition-colors ${
|
|
143
|
-
i === highlight ? 'bg-[var(--editor-text)]
|
|
143
|
+
i === highlight ? 'bg-[color-mix(in_srgb,var(--editor-text)_10%,transparent)] text-[var(--editor-text)]' : 'text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)]'
|
|
144
144
|
}`}
|
|
145
145
|
>
|
|
146
146
|
<span>{cmd.label}</span>
|
|
147
147
|
{cmd.keyHint && cmd.keyHint.length > 0 && (
|
|
148
148
|
<span className="flex items-center gap-1 shrink-0">
|
|
149
149
|
{cmd.keyHint.map((k, j) => (
|
|
150
|
-
<kbd key={j} className="text-[10px] font-mono px-1.5 py-0.5 rounded border border-[var(--editor-border)] text-[var(--editor-text)]
|
|
150
|
+
<kbd key={j} className="text-[10px] font-mono px-1.5 py-0.5 rounded border border-[var(--editor-border)] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">
|
|
151
151
|
{k}
|
|
152
152
|
</kbd>
|
|
153
153
|
))}
|
|
@@ -16,9 +16,8 @@ export interface ImageToneMenuProps {
|
|
|
16
16
|
* For host chrome at the top of the page.
|
|
17
17
|
*/
|
|
18
18
|
variant?: 'icon' | 'header'
|
|
19
|
-
/** Editor theme mode — light/dark.
|
|
20
|
-
*
|
|
21
|
-
* dark, matching every existing caller. */
|
|
19
|
+
/** Editor theme mode — light/dark. Kept for API compatibility; the active state now
|
|
20
|
+
* reads the theme's accent tokens, which already adapt to light and dark. */
|
|
22
21
|
mode?: 'light' | 'dark'
|
|
23
22
|
}
|
|
24
23
|
|
|
@@ -29,7 +28,7 @@ export interface ImageToneMenuProps {
|
|
|
29
28
|
* caller gates on settings.colorSpace). Each option shows an example: the same
|
|
30
29
|
* generic photo converted through the actual render pipeline under that mode.
|
|
31
30
|
*/
|
|
32
|
-
export default function ImageToneMenu({ value, onChange, variant = 'icon'
|
|
31
|
+
export default function ImageToneMenu({ value, onChange, variant = 'icon' }: ImageToneMenuProps) {
|
|
33
32
|
const [open, setOpen] = useState(false)
|
|
34
33
|
const rootRef = useRef<HTMLDivElement>(null)
|
|
35
34
|
|
|
@@ -63,7 +62,7 @@ export default function ImageToneMenu({ value, onChange, variant = 'icon', mode
|
|
|
63
62
|
title="How photos and logos are converted for the HDR render"
|
|
64
63
|
aria-haspopup="menu"
|
|
65
64
|
aria-expanded={open}
|
|
66
|
-
className="flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-md border border-[var(--editor-border)] bg-[var(--editor-surface)] text-[var(--editor-text)]
|
|
65
|
+
className="flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-md border border-[var(--editor-border)] bg-[var(--editor-surface)] text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)] hover:text-[var(--editor-text)] transition-colors"
|
|
67
66
|
>
|
|
68
67
|
<Palette size={12} className="opacity-70" />
|
|
69
68
|
<span className="opacity-60">Image color:</span>
|
|
@@ -78,8 +77,8 @@ export default function ImageToneMenu({ value, onChange, variant = 'icon', mode
|
|
|
78
77
|
aria-haspopup="menu"
|
|
79
78
|
className={`flex items-center justify-center w-5 h-5 rounded transition-colors ${
|
|
80
79
|
open
|
|
81
|
-
?
|
|
82
|
-
: 'text-[var(--editor-text)]
|
|
80
|
+
? 'text-[var(--editor-accent-text)] bg-[color-mix(in_srgb,var(--editor-accent)_15%,transparent)] hover:bg-[color-mix(in_srgb,var(--editor-accent)_25%,transparent)]'
|
|
81
|
+
: 'text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] bg-transparent hover:text-[var(--editor-text)]'
|
|
83
82
|
}`}
|
|
84
83
|
>
|
|
85
84
|
<Palette size={12} />
|
|
@@ -89,11 +88,11 @@ export default function ImageToneMenu({ value, onChange, variant = 'icon', mode
|
|
|
89
88
|
{open && (
|
|
90
89
|
<div
|
|
91
90
|
role="menu"
|
|
92
|
-
className={`${menuPosition} z-40 w-[300px] rounded-xl border border-[var(--editor-border)] bg-[var(--editor-surface)] shadow-2xl p-2 flex flex-col gap-1`}
|
|
91
|
+
className={`${menuPosition} z-40 w-[300px] rounded-xl border border-[var(--editor-border)] bg-[var(--editor-surface)] text-[var(--editor-text)] shadow-2xl p-2 flex flex-col gap-1`}
|
|
93
92
|
>
|
|
94
|
-
<p className="px-2 pt-1 pb-1.5 text-[11px] font-semibold text-[var(--editor-text)]
|
|
93
|
+
<p className="px-2 pt-1 pb-1.5 text-[11px] font-semibold text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)]">
|
|
95
94
|
Image color mapping
|
|
96
|
-
<span className="block font-normal text-[10px] text-[var(--editor-text)]
|
|
95
|
+
<span className="block font-normal text-[10px] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">
|
|
97
96
|
How photos and logos are converted for the HDR render.
|
|
98
97
|
</span>
|
|
99
98
|
</p>
|
|
@@ -107,8 +106,8 @@ export default function ImageToneMenu({ value, onChange, variant = 'icon', mode
|
|
|
107
106
|
onClick={() => { onChange(tone.id); setOpen(false) }}
|
|
108
107
|
className={`flex items-start gap-2.5 rounded-lg p-2 text-left transition-colors border ${
|
|
109
108
|
active
|
|
110
|
-
? 'border-
|
|
111
|
-
: 'border-transparent hover:bg-[var(--editor-text)]
|
|
109
|
+
? 'border-[var(--editor-accent)] bg-[color-mix(in_srgb,var(--editor-accent)_12%,transparent)]'
|
|
110
|
+
: 'border-transparent hover:bg-[color-mix(in_srgb,var(--editor-text)_5%,transparent)]'
|
|
112
111
|
}`}
|
|
113
112
|
>
|
|
114
113
|
<img
|
|
@@ -120,10 +119,10 @@ export default function ImageToneMenu({ value, onChange, variant = 'icon', mode
|
|
|
120
119
|
<span className="text-xs font-semibold text-[var(--editor-text)] flex items-center gap-1.5">
|
|
121
120
|
{tone.label}
|
|
122
121
|
{tone.id === DEFAULT_IMAGE_TONE && (
|
|
123
|
-
<span className="text-[9px] font-normal px-1 py-px rounded bg-[var(--editor-text)]
|
|
122
|
+
<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)_55%,transparent)]">default</span>
|
|
124
123
|
)}
|
|
125
124
|
</span>
|
|
126
|
-
<span className="text-[10px] leading-snug text-[var(--editor-text)]
|
|
125
|
+
<span className="text-[10px] leading-snug text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]">
|
|
127
126
|
{tone.summary}
|
|
128
127
|
</span>
|
|
129
128
|
</span>
|
|
@@ -216,9 +216,9 @@ function normalizeDegrees(deg: number): number {
|
|
|
216
216
|
|
|
217
217
|
// ── Shared chrome ────────────────────────────────────────────────────────
|
|
218
218
|
|
|
219
|
-
const ROW_LABEL_CLASS = 'w-14 shrink-0 text-[11px] text-[var(--editor-text)]
|
|
219
|
+
const ROW_LABEL_CLASS = 'w-14 shrink-0 text-[11px] text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)]'
|
|
220
220
|
const ICON_BUTTON_CLASS =
|
|
221
|
-
'flex h-5 w-5 shrink-0 items-center justify-center rounded text-[var(--editor-text)]
|
|
221
|
+
'flex h-5 w-5 shrink-0 items-center justify-center rounded text-[color-mix(in_srgb,var(--editor-text)_45%,transparent)] transition-colors hover:bg-[var(--editor-surface)] hover:text-[var(--editor-text)] disabled:pointer-events-none disabled:opacity-25'
|
|
222
222
|
|
|
223
223
|
function IconButton({
|
|
224
224
|
label,
|
|
@@ -282,10 +282,10 @@ function KeyframeNav({
|
|
|
282
282
|
// invites a support question; one that is visibly unavailable and
|
|
283
283
|
// says why reads as a limitation, which is what it is.
|
|
284
284
|
diamondDisabled
|
|
285
|
-
? 'cursor-not-allowed text-[var(--editor-text)]
|
|
285
|
+
? 'cursor-not-allowed text-[color-mix(in_srgb,var(--editor-text)_20%,transparent)]'
|
|
286
286
|
: pressed
|
|
287
287
|
? 'text-[var(--editor-accent)]'
|
|
288
|
-
: 'text-[var(--editor-text)]
|
|
288
|
+
: 'text-[color-mix(in_srgb,var(--editor-text)_40%,transparent)] hover:text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)]',
|
|
289
289
|
)}
|
|
290
290
|
>
|
|
291
291
|
<Diamond size={11} fill={pressed ? 'currentColor' : 'none'} />
|
|
@@ -518,7 +518,7 @@ export default function OverlayInspector({ item, clock, onPreview, onCommit, onC
|
|
|
518
518
|
if (!canKeyframe(item)) {
|
|
519
519
|
return (
|
|
520
520
|
<div className={SECTION_CLASS}>
|
|
521
|
-
<div className="px-3 py-6 text-center text-[11px] text-[var(--editor-text)]
|
|
521
|
+
<div className="px-3 py-6 text-center text-[11px] text-[color-mix(in_srgb,var(--editor-text)_45%,transparent)]">
|
|
522
522
|
Select an overlay to edit its properties.
|
|
523
523
|
</div>
|
|
524
524
|
</div>
|
|
@@ -769,7 +769,7 @@ export default function OverlayInspector({ item, clock, onPreview, onCommit, onC
|
|
|
769
769
|
only hosts the ALL-properties actions (reset-all + keyframe-all); the
|
|
770
770
|
muted "All" scopes them apart from each row's own per-property unit. */}
|
|
771
771
|
<div className="shrink-0 flex items-center gap-1 border-b border-[var(--editor-border)] px-2 py-1.5">
|
|
772
|
-
<span className="px-1 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[var(--editor-text)]
|
|
772
|
+
<span className="px-1 py-0.5 text-[10px] font-medium uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_40%,transparent)]">
|
|
773
773
|
All
|
|
774
774
|
</span>
|
|
775
775
|
<div className="ml-auto flex items-center gap-1">
|
|
@@ -857,7 +857,7 @@ export default function OverlayInspector({ item, clock, onPreview, onCommit, onC
|
|
|
857
857
|
(Space/Enter both fire click), so it needs no key handler of its
|
|
858
858
|
own — only a visible focus ring. */}
|
|
859
859
|
<div className="flex items-center gap-2">
|
|
860
|
-
<span className="shrink-0 text-[11px] text-[var(--editor-text)]
|
|
860
|
+
<span className="shrink-0 text-[11px] text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)]">Uniform scale</span>
|
|
861
861
|
<button
|
|
862
862
|
type="button"
|
|
863
863
|
role="checkbox"
|