chore: stage all pending work — caption styling, media server, processing modal, docs, summaries

Includes:
- Extended caption styling (font, shadow, dimmed color, bg toggle)
- Media server, subtitle downloader, VTT parser, processing modal
- Waveform tiers, thumbnail/timeline improvements, transport controls
- Hybrid download model, dependency management, clip export enhancements
- 21 chat summaries, 2 implementation plans, 2 design specs

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 10:48:16 -04:00
parent fd05ae19b5
commit 8ad2f1c800
57 changed files with 4929 additions and 210 deletions

View File

@@ -8,6 +8,7 @@
import SetupWizard from '$lib/components/SetupWizard.svelte';
import PreferencesPanel from '$lib/components/PreferencesPanel.svelte';
import ExportDialog from '$lib/components/ExportDialog.svelte';
import ProcessingModal from '$lib/components/ProcessingModal.svelte';
import { loadPreferences } from '$lib/stores/preferences.svelte';
import { getSelectedClipId, removeClip } from '$lib/stores/clips.svelte';
import { adjustShuttle, resetShuttleRate } from '$lib/transport/playback';
@@ -21,6 +22,13 @@
let transportControls = $state<TransportControls | null>(null);
// Resizable split: timeline height in px (clip list gets the rest)
let timelineHeight = $state(180);
let isResizing = $state(false);
let resizeStartY = $state(0);
let resizeStartHeight = $state(0);
let splitAreaEl = $state<HTMLDivElement | null>(null);
$effect(() => {
loadPreferences();
});
@@ -36,6 +44,25 @@
transportControls?.handleKeyAction(action);
}
function handleResizeStart(e: MouseEvent) {
e.preventDefault();
isResizing = true;
resizeStartY = e.clientY;
resizeStartHeight = timelineHeight;
}
function handleResizeMove(e: MouseEvent) {
if (!isResizing || !splitAreaEl) return;
const delta = e.clientY - resizeStartY;
const totalHeight = splitAreaEl.clientHeight;
const newHeight = Math.max(40, Math.min(totalHeight - 20, resizeStartHeight + delta));
timelineHeight = newHeight;
}
function handleResizeEnd() {
isResizing = false;
}
function handleGlobalKeydown(e: KeyboardEvent) {
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') return;
@@ -110,7 +137,11 @@
}
</script>
<svelte:window onkeydown={handleGlobalKeydown} />
<svelte:window
onkeydown={handleGlobalKeydown}
onmousemove={handleResizeMove}
onmouseup={handleResizeEnd}
/>
{#if showSetupWizard}
<SetupWizard onComplete={() => (showSetupWizard = false)} />
@@ -124,6 +155,10 @@
<ExportDialog onClose={() => (showExportDialog = false)} />
{/if}
{#if session.processingStep !== 'idle' && session.processingStep !== 'done'}
<ProcessingModal />
{/if}
<div class="app-shell">
<header class="toolbar">
<UrlInput />
@@ -133,8 +168,21 @@
<main class="content">
<VideoPlayer />
<TransportControls bind:this={transportControls} />
<Timeline />
<ClipList onExport={() => (showExportDialog = true)} />
<div class="timeline-clip-area" bind:this={splitAreaEl}>
<div class="timeline-pane" style="height: {timelineHeight}px">
<Timeline />
</div>
<div
class="resize-handle"
class:active={isResizing}
role="separator"
aria-orientation="horizontal"
onmousedown={handleResizeStart}
></div>
<div class="cliplist-pane">
<ClipList onExport={() => (showExportDialog = true)} />
</div>
</div>
</main>
<StatusBar />
@@ -163,6 +211,38 @@
overflow: hidden;
}
.timeline-clip-area {
display: flex;
flex-direction: column;
flex-shrink: 0;
min-height: 80px;
}
.timeline-pane {
flex-shrink: 0;
min-height: 40px;
overflow: hidden;
}
.resize-handle {
height: 5px;
background: var(--border, #353550);
cursor: ns-resize;
flex-shrink: 0;
transition: background 0.1s;
}
.resize-handle:hover,
.resize-handle.active {
background: var(--accent, #89b4fa);
}
.cliplist-pane {
flex: 1;
min-height: 20px;
overflow: hidden;
}
.prefs-btn {
font-size: 18px;
padding: 4px 8px;