/* Lazy classic chunk; its executing singleton stays pinned until reload. */ (window.YT ||= {}).StatsView = { render: async function renderStats() { try { await Lazy.load('feature:stats'); } catch (error) { toast(error.message); return; } if (view.type !== 'stats') return; StatsTrack.flush(); els.listTitle.textContent = 'Stats'; resetListHeaderExtras(); els.listActions.innerHTML = ''; els.status.classList.add('hidden'); const c = els.cards; const S = StatsCore.summarize(data.stats); if (!S.totalPlays && S.totalSec < 60) { c.innerHTML = `
📊

No listening stats yet

Stats start counting now — a song counts as played after 30 seconds. They sync with your online profile.

`; return; } const hrs = (sec) => StatsCore.fmtHours(sec); const delta = S.thisMonthSec - S.lastMonthSec; const deltaTxt = S.lastMonthSec || S.thisMonthSec ? `${delta >= 0 ? '▲ +' : '▼ −'}${(Math.abs(delta) / 3600).toFixed(1)}h vs last month` : ''; const maxArtist = Math.max(1, ...S.topArtists.map((a) => a.plays)); const spark = (vals) => { const w = 96, h = 26, max = Math.max(1, ...vals); const pts = vals.map((v, i) => `${(i / (vals.length - 1)) * w},${h - 3 - (v / max) * (h - 6)}`).join(' '); return ``; }; const heatMax = Math.max(1, ...S.heat.flat().map((x) => x.minutes)); const level = (m) => (m <= 0 ? 0 : Math.min(4, 1 + Math.floor((m / heatMax) * 3.999))); const fmtDay = (k) => new Date(`${k}T12:00:00`).toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' }); // Month label over the first week column of each month. const months = S.heat.map((col, w) => { const d = new Date(`${col[0].date}T12:00:00`); const prev = w ? new Date(`${S.heat[w - 1][0].date}T12:00:00`) : null; return !prev || prev.getMonth() !== d.getMonth() ? d.toLocaleDateString(undefined, { month: 'short' }) : ''; }); const playlistsWith = data.playlists.length; c.innerHTML = `
⏱ Listening this month
${hrs(S.thisMonthSec)}
${escapeHtml(deltaTxt)}${deltaTxt ? ' · ' : ''}${hrs(S.totalSec)} all time
▶ Track plays
${S.totalPlays}
${S.songsPlayed} song${S.songsPlayed === 1 ? '' : 's'} · ${playlistsWith} playlist${playlistsWith === 1 ? '' : 's'}
🔥 Practice streak
${S.streak.current}-day
${S.streak.current ? 'Streak active' : 'Listen 5 min today to start one'} · best ${S.streak.best} day${S.streak.best === 1 ? '' : 's'}
🎤 Top artistsplays this month
${S.topArtists.length ? S.topArtists.map((a) => `
${escapeHtml(a.name)} ${a.plays}
`).join('') : '
No plays this month yet.
'}
🎵 Top songslast 30 days
${S.topSongs.length ? S.topSongs.map((t, i) => `
${String(i + 1).padStart(2, '0')} ${escapeHtml(t.title)}${escapeHtml(t.channel)} ${spark(t.spark)} ${t.plays}
`).join('') : '
No plays in the last 30 days.
'}
🗓 Listening habitslast 16 weeks${S.peak ? ` · peak day: ${S.peak.day}s (avg ${S.peak.avgHours.toFixed(1)} h)` : ''}
MonWedFriSun
${months.map((m) => `${m}`).join('')}
${S.heat.map((col) => col.map((x) => x.future ? '' : ``).join('')).join('')}
Less${[0, 1, 2, 3, 4].map((l) => ``).join('')}More
Show as table ${S.heat.flat().filter((x) => !x.future && x.minutes > 0).reverse().map((x) => ``).join('') || ''}
DayMinutes
${fmtDay(x.date)}${x.minutes}
No listening in this window.
`; // One tooltip for every mark (bars, songs, heat cells). const root = c.querySelector('.stats'); const tip = $('statsTip'); root.addEventListener('pointermove', (e) => { const el = e.target.closest('[data-tip]'); if (!el) { tip.classList.add('hidden'); return; } tip.textContent = el.dataset.tip; tip.classList.remove('hidden'); const r = root.getBoundingClientRect(); const half = tip.offsetWidth / 2 + 4; // keep the whole tip inside the panel tip.style.left = `${Math.min(r.width - half, Math.max(half, e.clientX - r.left))}px`; tip.style.top = `${e.clientY - r.top - 14}px`; }); root.addEventListener('pointerleave', () => tip.classList.add('hidden')); root.querySelectorAll('.st-song').forEach((row) => row.addEventListener('click', () => { const v = data.history.find((x) => x.id === row.dataset.id) || { id: row.dataset.id, title: row.querySelector('b').textContent }; Player.loadVideo(v); })); } };