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/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/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..f5ae910 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)'; @@ -727,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/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..926cc4f 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 { @@ -1162,7 +1165,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 +1229,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 +1667,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; @@ -2196,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 { @@ -2224,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; } @@ -2454,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 {