From 99f2ccfdb2065dff757a74a4961224f0dd3dbdea Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Thu, 25 Dec 2025 20:03:16 +0100 Subject: [PATCH 1/3] Refactor SVG icons and improve settings UI --- index.html | 48 +++++++++++++++---------------------------- js/app.js | 6 +++++- js/downloads.js | 12 +++-------- js/lyrics.js | 13 +++--------- js/storage.js | 4 ++-- js/ui-interactions.js | 7 ++----- js/ui.js | 38 ++++++++++++++++------------------ js/utils.js | 3 +++ styles.css | 10 ++------- 9 files changed, 55 insertions(+), 86 deletions(-) diff --git a/index.html b/index.html index e1d58d1..ef3f00f 100644 --- a/index.html +++ b/index.html @@ -168,17 +168,9 @@
@@ -196,17 +188,9 @@

@@ -229,11 +213,6 @@ Artist Radio @@ -276,6 +255,16 @@ +
+
+ Album Cover Background + Use the album cover as a blurred background on album pages +
+ +
Last.fm Scrobbling @@ -339,16 +328,6 @@
-
-
- Album Cover Background - Use the album cover as a blurred background on album pages -
- -
Gapless Playback @@ -373,6 +352,13 @@
+
+
+ Keyboard Shortcuts + View available keyboard shortcuts +
+ +
Cache diff --git a/js/app.js b/js/app.js index 527a44d..e577baf 100644 --- a/js/app.js +++ b/js/app.js @@ -568,7 +568,11 @@ document.addEventListener('DOMContentLoaded', async () => { } }); - if (!localStorage.getItem('shortcuts-shown')) { + document.getElementById('show-shortcuts-btn')?.addEventListener('click', () => { + showKeyboardShortcuts(); + }); + + if (!localStorage.getItem('shortcuts-shown') && window.innerWidth > 768) { setTimeout(() => { showKeyboardShortcuts(); localStorage.setItem('shortcuts-shown', 'true'); diff --git a/js/downloads.js b/js/downloads.js index ceb8a8f..c652c33 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -1,5 +1,5 @@ //js/downloads.js -import { buildTrackFilename, sanitizeForFilename, RATE_LIMIT_ERROR_MESSAGE, getTrackArtists, getTrackTitle, formatTemplate } from './utils.js'; +import { buildTrackFilename, sanitizeForFilename, RATE_LIMIT_ERROR_MESSAGE, getTrackArtists, getTrackTitle, formatTemplate, SVG_CLOSE } from './utils.js'; import { lyricsSettings } from './storage.js'; const downloadTasks = new Map(); @@ -65,10 +65,7 @@ export function addDownloadTask(trackId, track, filename, api) {
Starting...
`; @@ -132,10 +129,7 @@ export function completeDownloadTask(trackId, success = true, message = null) { statusEl.textContent = message || '✗ Download failed'; statusEl.style.color = '#ef4444'; cancelBtn.innerHTML = ` - - - - + ${SVG_CLOSE} `; cancelBtn.onclick = () => removeDownloadTask(trackId); diff --git a/js/lyrics.js b/js/lyrics.js index b810f27..f4c297f 100644 --- a/js/lyrics.js +++ b/js/lyrics.js @@ -1,5 +1,5 @@ //js/lyrics.js -import { getTrackTitle, getTrackArtists } from './utils.js'; +import { getTrackTitle, getTrackArtists, SVG_DOWNLOAD, SVG_CLOSE } from './utils.js'; export class LyricsManager { constructor(api) { @@ -104,17 +104,10 @@ export function createLyricsPanel() {

Lyrics

diff --git a/js/storage.js b/js/storage.js index ef58215..0d9b75c 100644 --- a/js/storage.js +++ b/js/storage.js @@ -305,9 +305,9 @@ export const nowPlayingSettings = { getMode() { try { - return localStorage.getItem(this.STORAGE_KEY) || 'album'; + return localStorage.getItem(this.STORAGE_KEY) || 'cover'; } catch (e) { - return 'album'; + return 'cover'; } }, diff --git a/js/ui-interactions.js b/js/ui-interactions.js index 851286a..5070aa8 100644 --- a/js/ui-interactions.js +++ b/js/ui-interactions.js @@ -1,5 +1,5 @@ //js/ui-interactions.js -import { formatTime, trackDataStore, getTrackTitle, getTrackArtists } from './utils.js'; +import { SVG_CLOSE, formatTime, trackDataStore, getTrackTitle, getTrackArtists } from './utils.js'; export function initializeUIInteractions(player, api) { const sidebar = document.querySelector('.sidebar'); @@ -78,10 +78,7 @@ export function initializeUIInteractions(player, api) {
${formatTime(track.duration)}
`; diff --git a/js/ui.js b/js/ui.js index 7a33729..503eada 100644 --- a/js/ui.js +++ b/js/ui.js @@ -1,5 +1,5 @@ //js/ui.js -import { formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackArtists, getTrackTitle, calculateTotalDuration, formatDuration } from './utils.js'; +import { SVG_PLAY, SVG_DOWNLOAD, SVG_MENU, formatTime, createPlaceholder, trackDataStore, hasExplicitContent, getTrackArtists, getTrackTitle, calculateTotalDuration, formatDuration } from './utils.js'; import { recentActivityManager, backgroundSettings, trackListSettings } from './storage.js'; export class UIRenderer { @@ -42,11 +42,7 @@ export class UIRenderer { createTrackMenuButton() { return ` `; } @@ -61,7 +57,7 @@ export class UIRenderer { } createTrackItemHTML(track, index, showCover = false, hasMultipleDiscs = false) { - const playIconSmall = ''; + const playIconSmall = SVG_PLAY; const trackImageHTML = showCover ? `Track Cover` : ''; let displayIndex; @@ -107,19 +103,11 @@ export class UIRenderer { `; @@ -488,6 +476,10 @@ export class UIRenderer { const metaEl = document.getElementById('album-detail-meta'); const prodEl = document.getElementById('album-detail-producer'); const tracklistContainer = document.getElementById('album-detail-tracklist'); + const playBtn = document.getElementById('play-album-btn'); + if (playBtn) playBtn.innerHTML = `${SVG_PLAY}Play Album`; + const dlBtn = document.getElementById('download-album-btn'); + if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD}Download Album`; imageEl.src = ''; imageEl.style.backgroundColor = 'var(--muted)'; @@ -616,10 +608,14 @@ async renderPlaylistPage(playlistId) { const imageEl = document.getElementById('playlist-detail-image'); const titleEl = document.getElementById('playlist-detail-title'); const metaEl = document.getElementById('playlist-detail-meta'); - const descEl = document.getElementById('playlist-detail-description'); - const tracklistContainer = document.getElementById('playlist-detail-tracklist'); + const descEl = document.getElementById('playlist-detail-description'); + const tracklistContainer = document.getElementById('playlist-detail-tracklist'); + const playBtn = document.getElementById('play-playlist-btn'); + if (playBtn) playBtn.innerHTML = `${SVG_PLAY}Play`; + const dlBtn = document.getElementById('download-playlist-btn'); + if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD}Download`; - imageEl.src = ''; + imageEl.src = ''; imageEl.style.backgroundColor = 'var(--muted)'; titleEl.innerHTML = '
'; metaEl.innerHTML = '
'; @@ -676,6 +672,8 @@ async renderPlaylistPage(playlistId) { const metaEl = document.getElementById('artist-detail-meta'); const tracksContainer = document.getElementById('artist-detail-tracks'); const albumsContainer = document.getElementById('artist-detail-albums'); + const dlBtn = document.getElementById('download-discography-btn'); + if (dlBtn) dlBtn.innerHTML = `${SVG_DOWNLOAD}Download Discography`; imageEl.src = ''; imageEl.style.backgroundColor = 'var(--muted)'; diff --git a/js/utils.js b/js/utils.js index 112c925..4010e4d 100644 --- a/js/utils.js +++ b/js/utils.js @@ -30,6 +30,9 @@ export const SVG_PLAY = ' { if (isNaN(seconds)) return '0:00'; diff --git a/styles.css b/styles.css index bffa144..14964c2 100644 --- a/styles.css +++ b/styles.css @@ -1162,7 +1162,7 @@ input:checked + .slider::before { .progress-bar .progress-fill { width: 0; height: 100%; - background-color: var(--foreground); + background-color: var(--muted-foreground); border-radius: 3px; transition: background-color 0.2s ease; position: relative; @@ -1226,7 +1226,7 @@ input:checked + .slider::before { .volume-controls .volume-bar .volume-fill { width: var(--volume-level, 70%); height: 100%; - background-color: var(--foreground); + background-color: var(--muted-foreground); border-radius: 2px; transition: background-color 0.2s ease; position: relative; @@ -1664,12 +1664,6 @@ input:checked + .slider::before { width: 100%; } -#api-instance-manager { - margin-top: 1rem; - padding-top: 1rem; - border-top: 1px solid var(--border); -} - #api-instance-list { list-style: none; margin-bottom: 1rem; From 31862a835da96c071759853962840978d1e96a26 Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Thu, 25 Dec 2025 20:30:56 +0100 Subject: [PATCH 2/3] Improve mobile UI and fix Media Session and API settings bugs --- js/events.js | 5 +++++ js/ui.js | 2 +- styles.css | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/js/events.js b/js/events.js index 60fe12a..931ee08 100644 --- a/js/events.js +++ b/js/events.js @@ -37,6 +37,11 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler) { updateTabTitle(player); }); + audioPlayer.addEventListener('playing', () => { + player.updateMediaSessionPlaybackState(); + player.updateMediaSessionPositionState(); + }); + audioPlayer.addEventListener('pause', () => { playPauseBtn.innerHTML = SVG_PLAY; player.updateMediaSessionPlaybackState(); diff --git a/js/ui.js b/js/ui.js index 503eada..f5ae910 100644 --- a/js/ui.js +++ b/js/ui.js @@ -725,7 +725,7 @@ async renderPlaylistPage(playlistId) { container.innerHTML = instances.map((url, index) => { const speedInfo = speeds[url]; const speedText = speedInfo - ? (speedInfo.speed === Infinity + ? (speedInfo.speed === Infinity || typeof speedInfo.speed !== 'number' ? `Failed` : `${speedInfo.speed.toFixed(0)}ms`) : ''; diff --git a/styles.css b/styles.css index 14964c2..c87a2c6 100644 --- a/styles.css +++ b/styles.css @@ -142,6 +142,7 @@ box-sizing: border-box; margin: 0; padding: 0; + -webkit-tap-highlight-color: transparent; } html { @@ -869,6 +870,7 @@ body.has-page-background .track-item:hover { cursor: pointer; transition: all var(--transition); box-shadow: var(--shadow-sm); + -webkit-tap-highlight-color: transparent; } .btn-primary:hover { @@ -1088,6 +1090,7 @@ input:checked + .slider::before { height: 32px; border-radius: 50%; position: relative; + -webkit-tap-highlight-color: transparent; } .player-controls .buttons button:hover { From a2363f40f16eb496f17af919980036396d613f48 Mon Sep 17 00:00:00 2001 From: Julien Maille Date: Thu, 25 Dec 2025 22:45:50 +0100 Subject: [PATCH 3/3] Improve mobile header layout for album, artist, and playlist pages --- styles.css | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/styles.css b/styles.css index c87a2c6..926cc4f 100644 --- a/styles.css +++ b/styles.css @@ -2193,25 +2193,32 @@ input:checked + .slider::before { } .detail-header { - flex-direction: column; - align-items: flex-start; - gap: var(--spacing-lg); - padding-bottom: var(--spacing-md); + flex-direction: row; + gap: var(--spacing-md); + padding-bottom: var(--spacing-sm); margin-bottom: var(--spacing-lg); } .detail-header-image { - width: 150px; - height: 150px; + width: 120px; + height: 120px; + } + + .detail-header-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + justify-content: center; } .detail-header-info .title { - font-size: 2rem; + font-size: 1.5rem; line-height: 1.2; } .detail-header-info .title.long-title { - font-size: 1.5rem; + font-size: 1.35rem; } .detail-header-info .title.very-long-title { @@ -2221,15 +2228,17 @@ input:checked + .slider::before { .detail-header-info .meta { font-size: 0.85rem; gap: 0.35rem; + margin-top: 0.25em; } .detail-header-actions { width: auto; + margin-top: 0.5em; } .detail-header-actions .btn-primary { width: auto; - padding: 0.875rem; + padding: 0.5rem; border-radius: 50%; aspect-ratio: 1/1; } @@ -2451,15 +2460,15 @@ input:checked + .slider::before { } .detail-header-info .title { - font-size: 1.75rem; + font-size: 1.25rem; } .detail-header-info .title.long-title { - font-size: 1.35rem; + font-size: 1.10rem; } .detail-header-info .title.very-long-title { - font-size: 1.1rem; + font-size: 0.9rem; } .search-tab {