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

@@ -0,0 +1,183 @@
<script lang="ts">
import { session, type ProcessingStep } from '$lib/stores/videoSession.svelte';
const STEP_LABELS: Record<ProcessingStep, string> = {
idle: '',
downloading: 'Downloading preview…',
waveform: 'Generating waveform…',
keyframes: 'Extracting keyframes…',
thumbnails: 'Generating thumbnails…',
captions: 'Downloading captions…',
done: 'Ready!',
};
const STEP_ORDER: ProcessingStep[] = [
'downloading',
'waveform',
'keyframes',
'thumbnails',
'captions',
'done',
];
let currentStepIndex = $derived(STEP_ORDER.indexOf(session.processingStep));
// Pick progress source based on current step
let stepProgress = $derived(() => {
switch (session.processingStep) {
case 'downloading':
return session.previewProgress;
case 'waveform':
case 'keyframes':
case 'thumbnails':
case 'captions':
return session.processingProgress;
case 'done':
return 1;
default:
return 0;
}
});
let progressPercent = $derived(Math.round(stepProgress() * 100));
let showProgressBar = $derived(
session.processingStep !== 'idle' && session.processingStep !== 'done'
);
</script>
<div class="modal-overlay">
<div class="modal">
<h2>Processing Video</h2>
<p class="title">{session.title}</p>
<div class="steps">
{#each STEP_ORDER as step, i}
{@const stepLabel = STEP_LABELS[step]}
{@const isDone = i < currentStepIndex}
{@const isCurrent = step === session.processingStep}
<div class="step" class:done={isDone} class:current={isCurrent}>
<span class="icon">
{#if isDone}
✓
{:else if isCurrent}
⏳
{:else}
○
{/if}
</span>
<span class="label">{stepLabel}</span>
{#if isCurrent && progressPercent > 0}
<span class="step-pct">{progressPercent}%</span>
{/if}
</div>
{/each}
</div>
{#if showProgressBar}
<div class="progress-bar">
<div class="progress-fill" style="width: {progressPercent}%"></div>
</div>
<p class="progress-text">{progressPercent}%</p>
{/if}
</div>
</div>
<style>
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal {
background: var(--bg-primary, #1e1e2e);
border: 1px solid var(--border, #353550);
border-radius: 12px;
padding: 32px 40px;
min-width: 360px;
max-width: 480px;
text-align: center;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
}
h2 {
margin: 0 0 4px;
font-size: 18px;
font-weight: 600;
color: var(--text-primary, #cdd6f4);
}
.title {
margin: 0 0 24px;
font-size: 13px;
color: var(--text-secondary, #6c7086);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.steps {
display: flex;
flex-direction: column;
gap: 8px;
text-align: left;
margin-bottom: 20px;
}
.step {
display: flex;
align-items: center;
gap: 10px;
font-size: 13px;
color: var(--text-secondary, #6c7086);
transition: color 0.2s;
}
.step.done {
color: #a6e3a1;
}
.step.current {
color: var(--text-primary, #cdd6f4);
font-weight: 500;
}
.icon {
width: 18px;
text-align: center;
flex-shrink: 0;
font-size: 14px;
}
.step-pct {
margin-left: auto;
font-size: 12px;
color: var(--text-secondary, #6c7086);
font-weight: 400;
}
.progress-bar {
height: 6px;
background: var(--bg-tertiary, #2a2a3e);
border-radius: 3px;
overflow: hidden;
margin-bottom: 6px;
}
.progress-fill {
height: 100%;
background: #89b4fa;
border-radius: 3px;
transition: width 0.3s ease;
}
.progress-text {
margin: 0;
font-size: 12px;
color: var(--text-secondary, #6c7086);
}
</style>