style: auto-fix linting issues

This commit is contained in:
SamidyFR 2026-01-24 12:13:44 +00:00 committed by github-actions[bot]
parent b7bc90f4f1
commit 4ede3b2664
23 changed files with 413 additions and 273 deletions

View file

@ -22,4 +22,4 @@ UI:
| QQDL | https://tidal.qqdl.site/ | | QQDL | https://tidal.qqdl.site/ |
| Arjix | https://music.arjix.dev/ | | Arjix | https://music.arjix.dev/ |
| Spofree | https://spo.free.nf | | Spofree | https://spo.free.nf |
| Mappl | https://mappl.tv/music | | Mappl | https://mappl.tv/music |

View file

@ -67,14 +67,16 @@ class ServerAPI {
export async function onRequest(context) { export async function onRequest(context) {
const { request, params, env } = context; const { request, params, env } = context;
const userAgent = request.headers.get('User-Agent') || ''; 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; const albumId = params.id;
if (isBot && albumId) { if (isBot && albumId) {
try { try {
const api = new ServerAPI(); const api = new ServerAPI();
const data = await api.getAlbumMetadata(albumId); 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 || []; const tracks = album.items || data.tracks || [];
if (album && (album.title || album.name)) { if (album && (album.title || album.name)) {
@ -82,9 +84,11 @@ export async function onRequest(context) {
const artist = album.artist?.name || 'Unknown Artist'; const artist = album.artist?.name || 'Unknown Artist';
const year = album.releaseDate ? new Date(album.releaseDate).getFullYear() : ''; const year = album.releaseDate ? new Date(album.releaseDate).getFullYear() : '';
const trackCount = album.numberOfTracks || tracks.length; const trackCount = album.numberOfTracks || tracks.length;
const description = `Album by ${artist}${year}${trackCount} Tracks\nListen on Monochrome`; 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 pageUrl = new URL(request.url).href;
const metaHtml = ` const metaHtml = `

View file

@ -67,7 +67,9 @@ class ServerAPI {
export async function onRequest(context) { export async function onRequest(context) {
const { request, params, env } = context; const { request, params, env } = context;
const userAgent = request.headers.get('User-Agent') || ''; 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; const artistId = params.id;
if (isBot && artistId) { if (isBot && artistId) {
@ -79,7 +81,9 @@ export async function onRequest(context) {
if (artist && (artist.name || artist.title)) { if (artist && (artist.name || artist.title)) {
const name = artist.name || artist.title; const name = artist.name || artist.title;
const description = `Listen to ${name} on Monochrome`; 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 pageUrl = new URL(request.url).href;
const metaHtml = ` const metaHtml = `

View file

@ -68,7 +68,9 @@ class ServerAPI {
export async function onRequest(context) { export async function onRequest(context) {
const { request, params, env } = context; const { request, params, env } = context;
const userAgent = request.headers.get('User-Agent') || ''; 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; const playlistId = params.id;
if (isBot && playlistId) { if (isBot && playlistId) {
@ -82,7 +84,9 @@ export async function onRequest(context) {
const trackCount = playlist.numberOfTracks; const trackCount = playlist.numberOfTracks;
const description = `Playlist • ${trackCount} Tracks\nListen on Monochrome`; const description = `Playlist • ${trackCount} Tracks\nListen on Monochrome`;
const imageId = playlist.squareImage || playlist.image; 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 pageUrl = new URL(request.url).href;
const metaHtml = ` const metaHtml = `

View file

@ -64,7 +64,7 @@ class ServerAPI {
const json = await response.json(); const json = await response.json();
const data = json.data || json; const data = json.data || json;
const items = Array.isArray(data) ? data : [data]; 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) { if (found) {
return found.item || found; return found.item || found;
} }
@ -87,7 +87,9 @@ class ServerAPI {
export async function onRequest(context) { export async function onRequest(context) {
const { request, params, env } = context; const { request, params, env } = context;
const userAgent = request.headers.get('User-Agent') || ''; 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; const trackId = params.id;
if (isBot && trackId) { if (isBot && trackId) {
@ -112,12 +114,14 @@ export async function onRequest(context) {
} }
} }
// this prob wont work im js winging it // this prob wont work im js winging it
const audioMeta = audioUrl ? ` const audioMeta = audioUrl
? `
<meta property="og:audio" content="${audioUrl}"> <meta property="og:audio" content="${audioUrl}">
<meta property="og:audio:type" content="audio/mp4"> <meta property="og:audio:type" content="audio/mp4">
<meta property="og:video" content="${audioUrl}"> <meta property="og:video" content="${audioUrl}">
<meta property="og:video:type" content="audio/mp4"> <meta property="og:video:type" content="audio/mp4">
` : ''; `
: '';
const metaHtml = ` const metaHtml = `
<!DOCTYPE html> <!DOCTYPE html>
@ -164,4 +168,4 @@ export async function onRequest(context) {
const url = new URL(request.url); const url = new URL(request.url);
url.pathname = '/'; url.pathname = '/';
return env.ASSETS.fetch(new Request(url, request)); return env.ASSETS.fetch(new Request(url, request));
} }

View file

@ -1,24 +1,25 @@
// functions/userplaylist/[id].js // 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) // 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) { export async function onRequest(context) {
const { request, params, env } = context; const { request, params, env } = context;
const userAgent = request.headers.get('User-Agent') || ''; 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; const playlistId = params.id;
if (isBot && playlistId) { if (isBot && playlistId) {
try { try {
let pbUrl = `https://monodb.samidy.com/api/collections/user_playlists/records/${playlistId}`; let pbUrl = `https://monodb.samidy.com/api/collections/user_playlists/records/${playlistId}`;
let response = await fetch(pbUrl); let response = await fetch(pbUrl);
if (!response.ok) { if (!response.ok) {
pbUrl = `https://monodb.samidy.com/api/collections/public_playlists/records?filter=(uuid='${playlistId}')`; pbUrl = `https://monodb.samidy.com/api/collections/public_playlists/records?filter=(uuid='${playlistId}')`;
response = await fetch(pbUrl); response = await fetch(pbUrl);
} }
if (response.ok) { if (response.ok) {
let playlist = await response.json(); let playlist = await response.json();
if (playlist.items && Array.isArray(playlist.items) && playlist.items.length > 0) { 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'; const title = playlist.name || playlist.title || 'User Playlist';
let tracks = []; let tracks = [];
try { 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) { } catch (e) {
console.error('Failed to parse tracks JSON', 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`; const description = `User Playlist • ${trackCount} Tracks\nListen on Monochrome`;
let imageUrl = 'https://monochrome.samidy.com/assets/appicon.png'; let imageUrl = 'https://monochrome.samidy.com/assets/appicon.png';
if (playlist.cover) { if (playlist.cover) {
if (playlist.cover.startsWith('http')) { if (playlist.cover.startsWith('http')) {
@ -45,7 +50,12 @@ export async function onRequest(context) {
} else { } else {
imageUrl = `https://monodb.samidy.com/api/files/${playlist.collectionId}/${playlist.id}/${playlist.cover}`; 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; const cover = tracks[0].album.cover;
imageUrl = `https://resources.tidal.com/images/${cover.replace(/-/g, '/')}/1280x1280.jpg`; imageUrl = `https://resources.tidal.com/images/${cover.replace(/-/g, '/')}/1280x1280.jpg`;
} }

View file

@ -843,11 +843,11 @@ export class LosslessAPI {
const response = await this.fetchWithRetry(`/info/?id=${id}`, { type: 'api' }); const response = await this.fetchWithRetry(`/info/?id=${id}`, { type: 'api' });
const json = await response.json(); const json = await response.json();
const data = json.data || json; const data = json.data || json;
let track; let track;
const items = Array.isArray(data) ? data : [data]; 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) { if (found) {
track = this.prepareTrack(found.item || found); track = this.prepareTrack(found.item || found);
await this.cache.set('track', cacheKey, track); await this.cache.set('track', cacheKey, track);

View file

@ -15,7 +15,13 @@ import { createRouter, updateTabTitle, navigate } from './router.js';
import { initializeSettings } from './settings.js'; import { initializeSettings } from './settings.js';
import { initializePlayerEvents, initializeTrackInteractions, handleTrackAction } from './events.js'; import { initializePlayerEvents, initializeTrackInteractions, handleTrackAction } from './events.js';
import { initializeUIInteractions } from './ui-interactions.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 { debounce, SVG_PLAY } from './utils.js';
import { sidePanelManager } from './side-panel.js'; import { sidePanelManager } from './side-panel.js';
import { db } from './db.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 // i love ios and macos!!!! webkit fucking SUCKS BULLSHIT sorry ios/macos heads yall getting lossless only
const ua = navigator.userAgent.toLowerCase(); const ua = navigator.userAgent.toLowerCase();
const isIOS = /iphone|ipad|ipod/.test(ua) || (ua.includes('mac') && navigator.maxTouchPoints > 1); 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) { if (isIOS || isSafari) {
const qualitySelect = document.getElementById('streaming-quality-setting'); const qualitySelect = document.getElementById('streaming-quality-setting');
const downloadSelect = document.getElementById('download-quality-setting'); const downloadSelect = document.getElementById('download-quality-setting');
const removeHiRes = (select) => { const removeHiRes = (select) => {
if (!select) return; if (!select) return;
const option = select.querySelector('option[value="HI_RES_LOSSLESS"]'); const option = select.querySelector('option[value="HI_RES_LOSSLESS"]');
@ -228,7 +235,7 @@ document.addEventListener('DOMContentLoaded', async () => {
try { try {
const playlist = await db.getPlaylist(id); const playlist = await db.getPlaylist(id);
const imgElement = document.getElementById('playlist-detail-image'); const imgElement = document.getElementById('playlist-detail-image');
if (!imgElement) return; if (!imgElement) return;
let container = imgElement.parentElement; let container = imgElement.parentElement;
@ -239,7 +246,7 @@ document.addEventListener('DOMContentLoaded', async () => {
container.className = 'detail-header-cover-container'; container.className = 'detail-header-cover-container';
imgElement.parentNode.insertBefore(container, imgElement); imgElement.parentNode.insertBefore(container, imgElement);
container.appendChild(imgElement); container.appendChild(imgElement);
collageElement = document.createElement('div'); collageElement = document.createElement('div');
collageElement.id = 'playlist-detail-collage'; collageElement.id = 'playlist-detail-collage';
collageElement.className = 'detail-header-collage'; collageElement.className = 'detail-header-collage';
@ -389,7 +396,7 @@ document.addEventListener('DOMContentLoaded', async () => {
const isCollapsed = document.body.classList.contains('sidebar-collapsed'); const isCollapsed = document.body.classList.contains('sidebar-collapsed');
const toggleBtn = document.getElementById('sidebar-toggle'); const toggleBtn = document.getElementById('sidebar-toggle');
if (toggleBtn) { if (toggleBtn) {
toggleBtn.innerHTML = isCollapsed toggleBtn.innerHTML = isCollapsed
? '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>' ? '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>'
: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>'; : '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>';
} }
@ -806,10 +813,10 @@ document.addEventListener('DOMContentLoaded', async () => {
e.stopPropagation(); e.stopPropagation();
const btn = e.target.closest('.remove-from-playlist-btn'); const btn = e.target.closest('.remove-from-playlist-btn');
const playlistId = window.location.pathname.split('/')[2]; const playlistId = window.location.pathname.split('/')[2];
db.getPlaylist(playlistId).then(async (playlist) => { db.getPlaylist(playlistId).then(async (playlist) => {
let trackId = null; let trackId = null;
// Prefer ID if available (from sorted view) // Prefer ID if available (from sorted view)
if (btn.dataset.trackId) { if (btn.dataset.trackId) {
trackId = btn.dataset.trackId; trackId = btn.dataset.trackId;
@ -919,11 +926,15 @@ document.addEventListener('DOMContentLoaded', async () => {
return; return;
} }
list.innerHTML = playlists.map(p => ` list.innerHTML = playlists
.map(
(p) => `
<div class="modal-option" data-id="${p.id}"> <div class="modal-option" data-id="${p.id}">
<span>${p.name}</span> <span>${p.name}</span>
</div> </div>
`).join(''); `
)
.join('');
const closeModal = () => { const closeModal = () => {
modal.classList.remove('active'); modal.classList.remove('active');
@ -1207,7 +1218,6 @@ document.addEventListener('DOMContentLoaded', async () => {
updateTabTitle(player); updateTabTitle(player);
}; };
await handleRouteChange(); await handleRouteChange();
window.addEventListener('popstate', handleRouteChange); window.addEventListener('popstate', handleRouteChange);
@ -1215,7 +1225,12 @@ document.addEventListener('DOMContentLoaded', async () => {
document.body.addEventListener('click', (e) => { document.body.addEventListener('click', (e) => {
const link = e.target.closest('a'); 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(); e.preventDefault();
navigate(link.pathname); navigate(link.pathname);
} }

View file

@ -77,7 +77,7 @@ export class DashDownloader {
adaptationSets.sort((a, b) => { adaptationSets.sort((a, b) => {
const getMaxBandwidth = (set) => { const getMaxBandwidth = (set) => {
const reps = Array.from(set.querySelectorAll('Representation')); 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); return getMaxBandwidth(b) - getMaxBandwidth(a);
}); });

View file

@ -545,7 +545,7 @@ export class MusicDatabase {
cover: cover, cover: cover,
playlists: [], playlists: [],
createdAt: Date.now(), createdAt: Date.now(),
updatedAt: Date.now() updatedAt: Date.now(),
}; };
await this.performTransaction('user_folders', 'readwrite', (store) => store.put(folder)); await this.performTransaction('user_folders', 'readwrite', (store) => store.put(folder));
return folder; return folder;

View file

@ -545,7 +545,16 @@ function createBulkDownloadNotification(type, name, _totalItems) {
notifEl.dataset.bulkType = type; notifEl.dataset.bulkType = type;
notifEl.dataset.bulkName = name; 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 = ` notifEl.innerHTML = `
<div style="display: flex; align-items: start; gap: 0.75rem;"> <div style="display: flex; align-items: start; gap: 0.75rem;">

View file

@ -170,7 +170,10 @@ export function initializePlayerEvents(player, audioPlayer, scrobbler, ui) {
const progressBar = document.getElementById('progress-bar'); const progressBar = document.getElementById('progress-bar');
const playerControls = document.querySelector('.player-controls'); 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 (!waveformSettings.isEnabled() || !player.currentTrack || isTracker) {
if (progressBar) { if (progressBar) {
@ -624,7 +627,7 @@ export async function handleTrackAction(
if (nowPlayingLikeBtn && type === 'track' && player?.currentTrack?.id === item.id) { if (nowPlayingLikeBtn && type === 'track' && player?.currentTrack?.id === item.id) {
elementsToUpdate.push(nowPlayingLikeBtn); elementsToUpdate.push(nowPlayingLikeBtn);
} }
const fsLikeBtn = document.getElementById('fs-like-btn'); const fsLikeBtn = document.getElementById('fs-like-btn');
if (fsLikeBtn && type === 'track' && player?.currentTrack?.id === item.id) { if (fsLikeBtn && type === 'track' && player?.currentTrack?.id === item.id) {
elementsToUpdate.push(fsLikeBtn); elementsToUpdate.push(fsLikeBtn);

View file

@ -12,28 +12,28 @@ class GeniusManager {
} }
getToken() { 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) { async searchTrack(title, artist) {
const cleanTitle = title.split('(')[0].split('-')[0].trim(); const cleanTitle = title.split('(')[0].split('-')[0].trim();
const query = encodeURIComponent(`${cleanTitle} ${artist}`); const query = encodeURIComponent(`${cleanTitle} ${artist}`);
const url = `https://api.genius.com/search?q=${query}`; const url = `https://api.genius.com/search?q=${query}`;
const token = this.getToken(); const token = this.getToken();
const response = await fetch(`https://corsproxy.io/?${encodeURIComponent(url)}`, { 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'); if (!response.ok) throw new Error('Failed to search Genius');
const data = await response.json(); const data = await response.json();
if (data.response.hits.length === 0) return null; 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 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); const hitArtist = normalize(h.result.primary_artist.name);
return hitArtist.includes(targetArtist) || targetArtist.includes(hitArtist); return hitArtist.includes(targetArtist) || targetArtist.includes(hitArtist);
}); });
@ -45,7 +45,7 @@ class GeniusManager {
const token = this.getToken(); const token = this.getToken();
const url = `https://api.genius.com/referents?song_id=${songId}&text_format=plain&per_page=50`; 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)}`, { 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'); if (!response.ok) throw new Error('Failed to fetch annotations');
@ -61,7 +61,7 @@ class GeniusManager {
this.loading = true; this.loading = true;
const artist = Array.isArray(track.artists) ? track.artists[0].name : track.artist.name; const artist = Array.isArray(track.artists) ? track.artists[0].name : track.artist.name;
const song = await this.searchTrack(track.title, artist); const song = await this.searchTrack(track.title, artist);
if (!song) { if (!song) {
this.loading = false; this.loading = false;
return null; return null;
@ -69,7 +69,7 @@ class GeniusManager {
const referents = await this.getReferents(song.id); const referents = await this.getReferents(song.id);
const result = { song, referents }; const result = { song, referents };
this.cache.set(track.id, result); this.cache.set(track.id, result);
this.loading = false; this.loading = false;
return result; return result;
@ -82,25 +82,32 @@ class GeniusManager {
findAnnotations(lineText, referents) { findAnnotations(lineText, referents) {
if (!referents || !lineText) return []; 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 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); const lineWords = getWordSet(normLine);
return referents.filter(ref => { return referents.filter((ref) => {
const normFragment = normalize(ref.fragment); const normFragment = normalize(ref.fragment);
if (normLine.includes(normFragment) || normFragment.includes(normLine)) return true; if (normLine.includes(normFragment) || normFragment.includes(normLine)) return true;
const fragmentWords = getWordSet(normFragment); const fragmentWords = getWordSet(normFragment);
if (fragmentWords.size === 0 || lineWords.size === 0) return false; if (fragmentWords.size === 0 || lineWords.size === 0) return false;
let matchCount = 0; 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) => { this.romajiObserver = new MutationObserver((mutations) => {
// Check if any relevant mutation occurred // Check if any relevant mutation occurred
const hasRelevantChange = mutations.some((mutation) => { const hasRelevantChange = mutations.some((mutation) => {
if (mutation.type === 'childList') { if (mutation.type === 'childList') {
let relevant = false; let relevant = false;
if (mutation.addedNodes.length > 0) { if (mutation.addedNodes.length > 0) {
for (const node of mutation.addedNodes) { 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; relevant = true;
break; break;
} }
} }
if (!relevant && mutation.removedNodes.length > 0) { if (!relevant && mutation.removedNodes.length > 0) {
for (const node of mutation.removedNodes) { 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; relevant = true;
break; break;
} }
@ -628,34 +636,38 @@ export class LyricsManager {
if (lineElements.length === 0) return; 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'); el.classList.remove('genius-annotated', 'genius-multi-start', 'genius-multi-end', 'genius-multi-mid');
delete el.__geniusAnnotations; 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); const fragment = normalize(ref.fragment);
if (!fragment) return; if (!fragment) return;
for (let i = 0; i < lineElements.length; i++) { for (let i = 0; i < lineElements.length; i++) {
let combinedText = ""; let combinedText = '';
let currentLines = []; let currentLines = [];
for (let j = i; j < lineElements.length; j++) { for (let j = i; j < lineElements.length; j++) {
const line = lineElements[j]; const line = lineElements[j];
const lineClone = line.cloneNode(true); const lineClone = line.cloneNode(true);
lineClone.querySelectorAll('.time, .timestamp, [class*="time"], .genius-indicator').forEach(n => n.remove()); lineClone
const text = normalize(lineClone.textContent || ""); .querySelectorAll('.time, .timestamp, [class*="time"], .genius-indicator')
.forEach((n) => n.remove());
const text = normalize(lineClone.textContent || '');
if (!text) continue; if (!text) continue;
if (currentLines.length > 0) combinedText += " "; if (currentLines.length > 0) combinedText += ' ';
combinedText += text; combinedText += text;
currentLines.push(line); currentLines.push(line);
@ -664,7 +676,7 @@ export class LyricsManager {
el.classList.add('genius-annotated'); el.classList.add('genius-annotated');
if (!el.__geniusAnnotations) el.__geniusAnnotations = []; 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); el.__geniusAnnotations.push(ref);
} }
@ -683,10 +695,9 @@ export class LyricsManager {
el.appendChild(smiley); el.appendChild(smiley);
} }
}); });
break; break;
} }
if (combinedText.length > fragment.length + 50) break; if (combinedText.length > fragment.length + 50) break;
} }
} }
@ -754,19 +765,22 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f
geniusBtn.addEventListener('click', async () => { geniusBtn.addEventListener('click', async () => {
manager.isGeniusMode = !manager.isGeniusMode; manager.isGeniusMode = !manager.isGeniusMode;
const enabled = manager.isGeniusMode; const enabled = manager.isGeniusMode;
geniusBtn.classList.toggle('active-genius', enabled); geniusBtn.classList.toggle('active-genius', enabled);
geniusBtn.style.color = enabled ? '#ffff64' : ''; geniusBtn.style.color = enabled ? '#ffff64' : '';
geniusBtn.innerHTML = enabled ? SVG_GENIUS_ACTIVE : SVG_GENIUS_INACTIVE; geniusBtn.innerHTML = enabled ? SVG_GENIUS_ACTIVE : SVG_GENIUS_INACTIVE;
if (enabled) { if (enabled) {
try { try {
geniusBtn.style.opacity = '0.5'; geniusBtn.style.opacity = '0.5';
await manager.geniusManager.getDataForTrack(track); await manager.geniusManager.getDataForTrack(track);
manager.currentGeniusData = manager.geniusManager.cache.get(track.id); manager.currentGeniusData = manager.geniusManager.cache.get(track.id);
const amLyrics = sidePanelManager.panel.querySelector('am-lyrics'); 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) { } catch (e) {
alert(e.message); alert(e.message);
manager.isGeniusMode = false; manager.isGeniusMode = false;
@ -776,13 +790,17 @@ export function openLyricsPanel(track, audioPlayer, lyricsManager, forceOpen = f
geniusBtn.style.opacity = '1'; geniusBtn.style.opacity = '1';
} }
} else { } else {
const amLyrics = sidePanelManager.panel.querySelector('am-lyrics'); const amLyrics = sidePanelManager.panel.querySelector('am-lyrics');
if (amLyrics) { if (amLyrics) {
const root = amLyrics.shadowRoot || amLyrics; const root = amLyrics.shadowRoot || amLyrics;
const lineElements = Array.from(root.querySelectorAll('.genius-annotated')); const lineElements = Array.from(root.querySelectorAll('.genius-annotated'));
lineElements.forEach(el => { lineElements.forEach((el) => {
el.classList.remove('genius-annotated', 'genius-multi-start', 'genius-multi-end', 'genius-multi-mid'); el.classList.remove(
'genius-annotated',
'genius-multi-start',
'genius-multi-end',
'genius-multi-mid'
);
delete el.__geniusAnnotations; delete el.__geniusAnnotations;
}); });
} }
@ -848,18 +866,22 @@ async function renderLyricsComponent(container, track, audioPlayer, lyricsManage
await lyricsManager.loadKuroshiro(); await lyricsManager.loadKuroshiro();
} }
lyricsManager
lyricsManager.fetchLyrics(track.id, track).then(async () => { .fetchLyrics(track.id, track)
if (lyricsManager.isGeniusMode) { .then(async () => {
try { if (lyricsManager.isGeniusMode) {
const data = await lyricsManager.geniusManager.getDataForTrack(track); try {
if (data) { const data = await lyricsManager.geniusManager.getDataForTrack(track);
lyricsManager.currentGeniusData = data; if (data) {
lyricsManager.applyGeniusAnnotations(amLyrics, data.referents); 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 // Wait for lyrics to appear, then do an immediate conversion
const waitForLyrics = () => { const waitForLyrics = () => {
@ -955,20 +977,21 @@ function setupSync(track, audioPlayer, amLyrics, lyricsManager) {
const onLineClick = (e) => { const onLineClick = (e) => {
if (e.detail && e.detail.timestamp !== undefined) { if (e.detail && e.detail.timestamp !== undefined) {
const manager = lyricsManager || sidePanelManager.panel.lyricsManager; const manager = lyricsManager || sidePanelManager.panel.lyricsManager;
if (manager && manager.isGeniusMode) { if (manager && manager.isGeniusMode) {
const timestampSeconds = e.detail.timestamp / 1000; const timestampSeconds = e.detail.timestamp / 1000;
const lyricsData = manager.lyricsCache.get(track.id); const lyricsData = manager.lyricsCache.get(track.id);
if (lyricsData && lyricsData.subtitles) { if (lyricsData && lyricsData.subtitles) {
const parsed = manager.parseSyncedLyrics(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) { 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); showGeniusAnnotations(annotations, line.text);
} }
} }
@ -1003,13 +1026,12 @@ function setupSync(track, audioPlayer, amLyrics, lyricsManager) {
} }
function showGeniusAnnotations(annotations, lineText) { function showGeniusAnnotations(annotations, lineText) {
const existing = document.querySelector('.genius-annotation-modal'); const existing = document.querySelector('.genius-annotation-modal');
if (existing) existing.remove(); if (existing) existing.remove();
const modal = document.createElement('div'); const modal = document.createElement('div');
modal.className = 'genius-annotation-modal'; modal.className = 'genius-annotation-modal';
let contentHtml = ` let contentHtml = `
<div class="genius-modal-content"> <div class="genius-modal-content">
<div class="genius-header"> <div class="genius-header">
@ -1026,7 +1048,7 @@ function showGeniusAnnotations(annotations, lineText) {
</div> </div>
`; `;
} else { } else {
annotations.forEach(ann => { annotations.forEach((ann) => {
const body = ann.annotations[0].body.plain; const body = ann.annotations[0].body.plain;
contentHtml += ` contentHtml += `
<div class="annotation-item"> <div class="annotation-item">
@ -1043,7 +1065,9 @@ function showGeniusAnnotations(annotations, lineText) {
modal.querySelector('.close-genius').addEventListener('click', () => modal.remove()); 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) { export async function renderLyricsInFullscreen(track, audioPlayer, lyricsManager, container) {

View file

@ -22,22 +22,22 @@ export async function addMetadataToAudio(audioBlob, track, api, quality) {
const buffer = await audioBlob.slice(0, 4).arrayBuffer(); const buffer = await audioBlob.slice(0, 4).arrayBuffer();
const view = new DataView(buffer); const view = new DataView(buffer);
const isFlac = view.byteLength >= 4 && const isFlac =
view.byteLength >= 4 &&
view.getUint8(0) === 0x66 && // f view.getUint8(0) === 0x66 && // f
view.getUint8(1) === 0x4c && // L view.getUint8(1) === 0x4c && // L
view.getUint8(2) === 0x61 && // a 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') { if (mime === 'audio/flac') {
return await addFlacMetadata(audioBlob, track, api); return await addFlacMetadata(audioBlob, track, api);
} }
if (mime === 'audio/mp4') { if (mime === 'audio/mp4') {
return await addM4aMetadata(audioBlob, track, api); return await addM4aMetadata(audioBlob, track, api);
} }
} }
/** /**

View file

@ -319,7 +319,10 @@ export class Player {
} }
streamUrl = track.audioUrl; 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; streamUrl = track.remoteUrl;
} }

View file

@ -11,7 +11,6 @@ export function navigate(path) {
export function createRouter(ui) { export function createRouter(ui) {
const router = async () => { const router = async () => {
if (window.location.hash && window.location.hash.length > 1) { if (window.location.hash && window.location.hash.length > 1) {
const hash = window.location.hash.substring(1); const hash = window.location.hash.substring(1);
if (hash.includes('/')) { if (hash.includes('/')) {
@ -21,7 +20,7 @@ export function createRouter(ui) {
} }
let path = window.location.pathname; let path = window.location.pathname;
if (path.startsWith('/')) path = path.substring(1); if (path.startsWith('/')) path = path.substring(1);
if (path.endsWith('/')) path = path.substring(0, path.length - 1); if (path.endsWith('/')) path = path.substring(0, path.length - 1);
if (path === '' || path === 'index.html') path = 'home'; if (path === '' || path === 'index.html') path = 'home';

View file

@ -626,7 +626,7 @@ export const visualizerSettings = {
getSensitivity() { getSensitivity() {
try { try {
const val = localStorage.getItem(this.SENSITIVITY_KEY); const val = localStorage.getItem(this.SENSITIVITY_KEY);
if (val === null) return 1.0; if (val === null) return 1.0;
return parseFloat(val); return parseFloat(val);
} catch { } catch {
return 1.0; return 1.0;

View file

@ -10,14 +10,20 @@ async function loadArtistsData() {
const response = await fetch('./artists.ndjson'); const response = await fetch('./artists.ndjson');
if (!response.ok) throw new Error('Network response was not ok'); if (!response.ok) throw new Error('Network response was not ok');
const text = await response.text(); const text = await response.text();
artistsData = text.trim().split('\n') artistsData = text
.filter(line => line.trim()) .trim()
.map(line => { .split('\n')
try { return JSON.parse(line); } catch (e) { return null; } .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) { } 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) { async function fetchTrackerData(sheetId) {
try { 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; if (!response.ok) return null;
return await response.json(); return await response.json();
} catch (e) { } catch (e) {
console.error("Failed to fetch tracker data", e); console.error('Failed to fetch tracker data', e);
return null; return null;
} }
} }
@ -62,21 +70,21 @@ function getDirectUrl(rawUrl) {
function renderLoadButton(container, sheetId, artistName) { function renderLoadButton(container, sheetId, artistName) {
container.innerHTML = ''; container.innerHTML = '';
container.style.display = 'block'; container.style.display = 'block';
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
wrapper.style.textAlign = 'center'; wrapper.style.textAlign = 'center';
wrapper.style.padding = '2rem'; wrapper.style.padding = '2rem';
const button = document.createElement('button'); const button = document.createElement('button');
button.className = 'btn-primary'; button.className = 'btn-primary';
button.textContent = 'Load Unreleased Projects'; button.textContent = 'Load Unreleased Projects';
button.style.fontSize = '1.1rem'; button.style.fontSize = '1.1rem';
button.style.padding = '1rem 2rem'; button.style.padding = '1rem 2rem';
button.onclick = async () => { button.onclick = async () => {
button.textContent = 'Loading...'; button.textContent = 'Loading...';
button.disabled = true; button.disabled = true;
const trackerData = await fetchTrackerData(sheetId); const trackerData = await fetchTrackerData(sheetId);
if (trackerData) { if (trackerData) {
renderTracker(trackerData, container, artistName); renderTracker(trackerData, container, artistName);
@ -88,7 +96,7 @@ function renderLoadButton(container, sheetId, artistName) {
}, 2000); }, 2000);
} }
}; };
wrapper.appendChild(button); wrapper.appendChild(button);
container.appendChild(wrapper); container.appendChild(wrapper);
} }
@ -100,7 +108,7 @@ function renderTracker(trackerData, container, artistName) {
Unreleased Songs & Info Provided By <a href="https://artistgrid.cx" target="_blank" style="text-decoration: underline;">ArtistGrid</a>. Consider Donating to Them. Unreleased Songs & Info Provided By <a href="https://artistgrid.cx" target="_blank" style="text-decoration: underline;">ArtistGrid</a>. Consider Donating to Them.
</p> </p>
`; `;
const erasContainer = document.createElement('div'); const erasContainer = document.createElement('div');
erasContainer.className = 'card-grid'; erasContainer.className = 'card-grid';
erasContainer.style.opacity = '0'; erasContainer.style.opacity = '0';
@ -110,11 +118,11 @@ function renderTracker(trackerData, container, artistName) {
if (!trackerData.eras) return; if (!trackerData.eras) return;
Object.values(trackerData.eras).forEach(era => { Object.values(trackerData.eras).forEach((era) => {
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'card'; card.className = 'card';
card.style.cursor = 'pointer'; card.style.cursor = 'pointer';
const imgWrapper = document.createElement('div'); const imgWrapper = document.createElement('div');
imgWrapper.className = 'card-image-wrapper'; 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.src = era.image ? `https://corsproxy.io/?${encodeURIComponent(era.image)}` : 'assets/logo.svg';
img.alt = era.name; img.alt = era.name;
img.loading = 'lazy'; img.loading = 'lazy';
imgWrapper.appendChild(img); imgWrapper.appendChild(img);
const title = document.createElement('div'); const title = document.createElement('div');
title.className = 'card-title'; title.className = 'card-title';
title.textContent = era.name; title.textContent = era.name;
const subtitle = document.createElement('div'); const subtitle = document.createElement('div');
subtitle.className = 'card-subtitle'; subtitle.className = 'card-subtitle';
subtitle.textContent = era.timeline || 'Unreleased'; subtitle.textContent = era.timeline || 'Unreleased';
@ -137,9 +145,9 @@ function renderTracker(trackerData, container, artistName) {
card.appendChild(imgWrapper); card.appendChild(imgWrapper);
card.appendChild(title); card.appendChild(title);
card.appendChild(subtitle); card.appendChild(subtitle);
card.onclick = () => showEraSongs(era, artistName); card.onclick = () => showEraSongs(era, artistName);
erasContainer.appendChild(card); erasContainer.appendChild(card);
}); });
@ -153,7 +161,6 @@ function showEraSongs(era, artistName) {
const modal = document.getElementById('tracker-modal'); const modal = document.getElementById('tracker-modal');
const overlay = modal.querySelector('.modal-overlay'); const overlay = modal.querySelector('.modal-overlay');
const closeBtn = document.getElementById('close-tracker-modal'); const closeBtn = document.getElementById('close-tracker-modal');
const img = document.getElementById('tracker-header-image'); const img = document.getElementById('tracker-header-image');
const title = document.getElementById('tracker-header-title'); const title = document.getElementById('tracker-header-title');
@ -166,7 +173,7 @@ function showEraSongs(era, artistName) {
const trackList = document.getElementById('tracker-tracklist'); const trackList = document.getElementById('tracker-tracklist');
const filterContainer = document.getElementById('tracker-filters'); const filterContainer = document.getElementById('tracker-filters');
filterContainer.innerHTML = ''; filterContainer.innerHTML = '';
while (trackList.lastElementChild && !trackList.lastElementChild.classList.contains('track-list-header')) { while (trackList.lastElementChild && !trackList.lastElementChild.classList.contains('track-list-header')) {
trackList.removeChild(trackList.lastElementChild); trackList.removeChild(trackList.lastElementChild);
@ -178,15 +185,15 @@ function showEraSongs(era, artistName) {
{ label: 'Special', emoji: '✨' }, { label: 'Special', emoji: '✨' },
{ label: 'Grails', emoji: '🏆' }, { label: 'Grails', emoji: '🏆' },
{ label: 'Wanted', emoji: '🥇' }, { label: 'Wanted', emoji: '🥇' },
{ label: 'Worst Of', emoji: '🗑️' } { label: 'Worst Of', emoji: '🗑️' },
]; ];
let activeFilter = ''; let activeFilter = '';
const applyFilter = () => { const applyFilter = () => {
const items = trackList.querySelectorAll('.track-item'); const items = trackList.querySelectorAll('.track-item');
items.forEach(item => { items.forEach((item) => {
const titleEl = item.querySelector('.title'); const titleEl = item.querySelector('.title');
if (titleEl) { if (titleEl) {
const title = titleEl.textContent.trim(); const title = titleEl.textContent.trim();
@ -199,37 +206,37 @@ function showEraSongs(era, artistName) {
}); });
const categories = trackList.querySelectorAll('h4'); const categories = trackList.querySelectorAll('h4');
categories.forEach(cat => { categories.forEach((cat) => {
let next = cat.nextElementSibling; let next = cat.nextElementSibling;
let hasVisibleItems = false; let hasVisibleItems = false;
while(next && next.tagName !== 'H4') { while (next && next.tagName !== 'H4') {
if (next.classList.contains('track-item') && next.style.display !== 'none') { if (next.classList.contains('track-item') && next.style.display !== 'none') {
hasVisibleItems = true; hasVisibleItems = true;
break; break;
} }
next = next.nextElementSibling; next = next.nextElementSibling;
} }
cat.style.display = hasVisibleItems ? 'block' : 'none'; cat.style.display = hasVisibleItems ? 'block' : 'none';
}); });
}; };
filters.forEach(filter => { filters.forEach((filter) => {
const btn = document.createElement('button'); const btn = document.createElement('button');
btn.className = 'btn-secondary'; btn.className = 'btn-secondary';
btn.textContent = filter.emoji ? `${filter.emoji} ${filter.label}` : filter.label; btn.textContent = filter.emoji ? `${filter.emoji} ${filter.label}` : filter.label;
btn.style.fontSize = '0.85rem'; btn.style.fontSize = '0.85rem';
btn.style.padding = '0.4rem 0.8rem'; btn.style.padding = '0.4rem 0.8rem';
btn.style.borderRadius = '2rem'; btn.style.borderRadius = '2rem';
if (filter.emoji === '') { if (filter.emoji === '') {
btn.style.backgroundColor = 'var(--primary)'; btn.style.backgroundColor = 'var(--primary)';
btn.style.color = 'var(--primary-foreground)'; btn.style.color = 'var(--primary-foreground)';
} }
btn.onclick = () => { btn.onclick = () => {
Array.from(filterContainer.children).forEach(b => { Array.from(filterContainer.children).forEach((b) => {
b.style.backgroundColor = ''; b.style.backgroundColor = '';
b.style.color = ''; b.style.color = '';
}); });
@ -248,7 +255,7 @@ function showEraSongs(era, artistName) {
if (era.data) { if (era.data) {
Object.entries(era.data).forEach(([category, songs]) => { Object.entries(era.data).forEach(([category, songs]) => {
if (!songs || songs.length === 0) return; if (!songs || songs.length === 0) return;
const catTitle = document.createElement('h4'); const catTitle = document.createElement('h4');
catTitle.textContent = category; catTitle.textContent = category;
catTitle.style.padding = '1rem 0.5rem 0.5rem'; 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; const isValidUrl = (u) => u && typeof u === 'string' && u.trim().length > 0;
songs.forEach(song => { songs.forEach((song) => {
const trackItem = document.createElement('div'); const trackItem = document.createElement('div');
trackItem.className = 'track-item'; trackItem.className = 'track-item';
trackItem.innerHTML = ` trackItem.innerHTML = `
<div class="track-number">${globalIndex++}</div> <div class="track-number">${globalIndex++}</div>
<div class="track-item-info"> <div class="track-item-info">
@ -287,27 +294,40 @@ function showEraSongs(era, artistName) {
e.preventDefault(); e.preventDefault();
const contextMenu = document.getElementById('context-menu'); const contextMenu = document.getElementById('context-menu');
if (contextMenu) { 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 directUrl = getDirectUrl(rawUrl);
const track = { const track = {
id: `tracker-${song.name}`, id: `tracker-${song.name}`,
title: song.name, title: song.name,
artist: { name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }, artist: {
artists: [{ name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }], name:
artistName ||
document.getElementById('artist-detail-name')?.textContent ||
'Unknown Artist',
},
artists: [
{
name:
artistName ||
document.getElementById('artist-detail-name')?.textContent ||
'Unknown Artist',
},
],
album: { album: {
title: era.name, title: era.name,
cover: era.image cover: era.image,
}, },
duration: parseDuration(song.track_length), duration: parseDuration(song.track_length),
isTracker: true, isTracker: true,
audioUrl: directUrl, audioUrl: directUrl,
remoteUrl: directUrl remoteUrl: directUrl,
}; };
contextMenu._contextTrack = track; 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}"]`); const item = contextMenu.querySelector(`[data-action="${action}"]`);
if (item) item.style.display = 'none'; if (item) item.style.display = 'none';
}); });
@ -326,7 +346,7 @@ function showEraSongs(era, artistName) {
if (hasValidUrl) { if (hasValidUrl) {
trackItem.onclick = async () => { trackItem.onclick = async () => {
if (song.track_length === '-') { 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'); if (targetUrl) window.open(targetUrl, '_blank');
return; return;
} }
@ -337,8 +357,9 @@ function showEraSongs(era, artistName) {
trackItem.classList.add('loading'); trackItem.classList.add('loading');
const trackNumEl = trackItem.querySelector('.track-number'); const trackNumEl = trackItem.querySelector('.track-number');
const originalNum = trackNumEl.textContent; const originalNum = trackNumEl.textContent;
trackNumEl.innerHTML = '<svg class="animate-spin" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle></svg>'; trackNumEl.innerHTML =
'<svg class="animate-spin" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle></svg>';
let urlsToTry = []; let urlsToTry = [];
if (isValidUrl(song.url)) { if (isValidUrl(song.url)) {
urlsToTry.push(song.url); urlsToTry.push(song.url);
@ -349,12 +370,12 @@ function showEraSongs(era, artistName) {
let audioUrl = null; let audioUrl = null;
let successfulUrl = null; let successfulUrl = null;
for (let rawUrl of urlsToTry) { for (let rawUrl of urlsToTry) {
console.log(`Trying: ${rawUrl}`); console.log(`Trying: ${rawUrl}`);
let downloadUrl = rawUrl; let downloadUrl = rawUrl;
if (rawUrl.includes('pillows.su/f/')) { if (rawUrl.includes('pillows.su/f/')) {
const match = rawUrl.match(/pillows\.su\/f\/([a-f0-9]+)/); const match = rawUrl.match(/pillows\.su\/f\/([a-f0-9]+)/);
if (match) { if (match) {
@ -370,12 +391,14 @@ function showEraSongs(era, artistName) {
try { try {
console.log(`Fetching: ${downloadUrl}`); console.log(`Fetching: ${downloadUrl}`);
const response = await fetch(downloadUrl); const response = await fetch(downloadUrl);
if (response.ok) { if (response.ok) {
const contentType = response.headers.get('content-type') || ''; const contentType = response.headers.get('content-type') || '';
if (contentType.includes('audio/') || if (
contentType.includes('audio/') ||
contentType.includes('mpeg') || contentType.includes('mpeg') ||
contentType.includes('octet-stream')) { contentType.includes('octet-stream')
) {
const arrayBuffer = await response.arrayBuffer(); const arrayBuffer = await response.arrayBuffer();
if (arrayBuffer.byteLength > 1000) { if (arrayBuffer.byteLength > 1000) {
const blob = new Blob([arrayBuffer], { type: 'audio/mpeg' }); const blob = new Blob([arrayBuffer], { type: 'audio/mpeg' });
@ -394,26 +417,43 @@ function showEraSongs(era, artistName) {
document.body.style.cursor = 'default'; document.body.style.cursor = 'default';
trackItem.classList.remove('loading'); trackItem.classList.remove('loading');
trackNumEl.textContent = originalNum; trackNumEl.textContent = originalNum;
if (!audioUrl) { 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; return;
} }
if (globalPlayer) { if (globalPlayer) {
const track = { const track = {
id: `tracker-${song.name}`, id: `tracker-${song.name}`,
title: song.name, title: song.name,
artist: { name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }, artist: {
artists: [{ name: artistName || document.getElementById('artist-detail-name')?.textContent || 'Unknown Artist' }], name:
artistName ||
document.getElementById('artist-detail-name')?.textContent ||
'Unknown Artist',
},
artists: [
{
name:
artistName ||
document.getElementById('artist-detail-name')?.textContent ||
'Unknown Artist',
},
],
album: { album: {
title: era.name, title: era.name,
cover: era.image cover: era.image,
}, },
duration: parseDuration(song.track_length), duration: parseDuration(song.track_length),
isTracker: true, isTracker: true,
audioUrl: audioUrl, 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); globalPlayer.setQueue([track], 0);
@ -455,18 +495,18 @@ export async function initTracker(player, ui) {
const observer = new MutationObserver(async () => { const observer = new MutationObserver(async () => {
const artistNameEl = document.getElementById('artist-detail-name'); const artistNameEl = document.getElementById('artist-detail-name');
const trackerSection = document.getElementById('artist-tracker-section'); const trackerSection = document.getElementById('artist-tracker-section');
if (artistNameEl && trackerSection && artistNameEl.textContent) { if (artistNameEl && trackerSection && artistNameEl.textContent) {
const artistName = artistNameEl.textContent.trim(); const artistName = artistNameEl.textContent.trim();
if (trackerSection.dataset.artist === artistName) return; if (trackerSection.dataset.artist === artistName) return;
trackerSection.dataset.artist = artistName; trackerSection.dataset.artist = artistName;
trackerSection.innerHTML = ''; trackerSection.innerHTML = '';
trackerSection.style.display = 'none'; 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) { if (artistEntry && artistEntry.url) {
const sheetId = getSheetId(artistEntry.url); const sheetId = getSheetId(artistEntry.url);
if (sheetId) { if (sheetId) {
@ -480,4 +520,4 @@ export async function initTracker(player, ui) {
if (artistPage) { if (artistPage) {
observer.observe(artistPage, { attributes: true, childList: true, subtree: true }); observer.observe(artistPage, { attributes: true, childList: true, subtree: true });
} }
} }

View file

@ -414,7 +414,7 @@ export function initializeUIInteractions(player, api, ui) {
if (sidePanelManager.isActive('queue')) { if (sidePanelManager.isActive('queue')) {
refreshQueuePanel(); refreshQueuePanel();
} }
const overlay = document.getElementById('fullscreen-cover-overlay'); const overlay = document.getElementById('fullscreen-cover-overlay');
if (overlay && getComputedStyle(overlay).display !== 'none') { if (overlay && getComputedStyle(overlay).display !== 'none') {
ui.updateFullscreenMetadata(player.currentTrack, player.getNextTrack()); ui.updateFullscreenMetadata(player.currentTrack, player.getNextTrack());

126
js/ui.js
View file

@ -685,7 +685,7 @@ export class UIRenderer {
const nextTrackEl = document.getElementById('fullscreen-next-track'); const nextTrackEl = document.getElementById('fullscreen-next-track');
const coverUrl = this.api.getCoverUrl(track.album?.cover, '1280'); const coverUrl = this.api.getCoverUrl(track.album?.cover, '1280');
const fsLikeBtn = document.getElementById('fs-like-btn'); const fsLikeBtn = document.getElementById('fs-like-btn');
if (fsLikeBtn) { if (fsLikeBtn) {
this.updateLikeState(fsLikeBtn.parentElement, 'track', track.id); this.updateLikeState(fsLikeBtn.parentElement, 'track', track.id);
@ -787,7 +787,7 @@ export class UIRenderer {
closeFullscreenCover() { closeFullscreenCover() {
const overlay = document.getElementById('fullscreen-cover-overlay'); const overlay = document.getElementById('fullscreen-cover-overlay');
overlay.style.display = 'none'; overlay.style.display = 'none';
const playerBar = document.querySelector('.now-playing-bar'); const playerBar = document.querySelector('.now-playing-bar');
if (playerBar) playerBar.style.removeProperty('display'); if (playerBar) playerBar.style.removeProperty('display');
@ -824,34 +824,38 @@ export class UIRenderer {
lastPausedState = isPaused; lastPausedState = isPaused;
if (isPaused) { if (isPaused) {
playBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>'; playBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>';
} else { } else {
playBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>'; playBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"></rect><rect x="14" y="4" width="4" height="16"></rect></svg>';
} }
}; };
updatePlayBtn(); updatePlayBtn();
playBtn.onclick = () => { playBtn.onclick = () => {
this.player.handlePlayPause(); this.player.handlePlayPause();
updatePlayBtn(); updatePlayBtn();
}; };
prevBtn.onclick = () => this.player.playPrev(); prevBtn.onclick = () => this.player.playPrev();
nextBtn.onclick = () => this.player.playNext(); nextBtn.onclick = () => this.player.playNext();
shuffleBtn.onclick = () => { shuffleBtn.onclick = () => {
this.player.toggleShuffle(); this.player.toggleShuffle();
shuffleBtn.classList.toggle('active', this.player.shuffleActive); shuffleBtn.classList.toggle('active', this.player.shuffleActive);
}; };
repeatBtn.onclick = () => { repeatBtn.onclick = () => {
const mode = this.player.toggleRepeat(); const mode = this.player.toggleRepeat();
repeatBtn.classList.toggle('active', mode !== 0); repeatBtn.classList.toggle('active', mode !== 0);
if (mode === 2) { if (mode === 2) {
repeatBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/><path d="M11 10h1v4"/></svg>'; repeatBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/><path d="M11 10h1v4"/></svg>';
} else { } else {
repeatBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></svg>'; repeatBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></svg>';
} }
}; };
@ -883,26 +887,27 @@ export class UIRenderer {
const mode = this.player.repeatMode; const mode = this.player.repeatMode;
repeatBtn.classList.toggle('active', mode !== 0); repeatBtn.classList.toggle('active', mode !== 0);
if (mode === 2) { if (mode === 2) {
repeatBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/><path d="M11 10h1v4"/></svg>'; repeatBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m17 2 4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="m7 22-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/><path d="M11 10h1v4"/></svg>';
} }
const update = () => { const update = () => {
if (document.getElementById('fullscreen-cover-overlay').style.display === 'none') return; if (document.getElementById('fullscreen-cover-overlay').style.display === 'none') return;
const duration = audioPlayer.duration || 0; const duration = audioPlayer.duration || 0;
const current = audioPlayer.currentTime || 0; const current = audioPlayer.currentTime || 0;
if (duration > 0) { if (duration > 0) {
const percent = (current / duration) * 100; const percent = (current / duration) * 100;
progressFill.style.width = `${percent}%`; progressFill.style.width = `${percent}%`;
currentTimeEl.textContent = formatTime(current); currentTimeEl.textContent = formatTime(current);
totalDurationEl.textContent = formatTime(duration); totalDurationEl.textContent = formatTime(duration);
} }
updatePlayBtn(); updatePlayBtn();
this.fullscreenUpdateInterval = requestAnimationFrame(update); this.fullscreenUpdateInterval = requestAnimationFrame(update);
}; };
if (this.fullscreenUpdateInterval) cancelAnimationFrame(this.fullscreenUpdateInterval); if (this.fullscreenUpdateInterval) cancelAnimationFrame(this.fullscreenUpdateInterval);
this.fullscreenUpdateInterval = requestAnimationFrame(update); this.fullscreenUpdateInterval = requestAnimationFrame(update);
} }
@ -913,7 +918,10 @@ export class UIRenderer {
}); });
document.querySelectorAll('.sidebar-nav a').forEach((link) => { 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; document.querySelector('.main-content').scrollTop = 0;
@ -939,7 +947,7 @@ export class UIRenderer {
const yearEl = document.getElementById('track-detail-year'); const yearEl = document.getElementById('track-detail-year');
const albumSection = document.getElementById('track-album-section'); const albumSection = document.getElementById('track-album-section');
const albumTracksContainer = document.getElementById('track-detail-album-tracks'); const albumTracksContainer = document.getElementById('track-detail-album-tracks');
const playBtn = document.getElementById('play-track-btn'); const playBtn = document.getElementById('play-track-btn');
const lyricsBtn = document.getElementById('track-lyrics-btn'); const lyricsBtn = document.getElementById('track-lyrics-btn');
const shareBtn = document.getElementById('share-track-btn'); const shareBtn = document.getElementById('share-track-btn');
@ -957,11 +965,11 @@ export class UIRenderer {
try { try {
const trackData = await this.api.getTrack(trackId); const trackData = await this.api.getTrack(trackId);
const track = trackData.track; const track = trackData.track;
const coverUrl = this.api.getCoverUrl(track.album?.cover); const coverUrl = this.api.getCoverUrl(track.album?.cover);
imageEl.src = coverUrl; imageEl.src = coverUrl;
imageEl.style.backgroundColor = ''; imageEl.style.backgroundColor = '';
this.setPageBackground(coverUrl); this.setPageBackground(coverUrl);
if (backgroundSettings.isEnabled() && track.album?.cover) { if (backgroundSettings.isEnabled() && track.album?.cover) {
this.extractAndApplyColor(this.api.getCoverUrl(track.album.cover, '80')); this.extractAndApplyColor(this.api.getCoverUrl(track.album.cover, '80'));
@ -974,7 +982,7 @@ export class UIRenderer {
artistEl.innerHTML = `<a href="/artist/${track.artist.id}">${escapeHtml(track.artist.name)}</a>`; artistEl.innerHTML = `<a href="/artist/${track.artist.id}">${escapeHtml(track.artist.name)}</a>`;
albumEl.innerHTML = `<a href="/album/${track.album.id}">${escapeHtml(track.album.title)}</a>`; albumEl.innerHTML = `<a href="/album/${track.album.id}">${escapeHtml(track.album.title)}</a>`;
if (track.album.releaseDate) { if (track.album.releaseDate) {
const date = new Date(track.album.releaseDate); const date = new Date(track.album.releaseDate);
yearEl.textContent = date.getFullYear(); yearEl.textContent = date.getFullYear();
@ -1004,7 +1012,7 @@ export class UIRenderer {
this.updateLikeState(likeBtn, 'track', track.id); this.updateLikeState(likeBtn, 'track', track.id);
trackDataStore.set(likeBtn, track); trackDataStore.set(likeBtn, track);
downloadBtn.dataset.action = 'download'; downloadBtn.dataset.action = 'download';
downloadBtn.classList.add('track-action-btn'); downloadBtn.classList.add('track-action-btn');
trackDataStore.set(downloadBtn, track); trackDataStore.set(downloadBtn, track);
@ -1012,10 +1020,10 @@ export class UIRenderer {
if (track.album.id) { if (track.album.id) {
const albumData = await this.api.getAlbum(track.album.id); const albumData = await this.api.getAlbum(track.album.id);
const tracks = albumData.tracks; const tracks = albumData.tracks;
if (tracks.length > 1) { if (tracks.length > 1) {
albumSection.style.display = 'block'; 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); this.renderListWithTracks(albumTracksContainer, otherTracks, false);
} else { } else {
albumSection.style.display = 'none'; albumSection.style.display = 'none';
@ -1121,7 +1129,7 @@ export class UIRenderer {
const folders = await db.getFolders(); const folders = await db.getFolders();
if (foldersContainer) { 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'; foldersContainer.style.display = folders.length ? 'grid' : 'none';
} }
@ -1965,7 +1973,7 @@ export class UIRenderer {
removeBtn.innerHTML = removeBtn.innerHTML =
'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>'; '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>';
removeBtn.dataset.trackId = currentTracks[index].id; removeBtn.dataset.trackId = currentTracks[index].id;
const menuBtn = actionsDiv.querySelector('.track-menu-btn'); const menuBtn = actionsDiv.querySelector('.track-menu-btn');
actionsDiv.insertBefore(removeBtn, menuBtn); actionsDiv.insertBefore(removeBtn, menuBtn);
}); });
@ -1990,7 +1998,9 @@ export class UIRenderer {
} else if (sortType === 'added-oldest') { } else if (sortType === 'added-oldest') {
currentTracks = [...originalTracks].sort((a, b) => (a.addedAt || 0) - (b.addedAt || 0)); currentTracks = [...originalTracks].sort((a, b) => (a.addedAt || 0) - (b.addedAt || 0));
} else if (sortType === 'title') { } 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') { } else if (sortType === 'artist') {
currentTracks = [...originalTracks].sort((a, b) => { currentTracks = [...originalTracks].sort((a, b) => {
const artistA = a.artist?.name || a.artists?.[0]?.name || ''; const artistA = a.artist?.name || a.artists?.[0]?.name || '';
@ -2015,7 +2025,13 @@ export class UIRenderer {
} }
// Render Actions (Shuffle, Edit, Delete, Share, Sort) // 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 = () => { playBtn.onclick = () => {
this.player.setQueue(currentTracks, 0); this.player.setQueue(currentTracks, 0);
@ -2137,9 +2153,11 @@ export class UIRenderer {
if (!folder) throw new Error('Folder not found'); if (!folder) throw new Error('Folder not found');
imageEl.src = folder.cover || '/assets/folder.png'; imageEl.src = folder.cover || '/assets/folder.png';
imageEl.onerror = () => { imageEl.src = '/assets/folder.png'; }; imageEl.onerror = () => {
imageEl.src = '/assets/folder.png';
};
imageEl.style.backgroundColor = ''; imageEl.style.backgroundColor = '';
titleEl.textContent = folder.name; titleEl.textContent = folder.name;
metaEl.textContent = `Created ${new Date(folder.createdAt).toLocaleDateString()}`; 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'); const actionsDiv = document.getElementById('page-playlist').querySelector('.detail-header-actions');
// Cleanup existing dynamic buttons // 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}`); const btn = actionsDiv.querySelector(`#${id}`);
if (btn) btn.remove(); if (btn) btn.remove();
}); });
@ -2493,11 +2517,11 @@ export class UIRenderer {
sortBtn.className = 'btn-secondary'; sortBtn.className = 'btn-secondary';
sortBtn.innerHTML = sortBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M7 12h10"/><path d="M10 18h4"/></svg><span>Sort</span>'; '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M7 12h10"/><path d="M10 18h4"/></svg><span>Sort</span>';
sortBtn.onclick = (e) => { sortBtn.onclick = (e) => {
e.stopPropagation(); e.stopPropagation();
const menu = document.getElementById('sort-menu'); const menu = document.getElementById('sort-menu');
const rect = sortBtn.getBoundingClientRect(); const rect = sortBtn.getBoundingClientRect();
menu.style.top = `${rect.bottom + 5}px`; menu.style.top = `${rect.bottom + 5}px`;
menu.style.left = `${rect.left}px`; menu.style.left = `${rect.left}px`;
@ -2517,7 +2541,7 @@ export class UIRenderer {
}; };
menu.onclick = handleSort; menu.onclick = handleSort;
setTimeout(() => document.addEventListener('click', closeMenu), 0); setTimeout(() => document.addEventListener('click', closeMenu), 0);
}; };
fragment.appendChild(sortBtn); fragment.appendChild(sortBtn);
@ -2776,7 +2800,8 @@ export class UIRenderer {
document.body.classList.add('sidebar-collapsed'); document.body.classList.add('sidebar-collapsed');
const toggleBtn = document.getElementById('sidebar-toggle'); const toggleBtn = document.getElementById('sidebar-toggle');
if (toggleBtn) { if (toggleBtn) {
toggleBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>'; toggleBtn.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>';
} }
const imageEl = document.getElementById('track-detail-image'); const imageEl = document.getElementById('track-detail-image');
@ -2788,7 +2813,7 @@ export class UIRenderer {
const albumTracksContainer = document.getElementById('track-detail-album-tracks'); const albumTracksContainer = document.getElementById('track-detail-album-tracks');
const similarSection = document.getElementById('track-similar-section'); const similarSection = document.getElementById('track-similar-section');
const similarTracksContainer = document.getElementById('track-detail-similar-tracks'); const similarTracksContainer = document.getElementById('track-detail-similar-tracks');
const playBtn = document.getElementById('play-track-btn'); const playBtn = document.getElementById('play-track-btn');
const lyricsBtn = document.getElementById('track-lyrics-btn'); const lyricsBtn = document.getElementById('track-lyrics-btn');
const shareBtn = document.getElementById('share-track-btn'); const shareBtn = document.getElementById('share-track-btn');
@ -2813,11 +2838,11 @@ export class UIRenderer {
try { try {
const track = await this.api.getTrackMetadata(trackId); const track = await this.api.getTrackMetadata(trackId);
const coverUrl = this.api.getCoverUrl(track.album?.cover); const coverUrl = this.api.getCoverUrl(track.album?.cover);
imageEl.src = coverUrl; imageEl.src = coverUrl;
imageEl.style.backgroundColor = ''; imageEl.style.backgroundColor = '';
this.setPageBackground(coverUrl); this.setPageBackground(coverUrl);
if (backgroundSettings.isEnabled() && track.album?.cover) { if (backgroundSettings.isEnabled() && track.album?.cover) {
this.extractAndApplyColor(this.api.getCoverUrl(track.album.cover, '80')); this.extractAndApplyColor(this.api.getCoverUrl(track.album.cover, '80'));
@ -2852,7 +2877,7 @@ export class UIRenderer {
const date = new Date(track.album.releaseDate); const date = new Date(track.album.releaseDate);
yearEl.textContent = date.getFullYear(); yearEl.textContent = date.getFullYear();
} }
if (track.copyright || track.album.copyright) { if (track.copyright || track.album.copyright) {
yearEl.textContent += `${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); this.updateLikeState(likeBtn, 'track', track.id);
trackDataStore.set(likeBtn, track); trackDataStore.set(likeBtn, track);
downloadBtn.dataset.action = 'download'; downloadBtn.dataset.action = 'download';
downloadBtn.classList.add('track-action-btn'); downloadBtn.classList.add('track-action-btn');
trackDataStore.set(downloadBtn, track); trackDataStore.set(downloadBtn, track);
@ -2893,7 +2918,7 @@ export class UIRenderer {
const tracks = albumData.tracks; const tracks = albumData.tracks;
if (tracks.length > 1) { if (tracks.length > 1) {
albumSection.style.display = 'block'; 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); this.renderListWithTracks(albumTracksContainer, otherTracks, false, false, true);
} }
} catch (err) { } catch (err) {
@ -2901,14 +2926,17 @@ export class UIRenderer {
} }
} }
this.api.getRecommendedTracksForPlaylist([track], 5).then(similarTracks => { this.api
if (similarTracks.length > 0) { .getRecommendedTracksForPlaylist([track], 5)
this.renderListWithTracks(similarTracksContainer, similarTracks, true); .then((similarTracks) => {
similarSection.style.display = 'block'; if (similarTracks.length > 0) {
} else { this.renderListWithTracks(similarTracksContainer, similarTracks, true);
similarSection.style.display = 'none'; similarSection.style.display = 'block';
} } else {
}).catch(() => similarSection.style.display = 'none'); similarSection.style.display = 'none';
}
})
.catch(() => (similarSection.style.display = 'none'));
document.title = `${displayTitle} - ${artistName}`; document.title = `${displayTitle} - ${artistName}`;
} catch (e) { } catch (e) {

View file

@ -285,10 +285,14 @@ function resizeImageBlob(blob, size) {
ctx.imageSmoothingEnabled = true; ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high'; ctx.imageSmoothingQuality = 'high';
ctx.drawImage(img, 0, 0, size, size); ctx.drawImage(img, 0, 0, size, size);
canvas.toBlob((resizedBlob) => { canvas.toBlob(
if (resizedBlob) resolve(resizedBlob); (resizedBlob) => {
else reject(new Error('Canvas toBlob failed')); if (resizedBlob) resolve(resizedBlob);
}, blob.type || 'image/jpeg', 0.9); else reject(new Error('Canvas toBlob failed'));
},
blob.type || 'image/jpeg',
0.9
);
}; };
img.onerror = (e) => { img.onerror = (e) => {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
@ -309,18 +313,18 @@ export async function getCoverBlob(api, coverId) {
if (sizeStr.includes('x')) { if (sizeStr.includes('x')) {
sizeStr = sizeStr.split('x')[0]; sizeStr = sizeStr.split('x')[0];
} }
let requestedSize = parseInt(sizeStr, 10); let requestedSize = parseInt(sizeStr, 10);
if (isNaN(requestedSize) || requestedSize <= 0) requestedSize = 1280; if (isNaN(requestedSize) || requestedSize <= 0) requestedSize = 1280;
const cacheKey = `${coverId}-${requestedSize}`; const cacheKey = `${coverId}-${requestedSize}`;
if (coverCache.has(cacheKey)) return coverCache.get(cacheKey); 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]; const supportedSizes = [80, 160, 320, 640, 1280];
let fetchSize = 1280; let fetchSize = 1280;
const bestSize = supportedSizes.find(s => s >= requestedSize); const bestSize = supportedSizes.find((s) => s >= requestedSize);
if (bestSize) { if (bestSize) {
fetchSize = bestSize; fetchSize = bestSize;
} }

View file

@ -11,7 +11,7 @@ export class Visualizer {
this.isActive = false; this.isActive = false;
this.animationId = null; this.animationId = null;
this.particles = []; this.particles = [];
this.kick = 0; this.kick = 0;
this.lastIntensity = 0; this.lastIntensity = 0;
this.lastBeatTime = 0; this.lastBeatTime = 0;
@ -33,7 +33,7 @@ export class Visualizer {
this.source.connect(this.analyser); this.source.connect(this.analyser);
this.analyser.connect(this.audioContext.destination); this.analyser.connect(this.audioContext.destination);
} catch (e) { } 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') { if (this.audioContext.state === 'suspended') {
this.audioContext.resume(); this.audioContext.resume();
} }
this.resize(); this.resize();
window.addEventListener('resize', this.resizeBound); window.addEventListener('resize', this.resizeBound);
this.canvas.style.display = 'block'; this.canvas.style.display = 'block';
this.particles = []; this.particles = [];
this.energyAverage = 0.3; this.energyAverage = 0.3;
this.kick = 0; this.kick = 0;
@ -61,7 +61,7 @@ export class Visualizer {
this.isActive = false; this.isActive = false;
if (this.animationId) cancelAnimationFrame(this.animationId); if (this.animationId) cancelAnimationFrame(this.animationId);
window.removeEventListener('resize', this.resizeBound); window.removeEventListener('resize', this.resizeBound);
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.canvas.style.display = 'none'; this.canvas.style.display = 'none';
} }
@ -93,15 +93,12 @@ export class Visualizer {
for (let i = 0; i < 4; i++) bassSum += dataArray[i]; for (let i = 0; i < 4; i++) bassSum += dataArray[i];
const bass = bassSum / 4 / 255; const bass = bassSum / 4 / 255;
const intensity = bass * bass;
const intensity = bass * bass;
this.energyAverage = this.energyAverage * 0.99 + intensity * 0.01; this.energyAverage = this.energyAverage * 0.99 + intensity * 0.01;
this.upbeatSmoother = this.upbeatSmoother * 0.92 + intensity * 0.08; this.upbeatSmoother = this.upbeatSmoother * 0.92 + intensity * 0.08;
if (visualizerSettings.isSmartIntensityEnabled()) { if (visualizerSettings.isSmartIntensityEnabled()) {
let target = 0.1; let target = 0.1;
if (this.energyAverage > 0.4) { if (this.energyAverage > 0.4) {
@ -113,18 +110,14 @@ export class Visualizer {
sensitivity = target; sensitivity = target;
} }
let threshold = 0.5; let threshold = 0.5;
if (this.energyAverage < 0.3) { if (this.energyAverage < 0.3) {
threshold = 0.5 + (0.3 - this.energyAverage) * 2; threshold = 0.5 + (0.3 - this.energyAverage) * 2;
} }
const now = Date.now(); const now = Date.now();
if (intensity > threshold) { if (intensity > threshold) {
if (intensity > this.lastIntensity + 0.05 && now - this.lastBeatTime > 50) { if (intensity > this.lastIntensity + 0.05 && now - this.lastBeatTime > 50) {
this.kick = 1.0; this.kick = 1.0;
this.lastBeatTime = now; this.lastBeatTime = now;
@ -145,7 +138,6 @@ export class Visualizer {
} }
this.lastIntensity = intensity; this.lastIntensity = intensity;
let shakeX = 0; let shakeX = 0;
let shakeY = 0; let shakeY = 0;
if (this.kick > 0.1) { if (this.kick > 0.1) {
@ -154,8 +146,8 @@ export class Visualizer {
shakeY = (Math.random() - 0.5) * shakeAmt; 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; const particleCount = 180;
if (this.particles.length !== particleCount) { if (this.particles.length !== particleCount) {
@ -167,22 +159,21 @@ export class Visualizer {
vx: (Math.random() - 0.5) * 2, vx: (Math.random() - 0.5) * 2,
vy: (Math.random() - 0.5) * 2, vy: (Math.random() - 0.5) * 2,
size: Math.random() * 3 + 1, size: Math.random() * 3 + 1,
baseSize: Math.random() * 3 + 1 baseSize: Math.random() * 3 + 1,
}); });
} }
} }
ctx.save(); ctx.save();
ctx.translate(shakeX, shakeY); ctx.translate(shakeX, shakeY);
ctx.fillStyle = primaryColor; ctx.fillStyle = primaryColor;
ctx.strokeStyle = primaryColor; ctx.strokeStyle = primaryColor;
for (let i = 0; i < this.particles.length; i++) { for (let i = 0; i < this.particles.length; i++) {
let p = this.particles[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.x += p.vx * speedMult;
p.y += p.vy * speedMult; p.y += p.vy * speedMult;
@ -196,9 +187,8 @@ export class Visualizer {
if (p.y < 0) p.y = h; if (p.y < 0) p.y = h;
if (p.y > h) p.y = 0; if (p.y > h) p.y = 0;
const size = p.baseSize * (1 + intensity * 0.5 + this.kick * 0.8 * sensitivity); 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.beginPath();
ctx.arc(p.x, p.y, size, 0, Math.PI * 2); ctx.arc(p.x, p.y, size, 0, Math.PI * 2);
ctx.fill(); ctx.fill();
@ -207,8 +197,8 @@ export class Visualizer {
const p2 = this.particles[j]; const p2 = this.particles[j];
const dx = p.x - p2.x; const dx = p.x - p2.x;
const dy = p.y - p2.y; 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 maxDist = 150 + intensity * 50 + this.kick * 50 * sensitivity;
const maxDistSq = maxDist * maxDist; const maxDistSq = maxDist * maxDist;
@ -225,4 +215,4 @@ export class Visualizer {
} }
ctx.restore(); ctx.restore();
} }
} }

View file

@ -2076,7 +2076,7 @@ input:checked + .slider::before {
.fullscreen-progress-container .progress-bar { .fullscreen-progress-container .progress-bar {
flex: 1; flex: 1;
height: 6px; height: 6px;
background: rgba(255, 255, 255, 0.2); background: rgb(255, 255, 255, 0.2);
border-radius: 3px; border-radius: 3px;
cursor: pointer; cursor: pointer;
position: relative; position: relative;
@ -2110,7 +2110,7 @@ input:checked + .slider::before {
} }
.fullscreen-buttons button:hover { .fullscreen-buttons button:hover {
background: rgba(255, 255, 255, 0.1); background: rgb(255, 255, 255, 0.1);
transform: scale(1.1); transform: scale(1.1);
} }
@ -3193,7 +3193,7 @@ img[src=''] {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 6rem 2rem 2rem 2rem; padding: 6rem 2rem 2rem;
transition: flex 0.3s ease; transition: flex 0.3s ease;
} }
@ -3627,7 +3627,7 @@ img[src=''] {
padding: var(--spacing-lg); padding: var(--spacing-lg);
} }
.now-playing-bar { .now-playing-bar {
width: calc(96% - 160px) !important; width: calc(96% - 160px) !important;
left: calc(160px + 2%); left: calc(160px + 2%);
} }
@ -4348,7 +4348,6 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* Genius i love genius brah!! */ /* Genius i love genius brah!! */
.genius-annotation-modal { .genius-annotation-modal {
position: fixed; position: fixed;
@ -4357,7 +4356,7 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: rgba(0, 0, 0, 0.6); background: rgb(0, 0, 0, 0.6);
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
animation: fade-in 0.2s ease; animation: fade-in 0.2s ease;
} }
@ -4424,14 +4423,14 @@ body:has(#fullscreen-cover-overlay:not([style*='display: none'])) .now-playing-b
} }
.genius-annotated { .genius-annotated {
background-color: rgba(255, 255, 100, 0.1); background-color: rgb(255, 255, 100, 0.1);
cursor: pointer; cursor: pointer;
border-radius: 4px; border-radius: 4px;
transition: background-color 0.2s; transition: background-color 0.2s;
} }
.genius-annotated:hover { .genius-annotated:hover {
background-color: rgba(255, 255, 100, 0.2); background-color: rgb(255, 255, 100, 0.2);
} }
.genius-multi-start { .genius-multi-start {
@ -4491,7 +4490,7 @@ body.sidebar-collapsed #sidebar-toggle {
#page-track .detail-header-image { #page-track .detail-header-image {
width: 350px; width: 350px;
height: 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 { #page-track .detail-header-info {
@ -4526,12 +4525,12 @@ body.sidebar-collapsed #sidebar-toggle {
width: 250px; width: 250px;
height: 250px; height: 250px;
} }
#page-track .detail-header-info .title { #page-track .detail-header-info .title {
font-size: 2rem; font-size: 2rem;
} }
} }
.tracker-modal .track-item.loading { .tracker-modal .track-item.loading {
opacity: 0.7; opacity: 0.7;
pointer-events: none; pointer-events: none;
@ -4608,4 +4607,4 @@ body.sidebar-collapsed #sidebar-toggle {
overflow-y: auto; overflow-y: auto;
display: block; display: block;
} }
} }