/* 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);
  /* Text drawn ON an accent fill. Near-black, because the dark accent is a
     bright mint: white on it is 2.54:1 and this is 7.8:1. */
  --dk-brand-ink: #0E1116;

  /* 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);
  /* White, because the light accent is the much darker #067A55. */
  --lt-brand-ink: #FFFFFF;

  /* ---- 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);
  /* Ink for text ON a --brand fill, and it has to follow the theme because
     --brand defaults to --accent, which does. The dark accent is a bright mint
     (#10B981, relative luminance 0.36): white on it measures 2.54:1, well under
     AA, and it was every primary button, price and count badge on the site —
     93 failing text runs in the dark theme against none in light, which is the
     asymmetry that gave it away. Near-black on the same mint is 7.8:1.
     Light maps this back to white, over the much darker #067A55. */
  --brand-ink: var(--dk-brand-ink);

  /* Ink for whatever sits ON --void, and it is deliberately not --ink.
     --void is the one surface that does NOT follow the theme: it stays a very
     dark slate in light mode (see --lt-void) so the terminal reads the same in
     both. Anything drawn inside it therefore has to keep the dark palette too.
     Reaching for --ink there is the bug this exists to prevent — it flips to
     #101828 in light mode, and every command example in a lab brief turns
     black-on-black the moment somebody picks the white theme. */
  --void-ink:   var(--dk-ink);
  --void-muted: var(--dk-muted);
  --void-edge:  var(--dk-edge);
  --void-fail:  var(--dk-fail);
  --void-warn:  var(--dk-warn);
  --void-pass:  var(--dk-pass);

  /* Ink for text ON a --pass / --fail fill. Near-black suits the bright dark
     theme fills; the light theme's fills are deep green and red, where the
     same near-black measured 2.9:1 and 2.6:1 — a passed task chip and the
     exam's own PASS pill were the least readable thing on the page. */
  --on-pass: #06180E;
  --on-fail: #1B0505;

  --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; --s8: 64px;

  --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);
  --brand-ink: var(--lt-brand-ink);
  --on-pass: #FFFFFF;
  --on-fail: #FFFFFF;
  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);
    --brand-ink: var(--lt-brand-ink);
    --on-pass: #FFFFFF;
    --on-fail: #FFFFFF;
    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;
  /* A glow low in one corner, mixed from --accent rather than a fixed hue, so
     it is still correct after a tenant recolour and still legible in light
     mode, where the same accent is a much darker green. Fixed, so it stays
     put behind scrolling content instead of repeating down the page — the
     one place this differs from every other page background here, which is
     why it is spelled out rather than left to the shorthand default. */
  background: radial-gradient(115% 95% at -10% 115%,
                              color-mix(in srgb, var(--accent) 55%, transparent) 0%,
                              color-mix(in srgb, var(--accent) 30%, transparent) 24%,
                              color-mix(in srgb, var(--accent) 10%, transparent) 46%,
                              transparent 72%) fixed,
             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 */

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);
}

: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(--control-edge, 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%; }

/* Keyboard focus, which the panel had been leaving to the browser default: a
   dotted UA ring on --raised is nearly invisible in dark mode. :focus-visible
   rather than :focus, so a mouse click does not draw it. The ring is drawn
   OUTSIDE the border with a spread shadow instead of an outline, because an
   outline on a 1px-bordered pill clips against the neighbouring button. */
.btn:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 32%, transparent);
}
.btn.primary:focus-visible {
  box-shadow: var(--shadow), 0 0 0 3px color-mix(in srgb, var(--accent) 45%, transparent);
}

/* The busy state, for a button whose click is a request. Its label is swapped
   in JS; this is the spinner beside it and the hold on hover/press feedback —
   a button that still lifts under the cursor while it is disabled reads as
   clickable, and gets clicked again. */
.btn.is-busy { cursor: progress; opacity: 1; }
.btn.is-busy .btn-label { opacity: 0.85; }

/* The actions at the end of a table row. Spaced by the row rather than by a
   margin on each button, which also left one hanging after the last, and
   which was set inline on every button the admin studio built — thirty-nine
   copies of one spacing decision on a single screen. */
.row-actions { display: flex; gap: 6px; align-items: center; }
.btn-spin {
  inline-size: 13px;
  block-size: 13px;
  flex: none;
  border: 2px solid currentColor;
  border-block-start-color: transparent;
  border-radius: 50%;
  animation: btn-spin 0.6s linear infinite;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }

/* The same ring at the size of a page, for a wait that owns the screen rather
   than a button. The track is drawn under the moving arc so the circle stays a
   circle at every frame; an arc alone reads as a fragment while it is on the
   far side. */
.load-ring {
  inline-size: 44px;
  block-size: 44px;
  margin: 0 auto var(--s3);
  border: 3px solid var(--edge);
  border-block-start-color: var(--accent);
  border-radius: 50%;
  animation: btn-spin 0.8s linear infinite;
}

/* Respect the setting rather than spinning regardless: this is the one piece
   of motion on the page that runs for seconds at a time. The ring still shows
   as a static arc, so the state is not conveyed by motion alone. */
@media (prefers-reduced-motion: reduce) {
  .btn-spin { animation-duration: 2.4s; }
  .load-ring { animation-duration: 3s; }
  .btn { transition: none; }
}

/* Buttons on a card sit in a row that wraps, instead of each call site adding
   margin-inline-start to the second one — which broke in RTL, where the gap
   landed on the wrong side of the pair. */
.card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-block-start: var(--s3);
}

/* ------------------------------------------------------------------ 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;
}
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; }
.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);
  color: var(--void-ink);
  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);
}
/* The surfaces that stay dark in the light theme take a dark colour-scheme
   with them, or the browser draws a white scrollbar down a black terminal. */
.pane, .term, .prep-log, .prose pre, .rep-tbl pre { color-scheme: dark; }
.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(--void-edge);
}
.pane-tab {
  padding: 7px 12px 6px;
  border: 0;
  border-block-end: 2px solid transparent;
  background: none;
  color: var(--void-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(--void-muted); text-decoration: none; }
.pane-tab:hover, a.pane-tab:hover { color: var(--void-ink); }
.pane-tab.current { color: var(--void-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(--void-edge);
  margin-block-end: 7px;
  font-size: 12px;
  color: var(--void-muted);
}
.term-head .host {
  color: var(--void-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(--void-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(--void-muted);
  font-size: 12.5px;
  border-block-end: 1px solid var(--void-edge);
  margin-block-end: 8px;
}
.boot .state { color: var(--void-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(--void-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(--control-edge, 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: var(--on-pass);
  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); }
/* raised by a header control, so it sits directly under the bar rather than
   at the foot of a page the button cannot see */
#bar-ask.ask { margin: var(--s3) var(--s4) 0; }
#bar-ask:empty { display: none; }

/* ----------------------------------------------------------------- sheet */

/* The panel a card opens for everything that does not belong on the card.
   A native <dialog>, so the backdrop, the focus trap, Escape and the page
   behind going inert all come from the platform rather than from us. */
.sheet {
  inline-size: min(560px, calc(100vw - 2 * var(--s4)));
  max-block-size: min(80vh, 640px);
  /* transitioned so widening for the certificate does not snap */
  transition: inline-size 180ms ease-out;
  padding: 0;
  border: 1px solid var(--edge);
  border-radius: var(--r);
  background: var(--surface);
  color: var(--ink);
  overflow: hidden;
}
.sheet::backdrop { background: rgb(0 0 0 / 0.55); backdrop-filter: blur(2px); }
.sheet-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s3) var(--s4);
  border-block-end: 1px solid var(--edge);
}
/* the title takes the slack, so the close button stays pinned to the end */
.sheet-head h3 { flex: 1 1 auto; margin: 0; font-size: 15px; }
.sheet-body { padding: var(--s4); overflow-y: auto; }
.sheet-body p { margin: 0; color: var(--ink-muted); font-size: 13.5px; line-height: 1.6; }
.sheet-body h4 {
  margin: var(--s4) 0 var(--s2);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
@media (prefers-reduced-motion: no-preference) {
  .sheet[open] { animation: sheet-in 160ms cubic-bezier(0.16, 1, 0.3, 1); }
  @keyframes sheet-in {
    from { opacity: 0; transform: translateY(-6px) scale(0.985); }
  }
}

/* The certificate viewer: a landscape A4 shown whole, at whatever size the
   window allows. Same dialog as the exam panel — it swaps to this mode rather
   than opening a second one — so the width transition below carries it between
   the two.

   The sizing is driven by HEIGHT rather than width, which is the opposite of
   how everything else here works and is the whole point. A 1123x794 document
   given `inline-size: 100%` of a panel is as tall as that width divided by
   1.41, and inside a scrolling panel that shares the viewport with a
   description and a row of badges it came out cropped — the credential id and
   the verify URL, the two things that make it a credential rather than a
   picture, could not be read. Widening the panel did nothing, because the
   constraint was never width.

   So: fix the image's height to what is left after the header and the note,
   let its width follow from the aspect ratio, and let the dialog shrink-wrap
   that width. No scrolling, no letterbox down either side. */
.sheet.cert-view {
  inline-size: max-content;
  max-inline-size: calc(100vw - 2 * var(--s4));
  max-block-size: none;
}
/* the header chevron points the way reading runs, so it mirrors under RTL */
.cert {
  display: block;
  /* Paper stock, so it keeps its own light background in either theme — it is
     a document being previewed, not a panel of the UI, and a certificate that
     inverts in dark mode is one nobody could print. */
  background: #FBFAF6;
  /* held before the SVG lands, so opening does not reflow the dialog */
  aspect-ratio: 1123 / 794;
  /* The 132px is the header and the note under it. The second term is the
     narrow-window case: below roughly 1100px wide it is width that binds, and
     without it the sides would crop instead. */
  block-size: min(calc(100vh - 132px), calc((100vw - 2 * var(--s4)) * 794 / 1123));
  inline-size: auto;
  max-inline-size: 100%;
}
.cert-note {
  margin: 0;
  padding: var(--s3) var(--s4);
  border-block-start: 1px solid var(--edge);
  font-size: 12.5px;
}
.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; }

/* The card's one line of prose, held to three of them.
 *
 * A catalogue is read by scanning, and a card is a claim you either take or
 * leave — so the copy has a budget, and the budget has to be enforced HERE
 * rather than trusted to whoever writes the next metadata.yaml. The exam
 * descriptions ran to 477 characters and buried the buttons below the fold;
 * the clamp means the worst a long one can now do is end in an ellipsis.
 *
 * line-clamp needs the -webkit- prefix in every shipping browser, including
 * Firefox, which implements the prefixed form rather than the standard one. */
.card-lede {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
  /* An explicit line-height, because the clamp is in LINES: leave it to the
     inherited value and the reserved space drifts with the font stack. */
  line-height: 1.5;
}

.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;
}
.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; max-inline-size: 62ch; margin-inline: auto; }
/* an empty state that explains a dead end has to offer a live one */
.empty .actions {
  display: flex; justify-content: center; flex-wrap: wrap;
  gap: var(--s2); margin-block-start: var(--s4);
}

/* 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; }

/* ------------------------------------------------------ grading overlay
   The wait between handing the paper in and reading the report.

   It sits OVER the runner rather than replacing it. Swapping the screen out
   entirely made Submit feel like a navigation — the paper vanished, and with
   it any evidence of what had just been sent. Behind glass it reads as
   finished rather than gone, and the header stays sharp above the blur on
   purpose: the stopped clock up there is the proof that pressing Submit
   really did stop the timer. */
.grading {
  position: fixed;
  inset: var(--bar-h) 0 0 0;
  z-index: 40;
  display: none;
  align-items: center;
  justify-content: center;
  padding: var(--s4);
  background: color-mix(in srgb, var(--void) 62%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(115%);
  backdrop-filter: blur(14px) saturate(115%);
}
.grading.on { display: flex; }

/* Without a backdrop filter the paper stays legible through a 62% wash — and
   a legible exam under a "submitted" modal invites a candidate to keep
   reading it. The wash carries the whole job instead. */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .grading { background: color-mix(in srgb, var(--void) 94%, transparent); }
}

.grading-card {
  inline-size: 100%;
  max-inline-size: 470px;
  padding: var(--s6) var(--s5) var(--s5);
  text-align: center;
  border-radius: var(--r-lg);
  border: 1px solid color-mix(in srgb, var(--ink) 14%, transparent);
  /* Glass: a translucent raised surface, lit from where the ring sits so the
     glow reads as coming off the ring rather than painted on the card. */
  background:
    radial-gradient(120% 80% at 50% 0%,
                    color-mix(in srgb, var(--accent) 13%, transparent), transparent 62%),
    color-mix(in srgb, var(--raised) 84%, transparent);
  box-shadow: 0 32px 80px -20px rgba(0, 0, 0, 0.7), var(--lip);
}
.grading-card h2 { font-size: 21px; margin-block: 0 var(--s2); }
/* Not `.why`: quiz.html styles that class as its dark answer-explanation
   panel, and the subtitle came out as a black block with a left rule. */
.grading-card .gwhy { color: var(--ink-muted); font-size: 13.5px; line-height: 1.55;
                      margin-block: 0; }

/* ---- the ring ---- */
.gring { position: relative; inline-size: 128px; block-size: 128px;
         margin: 0 auto var(--s5); }
.gring svg { display: block; inline-size: 100%; block-size: 100%;
             transform: rotate(-90deg); }
.gring circle { fill: none; stroke-width: 9; }
.gring .track { stroke: color-mix(in srgb, var(--ink) 13%, transparent); }
.gring .arc {
  stroke: url(#gring-grad);
  stroke-linecap: round;
  /* 2πr, r=52. Set here rather than in the markup so the one number that has
     to match the radius lives beside the radius. */
  stroke-dasharray: 326.73;
  stroke-dashoffset: 326.73;
  transition: stroke-dashoffset 600ms cubic-bezier(.25, .8, .3, 1);
  filter: drop-shadow(0 0 7px color-mix(in srgb, var(--accent) 65%, transparent));
}
/* A gradient mixed FROM the accent, not a second hard-coded hue: the tenant
   recolour has to survive this screen like every other. */
#gring-a { stop-color: var(--accent); }
#gring-b { stop-color: color-mix(in srgb, var(--accent) 52%, #FFFFFF); }
.gring-num {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font: 700 27px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  direction: ltr;
}

/* ---- the three steps ---- */
.gsteps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0;                      /* the connectors span column edges; a gap here
                                  would leave them short at both ends */
  margin-block: var(--s6) var(--s5);
  padding: 0;
  list-style: none;
}
.gstep {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding-inline: var(--s1);
}
/* Drawn at the icon row rather than the label row: labels wrap at three
   languages' worth of lengths, and a connector pinned to a wrapping line ends
   up at three different heights across the three columns. */
.gstep:not(:last-child)::after {
  content: "";
  position: absolute;
  inset-block-start: 21px;
  inset-inline-start: calc(50% + 28px);
  inset-inline-end: calc(-50% + 28px);
  block-size: 1px;
  background: var(--edge);
}
.gstep.is-done::after { background: color-mix(in srgb, var(--accent) 55%, transparent); }

.gstep-ico {
  display: grid; place-items: center;
  inline-size: 44px; block-size: 44px;
  border-radius: 50%;
  border: 1px solid var(--edge);
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  color: var(--ink-muted);
}
.gstep-ico svg { inline-size: 19px; block-size: 19px; }
.gstep.is-active .gstep-ico,
.gstep.is-done .gstep-ico {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.gstep-label { font-size: 12px; line-height: 1.35; color: var(--ink-muted); }
.gstep.is-active .gstep-label, .gstep.is-done .gstep-label { color: var(--ink); }

/* The state, as a shape and not only as a colour: an empty ring, a spinning
   arc, a filled tick. */
.gstep-mark {
  inline-size: 18px; block-size: 18px;
  border-radius: 50%;
  border: 2px solid var(--edge);
}
.gstep.is-active .gstep-mark {
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
  border-block-start-color: var(--accent);
  animation: btn-spin 0.8s linear infinite;
}
.gstep.is-done .gstep-mark {
  position: relative;
  border-color: var(--accent);
  background: var(--accent);
}
/* Centred by transform rather than by inset-inline: a tick is not a
   directional glyph, and it must not mirror under dir="rtl". */
.gstep.is-done .gstep-mark::after {
  content: "";
  position: absolute;
  inset-block-start: 50%; left: 50%;
  inline-size: 4px; block-size: 8px;
  border: solid var(--brand-ink);
  border-width: 0 2px 2px 0;
  transform: translate(-50%, -62%) rotate(45deg);
}

/* ---- the footer pill ---- */
.gpill {
  display: inline-flex; align-items: center; gap: var(--s2);
  padding: 7px 14px;
  border-radius: 999px;
  border: 1px solid var(--edge);
  background: color-mix(in srgb, var(--ink) 4%, transparent);
  color: var(--ink-muted);
  font-size: 12.5px;
}
.gpill .btn-spin { color: var(--accent); }

/* ---- the finished state ---- */
.gdone-mark {
  display: grid; place-items: center;
  inline-size: 64px; block-size: 64px;
  margin: 0 auto var(--s4);
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 40%, transparent),
              0 0 34px -6px color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--accent);
}
.gdone-mark svg { inline-size: 30px; block-size: 30px; }
/* Grading that has stalled is not a success, and it must not be lit like one —
   the card's own accent glow goes with it. */
.gdone-mark.warn {
  color: var(--warn);
  background: color-mix(in srgb, var(--warn) 15%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--warn) 40%, transparent);
}
.grading-card.is-stuck {
  background: color-mix(in srgb, var(--raised) 84%, transparent);
}
.gdone-score {
  margin-block: var(--s5) var(--s5);
  font: 700 40px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  direction: ltr;
}
.gdone-score .lbl {
  display: block;
  font: 500 12px/1 var(--ui);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-block-end: var(--s2);
}
.gdone-score.pass { color: var(--pass); }
.gdone-score.fail { color: var(--fail); }
/* Not scored: no number at all, for the same reason the results page shows a
   dash — a 0% here would read as a verdict on the candidate. */
