/** * LeaderboardUI — renders the live leaderboard and user chip. */ import { Auth } from '../auth/Auth.js'; import { Leaderboard } from '../auth/Leaderboard.js'; export class LeaderboardUI { constructor() { this._interval = null; } $(id) { return document.getElementById(id); } startAutoRefresh() { if (this._interval) clearInterval(this._interval); this._interval = setInterval(() => { Leaderboard.bumpPeers(); this.render(); }, 12000); } refreshUserChip() { const u = Auth.current(); if (u) { this.$('userName').textContent = u.name || u.email.split('@')[0]; this.$('userAvatar').textContent = (u.name || u.email).slice(0, 1).toUpperCase(); this.$('signOutBtn').style.display = ''; // Sync persisted star count to header this.$('t-stars').textContent = (u.stars || 0).toLocaleString(); } else { this.$('userName').textContent = 'Guest'; this.$('userAvatar').textContent = 'G'; this.$('signOutBtn').style.display = 'none'; this.$('t-stars').textContent = '0'; } } render() { const u = Auth.current(); const rows = Leaderboard.combinedRanks(u); const top = rows.slice(0, 8); const meRow = rows.find(r => r.me); if (meRow && !top.find(r => r.me)) top.push(meRow); const html = top.map(r => { const rankCls = r.rank <= 3 ? ` r${r.rank}` : ''; const meCls = r.me ? ' me' : ''; const rankIcon = r.rank <= 3 ? ['🥇', '🥈', '🥉'][r.rank - 1] : r.rank; return `
${rankIcon}
${r.avatar || r.name.slice(0, 1).toUpperCase()}
${r.me ? 'You' : r.name}
⭐ ${r.stars.toLocaleString()}
`; }).join(''); this.$('lbList').innerHTML = html; this.$('lb-total').textContent = rows.length.toLocaleString(); this.$('lb-globalStars').textContent = rows.reduce((s, r) => s + r.stars, 0).toLocaleString(); this.$('onlineCount').textContent = Leaderboard.onlineCount(); if (u && meRow) this.$('userRank').textContent = `Rank #${meRow.rank} · ⭐ ${u.stars || 0}`; else this.$('userRank').textContent = u ? `⭐ ${u.stars || 0}` : 'Guest mode'; } bumpUserStats(deltaStars, deltaMastered, currentLen) { const u = Auth.current(); if (!u) return; const patch = { stars: (u.stars || 0) + deltaStars, mastered: (u.mastered || 0) + deltaMastered, grade: currentLen, }; Auth.update(patch); // Also push to the backend (no-op when offline). Auth.saveProgress(patch).catch(() => {}); this.refreshUserChip(); this.render(); const fresh = Auth.current(); const el = document.getElementById('t-stars'); el.textContent = (fresh.stars || 0).toLocaleString(); el.classList.remove('bump'); void el.offsetWidth; el.classList.add('bump'); } }