/* SahabLabs design system — "the console surround".
 *
 * One stylesheet for every surface. Before this file each page carried its
 * own copy of the same :root block and the same button/table/pill rules;
 * tokens could not exist because there were ten of them.
 *
 * Two token layers:
 *   semantic  surface/ink/edge/pass/warn/fail — fixed, components consume these
 *   brand     --brand + computed --brand-ink   — tenant-overridable (embed only)
 * Brand may fill actions and focus. Brand may never colour a status or a
 * chart, which is why a tenant recolour cannot destroy legibility.
 *
 * Everything uses logical properties (margin-inline, border-inline-end) so
 * the whole UI mirrors under dir="rtl". The terminal, code, paths and YAML
 * are pinned LTR with .ltr — they never mirror.
 */

/* ----------------------------------------------------------------- tokens */

/* Both palettes are written out ONCE here, as --dk-* / --lt-* values. What a
 * theme switch does is repoint the semantic tokens at one set or the other,
 * which is the short list below. Keeping it this way means a colour is edited
 * in exactly one place, and the two mappings cannot drift apart into a light
 * theme that quietly lost a token.
 *
 * The mapping appears twice on purpose, and CSS gives no way around it: the
 * explicit choice is an attribute selector and the default is a media query,
 * and a media query cannot be merged into a selector list. */
:root {
  /* dark — a cool near-black, so the accent and the code colours sit on a
     neutral rather than fighting a warm brown */
  --dk-surface: #0E1116;
  --dk-raised:  #151A21;
  --dk-edge:    #232B36;
  --dk-ink:     #E6EAF0;
  --dk-muted:   #8B96A5;
  --dk-accent:  #10B981;
  --dk-pass:    #3FA96B;
  --dk-warn:    #D6A126;
  --dk-fail:    #E5484D;
  --dk-void:    #05070A;
  --dk-lip:     inset 0 1px 0 rgba(255, 255, 255, 0.04);
  --dk-shadow:  0 1px 2px rgba(0, 0, 0, 0.35);

  /* light */
  --lt-surface: #F5F7FA;
  --lt-raised:  #FFFFFF;
  --lt-edge:    #E3E8EF;
  --lt-ink:     #101828;
  --lt-muted:   #5A6675;
  --lt-accent:  #067A55;
  --lt-pass:    #1A6B42;
  --lt-warn:    #7A5810;
  --lt-fail:    #B02226;
  /* not #000: a pure-black terminal on a white page is the maximum-halation
     configuration, which is why light used to be refused on runner pages
     altogether. A very dark slate keeps the contrast without the glare. */
  --lt-void:    #0B0F14;
  --lt-lip:     inset 0 1px 0 rgba(255, 255, 255, 0.9);
  --lt-shadow:  0 1px 2px rgba(16, 24, 40, 0.06), 0 1px 3px rgba(16, 24, 40, 0.10);

  /* ---- the mapping: dark is the default ---- */
  --surface: var(--dk-surface);
  --raised:  var(--dk-raised);
  --edge:    var(--dk-edge);
  --ink:     var(--dk-ink);
  --ink-muted: var(--dk-muted);
  --accent:  var(--dk-accent);
  --pass:    var(--dk-pass);
  --warn:    var(--dk-warn);
  --fail:    var(--dk-fail);
  --void:    var(--dk-void);
  --lip:     var(--dk-lip);
  --shadow:  var(--dk-shadow);
  color-scheme: dark;

  /* brand layer — the embed overrides --brand; --brand-ink is computed */
  --brand: var(--accent);
  --brand-ink: #FFFFFF;

  --ui: system-ui, -apple-system, "Segoe UI", Roboto, "Noto Sans Arabic",
        "Geeza Pro", sans-serif;
  --mono: ui-monospace, "Cascadia Mono", "DejaVu Sans Mono", "Liberation Mono",
          Consolas, monospace;

  --r-sm: 6px;
  --r: 10px;
  --r-lg: 14px;

  --ease: 120ms cubic-bezier(.2, 0, .2, 1);

  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px;

  --bar-h: 56px;
}

/* An explicit choice, set by the theme button and stamped server-side from the
   cookie so the page does not paint dark for a frame and then flip. */
:root[data-theme="light"] {
  --surface: var(--lt-surface);
  --raised:  var(--lt-raised);
  --edge:    var(--lt-edge);
  --ink:     var(--lt-ink);
  --ink-muted: var(--lt-muted);
  --accent:  var(--lt-accent);
  --pass:    var(--lt-pass);
  --warn:    var(--lt-warn);
  --fail:    var(--lt-fail);
  --void:    var(--lt-void);
  --lip:     var(--lt-lip);
  --shadow:  var(--lt-shadow);
  color-scheme: light;
}

/* No choice made yet: follow the operating system. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --surface: var(--lt-surface);
    --raised:  var(--lt-raised);
    --edge:    var(--lt-edge);
    --ink:     var(--lt-ink);
    --ink-muted: var(--lt-muted);
    --accent:  var(--lt-accent);
    --pass:    var(--lt-pass);
    --warn:    var(--lt-warn);
    --fail:    var(--lt-fail);
    --void:    var(--lt-void);
    --lip:     var(--lt-lip);
    --shadow:  var(--lt-shadow);
    color-scheme: light;
  }
}

/* ------------------------------------------------------------------- base */

* { box-sizing: border-box; }

html, body { height: 100%; overflow-x: clip; }
/* only the console surfaces need an exact viewport height (the split
   fills it); data surfaces scroll normally */
body.app { height: auto; min-height: 100%; }

body {
  margin: 0;
  /* xterm parks an off-screen measuring span at a large negative offset,
     which under dir=rtl extends the document and gives the whole page a
     horizontal scrollbar. Nothing here is meant to overflow the body. */
  overflow-x: clip;
  background: var(--surface);
  color: var(--ink);
  font: 400 14px/1.5 var(--ui);
  -webkit-font-smoothing: antialiased;
}

/* Arabic needs more room to breathe, and no case tricks — see .label */
:lang(ar) body { font-size: 15px; line-height: 1.7; }

