/* ===================================================================
   Cyber-Labs — dark "security console" theme
   =================================================================== */

/* Self-hosted webfonts — variable fonts, one file per family covers the
   full weight range we use (400-800). No CDN at runtime: these ship in
   assets/fonts/ and are precached by the service worker like any other
   asset, so the app stays fully offline/air-gapped once installed. */
@font-face{
  font-family:'Inter'; font-style:normal; font-weight:100 900; font-display:swap;
  src:url('../assets/fonts/inter-var.woff2') format('woff2-variations'), url('../assets/fonts/inter-var.woff2') format('woff2');
}
@font-face{
  font-family:'Inter'; font-style:italic; font-weight:100 900; font-display:swap;
  src:url('../assets/fonts/inter-italic-var.woff2') format('woff2-variations'), url('../assets/fonts/inter-italic-var.woff2') format('woff2');
}
@font-face{
  font-family:'JetBrains Mono'; font-style:normal; font-weight:100 800; font-display:swap;
  src:url('../assets/fonts/jetbrains-mono-var.woff2') format('woff2-variations'), url('../assets/fonts/jetbrains-mono-var.woff2') format('woff2');
}

/* ===================================================================
   DESIGN TOKENS — "Elevated SOC" · emerald signature · dark + light
   -------------------------------------------------------------------
   The whole app is token-driven, so a theme is just a token set. Every
   color/tint in the sheet resolves through these names (tints use the
   `--x-rgb` channel tokens via rgba(var(--x-rgb), a)). Dark is the
   default :root; light overrides the SAME names under
   [data-theme="light"] and as the no-JS prefers-color-scheme default.
   =================================================================== */
:root{
  /* ===== THE LIGHT SOURCE =====================================
     Premium dark UIs are not "grey rectangles with a colour" — they are
     surfaces lit from above. This ONE number, expressed three ways (edge
     light, border, spotlight), is what makes the whole app read as a single
     coherent object rather than a pile of divs. Every white-alpha value in
     this file traces back to it. Change it here or nowhere.
     (Sourced from shipped CSS: Raycast/Linear/Geist/Resend/Railway/Clerk
     all converge on white at 6–16% for borders and edges.) */
  --light:255 255 255;

  /* — surfaces: a real elevation ladder (deepest → overlay). Four levels;
       Material's dark ramp is logarithmic so beyond ~4 you cannot perceive
       the difference and you're just making work. Never pure black — it
       kills the elevation system before you start. — */
  --bg:#080b12; --bg2:#0c111b; --panel:#111826; --panel2:#18212f; --elev:#1e2837;
  --input-bg:#05080e;

  /* — LIT SURFACES: a top-anchored radial spanning ONE ramp step, at 150%
       extent so only the first slice of the falloff is visible. This is the
       single highest-leverage device in the whole redesign — it is what
       "lit from above" actually looks like in CSS.
       NOTE these are gradients, so they can only be used as `background`.
       --panel / --panel2 stay real COLOURS because color-mix() and border
       declarations need a colour and will silently fail on a gradient. — */
  --lit-panel:radial-gradient(100% 100% at 50% 0, var(--panel2) 0, var(--panel) 150%);
  --lit-raised:radial-gradient(100% 100% at 50% 0, var(--elev) 0, var(--panel2) 150%);
  --lit-inset:radial-gradient(100% 100% at 50% 0, var(--panel) 0, var(--bg2) 150%);

  /* — borders: translucent white, NOT hex. One token then composites
       correctly at every elevation instead of needing a variant per surface. — */
  --line:rgb(var(--light)/.06); --line2:rgb(var(--light)/.11);
  --line-strong:rgb(var(--light)/.16);

  /* — ink: never pure white. Light-on-dark text blooms (halation) and reads
       heavier than the same weight on light, so the top step stops short of
       #fff and body copy runs a touch lighter than you'd set on white. — */
  /* --faint measured 4.05:1 on --bg, under AA's 4.5 for normal text — it's
     used for timestamps/meta at ~12px, which IS normal text. Lifted to 4.75.
     Re-measure if --bg ever moves. */
  --txt:#eef3f9; --muted:#93a2ba; --faint:#6f7e99;
  --code-txt:#9fe7d2;

  /* — signature accent: emerald / signal-green.
       AFFORDANCE ONLY — brand mark, focus ring, primary action, progress,
       live state. Never decorative. If it isn't telling the user "you can
       act here" or "this is your progress", it must not be accent. — */
  --accent:#2dd4a0; --accent-rgb:45,212,160; --accent-deep:#1fae83; --on-accent:#032018;
  --accent-hi:#6df5c6;
  /* — secondary / info & links: sky blue — */
  --accent2:#4aa8ff; --accent2-rgb:74,168,255; --on-accent2:#03121f;

  /* — per-cert identity: DELIBERATELY NEUTRAL.
       These were blue / teal / violet / rose, which — with the accent, the
       secondary and three semantic states — put NINE hues on screen and is
       most of why the app read as "too colourful". Category is now encoded
       by LABEL, not by hue; colour is reserved for state and affordance.
       Kept as tokens (rather than deleted) so the ~41 existing call sites
       keep working and the decision stays reversible from one place. — */
  --sec:#8fa0b8; --sec-rgb:143,160,184;
  --net:#8fa0b8; --net-rgb:143,160,184;
  --ap:#8fa0b8;  --ap-rgb:143,160,184;
  --pen:#8fa0b8; --pen-rgb:143,160,184;

  /* — semantic — */
  --good:#41d992; --good-rgb:65,217,146; --on-good:#032015;
  --warn:#f5b845; --warn-rgb:245,184,69; --on-warn:#231903;
  --bad:#f7708a;  --bad-rgb:247,112,138;  --on-bad:#2a0a12;

  /* — radii: 6 and 12 carry everything. Geist ships four values total;
       Linear's most-used pair is 6/12. Both landed there independently. — */
  --r:12px; --r-sm:6px; --r-lg:16px;

  /* — motion —
       150 / 200 / 300ms for state / popover / modal. Geist states this
       verbatim and Linear's shipped numbers match exactly.
       Past ~300ms attention fractures; under 100ms reads as instant.
       --ease is Raycast's easeOutQuint. NO overshoot/spring anywhere:
       overshoot is the single strongest "childish" signal in motion, and a
       system that sometimes springs reads as unfinished. — */
  --ease:cubic-bezier(.23,1,.32,1);
  --fast:150ms; --base:200ms; --slow:300ms;

  /* — icons: 1.5 everywhere, zero exceptions (see js/icons.js) — */
  --icon-stroke:1.5;

  --mono:'JetBrains Mono','Cascadia Code','Fira Code',ui-monospace,SFMono-Regular,Menlo,monospace;
  --sans:'Inter',-apple-system,'Segoe UI',system-ui,Roboto,sans-serif;

  /* — elevation —
       Drop shadows are nearly USELESS on dark: a black shadow on a near-black
       canvas has almost no contrast. Linear ships essentially none in dark
       mode; separation there is carried entirely by borders and edge light.
       These are kept only as a faint contact/vignette and are deliberately
       far weaker than they look like they should be. Do not "fix" them by
       turning the alpha up — reach for --edge-hi instead. — */
  --shadow-sm:0 1px 2px rgb(0 0 0/.30);
  --shadow-md:0 2px 8px rgb(0 0 0/.32);
  --shadow-lg:0 8px 28px rgb(0 0 0/.40);
  --glow-accent:0 0 0 1px rgb(var(--accent-rgb)/.35), 0 0 22px rgb(var(--accent-rgb)/.22);

  /* — THE EDGE: paired light + occlusion. A bright half-pixel on top (the
       lip catching the light) and a dark pixel underneath (contact shade).
       Half-a-pixel at high alpha reads sharper on retina than 1px at low
       alpha. This pair — not the shadow — is what sells "object" over "div".
       Used inside box-shadow lists, so it drops in wherever it already was. — */
  --edge-hi:inset 0 .5px 0 0 rgb(var(--light)/.30), inset 0 -1px 0 0 rgb(0 0 0/.20);
  /* the app-background wash */
  --app-bg:radial-gradient(1200px 620px at 82% -12%, rgb(var(--accent-rgb)/.07) 0%, transparent 60%), var(--bg);
}

