/* HafezCTF interface.
   Fonts are self-hosted under /assets/fonts (works air-gapped; no external CDN).
   English uses Bricolage Grotesque + IBM Plex; Persian (:lang(fa)) swaps in Morabba + Peyda.

   Two rules hold this together:
   1. Chrome is neutral. Colour only ever carries meaning — a state, a result, a warning — so
      every coloured pixel on a page is a signal. The brand maroon appears on the mark and on
      the one primary action, and nowhere else.
   2. Borders carry the design, not shadows. Shadow is reserved for things that genuinely float
      above the page: the command palette, toasts, an expanded panel.

   There are no inline styles anywhere in the templates: the CSP sets style-src 'self' with no
   'unsafe-inline', so a style attribute is dropped silently. Proportional widths use the
   quantised .w-* classes near the end of this file. */

/* ── fonts ──────────────────────────────────────────────────────────────── */
@font-face { font-family:'Bricolage Grotesque'; src:url('/assets/fonts/bricolage-var.woff2') format('woff2'); font-weight:600 800; font-display:swap; }
@font-face { font-family:'IBM Plex Sans'; src:url('/assets/fonts/plexsans-var.woff2') format('woff2'); font-weight:400 700; font-display:swap; }
@font-face { font-family:'IBM Plex Mono'; src:url('/assets/fonts/plexmono-400.woff2') format('woff2'); font-weight:400; font-display:swap; }
@font-face { font-family:'IBM Plex Mono'; src:url('/assets/fonts/plexmono-500.woff2') format('woff2'); font-weight:500; font-display:swap; }
@font-face { font-family:'Morabba'; src:url('/assets/fonts/morabba-medium.woff') format('woff'); font-weight:500; font-display:swap; }
@font-face { font-family:'Morabba'; src:url('/assets/fonts/morabba-black.woff') format('woff'); font-weight:900; font-display:swap; }
@font-face { font-family:'Peyda'; src:url('/assets/fonts/peyda-regular.woff2') format('woff2'); font-weight:400; font-display:swap; }
@font-face { font-family:'Peyda'; src:url('/assets/fonts/peyda-medium.woff2') format('woff2'); font-weight:500; font-display:swap; }
@font-face { font-family:'Peyda'; src:url('/assets/fonts/peyda-semibold.woff2') format('woff2'); font-weight:600; font-display:swap; }
@font-face { font-family:'Peyda'; src:url('/assets/fonts/peyda-bold.woff2') format('woff2'); font-weight:700; font-display:swap; }

/* ── tokens ─────────────────────────────────────────────────────────────── */
:root {
  --bg:#fbf8ef; --surface:#ffffff; --surface-2:#f4eddb; --raise:#ffffff;
  --line:#e6dcc3; --line-2:#d3c5a6;
  --ink:#341933; --ink-2:#5a4257; --muted:#8a7385;

  --accent:#6c1c4f; --accent-2:#54153d; --accent-text:#6c1c4f; --on-accent:#f4eddb;
  --accent-soft:#f6efdf; --accent-line:#ddcbb0;

  --ok:#1c6b4a; --ok-bg:#e8f2ec; --ok-line:#bcdbc9;
  --warn:#8a5410; --warn-bg:#fbf0e2; --warn-line:#e8d3ae;
  --err:#a01d3c; --err-bg:#fbe9ee; --err-line:#eec3cf;
  --info:#1f4f7a; --info-bg:#e8f0f7; --info-line:#c2d7e8;
  --heat:108,28,79;

  --sans:'IBM Plex Sans',system-ui,-apple-system,'Segoe UI',sans-serif;
  --display:'Bricolage Grotesque','IBM Plex Sans',sans-serif;
  --mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,monospace;

  --maxw:1180px; --maxw-wide:1440px;
  --radius:8px; --radius-sm:6px; --radius-lg:12px;
  --shadow:0 1px 2px rgba(26,19,21,.05),0 10px 30px -18px rgba(26,19,21,.4);
  --shadow-lg:0 20px 50px -22px rgba(26,19,21,.45);
}

/* The dark theme is designed, not inverted: its own surface ladder, and an accent lifted to
   something that actually reads on a dark ground. */
@media (prefers-color-scheme:dark) {
  :root:not([data-theme="light"]) {
    --bg:#1b0d1a; --surface:#251327; --surface-2:#341933; --raise:#3a1d39;
    --line:#412440; --line-2:#4d2f4b;
    --ink:#f4eddb; --ink-2:#cdbfa8; --muted:#9a8894;
    --accent:#a8306f; --accent-2:#c03f82; --accent-text:#ecd9a8; --on-accent:#f4eddb;
    --accent-soft:#2e1630; --accent-line:#4a2a47;
    --ok:#5fc493; --ok-bg:#152a20; --ok-line:#264634;
    --warn:#dda664; --warn-bg:#2a2116; --warn-line:#463823;
    --err:#ef9088; --err-bg:#331a1c; --err-line:#4c2b2a;
    --info:#7db4e0; --info-bg:#152430; --info-line:#26394a;
    --heat:168,48,111;
    --shadow:0 1px 2px rgba(0,0,0,.4),0 12px 32px -18px rgba(0,0,0,.9);
    --shadow-lg:0 24px 56px -24px rgba(0,0,0,.95);
  }
}
:root[data-theme="dark"] {
  --bg:#1b0d1a; --surface:#251327; --surface-2:#341933; --raise:#3a1d39;
  --line:#412440; --line-2:#4d2f4b;
  --ink:#f4eddb; --ink-2:#cdbfa8; --muted:#9a8894;
  --accent:#a8306f; --accent-2:#c03f82; --accent-text:#ecd9a8; --on-accent:#f4eddb;
  --accent-soft:#2e1630; --accent-line:#4a2a47;
  --ok:#5fc493; --ok-bg:#152a20; --ok-line:#264634;
  --warn:#dda664; --warn-bg:#2a2116; --warn-line:#463823;
  --err:#ef9088; --err-bg:#331a1c; --err-line:#4c2b2a;
  --info:#7db4e0; --info-bg:#152430; --info-line:#26394a;
  --heat:168,48,111;
  --shadow:0 1px 2px rgba(0,0,0,.4),0 12px 32px -18px rgba(0,0,0,.9);
  --shadow-lg:0 24px 56px -24px rgba(0,0,0,.95);
}

/* ── base ───────────────────────────────────────────────────────────────── */
*,*::before,*::after { box-sizing:border-box; }
html { -webkit-text-size-adjust:100%; }
body {
  margin:0; background:var(--bg); color:var(--ink);
  font:400 15px/1.55 var(--sans);
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
}
:lang(fa) body, body:lang(fa) { font-family:'Peyda',var(--sans); }
:lang(fa) h1, :lang(fa) h2, :lang(fa) h3, :lang(fa) .brandname { font-family:'Morabba','Peyda',var(--display); }

h1,h2,h3,h4 { font-family:var(--display); margin:0; letter-spacing:-.015em; text-wrap:balance; line-height:1.22; }
h1 { font-size:clamp(22px,2.6vw,28px); font-weight:600; }
h2 { font-size:17px; font-weight:600; }
h3 { font-size:15px; font-weight:600; }
p { margin:0; }
a { color:var(--accent-text); text-decoration:none; }
a:hover { text-decoration:underline; }
img,svg { max-width:100%; }
hr { border:0; border-top:1px solid var(--line); margin:22px 0; }
:focus-visible { outline:2px solid var(--accent); outline-offset:2px; border-radius:3px; }
[hidden] { display:none !important; }