h1, h2, h3 { margin: 0; font-weight: 600; }
h1 { font-size: 20px; line-height: 1.3; }
h2 { font-size: 16px; line-height: 1.4; }
h3 { font-size: 14px; }
p { margin: 0 0 var(--s3); }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Machine text is always LTR, always mono. Applied to the terminal, code,
 * paths, YAML and every numeral in an instrument readout. */
.ltr { direction: ltr; text-align: start; unicode-bidi: isolate; }
.mono, code, pre, kbd, samp { font-family: var(--mono); }
.num {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  direction: ltr;
  unicode-bidi: isolate;
}

.muted { color: var(--ink-muted); }
.spacer { flex: 1 1 auto; }
.hidden { display: none !important; }

/* Engraved label. Uppercase is Latin-only — Arabic has no case, so an
 * uppercase-label hierarchy would silently vanish in one of our languages. */
.label {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
:lang(ar) .label { text-transform: none; letter-spacing: 0; font-size: 12px; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

::selection { background: var(--accent); color: #fff; }

/* ------------------------------------------------------------------- mark */

/* A cloud with a gear cut out of it and a cursor on it. Inline SVG, ~1KB, so
   it costs no request and takes its colour from here: the cloud is --brand and
   white-labels, the gear and cursor are currentColor.

   The ring around the gear is a HOLE, not a pale stroke — the page shows
   through it. That is what makes one mark correct on the bar, on the page, in
   light mode and behind a tenant's own colour, with nothing to keep in sync. */
.mark { flex: 0 0 auto; display: block; color: var(--ink-muted); }
.lockup { display: flex; align-items: center; gap: 9px; cursor: pointer; }
.lockup .wordmark { font-size: 15px; }

/* --------------------------------------------------------------- the bar */

.bar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--s3);
  block-size: var(--bar-h);
  padding-inline: var(--s4);
  background: var(--raised);
  border-block-end: 1px solid var(--edge);
  box-shadow: var(--lip);
}
/* Explicit columns: a hidden instrument must not shift the controls into the
   middle of the bar. display:none removes a grid item from placement. */
.bar-id { grid-column: 1; display: flex; align-items: center; gap: var(--s2); min-inline-size: 0; }
.bar-slot { grid-column: 2; }
.bar > .bar-ctl { grid-column: 3; }
.bar-id .name { font-weight: 600; white-space: nowrap; }
.bar-id .sub {
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bar-ctl {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s2);
}
.wordmark { font-weight: 600; letter-spacing: -0.01em; cursor: pointer; }
.wordmark b { color: var(--brand); font-weight: 600; }
.bar img.logo { max-block-size: 26px; max-inline-size: 130px; }

/* ------------------------------------------------------------- the clock */

/* The single highest-stakes component. Fixed slot, centred (the one position
 * that does not change side between LTR and RTL), sized in ch so digits
 * cannot reflow. Urgency is state change at thresholds, never continuous
 * motion — anything moving every second lives in peripheral vision and is
 * exactly what breaks concentration over two hours. */
.clock {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 5px var(--s3) 6px;
  border: 1px solid transparent;
  border-radius: var(--r);
  min-inline-size: 10ch;
}
.clock-face {
  font: 600 22px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  direction: ltr;
  color: var(--ink-muted);
}
.clock-rule {
  position: relative;
  inline-size: 100%;
  block-size: 1px;
  background: var(--edge);
  overflow: hidden;
}
.clock-rule i {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: var(--left, 100%);
  background: var(--ink-muted);
}
.clock[data-state="notice"] .clock-face { color: var(--ink); }
.clock[data-state="notice"] .clock-rule i { background: var(--ink); }
.clock[data-state="warn"] { border-color: var(--warn); }
.clock[data-state="warn"] .clock-face { color: var(--ink); }
.clock[data-state="warn"] .clock-rule i { background: var(--warn); }
.clock[data-state="critical"] { border-color: var(--fail); }
.clock[data-state="critical"] .clock-face { color: var(--fail); font-weight: 700; }
.clock[data-state="critical"] .clock-rule i { background: var(--fail); }

/* Fires exactly twice in a session — at 60s and at 10s. Never per second. */
@keyframes clock-pulse {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.05); }
  100% { transform: scale(1); }
}
.clock.pulse { animation: clock-pulse 200ms ease-out; }

.clock-label { font-size: 11px; color: var(--ink-muted); }

/* ---------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  block-size: 38px;
  padding-inline: 16px;
  border: 1px solid var(--edge);
  border-radius: var(--r);
  background: var(--raised);
  color: var(--ink);
  font: 600 13.5px/1.2 var(--ui);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--ease), border-color var(--ease), color var(--ease);
}
.btn:hover:not(:disabled) {
  border-color: var(--ink-muted);
  background: color-mix(in srgb, var(--ink) 9%, transparent);
}
.btn:active:not(:disabled) { transform: translateY(0.5px); }
.btn:disabled { opacity: 0.45; cursor: default; }
.btn.primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  box-shadow: var(--shadow);
}
.btn.primary:hover:not(:disabled) { filter: brightness(1.08); }

/* The theme switch. Icon-only, so it needs its own square rather than a
   button with an empty label. */
.btn.icon { inline-size: 36px; padding-inline: 0; }
.btn.icon svg { inline-size: 17px; block-size: 17px; }
.btn.danger { color: var(--fail); border-color: color-mix(in srgb, var(--fail) 45%, transparent); }
.btn.danger:hover:not(:disabled) { border-color: var(--fail); }
.btn.quiet { background: transparent; border-color: transparent; color: var(--ink-muted); font-weight: 400; }
.btn.quiet:hover:not(:disabled) { color: var(--ink); border-color: var(--edge); }
.btn.sm { block-size: 28px; padding-inline: 10px; font-size: 12.5px; }
.btn.block { inline-size: 100%; }

/* ------------------------------------------------------------------ forms */

.field { display: block; margin-block-end: var(--s3); }
.field > label { display: block; margin-block-end: var(--s1); font-size: 12px; color: var(--ink-muted); }

input, select, textarea {
  inline-size: 100%;
  min-block-size: 36px;
  padding: 8px 11px;
  background: var(--surface);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  color: var(--ink);
  font: 400 14px/1.4 var(--ui);
  transition: border-color var(--ease);
}
/* the chevron is an inline data URI — a native select arrow is the single
   most OS-specific thing on the page, and it costs no request to fix */
