style: redesign exam web UI with a dark developer theme

The UI was light everywhere except the embedded terminal, which felt
disjointed. Switch to a cohesive dark palette with layered surfaces,
tinted state colors for pass/fail/warn/flag, and polish on buttons,
cards, overlays, and the question nav (hover/active states, shadows,
custom scrollbars).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
vumast3r 2026-09-12 10:24:38 -06:00
parent b7c059151d
commit 86bf245c19

View File

@ -6,109 +6,131 @@
<title>Exam simulator</title> <title>Exam simulator</title>
<style> <style>
:root { :root {
--bg: #eceef1; --pane: #ffffff; --ink: #1d2126; --muted: #5f6b78; --line: #d5dae1; --bg: #0a0c10; --bg-alt: #10131a; --pane: #14171f; --pane-2: #1a1e28;
--accent: #1f5fbf; --accent-ink: #ffffff; --warn: #b7791f; --danger: #b42318; --ok: #1e7f4f; --ink: #e8ebf1; --muted: #8891a3; --line: #232833; --line-soft: #1c212b;
--flag: #d9a400; --term: #1a1b1e; --accent: #5b8cff; --accent-hover: #7aa2ff; --accent-ink: #ffffff;
--warn: #eab34a; --danger: #f2555f; --ok: #35d48f; --flag: #eab34a;
--warn-soft: rgba(234,179,74,.14); --danger-soft: rgba(242,85,95,.14);
--ok-soft: rgba(53,212,143,.14); --accent-soft: rgba(91,140,255,.14); --flag-soft: rgba(234,179,74,.16);
--term: #0a0c10; --shadow: 0 12px 32px rgba(0,0,0,.45);
--sans: "IBM Plex Sans", "Segoe UI", system-ui, -apple-system, sans-serif; --sans: "IBM Plex Sans", "Segoe UI", system-ui, -apple-system, sans-serif;
--mono: "IBM Plex Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace; --mono: "IBM Plex Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace;
} }
* { box-sizing: border-box; } * { box-sizing: border-box; }
html, body { height: 100%; margin: 0; } html, body { height: 100%; margin: 0; }
body { font: 14px/1.5 var(--sans); color: var(--ink); background: var(--bg); } body { font: 14px/1.55 var(--sans); color: var(--ink); background: var(--bg); }
button { font: inherit; cursor: pointer; } button { font: inherit; cursor: pointer; }
a { color: var(--accent); } a { color: var(--accent); }
code { font-family: var(--mono); font-size: .92em; } code { font-family: var(--mono); font-size: .92em; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
::selection { background: var(--accent-soft); color: var(--ink); }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--line); border-radius: 6px; border: 2px solid transparent; background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: #2c3341; background-clip: padding-box; }
* { scrollbar-color: var(--line) transparent; scrollbar-width: thin; }
/* ---- top bar: exam name left, clock right, controls far right ---- */ /* ---- top bar: exam name left, clock right, controls far right ---- */
header { height: 48px; display: flex; align-items: center; gap: 16px; padding: 0 14px; header { height: 52px; display: flex; align-items: center; gap: 16px; padding: 0 16px;
background: #252a31; color: #e8ebef; } background: var(--bg-alt); color: var(--ink); border-bottom: 1px solid var(--line); }
header .name { font-weight: 600; } header .name { font-weight: 600; letter-spacing: .01em; }
header .mode { color: #a9b2bd; font-size: 13px; } header .mode { color: var(--muted); font-size: 13px; }
header .spacer { flex: 1; } header .spacer { flex: 1; }
#clock { font-family: var(--mono); font-size: 20px; letter-spacing: .04em; font-variant-numeric: tabular-nums; } #clock { font-family: var(--mono); font-size: 20px; letter-spacing: .04em; font-variant-numeric: tabular-nums; color: var(--ink); }
#clock.warn { color: #f5c451; } #clock.danger { color: #ff7b72; } #clock.paused { color: #a9b2bd; } #clock.warn { color: var(--warn); } #clock.danger { color: var(--danger); animation: pulse 1.4s ease-in-out infinite; } #clock.paused { color: var(--muted); }
.btn { border: 1px solid var(--line); background: var(--pane); color: var(--ink); border-radius: 4px; padding: 6px 12px; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); } .btn { border: 1px solid var(--line); background: var(--pane-2); color: var(--ink); border-radius: 6px; padding: 7px 14px;
transition: border-color .15s, background .15s, transform .1s; }
.btn:hover:not(:disabled) { border-color: #333a48; background: #1f2430; }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
.btn.primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.danger { background: var(--danger); border-color: var(--danger); color: #fff; } .btn.danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn.ghost { background: transparent; color: #e8ebef; border-color: #4a525c; } .btn.danger:hover:not(:disabled) { filter: brightness(1.1); }
.btn:disabled { opacity: .5; cursor: default; } .btn.ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn.ghost:hover:not(:disabled) { background: var(--pane-2); }
.btn:disabled { opacity: .4; cursor: default; }
/* ---- main split ---- */ /* ---- main split ---- */
main { height: calc(100% - 48px); display: grid; grid-template-columns: 56px 440px 6px 1fr; } main { height: calc(100% - 52px); display: grid; grid-template-columns: 56px 440px 6px 1fr; }
#qnav { background: #f6f7f9; border-right: 1px solid var(--line); overflow-y: auto; padding: 8px 0; } #qnav { background: var(--bg-alt); border-right: 1px solid var(--line); overflow-y: auto; padding: 10px 0; }
#qnav button { display: block; width: 40px; height: 34px; margin: 4px auto; border: 1px solid var(--line); border-radius: 4px; #qnav button { display: block; width: 38px; height: 32px; margin: 4px auto; border: 1px solid var(--line); border-radius: 6px;
background: var(--pane); position: relative; font-variant-numeric: tabular-nums; } background: var(--pane); color: var(--ink); position: relative; font-variant-numeric: tabular-nums;
#qnav button.current { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); font-weight: 600; } transition: border-color .12s, transform .08s, background .12s; }
#qnav button:hover { border-color: var(--accent); transform: translateY(-1px); }
#qnav button.current { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 0 12px var(--accent-soft); font-weight: 600; color: var(--accent-hover); }
#qnav button.flagged::after { content: ""; position: absolute; top: -4px; right: -4px; width: 10px; height: 10px; #qnav button.flagged::after { content: ""; position: absolute; top: -4px; right: -4px; width: 10px; height: 10px;
background: var(--flag); border-radius: 50%; border: 2px solid #f6f7f9; } background: var(--flag); border-radius: 50%; border: 2px solid var(--bg-alt); }
#qnav button.pass { background: #e3f3ea; border-color: var(--ok); } #qnav button.pass { background: var(--ok-soft); border-color: var(--ok); color: var(--ok); }
#qnav button.partial { background: #fbf1dc; border-color: var(--warn); } #qnav button.partial { background: var(--warn-soft); border-color: var(--warn); color: var(--warn); }
#qnav button.fail { background: #f9e3e1; border-color: var(--danger); } #qnav button.fail { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }
#task { background: var(--pane); display: flex; flex-direction: column; min-height: 0; } #task { background: var(--pane); display: flex; flex-direction: column; min-height: 0; }
#task .head { padding: 14px 18px 10px; border-bottom: 1px solid var(--line); } #task .head { padding: 16px 20px 12px; border-bottom: 1px solid var(--line); }
#task .head .sub { color: var(--muted); font-size: 13px; } #task .head .sub { color: var(--muted); font-size: 12.5px; text-transform: uppercase; letter-spacing: .05em; }
#task h2 { margin: 2px 0 0; font-size: 17px; font-weight: 600; } #task h2 { margin: 4px 0 0; font-size: 18px; font-weight: 600; letter-spacing: -.01em; }
#task .body { flex: 1; overflow-y: auto; padding: 14px 18px; } #task .body { flex: 1; overflow-y: auto; padding: 16px 20px; }
.prose p { margin: 0 0 10px; white-space: pre-wrap; } .prose p { margin: 0 0 12px; white-space: pre-wrap; }
.prose code { background: #eef2f7; padding: 1px 5px; border-radius: 3px; cursor: copy; border: 1px solid #dfe5ed; } .prose code { background: var(--pane-2); padding: 1px 6px; border-radius: 4px; cursor: copy; border: 1px solid var(--line); color: #c9d4f2; }
.prose code:hover { background: #dce6f5; } .prose code:hover { background: var(--accent-soft); border-color: var(--accent); }
.prose code.copied { background: #cfe9da; border-color: var(--ok); } .prose code.copied { background: var(--ok-soft); border-color: var(--ok); color: var(--ok); }
#task .foot { border-top: 1px solid var(--line); padding: 10px 18px; display: flex; gap: 8px; align-items: center; } #task .foot { border-top: 1px solid var(--line); padding: 12px 20px; display: flex; gap: 8px; align-items: center; }
#task .foot .spacer { flex: 1; } #task .foot .spacer { flex: 1; }
.btn.flag.on { background: #fff4c7; border-color: var(--flag); } .btn.flag.on { background: var(--flag-soft); border-color: var(--flag); color: var(--flag); }
#checks { margin-top: 12px; border: 1px solid var(--line); border-radius: 4px; padding: 10px 12px; font-size: 13px; } #checks { margin-top: 14px; background: var(--pane-2); border: 1px solid var(--line); border-radius: 6px; padding: 12px 14px; font-size: 13px; }
#checks div { padding: 2px 0; } #checks div { padding: 3px 0; }
#solution { margin-top: 12px; border: 1px solid var(--line); border-radius: 4px; padding: 8px 12px; font-size: 13px; } #solution { margin-top: 14px; border: 1px solid var(--line); border-radius: 6px; padding: 10px 14px; font-size: 13px; background: var(--pane-2); }
#solution summary { cursor: pointer; font-weight: 600; } #solution summary { cursor: pointer; font-weight: 600; color: var(--ink); }
#solution pre { margin: 8px 0 0; white-space: pre-wrap; font: 12.5px/1.5 var(--mono); background: #f6f7f9; padding: 10px; border-radius: 4px; } #solution pre { margin: 8px 0 0; white-space: pre-wrap; font: 12.5px/1.55 var(--mono); background: var(--bg-alt); border: 1px solid var(--line); padding: 12px; border-radius: 6px; color: #c9d4f2; }
#checks .ok::before { content: "✓ "; color: var(--ok); } #checks .no::before { content: "✗ "; color: var(--danger); } #checks .ok::before { content: "✓ "; color: var(--ok); } #checks .no::before { content: "✗ "; color: var(--danger); }
#notes { width: 100%; height: 120px; border: 0; border-top: 1px solid var(--line); resize: none; padding: 10px 18px; #notes { width: 100%; height: 120px; border: 0; border-top: 1px solid var(--line); resize: none; padding: 12px 20px;
font: 13px/1.45 var(--mono); background: #fbfbf6; color: var(--ink); } font: 13px/1.5 var(--mono); background: var(--bg-alt); color: var(--ink); }
#notes::placeholder { color: #9aa3ad; } #notes::placeholder { color: var(--muted); }
#handle { background: var(--line); cursor: col-resize; } #handle { background: var(--line); cursor: col-resize; transition: background .15s; }
#handle:hover { background: var(--accent); }
#termwrap { background: var(--term); position: relative; min-width: 0; } #termwrap { background: var(--term); position: relative; min-width: 0; }
#term { width: 100%; height: 100%; border: 0; display: block; } #term { width: 100%; height: 100%; border: 0; display: block; }
#noterm { color: #cbd2da; padding: 32px; font-family: var(--mono); font-size: 13px; line-height: 1.7; max-width: 70ch; } #noterm { color: var(--muted); padding: 32px; font-family: var(--mono); font-size: 13px; line-height: 1.7; max-width: 70ch; }
#noterm code { color: #f5c451; } #noterm code { color: var(--warn); }
/* ---- overlays: start, confirm, results ---- */ /* ---- overlays: start, confirm, results ---- */
.overlay { position: fixed; inset: 0; background: rgba(20,24,30,.55); display: flex; align-items: center; justify-content: center; } .overlay { position: fixed; inset: 0; background: rgba(4,5,8,.7); backdrop-filter: blur(2px); display: flex; align-items: center; justify-content: center; }
.card { position: relative; background: var(--pane); border-radius: 6px; padding: 26px 28px; width: min(640px, 92vw); max-height: 88vh; overflow-y: auto; } .card { position: relative; background: var(--pane); border: 1px solid var(--line); box-shadow: var(--shadow); border-radius: 10px;
.card h1 { margin: 0 0 4px; font-size: 20px; } padding: 28px 30px; width: min(640px, 92vw); max-height: 88vh; overflow-y: auto; }
.card h1 { margin: 0 0 4px; font-size: 20px; letter-spacing: -.01em; }
.card .sub { color: var(--muted); margin-bottom: 18px; } .card .sub { color: var(--muted); margin-bottom: 18px; }
.card .row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 16px; } .card .row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 16px; }
.opts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .opts { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.opt { border: 1px solid var(--line); border-radius: 6px; padding: 14px; text-align: left; background: var(--pane); } .opt { border: 1px solid var(--line); border-radius: 8px; padding: 15px; text-align: left; background: var(--pane-2);
.opt:hover { border-color: var(--accent); } transition: border-color .15s, transform .12s, box-shadow .15s; }
.opt b { display: block; margin-bottom: 4px; } .opt:hover { border-color: var(--accent); transform: translateY(-1px); box-shadow: 0 6px 18px rgba(0,0,0,.35); }
.opt b { display: block; margin-bottom: 4px; color: var(--ink); }
.opt span { color: var(--muted); font-size: 13px; } .opt span { color: var(--muted); font-size: 13px; }
.picker { columns: 2; margin: 10px 0 0; font-size: 13px; } .picker { columns: 2; margin: 10px 0 0; font-size: 13px; }
.picker label { display: block; padding: 2px 0; } .picker label { display: block; padding: 3px 0; }
pre.log { background: #f6f7f9; border: 1px solid var(--line); padding: 10px; font: 12px/1.5 var(--mono); max-height: 240px; overflow: auto; } pre.log { background: var(--bg-alt); border: 1px solid var(--line); padding: 12px; font: 12px/1.5 var(--mono); max-height: 240px; overflow: auto; border-radius: 6px; color: var(--muted); }
.score { font-size: 34px; font-family: var(--mono); } .score { font-size: 36px; font-family: var(--mono); font-weight: 600; }
.score small { font-size: 14px; color: var(--muted); font-family: var(--sans); } .score small { font-size: 14px; color: var(--muted); font-family: var(--sans); font-weight: 400; }
.verdict { display: inline-block; padding: 2px 10px; border-radius: 4px; color: #fff; margin-left: 10px; font-size: 14px; vertical-align: middle; } .verdict { display: inline-block; padding: 3px 12px; border-radius: 100px; color: #fff; margin-left: 10px; font-size: 13px; font-weight: 600; vertical-align: middle; }
.verdict.pass { background: var(--ok); } .verdict.fail { background: var(--danger); } .verdict.pass { background: var(--ok); } .verdict.fail { background: var(--danger); }
.bar { display: grid; grid-template-columns: 70px 1fr 60px; gap: 10px; align-items: center; margin: 6px 0; font-size: 13px; } .bar { display: grid; grid-template-columns: 70px 1fr 60px; gap: 10px; align-items: center; margin: 8px 0; font-size: 13px; }
.bar .track { height: 10px; background: #e6e9ee; border-radius: 5px; overflow: hidden; } .bar .track { height: 8px; background: var(--pane-2); border: 1px solid var(--line); border-radius: 5px; overflow: hidden; }
.bar .fill { height: 100%; background: var(--accent); } .bar .fill { height: 100%; background: var(--accent); border-radius: 5px; }
.qres { margin-top: 16px; } .qres { margin-top: 16px; }
.qres details { border-top: 1px solid var(--line); padding: 6px 0; } .qres details { border-top: 1px solid var(--line); padding: 8px 0; }
.qres summary { cursor: pointer; } .qres summary { cursor: pointer; }
.qres summary .pts { float: right; font-family: var(--mono); } .qres summary .pts { float: right; font-family: var(--mono); }
.qres .t-pass summary .pts { color: var(--ok); } .qres .t-fail summary .pts { color: var(--danger); } .qres .t-partial summary .pts { color: var(--warn); } .qres .t-pass summary .pts { color: var(--ok); } .qres .t-fail summary .pts { color: var(--danger); } .qres .t-partial summary .pts { color: var(--warn); }
.hidden { display: none !important; } .hidden { display: none !important; }
.pill { font-size:12px; padding:2px 8px; border-radius:10px; border:1px solid var(--line); color:var(--muted); } .pill { font-size:12px; padding:3px 10px; border-radius:100px; border:1px solid var(--line); color:var(--muted); background: var(--pane-2); transition: border-color .12s, color .12s; }
.pill.sel { background:var(--accent); border-color:var(--accent); color:#fff; } .pill.sel { background:var(--accent); border-color:var(--accent); color:#fff; }
.pill.pick { cursor:pointer; } .pill.pick { cursor:pointer; }
.pill.pick:hover { border-color:var(--accent); color:var(--accent); } .pill.pick:hover { border-color:var(--accent); color:var(--accent-hover); }
.pill.done.pick:hover { color:var(--ok); } .pill.done.pick:hover { color:var(--ok); }
.pill.done { background:#e3f3ea; border-color:var(--ok); color:var(--ok); } .pill.done { background:var(--ok-soft); border-color:var(--ok); color:var(--ok); }
.hardtag { font-size:11px; background:var(--danger); color:#fff; border-radius:3px; padding:1px 5px; margin-left:6px; vertical-align:middle; } .hardtag { font-size:11px; background:var(--danger); color:#fff; border-radius:3px; padding:1px 5px; margin-left:6px; vertical-align:middle; }
@media (prefers-reduced-motion: no-preference) { .prose code { transition: background .15s; } } @media (prefers-reduced-motion: no-preference) { .prose code { transition: background .15s, border-color .15s; } }
@media (max-width: 900px) { main { grid-template-columns: 56px 1fr; } #handle, #termwrap { display: none; } } @media (max-width: 900px) { main { grid-template-columns: 56px 1fr; } #handle, #termwrap { display: none; } }
</style> </style>
</head> </head>
@ -409,7 +431,7 @@
const cls = r.pass ? 't-pass' : (r.earned ? 't-partial' : 't-fail'); const cls = r.pass ? 't-pass' : (r.earned ? 't-partial' : 't-fail');
return `<details class="${cls}"><summary>Q${q.id} ${esc(q.title)}<span class="pts">${r.earned}/${r.total}</span></summary>` + return `<details class="${cls}"><summary>Q${q.id} ${esc(q.title)}<span class="pts">${r.earned}/${r.total}</span></summary>` +
r.checks.map(c => `<div class="${c.pass ? 'ok' : 'no'}" style="padding-left:14px">${c.pass ? '✓' : '✗'} ${esc(c.name)} (${c.pts})</div>`).join('') + r.checks.map(c => `<div class="${c.pass ? 'ok' : 'no'}" style="padding-left:14px">${c.pass ? '✓' : '✗'} ${esc(c.name)} (${c.pts})</div>`).join('') +
(q.solution ? `<details style="margin:6px 0 4px 14px"><summary style="font-size:13px">Reference solution</summary><pre style="white-space:pre-wrap;font:12px/1.5 var(--mono);background:#f6f7f9;padding:8px;border-radius:4px">${esc(q.solution)}</pre></details>` : '') + '</details>'; }).join(''); (q.solution ? `<details style="margin:6px 0 4px 14px"><summary style="font-size:13px">Reference solution</summary><pre style="white-space:pre-wrap;font:12px/1.5 var(--mono);background:var(--bg-alt);border:1px solid var(--line);color:#c9d4f2;padding:8px;border-radius:6px">${esc(q.solution)}</pre></details>` : '') + '</details>'; }).join('');
const missed = S.questions.filter(q => s.results[q.id] && !s.results[q.id].pass).map(q => q.id); const missed = S.questions.filter(q => s.results[q.id] && !s.results[q.id].pass).map(q => q.id);
$('#btnDrillMissed').disabled = !missed.length; $('#btnDrillMissed').onclick = () => { $('#results').classList.add('hidden'); start('drill', missed); }; $('#btnDrillMissed').disabled = !missed.length; $('#btnDrillMissed').onclick = () => { $('#results').classList.add('hidden'); start('drill', missed); };
$('#results').classList.remove('hidden'); $('#lobby').classList.add('hidden'); $('#results').classList.remove('hidden'); $('#lobby').classList.add('hidden');