Leaderboard and dynamic layout

This commit is contained in:
2026-10-21 11:53:47 -04:00
parent 77ec52b053
commit 02e7bcf293
4 changed files with 161 additions and 17 deletions
+58 -5
View File
@@ -9,6 +9,40 @@ let token = localStorage.getItem(TOKEN_KEY);
let statsLoaded = false;
let subscribed = false;
let currentShareText = "";
let selectedSection = null;
function renderSections() {
const available = {
"account-section": true,
"leaderboard-section": true,
"stats-section": statsLoaded,
rules: true,
"mailing-list": !!token && !subscribed,
"share-controls": !!currentShareText,
more: true,
donate: true,
};
if (!available[selectedSection]) selectedSection = null;
document.querySelectorAll("[data-section]").forEach((button) => {
const id = button.dataset.section;
button.hidden = !available[id];
button.setAttribute("aria-expanded", String(selectedSection === id));
if (id === "account-section") button.textContent = token ? "Your score" : "Log in / Join";
});
document.querySelectorAll("[data-panel]").forEach((panel) => {
panel.hidden = panel.id !== selectedSection;
});
$("section-hint").hidden = selectedSection !== null;
}
$("section-nav").addEventListener("click", (event) => {
const button = event.target.closest("[data-section]");
if (!button) return;
selectedSection = selectedSection === button.dataset.section ? null : button.dataset.section;
renderSections();
if (selectedSection === "account-section") loadMe();
if (selectedSection === "leaderboard-section") loadLeaderboard();
});
async function api(method, path, body) {
const headers = { "Content-Type": "application/json" };
@@ -150,13 +184,15 @@ async function refreshState() {
if (first) buildStaticUi();
if (game.ended && !statsLoaded) loadStats();
updateButton();
await loadLeaderboard();
}
function updateButton() {
$("the-button").disabled = !token || !game || game.ended;
$("register-section").hidden = !!token;
$("mailing-list").hidden = !token || subscribed;
$("me-section").hidden = !token;
$("my-subscribed").hidden = !token || !subscribed;
renderSections();
}
function tick() {
@@ -179,7 +215,6 @@ async function loadMe() {
if (!token) return;
try {
const me = await api("GET", "/api/me");
$("me-section").hidden = false;
$("my-name").textContent = me.name;
$("my-points").textContent = me.total_points.toLocaleString();
$("my-presses").textContent = me.press_count;
@@ -202,11 +237,28 @@ async function loadMe() {
}
}
async function loadLeaderboard() {
try {
const { leaderboard } = await api("GET", "/api/leaderboard");
fillTable(
$("leaderboard"),
["Rank", "Player", "Points", "Presses"],
leaderboard.map((player, index) => [
index + 1, player.name, player.total_points.toLocaleString(), player.press_count.toLocaleString(),
]),
);
$("leaderboard").hidden = leaderboard.length === 0;
$("leaderboard-status").textContent = leaderboard.length ? "" : "No players yet. Be the first to join!";
} catch {
$("leaderboard-status").textContent = "Scores could not be refreshed. Retrying automatically...";
}
}
async function loadStats() {
try {
const s = await api("GET", "/api/stats");
statsLoaded = true;
$("stats-section").hidden = false;
renderSections();
const summary = $("stats-summary");
summary.replaceChildren(
el("p", `Lasted ${formatMs(s.ended_at - s.started_at)} \u00b7 ${s.total_players} players \u00b7 ${s.total_presses} presses`),
@@ -242,8 +294,8 @@ async function loadStats() {
$("the-button").addEventListener("click", async () => {
const btn = $("the-button");
btn.disabled = true;
$("share-controls").hidden = true;
currentShareText = "";
renderSections();
try {
const r = await api("POST", "/api/press");
game.deadline = r.deadline;
@@ -251,8 +303,8 @@ $("the-button").addEventListener("click", async () => {
$("press-result").textContent =
`You pressed in the ${r.press.zone} zone with ${formatPrecise(r.press.remaining_ms)} remaining: +${r.press.points} points`;
updateShareLinks();
$("share-controls").hidden = false;
await loadMe();
await loadLeaderboard();
} catch (e) {
$("press-result").textContent = e.message;
await refreshState().catch(() => {});
@@ -277,6 +329,7 @@ $("register-form").addEventListener("submit", async (ev) => {
$("password-input").value = "";
updateButton();
await loadMe();
await loadLeaderboard();
} catch (e) {
$("register-error").textContent = e.message;
}