select {
  appearance: none;
  padding-inline-end: 30px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23A09A91' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 10px 6px;
}
:dir(rtl) select { background-position: left 11px center; }
textarea { min-block-size: 0; font-family: var(--mono); font-size: 13px; resize: vertical; direction: ltr; }
input::placeholder, textarea::placeholder { color: var(--ink-muted); opacity: 0.75; }
input:focus, select:focus, textarea:focus { border-color: var(--accent); outline: none; }
input:hover:not(:focus), select:hover:not(:focus) { border-color: var(--ink-muted); }

.row { display: flex; gap: var(--s3); align-items: flex-end; flex-wrap: wrap; }
.row > * { flex: 1 1 160px; min-inline-size: 0; }

/* ------------------------------------------------------------------ prose */

/* The thing people read for two hours. Wider line-height, bounded measure. */
.prose { font-size: 16px; line-height: 1.65; max-inline-size: 68ch; }
.prose, .prose > *, .prose li, .lab-card h3, .lab-card p { unicode-bidi: plaintext; }
:lang(ar) .prose { font-size: 17px; line-height: 1.85; }
.prose > :first-child { margin-block-start: 0; }
.prose h1 { font-size: 19px; margin-block: var(--s5) var(--s2); }
.prose h2 { font-size: 16px; margin-block: var(--s4) var(--s2); }
.prose ul, .prose ol { padding-inline-start: 1.4em; margin-block: 0 var(--s3); }
.prose li { margin-block-end: 4px; }
.prose code {
  font-size: 0.88em;
  padding: 1px 5px;
  background: var(--surface);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  direction: ltr;
  unicode-bidi: isolate;
  overflow-wrap: anywhere;
}
.prose pre {
  margin-block: 0 var(--s3);
  padding: 10px 12px;
  background: var(--void);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  font-size: 13px;
  line-height: 1.6;
  overflow-x: auto;
  direction: ltr;
  text-align: left;
}
.prose pre code { padding: 0; border: 0; background: none; }

/* Commands in a lab brief exist to be run, so they exist to be copied. The
 * button is placed by JS after the markdown is rendered, stays out of the way
 * until the block is hovered or focused, and is always reachable by keyboard.
 * Anchored to the inline-end so it mirrors under RTL — the code inside stays
 * LTR, but the control belongs to the reading direction. */
.prose pre { position: relative; }
.prose pre .copy {
  position: absolute;
  inset-block-start: 6px;
  inset-inline-end: 6px;
  padding: 2px 7px;
  font-size: 11.5px;
  line-height: 1.6;
  color: var(--ink-muted);
  background: var(--raised);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--ease), color var(--ease);
}
.prose pre:hover .copy, .prose pre .copy:focus-visible { opacity: 1; }
.prose pre .copy:hover { color: var(--ink); border-color: var(--ink-muted); }
.prose pre .copy.done { opacity: 1; color: var(--pass); border-color: var(--pass); }
.prose table { inline-size: 100%; border-collapse: collapse; margin-block-end: var(--s3); }
.prose th, .prose td { padding: 6px 8px; border: 1px solid var(--edge); text-align: start; }

/* ----------------------------------------------------------- console split */

/* The runner: an aside of instructions beside the terminal. Declared in
 * logical order [aside, main] so dir="rtl" mirrors the panes for free. */
/* Two tracks by default — [instructions, terminal] — because that is what
 * every surface here has. The exam, the candidate assessment, the embed and
 * the race runner all reuse .split without a drag handle, and when this rule
 * unconditionally declared the handle's third track their terminal was placed
 * into it and rendered one character wide. Only add the track where the
 * element that fills it exists. */
.split {
  display: grid;
  grid-template-columns: minmax(320px, var(--aside-w, 42%)) minmax(440px, 1fr);
  block-size: calc(100% - var(--bar-h));
}
/* --aside-w is set by the drag handle and restored per user on load. The
 * minmax floors survive a bad stored value and a small window: the terminal
 * can never be squeezed below a usable 80 columns, and the instructions can
 * never be dragged away to nothing. */
.split.resizable {
  grid-template-columns: minmax(320px, var(--aside-w, 42%)) 0 minmax(440px, 1fr);
}
.aside {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
  overflow: hidden;
  border-inline-end: 1px solid var(--edge);
  background: var(--raised);
  box-shadow: var(--lip);
}
/* The grip occupies a zero-width grid track and hangs into both panes, so
 * dragging never shifts the layout by the width of the handle itself. It is a
 * real focusable separator: keyboard users resize with the arrow keys, which
 * is the only way this is reachable without a pointer. */
.grip {
  position: relative;
  z-index: 2;
  inline-size: 9px;
  margin-inline: -4px;
  cursor: col-resize;
  background: transparent;
  border: 0;
  padding: 0;
  touch-action: none;
}
.grip:hover::after, .grip:focus-visible::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 4px;
  inline-size: 1px;
  background: var(--accent);
}
.grip:focus-visible { outline: none; }
body.resizing { cursor: col-resize; user-select: none; }

.aside-head { padding: var(--s3) var(--s4); border-block-end: 1px solid var(--edge); }
.aside-body { flex: 1 1 auto; overflow-y: auto; padding: var(--s4); }
.aside-foot {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s3) var(--s4);
  border-block-start: 1px solid var(--edge);
}

/* The terminal is a hole cut in the panel: no frame, no letterbox. The pane
 * and the xterm canvas share --void so any fit-addon sliver is invisible. */
/* The column that holds the tab strip and the two views it switches between.
 * It takes the grid track the terminal used to take, so .split stays a
 * two-child grid and .term keeps its own semantics untouched. */
.pane {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
  background: var(--void);
}
.pane > .term { flex: 1 1 auto; min-block-size: 0; }

/* Tabs that select a PANE — the terminal, or the documentation the exams
 * allow. Not machine tabs: one session is one container, and the terminal
 * header still refuses to pretend otherwise. Flat, one hairline, the active
 * one marked by a rule rather than a raised tab shape. */