.gdone-score.void { color: var(--ink-muted); font-size: 30px; }

/* ================================================ preparing the environment
   The wait before the paper. This is the first thing a candidate sees on exam
   morning, on a screen where nothing they do matters yet — so it carries the
   reassurance the rest of the product does not have room for.

   Everything on it that looks like progress is real. There is no phase model
   behind provisioning: the container already exists by the time this renders,
   and readiness is one shell command with a pass/fail. So the ring here is
   INDETERMINATE, unlike the grading ring, which has milestones to sit on. A
   percentage would have to be invented, and on the screen whose whole job is
   "your clock has not started, trust us", an invented number is the one thing
   that cannot appear. The elapsed time in the middle is the honest number and
   the more useful one. */
/* `main.page` caps every other page at 1080px and pads it; this one is a
   full-bleed layout with its own rails, so it takes the width back. */
main.page.prep { max-inline-size: none; margin: 0; padding-inline: 0; }
.prep-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2.1fr) minmax(0, 1fr);
  gap: var(--s6);
  align-items: center;
  max-inline-size: 1400px;
  margin-inline: auto;
  padding: var(--s6) var(--s5) var(--s4);
}
/* The rails are reassurance, and reassurance is what you drop first: below
   1200px the card takes the full width and they stack under it. */
.prep-rail { min-inline-size: 0; }
@media (max-width: 1200px) {
  .prep-grid { grid-template-columns: minmax(0, 1fr); gap: var(--s5); }
  .prep-rail-brand { display: none; }
  .prep-rail-benefits { display: grid; grid-template-columns: repeat(3, 1fr);
                        gap: var(--s3); }
}
@media (max-width: 720px) {
  .prep-rail-benefits { grid-template-columns: 1fr; }
  .prep-grid { padding: var(--s4) var(--s3); }
}

/* ---- left rail: the brand line ---- */
.prep-lede {
  font-size: clamp(26px, 2.6vw, 38px);
  line-height: 1.14;
  letter-spacing: -0.02em;
  margin: 0;
}
.prep-lede b { color: var(--accent); font-weight: inherit; }
.prep-rule {
  inline-size: 46px; block-size: 3px; border-radius: 2px;
  background: var(--accent);
  margin-block: var(--s4) var(--s4);
}
.prep-real { color: var(--ink-muted); font-size: 13.5px; line-height: 1.9; margin: 0; }
/* The exam's own technology mark, blown up as artwork. Nothing else on the
   rail is specific to the paper being sat, and a generic illustration here
   would be decoration; this is the one that tells you which exam you opened. */
.prep-art {
  margin-block-start: var(--s6);
  display: grid; place-items: center;
  inline-size: 168px; block-size: 168px;
  border-radius: var(--r-lg);
  border: 1px solid var(--edge);
  background:
    radial-gradient(80% 80% at 50% 20%,
                    color-mix(in srgb, var(--accent) 14%, transparent), transparent 70%),
    color-mix(in srgb, var(--ink) 3%, transparent);
}
.prep-art .tool-mark { inline-size: 88px; block-size: 88px; border-radius: var(--r-lg); }

/* ---- the card ---- */
.prep-card {
  border-radius: var(--r-lg);
  border: 1px solid color-mix(in srgb, var(--ink) 13%, transparent);
  background:
    radial-gradient(100% 60% at 20% 0%,
                    color-mix(in srgb, var(--accent) 10%, transparent), transparent 60%),
    color-mix(in srgb, var(--raised) 88%, transparent);
  box-shadow: 0 28px 70px -24px rgba(0, 0, 0, 0.6), var(--lip);
  padding: var(--s5);
}
.prep-card-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); flex-wrap: wrap;
  margin-block-end: var(--s5);
}
.prep-chip {
  display: inline-flex; align-items: center; gap: var(--s2);
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--edge);
  background: color-mix(in srgb, var(--ink) 4%, transparent);
  font-size: 12.5px;
  color: var(--ink);
}
.prep-chip .tool-mark { inline-size: 20px; block-size: 20px; border-radius: 5px; }
.prep-chip.accent {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.prep-hero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s5);
  align-items: center;
}
@media (max-width: 760px) {
  .prep-hero { grid-template-columns: minmax(0, 1fr); justify-items: center;
               text-align: center; }
}
.prep-copy h2 { font-size: clamp(19px, 2vw, 25px); margin: 0 0 var(--s2);
                letter-spacing: -0.01em; }
.prep-copy > p { color: var(--ink-muted); font-size: 13.5px; line-height: 1.6;
                 margin: 0 0 var(--s4); }

/* ---- the ring: indeterminate, by design (see the note above) ---- */
.prep-ring { position: relative; inline-size: 172px; block-size: 172px; flex: none; }
.prep-ring svg { display: block; inline-size: 100%; block-size: 100%; }
.prep-ring circle { fill: none; stroke-width: 9; }
.prep-ring .track { stroke: color-mix(in srgb, var(--ink) 11%, transparent); }
.prep-ring .arc {
  stroke: url(#pring-grad);
  stroke-linecap: round;
  /* 2πr for r=60 is 376.99; a 96-long dash is a quarter turn of arc. */
  stroke-dasharray: 96 281;
  transform-origin: 50% 50%;
  animation: pring 1.5s linear infinite;
  filter: drop-shadow(0 0 8px color-mix(in srgb, var(--accent) 60%, transparent));
}
@keyframes pring { to { transform: rotate(360deg); } }
#pring-a { stop-color: var(--accent); }
#pring-b { stop-color: color-mix(in srgb, var(--accent) 45%, #FFFFFF); }
.prep-ring-mid {
  position: absolute; inset: 0;
  display: grid; place-content: center; justify-items: center; gap: 4px;
}
.prep-ring-num {
  font: 700 27px/1 var(--mono);
  font-variant-numeric: tabular-nums;
  direction: ltr;
}
.prep-ring-lbl { font-size: 12px; color: var(--ink-muted); }

/* ---- the three facts about the paper ---- */
.prep-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
              gap: var(--s3); }
@media (max-width: 560px) { .prep-stats { grid-template-columns: 1fr; } }
.prep-stat {
  display: flex; align-items: center; gap: var(--s3);
  padding: 12px var(--s3);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--ink) 3%, transparent);
  text-align: start;
}
.prep-stat > svg { inline-size: 19px; block-size: 19px; flex: none; color: var(--ink-muted); }
.prep-stat.accent > svg { color: var(--accent); }
.prep-stat b { display: block; font-size: 15px; font-weight: 650; line-height: 1.25; }
.prep-stat span { display: block; font-size: 11.5px; color: var(--ink-muted); }

/* ---- the steps reuse the grading overlay's stepper; only the spacing differs ---- */
.prep-card .gsteps { margin-block: var(--s6) var(--s5); }

/* ---- the live log ---- */
.prep-log {
  border: 1px solid var(--edge);
  border-radius: var(--r);
  overflow: hidden;
  background: var(--void);
}
.prep-log-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  padding: 9px var(--s3);
  border-block-end: 1px solid color-mix(in srgb, var(--void-edge, var(--edge)) 90%, transparent);
  background: color-mix(in srgb, var(--void-ink) 4%, transparent);
  font-size: 12.5px;
}
/* --void-* throughout: the log is the terminal's surface, which stays dark in
   the light theme, and theme ink on it went black-on-black there. */
.prep-log-title { display: inline-flex; align-items: center; gap: var(--s2);
                  color: var(--void-ink); }
.prep-caret { font-family: var(--mono); color: var(--accent); }
.prep-live { display: inline-flex; align-items: center; gap: 7px;
             color: var(--void-muted); font-size: 11.5px; }
.prep-live i {
  inline-size: 7px; block-size: 7px; border-radius: 50%;
  background: var(--pass);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--pass) 22%, transparent);
}
@media (prefers-reduced-motion: no-preference) {
  .prep-live i { animation: prep-live 2s ease-in-out infinite; }
  @keyframes prep-live { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
}
.prep-log-body {
  margin: 0; padding: var(--s3);
  list-style: none;
  /* Tall enough to show the last several lines, short enough that the card
     does not grow every three seconds for two minutes. */
  block-size: 132px;
  overflow-y: auto;
  font: 400 12.5px/1.85 var(--mono);
  /* A log is machine output: left-to-right even on the Arabic page, where the
     timestamps would otherwise reorder around the text. */
  direction: ltr; text-align: left;
  color: var(--void-ink, var(--ink));
}
.prep-log-body .ts { color: var(--void-muted); }
.prep-log-body .ok { color: var(--void-pass); }

/* ---- the footer banner ---- */
.prep-note {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin-block-start: var(--s4);
  padding: 11px var(--s3);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  background: color-mix(in srgb, var(--ink) 3%, transparent);
  font-size: 12.5px;
  color: var(--ink-muted);
}
.prep-note .spacer { flex: 1 1 auto; }
.prep-note-i {
  display: grid; place-items: center;
  inline-size: 20px; block-size: 20px; border-radius: 50%; flex: none;
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  color: var(--accent);
  font: 600 12px/1 var(--ui);
}
.prep-note-t { color: var(--ink); font-family: var(--mono);
               font-variant-numeric: tabular-nums; direction: ltr; }

/* ---- right rail: what the wait is buying ---- */
.prep-benefit {
  display: flex; align-items: flex-start; gap: var(--s3);
  padding: var(--s3);
  border-radius: var(--r);
  border: 1px solid transparent;
}
.prep-rail-benefits > .prep-benefit + .prep-benefit { margin-block-start: var(--s2); }
@media (max-width: 1200px) {
  .prep-benefit { border-color: var(--edge);
                  background: color-mix(in srgb, var(--ink) 3%, transparent); }
  .prep-rail-benefits > .prep-benefit + .prep-benefit { margin-block-start: 0; }
}
.prep-benefit-ico {
  display: grid; place-items: center; flex: none;
  inline-size: 38px; block-size: 38px;
  border-radius: var(--r-sm);
  border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
  background: color-mix(in srgb, var(--accent) 11%, transparent);
  color: var(--accent);
}
.prep-benefit-ico svg { inline-size: 18px; block-size: 18px; }
.prep-benefit b { display: block; font-size: 13.5px; font-weight: 600; }
.prep-benefit span { display: block; font-size: 12px; color: var(--ink-muted);
                     line-height: 1.5; }

.prep-footer {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  max-inline-size: 1400px;
  margin-inline: auto;
  padding: var(--s4) var(--s5) var(--s5);
  border-block-start: 1px solid var(--edge);
  font-size: 12.5px;
  color: var(--ink-muted);
}

@media (max-width: 480px) {
  .grading-card { padding: var(--s5) var(--s4) var(--s4); }
  .gring { inline-size: 104px; block-size: 104px; }
  .gring-num { font-size: 22px; }
  .gstep-label { font-size: 11px; }
  .gstep:not(:last-child)::after { inset-inline-start: calc(50% + 26px);
                                   inset-inline-end: calc(-50% + 26px); }
}

/* The ring still arrives at its true value; it just stops sweeping there. */
@media (prefers-reduced-motion: reduce) {
  .gring .arc { transition-duration: 1ms; }
  .gstep.is-active .gstep-mark { animation-duration: 3s; }
}

.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; }

/* ---------------------------------------------------------- score report */

/* The page a candidate came for. It is a record first and a dashboard second:
   every number on it is one the grader actually produced, and nothing is
   plotted twice in two shapes. Shared by the lab runner and the quiz runner so
   a result page is a result page whichever paper you sat. */

main.page.report { max-inline-size: 1240px; }

.rep { display: flex; flex-direction: column; gap: var(--s5); }

/* ---- the masthead: which paper this is a report on ---- */

.rep-head { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.rep-head h1 {
  margin: 0;
  font-size: clamp(19px, 2.4vw, 24px);
  line-height: 1.25;
}
.rep-head .toolmark { flex: 0 0 auto; }

/* ---- the hero: ring, verdict, what to do next ---- */

.rep-hero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s6);
  align-items: center;
}
.rep-hero-body { min-inline-size: 0; }
.rep-verdict { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.rep-hero h2 {
  margin: var(--s3) 0 var(--s2);
  font-size: clamp(22px, 3vw, 30px);
  line-height: 1.15;
}
.rep-scored { margin: 0 0 var(--s2); font-size: 15px; }
.rep-lede {
  margin: 0;
  color: var(--ink-muted);
  font-size: 14px;
  line-height: 1.6;
  max-inline-size: 62ch;
}
.rep-acts { display: flex; flex-wrap: wrap; gap: var(--s3); margin-block-start: var(--s5); }

/* The score ring. Determinate and flat-filled: this one really is a fraction
   of a known whole, so it gets no gradient and no glow — the colour is the
   verdict and nothing else on the page may borrow it. */
.rep-ring { position: relative; inline-size: 168px; block-size: 168px; flex: 0 0 auto; }
.rep-ring svg { inline-size: 100%; block-size: 100%; transform: rotate(-90deg); }
.rep-ring .track { fill: none; stroke: var(--edge); stroke-width: 10; }
.rep-ring .arc {
  fill: none;
  stroke: var(--ink-muted);
  stroke-width: 10;
  stroke-linecap: round;
  /* 2πr for r = 64. */
  stroke-dasharray: 402.12;
  stroke-dashoffset: 402.12;
  transition: stroke-dashoffset 900ms cubic-bezier(.25, .8, .3, 1);
}
.rep-ring.pass .arc { stroke: var(--pass); }
.rep-ring.fail .arc { stroke: var(--fail); }
.rep-ring-mid {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
}
.rep-ring-num {
  font: 600 40px/1 var(--ui);
  font-variant-numeric: tabular-nums;
  direction: ltr;
}
.rep-ring-lbl { font-size: 11.5px; color: var(--ink-muted); text-align: center; }

/* The pass mark, drawn on the ring as a tick so the score is read against it
   rather than against nothing. A number in a circle is not a verdict until you
   can see where the line is. */
.rep-ring .mark { stroke: var(--ink-muted); stroke-width: 2; opacity: 0.7; }

/* ---- the stat strip ---- */

.rep-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: var(--s4);
}
.rep-stat { display: flex; align-items: center; gap: var(--s3); padding: var(--s4); }
/* Unlike .stat-ico these tiles are tinted rather than filled, and two of them
   are keyed to a STATUS (passed, failed) rather than to a position. That is the
   deliberate exception to the decorative-tile rule: on this page green and red
   already mean something, and a passed-count in a purple box would be the only
   place they did not. */
