style: refinement pass for admin + student SPAs

Targeted fixes on top of the editorial-lecture-hall pass:

- Leaderboard rank columns now align across all rows; medal stripes
  reserve their 3px width on every row (no more 6px shift between
  podium and chasers). Silver bumps to higher-contrast values in both
  light and dark modes.
- Student leaderboard gains a visible "you" highlight (blue stripe,
  blue name + score, small "you" eyebrow under the name). Matches by
  display name since the server's student-facing top5 doesn't include
  student_id.
- Lobby and Finished states share an editorial state-cta treatment:
  display-serif "Ready to start." / "That's a wrap." with a numeric
  cta-stats strip that anchors the right column on a projector.
- "02 PRE-FLIGHT" eyebrow continues the "01 JOIN" sequence on the
  side panel, giving the page a magazine-spread rhythm.
- Live distribution suppresses empty bars when zero submissions and
  shows a calm italic "Bars appear once the first answer comes in."
  line instead.
- Roster orders newest-first; the top three rows light their dot
  green and the freshest row gets a soft pulsing halo, so the
  operator sees the room filling up at a glance.
- Student reveal "Your pick" tag moves to a top-edge ribbon above the
  option text so it stops colliding with the count column on phones.
This commit is contained in:
ameer
2026-05-02 22:11:55 +08:00
parent 029d0dd399
commit b40f05220c
3 changed files with 196 additions and 42 deletions

View File

@@ -314,15 +314,23 @@ function renderFinished(message) {
function renderBoard(rows = []) {
if (!rows || !rows.length) return `<p class="muted small">No scores yet.</p>`;
// The server's student-facing top5 doesn't include student_id, so match
// on display name. Same-name collisions are rare in a single classroom
// session; if it ever happens both rows highlight, which still reads
// as "yours might be one of these" to the student.
const myName = store.me?.name;
return `
<ol class="leaderboard">
${rows.map((r) => `
<li>
${rows.map((r) => {
const isYou = myName && r.name && r.name === myName;
return `
<li class="${isYou ? "is-you" : ""}">
<span class="rank">${r.rank}</span>
<span class="who"><b>${escapeText(r.name)}</b></span>
<span class="score">${r.score}</span>
</li>
`).join("")}
`;
}).join("")}
</ol>
`;
}