.pane-tabs {
  display: flex;
  align-items: stretch;
  flex: 0 0 auto;
  gap: 2px;
  padding-inline: 8px;
  background: var(--void);
  border-block-end: 1px solid var(--edge);
}
.pane-tab {
  padding: 7px 12px 6px;
  border: 0;
  border-block-end: 2px solid transparent;
  background: none;
  color: var(--ink-muted);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
  transition: color var(--ease), border-color var(--ease);
}
/* the documentation entry is an anchor, so the global link colour and the
   hover underline both have to be turned off for it to read as a tab */
a.pane-tab, a.pane-tab:hover { color: var(--ink-muted); text-decoration: none; }
.pane-tab:hover, a.pane-tab:hover { color: var(--ink); }
.pane-tab.current { color: var(--ink); border-block-end-color: var(--accent); }
.pane-tab:focus-visible { outline: 1px solid var(--accent); outline-offset: -2px; }
/* Says it leaves the page. In CSS rather than in the string, so it needs no
   translation and mirrors with the logical margin under dir="rtl". */
.pane-tab.docs-link::after,
.pane-tab.preview-link::after {
  content: "\2197";
  margin-inline-start: 5px;
  font-size: 11px;
  opacity: .75;
}

.term {
  display: flex;
  flex-direction: column;
  background: var(--void);
  min-inline-size: 0;
  padding: 8px;
  direction: ltr;
}
.term-canvas { flex: 1 1 auto; min-block-size: 0; }

/* One of the five frames this product is screenshotted in, so it is held to
 * the same rules as everything else: flat, one hairline, no chrome gradient,
 * no fake tabs. It sits INSIDE the void rather than on the panel above it, so
 * the terminal still reads as a hole cut in the surface rather than a widget
 * in a titlebar. Stays LTR with the terminal even under dir="rtl": it labels
 * machine output, and machine output does not mirror. */