.rep-stat-ico {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  inline-size: 38px;
  block-size: 38px;
  border-radius: var(--r);
  background: color-mix(in srgb, var(--ink-muted) 16%, transparent);
  color: var(--ink-muted);
}
.rep-stat-ico svg { inline-size: 18px; block-size: 18px; }
.rep-stat-ico.pass { background: color-mix(in srgb, var(--pass) 16%, transparent); color: var(--pass); }
.rep-stat-ico.fail { background: color-mix(in srgb, var(--fail) 16%, transparent); color: var(--fail); }
.rep-stat-txt { min-inline-size: 0; }
.rep-stat-v {
  font: 600 21px/1.15 var(--ui);
  font-variant-numeric: tabular-nums;
  direction: ltr;
  unicode-bidi: isolate;
}
.rep-stat-l { font-size: 12.5px; color: var(--ink-muted); margin-block-start: 2px; }
/* The denominator, where there is one — "of 120 min", "pass mark 66%". A bare
   number is a fact; a number against its budget is a result. */
.rep-stat-note { font-size: 11px; color: var(--ink-muted); margin-block-start: 3px; }
.rep-stat-txt > span { display: block; }

/* ---- domain breakdown ---- */

.rep-dom { display: flex; flex-direction: column; }
.rep-dom-row {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(90px, 2fr) 52px;
  gap: var(--s4);
  align-items: center;
  padding-block: var(--s3);
}
.rep-dom-row + .rep-dom-row { border-block-start: 1px solid var(--edge); }
.rep-dom-name { font-size: 13.5px; min-inline-size: 0; }
.rep-dom-sub {
  display: block;
  font-size: 11.5px;
  color: var(--ink-muted);
  margin-block-start: 2px;
  font-variant-numeric: tabular-nums;
}
.rep-bar {
  block-size: 8px;
  border-radius: 999px;
  background: var(--edge);
  overflow: hidden;
}
.rep-bar > i { display: block; block-size: 100%; background: var(--fail); border-radius: 999px; }
.rep-bar.ok > i { background: var(--pass); }
.rep-dom-n {
  text-align: end;
  font: 600 13px var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--fail);
  direction: ltr;
  unicode-bidi: isolate;
}
.rep-dom-row.ok .rep-dom-n { color: var(--pass); }

/* ---- insights ---- */

.rep-ins { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: var(--s4); }
.rep-ins-head { display: flex; align-items: center; gap: var(--s3); margin-block-end: var(--s4); }
.rep-ins-head h3 { margin: 0; font-size: 14.5px; }
.rep-ins-ico {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  inline-size: 30px;
  block-size: 30px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}
.rep-ins-ico svg { inline-size: 16px; block-size: 16px; }
.rep-ins-ico.warn { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn); }
.rep-ins-ico.pass { background: color-mix(in srgb, var(--pass) 16%, transparent); color: var(--pass); }
.rep-ins ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s3); }
.rep-ins li {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  gap: var(--s3);
  align-items: start;
  font-size: 13.5px;
  line-height: 1.55;
}
.rep-ins li .dot {
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  margin: 7px 5px;
  background: var(--ink-muted);
}
.rep-ins li.good .dot { background: var(--pass); }
.rep-ins li.bad .dot { background: var(--fail); }
/* The gap between the domain and its score is geometric, not an inline margin:
   the name is Latin inside an Arabic line, so bidi reorders the two spans and
   an inline-start margin lands on whichever side is not between them. */
.rep-ins li > span:last-child {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 8px;
}
.rep-ins li .n {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  direction: ltr;
  unicode-bidi: isolate;
}
.rep-ins .quiet-note { color: var(--ink-muted); font-size: 13.5px; margin: 0; line-height: 1.55; }

/* ---- the review table and its filters ---- */

.rep-tools {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-block-end: var(--s4);
}
.rep-tools .tabs.inline { flex: 1 1 auto; }
.rep-find { position: relative; flex: 0 1 260px; min-inline-size: 180px; }
.rep-find input { inline-size: 100%; padding-inline-start: 32px; }
.rep-find svg {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 10px;
  inline-size: 14px;
  block-size: 14px;
  margin-block-start: -7px;
  color: var(--ink-muted);
  pointer-events: none;
}

/* Six columns will not fit a phone. The table scrolls inside its own frame
   rather than pushing the page sideways — and .tbl-wrap CLIPS by default, which
   would quietly amputate the status column instead of letting it be reached. */
.rep-tbl-wrap { overflow-x: auto; }

.rep-tbl td.t { font-size: 13.5px; }
.rep-tbl .idx {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  inline-size: 1%;
  white-space: nowrap;
}
.rep-tbl .dom { color: var(--ink-muted); font-size: 12.5px; }
.rep-tbl .act { inline-size: 1%; white-space: nowrap; text-align: end; }
.rep-tbl tr.detail > td { padding: 0; }
.rep-tbl tr.detail .box {
  padding: var(--s3) var(--s4) var(--s4);
  border-block-end: 1px solid var(--edge);
}
.rep-tbl .cap {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-block-end: 6px;
}
.rep-tbl pre {
  margin: 0;
  padding: 10px 12px;
  background: var(--void);
  border: 1px solid var(--void-edge);
  border-radius: var(--r-sm);
  font-size: 12px;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--void-warn);
  direction: ltr;
  text-align: left;
  max-block-size: 260px;
  overflow: auto;
}

/* ---- the empty state, which is a screen and not a sentence ---- */

.rep-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--s3);
  padding: var(--s6) var(--s4);
}
.rep-empty-ico {
  display: grid;
  place-items: center;
  inline-size: 48px;
  block-size: 48px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink-muted) 14%, transparent);
  color: var(--ink-muted);
}
.rep-empty-ico svg { inline-size: 22px; block-size: 22px; }
.rep-empty h4 { margin: 0; font-size: 15px; }
.rep-empty p { margin: 0; color: var(--ink-muted); font-size: 13.5px; max-inline-size: 44ch; line-height: 1.55; }

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

.rep-foot {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  padding-block-start: var(--s5);
  border-block-start: 1px solid var(--edge);
}
.rep-foot .spacer { flex: 1 1 auto; }
.rep-foot .muted { font-size: 12.5px; }

@media (max-width: 720px) {
  .rep-hero { grid-template-columns: minmax(0, 1fr); gap: var(--s5); justify-items: start; }
  .rep-ring { inline-size: 132px; block-size: 132px; }
  .rep-ring-num { font-size: 32px; }
  .rep-dom-row { grid-template-columns: minmax(0, 1fr) 52px; row-gap: var(--s2); }
  .rep-dom-row .rep-bar { grid-column: 1 / -1; }
  .rep-find { flex: 1 1 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .rep-ring .arc { transition-duration: 1ms; }
}

/* A lab row is title, difficulty and a button. On a phone that is three things
   competing for 340px, and the title loses — it wrapped to four lines with the
   badge sitting in the middle of it. Below this width the row becomes two: what
   the lab is, then what you can do about it. */
@media (max-width: 560px) {
  .cert-lab { flex-wrap: wrap; row-gap: var(--s2); }
  .cert-lab .t { flex: 1 1 calc(100% - 36px); }
  .cert-lab .badges { margin-inline-start: 36px; }
  .cert-lab .btn { margin-inline-start: auto; }
}

/* --------------------------------------------------- certification catalogue */

/* Two surfaces — the catalogue of credentials, and the page for one of them —
   laid out to the design mock: dense cards, five to a row, groups that share a
   row when they are small, and a certification page of stacked panels.

   Built from the site's own tokens throughout. Gradients are mixed FROM
   --accent rather than reaching for a fixed second hue, so a tenant recolour
   survives the first thing anyone sees. */

/* ---- section nav in the top bar ---- */

.cert-nav { display: flex; align-items: center; gap: var(--s5); margin-inline-start: var(--s5); }
.cert-nav a {
  position: relative;
  padding-block: 6px;
  font-size: 13.5px;
  color: var(--ink-muted);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--ease);
}
.cert-nav a:hover { color: var(--ink); }
.cert-nav a.on { color: var(--ink); font-weight: 500; }
.cert-nav a.on::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: -11px;
  block-size: 2px;
  border-radius: 2px;
  background: var(--accent);
}
@media (max-width: 860px) { .cert-nav { display: none; } }
/* Two navigations for the same three destinations is one too many. Above the
   width where the bar's links fit, the tab strip stands down; below it, the
   strip is the only way through and the links are the ones that go. */
@media (min-width: 861px) { #home-tabs { display: none; } }

/* ---- the hero ---- */

.cert-hero {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s5);
  align-items: center;
  padding: var(--s6);
  /* The band the design calls for. A min rather than a fixed height so a
     wrapped headline or a long translation grows the panel instead of
     spilling out of it. */
  min-block-size: 300px;
  margin-block-end: var(--s6);
  border: 1px solid var(--edge);
  border-radius: var(--r-lg);
  overflow: hidden;
  background:
    radial-gradient(130% 150% at 100% 0%,
                    color-mix(in srgb, var(--accent) 16%, transparent) 0%,
                    transparent 60%),
    linear-gradient(120deg,
                    color-mix(in srgb, var(--accent) 6%, var(--raised)) 0%,
                    var(--raised) 55%);
  box-shadow: var(--shadow);
}
.cert-hero-eyebrow {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--accent);
  margin-block-end: var(--s2);
}
.cert-hero h1 {
  margin: 0 0 var(--s2);
  font-size: clamp(22px, 3.2vw, 32px);
  line-height: 1.15;
  max-inline-size: 20ch;
}
.cert-hero p {
  margin: 0;
  color: var(--ink-muted);
  font-size: 14px;
  line-height: 1.6;
  max-inline-size: 54ch;
}
.cert-hero-side { display: flex; flex-direction: column; align-items: center; gap: var(--s3); }
/* Mark on a ridge line. The scene is sized in px rather than by its contents so
   the peaks always meet the badge at the same point. */
.cert-hero-scene {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: 240px;
  block-size: 154px;
}
/* Openly decorative, and the one thing on the page that is. Three ridges of the
   same accent at rising opacity — no second hue to strand when a tenant
   recolours, and no gradient stop that has to be kept in step with one. */
.cert-hero-peaks {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  inline-size: 100%;
  block-size: 88px;
  /* Dissolved at both ends rather than cut off. Without this the ridges stop on
     a hard vertical edge and the whole thing reads as a pasted-in rectangle. */
  mask-image: linear-gradient(to right, transparent 0%, #000 32%, #000 68%, transparent 100%);
}
.cert-hero-peaks .p3 { fill: color-mix(in srgb, var(--accent) 11%, transparent); }
.cert-hero-peaks .p2 { fill: color-mix(in srgb, var(--accent) 18%, transparent); }
.cert-hero-peaks .p1 { fill: color-mix(in srgb, var(--accent) 30%, transparent); }
/* The project's own mark, on a glow of the brand accent. A real logo rather
   than an illustration: it is the one graphic here that is a fact about the
   subject, and toolmarks.js keeps it the colour the project actually uses. */
.cert-hero-art {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: 96px;
  block-size: 96px;
  margin-block-end: 26px;
  border-radius: 22px;
  background:
    radial-gradient(70% 70% at 50% 40%,
                    color-mix(in srgb, var(--accent) 26%, transparent) 0%,
                    color-mix(in srgb, var(--accent) 7%, transparent) 70%),
    color-mix(in srgb, var(--raised) 82%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 26%, transparent);
}
.cert-hero-quote {
  text-align: center;
  font-size: 12.5px;
  font-style: italic;
  line-height: 1.5;
  color: var(--ink-muted);
}
.cert-hero-quote b { display: block; color: var(--ink); font-weight: 500; }

/* The one animated line in the hero — a promise, not a pull-quote, so it gets
   its own weight: a big, bordered banner with an icon and a light sweep, not
   italics. Big on purpose — this is the line the whole hero is meant to leave
   a visitor with, so it reads as a headline claim, not a footnote beside one.
   Every animation here (entrance, icon pulse, the sweep) lives under
   prefers-reduced-motion: no-preference; with motion off this is a plain
   banner with the same content, which is the whole point of it — the motion
   adds attention, not information. */
.cert-hero-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 14px;
  margin: var(--s5) 0 0;
  padding-block: 16px;
  padding-inline: 18px 28px;
  border-radius: var(--r-lg);
  border: 1px solid color-mix(in srgb, var(--accent) 40%, var(--edge));
  background:
    linear-gradient(120deg,
                    color-mix(in srgb, var(--accent) 16%, transparent) 0%,
                    color-mix(in srgb, var(--accent) 7%, transparent) 100%),
    color-mix(in srgb, var(--raised) 70%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 10%, transparent),
              0 10px 28px -14px color-mix(in srgb, var(--accent) 55%, transparent);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.3;
  color: var(--ink);
  /* Clips the light-sweep pseudo-element to the banner's own rounded corners. */
  overflow: hidden;
}
.cert-hero-badge-ico {
  position: relative;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  inline-size: 34px;
  block-size: 34px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  color: var(--accent);
}
.cert-hero-badge-ico svg { inline-size: 19px; block-size: 19px; }
/* The sweep: a soft diagonal band of light crossing the banner on a slow
   loop — the "premium SaaS badge" tell. A pseudo-element rather than a second
   real one so it never competes with the text for a screen reader or a tab
   stop; aria-hidden is not even needed because it carries no content at all. */
.cert-hero-badge::before {
  content: '';
  position: absolute;
  inset-block: 0;
  inline-size: 45%;
  inset-inline-start: -55%;
  background: linear-gradient(100deg,
                              transparent,
                              color-mix(in srgb, var(--accent) 22%, transparent) 45%,
                              color-mix(in srgb, var(--accent) 22%, transparent) 55%,
                              transparent);
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  .cert-hero-badge {
    animation: cert-badge-in 600ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-delay: 150ms;
  }
  .cert-hero-badge-ico::after {
    content: '';
    position: absolute;
    inset: -5px;
    border-radius: 50%;
    border: 1.5px solid color-mix(in srgb, var(--accent) 55%, transparent);
    animation: cert-badge-ring 2.2s ease-out infinite;
  }
  .cert-hero-badge::before { animation: cert-badge-sweep 3.6s ease-in-out infinite; }
  @keyframes cert-badge-in {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
  }
  @keyframes cert-badge-ring {
    from { opacity: .55; transform: scale(0.85); }
    to { opacity: 0; transform: scale(1.35); }
  }
  @keyframes cert-badge-sweep {
    0%, 15% { inset-inline-start: -55%; }
    65%, 100% { inset-inline-start: 130%; }
  }
}

/* ---- category filter ---- */

.cert-tools {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-block-end: var(--s5);
  border-block-end: 1px solid var(--edge);
}
.cert-tools .tabs.inline { flex: 1 1 auto; border: 0; gap: var(--s4); }
.cert-tools .tabs.inline button { padding-block: 10px; font-size: 13.5px; }
.cert-find { position: relative; flex: 0 1 250px; min-inline-size: 180px; margin-block-end: 6px; }
/* Two boxes, one query, and only ever one of them on screen. They hand over at
   the width the bar's own nav does, so the search never disappears with the
   header links and never doubles up with them either. */
