Dark mode for the browser UI: Auto, Light or Dark
Every colour of the page is now a token with a light and a dark value. Auto, the default, follows the system's light or dark setting; Light and Dark override it. The switch is in the landing page's top bar, in the Exam Interface menu and on the results page, and the browser keeps the choice - applied before the page draws, so the other theme never flashes. Native controls and scrollbars follow the theme; the remote desktop keeps its own look. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JZPd3GGWyxTgAkL6a4gAJz
This commit is contained in:
parent
bab85d14b7
commit
db2575b15e
@ -106,6 +106,9 @@ the Linux Foundation's exam UI), written anew for this simulator:
|
||||
the remote desktop. The clock starts when you click **Start** in the dialog that opens the session.
|
||||
- **Results page** — points and pass or fail, every task with its checks, the answers, what to drill
|
||||
next, and a way back to the tasks to review them on their hosts.
|
||||
- **Theme** — Auto (your system's light or dark setting, the default), Light or Dark, from the
|
||||
landing page's top bar, the *Exam Interface* menu or the results page; the browser remembers it.
|
||||
The remote desktop keeps its own look.
|
||||
|
||||
Firefox in the desktop opens only the allowed documentation for the track (`KSIM_OPEN_WEB=1`
|
||||
lifts that). Submitting grades every task on its own host; the hosts stay up for review until the
|
||||
|
||||
266
web/index.html
266
web/index.html
@ -4,27 +4,88 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Kubestronaut exam simulator</title>
|
||||
<script>/* the theme you picked, before anything is drawn: no flash of the other one */
|
||||
try { const t = localStorage.getItem('ksim.theme'); if (t === 'light' || t === 'dark') document.documentElement.dataset.theme = t; } catch (e) { /* storage blocked: follow the system */ }</script>
|
||||
<style>
|
||||
/* The look of CK-X (github.com/sailor-sh/CK-X), itself modelled on the Linux Foundation exam UI:
|
||||
an indigo landing page, an exam screen with a dark top bar and a light question panel, and a
|
||||
plain results page. Written from scratch - no CK-X code is copied - and without anything from
|
||||
the network: the page has to work offline. */
|
||||
plain results page - plus a dark theme of its own. Written from scratch - no CK-X code is
|
||||
copied - and without anything from the network: the page has to work offline.
|
||||
Colours are tokens: light below, dark for data-theme="dark" and for Auto on a dark system. */
|
||||
:root {
|
||||
--indigo: #4f46e5; --indigo-dark: #3730a3; --indigo-light: #818cf8; --indigo-soft: #eef2ff;
|
||||
--ink: #111827; --text: #212529; --muted: #6b7280; --bg: #f9fafb; --line: #dee2e6; --line-2: #ddd;
|
||||
--blue: #0d6efd; --blue-hover: #0b5ed7; --divider: #1e88e5;
|
||||
--green: #198754; --green-hover: #157347; --ok: #27ae60; --bad: #e74c3c;
|
||||
color-scheme: light;
|
||||
--indigo: #4f46e5; --indigo-dark: #3730a3; --indigo-light: #818cf8;
|
||||
--accent-text: #4f46e5; --accent-strong: #3730a3; --accent-soft: #eef2ff; --accent-soft-text: #312e81;
|
||||
--hero-a: #4f46e5; --hero-b: #818cf8;
|
||||
--bg: #f9fafb; --surface: #fff; --surface-2: #f8f9fa; --surface-3: #f5f5f5; --hover: #e9ecef;
|
||||
--pre-bg: #f6f8fa; --results-bg: #f4f6f9;
|
||||
--ink: #111827; --text: #212529; --muted: #6b7280; --faint: #9ca3af;
|
||||
--line: #dee2e6; --line-2: #ddd; --line-soft: #edf0f3; --input-line: #ced4da;
|
||||
--link: #0d6efd; --blue: #0d6efd; --blue-hover: #0b5ed7; --divider: #1e88e5;
|
||||
--green: #198754; --green-hover: #157347; --ok: #27ae60; --bad: #e74c3c; --partial: #d68910;
|
||||
--ok-text: #198754; --warn-text: #b58100; --bad-text: #dc3545;
|
||||
--yellow: #ffc107; --red: #dc3545; --red-hover: #bb2d3b; --grey: #6c757d; --grey-hover: #5c636a;
|
||||
--code: #d63384; --code-bg: rgba(0,0,0,.06);
|
||||
--head: #333; --head-btn: #444;
|
||||
--code: #d63384; --code-bg: rgba(0,0,0,.06); --code-bg-hover: rgba(0,0,0,.12); --copied-bg: #d1e7dd;
|
||||
--head: #333; --head-btn: #444; --head-btn-hover: #555; --head-line: #444;
|
||||
--menu-bg: #fff; --menu-line: rgba(0,0,0,.15); --backdrop: rgba(0,0,0,.5); --shadow: 0 .5rem 1rem rgba(0,0,0,.15);
|
||||
--card-shadow: 0 10px 30px rgba(0,0,0,.06);
|
||||
--info-bg: #cff4fc; --info-line: #b6effb; --info-text: #055160;
|
||||
--success-bg: #d1e7dd; --success-line: #badbcc; --success-text: #0f5132;
|
||||
--warning-bg: #fff3cd; --warning-line: #ffecb5; --warning-text: #664d03;
|
||||
--danger-bg: #f8d7da; --danger-line: #f5c2c7; --danger-text: #842029;
|
||||
--res-head: #2c3e50; --score-bg: #2c3e50; --qcard: #f2f4f8; --qcard-h: #e9ecef; --track: #e9ecef;
|
||||
--gate: #92400e; --progress-track: #e5e7eb; --log-text: #374151; --spinner-track: #cbd5e1; --divider-grip: #999;
|
||||
--scroll-thumb: #c1c7cf;
|
||||
--sans: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif;
|
||||
--mono: "JetBrains Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
:root[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
--accent-text: #a5b4fc; --accent-strong: #c7d2fe; --accent-soft: rgba(129,140,248,.14); --accent-soft-text: #c7d2fe;
|
||||
--hero-a: #312e81; --hero-b: #4f46e5;
|
||||
--bg: #0f1115; --surface: #171a21; --surface-2: #1d2129; --surface-3: #1a1e25; --hover: #2a303b;
|
||||
--pre-bg: #12151b; --results-bg: #0f1115;
|
||||
--ink: #f3f4f6; --text: #e3e6ea; --muted: #9aa3b2; --faint: #6b7280;
|
||||
--line: #2c323d; --line-2: #2a2f39; --line-soft: #242933; --input-line: #3a414d;
|
||||
--link: #6ea8fe; --ok-text: #4ade80; --warn-text: #fbbf24; --bad-text: #f87171;
|
||||
--code: #f472b6; --code-bg: rgba(255,255,255,.08); --code-bg-hover: rgba(255,255,255,.14); --copied-bg: rgba(74,222,128,.18);
|
||||
--head: #1f232b; --head-btn: #2e333d; --head-btn-hover: #3a404c; --head-line: #2e333d;
|
||||
--menu-bg: #1d2129; --menu-line: rgba(255,255,255,.12); --backdrop: rgba(0,0,0,.65); --shadow: 0 .5rem 1.5rem rgba(0,0,0,.5);
|
||||
--card-shadow: 0 10px 30px rgba(0,0,0,.35);
|
||||
--info-bg: #032830; --info-line: #087990; --info-text: #6edff6;
|
||||
--success-bg: #051b11; --success-line: #0f5132; --success-text: #75b798;
|
||||
--warning-bg: #332701; --warning-line: #997404; --warning-text: #ffda6a;
|
||||
--danger-bg: #2c0b0e; --danger-line: #842029; --danger-text: #ea868f;
|
||||
--res-head: #e3e6ea; --score-bg: #334155; --qcard: #1a1e26; --qcard-h: #232833; --track: #2a303b;
|
||||
--gate: #fbbf24; --progress-track: #2a303b; --log-text: #c9d1d9; --spinner-track: #3a414d; --divider-grip: #5a6170; --scroll-thumb: #3a414d;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
color-scheme: dark;
|
||||
--accent-text: #a5b4fc; --accent-strong: #c7d2fe; --accent-soft: rgba(129,140,248,.14); --accent-soft-text: #c7d2fe;
|
||||
--hero-a: #312e81; --hero-b: #4f46e5;
|
||||
--bg: #0f1115; --surface: #171a21; --surface-2: #1d2129; --surface-3: #1a1e25; --hover: #2a303b;
|
||||
--pre-bg: #12151b; --results-bg: #0f1115;
|
||||
--ink: #f3f4f6; --text: #e3e6ea; --muted: #9aa3b2; --faint: #6b7280;
|
||||
--line: #2c323d; --line-2: #2a2f39; --line-soft: #242933; --input-line: #3a414d;
|
||||
--link: #6ea8fe; --ok-text: #4ade80; --warn-text: #fbbf24; --bad-text: #f87171;
|
||||
--code: #f472b6; --code-bg: rgba(255,255,255,.08); --code-bg-hover: rgba(255,255,255,.14); --copied-bg: rgba(74,222,128,.18);
|
||||
--head: #1f232b; --head-btn: #2e333d; --head-btn-hover: #3a404c; --head-line: #2e333d;
|
||||
--menu-bg: #1d2129; --menu-line: rgba(255,255,255,.12); --backdrop: rgba(0,0,0,.65); --shadow: 0 .5rem 1.5rem rgba(0,0,0,.5);
|
||||
--card-shadow: 0 10px 30px rgba(0,0,0,.35);
|
||||
--info-bg: #032830; --info-line: #087990; --info-text: #6edff6;
|
||||
--success-bg: #051b11; --success-line: #0f5132; --success-text: #75b798;
|
||||
--warning-bg: #332701; --warning-line: #997404; --warning-text: #ffda6a;
|
||||
--danger-bg: #2c0b0e; --danger-line: #842029; --danger-text: #ea868f;
|
||||
--res-head: #e3e6ea; --score-bg: #334155; --qcard: #1a1e26; --qcard-h: #232833; --track: #2a303b;
|
||||
--gate: #fbbf24; --progress-track: #2a303b; --log-text: #c9d1d9; --spinner-track: #3a414d; --divider-grip: #5a6170; --scroll-thumb: #3a414d;
|
||||
}
|
||||
}
|
||||
* { box-sizing: border-box; scrollbar-color: var(--scroll-thumb) transparent; }
|
||||
html, body { margin: 0; }
|
||||
body { font: 15px/1.5 var(--sans); color: var(--text); background: var(--bg); }
|
||||
button { font: inherit; }
|
||||
a { color: var(--blue); }
|
||||
a { color: var(--link); }
|
||||
code, pre { font-family: var(--mono); }
|
||||
.hidden { display: none !important; }
|
||||
.muted { color: var(--muted); }
|
||||
@ -45,21 +106,21 @@
|
||||
.btn-secondary { background: var(--grey); border-color: var(--grey); color: #fff; }
|
||||
.btn-secondary:hover:not(:disabled) { background: var(--grey-hover); }
|
||||
.btn-warning { background: var(--yellow); border-color: var(--yellow); color: #000; }
|
||||
.btn-outline-warning { border-color: var(--yellow); color: #b58100; }
|
||||
.btn-outline-warning { border-color: var(--yellow); color: var(--warn-text); }
|
||||
.btn-outline-warning:hover { background: var(--yellow); color: #000; }
|
||||
.btn-outline-primary { border-color: var(--blue); color: var(--blue); }
|
||||
.btn-outline-primary:hover:not(:disabled) { background: var(--blue); color: #fff; }
|
||||
.btn-outline-primary { border-color: var(--link); color: var(--link); }
|
||||
.btn-outline-primary:hover:not(:disabled) { background: var(--blue); border-color: var(--blue); color: #fff; }
|
||||
.alert { padding: .75rem 1rem; border: 1px solid transparent; border-radius: .375rem; margin: 0 0 1rem; }
|
||||
.alert ul { margin: .25rem 0 0; padding-left: 1.2rem; }
|
||||
.alert p { margin: 0 0 .4rem; }
|
||||
.alert-info { background: #cff4fc; border-color: #b6effb; color: #055160; }
|
||||
.alert-success { background: #d1e7dd; border-color: #badbcc; color: #0f5132; }
|
||||
.alert-warning { background: #fff3cd; border-color: #ffecb5; color: #664d03; }
|
||||
.alert-danger { background: #f8d7da; border-color: #f5c2c7; color: #842029; white-space: pre-wrap; }
|
||||
.alert-info { background: var(--info-bg); border-color: var(--info-line); color: var(--info-text); }
|
||||
.alert-success { background: var(--success-bg); border-color: var(--success-line); color: var(--success-text); }
|
||||
.alert-warning { background: var(--warning-bg); border-color: var(--warning-line); color: var(--warning-text); }
|
||||
.alert-danger { background: var(--danger-bg); border-color: var(--danger-line); color: var(--danger-text); white-space: pre-wrap; }
|
||||
/* a value in a task: pink, and a click copies it */
|
||||
code.ic { background: var(--code-bg); border-radius: 3px; padding: 2px 4px; font-size: .86em; color: var(--code); cursor: copy; overflow-wrap: anywhere; }
|
||||
code.ic:hover { background: rgba(0,0,0,.12); }
|
||||
code.ic.copied { background: #d1e7dd; color: var(--green); }
|
||||
code.ic:hover { background: var(--code-bg-hover); }
|
||||
code.ic.copied { background: var(--copied-bg); color: var(--ok-text); }
|
||||
.hardtag { font-size: 11px; background: var(--red); color: #fff; border-radius: 3px; padding: 1px 6px; margin-left: 6px; vertical-align: middle; }
|
||||
|
||||
/* ---- landing page ---- */
|
||||
@ -76,7 +137,7 @@
|
||||
.tpill:hover { border-color: #fff; color: #fff; }
|
||||
.tpill.sel { background: #fff; border-color: #fff; color: var(--indigo); font-weight: 600; }
|
||||
.hero { position: relative; overflow: hidden; min-height: 60vh; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
||||
text-align: center; padding: 64px 24px 72px; color: #fff; background: linear-gradient(135deg, var(--indigo) 0%, var(--indigo-light) 100%); }
|
||||
text-align: center; padding: 64px 24px 72px; color: #fff; background: linear-gradient(135deg, var(--hero-a) 0%, var(--hero-b) 100%); }
|
||||
.hero::before { content: ""; position: absolute; inset: 0; opacity: .5;
|
||||
background-image: radial-gradient(rgba(255,255,255,.18) 1.5px, transparent 1.5px), radial-gradient(rgba(255,255,255,.1) 1px, transparent 1px);
|
||||
background-size: 48px 48px, 22px 22px; background-position: 0 0, 11px 11px; }
|
||||
@ -94,85 +155,85 @@
|
||||
.lastline a { color: #fff; font-weight: 600; }
|
||||
.scrollhint { position: absolute; bottom: 52px; left: 0; right: 0; font-size: 11px; letter-spacing: .14em; opacity: .75; } /* above the first card, which overlaps the hero */
|
||||
.wrap { max-width: 1040px; margin: -36px auto 0; padding: 0 20px 40px; position: relative; z-index: 2; }
|
||||
.card { background: #fff; border-radius: 18px; box-shadow: 0 10px 30px rgba(0,0,0,.06); padding: 30px 34px; margin-bottom: 26px; }
|
||||
.card { background: var(--surface); border-radius: 18px; box-shadow: var(--card-shadow); padding: 30px 34px; margin-bottom: 26px; }
|
||||
.section-title { margin: 0 0 6px; font-size: 24px; font-weight: 800; color: var(--ink); letter-spacing: -.01em; }
|
||||
.section-title::after { content: ""; display: block; width: 46px; height: 4px; border-radius: 2px; background: var(--indigo); margin-top: 10px; }
|
||||
.section-sub { color: var(--muted); margin: 12px 0 18px; }
|
||||
.ladder { display: grid; grid-template-columns: 34px 1fr auto; gap: 6px 14px; align-items: center; margin: 0 0 18px; }
|
||||
.rung { display: contents; }
|
||||
.rung .step { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; font-size: 13px; font-weight: 700;
|
||||
background: var(--indigo-soft); color: var(--indigo); }
|
||||
background: var(--accent-soft); color: var(--accent-text); }
|
||||
.rung .where { color: var(--muted); font-size: 14px; }
|
||||
.rung.here .step { background: var(--indigo); color: #fff; }
|
||||
.rung.here > span:nth-child(2) { font-weight: 700; color: var(--indigo-dark); }
|
||||
.pathgo { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; padding: 16px 18px; border-radius: 12px; background: var(--indigo-soft); }
|
||||
.pathgo .why { flex: 1; min-width: 240px; color: #312e81; font-size: 14px; }
|
||||
.rung.here > span:nth-child(2) { font-weight: 700; color: var(--accent-strong); }
|
||||
.pathgo { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; padding: 16px 18px; border-radius: 12px; background: var(--accent-soft); }
|
||||
.pathgo .why { flex: 1; min-width: 240px; color: var(--accent-soft-text); font-size: 14px; }
|
||||
.dpills { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; }
|
||||
.dpill { text-align: left; border: 1px solid var(--line); background: #fff; border-radius: 12px; padding: 12px 14px; cursor: pointer;
|
||||
.dpill { text-align: left; border: 1px solid var(--line); background: var(--surface); color: var(--text); border-radius: 12px; padding: 12px 14px; cursor: pointer;
|
||||
transition: border-color .15s, transform .15s, box-shadow .15s; }
|
||||
.dpill:hover { border-color: var(--indigo-light); transform: translateY(-2px); box-shadow: 0 6px 16px rgba(79,70,229,.12); }
|
||||
.dpill b { font-family: var(--mono); color: var(--indigo); }
|
||||
.dpill b { font-family: var(--mono); color: var(--accent-text); }
|
||||
.dpill .mk { float: right; font-weight: 700; color: var(--muted); }
|
||||
.dpill.done .mk { color: var(--green); } .dpill.known .mk { color: var(--indigo); }
|
||||
.dpill.done .mk { color: var(--ok-text); } .dpill.known .mk { color: var(--accent-text); }
|
||||
.dpill span { display: block; color: var(--muted); font-size: 13px; margin-top: 4px; }
|
||||
.gate { margin-top: 14px; font-size: 14px; color: #92400e; }
|
||||
.gate { margin-top: 14px; font-size: 14px; color: var(--gate); }
|
||||
/* objectives */
|
||||
.legend { color: var(--muted); font-size: 13px; margin: 0 0 8px; }
|
||||
#objList h3 { margin: 22px 0 6px; font-size: 13px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-weight: 700; }
|
||||
#objList h3 a { text-transform: none; letter-spacing: 0; font-weight: 400; margin-left: 10px; }
|
||||
details.obj { border-top: 1px solid #edf0f3; padding: 9px 0; font-size: 14px; }
|
||||
details.obj { border-top: 1px solid var(--line-soft); padding: 9px 0; font-size: 14px; }
|
||||
details.obj > summary { cursor: pointer; display: grid; grid-template-columns: 18px 34px 1fr auto; gap: 8px; align-items: baseline; list-style: none; }
|
||||
details.obj > summary::-webkit-details-marker { display: none; }
|
||||
details.obj > summary:hover .otext { color: var(--indigo); }
|
||||
details.obj > summary:hover .otext { color: var(--accent-text); }
|
||||
details.obj[open] > summary { margin-bottom: 8px; }
|
||||
details.obj .oid { font-family: var(--mono); color: var(--muted); }
|
||||
.mk { font-family: var(--mono); font-weight: 700; text-align: center; }
|
||||
.st-done, .st-passed { color: var(--green); } .st-started, .st-focus { color: #b58100; } .st-failed { color: var(--red); } .st-new { color: #9ca3af; } .st-known { color: var(--indigo); }
|
||||
.st-done, .st-passed { color: var(--ok-text); } .st-started, .st-focus { color: var(--warn-text); } .st-failed { color: var(--bad-text); } .st-new { color: var(--faint); } .st-known { color: var(--accent-text); }
|
||||
.chips { font: 12px var(--mono); white-space: nowrap; color: var(--muted); }
|
||||
.chips span { margin-left: 6px; }
|
||||
.objrow { margin: 10px 0 6px; }
|
||||
.notes h4 { margin: 12px 0 4px; font-size: 11.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); font-weight: 700; }
|
||||
.notes ul { margin: 0; padding-left: 18px; } .notes li { margin: 3px 0; }
|
||||
.notes code { background: var(--code-bg); padding: 1px 5px; border-radius: 3px; color: var(--code); font-size: .9em; }
|
||||
.notes pre { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12.5px; line-height: 1.55; background: #f6f8fa; border: 1px solid var(--line); padding: 10px 12px; border-radius: 6px; }
|
||||
.notes pre { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12.5px; line-height: 1.55; background: var(--pre-bg); border: 1px solid var(--line); padding: 10px 12px; border-radius: 6px; color: var(--text); }
|
||||
.notes a { overflow-wrap: anywhere; }
|
||||
footer.foot { text-align: center; color: var(--muted); font-size: 13px; padding: 10px 20px 34px; }
|
||||
|
||||
/* ---- dialogs ---- */
|
||||
.modal-back { position: fixed; inset: 0; z-index: 50; background: rgba(0,0,0,.5); display: flex; align-items: center; justify-content: center; padding: 16px; }
|
||||
.modal { background: #fff; border-radius: .5rem; width: min(560px, 100%); max-height: 92vh; display: flex; flex-direction: column; box-shadow: 0 .5rem 1rem rgba(0,0,0,.15); color: var(--text); }
|
||||
.modal-back { position: fixed; inset: 0; z-index: 50; background: var(--backdrop); display: flex; align-items: center; justify-content: center; padding: 16px; }
|
||||
.modal { background: var(--surface); border-radius: .5rem; width: min(560px, 100%); max-height: 92vh; display: flex; flex-direction: column; box-shadow: var(--shadow); color: var(--text); }
|
||||
.modal.wide { width: min(720px, 100%); }
|
||||
.modal .mh { display: flex; align-items: center; padding: 1rem 1rem; border-bottom: 1px solid var(--line); }
|
||||
.modal .mh h5 { margin: 0; font-size: 1.25rem; font-weight: 500; flex: 1; }
|
||||
.modal .x { border: 0; background: none; font-size: 24px; line-height: 1; color: #6c757d; cursor: pointer; padding: 0 4px; }
|
||||
.modal .x { border: 0; background: none; font-size: 24px; line-height: 1; color: var(--muted); cursor: pointer; padding: 0 4px; }
|
||||
.modal .mb { padding: 1rem; overflow-y: auto; }
|
||||
.modal .mf { display: flex; gap: 8px; justify-content: flex-end; padding: .75rem 1rem; border-top: 1px solid var(--line); }
|
||||
.field { margin-bottom: 1rem; }
|
||||
.field label.lbl { display: block; margin-bottom: .4rem; font-weight: 500; }
|
||||
select, textarea { font: inherit; }
|
||||
.select { width: 100%; padding: .375rem 2.25rem .375rem .75rem; border: 1px solid #ced4da; border-radius: .375rem; background: #fff; color: var(--text); }
|
||||
.select { width: 100%; padding: .375rem 2.25rem .375rem .75rem; border: 1px solid var(--input-line); border-radius: .375rem; background: var(--surface); color: var(--text); }
|
||||
.check { display: flex; gap: 8px; align-items: flex-start; }
|
||||
.check input { margin-top: 4px; }
|
||||
.picker { columns: 2; font-size: 13px; max-height: 38vh; overflow-y: auto; border: 1px solid var(--line); border-radius: .375rem; padding: 8px 10px; }
|
||||
.picker label { display: block; padding: 2px 0; break-inside: avoid; }
|
||||
/* preparing the environment */
|
||||
.loading-card { background: #fff; border-radius: 16px; width: min(640px, 100%); padding: 30px 32px; box-shadow: 0 20px 50px rgba(0,0,0,.25); }
|
||||
.loading-card { background: var(--surface); color: var(--text); border-radius: 16px; width: min(640px, 100%); padding: 30px 32px; box-shadow: 0 20px 50px rgba(0,0,0,.25); }
|
||||
.loading-card h2 { margin: 0 0 18px; font-size: 22px; color: var(--ink); }
|
||||
.progress { height: 10px; border-radius: 10px; background: #e5e7eb; overflow: hidden; }
|
||||
.progress { height: 10px; border-radius: 10px; background: var(--progress-track); overflow: hidden; }
|
||||
.progress span { display: block; height: 100%; width: 40%; border-radius: 10px; background: linear-gradient(90deg, var(--indigo), var(--indigo-light));
|
||||
animation: slide 1.6s ease-in-out infinite; }
|
||||
@keyframes slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(260%); } }
|
||||
.loading-msg { margin: 14px 0 10px; font-family: var(--mono); font-size: 13px; color: var(--indigo-dark); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.loading-msg { margin: 14px 0 10px; font-family: var(--mono); font-size: 13px; color: var(--accent-strong); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.loading-card details summary { cursor: pointer; color: var(--muted); font-size: 13px; }
|
||||
pre.log { background: #f6f8fa; border: 1px solid var(--line); padding: 10px 12px; font-size: 12px; line-height: 1.5; max-height: 220px; overflow: auto; border-radius: 6px; color: #374151; margin: 8px 0 0; white-space: pre-wrap; }
|
||||
pre.log { background: var(--pre-bg); border: 1px solid var(--line); padding: 10px 12px; font-size: 12px; line-height: 1.5; max-height: 220px; overflow: auto; border-radius: 6px; color: var(--log-text); margin: 8px 0 0; white-space: pre-wrap; }
|
||||
.loading-card .note { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); color: var(--muted); font-size: 13px; text-align: center; }
|
||||
.spinner { width: 2.4rem; height: 2.4rem; border: .25em solid #cbd5e1; border-right-color: var(--blue); border-radius: 50%; animation: spin .75s linear infinite; margin: 6px auto 14px; }
|
||||
.spinner { width: 2.4rem; height: 2.4rem; border: .25em solid var(--spinner-track); border-right-color: var(--blue); border-radius: 50%; animation: spin .75s linear infinite; margin: 6px auto 14px; }
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* ---- exam screen ---- */
|
||||
#exam { position: fixed; inset: 0; display: flex; flex-direction: column; background: var(--head); color: #333; }
|
||||
#exam { position: fixed; inset: 0; display: flex; flex-direction: column; background: var(--head); color: var(--text); }
|
||||
.xhead { flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: .5rem 1rem; background: var(--head);
|
||||
color: #fff; border-bottom: 1px solid var(--head-btn); }
|
||||
color: #fff; border-bottom: 1px solid var(--head-line); }
|
||||
.xtitlebox { display: flex; align-items: center; gap: 15px; min-width: 0; }
|
||||
.xtitle { font-size: 1.25rem; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.timer { padding: .25rem .5rem; background: rgba(255,255,255,.1); border-radius: .25rem; color: #fff; font-weight: 500; font-variant-numeric: tabular-nums; transition: background-color .3s, color .3s; }
|
||||
@ -182,51 +243,54 @@
|
||||
.xmenus { display: flex; align-items: center; flex-shrink: 0; }
|
||||
.dd { position: relative; display: inline-block; }
|
||||
.btn-custom { background: var(--head-btn); color: #fff; border: 0; padding: .35rem .75rem; margin-left: .5rem; border-radius: .25rem; cursor: pointer; }
|
||||
.btn-custom:hover { background: #555; }
|
||||
.btn-custom:hover { background: var(--head-btn-hover); }
|
||||
.dd-toggle::after { content: ""; display: inline-block; margin-left: .45em; vertical-align: .2em; border-top: .3em solid; border-right: .3em solid transparent; border-left: .3em solid transparent; }
|
||||
.dd-menu { display: none; position: absolute; right: 0; top: calc(100% + 4px); z-index: 40; min-width: 220px; padding: .5rem 0; background: #fff;
|
||||
border: 1px solid rgba(0,0,0,.15); border-radius: .375rem; box-shadow: 0 .5rem 1rem rgba(0,0,0,.15); }
|
||||
.dd-menu { display: none; position: absolute; right: 0; top: calc(100% + 4px); z-index: 40; min-width: 220px; padding: .5rem 0; background: var(--menu-bg);
|
||||
border: 1px solid var(--menu-line); border-radius: .375rem; box-shadow: var(--shadow); }
|
||||
.dd-menu.open { display: block; }
|
||||
.dd-menu.left { left: 0; right: 0; max-height: 60vh; overflow-y: auto; }
|
||||
.dd-item { display: block; width: 100%; text-align: left; padding: .3rem 1rem; border: 0; background: none; color: #212529; cursor: pointer; text-decoration: none; white-space: nowrap; }
|
||||
.dd-item { display: block; width: 100%; text-align: left; padding: .3rem 1rem; border: 0; background: none; color: var(--text); cursor: pointer; text-decoration: none; white-space: nowrap; }
|
||||
.dd-menu.left .dd-item { white-space: normal; }
|
||||
.dd-item:hover { background: #e9ecef; }
|
||||
.dd-item.danger { color: var(--red); }
|
||||
.dd-item:hover { background: var(--hover); }
|
||||
.dd-item.danger { color: var(--bad-text); }
|
||||
.dd-item.on { font-weight: 600; }
|
||||
.dd-item.on::after { content: " ✓"; color: var(--ok-text); }
|
||||
.dd-head { padding: .25rem 1rem; font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
|
||||
.dd-item .flag { color: var(--yellow); }
|
||||
.dd-item.pass::before, .dd-item.partial::before, .dd-item.fail::before { content: "●"; margin-right: 6px; }
|
||||
.dd-item.pass::before { color: var(--green); } .dd-item.partial::before { color: #e0a800; } .dd-item.fail::before { color: var(--red); }
|
||||
.dd-sep { height: 0; margin: .5rem 0; border-top: 1px solid rgba(0,0,0,.15); }
|
||||
.dd-sep { height: 0; margin: .5rem 0; border-top: 1px solid var(--menu-line); }
|
||||
.xmain { flex: 1; display: flex; min-height: 0; position: relative; overflow: hidden; }
|
||||
.qpanel { width: 32%; min-width: 320px; max-width: 70%; background: #fff; display: flex; flex-direction: column; min-height: 0; }
|
||||
.qnav { display: flex; padding: .5rem; background: #f5f5f5; border-bottom: 1px solid var(--line-2); flex-shrink: 0; }
|
||||
.nav-arrow { width: 35px; height: 35px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; background: #f8f9fa;
|
||||
border: 1px solid var(--line-2); border-radius: 4px; font-weight: 700; cursor: pointer; color: #333; }
|
||||
.nav-arrow:disabled { opacity: .5; cursor: not-allowed; background: #e9ecef; }
|
||||
.qpanel { width: 32%; min-width: 320px; max-width: 70%; background: var(--surface); display: flex; flex-direction: column; min-height: 0; }
|
||||
.qnav { display: flex; padding: .5rem; background: var(--surface-3); border-bottom: 1px solid var(--line-2); flex-shrink: 0; }
|
||||
.nav-arrow { width: 35px; height: 35px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; background: var(--surface-2);
|
||||
border: 1px solid var(--line-2); border-radius: 4px; font-weight: 700; cursor: pointer; color: var(--text); }
|
||||
.nav-arrow:disabled { opacity: .5; cursor: not-allowed; background: var(--hover); }
|
||||
.qselect { flex: 1; margin: 0 .5rem; }
|
||||
.qselect .dd-toggle { width: 100%; height: 35px; border: 0; background: #f8f9fa; border-radius: .375rem; cursor: pointer; color: #212529; }
|
||||
.qselect .dd-toggle:hover { background: #e9ecef; }
|
||||
.qselect .dd-toggle { width: 100%; height: 35px; border: 0; background: var(--surface-2); border-radius: .375rem; cursor: pointer; color: var(--text); }
|
||||
.qselect .dd-toggle:hover { background: var(--hover); }
|
||||
.qcontent { flex: 1; overflow-y: auto; padding: 1.5rem; }
|
||||
.qhead .line { margin-bottom: .8rem; }
|
||||
.qhead .sub { margin-top: -.55rem; color: var(--muted); font-size: 13px; }
|
||||
.tp { color: var(--blue); }
|
||||
.qcontent hr { border: 0; border-top: 1px solid #dee2e6; margin: 1rem 0; }
|
||||
.qtitle { font-size: 18px; margin: 0 0 .6rem; color: #212529; }
|
||||
.tp { color: var(--link); }
|
||||
.qcontent hr { border: 0; border-top: 1px solid var(--line); margin: 1rem 0; }
|
||||
.qtitle { font-size: 18px; margin: 0 0 .6rem; color: var(--ink); }
|
||||
.qbody p { margin: 0 0 12px; white-space: pre-wrap; }
|
||||
.checkrow { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 16px; font-size: 13px; }
|
||||
.checks { margin-top: 14px; background: #f8f9fa; border: 1px solid var(--line); border-radius: 6px; padding: 12px 14px; font-size: 14px; }
|
||||
.checks { margin-top: 14px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 6px; padding: 12px 14px; font-size: 14px; }
|
||||
.checks div { padding: 3px 0; }
|
||||
.checks .ok::before { content: "✓ "; color: var(--ok); font-weight: 700; } .checks .no::before { content: "✗ "; color: var(--bad); font-weight: 700; }
|
||||
.checks .pts { color: var(--muted); }
|
||||
details.fold { margin-top: 14px; border: 1px solid var(--line); border-radius: 6px; padding: 10px 14px; font-size: 14px; background: #fcfcfd; }
|
||||
details.fold { margin-top: 14px; border: 1px solid var(--line); border-radius: 6px; padding: 10px 14px; font-size: 14px; background: var(--surface-2); }
|
||||
details.fold summary { cursor: pointer; font-weight: 600; }
|
||||
details.fold pre { margin: 8px 0 0; white-space: pre-wrap; font-size: 12.5px; line-height: 1.55; background: #f6f8fa; border: 1px solid var(--line); padding: 12px; border-radius: 6px; }
|
||||
.qactions { flex-shrink: 0; display: flex; gap: 8px; justify-content: space-between; align-items: stretch; padding: 10px 1rem; background: #fff; border-top: 1px solid var(--line-2); }
|
||||
details.fold pre { margin: 8px 0 0; white-space: pre-wrap; font-size: 12.5px; line-height: 1.55; background: var(--pre-bg); border: 1px solid var(--line); padding: 12px; border-radius: 6px; color: var(--text); }
|
||||
.qactions { flex-shrink: 0; display: flex; gap: 8px; justify-content: space-between; align-items: stretch; padding: 10px 1rem; background: var(--surface); border-top: 1px solid var(--line-2); }
|
||||
.qactions .btn { white-space: normal; text-align: center; } /* a narrow panel wraps the labels, as CK-X does, rather than the buttons */
|
||||
.notepad { flex-shrink: 0; border-top: 1px solid var(--line-2); background: #fafafa; }
|
||||
.notepad { flex-shrink: 0; border-top: 1px solid var(--line-2); background: var(--surface-2); }
|
||||
.notepad summary { cursor: pointer; padding: 8px 1.5rem; color: var(--muted); font-size: 13px; }
|
||||
.notepad textarea { display: block; width: 100%; height: 120px; border: 0; border-top: 1px solid #eee; resize: vertical; padding: 10px 1.5rem; font: 13px/1.5 var(--mono); background: #fff; color: #212529; }
|
||||
.notepad textarea { display: block; width: 100%; height: 120px; border: 0; border-top: 1px solid var(--line); resize: vertical; padding: 10px 1.5rem; font: 13px/1.5 var(--mono); background: var(--surface); color: var(--text); }
|
||||
.divider { width: 8px; flex-shrink: 0; background: var(--line-2); cursor: col-resize; position: relative; transition: background .2s; }
|
||||
.divider::after { content: ""; position: absolute; top: 50%; left: 2px; width: 4px; height: 30px; margin-top: -15px; background: #999; border-radius: 2px; }
|
||||
.divider::after { content: ""; position: absolute; top: 50%; left: 2px; width: 4px; height: 30px; margin-top: -15px; background: var(--divider-grip); border-radius: 2px; }
|
||||
.divider:hover, .divider.dragging { background: var(--divider); }
|
||||
.divider:hover::after, .divider.dragging::after { background: #fff; }
|
||||
.divider .hint { display: none; position: absolute; top: calc(50% + 24px); left: 50%; transform: translateX(-50%); background: rgba(0,0,0,.7); color: #fff;
|
||||
@ -244,45 +308,46 @@
|
||||
#noterm code { color: var(--yellow); }
|
||||
|
||||
/* ---- results page ---- */
|
||||
#results { min-height: 100vh; background: #f4f6f9; padding: 24px 16px 40px; }
|
||||
#results { min-height: 100vh; background: var(--results-bg); padding: 24px 16px 40px; }
|
||||
.res-actions { max-width: 1000px; margin: 0 auto 18px; display: flex; flex-wrap: wrap; gap: 10px; }
|
||||
.res-wrap { max-width: 1000px; margin: 0 auto; background: #fff; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,.06); padding: 28px 30px; }
|
||||
.res-header { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; border-bottom: 1px solid #eee; padding-bottom: 14px; margin-bottom: 20px; }
|
||||
.res-header h2 { margin: 0; font-size: 26px; color: #2c3e50; }
|
||||
.res-header .meta { color: #666; font-size: 14px; text-align: right; }
|
||||
.summary { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; background: #f9f9f9; border-radius: 8px; padding: 18px 22px; margin-bottom: 20px; }
|
||||
.summary h3 { margin: 0 0 8px; font-size: 16px; color: #333; font-weight: 600; }
|
||||
.res-actions .dd { margin-left: auto; }
|
||||
.res-wrap { max-width: 1000px; margin: 0 auto; background: var(--surface); border-radius: 10px; box-shadow: var(--card-shadow); padding: 28px 30px; }
|
||||
.res-header { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; border-bottom: 1px solid var(--line); padding-bottom: 14px; margin-bottom: 20px; }
|
||||
.res-header h2 { margin: 0; font-size: 26px; color: var(--res-head); }
|
||||
.res-header .meta { color: var(--muted); font-size: 14px; text-align: right; }
|
||||
.summary { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; background: var(--surface-2); border-radius: 8px; padding: 18px 22px; margin-bottom: 20px; }
|
||||
.summary h3 { margin: 0 0 8px; font-size: 16px; color: var(--text); font-weight: 600; }
|
||||
.score-row { display: flex; align-items: center; gap: 16px; }
|
||||
.score-box { background: #2c3e50; color: #fff; font-size: 22px; font-weight: 700; padding: 8px 18px; border-radius: 4px; font-variant-numeric: tabular-nums; }
|
||||
.pct { font-size: 30px; font-weight: 800; color: #2c3e50; }
|
||||
.score-box { background: var(--score-bg); color: #fff; font-size: 22px; font-weight: 700; padding: 8px 18px; border-radius: 4px; font-variant-numeric: tabular-nums; }
|
||||
.pct { font-size: 30px; font-weight: 800; color: var(--res-head); }
|
||||
.rank-badge { padding: 8px 16px; border-radius: 4px; font-weight: 700; color: #fff; }
|
||||
.rank-high { background: var(--ok); } .rank-low { background: var(--bad); }
|
||||
.bars { margin: 0 0 18px; }
|
||||
.bar { display: grid; grid-template-columns: 76px 1fr 64px; gap: 10px; align-items: center; margin: 6px 0; font-size: 14px; }
|
||||
.bar .track { height: 8px; background: #e9ecef; border-radius: 5px; overflow: hidden; }
|
||||
.bar .track { height: 8px; background: var(--track); border-radius: 5px; overflow: hidden; }
|
||||
.bar .fill { height: 100%; background: var(--indigo); border-radius: 5px; }
|
||||
.bar span:first-child { font-family: var(--mono); }
|
||||
#resPath { font-size: 14px; margin-bottom: 20px; }
|
||||
#resPath .milestone { color: var(--green); }
|
||||
#resPath .milestone { color: var(--ok-text); }
|
||||
#resPath code { background: var(--code-bg); padding: 1px 5px; border-radius: 3px; color: var(--code); }
|
||||
.qcard { background: #f2f4f8; border-radius: 8px; margin-bottom: 14px; overflow: hidden; }
|
||||
.qcard-h { background: #e9ecef; padding: 11px 16px; display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }
|
||||
.qcard-h h3 { margin: 0; font-size: 16px; color: #2c3e50; }
|
||||
.qcard { background: var(--qcard); border-radius: 8px; margin-bottom: 14px; overflow: hidden; }
|
||||
.qcard-h { background: var(--qcard-h); padding: 11px 16px; display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }
|
||||
.qcard-h h3 { margin: 0; font-size: 16px; color: var(--res-head); }
|
||||
.qcard-h .qs { font-weight: 700; white-space: nowrap; font-variant-numeric: tabular-nums; }
|
||||
.qcard.t-pass .qs { color: var(--ok); } .qcard.t-partial .qs { color: #d68910; } .qcard.t-fail .qs { color: var(--bad); }
|
||||
.qcard.t-pass .qs { color: var(--ok); } .qcard.t-partial .qs { color: var(--partial); } .qcard.t-fail .qs { color: var(--bad); }
|
||||
.vitems { list-style: none; margin: 0; padding: 0 16px; }
|
||||
.vitem { display: flex; justify-content: space-between; gap: 12px; background: #fff; padding: 9px 12px; border-bottom: 1px solid #eef0f3; font-size: 14px; }
|
||||
.vitem { display: flex; justify-content: space-between; gap: 12px; background: var(--surface); padding: 9px 12px; border-bottom: 1px solid var(--line-soft); font-size: 14px; }
|
||||
.vitems .vitem:first-child { margin-top: 12px; border-radius: 6px 6px 0 0; }
|
||||
.vitems .vitem:last-child { border-radius: 0 0 6px 6px; border-bottom: 0; }
|
||||
.vstat { font-weight: 700; } .vstat.ok { color: var(--ok); } .vstat.no { color: var(--bad); }
|
||||
.qcard details.fold { margin: 12px 16px 16px; background: #fff; }
|
||||
.qcard details.fold { margin: 12px 16px 16px; background: var(--surface); }
|
||||
.qcard .vitems + .pad { height: 14px; }
|
||||
/* history */
|
||||
.hist { width: 100%; border-collapse: collapse; font-size: 14px; }
|
||||
.hist th, .hist td { text-align: left; padding: 6px 8px; border-bottom: 1px solid #eee; vertical-align: top; }
|
||||
.hist th, .hist td { text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
|
||||
.hist th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; }
|
||||
.answers h4 { margin: 18px 0 6px; font-size: 15px; } .answers h4:first-child { margin-top: 0; }
|
||||
.answers pre { margin: 0; white-space: pre-wrap; font-size: 12.5px; line-height: 1.55; background: #f6f8fa; border: 1px solid var(--line); padding: 12px; border-radius: 6px; }
|
||||
.answers pre { margin: 0; white-space: pre-wrap; font-size: 12.5px; line-height: 1.55; background: var(--pre-bg); border: 1px solid var(--line); padding: 12px; border-radius: 6px; color: var(--text); }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) { .progress span, .spinner, .timer.warning { animation: none; } }
|
||||
@media (max-width: 900px) { .divider, .vnc { display: none; } .qpanel { width: 100% !important; max-width: none; } .xmenus .btn-custom { margin-left: .25rem; padding: .3rem .5rem; } }
|
||||
@ -299,6 +364,11 @@
|
||||
<span class="spacer"></span>
|
||||
<button class="navlink hidden" id="navResults">View results</button>
|
||||
<button class="navlink" id="navHistory">History</button>
|
||||
<div class="dd">
|
||||
<button class="navlink dd-toggle" id="themeBtn" aria-expanded="false" title="Light, dark, or follow your system">◐ Auto</button>
|
||||
<div class="dd-menu"><div class="dd-head">Theme</div>
|
||||
<button class="dd-item" data-theme-set="auto">Auto - follow the system</button><button class="dd-item" data-theme-set="light">Light</button><button class="dd-item" data-theme-set="dark">Dark</button></div>
|
||||
</div>
|
||||
<span class="ver" id="ver"></span>
|
||||
</nav>
|
||||
<section class="hero">
|
||||
@ -347,6 +417,9 @@
|
||||
<button class="dd-item" data-act="resetPanels">Reset panel sizes</button>
|
||||
<div class="dd-sep"></div>
|
||||
<button class="dd-item" data-act="reconnect">Reconnect the remote desktop</button>
|
||||
<div class="dd-sep"></div>
|
||||
<div class="dd-head">Theme</div>
|
||||
<button class="dd-item" data-theme-set="auto">Auto - follow the system</button><button class="dd-item" data-theme-set="light">Light</button><button class="dd-item" data-theme-set="dark">Dark</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dd">
|
||||
@ -401,6 +474,10 @@
|
||||
<button class="btn btn-outline-primary" data-act="review">Review tasks</button>
|
||||
<button class="btn btn-outline-primary" data-act="history">History</button>
|
||||
<button class="btn btn-danger" data-act="terminate">Delete the hosts</button>
|
||||
<div class="dd">
|
||||
<button class="btn btn-outline-primary dd-toggle" aria-expanded="false">Theme</button>
|
||||
<div class="dd-menu"><button class="dd-item" data-theme-set="auto">Auto - follow the system</button><button class="dd-item" data-theme-set="light">Light</button><button class="dd-item" data-theme-set="dark">Dark</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="res-wrap">
|
||||
<div class="res-header"><h2 id="resTitle">Results</h2><div class="meta" id="resMeta"></div></div>
|
||||
@ -527,6 +604,18 @@
|
||||
const open = all.filter(d => !skipped.includes(d));
|
||||
return (open.length ? open : all).sort((a, b) => (drilled[a] || 0) - (drilled[b] || 0))[0];
|
||||
}
|
||||
// ---- theme: auto (the system's), light or dark - kept in this browser ----
|
||||
const THEME = {auto: '◐ Auto', light: '☀ Light', dark: '☾ Dark'};
|
||||
function setTheme(t) {
|
||||
if (t === 'auto') delete document.documentElement.dataset.theme; else document.documentElement.dataset.theme = t;
|
||||
try { if (t === 'auto') localStorage.removeItem('ksim.theme'); else localStorage.setItem('ksim.theme', t); } catch (e) { /* this visit only */ }
|
||||
paintTheme();
|
||||
}
|
||||
function paintTheme() {
|
||||
const t = document.documentElement.dataset.theme || 'auto';
|
||||
$$('[data-theme-set]').forEach(b => b.classList.toggle('on', b.dataset.themeSet === t));
|
||||
$('#themeBtn').textContent = THEME[t];
|
||||
}
|
||||
function setHard(v) {
|
||||
hard = !!v; $('#hardHero').checked = hard; $('#hardPicker').checked = hard;
|
||||
try { localStorage.setItem('ksim.hard', hard ? '1' : '0'); } catch (e) { /* not kept: fine */ }
|
||||
@ -971,6 +1060,8 @@
|
||||
document.addEventListener('click', e => {
|
||||
const t = e.target.closest('.dd-toggle');
|
||||
if (t) { const m = t.nextElementSibling, open = !m.classList.contains('open'); closeMenus(); if (open) { m.classList.add('open'); t.setAttribute('aria-expanded', 'true'); } return; }
|
||||
const th = e.target.closest('[data-theme-set]');
|
||||
if (th) { closeMenus(); setTheme(th.dataset.themeSet); return; }
|
||||
const a = e.target.closest('[data-act]');
|
||||
if (a) { closeMenus(); Promise.resolve(ACT[a.dataset.act]()).catch(err => alert(err.message)); return; }
|
||||
const c = e.target.closest('[data-close]');
|
||||
@ -1018,6 +1109,7 @@
|
||||
};
|
||||
addEventListener('mousemove', mv); addEventListener('mouseup', up);
|
||||
};
|
||||
paintTheme();
|
||||
load().catch(e => lobbyError(e.message === 'Failed to fetch' ? 'Cannot reach ./ksim web - is it running?' : e.message));
|
||||
})();
|
||||
</script>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user