From 8361d31408acd70f2391225cf5f2a7620937fe82 Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Tue, 23 Dec 2025 22:55:23 +0100 Subject: [PATCH] feat: add next track info to enlarged cover view with animation --- index.html | 4 ++++ js/app.js | 10 +++++++++- js/player.js | 13 +++++++++++++ js/ui.js | 16 +++++++++++++++- styles.css | 27 +++++++++++++++++++++++++++ 5 files changed, 68 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 8d00374..53b2a85 100644 --- a/index.html +++ b/index.html @@ -43,6 +43,10 @@

+
diff --git a/js/app.js b/js/app.js index 5e04325..3c2feac 100644 --- a/js/app.js +++ b/js/app.js @@ -252,7 +252,8 @@ document.addEventListener('DOMContentLoaded', async () => { clearLyricsPanelSync(audioPlayer, lyricsPanel); } } else if (mode === 'cover') { - ui.showFullscreenCover(player.currentTrack); + const nextTrack = player.getNextTrack(); + ui.showFullscreenCover(player.currentTrack, nextTrack); } else { // Default to 'album' mode - navigate to album if (player.currentTrack.album?.id) { @@ -315,6 +316,13 @@ document.addEventListener('DOMContentLoaded', async () => { } } } + + // Update Fullscreen/Enlarged Cover if it's open + const fullscreenOverlay = document.getElementById('fullscreen-cover-overlay'); + if (fullscreenOverlay && getComputedStyle(fullscreenOverlay).display !== 'none') { + const nextTrack = player.getNextTrack(); + ui.showFullscreenCover(player.currentTrack, nextTrack); + } }); document.addEventListener('click', async (e) => { diff --git a/js/player.js b/js/player.js index c67b689..8cb5090 100644 --- a/js/player.js +++ b/js/player.js @@ -366,6 +366,19 @@ export class Player { return this.shuffleActive ? this.shuffledQueue : this.queue; } + getNextTrack() { + const currentQueue = this.getCurrentQueue(); + if (this.currentQueueIndex === -1 || currentQueue.length === 0) return null; + + const nextIndex = this.currentQueueIndex + 1; + if (nextIndex < currentQueue.length) { + return currentQueue[nextIndex]; + } else if (this.repeatMode === REPEAT_MODE.ALL) { + return currentQueue[0]; + } + return null; + } + updatePlayingTrackIndicator() { const currentTrack = this.getCurrentQueue()[this.currentQueueIndex]; document.querySelectorAll('.track-item').forEach(item => { diff --git a/js/ui.js b/js/ui.js index ef8041b..179c745 100644 --- a/js/ui.js +++ b/js/ui.js @@ -259,13 +259,14 @@ export class UIRenderer { root.style.removeProperty('--ring'); } - showFullscreenCover(track) { + showFullscreenCover(track, nextTrack) { if (!track) return; const overlay = document.getElementById('fullscreen-cover-overlay'); const image = document.getElementById('fullscreen-cover-image'); const title = document.getElementById('fullscreen-track-title'); const artist = document.getElementById('fullscreen-track-artist'); + const nextTrackEl = document.getElementById('fullscreen-next-track'); const coverUrl = this.api.getCoverUrl(track.album?.cover, '1280'); @@ -273,6 +274,19 @@ export class UIRenderer { title.textContent = track.title; artist.textContent = track.artist?.name || 'Unknown Artist'; + if (nextTrack) { + nextTrackEl.style.display = 'flex'; + nextTrackEl.querySelector('.value').textContent = `${nextTrack.title} • ${nextTrack.artist?.name || 'Unknown'}`; + + // Replay animation + nextTrackEl.classList.remove('animate-in'); + void nextTrackEl.offsetWidth; // Trigger reflow + nextTrackEl.classList.add('animate-in'); + } else { + nextTrackEl.style.display = 'none'; + nextTrackEl.classList.remove('animate-in'); + } + // Set the background image via CSS variable for the pseudo-element to use overlay.style.setProperty('--bg-image', `url('${coverUrl}')`); diff --git a/styles.css b/styles.css index 6575356..61543d0 100644 --- a/styles.css +++ b/styles.css @@ -1319,6 +1319,33 @@ input:checked + .slider::before { text-shadow: 0 2px 10px rgba(0,0,0,0.5); } +#fullscreen-next-track { + margin-top: 1.5rem; + font-size: 0.9rem; + color: rgba(255, 255, 255, 0.6); + text-shadow: 0 1px 4px rgba(0,0,0,0.5); + display: flex; + flex-direction: column; + gap: 0.2rem; + opacity: 0; /* Initially hidden for animation */ +} + +#fullscreen-next-track.animate-in { + animation: fadeIn 0.5s ease 0.2s forwards; /* Added forwards to keep opacity 1 */ +} + +#fullscreen-next-track .label { + text-transform: uppercase; + letter-spacing: 0.05em; + font-size: 0.75rem; + opacity: 0.8; +} + +#fullscreen-next-track .value { + font-weight: 500; + color: rgba(255, 255, 255, 0.9); +} + @media (max-width: 768px) { #fullscreen-cover-overlay { padding-bottom: 160px; /* Account for taller mobile player bar */