improved tooltips

This commit is contained in:
2026-05-06 11:41:32 -06:00
parent fd6697b48e
commit a96e42c9f9
13 changed files with 92 additions and 131 deletions

View File

@ -660,7 +660,7 @@ export default function App() {
value={gainModeDb} value={gainModeDb}
onChange={(e) => setGainModeDb(Math.max(-24, Math.min(24, Number(e.target.value) || 0)))} onChange={(e) => setGainModeDb(Math.max(-24, Math.min(24, Number(e.target.value) || 0)))}
className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent" className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent"
data-tooltip="Volume adjustment in decibels for new gain zones — positive boosts, negative reduces" title="Volume adjustment in decibels for new gain zones — positive boosts, negative reduces"
disabled={!canEdit} disabled={!canEdit}
/> />
</div> </div>
@ -681,7 +681,7 @@ export default function App() {
value={speedModeValue} value={speedModeValue}
onChange={(e) => setSpeedModeValue(Math.max(0.25, Math.min(4, Number(e.target.value) || 1)))} onChange={(e) => setSpeedModeValue(Math.max(0.25, Math.min(4, Number(e.target.value) || 1)))}
className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent" className="w-16 px-1.5 py-1 text-xs bg-editor-surface border border-editor-border rounded text-editor-text focus:outline-none focus:border-editor-accent"
data-tooltip="Playback speed multiplier for new speed zones — 1x is normal, 2x is double speed" title="Playback speed multiplier for new speed zones — 1x is normal, 2x is double speed"
disabled={!canEdit} disabled={!canEdit}
/> />
</div> </div>
@ -754,7 +754,7 @@ export default function App() {
<button <button
onClick={handleReprocessProject} onClick={handleReprocessProject}
disabled={isTranscribing || !videoPath || !canEdit} disabled={isTranscribing || !videoPath || !canEdit}
data-tooltip="Re-run transcription with the selected Whisper model — replaces current transcript" title="Re-run transcription with the selected Whisper model — replaces current transcript"
className="flex items-center gap-1 px-2 py-1 rounded text-xs text-editor-text hover:bg-editor-bg disabled:opacity-40 disabled:cursor-not-allowed" className="flex items-center gap-1 px-2 py-1 rounded text-xs text-editor-text hover:bg-editor-bg disabled:opacity-40 disabled:cursor-not-allowed"
> >
<RefreshCw className={`w-3 h-3 ${isTranscribing ? 'animate-spin' : ''}`} /> <RefreshCw className={`w-3 h-3 ${isTranscribing ? 'animate-spin' : ''}`} />
@ -984,7 +984,7 @@ function ToolbarButton({
title?: string; title?: string;
}) { }) {
return ( return (
<span data-tooltip={title || label}> <span title={title || label}>
<button <button
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
@ -1015,7 +1015,7 @@ function DropdownItem({
title?: string; title?: string;
}) { }) {
return ( return (
<span data-tooltip={title || label}> <span title={title || label}>
<button <button
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}

View File

@ -150,14 +150,14 @@ export default function AIPanel() {
onClick={() => setActiveTab('filler')} onClick={() => setActiveTab('filler')}
icon={<Scissors className="w-3.5 h-3.5" />} icon={<Scissors className="w-3.5 h-3.5" />}
label="Filler Words" label="Filler Words"
data-tooltip="Detect and remove filler words from transcript" title="Detect and remove filler words from transcript"
/> />
<TabButton <TabButton
active={activeTab === 'clips'} active={activeTab === 'clips'}
onClick={() => setActiveTab('clips')} onClick={() => setActiveTab('clips')}
icon={<Film className="w-3.5 h-3.5" />} icon={<Film className="w-3.5 h-3.5" />}
label="Create Clips" label="Create Clips"
data-tooltip="Find the best segments for social media clips" title="Find the best segments for social media clips"
/> />
</div> </div>
@ -183,7 +183,7 @@ export default function AIPanel() {
<button <button
onClick={detectFillers} onClick={detectFillers}
disabled={isProcessing || words.length === 0} disabled={isProcessing || words.length === 0}
data-tooltip="Scan the entire transcript for filler words (um, uh, like, you know) and mark for removal" title="Scan the entire transcript for filler words (um, uh, like, you know) and mark for removal"
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-50 rounded-lg text-sm font-medium transition-colors" className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-50 rounded-lg text-sm font-medium transition-colors"
> >
{isProcessing ? ( {isProcessing ? (
@ -208,14 +208,14 @@ export default function AIPanel() {
<div className="flex gap-1"> <div className="flex gap-1">
<button <button
onClick={applyFillerDeletions} onClick={applyFillerDeletions}
data-tooltip="Create cut ranges for all detected filler words at once" title="Create cut ranges for all detected filler words at once"
className="flex items-center gap-1 px-2 py-1 text-xs bg-editor-success/20 text-editor-success rounded hover:bg-editor-success/30" className="flex items-center gap-1 px-2 py-1 text-xs bg-editor-success/20 text-editor-success rounded hover:bg-editor-success/30"
> >
<Check className="w-3 h-3" /> Apply All <Check className="w-3 h-3" /> Apply All
</button> </button>
<button <button
onClick={() => setFillerResult(null)} onClick={() => setFillerResult(null)}
data-tooltip="Clear detected filler word results without applying" title="Clear detected filler word results without applying"
className="flex items-center gap-1 px-2 py-1 text-xs bg-editor-border text-editor-text-muted rounded hover:bg-editor-surface" className="flex items-center gap-1 px-2 py-1 text-xs bg-editor-border text-editor-text-muted rounded hover:bg-editor-surface"
> >
<X className="w-3 h-3" /> Dismiss <X className="w-3 h-3" /> Dismiss
@ -253,7 +253,7 @@ export default function AIPanel() {
<button <button
onClick={createClips} onClick={createClips}
disabled={isProcessing || words.length === 0} disabled={isProcessing || words.length === 0}
data-tooltip="Analyze transcript to find the most engaging 20-60 second segments for social media" title="Analyze transcript to find the most engaging 20-60 second segments for social media"
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-50 rounded-lg text-sm font-medium transition-colors" className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-50 rounded-lg text-sm font-medium transition-colors"
> >
{isProcessing ? ( {isProcessing ? (
@ -283,7 +283,7 @@ export default function AIPanel() {
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={() => handlePreviewClip(clip)} onClick={() => handlePreviewClip(clip)}
data-tooltip="Seek to this clip's position and play a preview" title="Seek to this clip's position and play a preview"
className="flex-1 flex items-center justify-center gap-1 px-2 py-1.5 text-xs bg-editor-accent/20 text-editor-accent rounded hover:bg-editor-accent/30 transition-colors" className="flex-1 flex items-center justify-center gap-1 px-2 py-1.5 text-xs bg-editor-accent/20 text-editor-accent rounded hover:bg-editor-accent/30 transition-colors"
> >
<Play className="w-3 h-3" /> Preview <Play className="w-3 h-3" /> Preview
@ -291,7 +291,7 @@ export default function AIPanel() {
<button <button
onClick={() => handleExportClip(clip, i)} onClick={() => handleExportClip(clip, i)}
disabled={exportingClipIndex === i} disabled={exportingClipIndex === i}
data-tooltip="Export just this segment as a standalone video file" title="Export just this segment as a standalone video file"
className="flex-1 flex items-center justify-center gap-1 px-2 py-1.5 text-xs bg-editor-success/20 text-editor-success rounded hover:bg-editor-success/30 disabled:opacity-50 transition-colors" className="flex-1 flex items-center justify-center gap-1 px-2 py-1.5 text-xs bg-editor-success/20 text-editor-success rounded hover:bg-editor-success/30 disabled:opacity-50 transition-colors"
> >
{exportingClipIndex === i ? ( {exportingClipIndex === i ? (

View File

@ -45,7 +45,7 @@ export default function AppendClipPanel() {
onClick={() => reorderAdditionalClip(clip.id, -1)} onClick={() => reorderAdditionalClip(clip.id, -1)}
disabled={idx === 0} disabled={idx === 0}
className="p-0.5 rounded hover:bg-editor-bg disabled:opacity-30 text-editor-text-muted hover:text-editor-text" className="p-0.5 rounded hover:bg-editor-bg disabled:opacity-30 text-editor-text-muted hover:text-editor-text"
data-tooltip="Move up" title="Move up"
> >
<ChevronUp className="w-3 h-3" /> <ChevronUp className="w-3 h-3" />
</button> </button>
@ -53,7 +53,7 @@ export default function AppendClipPanel() {
onClick={() => reorderAdditionalClip(clip.id, 1)} onClick={() => reorderAdditionalClip(clip.id, 1)}
disabled={idx === additionalClips.length - 1} disabled={idx === additionalClips.length - 1}
className="p-0.5 rounded hover:bg-editor-bg disabled:opacity-30 text-editor-text-muted hover:text-editor-text" className="p-0.5 rounded hover:bg-editor-bg disabled:opacity-30 text-editor-text-muted hover:text-editor-text"
data-tooltip="Move down" title="Move down"
> >
<ChevronDown className="w-3 h-3" /> <ChevronDown className="w-3 h-3" />
</button> </button>
@ -61,7 +61,7 @@ export default function AppendClipPanel() {
<button <button
onClick={() => removeAdditionalClip(clip.id)} onClick={() => removeAdditionalClip(clip.id)}
className="p-0.5 rounded hover:bg-red-500/20 text-red-400" className="p-0.5 rounded hover:bg-red-500/20 text-red-400"
data-tooltip="Remove clip" title="Remove clip"
> >
<Trash2 className="w-3 h-3" /> <Trash2 className="w-3 h-3" />
</button> </button>
@ -74,7 +74,7 @@ export default function AppendClipPanel() {
onClick={handleAddClip} onClick={handleAddClip}
disabled={!videoPath} disabled={!videoPath}
className="w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg border-2 border-dashed border-editor-border text-xs text-editor-text-muted hover:text-editor-text hover:border-editor-text-muted disabled:opacity-40 transition-colors" className="w-full flex items-center justify-center gap-2 px-3 py-2 rounded-lg border-2 border-dashed border-editor-border text-xs text-editor-text-muted hover:text-editor-text hover:border-editor-text-muted disabled:opacity-40 transition-colors"
data-tooltip="Select a video or audio file to append during export" title="Select a video or audio file to append during export"
> >
<Plus className="w-3.5 h-3.5" /> <Plus className="w-3.5 h-3.5" />
Add Clip Add Clip

View File

@ -38,7 +38,7 @@ export default function BackgroundMusicPanel() {
<button <button
onClick={handleLoadMusic} onClick={handleLoadMusic}
className="w-full flex items-center justify-center gap-2 px-4 py-3 rounded-lg border-2 border-dashed border-editor-border text-xs text-editor-text-muted hover:text-editor-text hover:border-editor-text-muted transition-colors" className="w-full flex items-center justify-center gap-2 px-4 py-3 rounded-lg border-2 border-dashed border-editor-border text-xs text-editor-text-muted hover:text-editor-text hover:border-editor-text-muted transition-colors"
data-tooltip="Select an audio file to use as background music" title="Select an audio file to use as background music"
> >
<Disc3 className="w-4 h-4" /> <Disc3 className="w-4 h-4" />
Load Music File Load Music File
@ -53,7 +53,7 @@ export default function BackgroundMusicPanel() {
<button <button
onClick={handleRemoveMusic} onClick={handleRemoveMusic}
className="p-1 rounded hover:bg-red-500/20 text-red-400 transition-colors" className="p-1 rounded hover:bg-red-500/20 text-red-400 transition-colors"
data-tooltip="Remove music" title="Remove music"
> >
<Trash2 className="w-3 h-3" /> <Trash2 className="w-3 h-3" />
</button> </button>
@ -71,7 +71,7 @@ export default function BackgroundMusicPanel() {
value={backgroundMusic.volumeDb} value={backgroundMusic.volumeDb}
onChange={(e) => updateBackgroundMusic({ volumeDb: Number(e.target.value) })} onChange={(e) => updateBackgroundMusic({ volumeDb: Number(e.target.value) })}
className="flex-1 h-1.5" className="flex-1 h-1.5"
data-tooltip="Background music volume relative to main audio — positive boosts, negative reduces" title="Background music volume relative to main audio — positive boosts, negative reduces"
/> />
<span className="text-xs text-editor-text w-10 text-right">{backgroundMusic.volumeDb} dB</span> <span className="text-xs text-editor-text w-10 text-right">{backgroundMusic.volumeDb} dB</span>
</div> </div>
@ -83,7 +83,7 @@ export default function BackgroundMusicPanel() {
checked={backgroundMusic.duckingEnabled} checked={backgroundMusic.duckingEnabled}
onChange={(e) => updateBackgroundMusic({ duckingEnabled: e.target.checked })} onChange={(e) => updateBackgroundMusic({ duckingEnabled: e.target.checked })}
className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent" className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent"
data-tooltip="Automatically lower music volume when speech is detected" title="Automatically lower music volume when speech is detected"
/> />
<div> <div>
<span className="text-xs font-medium">Auto-ducking</span> <span className="text-xs font-medium">Auto-ducking</span>
@ -105,7 +105,7 @@ export default function BackgroundMusicPanel() {
value={backgroundMusic.duckingDb} value={backgroundMusic.duckingDb}
onChange={(e) => updateBackgroundMusic({ duckingDb: Number(e.target.value) })} onChange={(e) => updateBackgroundMusic({ duckingDb: Number(e.target.value) })}
className="flex-1 h-1.5" className="flex-1 h-1.5"
data-tooltip="How much to reduce music volume during speech (1-20 dB)" title="How much to reduce music volume during speech (1-20 dB)"
/> />
<span className="text-xs text-editor-text w-10 text-right">{backgroundMusic.duckingDb} dB</span> <span className="text-xs text-editor-text w-10 text-right">{backgroundMusic.duckingDb} dB</span>
</div> </div>
@ -119,7 +119,7 @@ export default function BackgroundMusicPanel() {
value={backgroundMusic.duckingAttackMs} value={backgroundMusic.duckingAttackMs}
onChange={(e) => updateBackgroundMusic({ duckingAttackMs: Number(e.target.value) })} onChange={(e) => updateBackgroundMusic({ duckingAttackMs: Number(e.target.value) })}
className="flex-1 h-1.5" className="flex-1 h-1.5"
data-tooltip="How quickly the ducking effect engages when speech starts" title="How quickly the ducking effect engages when speech starts"
/> />
<span className="text-xs text-editor-text w-10 text-right">{backgroundMusic.duckingAttackMs}ms</span> <span className="text-xs text-editor-text w-10 text-right">{backgroundMusic.duckingAttackMs}ms</span>
</div> </div>
@ -133,7 +133,7 @@ export default function BackgroundMusicPanel() {
value={backgroundMusic.duckingReleaseMs} value={backgroundMusic.duckingReleaseMs}
onChange={(e) => updateBackgroundMusic({ duckingReleaseMs: Number(e.target.value) })} onChange={(e) => updateBackgroundMusic({ duckingReleaseMs: Number(e.target.value) })}
className="flex-1 h-1.5" className="flex-1 h-1.5"
data-tooltip="How quickly the ducking effect fades when speech ends" title="How quickly the ducking effect fades when speech ends"
/> />
<span className="text-xs text-editor-text w-10 text-right">{backgroundMusic.duckingReleaseMs}ms</span> <span className="text-xs text-editor-text w-10 text-right">{backgroundMusic.duckingReleaseMs}ms</span>
</div> </div>

View File

@ -252,7 +252,7 @@ export default function ExportDialog() {
{ value: '1080p', label: '1080p (Full HD)' }, { value: '1080p', label: '1080p (Full HD)' },
{ value: '4k', label: '4K (Ultra HD)' }, { value: '4k', label: '4K (Ultra HD)' },
]} ]}
data-tooltip="Output video resolution — higher resolution = larger file" title="Output video resolution — higher resolution = larger file"
/> />
)} )}
@ -267,7 +267,7 @@ export default function ExportDialog() {
{ value: 'webm', label: 'WebM (VP9)' }, { value: 'webm', label: 'WebM (VP9)' },
...(isAudioOnly ? [{ value: 'wav' as const, label: 'WAV (Uncompressed)' }] : []), ...(isAudioOnly ? [{ value: 'wav' as const, label: 'WAV (Uncompressed)' }] : []),
]} ]}
data-tooltip="Output container format — MP4 is most compatible" title="Output container format — MP4 is most compatible"
/> />
{/* Video zoom / punch-in */} {/* Video zoom / punch-in */}
@ -278,7 +278,7 @@ export default function ExportDialog() {
checked={options.zoom?.enabled || false} checked={options.zoom?.enabled || false}
onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, enabled: e.target.checked } }))} onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, enabled: e.target.checked } }))}
className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent" className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent"
data-tooltip="Crop and reposition the video frame — useful for removing black bars or reframing" title="Crop and reposition the video frame — useful for removing black bars or reframing"
/> />
<div> <div>
<span className="text-xs font-medium flex items-center gap-1"> <span className="text-xs font-medium flex items-center gap-1">
@ -302,7 +302,7 @@ export default function ExportDialog() {
value={options.zoom?.zoomFactor || 1} value={options.zoom?.zoomFactor || 1}
onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, zoomFactor: Number(e.target.value) } }))} onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, zoomFactor: Number(e.target.value) } }))}
className="flex-1 h-1.5" className="flex-1 h-1.5"
data-tooltip="Magnification level — 1.0x is original, higher values zoom in" title="Magnification level — 1.0x is original, higher values zoom in"
/> />
<span className="text-xs text-editor-text w-10 text-right">{options.zoom?.zoomFactor?.toFixed(2)}x</span> <span className="text-xs text-editor-text w-10 text-right">{options.zoom?.zoomFactor?.toFixed(2)}x</span>
</div> </div>
@ -316,7 +316,7 @@ export default function ExportDialog() {
value={options.zoom?.panX || 0} value={options.zoom?.panX || 0}
onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, panX: Number(e.target.value) } }))} onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, panX: Number(e.target.value) } }))}
className="flex-1 h-1.5" className="flex-1 h-1.5"
data-tooltip="Horizontal position of the crop window — negative moves left, positive moves right" title="Horizontal position of the crop window — negative moves left, positive moves right"
/> />
<span className="text-xs text-editor-text w-10 text-right">{((options.zoom?.panX || 0) * 100).toFixed(0)}%</span> <span className="text-xs text-editor-text w-10 text-right">{((options.zoom?.panX || 0) * 100).toFixed(0)}%</span>
</div> </div>
@ -330,7 +330,7 @@ export default function ExportDialog() {
value={options.zoom?.panY || 0} value={options.zoom?.panY || 0}
onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, panY: Number(e.target.value) } }))} onChange={(e) => setOptions((o) => ({ ...o, zoom: { ...o.zoom!, panY: Number(e.target.value) } }))}
className="flex-1 h-1.5" className="flex-1 h-1.5"
data-tooltip="Vertical position of the crop window — negative moves up, positive moves down" title="Vertical position of the crop window — negative moves up, positive moves down"
/> />
<span className="text-xs text-editor-text w-10 text-right">{((options.zoom?.panY || 0) * 100).toFixed(0)}%</span> <span className="text-xs text-editor-text w-10 text-right">{((options.zoom?.panY || 0) * 100).toFixed(0)}%</span>
</div> </div>
@ -347,7 +347,7 @@ export default function ExportDialog() {
checked={options.removeBackground || false} checked={options.removeBackground || false}
onChange={(e) => setOptions((o) => ({ ...o, removeBackground: e.target.checked }))} onChange={(e) => setOptions((o) => ({ ...o, removeBackground: e.target.checked }))}
className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent" className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent"
data-tooltip="Remove or replace the background behind the speaker" title="Remove or replace the background behind the speaker"
/> />
<div> <div>
<span className="text-xs font-medium flex items-center gap-1"> <span className="text-xs font-medium flex items-center gap-1">
@ -416,7 +416,7 @@ export default function ExportDialog() {
checked={options.normalizeAudio} checked={options.normalizeAudio}
onChange={(e) => setOptions((o) => ({ ...o, normalizeAudio: e.target.checked }))} onChange={(e) => setOptions((o) => ({ ...o, normalizeAudio: e.target.checked }))}
className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent" className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent"
data-tooltip="Normalize audio to a consistent loudness target" title="Normalize audio to a consistent loudness target"
/> />
<div> <div>
<span className="text-xs font-medium">Normalize loudness</span> <span className="text-xs font-medium">Normalize loudness</span>
@ -432,7 +432,7 @@ export default function ExportDialog() {
value={options.normalizeTarget} value={options.normalizeTarget}
onChange={(e) => setOptions((o) => ({ ...o, normalizeTarget: Number(e.target.value) }))} onChange={(e) => setOptions((o) => ({ ...o, normalizeTarget: Number(e.target.value) }))}
className="flex-1 px-2 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:outline-none focus:border-editor-accent [color-scheme:dark]" className="flex-1 px-2 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:outline-none focus:border-editor-accent [color-scheme:dark]"
data-tooltip="Loudness target — YouTube (-14), Spotify (-16), Broadcast (-23)" title="Loudness target — YouTube (-14), Spotify (-16), Broadcast (-23)"
> >
<option value={-14}>YouTube (-14 LUFS)</option> <option value={-14}>YouTube (-14 LUFS)</option>
<option value={-16}>Spotify (-16 LUFS)</option> <option value={-16}>Spotify (-16 LUFS)</option>
@ -451,7 +451,7 @@ export default function ExportDialog() {
checked={options.enhanceAudio} checked={options.enhanceAudio}
onChange={(e) => setOptions((o) => ({ ...o, enhanceAudio: e.target.checked }))} onChange={(e) => setOptions((o) => ({ ...o, enhanceAudio: e.target.checked }))}
className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent" className="w-4 h-4 rounded bg-editor-surface border-editor-border accent-editor-accent"
data-tooltip="Apply noise reduction and speech enhancement" title="Apply noise reduction and speech enhancement"
/> />
<span className="text-xs">Enhance audio (Studio Sound)</span> <span className="text-xs">Enhance audio (Studio Sound)</span>
</label> </label>
@ -466,7 +466,7 @@ export default function ExportDialog() {
{ value: 'burn-in', label: 'Burn-in (permanent)' }, { value: 'burn-in', label: 'Burn-in (permanent)' },
{ value: 'sidecar', label: 'Sidecar SRT file' }, { value: 'sidecar', label: 'Sidecar SRT file' },
]} ]}
data-tooltip="Burn captions into video, export as separate SRT/VTT file, or none" title="Burn captions into video, export as separate SRT/VTT file, or none"
/> />
{/* Transcript-only export */} {/* Transcript-only export */}
@ -491,7 +491,7 @@ export default function ExportDialog() {
onClick={handleTranscriptExport} onClick={handleTranscriptExport}
disabled={isTranscribingTranscript || words.length === 0} disabled={isTranscribingTranscript || words.length === 0}
className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-editor-accent/20 text-editor-accent hover:bg-editor-accent/30 disabled:opacity-40 transition-colors" className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-editor-accent/20 text-editor-accent hover:bg-editor-accent/30 disabled:opacity-40 transition-colors"
data-tooltip="Export just the transcript text or subtitles without the video" title="Export just the transcript text or subtitles without the video"
> >
{isTranscribingTranscript ? ( {isTranscribingTranscript ? (
<Loader2 className="w-3 h-3 animate-spin" /> <Loader2 className="w-3 h-3 animate-spin" />
@ -508,7 +508,7 @@ export default function ExportDialog() {
onClick={handleExport} onClick={handleExport}
disabled={isExporting || !videoPath} disabled={isExporting || !videoPath}
className="w-full flex items-center justify-center gap-2 px-4 py-3 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-50 rounded-lg text-sm font-semibold transition-colors" className="w-full flex items-center justify-center gap-2 px-4 py-3 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-50 rounded-lg text-sm font-semibold transition-colors"
data-tooltip="Start export with current settings" title="Start export with current settings"
> >
{isExporting ? ( {isExporting ? (
<> <>

View File

@ -144,7 +144,7 @@ function LicenseActivateDialog({
<button <button
onClick={onClose} onClick={onClose}
className="p-1 rounded hover:bg-editor-surface text-editor-text-muted" className="p-1 rounded hover:bg-editor-surface text-editor-text-muted"
data-tooltip="Close dialog" title="Close dialog"
> >
<X className="w-4 h-4" /> <X className="w-4 h-4" />
</button> </button>

View File

@ -92,7 +92,7 @@ export default function MarkersPanel() {
<button <button
onClick={addAtCurrentTime} onClick={addAtCurrentTime}
className="w-full flex items-center justify-center gap-1 px-2 py-1.5 text-xs bg-editor-accent/20 text-editor-accent hover:bg-editor-accent/30 rounded" className="w-full flex items-center justify-center gap-1 px-2 py-1.5 text-xs bg-editor-accent/20 text-editor-accent hover:bg-editor-accent/30 rounded"
data-tooltip="Add a marker at the current playhead position" title="Add a marker at the current playhead position"
> >
<MapPin className="w-3 h-3" /> <MapPin className="w-3 h-3" />
Add Add
@ -123,8 +123,8 @@ export default function MarkersPanel() {
) : ( ) : (
<> <>
<span className="flex-1 truncate">{m.label}</span> <span className="flex-1 truncate">{m.label}</span>
<button onClick={() => startEdit(m.id, m.label)} className="p-0.5 hover:text-editor-accent" data-tooltip="Edit marker label and color"><PencilLine className="w-3 h-3" /></button> <button onClick={() => startEdit(m.id, m.label)} className="p-0.5 hover:text-editor-accent" title="Edit marker label and color"><PencilLine className="w-3 h-3" /></button>
<button onClick={() => removeTimelineMarker(m.id)} className="p-0.5 hover:text-editor-danger" data-tooltip="Delete this marker"><Trash2 className="w-3 h-3" /></button> <button onClick={() => removeTimelineMarker(m.id)} className="p-0.5 hover:text-editor-danger" title="Delete this marker"><Trash2 className="w-3 h-3" /></button>
</> </>
)} )}
</div> </div>
@ -151,7 +151,7 @@ export default function MarkersPanel() {
<button <button
onClick={exportChapters} onClick={exportChapters}
className="flex items-center gap-1 text-[10px] text-editor-accent hover:underline" className="flex items-center gap-1 text-[10px] text-editor-accent hover:underline"
data-tooltip="Copy chapter timestamps to clipboard in YouTube format" title="Copy chapter timestamps to clipboard in YouTube format"
> >
<Copy className="w-2.5 h-2.5" /> <Copy className="w-2.5 h-2.5" />
Copy as YouTube timestamps Copy as YouTube timestamps

View File

@ -149,7 +149,7 @@ export default function SettingsPanel() {
value={zonePreviewPaddingSeconds} value={zonePreviewPaddingSeconds}
onChange={(e) => setZonePreviewPaddingSeconds(Number(e.target.value) || 0)} onChange={(e) => setZonePreviewPaddingSeconds(Number(e.target.value) || 0)}
className="flex-1 h-1.5" className="flex-1 h-1.5"
data-tooltip="Extra time in seconds to show before and after each zone during preview" title="Extra time in seconds to show before and after each zone during preview"
/> />
<input <input
type="number" type="number"
@ -159,7 +159,7 @@ export default function SettingsPanel() {
value={zonePreviewPaddingSeconds} value={zonePreviewPaddingSeconds}
onChange={(e) => setZonePreviewPaddingSeconds(Number(e.target.value) || 0)} onChange={(e) => setZonePreviewPaddingSeconds(Number(e.target.value) || 0)}
className="w-16 px-2 py-1 bg-editor-bg border border-editor-border rounded-lg text-xs text-editor-text focus:outline-none focus:border-editor-accent" className="w-16 px-2 py-1 bg-editor-bg border border-editor-border rounded-lg text-xs text-editor-text focus:outline-none focus:border-editor-accent"
data-tooltip="Extra time in seconds to show before and after each zone during preview" title="Extra time in seconds to show before and after each zone during preview"
/> />
<span className="text-xs text-editor-text-muted w-6">s</span> <span className="text-xs text-editor-text-muted w-6">s</span>
</div> </div>
@ -182,7 +182,7 @@ export default function SettingsPanel() {
value={confidenceThreshold} value={confidenceThreshold}
onChange={(e) => setConfidenceThreshold(Number(e.target.value))} onChange={(e) => setConfidenceThreshold(Number(e.target.value))}
className="flex-1 h-1.5" className="flex-1 h-1.5"
data-tooltip="Words below this confidence get an orange underline — lower values show fewer warnings" title="Words below this confidence get an orange underline — lower values show fewer warnings"
/> />
<input <input
type="number" type="number"
@ -192,7 +192,7 @@ export default function SettingsPanel() {
value={confidenceThreshold} value={confidenceThreshold}
onChange={(e) => setConfidenceThreshold(Math.max(0, Math.min(1, Number(e.target.value) || 0)))} onChange={(e) => setConfidenceThreshold(Math.max(0, Math.min(1, Number(e.target.value) || 0)))}
className="w-16 px-2 py-1 bg-editor-bg border border-editor-border rounded-lg text-xs text-editor-text focus:outline-none focus:border-editor-accent" className="w-16 px-2 py-1 bg-editor-bg border border-editor-border rounded-lg text-xs text-editor-text focus:outline-none focus:border-editor-accent"
data-tooltip="Words below this confidence get an orange underline — lower values show fewer warnings" title="Words below this confidence get an orange underline — lower values show fewer warnings"
/> />
</div> </div>
<div className="flex items-center justify-between text-[10px]"> <div className="flex items-center justify-between text-[10px]">
@ -212,14 +212,14 @@ export default function SettingsPanel() {
<button <button
onClick={() => applyPresetAction('standard')} onClick={() => applyPresetAction('standard')}
className="flex-1 px-2 py-1.5 text-xs rounded bg-editor-accent/20 text-editor-accent hover:bg-editor-accent/30" className="flex-1 px-2 py-1.5 text-xs rounded bg-editor-accent/20 text-editor-accent hover:bg-editor-accent/30"
data-tooltip="Reset all shortcuts to the Standard preset" title="Reset all shortcuts to the Standard preset"
> >
Standard Preset Standard Preset
</button> </button>
<button <button
onClick={() => applyPresetAction('left-hand')} onClick={() => applyPresetAction('left-hand')}
className="flex-1 px-2 py-1.5 text-xs rounded bg-editor-accent/20 text-editor-accent hover:bg-editor-accent/30" className="flex-1 px-2 py-1.5 text-xs rounded bg-editor-accent/20 text-editor-accent hover:bg-editor-accent/30"
data-tooltip="Reset all shortcuts to the Left-Hand preset" title="Reset all shortcuts to the Left-Hand preset"
> >
Left-Hand Preset Left-Hand Preset
</button> </button>
@ -243,12 +243,12 @@ export default function SettingsPanel() {
onKeyDown={(e) => handleKeyCapture(e, i)} onKeyDown={(e) => handleKeyCapture(e, i)}
className="w-28 px-2 py-1 text-[10px] font-mono bg-editor-bg border border-editor-border rounded text-center focus:outline-none focus:border-editor-accent" className="w-28 px-2 py-1 text-[10px] font-mono bg-editor-bg border border-editor-border rounded text-center focus:outline-none focus:border-editor-accent"
placeholder="Type shortcut" placeholder="Type shortcut"
data-tooltip="Click then press the desired key combination" title="Click then press the desired key combination"
/> />
<button <button
onClick={() => handleReset(i)} onClick={() => handleReset(i)}
className="text-[10px] text-editor-text-muted hover:text-editor-text px-1" className="text-[10px] text-editor-text-muted hover:text-editor-text px-1"
data-tooltip="Reset this shortcut to default" title="Reset this shortcut to default"
> >
</button> </button>
@ -311,7 +311,7 @@ export default function SettingsPanel() {
onClick={() => handleDeleteModel(m)} onClick={() => handleDeleteModel(m)}
disabled={deleting === m.path} disabled={deleting === m.path}
className="p-1.5 rounded text-editor-text-muted hover:text-red-400 hover:bg-red-500/10 transition-colors disabled:opacity-40" className="p-1.5 rounded text-editor-text-muted hover:text-red-400 hover:bg-red-500/10 transition-colors disabled:opacity-40"
data-tooltip="Delete model" title="Delete model"
> >
<Trash2 className="w-3.5 h-3.5" /> <Trash2 className="w-3.5 h-3.5" />
</button> </button>
@ -323,7 +323,7 @@ export default function SettingsPanel() {
onClick={fetchModels} onClick={fetchModels}
disabled={loadingModels} disabled={loadingModels}
className="text-[10px] text-editor-accent hover:underline flex items-center gap-0.5" className="text-[10px] text-editor-accent hover:underline flex items-center gap-0.5"
data-tooltip="Refresh list of downloaded models" title="Refresh list of downloaded models"
> >
<RefreshCw className={`w-2.5 h-2.5 ${loadingModels ? 'animate-spin' : ''}`} /> <RefreshCw className={`w-2.5 h-2.5 ${loadingModels ? 'animate-spin' : ''}`} />
Refresh Refresh
@ -348,7 +348,7 @@ export default function SettingsPanel() {
onClick={fetchOllamaModels} onClick={fetchOllamaModels}
disabled={loadingOllamaModels} disabled={loadingOllamaModels}
className="text-[10px] text-editor-accent hover:underline flex items-center gap-0.5" className="text-[10px] text-editor-accent hover:underline flex items-center gap-0.5"
data-tooltip="Refresh available Ollama models" title="Refresh available Ollama models"
> >
<RefreshCw className={`w-2.5 h-2.5 ${loadingOllamaModels ? 'animate-spin' : ''}`} /> <RefreshCw className={`w-2.5 h-2.5 ${loadingOllamaModels ? 'animate-spin' : ''}`} />
Refresh Refresh
@ -359,7 +359,7 @@ export default function SettingsPanel() {
value={providers.ollama.model} value={providers.ollama.model}
onChange={(e) => setProviderConfig('ollama', { model: e.target.value })} onChange={(e) => setProviderConfig('ollama', { model: e.target.value })}
className="w-full px-3 py-2 bg-editor-surface border border-editor-border rounded-lg text-xs text-white focus:outline-none focus:border-editor-accent" className="w-full px-3 py-2 bg-editor-surface border border-editor-border rounded-lg text-xs text-white focus:outline-none focus:border-editor-accent"
data-tooltip="Which Ollama model to use for AI features" title="Which Ollama model to use for AI features"
> >
{ollamaModels.map((m) => ( {ollamaModels.map((m) => (
<option key={m} value={m}>{m}</option> <option key={m} value={m}>{m}</option>
@ -461,7 +461,7 @@ function InputField({
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
placeholder={placeholder} placeholder={placeholder}
data-tooltip={title} title={title}
className="w-full px-3 py-2 bg-editor-bg border border-editor-border rounded-lg text-xs text-editor-text placeholder:text-editor-text-muted/50 focus:outline-none focus:border-editor-accent" className="w-full px-3 py-2 bg-editor-bg border border-editor-border rounded-lg text-xs text-editor-text placeholder:text-editor-text-muted/50 focus:outline-none focus:border-editor-accent"
/> />
</div> </div>

View File

@ -134,7 +134,7 @@ export default function SilenceTrimmerPanel() {
value={minSilenceMs} value={minSilenceMs}
onChange={(e) => setMinSilenceMs(Number(e.target.value) || 500)} onChange={(e) => setMinSilenceMs(Number(e.target.value) || 500)}
className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none"
data-tooltip="Minimum duration of silence to detect in milliseconds" title="Minimum duration of silence to detect in milliseconds"
/> />
</div> </div>
@ -150,7 +150,7 @@ export default function SilenceTrimmerPanel() {
value={silenceDb} value={silenceDb}
onChange={(e) => setSilenceDb(Number(e.target.value) || -35)} onChange={(e) => setSilenceDb(Number(e.target.value) || -35)}
className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none"
data-tooltip="Volume threshold in dB — lower values detect quieter sounds as silence" title="Volume threshold in dB — lower values detect quieter sounds as silence"
/> />
</div> </div>
@ -167,7 +167,7 @@ export default function SilenceTrimmerPanel() {
value={preBufferMs} value={preBufferMs}
onChange={(e) => setPreBufferMs(Number(e.target.value) || 0)} onChange={(e) => setPreBufferMs(Number(e.target.value) || 0)}
className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none"
data-tooltip="Extra time to add before each detected silence" title="Extra time to add before each detected silence"
/> />
</div> </div>
<div className="space-y-1.5"> <div className="space-y-1.5">
@ -182,7 +182,7 @@ export default function SilenceTrimmerPanel() {
value={postBufferMs} value={postBufferMs}
onChange={(e) => setPostBufferMs(Number(e.target.value) || 0)} onChange={(e) => setPostBufferMs(Number(e.target.value) || 0)}
className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" className="w-full px-2.5 py-1.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none"
data-tooltip="Extra time to add after each detected silence" title="Extra time to add after each detected silence"
/> />
</div> </div>
</div> </div>
@ -191,7 +191,7 @@ export default function SilenceTrimmerPanel() {
onClick={detectSilence} onClick={detectSilence}
disabled={isDetecting || !videoPath} disabled={isDetecting || !videoPath}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-50 rounded-lg text-sm font-medium transition-colors" className="w-full flex items-center justify-center gap-2 px-4 py-2.5 bg-editor-accent hover:bg-editor-accent-hover disabled:opacity-50 rounded-lg text-sm font-medium transition-colors"
data-tooltip="Scan the entire audio track for silent pauses" title="Scan the entire audio track for silent pauses"
> >
{isDetecting ? ( {isDetecting ? (
<> <>
@ -219,7 +219,7 @@ export default function SilenceTrimmerPanel() {
<button <button
onClick={reapplySelectedGroup} onClick={reapplySelectedGroup}
className="flex items-center gap-1 px-2 py-1 text-xs bg-editor-warning/20 text-editor-warning rounded hover:bg-editor-warning/30" className="flex items-center gap-1 px-2 py-1 text-xs bg-editor-warning/20 text-editor-warning rounded hover:bg-editor-warning/30"
data-tooltip="Re-apply this silence trim group with current settings" title="Re-apply this silence trim group with current settings"
> >
<RotateCcw className="w-3 h-3" /> <RotateCcw className="w-3 h-3" />
Reapply Group Reapply Group
@ -228,7 +228,7 @@ export default function SilenceTrimmerPanel() {
<button <button
onClick={applyAsNewGroup} onClick={applyAsNewGroup}
className="flex items-center gap-1 px-2 py-1 text-xs bg-editor-accent/20 text-editor-accent rounded hover:bg-editor-accent/30" className="flex items-center gap-1 px-2 py-1 text-xs bg-editor-accent/20 text-editor-accent rounded hover:bg-editor-accent/30"
data-tooltip="Create a new silence trim group from detected pauses" title="Create a new silence trim group from detected pauses"
> >
<Scissors className="w-3 h-3" /> <Scissors className="w-3 h-3" />
Apply As New Group Apply As New Group
@ -271,14 +271,14 @@ export default function SilenceTrimmerPanel() {
<button <button
onClick={() => loadGroupForEditing(group.id)} onClick={() => loadGroupForEditing(group.id)}
className="px-1.5 py-1 rounded hover:bg-editor-accent/20 text-editor-accent" className="px-1.5 py-1 rounded hover:bg-editor-accent/20 text-editor-accent"
data-tooltip="Edit and reapply this group" title="Edit and reapply this group"
> >
<PencilLine className="w-3 h-3" /> <PencilLine className="w-3 h-3" />
</button> </button>
<button <button
onClick={() => removeGroup(group.id)} onClick={() => removeGroup(group.id)}
className="px-1.5 py-1 rounded hover:bg-editor-danger/20 text-editor-danger" className="px-1.5 py-1 rounded hover:bg-editor-danger/20 text-editor-danger"
data-tooltip="Delete all cuts from this group" title="Delete all cuts from this group"
> >
<Trash2 className="w-3 h-3" /> <Trash2 className="w-3 h-3" />
</button> </button>

View File

@ -511,7 +511,7 @@ export default function TranscriptEditor({
requestAnimationFrame(() => searchInputRef.current?.focus()); requestAnimationFrame(() => searchInputRef.current?.focus());
}} }}
className="flex items-center gap-1 px-2 py-1 text-xs text-editor-text-muted hover:text-editor-text hover:bg-editor-surface rounded" className="flex items-center gap-1 px-2 py-1 text-xs text-editor-text-muted hover:text-editor-text hover:bg-editor-surface rounded"
data-tooltip="Find (Ctrl+F)" title="Find (Ctrl+F)"
> >
<Search className="w-3 h-3" /> <Search className="w-3 h-3" />
Find Find
@ -534,21 +534,21 @@ export default function TranscriptEditor({
<button <button
onClick={() => jumpToMatch(safeActiveMatchIdx - 1)} onClick={() => jumpToMatch(safeActiveMatchIdx - 1)}
className="p-0.5 rounded hover:bg-editor-bg text-editor-text-muted hover:text-editor-text" className="p-0.5 rounded hover:bg-editor-bg text-editor-text-muted hover:text-editor-text"
data-tooltip="Previous match (Shift+Enter)" title="Previous match (Shift+Enter)"
> >
<ChevronUp className="w-3 h-3" /> <ChevronUp className="w-3 h-3" />
</button> </button>
<button <button
onClick={() => jumpToMatch(safeActiveMatchIdx + 1)} onClick={() => jumpToMatch(safeActiveMatchIdx + 1)}
className="p-0.5 rounded hover:bg-editor-bg text-editor-text-muted hover:text-editor-text" className="p-0.5 rounded hover:bg-editor-bg text-editor-text-muted hover:text-editor-text"
data-tooltip="Next match (Enter)" title="Next match (Enter)"
> >
<ChevronDown className="w-3 h-3" /> <ChevronDown className="w-3 h-3" />
</button> </button>
<button <button
onClick={() => setSearchOpen(false)} onClick={() => setSearchOpen(false)}
className="p-0.5 rounded hover:bg-editor-bg text-editor-text-muted hover:text-editor-text" className="p-0.5 rounded hover:bg-editor-bg text-editor-text-muted hover:text-editor-text"
data-tooltip="Close search (Esc)" title="Close search (Esc)"
> >
<X className="w-3 h-3" /> <X className="w-3 h-3" />
</button> </button>
@ -561,7 +561,7 @@ export default function TranscriptEditor({
onClick={cutSelectedWords} onClick={cutSelectedWords}
disabled={!canEdit} disabled={!canEdit}
className="flex items-center gap-1 px-2 py-1 text-xs bg-red-500/20 text-red-300 rounded hover:bg-red-500/30 transition-colors disabled:opacity-40" className="flex items-center gap-1 px-2 py-1 text-xs bg-red-500/20 text-red-300 rounded hover:bg-red-500/30 transition-colors disabled:opacity-40"
data-tooltip="Remove this word range from the output" title="Remove this word range from the output"
> >
<Scissors className="w-3 h-3" /> <Scissors className="w-3 h-3" />
Cut Cut
@ -570,7 +570,7 @@ export default function TranscriptEditor({
onClick={muteSelectedWords} onClick={muteSelectedWords}
disabled={!canEdit} disabled={!canEdit}
className="flex items-center gap-1 px-2 py-1 text-xs bg-blue-500/20 text-blue-300 rounded hover:bg-blue-500/30 transition-colors disabled:opacity-40" className="flex items-center gap-1 px-2 py-1 text-xs bg-blue-500/20 text-blue-300 rounded hover:bg-blue-500/30 transition-colors disabled:opacity-40"
data-tooltip="Silence audio for this word range" title="Silence audio for this word range"
> >
<VolumeX className="w-3 h-3" /> <VolumeX className="w-3 h-3" />
Mute Mute
@ -579,7 +579,7 @@ export default function TranscriptEditor({
onClick={gainSelectedWords} onClick={gainSelectedWords}
disabled={!canEdit} disabled={!canEdit}
className="flex items-center gap-1 px-2 py-1 text-xs bg-amber-500/20 text-amber-300 rounded hover:bg-amber-500/30 transition-colors disabled:opacity-40" className="flex items-center gap-1 px-2 py-1 text-xs bg-amber-500/20 text-amber-300 rounded hover:bg-amber-500/30 transition-colors disabled:opacity-40"
data-tooltip="Adjust volume for this word range — positive boosts, negative reduces" title="Adjust volume for this word range — positive boosts, negative reduces"
> >
<SlidersHorizontal className="w-3 h-3" /> <SlidersHorizontal className="w-3 h-3" />
Gain ({gainModeDb > 0 ? '+' : ''}{gainModeDb.toFixed(1)} dB) Gain ({gainModeDb > 0 ? '+' : ''}{gainModeDb.toFixed(1)} dB)
@ -588,7 +588,7 @@ export default function TranscriptEditor({
onClick={speedSelectedWords} onClick={speedSelectedWords}
disabled={!canEdit} disabled={!canEdit}
className="flex items-center gap-1 px-2 py-1 text-xs bg-emerald-500/20 text-emerald-300 rounded hover:bg-emerald-500/30 transition-colors disabled:opacity-40" className="flex items-center gap-1 px-2 py-1 text-xs bg-emerald-500/20 text-emerald-300 rounded hover:bg-emerald-500/30 transition-colors disabled:opacity-40"
data-tooltip="Change playback speed for this word range — lower is slower, higher is faster" title="Change playback speed for this word range — lower is slower, higher is faster"
> >
<Gauge className="w-3 h-3" /> <Gauge className="w-3 h-3" />
Speed {speedModeValue.toFixed(2)}x Speed {speedModeValue.toFixed(2)}x
@ -597,7 +597,7 @@ export default function TranscriptEditor({
onClick={handleReTranscribe} onClick={handleReTranscribe}
disabled={isReTranscribing || !canEdit} disabled={isReTranscribing || !canEdit}
className="flex items-center gap-1 px-2 py-1 text-xs bg-purple-500/20 text-purple-300 rounded hover:bg-purple-500/30 disabled:opacity-40 transition-colors" className="flex items-center gap-1 px-2 py-1 text-xs bg-purple-500/20 text-purple-300 rounded hover:bg-purple-500/30 disabled:opacity-40 transition-colors"
data-tooltip="Re-run Whisper transcription on this segment" title="Re-run Whisper transcription on this segment"
> >
<RefreshCw className={`w-3 h-3 ${isReTranscribing ? 'animate-spin' : ''}`} /> <RefreshCw className={`w-3 h-3 ${isReTranscribing ? 'animate-spin' : ''}`} />
{isReTranscribing ? 'Re-transcribing...' : 'Re-transcribe'} {isReTranscribing ? 'Re-transcribing...' : 'Re-transcribe'}

View File

@ -1259,7 +1259,7 @@ export default function WaveformTimeline({
{markOutTime !== null && <span className="text-[10px] text-yellow-300">O {markOutTime.toFixed(2)}s</span>} {markOutTime !== null && <span className="text-[10px] text-yellow-300">O {markOutTime.toFixed(2)}s</span>}
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<label className="flex items-center gap-1 text-[10px] text-editor-text-muted select-none" data-tooltip="Compress cut regions to preview the output timeline without gaps"> <label className="flex items-center gap-1 text-[10px] text-editor-text-muted select-none" title="Compress cut regions to preview the output timeline without gaps">
<input <input
type="checkbox" type="checkbox"
checked={showAdjustedTimeline} checked={showAdjustedTimeline}
@ -1273,28 +1273,28 @@ export default function WaveformTimeline({
<button <button
onClick={() => setShowCutZones((v) => !v)} onClick={() => setShowCutZones((v) => !v)}
className={`px-1.5 py-0.5 rounded text-[10px] border ${showCutZones ? 'border-red-500/60 text-red-300 bg-red-500/10' : 'border-editor-border text-editor-text-muted'}`} className={`px-1.5 py-0.5 rounded text-[10px] border ${showCutZones ? 'border-red-500/60 text-red-300 bg-red-500/10' : 'border-editor-border text-editor-text-muted'}`}
data-tooltip="Toggle cut zones on the timeline — red overlays show removed segments" title="Toggle cut zones on the timeline — red overlays show removed segments"
> >
Cut Cut
</button> </button>
<button <button
onClick={() => setShowMuteZones((v) => !v)} onClick={() => setShowMuteZones((v) => !v)}
className={`px-1.5 py-0.5 rounded text-[10px] border ${showMuteZones ? 'border-blue-500/60 text-blue-300 bg-blue-500/10' : 'border-editor-border text-editor-text-muted'}`} className={`px-1.5 py-0.5 rounded text-[10px] border ${showMuteZones ? 'border-blue-500/60 text-blue-300 bg-blue-500/10' : 'border-editor-border text-editor-text-muted'}`}
data-tooltip="Toggle mute zones on the timeline — blue overlays show silenced segments" title="Toggle mute zones on the timeline — blue overlays show silenced segments"
> >
Mute Mute
</button> </button>
<button <button
onClick={() => setShowGainZones((v) => !v)} onClick={() => setShowGainZones((v) => !v)}
className={`px-1.5 py-0.5 rounded text-[10px] border ${showGainZones ? 'border-amber-500/60 text-amber-300 bg-amber-500/10' : 'border-editor-border text-editor-text-muted'}`} className={`px-1.5 py-0.5 rounded text-[10px] border ${showGainZones ? 'border-amber-500/60 text-amber-300 bg-amber-500/10' : 'border-editor-border text-editor-text-muted'}`}
data-tooltip="Toggle gain zones on the timeline — amber overlays show volume adjustments" title="Toggle gain zones on the timeline — amber overlays show volume adjustments"
> >
Gain Gain
</button> </button>
<button <button
onClick={() => setShowSpeedZones((v) => !v)} onClick={() => setShowSpeedZones((v) => !v)}
className={`px-1.5 py-0.5 rounded text-[10px] border ${showSpeedZones ? 'border-emerald-500/60 text-emerald-300 bg-emerald-500/10' : 'border-editor-border text-editor-text-muted'}`} className={`px-1.5 py-0.5 rounded text-[10px] border ${showSpeedZones ? 'border-emerald-500/60 text-emerald-300 bg-emerald-500/10' : 'border-editor-border text-editor-text-muted'}`}
data-tooltip="Toggle speed zones on the timeline — emerald overlays show speed changes" title="Toggle speed zones on the timeline — emerald overlays show speed changes"
> >
Speed Speed
</button> </button>
@ -1309,7 +1309,7 @@ export default function WaveformTimeline({
<AlertTriangle className="w-4 h-4 text-yellow-500 mt-0.5 shrink-0" /> <AlertTriangle className="w-4 h-4 text-yellow-500 mt-0.5 shrink-0" />
<pre <pre
className="select-text cursor-text whitespace-pre-wrap break-all leading-relaxed" className="select-text cursor-text whitespace-pre-wrap break-all leading-relaxed"
data-tooltip="Highlight this text to copy" title="Highlight this text to copy"
> >
{audioError} {audioError}
</pre> </pre>

View File

@ -176,7 +176,7 @@ export default function ZoneEditor() {
value={zonePreviewPaddingSeconds} value={zonePreviewPaddingSeconds}
onChange={(e) => setZonePreviewPaddingSeconds(Number(e.target.value) || 0)} onChange={(e) => setZonePreviewPaddingSeconds(Number(e.target.value) || 0)}
className="w-16 px-2 py-1 bg-editor-bg border border-editor-border rounded text-xs text-editor-text focus:outline-none focus:border-editor-accent" className="w-16 px-2 py-1 bg-editor-bg border border-editor-border rounded text-xs text-editor-text focus:outline-none focus:border-editor-accent"
data-tooltip="Preview time before and after each zone" title="Preview time before and after each zone"
/> />
<span className="text-xs text-editor-text-muted">sec</span> <span className="text-xs text-editor-text-muted">sec</span>
</div> </div>
@ -193,7 +193,7 @@ export default function ZoneEditor() {
? 'bg-editor-accent text-white' ? 'bg-editor-accent text-white'
: 'text-editor-text-muted hover:text-editor-text' : 'text-editor-text-muted hover:text-editor-text'
}`} }`}
data-tooltip="Show all zones" title="Show all zones"
> >
All All
</button> </button>
@ -204,7 +204,7 @@ export default function ZoneEditor() {
? 'bg-red-500/30 text-red-500' ? 'bg-red-500/30 text-red-500'
: 'text-editor-text-muted hover:text-editor-text' : 'text-editor-text-muted hover:text-editor-text'
}`} }`}
data-tooltip="Show only Cut zones" title="Show only Cut zones"
> >
Cut Cut
</button> </button>
@ -215,7 +215,7 @@ export default function ZoneEditor() {
? 'bg-orange-500/30 text-orange-500' ? 'bg-orange-500/30 text-orange-500'
: 'text-editor-text-muted hover:text-editor-text' : 'text-editor-text-muted hover:text-editor-text'
}`} }`}
data-tooltip="Show only Mute zones" title="Show only Mute zones"
> >
Mute Mute
</button> </button>
@ -226,7 +226,7 @@ export default function ZoneEditor() {
? 'bg-amber-500/30 text-amber-500' ? 'bg-amber-500/30 text-amber-500'
: 'text-editor-text-muted hover:text-editor-text' : 'text-editor-text-muted hover:text-editor-text'
}`} }`}
data-tooltip="Show only Gain zones" title="Show only Gain zones"
> >
Gain Gain
</button> </button>
@ -237,7 +237,7 @@ export default function ZoneEditor() {
? 'bg-emerald-500/30 text-emerald-500' ? 'bg-emerald-500/30 text-emerald-500'
: 'text-editor-text-muted hover:text-editor-text' : 'text-editor-text-muted hover:text-editor-text'
}`} }`}
data-tooltip="Show only Speed zones" title="Show only Speed zones"
> >
Speed Speed
</button> </button>
@ -279,7 +279,7 @@ export default function ZoneEditor() {
removeZone('cut', range.id); removeZone('cut', range.id);
}} }}
className="p-1 rounded hover:bg-red-500/20 text-red-500/70 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity" className="p-1 rounded hover:bg-red-500/20 text-red-500/70 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
data-tooltip="Delete cut zone" title="Delete cut zone"
> >
<Trash2 className="w-3.5 h-3.5" /> <Trash2 className="w-3.5 h-3.5" />
</button> </button>
@ -316,7 +316,7 @@ export default function ZoneEditor() {
removeZone('mute', range.id); removeZone('mute', range.id);
}} }}
className="p-1 rounded hover:bg-orange-500/20 text-orange-500/70 hover:text-orange-500 opacity-0 group-hover:opacity-100 transition-opacity" className="p-1 rounded hover:bg-orange-500/20 text-orange-500/70 hover:text-orange-500 opacity-0 group-hover:opacity-100 transition-opacity"
data-tooltip="Delete mute zone" title="Delete mute zone"
> >
<Trash2 className="w-3.5 h-3.5" /> <Trash2 className="w-3.5 h-3.5" />
</button> </button>
@ -355,7 +355,7 @@ export default function ZoneEditor() {
value={globalGainDb} value={globalGainDb}
onChange={(e) => setGlobalGainDb(Math.max(-24, Math.min(24, Number(e.target.value) || 0)))} onChange={(e) => setGlobalGainDb(Math.max(-24, Math.min(24, Number(e.target.value) || 0)))}
className="w-14 px-1.5 py-0.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" className="w-14 px-1.5 py-0.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none"
data-tooltip="Volume adjustment in decibels — +6 dB doubles volume, -6 dB halves it" title="Volume adjustment in decibels — +6 dB doubles volume, -6 dB halves it"
/> />
<span className="text-xs text-amber-500/80 font-medium w-6 text-right">dB</span> <span className="text-xs text-amber-500/80 font-medium w-6 text-right">dB</span>
</div> </div>
@ -385,7 +385,7 @@ export default function ZoneEditor() {
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onChange={(e) => updateGainRange(range.id, Number(e.target.value) || 0)} onChange={(e) => updateGainRange(range.id, Number(e.target.value) || 0)}
className="w-16 px-1.5 py-0.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" className="w-16 px-1.5 py-0.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none"
data-tooltip="Volume adjustment in decibels — +6 dB doubles volume, -6 dB halves it" title="Volume adjustment in decibels — +6 dB doubles volume, -6 dB halves it"
/> />
{renderPreviewButton(range.start, range.end, 'hover:bg-amber-500/20 text-amber-500/70 hover:text-amber-500')} {renderPreviewButton(range.start, range.end, 'hover:bg-amber-500/20 text-amber-500/70 hover:text-amber-500')}
<button <button
@ -394,7 +394,7 @@ export default function ZoneEditor() {
removeZone('gain', range.id); removeZone('gain', range.id);
}} }}
className="p-1 rounded hover:bg-amber-500/20 text-amber-500/70 hover:text-amber-500 opacity-0 group-hover:opacity-100 transition-opacity" className="p-1 rounded hover:bg-amber-500/20 text-amber-500/70 hover:text-amber-500 opacity-0 group-hover:opacity-100 transition-opacity"
data-tooltip="Delete gain zone" title="Delete gain zone"
> >
<Trash2 className="w-3.5 h-3.5" /> <Trash2 className="w-3.5 h-3.5" />
</button> </button>
@ -435,7 +435,7 @@ export default function ZoneEditor() {
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onChange={(e) => updateSpeedRange(range.id, Number(e.target.value) || 1)} onChange={(e) => updateSpeedRange(range.id, Number(e.target.value) || 1)}
className="w-16 px-1.5 py-0.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none" className="w-16 px-1.5 py-0.5 text-xs bg-editor-surface border border-editor-border rounded focus:border-editor-accent focus:outline-none"
data-tooltip="Playback speed multiplier — 1.0x is normal, 2.0x is twice as fast" title="Playback speed multiplier — 1.0x is normal, 2.0x is twice as fast"
/> />
{renderPreviewButton(range.start, range.end, 'hover:bg-emerald-500/20 text-emerald-500/70 hover:text-emerald-500')} {renderPreviewButton(range.start, range.end, 'hover:bg-emerald-500/20 text-emerald-500/70 hover:text-emerald-500')}
<button <button
@ -444,7 +444,7 @@ export default function ZoneEditor() {
removeZone('speed', range.id); removeZone('speed', range.id);
}} }}
className="p-1 rounded hover:bg-emerald-500/20 text-emerald-500/70 hover:text-emerald-500 opacity-0 group-hover:opacity-100 transition-opacity" className="p-1 rounded hover:bg-emerald-500/20 text-emerald-500/70 hover:text-emerald-500 opacity-0 group-hover:opacity-100 transition-opacity"
data-tooltip="Delete speed zone" title="Delete speed zone"
> >
<Trash2 className="w-3.5 h-3.5" /> <Trash2 className="w-3.5 h-3.5" />
</button> </button>

View File

@ -47,45 +47,6 @@ video::-webkit-media-controls {
display: none !important; display: none !important;
} }
[data-tooltip] {
position: relative;
}
[data-tooltip]::after {
content: attr(data-tooltip);
position: absolute;
bottom: calc(100% + 6px);
left: 50%;
transform: translateX(-50%);
padding: 4px 8px;
border-radius: 4px;
background: #1f2133;
color: #e2e8f0;
font-size: 11px;
line-height: 1.3;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.1s ease;
z-index: 100;
border: 1px solid #2a2d3a;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
font-family: 'Inter', system-ui, sans-serif;
}
[data-tooltip]:hover::after {
opacity: 1;
}
/* Tooltip on the left side for elements near the right edge */
[data-tooltip-side="left"]::after {
left: auto;
right: 0;
transform: none;
}
/* Tooltip on the right side for elements near the left edge */
[data-tooltip-side="right"]::after {
left: 0;
transform: none;
}