diff --git a/static/admin.js b/static/admin.js index f5fa3ef..0cf1588 100644 --- a/static/admin.js +++ b/static/admin.js @@ -546,6 +546,7 @@ function tickCountdown() { const remaining = Math.max(0, store.questionDeadlineMs - Date.now()); const limit = (store.currentQuestion?.time_limit ?? 60) * 1000; el.textContent = `${Math.ceil(remaining / 1000)}s`; + el.classList.toggle("urgent", remaining > 0 && remaining <= 10000); fill.style.width = `${Math.max(0, Math.min(100, remaining / limit * 100))}%`; if (remaining <= 0) stopCountdown(); } diff --git a/static/quiz.js b/static/quiz.js index d72c1db..1fa138d 100644 --- a/static/quiz.js +++ b/static/quiz.js @@ -354,6 +354,7 @@ function tickCountdown() { const remaining = Math.max(0, store.deadlineMs - Date.now()); const limit = (store.currentQuestion?.time_limit ?? 60) * 1000; el.textContent = `${Math.ceil(remaining / 1000)}s`; + el.classList.toggle("urgent", remaining > 0 && remaining <= 10000); fill.style.width = `${Math.max(0, Math.min(100, remaining / limit * 100))}%`; if (remaining <= 0) stopCountdown(); } diff --git a/static/style.css b/static/style.css index 0ad9043..2281b3f 100644 --- a/static/style.css +++ b/static/style.css @@ -1,32 +1,73 @@ /* ============================================================ - * 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. + * Quiz portal — Editorial Lecture Hall stylesheet. + * + * Aesthetic: a calm, typographic, textbook-like surface for an in-class + * live quiz. Source Serif 4 carries question prose; IBM Plex Sans is the + * chrome face; IBM Plex Mono handles tabular numerics. One ink-blue + * accent in light mode; brass-amber in dark mode. + * + * Class names align with admin.js and quiz.js. Behaviour is unchanged; + * this file is purely visual. * ============================================================ */ +@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600;8..60,700&display=swap"); + :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; + + /* Surfaces — warm paper, restrained */ + --bg: #f4efe6; + --bg-grain: rgba(31, 29, 24, 0.018); + --surface: #fdfbf6; + --surface-2: #f8f3e8; + --border: #d9d2c2; + --border-strong: #1a1a1a; + --rule: #1a1a1a; + + /* Ink */ + --text: #15171c; + --text-soft: #2c2f38; + --muted: #6a6760; + --muted-2: #908b80; + + /* One accent — ink-blue */ + --primary: #1f3d6b; + --primary-soft: #2e5292; + --primary-text: #fbf7ec; + --accent: #1f3d6b; + + /* Status */ + --warn: #8a5a14; + --warn-text: #fbf7ec; + --danger: #7a221c; + --danger-text: #fbf7ec; + --info: #1f3d6b; + + --correct-border: #4f6b35; + --correct-bg: rgba(79, 107, 53, 0.08); + --wrong-border: #7a221c; + --wrong-bg: rgba(122, 34, 28, 0.08); + + /* Top-3 medals — subtle, restrained */ + --medal-gold: #a07a1f; + --medal-silver: #6f6f72; + --medal-bronze: #8a4a1f; + + --shadow-sm: 0 1px 0 rgba(20, 22, 28, 0.04); + --shadow: 0 1px 0 rgba(20, 22, 28, 0.05), 0 12px 36px -20px rgba(20, 22, 28, 0.18); + --shadow-strong: 0 1px 0 rgba(20, 22, 28, 0.06), 0 22px 48px -24px rgba(20, 22, 28, 0.28); + + /* Type system */ + --font-display: "Source Serif 4", "Source Serif Pro", ui-serif, Georgia, "Times New Roman", serif; + --font-sans: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + + font-family: var(--font-sans); background: var(--bg); color: var(--text); + font-feature-settings: "ss01", "ss02", "cv11"; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; } * { box-sizing: border-box; } @@ -34,22 +75,69 @@ body { margin: 0; min-height: 100dvh; - background: var(--bg); + background: + radial-gradient(1200px 700px at 100% -10%, color-mix(in srgb, var(--primary) 5%, transparent) 0%, transparent 60%), + radial-gradient(900px 500px at -10% 110%, color-mix(in srgb, var(--primary) 4%, transparent) 0%, transparent 55%), + var(--bg); color: var(--text); font-size: 16px; - line-height: 1.45; + line-height: 1.5; + letter-spacing: 0.005em; } -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; } +::selection { background: color-mix(in srgb, var(--primary) 30%, transparent); color: var(--text); } + +/* ---------- Headings & inline ---------- */ + +h1, h2, h3 { margin: 0 0 0.4rem; line-height: 1.15; } +h1 { + font-family: var(--font-display); + font-size: 1.6rem; + font-weight: 600; + letter-spacing: -0.012em; + font-feature-settings: "ss01"; +} +h2 { + font-family: var(--font-sans); + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--text); + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 14px; + padding-bottom: 10px; + border-bottom: 1px solid var(--border); +} +h3 { + font-family: var(--font-sans); + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--muted); + margin: 18px 0 10px; +} p { margin: 0 0 0.6rem; } -code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.85em; } +code { + font-family: var(--font-mono); + font-size: 0.85em; + letter-spacing: -0.01em; +} .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; } +.eyebrow { + color: var(--muted); + font-family: var(--font-sans); + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.2em; + font-weight: 600; + margin: 0 0 0.6rem; +} /* ---------- Layout containers ---------- */ @@ -58,14 +146,29 @@ code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; fo place-items: center; min-height: 100dvh; color: var(--muted); - font-size: 0.95rem; + font-size: 0.78rem; + letter-spacing: 0.18em; + text-transform: uppercase; +} +.bootstrap-loading::after { + content: ""; + display: inline-block; + width: 22px; + height: 1px; + background: var(--text); + margin-left: 14px; + animation: lineblink 1.1s ease-in-out infinite; +} +@keyframes lineblink { + 0%, 100% { opacity: 0.2; transform: scaleX(0.4); } + 50% { opacity: 1; transform: scaleX(1); } } .centered-shell { display: grid; place-items: center; min-height: 100dvh; - padding: 24px; + padding: 24px 18px; } /* ---------- Cards & panels ---------- */ @@ -73,54 +176,73 @@ code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; fo .card { background: var(--surface); border: 1px solid var(--border); - border-radius: 14px; + border-radius: 4px; box-shadow: var(--shadow); - padding: 24px; + padding: 28px; + position: relative; } .card.narrow { width: min(440px, 100%); } -.card-header { margin-bottom: 16px; } +.card-header { margin-bottom: 18px; } +.card-header h1 { margin-bottom: 6px; } .card.center { text-align: center; } -.panel { padding: 20px; } +.panel { padding: 22px 24px; } .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; + background: transparent; + color: var(--muted); + border: 1px solid var(--border); + border-radius: 0; + padding: 1px 8px; + font-size: 0.72rem; + font-family: var(--font-mono); + letter-spacing: 0; + font-weight: 500; + margin-left: auto; } -.stack { display: grid; gap: 14px; } +.stack { display: grid; gap: 16px; } /* ---------- Forms ---------- */ -.field { display: grid; gap: 6px; } -.field > span { font-weight: 600; font-size: 0.9rem; color: var(--muted); } +.field { display: grid; gap: 8px; } +.field > span { + font-family: var(--font-sans); + font-weight: 600; + font-size: 0.7rem; + letter-spacing: 0.16em; + text-transform: uppercase; + color: var(--muted); +} input, textarea, select { font: inherit; + font-family: var(--font-sans); border: 1px solid var(--border); - border-radius: 10px; - padding: 10px 12px; + border-radius: 2px; + padding: 11px 14px; background: var(--surface); color: var(--text); outline: none; + transition: border-color 0.15s ease, box-shadow 0.15s ease; } input:focus, textarea:focus, select:focus { border-color: var(--primary); - box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 25%, transparent); + box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent); } /* ---------- Buttons ---------- */ .btn, button { - font: inherit; - border: 1px solid var(--border); - border-radius: 10px; - padding: 10px 16px; + font-family: var(--font-sans); + font-size: 0.85rem; + font-weight: 500; + letter-spacing: 0.04em; + border: 1px solid var(--border-strong); + border-radius: 2px; + padding: 10px 18px; background: var(--surface); color: var(--text); cursor: pointer; @@ -128,25 +250,87 @@ input:focus, textarea:focus, select:focus { 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; + gap: 8px; + transition: + transform 0.08s ease, + background 0.18s ease, + color 0.18s ease, + border-color 0.18s ease, + box-shadow 0.18s ease; +} +.btn:hover:not(:disabled), button:hover:not(:disabled) { + background: var(--text); + color: var(--surface); } .btn:active, button:active { transform: translateY(1px); } -.btn:disabled, button:disabled { cursor: not-allowed; opacity: 0.55; } +.btn:disabled, button:disabled { cursor: not-allowed; opacity: 0.45; } +.btn:focus-visible, button:focus-visible { + outline: none; + box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 35%, transparent); +} -.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.primary { + background: var(--primary); + border-color: var(--primary); + color: var(--primary-text); +} +.btn.primary:hover:not(:disabled) { + background: var(--text); + border-color: var(--text); + color: var(--surface); +} +.btn.warn { + background: var(--surface); + border-color: var(--warn); + color: var(--warn); +} +.btn.warn:hover:not(:disabled) { + background: var(--warn); + color: var(--warn-text); +} +.btn.danger { + background: var(--danger); + border-color: var(--danger); + color: var(--danger-text); +} +.btn.danger:hover:not(:disabled) { + background: var(--text); + border-color: var(--text); + color: var(--surface); +} +.btn.ghost { + background: transparent; + border-color: var(--border); + color: var(--text-soft); +} +.btn.ghost:hover:not(:disabled) { + background: transparent; + border-color: var(--text); + color: var(--text); +} .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; } +.btn.big { + padding: 14px 26px; + font-size: 0.95rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; +} +.btn.small { padding: 6px 12px; font-size: 0.75rem; letter-spacing: 0.06em; text-transform: uppercase; } /* ---------- 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); } +.alert { + padding: 12px 18px; + border-radius: 2px; + font-size: 0.88rem; + border-left: 3px solid var(--border); + background: var(--surface); + margin: 0 24px 16px; +} +.alert.error { border-left-color: var(--danger); color: var(--danger); background: var(--wrong-bg); } +.alert.error { margin: 0; } +.alert.info { border-left-color: var(--primary); color: var(--primary); background: color-mix(in srgb, var(--primary) 5%, transparent); } /* ---------- Admin topbar ---------- */ @@ -157,66 +341,191 @@ input:focus, textarea:focus, select:focus { justify-content: space-between; align-items: flex-end; gap: 16px; - padding: 20px 24px 16px; - border-bottom: 1px solid var(--border); + padding: 28px 32px 18px; + border-bottom: 1px solid var(--rule); flex-wrap: wrap; + position: relative; } -.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; +.topbar::before { + content: "Live Quiz"; + position: absolute; + top: 18px; + left: 32px; + font-family: var(--font-sans); + font-size: 0.65rem; font-weight: 600; - border: 1px solid var(--border); + letter-spacing: 0.32em; + text-transform: uppercase; + color: var(--muted-2); +} +.topbar-title { padding-top: 18px; } +.topbar-title h1 { + font-family: var(--font-display); + font-size: 1.7rem; + font-weight: 600; + letter-spacing: -0.012em; + margin-bottom: 4px; +} +.topbar-title p { + margin: 0; + font-size: 0.82rem; + color: var(--muted); + letter-spacing: 0.02em; +} +.topbar-actions { display: flex; gap: 12px; align-items: center; } + +/* State badge — readable across a lecture hall */ +.state-badge { + border-radius: 0; + padding: 7px 14px; + font-size: 0.72rem; + font-family: var(--font-sans); + font-weight: 600; + letter-spacing: 0.18em; + text-transform: uppercase; + border: 1px solid var(--border-strong); + background: var(--surface); + color: var(--text); + display: inline-flex; + align-items: center; + gap: 8px; +} +.state-badge::before { + content: ""; + width: 7px; + height: 7px; + border-radius: 999px; + background: var(--muted); + flex-shrink: 0; +} +.state-badge.state-lobby::before { background: var(--muted); } +.state-badge.state-question_open { + background: var(--primary); + color: var(--primary-text); + border-color: var(--primary); +} +.state-badge.state-question_open::before { + background: var(--primary-text); + animation: pulse 1.4s ease-in-out infinite; +} +.state-badge.state-question_closed::before { background: var(--warn); } +.state-badge.state-finished::before { background: var(--correct-border); } +.state-badge.state-correct { + background: var(--correct-bg); + color: var(--correct-border); + border-color: var(--correct-border); +} +.state-badge.state-correct::before { background: var(--correct-border); } +.state-badge.state-wrong { + background: var(--wrong-bg); + color: var(--danger); + border-color: var(--danger); +} +.state-badge.state-wrong::before { background: var(--danger); } + +@keyframes pulse { + 0%, 100% { opacity: 0.45; transform: scale(0.85); } + 50% { opacity: 1; transform: scale(1.15); } } -.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; + gap: 18px; + grid-template-columns: minmax(300px, 380px) 1fr; + padding: 22px 32px; align-items: start; } -@media (max-width: 800px) { - .dashboard { grid-template-columns: 1fr; } +@media (max-width: 900px) { + .dashboard { grid-template-columns: 1fr; padding: 18px; } + .topbar { padding: 26px 18px 16px; } + .topbar::before { left: 18px; } } -.dashboard-side { display: grid; gap: 16px; } -.dashboard-main { display: grid; gap: 16px; } +.dashboard-side { display: grid; gap: 18px; } +.dashboard-main { display: grid; gap: 18px; } /* ---------- Join panel (QR + URL + roster) ---------- */ +.join-panel { padding: 0; overflow: hidden; } +.join-panel h2 { + margin: 0; + padding: 18px 22px 14px; + border-bottom: 1px solid var(--rule); +} +.join-panel h2::before { + content: "01"; + font-family: var(--font-mono); + font-size: 0.72rem; + color: var(--muted-2); + letter-spacing: 0; + margin-right: 4px; +} + .qr-wrap { - background: #fff; - padding: 14px; - border-radius: 10px; - border: 1px solid var(--border); + background: #ffffff; + padding: 18px; + border: 0; + border-bottom: 1px solid var(--rule); display: grid; place-items: center; - margin-bottom: 12px; + margin: 0; + position: relative; +} +.qr-wrap::before { + content: "Scan to join"; + position: absolute; + top: 14px; + left: 18px; + font-size: 0.62rem; + letter-spacing: 0.22em; + text-transform: uppercase; + color: #2c2f38; + font-weight: 600; +} +.qr-wrap img { + width: 100%; + height: auto; + max-width: 260px; + display: block; + margin-top: 18px; + image-rendering: pixelated; } -.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-row { + display: flex; + gap: 10px; + align-items: stretch; + padding: 14px 22px; + border-bottom: 1px solid var(--border); +} .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; + background: var(--surface-2); + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: 2px; + font-family: var(--font-mono); + font-size: 0.78rem; word-break: break-all; + color: var(--text); + display: flex; + align-items: center; +} +.join-panel .small { + padding: 10px 22px 18px; + margin: 0; + font-size: 0.75rem; + color: var(--muted); + letter-spacing: 0.04em; +} +.join-panel .small code { + background: var(--surface-2); + padding: 1px 6px; + border: 1px solid var(--border); + border-radius: 2px; } .roster { @@ -224,130 +533,310 @@ input:focus, textarea:focus, select:focus { margin: 0; padding: 0; display: grid; - gap: 6px; - max-height: 360px; + gap: 0; + max-height: 380px; overflow-y: auto; } -.roster li { display: flex; align-items: center; gap: 10px; padding: 6px 0; border-bottom: 1px solid var(--border); } +.roster li { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 0; + border-bottom: 1px dotted var(--border); + animation: rosterIn 0.35s ease both; +} .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; } +.roster .dot { + width: 6px; + height: 6px; + border-radius: 999px; + background: var(--correct-border); + flex-shrink: 0; + box-shadow: 0 0 0 2px color-mix(in srgb, var(--correct-border) 22%, transparent); +} +.roster .who { display: grid; line-height: 1.25; } +.roster .who b { font-weight: 500; font-size: 0.95rem; } +.roster .who small { + color: var(--muted); + font-size: 0.74rem; + font-family: var(--font-mono); + letter-spacing: 0; +} +@keyframes rosterIn { + from { opacity: 0; transform: translateX(-6px); } + to { opacity: 1; transform: translateX(0); } +} /* ---------- State CTA panel (lobby / finished) ---------- */ -.state-cta { display: grid; gap: 10px; } -.state-cta .btn.big { justify-self: start; } +.state-cta { display: grid; gap: 14px; } +.state-cta h2 { + font-family: var(--font-display); + font-size: 1.5rem; + font-weight: 600; + letter-spacing: -0.01em; + text-transform: none; + color: var(--text); + border-bottom: 0; + padding-bottom: 0; + margin-bottom: 2px; +} +.state-cta p { + font-family: var(--font-display); + font-size: 1.02rem; + color: var(--text-soft); + line-height: 1.55; + max-width: 60ch; +} +.state-cta .btn.big { justify-self: start; margin-top: 6px; } -.action-row { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 16px; } +.action-row { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-top: 22px; + padding-top: 18px; + border-top: 1px solid var(--border); +} /* ---------- Question card (admin + student share) ---------- */ .question-head { display: flex; justify-content: space-between; - align-items: baseline; + align-items: center; gap: 12px; - margin-bottom: 10px; + margin-bottom: 12px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border); +} +.qnum { + font-family: var(--font-mono); + font-weight: 500; + color: var(--muted); + font-size: 0.78rem; + letter-spacing: 0.06em; + text-transform: uppercase; } -.qnum { font-weight: 700; color: var(--muted); font-size: 0.9rem; } .countdown { - font-weight: 700; - font-size: 1.1rem; + font-family: var(--font-mono); + font-weight: 500; + font-size: 1.5rem; font-variant-numeric: tabular-nums; - color: var(--primary); + color: var(--text); + letter-spacing: -0.02em; +} +.countdown.urgent { + color: var(--danger); + animation: urgent-blink 0.8s ease-in-out infinite; +} +@keyframes urgent-blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.55; } } .qbar { - height: 8px; - background: color-mix(in srgb, var(--primary) 14%, transparent); - border-radius: 999px; + height: 3px; + background: var(--surface-2); + border-radius: 0; overflow: hidden; - margin-bottom: 16px; + margin-bottom: 22px; + position: relative; } .qbar span { display: block; height: 100%; width: 100%; background: var(--primary); - transition: width 0.2s linear; + transition: width 0.25s linear; + transform-origin: left center; +} +.qbar::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--primary) 40%, transparent) 50%, transparent 100%); + animation: barbreath 2.4s ease-in-out infinite; + pointer-events: none; + mix-blend-mode: overlay; + opacity: 0.35; +} +@keyframes barbreath { + 0%, 100% { transform: translateX(-30%); } + 50% { transform: translateX(30%); } } -.question-text { font-size: 1.2rem; font-weight: 600; margin: 8px 0 16px; } -.question-text.small { font-size: 1rem; } +.question-text, +h2.question-text, +h1.question-text { + font-family: var(--font-display); + font-size: clamp(1.5rem, 2.4vw, 1.95rem); + font-weight: 500; + line-height: 1.3; + letter-spacing: -0.008em; + text-transform: none; + margin: 14px 0 24px; + color: var(--text); + max-width: 60ch; + border-bottom: 0; + padding-bottom: 0; + display: block; +} +.question-text.small, +h2.question-text.small { + font-size: 1.2rem; + font-weight: 500; + margin: 10px 0 16px; +} .options { list-style: none; margin: 0; padding: 0; display: grid; - gap: 10px; + gap: 8px; + counter-reset: option; } .options li { display: grid; - grid-template-columns: 36px 1fr auto; - gap: 12px; + grid-template-columns: 32px 1fr auto; + gap: 14px; align-items: center; - padding: 10px 14px; - background: color-mix(in srgb, var(--info) 4%, transparent); + padding: 12px 16px; + background: transparent; border: 1px solid var(--border); - border-radius: 10px; + border-radius: 2px; + transition: border-color 0.18s ease, background 0.18s ease; } .options .key { - background: var(--info); - color: #fff; - font-weight: 700; - border-radius: 6px; - padding: 4px 0; - text-align: center; - width: 36px; + font-family: var(--font-mono); + font-weight: 600; + font-size: 0.92rem; + letter-spacing: 0; + text-align: left; + width: auto; + color: var(--muted); + background: transparent; + padding: 0; + border-right: 1px solid var(--border); + padding-right: 14px; + height: 22px; + display: inline-flex; + align-items: center; +} +.options .opt-text { + color: var(--text); + font-family: var(--font-display); + font-size: 1.02rem; + line-height: 1.4; +} +.options .opt-count { + color: var(--muted); + font-size: 0.78rem; + font-family: var(--font-mono); + font-variant-numeric: tabular-nums; } -.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); + animation: correctDraw 0.5s ease both; +} +.options.reveal li.correct .key { color: var(--correct-border); border-right-color: var(--correct-border); } +.options.reveal li.wrong-pick { + border-color: var(--wrong-border); + background: var(--wrong-bg); +} +.options.reveal li.wrong-pick .key { color: var(--wrong-border); border-right-color: var(--wrong-border); } + +@keyframes correctDraw { + 0% { box-shadow: inset 0 0 0 0 var(--correct-border); } + 60% { box-shadow: inset 0 0 0 2px var(--correct-border); } + 100% { box-shadow: inset 0 0 0 0 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; + background: transparent; + padding: 14px 18px; + border-radius: 0; + border: 0; + border-left: 2px solid var(--primary); + margin-top: 18px; + font-size: 0.98rem; + font-family: var(--font-display); + font-style: italic; + color: var(--text-soft); + line-height: 1.5; + max-width: 62ch; } /* ---------- Histogram ---------- */ -.hist { margin-top: 16px; display: grid; gap: 8px; } -.hist-summary { display: flex; gap: 12px; flex-wrap: wrap; font-size: 0.9rem; } +.hist { margin-top: 22px; display: grid; gap: 10px; } +.hist::before { + content: "Live distribution"; + font-family: var(--font-sans); + font-size: 0.68rem; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--muted); + font-weight: 600; +} +.hist.final::before { content: "Final distribution"; } +.hist-summary { + display: flex; + gap: 18px; + flex-wrap: wrap; + font-size: 0.85rem; + font-family: var(--font-mono); + font-variant-numeric: tabular-nums; + color: var(--muted); + padding-bottom: 6px; +} +.hist-summary b { color: var(--text); font-weight: 600; } .hist-rows { display: grid; gap: 6px; } .hist-row { display: grid; - grid-template-columns: 36px 1fr 80px; - gap: 10px; + grid-template-columns: 22px 1fr 90px; + gap: 12px; align-items: center; + font-family: var(--font-mono); + font-size: 0.82rem; +} +.hist-row .key { + font-weight: 600; + color: var(--muted); + letter-spacing: 0; + font-family: var(--font-mono); + background: transparent; + border: 0; + padding: 0; + width: auto; + text-align: left; } .hist-row .bar { - height: 8px; - background: color-mix(in srgb, var(--info) 12%, transparent); - border-radius: 999px; + height: 6px; + background: var(--surface-2); + border-radius: 0; overflow: hidden; + border: 1px solid var(--border); } .hist-row .bar .fill { display: block; height: 100%; - background: var(--info); - transition: width 0.3s ease; + background: var(--primary); + transition: width 0.45s cubic-bezier(0.22, 0.61, 0.36, 1); } .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); } +.hist-row.is-correct .key { color: var(--correct-border); } +.hist-row.missed .bar { background: transparent; border-color: transparent; } +.hist-row.missed .key { color: var(--muted-2); } +.hist-row .num { + font-size: 0.78rem; + text-align: right; + color: var(--muted); + font-variant-numeric: tabular-nums; +} /* ---------- Leaderboard ---------- */ @@ -356,79 +845,176 @@ input:focus, textarea:focus, select:focus { margin: 0; padding: 0; display: grid; - gap: 4px; + gap: 0; + border-top: 1px solid var(--border); } .leaderboard li { display: grid; - grid-template-columns: 32px 1fr auto; - gap: 12px; + grid-template-columns: 36px 1fr auto; + gap: 14px; align-items: center; - padding: 8px 12px; - border-radius: 8px; + padding: 10px 12px; + border-radius: 0; + border-bottom: 1px solid var(--border); + position: relative; + transition: background 0.18s ease; +} +.leaderboard li:nth-child(even) { background: var(--surface-2); } +.leaderboard li:hover { background: color-mix(in srgb, var(--primary) 6%, transparent); } +.leaderboard .rank { + font-family: var(--font-mono); + font-weight: 600; + color: var(--muted); + font-variant-numeric: tabular-nums; + font-size: 0.85rem; + letter-spacing: 0; + text-align: right; + padding-right: 6px; +} +.leaderboard .who { display: grid; line-height: 1.2; min-width: 0; } +.leaderboard .who b { + font-weight: 500; + font-family: var(--font-display); + font-size: 1rem; + color: var(--text); + letter-spacing: -0.005em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.leaderboard .who small { + color: var(--muted); + font-size: 0.72rem; + font-family: var(--font-mono); + letter-spacing: 0; +} +.leaderboard .score { + font-family: var(--font-mono); + font-weight: 600; + font-variant-numeric: tabular-nums; + color: var(--text); + font-size: 1.05rem; + letter-spacing: -0.01em; } -.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) ---------- */ +/* Top-3 medal treatment — restrained left-border accent */ +.leaderboard li:nth-child(1), +.leaderboard li:nth-child(2), +.leaderboard li:nth-child(3) { + padding-left: 18px; +} +.leaderboard li:nth-child(1) { border-left: 3px solid var(--medal-gold); background: color-mix(in srgb, var(--medal-gold) 6%, var(--surface)); } +.leaderboard li:nth-child(2) { border-left: 3px solid var(--medal-silver); background: color-mix(in srgb, var(--medal-silver) 4%, var(--surface)); } +.leaderboard li:nth-child(3) { border-left: 3px solid var(--medal-bronze); background: color-mix(in srgb, var(--medal-bronze) 5%, var(--surface)); } +.leaderboard li:nth-child(1) .rank { color: var(--medal-gold); } +.leaderboard li:nth-child(2) .rank { color: var(--medal-silver); } +.leaderboard li:nth-child(3) .rank { color: var(--medal-bronze); } +.leaderboard li:nth-child(1) .score, +.leaderboard li:nth-child(2) .score, +.leaderboard li:nth-child(3) .score { + font-weight: 600; +} + +/* ---------- Student-side answer buttons ---------- */ + +.quiz-card { width: min(680px, 100%); padding: 28px 30px 32px; } -.quiz-card { width: min(640px, 100%); } .answer-grid { display: grid; - gap: 12px; - margin: 18px 0 0; + gap: 10px; + margin: 22px 0 0; } .answer-btn { display: grid; - grid-template-columns: 48px 1fr; - gap: 14px; + grid-template-columns: 36px 1fr; + gap: 18px; align-items: center; text-align: left; background: var(--surface); - border: 2px solid var(--border); - border-radius: 12px; - padding: 18px 18px; + border: 1px solid var(--border-strong); + border-radius: 2px; + padding: 18px 20px; font-size: 1rem; min-height: 64px; + font-family: var(--font-sans); + font-weight: 500; + color: var(--text); + position: relative; + transition: + transform 0.12s cubic-bezier(0.22, 0.61, 0.36, 1), + background 0.18s ease, + border-color 0.18s ease, + color 0.18s ease; } .answer-btn:hover:not(:disabled) { - border-color: var(--primary); - background: color-mix(in srgb, var(--primary) 6%, transparent); + background: var(--text); + color: var(--surface); + border-color: var(--text); +} +.answer-btn:hover:not(:disabled) .answer-key { + color: var(--surface); + border-right-color: var(--surface); } .answer-btn.picked { + background: var(--primary); + color: var(--primary-text); border-color: var(--primary); - background: color-mix(in srgb, var(--primary) 12%, transparent); + animation: pickSettle 0.32s cubic-bezier(0.22, 1.4, 0.36, 1) both; } +.answer-btn.picked .answer-key { + color: var(--primary-text); + border-right-color: color-mix(in srgb, var(--primary-text) 40%, transparent); +} +@keyframes pickSettle { + 0% { transform: scale(1); } + 35% { transform: scale(0.97); } + 100% { transform: scale(1); } +} + .answer-btn .answer-key { - background: var(--info); - color: #fff; - font-weight: 700; - border-radius: 8px; - padding: 6px 0; - text-align: center; - width: 48px; + font-family: var(--font-mono); + font-weight: 600; + border: 0; + border-right: 1px solid var(--border); + background: transparent; + color: var(--muted); + padding: 0 14px 0 0; + text-align: left; + width: auto; font-size: 1.05rem; + letter-spacing: 0; + height: 28px; + display: inline-flex; + align-items: center; + justify-content: flex-start; + transition: color 0.18s ease, border-right-color 0.18s ease; +} +.answer-btn .answer-text { + font-family: var(--font-display); + font-weight: 500; + font-size: 1.1rem; + line-height: 1.35; } -.answer-btn .answer-text { font-weight: 500; } .big-score { - font-size: 3rem; - font-weight: 800; - color: var(--primary); - margin: 8px 0 4px; + font-family: var(--font-mono); + font-size: 4.2rem; + font-weight: 500; + color: var(--text); + margin: 14px 0 6px; font-variant-numeric: tabular-nums; + letter-spacing: -0.04em; + line-height: 1; } .spinner { - width: 28px; - height: 28px; - margin: 12px auto 0; - border: 3px solid var(--border); - border-top-color: var(--primary); + width: 22px; + height: 22px; + margin: 18px auto 0; + border: 1.5px solid var(--border); + border-top-color: var(--text); border-radius: 50%; - animation: spin 1s linear infinite; + animation: spin 0.9s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } @@ -437,54 +1023,203 @@ input:focus, textarea:focus, select:focus { .reveal-stats { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 12px; - margin: 16px 0; + gap: 0; + margin: 20px 0 6px; + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); } .reveal-stats .stat { text-align: center; - padding: 12px 8px; - background: color-mix(in srgb, var(--info) 6%, transparent); - border-radius: 10px; + padding: 14px 8px; + background: transparent; + border-radius: 0; + border-right: 1px dotted var(--border); +} +.reveal-stats .stat:last-child { border-right: 0; } +.reveal-stats .stat span { + display: block; + font-size: 0.66rem; + color: var(--muted); + letter-spacing: 0.18em; + text-transform: uppercase; + font-weight: 600; +} +.reveal-stats .stat b { + display: block; + font-family: var(--font-mono); + font-size: 1.5rem; + font-weight: 500; + margin-top: 6px; + font-variant-numeric: tabular-nums; + letter-spacing: -0.02em; + color: var(--text); +} +.reveal-stats .stat.big b { + font-size: 2.6rem; + color: var(--primary); } -.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%); + width: min(680px, 100%); position: relative; + padding: 32px 30px 36px; } .celebration-banner { - background: var(--primary); - color: #fff; - padding: 14px 20px; - border-radius: 10px; - font-weight: 700; - font-size: 1.1rem; - margin: -8px 0 20px; + background: transparent; + color: var(--text); + padding: 0 0 18px; + border-radius: 0; + font-family: var(--font-sans); + font-weight: 600; + font-size: 0.7rem; + letter-spacing: 0.32em; + text-transform: uppercase; + margin: -8px 0 14px; + border-bottom: 1px solid var(--rule); + position: relative; +} +.celebration-banner::before, +.celebration-banner::after { + content: "—"; + display: inline-block; + padding: 0 14px; + color: var(--muted-2); + font-weight: 400; + letter-spacing: 0; +} +.celebration-card h3 { + font-family: var(--font-display); + font-size: 1.2rem; + font-weight: 600; + text-transform: none; + letter-spacing: -0.005em; + color: var(--text); + margin-top: 24px; } -/* ---------- Dark mode ---------- */ +/* ---------- Student-specific reveal trims ---------- */ + +.options.student-reveal li.yours { + position: relative; +} +.options.student-reveal li.yours::after { + content: "Your pick"; + position: absolute; + top: 4px; + right: 12px; + font-family: var(--font-sans); + font-size: 0.6rem; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--muted); + font-weight: 600; +} +.options.student-reveal li.yours.correct::after { color: var(--correct-border); } +.options.student-reveal li.yours.wrong-pick::after { color: var(--wrong-border); } + +/* ---------- Responsive: mobile student view ---------- */ + +@media (max-width: 480px) { + body { font-size: 15px; } + .card { padding: 22px 18px; } + .panel { padding: 18px 16px; } + .quiz-card, .celebration-card { padding: 24px 20px; } + .question-text { font-size: 1.4rem; } + .answer-btn .answer-text { font-size: 1.02rem; } + .answer-btn { padding: 16px; min-height: 60px; } + .topbar { padding: 24px 16px 14px; } + .topbar::before { left: 16px; } + .dashboard { padding: 16px; } + .reveal-stats .stat b { font-size: 1.25rem; } + .reveal-stats .stat.big b { font-size: 2rem; } + .big-score { font-size: 3.4rem; } + h2 { font-size: 0.72rem; } +} + +@media (max-width: 360px) { + .answer-btn { grid-template-columns: 28px 1fr; gap: 12px; padding: 14px; } + .answer-btn .answer-text { font-size: 0.96rem; } +} + +/* ---------- Dark mode: inkwell + brass ---------- */ @media (prefers-color-scheme: dark) { :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); + --bg: #0c1018; + --surface: #131828; + --surface-2: #181f33; + --border: #2a3145; + --border-strong: #4a526a; + --rule: #404867; + + --text: #ece7d6; + --text-soft: #d6d1c0; + --muted: #8d8a82; + --muted-2: #5e6377; + + --primary: #c79a4a; /* brass */ + --primary-soft: #d8b06a; + --primary-text: #15171c; + --accent: #c79a4a; + --info: #c79a4a; + + --warn: #d8a84a; + --warn-text: #15171c; + --danger: #d36556; + --danger-text: #15171c; + + --correct-border: #8db96b; + --correct-bg: rgba(141, 185, 107, 0.10); + --wrong-border: #d36556; + --wrong-bg: rgba(211, 101, 86, 0.10); + + --medal-gold: #d8b06a; + --medal-silver: #a4a8b6; + --medal-bronze: #c98a5a; + + --shadow-sm: 0 1px 0 rgba(0, 0, 0, 0.4); + --shadow: 0 1px 0 rgba(0, 0, 0, 0.5), 0 16px 38px -22px rgba(0, 0, 0, 0.7); + --shadow-strong: 0 1px 0 rgba(0, 0, 0, 0.6), 0 24px 50px -24px rgba(0, 0, 0, 0.85); + } + body { + background: + radial-gradient(1200px 700px at 100% -10%, color-mix(in srgb, var(--primary) 8%, transparent) 0%, transparent 60%), + radial-gradient(900px 500px at -10% 110%, color-mix(in srgb, var(--primary) 5%, transparent) 0%, transparent 55%), + var(--bg); + } + .qr-wrap { background: #f6f1e2; } + .qr-wrap::before { color: #2c2f38; } + .join-url { background: var(--surface-2); border-color: var(--border); color: var(--text); } + .leaderboard li:nth-child(even) { background: color-mix(in srgb, var(--surface-2) 80%, transparent); } + + .btn:hover:not(:disabled), button:hover:not(:disabled) { + background: var(--text); + color: var(--surface); + border-color: var(--text); + } + .btn.primary:hover:not(:disabled) { + background: var(--primary-soft); + border-color: var(--primary-soft); + color: var(--primary-text); + } + .answer-btn:hover:not(:disabled) { + background: var(--text); + color: var(--surface); + border-color: var(--text); + } + .answer-btn.picked { + background: var(--primary); + color: var(--primary-text); + border-color: var(--primary); + } +} + +/* Honour reduced-motion preference */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; } - .qr-wrap { background: #fff; } - .join-url { background: rgba(84, 137, 198, 0.12); } }