//js/tracker.js import { escapeHtml, SVG_DOWNLOAD } from './utils.js'; let artistsData = []; let globalPlayer = null; let globalUi = null; async function loadArtistsData() { try { 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; } }) .filter(item => item !== null); } catch (e) { console.error("Failed to load Artists LIst:", e); } } function getSheetId(url) { if (!url) return null; const match = url.match(/spreadsheets\/d\/([a-zA-Z0-9-_]+)/); return match ? match[1] : null; } async function fetchTrackerData(sheetId) { try { 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); return null; } } function parseDuration(durationStr) { if (!durationStr || durationStr === 'N/A') return 0; const parts = durationStr.split(':'); if (parts.length === 2) { return parseInt(parts[0]) * 60 + parseInt(parts[1]); } return 0; } function getDirectUrl(rawUrl) { if (!rawUrl) return null; if (rawUrl.includes('pillows.su/f/')) { const match = rawUrl.match(/pillows\.su\/f\/([a-f0-9]+)/); if (match) return `https://api.pillows.su/api/download/${match[1]}`; } else if (rawUrl.includes('music.froste.lol/song/')) { const match = rawUrl.match(/music\.froste\.lol\/song\/([a-f0-9]+)/); if (match) return `https://music.froste.lol/song/${match[1]}/download`; } return 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); } else { button.textContent = 'Failed to load'; setTimeout(() => { button.disabled = false; button.textContent = 'Load Unreleased Projects'; }, 2000); } }; wrapper.appendChild(button); container.appendChild(wrapper); } function renderTracker(trackerData, container, artistName) { container.innerHTML = `
Unreleased Songs & Info Provided By ArtistGrid. Consider Donating to Them.
`; const erasContainer = document.createElement('div'); erasContainer.className = 'card-grid'; erasContainer.style.opacity = '0'; erasContainer.style.transform = 'translateY(20px)'; erasContainer.style.transition = 'opacity 0.5s ease, transform 0.5s ease'; container.appendChild(erasContainer); if (!trackerData.eras) return; 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'; const img = document.createElement('img'); img.className = 'card-image'; 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'; card.appendChild(imgWrapper); card.appendChild(title); card.appendChild(subtitle); card.onclick = () => showEraSongs(era, artistName); erasContainer.appendChild(card); }); requestAnimationFrame(() => { erasContainer.style.opacity = '1'; erasContainer.style.transform = 'translateY(0)'; }); } 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'); const meta = document.getElementById('tracker-header-meta'); img.src = era.image ? `https://corsproxy.io/?${encodeURIComponent(era.image)}` : 'assets/logo.svg'; img.alt = era.name; title.textContent = era.name; meta.textContent = `${artistName} • ${era.timeline || 'Unreleased'}`; 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); } const filters = [ { label: 'All', emoji: '' }, { label: 'Best Of', emoji: '⭐' }, { label: 'Special', emoji: '✨' }, { label: 'Grails', emoji: '🏆' }, { label: 'Wanted', emoji: '🥇' }, { label: 'Worst Of', emoji: '🗑️' } ]; let activeFilter = ''; const applyFilter = () => { const items = trackList.querySelectorAll('.track-item'); items.forEach(item => { const titleEl = item.querySelector('.title'); if (titleEl) { const title = titleEl.textContent.trim(); if (activeFilter && !title.startsWith(activeFilter)) { item.style.display = 'none'; } else { item.style.display = ''; } } }); const categories = trackList.querySelectorAll('h4'); categories.forEach(cat => { let next = cat.nextElementSibling; let hasVisibleItems = false; 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 => { 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.onclick = () => { Array.from(filterContainer.children).forEach(b => { b.style.backgroundColor = ''; b.style.color = ''; }); btn.style.backgroundColor = 'var(--primary)'; btn.style.color = 'var(--primary-foreground)'; activeFilter = filter.emoji; applyFilter(); }; filterContainer.appendChild(btn); }); let globalIndex = 1; 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'; catTitle.style.color = 'var(--highlight)'; catTitle.style.fontWeight = '600'; catTitle.style.borderBottom = '1px solid var(--border)'; catTitle.style.marginBottom = '0.5rem'; trackList.appendChild(catTitle); const isValidUrl = (u) => u && typeof u === 'string' && u.trim().length > 0; songs.forEach(song => { const trackItem = document.createElement('div'); trackItem.className = 'track-item'; trackItem.innerHTML = `