Achievements

This commit is contained in:
2026-10-21 13:19:43 -04:00
parent f9afa8ccbe
commit a9f578571c
6 changed files with 260 additions and 3 deletions
+37
View File
@@ -289,6 +289,42 @@ function tick() {
requestAnimationFrame(tick);
}
function renderAchievements(achievements) {
const list = $("my-achievements");
const signature = JSON.stringify(achievements);
if (list.dataset.achievements === signature) return;
list.dataset.achievements = signature;
$("my-achievement-count").textContent = `${achievements.filter((a) => a.unlocked).length} / ${achievements.length} unlocked`;
list.replaceChildren(...achievements.map((achievement) => {
const item = el("li", undefined, `achievement-card${achievement.unlocked ? " is-unlocked" : ""}`);
const icon = el("span", achievement.unlocked ? "\u2713" : "\u25c7", "achievement-icon");
icon.setAttribute("aria-hidden", "true");
const content = el("div", undefined, "achievement-content");
const heading = el("div", undefined, "achievement-title");
heading.append(el("h4", achievement.name), el("span", achievement.unlocked ? "Unlocked" : "Locked", "achievement-state"));
content.append(heading, el("p", achievement.description));
let progressText = "One well-timed press unlocks this badge.";
if (achievement.id === "full_spectrum") {
progressText = `${achievement.progress} / ${achievement.target} zones visited`;
} else if (achievement.id === "keeping_us_alive") {
progressText = `Longest quiet stretch before your press: ${formatPrecise(achievement.progress)} / ${formatPrecise(achievement.target)} needed`;
}
if (achievement.unlocked) {
progressText = `Unlocked ${new Date(achievement.unlocked_at).toLocaleString()}`;
}
content.append(el("small", progressText));
if (["full_spectrum", "keeping_us_alive"].includes(achievement.id) && !achievement.unlocked) {
const progress = el("progress");
progress.max = achievement.target;
progress.value = achievement.progress;
progress.setAttribute("aria-label", `${achievement.name} progress`);
content.append(progress);
}
item.append(icon, content);
return item;
}));
}
async function loadMe() {
if (!token) return;
try {
@@ -296,6 +332,7 @@ async function loadMe() {
$("my-name").textContent = me.name;
$("my-points").textContent = me.total_points.toLocaleString();
$("my-presses").textContent = me.press_count;
renderAchievements(me.achievements);
const closest = me.personal_bests.closest_call;
const best = me.personal_bests.highest_scoring_press;
$("my-closest-call").textContent = closest ? formatPrecise(closest.remaining_ms) : "--";
+3
View File
@@ -131,6 +131,9 @@
<p id="my-rival-description"></p>
<p class="fine-print">Positions follow the leaderboard: total points, then player name for ties. Any positive points gain puts you ahead of an equal score.</p>
</div>
<div class="header-topline achievements-heading"><h3 id="achievements-heading" class="subheading">Your achievements</h3><span id="my-achievement-count" class="site-badge">Loading badges&hellip;</span></div>
<p class="fine-print">Earned through timing, not volume. Your existing history counts. No bonus points.</p>
<ul id="my-achievements" class="achievement-list" aria-labelledby="achievements-heading"></ul>
<h3 class="subheading">Your scoring zones</h3>
<div class="table-scroll"><table id="my-zones" class="zone-table"></table></div>
<details>
+15
View File
@@ -202,6 +202,21 @@ details[open] summary { margin-bottom: 1rem; }
.rival-card #my-rival-title { margin: 0.8rem 0 0.4rem; font-size: 1rem; color: var(--lavender); overflow-wrap: anywhere; }
#my-rival-description { font-size: 0.85rem; color: var(--text); margin: 0; }
.rival-card .fine-print { font-size: 0.65rem; margin-bottom: 0; }
.achievements-heading { margin: 1.75rem 0 0.5rem; }
.achievements-heading .subheading { margin: 0; }
.achievement-list { display: grid; gap: 0.7rem; list-style: none; margin: 1rem 0; padding: 0; }
.achievement-card { display: flex; align-items: flex-start; gap: 0.85rem; padding: 1rem; background: var(--base); border: 1px solid var(--surface); border-radius: 12px; }
.achievement-icon { display: grid; place-items: center; width: 34px; height: 34px; flex: 0 0 34px; border-radius: 11px; color: var(--subtext); background: var(--surface); font-size: 1.1rem; }
.achievement-content { flex: 1; min-width: 0; }
.achievement-title { display: flex; align-items: center; flex-wrap: wrap; justify-content: space-between; gap: 0.4rem; }
.achievement-title h4 { margin: 0; color: var(--text); font-size: 0.85rem; }
.achievement-state { color: var(--subtext); font-size: 0.65rem; }
.achievement-content p { color: var(--subtext); font-size: 0.75rem; margin: 0.3rem 0; line-height: 1.6; }
.achievement-content small { display: block; color: var(--subtext); font-size: 0.65rem; overflow-wrap: anywhere; }
.achievement-content progress { display: block; width: 100%; height: 5px; margin-top: 0.7rem; accent-color: var(--mauve); }
.achievement-card.is-unlocked { border-color: #a6e3a135; background: linear-gradient(110deg, #a6e3a108, transparent), var(--base); }
.is-unlocked .achievement-icon { background: #a6e3a118; color: var(--green); }
.is-unlocked .achievement-state { color: var(--green); }
#my-subscribed { padding: 0.65rem; border-radius: 8px; background: #a6e3a110; color: var(--green); font-size: 0.75rem; }
#leaderboard-status:empty { display: none; }