From 1d5f4a620dd991ca3d04f971f491874c22a2c16f Mon Sep 17 00:00:00 2001 From: Honey Date: Thu, 4 Jun 2026 11:03:54 +0200 Subject: [PATCH] fix: NP flicker, fully-random shuffle, playlist count backpatch NP overlay flicker (stronger fix): - Remove contain:layout style paint (causes layer issues on some Android WebView) - Add ::before pseudo-element with solid background so no frame can be transparent - Add -webkit-transform prefixes for both open and closed states Shuffle fully random: - playQueue no longer pins startIdx when shuffle is on - All songs shuffled into a completely random order, S.qIdx=0 always - toggleShuffle mid-queue still pins current song first (Spotify toggle behavior) Playlist count wrong in overview: - Navidrome's getPlaylists returns stale/wrong songCount in its index - Add data-pl-id to each playlist card in loadPlaylists - After loadPlaylistDetail gets the accurate entry count, backpatch the matching overview card's subtitle so the grid shows the real song count Co-Authored-By: Claude Sonnet 4.6 --- src/player.js | 8 +++----- src/views.js | 5 +++++ styles.css | 12 ++++++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/player.js b/src/player.js index 1041096..ee06f71 100644 --- a/src/player.js +++ b/src/player.js @@ -359,13 +359,11 @@ export function playQueue(songs, startIdx) { _radioFilling = false; // cancel any in-flight radio fill for the old queue S._origQueue = null; _histClear(); - // Spotify-style shuffle: build a fixed shuffled order once, pin the tapped song first. - // Applies to both web and Android โ€” queue is traversed sequentially in this fixed order. + // Shuffle: fully random order, no pinning โ€” startIdx is ignored. + // Both web and Android traverse this fixed shuffled order sequentially. if (S.shuffle && songs.length > 1) { - const start = songs[startIdx]; - const rest = songs.filter((_, i) => i !== startIdx).sort(() => Math.random() - 0.5); S._origQueue = songs.map(s => ({...s})); - S.queue = [start, ...rest].map(s => ({...s})); + S.queue = [...songs].sort(() => Math.random() - 0.5).map(s => ({...s})); S.qIdx = 0; } else { S.queue = songs.map(s => ({...s})); diff --git a/src/views.js b/src/views.js index be25d07..02a94d6 100644 --- a/src/views.js +++ b/src/views.js @@ -687,6 +687,7 @@ export async function loadPlaylists() { lists.forEach(pl => { const cv = coverUrl(pl.coverArt, 512); const c = makeCard(pl.name, pl.songCount+' Titel', cv, '๐ŸŽถ'); + c.dataset.plId = pl.id; c.querySelector('.card-art').onclick = (e) => { e.stopPropagation(); quickPlayPlaylist(pl); }; c.querySelector('.card-info').onclick = () => navigate('playlist-detail', pl); g.appendChild(c); @@ -728,6 +729,10 @@ export async function loadPlaylistDetail(pl) { const songs = full.entry || []; const totalMin = Math.round(songs.reduce((s,t) => s+(t.duration||0), 0) / 60); document.getElementById('playlist-detail-stats').textContent = `${songs.length} Titel ยท ${totalMin} Min.`; + // Navidrome's getPlaylists can return a stale songCount โ€” backpatch the overview card + // with the real count we just got from getPlaylist so the grid always shows truth. + const overviewCard = document.querySelector(`#playlists-grid [data-pl-id="${pl.id}"]`); + if (overviewCard) overviewCard.querySelector('.card-sub').textContent = songs.length + ' Titel'; const descEl = document.getElementById('playlist-detail-desc'); if(descEl) { descEl.textContent = full.comment||''; descEl.style.display = full.comment ? '' : 'none'; } document.getElementById('btn-play-playlist').onclick = () => playQueue(songs, 0); diff --git a/styles.css b/styles.css index 2934e52..96d6f0c 100644 --- a/styles.css +++ b/styles.css @@ -573,16 +573,20 @@ button:focus{outline:none} position:fixed;inset:0;z-index:300; display:flex;flex-direction:column; background:var(--bg); - /* translate3d keeps the element on a stable GPU layer without will-change's - layer-creation/destruction cycle that causes WebView to flash the content below */ transform:translate3d(0,100%,0); + -webkit-transform:translate3d(0,100%,0); transition:transform .35s cubic-bezier(.4,0,.2,1); overflow:hidden; backface-visibility:hidden;-webkit-backface-visibility:hidden; - contain:layout style paint; isolation:isolate; } -.np-overlay.open{transform:translate3d(0,0,0)} +/* Solid opaque floor so no frame can ever be transparent to content below */ +.np-overlay::before{ + content:'';position:absolute;inset:0; + background:var(--bg);z-index:-1; + transform:translateZ(0); +} +.np-overlay.open{transform:translate3d(0,0,0);-webkit-transform:translate3d(0,0,0)} /* Blurred album art as background โ€” two layers for crossfade */ .np-bg{