/* — LIGHT THEME — same token names, warm off-white surfaces, AA-tuned — */
:root[data-theme="light"]{
  --bg:#f6f8fb; --bg2:#eef2f7; --panel:#ffffff; --panel2:#f5f8fc; --elev:#ffffff;
  --input-bg:#eef2f8;

  /* On light the lighting model INVERTS: the light still comes from above,
     but a white surface can't get lighter — so the radial runs the other way
     (the top stays paper-white, the bottom picks up a touch of shade) and the
     "edge" becomes a real shadow rather than a highlight. */
  --lit-panel:radial-gradient(100% 100% at 50% 0, var(--panel) 0, var(--panel2) 150%);
  --lit-raised:radial-gradient(100% 100% at 50% 0, #ffffff 0, var(--panel2) 150%);
  --lit-inset:radial-gradient(100% 100% at 50% 0, var(--bg2) 0, var(--bg) 150%);

  /* Translucent INK, mirroring the dark theme's translucent white. */
  --line:rgb(16 24 40/.09); --line2:rgb(16 24 40/.15);
  --line-strong:rgb(16 24 40/.24);

  /* --faint was 4.22:1 on --bg (AA needs 4.5 for normal text) — deepened to 4.8. */
  --txt:#101a2b; --muted:#51617a; --faint:#656f80;
  --code-txt:#0b6b52;

  --accent:#0a8058; --accent-rgb:10,128,88; --accent-deep:#086a49; --on-accent:#ffffff;
  --accent-hi:#0d9c6c;
  --accent2:#2b7fff; --accent2-rgb:43,127,255; --on-accent2:#ffffff;

  /* Neutral, matching dark — category is a label, not a hue. */
  --sec:#63728c; --sec-rgb:99,114,140;
  --net:#63728c; --net-rgb:99,114,140;
  --ap:#63728c;  --ap-rgb:99,114,140;
  --pen:#63728c; --pen-rgb:99,114,140;

  /* These three are used as TEXT COLOUR 87 times across the sheet (".fb.ok",
     "✓ correct", "✗ your pick", hint labels, the footer warning …), so their
     contrast against the CANVAS is what matters — not just against their own
     fills. On light they measured good 2.88 / warn 3.42 / bad 4.35, all under
     AA's 4.5 for normal text; a learner with low vision could not reliably
     read their own right/wrong feedback. Deepened to 5.1 / 4.9 / 5.0.
     Darkening also improves the white-on-fill pairing, so nothing regresses.
     Dark theme already passed (10.8 / 11.1 / 7.2) and is untouched. */
  --good:#0b7a51; --good-rgb:11,122,81;  --on-good:#ffffff;
  --warn:#96620f; --warn-rgb:150,98,15;  --on-warn:#ffffff;
  --bad:#cf2438;  --bad-rgb:207,36,56;   --on-bad:#ffffff;

  /* Shadows DO work on light — unlike dark, there's contrast to cast into.
     This is the one place the dark theme's "shadows are useless" rule flips. */
  --shadow-sm:0 1px 2px rgb(16 24 40/.06), 0 1px 3px rgb(16 24 40/.09);
  --shadow-md:0 4px 12px rgb(16 24 40/.08), 0 10px 26px rgb(16 24 40/.10);
  --shadow-lg:0 12px 30px rgb(16 24 40/.12), 0 24px 56px rgb(16 24 40/.14);
  --glow-accent:0 0 0 1px rgb(var(--accent-rgb)/.3), 0 6px 18px rgb(var(--accent-rgb)/.2);
  --edge-hi:inset 0 1px 0 rgb(255 255 255/.9), inset 0 -1px 0 0 rgb(16 24 40/.05);
  --app-bg:radial-gradient(1200px 620px at 82% -12%, rgb(var(--accent-rgb)/.06) 0%, transparent 55%), var(--bg);
}
/* No-JS default: honour the OS preference until the user picks a theme.
   The JS sets an explicit data-theme, which always wins over this. */
@media (prefers-color-scheme: light){
  :root:not([data-theme="dark"]):not([data-theme="light"]){
    --bg:#f6f8fb; --bg2:#eef2f7; --panel:#ffffff; --panel2:#f5f8fc; --elev:#ffffff;
    --input-bg:#eef2f8;
    --line:#e3e9f0; --line2:#cfd9e5;
    /* --faint was 4.22:1 on --bg (AA needs 4.5 for normal text) — deepened to 4.8. */
  --txt:#101a2b; --muted:#51617a; --faint:#656f80;
    --code-txt:#0b6b52;
    --accent:#0a8058; --accent-rgb:10,128,88; --accent-deep:#086a49; --on-accent:#ffffff;
    --accent2:#2b7fff; --accent2-rgb:43,127,255; --on-accent2:#ffffff;
    --sec:#2b7fff; --sec-rgb:43,127,255; --net:#0d9488; --net-rgb:13,148,136;
    --ap:#7c5cfc; --ap-rgb:124,92,252; --pen:#e11d48; --pen-rgb:225,29,72;
    /* AA-corrected — see the note in the [data-theme="light"] block. */
    --good:#0b7a51; --good-rgb:11,122,81;  --on-good:#ffffff;
    --warn:#96620f; --warn-rgb:150,98,15;  --on-warn:#ffffff;
    --bad:#cf2438;  --bad-rgb:207,36,56;   --on-bad:#ffffff;
    --shadow-sm:0 1px 2px rgba(16,24,40,.06), 0 1px 3px rgba(16,24,40,.09);
    --shadow-md:0 4px 12px rgba(16,24,40,.08), 0 10px 26px rgba(16,24,40,.10);
    --shadow-lg:0 12px 30px rgba(16,24,40,.12), 0 24px 56px rgba(16,24,40,.14);
    --glow-accent:0 0 0 1px rgba(var(--accent-rgb),.3), 0 6px 18px rgba(var(--accent-rgb),.2);
    --edge-hi:inset 0 1px 0 rgb(255 255 255/.9), inset 0 -1px 0 0 rgb(16 24 40/.05);
    --app-bg:radial-gradient(1200px 620px at 82% -12%, rgba(var(--accent-rgb),.06) 0%, transparent 55%), var(--bg);
    /* the no-JS light default needs the inverted lighting + ink borders too,
       or an OS-light visitor who never touches the toggle gets dark-theme
       surfaces on a light canvas */
    --lit-panel:radial-gradient(100% 100% at 50% 0, var(--panel) 0, var(--panel2) 150%);
    --lit-raised:radial-gradient(100% 100% at 50% 0, #ffffff 0, var(--panel2) 150%);
    --lit-inset:radial-gradient(100% 100% at 50% 0, var(--bg2) 0, var(--bg) 150%);
    --line:rgb(16 24 40/.09); --line2:rgb(16 24 40/.15); --line-strong:rgb(16 24 40/.24);
    --accent-hi:#0d9c6c;
    --sec:#63728c; --sec-rgb:99,114,140; --net:#63728c; --net-rgb:99,114,140;
    --ap:#63728c;  --ap-rgb:99,114,140;  --pen:#63728c; --pen-rgb:99,114,140;
  }
}
*{box-sizing:border-box}
html,body{margin:0}
/* The [hidden] attribute only sets display:none via the UA stylesheet, so ANY
   author rule that sets display (e.g. .railnav{display:flex}) silently beats it
   and the element keeps its layout box while looking gone. That exact bug cost
   a debugging cycle: a hidden nav kept 210px of the rail, squeezing the exam
   panel until Restart slid under the profile chip. Make [hidden] mean hidden. */
[hidden]{display:none!important}
:root[data-theme="light"]{color-scheme:light}
:root[data-theme="dark"]{color-scheme:dark}
body{
  background-color:var(--bg);
  background-image:radial-gradient(1200px 620px at 82% -12%, rgba(var(--accent-rgb),.08) 0%, transparent 60%);
  background-attachment:fixed; background-repeat:no-repeat;
  color:var(--txt); font-family:var(--sans); font-size:15px; line-height:1.6;
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
}
/* smooth cross-fade when the theme toggles (never on first paint / reduced-motion) */
.theme-anim, .theme-anim *{transition:background-color .32s var(--ease), border-color .32s var(--ease), color .28s var(--ease), box-shadow .32s var(--ease) !important}
a{color:var(--accent2); text-decoration:none}
a:hover{text-decoration:underline}
/* ---- TYPE LADDER ----
   Tracking is not one value — it decays with size and flips positive on
   eyebrows. This is Geist's published spec, and it's more extreme than most
   dare, which is exactly why it reads expensive:
       >=40px  -.06em      24-32px -.04em
       <=20px  -.02em      copy/label/button  0
   Line-height collapses toward 1.0 as size grows.
   Weights live in a narrow 450-590 band. NO 700+ — heavy weights are a
   "generic dashboard" tell, and light-on-dark text already blooms (halation)
   so it reads heavier than the same weight would on white. Inter is variable
   here, so 510/560/590 are real, usable values, not rounded to a named stop. */
h1,h2,h3,h4{line-height:1.2; font-weight:560; letter-spacing:-.02em}
h1{font-size:32px; letter-spacing:-.04em; line-height:1.14}
h2{font-size:22px; letter-spacing:-.03em}
h3{font-size:17px}
h4{font-size:15px}
/* Numerals in stats/timers/tables must not jitter as they tick. */
.num,.stat .st-num,.exam-timer,.lb-pts,.pill b,time{font-variant-numeric:tabular-nums}
/* Base "chip" treatment for every inline <code> span site-wide — CLI output, config keys,
   file paths, port numbers, etc. Previously bare `code` only had font-family set, so an inline
   snippet in a question or explanation (the surface users spend the most time on, and roughly
   half the question bank contains shell/config output) read as plain, undifferentiated body
   text. A handful of contexts (.approach-list, .pbq-fieldguide .acc-body, .set-help) had already
   grown their own near-identical override; this is the one base rule they were all reinventing —
   those overrides still win on specificity where they intentionally differ (e.g. font-size). */
code{font-family:var(--mono); font-size:.88em; background:rgba(var(--accent-rgb),.1); color:var(--code-txt);
  padding:1px 6px; border-radius:5px; word-break:break-word}
kbd{font-family:var(--mono)}
pre code{background:none; padding:0; border-radius:0; color:inherit; font-size:inherit} /* fenced blocks own their own chrome */

/* thin, token-aware scrollbars (absorbed from the removed design-vault.css) */
*{scrollbar-width:thin; scrollbar-color:var(--line2) transparent}
::-webkit-scrollbar{width:10px; height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--line2); border-radius:99px; border:2px solid transparent; background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:var(--faint); background-clip:padding-box}

/* ===================================================================
   SHELL — fixed side rail + independently scrolling content column.

   The rail is not a styling preference; it's the structural fix for a real
   bug. A top bar occupies the same top edge as the page, so a sticky exam
   timer had to fight it for space and slid out of reach on scroll — you had
   to jump to the top of a 90-question exam to hit Pause. A rail is its own
   column: it cannot scroll away, so the timer is permanently on screen by
   construction. That also retires the --topbar-h / --exam-bar-h measuring
   hack, since nothing needs to offset around a top bar any more.

   Phones get a BOTTOM tab bar instead (thumb-reachable, the native pattern),
   and during an exam that bar becomes the exam controls — which puts the
   timer and Pause under the thumb exactly when they matter most.
   =================================================================== */
.shell{display:grid; grid-template-columns:230px minmax(0,1fr); min-height:100vh}
.content{min-width:0; display:flex; flex-direction:column}
.content > main{flex:1}

.rail{
  position:sticky; top:0; align-self:start; height:100vh; z-index:40;
  display:flex; flex-direction:column; gap:22px; padding:18px 14px;
  background:radial-gradient(120% 60% at 50% 0, var(--panel) 0, var(--bg2) 100%);
  border-right:1px solid var(--line);
  box-shadow:inset -1px 0 0 var(--edge-hi);
}
.rail .brand{padding:0 8px}
.railnav{display:flex; flex-direction:column; gap:2px}
.railnav a{
  display:flex; align-items:center; gap:11px;
  color:var(--muted)!important; text-decoration:none!important;
  padding:9px 10px; border-radius:var(--r-sm); font-size:14px; font-weight:500; letter-spacing:-.012em;
  transition:color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.railnav a .rn-i{display:flex; color:var(--faint); transition:color var(--fast) var(--ease)}
.railnav a:hover{color:var(--txt)!important; background:rgb(var(--light)/.04)}
.railnav a:hover .rn-i{color:var(--muted)}
/* The active item is the one place nav earns the accent — it's answering
   "where am I", which is affordance, not decoration. */
.railnav a.active{color:var(--txt)!important; background:rgb(var(--light)/.07); font-weight:560}
.railnav a.active .rn-i{color:var(--accent)}
.rail-foot{margin-top:auto; display:flex; align-items:center; gap:8px; padding:0 4px}
.rail-foot .profilechip{flex:1; min-width:0}

@media(max-width:900px){
  .shell{grid-template-columns:1fr}
  /* Bottom tab bar. env(safe-area-inset-bottom) keeps it clear of the iPhone
     home indicator — without it the last row of tabs is unpressable. */
  .rail{
    position:fixed; top:auto; bottom:0; left:0; right:0; height:auto; align-self:auto;
    flex-direction:row; align-items:center; gap:0; padding:0;
    border-right:0; border-top:1px solid var(--line);
    box-shadow:inset 0 1px 0 var(--edge-hi);
    background:color-mix(in srgb, var(--panel) 92%, transparent);
    backdrop-filter:blur(16px) saturate(1.3); -webkit-backdrop-filter:blur(16px) saturate(1.3);
    padding-bottom:env(safe-area-inset-bottom, 0px);
  }
  .rail .brand, .rail-foot{display:none}
  .railnav{flex-direction:row; flex:1; gap:0}
  .railnav a{
    flex:1; flex-direction:column; gap:3px; justify-content:center;
    padding:9px 2px; min-height:52px; border-radius:0; font-size:10.5px; letter-spacing:0;
  }
  .railnav a.active{background:transparent; color:var(--accent)!important}
  .railnav a.active .rn-i{color:var(--accent)}
  /* Clear the fixed bar so the footer/CTAs aren't trapped underneath it. */
  .content{padding-bottom:calc(58px + env(safe-area-inset-bottom, 0px))}
}
/* Brand lockup: the EP+ mark + wordmark. The wordmark mirrors the mark —
   "ExamPrep" in ink, "+" in accent — so the two read as one object.
   (This replaced a placeholder that was literally two block characters.) */
.brand{display:flex; align-items:center; gap:9px; color:var(--txt)!important; text-decoration:none!important}
.brand-mark{display:block; width:26px; height:26px; flex:0 0 auto}
.brand-text{font-weight:590; letter-spacing:-.02em; font-size:15.5px}
.brand-text i{color:var(--accent); font-style:normal}
/* (.topnav and its .nav-sep dividers are gone — see the SHELL block above.
   The nav is a rail now, and 5 items need no chunking.) */
/* theme toggle — lives in the rail foot; glyph set by JS */
.theme-toggle{width:34px; height:34px; flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center;
  border:1px solid var(--line2); background:var(--lit-panel); color:var(--muted); border-radius:var(--r-sm); cursor:pointer;
  font-size:16px; line-height:1; transition:border-color .15s var(--ease), color .15s var(--ease), background .15s var(--ease)}
.theme-toggle:hover{border-color:var(--accent); color:var(--accent); background:rgba(var(--accent-rgb),.08)}
.theme-toggle:active{transform:translateY(1px)}
.targets-status{margin-left:auto; display:flex; gap:8px; align-items:center}
.dot{width:9px; height:9px; border-radius:50%; background:var(--faint); box-shadow:0 0 0 3px rgba(255,255,255,.03)}
.dot.up{background:var(--good); box-shadow:0 0 8px var(--good)}
.dot.down{background:var(--bad)}

/* ---- Layout ----
   Content is capped and centred WITHIN the content column, not the viewport,
   so the rail doesn't shove the page off-centre on wide screens. */
#app{max-width:1120px; margin:0 auto; padding:34px 26px 60px; width:100%}
@media(max-width:900px){ #app{padding:22px 16px 40px} }

/* ---- Hero ---- */
.hero{margin:8px 0 28px}
/* Display sizes take the deepest tracking and collapse to ~1.0 line-height.
   The hero is set off-scale by hand — a 1.2 modular scale overshoots at the
   top end and leaves no mid-range for dense UI, so the display step is a
   deliberate jump rather than another rung. */
.hero h1{font-size:clamp(30px,4vw,44px); margin:0 0 10px; letter-spacing:-.055em; font-weight:510; line-height:1.02}
.hero p{color:var(--muted); max-width:760px; margin:0; font-size:15.5px}
.hero .pillrow{display:flex; gap:10px; flex-wrap:wrap; margin-top:16px}
.pill{
  font-size:12.5px; padding:5px 11px; border-radius:999px; border:1px solid var(--line2);
  background:var(--lit-panel); color:var(--muted); font-weight:500;
}
.pill b{color:var(--txt)}

/* ---- Icons ----
   One weight everywhere. `currentColor` means an icon inherits its context's
   colour, so it can never drift out of the palette the way an emoji did. */
.icon{flex:0 0 auto}
.i-inline{vertical-align:-.16em; margin-right:.1em}
/* An inline icon inside a DISPLAY heading.
   ico() emits a fixed 15px glyph and .i-inline's -.16em baseline nudge is tuned
   for 15.5px body text. In a clamp(30px,4vw,44px) h1 that same 15px mark lands
   in the bottom third of the letterform — measured on #/search: cap height 31px,
   icon 15px sitting 7px BELOW the baseline — so it reads as a stray dot to the
   lower-left of the word rather than as the title's icon. Three live titles do
   this (Search, Leaderboard, Pop Quiz), so it is fixed once for the pattern
   instead of three times by hand.
   Sizing in `em` ties the icon to the type it sits in, which also means the
   stroke-to-size RATIO is preserved — the icon-system rule is one uniform
   ratio, not one absolute pixel weight, and display type wants a display icon. */
.hero h1 .i-inline{width:.74em; height:.74em; vertical-align:-.045em; margin-right:.24em}

/* ---- Dashboard hero eyebrow ----
   The greeting demoted from an h1 to an eyebrow. It's context, not news. */
.dash-eyebrow{
  display:flex; align-items:center; gap:5px; margin:0 0 10px;
  font-size:11.5px; letter-spacing:.08em; text-transform:uppercase; color:var(--faint); font-weight:600;
}
.dash-eyebrow .icon{color:var(--accent)}
.hero .linkbtn{font-size:inherit}

/* ---- Exam-date dialog ---- */
.exd-card{max-width:440px; text-align:left}
.exd-list{display:flex; flex-direction:column; gap:8px}
.exd-row{
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:10px 12px; border:1px solid var(--line); border-radius:var(--r-sm); background:var(--bg2);
}
.exd-name{font-size:13.5px; font-weight:510; display:flex; flex-direction:column}
.exd-name small{color:var(--faint); font-family:var(--mono); font-size:11px; font-weight:400}
.exd-row input[type="date"]{
  background:var(--input-bg); border:1px solid var(--line2); color:var(--txt);
  border-radius:var(--r-sm); padding:7px 9px; font-family:var(--sans); font-size:13px;
  color-scheme:dark;
}
:root[data-theme="light"] .exd-row input[type="date"]{color-scheme:light}
.exd-row input[type="date"]:focus{outline:none; border-color:var(--accent)}

/* ---- Progress: doors to Results / Leaderboard ----
   These two were top-level nav items; they're grouped under Progress now, so
   this page has to actually offer the way in. */
.prog-links{display:grid; grid-template-columns:1fr 1fr; gap:10px; margin:22px 0 28px}
.prog-link{
  display:flex; align-items:center; gap:13px; padding:14px 16px;
  background:var(--lit-panel); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--edge-hi); text-decoration:none!important; color:var(--txt)!important;
  transition:border-color var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.prog-link:hover{border-color:var(--line-strong)}
.prog-link:active{transform:scale(.99)}
.pl-ico{width:34px; height:34px; flex:0 0 auto; border-radius:var(--r-sm); display:grid; place-items:center;
  color:var(--accent); background:rgb(var(--accent-rgb)/.10); box-shadow:inset 0 0 0 1px rgb(var(--accent-rgb)/.22)}
.prog-link b{display:block; font-size:14px; font-weight:560; letter-spacing:-.015em}
.prog-link small{display:block; color:var(--faint); font-size:12px; margin-top:2px; line-height:1.5}
.pl-go{margin-left:auto; color:var(--faint); flex:0 0 auto}
.prog-link:hover .pl-go{color:var(--accent)}
@media(max-width:720px){ .prog-links{grid-template-columns:1fr} }

/* ---- Practice hub ----
   Wide rows, not a card grid: each mode needs a sentence to explain the JOB it
   does, and a 3-up grid would force that sentence into a column too narrow to
   read. The point of this page is choosing well, not scanning fast. */
.practice-grid{display:flex; flex-direction:column; gap:10px}
.practice-card{
  display:flex; align-items:flex-start; gap:15px; padding:18px 20px;
  background:var(--lit-panel); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--edge-hi); text-decoration:none!important; color:var(--txt)!important;
  transition:border-color var(--fast) var(--ease), box-shadow var(--fast) var(--ease), transform var(--fast) var(--ease);
}
.practice-card:hover{border-color:var(--line-strong); box-shadow:inset 0 .5px 0 0 rgb(var(--light)/.45), inset 0 -1px 0 0 rgb(0 0 0/.2)}
.practice-card:active{transform:scale(.995); box-shadow:inset 0 1px 4px rgb(0 0 0/.28)}
.pc-ico{
  width:38px; height:38px; flex:0 0 auto; border-radius:var(--r-sm); display:grid; place-items:center;
  color:var(--accent); background:rgb(var(--accent-rgb)/.10); box-shadow:inset 0 0 0 1px rgb(var(--accent-rgb)/.22);
}
.pc-body{flex:1; min-width:0; display:block}
.pc-t{display:flex; align-items:center; gap:9px; flex-wrap:wrap; font-size:16px; font-weight:560; letter-spacing:-.025em}
/* One-word statement of the JOB this mode does — the fastest way to choose. */
.pc-tag{
  font-size:10px; font-weight:600; letter-spacing:.07em; text-transform:uppercase;
  color:var(--faint); border:1px solid var(--line2); border-radius:4px; padding:2px 6px;
  white-space:nowrap; flex:none;
}
.pc-lead{display:block; font-size:13.5px; color:var(--accent); margin-top:2px; letter-spacing:-.01em}
.pc-desc{display:block; font-size:13.5px; color:var(--muted); margin-top:7px; line-height:1.6; max-width:62ch}
.pc-meta{display:block; font-size:12px; color:var(--faint); margin-top:9px; font-variant-numeric:tabular-nums}
.pc-go{
  flex:0 0 auto; align-self:center; display:inline-flex; align-items:center; gap:6px;
  font-size:13px; font-weight:560; color:var(--accent); white-space:nowrap; letter-spacing:-.01em;
}
@media(max-width:720px){
  .practice-card{flex-wrap:wrap; padding:16px}
  .pc-go{width:100%; justify-content:flex-end; margin-top:4px}
  .pc-desc{max-width:none}
}

/* ---- Range callout ---- */
.range-callout{
  margin:0 0 22px; padding:16px 18px; border-radius:var(--r); border:1px solid rgba(var(--accent-rgb),.35);
  background:linear-gradient(135deg, rgba(var(--accent-rgb),.08), rgba(var(--accent-rgb),.02));
}
.range-callout .rc-h{font-size:15px; font-weight:700; margin-bottom:6px}
.range-callout p{color:var(--muted); font-size:13.5px; max-width:680px; margin:0 0 12px}

/* ---- Filter bar ---- */
.filters{
  display:flex; flex-wrap:wrap; gap:14px; align-items:center; margin:0 0 22px;
  padding:14px; background:var(--lit-panel); border:1px solid var(--line); border-radius:var(--r);
}
.seg{display:flex; gap:4px; background:var(--bg2); padding:4px; border-radius:10px; border:1px solid var(--line)}
.seg button{
  border:0; background:transparent; color:var(--muted); font-family:var(--sans);
  padding:7px 14px; border-radius:7px; cursor:pointer; font-weight:600; font-size:13.5px;
  transition:.15s background,.15s color;
}
.seg button:hover{color:var(--txt)}
.seg button.on{background:var(--panel2); color:var(--txt)}
.seg button.on[data-cert="security+"]{box-shadow:inset 0 -2px 0 var(--sec)}
.seg button.on[data-cert="pentest+"]{box-shadow:inset 0 -2px 0 var(--pen)}
.chips{display:flex; gap:7px; flex-wrap:wrap}
.chip{
  font-size:12.5px; padding:6px 11px; border-radius:999px; cursor:pointer; user-select:none;
  border:1px solid var(--line2); background:var(--bg2); color:var(--muted); font-weight:500;
}
.chip:hover{border-color:var(--accent); color:var(--txt)}
.chip.on{background:var(--accent); color:var(--on-accent); border-color:var(--accent); font-weight:600}
.search{margin-left:auto; position:relative}
.search input{
  background:var(--bg2); border:1px solid var(--line2); color:var(--txt); border-radius:9px;
  padding:9px 13px 9px 34px; width:230px; font-family:var(--sans); font-size:14px;
}
.search input:focus{outline:none; border-color:var(--accent)}
.search::before{content:"\1F50D"; position:absolute; left:10px; top:8px; opacity:.5; font-size:13px}

/* ---- Card grid ---- */
/* min() so a container NARROWER than the track floor gets a full-width column instead of a
   330px one it cannot hold. Bare minmax(330px,1fr) is a hard minimum: at 375px the
   `.prog-blank` panel is 305px wide and the grid overflowed it by exactly 25px. Identical
   behaviour at any container ≥330px. */
.grid{display:grid; grid-template-columns:repeat(auto-fill,minmax(min(330px,100%),1fr)); gap:16px}
.card{
  position:relative; background:var(--lit-panel);
  border:1px solid var(--line); border-radius:var(--r); padding:18px 18px 16px;
  cursor:pointer; transition:border-color .18s var(--ease),transform .18s var(--ease),box-shadow .18s var(--ease); overflow:hidden;
  box-shadow:var(--shadow-sm), var(--edge-hi);
}
/* NO HOVER LIFT. Verified absent across Linear, Raycast, Vercel, Resend,
   Railway, Clerk and Stripe — all seven. A card that jumps toward the cursor
   is the most common "generic bootstrap" tell there is. Hover raises the
   surface's own light instead (the border and edge brighten); the object
   stays put. Press moves DOWN, the way a real button does. */
.card:hover{border-color:var(--line-strong); box-shadow:var(--shadow-sm), inset 0 .5px 0 0 rgb(var(--light)/.45), inset 0 -1px 0 0 rgb(0 0 0/.2)}
.card:active{transform:scale(.995); box-shadow:inset 0 1px 4px rgb(0 0 0/.28)}
/* Category stripe — now neutral (see the --sec/--net/--ap/--pen note in the
   token block). Category is carried by the badge LABEL, not by hue. */
.card::before{content:""; position:absolute; left:0; top:0; bottom:0; width:3px;
  background:var(--cstripe, var(--sec)); opacity:.5; transition:opacity var(--fast) var(--ease)}
.card:hover::before{opacity:1}
.card[data-cert="pentest+"]{--cstripe:var(--pen)}
.card[data-cert="network+"]{--cstripe:var(--net)}
.card[data-cert="a+"],.card[data-cert="a"]{--cstripe:var(--ap)}
/* Both children are single unbreakable words ("Security+" 68px + "SY0-701" 31px + 8px gap
   = 107 inside a 102px card at 320px). Nothing can shrink, so the honest fix is to let the
   pair wrap onto two lines rather than clip the exam code. */
.card .ctop{display:flex; justify-content:space-between; align-items:center; gap:8px; margin-bottom:10px; flex-wrap:wrap}
.card .id{font-family:var(--mono); font-size:12px; color:var(--faint); letter-spacing:.5px}
.card h3{font-size:16.5px; margin:2px 0 7px}
.card .blurb{color:var(--muted); font-size:13.5px; min-height:38px}
.card .cmeta{display:flex; gap:8px; flex-wrap:wrap; margin-top:13px; align-items:center}
.badge{font-size:11px; font-weight:600; padding:3px 9px; border-radius:999px; letter-spacing:.3px}
.badge.dom{background:rgba(var(--sec-rgb),.14); color:var(--sec); border:1px solid rgba(var(--sec-rgb),.3)}
.card[data-cert="pentest+"] .badge.dom{background:rgba(var(--pen-rgb),.14); color:var(--pen); border:1px solid rgba(var(--pen-rgb),.3)}
.card[data-cert="network+"] .badge.dom{background:rgba(var(--net-rgb),.14); color:var(--net); border:1px solid rgba(var(--net-rgb),.3)}
.card[data-cert="a+"] .badge.dom,.card[data-cert="a"] .badge.dom{background:rgba(var(--ap-rgb),.14); color:var(--ap); border:1px solid rgba(var(--ap-rgb),.3)}
.badge.diff{border:1px solid var(--line2); color:var(--muted)}
.badge.diff[data-d="Beginner"],.badge.diff[data-d="Easy"]{color:var(--good); border-color:rgba(var(--good-rgb),.4)}
.badge.diff[data-d="Intermediate"],.badge.diff[data-d="Medium"]{color:var(--warn); border-color:rgba(var(--warn-rgb),.4)}
.badge.diff[data-d="Advanced"],.badge.diff[data-d="Expert"],.badge.diff[data-d="Hard"]{color:var(--pen); border-color:rgba(var(--pen-rgb),.4)}
.time{color:var(--faint); font-size:12px; font-family:var(--mono)}
.card .time{margin-left:auto}
.card .done{
  position:absolute; top:14px; right:14px; width:20px; height:20px; border-radius:50%;
  background:var(--good); color:var(--on-accent); display:none; align-items:center; justify-content:center;
  font-size:13px; font-weight:800;
}
.card.complete .done{display:flex}
.card.complete{opacity:.82}

.empty{text-align:center; color:var(--faint); padding:60px 0}

/* ---- Lab detail ---- */
.back{display:inline-flex; gap:7px; align-items:center; color:var(--muted); margin-bottom:16px; cursor:pointer}
.back:hover{color:var(--txt)}
.lab-head, .mission-briefing{position:relative; border:1px solid var(--line); border-radius:var(--r-lg); padding:24px 26px;
  background:var(--lit-panel); margin-bottom:18px; box-shadow:var(--shadow-md), var(--edge-hi); overflow:hidden}
.lab-head::before, .mission-briefing::before{content:""; position:absolute; left:0; top:0; bottom:0; width:3px; background:var(--accent)}
.lab-head .id, .mission-briefing .id{font-family:var(--mono); color:var(--faint); font-size:12.5px; letter-spacing:.5px}
.lab-head h1, .mission-briefing h1{font-size:25px; margin:6px 0 12px}
.lab-head .row, .mission-briefing .row{display:flex; gap:10px; flex-wrap:wrap; align-items:center; margin-bottom:16px}
.cert-tag{font-family:var(--mono); font-size:11.5px; font-weight:600; padding:4px 10px; border-radius:6px}
.cert-tag.security\+{background:rgba(var(--sec-rgb),.16); color:var(--sec)}
.cert-tag.pentest\+{background:rgba(var(--pen-rgb),.16); color:var(--pen)}
.cert-tag.network\+{background:rgba(var(--net-rgb),.16); color:var(--net)}
.cert-tag.a\+{background:rgba(var(--ap-rgb),.16); color:var(--ap)}
.lab-head .desc, .mission-briefing .desc{color:var(--muted)}
.mb-label{font-size:11px; font-weight:800; letter-spacing:1.5px; color:var(--accent); margin-bottom:6px}
.mb-skills{margin-top:14px}
.mb-skills b{font-size:12.5px; color:var(--muted)}
.chip-row{display:flex; flex-wrap:wrap; gap:6px; margin-top:6px}
.chip-row .chip{cursor:default}
.chip-row .chip:hover{border-color:var(--line2); color:inherit}
.approach-list{margin:0; padding-left:18px; color:var(--muted); font-size:13.5px}
.approach-list li{margin:6px 0}
.approach-list code{font-family:var(--mono); font-size:12px; background:var(--bg2); padding:1px 6px; border-radius:4px; color:var(--code-txt)}
.debrief-panel{border:1px solid var(--line); border-left:3px solid var(--good); border-radius:var(--r); padding:16px 20px; background:var(--lit-panel); margin:18px 0}
.debrief-panel h3{margin:0 0 10px; font-size:15px}
.debrief-panel ul{margin:0 0 12px; padding-left:18px; color:var(--muted)}
.debrief-panel li{margin:5px 0}
.debrief-panel .pbq-fieldguide{margin:0}
.objbox{margin-top:16px; padding:14px 16px; background:var(--bg2); border:1px solid var(--line); border-radius:var(--r-sm)}
.objbox h4{margin:0 0 8px; font-size:12px; text-transform:uppercase; letter-spacing:1px; color:var(--accent)}
.objbox ul{margin:0; padding-left:18px; color:var(--muted); font-size:13.5px}
.objbox li{margin:3px 0}
.objbox .mapped{font-family:var(--mono); color:var(--faint); font-size:12px}

/* The primary action. Lit like a physical object: gradient runs top-down
   (light above), bright inner highlight on the top lip, occlusion underneath. */
.launch{
  display:inline-flex; gap:9px; align-items:center; margin-top:16px; padding:11px 18px;
  background:linear-gradient(180deg,var(--accent-hi) 0%,var(--accent) 100%); color:var(--on-accent); border-radius:var(--r-sm);
  font-weight:560; letter-spacing:-.01em; cursor:pointer; border:0; font-size:14px; text-decoration:none!important;
  box-shadow:inset 0 1px 0 rgb(var(--light)/.35), inset 0 -1px 0 rgb(0 0 0/.22), 0 1px 2px rgb(0 0 0/.4);
  transition:transform var(--fast) var(--ease), box-shadow var(--fast) var(--ease), filter var(--fast) var(--ease);
}
.launch:hover{filter:brightness(1.06)}
/* THE PRESS: scale down AND the edge highlight goes out — a pressed surface
   cannot catch the light from above. Most implementations skip the second
   half, which is exactly why their buttons feel dead. */
.launch:active{transform:scale(.97); box-shadow:inset 0 1px 3px rgb(0 0 0/.45)}
.launch.ghost{background:transparent; color:var(--accent); border:1px solid rgba(var(--accent-rgb),.5); box-shadow:none}
.launch.ghost:hover{background:rgba(var(--accent-rgb),.1); border-color:var(--accent); box-shadow:var(--glow-accent)}

/* ---- Steps ---- */
.step{border:1px solid var(--line); border-radius:var(--r); margin-bottom:14px; background:var(--lit-panel); overflow:hidden}
.step-h{display:flex; gap:12px; align-items:flex-start; padding:16px 18px; cursor:pointer}
.step-n{
  flex:0 0 auto; width:26px; height:26px; border-radius:7px; background:var(--bg2); border:1px solid var(--line2);
  display:flex; align-items:center; justify-content:center; font-family:var(--mono); font-size:13px; color:var(--muted); font-weight:600;
}
.step.done .step-n{background:var(--good); color:var(--on-accent); border-color:var(--good)}
.step-h h3{font-size:15.5px; margin:1px 0 0; flex:1}
.step-toggle{color:var(--faint); transition:.2s transform}
.step.open .step-toggle{transform:rotate(90deg)}
.step-body{display:none; padding:0 18px 18px 56px}
.step.open .step-body{display:block}
.step-body p{margin:0 0 12px; color:var(--txt)}
.step-body .muted{color:var(--muted)}

/* flow-root so the floated copy button below is contained: on a coarse pointer that
   button is 44px tall, which in a one-line command block is taller than the text and
   would otherwise hang out of the box. */
.cmd{
  position:relative; display:flow-root;
  background:var(--input-bg); border:1px solid var(--line2); border-radius:var(--r-sm);
  padding:12px 14px; font-family:var(--mono); font-size:13px; color:var(--code-txt); margin:8px 0;
  white-space:pre-wrap; word-break:break-word;
}
/* The copy button FLOATS rather than sitting absolutely on top of the command.
   It was absolutely positioned with 44px of right padding reserved for it, but the
   button occupies 53px -- so every command block under-reserved, and measured across
   38 blocks at 375px, 14 of them had code running underneath it, the worst by 66px.
   A learner reading a half-hidden command is being shown something untrue.
   Float beats simply widening the reserve: padding shortens EVERY line, while a float
   only shortens the lines actually beside the button. Measured on the worst block,
   float gave 9px more code width per line than a 64px reserve AND 21px less height --
   on a 375px screen both matter. */
.cmd .copy{
  float:right; margin:0 0 4px 10px; background:var(--panel2); border:1px solid var(--line2);
  color:var(--muted); border-radius:6px; padding:4px 8px; font-size:11px; cursor:pointer; font-family:var(--sans);
}
.cmd .copy:hover{color:var(--txt); border-color:var(--accent)}
.cmd .copy.ok{color:var(--good); border-color:var(--good)}

/* hints */
.hints{margin:10px 0}
/* Easy tier: hints shown open, framed so they read as "provided" not "revealed" */
.hints[data-mode="open"]{
  padding:10px 13px; border:1px dashed rgba(var(--warn-rgb),.3); border-radius:8px;
  background:rgba(var(--warn-rgb),.05);
}
.hints[data-mode="open"] .hint-lbl{color:var(--warn); font-weight:700; font-size:12.5px; margin-bottom:2px}
.hints[data-mode="open"] .hint{margin-top:6px}
/* Hard tier: the gate the learner must click before any hint appears */
.hint-gate{
  background:rgba(var(--pen-rgb),.08); border:1px solid rgba(var(--pen-rgb),.32); color:var(--pen);
  padding:8px 13px; border-radius:8px; cursor:pointer; font-weight:600; font-size:13px; font-family:var(--sans);
}
.hint-gate:hover{background:rgba(var(--pen-rgb),.16)}
.hint-btn{
  background:rgba(var(--warn-rgb),.08); border:1px solid rgba(var(--warn-rgb),.3); color:var(--warn);
  padding:8px 13px; border-radius:8px; cursor:pointer; font-weight:600; font-size:13px; font-family:var(--sans);
}
.hint-btn:hover{background:rgba(var(--warn-rgb),.15)}
.hint{
  margin-top:8px; padding:11px 14px; border-left:3px solid var(--warn); background:var(--bg2);
  border-radius:0 8px 8px 0; color:var(--muted); font-size:13.5px;
}
.hint b{color:var(--warn)}

/* teaching note */
.note{
  margin:10px 0; padding:12px 14px 12px 40px; position:relative; background:rgba(var(--accent-rgb),.06);
  border:1px solid rgba(var(--accent-rgb),.22); border-radius:var(--r-sm); color:var(--muted); font-size:13.5px;
}
.note::before{content:"\1F4A1"; position:absolute; left:13px; top:11px}
.note b{color:var(--accent)}

/* answer check */
.check{margin:12px 0 4px; padding:13px 15px; background:var(--bg2); border:1px solid var(--line2); border-radius:var(--r-sm)}
.check .q{font-weight:600; margin-bottom:9px; font-size:14px}
.check .row{display:flex; gap:8px; flex-wrap:wrap}
/* min-width:0 is load-bearing, not tidying. A flex item's automatic minimum size is its
   MIN-CONTENT width, and for an <input> that is its `size` (20 chars ≈ 216px) — so `flex:1`
   alone could not shrink it. In a 235px row the input + gap + button measured 298px and
   pushed the Check button's right edge to 387px on a 375px screen: the control that submits
   a lab answer rendered as "Chec", clipped off-screen. Verified by measurement, not by eye. */
.check input{
  flex:1 1 120px; min-width:0;
  background:var(--input-bg); border:1px solid var(--line2); color:var(--txt); border-radius:7px;
  padding:9px 12px; font-family:var(--mono); font-size:13px;
}
.check input:focus{outline:none; border-color:var(--accent)}
.check button{
  background:var(--accent2); color:var(--on-accent2); border:0; border-radius:7px; padding:9px 16px;
  font-weight:700; cursor:pointer; font-family:var(--sans);
}
.check .fb{margin-top:9px; font-size:13.5px; font-weight:600; display:none}
.check .fb.ok{display:block; color:var(--good)}
.check .fb.no{display:block; color:var(--bad)}

.step-foot{margin-top:14px}
.mark{
  background:transparent; border:1px solid var(--line2); color:var(--muted); border-radius:8px;
  padding:8px 14px; cursor:pointer; font-weight:600; font-family:var(--sans); font-size:13px;
  transition:.15s border-color,.15s color,.15s transform;
}
.mark:hover{border-color:var(--good); color:var(--good)}
.mark:active:not(:disabled){transform:translateY(1px)}
.step.done .mark{border-color:var(--good); color:var(--good)}

/* solution / takeaways */
.accordion{border:1px solid var(--line); border-radius:var(--r); margin:18px 0; background:var(--lit-panel)}
.acc-h{display:flex; align-items:center; gap:10px; padding:15px 18px; cursor:pointer; font-weight:700}
.acc-h .lock{color:var(--warn); font-size:13px; font-weight:600; margin-left:auto; font-family:var(--mono)}
.acc-body{display:none; padding:4px 18px 18px; border-top:1px solid var(--line); color:var(--muted)}
.accordion.open .acc-body{display:block}
.acc-body p{color:var(--txt)}

/* ---- inline "here's the right answer" reveal on a wrong match row / fill field / order slot ----
   Grading used to only tint the row red; the learner never saw WHICH answer belonged there, so a
   prose explanation naming a different term read as "the options don't match the explanation." ---- */
.pbq-answer-reveal{display:flex; align-items:baseline; gap:6px; margin-top:6px; font-size:12.5px;
  color:var(--good); font-weight:600; line-height:1.45}
.pbq-answer-reveal b{color:var(--good); font-weight:800}
.pbq-answer-reveal svg{flex:0 0 auto; width:14px; height:14px}
.pbq-order li .pbq-answer-reveal{margin-top:4px}

/* ---- command breakdown ("Understand the command — part by part") ---- */
.cmd-breakdown{margin:10px 0 4px; background:var(--bg2)}
.cmd-breakdown .acc-h{padding:11px 14px; font-weight:600; font-size:13.5px; color:var(--accent2, var(--accent))}
.cmd-breakdown .acc-h .lock{color:var(--faint); font-weight:600}
.cmd-breakdown .acc-body{padding:0 14px 14px}
.cbk-cmd{display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:10px 0 12px; padding:9px 12px;
  background:var(--bg); border:1px solid var(--line2); border-radius:8px}
.cbk-cmd code{font-family:var(--mono); font-size:13.5px; color:var(--txt); word-break:break-all}
.cbk-fam{font-size:10px; font-weight:700; letter-spacing:.4px; text-transform:uppercase; color:var(--faint);
  border:1px solid var(--line2); border-radius:6px; padding:2px 7px; white-space:nowrap}
.cbk-row{display:flex; gap:12px; align-items:baseline; padding:7px 0; border-top:1px solid var(--line)}
.cbk-row:first-of-type{border-top:0}
.cbk-tok{flex:0 0 auto; min-width:92px; max-width:200px; font-family:var(--mono); font-size:13px; font-weight:700;
  color:var(--accent); background:rgba(var(--accent-rgb),.08); border-radius:6px; padding:3px 8px; word-break:break-all}
.cbk-gloss{flex:1; min-width:0; color:var(--txt); font-size:13.5px; line-height:1.5}
.cbk-gloss b{color:var(--accent)}
@media(max-width:560px){ .cbk-row{flex-direction:column; gap:3px} .cbk-tok{max-width:none} }

/* SAFETY NET, now global. The 375px sweep found three surfaces overflowing by up to 274px
   from tokens with NO break opportunity — arrow chains ("Processing→Charging→…") and a full
   IPv6 address, each one enormous "word" to the line-breaker. The 320px sweep then found a
   fourth in a container the targeted list did not cover ("accept/avoid/transfer/mitigate)."
   at 201px inside a 182px <li>), which is the argument for making this a baseline rather
   than a list to keep extending: a slash list, an address, a hash or a URL cannot be spaced,
   so ANY element rendering authored text has to be able to break one. `break-word` (not
   `anywhere`) so intrinsic min-content sizing is left alone — only actual overflow breaks. */
body{overflow-wrap:break-word}
.takeaways{border:1px solid var(--line); border-left:3px solid var(--accent); border-radius:var(--r); padding:16px 20px; background:var(--lit-panel); margin:18px 0}
.takeaways h3{margin:0 0 10px; font-size:15px}
.takeaways ul{margin:0; padding-left:18px; color:var(--muted)}
.takeaways li{margin:5px 0}

.refs{margin:18px 0}
.refs h4{font-size:12px; text-transform:uppercase; letter-spacing:1px; color:var(--faint); margin:0 0 8px}
.refs a{display:inline-block; margin:0 12px 6px 0; font-size:13.5px}

/* min-width:0 lets each anchor shrink past its longest word; without it the two
   flex:1 links measured 149+146+12 = 307 inside 288 at 320px. wrap is the floor:
   below ~300px they stack rather than clip. */
.lab-nav{display:flex; justify-content:space-between; gap:12px; margin-top:24px; flex-wrap:wrap}
.lab-nav a{min-width:0; flex:1 1 130px}
.lab-nav a{
  flex:1; border:1px solid var(--line); border-radius:var(--r); padding:13px 16px; background:var(--lit-panel);
  color:var(--muted)!important; text-decoration:none!important;
}
.lab-nav a:hover{border-color:var(--line2); color:var(--txt)!important}
.lab-nav a.next{text-align:right}
.lab-nav small{display:block; color:var(--faint); font-size:11px; text-transform:uppercase; letter-spacing:1px}

/* ---- Progress page ---- */
.prog-top{display:flex; gap:24px; flex-wrap:wrap; align-items:center; margin-bottom:28px}
.ring{--p:0; width:132px; height:132px; border-radius:50%;
  background:conic-gradient(var(--accent) calc(var(--p)*1%), var(--line2) 0);
  display:flex; align-items:center; justify-content:center; flex:0 0 auto;
  box-shadow:0 0 26px rgba(var(--accent-rgb),.18)}
.ring .inner{width:104px; height:104px; border-radius:50%; background:var(--lit-panel); box-shadow:var(--edge-hi);
  display:flex; flex-direction:column; align-items:center; justify-content:center}
.ring .pct{font-size:28px; font-weight:800; font-family:var(--mono); color:var(--accent)}
.ring .lbl{font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:1px}
.prog-cert{margin:26px 0}
.prog-cert h2{font-size:18px; margin:0 0 4px}
.prog-cert .sub{color:var(--faint); font-size:13px; margin-bottom:14px}
.dom-row{display:flex; align-items:center; gap:14px; margin:9px 0}
.dom-row .dl{flex:0 0 320px; font-size:13.5px; color:var(--muted)}
.dom-row .dl b{color:var(--txt)}
.bar{flex:1; height:9px; background:var(--line); border-radius:99px; overflow:hidden}
.bar > i{display:block; height:100%; background:linear-gradient(90deg,var(--accent2),var(--accent)); border-radius:99px}
.dom-row .dc{flex:0 0 54px; text-align:right; font-family:var(--mono); font-size:12.5px; color:var(--muted)}
.reset{margin-top:24px; background:transparent; border:1px solid var(--line2); color:var(--muted); padding:9px 15px; border-radius:8px; cursor:pointer}
.reset:hover{border-color:var(--bad); color:var(--bad)}

/* ---- Start page ---- */
.doc{max-width:820px}
.doc h2{font-size:21px; margin:26px 0 10px; border-bottom:1px solid var(--line); padding-bottom:8px}
.doc h3{font-size:16px; margin:18px 0 8px}
.doc p,.doc li{color:var(--muted)}
.doc li{margin:4px 0}
.doc .cmd{color:var(--code-txt)}
.callout{padding:13px 16px; border-radius:var(--r-sm); border:1px solid var(--line2); background:var(--lit-panel); margin:14px 0}
.callout.warn{border-color:rgba(var(--warn-rgb),.4); background:rgba(var(--warn-rgb),.06)}

/* ---- Toast ---- */
.toast{
  position:fixed; bottom:24px; left:50%; transform:translateX(-50%) translateY(20px);
  background:var(--panel2); border:1px solid var(--line2); color:var(--txt); padding:11px 18px;
  border-radius:10px; opacity:0; transition:.25s; pointer-events:none; z-index:80; font-size:14px;
}
.toast.show{opacity:1; transform:translateX(-50%) translateY(0)}

/* ---- Footer ---- */
.footer{border-top:1px solid var(--line); padding:18px 22px; display:flex; justify-content:space-between;
  gap:10px 16px; flex-wrap:wrap; color:var(--faint); font-size:12.5px; max-width:1180px; margin:0 auto}
/* The trademark disclaimer is legally load-bearing but visually subordinate —
   it should be findable, not shouted. */
.footer-note{flex:1 1 100%; order:3; font-size:11.5px; color:var(--faint); opacity:.75; line-height:1.5}
/* Ownership + legal links. order:2 keeps them ABOVE the trademark note and the lab
   warning, since these are the ones a store reviewer or a buyer goes looking for.
   The links carry a 24px min-height for WCAG 2.5.8 at every pointer type, matching
   the AA baseline the rest of the app holds outside any media query. */
.footer-legal{flex:1 1 100%; order:2; font-size:12px; color:var(--muted); line-height:1.7}
.footer-legal a{color:var(--accent); text-decoration:none; display:inline-block; min-height:24px}
.footer-legal a:hover{text-decoration:underline}
.footer-warn{color:var(--warn)}
@media(max-width:680px){ .footer{padding:16px 14px; font-size:12px} }

@media(max-width:680px){
  .dom-row .dl{flex-basis:160px}
  .search input{width:150px}
  /* Give the display type room to breathe on a narrow column. */
  .hero h1{letter-spacing:-.04em}
}

/* ===================================================================
   PBQ engine — cards, task controls, grading
   =================================================================== */
.pcard::before{background:var(--cstripe, var(--accent))}
.pcard .pbq-cert{font-family:var(--mono); font-size:12px; font-weight:600}
.badge.pdom{background:var(--bg2); color:var(--muted); border:1px solid var(--line2)}
.pbq-badge{
  position:absolute; top:13px; right:13px; background:var(--panel2); border:1px solid var(--line2);
  color:var(--muted); font-family:var(--mono); font-size:11.5px; font-weight:600; padding:2px 8px; border-radius:7px;
}
.pbq-badge.full{background:var(--good); color:var(--on-accent); border-color:var(--good)}

/* task block */
.pbq-task{border:1px solid var(--line); border-radius:var(--r); margin-bottom:16px; background:var(--lit-panel); padding:18px 20px}
.pbq-q{font-size:15.5px; font-weight:600; margin-bottom:14px; display:flex; gap:10px; align-items:flex-start}
.pbq-qn{flex:0 0 auto; width:24px; height:24px; border-radius:7px; background:var(--bg2); border:1px solid var(--line2);
  display:flex; align-items:center; justify-content:center; font-family:var(--mono); font-size:13px; color:var(--accent); font-weight:700}
/* The prompt prose MUST be one single flex child. fmtInline() emits bare <strong>/<code>/<a>
   siblings, so without this wrapper the browser blocks each text run into its own anonymous flex
   item and the sentence renders as columns separated by the 10px gap. min-width:0 overrides the
   flex default of min-width:auto so a long <code> chip or URL wraps instead of overflowing. */
.pbq-qtext{flex:1 1 auto; min-width:0}

/* single / multi */
.pbq-opts{display:flex; flex-direction:column; gap:8px}
.pbq-opt{display:flex; gap:11px; align-items:flex-start; padding:11px 14px; border:1px solid var(--line2);
  border-radius:var(--r-sm); background:var(--bg2); cursor:pointer; transition:.14s border-color,.14s background,.14s box-shadow}
.pbq-opt:hover{border-color:var(--accent); background:rgba(var(--accent-rgb),.05); box-shadow:0 0 0 2px rgba(var(--accent-rgb),.08)}
.pbq-opt input{margin-top:3px; accent-color:var(--accent); cursor:pointer}
/* The option TEXT is the flex item that must shrink; min-width:auto pinned it to its
   longest word (221px) and pushed the label 23px past the card at 320px. */
.pbq-opt>span{min-width:0; flex:1 1 auto}
.pbq-opt.correct{border-color:var(--good); background:rgba(var(--good-rgb),.1); box-shadow:0 0 0 2px rgba(var(--good-rgb),.18)}
.pbq-opt.correct::after{content:"\2713 correct"; margin-left:auto; color:var(--good); font-size:12px; font-weight:700; font-family:var(--mono)}
.pbq-opt.wrong{border-color:var(--bad); background:rgba(var(--bad-rgb),.12); box-shadow:0 0 0 2px rgba(var(--bad-rgb),.18)}
.pbq-opt.wrong::after{content:"\2717 your pick"; margin-left:auto; color:var(--bad); font-size:12px; font-weight:700; font-family:var(--mono)}

/* order */
.pbq-order{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:8px}
.pbq-order li{display:flex; gap:12px; align-items:center; padding:11px 14px; border:1px solid var(--line2);
  border-radius:var(--r-sm); background:var(--bg2)}
.ord-move{display:flex; flex-direction:column; gap:2px}
.ord-move button{width:24px; height:18px; line-height:1; padding:0; border:1px solid var(--line2); background:var(--panel2);
  color:var(--muted); border-radius:5px; cursor:pointer; font-size:9px}
.ord-move button:hover{color:var(--accent); border-color:var(--accent)}
.ord-txt{font-size:14px}
.pbq-order li.correct{border-color:var(--good); background:rgba(var(--good-rgb),.1)}
.pbq-order li.wrong{border-color:var(--bad); background:rgba(var(--bad-rgb),.08)}
.pbq-order li.correct .ord-txt::after{content:" \2713"; color:var(--good); font-weight:700}
.pbq-order li.wrong .ord-txt::after{content:" \2717 wrong position"; color:var(--bad); font-size:12px; font-family:var(--mono)}

/* match */
.pbq-match,.pbq-fill{width:100%; border-collapse:collapse}
.pbq-match td,.pbq-fill td{padding:9px 10px; vertical-align:middle; border-bottom:1px solid var(--line)}
.pbq-match .ml{font-size:14px; color:var(--txt); width:62%}
.pbq-match select,.pbq-fill input{width:100%; background:var(--input-bg); border:1px solid var(--line2); color:var(--txt);
  border-radius:7px; padding:9px 11px; font-family:var(--sans); font-size:13.5px}
.pbq-match select:focus,.pbq-fill input:focus{outline:none; border-color:var(--accent)}
/* NARROW SCREENS: stack label above control. `.ml{width:62%}` is only a hint to an
   auto-layout table — a long label takes more, and at 320px it squeezed the select cell to a
   22px box the learner cannot read or operate (the worst finding of the width sweep, and one
   the audit missed entirely because it skips <select>). Stacking is the standard responsive
   -table pattern and gives the control a full line instead of a sliver. */
@media (max-width: 420px){
  /* Give the ticket text and its verdict select a full line each. flex-wrap alone
     stopped the ROW overflowing but starved .triage-body to a 13px column beside a
     307px select -- narrower is not the same as fixed. */
  .triage-body{flex:1 1 100%}
  .triage-select{flex:1 1 100%; width:100%}
  .pbq-match tr,.pbq-fill tr{display:block; padding:8px 0; border-bottom:1px solid var(--line)}
  .pbq-match td,.pbq-fill td{display:block; width:auto; padding:3px 0; border-bottom:0}
  .pbq-match .ml,.pbq-fill .ml{width:auto; padding-bottom:5px}
}
.pbq-match tr.correct .mr select{border-color:var(--good); background:rgba(var(--good-rgb),.08)}
.pbq-match tr.wrong .mr select{border-color:var(--bad); background:rgba(var(--bad-rgb),.06)}
.pbq-match tr.correct .ml::before{content:"\2713 "; color:var(--good); font-weight:700}
.pbq-match tr.wrong .ml::before{content:"\2717 "; color:var(--bad); font-weight:700}

/* fill */
.pbq-fill .fl{font-size:13.5px; color:var(--muted); width:48%}
.pbq-fill input{font-family:var(--mono)}
.pbq-fill input.correct{border-color:var(--good); background:rgba(var(--good-rgb),.08); color:var(--good)}
.pbq-fill input.wrong{border-color:var(--bad); background:rgba(var(--bad-rgb),.06)}

/* explanation (hidden until graded) */
.pbq-exp{display:none; margin-top:14px; padding:13px 16px 13px 16px; border-left:3px solid var(--accent2);
  background:var(--bg2); border-radius:0 8px 8px 0}
.pbq-exp.show{display:block}
.pbq-exp > b{color:var(--accent2); font-size:13px}
.pbq-exp p{margin:6px 0 0; color:var(--muted); font-size:13.5px}

/* actions + result */
.pbq-actions{display:flex; gap:12px; align-items:center; flex-wrap:wrap; margin:20px 0 4px}
.pbq-result{font-weight:600; font-size:14px}
.pbq-result.ok{color:var(--good)}
.pbq-result.partial{color:var(--warn)}
.pbq-result b{font-family:var(--mono)}

/* concept primer (collapsible "walk me through it") */
.pbq-primer{border-left:3px solid var(--accent); margin:0 0 18px}
.pbq-primer .acc-h{color:var(--accent)}
.pbq-primer .acc-body p{color:var(--txt)}

/* ASCII diagram box (network topology, firewall zones, folder trees) */
.pbq-diagram{margin:12px 0}
.pbq-diagram pre{
  margin:0; background:var(--input-bg); border:1px solid var(--line2); border-radius:var(--r-sm);
  padding:14px 16px; font-family:var(--mono); font-size:12.5px; line-height:1.5; color:var(--code-txt);
  overflow-x:auto; white-space:pre;
}
/* BLOCK, not flex. This was `display:flex; justify-content:center`, and centring a
   flex child that is WIDER than its scroll container puts the child's left edge at a
   negative offset that scrolling cannot reach -- so the moment the min-width below
   started making diagrams overflow, the left of every wide diagram became
   permanently unreachable. A block container with `margin-inline:auto` on the child
   centres it when it fits and starts it flush-left when it does not, which is the
   behaviour that actually scrolls. */
.pbq-diagram-svg{
  background:var(--bg2); border:1px solid var(--line2); border-radius:var(--r-sm);
  padding:12px; display:block; overflow-x:auto;
}
/* min-width is the whole fix: --dw is the diagram's natural width, published by
   svgWrap because CSS cannot read a viewBox. Wide container -> width:100% wins and
   the diagram fills it. Narrow container -> --dw wins, the diagram keeps its real
   size and .pbq-diagram-svg scrolls. Specs that already fitted are untouched,
   because min-width only binds when the container is narrower than the diagram. */
.diagram-svg{
  display:block; height:auto; width:100%;
  min-width:var(--dw, 0); max-width:none; margin-inline:auto;
}

/* Ad slots — reserved, currently always empty (see index.html's comment for how to enable).
   :empty means these take zero space and are invisible until something is actually placed
   inside one; the moment content is injected they get a consistent contained wrapper. */
.ad-slot:empty{ display:none }
.ad-slot{ max-width:1100px; margin:0 auto; padding:10px 20px; display:flex; justify-content:center }

/* ===================================================================
   EXAM MODE
   =================================================================== */
.ex-card .ex-start{margin-top:14px; width:100%; justify-content:center}
.ex-warn{margin-top:14px; font-size:12.5px; color:var(--warn); border:1px dashed rgba(var(--warn-rgb),.4); border-radius:var(--r-sm); padding:9px 11px}
.ex-last{margin-left:auto; font-size:12px; color:var(--muted); font-family:var(--mono)}

/* ---- Exam rail ----
   The old .exam-bar is gone. It was a sticky bar at the top of the CONTENT, so
   it shared an edge with the topbar and slid out of reach mid-exam — you had to
   scroll back to the top of 90 questions to hit Pause. These controls live in
   the fixed rail now, where they cannot scroll away at all. The bug is gone by
   construction rather than by offset math. */
.examrail{display:flex; flex-direction:column; gap:12px; min-height:0; flex:1}
.exrail-back{
  display:flex; align-items:center; gap:7px; align-self:flex-start;
  background:none; border:0; cursor:pointer; font-family:inherit;
  color:var(--muted); font-size:12.5px; font-weight:500; padding:5px 6px; border-radius:var(--r-sm);
  transition:color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.exrail-back .icon{transform:rotate(180deg)}
.exrail-back:hover{color:var(--txt); background:rgb(var(--light)/.05)}
.exrail-track{font-size:14px; font-weight:560; letter-spacing:-.02em; padding:0 6px; display:flex; flex-direction:column; gap:2px}
.exrail-track small{font-family:var(--mono); font-size:11px; color:var(--faint); font-weight:400; letter-spacing:0}
.exrail-prog{padding:0 6px; font-size:12px; color:var(--faint); font-variant-numeric:tabular-nums}
.exrail-actions{margin-top:auto; display:flex; flex-direction:column; gap:7px; padding-top:10px}
.exrail-actions .launch{margin-top:0; width:100%; justify-content:center}
.exrail-actions .mark{width:100%; display:flex; align-items:center; gap:6px; justify-content:center}
.exrail-actions .linkbtn{display:flex; align-items:center; gap:6px; justify-content:center; font-size:11.5px}
/* The timer is the most-glanced element in the app while an exam runs — it gets
   real size, tabular figures so it doesn't jitter every second, and it now sits
   in the rail where it is permanently visible. */
.exam-timer{
  font-family:var(--mono); font-size:26px; font-weight:560; letter-spacing:-.02em;
  font-variant-numeric:tabular-nums; padding:9px 10px; text-align:center;
  border:1px solid var(--line2); border-radius:var(--r-sm); background:var(--lit-inset); color:var(--txt);
}
.exam-timer.warn{color:var(--warn); border-color:rgba(var(--warn-rgb),.5)}
.exam-timer.crit{color:var(--bad); border-color:rgba(var(--bad-rgb),.6); animation:expulse 1s infinite}
@keyframes expulse{50%{background:rgba(var(--bad-rgb),.12)}}

/* Single column now — the question grid moved into the rail. */
.exam-wrap{display:block}
/* `flex:1 1 auto` is load-bearing: as a flex child with min-height:0 and
   overflow:auto, the grid otherwise resolves to ZERO height and silently
   swallows all 90 cells (measured: height 0, scrollHeight 697). It has to
   claim the rail's leftover space, then scroll inside it.
   align-content:start stops the rows stretching when there are few questions. */
.exnav-grid{
  display:grid; grid-template-columns:repeat(5,1fr); gap:4px; align-content:start;
  flex:1 1 auto; min-height:60px; overflow-y:auto; overscroll-behavior:contain;
  padding-right:2px;
}
.exnav-cell{
  position:relative; aspect-ratio:1; border:1px solid var(--line2); background:var(--bg2); color:var(--muted);
  border-radius:6px; font-size:11px; cursor:pointer; font-family:var(--mono); padding:0;
}
.exnav-cell:hover{border-color:var(--accent); color:var(--txt)}
.exnav-cell i{position:absolute; top:1px; right:3px; font-style:normal; font-size:8px; color:var(--accent)}
.exnav-cell.answered{background:var(--panel2); color:var(--txt); border-color:var(--line2)}
/* The rail grid is how you FIND a flagged question again, so the ring alone (which reads as a
   thin outline next to an .answered fill) gets an amber tint and text to match the card. */
.exnav-cell.flagged{box-shadow:inset 0 0 0 2px var(--warn); background:rgba(var(--warn-rgb),.16); color:var(--warn)}
.exnav-cell.c-ok{background:rgba(var(--good-rgb),.25); border-color:var(--good); color:var(--good)}
.exnav-cell.c-part{background:rgba(var(--warn-rgb),.22); border-color:var(--warn); color:var(--warn)}
.exnav-cell.c-no{background:rgba(var(--bad-rgb),.2); border-color:var(--bad); color:var(--bad)}
.exnav-legend{margin-top:10px; display:flex; flex-direction:column; gap:5px; font-size:11px; color:var(--faint)}
.exnav-legend i{display:inline-block; width:10px; height:10px; border-radius:3px; margin-right:6px; vertical-align:-1px}
.exnav-legend .lg-a{background:var(--panel2); border:1px solid var(--line2)}
.exnav-legend .lg-f{background:rgba(var(--warn-rgb),.16); box-shadow:inset 0 0 0 2px var(--warn)}

.exam-item{border:1px solid var(--line); border-radius:var(--r);
  background:linear-gradient(180deg,var(--panel2),var(--panel)); padding:16px 18px; margin-bottom:16px;
  scroll-margin-top:84px; box-shadow:var(--shadow-sm); transition:.18s box-shadow,.18s border-color}
/* FLAGGED — must be identifiable at a glance while scrolling a 90-question exam, without becoming
   the loudest thing on screen. Three quiet, additive cues instead of one: a solid amber edge bar
   (the part you actually catch in peripheral vision), a soft outward glow, and a faint amber wash
   across the card. Deliberately NOT a heavy fill or an animation — the question still has to be
   comfortable to read and answer. Graded/correct/wrong states override this after submit. */
.exam-item.flagged{
  border-color:rgba(var(--warn-rgb),.5);
  /* Keep the card's resting elevation (--shadow-sm) in the stack — overriding box-shadow
     without it makes a flagged card sit subtly "flatter" than its neighbours. */
  box-shadow:inset 3px 0 0 0 var(--warn), 0 0 0 1px rgba(var(--warn-rgb),.3), 0 0 18px rgba(var(--warn-rgb),.15), var(--shadow-sm);
  /* Layered: the amber wash sits ON TOP of the normal panel gradient (first layer paints last). */
  background:linear-gradient(180deg,rgba(var(--warn-rgb),.06),rgba(var(--warn-rgb),.025)),
             linear-gradient(180deg,var(--panel2),var(--panel));
}
.exam-item.graded.item-correct{border-color:rgba(var(--good-rgb),.55); box-shadow:0 0 0 1px rgba(var(--good-rgb),.35),0 0 16px rgba(var(--good-rgb),.14)}
.exam-item.graded.item-partial{border-color:rgba(var(--warn-rgb),.55); box-shadow:0 0 0 1px rgba(var(--warn-rgb),.32),0 0 16px rgba(var(--warn-rgb),.12)}
.exam-item.graded.item-wrong{border-color:rgba(var(--bad-rgb),.55); box-shadow:0 0 0 1px rgba(var(--bad-rgb),.32),0 0 16px rgba(var(--bad-rgb),.12)}
.exam-item.training-correct{border-color:rgba(var(--good-rgb),.55); box-shadow:0 0 0 1px rgba(var(--good-rgb),.35),0 0 16px rgba(var(--good-rgb),.14)}
.exam-item.training-wrong{border-color:rgba(var(--bad-rgb),.55); box-shadow:0 0 0 1px rgba(var(--bad-rgb),.32),0 0 16px rgba(var(--bad-rgb),.12)}
.exam-ihead{display:flex; align-items:center; gap:10px; margin-bottom:10px}
.exam-ihead .exq{font-family:var(--mono); font-weight:700; color:var(--accent); font-size:13px}
.exam-ihead .exflag{margin-left:auto; background:transparent; border:1px solid var(--line2); color:var(--faint); border-radius:6px; padding:3px 9px; font-size:11px; cursor:pointer}
.exam-ihead .exflag:hover{border-color:var(--warn); color:var(--warn)}
.exam-item.flagged .exflag{border-color:var(--warn); color:var(--warn)}
.exam-scen{color:var(--muted); font-size:13.5px; margin-bottom:10px}
.exam-item .pbq-task{border:0; padding:6px 0 0; margin:0; background:transparent}

.exam-result-card{position:relative; display:flex; align-items:center; gap:22px; flex-wrap:wrap; border:1px solid var(--line2);
  border-radius:var(--r-lg); padding:24px 26px; margin:6px 0 18px; background:var(--lit-panel); box-shadow:var(--shadow-md), var(--edge-hi); overflow:hidden}
.exam-result-card.pass{border-color:rgba(var(--good-rgb),.55); background:linear-gradient(120deg,rgba(var(--good-rgb),.14),var(--panel) 62%);
  box-shadow:var(--shadow-lg), 0 0 0 1px rgba(var(--good-rgb),.3), 0 0 40px rgba(var(--good-rgb),.12)}
.exam-result-card.fail{border-color:rgba(var(--bad-rgb),.5); background:linear-gradient(120deg,rgba(var(--bad-rgb),.12),var(--panel) 62%);
  box-shadow:var(--shadow-lg), 0 0 0 1px rgba(var(--bad-rgb),.25)}
.exr-score{display:flex; align-items:baseline; gap:6px}
.exr-num{font-family:var(--mono); font-size:52px; font-weight:800; line-height:1; letter-spacing:-.02em}
.exr-of{color:var(--faint); font-family:var(--mono)}
.exr-pf{font-size:20px; font-weight:800}
.exr-pf.pass{color:var(--good)} .exr-pf.fail{color:var(--bad)}
.exrail-actions{margin-left:auto; display:flex; gap:10px}
.exam-domains{border:1px solid var(--line); border-radius:var(--r); padding:16px 20px; background:var(--lit-panel); margin-bottom:18px}
.exam-domains h3{margin:0 0 12px; font-size:15px}

/* ---- Exam rail on a phone ----
   The bottom bar becomes the exam controls. This is the best part of the rail
   idea: the timer and Pause end up under the thumb exactly when they matter,
   instead of at the top of a 90-question scroll. The question grid is dropped
   here (it would eat the screen) — the row of controls is what you need in
   reach; jumping between questions is a desktop affordance. */
@media(max-width:900px){
  .rail.exam-mode{
    flex-direction:row; align-items:center; gap:10px;
    padding:8px 12px calc(8px + env(safe-area-inset-bottom, 0px));
  }
  .examrail{
    flex-direction:row; align-items:center; gap:10px; flex:1; min-width:0;
  }
  .exrail-track, .exnav-grid, .exnav-legend, .exrail-prog{display:none}
  /* Collapse every control to its icon — 375px cannot hold four labelled
     buttons plus a timer. Each keeps its title/aria label for a11y. */
  .exrail-back{align-self:center; flex:0 0 auto; padding:10px 8px; min-height:44px; min-width:44px; justify-content:center}
  .exrail-back .lbl, .exrail-actions .mark .lbl, .lbl-full{display:none}
  .exam-timer{
    font-size:19px; padding:6px 8px; flex:0 0 auto; border:0; background:none;
  }
  .exrail-actions{
    margin-top:0; padding-top:0; flex-direction:row; align-items:center; gap:8px;
    margin-left:auto; flex:0 0 auto;
  }
  .exrail-actions .launch{width:auto; padding:10px 14px; min-height:44px; font-size:13px}
  .exrail-actions .mark{width:auto; padding:10px; min-height:44px; min-width:44px; font-size:13px}
  /* Restart is rare and destructive — it does not earn thumb space. */
  .exrail-actions .linkbtn{display:none}
}
@media(max-width:680px){
  .exnav-grid{grid-template-columns:repeat(8,1fr)}
}

/* ===================================================================
   PROFILES · ONBOARDING · DASHBOARD · LEADERBOARD · A11Y
   =================================================================== */
/* accessibility: skip link + visible focus + reduced motion */
.skip{position:absolute; left:-999px; top:8px; z-index:100; background:var(--accent); color:var(--on-accent);
  padding:9px 14px; border-radius:8px; font-weight:700; text-decoration:none}
.skip:focus{left:12px}
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible,
.card:focus-visible, .exnav-cell:focus-visible{ outline:2px solid var(--accent); outline-offset:2px }
@media (prefers-reduced-motion: reduce){ *{animation:none !important; transition:none !important; scroll-behavior:auto !important} }

/* profile chip (header) */
.profilechip{margin-left:auto; position:relative}
.pchip{display:flex; align-items:center; gap:8px; background:var(--lit-panel); border:1px solid var(--line2);
  color:var(--txt); border-radius:999px; padding:5px 12px 5px 5px; cursor:pointer; font-family:var(--sans); font-size:13.5px}
.pchip:hover{border-color:var(--accent)}
.pavatar{width:24px; height:24px; border-radius:50%; background:linear-gradient(135deg,var(--accent),var(--accent2));
  color:var(--on-accent); display:flex; align-items:center; justify-content:center; font-weight:800; font-size:12px}
.pavatar.sm{width:20px; height:20px; font-size:10px}
.pname{font-weight:600; max-width:120px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.pcaret{color:var(--faint); font-size:10px}
/* Opens UPWARD (bottom-anchored), not downward: the profile chip lives in .rail-foot, which is
   pinned to the very bottom of the sidebar (margin-top:auto). A menu that opened downward from
   there had nowhere to go but off the bottom of the viewport — the exact "list goes down and I
   can't see anything" bug. max-height + scroll is a second safety net for very short viewports. */
.pmenu{position:absolute; right:0; bottom:calc(100% + 8px); min-width:210px; max-width:min(260px, calc(100vw - 24px));
  max-height:min(420px, calc(100vh - 24px)); overflow-y:auto; background:var(--panel2); border:1px solid var(--line2);
  border-radius:12px; padding:7px; z-index:60; box-shadow:0 -14px 40px rgba(0,0,0,.5)}
.pmenu-h{font-size:11px; text-transform:uppercase; letter-spacing:1px; color:var(--faint); padding:6px 9px}
.pmenu-item{display:flex; align-items:center; gap:9px; width:100%; text-align:left; background:transparent; border:0;
  color:var(--txt)!important; padding:9px 9px; border-radius:8px; cursor:pointer; font-family:var(--sans); font-size:13.5px; text-decoration:none!important}
.pmenu-item:hover{background:var(--bg2)}
.pmenu-item.on{color:var(--accent)!important}
.pmenu-sep{height:1px; background:var(--line); margin:6px 4px}

/* onboarding */
.onboard{min-height:70vh; display:flex; align-items:center; justify-content:center; padding:20px 0}
.onboard-card{max-width:560px; width:100%; text-align:center; background:linear-gradient(180deg,var(--panel),var(--bg2));
  border:1px solid var(--line2); border-radius:18px; padding:38px 34px}
.ob-logo{font-family:var(--mono); color:var(--accent); letter-spacing:-4px; font-size:34px; margin-bottom:6px}
.onboard-card h1{font-size:28px; margin:0 0 10px}
.ob-sub{color:var(--muted); margin:0 auto 22px; max-width:440px}
.ob-label{display:block; font-size:13px; color:var(--faint); margin-bottom:8px; text-align:left}
.ob-row{display:flex; gap:10px}
.ob-row input{flex:1; background:var(--input-bg); border:1px solid var(--line2); color:var(--txt); border-radius:10px; padding:12px 14px; font-size:15px}
.ob-row input:focus{outline:none; border-color:var(--accent)}
.ob-tour{display:grid; grid-template-columns:repeat(3,1fr); gap:10px; margin:24px 0 6px; text-align:left}
.ob-step{background:var(--bg2); border:1px solid var(--line); border-radius:10px; padding:11px 12px; font-size:12.5px; color:var(--muted)}
.ob-step span{display:inline-flex; width:20px; height:20px; border-radius:50%; background:var(--accent); color:var(--on-accent);
  align-items:center; justify-content:center; font-weight:800; font-size:11px; margin-right:6px}
.ob-step b{display:block; color:var(--txt); margin:4px 0 2px}
.ob-foot{color:var(--faint); font-size:12px; margin:18px 0 0}
.ob-ai{margin:18px 0 4px; text-align:left; background:var(--bg2); border:1px solid var(--line); border-radius:10px; padding:0 14px}
.ob-ai summary{cursor:pointer; padding:12px 0; font-weight:600; color:var(--txt); list-style:none}
.ob-ai summary::-webkit-details-marker{display:none}
.ob-ai[open]{padding-bottom:12px}
.ob-ai .set-row{margin:8px 0}

/* Onboarding: cert picker + exam date + diagnostic toggle */
.ob-certs-block{margin-top:22px; text-align:left}
.ob-certs{display:grid; grid-template-columns:repeat(2,1fr); gap:9px}
.ob-cert-chip{position:relative; display:flex; flex-direction:column; gap:6px; padding:10px 12px; border-radius:10px;
  border:1px solid var(--line2); border-left:3px solid var(--cstripe,var(--line2)); background:var(--bg2); cursor:pointer;
  transition:border-color .12s, background .12s}
.ob-cert-chip:hover{border-color:var(--accent)}
.ob-cert-chip.checked{background:rgba(var(--accent-rgb),.08); border-color:var(--accent)}
.ob-cert-chip input[type="checkbox"]{position:absolute; top:10px; right:10px; width:16px; height:16px; accent-color:var(--accent)}
.ob-cert-name{font-weight:700; font-size:13.5px; padding-right:22px}
.ob-cert-code{display:block; font-size:11px; color:var(--faint); font-family:var(--mono)}
.ob-cert-date{margin-top:2px; background:var(--input-bg); border:1px solid var(--line2); color:var(--txt); border-radius:7px;
  padding:6px 8px; font-size:12.5px; width:100%}
.ob-cert-date:disabled{opacity:.4; cursor:not-allowed}
.ob-diag{display:flex; align-items:flex-start; gap:11px; text-align:left; margin:18px 0 0; padding:13px 14px;
  border-radius:10px; background:rgba(var(--accent-rgb),.06); border:1px solid rgba(var(--accent-rgb),.22); cursor:pointer}
.ob-diag input{margin-top:3px; width:16px; height:16px; accent-color:var(--accent); flex:0 0 auto}
.ob-diag b{display:block; font-size:13.5px}
.ob-diag small{display:block; color:var(--muted); font-size:12px; margin-top:2px; line-height:1.4}

/* Onboarding diagnostic quiz screen */
.diag-card{text-align:left}
.diag-progress{display:flex; align-items:center; gap:10px; margin-bottom:16px}
.diag-bar{flex:1; height:5px; border-radius:3px; background:var(--line); overflow:hidden}
.diag-bar i{display:block; height:100%; background:var(--accent); transition:width .25s}
.diag-progress span{font-size:12px; color:var(--faint); font-family:var(--mono); white-space:nowrap}
.diag-tag{color:var(--accent); font-weight:700; font-size:12.5px; margin:0 0 8px; display:flex; align-items:center; gap:6px}
.diag-q{font-size:17px; line-height:1.5; margin-bottom:16px}

/* account picker ("who's studying?") */
.picker{min-height:78vh; display:flex; align-items:center; justify-content:center; padding:24px 0}
.picker-inner{max-width:720px; width:100%; text-align:center}
.picker-inner h1{font-size:30px; margin:10px 0 6px}
.picker-sub{color:var(--muted); margin:0 0 28px}
.acct-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:16px; justify-content:center; max-width:640px; margin:0 auto}
.acct-card{display:flex; flex-direction:column; align-items:center; gap:8px; padding:22px 14px; cursor:pointer;
  background:linear-gradient(180deg,var(--panel),var(--bg2)); border:1px solid var(--line2); border-radius:14px;
  color:var(--txt); transition:border-color .18s, box-shadow .18s, transform .18s}
.acct-card:hover{border-color:var(--line-strong); box-shadow:var(--edge-hi)}
.acct-card:active{transform:scale(.99)}
.acct-card:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
.acct-avatar{display:inline-flex; width:64px; height:64px; border-radius:50%; align-items:center; justify-content:center;
  font-size:28px; font-weight:800; color:var(--on-accent); background:linear-gradient(135deg,var(--accent),var(--accent2))}
.acct-name{font-weight:700; font-size:15px}
.acct-sub{color:var(--faint); font-size:12px}
.acct-card.add .acct-avatar{background:var(--bg2); color:var(--accent); border:1px dashed var(--line2)}

/* dashboard */
.dash-hero{display:flex; align-items:center; justify-content:space-between; gap:24px; flex-wrap:wrap}
.dash-stats{display:grid; grid-template-columns:repeat(4,1fr); gap:14px; margin:6px 0 26px}
.stat-tile{position:relative; background:var(--lit-panel); border:1px solid var(--line);
  border-radius:var(--r); padding:16px 18px; box-shadow:var(--shadow-sm), var(--edge-hi); overflow:hidden;
  transition:transform .16s var(--ease), border-color .16s var(--ease)}
.stat-tile::before{content:""; position:absolute; inset:0 auto 0 0; width:3px; background:var(--accent); opacity:.55}
.stat-tile:hover{border-color:var(--line-strong)}
.st-num{font-family:var(--mono); font-size:30px; font-weight:800; color:var(--txt); letter-spacing:-.02em}
.st-num small{font-size:15px; color:var(--faint); font-weight:600}
.st-lbl{color:var(--muted); font-size:13px; margin-top:2px}
.dash-sec{margin:26px 0}
.dash-sec h2{font-size:18px; margin:0 0 14px}
.dx-grid{grid-template-columns:repeat(4,1fr)}
.dx-card{padding:15px 16px}
.dx-score{font-family:var(--mono); font-size:26px; font-weight:800; margin:8px 0 12px}
.dx-score small{display:block; font-size:11px; font-weight:600; color:var(--faint); font-family:var(--sans)}
.dx-score.pass{color:var(--good)} .dx-score.fail{color:var(--warn)} .dx-score.none{font-size:14px; color:var(--faint); font-weight:600}
.launch.sm{padding:8px 13px; font-size:13px}
.launch.ghost{background:transparent; color:var(--accent); border:1px solid var(--accent)}
.ex-warn.sm{font-size:11.5px; padding:6px 9px; display:inline-block}
/* auto-fit (not a fixed 4-col repeat) so a lone last-row card (e.g. Leaderboard when the
   Hands-On Range tile is also present, making 5 items) stretches to fill the row instead of
   sitting alone next to empty columns — empty tracks collapse, so every row's cards always
   span the full width evenly regardless of item count. */
.quick-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:14px}
.quick{position:relative; display:flex; flex-direction:column; gap:3px; background:var(--lit-panel);
  border:1px solid var(--line); border-radius:var(--r); padding:18px; color:var(--txt)!important; text-decoration:none!important;
  box-shadow:var(--shadow-sm), var(--edge-hi); overflow:hidden;
  transition:border-color .16s var(--ease), transform .16s var(--ease), box-shadow .16s var(--ease)}
.quick:hover{border-color:var(--line-strong); box-shadow:inset 0 .5px 0 0 rgb(var(--light)/.45), inset 0 -1px 0 0 rgb(0 0 0/.2)}
.quick:active{transform:scale(.99)}
.q-ico{display:inline-flex; align-items:center; justify-content:center; width:44px; height:44px; margin-bottom:8px; font-size:22px;
  border-radius:12px; background:rgba(var(--accent-rgb),.12); border:1px solid rgba(var(--accent-rgb),.22)}
.quick b{font-size:15px}
.quick small{color:var(--faint)}

/* leaderboard */
.lb-wrap{overflow-x:auto; border:1px solid var(--line); border-radius:var(--r); background:var(--lit-panel)}
.lb-table{width:100%; border-collapse:collapse; min-width:520px}
.lb-table th{text-align:left; font-size:11px; text-transform:uppercase; letter-spacing:.5px; color:var(--faint); padding:13px 14px; border-bottom:1px solid var(--line2)}
.lb-table td{padding:13px 14px; border-bottom:1px solid var(--line); font-size:14px}
.lb-table tr:last-child td{border-bottom:0}
.lb-table tr.me{background:rgba(var(--accent-rgb),.07)}
.lb-rank{font-size:20px; width:48px; text-align:center}
.lb-rank .rank{font-family:var(--mono); font-size:14px; color:var(--faint)}
.lb-name{font-weight:600}
.lb-you{font-size:10px; background:var(--accent); color:var(--on-accent); border-radius:5px; padding:1px 6px; font-weight:700; margin-left:6px}
.lb-imp{font-size:10px; border:1px solid var(--line2); color:var(--faint); border-radius:5px; padding:1px 6px; margin-left:6px}
.lb-chase{display:inline-flex; align-items:center; gap:3px; font-size:11px; color:var(--faint); margin-left:8px; white-space:nowrap}
.lb-passed{margin-top:12px; display:inline-flex; align-items:center; gap:7px; font-size:13.5px; color:var(--text); background:rgba(var(--accent-rgb),.08); border:1px solid rgba(var(--accent-rgb),.25); border-radius:9px; padding:8px 13px}
.lb-passed b{color:var(--accent)}
.lb-pts{font-family:var(--mono); font-weight:700; color:var(--accent)}
/* auto-fit so a single track's score card fills the row instead of being stranded at 220px. */
.tb-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:14px}
.tb-card{background:var(--lit-panel); border:1px solid var(--line); border-radius:var(--r); padding:14px 16px}
.tb-card h4{margin:0 0 10px; font-size:14px}
.tb-row{display:flex; justify-content:space-between; padding:5px 0; font-size:13px; color:var(--muted); border-bottom:1px solid var(--line)}
.tb-row:last-child{border-bottom:0}
.tb-row b.pass{color:var(--good)} .tb-row b.fail{color:var(--warn)}
.share-row{display:flex; gap:10px; flex-wrap:wrap; margin-bottom:10px}
.mark.sm{padding:8px 13px; font-size:13px}
.share-code{width:100%; min-height:64px; background:var(--input-bg); border:1px solid var(--line2); color:var(--accent);
  border-radius:10px; padding:11px 13px; font-family:var(--mono); font-size:12px; resize:vertical}
.share-code:focus{outline:none; border-color:var(--accent)}

/* Training Mode */
/* NB: the token is --txt. This rule used to name a token that has never
   existed, so its colour silently resolved to nothing and just inherited. */
.training-feedback{padding:16px 12px; margin-top:12px; border-radius:var(--r-sm); background:rgb(var(--good-rgb)/.10);
  border-left:3px solid var(--good); color:var(--txt)}
.training-feedback p{margin:0 0 12px 0; font-weight:500}
.training-correct .training-feedback{background:rgba(var(--good-rgb),0.1); border-left-color:var(--good)}
.training-wrong .training-feedback{background:rgba(var(--bad-rgb),0.1); border-left-color:var(--bad)}
.training-feedback button{margin-top:8px}

/* Field Guide Accordion */
.pbq-fieldguide.accordion .acc-h{background:rgba(var(--accent-rgb),0.08); color:var(--accent); font-weight:600; padding:12px}
.pbq-fieldguide.accordion .acc-body{background:rgba(var(--accent-rgb),0.03); padding:14px 16px; line-height:1.65; font-size:14px}
.pbq-fieldguide.accordion .lock{color:var(--faint); font-size:11px}
.pbq-fieldguide .acc-body strong{color:var(--txt)}
.pbq-fieldguide .acc-body code{background:rgba(var(--accent-rgb),.1); color:var(--accent); padding:1px 5px; border-radius:4px; font-size:12.5px}
/* Fenced ASCII tables inside Field Guides / explanations */
.fg-pre{background:var(--input-bg); border:1px solid var(--line2); border-left:3px solid var(--accent); border-radius:8px;
  padding:11px 14px; margin:10px 0; font-family:var(--mono); font-size:12.5px; line-height:1.5; color:var(--code-txt);
  overflow-x:auto; white-space:pre}

/* Gamification — XP / levels / badges */
.xp-banner{position:relative; margin:0 0 22px; padding:18px 20px; border:1px solid rgba(var(--accent-rgb),.28); border-radius:var(--r);
  background:linear-gradient(135deg, rgba(var(--accent-rgb),.12), rgba(var(--accent2-rgb),.07) 55%, var(--panel));
  box-shadow:var(--shadow-md), var(--edge-hi); overflow:hidden}
.xp-banner::after{content:""; position:absolute; top:-40%; right:-6%; width:220px; height:220px; pointer-events:none;
  background:radial-gradient(circle, rgba(var(--accent-rgb),.16), transparent 70%)}
.xp-top{display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:10px; flex-wrap:wrap}
.xp-level{display:flex; align-items:center; gap:10px; font-size:17px}
.xp-level b{color:var(--txt); letter-spacing:.2px}
.xp-tier{font-family:var(--mono); font-size:11px; font-weight:700; color:var(--on-accent); background:var(--accent);
  padding:3px 8px; border-radius:999px; letter-spacing:.5px}
.xp-pts{color:var(--muted); font-size:13px}
.xp-pts b{color:var(--accent)}
.xp-bar{height:9px; background:var(--input-bg); border:1px solid var(--line2); border-radius:999px; overflow:hidden}
.xp-bar i{display:block; height:100%; border-radius:999px; background:linear-gradient(90deg,var(--accent2),var(--accent));
  box-shadow:0 0 12px rgba(var(--accent-rgb),.5); transition:width .8s cubic-bezier(.2,.7,.2,1)}
.badge-shelf{display:flex; gap:8px; flex-wrap:wrap; margin-top:14px}
.ach{display:flex; flex-direction:column; align-items:center; gap:3px; width:74px; padding:9px 4px;
  border:1px solid var(--line); border-radius:10px; background:var(--bg2); text-align:center; transition:.16s transform,.16s border-color}
.ach .ach-i{font-size:20px; filter:grayscale(1) opacity(.4)}
.ach small{font-size:9.5px; color:var(--faint); line-height:1.15}
.ach.got{border-color:rgba(var(--accent-rgb),.4); background:rgba(var(--accent-rgb),.06)}
.ach.got .ach-i{filter:none}
.ach.got small{color:var(--muted)}
.ach.got:hover{border-color:var(--line-strong)}

/* Celebratory confetti overlay */
.confetti-canvas{position:fixed; inset:0; width:100vw; height:100vh; pointer-events:none; z-index:9999}
@media(prefers-reduced-motion:reduce){ .confetti-canvas{display:none} .xp-bar i{transition:none} }

/* Pause / Restart buttons + resume banner */
.expause{background:transparent; border:1px solid var(--accent2); color:var(--accent2); border-radius:8px;
  padding:8px 13px; font-size:13px; font-weight:700; cursor:pointer; transition:.15s border-color,.15s color,.15s background}
.expause:hover{background:rgba(var(--accent2-rgb),.1); box-shadow:0 0 12px rgba(var(--accent2-rgb),.25)}
.exreset{background:transparent; border:1px solid var(--line2); color:var(--muted); border-radius:8px;
  padding:8px 13px; font-size:13px; font-weight:600; cursor:pointer; transition:.15s border-color,.15s color,.15s background}
.exreset:hover{border-color:var(--warn); color:var(--warn); background:rgba(var(--warn-rgb),.08)}
.exexit{background:transparent; border:1px solid var(--line2); color:var(--muted); border-radius:8px;
  padding:8px 13px; font-size:13px; font-weight:600; cursor:pointer; transition:.15s border-color,.15s color,.15s background}
.exexit:hover{border-color:var(--bad); color:var(--bad); background:rgba(var(--bad-rgb),.08)}
.launch.sm{padding:7px 13px; font-size:13px; margin-top:0}
.resume-banner.paused{background:linear-gradient(135deg, rgba(var(--warn-rgb),.14), rgba(var(--accent2-rgb),.08));
  border-color:rgba(var(--warn-rgb),.4); display:flex; align-items:center; gap:10px; flex-wrap:wrap}
.resume-chip.paused{color:var(--warn); background:rgba(var(--warn-rgb),.08); border-color:rgba(var(--warn-rgb),.35)}

/* Pause overlay modal */
.pause-overlay{position:fixed; inset:0; z-index:9998; display:flex; align-items:center; justify-content:center;
  background:rgba(6,9,14,.78); backdrop-filter:blur(6px)}
.pause-overlay[hidden]{display:none}
.pause-card{max-width:420px; width:90%; text-align:center; padding:30px 28px; border-radius:16px;
  background:linear-gradient(180deg,var(--panel2),var(--panel)); border:1px solid var(--line2);
  box-shadow:0 24px 60px rgba(0,0,0,.5)}
.pause-ico{font-size:40px; color:var(--accent2); margin-bottom:6px}
.pause-card h2{margin:0 0 8px; font-size:22px}
.pause-card p{color:var(--muted); margin:0 0 20px; font-size:14px; line-height:1.6}
.pause-actions{display:flex; gap:12px; justify-content:center; align-items:center; flex-wrap:wrap}
.pause-actions .launch{margin-top:0}
.resume-banner{margin:0 0 16px; padding:11px 15px; border-radius:10px; font-size:13.5px; color:var(--txt);
  background:linear-gradient(135deg, rgba(var(--accent2-rgb),.12), rgba(var(--accent-rgb),.08)); border:1px solid rgba(var(--accent2-rgb),.3)}
.linkbtn{background:none; border:0; color:var(--accent); font:inherit; cursor:pointer; text-decoration:underline; padding:0 0 0 4px}
.exam-result-card{scroll-margin-top:84px}

/* ===== Study & Review browser ===== */
.missed-badge{background:rgba(var(--warn-rgb),.12); color:var(--warn); border:1px solid rgba(var(--warn-rgb),.3)}
.review-bar{display:flex; align-items:center; gap:16px; flex-wrap:wrap; margin-bottom:18px; padding:12px 16px;
  background:linear-gradient(180deg, var(--panel2), var(--panel)); border:1px solid var(--line); border-radius:var(--r); box-shadow:var(--shadow-sm)}
.review-bar .back{margin:0; cursor:pointer; background:none; border:0; color:var(--muted); font:inherit}
.review-bar .back:hover{color:var(--txt)}
.review-filters{margin-left:auto; display:flex; gap:10px; align-items:center; flex-wrap:wrap}
.review-filters select{background:var(--bg2); border:1px solid var(--line2); color:var(--txt); border-radius:8px; padding:8px 11px; font-family:var(--sans); font-size:13.5px}
.seg.sm button{padding:6px 11px; font-size:12.5px}
.rv-card{max-width:760px; margin:0 auto; background:linear-gradient(180deg,var(--panel2),var(--panel));
  border:1px solid var(--line2); border-radius:14px; padding:22px 24px; box-shadow:var(--shadow-md)}
.rv-head{display:flex; align-items:center; gap:10px; margin-bottom:14px}
.rv-count{font-family:var(--mono); font-size:13px; color:var(--accent); font-weight:700; margin-right:auto}
.rv-q{font-size:18px; line-height:1.5; font-weight:500; margin-bottom:18px}
.rv-opts{display:flex; flex-direction:column; gap:10px; margin-bottom:16px}
.rv-opt{display:flex; gap:12px; align-items:flex-start; text-align:left; width:100%; padding:13px 15px;
  border:1px solid var(--line2); border-radius:10px; background:var(--bg2); color:var(--txt); cursor:pointer;
  font:inherit; font-size:14.5px; transition:.14s border-color,.14s background,.14s box-shadow}
.rv-opt:hover:not(:disabled){border-color:var(--accent); background:rgba(var(--accent-rgb),.05)}
.rv-opt:disabled{cursor:default}
.rv-letter{flex:0 0 auto; width:24px; height:24px; border-radius:6px; background:var(--panel2); border:1px solid var(--line2);
  display:flex; align-items:center; justify-content:center; font-family:var(--mono); font-weight:700; font-size:12px; color:var(--muted)}
.rv-opt.correct{border-color:var(--good); background:rgba(var(--good-rgb),.12); box-shadow:0 0 0 2px rgba(var(--good-rgb),.18)}
.rv-opt.correct .rv-letter{background:var(--good); color:var(--on-accent); border-color:var(--good)}
.rv-opt.correct::after{content:"\2713 correct"; margin-left:auto; color:var(--good); font-size:12px; font-weight:700; font-family:var(--mono); white-space:nowrap}
.rv-opt.wrong{border-color:var(--bad); background:rgba(var(--bad-rgb),.13); box-shadow:0 0 0 2px rgba(var(--bad-rgb),.18)}
.rv-opt.wrong .rv-letter{background:var(--bad); color:var(--on-bad); border-color:var(--bad)}
.rv-opt.wrong::after{content:"\2717 your pick"; margin-left:auto; color:var(--bad); font-size:12px; font-weight:700; font-family:var(--mono); white-space:nowrap}
.rv-opt.dim{opacity:.55}
.rv-opt-check{cursor:pointer}
.rv-opt-check input[type="checkbox"]{flex:0 0 auto; width:17px; height:17px; margin:2px 2px 0 0; accent-color:var(--accent); cursor:pointer}
.rv-opt-check input:disabled{cursor:default}
.rv-opt-check.picked{border-color:var(--accent); background:rgba(var(--accent-rgb),.08)}
.rv-reveal-row{display:flex; gap:12px; align-items:center; flex-wrap:wrap; margin-bottom:6px}
.rv-tip{font-size:12px}
.rv-explain{margin-top:10px}
.rv-hint{padding:11px 14px; border-radius:8px; background:rgba(var(--warn-rgb),.08); border-left:3px solid var(--warn);
  font-size:13.5px; margin-bottom:12px; line-height:1.55}
.rv-exp{padding:13px 16px; border-radius:8px; background:rgba(var(--accent-rgb),.05); border-left:3px solid var(--accent); line-height:1.6}
.rv-exp>b{color:var(--accent); display:block; margin-bottom:6px}
.rv-nav{display:flex; justify-content:space-between; gap:12px; margin-top:18px; padding-top:16px; border-top:1px solid var(--line)}
.rv-nav .mark[disabled]{opacity:.4; cursor:default}

/* Spaced-repetition self-rating (Study & Review). The four ratings set the Leitner box + next-due. */
.rv-rate{margin-top:14px; padding-top:14px; border-top:1px dashed var(--line2)}
.rv-rate-q{display:block; font-size:13px; font-weight:600; margin-bottom:9px}
.rv-rate-q .muted{font-weight:400}
.rv-rate-btns{display:grid; grid-template-columns:repeat(4,1fr); gap:8px}
.rv-rate-btn{display:flex; flex-direction:column; align-items:center; gap:2px; padding:9px 6px; border-radius:9px;
  border:1px solid var(--line2); background:var(--panel2); color:var(--text); font-weight:600; font-size:13px; cursor:pointer;
  transition:border-color .12s, background .12s, transform .08s}
.rv-rate-btn small{font-weight:400; font-size:10.5px; color:var(--faint)}
.rv-rate-btn:hover{border-color:var(--accent); background:rgba(var(--accent-rgb),.06)}
.rv-rate-btn:active{transform:translateY(1px)}
.rv-rate-btn[disabled]{opacity:.45; cursor:default}
/* The pre-suggested rating (from whether the pick was correct) gets a subtle emerald ring. */
.rv-rate-btn.suggested{border-color:var(--accent); box-shadow:inset 0 0 0 1px rgba(var(--accent-rgb),.5)}
/* Answering now records the grade on its own; these show WHAT was recorded and let the learner
   override it, so the state has to be legible at a glance rather than implied. */
.rv-rate-btn.chosen{border-color:var(--accent); background:rgba(var(--accent-rgb),.12); color:var(--text)}
.rv-rate-note{display:flex; align-items:center; gap:6px; flex-wrap:wrap; font-size:12.5px;
  color:var(--good); margin-bottom:9px}
.rv-rate-note b{color:var(--text)}
.rv-cleared{color:var(--accent); font-weight:600}
.rv-next-wrap{display:flex; justify-content:flex-end; margin-top:10px}
/* A PBQ inside the review queue: the task engine is reused verbatim, so this only has to space
   the scenario and keep the embedded .pbq-task blocks from double-framing inside the card. */
.rv-card-pbq .rv-pbq-scenario{color:var(--muted); font-size:13.5px; line-height:1.6; margin:8px 0 12px}
.rv-card-pbq #ptasks{margin-top:10px}
.rv-card-pbq .pbq-actions{margin-top:14px; display:flex; align-items:center; gap:12px; flex-wrap:wrap}

/* Field-guide one-line "how to solve" tip, pinned above the full guide text. */
.fg-tip{margin:0 0 10px; padding:9px 12px; border-radius:8px; background:rgba(var(--warn-rgb),.08);
  border-left:3px solid var(--warn); font-size:13px; line-height:1.5}

/* ---- Spaced-repetition daily loop ---- */
/* Dashboard "N cards due" banner — the primary daily action. */
.due-banner{display:flex; align-items:center; gap:14px; margin-bottom:18px; padding:15px 18px; border-radius:12px;
  text-decoration:none; color:var(--text); border:1px solid rgba(var(--accent-rgb),.3);
  background:rgba(var(--accent-rgb),.07); transition:border-color .14s, background .14s}
.due-banner:hover{border-color:var(--accent); background:rgba(var(--accent-rgb),.11)}
.due-b-ico{display:flex; color:var(--accent); flex:0 0 auto}
.due-b-txt{display:flex; flex-direction:column; gap:2px; flex:1 1 auto; min-width:0}
.due-b-txt b{font-size:15px}
.due-b-txt small{color:var(--faint); font-size:12.5px}
.due-b-go{display:inline-flex; align-items:center; gap:5px; flex:0 0 auto; font-weight:600; font-size:13px; color:var(--accent)}
@media(max-width:560px){ .due-b-go span{display:none} .due-b-txt small{display:none} }
/* Due-queue progress + empty state */
.due-progress{font-size:13px; color:var(--faint)}
.due-progress b{color:var(--text)}
.due-empty{text-align:center; max-width:440px; margin:40px auto; padding:28px}
.due-empty-ico{display:inline-flex; color:var(--accent); margin-bottom:8px}
.due-empty h2{margin:6px 0 8px}
.due-actions{display:flex; gap:10px; justify-content:center; flex-wrap:wrap; margin-top:18px}
/* Boot skeleton (markup lives in index.html so it paints with the static shell).
   Shapes only, in the existing panel idiom — it should read as "the layout is
   here, the content is coming", never as content. Uses --line rather than an
   accent so it stays quiet: this is the least important thing on screen and
   should not compete with the app that is about to replace it. */
.skel{padding:6px 0 0}
.skel-bar{height:14px; border-radius:7px; background:var(--line); margin:0 0 12px;
  position:relative; overflow:hidden}
.skel-bar:last-child{margin-bottom:0}
.skel-bar.t{height:26px; width:62%; margin-bottom:18px}
.skel-bar.s{width:88%}
.skel-bar.s2{width:74%}
.skel-card{background:var(--lit-panel); border:1px solid var(--line); border-radius:var(--r);
  box-shadow:var(--edge-hi); padding:16px; margin:0 0 12px}
.skel-bar::after{content:""; position:absolute; inset:0; transform:translateX(-100%);
  background:linear-gradient(90deg, transparent, rgba(var(--accent-rgb),.13), transparent);
  animation:skelsweep 1.25s ease-in-out infinite}
@keyframes skelsweep{100%{transform:translateX(100%)}}
/* This needs its own reduced-motion rule even though there is a global
   `*{animation:none !important}` above: the universal selector matches
   ELEMENTS, and ::after is a generated box, not an element — so the global
   kill-switch never reaches it. This is the first animated pseudo-element in
   the stylesheet, which is why nothing had exposed that gap before. Hide the
   sweep entirely rather than freezing it, or it parks mid-gradient. */
@media (prefers-reduced-motion: reduce){ .skel-bar::after{animation:none; display:none} }
.ns-msg{max-width:52ch; margin:28px auto; line-height:1.6; color:var(--muted)}
/* Progress zero state. Same accent-tinted "here's what to do" treatment as the
   post-exam prescription below, deliberately: both answer the same question, so
   they should not look like two different kinds of thing. The 6% wash is the one
   already proven to keep body text at AA over the page background. */
.prog-blank{margin:22px 0 6px; padding:18px; border-radius:12px;
  border:1px solid rgba(var(--accent-rgb),.28); background:rgba(var(--accent-rgb),.06)}
.prog-blank h2{margin:0 0 6px; font-size:17px}
.prog-blank p{margin:0 0 14px; line-height:1.55}
/* Exam-result prescription ("Do this next") */
.exr-rx{margin-top:16px; padding:16px 18px; border-radius:12px; border:1px solid rgba(var(--accent-rgb),.28);
  background:rgba(var(--accent-rgb),.06)}
.exr-rx-h{display:flex; align-items:center; gap:7px; font-weight:700; color:var(--accent); margin-bottom:6px}
.exr-rx p{margin:0 0 12px; line-height:1.55}
.exr-rx-actions{display:flex; gap:10px; flex-wrap:wrap}

/* Anthropic flashcards */
.fc-tabs{display:flex; gap:7px; flex-wrap:wrap; margin:0 auto 18px; max-width:720px; justify-content:center}
.flashcard{max-width:560px; margin:0 auto; height:300px; perspective:1400px; cursor:pointer}
.fc-inner{position:relative; width:100%; height:100%; transition:transform .5s cubic-bezier(.2,.7,.2,1); transform-style:preserve-3d}
.flashcard.flipped .fc-inner{transform:rotateY(180deg)}
.fc-face{position:absolute; inset:0; backface-visibility:hidden; -webkit-backface-visibility:hidden; border-radius:16px;
  display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; padding:28px;
  border:1px solid var(--line2); box-shadow:var(--shadow-md)}
.fc-front{background:linear-gradient(160deg,var(--panel2),var(--panel))}
.fc-back{background:linear-gradient(160deg,rgba(var(--accent-rgb),.1),var(--panel)); transform:rotateY(180deg)}
.fc-cat{position:absolute; top:14px; left:16px; font-family:var(--mono); font-size:11px; color:var(--accent); letter-spacing:.5px; text-transform:uppercase}
.fc-text{font-size:19px; line-height:1.5; font-weight:500; color:var(--txt)}
.fc-back .fc-text{font-size:16px; font-weight:400}
.fc-tip{position:absolute; bottom:14px; font-size:11px; color:var(--faint); letter-spacing:.5px}
.fc-nav{display:flex; align-items:center; justify-content:center; gap:18px; margin-top:18px}
.fc-count{font-family:var(--mono); color:var(--muted); font-size:13px}
@media(prefers-reduced-motion:reduce){ .fc-inner{transition:none} }

/* Study Coach panel */
.coach-sec{border:1px solid var(--line2); border-radius:var(--r); padding:18px 20px;
  background:linear-gradient(135deg, rgba(var(--accent-rgb),.06), rgba(var(--accent2-rgb),.05) 60%, var(--panel)); box-shadow:var(--shadow-sm)}
.coach-sechead{display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap}
.coach-sechead h2{margin:0}
/* auto-fit, not auto-fill: with a single track a lone coach card must stretch to fill the panel
   (auto-fill would pin it at the 300px min and leave the weak-domain rows too cramped to lay out). */
.coach-grid{display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:14px}
.coach-card{position:relative; border:1px solid var(--line); border-left:3px solid var(--cstripe,var(--accent));
  border-radius:11px; padding:14px 16px; background:var(--panel2); box-shadow:var(--shadow-sm)}
.coach-head{display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:10px}
.coach-ready{font-family:var(--mono); font-weight:800; font-size:20px; line-height:1; text-align:center; color:var(--accent)}
.coach-ready small{display:block; font-size:9px; font-weight:600; color:var(--faint); letter-spacing:.5px; text-transform:uppercase; margin-top:2px}
.coach-ready.good{color:var(--good)} .coach-ready.mid{color:var(--warn)} .coach-ready.low{color:var(--bad)}
.coach-weaklbl{font-size:11px; text-transform:uppercase; letter-spacing:.6px; color:var(--faint); margin-bottom:8px}
/* Two-line layout: the domain name gets its own line (it can be long, e.g. "Hardware & Network
   Troubleshooting"), and the confidence badge + score + drill links sit on a second .cw-meta line.
   The old single flex row centered those controls against the wrapping title, so a long name
   collided under the badge (the "Networking" overlap). Stacking makes overlap impossible at any
   card width. */
.coach-weak{display:flex; flex-direction:column; gap:7px; padding:9px 0; border-top:1px solid var(--line)}
.coach-weak:first-of-type{border-top:0}
.cw-main{font-size:13.5px; line-height:1.35}
.cw-meta{display:flex; align-items:center; gap:8px; flex-wrap:wrap}
.cw-score{font-family:var(--mono); font-weight:700; font-size:13px; color:var(--muted)}
.cw-score.low{color:var(--bad)} .cw-score.mid{color:var(--warn)}
.cw-links{display:flex; gap:6px; flex-wrap:wrap; margin-left:auto}
.chip-link{font-size:11.5px; font-weight:700; padding:4px 9px; border-radius:7px; background:var(--bg2);
  border:1px solid var(--line2); color:var(--accent)!important; text-decoration:none!important; white-space:nowrap}
.chip-link:hover{border-color:var(--accent); background:rgba(var(--accent-rgb),.08)}
.chip-link.lab{border-color:var(--accent); background:rgba(var(--accent-rgb),.10)}
.chip-link.lab:hover{background:rgba(var(--accent-rgb),.18)}
/* ---- AI Tutor: one reusable multi-turn chat surface (Phase 5) ----
   Mounted on: review-card reveal, lab pages, exam/training results, pop quiz, dashboard coach.
   Replaces the old one-shot .coach-ai narrators (removed) with a real conversation. */
.tutor{margin-top:14px; padding:14px 16px; border-radius:10px; background:rgba(var(--accent2-rgb),.07);
  border:1px solid rgba(var(--accent2-rgb),.3)}
.tutor-h{display:flex; align-items:center; gap:7px; font-weight:700; color:var(--accent2); margin-bottom:4px; font-size:13.5px}
.tutor-sub, .tutor-hint{font-weight:400; font-size:11.5px}
.tutor-locked p{margin:6px 0 10px; font-size:13px; line-height:1.55}
.tutor-log{display:flex; flex-direction:column; gap:8px; margin:10px 0; max-height:340px; overflow-y:auto}
.tutor-log:empty{display:none}
/* Tutor mounts are emptied (not removed) when the master AI switch is off — collapse them so a
   disabled AI leaves no ghost gap in the layout. */
.rv-tutor:empty{display:none}
.tutor-bubble{padding:9px 12px; border-radius:10px; font-size:13.5px; line-height:1.55; max-width:88%}
.tutor-bubble.me{align-self:flex-end; background:rgba(var(--accent-rgb),.14); color:var(--text)}
.tutor-bubble.ai{align-self:flex-start; background:var(--bg2); border:1px solid var(--line2)}
.tutor-bubble.loading{color:var(--faint); font-style:italic}
.tutor-quick{display:flex; flex-wrap:wrap; gap:7px; margin-bottom:10px}
.tutor-qbtn{cursor:pointer; border:none; font:inherit}
.tutor-row{display:flex; gap:8px}
.tutor-row input{flex:1; background:var(--input-bg); border:1px solid var(--line2); color:var(--txt);
  border-radius:8px; padding:9px 12px; font-size:13.5px}
.tutor-row input:focus{outline:none; border-color:var(--accent2)}
.time-badge{margin-left:auto; font-family:var(--mono); font-size:11px; color:var(--faint); background:var(--bg2);
  border:1px solid var(--line2); border-radius:6px; padding:2px 7px}

/* Settings */
.settings-card{max-width:640px; margin:0 auto 16px; border:1px solid var(--line2); border-radius:var(--r);
  padding:20px 22px; background:var(--lit-panel); box-shadow:var(--shadow-sm)}
.settings-card h3{margin:0 0 6px}
.set-row{display:flex; align-items:center; gap:14px; margin:12px 0}
.set-row label{flex:0 0 110px; color:var(--muted); font-size:13.5px}
/* min-height meets WCAG 2.2 AA target size (2.5.8, 24x24 CSS px). The text
   inputs already clear it via padding, but the two checkbox toggles in this row
   computed to 297x23 — one pixel short, which an audit catches and an eye never
   will. Applies to the row's controls generally so a future control added here
   inherits the floor instead of re-introducing the gap. */
.set-row input, .set-row select{flex:1; min-height:24px; background:var(--bg2); border:1px solid var(--line2); color:var(--txt);
  border-radius:8px; padding:9px 12px; font-family:var(--sans); font-size:14px}
.set-row input:focus, .set-row select:focus{outline:none; border-color:var(--accent); box-shadow:0 0 0 3px rgba(var(--accent-rgb),.15)}
/* .switch-label wraps a checkbox plus its text, so IT is the real click target
   — clicking a label activates its control. It had no rule at all and inherited
   a 22px line box, one pixel under the WCAG 2.2 AA 24px minimum (2.5.8).
   Styling the input alone does not fix this: the audit measures the label,
   because that is what a finger or pointer actually hits. */
.switch-label{display:inline-flex; align-items:center; gap:8px; min-height:24px}
.set-actions{display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:14px}
.set-help{margin-top:18px; padding-top:16px; border-top:1px solid var(--line); font-size:13.5px; line-height:1.6}
.set-help ol{margin:8px 0; padding-left:20px} .set-help li{margin:4px 0}
.set-help code{background:rgba(var(--accent-rgb),.1); color:var(--accent); padding:1px 5px; border-radius:4px; font-size:12.5px}
.mark.sm{padding:7px 13px; font-size:13px}

/* My Results — attempt list */
.attempt-list{display:flex; flex-direction:column; gap:10px}
.attempt-row{display:flex; align-items:center; gap:16px; padding:14px 18px; border:1px solid var(--line);
  border-radius:var(--r); background:linear-gradient(180deg,var(--panel2),var(--panel)); box-shadow:var(--shadow-sm);
  transition:.15s border-color,.15s box-shadow}
.attempt-row:hover{border-color:var(--line2); box-shadow:var(--shadow-md)}
.attempt-main{flex:1; min-width:0}
.attempt-when{font-size:12px; margin-top:3px}
.attempt-score{display:flex; align-items:center; gap:8px; white-space:nowrap}
.attempt-verdict{font-size:12px}
.attempt-action{margin-left:8px}
.mark.sm{padding:7px 13px; font-size:13px}

/* Post-exam review filter */
.rv-filter{display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin:16px 0; padding:12px 14px;
  border:1px solid var(--line); border-radius:10px; background:var(--lit-panel)}
.seg-btn{background:var(--bg2); border:1px solid var(--line2); color:var(--muted); border-radius:8px; padding:7px 13px;
  font-size:13px; font-weight:600; cursor:pointer; transition:.14s}
.seg-btn:hover{color:var(--txt); border-color:var(--accent)}
.seg-btn.on{background:var(--accent); color:var(--on-accent); border-color:var(--accent)}
.rv-filter-tip{font-size:12px; margin-left:auto}
.exam-items.only-wrong .exam-item.item-correct,
.exam-items.only-wrong .exam-item.training-correct{display:none}

/* Progress history chips + resume indicator on exam/training cards */
.hist-row{display:flex; align-items:center; gap:5px; flex-wrap:wrap; margin:10px 0 4px; font-size:12px}
.hist-lbl{color:var(--faint); margin-right:3px}
.hist-chip{font-family:var(--mono); font-size:11.5px; font-weight:700; padding:2px 7px; border-radius:6px;
  background:var(--bg2); border:1px solid var(--line2)}
.hist-chip.pass{color:var(--good); border-color:rgba(var(--good-rgb),.4)}
.hist-chip.fail{color:var(--warn); border-color:rgba(var(--warn-rgb),.35)}
.hist-arrow{color:var(--faint); font-size:11px}

/* Dashboard readiness trend — chips reuse .hist-chip; this just adds the label + net delta. */
.readiness-trend{display:flex; align-items:center; gap:6px; flex-wrap:wrap; margin-top:14px}
.trend-lbl{display:inline-flex; align-items:center; gap:4px; color:var(--faint); font-size:11.5px; margin-right:2px}
.trend-delta{font-family:var(--mono); font-size:11.5px; font-weight:700; margin-left:4px}
.trend-delta.up{color:var(--good)}
.trend-delta.down{color:var(--warn)}
.resume-chip{margin:8px 0 2px; padding:6px 10px; border-radius:8px; font-size:12px; color:var(--accent);
  background:rgba(var(--accent-rgb),.08); border:1px solid rgba(var(--accent-rgb),.3)}

.paused-exams{background:rgba(var(--warn-rgb),.06); border:1px solid rgba(var(--warn-rgb),.25); border-radius:12px; padding:16px 18px}
.paused-exams h2{color:var(--warn)}
.paused-exam-row{display:flex; align-items:center; gap:14px; flex-wrap:wrap; padding:8px 0; border-top:1px solid rgba(var(--warn-rgb),.15)}
.paused-exam-row:first-of-type{border-top:none}
.paused-exam-row .pe-track{font-weight:700; min-width:110px}
.paused-exam-row .resume-chip{margin:0; flex:1}
.paused-exam-row .launch.sm{margin-left:auto}

@media(max-width:780px){
  .dash-stats{grid-template-columns:repeat(2,1fr)}
  .dx-grid{grid-template-columns:repeat(2,1fr)}
  .ob-tour{grid-template-columns:1fr}
  .ob-certs{grid-template-columns:1fr}
}

/* ================= SIEM lab (hands-on SOC console) ================= */
.badge.siem{background:rgba(var(--good-rgb),.10); color:var(--good); border:1px solid rgba(var(--good-rgb),.3)}
.siem-wrap{display:grid; grid-template-columns:1fr 340px; gap:18px; margin-top:20px; align-items:start}
.siem-console{background:var(--lit-panel); border:1px solid var(--line); border-radius:var(--r); overflow:hidden}
.siem-toolbar{display:flex; flex-wrap:wrap; gap:10px; align-items:center; padding:12px 14px; border-bottom:1px solid var(--line); background:var(--bg2)}
.siem-toolbar input{
  flex:1; min-width:180px; background:var(--input-bg); border:1px solid var(--line2); color:var(--txt); border-radius:7px;
  padding:9px 12px; font-family:var(--mono); font-size:13px;
}
.siem-toolbar input:focus{outline:none; border-color:var(--accent)}
.siem-sources{display:flex; flex-wrap:wrap; gap:6px}
.siem-sources .chip{
  font-size:12px; padding:6px 11px; border-radius:999px; border:1px solid var(--line2); color:var(--muted);
  cursor:pointer; background:transparent; font-family:var(--sans);
}
.siem-sources .chip:hover{border-color:var(--sacc,var(--accent)); color:var(--txt)}
.siem-sources .chip.on{background:var(--sacc,var(--accent)); border-color:var(--sacc,var(--accent)); color:var(--on-accent); font-weight:700}
.siem-sort{margin-left:auto}
.siem-flagcount{font-size:12px; color:var(--faint); font-family:var(--mono); white-space:nowrap}
.siem-log{max-height:560px; overflow-y:auto; font-family:var(--mono); font-size:12.5px}
.siem-row{
  display:grid; grid-template-columns:20px 66px 108px 76px 1fr; gap:10px; align-items:center;
  padding:8px 12px; border-bottom:1px solid var(--line); cursor:default; transition:background .12s;
}
.siem-row:hover{background:var(--bg2)}
.siem-row.flagged{background:rgba(var(--warn-rgb),.08); border-left:3px solid var(--warn)}
.siem-flag{cursor:pointer; opacity:.35; font-size:13px}
.siem-flag:hover{opacity:1}
.siem-row.flagged .siem-flag{opacity:1}
.siem-t{color:var(--faint)}
.siem-src{
  color:var(--sacc,var(--muted)); border:1px solid var(--sacc,var(--line2)); border-radius:5px;
  padding:2px 7px; font-size:11px; text-align:center; background:color-mix(in srgb, var(--sacc,transparent) 12%, transparent);
}
.siem-host{color:var(--muted)}
.siem-raw{color:var(--txt); word-break:break-word}
/* A TEXT token, because severity was previously carried by hue alone on .siem-raw --
   a WCAG 1.4.1 failure on the exact evidence the learner is graded on triaging, and
   invisible to anyone who cannot resolve amber from the body colour. crit already had
   a shape cue (its left border); warn had nothing at all.
   Deliberately not a coloured dot: that would be a second colour-only channel. The
   letters carry the meaning and the colour merely agrees with them.
   The token also stays distinct from the flagged state, which repaints the LEFT BORDER
   and background rather than this chip -- so a flagged critical row still reads as
   both, which was the risk in adding a severity marker at all. */
.siem-sev{
  flex:none; align-self:center; font-family:var(--mono); font-size:9.5px; font-weight:700;
  letter-spacing:.08em; padding:1px 5px; border-radius:3px; border:1px solid currentColor;
  opacity:.9;
}
.siem-row.sev-warn .siem-sev{color:var(--warn)}
.siem-row.sev-crit .siem-sev{color:var(--bad)}
.siem-row.sev-warn .siem-raw{color:var(--warn)}
.siem-row.sev-crit .siem-raw{color:var(--pen)}
.siem-row.sev-crit{border-left:3px solid var(--bad)}
.siem-row.sev-crit.flagged{border-left:3px solid var(--warn)}
/* click-to-pivot entities (auto-detected IPs) inside a log row — the "investigate → jump to
   everything involving this IP" motion a real analyst uses instead of retyping the search */
.siem-entity{color:var(--accent); text-decoration:underline; text-decoration-style:dotted; text-underline-offset:2px; cursor:pointer}
.siem-entity:hover{color:var(--accent2)}

.siem-case{position:sticky; top:16px; background:var(--lit-panel); border:1px solid var(--line); border-radius:var(--r); padding:16px}
.siem-case h4{margin:0 0 10px; font-size:13px; text-transform:uppercase; letter-spacing:.6px; color:var(--accent)}
/* the "build your case" evidence-flagging objective — optional (lab.caseObjective) */
.siem-case-obj{margin-bottom:14px; padding:11px 13px; border-radius:var(--r-sm); background:var(--bg2); border:1px solid var(--line2); transition:border-color .15s var(--ease), background .15s var(--ease)}
.siem-case-obj.done{border-color:rgba(var(--good-rgb),.45); background:rgba(var(--good-rgb),.06)}
.siem-case-obj-h{font-size:13px; font-weight:700}
.siem-case-obj-stat{margin-top:5px; font-size:12px; color:var(--faint); font-family:var(--mono)}
.siem-case-fp{color:var(--warn); font-weight:700}
.siem-progress{height:6px; background:var(--bg2); border-radius:999px; overflow:hidden; margin-bottom:6px}
.siem-progress-bar{height:100%; background:linear-gradient(90deg,var(--accent),var(--accent2)); border-radius:999px; transition:width .3s}
.siem-progress-label{font-size:12px; color:var(--faint); margin-bottom:14px; font-family:var(--mono)}
.siem-q{margin-bottom:12px; padding:12px 13px; background:var(--bg2); border:1px solid var(--line2); border-radius:var(--r-sm)}
.siem-q.done{border-color:rgba(var(--good-rgb),.4); background:rgba(var(--good-rgb),.05)}
.siem-q-h{display:flex; gap:9px; align-items:flex-start; margin-bottom:9px}
.siem-q-n{
  flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center; width:20px; height:20px;
  border-radius:50%; background:var(--line2); color:var(--muted); font-size:11px; font-weight:700; font-family:var(--mono);
}
.siem-q.done .siem-q-n{background:var(--good); color:var(--on-accent)}
.siem-q .q{font-size:13.5px; font-weight:600; line-height:1.4}
.siem-q .row{display:flex; gap:6px}
.siem-q input{
  flex:1; min-width:0; background:var(--input-bg); border:1px solid var(--line2); color:var(--txt); border-radius:6px;
  padding:8px 10px; font-family:var(--mono); font-size:12.5px;
}
.siem-q input:focus{outline:none; border-color:var(--accent)}
.siem-q button{
  background:var(--accent2); color:var(--on-accent2); border:0; border-radius:6px; padding:8px 13px;
  font-weight:700; cursor:pointer; font-family:var(--sans); font-size:12.5px; white-space:nowrap;
}
.siem-q button:disabled, .siem-q input:disabled{opacity:.5; cursor:default}
.siem-q .fb{margin-top:8px; font-size:12.5px; font-weight:600; display:none}
.siem-q .fb.ok{display:block; color:var(--good)}
.siem-q .fb.no{display:block; color:var(--bad)}
.siem-q .hints{margin-top:8px}
.siem-q .hint-btn{font-size:12px; padding:6px 10px}
.siem-q .hint{font-size:12.5px}

@media (max-width:900px){
  /* minmax(0,1fr), not 1fr. A grid ITEM defaults to min-width:auto, so a plain
     1fr track cannot shrink below its content's min-content width. Config labs
     render <select> elements whose intrinsic width is their LONGEST OPTION, and
     that inflated this track to 628px inside a 343px grid — 270px of sideways
     page scroll on a phone. minmax(0,...) lets the track shrink; min-width:0 on
     the item stops it re-inflating from the inside. */
  .siem-wrap{grid-template-columns:minmax(0,1fr)}
  .siem-wrap > *{min-width:0}
  .siem-case{position:static}
  .siem-row{grid-template-columns:16px 56px 1fr; grid-template-areas:"flag t src" "flag host raw"; row-gap:4px}
  .siem-row .siem-flag{grid-area:flag} .siem-row .siem-t{grid-area:t} .siem-row .siem-src{grid-area:src; justify-self:end}
  .siem-row .siem-host{grid-area:host} .siem-row .siem-raw{grid-area:raw; grid-column:1/-1; padding-left:16px}
}

.config-field{margin-bottom:14px; padding:12px 13px; background:var(--bg2); border:1px solid var(--line2); border-radius:var(--r-sm); display:flex; align-items:center; gap:10px; flex-wrap:wrap}
.config-field label{flex:1 1 220px; font-size:13px; font-weight:600}
/* .config-field is a wrapping flex ROW, so a per-field hint ladder would otherwise be laid out as
   a narrow third column beside the label and the input. Force it onto its own full-width line. */
.config-field > .hints{flex:1 0 100%; margin-top:2px}
.config-input{
  flex:1 1 240px; min-width:0; background:var(--input-bg); border:1px solid var(--line2); color:var(--txt); border-radius:6px;
  padding:8px 10px; font-family:var(--mono); font-size:12.5px;
}
.config-input:focus{outline:none; border-color:var(--accent)}
.config-fb{font-size:14px; font-weight:700; width:18px; text-align:center}
.config-fb.ok{color:var(--good)}
.config-fb.no{color:var(--bad)}

/* ===================================================================
   INTERACTIVE LAB ENGINES — terminal · builder · triage
   (these had no styling at all before the redesign)
   =================================================================== */
/* the little lab-type chip on cards + lab headers */
.badge.lab-type{background:var(--bg2); color:var(--muted); border:1px solid var(--line2); text-transform:uppercase; letter-spacing:.4px; font-size:10px}

/* ---- Terminal lab ---- */
.term-tasks{display:flex; flex-direction:column; gap:8px; margin:18px 0}
.term-task{padding:12px 14px; border:1px solid var(--line); border-radius:var(--r-sm); background:var(--lit-panel); transition:border-color .15s var(--ease)}
.term-task.done{border-color:rgba(var(--good-rgb),.5); background:rgba(var(--good-rgb),.06)}
.term-task-prompt{display:flex; gap:10px; align-items:flex-start; font-size:14px; line-height:1.5}
.term-task-n{flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center; width:22px; height:22px; border-radius:50%;
  background:var(--bg2); border:1px solid var(--line2); color:var(--muted); font-family:var(--mono); font-size:12px; font-weight:700}
.term-task.done .term-task-n{background:var(--good); color:var(--on-good); border-color:var(--good)}
/* Same single-flex-child rule as .pbq-qtext — see the note there. The badge (22px) + gap (10px)
   stays exactly 32px, which is what .term-task-fb's 32px left margin aligns against. */
.tt-text{flex:1 1 auto; min-width:0}
.term-task-fb{margin:8px 0 0 32px; font-size:13px; color:var(--good)}
.terminal{background:var(--input-bg); border:1px solid var(--line2); border-radius:var(--r-sm) var(--r-sm) 0 0; border-bottom:0;
  padding:34px 16px 14px; min-height:150px; max-height:340px; overflow-y:auto; position:relative;
  font-family:var(--mono); font-size:13px; line-height:1.6; color:var(--code-txt)}
/* window-chrome dot cluster + title */
.terminal::before{content:"● ● ●"; position:absolute; top:0; left:0; right:0; height:26px; padding:0 12px;
  display:flex; align-items:center; letter-spacing:2px; font-size:9px; color:var(--faint);
  background:var(--panel2); border-bottom:1px solid var(--line2); border-radius:var(--r-sm) var(--r-sm) 0 0}
/* Title comes from the lab (data-shell), defaulting to the neutral "console".
   It used to be hardcoded "bash" for all 31 terminal labs -- but most of them are
   A+ scenarios driving diskpart, chkdsk, bootrec, robocopy and sfc, i.e. Windows.
   Deriving the platform from the commands was tried and abandoned: a keyword
   classifier confidently labelled `chkdsk`/`robocopy` labs as Unix, so it would
   have replaced one false title with another. A title the lab states is right; a
   title we guess is not, and "console" is true everywhere. */
.terminal::after{content:attr(data-shell); position:absolute; top:0; left:0; right:0; height:26px; display:flex; align-items:center;
  justify-content:center; font-family:var(--mono); font-size:10px; color:var(--faint); pointer-events:none}
.term-line{white-space:pre-wrap; word-break:break-word; margin:1px 0}
.term-prompt{color:var(--accent); font-weight:700; margin-right:6px}
.term-input-row{display:flex; align-items:center; gap:8px; background:var(--input-bg); border:1px solid var(--line2);
  border-top:1px dashed var(--line2); border-radius:0 0 var(--r-sm) var(--r-sm); padding:10px 14px}
.term-input-row .term-prompt{margin:0}
.term-input{flex:1; min-width:0; background:transparent; border:0; outline:none; color:var(--txt);
  font-family:var(--mono); font-size:13px; caret-color:var(--accent)}
.term-input::placeholder{color:var(--faint)}
.term-input-row .launch.sm{margin:0}
.term-line-cmd{color:var(--accent); font-weight:600}
.term-line-err{color:var(--bad)}
/* Captured console output printed in response to a matching command. Deliberately
   plainer than the command line above it: on a real machine what you typed and what
   the machine answered do not look alike, and that contrast is most of what makes a
   transcript readable. */
.term-out{color:var(--code-txt); opacity:.92}
/* An author's ">>" read of the output. NOT something the machine printed, so it must
   never share the styling of real output -- the rule left of it marks it as margin
   commentary.
   NAMED term-note, NOT note, deliberately: `.note` is an existing app class (above)
   carrying an emerald wash and a decorative lightbulb ::before. `.term-out.note`
   would have out-specified its colours but NOT its ::before, so a 💡 would have
   appeared inside a simulated console -- in an app that was deliberately
   de-emojified. Reusing a generic class name inherits everything you forgot to
   override, so the class does not collide in the first place. */
.term-out.term-note{display:block; margin:6px 0 2px; padding-left:9px; border-left:2px solid var(--warn);
  color:var(--warn); font-weight:600}
.term-out.dim{color:var(--faint); font-style:italic}

/* ---- Shell lab (type:"shell") — a genuine simulated server console,
   two-pane like the SIEM engine: live terminal + live objectives ---- */
.badge.shell-badge{background:rgba(var(--good-rgb),.10); color:var(--good); border:1px solid rgba(var(--good-rgb),.3)}
.badge.range{background:rgba(var(--accent-rgb),.12); color:var(--accent); border:1px solid rgba(var(--accent-rgb),.3)}
.shell-wrap{display:grid; grid-template-columns:1fr 360px; gap:18px; margin-top:20px; align-items:start}
.shell-console{display:flex; flex-direction:column}
.shell-objectives{position:sticky; top:16px; background:var(--lit-panel); border:1px solid var(--line); border-radius:var(--r); padding:16px}
.shell-objectives h4{margin:0 0 10px; font-size:13px; text-transform:uppercase; letter-spacing:.6px; color:var(--accent)}
.shell-obj-progress{height:6px; background:var(--bg2); border-radius:999px; overflow:hidden; margin-bottom:6px}
.shell-obj-bar{height:100%; background:linear-gradient(90deg,var(--accent),var(--accent2)); border-radius:999px; transition:width .3s}
.shell-obj-label{font-size:12px; color:var(--faint); margin-bottom:14px; font-family:var(--mono)}
.shell-obj{margin-bottom:12px; padding:12px 13px; background:var(--bg2); border:1px solid var(--line2); border-radius:var(--r-sm); transition:border-color .15s var(--ease), background .15s var(--ease)}
.shell-obj.done{border-color:rgba(var(--good-rgb),.4); background:rgba(var(--good-rgb),.05)}
.shell-obj-h{display:flex; gap:9px; align-items:flex-start; margin-bottom:9px}
.shell-obj-n{
  flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center; width:20px; height:20px;
  border-radius:50%; background:var(--line2); color:var(--muted); font-size:11px; font-weight:700; font-family:var(--mono);
}
.shell-obj.done .shell-obj-n{background:var(--good); color:var(--on-good)}
.shell-obj .q{font-size:13.5px; font-weight:600; line-height:1.4}
.shell-obj-row{display:flex; gap:6px}
.shell-obj-row input{
  flex:1; min-width:0; background:var(--input-bg); border:1px solid var(--line2); color:var(--txt); border-radius:6px;
  padding:8px 10px; font-family:var(--mono); font-size:12.5px;
}
.shell-obj-row input:focus{outline:none; border-color:var(--accent)}
.shell-obj-row button{
  background:var(--accent2); color:var(--on-accent2); border:0; border-radius:6px; padding:8px 13px;
  font-weight:700; cursor:pointer; font-family:var(--sans); font-size:12.5px; white-space:nowrap;
}
.shell-obj-row button:disabled, .shell-obj-row input:disabled{opacity:.5; cursor:default}
.shell-obj .fb{margin-top:8px; font-size:12.5px; font-weight:600; display:none}
.shell-obj .fb.ok{display:block; color:var(--good)}
.shell-obj .fb.no{display:block; color:var(--bad)}
.shell-obj .hints{margin-top:8px}
.shell-obj .hint-btn{font-size:12px; padding:6px 10px}
.shell-obj .hint{font-size:12.5px}
@media (max-width:900px){
  /* Same min-content trap as .siem-wrap above: the terminal box overflowed its
     own column by 10px. */
  .shell-wrap{grid-template-columns:minmax(0,1fr)}
  .shell-wrap > *{min-width:0}
  .shell-objectives{position:static}
}

/* ---- Builder lab (drag & drop) ---- */
.builder-wrap{display:flex; flex-direction:column; gap:14px; margin:18px 0}
.builder-items{display:flex; flex-wrap:wrap; gap:9px; padding:14px; min-height:56px; border-radius:var(--r-sm);
  background:var(--bg2); border:1px solid var(--line)}
.builder-item{padding:9px 14px; border-radius:8px; background:var(--panel2); border:1px solid var(--line2); color:var(--txt);
  font-size:13.5px; font-weight:600; cursor:grab; user-select:none; box-shadow:var(--shadow-sm);
  transition:transform .12s var(--ease), border-color .12s var(--ease), box-shadow .12s var(--ease)}
.builder-item:hover{border-color:var(--accent)}
.builder-item:active{cursor:grabbing}
.builder-item.dragging{opacity:.5; border-style:dashed}
.drop-zone{display:flex; flex-wrap:wrap; align-items:center; gap:9px; padding:14px 16px; min-height:60px; border-radius:var(--r-sm);
  background:var(--lit-panel); border:1.5px dashed var(--line2); position:relative; transition:border-color .15s var(--ease), background .15s var(--ease)}
/* Reads data-zLABEL, not data-zone. data-zone is the grading key and is a bare array
   index in eight of the eleven builder labs, so printing it captioned every category
   with "0" / "1" / "2". The caption now appears only where the authored id is
   meaningful to a human, and the top padding it reserved is applied only then --
   otherwise every zone kept 24px of empty space for a label that is not there. */
.drop-zone::before{content:attr(data-zlabel); position:absolute; top:6px; left:12px; font-size:10px; text-transform:uppercase;
  letter-spacing:.6px; color:var(--faint); font-family:var(--mono)}
.drop-zone[data-zlabel]{padding-top:24px}
.drop-zone.over{border-color:var(--accent); background:rgba(var(--accent-rgb),.07)}
.drop-zone.correct{border-style:solid; border-color:rgba(var(--good-rgb),.55); background:rgba(var(--good-rgb),.07)}
.drop-zone.wrong{border-style:solid; border-color:rgba(var(--bad-rgb),.5); background:rgba(var(--bad-rgb),.06)}
.builder-actions{display:flex; gap:12px; align-items:center; flex-wrap:wrap; margin:6px 0}
#builderresult{font-weight:600; font-size:14px}

/* ---- Triage lab (alert inbox) ---- */
.triage-inbox{display:flex; flex-direction:column; gap:10px; margin:18px 0; min-width:0}
/* Column wrapper so a ticket's hint ladder stacks UNDER its row instead of becoming a third
   column inside the .triage-item flex row. */
.triage-entry{display:flex; flex-direction:column; gap:6px; min-width:0}
/* min-width:0 so the text column can shrink; without it the row is pinned to its
   longest word and the ticket overflows on a phone. Never audited before S2-36 —
   AP1-TRIAGE-1 is one of the 152 labs the sweep could not see. */
.triage-item{display:flex; gap:14px; align-items:flex-start; padding:14px 16px; border-radius:var(--r-sm); min-width:0; flex-wrap:wrap;
  background:var(--lit-panel); border:1px solid var(--line); border-left:3px solid var(--line2); box-shadow:var(--shadow-sm);
  transition:border-color .15s var(--ease)}
.triage-item.correct{border-left-color:var(--good); background:rgba(var(--good-rgb),.05)}
.triage-item.wrong{border-left-color:var(--bad); background:rgba(var(--bad-rgb),.05)}
.triage-body{flex:1; min-width:0; min-width:0; overflow-wrap:anywhere}
.triage-body b{font-size:14px}
.triage-select{flex:0 0 auto; align-self:center; background:var(--bg2); border:1px solid var(--line2); color:var(--txt);
  border-radius:8px; padding:8px 11px; font-family:var(--sans); font-size:13px; min-width:0; max-width:100%}
.triage-select:focus{outline:none; border-color:var(--accent)}
.triage-actions{display:flex; gap:12px; align-items:center; flex-wrap:wrap; margin:8px 0}
#triageresult{font-weight:600; font-size:14px}

/* === DESIGN VAULT === */
/* ===================================================================
   Cyber-Labs — Award-Winning Dark Theme Redesign
   Inspired by Stripe, Linear, Vercel dark mode & SentinelOne
   Imported from design-vault.css — see that file for full source
   =================================================================== */
.coach-conf { display:inline-block; padding:1px 8px; border-radius:10px; font-size:11px; font-weight:600; }
.coach-conf.good { background:rgba(var(--good-rgb),0.15); color:var(--good); }
.coach-conf.mid { background:rgba(var(--warn-rgb),0.15); color:var(--warn); }
.coach-conf.low { background:rgba(var(--bad-rgb),0.15); color:var(--bad); }
.pq-opt { display:block; padding:8px 12px; margin:4px 0; border-radius:8px; cursor:pointer; transition:background .15s; border:1px solid var(--line2); }
.pq-opt:hover { background:var(--bg2); }
.pq-opt input { margin-right:8px; }
.ei-fb { margin-top:8px; padding:8px 12px; border-radius:8px; background:var(--bg2); font-size:13px; }
.pq-bar { display:flex; gap:10px; flex-wrap:wrap; margin-top:20px; padding:16px; background:var(--bg2); border-radius:12px; }
/* Pop Quiz — immediate feedback, per-item lock, and the always-visible score card */
.pq-progress { position:sticky; top:0; z-index:5; margin:0 0 12px; padding:8px 14px; font-weight:600; font-size:13px; background:var(--bg2); border:1px solid var(--line2); border-radius:10px; display:inline-block; }
.pq-check { margin-top:8px; }
.pq-opt.correct { background:rgba(var(--good-rgb),0.16); border-color:var(--good); box-shadow:0 0 0 2px rgba(var(--good-rgb),.16); }
.pq-opt.wrong { background:rgba(var(--bad-rgb),0.16); border-color:var(--bad); box-shadow:0 0 0 2px rgba(var(--bad-rgb),.16); }
.exam-item.pq-graded { position:relative; }
.exam-item.pq-graded .pq-opt { cursor:default; }
.exam-item.pq-correct { border-left:3px solid var(--good); }
.exam-item.pq-wrong { border-left:3px solid var(--bad); }
.pq-scorecard { display:flex; align-items:center; gap:20px; margin:20px 0; padding:22px 26px; border-radius:16px; background:var(--bg2); border:1px solid var(--line2); }
.pq-scorecard.pass { border-color:var(--good); box-shadow:0 0 0 1px rgba(var(--good-rgb),0.25); }
.pq-scorecard.fail { border-color:var(--warn); box-shadow:0 0 0 1px rgba(var(--warn-rgb),0.2); }
.pq-scnum { font-size:44px; font-weight:800; line-height:1; }
.pq-scnum span { font-size:22px; color:var(--faint); font-weight:600; }
.pq-scmeta { flex:1; }
.pq-scpct { font-size:22px; font-weight:700; margin-bottom:4px; }
.settings-card pre { font-family:var(--mono); font-size:12px; max-height:400px; overflow:auto; background:var(--input-bg); padding:12px; border-radius:8px; color:var(--code-txt); }

/* =================================================================
   SUBNET CITY (.sg-*) — the subnetting game.
   Stays inside the Lit Instrument system: emerald = network/"street",
   hollow neutral = host/"house", amber (--warn) is reserved for the
   mailbox/broadcast + stars. No new hues; the fun is motion + light.
   ================================================================= */
.sg-root{max-width:880px; margin:0 auto}
.sg-hud{display:flex; align-items:center; gap:14px; margin-bottom:16px}
.sg-back{background:transparent; border:1px solid var(--line2); color:var(--muted); border-radius:8px;
  padding:6px 12px; font:inherit; font-size:13px; cursor:pointer}
.sg-back:hover{border-color:var(--accent); color:var(--text)}
.sg-hud-t{display:flex; flex-direction:column}
.sg-hud-t b{font-size:17px; letter-spacing:.01em}
.sg-hud-t span{font-size:12px; color:var(--faint)}
.sg-hud-r{margin-left:auto; display:flex; align-items:center; gap:12px}

.sg-pips{display:inline-flex; gap:5px}
.sg-pips i{width:9px; height:9px; border-radius:50%; background:var(--bg2); border:1px solid var(--line2)}
.sg-pips i.on{background:var(--accent); border-color:var(--accent)}
.sg-miss{display:inline-flex; gap:4px}
.sg-miss i{width:8px; height:8px; border-radius:2px; background:var(--bg2); border:1px solid var(--line2)}
.sg-miss i.on{background:rgba(var(--bad-rgb),.7); border-color:var(--bad)}

.sg-stars{display:inline-flex; gap:2px}
.sg-stars svg{width:16px; height:16px; fill:none; stroke:var(--line2); stroke-width:1.5}
.sg-stars svg.on{fill:var(--warn); stroke:var(--warn)}
.sg-stars.big svg{width:34px; height:34px}

.sg-intro{display:flex; align-items:flex-start; gap:18px; padding:14px 16px; border:1px solid var(--line);
  border-radius:var(--r); background:linear-gradient(180deg,var(--panel2),var(--panel)); margin-bottom:16px}
.sg-intro p{margin:0; font-size:13.5px; line-height:1.6; color:var(--muted)}
.sg-k-net{color:var(--accent)}
.sg-k-host{color:var(--text)}
/* gap:6px split "0" from "/21 stars" — the gap is wider than a space, so it read as
   "0 /21 stars". Same fraction shape as .dayrun-ring-inner, which uses a 1px optical
   kern and is correctly silent; this one was six times a space. Detector F caught it
   once the sweep reached the 152 labs it had never seen. */
.sg-total{margin-left:auto; white-space:nowrap; font-size:13px; color:var(--faint); display:flex; align-items:center; gap:2px}
.sg-total b{color:var(--text); font-size:16px}

.sg-map{display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:10px}
.sg-lvcard{display:flex; align-items:center; gap:12px; text-align:left; padding:13px 15px; font:inherit;
  border:1px solid var(--line); border-radius:var(--r-sm); cursor:pointer; color:var(--text);
  background:linear-gradient(180deg,var(--panel2),var(--panel)); transition:.16s border-color,.16s box-shadow}
.sg-lvcard:hover:not(.locked){border-color:var(--accent); box-shadow:0 0 0 1px rgba(var(--accent-rgb),.35),0 0 14px rgba(var(--accent-rgb),.12)}
.sg-lvcard:active:not(.locked){transform:translateY(1px)}
.sg-lvcard.locked{opacity:.55; cursor:default}
.sg-lvnum{width:34px; height:34px; border-radius:9px; display:flex; align-items:center; justify-content:center;
  font-family:var(--mono); font-weight:700; color:var(--accent); background:rgba(var(--accent-rgb),.1);
  border:1px solid rgba(var(--accent-rgb),.3); flex:none}
.sg-lvname{display:flex; flex-direction:column; gap:2px; min-width:0; flex:1}
.sg-lvname b{font-size:14px}
.sg-lvname i{font-style:normal; font-size:11.5px; color:var(--faint)}
.sg-lock{font-size:11px; color:var(--faint); white-space:nowrap}
.sg-best{font-size:12px; color:var(--warn); font-family:var(--mono); white-space:nowrap}
.sg-rush .sg-lvnum{color:var(--warn); background:rgba(var(--warn-rgb),.1); border-color:rgba(var(--warn-rgb),.3)}
.badge.sg-new{background:rgba(var(--accent-rgb),.15); color:var(--accent); font-weight:700; letter-spacing:.06em}

.sg-stage{min-height:340px}
.sg-card{padding:18px; border:1px solid var(--line); border-radius:var(--r);
  background:linear-gradient(180deg,var(--panel2),var(--panel))}
.sg-brief{margin:0 0 14px; font-size:13.5px; line-height:1.6; color:var(--muted)}
.sg-request,.sg-ask{font-size:15px; margin:10px 0 14px; line-height:1.5}
.sg-slip{display:inline-block; font-size:12.5px; color:var(--faint); border:1px dashed var(--line2);
  border-radius:8px; padding:6px 10px; margin-bottom:10px}
.sg-target{font-size:22px; margin:8px 0 12px}
.sg-target b{font-family:var(--mono); color:var(--accent); font-size:30px}

/* bit switches / bit rows */
.sg-bits{display:flex; gap:6px; flex-wrap:wrap}
.sg-bit{display:flex; flex-direction:column; align-items:center; gap:3px; width:clamp(30px,9vw,44px);
  padding:6px 0; border:1px solid var(--line2); border-radius:8px; cursor:pointer; user-select:none;
  background:var(--bg2); transition:.12s background,.12s border-color}
.sg-bit i{font-style:normal; font-size:10px; color:var(--faint); font-family:var(--mono)}
.sg-bit b{font-family:var(--mono); font-size:16px; color:var(--faint)}
.sg-bit.on{background:rgba(var(--accent-rgb),.14); border-color:var(--accent)}
.sg-bit.on b{color:var(--accent)}
.sg-bit.lo{box-shadow:inset 0 0 0 2px var(--accent)}
.sg-bit.hi{box-shadow:inset 0 0 0 2px var(--warn)}
.sg-bit.pulse{animation:sgpulse .45s ease-in-out 2}
@keyframes sgpulse{50%{border-color:var(--accent); box-shadow:0 0 10px rgba(var(--accent-rgb),.5)}}
.sg-sum{font-family:var(--mono); font-size:20px; margin:12px 0; color:var(--muted)}
.sg-sum.hit{color:var(--accent)}
.sg-sum.over{color:var(--bad)}

/* the Bit Fence */
.sg-fencewrap{margin:6px 0 4px}
.sg-fence-legend{display:flex; justify-content:space-between; font-size:11px; margin-bottom:6px}
.sg-fence-legend .net{color:var(--accent)}
.sg-fence-legend .host{color:var(--faint)}
.sg-fencerow{display:flex; align-items:center; gap:8px}
.sg-prefixchip{font-family:var(--mono); font-size:14px; color:var(--accent); background:rgba(var(--accent-rgb),.1);
  border:1px solid rgba(var(--accent-rgb),.3); border-radius:8px; padding:8px 10px; white-space:nowrap}
.sg-fbits{display:flex; align-items:stretch; gap:3px; flex:1; min-width:0; touch-action:none}
.sg-fbit{flex:1; min-width:0; display:flex; align-items:center; justify-content:center; height:44px;
  border-radius:7px; border:1px solid var(--line2); transition:.14s background,.14s border-color}
.sg-fbit i{font-style:normal; font-family:var(--mono); font-size:clamp(8px,1.6vw,11px); color:inherit}
.sg-fbit.net{background:rgba(var(--accent-rgb),.16); border-color:rgba(var(--accent-rgb),.5); color:var(--accent)}
.sg-fbit.host{background:var(--bg2); color:var(--faint)}
.sg-fence{width:14px; flex:none; display:flex; align-items:center; justify-content:center; cursor:ew-resize;
  border:0; background:transparent; padding:0}
.sg-fence b{width:5px; height:52px; border-radius:3px; background:var(--warn);
  box-shadow:0 0 10px rgba(var(--warn-rgb),.5)}
.sg-fence:focus-visible b{outline:2px solid var(--warn); outline-offset:3px}
.sg-freadout{display:flex; flex-wrap:wrap; gap:8px 18px; align-items:baseline; margin:12px 0 4px;
  font-size:13px; color:var(--muted)}
.sg-freadout b{color:var(--text); font-size:16px}
.sg-cidr{font-family:var(--mono); font-size:15px; color:var(--faint)}
.sg-cidr b{color:var(--accent); font-size:22px}
.sg-freadout code{font-size:12.5px}
.sg-formula{font-style:normal; color:var(--faint); font-size:11.5px}

/* the little city */
.sg-city,.sg-minicity{display:flex; flex-direction:column; gap:5px; margin-top:10px}
.sg-street{display:flex; align-items:center; gap:8px; font-family:var(--mono); font-size:11px}
.sg-street.more{color:var(--faint); padding-left:4px; font-family:inherit; font-size:12px}
.sg-sign{color:var(--accent); min-width:38px; text-align:right}
.sg-mailbox{color:var(--warn); min-width:38px}
.sg-houses{display:flex; align-items:center; gap:3px; flex-wrap:wrap; flex:1}
.sg-houses i{width:9px; height:9px; border-radius:2px; background:rgba(var(--accent-rgb),.25);
  border:1px solid rgba(var(--accent-rgb),.35)}
.sg-houses em{font-style:normal; color:var(--faint); font-size:10.5px; font-family:inherit}

/* packets, bins, street spots, options */
.sg-packet{display:flex; align-items:center; gap:10px; padding:12px 14px; margin:6px 0 14px;
  border:1px solid rgba(var(--accent-rgb),.4); border-radius:var(--r-sm);
  background:rgba(var(--accent-rgb),.07); width:fit-content; max-width:100%}
.sg-packet span{font-size:20px}
.sg-packet code{font-size:15px}
.sg-packet i{font-style:normal; font-size:11.5px; color:var(--faint)}
.sg-bins{display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:10px}
.sg-bin{display:flex; flex-direction:column; gap:3px; align-items:flex-start; padding:14px; font:inherit;
  border:1px solid var(--line2); border-radius:var(--r-sm); background:var(--bg2); cursor:pointer;
  transition:.14s border-color,.14s box-shadow; color:var(--text)}
.sg-bin:hover{border-color:var(--accent)}
.sg-bin b{font-family:var(--mono); font-size:14px}
.sg-bin i{font-style:normal; font-size:11px; color:var(--faint)}
.sg-streetbar{display:grid; grid-template-columns:repeat(auto-fit,minmax(120px,1fr)); gap:10px}
.sg-spot{display:flex; flex-direction:column; gap:2px; align-items:center; padding:12px 8px; font:inherit;
  border:1px solid var(--line2); border-radius:var(--r-sm); background:var(--bg2); cursor:pointer; color:var(--text)}
.sg-spot:hover{border-color:var(--accent)}
.sg-spot b{font-family:var(--mono); font-size:17px}
.sg-spot i{font-style:normal; font-size:10.5px; color:var(--faint); font-family:var(--mono)}
.sg-spot.is-sign{border-color:var(--accent); background:rgba(var(--accent-rgb),.1)}
.sg-spot.is-mailbox{border-color:var(--warn); background:rgba(var(--warn-rgb),.1)}
.sg-opts{display:grid; grid-template-columns:repeat(auto-fit,minmax(110px,1fr)); gap:10px; margin-top:8px}
.sg-opts-col{grid-template-columns:repeat(auto-fit,minmax(190px,1fr))}
.sg-opt{display:flex; flex-direction:column; gap:2px; align-items:center; padding:13px 10px; font:inherit;
  font-size:16px; font-family:var(--mono); border:1px solid var(--line2); border-radius:var(--r-sm);
  background:var(--bg2); cursor:pointer; color:var(--text); transition:.14s border-color,.14s background}
.sg-opt:hover:not([disabled]){border-color:var(--accent)}
.sg-opt i{font-style:normal; font-size:10.5px; color:var(--faint)}
.sg-opt.hit{border-color:var(--good); background:rgba(var(--good-rgb),.12)}
.sg-opt.bad{border-color:var(--bad); background:rgba(var(--bad-rgb),.1)}
.sg-actions{margin-top:14px}

/* teach-on-miss */
.sg-teach{margin-top:14px; padding:14px 16px; border-radius:var(--r-sm);
  border:1px solid rgba(var(--warn-rgb),.4); background:rgba(var(--warn-rgb),.06)}
.sg-teach-h{font-weight:700; font-size:12.5px; letter-spacing:.04em; color:var(--warn); margin-bottom:8px}
.sg-teach p{margin:10px 0; font-size:13.5px; line-height:1.6}
.sg-teach .sg-bits{pointer-events:none}
.sg-teach-cmp{display:flex; flex-direction:column; gap:4px; font-family:var(--mono); font-size:14px}
.sg-teach-cmp span{display:inline-block; min-width:86px; font-family:inherit; font-size:11px; color:var(--faint)}

/* clear + rush screens */
.sg-clear{text-align:center; padding:36px 18px; border:1px solid var(--line); border-radius:var(--r);
  background:linear-gradient(180deg,var(--panel2),var(--panel))}
.sg-clear h2{margin:14px 0 4px}
.sg-clear p{color:var(--muted); margin:4px 0}
.sg-clear-note{font-size:12.5px; color:var(--accent)}
.sg-clear-btns{display:flex; gap:10px; justify-content:center; flex-wrap:wrap; margin-top:20px}
.sg-rushbar{height:7px; border-radius:4px; background:var(--bg2); border:1px solid var(--line2);
  overflow:hidden; margin-bottom:8px}
.sg-rushfill{height:100%; background:var(--accent); width:100%}
.sg-rushhud{display:flex; align-items:center; gap:14px; margin-bottom:12px; font-size:14px}
.sg-rushhud b{font-family:var(--mono); font-size:19px}
.sg-combochip{font-family:var(--mono); font-size:13px; color:var(--faint); transition:.15s color}
.sg-combochip.c3{color:var(--accent)}
.sg-combochip.c5{color:var(--accent); text-shadow:0 0 10px rgba(var(--accent-rgb),.5)}
.sg-combochip.c10{color:var(--warn); text-shadow:0 0 12px rgba(var(--warn-rgb),.55)}
.sg-rushq .sg-ask{font-size:17px}
.sg-rushscore{font-family:var(--mono); font-size:52px; font-weight:800; color:var(--accent)}
.sg-milestones{display:flex; gap:8px; justify-content:center; margin:14px 0}
.sg-milestones span{font-family:var(--mono); font-size:12px; padding:4px 10px; border-radius:14px;
  border:1px solid var(--line2); color:var(--faint)}
.sg-milestones span.hit{border-color:var(--warn); color:var(--warn); background:rgba(var(--warn-rgb),.08)}

@media (max-width:520px){
  .sg-prefixchip{font-size:11px; padding:6px 6px}
  .sg-fbit{height:38px}
  .sg-fence b{height:44px}
  .sg-intro{flex-direction:column; gap:8px}
  .sg-total{margin-left:0}
}
@media (prefers-reduced-motion: reduce){
  .sg-bit.pulse{animation:none; box-shadow:inset 0 0 0 2px var(--accent)}
}
.sg-pkico{display:flex; color:var(--accent)}
.sg-lvnum svg{display:block}

/* =================================================================
   DAILY RUN (.dayrun-*) — Plan Phase 4. Absorbs the old single-link
   "Do this next" banner into a bounded, honest 3-segment session arc.
   Same visual family as .xp-banner/.due-banner: panel gradient, thin
   accent border, no new hues. --good marks a done segment; the ring
   is a 3-way conic-gradient, same technique as .ring's single-value
   version above (Progress page).
   ================================================================= */
.dayrun-card{display:flex; gap:18px; align-items:center; margin-bottom:18px; padding:16px 18px;
  border-radius:var(--r); border:1px solid rgba(var(--accent-rgb),.28);
  background:linear-gradient(180deg,var(--panel2),var(--panel))}
.dayrun-card.all-done{border-color:rgba(var(--good-rgb),.35)}
.dayrun-ring{--rs:74px; width:var(--rs); height:var(--rs); border-radius:50%; flex:0 0 auto;
  display:flex; align-items:center; justify-content:center}
.dayrun-ring-inner{width:calc(var(--rs) - 14px); height:calc(var(--rs) - 14px); border-radius:50%;
  background:var(--lit-panel); box-shadow:var(--edge-hi); display:flex; align-items:baseline;
  justify-content:center; gap:1px; font-family:var(--mono)}
.dayrun-ring-inner b{font-size:19px; font-weight:800; color:var(--text)}
.dayrun-ring-inner small{font-size:11px; color:var(--faint)}
.dayrun-body{flex:1; min-width:0}
.dayrun-head{display:flex; align-items:center; gap:10px; margin-bottom:8px}
.dayrun-head h3{margin:0; font-size:14px; display:flex; align-items:center; gap:6px; color:var(--text)}
.dayrun-done-chip{display:inline-flex; align-items:center; gap:4px; font-size:11px; font-weight:700;
  color:var(--good); background:rgba(var(--good-rgb),.12); border:1px solid rgba(var(--good-rgb),.35);
  border-radius:99px; padding:2px 9px}
.dayrun-segs{display:grid; grid-template-columns:repeat(3,1fr); gap:8px}
.dayrun-seg{display:flex; align-items:flex-start; gap:8px; padding:9px 10px; border-radius:9px;
  border:1px solid var(--line2); background:var(--bg2); text-decoration:none; color:var(--text);
  transition:.14s border-color,.14s background; min-width:0}
.dayrun-seg:hover{border-color:var(--accent)}
.dayrun-seg.done{border-color:rgba(var(--good-rgb),.35); background:rgba(var(--good-rgb),.06)}
.dr-ico{display:flex; color:var(--accent); flex:0 0 auto; margin-top:1px}
.dayrun-seg.done .dr-ico{color:var(--good)}
.dr-txt{display:flex; flex-direction:column; gap:1px; min-width:0}
.dr-txt b{font-size:12.5px}
.dr-txt small{color:var(--faint); font-size:11px; line-height:1.35}
@media(max-width:720px){ .dayrun-segs{grid-template-columns:1fr} }
@media(max-width:560px){ .dayrun-card{flex-direction:column; align-items:stretch; text-align:center} }

/* =================================================================
   MASTERY GRID + BADGE SHELF (Progress page, Plan Phase 6).
   Tiers reuse --bad/--warn/--accent/--good — no new hues. "hollow" is
   a dashed outline (literally nothing to show yet, not a color at all).
   ================================================================= */
.mastery-grid{margin:28px 0}
.mastery-grid h2{display:flex; align-items:center; gap:8px; margin:0 0 2px; font-size:18px}
.mgrid-row{display:flex; align-items:center; gap:14px; padding:10px 0; border-top:1px solid var(--line)}
.mgrid-row:first-of-type{border-top:0}
.mgrid-tname{flex:0 0 168px; border-left:3px solid var(--cstripe,var(--accent)); padding-left:10px;
  display:flex; flex-direction:column; gap:1px}
.mgrid-tname b{font-size:13.5px}
.mgrid-tname small{color:var(--faint); font-size:11px}
.mgrid-cells{display:flex; gap:6px; flex-wrap:wrap}
.mgrid-cell{width:40px; height:40px; border-radius:9px; font:inherit; font-size:11.5px; font-weight:700;
  font-family:var(--mono); cursor:pointer; color:var(--muted); background:var(--bg2);
  border:1px solid var(--line2); transition:.14s transform,.14s box-shadow}
.mgrid-cell:hover{transform:translateY(-1px)}
.mgrid-cell.active{box-shadow:0 0 0 2px var(--accent)}
.mgrid-cell.tier-hollow{background:transparent; border:1px dashed var(--line2); color:var(--faint)}
.mgrid-cell.tier-low{background:rgba(var(--bad-rgb),.14); border-color:rgba(var(--bad-rgb),.4); color:var(--bad)}
.mgrid-cell.tier-mid{background:rgba(var(--warn-rgb),.14); border-color:rgba(var(--warn-rgb),.4); color:var(--warn)}
.mgrid-cell.tier-high{background:rgba(var(--accent-rgb),.14); border-color:rgba(var(--accent-rgb),.4); color:var(--accent)}
.mgrid-cell.tier-solid{background:var(--good); border-color:var(--good); color:var(--on-good)}
.mgrid-legend{display:flex; gap:14px; flex-wrap:wrap; margin-top:14px; font-size:11.5px; color:var(--faint)}
.mgrid-lg{display:flex; align-items:center; gap:5px}
.mgrid-lg i{width:11px; height:11px; border-radius:3px; display:inline-block}
.mgrid-lg i.tier-hollow{border:1px dashed var(--line2)}
.mgrid-lg i.tier-low{background:var(--bad)}
.mgrid-lg i.tier-mid{background:var(--warn)}
.mgrid-lg i.tier-high{background:var(--accent)}
.mgrid-lg i.tier-solid{background:var(--good)}
.mgrid-detail{margin-top:12px; padding:12px 14px; border-radius:var(--r-sm); border:1px solid var(--line2);
  background:linear-gradient(180deg,var(--panel2),var(--panel))}
.mgrid-detail-h{font-size:13.5px; margin-bottom:2px}
.mgrid-detail-sub{color:var(--muted); font-size:12.5px; margin-bottom:9px}
.mgrid-detail-links{display:flex; gap:6px; flex-wrap:wrap}
@media(max-width:640px){
  .mgrid-row{flex-direction:column; align-items:flex-start; gap:8px}
  .mgrid-tname{flex:none; border-left:0; border-top:2px solid var(--cstripe,var(--accent)); padding-left:0; padding-top:6px}
}

.prog-trends{margin:28px 0}
.prog-trends h2{display:flex; align-items:center; gap:8px; margin:0 0 10px; font-size:18px}
.prog-trend-row{display:flex; align-items:center; gap:14px; padding:8px 0; border-top:1px solid var(--line);
  flex-wrap:wrap}
.prog-trend-row:first-of-type{border-top:0}
.prog-trend-row b{flex:0 0 168px; font-size:13px}

.badge-shelf-sec{margin:28px 0}
.badge-shelf-sec h2{display:flex; align-items:center; gap:8px; margin:0 0 12px; font-size:18px}
.badge-shelf.big{display:flex; gap:10px; flex-wrap:wrap}
.badge-shelf.big .ach{width:88px; padding:12px 6px}
.badge-shelf.big .ach-i{font-size:22px}
.ach-hint{font-style:normal; font-size:9.5px; font-family:var(--mono); color:var(--faint); margin-top:1px}
.ach.got .ach-hint{color:var(--accent)}

/* =================================================================
   DAILY 10 (.daily10-*, .d10-*) — Plan Phase 7. Dashboard chip is a slim
   single-row banner in the same family as .dayrun-card/.xp-banner: panel
   gradient, thin accent border, --good only once the day's set is done.
   ================================================================= */
.daily10-chip{display:flex; align-items:center; gap:12px; margin-bottom:18px; padding:12px 16px;
  border-radius:var(--r); border:1px solid rgba(var(--accent-rgb),.22);
  background:linear-gradient(180deg,var(--panel2),var(--panel)); text-decoration:none; color:var(--text);
  transition:.14s border-color}
.daily10-chip:hover{border-color:var(--accent)}
.daily10-chip.done{border-color:rgba(var(--good-rgb),.35)}
.d10c-ico{display:flex; color:var(--accent); flex:0 0 auto}
.daily10-chip.done .d10c-ico{color:var(--good)}
.d10c-txt{display:flex; flex-direction:column; gap:1px; flex:1; min-width:0}
.d10c-txt b{font-size:13px}
.d10c-txt small{color:var(--faint); font-size:11.5px}
.d10c-go{display:flex; color:var(--faint); flex:0 0 auto}
@media(max-width:560px){ .daily10-chip{padding:11px 14px} }

.d10-miss{padding:12px 0; border-top:1px solid var(--line)}
.d10-miss:first-of-type{border-top:0}
.d10-miss-q{font-size:13.5px; margin-bottom:6px}
.d10-miss-a{display:flex; align-items:center; gap:6px; color:var(--good); font-size:12.5px; margin-bottom:8px}

/* =================================================================
   FLOW & FEEL — Plan Phase 5: keyboard layer, combo chip, session summary.
   No new hues: the combo tiers walk --accent -> --warn -> --good, which are
   already the app's "going well" ramp. Everything here respects
   prefers-reduced-motion, and every keyboard affordance is hidden on touch
   (a phone has no 1/2/3/4 keys to press, so the chips would be a lie).
   ================================================================= */

/* ---- combo chip ----
   Fixed bottom-LEFT on purpose: toast() owns bottom-centre (.toast above) and
   the two would overlap on every graded answer. z-index sits under the toast
   so a message always wins. */
.combo-chip{
  position:fixed; left:20px; bottom:22px; z-index:70; pointer-events:none;
  display:flex; align-items:center; gap:7px; padding:8px 13px; border-radius:99px;
  font-family:var(--mono); background:var(--panel2); color:var(--accent);
  border:1px solid rgba(var(--accent-rgb),.4);
  opacity:0; transform:translateY(8px) scale(.96); transition:.18s ease;
}
.combo-chip.show{opacity:1; transform:translateY(0) scale(1)}
.combo-chip b{font-size:16px; font-weight:800; line-height:1}
.combo-chip small{font-size:10.5px; color:var(--faint); letter-spacing:.02em}
.combo-chip[data-tier="1"]{box-shadow:0 0 0 3px rgba(var(--accent-rgb),.12)}
.combo-chip[data-tier="2"]{
  color:var(--warn); border-color:rgba(var(--warn-rgb),.5);
  box-shadow:0 0 0 4px rgba(var(--warn-rgb),.14), 0 0 18px rgba(var(--warn-rgb),.2);
}
.combo-chip[data-tier="3"]{
  color:var(--good); border-color:rgba(var(--good-rgb),.6);
  box-shadow:0 0 0 5px rgba(var(--good-rgb),.16), 0 0 26px rgba(var(--good-rgb),.3);
  animation:comboPulse 1.6s ease-in-out infinite;
}
@keyframes comboPulse{ 50%{ box-shadow:0 0 0 7px rgba(var(--good-rgb),.1), 0 0 34px rgba(var(--good-rgb),.38) } }
@media (prefers-reduced-motion: reduce){
  .combo-chip{transition:none; transform:none}
  .combo-chip.show{transform:none}
  .combo-chip[data-tier="3"]{animation:none}
}
@media(max-width:560px){ .combo-chip{left:12px; bottom:14px; padding:6px 11px} }

/* ---- keyboard layer ----
   The active question gets an outline (never a border/shadow that reflows) plus
   1..n chips on its options, generated with a CSS counter so there is no DOM
   churn per keystroke. Scoped to .kb-active so a 90-item page shows the numbers
   on exactly one question instead of 360 chips of visual noise. */
.exam-item.kb-active{outline:2px solid rgba(var(--accent-rgb),.45); outline-offset:6px; border-radius:var(--r-sm)}
.exam-item.kb-keys .pbq-opts,
.exam-item.kb-keys .ei-opts{counter-reset:kbopt}
.exam-item.kb-keys .pbq-opt::before,
.exam-item.kb-keys .pq-opt::before{
  counter-increment:kbopt; content:counter(kbopt);
  flex:0 0 auto; width:19px; height:19px; margin-right:2px; border-radius:5px;
  display:inline-flex; align-items:center; justify-content:center;
  font-family:var(--mono); font-size:10.5px; font-weight:700;
  background:var(--panel2); border:1px solid var(--line2); color:var(--muted);
}
.exam-item.kb-keys .pbq-opt:hover::before{border-color:var(--accent); color:var(--accent)}
/* Touch devices have no number row to press — never show a shortcut that can't be used. */
@media (hover: none){
  .exam-item.kb-active{outline:none}
  .exam-item.kb-keys .pbq-opt::before,
  .exam-item.kb-keys .pq-opt::before{display:none}
}

/* ---- session summary ---- */
.sess-sum{margin:18px 0; padding:16px 18px; border-radius:var(--r);
  border:1px solid rgba(var(--accent-rgb),.24); background:linear-gradient(180deg,var(--panel2),var(--panel))}
.sess-h{display:flex; align-items:center; gap:8px; font-size:13.5px; font-weight:700; margin-bottom:12px}
.sess-stats{display:grid; grid-template-columns:repeat(4,1fr); gap:10px}
.sess-stat{display:flex; flex-direction:column; gap:2px; padding:10px 12px; border-radius:9px;
  background:var(--bg2); border:1px solid var(--line2)}
.sess-stat b{font-family:var(--mono); font-size:19px; font-weight:800; line-height:1.1}
.sess-stat small{color:var(--faint); font-size:11px}
.sess-sub{color:var(--faint); font-size:11.5px; margin:14px 0 6px; font-weight:600}
.sess-delta{display:flex; align-items:center; gap:10px; padding:6px 0; border-top:1px solid var(--line)}
.sess-delta:first-of-type{border-top:0}
.sd-name{flex:1; min-width:0; font-size:12.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.sd-now{font-family:var(--mono); font-size:12.5px; color:var(--muted)}
.sd-diff{font-family:var(--mono); font-size:12px; font-weight:700; min-width:42px; text-align:right}
.sd-diff.up{color:var(--good)}
.sd-diff.down{color:var(--bad)}
.sd-diff.flat{color:var(--faint)}
.sess-actions{display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin-top:14px}
@media(max-width:560px){ .sess-stats{grid-template-columns:repeat(2,1fr)} }

/* =================================================================
   PHASE 8 — final ship QA (Fable). Two PRE-EXISTING surfaces scrolled the
   whole PAGE sideways at 375px, caught by tools/layout-audit.js run app-wide:
     • Settings `.set-row` — a fixed 110px label + a wide segmented control or
       <select> is `flex-wrap:nowrap`, so the row can't shrink below its content.
     • Labs `.filters` — the group wraps, but the cert `.seg` inside it is one
       non-wrapping row of 5 buttons, itself wider than the viewport.
   Every Phase 4-7 surface audited clean; these are the only two. Fix by
   reflowing at the app's standard 560px mobile breakpoint — no hidden content,
   no horizontal scroll, consistent with the redesign's "everything fits" rule.
   ================================================================= */
@media(max-width:560px){
  /* Stack each settings row: label sits above its control, both full-width. */
  .set-row{flex-direction:column; align-items:stretch; gap:6px}
  .set-row label{flex:none}
  .set-row .seg{flex-wrap:wrap}
  /* Let the Labs scope/cert segmented controls wrap to a second row. */
  .filters .seg{flex-wrap:wrap}
}

/* =================================================================
   ONBOARDING — proof of scale + honest feature strip (Sprint 01, S1-04).
   A stranger's first 30 seconds decide whether they invest an evening.
   The old screen described the product but never PROVED it: no counts, and
   it claimed labs were "Docker exercises" (false since they moved in-browser).
   These numbers are rendered from the live banks, so they can never go stale.
   Emerald accent only — no new hues, per the design system.
   ================================================================= */
.ob-proof{
  display:grid; grid-template-columns:repeat(4,1fr); gap:8px;
  margin:0 0 22px; padding:14px 10px;
  background:linear-gradient(180deg,var(--panel2),var(--panel));
  border:1px solid rgba(var(--accent-rgb),.22); border-radius:var(--r);
}
.ob-proof div{display:flex; flex-direction:column; gap:2px; align-items:center}
.ob-proof b{
  font-family:var(--mono); font-size:20px; font-weight:800; line-height:1;
  color:var(--accent); letter-spacing:-.02em;
}
.ob-proof small{font-size:10.5px; color:var(--faint); text-align:center; line-height:1.25}

/* 4 feature cards read better 2x2 than squeezed 4-across — the copy is a full
   sentence now, not a two-word label. */
.ob-tour{grid-template-columns:repeat(2,1fr); gap:10px}
.ob-step{line-height:1.45}
.ob-step span{margin-right:0; margin-bottom:2px}
.ob-step span svg{width:13px; height:13px}

@media(max-width:560px){
  .ob-proof{grid-template-columns:repeat(2,1fr); gap:12px 8px}
  .ob-tour{grid-template-columns:1fr}
}

/* =================================================================
   GLOBAL SEARCH (.sr-*) — Sprint 01.
   Results are teaching surfaces, not link lists: a matched question opens
   its answer + explanation in place. Emerald accent only; <mark> uses the
   accent wash rather than the browser's default yellow, which would be the
   only non-system hue in the entire app.
   ================================================================= */
.sr-bar{display:flex; align-items:center; gap:12px; margin:0 0 20px}
.sr-bar input{
  flex:1; min-width:0; background:var(--input-bg); border:1px solid var(--line2); color:var(--txt);
  border-radius:12px; padding:14px 16px; font-family:var(--sans); font-size:16px;
}
.sr-bar input:focus{outline:none; border-color:var(--accent); box-shadow:0 0 0 3px rgba(var(--accent-rgb),.15)}
.sr-total{flex:0 0 auto; font-family:var(--mono); font-size:12.5px; color:var(--faint)}

.sr-group{margin:0 0 26px}
.sr-group h2{display:flex; align-items:center; gap:9px; font-size:15px; margin:0 0 10px}
.sr-count{font-family:var(--mono); font-size:11.5px; color:var(--faint); background:var(--bg2);
  border:1px solid var(--line2); border-radius:99px; padding:1px 8px}
.sr-more{color:var(--faint); font-size:12px; margin:8px 0 0}

.sr-item{
  display:block; background:linear-gradient(180deg,var(--panel2),var(--panel));
  border:1px solid var(--line2); border-radius:var(--r-sm); padding:12px 14px; margin-bottom:8px;
  transition:.14s border-color;
}
.sr-item:hover{border-color:var(--accent)}
.sr-item summary{cursor:pointer; list-style:none; display:flex; flex-direction:column; gap:4px}
.sr-item summary::-webkit-details-marker{display:none}
.sr-item b{font-size:13.5px; font-weight:600; line-height:1.45; color:var(--txt)}
.sr-kind{font-family:var(--mono); font-size:10px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--accent); font-weight:700}
.sr-tag{font-family:var(--mono); font-size:11px; color:var(--faint)}
.sr-snip{font-size:12.5px; color:var(--muted); line-height:1.5}
.sr-link{text-decoration:none}
.sr-link b{display:block; margin:2px 0}

.sr-body{margin-top:11px; padding-top:11px; border-top:1px solid var(--line)}
.sr-ans{display:flex; align-items:center; gap:7px; color:var(--good); font-size:13.5px; margin-bottom:8px}
.sr-exp{color:var(--muted); font-size:13px; line-height:1.65}
.sr-body .chip-link{display:inline-block; margin-top:10px}

.sr-hint{padding:18px 0}
.sr-examples{display:flex; gap:7px; flex-wrap:wrap; margin-top:12px}

/* Accent wash instead of the browser's default yellow — the palette is
   deliberately two-hue and a stock <mark> would break it. */
mark{background:rgba(var(--accent-rgb),.22); color:inherit; border-radius:3px; padding:0 2px}

@media(max-width:560px){
  .sr-bar{flex-direction:column; align-items:stretch; gap:8px}
  .sr-bar input{font-size:16px}   /* keep >=16px so iOS doesn't zoom the viewport on focus */
  .sr-total{text-align:right}
}

/* =================================================================
   CONTENT REPORTS (.rep-*) — the "this answer looks wrong" path.
   Deliberately quiet: it must be findable when you need it and invisible
   when you don't, so it never competes with the explanation it sits under.
   ================================================================= */
.rv-report{margin-top:12px; padding-top:10px; border-top:1px solid var(--line)}
.rep-btn{
  background:transparent; border:1px solid var(--line2); color:var(--faint);
  border-radius:8px; padding:5px 10px; font-family:var(--sans); font-size:11.5px;
  cursor:pointer; display:inline-flex; align-items:center; gap:5px; transition:.14s;
}
.rep-btn:hover{color:var(--warn); border-color:var(--warn)}
.rep-box{margin-top:10px; padding:12px 14px; border-radius:var(--r-sm);
  background:var(--bg2); border:1px solid var(--line2)}
.rep-q{font-size:12.5px; margin-bottom:9px; color:var(--txt)}
.rep-opts{display:flex; gap:6px; flex-wrap:wrap}
.rep-note{margin:10px 0 0; font-size:11px; color:var(--faint); display:flex; align-items:center; gap:5px}
.rep-done{display:flex; align-items:center; gap:6px; color:var(--good); font-size:12.5px}
.rep-dump{width:100%; margin-top:10px; background:var(--input-bg); color:var(--txt);
  border:1px solid var(--line2); border-radius:8px; padding:10px; font-family:var(--mono); font-size:11.5px}

/* =================================================================
   TOUCH TARGETS — app-store / mobile readiness.

   The layout was already responsive, but nothing in the stylesheet ever
   asked whether the INPUT is a finger. At 375px an audit found 17 live
   controls under 32px tall (.launch.sm and .mark.sm are 29px, the theme
   segmented control 28px, inline help links 16px). Apple's HIG minimum is
   44pt and WCAG 2.5.5 AAA agrees; 29px is fine with a mouse and genuinely
   annoying with a thumb.

   WHY `pointer: coarse` AND NOT A WIDTH BREAKPOINT. Width tells you how
   much room there is; it does not tell you how precise the pointer is. A
   1280px touchscreen laptop needs these targets and a 375px-wide desktop
   window does not. The media feature asks the question we actually mean.

   WHY REAL HEIGHT AND NOT AN INVISIBLE ::after HIT AREA. Expanding the hit
   zone past the visual box is free in layout terms, which is why it's the
   usual trick — but in a dense row two expanded zones overlap, and the tap
   lands on whichever paints last. A tap that activates the WRONG control is
   worse than one that's merely small, so on touch we pay the real pixels.
   Nothing below changes a single pixel for mouse users.
   ================================================================= */
@media (pointer: coarse){
  /* Buttons whose small variants sit under the threshold. min-height rather
     than extra padding so the label stays optically centered and the type
     scale is untouched. */
  .launch.sm, .mark.sm, .launch.ghost.sm, .launch.sm.ghost,
  .seg button, .seg-btn, .rep-btn, .rv-rate-btn, .back{
    min-height:44px;
  }
  /* .back ("← All decks") is a bare inline anchor, so it needs a box first. */
  .back{display:inline-flex; align-items:center}
  /* These are inline by default; min-height is ignored on inline boxes. */
  .chip-link, .rep-btn{
    display:inline-flex; align-items:center; justify-content:center;
  }
  .chip-link{min-height:40px; padding-left:12px; padding-right:12px}

  /* Inline prose links (e.g. "console.anthropic.com" in Settings/About)
     measured 16–19px tall. WCAG 2.5.8 explicitly EXEMPTS a target that sits
     in a sentence, because its size is constrained by the surrounding
     line-height — so these are conformant as-is and enlarging the box would
     open ragged gaps mid-paragraph. What we can do for free is pad the
     line-box and pull the margin back by the same amount: the hit area grows
     vertically, the text does not move a pixel, and the paragraph is
     unchanged. Negative margin is what keeps this layout-neutral. */
  #app p a, #app li a, .muted a{padding:7px 2px; margin:-7px 0}

  /* A finger covering the control can't see a hover state, and :hover
     sticks on touch until you tap elsewhere. Feedback comes from :active. */
  .launch:hover, .mark:hover, .chip-link:hover, .rep-btn:hover{filter:none}
}

/* =================================================================
   BUILDER — tap/keyboard placement state.
   The picked item must be unmistakable: on a phone the gap between
   "I tapped it" and "did that register?" is where people give up.
   Emerald accent only, reusing the existing focus-ring idiom.
   ================================================================= */
.builder-hint{
  margin:0 0 10px; font-size:12.5px; color:var(--muted);
}
.builder-item{cursor:pointer}
.builder-item.picked{
  border-color:var(--accent);
  background:rgba(var(--accent-rgb),.14);
  box-shadow:0 0 0 2px rgba(var(--accent-rgb),.35);
}
/* Only while something is picked do the zones advertise themselves as
   targets — an always-on affordance would be noise the rest of the time. */
.builder-wrap:has(.builder-item.picked) .drop-zone,
.builder-wrap:has(.builder-item.picked) .builder-items{
  border-color:rgba(var(--accent-rgb),.55);
}
.drop-zone:focus-visible, .builder-item:focus-visible{
  outline:2px solid var(--accent); outline-offset:2px;
}
/* The explicit "end" target for sequencing labs. Dashed + quiet so it reads as
   a slot rather than another item you could reorder; only offers itself while
   something is actually picked, since it is meaningless otherwise. */
.builder-endslot{
  padding:9px 14px; border-radius:8px; border:1px dashed var(--line2);
  color:var(--faint); font-size:12.5px; font-weight:600; cursor:pointer;
  display:none; align-items:center;
}
.builder-items:has(.builder-item.picked) .builder-endslot{display:inline-flex}
.builder-endslot:hover{border-color:var(--accent); color:var(--accent)}
.builder-endslot:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
@media (pointer: coarse){ .builder-endslot{min-height:44px} }

/* =================================================================
   KEYBOARD FOCUS for the controls that wireActivatable() adopts.
   They were divs/spans/articles with click handlers, so they had no
   focus ring of any kind — becoming focusable without a visible ring
   would move the problem rather than fix it (you can reach it, but you
   cannot see where you are).
   ================================================================= */
.step-h:focus-visible,
.acc-h:focus-visible,
.card:focus-visible,
.siem-flag:focus-visible,
.sg-bit:focus-visible{
  outline:2px solid var(--accent); outline-offset:2px; border-radius:8px;
}
/* The step/accordion headers are full-width bars; inset the ring so it
   traces the bar rather than colliding with the panel border. */
.step-h:focus-visible, .acc-h:focus-visible{ outline-offset:-3px }

/* =================================================================
   iOS ZOOM-ON-FOCUS.
   Safari zooms the whole viewport when a focused input's font-size is
   under 16px, and then does NOT zoom back out — the learner is left on
   a magnified, sideways-scrolling page mid-question and has to pinch
   out manually. Fourteen inputs across the app were 12.5–15px; only
   the search box had been fixed.

   Scoped to coarse pointers so desktop keeps the compact type this
   design uses deliberately. NOT fixed with maximum-scale/user-scalable=no,
   which "solves" it by taking pinch-zoom away from everyone who needs it.
   ================================================================= */
@media (pointer: coarse){
  .check input,
  .config-input,
  .term-input,
  .triage-select,
  .review-filters select,
  .tutor-row input,
  .set-row input, .set-row select,
  .siem-toolbar input,
  .siem-q input,
  .shell-obj-row input,
  .pbq-match select, .pbq-fill input,
  .ob-row input,
  .exd-row input[type="date"],
  .search input{
    font-size:16px;
  }
}

/* =================================================================
   TOUCH TARGETS, PART 2 — the controls the first pass missed.

   The earlier pass set min-height:44px on the SMALL button variants
   (.launch.sm, .seg button, .rep-btn, .rv-rate-btn, .back) and gave the
   form controls only the 16px font-size fix above. Measured under a real
   coarse pointer, everything below still sat between 30px and 43px:
   full-size .launch and .mark, .hint-btn, .reset, every Mastery-Grid
   cell, .chip-link (explicitly pinned at 40), and every text input and
   <select> in the app.

   This was never caught because headless Chrome reports pointer:fine
   unless touch emulation is switched on, so every previous "verified at
   375px" run silently skipped the whole @media (pointer: coarse) block.
   Width was being tested; touch was not.
   ================================================================= */
/* =================================================================
   TARGET SIZE, PART 3 — the AA floor that applies to EVERY pointer.

   WCAG has two target-size criteria and this app was only honouring one:
     2.5.5 Target Size (Enhanced, AAA) = 44x44, TOUCH  -> the @media block below
     2.5.8 Target Size (Minimum, AA)   = 24x24, ALL pointers -> this block

   Everything sat inside the coarse-pointer media query, so on a desktop nothing
   enforced any minimum at all: 227 controls measured under 24x24 with a mouse,
   worst being .siem-flag at 20x20.8 and the reorder arrows at 24x18. A control
   too small to hit is too small with a mouse too -- it is just more forgiving.
   ================================================================= */
.siem-flag, .up, .dn, .chip-link, .refs a, #terminput, .term-input{min-height:24px}
.siem-flag, .up, .dn{min-width:24px}
.siem-flag, .chip-link, .up, .dn{display:inline-flex; align-items:center; justify-content:center}
.refs a{display:inline-block}

@media (pointer: coarse){
  .launch, .mark, .hint-btn, .reset, .mgrid-cell, .chip-link,
  .pbq-opt, .copy, .up, .dn, .skip,
  /* .check button stretched to match the input beside it at 375px and so LOOKED fine;
     at 320px the row wraps (S2-28) and it fell back to its natural 34px. Only sweeping
     more than one width shows a defect that exists at one width and not another. */
  .check button{
    min-height:44px;
  }
  /* min-height does nothing on an inline box, and several of these are
     inline anchors/buttons by default. */
  .chip-link, .copy, .skip{display:inline-flex; align-items:center; justify-content:center}
  /* Form controls: the 16px font-size above stops iOS zooming, but the box
     itself stayed under the threshold. */
  input:not([type=checkbox]):not([type=radio]):not([type=hidden]),
  select, textarea{
    min-height:44px;
  }
  /* The reorder arrows were the worst in the app at 18px — a control small
     enough that a mis-tap reorders the wrong row. */
  .up, .dn{min-width:44px}
  /* Settings rows carry an explicit min-height:24px (0,1,1) which outranks the
     bare element selectors above (0,0,1), so they need naming outright — the
     toggles and the question-count select were still 30–43px. */
  .set-row input, .set-row select, .switch-label{min-height:44px}

  /* ---- LAB CONTROLS. Invisible until the sweep read the ASSEMBLED catalog ----
     Every audit before this read `window.LABS` — labs.js only, 32 of 184 — so
     these six control classes across the other ten banks had never once been
     measured. 543 findings, all of them here:
       .siem-flag      16 x 20.8   the worst in the app; a log-row flag toggle
       .hint-gate      x 34        reveal-a-hint, on nearly every lab step
       #configSubmit   x 33        the submit button on config labs
       .chip           x 29        SIEM source filters
       .builder-item   x 41.6      drag items in builder labs
       #sgback         x 34.8      Subnet City back button
     .siem-flag needs min-width too: at 16px wide it is not a target at all. */
  .siem-flag, .hint-gate, .chip, .builder-item, #configSubmit, #sgback{
    min-height:44px;
  }
  .siem-flag{
    min-width:44px; display:inline-flex; align-items:center; justify-content:center;
  }
  .hint-gate, .chip, #configSubmit, #sgback{display:inline-flex; align-items:center; justify-content:center}
  .builder-item{display:flex; align-items:center}
  /* WIDTH counts too, and a height-only sweep hid these. The Mastery-Grid cells
     took min-height:44px above and were then declared fixed — they were still
     40px WIDE. Detector G checks both dimensions and caught it. At 320px the row
     is 5 cells + 4 gaps = 244px inside ~246px, so 44 is the largest that fits. */
  .mgrid-cell{min-width:44px}
  /* A standalone reference link, not inline prose — WCAG's inline exemption does
     not cover it, and it was 22px tall. */
  /* max-width is load-bearing: inline-flex sizes to max-content, so a long
     reference link stopped wrapping and overflowed its card by 28px. My own
     regression from S2-35, invisible until the sweep reached all 184 labs. */
  /* inline-BLOCK, not inline-flex: a flex box sizes to max-content, so the link
     stopped wrapping and overflowed its card. inline-block honours min-height AND
     wraps text normally. My own S2-35 regression, invisible until the sweep saw
     all 184 labs. */
  /* the anchor carries margin-right:12px, so a 100% max-width overflows the card by
     exactly that 12px — subtract it rather than leaving the arithmetic implicit. */
  .refs a{min-height:44px; display:inline-block; padding:11px 0; box-sizing:border-box; max-width:calc(100% - 12px)}
}
/* Exam code beside the section title on Progress — "A+ Core 1  220-1201".
   Quiet: the name is the heading, the code is provenance for anyone matching
   it against CompTIA's own blueprint. */