.bar-find {
  flex: 0 1 220px;
  min-inline-size: 140px;
  margin-block-end: 0;
  margin-inline-start: var(--s5);
}
@media (max-width: 860px) { .bar-find { display: none; } }
@media (min-width: 861px) { .tools-find { display: none; } }
.cert-find input { inline-size: 100%; padding-inline-start: 32px; block-size: 32px; font-size: 13px; }
.cert-find svg {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 10px;
  inline-size: 14px;
  block-size: 14px;
  margin-block-start: -7px;
  color: var(--ink-muted);
  pointer-events: none;
}

/* ---- groups ---- */

/* Five columns, and a group spans as many as it has cards. That is what puts
   GitOps and Service Mesh side by side in the mock instead of giving a
   two-card group a row of its own with three columns of nothing in it. Below
   the desktop breakpoint every group goes full width and the cards reflow. */
.cert-groups {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  /* Rows far enough apart that the four small groups sharing the second row
     read as their own band rather than as more of the Kubernetes one. */
  gap: var(--s7) var(--s4);
}
.cert-group {
  grid-column: span var(--span, 5);
  min-inline-size: 0;
  /* head, then the cards filling whatever height the row settles on, so a
     one-card group lines up with the four beside it. */
  display: grid;
  grid-template-rows: auto 1fr;
}
.cert-group-head {
  display: flex;
  /* baseline, not flex-start: the "View all" sits on the h2's baseline rather
     than floating at the top of a two-line eyebrow+title block. */
  align-items: baseline;
  gap: var(--s3);
  margin-block-end: var(--s4);
  /* Eyebrow + two lines of title, so a heading that wraps in a narrow column
     does not push its cards below the ones beside it. */
  min-block-size: 64px;
}
.cert-group-title { min-inline-size: 0; }
/* The eyebrow names the kind of group; small, wide-tracked and quiet on
   purpose, so it labels the title without competing with it. */
.cert-group-eyebrow {
  display: block;
  margin-block-end: 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-muted);
}
/* The section title is the page's scanning anchor: it has to win against a
   grid of cards directly under it, which is why this is a step up in size AND
   weight rather than one or the other. Tight tracking is what keeps a heavy
   28-32px line from reading as shouty — the size carries the emphasis and the
   tracking pulls it back into something that still looks composed.
   clamp() so the same rule serves the narrow layout without a second block. */
.cert-group-head h2 {
  margin: 0;
  font-size: clamp(21px, 2.2vw, 28px);
  font-weight: 800;
  letter-spacing: -0.022em;
  line-height: 1.15;
  /* --ink over the h2's inherited colour: full-strength text, since the whole
     point is that this stops reading as secondary. */
  color: var(--ink);
  text-wrap: balance;
}
.cert-group-head .spacer { flex: 1 1 auto; }
.cert-view-all {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 14px;
  font-weight: 600;
  color: var(--accent);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  white-space: nowrap;
  /* Nudged off the baseline the flex row sets, so it optically centres against
     the title's cap height rather than sitting low beside a much larger word. */
  align-self: center;
  transition: gap 120ms ease, opacity 120ms ease;
}
.cert-view-all:hover { opacity: 0.82; gap: 9px; }
.cert-view-all svg { inline-size: 13px; block-size: 13px; }
.cert-grid { display: grid; grid-template-columns: repeat(var(--span, 5), minmax(0, 1fr)); gap: var(--s4); }

@media (max-width: 1180px) {
  .cert-groups { grid-template-columns: minmax(0, 1fr); }
  .cert-group { grid-column: 1 / -1; }
  .cert-grid { grid-template-columns: repeat(auto-fill, minmax(196px, 1fr)); }
}

/* ---- the certification card ---- */

.cert-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px;
  border-radius: var(--r);
  cursor: pointer;
  transition: border-color var(--ease), box-shadow var(--ease);
}
.cert-card:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--edge));
  box-shadow: var(--shadow), 0 0 0 3px color-mix(in srgb, var(--accent) 9%, transparent);
}
.cert-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.cert-card-top { display: flex; align-items: flex-start; gap: var(--s3); }
.cert-card-top .spacer { flex: 1 1 auto; }
.cert-pill {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--edge);
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  font-size: 10.5px;
  color: var(--ink-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.cert-pill svg { inline-size: 11px; block-size: 11px; flex: 0 0 auto; }
.cert-pill.new {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  color: var(--accent);
}
/* Your own result. The only place on the catalogue where the pass palette
   appears, because it is the only thing here that has passed anything. A score
   short of the mark stays neutral: the card is not the place to be told again
   that you failed. */
.cert-pill.pass {
  border-color: color-mix(in srgb, var(--pass) 42%, transparent);
  background: color-mix(in srgb, var(--pass) 13%, transparent);
  color: var(--pass);
}
.cert-pill.part { color: var(--ink); }
.cert-pill.part > span { font-variant-numeric: tabular-nums; direction: ltr; unicode-bidi: isolate; }
.cert-card h3 { margin: 0; font-size: 15px; line-height: 1.2; }
.cert-card-sub {
  margin-block-start: 3px;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--ink-muted);
  /* Two lines. One was the design's instinct and it is right for a short
     tagline, but these are credential names — clamped to one, "Certified
     Kubernetes Security Specialist" becomes "Certified Kubernetes…", which
     tells a reader strictly less than the code already above it. Two lines
     hold every name we have, and the card is uniform either way now that the
     body is a fixed three rows. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.cert-meta { display: flex; flex-direction: column; gap: 3px; margin-block-start: auto; }
.cert-meta-row {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: 11.5px;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.cert-meta-row svg { inline-size: 13px; block-size: 13px; flex: 0 0 auto; opacity: 0.8; }
.cert-foot {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;
  padding-block-start: 8px;
  border-block-start: 1px solid var(--edge);
}
.cert-foot .badge { font-size: 10.5px; padding: 2px 7px; white-space: nowrap; }
.cert-foot .spacer { flex: 1 1 auto; }
/* The level, as an outlined chip keyed to how hard the credential is. Not the
   pass/fail palette: nobody has passed or failed anything on this page. */
.cert-level {
  padding: 2px 7px;
  font-size: 10.5px;
  border-radius: var(--r-sm);
  border: 1px solid color-mix(in srgb, var(--tone) 45%, transparent);
  color: var(--tone);
  white-space: nowrap;
}
.cert-level.beginner { --tone: var(--pass); }
.cert-level.intermediate { --tone: var(--accent); }
.cert-level.advanced { --tone: var(--warn); }
.cert-arrow {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  inline-size: 26px;
  block-size: 26px;
  border-radius: 50%;
  border: 1px solid var(--edge);
  background: none;
  color: var(--ink-muted);
  cursor: pointer;
  transition: border-color var(--ease), color var(--ease), background var(--ease);
}
.cert-card:hover .cert-arrow {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.cert-arrow svg { inline-size: 13px; block-size: 13px; }
/* Hidden by default — a 26px circle has no room for a word, and the whole
   card is already the click target, so the icon alone is enough anywhere the
   card itself is compact. The wide desktop layout below turns this back on
   once the button has room to be a real "Start learning" pill. */
.cert-arrow-label { display: none; }

/* ---------------------------------------------------- wide desktop catalogue */

/* The catalogue is the one .page surface meant to fill a big monitor rather
   than read like a form. A dashboard table or a settings page gets MORE
   readable at 1080px — a paragraph that wide is already past comfortable line
   length — but a grid of certification tiles is the opposite: at 1080px on a
   1920px monitor it is a narrow column with 800px of dead margin either side,
   which is the exact complaint this responds to. Its own modifier rather than
   raising .page's cap for everyone, because that dashboard and that settings
   page are real and did not ask to get wider.

   inline-size as a percentage (not a fixed width) is what keeps this at
   "90-95% of the viewport" on an ordinary desktop; max-inline-size is what
   stops it going edge-to-edge and unreadable on a 3440px ultrawide. Gated
   behind a breakpoint rather than applying everywhere: below it the existing
   .page rule already does the right thing (fill the viewport minus its own
   padding), and layering a percentage width UNDER that breakpoint would just
   add a second, redundant margin on top of the phone and tablet tuning the
   rest of this file already does. */
@media (min-width: 1201px) {
  main.page.cert-catalogue-main {
    inline-size: 94%;
    max-inline-size: 1720px;
  }

  /* ---- hero: premium and dominant, not just wider ---- */
  .cert-catalogue-main .cert-hero {
    min-block-size: 392px;
    padding: var(--s7);
    gap: var(--s7);
  }
  .cert-catalogue-main .cert-hero h1 {
    font-size: clamp(32px, 2.6vw, 42px);
    max-inline-size: 22ch;
  }
  .cert-catalogue-main .cert-hero p { font-size: 15.5px; max-inline-size: 58ch; }
  .cert-catalogue-main .cert-hero-eyebrow { font-size: 13.5px; }
  .cert-catalogue-main .cert-hero-scene { inline-size: 300px; block-size: 192px; }
  .cert-catalogue-main .cert-hero-peaks { block-size: 110px; }
  .cert-catalogue-main .cert-hero-art { inline-size: 118px; block-size: 118px; margin-block-end: 30px; }
  .cert-catalogue-main .cert-hero-quote { font-size: 13.5px; }

  /* ---- section headers: five categories that read as five destinations ---- */
  /* Row gap carries most of the separation between bands: at this size the
     titles are heavy enough that without more air above them each one reads as
     a caption on the cards above rather than a header for the cards below. */
  .cert-catalogue-main .cert-groups { gap: var(--s8) var(--s5); }
  .cert-catalogue-main .cert-group-head {
    /* Eyebrow plus two lines of 32px title. Every group in a row reserves the
       same head height, so a two-line title and a one-line title still drop
       their cards onto the same line. */
    min-block-size: 100px;
    margin-block-end: var(--s5);
  }
  .cert-catalogue-main .cert-group-head h2 {
    font-size: 32px;
    font-weight: 800;
    letter-spacing: -0.028em;
    line-height: 1.12;
  }
  .cert-catalogue-main .cert-group-eyebrow {
    font-size: 11.5px;
    margin-block-end: 8px;
  }
  .cert-catalogue-main .cert-view-all { font-size: 15px; }

  /* ---- the card itself: fewer, larger, easier to scan at a glance.
     grid-template-columns keeps repeat(var(--span,5), 1fr) inside .cert-grid,
     so a card's width is always container/5 regardless of how many cards its
     own group has — see the comment on .cert-groups above it. Widening the
     container is therefore already most of "20-30% larger"; what is added
     here is the internal scale to match: bigger padding, a bigger title, more
     air between the three rows a candidate is here to compare. ---- */
  .cert-catalogue-main .cert-card {
    gap: 14px;
    padding: 22px;
    border-radius: var(--r-lg);
  }
  .cert-catalogue-main .cert-card-top { gap: var(--s3); }
  .cert-catalogue-main .cert-card h3 { font-size: 21px; }
  .cert-catalogue-main .cert-card-sub { font-size: 13px; margin-block-start: 5px; }
  .cert-catalogue-main .cert-pill { font-size: 11.5px; padding: 3px 10px; }
  .cert-catalogue-main .cert-meta { gap: 7px; margin-block-start: var(--s3); }
  .cert-catalogue-main .cert-meta-row { font-size: 13px; gap: 9px; }
  .cert-catalogue-main .cert-meta-row svg { inline-size: 15px; block-size: 15px; }
  .cert-catalogue-main .cert-foot { padding-block-start: 14px; gap: var(--s3); }
  .cert-catalogue-main .cert-level { font-size: 11.5px; padding: 3px 9px; }

  /* The pill "Start learning" button the requirement asks for, in place of
     the icon-only circle every narrower layout keeps. Still tabIndex=-1 and
     aria-hidden — see the comment beside it in sahablabs-react.js. */
  .cert-catalogue-main .cert-arrow {
    /* The base rule is a single-icon grid; a label and an icon side by side
       needs a row, not a stack. */
    display: inline-flex;
    align-items: center;
    inline-size: auto;
    block-size: auto;
    border-radius: 999px;
    padding: 8px 16px;
    gap: 6px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 32%, var(--edge));
    background: color-mix(in srgb, var(--accent) 9%, transparent);
  }
  .cert-catalogue-main .cert-card:hover .cert-arrow {
    background: color-mix(in srgb, var(--accent) 16%, transparent);
  }
  .cert-catalogue-main .cert-arrow-label { display: inline; }
}

/* Past 1600px there is room to let the hero's own share of the row breathe a
   little further and cap the card grid at its stated maximum. */
@media (min-width: 1601px) {
  main.page.cert-catalogue-main { max-inline-size: 1760px; }
}


/* ------------------------------------------------------- certification page */

.cert-page { display: flex; flex-direction: column; gap: var(--s5); }

.cert-crumbs { display: flex; align-items: center; gap: var(--s2); font-size: 12.5px; flex-wrap: wrap; }
.cert-crumbs a, .cert-crumbs button {
  color: var(--ink-muted); background: none; border: 0; padding: 0;
  font: inherit; cursor: pointer;
}
.cert-crumbs a:hover, .cert-crumbs button:hover { color: var(--ink); }
.cert-crumbs .sep { color: var(--ink-muted); opacity: 0.55; }
.cert-crumbs .here { color: var(--ink); }

.cert-head-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--s4);
  align-items: center;
  padding: var(--s5);
}
.cert-head-art {
  display: grid;
  place-items: center;
  inline-size: 58px;
  block-size: 58px;
  border-radius: var(--r);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
}
.cert-head-body { min-inline-size: 0; }
.cert-head-body h1 { margin: 0 0 5px; font-size: clamp(17px, 2.2vw, 21px); line-height: 1.2; }
.cert-head-body p { margin: 0 0 var(--s3); color: var(--ink-muted); font-size: 13px; line-height: 1.5; max-inline-size: 70ch; }
.cert-head-badges { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.cert-fact {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: var(--r-sm);
  border: 1px solid var(--edge);
  font-size: 11.5px;
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.cert-fact svg { inline-size: 12px; block-size: 12px; opacity: 0.85; }

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

.cert-tabs { display: flex; gap: var(--s5); flex-wrap: wrap; border-block-end: 1px solid var(--edge); }
.cert-tabs button {
  position: relative;
  padding: 0 0 10px;
  border: 0;
  background: none;
  color: var(--ink-muted);
  font: 400 13.5px var(--ui);
  cursor: pointer;
  white-space: nowrap;
}
.cert-tabs button:hover { color: var(--ink); }
.cert-tabs button.on { color: var(--ink); font-weight: 500; }
.cert-tabs button.on::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: -1px;
  block-size: 2px;
  border-radius: 2px;
  background: var(--accent);
}

/* ---- progress + about ---- */

.cert-cols { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--s5); }
.cert-cols > .card { display: flex; flex-direction: column; padding: var(--s5); }
.cert-panel-title { margin: 0 0 var(--s4); font-size: 14.5px; }

/* Grows to fill the card so the slack sits evenly above and below the ring
   rather than pooling under it: this card is always shorter than the one beside
   it, and centred slack reads as spacing where trailing slack reads as a bug. */
.cert-prog-body {
  display: flex;
  align-items: center;
  gap: var(--s5);
  flex-wrap: wrap;
  flex: 1 1 auto;
}
/* The score report's ring, at catalogue size. */
.cert-ring { position: relative; inline-size: 132px; block-size: 132px; flex: 0 0 auto; }
.cert-ring svg { inline-size: 100%; block-size: 100%; transform: rotate(-90deg); }
.cert-ring .track { fill: none; stroke: var(--edge); stroke-width: 11; }
.cert-ring .arc {
  fill: none;
  stroke: var(--accent);
  stroke-width: 11;
  stroke-linecap: round;
  /* 2πr for r = 58. */
  stroke-dasharray: 364.42;
  transition: stroke-dashoffset 900ms cubic-bezier(.25, .8, .3, 1);
}
.cert-ring-mid {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 1px;
}
.cert-ring-num { font: 600 27px/1 var(--ui); font-variant-numeric: tabular-nums; direction: ltr; }
.cert-ring-lbl { font-size: 10.5px; color: var(--ink-muted); }