.term-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex: 0 0 auto;
  padding: 4px 6px 7px;
  border-block-end: 1px solid var(--edge);
  margin-block-end: 7px;
  font-size: 12px;
  color: var(--ink-muted);
}
.term-head .host {
  color: var(--ink);
  font-family: var(--mono);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.term-head .conn { font-variant: small-caps; letter-spacing: .04em; }
.term-head .conn.bad { color: var(--fail); }

/* ---------------------------------------------------------- boot status */

/* The most important 30 seconds in the product. The brief is already on
 * screen and readable; this is the only thing that may occupy the terminal
 * pane while the sandbox starts, and it occupies it as one slim line rather
 * than a spinner, because a spinner would say "wait" when the correct
 * instruction is "read". It reports a STATE, never a fake percentage — the
 * provisioning call is one blocking request with no progress to report, and
 * inventing a progress bar for it would be a lie told sixty times a day.
 * The elapsed counter is the honest substitute: it is real, and it is the
 * number a user actually wants when something is taking too long. */
.boot {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  flex-wrap: wrap;
  padding: 6px 4px 8px;
  color: var(--ink-muted);
  font-size: 12.5px;
  border-block-end: 1px solid var(--edge);
  margin-block-end: 8px;
}
.boot .state { color: var(--ink); }
.boot .at { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.boot .why { flex: 1 1 100%; opacity: .8; }
.boot.failed .state { color: var(--fail); }
.boot .status-dot { align-self: center; }

/* A 1px seam that travels, not a progress bar: it encodes "working", which
 * is all we know. Hidden outright for reduced-motion — the elapsed counter
 * still carries the same information without the movement. */
.boot::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: -1px;
  block-size: 1px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  background-size: 40% 100%;
  background-repeat: no-repeat;
  animation: seam 1.4s linear infinite;
}
.boot.failed::after, .boot.done::after { content: none; }
@keyframes seam {
  from { background-position: -40% 0; }
  to   { background-position: 140% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .boot::after { content: none; }
}

/* ------------------------------------------------------------------- rail */

.rail { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.dot {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 26px;
  block-size: 26px;
  padding: 0 4px;
  border: 1px solid var(--edge);
  border-radius: 50%;
  background: transparent;
  color: var(--ink-muted);
  font: 400 12px/1 var(--mono);
  cursor: pointer;
}
.dot.current { border-color: var(--accent); color: var(--ink); }
.dot.passed {
  background: var(--pass);
  border-color: var(--pass);
  color: #06180E;
  transition: background 160ms ease;
}
/* Outlined, not filled — the opposite of .passed on purpose. While practising,
 * a task that is not done yet is the normal state and most of the rail will be
 * in it; filling seventeen squares with the failure colour would read as a
 * catastrophe rather than as a to-do list. */
.dot.failed { border-color: var(--fail); color: var(--fail); }
.dot.square { border-radius: var(--r-sm); inline-size: auto; min-inline-size: 28px; }
.dot.flagged { border-color: var(--warn); color: var(--warn); }

/* -------------------------------------------------------------- result log */

/* Was a single line replaced on every check — which destroyed the previous
 * reading, the thing you most want back while debugging. Now the last three,
 * newest first. A failed check is --warn, not --fail: it is a reading, not a
 * fault. --fail is reserved for the system actually being broken. */
.log { display: flex; flex-direction: column; gap: 6px; }
.log-row {
  display: grid;
  grid-template-columns: 16px auto 1fr;
  gap: var(--s2);
  padding: 7px 10px;
  border-inline-start: 2px solid var(--edge);
  background: var(--surface);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  font-size: 13px;
}
.log-row .glyph { text-align: center; }
.log-row .at { color: var(--ink-muted); font: 400 12px var(--mono); }
.log-row .msg { white-space: pre-wrap; overflow-wrap: anywhere; }
.log-row.pass { border-inline-start-color: var(--pass); }
.log-row.pass .glyph { color: var(--pass); }
.log-row.warn { border-inline-start-color: var(--warn); }
.log-row.warn .glyph { color: var(--warn); }
.log-row.fail { border-inline-start-color: var(--fail); }
.log-row.fail .glyph { color: var(--fail); }
.log-row.busy { border-inline-start-color: var(--accent); }
.log-empty { color: var(--ink-muted); font-size: 13px; }

/* The verdict: one line that is ALWAYS on screen and always current. The log
 * beneath it is history — three readings, newest first — and history answers
 * "what happened", not "where do I stand". While a check is in flight this
 * keeps showing the last known verdict rather than blanking, because the
 * previous result stays true until a new one replaces it. */
.verdict {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  margin-block-end: var(--s2);
  font-size: 13px;
  color: var(--ink-muted);
}
.verdict .glyph { inline-size: 16px; text-align: center; flex: 0 0 auto; }
.verdict .what { color: var(--ink); }
.verdict .how { color: var(--ink-muted); font-size: 12.5px; }

/* Confirmation, asked in the panel instead of in a native dialog. window
 * .confirm() is centred over the viewport by the browser, which means it is
 * the one thing left in this product capable of covering the terminal — and
 * it is unstyleable and untranslatable besides. This sits where the decision
 * is being made, next to the button that raised it. */
.ask {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-block-end: var(--s2);
  padding: 8px 10px;
  border: 1px solid var(--warn);
  border-radius: var(--r-sm);
  background: var(--surface);
  font-size: 13px;
}
.ask .q { flex: 1 1 auto; min-inline-size: 12ch; }
/* when it is inserted as a panel child rather than inside the log block, it
   needs the panel's own gutter */
.aside > .ask { flex: 0 0 auto; margin: 0 var(--s4) var(--s3); }
.ask .btn { block-size: 28px; padding-inline: 10px; font-size: 12.5px; }
.verdict.pass .glyph, .verdict.pass .what { color: var(--pass); }
.verdict.fail .glyph, .verdict.fail .what { color: var(--fail); }
.verdict.warn .glyph { color: var(--warn); }
.verdict.busy .glyph { color: var(--accent); }
/* pinned between the scrolling body and the action bar, so the verdict and the
   last three readings never scroll away while you work */
.aside > .log, .aside > .log-block {
  flex: 0 0 auto;
  padding: var(--s3) var(--s4);
  border-block-start: 1px solid var(--edge);
  background: var(--raised);
}
.aside > .log-block > .log { padding: 0; border: 0; background: none; }

/* --------------------------------------------------- the Page (incident) */

/* The signature. Break/fix must not read as a lesson chapter — it is an
 * alert. This is the only place in the product where a saturated colour
 * fills a large area. */
.page-alert {
  border: 1px solid var(--edge);
  border-inline-start: 3px solid var(--fail);
  border-radius: var(--r);
  overflow: hidden;
  margin-block-end: var(--s4);
}
.page-band {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  padding: 10px var(--s3);
  background: color-mix(in srgb, var(--fail) 30%, var(--raised));
  border-block-end: 1px solid var(--edge);
}
.page-sev {
  font: 700 12px/1 var(--mono);
  letter-spacing: 0.08em;
  color: var(--fail);
}
.page-service { font-weight: 600; }
.page-state {
  margin-inline-start: auto;
  font: 400 12px var(--mono);
  color: var(--ink-muted);
  direction: ltr;
}
.page-meta {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 6px var(--s3);
  padding: var(--s3);
  font-size: 13px;
}
.page-meta dt { color: var(--ink-muted); font-size: 12px; }
.page-meta dd { margin: 0; font-family: var(--mono); font-size: 12.5px; direction: ltr; text-align: start; }
.spark { display: inline-flex; align-items: flex-end; gap: 2px; block-size: 14px; }
.spark i { inline-size: 4px; background: var(--ink-muted); }
.spark i.hot { background: var(--fail); }

/* Resolved is the SAME page, not a new screen. Reusing the banner rather than
 * replacing it with a congratulations card is the whole point: an engineer
 * recognises the object they have been staring at for six minutes, now green,
 * and the readouts underneath swap from symptom to outcome. It also keeps the
 * one rule that matters — nothing covers the terminal, so the cluster is still
 * there to poke at after the win. */
.page-alert.resolved { border-inline-start-color: var(--pass); }
.page-alert.resolved .page-band {
  background: color-mix(in srgb, var(--pass) 26%, var(--raised));
}
.page-alert.resolved .page-sev { color: var(--pass); }
.page-alert.resolved .page-state { color: var(--pass); }

/* The root causes, revealed only now. Naming them during the exercise would
 * BE the answer; naming them at the end is the entire lesson, and it is the
 * thing a learner screenshots. */
.causes { margin: 0; padding: var(--s3); border-block-start: 1px solid var(--edge); }
.causes .label {
  font: 600 11px var(--mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-block-end: 6px;
}
.causes ul { list-style: none; margin: 0; padding: 0; }
.causes li {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  padding: 4px 0;
  font-size: 13px;
}
.causes li .id {
  font: 400 12px var(--mono);
  color: var(--ink-muted);
  direction: ltr;
}

/* ------------------------------------------------------------------ cards */

.card {
  background: var(--raised);
  border: 1px solid var(--edge);
  border-radius: var(--r-lg);
  padding: var(--s5);
  box-shadow: var(--shadow);
}
.card.interactive { transition: border-color var(--ease), box-shadow var(--ease); }
.card.interactive:hover {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--edge));
  box-shadow: var(--shadow), 0 0 0 3px color-mix(in srgb, var(--accent) 10%, transparent);
}
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--s4); }

/* ---------------------------------------------------------- stat tiles */

/* The headline-number card: a coloured icon tile, the number, what it counts,
   and an optional aside on the end. The tile is the only place a decorative
   colour is allowed to be a flat fill — it carries no meaning, so it is keyed
   to the metric's position rather than to a status. */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: var(--s4); }
