From 4ede3b26642e82e3cf29dcd8d4ee6da3f5e2d829 Mon Sep 17 00:00:00 2001 From: SamidyFR <168582143+SamidyFR@users.noreply.github.com> Date: Sat, 24 Jan 2026 12:13:44 +0000 Subject: [PATCH] style: auto-fix linting issues --- INSTANCES.md | 2 +- functions/album/[id].js | 12 ++- functions/artist/[id].js | 8 +- functions/playlist/[id].js | 8 +- functions/track/[id].js | 14 ++- functions/userplaylist/[id].js | 30 ++++-- js/api.js | 6 +- js/app.js | 39 +++++--- js/dash-downloader.js | 2 +- js/db.js | 2 +- js/downloads.js | 11 ++- js/events.js | 7 +- js/lyrics.js | 142 ++++++++++++++++----------- js/metadata.js | 10 +- js/player.js | 5 +- js/router.js | 3 +- js/storage.js | 2 +- js/tracker.js | 174 ++++++++++++++++++++------------- js/ui-interactions.js | 2 +- js/ui.js | 126 ++++++++++++++---------- js/utils.js | 20 ++-- js/visualizer.js | 40 +++----- styles.css | 21 ++-- 23 files changed, 413 insertions(+), 273 deletions(-) diff --git a/INSTANCES.md b/INSTANCES.md index decbb68..6a3739f 100644 --- a/INSTANCES.md +++ b/INSTANCES.md @@ -22,4 +22,4 @@ UI: | QQDL | https://tidal.qqdl.site/ | | Arjix | https://music.arjix.dev/ | | Spofree | https://spo.free.nf | -| Mappl | https://mappl.tv/music | +| Mappl | https://mappl.tv/music | diff --git a/functions/album/[id].js b/functions/album/[id].js index 7f64011..3d7d1db 100644 --- a/functions/album/[id].js +++ b/functions/album/[id].js @@ -67,14 +67,16 @@ class ServerAPI { export async function onRequest(context) { const { request, params, env } = context; const userAgent = request.headers.get('User-Agent') || ''; - const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(userAgent); + const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test( + userAgent + ); const albumId = params.id; if (isBot && albumId) { try { const api = new ServerAPI(); const data = await api.getAlbumMetadata(albumId); - const album = data.data || data.album || data; + const album = data.data || data.album || data; const tracks = album.items || data.tracks || []; if (album && (album.title || album.name)) { @@ -82,9 +84,11 @@ export async function onRequest(context) { const artist = album.artist?.name || 'Unknown Artist'; const year = album.releaseDate ? new Date(album.releaseDate).getFullYear() : ''; const trackCount = album.numberOfTracks || tracks.length; - + const description = `Album by ${artist} • ${year} • ${trackCount} Tracks\nListen on Monochrome`; - const imageUrl = album.cover ? api.getCoverUrl(album.cover, '1280') : 'https://monochrome.samidy.com/assets/appicon.png'; + const imageUrl = album.cover + ? api.getCoverUrl(album.cover, '1280') + : 'https://monochrome.samidy.com/assets/appicon.png'; const pageUrl = new URL(request.url).href; const metaHtml = ` diff --git a/functions/artist/[id].js b/functions/artist/[id].js index 2d7e558..50d1fe8 100644 --- a/functions/artist/[id].js +++ b/functions/artist/[id].js @@ -67,7 +67,9 @@ class ServerAPI { export async function onRequest(context) { const { request, params, env } = context; const userAgent = request.headers.get('User-Agent') || ''; - const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(userAgent); + const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test( + userAgent + ); const artistId = params.id; if (isBot && artistId) { @@ -79,7 +81,9 @@ export async function onRequest(context) { if (artist && (artist.name || artist.title)) { const name = artist.name || artist.title; const description = `Listen to ${name} on Monochrome`; - const imageUrl = artist.picture ? api.getArtistPictureUrl(artist.picture, '750') : 'https://monochrome.samidy.com/assets/appicon.png'; + const imageUrl = artist.picture + ? api.getArtistPictureUrl(artist.picture, '750') + : 'https://monochrome.samidy.com/assets/appicon.png'; const pageUrl = new URL(request.url).href; const metaHtml = ` diff --git a/functions/playlist/[id].js b/functions/playlist/[id].js index 8410923..858b8a2 100644 --- a/functions/playlist/[id].js +++ b/functions/playlist/[id].js @@ -68,7 +68,9 @@ class ServerAPI { export async function onRequest(context) { const { request, params, env } = context; const userAgent = request.headers.get('User-Agent') || ''; - const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(userAgent); + const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test( + userAgent + ); const playlistId = params.id; if (isBot && playlistId) { @@ -82,7 +84,9 @@ export async function onRequest(context) { const trackCount = playlist.numberOfTracks; const description = `Playlist • ${trackCount} Tracks\nListen on Monochrome`; const imageId = playlist.squareImage || playlist.image; - const imageUrl = imageId ? api.getCoverUrl(imageId, '1080') : 'https://monochrome.samidy.com/assets/appicon.png'; + const imageUrl = imageId + ? api.getCoverUrl(imageId, '1080') + : 'https://monochrome.samidy.com/assets/appicon.png'; const pageUrl = new URL(request.url).href; const metaHtml = ` diff --git a/functions/track/[id].js b/functions/track/[id].js index 632bf00..7b3240a 100644 --- a/functions/track/[id].js +++ b/functions/track/[id].js @@ -64,7 +64,7 @@ class ServerAPI { const json = await response.json(); const data = json.data || json; const items = Array.isArray(data) ? data : [data]; - const found = items.find(i => (i.id == id) || (i.item && i.item.id == id)); + const found = items.find((i) => i.id == id || (i.item && i.item.id == id)); if (found) { return found.item || found; } @@ -87,7 +87,9 @@ class ServerAPI { export async function onRequest(context) { const { request, params, env } = context; const userAgent = request.headers.get('User-Agent') || ''; - const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(userAgent); + const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test( + userAgent + ); const trackId = params.id; if (isBot && trackId) { @@ -112,12 +114,14 @@ export async function onRequest(context) { } } // this prob wont work im js winging it - const audioMeta = audioUrl ? ` + const audioMeta = audioUrl + ? ` - ` : ''; + ` + : ''; const metaHtml = ` @@ -164,4 +168,4 @@ export async function onRequest(context) { const url = new URL(request.url); url.pathname = '/'; return env.ASSETS.fetch(new Request(url, request)); -} \ No newline at end of file +} diff --git a/functions/userplaylist/[id].js b/functions/userplaylist/[id].js index 6b5461b..51a5fa1 100644 --- a/functions/userplaylist/[id].js +++ b/functions/userplaylist/[id].js @@ -1,24 +1,25 @@ // functions/userplaylist/[id].js - // note that, since this NEEDS a playlist to yknow, be public, this only works for PUBLIC playlists (and you will need an account) export async function onRequest(context) { const { request, params, env } = context; const userAgent = request.headers.get('User-Agent') || ''; - const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test(userAgent); + const isBot = /discordbot|twitterbot|facebookexternalhit|bingbot|googlebot|slurp|whatsapp|pinterest|slackbot/i.test( + userAgent + ); const playlistId = params.id; if (isBot && playlistId) { try { let pbUrl = `https://monodb.samidy.com/api/collections/user_playlists/records/${playlistId}`; let response = await fetch(pbUrl); - + if (!response.ok) { - pbUrl = `https://monodb.samidy.com/api/collections/public_playlists/records?filter=(uuid='${playlistId}')`; - response = await fetch(pbUrl); + pbUrl = `https://monodb.samidy.com/api/collections/public_playlists/records?filter=(uuid='${playlistId}')`; + response = await fetch(pbUrl); } - + if (response.ok) { let playlist = await response.json(); if (playlist.items && Array.isArray(playlist.items) && playlist.items.length > 0) { @@ -30,14 +31,18 @@ export async function onRequest(context) { const title = playlist.name || playlist.title || 'User Playlist'; let tracks = []; try { - tracks = Array.isArray(playlist.tracks) ? playlist.tracks : (playlist.tracks ? JSON.parse(playlist.tracks) : []); + tracks = Array.isArray(playlist.tracks) + ? playlist.tracks + : playlist.tracks + ? JSON.parse(playlist.tracks) + : []; } catch (e) { console.error('Failed to parse tracks JSON', e); } - const trackCount = tracks.length; + const trackCount = tracks.length; const description = `User Playlist • ${trackCount} Tracks\nListen on Monochrome`; - + let imageUrl = 'https://monochrome.samidy.com/assets/appicon.png'; if (playlist.cover) { if (playlist.cover.startsWith('http')) { @@ -45,7 +50,12 @@ export async function onRequest(context) { } else { imageUrl = `https://monodb.samidy.com/api/files/${playlist.collectionId}/${playlist.id}/${playlist.cover}`; } - } else if (tracks.length > 0 && typeof tracks[0] === 'object' && tracks[0].album && tracks[0].album.cover) { + } else if ( + tracks.length > 0 && + typeof tracks[0] === 'object' && + tracks[0].album && + tracks[0].album.cover + ) { const cover = tracks[0].album.cover; imageUrl = `https://resources.tidal.com/images/${cover.replace(/-/g, '/')}/1280x1280.jpg`; } diff --git a/js/api.js b/js/api.js index 8960c4b..8b6d7d0 100644 --- a/js/api.js +++ b/js/api.js @@ -843,11 +843,11 @@ export class LosslessAPI { const response = await this.fetchWithRetry(`/info/?id=${id}`, { type: 'api' }); const json = await response.json(); const data = json.data || json; - + let track; const items = Array.isArray(data) ? data : [data]; - const found = items.find(i => (i.id == id) || (i.item && i.item.id == id)); - + const found = items.find((i) => i.id == id || (i.item && i.item.id == id)); + if (found) { track = this.prepareTrack(found.item || found); await this.cache.set('track', cacheKey, track); diff --git a/js/app.js b/js/app.js index 7a48393..68a42d9 100644 --- a/js/app.js +++ b/js/app.js @@ -15,7 +15,13 @@ import { createRouter, updateTabTitle, navigate } from './router.js'; import { initializeSettings } from './settings.js'; import { initializePlayerEvents, initializeTrackInteractions, handleTrackAction } from './events.js'; import { initializeUIInteractions } from './ui-interactions.js'; -import { downloadAlbumAsZip, downloadDiscography, downloadPlaylistAsZip, downloadLikedTracks, showNotification } from './downloads.js'; +import { + downloadAlbumAsZip, + downloadDiscography, + downloadPlaylistAsZip, + downloadLikedTracks, + showNotification, +} from './downloads.js'; import { debounce, SVG_PLAY } from './utils.js'; import { sidePanelManager } from './side-panel.js'; import { db } from './db.js'; @@ -192,12 +198,13 @@ document.addEventListener('DOMContentLoaded', async () => { // i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only const ua = navigator.userAgent.toLowerCase(); const isIOS = /iphone|ipad|ipod/.test(ua) || (ua.includes('mac') && navigator.maxTouchPoints > 1); - const isSafari = ua.includes('safari') && !ua.includes('chrome') && !ua.includes('crios') && !ua.includes('android'); + const isSafari = + ua.includes('safari') && !ua.includes('chrome') && !ua.includes('crios') && !ua.includes('android'); if (isIOS || isSafari) { const qualitySelect = document.getElementById('streaming-quality-setting'); const downloadSelect = document.getElementById('download-quality-setting'); - + const removeHiRes = (select) => { if (!select) return; const option = select.querySelector('option[value="HI_RES_LOSSLESS"]'); @@ -228,7 +235,7 @@ document.addEventListener('DOMContentLoaded', async () => { try { const playlist = await db.getPlaylist(id); const imgElement = document.getElementById('playlist-detail-image'); - + if (!imgElement) return; let container = imgElement.parentElement; @@ -239,7 +246,7 @@ document.addEventListener('DOMContentLoaded', async () => { container.className = 'detail-header-cover-container'; imgElement.parentNode.insertBefore(container, imgElement); container.appendChild(imgElement); - + collageElement = document.createElement('div'); collageElement.id = 'playlist-detail-collage'; collageElement.className = 'detail-header-collage'; @@ -389,7 +396,7 @@ document.addEventListener('DOMContentLoaded', async () => { const isCollapsed = document.body.classList.contains('sidebar-collapsed'); const toggleBtn = document.getElementById('sidebar-toggle'); if (toggleBtn) { - toggleBtn.innerHTML = isCollapsed + toggleBtn.innerHTML = isCollapsed ? '' : ''; } @@ -806,10 +813,10 @@ document.addEventListener('DOMContentLoaded', async () => { e.stopPropagation(); const btn = e.target.closest('.remove-from-playlist-btn'); const playlistId = window.location.pathname.split('/')[2]; - + db.getPlaylist(playlistId).then(async (playlist) => { let trackId = null; - + // Prefer ID if available (from sorted view) if (btn.dataset.trackId) { trackId = btn.dataset.trackId; @@ -919,11 +926,15 @@ document.addEventListener('DOMContentLoaded', async () => { return; } - list.innerHTML = playlists.map(p => ` + list.innerHTML = playlists + .map( + (p) => ` - `).join(''); + ` + ) + .join(''); const closeModal = () => { modal.classList.remove('active'); @@ -1207,7 +1218,6 @@ document.addEventListener('DOMContentLoaded', async () => { updateTabTitle(player); }; - await handleRouteChange(); window.addEventListener('popstate', handleRouteChange); @@ -1215,7 +1225,12 @@ document.addEventListener('DOMContentLoaded', async () => { document.body.addEventListener('click', (e) => { const link = e.target.closest('a'); - if (link && link.origin === window.location.origin && link.target !== '_blank' && !link.hasAttribute('download')) { + if ( + link && + link.origin === window.location.origin && + link.target !== '_blank' && + !link.hasAttribute('download') + ) { e.preventDefault(); navigate(link.pathname); } diff --git a/js/dash-downloader.js b/js/dash-downloader.js index 6105b11..04a3976 100644 --- a/js/dash-downloader.js +++ b/js/dash-downloader.js @@ -77,7 +77,7 @@ export class DashDownloader { adaptationSets.sort((a, b) => { const getMaxBandwidth = (set) => { const reps = Array.from(set.querySelectorAll('Representation')); - return reps.length ? Math.max(...reps.map(r => parseInt(r.getAttribute('bandwidth') || '0', 10))) : 0; + return reps.length ? Math.max(...reps.map((r) => parseInt(r.getAttribute('bandwidth') || '0', 10))) : 0; }; return getMaxBandwidth(b) - getMaxBandwidth(a); }); diff --git a/js/db.js b/js/db.js index 12c1d91..6d6cd38 100644 --- a/js/db.js +++ b/js/db.js @@ -545,7 +545,7 @@ export class MusicDatabase { cover: cover, playlists: [], createdAt: Date.now(), - updatedAt: Date.now() + updatedAt: Date.now(), }; await this.performTransaction('user_folders', 'readwrite', (store) => store.put(folder)); return folder; diff --git a/js/downloads.js b/js/downloads.js index 04eb442..be3731f 100644 --- a/js/downloads.js +++ b/js/downloads.js @@ -545,7 +545,16 @@ function createBulkDownloadNotification(type, name, _totalItems) { notifEl.dataset.bulkType = type; notifEl.dataset.bulkName = name; - const typeLabel = type === 'album' ? 'Album' : type === 'playlist' ? 'Playlist' : type === 'liked' ? 'Liked Tracks' : type === 'queue' ? 'Queue' : 'Discography'; + const typeLabel = + type === 'album' + ? 'Album' + : type === 'playlist' + ? 'Playlist' + : type === 'liked' + ? 'Liked Tracks' + : type === 'queue' + ? 'Queue' + : 'Discography'; notifEl.innerHTML = `
diff --git a/js/events.js b/js/events.js index 8dc489a..4834859 100644 --- a/js/events.js +++ b/js/events.js @@ -170,7 +170,10 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) { const progressBar = document.getElementById('progress-bar'); const playerControls = document.querySelector('.player-controls'); - const isTracker = player.currentTrack && (player.currentTrack.isTracker || (player.currentTrack.id && String(player.currentTrack.id).startsWith('tracker-'))); + const isTracker = + player.currentTrack && + (player.currentTrack.isTracker || + (player.currentTrack.id && String(player.currentTrack.id).startsWith('tracker-'))); if (!waveformSettings.isEnabled() || !player.currentTrack || isTracker) { if (progressBar) { @@ -624,7 +627,7 @@ export async function handleTrackAction( if (nowPlayingLikeBtn && type === 'track' && player?.currentTrack?.id === item.id) { elementsToUpdate.push(nowPlayingLikeBtn); } - + const fsLikeBtn = document.getElementById('fs-like-btn'); if (fsLikeBtn && type === 'track' && player?.currentTrack?.id === item.id) { elementsToUpdate.push(fsLikeBtn); diff --git a/js/lyrics.js b/js/lyrics.js index a3067f5..53ed59b 100644 --- a/js/lyrics.js +++ b/js/lyrics.js @@ -12,28 +12,28 @@ class GeniusManager { } getToken() { - return "QmS9OvsS-7ifRBKx_ochIPQU7oejIS9Eo_z5iWHmCPyhwLVQID3pYTHJmJTa6z8z"; // idgaf anymore im js hardcoding this lmaooo + return 'QmS9OvsS-7ifRBKx_ochIPQU7oejIS9Eo_z5iWHmCPyhwLVQID3pYTHJmJTa6z8z'; // idgaf anymore im js hardcoding this lmaooo } async searchTrack(title, artist) { const cleanTitle = title.split('(')[0].split('-')[0].trim(); const query = encodeURIComponent(`${cleanTitle} ${artist}`); - + const url = `https://api.genius.com/search?q=${query}`; const token = this.getToken(); const response = await fetch(`https://corsproxy.io/?${encodeURIComponent(url)}`, { - headers: { 'Authorization': `Bearer ${token}` } + headers: { Authorization: `Bearer ${token}` }, }); if (!response.ok) throw new Error('Failed to search Genius'); - + const data = await response.json(); if (data.response.hits.length === 0) return null; - const normalize = str => str.toLowerCase().replace(/[^\p{L}\p{N}]/gu, ''); + const normalize = (str) => str.toLowerCase().replace(/[^\p{L}\p{N}]/gu, ''); const targetArtist = normalize(artist); - - const hit = data.response.hits.find(h => { + + const hit = data.response.hits.find((h) => { const hitArtist = normalize(h.result.primary_artist.name); return hitArtist.includes(targetArtist) || targetArtist.includes(hitArtist); }); @@ -45,7 +45,7 @@ class GeniusManager { const token = this.getToken(); const url = `https://api.genius.com/referents?song_id=${songId}&text_format=plain&per_page=50`; const response = await fetch(`https://corsproxy.io/?${encodeURIComponent(url)}`, { - headers: { 'Authorization': `Bearer ${token}` } + headers: { Authorization: `Bearer ${token}` }, }); if (!response.ok) throw new Error('Failed to fetch annotations'); @@ -61,7 +61,7 @@ class GeniusManager { this.loading = true; const artist = Array.isArray(track.artists) ? track.artists[0].name : track.artist.name; const song = await this.searchTrack(track.title, artist); - + if (!song) { this.loading = false; return null; @@ -69,7 +69,7 @@ class GeniusManager { const referents = await this.getReferents(song.id); const result = { song, referents }; - + this.cache.set(track.id, result); this.loading = false; return result; @@ -82,25 +82,32 @@ class GeniusManager { findAnnotations(lineText, referents) { if (!referents || !lineText) return []; - - const normalize = str => str.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, '').replace(/\s+/g, ' ').trim(); + + const normalize = (str) => + str + .toLowerCase() + .replace(/[^\p{L}\p{N}\s]/gu, '') + .replace(/\s+/g, ' ') + .trim(); const normLine = normalize(lineText); - const getWordSet = (str) => new Set(str.split(' ').filter(w => w.length > 0)); + const getWordSet = (str) => new Set(str.split(' ').filter((w) => w.length > 0)); const lineWords = getWordSet(normLine); - return referents.filter(ref => { + return referents.filter((ref) => { const normFragment = normalize(ref.fragment); - + if (normLine.includes(normFragment) || normFragment.includes(normLine)) return true; const fragmentWords = getWordSet(normFragment); if (fragmentWords.size === 0 || lineWords.size === 0) return false; let matchCount = 0; - fragmentWords.forEach(w => { if (lineWords.has(w)) matchCount++; }); + fragmentWords.forEach((w) => { + if (lineWords.has(w)) matchCount++; + }); - return (matchCount / Math.min(fragmentWords.size, lineWords.size)) > 0.6; + return matchCount / Math.min(fragmentWords.size, lineWords.size) > 0.6; }); } } @@ -454,19 +461,20 @@ export class LyricsManager { this.romajiObserver = new MutationObserver((mutations) => { // Check if any relevant mutation occurred const hasRelevantChange = mutations.some((mutation) => { - if (mutation.type === 'childList') { let relevant = false; if (mutation.addedNodes.length > 0) { for (const node of mutation.addedNodes) { - if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('genius-indicator')) continue; + if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('genius-indicator')) + continue; relevant = true; break; } } if (!relevant && mutation.removedNodes.length > 0) { for (const node of mutation.removedNodes) { - if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('genius-indicator')) continue; + if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains('genius-indicator')) + continue; relevant = true; break; } @@ -628,34 +636,38 @@ export class LyricsManager { if (lineElements.length === 0) return; - - lineElements.forEach(el => { + lineElements.forEach((el) => { el.classList.remove('genius-annotated', 'genius-multi-start', 'genius-multi-end', 'genius-multi-mid'); delete el.__geniusAnnotations; }); + const normalize = (str) => + str + .toLowerCase() + .replace(/[^\p{L}\p{N}\s]/gu, '') + .replace(/\s+/g, ' ') + .trim(); - const normalize = str => str.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, '').replace(/\s+/g, ' ').trim(); - - referents.forEach(ref => { + referents.forEach((ref) => { const fragment = normalize(ref.fragment); if (!fragment) return; - for (let i = 0; i < lineElements.length; i++) { - let combinedText = ""; + let combinedText = ''; let currentLines = []; for (let j = i; j < lineElements.length; j++) { const line = lineElements[j]; const lineClone = line.cloneNode(true); - lineClone.querySelectorAll('.time, .timestamp, [class*="time"], .genius-indicator').forEach(n => n.remove()); - const text = normalize(lineClone.textContent || ""); + lineClone + .querySelectorAll('.time, .timestamp, [class*="time"], .genius-indicator') + .forEach((n) => n.remove()); + const text = normalize(lineClone.textContent || ''); if (!text) continue; - if (currentLines.length > 0) combinedText += " "; + if (currentLines.length > 0) combinedText += ' '; combinedText += text; currentLines.push(line); @@ -664,7 +676,7 @@ export class LyricsManager { el.classList.add('genius-annotated'); if (!el.__geniusAnnotations) el.__geniusAnnotations = []; - if (!el.__geniusAnnotations.some(a => a.id === ref.id)) { + if (!el.__geniusAnnotations.some((a) => a.id === ref.id)) { el.__geniusAnnotations.push(ref); } @@ -683,10 +695,9 @@ export class LyricsManager { el.appendChild(smiley); } }); - break; + break; } - if (combinedText.length > fragment.length + 50) break; } } @@ -754,19 +765,22 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f geniusBtn.addEventListener('click', async () => { manager.isGeniusMode = !manager.isGeniusMode; const enabled = manager.isGeniusMode; - + geniusBtn.classList.toggle('active-genius', enabled); geniusBtn.style.color = enabled ? '#ffff64' : ''; geniusBtn.innerHTML = enabled ? SVG_GENIUS_ACTIVE : SVG_GENIUS_INACTIVE; if (enabled) { - try { geniusBtn.style.opacity = '0.5'; await manager.geniusManager.getDataForTrack(track); manager.currentGeniusData = manager.geniusManager.cache.get(track.id); const amLyrics = sidePanelManager.panel.querySelector('am-lyrics'); - if (amLyrics) manager.applyGeniusAnnotations(amLyrics, manager.geniusManager.cache.get(track.id)?.referents); + if (amLyrics) + manager.applyGeniusAnnotations( + amLyrics, + manager.geniusManager.cache.get(track.id)?.referents + ); } catch (e) { alert(e.message); manager.isGeniusMode = false; @@ -776,13 +790,17 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f geniusBtn.style.opacity = '1'; } } else { - const amLyrics = sidePanelManager.panel.querySelector('am-lyrics'); if (amLyrics) { const root = amLyrics.shadowRoot || amLyrics; const lineElements = Array.from(root.querySelectorAll('.genius-annotated')); - lineElements.forEach(el => { - el.classList.remove('genius-annotated', 'genius-multi-start', 'genius-multi-end', 'genius-multi-mid'); + lineElements.forEach((el) => { + el.classList.remove( + 'genius-annotated', + 'genius-multi-start', + 'genius-multi-end', + 'genius-multi-mid' + ); delete el.__geniusAnnotations; }); } @@ -848,18 +866,22 @@ async function renderLyricsComponent(container, track, audioPlayer, lyricsManage await lyricsManager.loadKuroshiro(); } - - lyricsManager.fetchLyrics(track.id, track).then(async () => { - if (lyricsManager.isGeniusMode) { - try { - const data = await lyricsManager.geniusManager.getDataForTrack(track); - if (data) { - lyricsManager.currentGeniusData = data; - lyricsManager.applyGeniusAnnotations(amLyrics, data.referents); + lyricsManager + .fetchLyrics(track.id, track) + .then(async () => { + if (lyricsManager.isGeniusMode) { + try { + const data = await lyricsManager.geniusManager.getDataForTrack(track); + if (data) { + lyricsManager.currentGeniusData = data; + lyricsManager.applyGeniusAnnotations(amLyrics, data.referents); + } + } catch (e) { + console.warn('Genius auto-load failed', e); } - } catch (e) { console.warn('Genius auto-load failed', e); } - } - }).catch(e => console.warn('Background lyrics fetch failed', e)); + } + }) + .catch((e) => console.warn('Background lyrics fetch failed', e)); // Wait for lyrics to appear, then do an immediate conversion const waitForLyrics = () => { @@ -955,20 +977,21 @@ function setupSync(track, audioPlayer, amLyrics, lyricsManager) { const onLineClick = (e) => { if (e.detail && e.detail.timestamp !== undefined) { - const manager = lyricsManager || sidePanelManager.panel.lyricsManager; if (manager && manager.isGeniusMode) { const timestampSeconds = e.detail.timestamp / 1000; - const lyricsData = manager.lyricsCache.get(track.id); if (lyricsData && lyricsData.subtitles) { const parsed = manager.parseSyncedLyrics(lyricsData.subtitles); - const line = parsed.find(l => Math.abs(l.time - timestampSeconds) < 1.0); - + const line = parsed.find((l) => Math.abs(l.time - timestampSeconds) < 1.0); + if (line && line.text && manager.currentGeniusData) { - const annotations = manager.geniusManager.findAnnotations(line.text, manager.currentGeniusData.referents); + const annotations = manager.geniusManager.findAnnotations( + line.text, + manager.currentGeniusData.referents + ); showGeniusAnnotations(annotations, line.text); } } @@ -1003,13 +1026,12 @@ function setupSync(track, audioPlayer, amLyrics, lyricsManager) { } function showGeniusAnnotations(annotations, lineText) { - const existing = document.querySelector('.genius-annotation-modal'); if (existing) existing.remove(); const modal = document.createElement('div'); modal.className = 'genius-annotation-modal'; - + let contentHtml = `
@@ -1026,7 +1048,7 @@ function showGeniusAnnotations(annotations, lineText) {
`; } else { - annotations.forEach(ann => { + annotations.forEach((ann) => { const body = ann.annotations[0].body.plain; contentHtml += `
@@ -1043,7 +1065,9 @@ function showGeniusAnnotations(annotations, lineText) { modal.querySelector('.close-genius').addEventListener('click', () => modal.remove()); - modal.addEventListener('click', (e) => { if(e.target === modal) modal.remove(); }); + modal.addEventListener('click', (e) => { + if (e.target === modal) modal.remove(); + }); } export async function renderLyricsInFullscreen(track, audioPlayer, lyricsManager, container) { diff --git a/js/metadata.js b/js/metadata.js index f1045b1..ca6b8dd 100644 --- a/js/metadata.js +++ b/js/metadata.js @@ -22,22 +22,22 @@ export async function addMetadataToAudio(audioBlob, track, api, quality) { const buffer = await audioBlob.slice(0, 4).arrayBuffer(); const view = new DataView(buffer); - const isFlac = view.byteLength >= 4 && + const isFlac = + view.byteLength >= 4 && view.getUint8(0) === 0x66 && // f view.getUint8(1) === 0x4c && // L view.getUint8(2) === 0x61 && // a - view.getUint8(3) === 0x43; // C + view.getUint8(3) === 0x43; // C - const mime = audioBlob.type; + const mime = audioBlob.type; if (mime === 'audio/flac') { return await addFlacMetadata(audioBlob, track, api); } - + if (mime === 'audio/mp4') { return await addM4aMetadata(audioBlob, track, api); } - } /** diff --git a/js/player.js b/js/player.js index 67d1cef..c7c07bc 100644 --- a/js/player.js +++ b/js/player.js @@ -319,7 +319,10 @@ export class Player { } streamUrl = track.audioUrl; - if ((!streamUrl || (typeof streamUrl === 'string' && streamUrl.startsWith('blob:'))) && track.remoteUrl) { + if ( + (!streamUrl || (typeof streamUrl === 'string' && streamUrl.startsWith('blob:'))) && + track.remoteUrl + ) { streamUrl = track.remoteUrl; } diff --git a/js/router.js b/js/router.js index fa4376d..f5a2f8a 100644 --- a/js/router.js +++ b/js/router.js @@ -11,7 +11,6 @@ export function navigate(path) { export function createRouter(ui) { const router = async () => { - if (window.location.hash && window.location.hash.length > 1) { const hash = window.location.hash.substring(1); if (hash.includes('/')) { @@ -21,7 +20,7 @@ export function createRouter(ui) { } let path = window.location.pathname; - + if (path.startsWith('/')) path = path.substring(1); if (path.endsWith('/')) path = path.substring(0, path.length - 1); if (path === '' || path === 'index.html') path = 'home'; diff --git a/js/storage.js b/js/storage.js index cbda74d..b9924fa 100644 --- a/js/storage.js +++ b/js/storage.js @@ -626,7 +626,7 @@ export const visualizerSettings = { getSensitivity() { try { const val = localStorage.getItem(this.SENSITIVITY_KEY); - if (val === null) return 1.0; + if (val === null) return 1.0; return parseFloat(val); } catch { return 1.0; diff --git a/js/tracker.js b/js/tracker.js index 56713ef..012d859 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -10,14 +10,20 @@ async function loadArtistsData() { const response = await fetch('./artists.ndjson'); if (!response.ok) throw new Error('Network response was not ok'); const text = await response.text(); - artistsData = text.trim().split('\n') - .filter(line => line.trim()) - .map(line => { - try { return JSON.parse(line); } catch (e) { return null; } + artistsData = text + .trim() + .split('\n') + .filter((line) => line.trim()) + .map((line) => { + try { + return JSON.parse(line); + } catch (e) { + return null; + } }) - .filter(item => item !== null); + .filter((item) => item !== null); } catch (e) { - console.error("Failed to load Artists LIst:", e); + console.error('Failed to load Artists LIst:', e); } } @@ -29,11 +35,13 @@ function getSheetId(url) { async function fetchTrackerData(sheetId) { try { - const response = await fetch(`https://corsproxy.io/?${encodeURIComponent(`https://tracker.israeli.ovh/get/${sheetId}`)}`); + const response = await fetch( + `https://corsproxy.io/?${encodeURIComponent(`https://tracker.israeli.ovh/get/${sheetId}`)}` + ); if (!response.ok) return null; return await response.json(); } catch (e) { - console.error("Failed to fetch tracker data", e); + console.error('Failed to fetch tracker data', e); return null; } } @@ -62,21 +70,21 @@ function getDirectUrl(rawUrl) { function renderLoadButton(container, sheetId, artistName) { container.innerHTML = ''; container.style.display = 'block'; - + const wrapper = document.createElement('div'); wrapper.style.textAlign = 'center'; wrapper.style.padding = '2rem'; - + const button = document.createElement('button'); button.className = 'btn-primary'; button.textContent = 'Load Unreleased Projects'; button.style.fontSize = '1.1rem'; button.style.padding = '1rem 2rem'; - + button.onclick = async () => { button.textContent = 'Loading...'; button.disabled = true; - + const trackerData = await fetchTrackerData(sheetId); if (trackerData) { renderTracker(trackerData, container, artistName); @@ -88,7 +96,7 @@ function renderLoadButton(container, sheetId, artistName) { }, 2000); } }; - + wrapper.appendChild(button); container.appendChild(wrapper); } @@ -100,7 +108,7 @@ function renderTracker(trackerData, container, artistName) { Unreleased Songs & Info Provided By ArtistGrid. Consider Donating to Them.

`; - + const erasContainer = document.createElement('div'); erasContainer.className = 'card-grid'; erasContainer.style.opacity = '0'; @@ -110,11 +118,11 @@ function renderTracker(trackerData, container, artistName) { if (!trackerData.eras) return; - Object.values(trackerData.eras).forEach(era => { + Object.values(trackerData.eras).forEach((era) => { const card = document.createElement('div'); card.className = 'card'; card.style.cursor = 'pointer'; - + const imgWrapper = document.createElement('div'); imgWrapper.className = 'card-image-wrapper'; @@ -123,13 +131,13 @@ function renderTracker(trackerData, container, artistName) { img.src = era.image ? `https://corsproxy.io/?${encodeURIComponent(era.image)}` : 'assets/logo.svg'; img.alt = era.name; img.loading = 'lazy'; - + imgWrapper.appendChild(img); const title = document.createElement('div'); title.className = 'card-title'; title.textContent = era.name; - + const subtitle = document.createElement('div'); subtitle.className = 'card-subtitle'; subtitle.textContent = era.timeline || 'Unreleased'; @@ -137,9 +145,9 @@ function renderTracker(trackerData, container, artistName) { card.appendChild(imgWrapper); card.appendChild(title); card.appendChild(subtitle); - + card.onclick = () => showEraSongs(era, artistName); - + erasContainer.appendChild(card); }); @@ -153,7 +161,6 @@ function showEraSongs(era, artistName) { const modal = document.getElementById('tracker-modal'); const overlay = modal.querySelector('.modal-overlay'); const closeBtn = document.getElementById('close-tracker-modal'); - const img = document.getElementById('tracker-header-image'); const title = document.getElementById('tracker-header-title'); @@ -166,7 +173,7 @@ function showEraSongs(era, artistName) { const trackList = document.getElementById('tracker-tracklist'); const filterContainer = document.getElementById('tracker-filters'); - + filterContainer.innerHTML = ''; while (trackList.lastElementChild && !trackList.lastElementChild.classList.contains('track-list-header')) { trackList.removeChild(trackList.lastElementChild); @@ -178,15 +185,15 @@ function showEraSongs(era, artistName) { { label: 'Special', emoji: '✨' }, { label: 'Grails', emoji: '🏆' }, { label: 'Wanted', emoji: '🥇' }, - { label: 'Worst Of', emoji: '🗑️' } + { label: 'Worst Of', emoji: '🗑️' }, ]; let activeFilter = ''; const applyFilter = () => { const items = trackList.querySelectorAll('.track-item'); - - items.forEach(item => { + + items.forEach((item) => { const titleEl = item.querySelector('.title'); if (titleEl) { const title = titleEl.textContent.trim(); @@ -199,37 +206,37 @@ function showEraSongs(era, artistName) { }); const categories = trackList.querySelectorAll('h4'); - categories.forEach(cat => { + categories.forEach((cat) => { let next = cat.nextElementSibling; let hasVisibleItems = false; - - while(next && next.tagName !== 'H4') { + + while (next && next.tagName !== 'H4') { if (next.classList.contains('track-item') && next.style.display !== 'none') { hasVisibleItems = true; break; } next = next.nextElementSibling; } - + cat.style.display = hasVisibleItems ? 'block' : 'none'; }); }; - filters.forEach(filter => { + filters.forEach((filter) => { const btn = document.createElement('button'); btn.className = 'btn-secondary'; btn.textContent = filter.emoji ? `${filter.emoji} ${filter.label}` : filter.label; btn.style.fontSize = '0.85rem'; btn.style.padding = '0.4rem 0.8rem'; btn.style.borderRadius = '2rem'; - + if (filter.emoji === '') { - btn.style.backgroundColor = 'var(--primary)'; - btn.style.color = 'var(--primary-foreground)'; + btn.style.backgroundColor = 'var(--primary)'; + btn.style.color = 'var(--primary-foreground)'; } btn.onclick = () => { - Array.from(filterContainer.children).forEach(b => { + Array.from(filterContainer.children).forEach((b) => { b.style.backgroundColor = ''; b.style.color = ''; }); @@ -248,7 +255,7 @@ function showEraSongs(era, artistName) { if (era.data) { Object.entries(era.data).forEach(([category, songs]) => { if (!songs || songs.length === 0) return; - + const catTitle = document.createElement('h4'); catTitle.textContent = category; catTitle.style.padding = '1rem 0.5rem 0.5rem'; @@ -260,10 +267,10 @@ function showEraSongs(era, artistName) { const isValidUrl = (u) => u && typeof u === 'string' && u.trim().length > 0; - songs.forEach(song => { + songs.forEach((song) => { const trackItem = document.createElement('div'); trackItem.className = 'track-item'; - + trackItem.innerHTML = `
${globalIndex++}
@@ -287,27 +294,40 @@ function showEraSongs(era, artistName) { e.preventDefault(); const contextMenu = document.getElementById('context-menu'); if (contextMenu) { - const rawUrl = (isValidUrl(song.url) ? song.url : null) || (song.urls ? song.urls.find(isValidUrl) : null); + const rawUrl = + (isValidUrl(song.url) ? song.url : null) || (song.urls ? song.urls.find(isValidUrl) : null); const directUrl = getDirectUrl(rawUrl); const track = { id: `tracker-${song.name}`, title: song.name, - artist: { name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }, - artists: [{ name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }], + artist: { + name: + artistName || + document.getElementById('artist-detail-name')?.textContent || + 'Unknown Artist', + }, + artists: [ + { + name: + artistName || + document.getElementById('artist-detail-name')?.textContent || + 'Unknown Artist', + }, + ], album: { title: era.name, - cover: era.image + cover: era.image, }, duration: parseDuration(song.track_length), isTracker: true, audioUrl: directUrl, - remoteUrl: directUrl + remoteUrl: directUrl, }; - + contextMenu._contextTrack = track; - - ['go-to-album', 'go-to-artist', 'toggle-like', 'download', 'track-mix'].forEach(action => { + + ['go-to-album', 'go-to-artist', 'toggle-like', 'download', 'track-mix'].forEach((action) => { const item = contextMenu.querySelector(`[data-action="${action}"]`); if (item) item.style.display = 'none'; }); @@ -326,7 +346,7 @@ function showEraSongs(era, artistName) { if (hasValidUrl) { trackItem.onclick = async () => { if (song.track_length === '-') { - const targetUrl = (song.urls && song.urls.length > 0) ? song.urls[0] : song.url; + const targetUrl = song.urls && song.urls.length > 0 ? song.urls[0] : song.url; if (targetUrl) window.open(targetUrl, '_blank'); return; } @@ -337,8 +357,9 @@ function showEraSongs(era, artistName) { trackItem.classList.add('loading'); const trackNumEl = trackItem.querySelector('.track-number'); const originalNum = trackNumEl.textContent; - trackNumEl.innerHTML = ''; - + trackNumEl.innerHTML = + ''; + let urlsToTry = []; if (isValidUrl(song.url)) { urlsToTry.push(song.url); @@ -349,12 +370,12 @@ function showEraSongs(era, artistName) { let audioUrl = null; let successfulUrl = null; - + for (let rawUrl of urlsToTry) { console.log(`Trying: ${rawUrl}`); - + let downloadUrl = rawUrl; - + if (rawUrl.includes('pillows.su/f/')) { const match = rawUrl.match(/pillows\.su\/f\/([a-f0-9]+)/); if (match) { @@ -370,12 +391,14 @@ function showEraSongs(era, artistName) { try { console.log(`Fetching: ${downloadUrl}`); const response = await fetch(downloadUrl); - + if (response.ok) { const contentType = response.headers.get('content-type') || ''; - if (contentType.includes('audio/') || + if ( + contentType.includes('audio/') || contentType.includes('mpeg') || - contentType.includes('octet-stream')) { + contentType.includes('octet-stream') + ) { const arrayBuffer = await response.arrayBuffer(); if (arrayBuffer.byteLength > 1000) { const blob = new Blob([arrayBuffer], { type: 'audio/mpeg' }); @@ -394,26 +417,43 @@ function showEraSongs(era, artistName) { document.body.style.cursor = 'default'; trackItem.classList.remove('loading'); trackNumEl.textContent = originalNum; - + if (!audioUrl) { - alert(`Unable to load this track! :( The source may be unavailable.\n\nTried ${urlsToTry.length} URL(s)`); + alert( + `Unable to load this track! :( The source may be unavailable.\n\nTried ${urlsToTry.length} URL(s)` + ); return; } - + if (globalPlayer) { const track = { id: `tracker-${song.name}`, title: song.name, - artist: { name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }, - artists: [{ name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }], + artist: { + name: + artistName || + document.getElementById('artist-detail-name')?.textContent || + 'Unknown Artist', + }, + artists: [ + { + name: + artistName || + document.getElementById('artist-detail-name')?.textContent || + 'Unknown Artist', + }, + ], album: { title: era.name, - cover: era.image + cover: era.image, }, duration: parseDuration(song.track_length), isTracker: true, audioUrl: audioUrl, - remoteUrl: successfulUrl || (urlsToTry.length > 0 ? getDirectUrl(urlsToTry[0]) : null) || getDirectUrl(song.url) + remoteUrl: + successfulUrl || + (urlsToTry.length > 0 ? getDirectUrl(urlsToTry[0]) : null) || + getDirectUrl(song.url), }; globalPlayer.setQueue([track], 0); @@ -455,18 +495,18 @@ export async function initTracker(player, ui) { const observer = new MutationObserver(async () => { const artistNameEl = document.getElementById('artist-detail-name'); const trackerSection = document.getElementById('artist-tracker-section'); - + if (artistNameEl && trackerSection && artistNameEl.textContent) { const artistName = artistNameEl.textContent.trim(); - + if (trackerSection.dataset.artist === artistName) return; - + trackerSection.dataset.artist = artistName; trackerSection.innerHTML = ''; trackerSection.style.display = 'none'; - const artistEntry = artistsData.find(a => a.name.toLowerCase() === artistName.toLowerCase()); - + const artistEntry = artistsData.find((a) => a.name.toLowerCase() === artistName.toLowerCase()); + if (artistEntry && artistEntry.url) { const sheetId = getSheetId(artistEntry.url); if (sheetId) { @@ -480,4 +520,4 @@ export async function initTracker(player, ui) { if (artistPage) { observer.observe(artistPage, { attributes: true, childList: true, subtree: true }); } -} \ No newline at end of file +} diff --git a/js/ui-interactions.js b/js/ui-interactions.js index cf96d80..3c4c10c 100644 --- a/js/ui-interactions.js +++ b/js/ui-interactions.js @@ -414,7 +414,7 @@ export function initializeUIInteractions(player, api, ui) { if (sidePanelManager.isActive('queue')) { refreshQueuePanel(); } - + const overlay = document.getElementById('fullscreen-cover-overlay'); if (overlay && getComputedStyle(overlay).display !== 'none') { ui.updateFullscreenMetadata(player.currentTrack, player.getNextTrack()); diff --git a/js/ui.js b/js/ui.js index 745dec1..bae921f 100644 --- a/js/ui.js +++ b/js/ui.js @@ -685,7 +685,7 @@ export class UIRenderer { const nextTrackEl = document.getElementById('fullscreen-next-track'); const coverUrl = this.api.getCoverUrl(track.album?.cover, '1280'); - + const fsLikeBtn = document.getElementById('fs-like-btn'); if (fsLikeBtn) { this.updateLikeState(fsLikeBtn.parentElement, 'track', track.id); @@ -787,7 +787,7 @@ export class UIRenderer { closeFullscreenCover() { const overlay = document.getElementById('fullscreen-cover-overlay'); overlay.style.display = 'none'; - + const playerBar = document.querySelector('.now-playing-bar'); if (playerBar) playerBar.style.removeProperty('display'); @@ -824,34 +824,38 @@ export class UIRenderer { lastPausedState = isPaused; if (isPaused) { - playBtn.innerHTML = ''; + playBtn.innerHTML = + ''; } else { - playBtn.innerHTML = ''; + playBtn.innerHTML = + ''; } }; - + updatePlayBtn(); - + playBtn.onclick = () => { this.player.handlePlayPause(); updatePlayBtn(); }; - + prevBtn.onclick = () => this.player.playPrev(); nextBtn.onclick = () => this.player.playNext(); - + shuffleBtn.onclick = () => { this.player.toggleShuffle(); shuffleBtn.classList.toggle('active', this.player.shuffleActive); }; - + repeatBtn.onclick = () => { const mode = this.player.toggleRepeat(); repeatBtn.classList.toggle('active', mode !== 0); if (mode === 2) { - repeatBtn.innerHTML = ''; + repeatBtn.innerHTML = + ''; } else { - repeatBtn.innerHTML = ''; + repeatBtn.innerHTML = + ''; } }; @@ -883,26 +887,27 @@ export class UIRenderer { const mode = this.player.repeatMode; repeatBtn.classList.toggle('active', mode !== 0); if (mode === 2) { - repeatBtn.innerHTML = ''; + repeatBtn.innerHTML = + ''; } const update = () => { if (document.getElementById('fullscreen-cover-overlay').style.display === 'none') return; - + const duration = audioPlayer.duration || 0; const current = audioPlayer.currentTime || 0; - + if (duration > 0) { const percent = (current / duration) * 100; progressFill.style.width = `${percent}%`; currentTimeEl.textContent = formatTime(current); totalDurationEl.textContent = formatTime(duration); } - + updatePlayBtn(); this.fullscreenUpdateInterval = requestAnimationFrame(update); }; - + if (this.fullscreenUpdateInterval) cancelAnimationFrame(this.fullscreenUpdateInterval); this.fullscreenUpdateInterval = requestAnimationFrame(update); } @@ -913,7 +918,10 @@ export class UIRenderer { }); document.querySelectorAll('.sidebar-nav a').forEach((link) => { - link.classList.toggle('active', link.pathname === `/${pageId}` || (pageId === 'home' && link.pathname === '/')); + link.classList.toggle( + 'active', + link.pathname === `/${pageId}` || (pageId === 'home' && link.pathname === '/') + ); }); document.querySelector('.main-content').scrollTop = 0; @@ -939,7 +947,7 @@ export class UIRenderer { const yearEl = document.getElementById('track-detail-year'); const albumSection = document.getElementById('track-album-section'); const albumTracksContainer = document.getElementById('track-detail-album-tracks'); - + const playBtn = document.getElementById('play-track-btn'); const lyricsBtn = document.getElementById('track-lyrics-btn'); const shareBtn = document.getElementById('share-track-btn'); @@ -957,11 +965,11 @@ export class UIRenderer { try { const trackData = await this.api.getTrack(trackId); const track = trackData.track; - + const coverUrl = this.api.getCoverUrl(track.album?.cover); imageEl.src = coverUrl; imageEl.style.backgroundColor = ''; - + this.setPageBackground(coverUrl); if (backgroundSettings.isEnabled() && track.album?.cover) { this.extractAndApplyColor(this.api.getCoverUrl(track.album.cover, '80')); @@ -974,7 +982,7 @@ export class UIRenderer { artistEl.innerHTML = `${escapeHtml(track.artist.name)}`; albumEl.innerHTML = `${escapeHtml(track.album.title)}`; - + if (track.album.releaseDate) { const date = new Date(track.album.releaseDate); yearEl.textContent = date.getFullYear(); @@ -1004,7 +1012,7 @@ export class UIRenderer { this.updateLikeState(likeBtn, 'track', track.id); trackDataStore.set(likeBtn, track); - + downloadBtn.dataset.action = 'download'; downloadBtn.classList.add('track-action-btn'); trackDataStore.set(downloadBtn, track); @@ -1012,10 +1020,10 @@ export class UIRenderer { if (track.album.id) { const albumData = await this.api.getAlbum(track.album.id); const tracks = albumData.tracks; - + if (tracks.length > 1) { albumSection.style.display = 'block'; - const otherTracks = tracks.filter(t => t.id !== track.id); + const otherTracks = tracks.filter((t) => t.id !== track.id); this.renderListWithTracks(albumTracksContainer, otherTracks, false); } else { albumSection.style.display = 'none'; @@ -1121,7 +1129,7 @@ export class UIRenderer { const folders = await db.getFolders(); if (foldersContainer) { - foldersContainer.innerHTML = folders.map(f => this.createFolderCardHTML(f)).join(''); + foldersContainer.innerHTML = folders.map((f) => this.createFolderCardHTML(f)).join(''); foldersContainer.style.display = folders.length ? 'grid' : 'none'; } @@ -1965,7 +1973,7 @@ export class UIRenderer { removeBtn.innerHTML = ''; removeBtn.dataset.trackId = currentTracks[index].id; - + const menuBtn = actionsDiv.querySelector('.track-menu-btn'); actionsDiv.insertBefore(removeBtn, menuBtn); }); @@ -1990,7 +1998,9 @@ export class UIRenderer { } else if (sortType === 'added-oldest') { currentTracks = [...originalTracks].sort((a, b) => (a.addedAt || 0) - (b.addedAt || 0)); } else if (sortType === 'title') { - currentTracks = [...originalTracks].sort((a, b) => (a.title || '').localeCompare(b.title || '')); + currentTracks = [...originalTracks].sort((a, b) => + (a.title || '').localeCompare(b.title || '') + ); } else if (sortType === 'artist') { currentTracks = [...originalTracks].sort((a, b) => { const artistA = a.artist?.name || a.artists?.[0]?.name || ''; @@ -2015,7 +2025,13 @@ export class UIRenderer { } // Render Actions (Shuffle, Edit, Delete, Share, Sort) - this.updatePlaylistHeaderActions(playlistData, !!ownedPlaylist, tracks, false, !!ownedPlaylist ? applySort : null); + this.updatePlaylistHeaderActions( + playlistData, + !!ownedPlaylist, + tracks, + false, + ownedPlaylist ? applySort : null + ); playBtn.onclick = () => { this.player.setQueue(currentTracks, 0); @@ -2137,9 +2153,11 @@ export class UIRenderer { if (!folder) throw new Error('Folder not found'); imageEl.src = folder.cover || '/assets/folder.png'; - imageEl.onerror = () => { imageEl.src = '/assets/folder.png'; }; + imageEl.onerror = () => { + imageEl.src = '/assets/folder.png'; + }; imageEl.style.backgroundColor = ''; - + titleEl.textContent = folder.name; metaEl.textContent = `Created ${new Date(folder.createdAt).toLocaleDateString()}`; @@ -2467,7 +2485,13 @@ export class UIRenderer { const actionsDiv = document.getElementById('page-playlist').querySelector('.detail-header-actions'); // Cleanup existing dynamic buttons - ['shuffle-playlist-btn', 'edit-playlist-btn', 'delete-playlist-btn', 'share-playlist-btn', 'sort-playlist-btn'].forEach((id) => { + [ + 'shuffle-playlist-btn', + 'edit-playlist-btn', + 'delete-playlist-btn', + 'share-playlist-btn', + 'sort-playlist-btn', + ].forEach((id) => { const btn = actionsDiv.querySelector(`#${id}`); if (btn) btn.remove(); }); @@ -2493,11 +2517,11 @@ export class UIRenderer { sortBtn.className = 'btn-secondary'; sortBtn.innerHTML = 'Sort'; - + sortBtn.onclick = (e) => { e.stopPropagation(); const menu = document.getElementById('sort-menu'); - + const rect = sortBtn.getBoundingClientRect(); menu.style.top = `${rect.bottom + 5}px`; menu.style.left = `${rect.left}px`; @@ -2517,7 +2541,7 @@ export class UIRenderer { }; menu.onclick = handleSort; - + setTimeout(() => document.addEventListener('click', closeMenu), 0); }; fragment.appendChild(sortBtn); @@ -2776,7 +2800,8 @@ export class UIRenderer { document.body.classList.add('sidebar-collapsed'); const toggleBtn = document.getElementById('sidebar-toggle'); if (toggleBtn) { - toggleBtn.innerHTML = ''; + toggleBtn.innerHTML = + ''; } const imageEl = document.getElementById('track-detail-image'); @@ -2788,7 +2813,7 @@ export class UIRenderer { const albumTracksContainer = document.getElementById('track-detail-album-tracks'); const similarSection = document.getElementById('track-similar-section'); const similarTracksContainer = document.getElementById('track-detail-similar-tracks'); - + const playBtn = document.getElementById('play-track-btn'); const lyricsBtn = document.getElementById('track-lyrics-btn'); const shareBtn = document.getElementById('share-track-btn'); @@ -2813,11 +2838,11 @@ export class UIRenderer { try { const track = await this.api.getTrackMetadata(trackId); - + const coverUrl = this.api.getCoverUrl(track.album?.cover); imageEl.src = coverUrl; imageEl.style.backgroundColor = ''; - + this.setPageBackground(coverUrl); if (backgroundSettings.isEnabled() && track.album?.cover) { this.extractAndApplyColor(this.api.getCoverUrl(track.album.cover, '80')); @@ -2852,7 +2877,7 @@ export class UIRenderer { const date = new Date(track.album.releaseDate); yearEl.textContent = date.getFullYear(); } - + if (track.copyright || track.album.copyright) { yearEl.textContent += ` • ${track.copyright || track.album.copyright}`; } @@ -2882,7 +2907,7 @@ export class UIRenderer { this.updateLikeState(likeBtn, 'track', track.id); trackDataStore.set(likeBtn, track); - + downloadBtn.dataset.action = 'download'; downloadBtn.classList.add('track-action-btn'); trackDataStore.set(downloadBtn, track); @@ -2893,7 +2918,7 @@ export class UIRenderer { const tracks = albumData.tracks; if (tracks.length > 1) { albumSection.style.display = 'block'; - const otherTracks = tracks.filter(t => t.id != track.id); + const otherTracks = tracks.filter((t) => t.id != track.id); this.renderListWithTracks(albumTracksContainer, otherTracks, false, false, true); } } catch (err) { @@ -2901,14 +2926,17 @@ export class UIRenderer { } } - this.api.getRecommendedTracksForPlaylist([track], 5).then(similarTracks => { - if (similarTracks.length > 0) { - this.renderListWithTracks(similarTracksContainer, similarTracks, true); - similarSection.style.display = 'block'; - } else { - similarSection.style.display = 'none'; - } - }).catch(() => similarSection.style.display = 'none'); + this.api + .getRecommendedTracksForPlaylist([track], 5) + .then((similarTracks) => { + if (similarTracks.length > 0) { + this.renderListWithTracks(similarTracksContainer, similarTracks, true); + similarSection.style.display = 'block'; + } else { + similarSection.style.display = 'none'; + } + }) + .catch(() => (similarSection.style.display = 'none')); document.title = `${displayTitle} - ${artistName}`; } catch (e) { diff --git a/js/utils.js b/js/utils.js index 46fbdb1..3d9afea 100644 --- a/js/utils.js +++ b/js/utils.js @@ -285,10 +285,14 @@ function resizeImageBlob(blob, size) { ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; ctx.drawImage(img, 0, 0, size, size); - canvas.toBlob((resizedBlob) => { - if (resizedBlob) resolve(resizedBlob); - else reject(new Error('Canvas toBlob failed')); - }, blob.type || 'image/jpeg', 0.9); + canvas.toBlob( + (resizedBlob) => { + if (resizedBlob) resolve(resizedBlob); + else reject(new Error('Canvas toBlob failed')); + }, + blob.type || 'image/jpeg', + 0.9 + ); }; img.onerror = (e) => { URL.revokeObjectURL(url); @@ -309,18 +313,18 @@ export async function getCoverBlob(api, coverId) { if (sizeStr.includes('x')) { sizeStr = sizeStr.split('x')[0]; } - + let requestedSize = parseInt(sizeStr, 10); if (isNaN(requestedSize) || requestedSize <= 0) requestedSize = 1280; const cacheKey = `${coverId}-${requestedSize}`; if (coverCache.has(cacheKey)) return coverCache.get(cacheKey); - // Tidal seems to only support these soooo + // Tidal seems to only support these soooo const supportedSizes = [80, 160, 320, 640, 1280]; let fetchSize = 1280; - - const bestSize = supportedSizes.find(s => s >= requestedSize); + + const bestSize = supportedSizes.find((s) => s >= requestedSize); if (bestSize) { fetchSize = bestSize; } diff --git a/js/visualizer.js b/js/visualizer.js index efa3cd1..65efa8c 100644 --- a/js/visualizer.js +++ b/js/visualizer.js @@ -11,7 +11,7 @@ export class Visualizer { this.isActive = false; this.animationId = null; this.particles = []; - + this.kick = 0; this.lastIntensity = 0; this.lastBeatTime = 0; @@ -33,7 +33,7 @@ export class Visualizer { this.source.connect(this.analyser); this.analyser.connect(this.audioContext.destination); } catch (e) { - console.warn("Visualizer init failed (likely CORS or already connected):", e); + console.warn('Visualizer init failed (likely CORS or already connected):', e); } } @@ -45,11 +45,11 @@ export class Visualizer { if (this.audioContext.state === 'suspended') { this.audioContext.resume(); } - + this.resize(); window.addEventListener('resize', this.resizeBound); this.canvas.style.display = 'block'; - + this.particles = []; this.energyAverage = 0.3; this.kick = 0; @@ -61,7 +61,7 @@ export class Visualizer { this.isActive = false; if (this.animationId) cancelAnimationFrame(this.animationId); window.removeEventListener('resize', this.resizeBound); - + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.canvas.style.display = 'none'; } @@ -93,15 +93,12 @@ export class Visualizer { for (let i = 0; i < 4; i++) bassSum += dataArray[i]; const bass = bassSum / 4 / 255; - - - const intensity = bass * bass; + const intensity = bass * bass; this.energyAverage = this.energyAverage * 0.99 + intensity * 0.01; this.upbeatSmoother = this.upbeatSmoother * 0.92 + intensity * 0.08; - if (visualizerSettings.isSmartIntensityEnabled()) { let target = 0.1; if (this.energyAverage > 0.4) { @@ -113,18 +110,14 @@ export class Visualizer { sensitivity = target; } - let threshold = 0.5; if (this.energyAverage < 0.3) { - threshold = 0.5 + (0.3 - this.energyAverage) * 2; } - const now = Date.now(); if (intensity > threshold) { - if (intensity > this.lastIntensity + 0.05 && now - this.lastBeatTime > 50) { this.kick = 1.0; this.lastBeatTime = now; @@ -145,7 +138,6 @@ export class Visualizer { } this.lastIntensity = intensity; - let shakeX = 0; let shakeY = 0; if (this.kick > 0.1) { @@ -154,8 +146,8 @@ export class Visualizer { shakeY = (Math.random() - 0.5) * shakeAmt; } - const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#ffffff'; - + const primaryColor = + getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#ffffff'; const particleCount = 180; if (this.particles.length !== particleCount) { @@ -167,22 +159,21 @@ export class Visualizer { vx: (Math.random() - 0.5) * 2, vy: (Math.random() - 0.5) * 2, size: Math.random() * 3 + 1, - baseSize: Math.random() * 3 + 1 + baseSize: Math.random() * 3 + 1, }); } } ctx.save(); ctx.translate(shakeX, shakeY); - + ctx.fillStyle = primaryColor; ctx.strokeStyle = primaryColor; for (let i = 0; i < this.particles.length; i++) { let p = this.particles[i]; - - const speedMult = 1 + (intensity * 2) + this.kick * 8 * sensitivity; + const speedMult = 1 + intensity * 2 + this.kick * 8 * sensitivity; p.x += p.vx * speedMult; p.y += p.vy * speedMult; @@ -196,9 +187,8 @@ export class Visualizer { if (p.y < 0) p.y = h; if (p.y > h) p.y = 0; - const size = p.baseSize * (1 + intensity * 0.5 + this.kick * 0.8 * sensitivity); - ctx.globalAlpha = 0.4 + (intensity * 0.2) + this.kick * 0.15 * sensitivity; + ctx.globalAlpha = 0.4 + intensity * 0.2 + this.kick * 0.15 * sensitivity; ctx.beginPath(); ctx.arc(p.x, p.y, size, 0, Math.PI * 2); ctx.fill(); @@ -207,8 +197,8 @@ export class Visualizer { const p2 = this.particles[j]; const dx = p.x - p2.x; const dy = p.y - p2.y; - const distSq = dx*dx + dy*dy; - + const distSq = dx * dx + dy * dy; + const maxDist = 150 + intensity * 50 + this.kick * 50 * sensitivity; const maxDistSq = maxDist * maxDist; @@ -225,4 +215,4 @@ export class Visualizer { } ctx.restore(); } -} \ No newline at end of file +} diff --git a/styles.css b/styles.css index b31f45e..b7594dc 100644 --- a/styles.css +++ b/styles.css @@ -2076,7 +2076,7 @@ input:checked + .slider::before { .fullscreen-progress-container .progress-bar { flex: 1; height: 6px; - background: rgba(255, 255, 255, 0.2); + background: rgb(255, 255, 255, 0.2); border-radius: 3px; cursor: pointer; position: relative; @@ -2110,7 +2110,7 @@ input:checked + .slider::before { } .fullscreen-buttons button:hover { - background: rgba(255, 255, 255, 0.1); + background: rgb(255, 255, 255, 0.1); transform: scale(1.1); } @@ -3193,7 +3193,7 @@ img[src=''] { flex-direction: column; align-items: center; justify-content: center; - padding: 6rem 2rem 2rem 2rem; + padding: 6rem 2rem 2rem; transition: flex 0.3s ease; } @@ -3627,7 +3627,7 @@ img[src=''] { padding: var(--spacing-lg); } - .now-playing-bar { + .now-playing-bar { width: calc(96% - 160px) !important; left: calc(160px + 2%); } @@ -4348,7 +4348,6 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b text-overflow: ellipsis; } - /* Genius i love genius brah!! */ .genius-annotation-modal { position: fixed; @@ -4357,7 +4356,7 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b display: flex; align-items: center; justify-content: center; - background: rgba(0, 0, 0, 0.6); + background: rgb(0, 0, 0, 0.6); backdrop-filter: blur(4px); animation: fade-in 0.2s ease; } @@ -4424,14 +4423,14 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b } .genius-annotated { - background-color: rgba(255, 255, 100, 0.1); + background-color: rgb(255, 255, 100, 0.1); cursor: pointer; border-radius: 4px; transition: background-color 0.2s; } .genius-annotated:hover { - background-color: rgba(255, 255, 100, 0.2); + background-color: rgb(255, 255, 100, 0.2); } .genius-multi-start { @@ -4491,7 +4490,7 @@ body.sidebar-collapsed #sidebar-toggle { #page-track .detail-header-image { width: 350px; height: 350px; - box-shadow: 0 30px 60px rgba(0, 0, 0, 0.5); + box-shadow: 0 30px 60px rgb(0, 0, 0, 0.5); } #page-track .detail-header-info { @@ -4526,12 +4525,12 @@ body.sidebar-collapsed #sidebar-toggle { width: 250px; height: 250px; } + #page-track .detail-header-info .title { font-size: 2rem; } } - .tracker-modal .track-item.loading { opacity: 0.7; pointer-events: none; @@ -4608,4 +4607,4 @@ body.sidebar-collapsed #sidebar-toggle { overflow-y: auto; display: block; } -} \ No newline at end of file +}