.cert-rows { display: flex; flex-direction: column; gap: var(--s4); flex: 1 1 190px; min-inline-size: 0; }
.cert-row { display: flex; align-items: center; gap: var(--s3); font-size: 13px; }
.cert-row-ico { flex: 0 0 auto; display: grid; place-items: center; inline-size: 18px; block-size: 18px; color: var(--ink-muted); }
.cert-row-ico svg { inline-size: 16px; block-size: 16px; }
.cert-row .l { min-inline-size: 0; color: var(--ink-muted); }
.cert-row .spacer { flex: 1 1 auto; }
.cert-row .v {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  direction: ltr;
  unicode-bidi: isolate;
  white-space: nowrap;
}

.cert-about-text { margin: 0 0 var(--s4); color: var(--ink-muted); font-size: 13px; line-height: 1.6; }
.cert-chips { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--s3); margin-block-start: auto; }
.cert-chip {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 9px 11px;
  border-radius: var(--r-sm);
  border: 1px solid var(--edge);
  background: color-mix(in srgb, var(--ink) 4%, transparent);
  font-size: 11.5px;
  line-height: 1.35;
}
.cert-chip svg { inline-size: 15px; block-size: 15px; flex: 0 0 auto; color: var(--accent); }

/* ---- mock exams ---- */

.cert-sec-head { display: flex; align-items: baseline; gap: var(--s3); }
.cert-sec-head h2 { margin: 0; font-size: 15px; }
.cert-sec-head .spacer { flex: 1 1 auto; }
.cert-sec-why { margin: 5px 0 var(--s4); color: var(--ink-muted); font-size: 12.5px; }

/* auto-fit, not auto-fill: with one paper on the shelf, auto-fill left three
   empty tracks and the card sat in a wide band of nothing. The card is asked to
   fill whatever track it gets, and changes shape below to suit it. */
.cert-exams { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: var(--s4); }
.cert-exam-cell { container-type: inline-size; display: grid; }
.cert-exam { display: flex; flex-direction: column; gap: var(--s3); padding: var(--s4); border-radius: var(--r); }
/* Given a wide track, the same card lies down: number and title on the left,
   the three facts in a row, the button on the right. A container query and not
   a media query, because what decides this is how much room this card got —
   five papers in five columns are narrow on the very screen where one paper is
   wide. */
@container (min-width: 430px) {
  .cert-exam {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: center;
    column-gap: var(--s5);
    row-gap: 3px;
    padding-inline: var(--s5);
  }
  .cert-exam-top { grid-column: 1; grid-row: 1 / span 2; }
  /* The spacer and the paper glyph both existed to balance the tall card's
     top row. Lying down there is no row left to balance, and the number
     badge alone reads better. (The glyph is wrapped in a span by Svg, so
     this matches the wrapper, not the svg.) */
  .cert-exam-top > .spacer, .cert-exam-top > .spacer ~ span { display: none; }
  .cert-exam h3 { grid-column: 2; grid-row: 1; font-size: 14px; }
  .cert-exam-state { grid-column: 2; grid-row: 2; }
  .cert-exam-facts { grid-column: 3; grid-row: 1 / span 2; flex-direction: row; gap: var(--s4); }
  .cert-exam .btn { grid-column: 4; grid-row: 1 / span 2; inline-size: auto; }
}
/* One more step down: past this the facts would crush the title, so they wrap
   under it and only the button stays on the right. */
@container (max-width: 640px) {
  .cert-exam-facts { flex-wrap: wrap; }
}
.cert-exam-top { display: flex; align-items: center; gap: var(--s2); }
.cert-exam-n {
  display: grid;
  place-items: center;
  inline-size: 22px;
  block-size: 22px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
  font: 600 11px/1 var(--mono);
}
.cert-exam-top .spacer { flex: 1 1 auto; }
.cert-exam-top svg { inline-size: 14px; block-size: 14px; color: var(--ink-muted); opacity: 0.7; }
.cert-exam h3 { margin: 0; font-size: 13px; line-height: 1.3; }
.cert-exam-facts { display: flex; flex-direction: column; gap: 5px; }
.cert-exam .btn { inline-size: 100%; justify-content: center; }
.cert-exam-state { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--ink-muted); }
.cert-exam-state .dot { inline-size: 6px; block-size: 6px; border-radius: 50%; background: var(--ink-muted); }
.cert-exam-state.done .dot { background: var(--pass); }
.cert-exam-state.done { color: var(--pass); }

/* A locked exam is dimmed rather than hidden — a candidate should see the
   whole path, including what is still ahead of them, which is the point of
   a certification "path" at all. The lock icon and the disabled button
   already say "not yet"; the card itself just needs to read as quieter. */
.cert-exam.locked { opacity: 0.55; cursor: not-allowed; }
.cert-exam.locked h3 { color: var(--ink-muted); }
.cert-exam.locked .cert-exam-top svg { color: var(--warn); opacity: 1; }
.cert-exam .btn svg { inline-size: 13px; block-size: 13px; margin-inline-end: 5px; }

/* ---- the certification roadmap: one full card per exam, in sitting order,
   joined by arrows — a game campaign's level select rather than a plain
   list, which is the whole point of gating the papers at all. ---- */

.cert-roadmap-card { padding: var(--s4) 0; margin-block: var(--s4); }
.cert-roadmap-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s3); margin-block-end: var(--s4); flex-wrap: wrap;
}
.cert-roadmap-progress { font-size: 12.5px; font-weight: 600; color: var(--ink-muted); }
.cert-roadmap-track {
  display: flex; align-items: stretch; gap: var(--s2);
  overflow-x: auto; padding-block: var(--s1) var(--s2);
}
/* The connector between two cards. Neutral until the card before it is
   passed, then it takes the pass colour — the line fills in exactly when the
   requirement it represents ("finish this, then that") has been met. */
.cert-roadmap-arrow {
  flex: 0 0 auto; align-self: center;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-muted); opacity: 0.5;
}
.cert-roadmap-arrow.done { color: var(--pass); opacity: 1; }

.cert-road-card {
  flex: 1 1 220px; min-inline-size: 220px; max-inline-size: 280px;
  display: flex; flex-direction: column; gap: var(--s2);
  padding: var(--s4); border-radius: var(--r-lg);
  background: var(--raised); border: 1px solid var(--edge);
  box-shadow: var(--shadow);
}
.cert-road-top { display: flex; align-items: center; justify-content: space-between; }
.cert-road-badge {
  inline-size: 32px; block-size: 32px; border-radius: 50%; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700;
  border: 2px solid var(--edge); background: var(--surface); color: var(--ink-muted);
}
.cert-road-badge svg { inline-size: 15px; block-size: 15px; }
.cert-road-n {
  margin: 0; font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--ink-muted);
}
.cert-road-title {
  margin: -4px 0 0; font-size: 13.5px; font-weight: 600; color: var(--ink);
  line-height: 1.3;
}
.cert-road-facts { display: flex; flex-direction: column; gap: 2px; margin-block-end: 2px; }
.cert-road-fact { font-size: 11.5px; color: var(--ink-muted); }
.cert-road-fact.strong { font-size: 13px; font-weight: 700; color: var(--pass); }
.cert-road-locked-text { margin: 0; font-size: 12px; color: var(--ink-muted); line-height: 1.4; }
.cert-road-card .btn { margin-block-start: auto; inline-size: 100%; justify-content: center; }
.cert-road-card .btn svg { inline-size: 13px; block-size: 13px; margin-inline-end: 5px; }

/* Done: a green check badge, the same tinted-background idiom the pass
   badges use elsewhere rather than a solid fill needing its own ink. */
.cert-road-card.done { border-color: color-mix(in srgb, var(--pass) 35%, var(--edge)); }
.cert-road-card.done .cert-road-badge {
  background: color-mix(in srgb, var(--pass) 20%, var(--surface));
  border-color: var(--pass); color: var(--pass);
}
/* Current: a highlighted border and a genuine glow, not just a colour swap —
   this is the one card still worth looking at, distinct from both "already
   cleared" and "not yet reachable". */
.cert-road-card.current {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent),
              0 0 22px color-mix(in srgb, var(--accent) 45%, transparent);
}
.cert-road-card.current .cert-road-badge { border-color: var(--accent); color: var(--accent); }
.cert-road-current-tag {
  font-size: 10px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent);
  padding: 3px 7px; border-radius: 999px;
}
/* Locked: reduced opacity, the same figure the locked mock-exam card uses
   below, so the two readings of "not yet" never disagree with each other. */
.cert-road-card.locked { opacity: 0.55; }
.cert-road-card.locked .cert-road-badge { color: var(--warn); }
.cert-road-card.locked .btn { cursor: not-allowed; }

/* ---- labs + domains, side by side ---- */

.cert-lower { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: var(--s5); align-items: start; }
.cert-lower > .card { padding: var(--s5); }

.cert-labs { display: flex; flex-direction: column; margin-block-start: var(--s3); }
.cert-lab { display: flex; align-items: center; gap: var(--s3); padding: 9px 0; font-size: 12.5px; }
.cert-lab + .cert-lab { border-block-start: 1px solid var(--edge); }
.cert-lab .idx {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  inline-size: 20px;
  block-size: 20px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  font: 400 10.5px var(--mono);
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}
.cert-lab .t { min-inline-size: 0; flex: 1 1 auto; }
.cert-lab .btn { flex: 0 0 auto; }

.cert-dom { display: flex; flex-direction: column; gap: var(--s3); margin-block-start: var(--s3); }
.cert-dom-row { display: grid; grid-template-columns: minmax(0, 1fr) 40px; gap: 4px var(--s3); align-items: center; }
.cert-dom-name { font-size: 12px; line-height: 1.3; }
/* The brand accent, which is what an accent is for: this bar is how much of the
   paper a domain is WORTH, a fixed property of the exam, and no per-domain
   result appears on this page for it to be confused with. The score report is
   where a domain gets a verdict, and there it is pass/fail red and green. */
.cert-bar { grid-column: 1; block-size: 6px; border-radius: 999px; background: var(--edge); overflow: hidden; }
.cert-bar > i { display: block; block-size: 100%; border-radius: 999px; background: var(--accent); }
.cert-dom-n {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  text-align: end;
  font: 600 11.5px var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
  direction: ltr;
  unicode-bidi: isolate;
}

/* ---- the closing banner ---- */

.cert-cta {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
  padding: var(--s4) var(--s5);
  border: 1px solid color-mix(in srgb, var(--accent) 26%, var(--edge));
  border-radius: var(--r);
  background:
    linear-gradient(90deg,
                    color-mix(in srgb, var(--accent) 10%, transparent) 0%,
                    transparent 55%),
    var(--raised);
}
.cert-cta-ico {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  inline-size: 36px;
  block-size: 36px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}
.cert-cta-ico svg { inline-size: 19px; block-size: 19px; }
.cert-cta-body { flex: 1 1 260px; min-inline-size: 0; }
.cert-cta-body b { display: block; font-size: 13.5px; }
.cert-cta-body span { font-size: 12.5px; color: var(--ink-muted); line-height: 1.5; }

@media (max-width: 980px) {
  .cert-cols, .cert-lower { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 760px) {
  .cert-hero { grid-template-columns: minmax(0, 1fr); padding: var(--s5); min-block-size: 0; }
  .cert-hero-side { flex-direction: row; align-items: center; gap: var(--s4); }
  .cert-hero-art { inline-size: 76px; block-size: 76px; border-radius: 18px; margin-block-end: 20px; }
  .cert-hero-quote { text-align: start; }
  .cert-head-card { grid-template-columns: auto minmax(0, 1fr); }
  .cert-head-card > .btn { grid-column: 1 / -1; justify-self: start; }
  .cert-hero-scene { inline-size: 190px; block-size: 122px; }
  .cert-hero-peaks { block-size: 68px; }
  .cert-find { flex: 1 1 100%; }
}
/* A lab row is a title, a level and a button. On a phone that is three things
   competing for 340px and the title loses, so below this width the row becomes
   two: what the lab is, then what you can do about it. */
@media (max-width: 560px) {
  .cert-lab { flex-wrap: wrap; row-gap: var(--s2); }
  .cert-lab .t { flex: 1 1 calc(100% - 32px); }
  .cert-lab .badges { margin-inline-start: 32px; }
  .cert-lab .btn { margin-inline-start: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .cert-ring .arc { transition-duration: 1ms; }
}

/* --------------------------------------------------------------- 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; }

/* The card widened from 380 to 400: three stacked full-width actions at 380
   push "Continue with GitHub" close enough to the edges to read as cramped. */
.signin { max-inline-size: 400px; }
.signin .card h1 { margin: 0; font-size: 21px; letter-spacing: -0.01em; }
.signin-lede {
  margin-block: 6px 0;
  color: var(--ink-muted);
  font-size: 13px;
  line-height: 1.5;
}

/* --- social sign-in
 *
 * These are the first actions on the page, and they are secondary-styled on
 * purpose. Filling them with --brand would put three saturated blocks in a
 * column with nothing to look at first; keeping them on the raised surface
 * lets the one primary button — the form's own — stay the loudest thing in
 * the card, which is what tells a first-time visitor the form is still real.
 */
.signin-social { margin-block-start: var(--s4); }
.social-btns { display: grid; gap: 10px; }
.btn.social {
  block-size: 44px;              /* a comfortable touch target, not 38px */
  gap: 10px;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  background: var(--raised);
  border-color: var(--edge);
}
.btn.social:hover { border-color: var(--ink-muted); }
.btn.social .pmark { inline-size: 18px; block-size: 18px; flex: none; }
/* The marks are logos, so they keep their orientation under dir="rtl" — a
   mirrored Octocat is a different drawing, not a translated one. The row
   itself still mirrors, which is the part that should. */
[dir="rtl"] .btn.social .pmark { transform: none; }

/* "or", drawn as a rule with a gap rather than a bordered box, so the eye
   reads one card rather than two panels. */
.signin-rule {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-block: var(--s4) 0;
  color: var(--ink-muted);
  font-size: 12px;
  text-transform: lowercase;
}
.signin-rule::before,
.signin-rule::after {
  content: "";
  flex: 1;
  block-size: 1px;
  background: var(--edge);
}

/* Phones: the card stops pretending to be a card and becomes the page. A
   420px-wide viewport has no room for a 24px inset AND a readable form. */
@media (max-width: 480px) {
  .signin { max-inline-size: none; }
  .signin .card { padding: var(--s4); border: 0; background: transparent; box-shadow: none; }
  .signin-why { margin-block-end: var(--s4); }
  .btn.social { block-size: 46px; }
}

/* 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; }

/* ------------------------------------------------ the exam room, light theme */

/* The light palette reads well on the catalogue and fell apart inside an exam,
 * for one reason: every control there — the task and question chips, the
 * answer options, Previous / Next / Flag — is drawn as a hairline in --edge,
 * and #E3E8EF on white is 1.2:1. In dark the same hairlines still separate
 * from the panel; in light the controls dissolved into it, and a candidate
 * under a clock could not see what was clickable or which answer was theirs.
 *
 * So the exam surfaces (exam.html, quiz.html, and a running lab or race —
 * body.in-lab) get three things in light only, and dark is left exactly as it
 * was:
 *   --control-edge  a border that measures 3:1 against white (WCAG 1.4.11)
 *   --edge          panel hairlines that are visible, not implied
 *   --ink-muted     secondary text a step darker: task meta, the resting
 *                   clock, "3 of 60 answered" are read constantly here
 * and the chosen / current states get a fill as well as a border, because a
 * one-pixel accent line is not enough to find your place in sixty chips.
 *
 * Tokens are set on body rather than :root so the catalogue's light palette
 * does not move. The mapping appears twice for the reason the palette does:
 * a media query cannot join a selector list. */
:root[data-theme="light"] body:is(.exam-room, .in-lab) {
  --edge: #CFD6E0;
  --control-edge: #7C8696;
  --ink-muted: #475467;
}
:root[data-theme="light"] body:is(.exam-room, .in-lab) .dot.current:not(.passed) {
  background: color-mix(in srgb, var(--accent) 12%, var(--raised));
  font-weight: 600;
}
:root[data-theme="light"] body:is(.exam-room, .in-lab) .dot.current.passed {
  outline: 1px solid var(--ink);
  outline-offset: 2px;
}
:root[data-theme="light"] body:is(.exam-room, .in-lab) .dot:not(.current):not(.passed):not(.failed):not(.flagged):hover {
  border-color: var(--ink);
  color: var(--ink);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) body:is(.exam-room, .in-lab) {
    --edge: #CFD6E0;
    --control-edge: #7C8696;
    --ink-muted: #475467;
  }
  :root:not([data-theme]) body:is(.exam-room, .in-lab) .dot.current:not(.passed) {
    background: color-mix(in srgb, var(--accent) 12%, var(--raised));
    font-weight: 600;
  }
  :root:not([data-theme]) body:is(.exam-room, .in-lab) .dot.current.passed {
    outline: 1px solid var(--ink);
  outline-offset: 2px;
  }
  :root:not([data-theme]) body:is(.exam-room, .in-lab) .dot:not(.current):not(.passed):not(.failed):not(.flagged):hover {
    border-color: var(--ink);
    color: var(--ink);
  }
}

/* --------------------------------------------------------------- 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;
}

/* 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); }

/* ===================================================================== */
/* The certification catalogue, redesigned.                              */
/*                                                                       */
/* Scoped to body.catalogue — which only index.html carries — so the      */
/* runner, /races and the vendor embed keep the bar and the cards they    */
/* already had. Everything below is built from the existing tokens; the   */
/* one place that reaches past them is the header, and the reason is      */
/* spelled out where it happens.                                          */
/* ===================================================================== */

/* ---- the header slab ---- */

/* A dark bar over a light page, in BOTH themes.
 *
 * This is the same exception --void already makes for the terminal, and for
 * the same reason: the bar is a fixed piece of furniture whose job is to
 * frame the page, and a header that turns white with the theme stops being a
 * frame and becomes another card. So it is pinned to the DARK ramp — which
 * means everything inside it has to take its ink from the --dk-* layer too.
 * Reaching for --ink in here is the bug this note exists to prevent: it is
 * #101828 in light mode, and every link in the bar would go black on navy.
 *
 * --nav-bg is a navy rather than the --dk-surface near-black because the bar
 * sits against a light page here and needs to read as a deliberate slab, not
 * as a shadow. It is mixed from the palette rather than typed in as a new
 * hue, so it still tracks a change to the dark ramp.
 */
body.catalogue {
  --nav-bg: color-mix(in srgb, var(--dk-surface) 88%, #16243D);
  --nav-ink: var(--dk-ink);
  --nav-muted: var(--dk-muted);
  --nav-edge: color-mix(in srgb, var(--dk-ink) 14%, transparent);
  --nav-accent: var(--dk-accent);
}
body.catalogue .bar {
  background: var(--nav-bg);
  border-block-end: 1px solid var(--nav-edge);
  color: var(--nav-ink);
  box-shadow: none;
  gap: var(--s4);
}
body.catalogue .bar .wordmark,
body.catalogue .bar .lockup { color: var(--nav-ink); }
body.catalogue .bar .wordmark b { color: var(--nav-accent); }
body.catalogue .bar .muted,
body.catalogue .bar .sub { color: var(--nav-muted); }
/* The buttons the header borrows from the global sheet — theme, language,
   sign out — are drawn for a page-coloured surface. On the slab they get the
   slab's ink and a hairline instead of the page's edge colour. */
body.catalogue .bar .btn {
  background: transparent;
  border-color: var(--nav-edge);
  color: var(--nav-ink);
}
body.catalogue .bar .btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--dk-ink) 10%, transparent);
  border-color: var(--nav-muted);
}
body.catalogue .bar .lang { border-color: var(--nav-edge); }
body.catalogue .bar .lang a { color: var(--nav-muted); }
body.catalogue .bar .lang a.on { color: var(--nav-ink); }
body.catalogue .bar :focus-visible { outline-color: var(--nav-accent); }

