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