.sr {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
.skip {
  position:absolute; inset-inline-start:-9999px; top:8px; z-index:100;
  background:var(--surface); border:1px solid var(--line-2); border-radius:var(--radius-sm); padding:8px 14px;
}
.skip:focus { inset-inline-start:12px; }

.muted { color:var(--muted); }
.mono { font-family:var(--mono); font-variant-numeric:tabular-nums; }
.num, .pts, td.num, th.num { font-family:var(--mono); font-variant-numeric:tabular-nums; text-align:end; }
.push { margin-inline-start:auto; }
.scroll { overflow-x:auto; }
.lede { color:var(--ink-2); margin-top:6px; max-width:74ch; font-size:14.5px; }

/* Tones. This is the whole colour vocabulary — nothing else is allowed to be coloured. */
.t-ok,.ok { color:var(--ok); }
.t-warn,.warn { color:var(--warn); }
.t-err,.err { color:var(--err); }
.t-info,.info { color:var(--info); }
.t-muted { color:var(--muted); }

/* ── shell ──────────────────────────────────────────────────────────────── */
.apphead { border-bottom:1px solid var(--line); background:var(--surface); position:sticky; top:0; z-index:40; }
.headin {
  max-width:var(--maxw); margin:0 auto; padding:0 22px;
  display:flex; align-items:center; gap:18px; min-height:54px;
}
body.wide .headin { max-width:var(--maxw-wide); }
.brand { display:flex; align-items:center; gap:9px; color:var(--ink); font-weight:700; letter-spacing:-.02em; }
.brand:hover { text-decoration:none; }
.brandname { font-family:var(--display); font-size:15.5px; }
.mark {
  width:21px; height:21px; border-radius:5px; background:var(--accent); flex:none;
  position:relative;
}
.mark::before {
  content:""; position:absolute; inset:5px; border-radius:1px;
  background:var(--on-accent); transform:rotate(45deg);
}
/* The operator nav carries ten items. Scrolling it beats wrapping the header onto two lines. */
.mainnav { display:flex; gap:1px; flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none; min-width:0; }
.mainnav::-webkit-scrollbar { display:none; }
body.v-operator .mainnav a { padding:5px 8px; font-size:13px; }
.mainnav a {
  color:var(--ink-2); padding:5px 10px; border-radius:var(--radius-sm); font-size:13.5px; white-space:nowrap;
}
.mainnav a:hover { color:var(--ink); background:var(--surface-2); text-decoration:none; }
.mainnav a[aria-current] { color:var(--ink); background:var(--surface-2); font-weight:500; }
.headright { margin-inline-start:auto; display:flex; align-items:center; gap:9px; flex:none; }
.who {
  font-size:12.5px; color:var(--muted); max-width:180px; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap;
}

main {
  /* width:100% matters: an auto cross-axis margin on a flex item cancels `stretch`, and the
     column would otherwise shrink to fit its content instead of filling to --maxw. */
  width:100%; max-width:var(--maxw); margin:0 auto; padding:26px 22px 64px;
  display:flex; flex-direction:column; gap:20px;
}
body.wide main { max-width:var(--maxw-wide); }
main:focus { outline:none; }
.pagehead { display:flex; align-items:flex-start; gap:18px; flex-wrap:wrap; }
.pagehead > div:first-child { flex:1 1 420px; min-width:0; }
.pageactions { display:flex; gap:8px; align-items:center; flex-wrap:wrap; }

.appfoot {
  border-top:1px solid var(--line); margin-top:auto;
  padding:20px 22px; display:flex; gap:14px; flex-wrap:wrap;
  max-width:var(--maxw); margin-inline:auto; width:100%; font-size:13px; color:var(--ink-2);
}
body { min-height:100vh; display:flex; flex-direction:column; }

/* ── forms and buttons ──────────────────────────────────────────────────── */
label { display:block; font-size:13.5px; color:var(--ink-2); margin-bottom:2px; }
input,textarea,select {
  font:inherit; font-size:14px; color:var(--ink); background:var(--surface);
  border:1px solid var(--line-2); border-radius:var(--radius-sm); padding:8px 11px; width:100%;
}
input[type="checkbox"],input[type="radio"] { width:auto; }
input::placeholder,textarea::placeholder { color:var(--muted); }
input:focus,textarea:focus,select:focus { border-color:var(--accent); outline:none; box-shadow:0 0 0 3px var(--accent-soft); }
textarea { resize:vertical; min-height:90px; }
select { cursor:pointer; }

button,.btn {
  font:inherit; font-size:14px; font-weight:500; cursor:pointer; white-space:nowrap;
  background:var(--accent); color:var(--on-accent); border:1px solid var(--accent);
  border-radius:var(--radius-sm); padding:8px 15px; width:auto; display:inline-block;
}
button:hover,.btn:hover { background:var(--accent-2); border-color:var(--accent-2); text-decoration:none; }
.btn.sm,button.sm { padding:5px 11px; font-size:13px; }
.ghost {
  background:var(--surface); color:var(--ink-2); border-color:var(--line-2);
}
.ghost:hover { background:var(--surface-2); color:var(--ink); border-color:var(--line-2); }
/* A destructive action is outlined, not filled. The accent belongs to the one thing a page
   wants you to do, and deleting your account is never that — but it must not be mistaken for a
   save either, which is what it looked like when it wore the same maroon. It fills on hover, so
   the weight of the thing is visible at the moment of the click. */
button.danger,.btn.danger { background:var(--err-bg); color:var(--err); border-color:var(--err-line); }
button.danger:hover,.btn.danger:hover { background:var(--err); color:var(--surface); border-color:var(--err); }
.block { width:100%; display:block; }
.iconbtn {
  background:none; border:1px solid var(--line); color:var(--ink-2); flex:none;
  border-radius:var(--radius-sm); padding:5px 10px; font-size:12.5px; font-weight:400;
}
.iconbtn:hover { background:var(--surface-2); color:var(--ink); border-color:var(--line-2); }
form.inline { display:inline-flex; gap:7px; align-items:center; flex-wrap:wrap; margin:0; }
form.inline input,form.inline select { width:auto; }
form.stack { display:grid; gap:12px; }
/* A grid item stretches to its column, so `width:auto` on a button is not enough on its own:
   without this, every submit in a stacked form rendered as a full-bleed slab. Three of those
   down one page is what left the accent meaning nothing. A button that genuinely wants the
   full width asks for it with `block`, which is what the sign-in card does. */
form.stack > button,form.stack > .btn { justify-self:start; }
form.stack > .block { justify-self:stretch; }

/* Controls that belong together on one line: a pair of buttons, or a short field and its
   action. Used by several templates long before it had a rule to its name. */
.row { display:flex; gap:9px; align-items:flex-end; flex-wrap:wrap; }
.row > button,.row > .btn { flex:none; }

/* A text field is sized to what it holds. A 24-character name in a full-width box reads as a
   text area and tells the reader nothing about what is expected. */
.field { display:grid; gap:5px; min-width:0; }
.field > label { margin:0; }
.field input,.field select { max-width:34ch; }
.field.wide input,.field.wide select { max-width:48ch; }
.field.narrow input { max-width:15ch; }
.field .hint { font-size:12.5px; color:var(--muted); }
/* In a control row the field takes the room up to its own comfortable width and the button
   follows immediately. Capping the input instead of the field would leave a gap between the two
   that reads as a mistake rather than as spacing. */
.row > .field { flex:1 1 180px; max-width:34ch; }
.row > .field input { max-width:none; }
.row > .field.narrow { flex:0 1 auto; max-width:15ch; }

/* ── flash ──────────────────────────────────────────────────────────────── */
.flash {
  border:1px solid var(--line-2); border-inline-start:3px solid var(--muted);
  background:var(--surface-2); border-radius:var(--radius-sm);
  padding:10px 14px; font-size:14px;
}
.flash.ok { border-color:var(--ok-line); border-inline-start-color:var(--ok); background:var(--ok-bg); color:var(--ok); }
.flash.warn { border-color:var(--warn-line); border-inline-start-color:var(--warn); background:var(--warn-bg); color:var(--warn); }
.flash.err { border-color:var(--err-line); border-inline-start-color:var(--err); background:var(--err-bg); color:var(--err); }
.flash.info { border-color:var(--info-line); border-inline-start-color:var(--info); background:var(--info-bg); color:var(--info); }

/* ── panels ─────────────────────────────────────────────────────────────── */
.panel { background:var(--surface); border:1px solid var(--line); border-radius:var(--radius); }
.panel-head {
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  padding:11px 14px; border-bottom:1px solid var(--line);
}
.panel-head h2 { font-size:14px; }
.panel-head .sub { font-size:12.5px; color:var(--muted); }
.panel-actions { margin-inline-start:auto; display:flex; gap:7px; align-items:center; }
.panel-note { padding:11px 14px 0; color:var(--ink-2); font-size:13.5px; max-width:78ch; }
.panel > .scroll { padding:0; }
/* A panel pads its own body. Bodies that already carry a gutter — a table's cells, a row list —
   opt out with `flush`, which is why the padding lives on this wrapper rather than on `.panel`.
   Before this existed, every panel holding prose or a form ran its content into the border and
   pushed full-width inputs past it. */
.panel-body { padding:14px; display:grid; gap:12px; align-content:start; }
.panel-note + .panel-body { padding-block-start:11px; }
.panel-body > .empty { padding:16px 0; }
/* Prose in a panel gets a reading measure, the same as a lede or a panel note. A paragraph run
   to the full width of a 1180px column is a wall. */
.panel-body > p { max-width:78ch; }
/* A panel whose action cannot be undone. Colour carrying meaning, as everywhere else: the
   hairline and the head are enough to set it apart without turning it into a warning poster. */
.panel.danger { border-color:var(--err-line); }
.panel.danger > .panel-head { background:var(--err-bg); border-bottom-color:var(--err-line); }
.panel.danger > .panel-head h2 { color:var(--err); }
.panel .empty { padding:30px 14px; text-align:center; color:var(--muted); font-size:14px; }
.empty { color:var(--muted); font-size:14px; }
td.empty { text-align:center; padding:26px 14px; }

/* ── stats ──────────────────────────────────────────────────────────────── */
.stats { display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:12px; }
.stat {
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius);
  padding:13px 15px; display:grid; gap:2px; color:var(--ink);
}
a.stat:hover { border-color:var(--line-2); text-decoration:none; }
.stat .n { font-family:var(--display); font-size:26px; font-weight:600; font-variant-numeric:tabular-nums; letter-spacing:-.02em; }
.stat .l { font-size:13px; color:var(--ink-2); }
.stat .s { font-size:12px; color:var(--muted); }
.stat.t-err .n { color:var(--err); }
.stat.t-warn .n { color:var(--warn); }
.stat.t-ok .n { color:var(--ok); }

/* ── tables ─────────────────────────────────────────────────────────────── */
table.board,table.data { width:100%; border-collapse:collapse; font-size:14px; }
table.board th,table.data th {
  text-align:start; font-weight:600; font-size:12.5px; color:var(--ink-2);
  padding:9px 12px; border-bottom:1px solid var(--line); background:var(--surface-2);
  white-space:nowrap; position:sticky; top:0; z-index:1;
}
table.board td,table.data td { padding:9px 12px; border-bottom:1px solid var(--line); }
table.board tbody tr:last-child td { border-bottom:0; }
table.board th.check,table.board td.check { width:1%; text-align:center; padding-inline-end:0; }
table.board tbody tr:hover { background:var(--surface-2); }
td.rk { font-family:var(--mono); color:var(--muted); text-align:end; width:46px; }
/* One column per table takes the leftover width so the numeric columns sit together on the
   right instead of drifting apart across the table. */
th.grow, td.grow { width:100%; }
/* A recommendation tier is a label, not prose: wrapping it over three lines to avoid a
   horizontal scrollbar makes the table harder to read, not easier. The same goes for every
   numeric or measured cell — "22 hours ago" broken over two lines made each leaderboard row
   two lines tall and the whole table ragged. Dense tables scroll; they do not wrap. */
td.tier, th.tier, td.num, th.num { white-space:nowrap; }

/* The standings bar: the shape of the field, in the width the name column used to waste. It is
   drawn in neutral ink because every row has one — the accent is spent on the reader's own row,
   which is the single thing they came to the page to find. */
td.share { width:100%; padding-inline:16px; }
td.share progress { background:var(--surface-2); }
td.share progress::-webkit-progress-value { background:var(--line-2); }
td.share progress::-moz-progress-bar { background:var(--line-2); }
tr.me td.share progress::-webkit-progress-value { background:var(--accent); }
tr.me td.share progress::-moz-progress-bar { background:var(--accent); }
/* Not `who`: that class is the signed-in user chip in the header, it is muted, clipped at
   180px and hidden below 760px — reusing the name emptied the participant column on a phone. */
td.player { white-space:nowrap; }
td.player .pill { margin-inline-start:7px; }
/* Below this the table already scrolls sideways; the bar is the first thing worth losing. */
@media (max-width:760px) { td.share, th.share { display:none; } }
tr.me { background:var(--accent-soft); }
tr.me td { font-weight:500; }
tr.row-err { background:var(--err-bg); }
tr.row-ok td:first-child { box-shadow:inset 3px 0 0 var(--ok); }
th.sortable a { color:inherit; display:inline-flex; gap:4px; align-items:center; }
th.sortable .arrow { color:var(--accent-text); font-size:11px; }
th.sorted { color:var(--ink); }
.medal {
  display:inline-grid; place-items:center; width:20px; height:20px; border-radius:50%;
  font-size:11.5px; font-weight:600; color:#3a2a10;
}
.m1 { background:#e8c35c; } .m2 { background:#cdcdd3; } .m3 { background:#d9a273; }

/* ── chips, pills, tags ─────────────────────────────────────────────────── */
.pill,.chip,.badge {
  display:inline-flex; align-items:center; gap:5px; border-radius:20px;
  font-size:11.5px; font-weight:500; padding:2px 9px;
  border:1px solid var(--line-2); background:var(--surface-2); color:var(--ink-2);
  white-space:nowrap;
}
.chip.t-ok { background:var(--ok-bg); color:var(--ok); border-color:var(--ok-line); }
.chip.t-warn { background:var(--warn-bg); color:var(--warn); border-color:var(--warn-line); }
.chip.t-err { background:var(--err-bg); color:var(--err); border-color:var(--err-line); }
.chip.t-info { background:var(--info-bg); color:var(--info); border-color:var(--info-line); }
.pill.t-ok { color:var(--ok); border-color:var(--ok-line); background:var(--ok-bg); }
.pill.t-warn { color:var(--warn); border-color:var(--warn-line); background:var(--warn-bg); }
.tag {
  font-family:var(--mono); font-size:11.5px; color:var(--ink-2);
  background:var(--surface-2); border:1px solid var(--line); padding:2px 7px; border-radius:var(--radius-sm);
}
.pts { font-family:var(--mono); font-variant-numeric:tabular-nums; font-weight:500; font-size:14px; color:var(--ink); }
.pts small { font-size:10.5px; color:var(--muted); font-weight:400; margin-inline-start:2px; }

/* ── the khatam difficulty mark ─────────────────────────────────────────── */
/* Each step is an eight-point star: a square plus the same square rotated 45°, which is how the
   khatam pattern is actually constructed. Filled to the challenge's difficulty rank. */
.khatam { display:inline-flex; gap:5px; align-items:center; vertical-align:middle; }
.khatam i {
  width:9px; height:9px; display:block; position:relative; transform:rotate(45deg);
  box-shadow:inset 0 0 0 1px var(--line-2);
}
.khatam i::before {
  content:""; position:absolute; inset:0; transform:rotate(45deg);
  box-shadow:inset 0 0 0 1px var(--line-2);
}
.khatam i.on,.khatam i.on::before { background:var(--ink-2); box-shadow:none; }
.khatam[data-rank="3"] i.on,.khatam[data-rank="3"] i.on::before { background:var(--accent); }

/* ── bars and charts ────────────────────────────────────────────────────── */
.bar { display:block; height:6px; border-radius:3px; background:var(--surface-2); overflow:hidden; }
.bar i { display:block; height:100%; background:var(--accent); border-radius:3px; }
.bar i.t-ok { background:var(--ok); } .bar i.t-warn { background:var(--warn); } .bar i.t-err { background:var(--err); }
.barrow { display:grid; grid-template-columns:96px minmax(0,1fr) 46px; gap:11px; align-items:center; font-size:13px; padding:3px 0; }
.barrow .bl { color:var(--ink-2); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.barrow .bv { font-family:var(--mono); font-variant-numeric:tabular-nums; font-size:12.5px; color:var(--ink-2); text-align:end; }
.spark { display:block; width:100%; height:46px; }
.sparkline { stroke:var(--accent); stroke-width:1.5; stroke-linejoin:round; }
.sparkfill { fill:var(--accent-soft); stroke:none; }
.sparkdot { fill:var(--accent); }

progress { width:100%; height:6px; border:0; border-radius:3px; background:var(--surface-2); display:block; }
progress::-webkit-progress-bar { background:var(--surface-2); border-radius:3px; }
progress::-webkit-progress-value { background:var(--accent); border-radius:3px; }
progress::-moz-progress-bar { background:var(--accent); border-radius:3px; }

.legend { display:flex; gap:14px; flex-wrap:wrap; font-size:12px; color:var(--muted); }
.legend-item { display:inline-flex; align-items:center; gap:5px; }
.swatch { width:11px; height:11px; border-radius:3px; border:1px solid var(--line-2); display:inline-block; }

/* ── heat scale ─────────────────────────────────────────────────────────── */
td.heat-0 { color:var(--muted); }
td.heat-1 { background:rgba(var(--heat),.10); }
td.heat-2 { background:rgba(var(--heat),.24); }
td.heat-3 { background:rgba(var(--heat),.44); }
td.heat-4 { background:rgba(var(--heat),.66); }
:root:not([data-theme="dark"]) td.heat-3,:root:not([data-theme="dark"]) td.heat-4 { color:#fff; }
/* A score backed by too few solves is drawn as provisional — an outline, never a fill, so it
   cannot be misread as a measurement. */
td.heat-thin {
  background:repeating-linear-gradient(135deg,transparent,transparent 4px,rgba(var(--heat),.14) 4px,rgba(var(--heat),.14) 8px);
  box-shadow:inset 0 0 0 1px var(--line-2); color:var(--ink-2);
}
td.heat-thin small { color:var(--muted); font-size:10px; margin-inline-start:3px; font-family:var(--mono); }

/* Column groups: one hairline where the meaning of the columns changes, plus a quiet caption
   row, so a reader can see where "points by domain" ends and "solves by difficulty" begins. */
table.board th.groupstart, table.board td.groupstart { border-inline-start:1px solid var(--line-2); }
tr.groupbar td {
  background:var(--surface-2); color:var(--muted); font-size:11px; padding:3px 12px;
  border-bottom:1px solid var(--line); position:sticky; top:34px; z-index:1;
}
.swatch.heat-1 { background:rgba(var(--heat),.10); }
.swatch.heat-2 { background:rgba(var(--heat),.24); }
.swatch.heat-3 { background:rgba(var(--heat),.44); }
.swatch.heat-4 { background:rgba(var(--heat),.66); }
.swatch.heat-thin { background:repeating-linear-gradient(135deg,transparent,transparent 3px,rgba(var(--heat),.2) 3px,rgba(var(--heat),.2) 6px); }

/* ── filter bar ─────────────────────────────────────────────────────────── */
.filterbar { display:flex; gap:9px; align-items:center; flex-wrap:wrap; }
.filterbar .search { position:relative; flex:1 1 240px; min-width:190px; margin:0; }
.filterbar .search input { padding-inline-end:32px; }
.filterbar .search kbd {
  position:absolute; inset-inline-end:8px; top:50%; transform:translateY(-50%);
  font-family:var(--mono); font-size:11px; color:var(--muted);
  border:1px solid var(--line); border-radius:3px; padding:0 5px; background:var(--surface-2);
}
.filterbar .selwrap { margin:0; }
.filterbar .count { font-size:12.5px; color:var(--muted); margin-inline-start:auto; }
.selwrap select { width:auto; }

/* ── cards and grids ────────────────────────────────────────────────────── */
.grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(310px,1fr)); gap:14px; }
.grid.two { grid-template-columns:repeat(auto-fit,minmax(340px,1fr)); }
.card {
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius);
  padding:15px; display:flex; flex-direction:column; gap:10px;
}
.card.solved { border-color:var(--ok-line); }
.chal-head { display:flex; align-items:center; gap:9px; flex-wrap:wrap; }

