From 23b30be35705902b8acbc832647de879ee5a1b8c Mon Sep 17 00:00:00 2001 From: cottongin Date: Tue, 22 Sep 2026 17:30:47 -0400 Subject: [PATCH] fix: CSS-grid layout to preserve video on toggle + magnification enhancements (brighter, larger, minor ticks) Co-authored-by: Cursor --- src/App.svelte | 282 +++++++++++++++++------------------ src/lib/timeline/renderer.ts | 33 +++- 2 files changed, 162 insertions(+), 153 deletions(-) diff --git a/src/App.svelte b/src/App.svelte index 3578d50..fdabbe6 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -37,6 +37,8 @@ let sidebarResizeStartWidth = $state(0); let windowWidth = $state(window.innerWidth); + let isLeft = $derived(preferences.clipListPosition === 'left'); + $effect(() => { loadPreferences(); }); @@ -238,106 +240,79 @@ - {#if preferences.clipListPosition === 'bottom'} -
+
+ + + + + +
-
-
- -
-
{ - if (e.key === 'ArrowDown') { - e.preventDefault(); - timelineHeight = Math.min(500, timelineHeight + 10); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - timelineHeight = Math.max(40, timelineHeight - 10); - } - }} - >
-
- (showExportDialog = true)} /> -
+
+ + +
+
{ + if (e.key === 'ArrowDown') { + e.preventDefault(); + timelineHeight = Math.min(500, timelineHeight + 10); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + timelineHeight = Math.max(40, timelineHeight - 10); + } + }} + >
+
+
-
- {:else} -
-
-
- (showExportDialog = true)} position="left" /> -
- -
- - -
-
-
-
{ - if (e.key === 'ArrowDown') { - e.preventDefault(); - timelineHeight = Math.min(500, timelineHeight + 10); - } else if (e.key === 'ArrowUp') { - e.preventDefault(); - timelineHeight = Math.max(40, timelineHeight - 10); - } - }} - >
-
- -
-
-
- {/if} + + + +
+ (showExportDialog = true)} /> +
+
(showAboutDialog = true)} /> @@ -358,20 +333,78 @@ gap: 8px; } + /* --- Grid layout: bottom mode (default) --- */ .content { flex: 1; - display: flex; - flex-direction: column; + display: grid; + grid-template-areas: + "video" + "lower" + "bottom-clips"; + grid-template-columns: 1fr; + grid-template-rows: 1fr auto auto; overflow: hidden; } - .timeline-clip-area { + .video-area { + grid-area: video; + display: flex; + flex-direction: column; + min-height: 0; + overflow: hidden; + } + + .lower-section { + grid-area: lower; display: flex; flex-direction: column; flex-shrink: 0; min-height: 80px; } + .bottom-cliplist { + grid-area: bottom-clips; + min-height: 20px; + overflow: hidden; + } + + /* Sidebar elements hidden in bottom mode */ + .sidebar-area { + grid-area: sidebar; + overflow-y: auto; + overflow-x: hidden; + border-right: 1px solid var(--border); + background: var(--bg-secondary); + } + + .sidebar-handle { + grid-area: handle; + width: 5px; + background: var(--border); + cursor: ew-resize; + flex-shrink: 0; + transition: background 0.1s; + } + + .sidebar-handle:hover, + .sidebar-handle.active { + background: var(--accent); + } + + .hidden { + display: none !important; + } + + /* --- Grid layout: left mode --- */ + .content.layout-left { + grid-template-areas: + "sidebar handle video" + "lower lower lower" + "bottom-clips bottom-clips bottom-clips"; + grid-template-columns: var(--sidebar-width, 220px) 5px 1fr; + grid-template-rows: 1fr auto 0; + } + .timeline-pane { flex-shrink: 0; min-height: 40px; @@ -391,55 +424,6 @@ background: var(--accent, #89b4fa); } - .cliplist-pane { - flex: 1; - min-height: 20px; - overflow: hidden; - } - - .content-left { - display: flex; - flex-direction: column; - overflow: hidden; - } - - .upper-section { - flex: 1; - display: flex; - flex-direction: row; - min-height: 0; - overflow: hidden; - } - - .cliplist-sidebar { - flex-shrink: 0; - overflow-y: auto; - overflow-x: hidden; - border-right: 1px solid var(--border); - background: var(--bg-secondary); - } - - .sidebar-resize-handle { - width: 5px; - background: var(--border); - cursor: ew-resize; - flex-shrink: 0; - transition: background 0.1s; - } - - .sidebar-resize-handle:hover, - .sidebar-resize-handle.active { - background: var(--accent); - } - - .right-column { - flex: 1; - display: flex; - flex-direction: column; - min-width: 0; - overflow: hidden; - } - .layout-btn { font-size: 16px; padding: 4px 8px; diff --git a/src/lib/timeline/renderer.ts b/src/lib/timeline/renderer.ts index 782f63c..07db195 100644 --- a/src/lib/timeline/renderer.ts +++ b/src/lib/timeline/renderer.ts @@ -28,8 +28,8 @@ const WAVEFORM_LANE_HEIGHT = 40; const CONTROLS_LANE_HEIGHT = 30; const TICK_HEIGHT = 10; const BASE_FONT_SIZE = 12; -const MAGNIFY_BONUS = 6; -const MAGNIFY_RADIUS = 80; +const MAGNIFY_BONUS = 10; +const MAGNIFY_RADIUS = 100; export function drawTimeline( ctx: CanvasRenderingContext2D, @@ -82,6 +82,21 @@ function drawTimeTicks( ctx.textAlign = 'center'; const baselineY = state.height - CONTROLS_LANE_HEIGHT + TICK_HEIGHT + 14; + // Draw minor ticks first (behind major ticks) + const minorInterval = tickInterval / 4; + const minorTickHeight = TICK_HEIGHT * 0.5; + for (let t = startTick - tickInterval; t <= state.visibleEnd + minorInterval; t += minorInterval) { + if (t < 0 || t > duration) continue; + if (Math.abs(t % tickInterval) < 0.001 || Math.abs(t % tickInterval - tickInterval) < 0.001) continue; + const x = timeToX(t, state); + ctx.strokeStyle = '#35354a'; + ctx.beginPath(); + ctx.moveTo(x, state.height - CONTROLS_LANE_HEIGHT); + ctx.lineTo(x, state.height - CONTROLS_LANE_HEIGHT + minorTickHeight); + ctx.stroke(); + } + + // Draw major ticks with labels for (let t = startTick; t <= state.visibleEnd; t += tickInterval) { if (t < 0 || t > duration) continue; const x = timeToX(t, state); @@ -93,15 +108,25 @@ function drawTimeTicks( ctx.stroke(); let fontSize = BASE_FONT_SIZE; + let scale = 0; if (mouseX !== null) { const distance = Math.abs(x - mouseX); if (distance < MAGNIFY_RADIUS) { - const scale = (1 + Math.cos((Math.PI * distance) / MAGNIFY_RADIUS)) / 2; + scale = (1 + Math.cos((Math.PI * distance) / MAGNIFY_RADIUS)) / 2; fontSize = BASE_FONT_SIZE + MAGNIFY_BONUS * scale; } } - ctx.fillStyle = '#6c7086'; + // Interpolate color from muted (#6c7086) to bright (#cdd6f4) + if (scale > 0) { + const r = Math.round(108 + (205 - 108) * scale); + const g = Math.round(112 + (214 - 112) * scale); + const b = Math.round(134 + (244 - 134) * scale); + ctx.fillStyle = `rgb(${r}, ${g}, ${b})`; + } else { + ctx.fillStyle = '#6c7086'; + } + ctx.font = `${Math.round(fontSize)}px -apple-system, sans-serif`; const label = formatTickLabel(t);