.stat { display: flex; flex-direction: column; gap: var(--s3); }
.stat-top { display: flex; align-items: center; gap: var(--s3); }
.stat-ico {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  inline-size: 42px;
  block-size: 42px;
  border-radius: var(--r);
  background: var(--tile, var(--accent));
  color: #fff;
}
.stat-ico svg { inline-size: 20px; block-size: 20px; }
.stat-note { margin-inline-start: auto; font-size: 12px; color: var(--ink-muted); }
.stat .v {
  font: 600 28px/1.05 var(--ui);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.stat .label { font-size: 13px; color: var(--ink-muted); }
/* Four fixed tile colours, so a row of stats looks composed rather than
   rainbow. They are decoration and never appear anywhere a status does. */
.stat:nth-child(4n+1) { --tile: #10B981; }
.stat:nth-child(4n+2) { --tile: #3B82F6; }
.stat:nth-child(4n+3) { --tile: #F97316; }
.stat:nth-child(4n+4) { --tile: #8B5CF6; }

/* ----------------------------------------------------- technology marks */

/* The logo of the thing an exam is about — Kubernetes on a CKA, Argo on a
   CAPA — on a solid tile of that project's own colour.

   Solid rather than a tint of it, which is the obvious choice and the wrong
   one: a tinted tile inherits the surface's contrast, and Istio's blue on the
   dark surface lands at 2.2:1, under even the 3:1 asked of a graphic. On a
   solid tile the pairing is the mark against the brand colour and nothing
   else, so it reads the same in both themes. Which ink each brand takes is
   computed in tools/make_toolmarks.py, not chosen by eye: white is right on
   Istio's blue and unreadable on Cilium's yellow. */
.tool-mark {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  border-radius: var(--r-sm);
  background: var(--tool, var(--accent));
}
.tool-mark svg { fill: var(--tool-ink, #fff); }
.card-head { display: flex; align-items: center; gap: var(--s3); }
.card-head h3 { margin: 0; }
/* In the runner bar the mark separates the platform's identity from the
   exam's, so it wants the same gap the lockup already has. */
.bar-id .tool-mark { margin-inline-start: var(--s2); vertical-align: middle; }

.lab-card { display: flex; flex-direction: column; gap: var(--s2); }
.lab-card h3 { font-size: 15px; }
.lab-card p { flex: 1 1 auto; margin: 0; color: var(--ink-muted); font-size: 13px; }

.badges { display: flex; gap: 6px; flex-wrap: wrap; }
.badge {
  padding: 2px 8px;
  font-variant-numeric: tabular-nums;
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  color: var(--ink-muted);
  font-size: 11.5px;
}
.badge.pass { color: var(--pass); border-color: color-mix(in srgb, var(--pass) 50%, transparent); }
.badge.warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 50%, transparent); }
.badge.fail { color: var(--fail); border-color: color-mix(in srgb, var(--fail) 50%, transparent); }
/* Announcement, not status. It takes the accent rather than a semantic colour
 * because nothing has passed or failed here — and it is filled, not outlined,
 * so it reads first in a row of otherwise identical badges. */
.badge.new {
  color: var(--surface);
  background: var(--accent);
  border-color: var(--accent);
  font-weight: 500;
}

/* A price claim, not a status: filled so it reads at a glance, and warn rather
 * than accent so it does not compete with .badge.new on a card that carries
 * both (an upcoming certification, on offer). */
.badge.promo {
  color: var(--surface);
  background: var(--warn);
  border-color: var(--warn);
  font-weight: 500;
}

/* The price the promotion replaced. Muted and lighter than the price being
 * charged, so the eye lands on what is actually owed. */
.price-was {
  opacity: 0.65;
  font-weight: 400;
  margin-inline-end: 2px;
}

/* The line under an upcoming exam's description. Sized between body and badge
 * so it reads as a footnote to the card rather than a second paragraph. */
.note {
  margin-block-start: var(--s2);
  color: var(--ink-muted);
  font-size: 12.5px;
  line-height: 1.5;
}

/* ------------------------------------------------------- tables & density */

/* Data surfaces are a maintenance log, not a card grid: hairline rows,
 * tabular numerals, one bar per row at most. */
.tbl-wrap {
  background: var(--raised);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  box-shadow: var(--lip);
  overflow: hidden;
}
.tbl-wrap > .tbl { font-size: 13px; }
.tbl-wrap > .tbl thead th { background: color-mix(in srgb, var(--ink) 4%, transparent); }
.tbl-wrap > .tbl tbody tr:last-child > td { border-block-end: 0; }
.tbl { inline-size: 100%; border-collapse: collapse; font-size: 13px; }
.tbl th {
  padding: 8px 10px;
  text-align: start;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  border-block-end: 1px solid var(--edge);
  white-space: nowrap;
}
:lang(ar) .tbl th { text-transform: none; letter-spacing: 0; font-size: 12px; }
.tbl td { padding: 9px 10px; border-block-end: 1px solid var(--edge); vertical-align: middle; }
.tbl tbody tr { transition: background var(--ease); }
.tbl tbody tr:hover { background: color-mix(in srgb, var(--ink) 4%, transparent); }
.tbl .n { text-align: end; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.tbl .sub td { color: var(--ink-muted); font-size: 12.5px; background: var(--raised); }

.readouts { display: flex; flex-wrap: wrap; gap: var(--s6); padding-block: var(--s3); }
.readout { display: flex; flex-direction: column; gap: 2px; }
.readout .v {
  font: 600 26px/1.1 var(--mono);
  font-variant-numeric: tabular-nums;
  direction: ltr;
}
.readout.alert .v { color: var(--fail); }

/* Charts never use brand — that is what makes a tenant recolour safe. */
.meter { inline-size: 100%; min-inline-size: 70px; block-size: 6px; background: var(--edge); border-radius: 999px; overflow: hidden; }
.meter > * { display: block; block-size: 100%; background: var(--ink-muted); }
.meter.done > * { background: var(--pass); }
.meter.late > * { background: var(--fail); }

.pill { padding: 2px 8px; border: 1px solid var(--edge); border-radius: 999px; font-size: 11.5px; color: var(--ink-muted); }
.pill.ok { color: var(--pass); border-color: color-mix(in srgb, var(--pass) 50%, transparent); }
.pill.warn { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 50%, transparent); }
.pill.err { color: var(--fail); border-color: color-mix(in srgb, var(--fail) 50%, transparent); }

/* ------------------------------------------------------------------- tabs */

.tabs { display: flex; gap: 2px; padding-inline: var(--s4); background: var(--raised); border-block-end: 1px solid var(--edge); flex-wrap: wrap; }
.tabs button {
  padding: 11px var(--s3);
  border: 0;
  border-block-end: 2px solid transparent;
  background: none;
  color: var(--ink-muted);
  font: 400 14px var(--ui);
  cursor: pointer;
}
.tabs button.on { color: var(--ink); border-block-end-color: var(--brand); }

/* The same control inside a page rather than welded under the top bar: no
   raised strip, no bleed padding, so it lines up with the content it filters.
   The count is part of the label on purpose — a tab you might click into an
   empty panel should say it is empty before you click it. */
.tabs.inline { background: none; padding-inline: 0; gap: var(--s3); }
.tabs.inline button { padding-inline: 2px; font-size: 14.5px; }
.tabs button .n {
  margin-inline-start: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--edge);
  color: var(--ink-muted);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
}
.tabs button.on .n { background: var(--brand); color: var(--brand-ink); }

/* ----------------------------------------------------------------- layout */

main.page { max-inline-size: 1080px; margin: var(--s5) auto var(--s7); padding-inline: var(--s5); }

/* one header pattern for every data surface: title, one line of context,
   optional controls pinned to the end */
.page-head {
  display: flex;
  align-items: flex-end;
  gap: var(--s4);
  flex-wrap: wrap;
  padding-block-end: var(--s3);
  margin-block-end: var(--s4);
  border-block-end: 1px solid var(--edge);
}
.page-head h1 { font-size: 19px; }
.page-head .why { flex: 1 1 240px; color: var(--ink-muted); font-size: 13px; }
.page-head .acts { display: flex; gap: var(--s2); }

/* an empty state is a place the product explains itself, not a grey sentence */
.empty {
  padding: var(--s6) var(--s4);
  text-align: center;
  border: 1px dashed var(--edge);
  border-radius: var(--r);
  color: var(--ink-muted);
}
.empty strong { display: block; margin-block-end: 4px; color: var(--ink); font-size: 14px; }
.empty p { margin: 0; font-size: 13px; }

/* skeletons, so a slow office connection sees the shape of the page rather
   than the word "Loading" */
.skel { border-radius: var(--r-sm); background: var(--edge); opacity: .5; }
.skel.row { block-size: 14px; margin-block-end: 10px; }
.skel.row:nth-child(2n) { inline-size: 78%; }
.skel.row:nth-child(3n) { inline-size: 62%; }
@media (prefers-reduced-motion: no-preference) {
  .skel { animation: skel-pulse 1.4s ease-in-out infinite; }
  @keyframes skel-pulse { 0%, 100% { opacity: .35; } 50% { opacity: .6; } }
}
main.page > h2 { margin-block: var(--s6) var(--s3); }
main.page > h2:first-child { margin-block-start: 0; }
main.page > .page-head + h2, main.page > section:first-of-type h2 { margin-block-start: var(--s2); }
.stack { display: flex; flex-direction: column; gap: var(--s3); }

.centred {
  display: flex;
  align-items: center;
  justify-content: center;
  min-block-size: calc(100% - var(--bar-h));
  padding: var(--s4);
}
.centred .card { inline-size: 100%; max-inline-size: 420px; }

.overlay {
  position: fixed;
  inset: var(--bar-h) 0 0 0;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--s4);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  z-index: 10;
}
.overlay.on { display: flex; }
.overlay .card { max-inline-size: 460px; text-align: center; }

.msg { min-block-size: 18px; font-size: 13px; }
.msg.ok { color: var(--pass); }
.msg.err { color: var(--fail); }

/* inline-block, because a bare <span> is inline and a size on an inline box is
   ignored — the dot was invisible anywhere it was not already a flex item. */
.status-dot { display: inline-block; inline-size: 8px; block-size: 8px;
              border-radius: 50%; background: var(--pass); }
.status-dot.dead { background: var(--fail); }

/* Every pixel of chrome competes with the terminal: while a session is live
   the bar keeps session controls only. */
body.in-lab .bar-ctl .nav { display: none; }

/* --------------------------------------------------------------- sign in */

/* The first thing anyone sees. It was a bare card; it is now an entrance:
   mark, product, one line of what this actually is. */
.signin { inline-size: 100%; max-inline-size: 380px; }
.signin-brand { display: flex; align-items: center; justify-content: center; gap: 10px; margin-block-end: var(--s2); }
.signin-brand .mark { inline-size: 34px; block-size: 34px; }
.signin-brand .wordmark { font-size: 19px; }
.signin-why {
  margin-block-end: var(--s5);
  color: var(--ink-muted);
  font-size: 13.5px;
  line-height: 1.5;
  text-align: center;
}
.signin .card { padding: var(--s5); }
.signin-fields { margin-block-start: var(--s4); }
.signin-alt { margin-block-start: var(--s3); text-align: center; font-size: 13px; }

/* a live session is a state, not a notification: quiet band, not a card */
.resume {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 10px var(--s4);
  margin-block-end: var(--s4);
  background: var(--raised);
  border: 1px solid var(--edge);
  border-inline-start: 2px solid var(--pass);
  border-radius: var(--r);
  font-size: 13.5px;
}

/* --------------------------------------------------------------- language */

/* The header slot holds the theme button and the language switcher. It has
   to be a flex row itself: .lang below is block-level, so beside an inline
   button it wrapped onto a second line and pushed the bar taller. */
#lang-slot { display: inline-flex; align-items: center; gap: var(--s2); }
.lang { display: flex; gap: 1px; }
.lang a { padding: 3px 7px; border: 1px solid var(--edge); color: var(--ink-muted); font-size: 12px; }
.lang a:first-child { border-start-start-radius: var(--r-sm); border-end-start-radius: var(--r-sm); }
.lang a:last-child { border-start-end-radius: var(--r-sm); border-end-end-radius: var(--r-sm); }
.lang a:hover { text-decoration: none; color: var(--ink); }
.lang a.on { color: var(--ink); border-color: var(--ink-muted); }

/* ------------------------------------------------------------- responsive */

.dock {
  display: none;
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: 6;
  align-items: center;
  gap: var(--s2);
  block-size: 48px;
  padding-inline: var(--s3);
  background: var(--raised);
  border-block-start: 1px solid var(--edge);
}

/* Below ~900px the aside becomes a sheet over the terminal and a docked bar
 * keeps the two things you need constantly — instructions and check. */
@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; position: relative; }
  .split > .aside {
    position: absolute;
    inset: 0 0 48px 0;
    z-index: 5;
    border-inline-end: 0;
    border-block-end: 1px solid var(--edge);
  }
  .split > .aside[hidden] { display: none; }
  .split > .term { grid-column: 1; grid-row: 1; padding-block-end: 48px; }
  /* nothing to drag when the panes are stacked, not side by side */
  .grip { display: none; }
  .dock { display: flex; }
}

@media (max-width: 700px) {
  .bar { grid-template-columns: 1fr auto; padding-inline: var(--s3); }
  .bar-id .sub { display: none; }
  main.page { margin-block-start: var(--s4); }
  .readouts { gap: var(--s4); }
}

/* ------------------------------------------------------- reduced motion */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
  /* the countdown still has to signal — thicker bezel instead of a pulse */
  .clock.pulse { border-width: 2px; }
}

