From afded2b335a469ac550c345d1676a910a650a988 Mon Sep 17 00:00:00 2001 From: skal Date: Thu, 5 Feb 2026 21:37:14 +0100 Subject: fix(timeline-editor): Dynamic sequence bounds, mouse wheel scroll, responsive layout Bug Fix #1: Dynamic Sequence Start Time - Sequences now dynamically adjust start position based on earliest effect - Sequence box shrinks from left when effects move later in time - Calculate visual bounds: min(effect.startTime) to max(effect.endTime) - Sequence position: seq.startTime + minEffectStart - Sequence width: maxEffectEnd - minEffectStart - Time display shows actual visual start time, not fixed seq.startTime - Eliminates empty space at sequence start when effects are delayed Bug Fix #2: Mouse Wheel Horizontal Scroll - Mouse wheel now scrolls timeline horizontally (natural navigation) - Prevents default vertical scroll behavior on timeline container - More intuitive than using horizontal scrollbar - Smooth scrolling with deltaY mapped to scrollLeft Bug Fix #3: Responsive Layout - Container now uses 100% width (was: fixed 1400px max) - Added window resize event listener to re-render timeline - Body uses box-sizing: border-box for proper padding - Timeline recalculates on browser window resize - Works correctly when going fullscreen Technical details: - seqVisualStart = seq.startTime + Math.min(effect.startTime) - seqVisualEnd = seq.startTime + Math.max(effect.endTime) - Wheel event uses { passive: false } to allow preventDefault() - Window resize debouncing not needed (renderTimeline is fast) --- tools/timeline_editor/index.html | 42 +++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 9 deletions(-) (limited to 'tools/timeline_editor/index.html') diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html index 4dd37ee..22e5239 100644 --- a/tools/timeline_editor/index.html +++ b/tools/timeline_editor/index.html @@ -16,11 +16,16 @@ background: #1e1e1e; color: #d4d4d4; padding: 20px; + margin: 0; + min-height: 100vh; + box-sizing: border-box; } .container { - max-width: 1400px; + max-width: 100%; + width: 100%; margin: 0 auto; + box-sizing: border-box; } header { @@ -393,6 +398,7 @@ // DOM elements const timeline = document.getElementById('timeline'); + const timelineContainer = document.querySelector('.timeline-container'); const fileInput = document.getElementById('fileInput'); const saveBtn = document.getElementById('saveBtn'); const addSequenceBtn = document.getElementById('addSequenceBtn'); @@ -573,30 +579,37 @@ seqDiv.className = 'sequence'; seqDiv.dataset.index = seqIndex; - // Calculate sequence duration based on effects - let seqDuration = 10; // Default + // Calculate sequence bounds based on effects (dynamic start/end) + let seqVisualStart = seq.startTime; + let seqVisualEnd = seq.startTime + 10; // Default 10s duration + if (seq.effects.length > 0) { - seqDuration = Math.max(...seq.effects.map(e => e.endTime)); + const minEffectStart = Math.min(...seq.effects.map(e => e.startTime)); + const maxEffectEnd = Math.max(...seq.effects.map(e => e.endTime)); + seqVisualStart = seq.startTime + minEffectStart; + seqVisualEnd = seq.startTime + maxEffectEnd; } + const seqVisualWidth = seqVisualEnd - seqVisualStart; + // Calculate sequence height based on number of effects (stacked vertically) const numEffects = seq.effects.length; const effectSpacing = 30; // Reduced from 40px const seqHeight = Math.max(70, 20 + numEffects * effectSpacing + 5); - seqDiv.style.left = `${seq.startTime * pixelsPerSecond}px`; + seqDiv.style.left = `${seqVisualStart * pixelsPerSecond}px`; seqDiv.style.top = `${seqIndex * 80}px`; - seqDiv.style.width = `${seqDuration * pixelsPerSecond}px`; + seqDiv.style.width = `${seqVisualWidth * pixelsPerSecond}px`; seqDiv.style.height = `${seqHeight}px`; - // Format time display based on mode + // Format time display based on mode (show visual start, not seq.startTime) let seqTimeDisplay; if (showBeats) { const beatDuration = 60.0 / bpm; - const startBeat = (seq.startTime / beatDuration).toFixed(1); + const startBeat = (seqVisualStart / beatDuration).toFixed(1); seqTimeDisplay = `Start: ${startBeat}b`; } else { - seqTimeDisplay = `Start: ${seq.startTime.toFixed(2)}s`; + seqTimeDisplay = `Start: ${seqVisualStart.toFixed(2)}s`; } // Create sequence name overlay (large, centered, fades on hover) @@ -916,6 +929,17 @@ updateProperties(); }); + // Mouse wheel horizontal scroll + timelineContainer.addEventListener('wheel', (e) => { + e.preventDefault(); + timelineContainer.scrollLeft += e.deltaY; + }, { passive: false }); + + // Window resize handler + window.addEventListener('resize', () => { + renderTimeline(); + }); + // Utilities function showMessage(text, type) { messageArea.innerHTML = `
${text}
`; -- cgit v1.2.3