@bycrux/editor 1.2.2 → 1.3.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 +4 -4
- package/src/__tests__/no-opacity-modifier-on-editor-vars.test.ts +84 -0
- package/src/__tests__/node-shims.d.ts +19 -0
- package/src/carousel/AddElementMenu.tsx +1 -1
- package/src/carousel/CarouselEditor.tsx +11 -11
- package/src/carousel/CarouselRenderModal.tsx +11 -11
- package/src/carousel/OverlayPicker.tsx +3 -3
- 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 +4 -4
- 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 +16 -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 +21 -21
- package/src/video/CaptionListPanel.test.tsx +8 -7
- package/src/video/CaptionListPanel.tsx +17 -17
- package/src/video/CaptionRegenModal.tsx +7 -7
- package/src/video/CaptionStyleGallery.tsx +1 -1
- package/src/video/CommandPalette.tsx +7 -7
- package/src/video/ImageToneMenu.tsx +7 -7
- package/src/video/OverlayInspector.tsx +7 -7
- package/src/video/RenderModal.tsx +52 -52
- package/src/video/VersionCompare.tsx +7 -7
- package/src/video/VersionPanel.tsx +2 -2
- package/src/video/VideoEditor.tsx +43 -23
- package/src/video/__tests__/VideoEditor.pendingSurface.test.tsx +233 -0
- package/src/video/__tests__/VideoEditor.saveError.test.tsx +105 -0
- 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/SocialPreviewMenu.tsx +5 -5
- 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 +3 -3
- package/src/video/timeline/VolumeControl.tsx +3 -3
package/src/text/FontPicker.tsx
CHANGED
|
@@ -221,14 +221,14 @@ export function FontFamilyPicker({ value, onChange, disabled, className, buttonC
|
|
|
221
221
|
setOpen(false)
|
|
222
222
|
}}
|
|
223
223
|
style={{ fontFamily: opt.value }}
|
|
224
|
-
className={`flex w-full items-center justify-between px-3 py-2 text-left text-[15px] leading-tight text-[var(--editor-text)] hover:bg-[var(--editor-accent)]
|
|
225
|
-
isActive ? 'bg-[var(--editor-accent)]
|
|
224
|
+
className={`flex w-full items-center justify-between px-3 py-2 text-left text-[15px] leading-tight text-[var(--editor-text)] hover:bg-[color-mix(in_srgb,var(--editor-accent)_20%,transparent)] focus:bg-[color-mix(in_srgb,var(--editor-accent)_20%,transparent)] focus:outline-none ${
|
|
225
|
+
isActive ? 'bg-[color-mix(in_srgb,var(--editor-accent)_20%,transparent)] font-medium' : ''
|
|
226
226
|
}`}
|
|
227
227
|
>
|
|
228
228
|
<span className="truncate">{opt.label}</span>
|
|
229
229
|
{isActive && (
|
|
230
230
|
<span
|
|
231
|
-
className="ml-2 shrink-0 text-xs text-[var(--editor-text)]
|
|
231
|
+
className="ml-2 shrink-0 text-xs text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)]"
|
|
232
232
|
style={{ fontFamily: 'system-ui, sans-serif' }}
|
|
233
233
|
>
|
|
234
234
|
✓
|
|
@@ -106,7 +106,7 @@ export function TextFormattingToolbar({
|
|
|
106
106
|
// toward the surface tone. One look for B / I / case / align across the bar.
|
|
107
107
|
const toolbarBtnBase = 'flex h-7 min-w-[1.75rem] items-center justify-center rounded-md border px-1.5 text-sm transition-colors focus:outline-none focus:ring-1 focus:ring-[var(--editor-accent)]'
|
|
108
108
|
const toolbarBtnActive = 'border-[var(--editor-accent)] bg-[var(--editor-accent)] text-[var(--editor-accent-foreground)]'
|
|
109
|
-
const toolbarBtnInactive = 'border-[var(--editor-border)] bg-[var(--editor-surface)] text-[var(--editor-text)]
|
|
109
|
+
const toolbarBtnInactive = 'border-[var(--editor-border)] bg-[var(--editor-surface)] text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)] hover:text-[var(--editor-text)] hover:border-[var(--editor-accent)]'
|
|
110
110
|
|
|
111
111
|
// Segment visibility helpers — group toggles vs size/font vs alignment.
|
|
112
112
|
const hasLeftGroup = supported.has('fontWeight') || supported.has('fontStyle') || supported.has('textTransform') || supported.has('color')
|
|
@@ -233,7 +233,7 @@ export function TextFormattingToolbar({
|
|
|
233
233
|
)}
|
|
234
234
|
|
|
235
235
|
{!hasAnyControlProp && (
|
|
236
|
-
<span className="px-1 py-1 text-xs text-[var(--editor-text)]
|
|
236
|
+
<span className="px-1 py-1 text-xs text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)]">
|
|
237
237
|
Edit via property panel
|
|
238
238
|
</span>
|
|
239
239
|
)}
|
|
@@ -243,7 +243,7 @@ export function TextFormattingToolbar({
|
|
|
243
243
|
type="button"
|
|
244
244
|
aria-label="Delete text overlay"
|
|
245
245
|
onClick={onDelete}
|
|
246
|
-
className={`${toolbarBtnBase} border-[var(--editor-border)] bg-[var(--editor-surface)] text-[var(--editor-text)]
|
|
246
|
+
className={`${toolbarBtnBase} border-[var(--editor-border)] bg-[var(--editor-surface)] text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] ${mode === 'light' ? 'hover:border-red-300 hover:bg-red-50 hover:text-red-700' : 'hover:border-red-500/50 hover:bg-red-900/30 hover:text-red-400'}`}
|
|
247
247
|
>
|
|
248
248
|
<Trash2 className="h-3.5 w-3.5" />
|
|
249
249
|
</button>
|
package/src/types.ts
CHANGED
|
@@ -1038,6 +1038,22 @@ export interface VideoEditorProps<P extends Project = Project> {
|
|
|
1038
1038
|
slots?: EditorSlots
|
|
1039
1039
|
readOnly?: boolean
|
|
1040
1040
|
onBackToSetup?: () => void
|
|
1041
|
+
/**
|
|
1042
|
+
* What renders while `project.status === 'pending'`:
|
|
1043
|
+
* - `'default'` — the built-in pending surface (agent prompt, `slots.pendingStatus`,
|
|
1044
|
+
* project id, back-to-setup).
|
|
1045
|
+
* - `'host'` — the full editor, exactly as for a draft. The editor draws no
|
|
1046
|
+
* pending UI of its own; any gate or overlay is the host's. `onBackToSetup`
|
|
1047
|
+
* and `slots.pendingStatus` are unused in this mode.
|
|
1048
|
+
* Defaults to `'default'`.
|
|
1049
|
+
*/
|
|
1050
|
+
pendingSurface?: 'default' | 'host'
|
|
1051
|
+
/**
|
|
1052
|
+
* Called on every edit the user makes (edits, finished gestures, undo, redo),
|
|
1053
|
+
* never for server frames. Lets a host tell its own user's edits from an
|
|
1054
|
+
* agent's writes. Fires on every edit; the host dedupes.
|
|
1055
|
+
*/
|
|
1056
|
+
onUserEdit?: () => void
|
|
1041
1057
|
/**
|
|
1042
1058
|
* Where the host's `slots.assetsPanel` is placed in the review layout:
|
|
1043
1059
|
* - `'sidebar'` — stacked inside the right-hand version/run-history rail, below
|
package/src/ui/Loader.tsx
CHANGED
|
@@ -56,7 +56,7 @@ export function Loader({ size = 'md', label, className = '' }: LoaderProps) {
|
|
|
56
56
|
<path d="M43 14 l7 10 h7 l-7 -10 z" fill="#facc15" />
|
|
57
57
|
</g>
|
|
58
58
|
</svg>
|
|
59
|
-
{label && <span className="text-[11px] text-[var(--editor-text)]
|
|
59
|
+
{label && <span className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">{label}</span>}
|
|
60
60
|
</div>
|
|
61
61
|
)
|
|
62
62
|
}
|
package/src/ui/NumberField.tsx
CHANGED
|
@@ -196,7 +196,7 @@ export function NumberField({
|
|
|
196
196
|
return (
|
|
197
197
|
<div className="flex shrink-0 items-center gap-1">
|
|
198
198
|
{prefix && (
|
|
199
|
-
<span aria-hidden="true" className="w-2 shrink-0 text-[10px] text-[var(--editor-text)]
|
|
199
|
+
<span aria-hidden="true" className="w-2 shrink-0 text-[10px] text-[color-mix(in_srgb,var(--editor-text)_40%,transparent)]">
|
|
200
200
|
{prefix}
|
|
201
201
|
</span>
|
|
202
202
|
)}
|
|
@@ -218,7 +218,7 @@ export function NumberField({
|
|
|
218
218
|
className={cn(inspectorInputClass, 'text-right disabled:opacity-50 disabled:cursor-not-allowed', className)}
|
|
219
219
|
/>
|
|
220
220
|
{unit && (
|
|
221
|
-
<span aria-hidden="true" className="text-[10px] text-[var(--editor-text)]
|
|
221
|
+
<span aria-hidden="true" className="text-[10px] text-[color-mix(in_srgb,var(--editor-text)_40%,transparent)]">
|
|
222
222
|
{unit}
|
|
223
223
|
</span>
|
|
224
224
|
)}
|
|
@@ -238,7 +238,7 @@ export function NumberField({
|
|
|
238
238
|
* than only being dimmed for show. */
|
|
239
239
|
function Stepper({ name, onStep, disabled }: { name: string; onStep: (direction: 1 | -1) => void; disabled?: boolean }) {
|
|
240
240
|
const btn = cn(
|
|
241
|
-
'flex h-3 w-4 items-center justify-center rounded-sm text-[var(--editor-text)]
|
|
241
|
+
'flex h-3 w-4 items-center justify-center rounded-sm text-[color-mix(in_srgb,var(--editor-text)_45%,transparent)] transition-colors',
|
|
242
242
|
disabled ? 'opacity-40 cursor-not-allowed' : 'hover:bg-[var(--editor-surface)] hover:text-[var(--editor-text)]',
|
|
243
243
|
)
|
|
244
244
|
return (
|
package/src/ui/SwatchInput.tsx
CHANGED
|
@@ -50,7 +50,7 @@ export function SwatchInput({
|
|
|
50
50
|
/>
|
|
51
51
|
</label>
|
|
52
52
|
{showValue && (
|
|
53
|
-
<span className="font-mono text-sm uppercase text-[var(--editor-text)]
|
|
53
|
+
<span className="font-mono text-sm uppercase text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)]">{value}</span>
|
|
54
54
|
)}
|
|
55
55
|
</div>
|
|
56
56
|
)
|
package/src/ui/Tooltip.tsx
CHANGED
|
@@ -103,7 +103,7 @@ export function Tooltip({ label, keys, side = 'top', className, children }: Tool
|
|
|
103
103
|
{keys?.map((k, i) => (
|
|
104
104
|
<kbd
|
|
105
105
|
key={i}
|
|
106
|
-
className="rounded border border-[var(--editor-border)] bg-[var(--editor-text)]
|
|
106
|
+
className="rounded border border-[var(--editor-border)] bg-[color-mix(in_srgb,var(--editor-text)_10%,transparent)] px-1 py-0.5 font-mono text-[9px] leading-none text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)]"
|
|
107
107
|
>
|
|
108
108
|
{k}
|
|
109
109
|
</kbd>
|
package/src/ui/input.tsx
CHANGED
|
@@ -12,7 +12,7 @@ export function Input({ className, ...props }: InputProps) {
|
|
|
12
12
|
return (
|
|
13
13
|
<input
|
|
14
14
|
className={cn(
|
|
15
|
-
'flex h-9 w-full rounded-md border border-[var(--editor-border)] bg-[var(--editor-surface)] px-3 py-1 text-sm text-[var(--editor-text)] placeholder:text-[var(--editor-text)]
|
|
15
|
+
'flex h-9 w-full rounded-md border border-[var(--editor-border)] bg-[var(--editor-surface)] px-3 py-1 text-sm text-[var(--editor-text)] placeholder:text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] focus:outline-none focus:ring-2 focus:ring-[var(--editor-accent)] disabled:opacity-50',
|
|
16
16
|
className,
|
|
17
17
|
)}
|
|
18
18
|
{...props}
|
package/src/ui/label.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { cn } from './utils'
|
|
|
3
3
|
export function Label({ className, ...props }: React.LabelHTMLAttributes<HTMLLabelElement>) {
|
|
4
4
|
return (
|
|
5
5
|
<label
|
|
6
|
-
className={cn('text-xs font-medium text-[var(--editor-text)]
|
|
6
|
+
className={cn('text-xs font-medium text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] leading-none', className)}
|
|
7
7
|
{...props}
|
|
8
8
|
/>
|
|
9
9
|
)
|
package/src/ui/textarea.tsx
CHANGED
|
@@ -6,7 +6,7 @@ export function Textarea({ className, ...props }: TextareaProps) {
|
|
|
6
6
|
return (
|
|
7
7
|
<textarea
|
|
8
8
|
className={cn(
|
|
9
|
-
'flex w-full rounded-md border border-[var(--editor-border)] bg-[var(--editor-surface)] px-3 py-2 text-sm text-[var(--editor-text)] placeholder:text-[var(--editor-text)]
|
|
9
|
+
'flex w-full rounded-md border border-[var(--editor-border)] bg-[var(--editor-surface)] px-3 py-2 text-sm text-[var(--editor-text)] placeholder:text-[color-mix(in_srgb,var(--editor-text)_60%,transparent)] focus:outline-none focus:ring-2 focus:ring-[var(--editor-accent)] disabled:opacity-50 resize-none',
|
|
10
10
|
className,
|
|
11
11
|
)}
|
|
12
12
|
{...props}
|
|
@@ -691,7 +691,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
691
691
|
<div className="flex items-center justify-between px-5 py-4 border-b border-[var(--editor-border)]">
|
|
692
692
|
<div className="flex flex-col gap-0.5">
|
|
693
693
|
<h2 className="text-sm font-semibold text-[var(--editor-text)]">Polish audio</h2>
|
|
694
|
-
<p className="text-[11px] text-[var(--editor-text)]
|
|
694
|
+
<p className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)]">
|
|
695
695
|
{targets.length === 1
|
|
696
696
|
? '1 clip in scope. Nothing is saved until you press Apply.'
|
|
697
697
|
: `${targets.length} clips in scope. Nothing is saved until you press Apply.`}
|
|
@@ -700,7 +700,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
700
700
|
<button
|
|
701
701
|
onClick={handleCancel}
|
|
702
702
|
aria-label="Close"
|
|
703
|
-
className="text-[var(--editor-text)]
|
|
703
|
+
className="text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)] hover:text-[var(--editor-text)] transition-colors text-lg leading-none"
|
|
704
704
|
>
|
|
705
705
|
×
|
|
706
706
|
</button>
|
|
@@ -712,7 +712,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
712
712
|
{PIECES.map(p => (
|
|
713
713
|
<label
|
|
714
714
|
key={p.id}
|
|
715
|
-
className="flex items-start gap-2 rounded-lg border border-[var(--editor-border)] p-2.5 cursor-pointer hover:bg-[var(--editor-text)]
|
|
715
|
+
className="flex items-start gap-2 rounded-lg border border-[var(--editor-border)] p-2.5 cursor-pointer hover:bg-[color-mix(in_srgb,var(--editor-text)_5%,transparent)] transition-colors"
|
|
716
716
|
>
|
|
717
717
|
<input
|
|
718
718
|
type="checkbox"
|
|
@@ -723,7 +723,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
723
723
|
/>
|
|
724
724
|
<span className="flex flex-col gap-0.5 min-w-0">
|
|
725
725
|
<span className="text-xs font-semibold text-[var(--editor-text)]">{p.label}</span>
|
|
726
|
-
<span className="text-[10px] leading-snug text-[var(--editor-text)]
|
|
726
|
+
<span className="text-[10px] leading-snug text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)]">{p.blurb}</span>
|
|
727
727
|
{phase === 'review' && pieces[p.id] && !analysedPieces[p.id] && (
|
|
728
728
|
<span className={`text-[10px] ${mode === 'light' ? 'text-amber-700' : 'text-amber-400'}`}>Not analysed yet. Run Analyse again.</span>
|
|
729
729
|
)}
|
|
@@ -734,7 +734,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
734
734
|
|
|
735
735
|
<div className="flex flex-wrap items-end gap-4">
|
|
736
736
|
<label className="flex flex-col gap-1.5">
|
|
737
|
-
<span className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]
|
|
737
|
+
<span className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">
|
|
738
738
|
Spoken language
|
|
739
739
|
</span>
|
|
740
740
|
<select
|
|
@@ -747,13 +747,13 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
747
747
|
<option key={l.id} value={l.id}>{l.label}</option>
|
|
748
748
|
))}
|
|
749
749
|
</select>
|
|
750
|
-
<span className="text-[10px] text-[var(--editor-text)]
|
|
750
|
+
<span className="text-[10px] text-[color-mix(in_srgb,var(--editor-text)_45%,transparent)] max-w-[15rem] leading-snug">
|
|
751
751
|
Set this before analysing. The wrong language makes real speech look like silence.
|
|
752
752
|
</span>
|
|
753
753
|
</label>
|
|
754
754
|
|
|
755
755
|
<label className="flex flex-col gap-1.5">
|
|
756
|
-
<span className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]
|
|
756
|
+
<span className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">
|
|
757
757
|
Loudness target
|
|
758
758
|
</span>
|
|
759
759
|
<select
|
|
@@ -770,7 +770,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
770
770
|
|
|
771
771
|
{targetId === 'custom' && (
|
|
772
772
|
<label className="flex flex-col gap-1.5">
|
|
773
|
-
<span className="text-[11px] font-semibold uppercase tracking-wide text-[var(--editor-text)]
|
|
773
|
+
<span className="text-[11px] font-semibold uppercase tracking-wide text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">
|
|
774
774
|
Custom LUFS
|
|
775
775
|
</span>
|
|
776
776
|
<NumberField
|
|
@@ -803,7 +803,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
803
803
|
{busy ? 'Analysing…' : phase === 'review' ? 'Analyse again' : 'Analyse'}
|
|
804
804
|
</button>
|
|
805
805
|
{busy && (
|
|
806
|
-
<span className="text-[10px] text-[var(--editor-text)]
|
|
806
|
+
<span className="text-[10px] text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)]">
|
|
807
807
|
{`Analysing ${Math.min(progress.done + 1, progress.total)} of ${progress.total}. ${progress.label}`}
|
|
808
808
|
</span>
|
|
809
809
|
)}
|
|
@@ -825,7 +825,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
825
825
|
{/* Review */}
|
|
826
826
|
<div className="flex-1 overflow-y-auto px-5 py-4 flex flex-col gap-5 min-h-[12rem]">
|
|
827
827
|
{phase === 'setup' && (
|
|
828
|
-
<p className="text-xs text-[var(--editor-text)]
|
|
828
|
+
<p className="text-xs text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)] italic">
|
|
829
829
|
Pick the cleanups you want, set the language, then press Analyse. You see every proposed
|
|
830
830
|
change on the timeline before anything is saved.
|
|
831
831
|
</p>
|
|
@@ -841,7 +841,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
841
841
|
<section key={clip.id} className="flex flex-col gap-2">
|
|
842
842
|
<header className="flex items-baseline justify-between gap-3 border-b border-[var(--editor-border)] pb-1">
|
|
843
843
|
<span className="text-xs font-semibold text-[var(--editor-text)] truncate">{clipLabel(clip)}</span>
|
|
844
|
-
<span className="text-[10px] font-mono text-[var(--editor-text)]
|
|
844
|
+
<span className="text-[10px] font-mono text-[color-mix(in_srgb,var(--editor-text)_45%,transparent)] shrink-0">
|
|
845
845
|
{`${clock(clip.start)} to ${clock(clip.end)}`}
|
|
846
846
|
</span>
|
|
847
847
|
</header>
|
|
@@ -859,7 +859,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
859
859
|
|
|
860
860
|
{/* Loudness */}
|
|
861
861
|
{pieces.loudness && a?.loudness && gainDb !== undefined && (
|
|
862
|
-
<p className="text-[11px] text-[var(--editor-text)]
|
|
862
|
+
<p className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)]">
|
|
863
863
|
{`Loudness: measured ${a.loudness.measuredI.toFixed(1)} LUFS, true peak ${a.loudness.measuredTP.toFixed(1)} dBTP. Gain ${signedDb(gainDb)} to reach ${targetLufs} LUFS.`}
|
|
864
864
|
{clamped && (
|
|
865
865
|
<span className={mode === 'light' ? 'text-amber-700' : 'text-amber-400'}>
|
|
@@ -885,12 +885,12 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
885
885
|
{pieces.voice && supports(clip, 'voice') && (
|
|
886
886
|
a?.vocalsUrl ? (
|
|
887
887
|
<div className="flex flex-col gap-1.5">
|
|
888
|
-
<span className="text-[11px] text-[var(--editor-text)]
|
|
888
|
+
<span className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)]">
|
|
889
889
|
Isolated voice. Compare the two before you apply.
|
|
890
890
|
</span>
|
|
891
891
|
<div className="flex flex-wrap items-center gap-3">
|
|
892
892
|
<span className="flex items-center gap-1.5">
|
|
893
|
-
<span className="text-[10px] text-[var(--editor-text)]
|
|
893
|
+
<span className="text-[10px] text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)]">Original</span>
|
|
894
894
|
<audio
|
|
895
895
|
controls
|
|
896
896
|
preload="none"
|
|
@@ -899,7 +899,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
899
899
|
/>
|
|
900
900
|
</span>
|
|
901
901
|
<span className="flex items-center gap-1.5">
|
|
902
|
-
<span className="text-[10px] text-[var(--editor-text)]
|
|
902
|
+
<span className="text-[10px] text-[color-mix(in_srgb,var(--editor-text)_55%,transparent)]">Isolated voice</span>
|
|
903
903
|
<audio
|
|
904
904
|
controls
|
|
905
905
|
preload="none"
|
|
@@ -914,12 +914,12 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
914
914
|
|
|
915
915
|
{/* Removals */}
|
|
916
916
|
{rows.length === 0 ? (
|
|
917
|
-
<p className="text-[11px] text-[var(--editor-text)]
|
|
917
|
+
<p className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_45%,transparent)] italic">No removals proposed for this clip.</p>
|
|
918
918
|
) : (
|
|
919
919
|
<ul className="flex flex-col gap-1">
|
|
920
920
|
{rows.map(row => (
|
|
921
921
|
<li key={row.key}>
|
|
922
|
-
<label className="flex items-start gap-2 text-[11px] text-[var(--editor-text)]
|
|
922
|
+
<label className="flex items-start gap-2 text-[11px] text-[color-mix(in_srgb,var(--editor-text)_80%,transparent)] cursor-pointer hover:bg-[color-mix(in_srgb,var(--editor-text)_5%,transparent)] rounded px-1 py-0.5">
|
|
923
923
|
<input
|
|
924
924
|
type="checkbox"
|
|
925
925
|
checked={Boolean(approved[row.key])}
|
|
@@ -928,10 +928,10 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
928
928
|
className="mt-0.5"
|
|
929
929
|
/>
|
|
930
930
|
<span className="font-mono shrink-0">{clock(row.tlStart)}</span>
|
|
931
|
-
<span className="font-mono text-[var(--editor-text)]
|
|
931
|
+
<span className="font-mono text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)] shrink-0">
|
|
932
932
|
{`${(row.tlEnd - row.tlStart).toFixed(2)}s`}
|
|
933
933
|
</span>
|
|
934
|
-
<span className="text-[var(--editor-text)]
|
|
934
|
+
<span className="text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)] shrink-0">
|
|
935
935
|
{row.piece === 'silence' ? 'silence' : 'filler'}
|
|
936
936
|
</span>
|
|
937
937
|
{row.text && <span className="italic truncate">{`"${row.text}"`}</span>}
|
|
@@ -952,7 +952,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
952
952
|
|
|
953
953
|
{/* Footer */}
|
|
954
954
|
<div className="flex items-center justify-between gap-2 px-5 py-3 border-t border-[var(--editor-border)]">
|
|
955
|
-
<span className="text-[11px] text-[var(--editor-text)]
|
|
955
|
+
<span className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">
|
|
956
956
|
{phase === 'review'
|
|
957
957
|
? `${totalApproved} removal${totalApproved === 1 ? '' : 's'} ticked. The timeline is showing the result.`
|
|
958
958
|
: 'Nothing is saved until you press Apply.'}
|
|
@@ -960,7 +960,7 @@ export default function AudioPolishModal<P extends Project = Project>({
|
|
|
960
960
|
<div className="flex items-center gap-2">
|
|
961
961
|
<button
|
|
962
962
|
onClick={handleCancel}
|
|
963
|
-
className={`text-sm px-4 py-1.5 rounded-md bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[var(--editor-text)]
|
|
963
|
+
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'}`}
|
|
964
964
|
>
|
|
965
965
|
Cancel
|
|
966
966
|
</button>
|
|
@@ -145,13 +145,14 @@ describe('CaptionListPanel list', () => {
|
|
|
145
145
|
// only a source-text check on the actual class string catches it.
|
|
146
146
|
//
|
|
147
147
|
// Scoped to just these two spans, not "no `/<number>` modifier anywhere in
|
|
148
|
-
// the file":
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
148
|
+
// the file": the other `text-[var(--editor-text)]/N` uses elsewhere in this
|
|
149
|
+
// component (toolbar, buttons, search, empty states) were the same no-op at
|
|
150
|
+
// the time this guard was written. They're since converted to the
|
|
151
|
+
// equivalent `color-mix(in_srgb,var(--editor-text)_N%,transparent)` form
|
|
152
|
+
// package-wide (see the CHANGELOG), which a repo-wide grep test now
|
|
153
|
+
// enforces — this test stays narrow because it predates that fix and these
|
|
154
|
+
// two spans specifically were fixed with a plain `opacity-N` utility
|
|
155
|
+
// instead, not `color-mix`.
|
|
155
156
|
it('the row index and timestamp spans do not use a no-op opacity-modifier color class', () => {
|
|
156
157
|
const indexSpan = src.match(/<span className="[^"]*">\{index \+ 1\}<\/span>/)?.[0] ?? ''
|
|
157
158
|
const timestampSpan = src.match(/<span className="[^"]*">\{formatTime\(seg\.start\)\}<\/span>/)?.[0] ?? ''
|
|
@@ -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'
|
|
@@ -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)_40%,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
|
|
@@ -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)_35%,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
|
))}
|