/* ── challenge rows (the board) ─────────────────────────────────────────── */
.chal { display:grid; gap:0; }
.chal > li { list-style:none; border-bottom:1px solid var(--line); }
.chal > li:last-child { border-bottom:0; }
.chal-row {
  display:grid; grid-template-columns:14px minmax(0,1fr) auto auto auto; gap:14px;
  align-items:center; padding:12px 14px;
}
.chal-row:hover { background:var(--surface-2); }
.chal-dot { width:8px; height:8px; border-radius:50%; border:1.5px solid var(--line-2); justify-self:center; }
.chal.solved .chal-dot,li.solved .chal-dot { background:var(--ok); border-color:var(--ok); }
.chal-title { font-weight:500; font-size:14.5px; color:var(--ink); }
.chal-meta { display:flex; gap:9px; align-items:center; font-size:12.5px; color:var(--muted); margin-top:2px; flex-wrap:wrap; }
.vrule { width:1px; height:11px; background:var(--line-2); display:inline-block; }
.chal-body { padding:0 14px 14px; display:grid; gap:10px; }
.env-up {
  font-family:var(--mono); font-size:13px; background:var(--surface-2);
  border:1px solid var(--line); border-radius:var(--radius-sm); padding:8px 11px;
  display:flex; gap:10px; align-items:center; flex-wrap:wrap;
}
.seed code { font-family:var(--mono); font-size:12.5px; word-break:break-all; }
details.writeup summary,details.lesson summary { cursor:pointer; font-size:14px; }
details.writeup { border-top:1px solid var(--line); padding-top:9px; }
pre.writeup-body {
  white-space:pre-wrap; word-break:break-word; font-family:var(--mono); font-size:13px;
  background:var(--surface-2); border:1px solid var(--line); border-radius:var(--radius-sm);
  padding:11px; max-height:340px; overflow:auto; margin:9px 0 0;
}

/* ── academy ────────────────────────────────────────────────────────────── */
.lesson { padding:8px 0; border-bottom:1px solid var(--line); }
.lesson:last-child { border-bottom:0; }
.doc { padding:8px 0; color:var(--ink-2); max-width:74ch; }
.brief { border-inline-start:2px solid var(--accent-line); padding-inline-start:12px; margin:0 0 10px; }
.hintblock { margin:0 0 12px; }
.footlinks { display:flex; flex-wrap:wrap; gap:12px; }
.footlinks a { color:var(--muted); }
.footlinks a:hover { color:var(--ink-2); }
.policy { max-width:64ch; }
/* Label/value pairs for a review record: the facts, not a form. */
.kv { display:grid; grid-template-columns:max-content 1fr; gap:6px 14px; margin:0 0 12px; }
.kv dt { color:var(--muted); font-size:12.5px; }
.kv dd { margin:0; }
.kv pre, .flash pre { white-space:pre-wrap; overflow-x:auto; margin:6px 0 0; font-size:12.5px; }
/* A lab runs inside its module: launch, open, submit, without leaving the lesson. */
.labctl { display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin:2px 0 10px; }
/* A solved lab says so where the flag box used to be. Before this the input simply vanished. */
.lab-solved { margin:0 0 8px; font-size:14px; }
.lab-solved b { color:var(--ok); }
/* The corner stack: what just happened to a lab, and anything still running. A line at the top of
   the page was the wrong place — launching a lab sent the reader up to read about a link that was
   back down where they started. */
.toasts { position:fixed; inset-block-end:16px; inset-inline-end:16px; z-index:40; display:flex; flex-direction:column; gap:8px; align-items:flex-end; max-width:min(92vw,420px); }
.toast { display:flex; align-items:center; gap:10px; width:100%; padding:11px 13px; border:1px solid var(--line); border-left:3px solid var(--line-2); border-radius:12px; background:var(--surface); box-shadow:0 8px 28px rgba(0,0,0,.18); font-size:13px; animation:toast-in .18s ease-out; }
.toast.t-ok { border-left-color:var(--ok); }
.toast.t-warn { border-left-color:var(--warn); }
.toast.t-err { border-left-color:var(--err); }
.toast.t-running { border-left-color:var(--ok); }
.toast-body { flex:1; min-width:0; display:flex; flex-direction:column; gap:1px; }
.toast-body b { font-size:13.5px; }
.toast-body span { color:var(--muted); font-size:12px; }
.toast-dot { width:8px; height:8px; border-radius:50%; background:var(--ok); flex:none; box-shadow:0 0 0 3px color-mix(in srgb, var(--ok) 25%, transparent); }
.toast form { margin:0; }
.toast-x { width:22px; height:22px; padding:0; flex:none; border:0; background:none; color:var(--muted); font-size:16px; line-height:1; cursor:pointer; }
.toast-x:hover { color:var(--ink); }
.toast.leaving { opacity:0; transform:translateY(6px); transition:opacity .25s ease, transform .25s ease; }
@keyframes toast-in { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:none; } }
@media (prefers-reduced-motion:reduce) { .toast { animation:none; } .toast.leaving { transition:none; } }
@media (max-width:520px) { .toasts { inset-inline:12px; max-width:none; align-items:stretch; } }
.labctl .lab-submit { display:flex; gap:6px; align-items:center; }
.labctl .lab-submit input { min-width:16ch; }
.visually-hidden {
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}
.hints { margin:0 0 8px; padding-inline-start:20px; }
.hints li { margin:0 0 4px; }
/* The lab briefing's hint ladder and write-up, below the launch controls. */
.labhelp { display:grid; gap:8px; margin:4px 0 0; }
details.hints { padding-inline-start:0; }
.hint-ladder { margin:8px 0 6px; padding-inline-start:22px; color:var(--ink-2); }
.hint-ladder li { margin:0 0 6px; }
.doc p { margin:0 0 9px; }
.doc h4 { margin:14px 0 6px; font-size:14.5px; color:var(--ink); }
.doc ul { margin:0 0 9px; padding-inline-start:20px; }
.doc code { font-family:var(--mono); font-size:12.5px; background:var(--surface-2); padding:1px 5px; border-radius:4px; }
.doc pre {
  font-family:var(--mono); font-size:12.5px; background:var(--surface-2);
  border:1px solid var(--line); border-radius:var(--radius-sm); padding:11px; overflow-x:auto;
}
.modprog { display:flex; align-items:center; gap:10px; font-size:12.5px; color:var(--muted); }
.modprog progress { width:90px; }
/* Quizzes: questions as a plain form (works with scripting off), reviewed with the key afterwards. */
.lesson.quiz { padding:8px 0; }
.quiz-list { list-style:none; margin:6px 0 12px; padding:0; display:grid; gap:14px; }
.quiz-q { padding:0; }
.quiz-prompt { margin:0 0 6px; font-weight:500; }
.quiz-opts { list-style:none; margin:0; padding:0; display:grid; gap:5px; }
.quiz-opts label { display:flex; gap:8px; align-items:flex-start; cursor:pointer; }
.quiz-opts .opt-correct { color:var(--ok); }
.quiz-opts .opt-wrong { color:var(--err); }
.quiz-order { margin:0; padding-inline-start:20px; display:grid; gap:5px; }
.quiz-order select { width:auto; display:inline-block; margin-inline-end:6px; }
.quiz-short { max-width:32ch; }
.quiz-explain { margin:6px 0 0; font-size:13px; color:var(--ink-2); }

/* ── account identity ───────────────────────────────────────────────────── */
/* Who the reader is, stated once at the top of their account, so the panels below are only
   about changing it. The mark is an initial on a neutral ground rather than a coloured avatar:
   colour here would carry no meaning, and the chrome stays neutral so the places colour does
   mean something still read. */
.idcard {
  display:flex; align-items:center; gap:16px; flex-wrap:wrap;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius);
  padding:15px 17px;
}
.avatar {
  width:46px; height:46px; border-radius:var(--radius-lg); flex:none;
  display:grid; place-items:center; text-transform:uppercase;
  background:var(--surface-2); border:1px solid var(--line-2); color:var(--ink);
  font-family:var(--display); font-size:21px; font-weight:600; line-height:1;
}
.idmain { flex:1 1 260px; min-width:0; }
.idname { font-size:19px; }
.idmeta {
  display:flex; align-items:center; gap:9px; flex-wrap:wrap;
  margin-top:4px; font-size:13px; color:var(--muted);
}
/* The separator leads the item it precedes rather than sitting between them as its own box, so
   a meta line that wraps does not strand a rule at the end of the first line. */
.idmeta > * + *::before { content:"·"; margin-inline-end:9px; color:var(--line-2); }
.idnote { margin-top:6px; font-size:13px; max-width:70ch; }
.idpills { display:flex; gap:7px; flex-wrap:wrap; margin-inline-start:auto; }

/* A description on one side and the control it describes on the other, rather than a button
   orphaned under a paragraph. */
.actionrow { display:flex; align-items:center; gap:18px; flex-wrap:wrap; }
.actionrow > p { flex:1 1 340px; min-width:0; }
.actionrow > .btn,.actionrow > button { flex:none; margin-inline-start:auto; }

/* ── auth screens ───────────────────────────────────────────────────────── */
.authpage { display:grid; place-items:center; padding:34px 0; }
.auth-card {
  width:min(420px,100%); background:var(--surface); border:1px solid var(--line);
  border-radius:var(--radius-lg); padding:26px; display:grid; gap:14px; box-shadow:var(--shadow);
}
.auth-card h1 { font-size:22px; }
.auth-card .hint { font-size:13.5px; color:var(--muted); }

