overhaul: single-session deployment + redesigned frontend
Backend simplification:
- The server now loads ONE pool JSON from $QUIZ_POOL_PATH at startup and
upserts a single canonical session. The session id comes from the pool
JSON's optional "session_id" field, falling back to $QUIZ_SESSION_ID.
- The multi-quiz / multi-session CRUD API is gone:
DELETED GET/POST /admin/api/quizzes
DELETED POST /admin/api/quizzes/upload
DELETED GET/POST /admin/api/sessions
DELETED GET /admin/login (HTML stub)
DELETED GET /admin/api/sessions/{sid}/csv (replaced by /admin/api/csv)
Replaced with a single-session control surface:
GET /admin/ — serves admin.html unconditionally
GET /admin/api/state — admin-gated; pool meta + state + QR + join URL
POST /admin/api/reset — admin-gated; wipe submissions + back to lobby
POST /admin/logout — clear admin cookie
GET /admin/api/csv — single-session results
WS /ws/instructor/{sid} — kept; new commands "next" + "reset"
- Instructor "Next" button is now a single state-driving command
(RoomManager.advance_to_next): from lobby it opens Q0; from question_open
it closes the current Q and opens the next; from question_closed it
opens the next; if past the last question it ends the session.
- New RoomManager.reset wipes submissions, participants, and per-question
state, then broadcasts a clean lobby.
- Student GET / now redirects to /?sid=<canonical> when no sid is given,
so the QR / share URL is fully deterministic.
Frontend rewrite (functional baseline; visual polish to follow):
- /admin/ is now a single SPA: GET /admin/api/state decides login form
vs dashboard. No separate /admin/login URL bar.
- Admin dashboard is state-driven with one primary action per state.
QR code, join URL, and live participant list are always visible on the
left so the operator can leave the page on a projector.
- Student answer buttons are big and tappable; reveal screen highlights
correct/wrong choice + shows score, total, and rank.
- Static admin/student SPAs share a CSS palette with light/dark support.
Tests rewritten around the single canonical session id.
The auto-bootstrapped session lets each test fixture skip the old
quiz/session creation dance. 39/39 tests pass.
Cleanup:
- Deleted CODEX_PROMPT.md, IMPLEMENTATION_REPORT.md, NOTES.md, SPEC.md,
static/observer.html (obsolete codex-build artifacts and the unused
observer view).
- .gitignore now blocks /pool.json (the runtime file the operator drops
on the server) and the leftover .codex_done / codex_run.log / etc.
- bootstrap.sh seeds /opt/quiz/pool.json from examples/pool_example.json
on first deploy so a fresh box reaches a usable state without manual
intervention; .env now includes QUIZ_POOL_PATH.
This commit is contained in:
711
static/style.css
711
static/style.css
@@ -1,299 +1,490 @@
|
||||
/* ============================================================
|
||||
* Quiz portal — functional baseline stylesheet.
|
||||
* Visual polish (typography, palette, micro-interactions) is layered
|
||||
* on top of this by the frontend-design pass; structural rules and
|
||||
* accessibility-relevant defaults live here.
|
||||
* ============================================================ */
|
||||
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
--bg: #f6f7f9;
|
||||
--surface: #ffffff;
|
||||
--border: #d9dee7;
|
||||
--text: #18212f;
|
||||
--muted: #5b6573;
|
||||
--primary: #0d6b57;
|
||||
--primary-text: #ffffff;
|
||||
--warn: #b67700;
|
||||
--warn-text: #ffffff;
|
||||
--danger: #a43831;
|
||||
--danger-text: #ffffff;
|
||||
--info: #254f7a;
|
||||
--accent: #1c8a72;
|
||||
--correct-bg: #e6f4ed;
|
||||
--correct-border: #199870;
|
||||
--wrong-border: #d04040;
|
||||
--shadow: 0 8px 28px rgba(15, 25, 42, 0.06);
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: #f6f7f9;
|
||||
color: #18212f;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background: #f6f7f9;
|
||||
color: #18212f;
|
||||
min-height: 100dvh;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 16px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.shell {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
h1, h2, h3 { margin: 0 0 0.4rem; line-height: 1.2; }
|
||||
h1 { font-size: 1.4rem; font-weight: 700; letter-spacing: -0.01em; }
|
||||
h2 { font-size: 1.05rem; font-weight: 700; }
|
||||
h3 { font-size: 0.95rem; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
|
||||
p { margin: 0 0 0.6rem; }
|
||||
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.85em; }
|
||||
|
||||
.panel {
|
||||
background: #ffffff;
|
||||
border: 1px solid #d9dee7;
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 10px 30px rgba(19, 33, 54, 0.08);
|
||||
}
|
||||
.muted { color: var(--muted); }
|
||||
.small { font-size: 0.85rem; }
|
||||
.eyebrow { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.08em; margin: 0 0 0.4rem; }
|
||||
|
||||
.narrow {
|
||||
max-width: 440px;
|
||||
margin: 8vh auto;
|
||||
}
|
||||
/* ---------- Layout containers ---------- */
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stack {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
input, textarea, select, button, .button {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
border: 1px solid #b8c0cc;
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
background: #fff;
|
||||
color: #18212f;
|
||||
}
|
||||
|
||||
button, .button {
|
||||
border: 1px solid #9aa7b8;
|
||||
border-radius: 8px;
|
||||
padding: 10px 14px;
|
||||
background: #eef1f5;
|
||||
color: #18212f;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background: #0d6b57;
|
||||
border-color: #0d6b57;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: #254f7a;
|
||||
border-color: #254f7a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background: #a43831;
|
||||
border-color: #a43831;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #a43831;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.status, .score {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin: 20px auto 0;
|
||||
border: 4px solid #cbd3df;
|
||||
border-top-color: #0d6b57;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.quiz-panel h1 {
|
||||
font-size: clamp(1.5rem, 4vw, 2.5rem);
|
||||
}
|
||||
|
||||
.topline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.bar {
|
||||
height: 12px;
|
||||
background: #e0e6ef;
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
margin: 12px 0 24px;
|
||||
}
|
||||
|
||||
.bar span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: #0d6b57;
|
||||
transition: width 0.2s linear;
|
||||
}
|
||||
|
||||
.answers {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.answer {
|
||||
display: grid;
|
||||
grid-template-columns: 42px 1fr;
|
||||
gap: 12px;
|
||||
min-height: 68px;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.answer strong {
|
||||
.bootstrap-loading {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: #254f7a;
|
||||
color: #fff;
|
||||
min-height: 100dvh;
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.histogram {
|
||||
.centered-shell {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.hist-row {
|
||||
display: grid;
|
||||
grid-template-columns: 72px 1fr 48px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
meter {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.leaderboard {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.leaderboard li {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #e2e7ee;
|
||||
}
|
||||
|
||||
.celebration {
|
||||
outline: 6px solid #f2c94c;
|
||||
}
|
||||
|
||||
.admin-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 280px 1fr;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
padding: 20px;
|
||||
background: #223044;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sidebar .list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.sidebar button {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.workspace {
|
||||
place-items: center;
|
||||
min-height: 100dvh;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
/* ---------- Cards & panels ---------- */
|
||||
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--shadow);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.card.narrow { width: min(440px, 100%); }
|
||||
.card-header { margin-bottom: 16px; }
|
||||
.card.center { text-align: center; }
|
||||
|
||||
.panel { padding: 20px; }
|
||||
.panel + .panel { margin-top: 16px; }
|
||||
.panel h2 { margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
|
||||
.panel h2 .count {
|
||||
background: var(--info);
|
||||
color: #fff;
|
||||
border-radius: 999px;
|
||||
padding: 2px 10px;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.stack { display: grid; gap: 14px; }
|
||||
|
||||
/* ---------- Forms ---------- */
|
||||
|
||||
.field { display: grid; gap: 6px; }
|
||||
.field > span { font-weight: 600; font-size: 0.9rem; color: var(--muted); }
|
||||
|
||||
input, textarea, select {
|
||||
font: inherit;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
}
|
||||
input:focus, textarea:focus, select:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 25%, transparent);
|
||||
}
|
||||
|
||||
/* ---------- Buttons ---------- */
|
||||
|
||||
.btn, button {
|
||||
font: inherit;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
padding: 10px 16px;
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
transition: transform 0.05s ease, background 0.15s ease, border-color 0.15s ease;
|
||||
}
|
||||
.btn:active, button:active { transform: translateY(1px); }
|
||||
.btn:disabled, button:disabled { cursor: not-allowed; opacity: 0.55; }
|
||||
|
||||
.btn.primary { background: var(--primary); border-color: var(--primary); color: var(--primary-text); }
|
||||
.btn.warn { background: var(--warn); border-color: var(--warn); color: var(--warn-text); }
|
||||
.btn.danger { background: var(--danger); border-color: var(--danger); color: var(--danger-text); }
|
||||
.btn.ghost { background: transparent; }
|
||||
.btn.block { width: 100%; }
|
||||
.btn.big { padding: 14px 20px; font-size: 1.05rem; font-weight: 600; }
|
||||
.btn.small { padding: 6px 10px; font-size: 0.85rem; }
|
||||
|
||||
/* ---------- Alerts ---------- */
|
||||
|
||||
.alert { padding: 10px 14px; border-radius: 8px; font-size: 0.9rem; }
|
||||
.alert.error { background: color-mix(in srgb, var(--danger) 15%, transparent); border: 1px solid var(--danger); color: var(--danger); }
|
||||
.alert.info { background: color-mix(in srgb, var(--info) 12%, transparent); border: 1px solid var(--info); color: var(--info); }
|
||||
|
||||
/* ---------- Admin topbar ---------- */
|
||||
|
||||
.admin-body { padding-bottom: 32px; }
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
gap: 16px;
|
||||
padding: 20px 24px 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.topbar-title h1 { font-size: 1.25rem; }
|
||||
.topbar-title p { margin: 0; }
|
||||
.topbar-actions { display: flex; gap: 10px; align-items: center; }
|
||||
|
||||
.state-badge {
|
||||
border-radius: 999px;
|
||||
padding: 4px 12px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.state-badge.state-lobby { background: color-mix(in srgb, var(--info) 18%, transparent); color: var(--info); border-color: var(--info); }
|
||||
.state-badge.state-question_open { background: color-mix(in srgb, var(--primary) 18%, transparent); color: var(--primary); border-color: var(--primary); }
|
||||
.state-badge.state-question_closed { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); border-color: var(--warn); }
|
||||
.state-badge.state-finished { background: color-mix(in srgb, var(--accent) 18%, transparent); color: var(--accent); border-color: var(--accent); }
|
||||
.state-badge.state-correct { background: var(--correct-bg); color: var(--accent); border-color: var(--correct-border); }
|
||||
.state-badge.state-wrong { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger); border-color: var(--wrong-border); }
|
||||
|
||||
/* ---------- Admin dashboard layout ---------- */
|
||||
|
||||
.dashboard {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: minmax(280px, 360px) 1fr;
|
||||
padding: 20px 24px;
|
||||
align-items: start;
|
||||
}
|
||||
@media (max-width: 800px) {
|
||||
.dashboard { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.dashboard-side { display: grid; gap: 16px; }
|
||||
.dashboard-main { display: grid; gap: 16px; }
|
||||
|
||||
/* ---------- Join panel (QR + URL + roster) ---------- */
|
||||
|
||||
.qr-wrap {
|
||||
background: #fff;
|
||||
padding: 14px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.qr-wrap img { width: 100%; height: auto; max-width: 280px; display: block; }
|
||||
.qr-fallback { padding: 40px; color: var(--muted); }
|
||||
|
||||
.join-url-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
||||
.join-url {
|
||||
flex: 1 1 200px;
|
||||
background: color-mix(in srgb, var(--info) 8%, transparent);
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.85rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.roster {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.roster li { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--border); }
|
||||
.roster li:last-child { border-bottom: none; }
|
||||
.roster .dot { width: 8px; height: 8px; border-radius: 999px; background: var(--accent); flex-shrink: 0; }
|
||||
.roster .who { display: grid; line-height: 1.2; }
|
||||
.roster .who small { color: var(--muted); font-size: 0.8rem; }
|
||||
|
||||
/* ---------- State CTA panel (lobby / finished) ---------- */
|
||||
|
||||
.state-cta { display: grid; gap: 10px; }
|
||||
.state-cta .btn.big { justify-self: start; }
|
||||
|
||||
.action-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 16px; }
|
||||
|
||||
/* ---------- Question card (admin + student share) ---------- */
|
||||
|
||||
.question-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.qnum { font-weight: 700; color: var(--muted); font-size: 0.9rem; }
|
||||
.countdown {
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.roster span {
|
||||
border: 1px solid #ccd4df;
|
||||
.qbar {
|
||||
height: 8px;
|
||||
background: color-mix(in srgb, var(--primary) 14%, transparent);
|
||||
border-radius: 999px;
|
||||
padding: 6px 10px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.qbar span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: var(--primary);
|
||||
transition: width 0.2s linear;
|
||||
}
|
||||
|
||||
.qr {
|
||||
width: min(280px, 100%);
|
||||
height: auto;
|
||||
background: #fff;
|
||||
padding: 12px;
|
||||
.question-text { font-size: 1.2rem; font-weight: 600; margin: 8px 0 16px; }
|
||||
.question-text.small { font-size: 1rem; }
|
||||
|
||||
.options {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.options li {
|
||||
display: grid;
|
||||
grid-template-columns: 36px 1fr auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
background: color-mix(in srgb, var(--info) 4%, transparent);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.options .key {
|
||||
background: var(--info);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
border-radius: 6px;
|
||||
padding: 4px 0;
|
||||
text-align: center;
|
||||
width: 36px;
|
||||
}
|
||||
.options .opt-text { color: var(--text); }
|
||||
.options .opt-count { color: var(--muted); font-size: 0.85rem; }
|
||||
|
||||
.options.reveal li.correct {
|
||||
background: var(--correct-bg);
|
||||
border-color: var(--correct-border);
|
||||
}
|
||||
.options.reveal li.correct .key { background: var(--correct-border); }
|
||||
.options.reveal li.wrong-pick { border-color: var(--wrong-border); }
|
||||
.options.reveal li.wrong-pick .key { background: var(--danger); }
|
||||
|
||||
.explanation {
|
||||
background: color-mix(in srgb, var(--accent) 8%, transparent);
|
||||
padding: 12px 14px;
|
||||
border-radius: 10px;
|
||||
border-left: 3px solid var(--accent);
|
||||
margin-top: 14px;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.shell, .workspace {
|
||||
padding: 14px;
|
||||
}
|
||||
/* ---------- Histogram ---------- */
|
||||
|
||||
.admin-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.panel {
|
||||
padding: 18px;
|
||||
}
|
||||
.hist { margin-top: 16px; display: grid; gap: 8px; }
|
||||
.hist-summary { display: flex; gap: 12px; flex-wrap: wrap; font-size: 0.9rem; }
|
||||
.hist-rows { display: grid; gap: 6px; }
|
||||
.hist-row {
|
||||
display: grid;
|
||||
grid-template-columns: 36px 1fr 80px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
.hist-row .bar {
|
||||
height: 8px;
|
||||
background: color-mix(in srgb, var(--info) 12%, transparent);
|
||||
border-radius: 999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.hist-row .bar .fill {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background: var(--info);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
.hist-row.is-correct .bar .fill { background: var(--correct-border); }
|
||||
.hist-row.missed .bar { background: transparent; }
|
||||
.hist-row .num { font-size: 0.85rem; text-align: right; color: var(--muted); }
|
||||
|
||||
/* ---------- Leaderboard ---------- */
|
||||
|
||||
.leaderboard {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
.leaderboard li {
|
||||
display: grid;
|
||||
grid-template-columns: 32px 1fr auto;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.leaderboard li:nth-child(odd) { background: color-mix(in srgb, var(--info) 5%, transparent); }
|
||||
.leaderboard .rank { font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; }
|
||||
.leaderboard .who { display: grid; line-height: 1.2; }
|
||||
.leaderboard .who small { color: var(--muted); font-size: 0.78rem; }
|
||||
.leaderboard .score { font-weight: 700; font-variant-numeric: tabular-nums; color: var(--primary); }
|
||||
|
||||
/* ---------- Student-side answer buttons (big, tappable) ---------- */
|
||||
|
||||
.quiz-card { width: min(640px, 100%); }
|
||||
.answer-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: 18px 0 0;
|
||||
}
|
||||
.answer-btn {
|
||||
display: grid;
|
||||
grid-template-columns: 48px 1fr;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
background: var(--surface);
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 18px 18px;
|
||||
font-size: 1rem;
|
||||
min-height: 64px;
|
||||
}
|
||||
.answer-btn:hover:not(:disabled) {
|
||||
border-color: var(--primary);
|
||||
background: color-mix(in srgb, var(--primary) 6%, transparent);
|
||||
}
|
||||
.answer-btn.picked {
|
||||
border-color: var(--primary);
|
||||
background: color-mix(in srgb, var(--primary) 12%, transparent);
|
||||
}
|
||||
.answer-btn .answer-key {
|
||||
background: var(--info);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
border-radius: 8px;
|
||||
padding: 6px 0;
|
||||
text-align: center;
|
||||
width: 48px;
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.answer-btn .answer-text { font-weight: 500; }
|
||||
|
||||
.big-score {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
color: var(--primary);
|
||||
margin: 8px 0 4px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin: 12px auto 0;
|
||||
border: 3px solid var(--border);
|
||||
border-top-color: var(--primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* ---------- Reveal stats (student) ---------- */
|
||||
|
||||
.reveal-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.reveal-stats .stat {
|
||||
text-align: center;
|
||||
padding: 12px 8px;
|
||||
background: color-mix(in srgb, var(--info) 6%, transparent);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.reveal-stats .stat span { display: block; font-size: 0.8rem; }
|
||||
.reveal-stats .stat b { display: block; font-size: 1.4rem; margin-top: 4px; }
|
||||
.reveal-stats .stat.big b { font-size: 2.2rem; color: var(--primary); }
|
||||
|
||||
.celebration-card {
|
||||
text-align: center;
|
||||
width: min(640px, 100%);
|
||||
position: relative;
|
||||
}
|
||||
.celebration-banner {
|
||||
background: var(--primary);
|
||||
color: #fff;
|
||||
padding: 14px 20px;
|
||||
border-radius: 10px;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
margin: -8px 0 20px;
|
||||
}
|
||||
|
||||
/* ---------- Dark mode ---------- */
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root, body {
|
||||
background: #10141b;
|
||||
color: #edf1f7;
|
||||
}
|
||||
|
||||
.panel, input, textarea, select, .answer {
|
||||
background: #171d27;
|
||||
color: #edf1f7;
|
||||
border-color: #344052;
|
||||
}
|
||||
|
||||
button, .button {
|
||||
background: #263246;
|
||||
color: #edf1f7;
|
||||
border-color: #4a586d;
|
||||
:root {
|
||||
--bg: #0f1117;
|
||||
--surface: #181c25;
|
||||
--border: #2c3340;
|
||||
--text: #edf1f7;
|
||||
--muted: #98a3b3;
|
||||
--primary: #4ec9aa;
|
||||
--primary-text: #061612;
|
||||
--warn: #f0b441;
|
||||
--warn-text: #1a1102;
|
||||
--danger: #f06a6a;
|
||||
--info: #5489c6;
|
||||
--accent: #6dd2b6;
|
||||
--correct-bg: rgba(78, 201, 170, 0.18);
|
||||
--correct-border: #4ec9aa;
|
||||
--shadow: 0 8px 28px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
.qr-wrap { background: #fff; }
|
||||
.join-url { background: rgba(84, 137, 198, 0.12); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user