/* ---------------------------------------------------------------- races */

/* Standings sit pinned in the runner's aside: the one thing a racer glances
   at without leaving the terminal. Coarse states only, matching what the
   channel is allowed to broadcast. */
.standings { display: flex; flex-direction: column; gap: 2px; max-block-size: 168px; overflow-y: auto; }
.stand-row {
  display: grid;
  grid-template-columns: 24px 1fr auto;
  gap: var(--s2);
  align-items: center;
  padding: 4px 6px;
  border-radius: var(--r-sm);
  font-size: 12.5px;
}
.stand-row .pos {
  font: 600 12px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: var(--ink-muted);
  direction: ltr;
}
.stand-row .who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stand-row .st { font-size: 11.5px; color: var(--ink-muted); }
.stand-row.you { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.stand-row.finished .pos, .stand-row.finished .st { color: var(--pass); }
.stand-row.dnf, .stand-row.excluded { opacity: .55; }
.stand-row.dnf .st, .stand-row.excluded .st { color: var(--warn); }

.aside > .standings-block {
  flex: 0 0 auto;
  padding: var(--s3) var(--s4);
  border-block-start: 1px solid var(--edge);
  background: var(--raised);
}
.standings-block .label { margin-block-end: 6px; }

/* the lobby: a list of people waiting, not a dashboard */
.lobby-list { display: flex; flex-direction: column; }
.lobby-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 10px var(--s3);
  border-block-end: 1px solid var(--edge);
  font-size: 13.5px;
}
.lobby-row:last-child { border-block-end: 0; }

