diff --git a/eslint.config.js b/eslint.config.js index 6b2eb91..fa49f24 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -19,7 +19,7 @@ export default [ }, rules: { 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], - 'no-console': ['warn', { allow: ['warn', 'error'] }], + 'no-console': ['warn', { allow: ['log', 'warn', 'error'] }], }, }, ]; diff --git a/js/accounts/config.js b/js/accounts/config.js index 1e0dc77..a8448dc 100644 --- a/js/accounts/config.js +++ b/js/accounts/config.js @@ -152,7 +152,7 @@ export function initializeFirebaseSettingsUI() { customFirebaseConfigContainer.classList.add('visible'); toggleFirebaseConfigBtn.textContent = 'Hide Custom Configuration'; } - } catch (e) { + } catch { firebaseConfigInput.value = currentConfig; } } @@ -180,7 +180,7 @@ export function initializeFirebaseSettingsUI() { prompt('Copy this link:', link); }); } - } catch (e) { + } catch { alert('Invalid configuration found.'); } }); diff --git a/js/accounts/pocketbase.js b/js/accounts/pocketbase.js index d8fb2f0..2f131d9 100644 --- a/js/accounts/pocketbase.js +++ b/js/accounts/pocketbase.js @@ -1,3 +1,4 @@ +import PocketBase from 'pocketbase'; import { db } from '../db.js'; import { authManager } from './auth.js'; @@ -292,7 +293,9 @@ const syncManager = { if (typeof extraData === 'string') { try { extraData = JSON.parse(extraData); - } catch (e) {} + } catch { + // Ignore + } } if (!rawCover && extraData && typeof extraData === 'object') { diff --git a/js/app.js b/js/app.js index a51d672..e7ca6cb 100644 --- a/js/app.js +++ b/js/app.js @@ -10,13 +10,7 @@ import { import { UIRenderer } from './ui.js'; import { Player } from './player.js'; import { LastFMScrobbler } from './lastfm.js'; -import { - LyricsManager, - openLyricsPanel, - clearLyricsPanelSync, - renderLyricsInFullscreen, - clearFullscreenLyricsSync, -} from './lyrics.js'; +import { LyricsManager, openLyricsPanel, clearLyricsPanelSync } from './lyrics.js'; import { createRouter, updateTabTitle } from './router.js'; import { initializeSettings } from './settings.js'; import { initializePlayerEvents, initializeTrackInteractions, handleTrackAction } from './events.js'; @@ -176,7 +170,7 @@ function showOfflineNotification() { document.body.appendChild(notification); setTimeout(() => { - notification.style.animation = 'slideOut 0.3s ease forwards'; + notification.style.animation = 'slide-out 0.3s ease forwards'; setTimeout(() => notification.remove(), 300); }, 5000); } @@ -184,7 +178,7 @@ function showOfflineNotification() { function hideOfflineNotification() { const notification = document.querySelector('.offline-notification'); if (notification) { - notification.style.animation = 'slideOut 0.3s ease forwards'; + notification.style.animation = 'slide-out 0.3s ease forwards'; setTimeout(() => notification.remove(), 300); } } @@ -398,7 +392,7 @@ document.addEventListener('DOMContentLoaded', async () => { if (!userPlaylist) { try { userPlaylist = await syncManager.getPublicPlaylist(playlistId); - } catch (e) { + } catch { // Not a public playlist } } @@ -461,7 +455,7 @@ document.addEventListener('DOMContentLoaded', async () => { } else { try { await syncManager.unpublishPlaylist(playlist.id); - } catch (e) { + } catch { // Ignore error if it wasn't public } } @@ -1292,7 +1286,7 @@ async function parseCSV(csvText, api, onProgress) { const cleanTitle = (t) => t .split(' - ')[0] - .replace(/\s*[\(\[]feat\.?.*?[\)\]]/i, '') + .replace(/\s*[([]feat\.?.*?[)\]]/i, '') .trim(); const cleanedTitle = cleanTitle(trackTitle); const isTitleCleaned = cleanedTitle !== trackTitle; diff --git a/js/cache.js b/js/cache.js index 4c63dc4..38501ba 100644 --- a/js/cache.js +++ b/js/cache.js @@ -57,7 +57,7 @@ export class APICache { return cached.data; } } catch (error) { - console.debug('IndexedDB read error:', error); + console.log('IndexedDB read error:', error); } } @@ -83,7 +83,7 @@ export class APICache { try { await this.setInIndexedDB(entry); } catch (error) { - console.debug('IndexedDB write error:', error); + console.log('IndexedDB write error:', error); } } } @@ -163,7 +163,7 @@ export class APICache { } }; } catch (error) { - console.debug('Failed to clear expired IndexedDB entries:', error); + console.log('Failed to clear expired IndexedDB entries:', error); } } } diff --git a/js/db.js b/js/db.js index 9ec3c76..85f37b9 100644 --- a/js/db.js +++ b/js/db.js @@ -136,7 +136,7 @@ export class MusicDatabase { try { const result = await this.performTransaction(storeName, 'readonly', (store) => store.get(id)); return !!result; - } catch (e) { + } catch { return false; } } @@ -316,12 +316,10 @@ export class MusicDatabase { return new Promise((resolve, reject) => { const transaction = db.transaction(storeName, 'readwrite'); const store = transaction.objectStore(storeName); - let hasChanges = false; // force clear on first sync console.log(`Clearing ${storeName} to Make Sure Everythings Good`); store.clear(); - hasChanges = true; itemsArray.forEach((item) => { if (item.id && typeof item.id === 'string' && !isNaN(item.id)) { diff --git a/js/downloads.js b/js/downloads.js index 1d1a8cb..ae67764 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -64,7 +64,7 @@ export function showNotification(message) { // Auto remove setTimeout(() => { - notifEl.style.animation = 'slideOut 0.3s ease'; + notifEl.style.animation = 'slide-out 0.3s ease forwards'; setTimeout(() => notifEl.remove(), 300); }, 1500); } @@ -162,7 +162,7 @@ function removeDownloadTask(trackId) { if (!task) return; const { taskEl } = task; - taskEl.style.animation = 'slideOut 0.3s ease'; + taskEl.style.animation = 'slide-out 0.3s ease forwards'; setTimeout(() => { taskEl.remove(); @@ -179,7 +179,7 @@ function removeBulkDownloadTask(notifEl) { const task = bulkDownloadTasks.get(notifEl); if (!task) return; - notifEl.style.animation = 'slideOut 0.3s ease'; + notifEl.style.animation = 'slide-out 0.3s ease forwards'; setTimeout(() => { notifEl.remove(); @@ -225,6 +225,7 @@ async function downloadTrackBlob(track, quality, api, lyricsManager = null, sign } // Handle DASH streams (blob URLs) + let blob; if (streamUrl.startsWith('blob:')) { try { const downloader = new DashDownloader(); @@ -266,7 +267,7 @@ async function generateAndDownloadZip(zip, filename, notification, progressTotal compression: 'STORE', streamFiles: true, }) - .on('data', (chunk, metadata) => { + .on('data', (chunk) => { writable.write(chunk); }) .on('error', (err) => { @@ -364,7 +365,7 @@ async function downloadTracksToZip( zip.file(`${folderName}/${lrcFilename}`, lrcContent); } } - } catch (error) { + } catch { console.log('Could not add lyrics for:', trackTitle); } } @@ -518,7 +519,7 @@ export async function downloadDiscography(artist, selectedReleases, api, quality zip.file(`${fullFolderPath}/${lrcFilename}`, lrcContent); } } - } catch (error) { + } catch { // Silent fail for lyrics in bulk } } @@ -547,7 +548,7 @@ export async function downloadDiscography(artist, selectedReleases, api, quality } } -function createBulkDownloadNotification(type, name, totalItems) { +function createBulkDownloadNotification(type, name, _totalItems) { const container = createDownloadNotification(); const notifEl = document.createElement('div'); @@ -608,7 +609,7 @@ function completeBulkDownload(notifEl, success = true, message = null) { statusEl.style.color = '#10b981'; setTimeout(() => { - notifEl.style.animation = 'slideOut 0.3s ease'; + notifEl.style.animation = 'slide-out 0.3s ease forwards'; setTimeout(() => notifEl.remove(), 300); }, 3000); } else { @@ -617,7 +618,7 @@ function completeBulkDownload(notifEl, success = true, message = null) { statusEl.style.color = '#ef4444'; setTimeout(() => { - notifEl.style.animation = 'slideOut 0.3s ease'; + notifEl.style.animation = 'slide-out 0.3s ease forwards'; setTimeout(() => notifEl.remove(), 300); }, 5000); } @@ -660,7 +661,7 @@ export async function downloadTrackWithMetadata(track, quality, api, lyricsManag ongoingDownloads.add(downloadKey); try { - const { taskEl } = addDownloadTask(track.id, enrichedTrack, filename, api, controller); + addDownloadTask(track.id, enrichedTrack, filename, api, controller); await api.downloadTrack(track.id, quality, filename, { signal: controller.signal, @@ -678,7 +679,7 @@ export async function downloadTrackWithMetadata(track, quality, api, lyricsManag if (lyricsData) { lyricsManager.downloadLRC(lyricsData, track); } - } catch (error) { + } catch { console.log('Could not download lyrics for track'); } } diff --git a/js/events.js b/js/events.js index 35e70e0..6efa818 100644 --- a/js/events.js +++ b/js/events.js @@ -1,25 +1,13 @@ //js/events.js -import { - SVG_PLAY, - SVG_PAUSE, - SVG_VOLUME, - SVG_MUTE, - REPEAT_MODE, - trackDataStore, - RATE_LIMIT_ERROR_MESSAGE, - buildTrackFilename, - getTrackTitle, - formatTime, -} from './utils.js'; +import { SVG_PLAY, SVG_PAUSE, SVG_VOLUME, SVG_MUTE, REPEAT_MODE, trackDataStore, formatTime } from './utils.js'; import { lastFMStorage, waveformSettings } from './storage.js'; import { showNotification, downloadTrackWithMetadata } from './downloads.js'; -import { lyricsSettings, downloadQualitySettings } from './storage.js'; +import { downloadQualitySettings } from './storage.js'; import { updateTabTitle } from './router.js'; import { db } from './db.js'; import { syncManager } from './accounts/pocketbase.js'; import { waveformGenerator } from './waveform.js'; -let currentWaveformPeaks = null; let currentTrackIdForWaveform = null; export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { @@ -396,7 +384,7 @@ function initializeSmoothSliders(audioPlayer, player) { } }); - document.addEventListener('mouseup', (e) => { + document.addEventListener('mouseup', () => { if (isSeeking) { // Commit the seek if (!isNaN(audioPlayer.duration)) { @@ -412,7 +400,7 @@ function initializeSmoothSliders(audioPlayer, player) { } }); - document.addEventListener('touchend', (e) => { + document.addEventListener('touchend', () => { if (isSeeking) { if (!isNaN(audioPlayer.duration)) { audioPlayer.currentTime = lastSeekPosition * audioPlayer.duration; @@ -562,7 +550,7 @@ export async function handleTrackAction( if (!playlist) { try { playlist = await syncManager.getPublicPlaylist(item.id); - } catch (e) { + } catch { // Ignore } } diff --git a/js/lastfm.js b/js/lastfm.js index 8db1447..7d49cfe 100644 --- a/js/lastfm.js +++ b/js/lastfm.js @@ -1,5 +1,4 @@ //js/lastfm.js -import { delay, getTrackArtists } from './utils.js'; export class LastFMScrobbler { constructor() { @@ -66,7 +65,7 @@ export class LastFMScrobbler { try { const { default: md5 } = await import('https://cdn.jsdelivr.net/npm/md5@2.3.0/+esm'); return md5(signatureString); - } catch (e) { + } catch { console.error('MD5 library not available'); throw new Error('MD5 library required for Last.fm'); } diff --git a/js/lyrics.js b/js/lyrics.js index d946b60..403fb87 100644 --- a/js/lyrics.js +++ b/js/lyrics.js @@ -1,10 +1,9 @@ //js/lyrics.js -import { getTrackTitle, getTrackArtists, buildTrackFilename, SVG_DOWNLOAD, SVG_CLOSE } from './utils.js'; +import { getTrackTitle, getTrackArtists, buildTrackFilename, SVG_CLOSE } from './utils.js'; import { sidePanelManager } from './side-panel.js'; // Dictionary path for kuromoji // Using CDN - the kuroshiro-analyzer loaded from unpkg will use this as base for fetching dict files -const KUROMOJI_DICT_PATH = 'https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/'; export class LyricsManager { constructor(api) { @@ -190,7 +189,7 @@ export class LyricsManager { getRomajiMode() { try { return localStorage.getItem('lyricsRomajiMode') === 'true'; - } catch (e) { + } catch { return false; } } @@ -549,7 +548,7 @@ export async function openLyricsPanel(track, audioPlayer, lyricsManager) { romajiBtn.addEventListener('click', async () => { const amLyrics = sidePanelManager.panel.querySelector('am-lyrics'); if (amLyrics) { - const newMode = await manager.toggleRomajiMode(amLyrics); + await manager.toggleRomajiMode(amLyrics); updateRomajiBtn(); } }); diff --git a/js/metadata.js b/js/metadata.js index ebfd6b5..ba51762 100644 --- a/js/metadata.js +++ b/js/metadata.js @@ -243,7 +243,6 @@ async function readMp3Metadata(file, metadata) { if (file.size > 128) { const tailBuffer = await file.slice(file.size - 128).arrayBuffer(); - const tailView = new DataView(tailBuffer); const tag = new TextDecoder().decode(new Uint8Array(tailBuffer, 0, 3)); if (tag === 'TAG') { const title = new TextDecoder() @@ -288,33 +287,6 @@ function readID3Text(view) { return decoder.decode(buffer).replace(/\0/g, ''); } -function readID3Picture(view) { - let offset = 1; - const encoding = view.getUint8(0); - - let mimeEnd = offset; - while (view.getUint8(mimeEnd) !== 0) mimeEnd++; - const mime = new TextDecoder('iso-8859-1').decode( - view.buffer.slice(view.byteOffset + offset, view.byteOffset + mimeEnd) - ); - offset = mimeEnd + 1; - - const picType = view.getUint8(offset); - offset++; - - let descEnd = offset; - while ( - view.getUint8(descEnd) !== 0 || - (encoding === 1 || encoding === 2 ? view.getUint8(descEnd + 1) !== 0 : false) - ) - descEnd++; - offset = descEnd + (encoding === 1 || encoding === 2 ? 2 : 1); - - const imgData = view.buffer.slice(view.byteOffset + offset, view.byteOffset + view.byteLength); - const blob = new Blob([imgData], { type: mime }); - return URL.createObjectURL(blob); -} - /** * Adds Vorbis comment metadata to FLAC files */ @@ -438,7 +410,7 @@ function createVorbisCommentBlock(track) { if (!isNaN(year)) { comments.push(['DATE', String(year)]); } - } catch (error) { + } catch { // Invalid date, skip } } @@ -787,7 +759,7 @@ function createMp4MetadataAtoms(track) { if (!isNaN(year)) { tags['©day'] = String(year); } - } catch (error) { + } catch { // Invalid date, skip } } @@ -862,12 +834,6 @@ function rebuildMp4WithMetadata(dataView, atoms, metadataAtoms) { // Write preserved children of moov for (const child of filteredMoovChildren) { - const childStart = moovAtom.offset + 8 + child.offset; // child.offset is relative to moov body start in our parseMp4Atoms helper usage? - // Wait, parseMp4Atoms returns absolute offsets usually? - // Let's verify parseMp4Atoms usage. - // When we passed a slice DataView, the offsets returned by parseMp4Atoms - // are relative to the start of that DataView (which is moov body start). - const absoluteChildStart = moovAtom.offset + 8 + child.offset; newFile.set(originalArray.subarray(absoluteChildStart, absoluteChildStart + child.size), offset); offset += child.size; diff --git a/js/player.js b/js/player.js index 7c001a0..ba8415b 100644 --- a/js/player.js +++ b/js/player.js @@ -618,7 +618,7 @@ export class Player { position: Math.min(this.audio.currentTime, duration), }); } catch (error) { - console.debug('Failed to update Media Session position:', error); + console.log('Failed to update Media Session position:', error); } } diff --git a/js/settings.js b/js/settings.js index 5f5c5d9..4650278 100644 --- a/js/settings.js +++ b/js/settings.js @@ -58,7 +58,7 @@ export function initializeSettings(scrobbler, player, api, ui) { authButtonsContainer.style.display = 'flex'; emailInput.value = ''; passwordInput.value = ''; - } catch (e) { + } catch { // Error handled in authManager } }); @@ -78,7 +78,7 @@ export function initializeSettings(scrobbler, player, api, ui) { authButtonsContainer.style.display = 'flex'; emailInput.value = ''; passwordInput.value = ''; - } catch (e) { + } catch { // Error handled in authManager } }); @@ -165,7 +165,7 @@ export function initializeSettings(scrobbler, player, api, ui) { lastfmToggle.checked = true; alert(`Successfully connected to Last.fm as ${result.username}!`); } - } catch (e) { + } catch { // Still waiting } }, 2000); diff --git a/js/smooth-scrolling.js b/js/smooth-scrolling.js index 3617de3..1d5aba9 100644 --- a/js/smooth-scrolling.js +++ b/js/smooth-scrolling.js @@ -1,4 +1,5 @@ //js/smooth-scrolling.js +/* global Lenis */ import { smoothScrollingSettings } from './storage.js'; let lenis = null; diff --git a/js/storage.js b/js/storage.js index 91b8ccb..44e8325 100644 --- a/js/storage.js +++ b/js/storage.js @@ -31,7 +31,7 @@ export const apiSettings = { if (isSimpleArray) { groupedInstances.api = [...data.api]; } else { - for (const [provider, config] of Object.entries(data.api)) { + for (const [, config] of Object.entries(data.api)) { if (config.cors === false && Array.isArray(config.urls)) { groupedInstances.api.push(...config.urls); } @@ -122,7 +122,7 @@ export const apiSettings = { } return data; - } catch (e) { + } catch { return { speeds: {}, timestamp: Date.now() }; } }, @@ -141,7 +141,7 @@ export const apiSettings = { try { localStorage.setItem(this.SPEED_TEST_CACHE_KEY, JSON.stringify(currentCache)); - } catch (e) { + } catch { console.warn('[SpeedTest] Failed to cache results'); } @@ -253,7 +253,7 @@ export const recentActivityManager = { if (!parsed.playlists) parsed.playlists = []; if (!parsed.mixes) parsed.mixes = []; return parsed; - } catch (e) { + } catch { return { artists: [], albums: [], playlists: [], mixes: [] }; } }, @@ -311,7 +311,7 @@ export const themeManager = { getTheme() { try { return localStorage.getItem(this.STORAGE_KEY) || 'system'; - } catch (e) { + } catch { return 'system'; } }, @@ -343,7 +343,7 @@ export const themeManager = { try { const stored = localStorage.getItem(this.CUSTOM_THEME_KEY); return stored ? JSON.parse(stored) : null; - } catch (e) { + } catch { return null; } }, @@ -363,13 +363,10 @@ export const themeManager = { }; export const lastFMStorage = { - STORAGE_KEY: 'lastfm-enabled', - LOVE_ON_LIKE_KEY: 'lastfm-love-on-like', - isEnabled() { try { return localStorage.getItem(this.STORAGE_KEY) === 'true'; - } catch (e) { + } catch { return false; } }, @@ -381,7 +378,7 @@ export const lastFMStorage = { shouldLoveOnLike() { try { return localStorage.getItem(this.LOVE_ON_LIKE_KEY) === 'true'; - } catch (e) { + } catch { return false; } }, @@ -397,7 +394,7 @@ export const nowPlayingSettings = { getMode() { try { return localStorage.getItem(this.STORAGE_KEY) || 'cover'; - } catch (e) { + } catch { return 'cover'; } }, @@ -413,7 +410,7 @@ export const lyricsSettings = { shouldDownloadLyrics() { try { return localStorage.getItem(this.DOWNLOAD_WITH_TRACKS) === 'true'; - } catch (e) { + } catch { return false; } }, @@ -430,7 +427,7 @@ export const backgroundSettings = { try { // Default to true if not set return localStorage.getItem(this.STORAGE_KEY) !== 'false'; - } catch (e) { + } catch { return true; } }, @@ -448,7 +445,7 @@ export const trackListSettings = { const mode = localStorage.getItem(this.STORAGE_KEY) || 'dropdown'; document.documentElement.setAttribute('data-track-actions-mode', mode); return mode; - } catch (e) { + } catch { return 'dropdown'; } }, @@ -467,7 +464,7 @@ export const cardSettings = { try { const val = localStorage.getItem(this.COMPACT_ARTIST_KEY); return val === null ? true : val === 'true'; - } catch (e) { + } catch { return true; } }, @@ -479,7 +476,7 @@ export const cardSettings = { isCompactAlbum() { try { return localStorage.getItem(this.COMPACT_ALBUM_KEY) === 'true'; - } catch (e) { + } catch { return false; } }, @@ -512,7 +509,7 @@ export const downloadQualitySettings = { getQuality() { try { return localStorage.getItem(this.STORAGE_KEY) || 'LOSSLESS'; - } catch (e) { + } catch { return 'LOSSLESS'; } }, @@ -527,7 +524,7 @@ export const waveformSettings = { isEnabled() { try { return localStorage.getItem(this.STORAGE_KEY) === 'true'; - } catch (e) { + } catch { return false; } }, @@ -543,7 +540,7 @@ export const smoothScrollingSettings = { isEnabled() { try { return localStorage.getItem(this.STORAGE_KEY) === 'true'; - } catch (e) { + } catch { return false; } }, @@ -560,7 +557,7 @@ export const queueManager = { try { const data = localStorage.getItem(this.STORAGE_KEY); return data ? JSON.parse(data) : null; - } catch (e) { + } catch { return null; } }, diff --git a/js/ui-interactions.js b/js/ui-interactions.js index f578843..ff66da4 100644 --- a/js/ui-interactions.js +++ b/js/ui-interactions.js @@ -5,7 +5,6 @@ import { SVG_HEART, SVG_DOWNLOAD, formatTime, - trackDataStore, getTrackTitle, getTrackArtists, escapeHtml, @@ -158,7 +157,7 @@ export function initializeUIInteractions(player, api) { try { let addedCount = 0; for (const track of currentQueue) { - const playlist = await db.addTrackToPlaylist(playlistId, track); + await db.addTrackToPlaylist(playlistId, track); addedCount++; } @@ -302,7 +301,6 @@ export function initializeUIInteractions(player, api) { trackMixItem.style.display = hasMix ? 'block' : 'none'; } - const rect = item.getBoundingClientRect(); const menuWidth = 150; const menuHeight = 200; @@ -325,7 +323,7 @@ export function initializeUIInteractions(player, api) { } }); - item.addEventListener('dragstart', (e) => { + item.addEventListener('dragstart', () => { draggedQueueIndex = index; item.style.opacity = '0.5'; }); diff --git a/js/ui.js b/js/ui.js index 113ffda..5ca00a7 100644 --- a/js/ui.js +++ b/js/ui.js @@ -15,8 +15,9 @@ import { escapeHtml, } from './utils.js'; import { openLyricsPanel } from './lyrics.js'; -import { recentActivityManager, backgroundSettings, trackListSettings, cardSettings } from './storage.js'; +import { recentActivityManager, backgroundSettings, cardSettings } from './storage.js'; import { db } from './db.js'; +import { showNotification } from './downloads.js'; import { getVibrantColorFromImage } from './vibrant-color.js'; import { syncManager } from './accounts/pocketbase.js'; @@ -68,7 +69,7 @@ export class UIRenderer { this.vibrantColorCache.set(url, null); this.resetVibrantColor(); } - } catch (e) { + } catch { this.vibrantColorCache.set(url, null); this.resetVibrantColor(); } @@ -161,7 +162,6 @@ export class UIRenderer { } createTrackItemHTML(track, index, showCover = false, hasMultipleDiscs = false) { - const playIconSmall = SVG_PLAY; const trackImageHTML = showCover ? `Track Cover` : ''; @@ -625,7 +625,6 @@ export class UIRenderer { const title = document.getElementById('fullscreen-track-title'); const artist = document.getElementById('fullscreen-track-artist'); const nextTrackEl = document.getElementById('fullscreen-next-track'); - const lyricsContainer = document.getElementById('fullscreen-lyrics-container'); const lyricsToggleBtn = document.getElementById('toggle-fullscreen-lyrics-btn'); const coverUrl = this.api.getCoverUrl(track.album?.cover, '1280'); diff --git a/js/utils.js b/js/utils.js index 33fdc2f..34192cc 100644 --- a/js/utils.js +++ b/js/utils.js @@ -288,7 +288,7 @@ export async function getCoverBlob(api, coverId) { return blob; } } - } catch (e) { + } catch { // Network error (CORS rejection not handled by SW), try proxy const url = api.getCoverUrl(coverId, '1280'); const blob = await fetchWithProxy(url); diff --git a/styles.css b/styles.css index 6b25062..6018498 100644 --- a/styles.css +++ b/styles.css @@ -1,3 +1,4 @@ +/* stylelint-disable no-descending-specificity */ :root { color-scheme: light dark; @@ -14,6 +15,8 @@ --shadow-lg: 0 10px 30px rgb(0, 0, 0, 0.5); --shadow-xl: 0 20px 60px rgb(0, 0, 0, 0.8); --cover-filter: blur(50px) brightness(0.4); + --player-bar-height-desktop: 90px; + --player-bar-height-mobile: 130px; } :root[data-theme='monochrome'] { @@ -408,6 +411,17 @@ kbd { cursor: pointer; } +.search-bar svg { + position: absolute; + left: 0.75rem; + top: 50%; + transform: translateY(-50%); + color: var(--muted-foreground); + width: 20px; + height: 20px; + pointer-events: none; +} + .sidebar-nav .nav-item a:hover { background-color: var(--secondary); color: var(--foreground); @@ -489,19 +503,11 @@ kbd { .search-bar { position: relative; - width: 100%; - max-width: 400px; -} - -.search-bar svg { - position: absolute; - left: 0.75rem; - top: 50%; - transform: translateY(-50%); - color: var(--muted-foreground); - width: 20px; - height: 20px; - pointer-events: none; + width: 80%; + max-width: 100%; + margin-bottom: var(--spacing-xl); + display: flex; + align-items: center; } .search-bar input { @@ -534,10 +540,10 @@ body.has-page-background .track-item:hover { .page.active { display: block; - animation: fadeIn 0.25s cubic-bezier(0.4, 0, 0.2, 1); + animation: fade-in 0.25s cubic-bezier(0.4, 0, 0.2, 1); } -@keyframes fadeIn { +@keyframes fade-in { from { opacity: 0; transform: translateY(4px); @@ -549,7 +555,7 @@ body.has-page-background .track-item:hover { } } -@keyframes scaleIn { +@keyframes scale-in { from { opacity: 0; transform: scale(0.95); @@ -561,7 +567,7 @@ body.has-page-background .track-item:hover { } } -@keyframes slideIn { +@keyframes slide-in { from { opacity: 0; transform: translateX(100%); @@ -573,7 +579,7 @@ body.has-page-background .track-item:hover { } } -@keyframes slideOut { +@keyframes slide-out { from { opacity: 1; transform: translateX(0); @@ -1000,9 +1006,11 @@ body.has-page-background .track-item:hover { display: flex; } -.track-item:hover .track-menu-btn { - opacity: 1; -} +.track-item:hover .track-menu-btn { + opacity: 1; + padding: 0.5rem; + margin: 0; + } .track-menu-btn:hover { background-color: rgb(var(--highlight-rgb), 0.2); @@ -1087,7 +1095,7 @@ body.has-page-background .track-item:hover { align-items: center; gap: 1rem; flex-wrap: wrap; - word-break: break-word; + overflow-wrap: break-word; } .detail-header-info .title.long-title { @@ -1547,9 +1555,10 @@ input:checked + .slider::before { .volume-controls { display: flex; - justify-content: flex-end; - align-items: center; - gap: 0.75rem; + justify-content: center !important; + align-items: flex-end !important; + gap: 0.5rem !important; + flex-direction: column !important; } .volume-controls button { @@ -1721,15 +1730,13 @@ input:checked + .slider::before { .fullscreen-cover-content { display: flex; - flex-direction: column; + flex-direction: row; align-items: center; justify-content: center; width: 100%; height: 100%; - - /* Remove fixed padding to allow flex centering to work within the overlay's padded box */ - padding: 1rem; position: relative; + padding: 1rem; } #close-fullscreen-cover-btn { @@ -1781,7 +1788,7 @@ input:checked + .slider::before { font-weight: 700; margin-bottom: 0.5rem; color: var(--foreground); - word-break: break-word; + overflow-wrap: break-word; } #fullscreen-track-artist { @@ -1825,7 +1832,7 @@ input:checked + .slider::before { display: flex; flex-direction: column; box-shadow: var(--shadow-xl); - animation: scaleIn 0.2s cubic-bezier(0.4, 0, 0.2, 1); + animation: scale-in 0.2s cubic-bezier(0.4, 0, 0.2, 1); } #queue-modal-header { @@ -2317,7 +2324,7 @@ input:checked + .slider::before { border-radius: var(--radius); padding: 1rem; box-shadow: var(--shadow-lg); - animation: slideIn 0.3s ease; + animation: slide-in 0.3s ease; min-width: 300px; } @@ -2419,7 +2426,7 @@ input:checked + .slider::before { align-items: center; gap: 1rem; max-width: 350px; - animation: slideIn 0.3s ease; + animation: slide-in 0.3s ease; } .offline-notification svg { @@ -2511,11 +2518,6 @@ input:checked + .slider::before { } /* Side Panels (Lyrics & Queue) */ -:root { - --player-bar-height-desktop: 90px; - --player-bar-height-mobile: 130px; -} - .side-panel { position: fixed; right: 0; @@ -2756,12 +2758,6 @@ input:checked + .slider::before { } /* Updated Volume Controls Layout */ -.volume-controls { - flex-direction: column !important; - align-items: flex-end !important; - gap: 0.5rem !important; - justify-content: center !important; -} .player-actions-row { display: flex; @@ -2864,21 +2860,6 @@ img[src=''] { padding: 1rem 0; } -.search-bar { - display: flex; - align-items: center; - width: 80%; - max-width: 100%; -} - -.fullscreen-cover-content { - display: flex; - flex-direction: row; - width: 100%; - height: 100%; - position: relative; -} - .fullscreen-main-view { flex: 1; display: flex; @@ -2945,7 +2926,7 @@ img[src=''] { max-width: 400px; width: 90%; box-shadow: var(--shadow-xl); - animation: scaleIn 0.2s ease; + animation: scale-in 0.2s ease; max-height: 90vh; overflow-y: auto; } @@ -3013,7 +2994,7 @@ img[src=''] { text-align: center; } -@keyframes scaleIn { +@keyframes scale-in { from { transform: scale(0.95); opacity: 0; @@ -3027,13 +3008,13 @@ img[src=''] { #playlist-modal { opacity: 1; - animation-name: fadeInOpacity; + animation-name: fade-in-opacity; animation-iteration-count: 1; animation-timing-function: ease-in; animation-duration: 0.1s; } -@keyframes fadeInOpacity { +@keyframes fade-in-opacity { 0% { opacity: 0; } @@ -3056,7 +3037,7 @@ img[src=''] { z-index: 10001; max-width: 400px; min-width: 350px; - animation: slideIn 0.3s ease; + animation: slide-in 0.3s ease; } .csv-import-progress .progress-header { @@ -3349,6 +3330,9 @@ img[src=''] { @media (max-width: 768px) { .player-controls .progress-container { order: -1; + max-width: none; + font-size: 0.75rem; + gap: 0.5rem; } #fullscreen-cover-overlay { @@ -3556,12 +3540,6 @@ img[src=''] { height: 32px; } - .player-controls .progress-container { - max-width: none; - font-size: 0.75rem; - gap: 0.5rem; - } - .desktop-only { display: none !important; } @@ -3632,11 +3610,6 @@ img[src=''] { white-space: nowrap; } - .track-menu-btn { - padding: 0.5rem; - margin: 0; - } - .track-menu-btn svg { width: 18px; height: 18px; @@ -3776,10 +3749,6 @@ img[src=''] { .mobile-only { display: flex !important; } - - .desktop-only { - display: none !important; - } } @media (max-width: 480px) { @@ -3922,6 +3891,7 @@ img[src=''] { } } +/* stylelint-disable-next-line media-feature-name-value-no-unknown */ @media (display-mode: window-controls-overlay) { .app-container { margin-top: env(titlebar-area-height, 0);