.prog-cert h2 .prog-code{
  margin-left:9px; font-family:var(--mono); font-size:11px; font-weight:600;
  letter-spacing:.04em; color:var(--faint); vertical-align:middle;
}

/* ===================================================================
   SCORE REPORT  (#/report/<store>/<idx>)
   A document, not a dashboard. Everything here is deliberately quieter
   than the rest of the app: this is the one surface a learner shows to
   somebody else, so it reads as a record rather than as a screen.
   =================================================================== */
.report-actions{display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin:0 0 12px}
.report-actions .back{margin:0}
.rep-status{min-height:1.2em; margin:0 0 10px; font-size:13px}
.rep-dump{width:100%; margin-top:10px; font-family:var(--mono); font-size:12px;
  background:var(--input-bg); color:var(--txt); border:1px solid var(--line2); border-radius:10px; padding:10px}

.report{
  max-width:760px; background:var(--lit-panel); border:1px solid var(--line);
  border-radius:16px; padding:26px 28px 22px;
}
.rep-head{display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding-bottom:14px; border-bottom:1px solid var(--line)}
/* No `gap` here on purpose. A bare text node inside a flex container becomes an
   anonymous flex ITEM, so gap:8px fell between "ExamPrep" and its "+" and the
   wordmark rendered as "ExamPrep +". The rail solves this by wrapping the
   wordmark in one element; this does the same, and spaces the mark with a
   margin so only the two real children are ever separated. */
