This commit is contained in:
2026-10-21 11:53:48 -04:00
parent 02e7bcf293
commit ec36013313
4 changed files with 81 additions and 7 deletions
+2 -1
View File
@@ -23,7 +23,7 @@ Higher-value zones are shorter, so scoring big is risky.
| Black | 16 | 46m |
| White | 32 | 23m |
Every press records the zone, the points, and the exact remaining time in milliseconds. Players can view their own score, presses per zone, and press history at any time. Global stats (leaderboard, presses per zone, closest call, full press log) stay hidden until the game ends.
Every press records the zone, the points, and the exact remaining time in milliseconds. Players can view their own score, presses per zone, and press history at any time. A public live leaderboard shows all players ranked by total points, with press counts; ties are sorted alphabetically. Scores refresh every five seconds and immediately after you press or join. Detailed global stats (presses per zone, closest call, full press log) stay hidden until the game ends.
Logged-in players can also join the mailing list.
@@ -89,6 +89,7 @@ Tip: `BUTTON_DURATION_SECONDS=60` makes for quick testing.
| POST | `/api/logout` | ✓ | Revoke the current session token |
| POST | `/api/press` | ✓ | Press the button |
| GET | `/api/me` | ✓ | Your score, zone counts, and press history |
| GET | `/api/leaderboard` | | Public player names, total points, and press counts |
| GET | `/api/stats` | | Global stats (403 until the game ends) |
| POST | `/api/subscribe` | ✓ | `{first_name, email, last_name?, phone?}` |
+1 -1
View File
@@ -17,7 +17,6 @@ function renderSections() {
"leaderboard-section": true,
"stats-section": statsLoaded,
rules: true,
"mailing-list": !!token && !subscribed,
"share-controls": !!currentShareText,
more: true,
donate: true,
@@ -32,6 +31,7 @@ function renderSections() {
document.querySelectorAll("[data-panel]").forEach((panel) => {
panel.hidden = panel.id !== selectedSection;
});
$("mailing-list").hidden = !token || subscribed;
$("section-hint").hidden = selectedSection !== null;
}
+3 -4
View File
@@ -38,7 +38,6 @@
<button type="button" data-section="leaderboard-section" aria-controls="leaderboard-section" aria-expanded="false">Leaderboard</button>
<button type="button" data-section="stats-section" aria-controls="stats-section" aria-expanded="false" hidden>Final stats</button>
<button type="button" data-section="rules" aria-controls="rules" aria-expanded="false">Rules</button>
<button type="button" data-section="mailing-list" aria-controls="mailing-list" aria-expanded="false" hidden>Mailing list</button>
<button type="button" data-section="share-controls" aria-controls="share-controls" aria-expanded="false" hidden>Share</button>
<button type="button" data-section="more" aria-controls="more" aria-expanded="false">More</button>
<button type="button" data-section="donate" aria-controls="donate" aria-expanded="false">Donate</button>
@@ -124,10 +123,9 @@
<section id="more" data-panel hidden>
<h2>More</h2>
<p>Visit <a href="https://www.eom.dev/">Eric's homepage</a> for contact information, social media, and links to his other projects.</p>
</section>
<section id="mailing-list" data-panel hidden>
<h2>Mailing list</h2>
<section id="mailing-list" hidden>
<h3>Mailing list</h3>
<p>Join the mailing list to receive updates on Eric's projects:</p>
<form id="subscribe-form">
<input id="sub-first" maxlength="50" placeholder="First name" required autocomplete="given-name">
@@ -138,6 +136,7 @@
</form>
<p id="subscribe-result" aria-live="polite"></p>
</section>
</section>
<section id="donate" data-panel hidden>
<h2>Donate</h2>
+75 -1
View File
@@ -23,6 +23,7 @@
}
* { box-sizing: border-box; }
[hidden] { display: none !important; }
body {
margin: 0;
@@ -32,12 +33,42 @@ body {
}
main {
max-width: 720px;
max-width: 1160px;
margin: 0 auto;
padding: 1rem;
text-align: center;
display: grid;
grid-template-columns: 340px minmax(0, 1fr);
gap: 2rem;
align-items: start;
}
.game-panel {
position: sticky;
top: 1rem;
z-index: 2;
background: var(--base);
border: 1px solid var(--surface);
border-radius: 16px;
padding: 1rem;
}
.game-panel h1 { font-size: 1.5rem; }
.game-panel #countdown { font-size: clamp(1.8rem, 3.5vw, 2.7rem); }
.game-panel #history-global-summary { font-size: 0.85rem; color: var(--subtext); }
.section-browser { min-width: 0; }
#section-nav { display: flex; flex-wrap: wrap; gap: 0.5rem; }
#section-nav button { flex: 1 0 auto; min-height: 44px; }
#section-nav button[aria-expanded="true"] {
background: var(--mauve);
color: var(--crust);
border-color: var(--mauve);
}
#section-hint { color: var(--subtext); margin-top: 2rem; }
[data-panel] { overflow-x: auto; padding: 1rem; }
[data-panel] h2 { margin-top: 0; }
form { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.5rem; }
form input { min-width: 0; max-width: 100%; }
h1 { letter-spacing: 0.3em; }
a { color: var(--sapphire); }
@@ -117,6 +148,25 @@ table {
}
th, td { padding: 0.3rem 0.6rem; border-bottom: 1px solid var(--surface); }
#leaderboard-section {
padding: 1rem;
background: var(--mantle);
border: 1px solid var(--surface);
border-radius: 12px;
}
#leaderboard-section > p { color: var(--subtext); font-size: 0.9rem; }
#leaderboard-status:empty { display: none; }
.leaderboard-scroll { overflow-x: auto; }
#leaderboard { width: 100%; }
#leaderboard th { color: var(--lavender); }
#leaderboard th:nth-child(2), #leaderboard td:nth-child(2) {
text-align: left;
overflow-wrap: anywhere;
}
#leaderboard td:not(:nth-child(2)) { font-variant-numeric: tabular-nums; }
#leaderboard tbody tr:first-child { color: var(--yellow); }
#leaderboard tbody tr:hover { background: var(--surface); }
input, button {
font-size: 1rem;
padding: 0.4rem 0.8rem;
@@ -171,3 +221,27 @@ button:focus-visible, a:focus-visible, input:focus-visible, summary:focus-visibl
.error { color: var(--red); }
#rules { text-align: left; font-size: 0.9rem; color: var(--subtext); }
@media (max-width: 760px) {
main { display: block; padding: 0.5rem; }
.game-panel {
top: 0;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.25rem 0.5rem;
padding: 0.5rem;
border-radius: 0 0 12px 12px;
box-shadow: 0 6px 16px var(--crust);
}
.game-panel h1 { grid-column: 1 / -1; margin: 0; font-size: 1rem; }
.game-panel section { margin: 0; }
.game-panel #countdown { font-size: clamp(1.4rem, 6vw, 2.5rem); }
#zone-label { font-size: 0.7rem; letter-spacing: 0.1em; }
#zone-bar { margin: 0.35rem auto; }
.game-panel #history-global-summary { margin: 0.25rem 0; font-size: 0.7rem; }
#the-button { width: 84px; height: 84px; font-size: 1rem; border-width: 4px; }
#button-section { align-self: center; }
#press-result { max-width: 110px; margin: 0.4rem 0 0; font-size: 0.7rem; }
#press-result:empty { display: none; }
.section-browser { margin-top: 1rem; }
}