/* ── landing ────────────────────────────────────────────────────────────── */
body.v-public main { gap:0; padding-bottom:0; max-width:none; padding-inline:0; }
.hero {
  max-width:var(--maxw); margin:0 auto; padding:56px 22px 60px;
  display:grid; grid-template-columns:minmax(0,1.15fr) minmax(320px,.85fr); gap:52px; align-items:center;
}
.hero h1 { font-size:clamp(30px,4.6vw,50px); font-weight:700; letter-spacing:-.03em; line-height:1.05; }
.hero .lead { color:var(--ink-2); font-size:17px; margin-top:16px; max-width:52ch; line-height:1.55; }
.eyebrow { font-size:12.5px; color:var(--accent-text); font-weight:500; display:block; margin-bottom:12px; }
.solveline {
  margin-top:26px; display:flex; gap:11px; align-items:center; flex-wrap:wrap;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--radius);
  padding:11px 14px; font-family:var(--mono); font-size:13px;
}
.solveline .flag { color:var(--ink); word-break:break-all; }
.solveline .tick { color:var(--ok); margin-inline-start:auto; white-space:nowrap; }
.trust { display:flex; gap:20px; flex-wrap:wrap; margin-top:22px; font-size:13.5px; color:var(--muted); }
.trust b { color:var(--ink-2); font-weight:600; }
/* hero focal moments (ADR 0025): a real solve on the CTF front, the lesson→flag path on the Academy front */
.termcard { margin-top:26px; max-width:52ch; border:1px solid var(--line); border-radius:var(--radius); background:var(--surface); overflow:hidden; box-shadow:var(--shadow); }
.termbar { display:flex; align-items:center; gap:7px; padding:9px 13px; background:var(--surface-2); border-bottom:1px solid var(--line); }
.termbar .dot { width:9px; height:9px; border-radius:50%; background:var(--line-2); }
.termbar .termtitle { margin-inline-start:8px; font-family:var(--mono); font-size:12px; color:var(--muted); }
.termbody { margin:0; padding:14px 15px; font-family:var(--mono); font-size:13px; line-height:1.75; color:var(--ink); white-space:pre-wrap; word-break:break-word; }
.termbody .tprompt { color:var(--accent-text); margin-inline-end:7px; }
.termbody .tok { color:var(--ok); font-weight:600; }
.termbody .tmut { color:var(--muted); }
.pathstep { list-style:none; margin:26px 0 0; padding:0; display:flex; flex-wrap:wrap; gap:10px; counter-reset:step; }
.pathstep li { display:flex; align-items:center; gap:9px; font-size:14px; color:var(--ink-2); background:var(--surface); border:1px solid var(--line); border-radius:999px; padding:8px; padding-inline-end:15px; }
.pathstep .pn { display:grid; place-items:center; width:23px; height:23px; border-radius:50%; background:var(--accent-soft); color:var(--accent-text); border:1px solid var(--accent-line); font-family:var(--mono); font-size:12px; font-weight:600; }
.tabs { display:flex; gap:3px; background:var(--surface-2); border-radius:var(--radius-sm); padding:3px; }
.tabs a {
  flex:1; text-align:center; padding:8px 10px; border-radius:var(--radius-sm);
  font-size:13.5px; font-weight:500; color:var(--ink-2);
}
.tabs a:hover { text-decoration:none; color:var(--ink); }
.tabs a[aria-selected="true"] { background:var(--surface); color:var(--ink); box-shadow:var(--shadow); }
.auth-body { display:grid; gap:12px; }
.auth-body h2 { font-size:18px; }
.auth-body .sub { font-size:13.5px; color:var(--muted); }
.band { border-top:1px solid var(--line); background:var(--surface); }
.band:nth-of-type(even) { background:var(--bg); }
.band > .bandin { max-width:var(--maxw); margin:0 auto; padding:54px 22px; }
.bandcta { display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin:22px 0 0; font-size:13.5px; }
.sechead { max-width:60ch; margin-bottom:28px; }
.sechead h2 { font-size:clamp(21px,2.7vw,28px); }
.sechead p { color:var(--ink-2); margin-top:9px; }
.features { display:grid; grid-template-columns:repeat(auto-fit,minmax(250px,1fr)); gap:18px; }
.feature { display:grid; gap:9px; align-content:start; padding-top:16px; border-top:2px solid var(--accent-line); }
.feature h3 { font-size:15.5px; }
.feature p { color:var(--ink-2); font-size:14px; }
.feature .tags { display:flex; gap:6px; flex-wrap:wrap; }
.secband { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1.1fr); gap:44px; align-items:start; }
.secband p.muted { color:var(--ink-2); margin-top:9px; }
.checks { display:grid; gap:14px; }
/* skill-map centerpiece (Academy) + closing CTA bands (ADR 0025 landing v2) */
.skillmap { display:grid; grid-template-columns:repeat(auto-fit,minmax(230px,1fr)); gap:16px; }
.smcat { border:1px solid var(--line); border-radius:var(--radius-lg); background:var(--surface); padding:18px; display:grid; gap:12px; align-content:start; box-shadow:var(--shadow); }
.smcat h3 { font-size:14.5px; color:var(--accent-text); }
.smsubs { display:flex; flex-wrap:wrap; gap:7px; }
.smsubs span { font-size:12.5px; color:var(--ink-2); background:var(--surface-2); border:1px solid var(--line); border-radius:999px; padding:5px 11px; }
.ctaband .bandin { text-align:center; }
.ctawrap { max-width:56ch; margin:0 auto; display:grid; gap:13px; justify-items:center; }
.ctawrap h2 { font-size:clamp(22px,3vw,30px); }
.ctawrap p { color:var(--ink-2); }
.btn.lg { font-size:15px; padding:12px 22px; }
.ctasub { font-size:13.5px; color:var(--muted); margin-top:2px; }
.check { display:flex; gap:11px; align-items:flex-start; }
.check .c {
  color:var(--ok); background:var(--ok-bg); border:1px solid var(--ok-line);
  width:21px; height:21px; border-radius:50%; display:grid; place-items:center; font-size:12px; flex:none;
}
.check b { display:block; font-size:14.5px; }
.check small { color:var(--ink-2); font-size:13px; }

/* ── floating layers ────────────────────────────────────────────────────── */
.toasts { position:fixed; inset-block-end:20px; inset-inline-end:20px; z-index:70; display:grid; gap:8px; justify-items:end; }
.toast {
  background:var(--raise); border:1px solid var(--line-2); border-inline-start:3px solid var(--accent);
  border-radius:var(--radius-sm); padding:10px 14px; font-size:13.5px; box-shadow:var(--shadow-lg);
  max-width:330px; animation:risein .25s ease;
}
.toast.t-ok { border-inline-start-color:var(--ok); }
.toast.t-err { border-inline-start-color:var(--err); }
@keyframes risein { from { opacity:0; transform:translateY(8px); } to { opacity:1; transform:none; } }

.palette { position:fixed; inset:0; z-index:80; background:rgba(20,14,18,.45); display:grid; place-items:start center; padding-top:12vh; }
.palette-box {
  width:min(560px,92vw); background:var(--raise); border:1px solid var(--line-2);
  border-radius:var(--radius-lg); box-shadow:var(--shadow-lg); overflow:hidden;
}
.palette-box input { border:0; border-bottom:1px solid var(--line); border-radius:0; font-size:16px; padding:15px 17px; background:none; }
.palette-box input:focus { box-shadow:none; }
.palette-list { list-style:none; margin:0; padding:6px; max-height:min(48vh,320px); overflow:auto; }
.palette-list li { padding:9px 12px; border-radius:var(--radius-sm); font-size:14px; display:flex; gap:10px; align-items:center; cursor:pointer; }
.palette-list li .k { font-family:var(--mono); font-size:11.5px; color:var(--muted); margin-inline-start:auto; }
.palette-list li[aria-selected="true"] { background:var(--accent-soft); color:var(--accent-text); }
.palette-foot { border-top:1px solid var(--line); padding:8px 14px; font-size:12px; color:var(--muted); }

.tip {
  position:fixed; z-index:60; pointer-events:none; background:var(--raise);
  border:1px solid var(--line-2); border-radius:var(--radius-sm); padding:7px 10px;
  font-size:12.5px; box-shadow:var(--shadow-lg); opacity:0; transition:opacity .12s;
}
.tip.on { opacity:1; }

/* An expanded panel fills the window. It leaves a placeholder behind so the page does not jump. */
.panel.expanded {
  position:fixed; inset:3vh 3vw; z-index:75; margin:0; overflow:auto;
  box-shadow:var(--shadow-lg); background:var(--surface);
}
.expand-placeholder { border:1px dashed var(--line-2); border-radius:var(--radius); }
.livedot { width:7px; height:7px; border-radius:50%; background:var(--err); display:inline-block; animation:pulse 1.9s ease-in-out infinite; }
/* A pulsing red dot with only a tooltip reads as an alarm. It says what it means. */
.liveflag { display:inline-flex; align-items:center; gap:6px; font-size:12px; color:var(--muted); }
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:.3; } }
tr.bump { animation:bump 1s ease; }
@keyframes bump { from { background:var(--accent-soft); } to { background:transparent; } }

/* ── quantised widths ───────────────────────────────────────────────────── */
/* Inline styles are forbidden by the CSP, so proportional widths come from here. */
.w-0{width:0} .w-5{width:5%} .w-10{width:10%} .w-15{width:15%} .w-20{width:20%}
.w-25{width:25%} .w-30{width:30%} .w-35{width:35%} .w-40{width:40%} .w-45{width:45%}
.w-50{width:50%} .w-55{width:55%} .w-60{width:60%} .w-65{width:65%} .w-70{width:70%}
.w-75{width:75%} .w-80{width:80%} .w-85{width:85%} .w-90{width:90%} .w-95{width:95%}
.w-100{width:100%}

/* ── responsive ─────────────────────────────────────────────────────────── */
@media (max-width:900px) {
  .hero { grid-template-columns:minmax(0,1fr); gap:34px; padding-top:36px; }
  .secband { grid-template-columns:minmax(0,1fr); gap:26px; }
}
@media (max-width:760px) {
  .headin { padding:8px 16px; flex-wrap:wrap; min-height:0; }
  .mainnav { order:3; width:100%; overflow-x:auto; }
  main { padding:18px 16px 48px; }
  .chal-row { grid-template-columns:14px minmax(0,1fr) auto; row-gap:8px; }
  .chal-row .khatam,.chal-row .tag { grid-column:2; }
  .who { display:none; }
  /* The card has wrapped by here: a right-aligned pill on its own line reads as stranded. */
  .idpills { margin-inline-start:0; }
}
@media (prefers-reduced-motion:reduce) { *,*::before,*::after { animation:none !important; transition:none !important; } }