.rep-brand{display:inline-flex; align-items:center; font-weight:640; letter-spacing:-.02em}
.rep-brand img{margin-right:8px}
.rep-brand i{color:var(--accent); font-style:normal}
.rep-kind{font-family:var(--mono); font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--faint)}

.rep-title{font-size:clamp(24px,3vw,32px); letter-spacing:-.045em; font-weight:560; margin:18px 0 4px}
.rep-code{margin-left:8px; font-family:var(--mono); font-size:12px; font-weight:600;
  letter-spacing:.04em; color:var(--faint); vertical-align:middle}
.rep-meta{color:var(--muted); margin:0 0 20px; font-size:14px}

.rep-scorebox{display:flex; align-items:baseline; flex-wrap:wrap; gap:8px 16px; margin-bottom:22px}
.rep-big{font-size:52px; font-weight:600; letter-spacing:-.05em; line-height:1}
.rep-big span{font-size:17px; font-weight:500; color:var(--faint); margin-left:6px; letter-spacing:-.01em}
.rep-big.pass{color:var(--good)} .rep-big.fail{color:var(--bad)}
.rep-verdict{font-family:var(--mono); font-size:12px; font-weight:700; letter-spacing:.1em;
  padding:4px 10px; border-radius:999px; border:1px solid var(--line2); color:var(--muted)}