/* ---- the five links ---- */

body.catalogue .cert-nav { gap: var(--s4); margin-inline-start: var(--s5); }
body.catalogue .cert-nav a { color: var(--nav-muted); font-size: 13.5px; }
body.catalogue .cert-nav a:hover { color: var(--nav-ink); }
body.catalogue .cert-nav a.on { color: var(--nav-ink); font-weight: 600; }
body.catalogue .cert-nav a.on::after { background: var(--nav-accent); }

/* ---- the account menu ---- */

.usermenu { position: relative; }
.usermenu > summary {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: 4px 8px 4px 4px;
  border: 1px solid transparent;
  border-radius: 999px;
  cursor: pointer;
  list-style: none;
  color: var(--nav-ink, var(--ink));
  font-size: 13px;
}
.usermenu > summary::-webkit-details-marker { display: none; }
.usermenu > summary:hover { border-color: var(--nav-edge, var(--edge)); }
.usermenu[open] > summary { border-color: var(--nav-edge, var(--edge)); }
.avatar {
  display: grid;
  place-items: center;
  inline-size: 26px;
  block-size: 26px;
  border-radius: 50%;
  background: var(--nav-accent, var(--accent));
  color: var(--dk-brand-ink);
  font: 600 12px/1 var(--ui);
  flex: 0 0 auto;
}
.usermenu-name {
  max-inline-size: 11ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The popup belongs to the PAGE, not to the slab: it is a menu on a surface,
   so it takes the page's own tokens and flips with the theme like every other
   panel does. */
.usermenu-pop {
  position: absolute;
  inset-block-start: calc(100% + 8px);
  inset-inline-end: 0;
  z-index: 40;
  min-inline-size: 208px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s2);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  background: var(--raised);
  color: var(--ink);
  box-shadow: 0 12px 28px rgba(8, 12, 20, 0.22);
}
.usermenu-who {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 6px 8px 8px;
  margin-block-end: 4px;
  border-block-end: 1px solid var(--edge);
  font-size: 12.5px;
}
.usermenu-who b { font-size: 13.5px; }
.usermenu-who .muted { overflow: hidden; text-overflow: ellipsis; }
.usermenu-pop a,
.usermenu-pop .btn {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  inline-size: 100%;
  block-size: 32px;
  padding-inline: 8px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink);
  font: 500 13px/1 var(--ui);
  text-decoration: none;
}
.usermenu-pop a:hover,
.usermenu-pop .btn:hover:not(:disabled) {
  background: color-mix(in srgb, var(--ink) 8%, transparent);
  text-decoration: none;
}

/* ---- the page ---- */

/* Wider than the default page, because the grid below wants five columns and
   the reference is a marketplace rather than a dashboard. */
body.catalogue .cert-catalogue-main {
  max-inline-size: 1320px;
  padding-inline: var(--s5);
  padding-block: var(--s6) var(--s8);
}

/* ---- the hero ---- */

body.catalogue .cert-hero {
  grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
  gap: var(--s7);
  align-items: center;
  padding: var(--s7) var(--s6);
  margin-block-end: var(--s6);
}
body.catalogue .cert-hero-eyebrow {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  margin-block-end: var(--s3);
}
body.catalogue .cert-hero h1 {
  font-size: clamp(26px, 3.4vw, 40px);
  line-height: 1.12;
  letter-spacing: -0.02em;
  margin-block-end: var(--s3);
  max-inline-size: 21ch;
}
body.catalogue .cert-hero p {
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink-muted);
  max-inline-size: 52ch;
  margin: 0;
}

/* The three claims. A list, because that is what it is, and each row is
   icon / name / one line — the name carries the weight so the trio scans as
   three things rather than six. */
.cert-props {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  margin: 0;
  padding: 0;
  list-style: none;
}
.cert-props li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  column-gap: var(--s3);
  align-items: center;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--edge);
  border-radius: var(--r);
  background: var(--raised);
}
.cert-prop-ico {
  grid-row: 1 / 3;
  display: grid;
  place-items: center;
  inline-size: 36px;
  block-size: 36px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}
.cert-prop-ico svg { inline-size: 19px; block-size: 19px; }
.cert-props b { font-size: 13.5px; font-weight: 600; align-self: end; }
.cert-props span { font-size: 12.5px; color: var(--ink-muted); align-self: start; }

/* ---- the filter pills ---- */

body.catalogue .cert-tools {
  display: flex;
  align-items: center;
  gap: var(--s4);
  margin-block-end: var(--s6);
  border: 0;
  padding: 0;
}
.cfilters {
  display: flex;
  flex: 1 1 auto;
  gap: var(--s2);
  min-inline-size: 0;
  overflow-x: auto;
  /* A filter strip that scrolls is fine; a PAGE that scrolls sideways is not,
     which is the whole reason this is its own scroll container. The bar is
     hidden because it would sit on top of the pills at this height. */
  scrollbar-width: none;
  padding-block-end: 2px;
}
.cfilters::-webkit-scrollbar { display: none; }
.cfilter {
  flex: 0 0 auto;
  block-size: 34px;
  padding-inline: 14px;
  border: 1px solid var(--edge);
  border-radius: 999px;
  background: var(--raised);
  color: var(--ink-muted);
  font: 500 13px/1 var(--ui);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--ease), border-color var(--ease), color var(--ease);
}
.cfilter:hover { color: var(--ink); border-color: var(--ink-muted); }
.cfilter.on {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--brand-ink);
  font-weight: 600;
}

/* ---- sections ---- */

.csecs { display: flex; flex-direction: column; gap: var(--s8); }
.csec { min-inline-size: 0; }
.csec-head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--s4);
  margin-block-end: var(--s5);
}
.csec-ico { display: grid; place-items: center; flex: 0 0 auto; }
.csec-ico-plain {
  display: grid;
  place-items: center;
  inline-size: 40px;
  block-size: 40px;
  border-radius: var(--r);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}
.csec-ico-plain svg { inline-size: 21px; block-size: 21px; }
.csec-title { min-inline-size: 0; }
.csec-eyebrow {
  display: block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-muted);
  margin-block-end: 4px;
}
.csec-head h2 {
  font-size: clamp(19px, 2vw, 25px);
  line-height: 1.2;
  letter-spacing: -0.015em;
  margin: 0;
}
.csec-why {
  margin: 6px 0 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--ink-muted);
  max-inline-size: 60ch;
}
.csec-head .cert-view-all { align-self: center; white-space: nowrap; }

/* ---- the grid ---- */

/* One card width for the whole page, and the browser picks the column count
   from it.
 *
 * The obvious thing — repeat(N) where N is how many cards the section has —
 * is what this replaced, and it was wrong in the way that is hard to unsee
 * once noticed: a section with one card gave that card half the page, so the
 * same CKS card was 232px wide under Kubernetes and 620px wide under
 * Kubernetes Security. auto-fill against a fixed minimum makes every card on
 * the page exactly the same size regardless of how many are beside it, which
 * is what §11 of the brief is actually asking for. A short section simply
 * leaves the rest of its row empty, which is what a shelf looks like. */
.ccard-grid {
  display: grid;
  /* 258px is chosen against the 1320px page: it yields four columns of ~310px
     on a desktop and three on a tablet, which is the 4-5 the brief asks for
     without making a three-card shelf look like a row that failed to load. */
  grid-template-columns: repeat(auto-fill, minmax(258px, 1fr));
  gap: var(--s4);
  align-items: stretch;
}

/* ---- the card ---- */

/* The rule this whole block exists to keep: every card in a row is the same
   height, and the price and the CTA are flush across it. That is done with
   grid rather than by hoping the content is the same length — the name block
   and the metadata list are fixed rows, and the two rows that must line up
   are pinned to the bottom. A title that wraps to two lines takes its space
   out of the name row and nothing below it moves. */
.ccard {
  display: grid;
  grid-template-rows: auto auto 1fr auto auto;
  gap: var(--s3);
  padding: var(--s4);
  border: 1px solid var(--edge);
  border-radius: var(--r-lg);
  background: var(--raised);
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: border-color var(--ease), box-shadow var(--ease), transform var(--ease);
}
.ccard:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--edge));
  box-shadow: 0 6px 18px rgba(16, 24, 40, 0.10);
  transform: translateY(-2px);
}
.ccard:focus-within { border-color: var(--accent); }

.ccard-top { display: flex; align-items: center; gap: 6px; min-block-size: 30px; }
.ccard-ico { display: inline-flex; flex: 0 0 auto; }

