// Leaderboard page: public leaderboard with tabs for Level, Gold, Kills. // Uses createElement + textContent only — DOM property is never overwritten. const VALUE_LABELS = { level: 'Level', gold: 'Gold', kills: 'Kills' }; let currentType = 'level'; let currentPage = 1; // ─── API helper (no auth needed) ────────────────────────────────────────── async function apiGet(path) { const res = await fetch(path); return res.json(); } // ─── Render: leaderboard entries ────────────────────────────────────────── function renderLeaderboard(entries, pagination) { const tbody = document.getElementById('leaderboard-body'); tbody.replaceChildren(); if (entries.length === 0) { const tr = document.createElement('tr'); const td = document.createElement('td'); td.className = 'placeholder'; td.textContent = 'No heroes yet.'; td.setAttribute('colspan', '4'); tr.appendChild(td); tbody.appendChild(tr); return; } for (const entry of entries) { const tr = document.createElement('tr'); // Rank const rankTd = document.createElement('td'); rankTd.className = 'rank-cell'; rankTd.textContent = entry.rank; tr.appendChild(rankTd); // Name (link to profile) const nameTd = document.createElement('td'); nameTd.className = 'name-cell'; const nameLink = document.createElement('a'); nameLink.href = `/profile.html?name=${encodeURIComponent(entry.name)}`; nameLink.textContent = entry.name; nameTd.appendChild(nameLink); tr.appendChild(nameTd); // Class const classTd = document.createElement('td'); classTd.className = 'class-cell'; classTd.textContent = entry.class; tr.appendChild(classTd); // Value (level / gold / kills) const valueTd = document.createElement('td'); valueTd.className = 'value-cell'; valueTd.textContent = entry[currentType]; tr.appendChild(valueTd); tbody.appendChild(tr); } } // ─── Render: pagination ────────────────────────────────────────────────── function renderPagination(page, totalPages) { const container = document.getElementById('pagination'); container.replaceChildren(); if (totalPages <= 1) return; // Previous button const prevBtn = document.createElement('button'); prevBtn.textContent = '‹'; prevBtn.disabled = page <= 1; prevBtn.addEventListener('click', () => { currentPage = page - 1; loadLeaderboard(); }); container.appendChild(prevBtn); // Page number buttons (show up to 5 pages) let startPage = Math.max(1, page - 2); let endPage = Math.min(totalPages, startPage + 4); if (endPage - startPage < 4) startPage = Math.max(1, endPage - 4); for (let p = startPage; p <= endPage; p++) { const btn = document.createElement('button'); btn.textContent = p; if (p === page) btn.className = 'active'; btn.addEventListener('click', () => { currentPage = p; loadLeaderboard(); }); container.appendChild(btn); } // Next button const nextBtn = document.createElement('button'); nextBtn.textContent = '›'; nextBtn.disabled = page >= totalPages; nextBtn.addEventListener('click', () => { currentPage = page + 1; loadLeaderboard(); }); container.appendChild(nextBtn); } // ─── Load leaderboard data ─────────────────────────────────────────────── async function loadLeaderboard() { const tbody = document.getElementById('leaderboard-body'); const tr = document.createElement('tr'); const td = document.createElement('td'); td.className = 'placeholder'; td.textContent = 'Loading...'; td.setAttribute('colspan', '4'); tr.appendChild(td); tbody.replaceChildren(tr); try { const data = await apiGet(`/api/leaderboard?type=${currentType}&page=${currentPage}`); if (!data || !data.ok) { throw new Error(data?.error?.message || 'Failed to load leaderboard.'); } renderLeaderboard(data.data.entries, data.data.pagination); renderPagination(data.data.pagination.page, data.data.pagination.totalPages); } catch (err) { const errorTr = document.createElement('tr'); const errorTd = document.createElement('td'); errorTd.className = 'placeholder'; errorTd.textContent = 'Failed to load leaderboard.'; errorTd.setAttribute('colspan', '4'); errorTr.appendChild(errorTd); tbody.replaceChildren(errorTr); } } // ─── Tab switching ─────────────────────────────────────────────────────── function switchTab(type) { currentType = type; currentPage = 1; document.querySelectorAll('#leaderboard-tabs .tab').forEach((t) => { const isActive = t.dataset.type === type; t.classList.toggle('active', isActive); t.setAttribute('aria-selected', isActive); }); // Update header document.getElementById('value-header').textContent = VALUE_LABELS[type]; loadLeaderboard(); } // ─── Init ───────────────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('#leaderboard-tabs .tab').forEach((tab) => { tab.addEventListener('click', () => switchTab(tab.dataset.type)); }); loadLeaderboard(); }); // Logout handler document.getElementById('logout-btn')?.addEventListener('click', async (e) => { e.preventDefault(); try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'include', }); } catch (_) { // Ignore } window.location.href = '/login.html'; });