/* ── print ──────────────────────────────────────────────────────────────── */
@media print {
  .apphead,.appfoot,.pageactions,.filterbar,.toasts,.palette,.skip,form { display:none !important; }
  body { background:#fff; color:#000; }
  .panel,.stat,.card { break-inside:avoid; border-color:#bbb; }
  table.board th { background:#eee; }
}

/* ── live feed ──────────────────────────────────────────────────────────── */
.feed { list-style:none; margin:0; padding:6px 0; max-height:230px; overflow:auto; }
.feed li { padding:7px 14px; font-size:13px; color:var(--ink-2); display:flex; gap:7px; align-items:baseline; }
.feed li b { color:var(--ink); font-weight:500; }
.feed li .k { margin-inline-start:auto; font-size:11.5px; color:var(--muted); flex:none; }
.feed li.t-ok b { color:var(--ok); }
.feed li.t-err b { color:var(--err); }
.feed li.empty { padding:22px 14px; justify-content:center; }

/* Challenge rows are <summary> elements: drop the disclosure triangle and lay them out as a grid. */
.chalitem > summary { list-style:none; cursor:pointer; }
.chalitem > summary::-webkit-details-marker { display:none; }
.chalitem > summary::marker { content:""; }
.chal-main { min-width:0; }
.chal-stages { display:flex; align-items:center; gap:7px; min-width:120px; }
.chal-stages .bar { width:70px; }

/* The way out of the current console. Quiet, and outside the scrolling nav. */
.crosslink {
  font-size:12.5px; color:var(--muted); white-space:nowrap; flex:none;
  border-inline-end:1px solid var(--line); padding-inline-end:10px;
}
.crosslink:hover { color:var(--ink); }
.marklogo { width:22px; height:22px; flex:none; object-fit:contain; border-radius:5px; }

/* ── bidirectional text ─────────────────────────────────────────────────── */
/* In an RTL document, a run of Latin text ("6 of 10", "HAFEZ{...}", a challenge title) is
   reordered by the bidi algorithm and comes out scrambled — "6 of 10" renders as "of 10 6".
   `plaintext` resolves each run by its own first strong character instead of inheriting the
   paragraph direction, which is the correct treatment for data and for content whose language
   is not the interface language. This is not a temporary patch for untranslated copy: a Persian
   interface will always carry English technique names, flags and identifiers. */
.mono, .num, .pts, .tag, .countdown, .stat .n, .barrow .bv, code, pre, .flag, .env-name {
  unicode-bidi:plaintext;
}
html[dir="rtl"] :is(p, h1, h2, h3, li, td, th, .lede, .chal-title, .chal-meta, .stat .l, .stat .s, summary) {
  unicode-bidi:plaintext;
}

/* ── operator sidebar ───────────────────────────────────────────────────── */
/* The console has nine sections. A horizontal bar dropped items off the end where nobody could
   reach them, so the operator navigates from a grouped vertical rail instead. The participant
   view keeps its top bar — three items fit, and the difference makes it obvious at a glance
   which console you are in. */
.shellmain { display:flex; flex-direction:column; min-width:0; }
body.v-operator { display:grid; grid-template-columns:238px minmax(0,1fr); }
body.v-operator main { max-width:var(--maxw); padding-top:22px; }
/* A wide page fills the column the sidebar leaves; capping it again just squeezes the table. */
body.v-operator.wide main { max-width:none; }

.sidebar {
  border-inline-end:1px solid var(--line); background:var(--surface);
  position:sticky; top:0; height:100vh; overflow:hidden;
  display:flex; flex-direction:column; gap:14px; padding:14px 12px;
}
.sidetop { display:flex; flex-direction:column; gap:10px; padding:2px 6px 0; }
/* Only the section list scrolls. The brand, the way out and the account row stay on screen at
   any window height — a way out that scrolls off the bottom is a way out nobody finds. */
.sidenav { flex:1 1 auto; min-height:0; overflow-y:auto; display:flex; flex-direction:column; gap:16px; }
.sidegroup { display:flex; flex-direction:column; gap:1px; }
.sidelabel {
  font-family:var(--sans); font-size:11px; font-weight:500; color:var(--muted);
  letter-spacing:.02em; padding:0 8px 4px; margin:0;
}
.sidenav a {
  display:flex; align-items:center; gap:9px; padding:6px 9px; border-radius:var(--radius-sm);
  color:var(--ink-2); font-size:13.5px; line-height:1.35;
}
.sidenav a:hover { background:var(--surface-2); color:var(--ink); text-decoration:none; }
.sidenav a[aria-current] { background:var(--accent-soft); color:var(--accent-text); font-weight:500; }
.sidenav a[aria-current] .ico { color:var(--accent-text); }
.ico { flex:none; color:var(--muted); }
.sidenav a:hover .ico { color:var(--ink-2); }

/* The switch back to the participant view. It reads as a control, not as footer small print:
   a real target with a border and a hover state, directly under the lockup that says which
   console this is. */
.modeswitch {
  display:flex; align-items:center; gap:8px;
  padding:6px 9px; border:1px solid var(--line); border-radius:var(--radius-sm);
  color:var(--ink-2); font-size:12.5px; line-height:1.35;
}
.modeswitch:hover {
  background:var(--surface-2); color:var(--ink); border-color:var(--ink-2);
  text-decoration:none;
}
.modeswitch:hover .ico { color:var(--ink-2); }
/* The arrow means "back to where you came from", so it follows the reading direction. */
html[dir="rtl"] .modeswitch .ico { transform:scaleX(-1); }

.sidefoot {
  flex:none; padding-top:12px; border-top:1px solid var(--line);
  display:flex; flex-direction:column; gap:9px;
}
.siderow { display:flex; align-items:center; gap:8px; flex-wrap:wrap; padding:0 8px; }
.siderow .who { max-width:100%; font-size:12px; }

/* Narrow screens: the rail becomes a strip above the content. Pure CSS, so it still works with
   scripting disabled — an operator on a phone is rare, but "rare" is not "broken". */
@media (max-width:920px) {
  body.v-operator { grid-template-columns:minmax(0,1fr); }
  .sidebar {
    height:auto; position:sticky; top:0; z-index:40; gap:10px; overflow:visible;
    border-inline-end:0; border-bottom:1px solid var(--line);
  }
  /* The lockup and the way out share one row, so the switch stays in view here too. */
  .sidetop { flex-direction:row; align-items:center; justify-content:space-between; gap:10px; }
  .modeswitch { flex:none; }
  .sidenav { flex-direction:row; overflow-x:auto; gap:10px; scrollbar-width:none; }
  .sidenav::-webkit-scrollbar { display:none; }
  .sidegroup { flex-direction:row; gap:1px; }
  .sidelabel { display:none; }
  .sidenav a { white-space:nowrap; }
  .sidefoot { flex-direction:row; align-items:center; justify-content:space-between; }
}

/* ── the skill graph ────────────────────────────────────────────────────────
   The canvas follows the theme. The first version was dark in both, on the argument that glow
   needs a dark ground; in use it made the map the one page that ignored the reader's choice, and
   what it bought was lost anyway to text too faint to read. Each theme now has its own palette,
   designed rather than inverted, and states are carried by colour and stroke weight that read on
   either ground.

   Three channels, kept independent, because three things are true of a subject at once: the
   border says where the learner stands, dashes say whether we have written it, and emphasis — a
   glow on dark, a lift on light — is spent only on finished and ready now. Unwritten subjects keep
   full-strength names: most of the map is unwritten, and fading those too made it unreadable. */
.gwrap { display:grid; grid-template-columns:minmax(0,1fr) 372px; gap:20px; align-items:start; }
.gstats { display:flex; flex-wrap:wrap; gap:8px 10px; list-style:none; margin:0 0 16px; padding:0; font-size:12px; }
.gstats li { background:var(--surface-2); border:1px solid var(--line); border-radius:999px; padding:5px 12px; color:var(--ink-2); }
.gstats b { color:var(--ink); font-weight:600; }

.gstage {
  --g-bg:#f6efe0; --g-glow-a:rgba(108,28,79,.06); --g-glow-b:rgba(192,138,28,.08); --g-dot:rgba(52,25,51,.10);
  --g-bar-a:#ffffff; --g-bar-b:#f4eddb; --g-line:#e2d6bd; --g-line-strong:#b3a07c;
  --g-surface:#ffffff; --g-surface-dim:#efe7d6; --g-text:#2a1429; --g-dim:#5a4257; --g-muted:#7d6878;
  --g-gold:#b7791f; --g-gold-fill:#fff5df; --g-started:#1f6fb2; --g-started-fill:#e9f2fb;
  --g-done:#1c7a52; --g-done-fill:#e5f3eb; --g-done-text:#135a3b; --g-focus:#6c1c4f; --g-hover:rgba(108,28,79,.07);
  --g-edge:.7; --g-dim-to:.42; --g-on-pc:#ffffff;
  --g-glow-ready:drop-shadow(0 1px 3px rgba(183,121,31,.30)); --g-glow-done:drop-shadow(0 1px 3px rgba(28,122,82,.28)); --g-glow-focus:drop-shadow(0 2px 8px rgba(108,28,79,.35));
  border:1px solid var(--g-line); border-radius:16px; overflow:hidden;
  background:radial-gradient(1200px 700px at 78% -8%,var(--g-glow-a),transparent 60%),radial-gradient(900px 600px at 8% 110%,var(--g-glow-b),transparent 55%),var(--g-bg);
}
@media (prefers-color-scheme:dark) {
  :root:not([data-theme="light"]) .gstage {
    --g-bg:#090e1a; --g-glow-a:rgba(127,211,255,.07); --g-glow-b:rgba(255,202,95,.06); --g-dot:rgba(148,170,205,.13);
    --g-bar-a:#161f34; --g-bar-b:#101827; --g-line:#293650; --g-line-strong:#4b5e82;
    --g-surface:#152036; --g-surface-dim:#121b2d; --g-text:#eef3fb; --g-dim:#bccadf; --g-muted:#8395b0;
    --g-gold:#ffca5f; --g-gold-fill:#1c2644; --g-started:#7fd3ff; --g-started-fill:#12273e;
    --g-done:#5fe6a6; --g-done-fill:#12302a; --g-done-text:#c8f7df; --g-focus:#7fd3ff; --g-hover:rgba(127,211,255,.09);
    --g-edge:.62; --g-dim-to:.4; --g-on-pc:#090e1a;
    --g-glow-ready:drop-shadow(0 0 7px rgba(255,202,95,.32)); --g-glow-done:drop-shadow(0 0 7px rgba(95,230,166,.32)); --g-glow-focus:drop-shadow(0 0 11px rgba(127,211,255,.5));
  }
  :root:not([data-theme="light"]) .pc-0 { --pc:#4cc4ff; }
  :root:not([data-theme="light"]) .pc-1 { --pc:#ff6f6b; }
  :root:not([data-theme="light"]) .pc-2 { --pc:#b98cff; }
  :root:not([data-theme="light"]) .pc-3 { --pc:#2fd4c9; }
  :root:not([data-theme="light"]) .pc-4 { --pc:#ff86c2; }
  :root:not([data-theme="light"]) .pc-5 { --pc:#ff9f43; }
}
:root[data-theme="dark"] .gstage {
  --g-bg:#090e1a; --g-glow-a:rgba(127,211,255,.07); --g-glow-b:rgba(255,202,95,.06); --g-dot:rgba(148,170,205,.13);
  --g-bar-a:#161f34; --g-bar-b:#101827; --g-line:#293650; --g-line-strong:#4b5e82;
  --g-surface:#152036; --g-surface-dim:#121b2d; --g-text:#eef3fb; --g-dim:#bccadf; --g-muted:#8395b0;
  --g-gold:#ffca5f; --g-gold-fill:#1c2644; --g-started:#7fd3ff; --g-started-fill:#12273e;
  --g-done:#5fe6a6; --g-done-fill:#12302a; --g-done-text:#c8f7df; --g-focus:#7fd3ff; --g-hover:rgba(127,211,255,.09);
  --g-edge:.62; --g-dim-to:.4; --g-on-pc:#090e1a;
  --g-glow-ready:drop-shadow(0 0 7px rgba(255,202,95,.32)); --g-glow-done:drop-shadow(0 0 7px rgba(95,230,166,.32)); --g-glow-focus:drop-shadow(0 0 11px rgba(127,211,255,.5));
}
:root[data-theme="dark"] .pc-0 { --pc:#4cc4ff; }
:root[data-theme="dark"] .pc-1 { --pc:#ff6f6b; }
:root[data-theme="dark"] .pc-2 { --pc:#b98cff; }
:root[data-theme="dark"] .pc-3 { --pc:#2fd4c9; }
:root[data-theme="dark"] .pc-4 { --pc:#ff86c2; }
:root[data-theme="dark"] .pc-5 { --pc:#ff9f43; }

/* Learning path colours. Six hues far enough apart to tell a path from its neighbour at a glance,
   set as a variable so a subject, a line, a chip and a progress bar all pick up the same one — and
   darker in the light theme, where the dark theme's brighter set washes out against white. */
.pc-0 { --pc:#1b8fd0; }
.pc-1 { --pc:#d9433d; }
.pc-2 { --pc:#7b4fe0; }
.pc-3 { --pc:#0f9a90; }
.pc-4 { --pc:#cc3f87; }
.pc-5 { --pc:#d9700f; }

.gtoolbar { display:flex; flex-wrap:wrap; gap:10px 18px; align-items:center; justify-content:space-between; padding:11px 14px; border-bottom:1px solid var(--g-line); background:linear-gradient(180deg,var(--g-bar-a),var(--g-bar-b)); }
.gkey { display:flex; flex-wrap:wrap; gap:6px 15px; list-style:none; margin:0; padding:0; font-size:12px; color:var(--g-dim); }
.gkey li { display:inline-flex; align-items:center; gap:7px; white-space:nowrap; }
.gkey .dot { width:11px; height:11px; border-radius:50%; flex:none; }
.gkey .d-done { background:var(--g-done); }
.gkey .d-started { background:var(--g-started); }
.gkey .d-ready { background:var(--g-gold); }
.gkey .d-locked { background:transparent; border:1.6px solid var(--g-line-strong); }
.gkey .boxdash { width:15px; height:11px; border-radius:3px; border:1.4px dashed var(--g-line-strong); flex:none; }
.gkey .ln { width:20px; border-top:2px solid var(--g-line-strong); flex:none; }
.gkey .ln.soft { border-top-style:dashed; }

.gactions { display:flex; gap:7px; flex-wrap:wrap; align-items:center; }
.gactions button, .gactions select, .gfilters button { font:inherit; font-size:12.5px; font-weight:600; line-height:1.2; cursor:pointer; width:auto; min-height:0; margin:0; background-color:transparent; color:var(--g-dim); border:1px solid var(--g-line-strong); border-radius:9px; padding:6px 11px; box-shadow:none; }
.gactions select { max-width:240px; color:var(--g-text); background-color:var(--g-surface); }
.gactions button:hover, .gactions select:hover, .gfilters button:hover { color:var(--g-text); border-color:var(--g-focus); background-color:var(--g-hover); }
.gactions button:focus-visible, .gactions select:focus-visible, .gfilters button:focus-visible { outline:2px solid var(--g-focus); outline-offset:2px; }
.gactions button:disabled, .gfilters button:disabled { opacity:.45; cursor:default; }
/* The isolate toggle when it is on: filled in the focus colour so "only this path" is unmistakably
   the active state, not just another button. */
.gactions .gtoggle[aria-pressed="true"] { color:var(--g-bg); background-color:var(--g-focus); border-color:var(--g-focus); }
.gactions .gtoggle[aria-pressed="true"]:hover { color:var(--g-bg); }
.gactions .gview { display:inline-flex; align-items:center; gap:7px; margin:0; font-size:12px; font-weight:600; color:var(--g-muted); }

.gfilters { flex-basis:100%; display:flex; flex-wrap:wrap; gap:7px; }

/* Filters read as a row of switches, not a row of primary actions: a group that is showing is
   stated quietly, and one that is hidden is struck through rather than merely dimmer. */
.gfilters .gfilter { position:relative; padding-inline-start:24px; }
.gfilters .gfilter::before { content:""; position:absolute; inset-inline-start:9px; top:50%; margin-top:-4px; width:8px; height:8px; border-radius:50%; border:1.5px solid var(--g-line-strong); }
.gfilters .gfilter[aria-pressed="true"] { color:var(--g-text); }
.gfilters .gfilter[aria-pressed="true"]::before { background:var(--g-gold); border-color:var(--g-gold); }
.gfilters .gfilter[aria-pressed="false"] { color:var(--g-muted); text-decoration:line-through; }
/* A difficulty switch is an area switch that marks a grade instead of a group: square rather than
   round, filled with the same escalation the spine on a box uses, so the toolbar and the map agree
   at a glance. */
.gfilters .glevel::before { border-radius:3px; }
.gfilters .glevel.lv-basic[aria-pressed="true"]::before { background:transparent; border-color:var(--g-line-strong); }
.gfilters .glevel.lv-advanced[aria-pressed="true"]::before { background:var(--g-surface); border-color:var(--g-line-strong); }
.gfilters .glevel.lv-expert[aria-pressed="true"]::before { background:var(--g-text); border-color:var(--g-text); }
/* Basic's mark is an empty square, which on its own would look the same switched off, so a hidden
   grade fades its mark as well as striking its label. */
.gfilters .glevel[aria-pressed="false"]::before { background:transparent; border-color:var(--g-dim); opacity:.45; }
/* The area switches read "on = showing". The free toggle is the other kind — "on = filtering" —
   so it drops the dot and strike-through and instead fills gold when active, matching the FREE tag. */
.gfilters .gfree { padding-inline-start:11px; }
.gfilters .gfree::before { display:none; }
.gfilters .gfree[aria-pressed="false"] { color:var(--g-dim); text-decoration:none; }
.gfilters .gfree[aria-pressed="true"] { background:var(--g-gold); border-color:var(--g-gold); color:var(--g-bg); }

/* The canvas. Without scripting it scrolls at the drawing's own size. With scripting the drawing
   is sized to the canvas and the script pans and zooms inside it. It used to keep its own pixel
   size while zoom enlarged what was inside, so everything past its original edge was cut off —
   worst in full screen, where the canvas is far larger than the drawing. */
.gviewport { position:relative; overflow:auto; height:clamp(460px,68vh,820px); background-image:radial-gradient(circle at 1px 1px,var(--g-dot) 1px,transparent 0); background-size:26px 26px; }
/* Attribution mark, fixed to the corner so it rides through pan, zoom and full screen and lands in
   any screenshot. Inert to the pointer so a drag that starts over it still pans the map. The
   diamond keeps the brand's own colours; the words take the map's ink to read on either theme. */
.gwatermark { position:absolute; right:14px; bottom:14px; z-index:3; display:flex; align-items:center; gap:9px; padding:8px 12px; border-radius:11px; background:color-mix(in srgb,var(--g-bg) 80%,transparent); border:1px solid var(--g-line-strong); box-shadow:0 2px 10px -4px rgba(0,0,0,.45); text-decoration:none; cursor:pointer; transition:border-color .15s,box-shadow .15s; }
.gwatermark:hover { border-color:var(--g-focus); box-shadow:0 3px 14px -4px rgba(0,0,0,.5); }
.gwatermark:focus-visible { outline:2px solid var(--g-focus); outline-offset:2px; }
/* The name, large and faint, behind the drawing: under every box and line, and inert to the pointer. */
.gbgmark { pointer-events:none; }
.gbgmark text { text-anchor:middle; fill:var(--g-text); font-family:var(--display); }
.gbgmark-name { font-weight:800; letter-spacing:-.02em; opacity:.06; }
.gbgmark-domain { font-family:var(--mono); font-weight:500; letter-spacing:.08em; opacity:.08; }
/* "Only this path", floating on the map; shown only once a path is picked. */
.gfloat { position:absolute; top:14px; right:14px; z-index:4; font:inherit; font-size:13px; font-weight:700; line-height:1.2; width:auto; min-height:0; margin:0; padding:9px 16px; border-radius:999px; cursor:pointer; color:var(--g-text); background-color:var(--g-surface); border:1px solid var(--g-focus); box-shadow:0 4px 16px -6px rgba(0,0,0,.45); transition:background-color .15s,color .15s; }
.gfloat:hover { background-color:var(--g-hover); }
.gfloat:focus-visible { outline:2px solid var(--g-focus); outline-offset:2px; }
.gfloat[aria-pressed="true"] { color:var(--g-bg); background-color:var(--g-focus); }
.gfloat[aria-pressed="true"]:hover { color:var(--g-bg); }
.gfloat[hidden] { display:none; }
.gwatermark .gwm-mark { flex:none; border-radius:5px; }
.gwatermark .gwm-text { display:flex; flex-direction:column; line-height:1.18; }
.gwatermark .gwm-text b { font-family:var(--display); font-size:14px; font-weight:700; letter-spacing:-.01em; color:var(--g-text); }
.gwatermark .gwm-text small { font-family:var(--mono); font-size:10.5px; color:var(--g-dim); }
.gviewport.grabbable { overflow:hidden; cursor:grab; touch-action:none; }
.gviewport.grabbing { cursor:grabbing; }
.skillgraph { display:block; max-width:none; }
.gviewport.grabbable .skillgraph { width:100%; height:100%; }

.gedge { fill:none; stroke:var(--g-line-strong); stroke-width:1.6; opacity:var(--g-edge); transition:opacity .2s,stroke .2s,stroke-width .2s; }
.gedge.soft { stroke-dasharray:5 6; opacity:calc(var(--g-edge) * .75); }
.gedge.dim { opacity:.1; }
.gedge.lit { stroke:var(--g-focus); opacity:.95; stroke-width:2.3; }
.gedge.onpath { stroke:var(--pc,var(--g-focus)); opacity:.95; stroke-width:2.6; }
.gedge.hidden { display:none; }

.gnode { transition:opacity .2s; }
.gnode a { cursor:pointer; }
.gbox { fill:var(--g-surface); stroke:var(--g-line-strong); stroke-width:1.6; transition:stroke .18s,stroke-width .18s,fill .18s; }
/* What a course is, in one line: release, difficulty, size. Quiet enough to skip, present enough
   to answer "is this maintained, and how big is it?" without opening anything. */
.coursefacts { font-size:11.5px; color:var(--muted); margin:2px 0 8px; letter-spacing:.01em; }
.gname { fill:var(--g-text); font-size:14px; font-weight:600; text-anchor:middle; }
.gnode.longname .gname { font-size:12px; }
/* The rough effort figure, under the title: the box height encodes it at a glance, this states it. */
.gsize { fill:var(--g-muted); font-size:10.5px; font-weight:600; text-anchor:middle; font-family:var(--mono); }
/* Free or paid, said in words on the node, because the old corner dot was too easy to miss and
   free-vs-paid is the first thing a visitor weighs. Free is the hook, so it is filled and gold;
   subscription is a quieter outline. Both are in the legend. */
.gtag { pointer-events:none; }
.gtagtx { font-size:8.5px; font-weight:700; letter-spacing:.08em; text-anchor:middle; dominant-baseline:central; }
.gnode.e-free .gtagbg { fill:var(--g-gold); stroke:none; }
.gnode.e-free .gtagtx { fill:var(--g-bg); }
.gnode.e-sub .gtagbg { fill:var(--g-surface); stroke:var(--g-line-strong); stroke-width:1; }
.gnode.e-sub .gtagtx { fill:var(--g-muted); }
.gtaglegend { font-size:8.5px; font-weight:700; letter-spacing:.05em; padding:2px 5px; border-radius:4px; line-height:1; }
.gtaglegend.free { background:var(--g-gold); color:var(--g-bg); }
.gtaglegend.paid { border:1px solid var(--g-line-strong); color:var(--g-muted); }
/* Difficulty down the right edge, mirroring price on the left. It escalates in weight rather than
   in hue: the colours on this map already mean learner state, and a third colour system competing
   with them would make both harder to read. ADVANCED is the longest word the spine has to hold,
   which sets the size. */
.glvl { pointer-events:none; }
.glvltx { font-size:7.5px; font-weight:700; letter-spacing:.04em; text-anchor:middle; dominant-baseline:central; }
.glvlbg { fill:none; stroke:var(--g-line); stroke-width:1; }
.gnode.l-basic .glvltx { fill:var(--g-dim); }
.gnode.l-advanced .glvlbg { fill:var(--g-surface); stroke:var(--g-line-strong); }
.gnode.l-advanced .glvltx { fill:var(--g-muted); }
.gnode.l-expert .glvlbg { fill:var(--g-text); stroke:none; }
.gnode.l-expert .glvltx { fill:var(--g-bg); }
.glvllegend { font-size:8.5px; font-weight:700; letter-spacing:.05em; padding:2px 5px; border-radius:4px; line-height:1; margin-right:3px; }
.glvllegend.l-basic { border:1px solid var(--g-line); color:var(--g-dim); }
.glvllegend.l-advanced { background:var(--g-surface); border:1px solid var(--g-line-strong); color:var(--g-muted); }
.glvllegend.l-expert { background:var(--g-text); color:var(--g-bg); }
.gnode.s-ready .gbox { stroke:var(--g-gold); stroke-width:2; fill:var(--g-gold-fill); }
.gnode.s-ready { filter:var(--g-glow-ready); }
.gnode.s-started .gbox { stroke:var(--g-started); stroke-width:2; fill:var(--g-started-fill); }
.gnode.s-done .gbox { stroke:var(--g-done); stroke-width:2; fill:var(--g-done-fill); }
.gnode.s-done { filter:var(--g-glow-done); }
.gnode.s-done .gname { fill:var(--g-done-text); }
.gnode.s-locked .gbox { fill:var(--g-surface-dim); }
.gnode.s-locked .gname { fill:var(--g-dim); }
.gnode.unwritten .gbox { stroke-dasharray:6 5; }
.gnode a:hover .gbox { stroke-width:2.6; }
.gnode a:focus-visible { outline:none; }
.gnode a:focus-visible .gbox { stroke:var(--g-focus); stroke-width:3; }
.gnode.lit .gbox { stroke:var(--g-focus); }
.gnode.selected .gbox { stroke:var(--g-focus); stroke-width:3; }
.gnode.selected { filter:var(--g-glow-focus); }
.gnode.onpath .gbox { stroke:var(--pc,var(--g-focus)); stroke-width:2.6; }
.gnode.assumed { opacity:.6; }
.gnode.assumed .gbox { stroke-dasharray:2 4; }
.gnode.dim { opacity:var(--g-dim-to); }
.gnode.hidden { display:none; }

/* The line a learning path walks, under the subjects and in the path's own colour. Dashed and
   moving, because it is a route someone takes rather than a dependency that simply exists. */
.gtrailline { fill:none; stroke:var(--pc,var(--g-focus)); stroke-width:2.8; stroke-linecap:round; stroke-dasharray:10 7; opacity:.95; animation:gtrail 1.4s linear infinite; }
@keyframes gtrail { to { stroke-dashoffset:-34; } }
.gsteps { pointer-events:none; }
.gstep { fill:var(--pc,var(--g-focus)); stroke:var(--g-bg); stroke-width:2.5; }
.gstepnum { fill:var(--g-on-pc); font-size:12px; font-weight:700; text-anchor:middle; }

/* Full screen: the map takes the viewport and the panel comes with it, scrolling on its own. */
.gwrap.full { position:fixed; inset:0; z-index:60; margin:0; padding:14px; gap:14px; background:var(--bg); overflow:hidden; }
.gwrap.full .gstage { height:calc(100vh - 28px); display:flex; flex-direction:column; }
.gwrap.full .gviewport { flex:1 1 auto; height:auto; min-height:0; }
.gwrap.full .gpanel { position:static; max-height:calc(100vh - 28px); overflow:auto; }

/* What the map had to ignore to draw. Operators only — a learner cannot fix a course that points
   at a subject nobody has, and silently dropping the edge would hide the one thing worth knowing. */
.gproblems { margin:0 0 16px; padding:12px 14px; border-radius:12px; background:var(--warn-bg); border:1px solid var(--warn-line); color:var(--warn); }
.gproblems .glabel { color:inherit; margin:0 0 6px; }
.gproblems ul { margin:0; padding-inline-start:18px; font-size:13px; }

/* ── the panel ──────────────────────────────────────────────────────────── */
.gpanel { display:flex; flex-direction:column; gap:16px; position:sticky; top:16px; }
.gcard { background:var(--surface); border:1px solid var(--line); border-radius:16px; padding:18px; }
.gjourneycard h2 { font-size:17px; margin:0 0 4px; }
.ghint { color:var(--muted); font-size:13px; margin:0; }
.gentry { padding-top:2px; }
.gdetailcard.enhanced:not(.picked) .gentry { display:none; }
.gpanel.picked .gdetailcard { order:-1; }
.gdetailcard.picked .gentry { display:none; }
.gdetailcard.picked .gentry.on { display:block; }
.gdetailcard.picked .ghint { display:none; }
/* Why a subject costs what it costs (ADR 0023): one quiet line under the summary. */
.gprice { font-size:13px; line-height:1.5; color:var(--muted); margin:2px 0 12px; }
.gentry + .gentry { margin-top:18px; padding-top:18px; border-top:1px solid var(--line); }
.gdetailcard.picked .gentry + .gentry { margin-top:0; padding-top:2px; border-top:none; }
.ghead { display:flex; align-items:center; gap:7px; flex-wrap:wrap; margin-bottom:9px; }
.gbadge { font-size:10.5px; font-weight:600; letter-spacing:.07em; text-transform:uppercase; padding:4px 9px; border-radius:6px; border:1px solid var(--line-2); color:var(--ink-2); }
.gbadge.b-done { background:var(--ok-bg); color:var(--ok); border-color:var(--ok-line); }
.gbadge.b-started { background:var(--info-bg); color:var(--info); border-color:var(--info-line); }
.gbadge.b-ready { background:var(--warn-bg); color:var(--warn); border-color:var(--warn-line); }
.gbadge.b-free { background:var(--ok-bg); color:var(--ok); border-color:var(--ok-line); }
.gbadge.b-lvl-basic { color:var(--muted); }
.gbadge.b-lvl-advanced { border-color:var(--line); color:var(--ink); }
.gbadge.b-lvl-expert { background:var(--ink); border-color:var(--ink); color:var(--surface); }
.gmetaline { font-size:11.5px; color:var(--muted); margin-inline-start:auto; }
.gtitle { font-size:21px; line-height:1.15; margin:0 0 6px; }
.gsummary { font-size:14px; color:var(--ink-2); margin:0 0 14px; }
.glabel { font-size:10.5px; letter-spacing:.17em; text-transform:uppercase; color:var(--muted); margin:0 0 8px; }
.gchips, .glinks, .gpathchips { list-style:none; margin:0 0 15px; padding:0; }
.gchips, .gpathchips { display:flex; flex-wrap:wrap; gap:6px; }
.gchip { font-size:12.5px; padding:5px 10px; border-radius:999px; background:var(--surface-2); border:1px solid var(--line); color:var(--ink-2); }
.glinks li { display:flex; align-items:center; gap:9px; padding:5px 0; font-size:13.5px; }
.glinks .k { font-size:10px; color:var(--muted); flex:none; min-width:34px; }
.glink.soft .k { color:var(--warn); }
.gpathchip a { display:inline-flex; align-items:center; gap:7px; font-size:12.5px; padding:5px 11px; border-radius:999px; background:var(--surface-2); border:1px solid var(--line); color:var(--ink-2); }
.gpathchip a:hover { color:var(--ink); text-decoration:none; }
.gpathchip .sw, .gjourney .sw { width:9px; height:9px; border-radius:50%; flex:none; background:var(--pc,var(--accent)); }
.gstudy { margin:10px 0 0; }
.gstudy .btn.block { width:100%; text-align:center; }
.gcta-note { display:block; margin-top:6px; font-size:12px; color:var(--muted); }
.gcta-link { font-weight:600; }

/* Each learning path in the panel: its header opens the path on the map, and beneath it the
   subjects in reading order and the earlier work it assumes. With scripting only the open path
   shows its steps; without, every path does, since the list is the whole reference then. */
.gjourneylist { list-style:none; margin:14px 0 0; padding:0; display:flex; flex-direction:column; gap:3px; }
.gjourney > a { display:block; padding:10px 11px; border-radius:12px; border:1px solid transparent; color:inherit; }
.gjourney > a:hover { background:var(--surface-2); text-decoration:none; }
.gjourney.on > a { background:var(--surface-2); border-color:var(--pc,var(--accent)); }
.jtop { display:flex; align-items:center; gap:9px; margin-bottom:7px; }
.jtop b { font-size:14px; }
.jtag { font-size:11.5px; color:var(--muted); margin-inline-start:auto; }
.jbar { display:block; height:6px; border-radius:99px; background:var(--surface-2); border:1px solid var(--line); overflow:hidden; }
.jfill { display:block; height:100%; background:var(--pc,var(--accent)); border-radius:99px; }
.jfoot { display:block; margin-top:7px; font-size:11.5px; color:var(--muted); }
.jassumes { margin:8px 11px 0; font-size:12px; color:var(--muted); }
.jsteps { list-style:none; margin:6px 0 6px; padding:0 11px; display:flex; flex-direction:column; gap:1px; counter-reset:jstep; }
.jsteps li { display:flex; align-items:center; gap:8px; font-size:13px; padding:3px 0; color:var(--ink-2); }
.jsteps li::before { counter-increment:jstep; content:counter(jstep); font-family:var(--mono); font-size:10.5px; color:var(--muted); min-width:14px; text-align:end; }
.jsteps .jdot { width:8px; height:8px; border-radius:50%; flex:none; border:1.5px solid var(--line-2); }
.jsteps .s-done .jdot { background:var(--ok); border-color:var(--ok); }
.jsteps .s-started .jdot { background:var(--info); border-color:var(--info); }
.jsteps .s-ready .jdot { background:var(--warn); border-color:var(--warn); }
.jsteps .unwritten a { color:var(--muted); }
.gjourneylist.enhanced .gjourney:not(.on) .jsteps, .gjourneylist.enhanced .gjourney:not(.on) .jassumes { display:none; }

/* Progress widths as classes: inline styles stay forbidden (ADR 0010), so the server rounds to
   the nearest 5% and picks one of these. */
.w-0{width:0}.w-5{width:5%}.w-10{width:10%}.w-15{width:15%}.w-20{width:20%}.w-25{width:25%}
.w-30{width:30%}.w-35{width:35%}.w-40{width:40%}.w-45{width:45%}.w-50{width:50%}.w-55{width:55%}
.w-60{width:60%}.w-65{width:65%}.w-70{width:70%}.w-75{width:75%}.w-80{width:80%}.w-85{width:85%}
.w-90{width:90%}.w-95{width:95%}.w-100{width:100%}

@media (max-width:1080px) {
  .gwrap, .gwrap.full { grid-template-columns:minmax(0,1fr); }
  .gpanel { position:static; }
  .gwrap.full { overflow:auto; }
  .gwrap.full .gstage { height:72vh; }
}
@media (prefers-reduced-motion:reduce) {
  .gedge, .gnode, .gbox { transition:none; }
  .gtrailline { animation:none; }
}

/* ── the catalogue and block lessons (ADR 0019) ─────────────────────────────── */
/* Compact tiles on shelves by track: forty courses should be scannable in a screen or two. */
.cat-totals { font-size:12px; color:var(--muted); margin:-4px 0 16px; letter-spacing:.02em; }
.cat-kicker { font-size:11.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--muted); font-weight:600; margin:0 0 8px; }
.cat-continue { margin:0 0 20px; }
.cat-continue ul { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(auto-fill,minmax(290px,1fr)); gap:8px; }
.cat-continue a { display:grid; grid-template-columns:minmax(0,1fr) auto auto; grid-template-areas:"t n go" "p p p"; gap:7px 10px; align-items:center; padding:10px 14px 11px; border:1px solid var(--accent-line); background:var(--accent-soft); border-radius:var(--radius); color:var(--ink); transition:border-color .15s; }
.cat-continue a:hover { border-color:var(--accent); text-decoration:none; }
.cc-title { grid-area:t; font-weight:600; font-size:14px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cc-n { grid-area:n; font-size:11.5px; color:var(--muted); }
.cc-go { grid-area:go; color:var(--accent-text); }
.cat-continue progress { grid-area:p; height:4px; }
.filterbar .chiprow { order:10; flex-basis:100%; display:flex; flex-wrap:wrap; gap:6px; border:0; padding:0; margin:2px 0 0; min-width:0; }
.fchip { position:relative; margin:0; }
.fchip input { position:absolute; opacity:0; width:1px; height:1px; margin:0; }
.fchip span { display:inline-flex; gap:7px; align-items:center; padding:5px 12px; border:1px solid var(--line-2); border-radius:999px; font-size:13px; color:var(--ink-2); background:var(--surface); cursor:pointer; transition:background .15s,border-color .15s,color .15s; }
.fchip span:hover { border-color:var(--accent); color:var(--ink); }
.fchip small { font-family:var(--mono); font-size:11px; color:var(--muted); }
.fchip input:checked + span { background:var(--accent); border-color:var(--accent); color:var(--on-accent); }
.fchip input:checked + span small { color:var(--on-accent); opacity:.75; }
.fchip input:focus-visible + span { outline:2px solid var(--accent); outline-offset:2px; }
.catalogue { margin-top:6px; }
/* Shelves share one column grid, so a small track can sit beside another and its tiles still line up. */
@supports (grid-template-columns: subgrid) {
  .catalogue { display:grid; grid-template-columns:repeat(auto-fill,minmax(255px,1fr)); column-gap:10px; }
  .shelf { grid-column:1 / -1; display:grid; grid-template-columns:subgrid; align-content:start; }
  .shelf > * { grid-column:1 / -1; }
  .shelf .ctiles { grid-template-columns:subgrid; }
  .shelf.span-1 { grid-column:span 1; }
  .shelf.span-2 { grid-column:span 2; }
  .catalogue > .empty { grid-column:1 / -1; }
}
.shelf { margin:0 0 26px; }
.shelf-head { display:flex; align-items:baseline; gap:10px; padding:0 0 8px; margin:0 0 12px; border-bottom:1px solid var(--line); }
.shelf-head h2 { font-size:17px; }
.shelf-kicker { font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--muted); font-weight:600; }
.shelf-n { margin-inline-start:auto; font-size:12px; color:var(--muted); }
.ctiles { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(auto-fill,minmax(255px,1fr)); gap:10px; }
.ctile > a { position:relative; overflow:hidden; display:flex; flex-direction:column; gap:6px; height:100%; padding:12px 14px 13px; background:var(--surface); border:1px solid var(--line); border-radius:var(--radius); color:var(--ink); transition:border-color .15s,background .15s,transform .15s; }
.ctile > a:hover { border-color:var(--accent); background:var(--raise); text-decoration:none; transform:translateY(-1px); }
.ctile.s-done > a { border-color:var(--ok-line); }
.ctile h3 { font-size:15px; line-height:1.3; }
.ctile-top { display:flex; justify-content:space-between; align-items:center; gap:8px; min-height:18px; }
.ctile-level { display:inline-flex; align-items:center; gap:8px; font-size:10.5px; font-weight:600; letter-spacing:.09em; text-transform:uppercase; color:var(--muted); }
.ctile-level .khatam { gap:4px; }
.ctile-level .khatam i { width:7px; height:7px; }
.ctile-state { font-family:var(--mono); font-size:11px; font-weight:500; }
.ctile-state .pill { font-family:var(--sans); }
.ctile-sum { font-size:12.5px; line-height:1.45; color:var(--muted); display:-webkit-box; -webkit-line-clamp:2; line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.ctile-facts { margin-top:auto; padding-top:2px; font-size:11px; color:var(--ink-2); }
.ctile progress { position:absolute; inset-inline:0; bottom:0; height:3px; border-radius:0; }
.ctile progress::-webkit-progress-bar,.ctile progress::-webkit-progress-value { border-radius:0; }
.ctile progress::-moz-progress-bar { border-radius:0; }
@media (prefers-reduced-motion: reduce) { .ctile > a { transition:none; } .ctile > a:hover { transform:none; } }
/* Outline beside the lesson; one column once there is no room for two. */
.lesson-shell { display:grid; grid-template-columns:270px minmax(0,1fr); gap:26px; align-items:start; }
.outline { position:sticky; top:14px; align-self:start; max-height:calc(100vh - 28px); overflow-y:auto; padding-inline-end:6px; }
.outline h2 { font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); margin:14px 0 8px; }
.outline-progress { background:var(--surface); border:1px solid var(--line); border-radius:var(--radius); padding:11px 12px; }
.outline-progress b { font-size:20px; }
.outline-progress p { margin:2px 0 7px; font-size:12px; }
.outline-progress progress { width:100%; }
.outline .tree, .outline .tree ul { list-style:none; margin:0; padding:0; }
.outline .mod > .row { font-size:11.5px; letter-spacing:.08em; text-transform:uppercase; color:var(--muted); font-weight:600; margin:12px 0 4px; }
.outline .mod > ul { border-inline-start:1px solid var(--line); padding-inline-start:9px; }
.lesson-link a, .lesson-link .locked { display:flex; gap:7px; align-items:baseline; padding:4px 8px; border-radius:var(--radius-sm); font-size:13.5px; color:var(--ink-2); text-decoration:none; }
.lesson-link a:hover { background:var(--surface-2); color:var(--ink); }
.lesson-link a[aria-current] { background:var(--accent-soft); color:var(--accent); font-weight:600; }
.lesson-link .locked { color:var(--muted); }
.lesson-link .lesson-name { flex:1; min-width:0; }
/* A flask beside a lesson that has something to do in it. Filled once its labs are solved, so one
   mark answers both "is there one" and "did I finish it". */
.lab-mark { flex:none; display:inline-flex; align-self:center; color:var(--muted); }
.lab-mark.solved { color:var(--ok); }
.lesson-link a:hover .lab-mark { color:var(--ink-2); }
.lesson-link a[aria-current] .lab-mark { color:var(--accent); }
.lesson-link a[aria-current] .lab-mark.solved { color:var(--ok); }
.outline .tick { flex:none; width:13px; font-size:10px; }
.outline .tick.done { color:var(--ok); }
.outline .tick.now { color:var(--accent); }
.lesson-main { min-width:0; max-width:74ch; }
.lesson-main h1 { font-size:30px; line-height:1.15; margin:0 0 16px; }
.lesson-main .crumb { font-size:12.5px; color:var(--muted); margin:0 0 10px; }
.lesson-main .crumb .sep { opacity:.55; }
.lesson-done { margin:22px 0 0; }
.callout.note { border-color:var(--line-2); background:var(--surface-2); }
.pager { display:flex; flex-wrap:wrap; gap:11px; justify-content:space-between; margin-top:26px; padding-top:18px; border-top:1px solid var(--line); }
.pager a { display:flex; flex-direction:column; gap:2px; padding:10px 14px; border:1px solid var(--line); border-radius:var(--radius); text-decoration:none; background:var(--surface); flex:1 1 210px; }
.pager a span { font-size:11.5px; letter-spacing:.09em; text-transform:uppercase; color:var(--muted); }
.pager a b { font-size:14px; color:var(--ink); }
.pager a.next { text-align:end; }
@media (max-width:900px) {
  .lesson-shell { grid-template-columns:minmax(0,1fr); }
  .outline { position:static; max-height:none; border-bottom:1px solid var(--line); padding-bottom:14px; }
}

/* ── lesson blocks (ADR 0019 content) ───────────────────────────────────────
   A lesson reads as a sequence of distinct moments — read, look at an example, be warned,
   try it, check yourself — so each authored block is its own unit with air around it, not one
   undifferentiated column. Colour still only ever carries meaning; the lab wears the accent
   because it is the one thing the page wants you to do. */
.lesson-main > * { margin:22px 0; }
.lesson-main > .doc { margin:14px 0; }
.lesson-main > h1 + *, .lesson-main > .crumb + h1 { margin-top:0; }
/* A code example: a titled card with a quiet header and a monospace body; an optional Result
   pane is divided off below the input it produced. */
.example { border:1px solid var(--line); border-radius:var(--radius); overflow:hidden; background:var(--surface); }
.example > header { font-size:11.5px; letter-spacing:.06em; text-transform:uppercase; color:var(--muted); font-weight:600; background:var(--surface-2); border-bottom:1px solid var(--line); padding:7px 13px; }
.example > .body { padding:0; }
.example pre { margin:0; border:0; border-radius:0; background:transparent; padding:12px 14px; font-family:var(--mono); font-size:12.5px; overflow-x:auto; }
.example .result { border-top:1px dashed var(--line-2); }
.example .result > span { display:block; font-size:10.5px; letter-spacing:.08em; text-transform:uppercase; color:var(--muted); padding:8px 14px 0; }
.example .result > pre { padding:5px 14px 12px; color:var(--ink-2); }
/* Callouts: a left rule and a tinted ground carry the tone — note is neutral, tip positive,
   warn cautionary. Nothing louder. */
.callout { border:1px solid var(--line-2); border-inline-start-width:3px; border-radius:var(--radius-sm); padding:11px 14px; background:var(--surface-2); color:var(--ink-2); }
.callout > :first-child { margin-top:0; }
.callout > :last-child { margin-bottom:0; }
/* position/opacity reset guards against the .tip tooltip utility (L661), whose class this shares. */
.callout.tip { border-color:var(--ok-line); border-inline-start-color:var(--ok); background:var(--ok-bg); color:var(--ink-2); position:static; opacity:1; pointer-events:auto; box-shadow:none; }
.callout.warn { border-color:var(--warn-line); border-inline-start-color:var(--warn); background:var(--warn-bg); color:var(--ink-2); }
/* The end-of-lesson recap: a soft accent panel with a ticked list, so it reads as a summing-up. */
.summary { background:var(--accent-soft); border:1px solid var(--accent-line); border-radius:var(--radius); padding:14px 17px; }
.summary h3 { margin:0 0 9px; font-size:14px; letter-spacing:.02em; }
.summary ul { list-style:none; margin:0; padding:0; display:grid; gap:7px; }
.summary li { position:relative; padding-inline-start:23px; color:var(--ink-2); }
.summary li::before { content:"✓"; position:absolute; inset-inline-start:0; color:var(--accent-text); font-weight:700; }
/* figures */
figure { text-align:center; }
figure img { max-width:100%; height:auto; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--surface); padding:6px; }
figcaption { margin-top:8px; font-size:12.5px; color:var(--muted); }
/* Quiz, lab and video are self-contained panels: a header row carrying the block's tag and title,
   and a padded body. The lab is the moment of doing, so its chrome takes the accent. */
.block.lab, .block.quiz, .block.video { border:1px solid var(--line); border-radius:var(--radius); background:var(--surface); overflow:hidden; }
.block.lab > header, .block.quiz > header, .block.video > header { display:flex; align-items:center; gap:10px; padding:10px 14px; border-bottom:1px solid var(--line); background:var(--surface-2); }
.block.lab > header h3, .block.quiz > header h3, .block.video > header h3 { margin:0; font-size:15px; }
.block.lab > .body, .block.quiz > .body, .block.video > .body { padding:14px; display:grid; gap:11px; }
.block.lab { border-color:var(--accent-line); }
.block.lab > header { background:var(--accent-soft); border-bottom-color:var(--accent-line); }
.block.lab > header .tag { background:var(--accent); color:var(--on-accent); border-color:var(--accent); }
.block.quiz > header .tag { background:var(--info-bg); color:var(--info); border-color:var(--info-line); }
.block.lab .brief { margin:0; }
.lab-objective { margin:0; padding:9px 13px; background:var(--surface-2); border:1px solid var(--line); border-radius:var(--radius-sm); font-size:13.5px; color:var(--ink-2); }
.block.lab .lab-objective { background:var(--surface); }
.lab-objective b { color:var(--ink); }
/* A lab whose challenge is not installed says so as a quiet inset, not a bare line of text. */
.lab-missing { margin:0; padding:10px 13px; border:1px dashed var(--line-2); border-radius:var(--radius-sm); background:var(--surface-2); color:var(--muted); font-size:13px; }

/* ── progress strands, quiz retake, and the labs index (academy feedback) ───── */
.strands { list-style:none; margin:10px 0 0; padding:0; display:flex; flex-direction:column; gap:6px; }
.strands li { display:grid; grid-template-columns:64px 1fr auto; align-items:center; gap:8px; font-size:12px; }
.strands .s-label { color:var(--muted); }
.strands progress { width:100%; height:6px; }
.strands .s-count { color:var(--muted); font-variant-numeric:tabular-nums; }
.quiz-passed { margin:0 0 8px; font-weight:600; }
details.retake { margin-top:6px; }
details.retake > summary { cursor:pointer; color:var(--accent); font-size:13px; width:max-content; }
details.retake[open] > summary { margin-bottom:10px; }
.learn-links { display:flex; flex-wrap:wrap; gap:4px 20px; margin-top:20px; }
.labs-ready { margin:0 0 26px; }
.labs-ready > h2 { font-size:19px; margin:0 0 2px; }
.labs-course { margin:0 0 22px; }
.labs-course > h3 { font-size:13px; letter-spacing:.09em; text-transform:uppercase; color:var(--muted); margin:0 0 8px; font-weight:600; }
/* One course at a time on the labs index. Links rather than scripted tabs, so the choice is in
   the address: it survives a reload, can be shared, and works with scripting off. */
.labtabs { display:flex; flex-wrap:wrap; gap:6px; margin:0 0 14px; }
.labtab { display:inline-flex; align-items:center; gap:7px; padding:6px 11px; border:1px solid var(--line); border-radius:999px; font-size:13px; color:var(--ink-2); text-decoration:none; }
.labtab:hover { background:var(--surface-2); color:var(--ink); }
.labtab.on { background:var(--accent-soft); border-color:var(--accent-soft); color:var(--accent); font-weight:600; }
.labtab-n { font-size:11.5px; color:var(--muted); font-family:var(--mono); }
.labtab.on .labtab-n { color:var(--accent); }
.lab-where { font-size:12px; }
.lab-rows { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:1px; }
/* status · name · which lesson it sits in · what it asks you to do */
.lab-row { display:grid; grid-template-columns:150px minmax(0,auto) minmax(0,auto) 1fr; align-items:baseline; gap:12px; padding:8px 10px; border-radius:var(--radius-sm); }
@media (max-width:820px) { .lab-row { grid-template-columns:120px 1fr; } .lab-row .lab-where, .lab-row .lab-goal { grid-column:2; } }
.lab-row:hover { background:var(--surface-2); }
.lab-row .lab-name { font-weight:500; text-decoration:none; color:var(--ink); }
.lab-row .lab-name:hover { text-decoration:underline; }
.lab-row .lab-goal { font-size:12.5px; }
.lab-status { font-size:11px; letter-spacing:.06em; text-transform:uppercase; font-weight:600; }
.card.lab-card .chal-head { align-items:baseline; }
.card.lab-card h4 { margin:0; font-size:15px; }
.card.lab-card h4 a { text-decoration:none; }
.s-ready .lab-status, .lab-card.s-ready .lab-status { color:var(--accent); }
.s-solved .lab-status, .lab-card.s-solved .lab-status { color:var(--ok); }
.s-locked .lab-status, .s-unread .lab-status { color:var(--muted); }
.s-missing .lab-status { color:var(--warn, #b45309); }
/* The operator pricing panel (ADR 0023): its notes sit inside the flush panel, padded like a body. */
.pricing-notes { padding:14px 18px 4px; }
.pricing-notes .flash { margin:0 0 10px; }
.pricing-notes p { margin:0 0 10px; }
.pricing-subject { min-width:260px; }
/* An operator previewing prices as an unsubscribed learner (ADR 0023): says so on every page. */
.preview-banner { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; margin:0 0 18px; }