.rep-verdict.pass{color:var(--good); border-color:rgb(var(--good-rgb)/.4)}
.rep-verdict.fail{color:var(--bad);  border-color:rgb(var(--bad-rgb)/.4)}
.rep-sub{flex:1 1 100%; color:var(--muted); font-size:13.5px}

.rep-facts{display:grid; grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr));
  gap:10px 22px; margin:0 0 22px; padding:16px 0; border-block:1px solid var(--line)}
.rep-facts dt{font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--faint); margin-bottom:3px}
.rep-facts dd{margin:0; font-size:14px}
.rep-nodetail{color:var(--muted); font-size:13.5px; margin:0 0 22px}

.rep-h2{font-size:15px; font-weight:620; letter-spacing:-.01em; margin:0 0 10px}
.rep-doms{width:100%; border-collapse:collapse; font-size:14px}
.rep-doms th, .rep-doms td{text-align:left; padding:9px 10px 9px 0; border-bottom:1px solid var(--line)}
.rep-doms thead th{font-size:11px; letter-spacing:.06em; text-transform:uppercase; color:var(--faint); font-weight:600}
.rep-doms tbody th{font-weight:450}
.rep-w{width:5.5em; color:var(--muted); font-family:var(--mono); font-size:12.5px}
/* The flex row lives in a wrapper INSIDE the cell, not on the cell.
   `display:flex` on a <td> takes it out of the table box model entirely, so the
   column stopped participating in width distribution and collapsed to its
   content — the bars rendered as 8px stubs. The cell stays a table-cell; the
   wrapper does the layout. */