/* Badges. Small, because they are labels on a product and not the product. */
.ccard-tag {
  flex: 0 0 auto;
  padding: 3px 8px;
  border-radius: 999px;
  border: 1px solid var(--edge);
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  color: var(--ink-muted);
  font: 600 10.5px/1.5 var(--ui);
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.ccard-tag.free {
  border-color: color-mix(in srgb, var(--pass) 40%, transparent);
  background: color-mix(in srgb, var(--pass) 14%, transparent);
  color: var(--pass);
}
/* The one warm colour on the page, and it is spent on the discount because a
   saving is the one fact here that is genuinely time-limited. */
.ccard-tag.off {
  border-color: color-mix(in srgb, var(--fail) 38%, transparent);
  background: color-mix(in srgb, var(--fail) 13%, transparent);
  color: var(--fail);
  font-variant-numeric: tabular-nums;
}
.ccard-tag.pass {
  border-color: color-mix(in srgb, var(--pass) 40%, transparent);
  color: var(--pass);
}
.ccard-tag.part { color: var(--ink); font-variant-numeric: tabular-nums; }
.ccard-tag.new {
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
}

/* Fixed height, not "whatever the text needs".
 *
 * This is the block that decides whether a row of cards lines up, because it
 * is the only one above the metadata whose content varies in length. Left to
 * grow, KCSA's two-line subtitle pushed its metadata, its price and its
 * button a line below KCNA's — three misalignments from one wrapped word.
 * So the block is a fixed box and the text clamps inside it: one line of
 * code, two of name. A certification code is three to five characters, so
 * the first clamp never actually fires; it is there so that the day somebody
 * publishes a long one, the grid does not come apart. */
.ccard-name {
  min-inline-size: 0;
  /* A fixed height, not a floor. min-block-size still grows when the content
     is one pixel taller than it, which is exactly what a two-line subtitle
     is — 20.4px of title, 3px of gap and 35px of subtitle — and the card
     beside it then sat a line higher. block-size plus overflow:hidden is the
     only version of this that cannot be argued out of by the text. */
  block-size: 62px;
  overflow: hidden;
}
.ccard-name h3 {
  margin: 0;
  font-size: 17px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ccard-sub {
  margin-block-start: 3px;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--ink-muted);
  /* Two lines, then an ellipsis. A subtitle allowed to run to four lines is
     the single commonest way one card in a row ends up taller than the rest,
     and the full text is on the page behind the card anyway. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.ccard-meta {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 0;
  padding: 0;
  list-style: none;
  align-content: start;
}
.ccard-meta li {
  display: flex;
  align-items: center;
  gap: 7px;
  min-inline-size: 0;
  font-size: 12px;
  line-height: 1.35;
  color: var(--ink-muted);
}
.ccard-meta li svg { inline-size: 13px; block-size: 13px; flex: 0 0 auto; opacity: 0.85; }
.ccard-meta li span { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis;
                      white-space: nowrap; }

/* Price row. Fixed height whether it holds two numbers or one word, so the
   CTA under it is flush across the row either way. */
.ccard-price {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-block-size: 30px;
  padding-block-start: var(--s3);
  border-block-start: 1px solid var(--edge);
}
.ccard-was {
  font: 500 13px/1 var(--mono);
  color: var(--ink-muted);
  text-decoration-thickness: 1.5px;
}
.ccard-amount {
  font: 700 22px/1 var(--mono);
  letter-spacing: -0.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.ccard-amount.free {
  font-family: var(--ui);
  font-size: 17px;
  letter-spacing: 0.04em;
  color: var(--pass);
}

.ccard-foot { display: flex; align-items: center; gap: var(--s2); }
.ccard-foot .spacer { flex: 1 1 auto; }
/* One height, one radius, one weight, on every card — and it may not grow to
   fit its label, because a button that is wider on one card than the next is
   what makes a tidy grid look hand-assembled. */
.ccard-cta {
  block-size: 34px;
  padding-inline: 12px;
  border-radius: 999px;
  font-size: 12.5px;
  gap: 5px;
  min-inline-size: 0;
}
.ccard-cta .btn-label { display: inline-flex; align-items: center; gap: 5px;
                        min-inline-size: 0; }
.ccard-cta .btn-label > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ccard-cta svg { inline-size: 13px; block-size: 13px; flex: 0 0 auto; }
.ccard-foot .cert-level { flex: 0 0 auto; }

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

/* The column count is the only thing that changes. Below 1180 the section's
   own --span is overridden, because a five-card section has no business
   keeping five columns on a tablet. */
@media (max-width: 860px) {
  body.catalogue .cert-hero {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s5);
    padding: var(--s6) var(--s5);
  }
  body.catalogue .cert-catalogue-main { padding-inline: var(--s4); }
  .csec-head { grid-template-columns: auto minmax(0, 1fr); }
  .csec-head .cert-view-all { grid-column: 2; justify-self: start; }
}
@media (max-width: 620px) {
  /* One per row, and the minimum track goes with it: a 216px floor on a
     360px screen with 16px gutters is fine, but on a 320px one it is the
     thing that pushes the page sideways. */
  .ccard-grid { grid-template-columns: minmax(0, 1fr); }
  .csecs { gap: var(--s7); }
  body.catalogue .cert-hero h1 { font-size: 27px; }
  .ccard-sub { -webkit-line-clamp: 3; }
}

/* The header on a narrow screen: the links wrap onto their own full-width
   row and scroll sideways there, rather than vanishing behind a hamburger.
   Five destinations is few enough to show; hiding them behind a button would
   be a menu whose only content is the navigation it replaced. */
@media (max-width: 900px) {
  body.catalogue .bar {
    display: flex;
    flex-wrap: wrap;
    block-size: auto;
    row-gap: var(--s2);
    padding-block: var(--s2);
  }
  /* The nav is a child of .bar-id, not of .bar, so ordering it against the
     bar's other groups is not something `order` can do — which is how it
     ended up 376px wide starting at x=126 on a 400px screen, pushing the
     whole document sideways. display:contents dissolves the wrapper for the
     duration of this breakpoint, so the lockup, the nav and the controls
     become siblings of each other and can be laid out as three: brand and
     instruments on one row, the five links on their own beneath. */
  body.catalogue .bar-id { display: contents; }
  body.catalogue .bar > .bar-ctl { order: 2; flex: 0 0 auto; }
  body.catalogue .bar .lockup { order: 1; flex: 1 1 auto; min-inline-size: 0; }
  body.catalogue .bar-id .sub { order: 5; }
  body.catalogue .bar-slot { order: 6; }
  body.catalogue .cert-nav {
    display: flex;
    order: 3;
    flex: 1 0 100%;
    min-inline-size: 0;
    margin-inline-start: 0;
    gap: var(--s4);
    overflow-x: auto;
    scrollbar-width: none;
  }
  body.catalogue .cert-nav::-webkit-scrollbar { display: none; }
  /* Pills and the box beside them stop sharing a row: below this the box's
     own 180px minimum plus a scrollable strip does not fit, and the strip is
     what gives. */
  body.catalogue .cert-tools { flex-wrap: wrap; }
  body.catalogue .cert-tools .tools-find { flex: 1 1 100%; min-inline-size: 0; }
  /* Opened on a narrow screen the popup would hang off the left edge, because
     it is anchored to a summary that is itself near the middle. Fixed to the
     viewport instead, which is the only frame that is guaranteed to fit it. */
  .usermenu[open] .usermenu-pop {
    position: fixed;
    inset-block-start: 58px;
    inset-inline-end: var(--s3);
    inset-inline-start: auto;
  }
  body.catalogue .cert-nav a.on::after { inset-block-end: -4px; }
  body.catalogue .bar-find { display: none; }
  body.catalogue .usermenu-name { display: none; }
  /* The strip in the page body is the wide-screen casualty here: the bar's
     own links are visible at every width now, so a second copy of two of them
     would be two navigations for the same place. */
  body.catalogue #home-tabs { display: none !important; }
}
@media (min-width: 901px) {
  body.catalogue #home-tabs { display: none !important; }
  body.catalogue .cert-nav { display: flex; }
}


/* ===================================================================== */
/* Races, the browse page.                                               */
/*                                                                       */
/* Scoped to body.races. It sits on the catalogue's header slab and its  */
/* filter pills, and adds a hero, a race card, and two explainer panels. */
/* Two surfaces here are dark in BOTH themes — the promo slab and the    */
/* picture at the top of a card — for the reason the header is: they are */
/* posters, not panels. Anything drawn on them takes fixed light inks,   */
/* never --ink, which would go navy-on-navy in the light theme.          */
/* ===================================================================== */

.sr-only {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
body.catalogue a.lockup,
body.catalogue a.lockup:hover { text-decoration: none; }

/* In-session chrome discipline, the rule the old races bar kept: while the
   terminal is live the header holds the clock and the organiser's Cancel, and
   nothing that navigates away. The bar is also held to one row on a narrow
   screen, because the runner's split is sized as the viewport minus --bar-h. */
body.races.in-lab .cert-nav { display: none; }
/* The race's lab title, beside the five links: set off by a rule so it reads
   as "where you are", not as a sixth link. */
body.races .bar-id .sub:not(:empty) {
  margin-inline-start: var(--s2);
  padding-inline-start: var(--s4);
  border-inline-start: 1px solid var(--nav-edge);
}
body.races.in-lab .bar-id .sub:not(:empty) { border: 0; padding-inline-start: 0; }
@media (max-width: 900px) {
  body.races.in-lab .bar { flex-wrap: nowrap; block-size: var(--bar-h); padding-block: 0; }
}

body.races .bar-find { flex-basis: 270px; }
body.races main.races-main {
  max-inline-size: 1320px;
  margin: 0 auto;
  padding: var(--s6) var(--s5) var(--s8);
}

/* ---- hero ---- */

.rx-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.12fr) minmax(0, 1fr);
  gap: var(--s6);
  align-items: center;
  margin-block-end: var(--s6);
}
.rx-eyebrow {
  margin-block-end: var(--s3);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}
.rx-hero h1 {
  margin: 0 0 var(--s3);
  font-size: clamp(32px, 3.25vw, 46px);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.035em;
  color: var(--ink);
  text-wrap: balance;
}
.rx-lede {
  margin: 0 0 var(--s5);
  max-inline-size: 54ch;
  font-size: 16px;
  line-height: 1.55;
  color: var(--ink-muted);
}
.rx-props {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, auto));
  justify-content: start;
  gap: var(--s3) var(--s5);
  margin: 0;
  padding: 0;
  list-style: none;
}
.rx-props li { display: flex; align-items: center; gap: var(--s3); min-inline-size: 0; }
.rx-props b { white-space: nowrap; }
.rx-prop-ico {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 44px;
  block-size: 44px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 12%, var(--raised));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--accent);
}
.rx-prop-ico svg { inline-size: 20px; block-size: 20px; }
.rx-prop-say { display: flex; flex-direction: column; min-inline-size: 0; }
.rx-prop-say b { font-size: 14px; font-weight: 700; color: var(--ink); }
.rx-prop-say span { font-size: 12.5px; line-height: 1.35; color: var(--ink-muted); }

/* ---- the promo slab ---- */

.rx-promo {
  --promo-ink: #EEF2F7;
  --promo-muted: #A9B4C4;
  --promo-green: #34D399;
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.08fr) minmax(90px, 0.62fr) minmax(158px, 0.92fr);
  align-items: center;
  gap: var(--s3);
  min-block-size: 250px;
  padding: var(--s5);
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 18px;
  color: var(--promo-ink);
  color-scheme: dark;
  background:
    radial-gradient(34% 62% at 50% 58%, rgba(16, 185, 129, 0.30), transparent 72%),
    radial-gradient(70% 110% at 0% 100%, rgba(16, 185, 129, 0.14), transparent 62%),
    linear-gradient(135deg, #0B1628 0%, #0D1E30 55%, #0A2A27 100%);
  box-shadow: 0 12px 32px rgba(8, 15, 30, 0.16);
}
.rx-promo-say { min-inline-size: 0; }
.rx-promo-say h2 {
  margin: 0 0 var(--s2);
  font-size: clamp(20px, 1.9vw, 26px);
  font-weight: 800;
  line-height: 1.16;
  letter-spacing: -0.02em;
  color: #FFFFFF;
}
.rx-promo-say h2 em { display: block; font-style: normal; color: var(--promo-green); }
.rx-promo-say p { margin: 0 0 var(--s4); font-size: 13.5px; line-height: 1.5; color: var(--promo-muted); }
/* The slab is dark in the light theme too, so the button is pinned to the
   light theme's deep green and white ink rather than following --brand to the
   dark theme's mint — the mint with near-black ink reads as a different
   product on a navy poster. */
.rx-promo .rx-promo-cta {
  block-size: 40px;
  padding-inline: 16px;
  border-color: #0E9F6E;
  background: #0E9F6E;
  color: #FFFFFF;
}
.rx-promo .rx-promo-cta:hover { text-decoration: none; filter: brightness(1.1); }
.rx-promo .rx-promo-cta svg { inline-size: 15px; block-size: 15px; }
.rx-promo :focus-visible { outline-color: var(--promo-green); }
.rx-promo-art { display: grid; place-items: center; min-inline-size: 0; }
.rx-promo-art svg {
  inline-size: 100%;
  max-inline-size: 150px;
  filter: drop-shadow(0 10px 22px rgba(245, 184, 61, 0.22));
}

.rx-mini {
  min-inline-size: 0;
  padding: 10px;
  border: 1px solid rgba(255, 255, 255, 0.09);
  border-radius: 12px;
  background: rgba(14, 25, 43, 0.78);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}
.rx-mini-list { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
.rx-mini-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  min-inline-size: 0;
  padding: 6px;
  border-radius: 8px;
  font-size: 12.5px;
  color: var(--promo-ink);
}
.rx-mini-list li + li { border-block-start: 1px solid rgba(255, 255, 255, 0.06); }
.rx-mini-list .who { flex: 1 1 auto; min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rx-mini-list .pts { font-size: 12px; }
.rx-mini-list li.me { background: rgba(52, 211, 153, 0.14); }
.rx-mini-list li.invite .who { color: var(--promo-green); font-weight: 700; }
.rx-mini-list li.invite .pts { color: var(--promo-muted); }
.rx-mini-list .skel { background: rgba(255, 255, 255, 0.14); }
.rx-mini-more {
  display: inline-block;
  margin: 6px 6px 0;
  padding: 0;
  border: 0;
  background: none;
  font: 600 12px/1.3 var(--ui);
  color: var(--promo-green);
  cursor: pointer;
}
.rx-mini-more:hover { text-decoration: underline; }
.rx-mini-note { margin: 6px 6px 0; font-size: 12px; line-height: 1.4; color: var(--promo-muted); }

/* Rank discs. Gold, silver and bronze are decoration with the numeral on
   them, so a colour-blind reader loses nothing. */
.rx-medal {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 20px;
  block-size: 20px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.12);
  color: var(--promo-ink, var(--ink));
  font: 700 11px/1 var(--ui);
  font-variant-numeric: tabular-nums;
}
.rx-board-tbl .rx-medal { background: color-mix(in srgb, var(--ink) 8%, transparent); color: var(--ink); }
.rx-medal.m1 { background: #F5B83D; color: #3A2600; }
.rx-medal.m2 { background: #CBD3DE; color: #1F2937; }
.rx-medal.m3 { background: #D6935E; color: #2B1605; }

/* ---- filters and sort ---- */

.rx-tools { display: flex; align-items: center; gap: var(--s4); margin-block-end: var(--s5); }
.rx-filters { flex: 1 1 auto; gap: 6px; }
/* A notch smaller than the catalogue's pills: eleven of them and the sort
   control have to share one row at desktop width, which is what the design
   shows, and a strip that has to be scrolled to find its last pill hides it. */
.rx-filters .cfilter { block-size: 32px; padding-inline: 12px; font-size: 12.5px; }
/* A tint, not the solid fill the catalogue uses: eleven pills with one of
   them a solid green slab reads as a button bar, and the reference asks for
   the chosen one to be marked, not shouted. */
.rx-filters .cfilter.on {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 11%, var(--raised));
  color: var(--accent);
  font-weight: 600;
}
.rx-sort {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  block-size: 36px;
  padding-inline-start: 12px;
  border: 1px solid var(--edge);
  border-radius: var(--r);
  background: var(--raised);
  box-shadow: var(--shadow);
  font-size: 13px;
  color: var(--ink-muted);
  white-space: nowrap;
}
.rx-sort:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}
.rx-sort select {
  inline-size: auto;
  block-size: 34px;
  padding-block: 0;
  padding-inline-start: 2px;
  border: 0;
  background-color: transparent;
  box-shadow: none;
  font: 600 13px/1 var(--ui);
  color: var(--ink);
  cursor: pointer;
}
.rx-sort select:hover:not(:focus) { border-color: transparent; }
.rx-find { display: none; }

/* ---- section heads ---- */

.rx-sec { margin-block-end: var(--s7); scroll-margin-block-start: var(--s4); }
.rx-sec:last-child { margin-block-end: 0; }
.rx-sec-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s3) var(--s4);
  margin-block-end: var(--s4);
}
.rx-sec-head h2 {
  margin: 0;
  font-size: clamp(21px, 2.1vw, 26px);
  font-weight: 800;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.rx-sec-head p { margin: 4px 0 0; font-size: 14px; color: var(--ink-muted); }
.rx-sec-acts { display: flex; align-items: center; gap: var(--s4); }
.rx-new { block-size: 34px; font-size: 13px; }
.rx-new svg { inline-size: 15px; block-size: 15px; }
a.rx-view-all:hover { text-decoration: none; }
.rx-view-all.back svg { transform: scaleX(-1); }

.rx-create { margin-block-end: var(--s5); }
.rx-create-head h3 { margin: 0; font-size: 16px; font-weight: 700; }
.rx-create-head p { margin: 2px 0 var(--s4); font-size: 13px; }

/* ---- the grid ---- */

.rx-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s4); }
.rx-grid.is-state { grid-template-columns: minmax(0, 1fr); }

/* ---- the card ---- */

/* Six rows — picture, title, description, metadata, CTA, schedule — and on a
   grid that can, the card borrows its rows from the grid itself (subgrid), so
   every title, CTA and schedule line in a visual row starts at the same height
   as its neighbours' without reserving two blank lines for a one-line title.
   .rx-card-body dissolves (display: contents) so its children ARE those rows.
   Where subgrid is missing the CTA row takes the slack instead, which keeps the
   schedule lines flush and only lets the text above them differ. */
.rx-card {
  display: grid;
  grid-row: span 6;
  grid-template-rows: auto auto auto auto 1fr auto;
  row-gap: 8px;
  min-inline-size: 0;
  overflow: hidden;
  border: 1px solid var(--edge);
  border-radius: 14px;
  background: var(--raised);
  box-shadow: var(--shadow);
  transition: border-color var(--ease), box-shadow var(--ease), transform var(--ease);
}
.rx-card:not(.rx-skel):hover {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--edge));
  box-shadow: 0 8px 22px rgba(16, 24, 40, 0.10);
  transform: translateY(-2px);
}
.rx-card:focus-within { border-color: color-mix(in srgb, var(--accent) 60%, var(--edge)); }

