commit 05c3789b2310086874148f0f65c14b84f8722352
parent 74ce21d31ff94b6da91de5b297c7e20cd7f7f9e2
Author: Hunter
Date:   Tue, 28 Oct 2025 09:36:19 -0400

add modern progress bar

Diffstat:
Mindex.html | 190++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
1 file changed, 135 insertions(+), 55 deletions(-)

diff --git a/index.html b/index.html @@ -105,6 +105,10 @@ color: var(--asphalt); } + .playlist-item:hover .playlist-item-artist { + opacity: 1; + } + .playlist-item:hover .playlist-item-title.current, .playlist-item:hover .playlist-item-artist.current { color: var(--asphalt); @@ -133,43 +137,66 @@ .progress-container { width: 100%; font-size: 24px; - background-color: var(--icee); - cursor: pointer; + cursor: grab; margin-top: 3px; height: 35px; + display: flex; + align-items: center; + padding: 0 12px; + box-sizing: border-box; + position: relative; } .progress-container:hover { - cursor: pointer; + cursor: grab; } .progress-bar { - width: 0%; - height: 34px; - background-color: var(--bubblegum); - border-bottom: 1px solid var(--blush); + width: 100%; + height: 4px; + background-color: var(--icee); position: relative; + border-radius: 1px; + --progress: 0%; + --circle-size: 16px; } - .progress-text { - text-shadow: 1px 1px 1px var(--asphalt); + .progress-bar::before { + content: ''; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: var(--progress); + background-color: var(--bubblegum); + border-radius: 1px; + transition: none; } - .progress-text-left { + .progress-bar::after { + content: ''; position: absolute; - left: 5px; + left: calc(var(--progress) - var(--circle-size) / 2); top: 50%; transform: translateY(-50%); - color: var(--icee); + width: var(--circle-size); + height: var(--circle-size); + background-color: var(--bubblegum); + border-radius: 50%; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); + transition: none; + } + + .progress-text { + display: none; + } + + .progress-text-left { + display: none; } .progress-text-center { - position: absolute; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - color: var(--icee); - opacity: 0; + display: none; } .control-button { @@ -238,6 +265,8 @@ let progressInterval; let playerReady = false; let songs = []; + let animationFrameId = null; + let prePlaySeekTime = 0; // Load tracks from tracks.json fetch('tracks.json') @@ -434,68 +463,119 @@ function startProgressBar() { stopProgressBar(); - progressInterval = setInterval(updateProgressBar, 100); + + function animate() { + updateProgressBar(); + animationFrameId = requestAnimationFrame(animate); + } + + animate(); } function stopProgressBar() { - clearInterval(progressInterval); + if (animationFrameId !== null) { + cancelAnimationFrame(animationFrameId); + animationFrameId = null; + } } function resetProgressBar() { - progressBar.style.width = '0%'; - const progressTextLeft = document.getElementById('progressTextLeft'); - const progressTextCenter = document.getElementById('progressTextCenter'); - progressTextLeft.textContent = '0%'; - progressTextCenter.textContent = '0%'; - progressTextLeft.style.opacity = '1'; - progressTextCenter.style.opacity = '0'; + progressBar.style.setProperty('--progress', '0%'); } function updateProgressBar() { - if (audio.duration) { + if (audio.duration && !isDragging) { const currentTime = audio.currentTime; const duration = audio.duration; const progressPercentage = (currentTime / duration) * 100; - const displayPercentage = isNaN(progressPercentage) ? 0 : Math.round(progressPercentage); - const percentageText = `${displayPercentage}%`; + const displayPercentage = isNaN(progressPercentage) ? 0 : progressPercentage; + + progressBar.style.setProperty('--progress', `${displayPercentage}%`); + } + } - progressBar.style.width = `${displayPercentage}%`; + let isDragging = false; + let wasPlayingBeforeDrag = false; + let pendingSeekPercentage = null; - const progressTextLeft = document.getElementById('progressTextLeft'); - const progressTextCenter = document.getElementById('progressTextCenter'); + function updateVisualProgress(event) { + if (!playerReady) return; - progressTextLeft.textContent = percentageText; - progressTextCenter.textContent = percentageText; + const rect = progressBar.getBoundingClientRect(); + const clickPosition = event.clientX - rect.left; + const clickPercentage = Math.max(0, Math.min(1, clickPosition / rect.width)); - // Get the width of the text - const textWidth = progressTextCenter.offsetWidth; - const barWidth = progressBar.offsetWidth; + progressBar.style.setProperty('--progress', `${clickPercentage * 100}%`); + return clickPercentage; + } - // Show centered text only when there's room - if (barWidth >= textWidth + 20) { - progressTextLeft.style.opacity = '0'; - progressTextCenter.style.opacity = '1'; - } else { - progressTextLeft.style.opacity = '1'; - progressTextCenter.style.opacity = '0'; - } + function applySeek(clickPercentage) { + if (!playerReady) return; + + // If audio hasn't been loaded yet, load it but don't play + if (!audio.src || audio.src === '') { + const song = songs[currentSongIndex]; + audio.src = `tracks/${song.filename}`; + + // Wait for metadata to be loaded before seeking + audio.addEventListener('loadedmetadata', function setInitialTime() { + const duration = audio.duration; + const seekTime = duration * clickPercentage; + audio.currentTime = seekTime; + prePlaySeekTime = seekTime; + audio.removeEventListener('loadedmetadata', setInitialTime); + }, { once: true }); + } else if (audio.duration) { + const duration = audio.duration; + const seekTime = duration * clickPercentage; + audio.currentTime = seekTime; + prePlaySeekTime = seekTime; } } - function seekTo(event) { - if (!playerReady || !audio.duration) return; - const rect = progressContainer.getBoundingClientRect(); - const clickPosition = event.clientX - rect.left; - const clickPercentage = clickPosition / rect.width; - const duration = audio.duration; - const seekTime = duration * clickPercentage; - audio.currentTime = seekTime; + function onProgressMouseDown(event) { + if (!playerReady) return; + isDragging = true; + wasPlayingBeforeDrag = isPlaying; + progressContainer.style.cursor = 'grabbing'; + document.body.style.cursor = 'grabbing'; + pendingSeekPercentage = updateVisualProgress(event); + event.preventDefault(); + } + + function onProgressMouseMove(event) { + if (isDragging) { + pendingSeekPercentage = updateVisualProgress(event); + } + } + + function onProgressMouseUp(event) { + if (isDragging) { + isDragging = false; + progressContainer.style.cursor = ''; + document.body.style.cursor = ''; + + // Apply the seek now that drag is complete + if (pendingSeekPercentage !== null) { + applySeek(pendingSeekPercentage); + pendingSeekPercentage = null; + } + + // If it was "Ready to play" (not playing before), start playing now + if (!wasPlayingBeforeDrag && !isPlaying && audio.src) { + audio.play(); + isPlaying = true; + updatePlayPauseButton(); + } + } } playPauseBtn.addEventListener('click', togglePlayPause); nextBtn.addEventListener('click', nextSong); prevBtn.addEventListener('click', prevSong); - progressContainer.addEventListener('click', seekTo); + progressContainer.addEventListener('mousedown', onProgressMouseDown); + document.addEventListener('mousemove', onProgressMouseMove); + document.addEventListener('mouseup', onProgressMouseUp); // Keyboard controls document.addEventListener('keydown', function(event) {