.rep-doms th:nth-child(3), .rep-doms td:nth-child(3){width:min(46%,280px)}
.rep-score-in{display:flex; align-items:center; gap:10px}
.rep-bar{flex:1; min-width:0; height:7px; border-radius:999px; background:var(--input-bg);
  border:1px solid var(--line2); overflow:hidden}
.rep-bar i{display:block; height:100%; background:var(--accent)}
.rep-pct{font-family:var(--mono); font-size:12.5px; min-width:3.2em; text-align:right}

.rep-weak{margin:18px 0 0; font-size:14px}
.rep-note{margin:22px 0 0; padding-top:14px; border-top:1px solid var(--line);
  color:var(--faint); font-size:11.5px; line-height:1.5}

/* ---- PRINT ----
   Layout only. There is deliberately NO colour in this block: themeCtl's
   beforeprint hook swaps the document to the existing LIGHT token set for the
   duration of the print, so paper gets the palette that is already defined and
   already contrast-audited. Restating ~40 colour tokens here would be a second
   definition of the same idea, which is this codebase's most repeated defect. */
@media print{
  /* Chrome and controls are screen furniture; a printed record has neither. */
  /* .rail covers the mobile bottom tab bar too — that bar IS the rail,
     restyled by a media query, not a separate element. (A .tabbar selector
     here would match nothing and quietly claim to handle something it does
     not.) */
  .rail, .footer, .ad-slot, .report-actions, .rep-status, .rep-dump,
  #toast, .examrail{display:none !important}

  .shell, .content{display:block !important; margin:0 !important; padding:0 !important;
    max-width:none !important; width:auto !important; height:auto !important; overflow:visible !important}
  html, body{height:auto !important; overflow:visible !important}

  /* The card's elevation is a screen device — on paper it becomes a stray
     rounded box floating inside the page margin. The page IS the card. */
  .report{max-width:none; border:0; border-radius:0; padding:0; background:none}

  /* Keep the bar fills: browsers strip backgrounds by default, and a domain
     table whose bars all print empty is worse than no bars at all. */
  .rep-bar, .rep-bar i, .rep-verdict{-webkit-print-color-adjust:exact; print-color-adjust:exact}

  /* A domain row split across a page break is unreadable. */
  .rep-doms tr, .rep-facts div{break-inside:avoid}
  .rep-doms thead{display:table-header-group}   /* repeat headers on page 2+ */
  .rep-h2{break-after:avoid}

  @page{margin:16mm}
}