.rx-art {
  --hue: #10B981;
  position: relative;
  display: grid;
  place-items: center;
  margin: 8px 8px 0;
  aspect-ratio: 2.3 / 1;
  min-block-size: 112px;
  max-block-size: 176px;
  overflow: hidden;
  border-radius: 10px;
  color: #FFFFFF;
  color-scheme: dark;
  background:
    radial-gradient(40% 72% at 50% 56%, color-mix(in srgb, var(--hue) 58%, transparent) 0%, transparent 72%),
    linear-gradient(135deg, #0A1324 0%, color-mix(in srgb, var(--hue) 24%, #0A1324) 100%);
}
.rx-art.skel { background: var(--edge); }
.rx-art[data-bucket="kubernetes"]    { --hue: #326CE5; }
.rx-art[data-bucket="linux"]         { --hue: #14B8A6; }
.rx-art[data-bucket="cloud"]         { --hue: #0EA5E9; }
.rx-art[data-bucket="devops"]        { --hue: #8B5CF6; }
.rx-art[data-bucket="networking"]    { --hue: #06B6D4; }
.rx-art[data-bucket="security"]      { --hue: #EF4444; }
.rx-art[data-bucket="gitops"]        { --hue: #F97316; }
.rx-art[data-bucket="observability"] { --hue: #F59E0B; }
.rx-art[data-bucket="servicemesh"]   { --hue: #6366F1; }
.rx-art[data-bucket="platform"]      { --hue: #10B981; }
.rx-art-decor { position: absolute; inset: 0; inline-size: 100%; block-size: 100%; }
.rx-art-decor rect { fill: rgba(255, 255, 255, 0.045); stroke: rgba(255, 255, 255, 0.14); stroke-width: 1; }
.rx-art-decor path { fill: none; stroke: rgba(255, 255, 255, 0.17); stroke-width: 2; stroke-linecap: round; }
.rx-art-decor .win > path:first-of-type { stroke-width: 1; }
.rx-art-decor circle.led { fill: var(--hue); stroke: none; }
.rx-art-decor circle.dot { fill: rgba(255, 255, 255, 0.3); stroke: none; }
.rx-glyph {
  position: relative;
  inline-size: 27%;
  max-inline-size: 66px;
  block-size: auto;
  aspect-ratio: 1;
  filter: drop-shadow(0 0 14px color-mix(in srgb, var(--hue) 85%, transparent));
}
.rx-glyph.mark-glyph path { fill: #FFFFFF; }
.rx-glyph.line-glyph { stroke-width: 1.6; }

.rx-status,
.rx-cat,
.rx-private {
  position: absolute;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 999px;
  font: 700 11px/1.4 var(--ui);
  white-space: nowrap;
}
.rx-status { inset-block-start: 9px; inset-inline-start: 9px; }
.rx-status svg { inline-size: 11px; block-size: 11px; }
.rx-status .btn-spin { inline-size: 9px; block-size: 9px; border-width: 1.5px; }
.rx-status.upcoming { background: #D1FAE5; color: #065F46; }
.rx-status.starting { background: #FEF3C7; color: #78350F; }
.rx-status.live { background: #DC2626; color: #FFFFFF; }
.rx-status.finished,
.rx-status.cancelled { background: #E5E7EB; color: #1F2937; }
.rx-cat { inset-block-start: 9px; inset-inline-end: 9px; max-inline-size: 48%; overflow: hidden;
          text-overflow: ellipsis; background: rgba(255, 255, 255, 0.94); color: #1E293B; }
.rx-private {
  inset-block-end: 9px;
  inset-inline-start: 9px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(10, 19, 36, 0.72);
  color: #E6EAF0;
  font-weight: 600;
}
.rx-private svg { inline-size: 11px; block-size: 11px; }
.rx-dot { inline-size: 6px; block-size: 6px; border-radius: 50%; background: currentColor; }
.rx-live-dot {
  position: relative;
  flex: none;
  inline-size: 7px;
  block-size: 7px;
  border-radius: 50%;
  background: currentColor;
}
@media (prefers-reduced-motion: no-preference) {
  .rx-live-dot::after {
    content: "";
    position: absolute;
    inset: -3px;
    border: 2px solid currentColor;
    border-radius: 50%;
    animation: rx-ping 1.6s ease-out infinite;
  }
  @keyframes rx-ping {
    from { transform: scale(0.6); opacity: 0.7; }
    to   { transform: scale(1.7); opacity: 0; }
  }
}

@supports (grid-template-rows: subgrid) {
  .rx-card { grid-template-rows: subgrid; }
}
.rx-card-body { display: contents; }
.rx-card-body > * { min-inline-size: 0; margin-inline: 14px; }
.rx-card-body h3 {
  margin-block: 6px 0;
  overflow: hidden;
  font-size: 16px;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.01em;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.rx-card-body h3 a { color: var(--ink); text-decoration: none; }
.rx-card-body h3 a:hover { color: var(--accent); }
.rx-desc {
  margin-block: 0;
  overflow: hidden;
  font-size: 13px;
  line-height: 1.45;
  color: var(--ink-muted);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.rx-meta { display: flex; flex-wrap: wrap; gap: 4px 8px; margin-block: 2px 0; padding: 0; list-style: none; }
.rx-meta li { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; color: var(--ink-muted); white-space: nowrap; }
.rx-meta svg { flex: none; inline-size: 13px; block-size: 13px; }
.rx-card-cta { display: flex; flex-direction: column; justify-content: flex-end; gap: 6px; padding-block: 6px 4px; }
.rx-card-act {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--s2);
}

/* Difficulty: a bar count, the word, AND a colour — never the colour alone.
   Intermediate is mixed toward orange from --warn rather than typed in, so it
   reads amber on both themes and still moves with the palette. */
.rx-level { --lvl: var(--pass); display: inline-flex; align-items: center; gap: 8px; color: var(--lvl); }
.rx-level.intermediate { --lvl: color-mix(in srgb, var(--warn) 55%, #EA580C); }
.rx-level.advanced { --lvl: var(--fail); }
.rx-diff {
  padding: 3px 10px;
  border: 1px solid color-mix(in srgb, var(--lvl) 35%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--lvl) 10%, transparent);
  font: 600 11.5px/1.3 var(--ui);
  white-space: nowrap;
}
.rx-bars { display: inline-flex; align-items: flex-end; gap: 2px; block-size: 14px; }
.rx-bars i { inline-size: 3.5px; border-radius: 1px; background: currentColor; opacity: 0.25; }
.rx-bars i:nth-child(1) { block-size: 6px; }
.rx-bars i:nth-child(2) { block-size: 10px; }
.rx-bars i:nth-child(3) { block-size: 14px; }
.rx-bars i.on { opacity: 1; }

/* The label on the section heading while the grid is showing example races. */
.rx-preview {
  display: inline-block;
  margin-inline-start: 8px;
  padding: 2px 9px;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent);
  font: 700 11px/1.5 var(--ui);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  vertical-align: middle;
}

.rx-cta { block-size: 36px; padding-inline: 14px; gap: 6px; border-radius: 8px; font-size: 13px; }
a.rx-cta:hover { text-decoration: none; }
.rx-cta svg { inline-size: 14px; block-size: 14px; transition: transform var(--ease); }
.rx-cta:hover:not(:disabled) svg { transform: translateX(2px); }
.rx-cta .btn-spin { inline-size: 12px; block-size: 12px; }
.btn.rx-quiet {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--edge));
  background: color-mix(in srgb, var(--accent) 6%, var(--raised));
  color: var(--accent);
}
.btn.rx-quiet:hover:not(:disabled) {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, var(--raised));
}
/* Full is a state, not a broken button: legible, just not green. */
.rx-cta:disabled:not(.is-busy) {
  opacity: 1;
  border-color: var(--edge);
  background: color-mix(in srgb, var(--ink) 5%, var(--raised));
  color: var(--ink-muted);
}
.rx-join-err { min-block-size: 0; font-size: 12px; }
.rx-join-err:empty { display: none; }
.rx-join-err.rx-note { color: var(--ink-muted); }

.rx-card-foot {
  display: flex;
  align-items: center;
  min-block-size: 58px;
  padding: 10px 14px 12px;
  border-block-start: 1px solid var(--edge);
}
.rx-when { display: flex; align-items: flex-start; gap: 9px; min-inline-size: 0; font-size: 12px; line-height: 1.35; color: var(--ink-muted); }
.rx-when > svg { flex: none; inline-size: 16px; block-size: 16px; margin-block-start: 1px; color: var(--accent); }
.rx-when > span:last-child { display: flex; flex-direction: column; min-inline-size: 0; }
.rx-when b { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.rx-when.live b { color: var(--fail); }
.rx-when.live .rx-live-dot { margin: 5px 4px 0 3px; color: var(--fail); }

/* ---- loading, empty, error ---- */

.rx-skel .skel.row { margin-block-end: 0; }
.rx-skel .rx-skel-desc { display: flex; flex-direction: column; gap: 8px; }
.rx-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: var(--s7) var(--s5);
  border: 1px dashed color-mix(in srgb, var(--ink) 18%, transparent);
  border-radius: 14px;
  background: var(--raised);
  text-align: center;
}
.card .rx-state { padding: var(--s4); border: 0; }
.rx-state-ico {
  display: grid;
  place-items: center;
  inline-size: 48px;
  block-size: 48px;
  margin-block-end: 6px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
}
.rx-state.err .rx-state-ico { background: color-mix(in srgb, var(--fail) 12%, transparent); color: var(--fail); }
.rx-state-ico svg { inline-size: 22px; block-size: 22px; }
.rx-state h3 { margin: 0; font-size: 17px; font-weight: 700; color: var(--ink); }
.rx-state p { margin: 0; max-inline-size: 52ch; font-size: 13.5px; color: var(--ink-muted); }
.rx-state-acts { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s2); margin-block-start: var(--s3); }
.rx-state-acts svg { inline-size: 14px; block-size: 14px; }
.rx-state-acts a.btn:hover { text-decoration: none; }

/* ---- leaderboard ---- */

.rx-board .tbl-wrap { border-radius: 14px; box-shadow: var(--shadow); }
.rx-board-tbl th:first-child, .rx-board-tbl td:first-child { inline-size: 64px; text-align: start; }
.rx-board-tbl .rx-medal { inline-size: 24px; block-size: 24px; font-size: 12px; }
.rx-board-tbl tr.me > td { background: color-mix(in srgb, var(--accent) 9%, transparent); }

/* ---- how races work / why join ---- */

.rx-lower {
  display: grid;
  grid-template-columns: minmax(0, 2.6fr) minmax(290px, 1fr);
  gap: var(--s4);
  align-items: stretch;
  margin-block-end: var(--s7);
}
.rx-how {
  padding: var(--s5) var(--s6) var(--s6);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--edge));
  border-radius: 16px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 9%, var(--raised)) 0%,
                                      color-mix(in srgb, var(--accent) 4%, var(--raised)) 100%);
}
.rx-how h2,
.rx-why h2 { margin: 0; font-size: 21px; font-weight: 800; letter-spacing: -0.02em; color: var(--ink); }
.rx-how-why { margin: 4px 0 var(--s5); font-size: 14px; color: var(--ink-muted); }
.rx-steps {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s5);
  margin: 0;
  padding: 0;
  list-style: none;
}
.rx-steps li { position: relative; display: flex; align-items: flex-start; gap: var(--s3); min-inline-size: 0; }
/* The arrow between two steps sits in the gap, centred on it, at the height
   of the numbered disc — so it points from one number to the next. */
.rx-steps li:not(:last-child)::after {
  content: "";
  position: absolute;
  inset-block-start: 8px;
  inset-inline-end: calc(-1 * var(--s5) / 2 - 7px);
  inline-size: 14px;
  block-size: 14px;
  background: color-mix(in srgb, var(--accent) 65%, transparent);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.5 12h14'/%3E%3Cpath d='m13 6.5 6 5.5-6 5.5'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.5 12h14'/%3E%3Cpath d='m13 6.5 6 5.5-6 5.5'/%3E%3C/svg%3E") center / contain no-repeat;
}
.rx-step-n {
  flex: none;
  display: grid;
  place-items: center;
  inline-size: 30px;
  block-size: 30px;
  border-radius: 50%;
  background: var(--brand);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--brand-ink);
  font: 700 13px/1 var(--ui);
}
.rx-step-say { display: flex; flex-direction: column; gap: 3px; min-inline-size: 0; }
.rx-step-say b { font-size: 13.5px; font-weight: 700; color: var(--ink); }
.rx-step-say span { font-size: 12.5px; line-height: 1.45; color: var(--ink-muted); }

.rx-why {
  position: relative;
  overflow: hidden;
  padding: var(--s5);
  border: 1px solid var(--edge);
  border-radius: 16px;
  background: var(--raised);
  box-shadow: var(--shadow);
}
.rx-why h2 { display: flex; align-items: center; gap: 10px; margin-block-end: var(--s4); }
.rx-why-ico {
  display: grid;
  place-items: center;
  inline-size: 34px;
  block-size: 34px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 13%, transparent);
  color: var(--accent);
}
.rx-why-ico svg { inline-size: 19px; block-size: 19px; }
.rx-checks {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 9px;
  max-inline-size: 74%;
  margin: 0;
  padding: 0;
  list-style: none;
}
.rx-checks li { display: flex; align-items: center; gap: 9px; font-size: 13.5px; color: var(--ink); }
.rx-checks svg {
  flex: none;
  inline-size: 18px;
  block-size: 18px;
  padding: 3px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
}
.rx-podium { position: absolute; inset-block-end: 0; inset-inline-end: 12px; inline-size: 30%; max-inline-size: 130px; }
.rx-podium .p1 { fill: color-mix(in srgb, var(--accent) 36%, var(--raised)); }
.rx-podium .p2 { fill: color-mix(in srgb, var(--accent) 24%, var(--raised)); }
.rx-podium .p3 { fill: color-mix(in srgb, var(--accent) 16%, var(--raised)); }
.rx-podium .cup { fill: #F5B83D; }
.rx-podium .cup-h { fill: none; stroke: #F5B83D; stroke-width: 2.5; stroke-linecap: round; }
.rx-podium .spark { fill: color-mix(in srgb, var(--accent) 55%, transparent); }

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

@media (max-width: 1180px) {
  .rx-hero { grid-template-columns: minmax(0, 1fr); gap: var(--s5); }
  .rx-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .rx-lower { grid-template-columns: minmax(0, 1fr); }
  .rx-steps { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s5); }
  .rx-steps li::after { display: none; }
}
@media (max-width: 900px) {
  body.races main.races-main { padding: var(--s5) var(--s4) var(--s7); }
  .rx-tools { flex-wrap: wrap; gap: var(--s3); }
  .rx-filters { flex: 1 0 100%; }
  .rx-find { display: block; flex: none; inline-size: 100%; margin: 0 0 var(--s5); }
}
@media (max-width: 700px) {
  .rx-props { grid-template-columns: minmax(0, 1fr); gap: var(--s3); }
  .rx-promo { grid-template-columns: minmax(0, 1fr) 96px; padding: var(--s4); }
  .rx-mini { grid-column: 1 / -1; }
}
@media (max-width: 640px) {
  .rx-grid { grid-template-columns: minmax(0, 1fr); }
  .rx-hero h1 { font-size: 34px; }
  .rx-how { padding: var(--s5) var(--s4); }
  .rx-steps { grid-template-columns: minmax(0, 1fr); gap: var(--s4); }
  .rx-checks { max-inline-size: none; }
  .rx-podium { opacity: 0.35; }
  .rx-sec-head { align-items: flex-start; }
}