/* a rank is a numeral, so it stays in the mono face */
.podium { font: 700 15px/1 var(--mono); font-variant-numeric: tabular-nums; direction: ltr; }
.podium.p1 { color: var(--pass); }

/* one command per row: when it ran, the gap before it, then the command */
.cmd-row { display: grid; grid-template-columns: 52px 48px 1fr; gap: 8px; padding: 1px 0; }
.cmd-row > .num { text-align: end; }

/* --------------------------------------------------------------- leagues */

/* League colours are semantic tokens, not decoration on top of the system.
 * Every value below is measured against BOTH surfaces of its theme and clears
 * WCAG AA (4.5:1); tests/test_league_tokens.py recomputes the ratios and fails
 * the build if an edit drops one, so these numbers cannot rot.
 *
 * Why the badge always spells the league out: clearing AA on a dark surface
 * confines all seven colours to roughly a 5:1-9:1 luminance band, so adjacent
 * leagues can only be ~1.2:1 apart from each other. Colour therefore cannot be
 * the distinguishing signal — the name in text is, and the colour is a thin
 * accent. That is the reason for the HARD RULE, not just taste.
 *
 * dark theme, ratios against --surface / --raised (see the test):
 *   bronze 5.56/5.09  silver 5.48/5.01  gold 6.11/5.59  platinum 9.10/8.32
 *   diamond 6.96/6.37  master 6.24/5.70  grandmaster 6.48/5.94
 */
:root {
  --league-bronze: #C08552;
  --league-silver: #8F99A1;
  --league-gold: #C79A1E;
  --league-platinum: #9FC2C4;
  --league-diamond: #74A9D8;
  --league-master: #B08BD8;
  --league-grandmaster: #DB7FC4;
}

/* The light values, applied by the same two selectors as every other token —
 * an explicit choice, or the OS when no choice has been made. Ratios are
 * recomputed against the real surfaces by tests/test_league_tokens.py, which
 * reads both the colours and the backgrounds out of this file rather than
 * trusting a comment.
 *
 * silver is deliberately cool here: the obvious warm grey is --ink-muted
 * itself, so a silver badge would have been invisible as a badge.
 */
:root[data-theme="light"] {
  --league-bronze: #8A5A2B;
  --league-silver: #4F5B63;
  --league-gold: #7E5C1E;
  --league-platinum: #3F6E70;
  --league-diamond: #2A5F8F;
  --league-master: #6B4A9B;
  --league-grandmaster: #9B2D7F;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --league-bronze: #8A5A2B;
    --league-silver: #4F5B63;
    --league-gold: #7E5C1E;
    --league-platinum: #3F6E70;
    --league-diamond: #2A5F8F;
    --league-master: #6B4A9B;
    --league-grandmaster: #9B2D7F;
  }
}

/* The mark: a bordered shape with the league colour as a thin accent and the
   name in text. No gradient, no glow, no shield — it has to read as a
   credential line on a CV, not a game skin. */
.league {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 2px 8px 2px 6px;
  border: 1px solid var(--edge);
  border-inline-start: 2px solid var(--league, var(--ink-muted));
  border-radius: var(--r-sm);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--league, var(--ink-muted));
  white-space: nowrap;
}
:lang(ar) .league { text-transform: none; letter-spacing: 0; font-size: 12.5px; }

/* the geometric mark: a small square, rotated, outlined — flat by construction */
.league .glyph {
  inline-size: 7px;
  block-size: 7px;
  border: 1.5px solid currentColor;
  transform: rotate(45deg);
  flex: 0 0 auto;
}
.league .rating {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  direction: ltr;
  color: var(--ink);
  letter-spacing: 0;
  text-transform: none;
}

.league[data-league="Bronze"] { --league: var(--league-bronze); }
.league[data-league="Silver"] { --league: var(--league-silver); }
.league[data-league="Gold"] { --league: var(--league-gold); }
.league[data-league="Platinum"] { --league: var(--league-platinum); }
.league[data-league="Diamond"] { --league: var(--league-diamond); }
.league[data-league="Master"] { --league: var(--league-master); }
.league[data-league="Grandmaster"] { --league: var(--league-grandmaster); }