/* ===================================================================
   CONTENT COVERAGE  (About page)
   A disclosure table, not a marketing panel. Quiet type, real numbers,
   and the "balance" column allowed to say we are thin somewhere.
   =================================================================== */
/* Plain block, NOT flex. A flex container turns every bare text node into an
   anonymous flex ITEM, so `gap` falls between words and punctuation — this line
   rendered as "July 27, 2026 ." with a 7px gap before the full stop. (Second
   time in one sitting; .rep-brand had it as "ExamPrep +".) The icon needs no
   flex anyway: ico() already emits .i-inline, which baseline-aligns it. */
.cov-updated{color:var(--muted); font-size:14px; margin:0 0 20px}
.cov-block{margin:0 0 22px}
.cov-h{display:flex; align-items:baseline; flex-wrap:wrap; gap:8px;
  font-size:15px; font-weight:620; letter-spacing:-.01em; margin:0 0 8px}
.cov-code{font-family:var(--mono); font-size:11px; font-weight:600; letter-spacing:.04em; color:var(--faint)}
.cov-tot{margin-left:auto; font-size:12.5px; color:var(--muted); font-weight:450}
.cov-table{width:100%; border-collapse:collapse; font-size:13.5px}
.cov-table th, .cov-table td{text-align:left; padding:7px 10px 7px 0; border-bottom:1px solid var(--line)}
.cov-table thead th{font-size:10.5px; letter-spacing:.05em; text-transform:uppercase; color:var(--faint); font-weight:600}
.cov-table tbody th{font-weight:450}
.cov-n{width:6.5em; font-family:var(--mono); font-size:12.5px; color:var(--muted)}
.cov-cmp{width:7.5em; font-size:12.5px}
.cov-ok{color:var(--good)}
.cov-off{color:var(--warn); font-family:var(--mono); font-size:12px}
.cov-na{color:var(--faint)}
.cov-note{font-size:13px; line-height:1.6; margin:0 0 12px}

/* The table has five columns of numbers; on a phone that is a horizontal
   scroll or nothing. Scrolling the TABLE is right — scrolling the PAGE is the
   bug this codebase has fixed twice (the grid min-content trap), so the
   wrapper is what scrolls and it is capped to its container. */
@media (max-width:620px){
  .cov-block{overflow-x:auto; -webkit-overflow-scrolling:touch}
  /* Sized so BALANCE — the column that carries the actual disclosure — is
     partially visible rather than entirely off-screen. A cut-off column is its
     own scroll affordance; a column that starts past the edge just looks like
     the table ends there, and the one honest number nobody sees might as well
     not be rendered. */
  .cov-table{min-width:412px}
  .cov-n{width:4.6em}
  .cov-cmp{width:6.2em}
  .cov-table th, .cov-table td{padding-right:6px}
  .cov-tot{margin-left:0; flex-basis:100%}
}

/* ===================================================================
   PACING HUD — sits under the exam clock in the rail
   Three states, and the colours are the ones the app already uses for
   good/warn/bad. "Behind" is warn, never bad: the model is a proxy, and
   an alarm colour would claim a certainty the estimate does not have.
   =================================================================== */
.exam-pace{
  display:block; margin:-4px 0 10px; padding:6px 9px; border-radius:9px;
  border:1px solid var(--line2); background:var(--panel);
  font-size:11.5px; line-height:1.35; text-align:center;
}
.exam-pace[hidden]{display:none}
.pace-main{display:block; font-size:12.5px; font-weight:650; letter-spacing:-.01em}
.pace-sub{display:block; color:var(--muted); font-family:var(--mono); font-size:10.5px; margin-top:1px}
.pace-on    .pace-main, .exam-pace.pace-on    .pace-main{color:var(--good)}
.pace-ahead .pace-main, .exam-pace.pace-ahead .pace-main{color:var(--good)}
.pace-behind{border-color:rgb(var(--warn-rgb)/.42)}
.pace-behind .pace-main{color:var(--warn)}
.pace-done .pace-main{color:var(--muted)}

/* On phones the rail becomes the bottom bar and vertical room is scarce, so
   the HUD collapses to a single line instead of a two-line card. */
@media (max-width:860px){
  .exam-pace{margin:0; padding:4px 8px; display:flex; align-items:baseline; justify-content:center; gap:6px}
  .pace-main, .pace-sub{display:inline}
  .pace-sub{margin-top:0}
}

/* ===================================================================
   STORAGE FAILURE BANNER
   Deliberately NOT a toast. A toast says "something happened"; this is a
   CONDITION that persists until the learner frees space, and it must
   survive every route change — so it is appended to <body>, outside the
   app root that routing replaces.
   =================================================================== */
.storage-warn{
  position:fixed; left:50%; transform:translateX(-50%);
  bottom:calc(16px + env(safe-area-inset-bottom));
  z-index:9999; max-width:min(560px, calc(100vw - 24px));
  padding:12px 15px; border-radius:12px;
  background:var(--panel2); color:var(--txt);
  border:1px solid rgb(var(--bad-rgb)/.55);
  box-shadow:0 12px 34px rgb(0 0 0/.45);
  font-size:13.5px; line-height:1.5;
}
.storage-warn b{color:var(--bad)}
.storage-warn a{color:var(--accent)}
/* On phones the bottom tab bar owns the bottom edge; sit above it rather than
   under it, or the one message that matters is the one you cannot read. */
@media (max-width:860px){
  .storage-warn{bottom:calc(72px + env(safe-area-inset-bottom)); font-size:13px}
}
