/* dashboard + practice — layered on style.css */

/* Vertical only. The `padding` shorthand here used to reset the `0 32px` that
   .wrap sets, which left the page with no side gutter — harmless while the
   content was 780px inside a 1240px wrap, obvious the moment it wasn't. */
.dash, .play { padding-top: 46px; padding-bottom: 90px; }

/* identity */
.who { display: flex; align-items: center; gap: 18px; margin-bottom: 26px; }
.avatar { width: 68px; height: 68px; border-radius: 50%; border: 2px solid var(--line); background: var(--panel); }
.who h1 { font-size: 30px; letter-spacing: -.02em; }
.rankline { color: var(--muted); font-size: 15px; }

/* xp bar */
.bar-wrap { margin-bottom: 30px; }
.bar { height: 9px; background: var(--panel-2); border-radius: 999px; overflow: hidden; border: 1px solid var(--line); }
.bar-fill { height: 100%; background: linear-gradient(90deg, var(--accent), #7aa2ff); border-radius: 999px; transition: width .5s ease; }
.bar-note { margin-top: 9px; font-size: 13.5px; color: var(--muted); }

/* stat tiles */
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px,1fr)); gap: 12px; margin-bottom: 30px; }
.tile { background: var(--panel); border: 1px solid var(--line); border-radius: 13px; padding: 18px; text-align: center; }
.tile .n { font-size: 26px; font-weight: 700; }

/* quick action cards — replaces a flat row of buttons on the account page */
.quickactions { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin: 4px 0 48px; }
.qa-card {
  display: flex; align-items: center; gap: 14px; padding: 16px 18px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 13px;
  color: var(--text);
  transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.qa-card:hover { border-color: #3a4a78; transform: translateY(-2px); text-decoration: none; }
.qa-card .ic {
  width: 40px; height: 40px; border-radius: 11px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(140deg, rgba(77,124,255,.18), rgba(77,124,255,.05));
  border: 1px solid rgba(77,124,255,.25); color: var(--accent-2);
}
.qa-card .ic svg { width: 20px; height: 20px; }
.qa-text { display: flex; flex-direction: column; }
.qa-text strong { font-size: 15px; }
.qa-text span { color: var(--muted); font-size: 13px; margin-top: 1px; }
@media (max-width: 620px) { .quickactions { grid-template-columns: 1fr; } }

/* account page: servers + leaderboard side by side instead of an endless
   single-column stack, since both are similarly-sized "where do I stand" widgets */
.acct-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 36px; margin-bottom: 14px; }
.acct-grid section { padding: 0; }
@media (max-width: 860px) {
  .acct-grid { grid-template-columns: 1fr; gap: 44px; }
}
.tile .l { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; margin-top: 3px; }

/* chips (tabs / pickers) */
.chips { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.chip {
  background: var(--panel); border: 1px solid var(--line); color: var(--muted);
  padding: 8px 16px; border-radius: 999px; font-size: 14px; cursor: pointer;
  font-family: inherit;
}
.chip:hover { color: var(--text); }
.chip.on { background: var(--accent); border-color: var(--accent); color: #fff; }

/* CodeType — deliberately minimal: no bordered panels, text floats directly
   on the page background, switching a setting resets straight into a fresh
   test (no separate "start" step). Controls live in a right-hand sidebar —
   code is narrower/more compact than prose, so there's room to spare. */
/* no max-width of its own: it takes .wrap's 1240px so the page lines up with
   the nav and with Home and Learn, which sat 170px wider than this on each
   side and made CodeType look like a different site */
.ct-page { padding-top: 54px; }

/* settings sit in one centered row above the text */
.ct-toolbar {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap;
  gap: 12px; margin: 0 0 22px;
}
.ct-toolbar .chips { margin-bottom: 0; gap: 3px; }
.ct-toolbar .chip {
  background: none; border-color: transparent; color: var(--muted);
  padding: 6px 12px; font-size: 12.5px; border-radius: 7px;
  font-family: ui-monospace, Menlo, monospace;
}
.ct-toolbar .chip:hover { background: var(--panel); color: var(--text); }
.ct-toolbar .chip.on { background: rgba(77,124,255,.16); color: var(--accent-2); font-weight: 600; }
/* the duration row keeps its space when it doesn't apply, so switching
   modes never shifts the rest of the toolbar sideways */
.ct-group { display: flex; transition: opacity var(--dur-fast) var(--ease); }
.ct-group.idle { opacity: .22; pointer-events: none; }
.tsep { width: 1px; height: 18px; background: var(--line); }

/* While a test is running the page strips back to the logo, the counter and
   the text — settings, progress bar and the restart row all go. Layout copied
   from the typing tools this mode is modelled on; the colours stay ours.
   Opacity rather than display:none so nothing reflows and the text doesn't
   jump the instant you start typing. */
.typing .ct-toolbar,
.typing .ct-progress,
.typing .ct-meta,
.typing .guestbar,
.typing header nav {
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease);
}
.typing header .brand-text { opacity: .35; transition: opacity var(--dur-fast) var(--ease); }

/* the typing block is centered in the page */
/* Sized off the type, not the other way round: snippet lines are written to a
   55-character limit, and at 26px a character is ~16.4px, so the stage has to
   clear ~905px or the longest lines wrap mid-token. 920 leaves a little room.
   The old 884px ceiling is gone — that existed because the countdown hung in
   the gutter beside this, and it sits above the text now. */
.ct-stage { position: relative; max-width: 920px; margin: 0 auto; }
/* Sits directly above the text, left-aligned with it, and says whatever the
   current mode measures: seconds left when timed, words done out of total
   when it's a fixed snippet, elapsed time in zen. One slot, three readings —
   which is what frees the side gutter the stage used to be capped by. */
.ct-countdown {
  font-family: ui-monospace, Menlo, monospace; font-size: 34px; font-weight: 700;
  color: var(--accent-2); font-variant-numeric: tabular-nums; line-height: 1;
  margin-bottom: 10px;
  transition: opacity var(--dur-fast) var(--ease);
  cursor: pointer; user-select: none; white-space: nowrap;
}
/* no clock to cycle when the reading is a word count */
.ct-countdown.counting { cursor: default; }
.ct-countdown.idle { opacity: .18; }
/* hundredths make the string noticeably wider; a little smaller keeps the
   line from dominating the text it sits above */
.ct-countdown.ms { font-size: 28px; }
/* "off" greys out the face at full size rather than disappearing: the clock is
   its own toggle, so removing it entirely would leave no way to switch it back
   on, and keeping the size means cycling modes doesn't shift the layout */
.ct-countdown.off { color: var(--muted); opacity: .3; }
.ct-countdown.off:hover { opacity: .55; }

/* The countdown used to hang in the gutter beside the stage, which needed its
   own breakpoint so a wide hundredths clock didn't run off the screen. It sits
   inline above the text now, so that whole class of overflow is gone and the
   gutter no longer caps how wide the stage may be. */
@media (max-width: 1020px) {
  .ct-toolbar { gap: 8px; margin-bottom: 16px; }
  .ct-progress { margin-bottom: 28px; }
  .tsep { display: none; }
}

/* rows: leaderboard + topics */
.board { background: var(--panel); border: 1px solid var(--line); border-radius: 13px; overflow: hidden; }
.row { display: flex; align-items: center; gap: 14px; padding: 13px 18px; border-bottom: 1px solid var(--line); font-size: 14.5px; }
.row:last-child { border-bottom: none; }
.row.you { background: rgba(77,124,255,.12); }
.rpos { width: 34px; flex: none; color: var(--muted); font-variant-numeric: tabular-nums; }
.medal {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 50%; font-size: 12px; font-weight: 700;
  font-family: ui-sans-serif, system-ui, sans-serif;
}
.medal.m1 { background: linear-gradient(140deg, #ffd76a, #e8a415); color: #3a2600; }
.medal.m2 { background: linear-gradient(140deg, #e4e7f0, #aab0c4); color: #20232c; }
.medal.m3 { background: linear-gradient(140deg, #e3a06b, #b5713a); color: #2a1808; }
.rname { flex: 1; font-weight: 550; }
.rsub { color: var(--muted); font-size: 13px; }
.rpct { font-variant-numeric: tabular-nums; font-weight: 600; min-width: 74px; text-align: right; }
.rbar { flex: 1; height: 7px; background: var(--panel-2); border-radius: 999px; overflow: hidden; max-width: 240px; }
.rbar > span { display: block; height: 100%; background: var(--accent); border-radius: 999px; }

/* practice */
.play { max-width: 780px; }
/* 780px is the right column for reading a lesson and the wrong one for a shelf
   of categories — four cards were getting half a laptop screen. The catalogue
   views opt into the full width; everything else keeps the reading measure. */
.play.wide { max-width: 1240px; }
.play h1 { font-size: 34px; letter-spacing: -.02em; margin-bottom: 6px; }
.picker { display: grid; gap: 22px; margin: 28px 0 26px; }
.pick-group label { display: block; font-size: 12.5px; text-transform: uppercase; letter-spacing: .07em; color: var(--muted); margin-bottom: 10px; }

.qmeta { display: flex; justify-content: space-between; font-size: 13px; color: var(--muted); margin-bottom: 14px; }
.qtext { font-size: clamp(21px, 3.4vw, 28px); line-height: 1.35; letter-spacing: -.015em; margin-bottom: 28px; font-weight: 600; }
/* ── The two reading games, played ──────────────────────────────────────────
   Made to look like the pictures of themselves on the shelf, because those
   turned out to be the better design: content on the page background, one
   window where a window is warranted, and nothing else. Held to a column
   rather than run to the full width — a 1200px-wide question with four short
   answers under it is mostly emptiness. */
/* The play screen is the card's picture at full size: same window, same tinted
   answer boxes, same hue. --hue is set per game when the view opens, so landing
   here from a card is a continuation of it rather than arrival somewhere else. */
.qplay { max-width: 760px; --hue: 77,124,255; }

/* Everything that used to be scattered across a breadcrumb, a meta row and a
   pair of pickers, in one line: where you are, what you are set to, and how
   many you have left. It doubles as the controls — changing a chip loads the
   next question at the new setting, so switching language mid-run no longer
   means going back to a setup page.

   Modelled on CodeType's bar, because this is the same problem: a row of
   settings that has to be reachable without ever being the thing you look at. */
.playbar {
  display: block; margin-bottom: 26px; padding-bottom: 16px;
  border-bottom: 1px solid var(--line);
}
/* Getting out and how you are doing — the two things that are about the session
   rather than the question. On their own line, because a back arrow sitting in
   the same row as nine language chips reads like a tenth one. */
/* The way out on the left, the count on the right — both on the line above the
   settings, and both aligned to the content column rather than to the page. The
   arrow spent a version out in the page's own corner, 200px from anything it
   related to. */
.playbar-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-bottom: 12px; min-height: 34px;
}

/* Centred, and stacked in the order you would say them: which language, then
   how hard. */
.playbar-set { display: flex; gap: 2px; flex-wrap: wrap; justify-content: center; }
.playbar-set + .playbar-set { margin-top: 6px; }
.playbar-set button {
  background: none; border: 1px solid transparent; color: var(--muted);
  font: inherit; font-size: 12px; padding: 5px 10px; border-radius: 7px;
  cursor: pointer; font-family: ui-monospace, Menlo, monospace;
  transition: background .15s var(--ease), color .15s var(--ease);
}
.playbar-set button:hover { background: rgba(var(--hue), .12); color: var(--text); }
.playbar-set button.on { background: rgba(var(--hue), .2); color: var(--text); font-weight: 600; }
.playbar-left { font-size: 12px; color: var(--muted);
                font-variant-numeric: tabular-nums; flex: none; }

/* The bar takes the colour too, so the line under it belongs to the game rather
   than being the site's generic divider. A wash sat here as well until the page
   orbs took it over — two of the same hue over each other only muddied it. */
/* No panel. The settings were sitting in a tinted, rounded box that framed them
   as a thing separate from the question — on a page whose whole point is that
   there is nothing between you and the question. The colour stays in the rule
   under them, which is enough to say the row belongs to this game. */
.playbar { border-bottom-color: rgba(var(--hue), .3); }
.qmeta-right { display: inline-flex; gap: 14px; align-items: baseline; }
.qmeta-right b { color: var(--accent-2); font-weight: 650; }

/* The same shape the motif draws: a plain rounded window sitting on the page,
   with no second panel around it. */
.codebox {
  background: rgba(6, 8, 15, .62); border: 1px solid rgba(var(--hue), .26);
  border-radius: 14px; padding: 20px 22px; margin: 0 0 22px;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 14.5px; line-height: 1.75; color: #dbe3ff; overflow-x: auto;
}
/* What it is supposed to print. Smaller and green, so it reads as the target
   rather than a second program. */
.codebox-out {
  color: var(--green); font-size: 13px; padding: 12px 16px; margin-bottom: 14px;
  border-color: rgba(74,222,128,.22); background: rgba(74,222,128,.05);
}
/* The one you edit. Same window, made writable — the point of this game is that
   the broken code and the fixed code are the same object. */
.codebox-edit {
  display: block; width: 100%; resize: vertical; margin-bottom: 16px;
  font-size: 14px; color: var(--text);
}
.codebox-edit:focus { outline: none; border-color: rgba(var(--hue), .6); }

/* What it should do, and what it does instead. Two lines, because that is two
   sentences' worth of information and it was in three bordered panels. */
.qbrief { font-size: 14.5px; color: var(--muted); line-height: 1.6; margin: -12px 0 8px; }
.qsymptom {
  font-size: 13.5px; color: #ff9b9d; line-height: 1.6; margin: 0 0 20px;
  padding-left: 12px; border-left: 2px solid rgba(237,66,69,.4);
}

/* ── Order the lines ────────────────────────────────────────────────────────
   The lines look like the code they are — same window material as .codebox,
   one row each — with the position on the left so the order you are building
   is readable as a list rather than inferred from where things sit. */
.ordlist { list-style: none; margin: 0 0 20px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.ordrow {
  display: flex; align-items: center; gap: 12px;
  background: rgba(6, 8, 15, .62); border: 1px solid rgba(var(--hue), .26);
  border-radius: 10px; padding: 9px 12px;
  cursor: grab; touch-action: none;   /* the browser must not claim the drag */
  user-select: none;
}
.ordrow.dragging {
  cursor: grabbing; position: relative; z-index: 5;
  border-color: rgba(var(--hue), .7); background: rgba(var(--hue), .14);
  box-shadow: 0 14px 30px -14px rgba(0, 0, 0, .8);
}
/* The handle. Says the row is draggable before you try it — the arrows are
   still there for keyboards, and for anyone who would rather not drag. */
.ordgrip {
  flex: none; width: 9px; height: 14px; opacity: .4;
  background: radial-gradient(circle, currentColor 1px, transparent 1.2px) 0 0 / 4.5px 4.5px;
  color: var(--muted);
}
.ordrow:hover .ordgrip { opacity: .7; }
.ordnum {
  flex: none; width: 20px; text-align: right; font-size: 11px;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
.ordcode {
  flex: 1; min-width: 0; font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 13.5px; line-height: 1.6; color: #dbe3ff; white-space: pre;
  overflow-x: auto;
}
.ordmove { flex: none; display: flex; gap: 4px; }
.ordbtn {
  background: none; border: 1px solid rgba(var(--hue), .28);
  color: rgb(var(--hue)); font: inherit; font-size: 12px; line-height: 1;
  width: 26px; height: 26px; border-radius: 7px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .15s var(--ease), border-color .15s var(--ease);
}
.ordbtn:hover:not(:disabled) { background: rgba(var(--hue), .18); border-color: rgba(var(--hue), .55); }
.ordbtn:disabled { opacity: .25; cursor: default; }

/* Two up, the way the motif draws them. Four full-width bars stacked down the
   page read as a form to fill in rather than a question to answer. */
.options { display: grid; gap: 10px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
.opt {
  background: var(--panel); border: 1px solid var(--line); color: var(--text);
  padding: 17px 20px; border-radius: 12px; font-size: 15.5px; text-align: left;
  cursor: pointer; font-family: inherit; transition: border-color .15s, background .15s;
}
.opt:hover:not(:disabled) { border-color: #3a4a78; background: var(--panel-2); }
/* Inside a play screen they take the game's colour, the way the motif draws
   them. Scoped to .qplay so every other .opt on the site is untouched. */
.qplay .opt { background: rgba(var(--hue), .09); border-color: rgba(var(--hue), .28); }
.qplay .opt:hover:not(:disabled) { background: rgba(var(--hue), .17); border-color: rgba(var(--hue), .5); }
/* Right and wrong still win — they are the answer, not the decoration. */
.qplay .opt.right { border-color: #2f9e5f; background: rgba(74,222,128,.13); }
.qplay .opt.wrong { border-color: #b8434a; background: rgba(237,66,69,.13); }
.opt:disabled { cursor: default; }
.opt.right { border-color: #2f9e5f; background: rgba(74,222,128,.13); }
.opt.wrong { border-color: #b8434a; background: rgba(237,66,69,.13); }

.qfoot { margin-top: 22px; }
/* One word, small, and the game's colour rather than the site's link blue —
   it is part of the question, not a navigation. */
/* The way out: an arrow, the size of the hint, in the game's colour. It sits
   with the other things you do rather than above the question. */
/* Hint on the left, Skip on the right: one is help with this question, the
   other is leaving it. Opposite intentions, opposite ends. */
.qfoot-row { display: flex; align-items: center; gap: 8px; }
/* Back and Hint are the same object at different jobs, so they are pinned to
   one height — 34px — and only ever change together. */
/* No outline. A bordered pill made the way out look like one of the settings
   below it; it is the only glyph on the page, which is enough to find. The hit
   area stays the same size — the padding is still there, just not drawn. */
.backbtn {
  background: none; border: 0;
  color: rgb(var(--play-hue, var(--hue, 77,124,255))); font: inherit; font-size: 18px;
  line-height: 1; padding: 0 12px 0 0; border-radius: 8px; cursor: pointer;
  display: inline-flex; align-items: center; min-height: 34px;
  opacity: .75; transition: opacity .15s var(--ease), transform .15s var(--ease);
}
.backbtn:hover { opacity: 1; transform: translateX(-2px); }
/* Keyboard users still need to see where they are, so focus keeps a ring even
   though hover does not. */
.backbtn:focus-visible { outline: 2px solid rgb(var(--play-hue, var(--hue, 77,124,255))); outline-offset: 3px; }

/* Hint, Skip and Reset are the same kind of thing: the ways past or around a
   question that cost you nothing. One look for all three, differing only in
   which side the padding sits on, so the single bordered control on the screen
   is always the one that commits an answer. */
.hintbtn, .skipbtn, .btn-quiet {
  background: none; border: 0; font: inherit; font-size: 13px;
  color: var(--muted); cursor: pointer;
  display: inline-flex; align-items: center; min-height: 34px;
  transition: color .15s var(--ease);
}
.hintbtn:hover, .skipbtn:hover, .btn-quiet:hover { color: var(--text); }
.hintbtn, .skipbtn, .btn-quiet {
  font-family: inherit; font-weight: 400; letter-spacing: 0; border-radius: 6px;
}
.hintbtn  { padding: 6px 14px 6px 0; }
.skipbtn  { padding: 6px 0 6px 14px; }
.btn-quiet { padding: 6px 12px; }
.hintbtn:focus-visible, .skipbtn:focus-visible, .btn-quiet:focus-visible {
  outline: 2px solid rgb(var(--play-hue, var(--hue, 77,124,255))); outline-offset: 3px;
}
@media (max-width: 620px) { .options { grid-template-columns: 1fr; } }
.linkbtn { background: none; border: none; color: var(--accent-2); cursor: pointer; font-size: 14px; font-family: inherit; padding: 0; }
.linkbtn:hover { text-decoration: underline; }
/* The hint takes the Hint button's place in the footer row rather than opening
   a new line under it. Revealing it used to hide the button, leaving a hole on
   the left and a whole extra row below — a lot of movement for one sentence. */
.hint {
  color: var(--muted); font-size: 13.5px; line-height: 1.45;
  margin: 0; padding-left: 12px; flex: 1; min-width: 0;
  border-left: 2px solid rgba(var(--play-hue, var(--hue, 77,124,255)), .5);
}

/* result */
.result { text-align: center; padding: 40px 0; }
.rbadge { font-size: 13px; text-transform: uppercase; letter-spacing: .09em; font-weight: 700; margin-bottom: 14px; }
.rbadge.ok { color: var(--green); }
.rbadge.no { color: #f2777d; }
.rline { font-size: 19px; margin-bottom: 6px; }
.rxp { font-size: 30px; font-weight: 700; color: var(--green); margin-bottom: 28px; min-height: 36px; }
.result .btn { margin: 0 5px; }
.capped { text-align: center; padding: 50px 0; }

/* animated XP progress on result screens — bar-fill already transitions
   width (see .bar-fill in app.css), this just triggers it from a real
   "before" position instead of snapping straight to the final one */
.rbar-wrap { max-width: 280px; margin: 4px auto 26px; }
.rbar-wrap .bar-note { font-size: 12px; margin-top: 8px; }
/* A filled gold pill with dark bold text is a button, whatever it says — and
   it sat in the middle of the result, right where you look for the way on.
   Someone finishing the daily tried to click it. It announces something; it
   does nothing. So it now reads like the XP line above it: bright text on the
   page, no fill, no border, nothing to press. */
.levelup-badge {
  display: block; margin: 6px 0 16px; padding: 0;
  background: none; border-radius: 0;
  color: var(--gold); font-weight: 750; font-size: 13px;
  letter-spacing: .14em; text-shadow: 0 0 20px rgba(255,198,61,.4);
  opacity: 0; transform: scale(.7);
}
.levelup-badge.play { animation: levelPop .6s var(--ease) forwards; }
@keyframes levelPop {
  0% { opacity: 0; transform: scale(.7); }
  60% { opacity: 1; transform: scale(1.12); }
  100% { opacity: 1; transform: scale(1); }
}
.rbadge.levelup { animation: badgePulse .6s var(--ease); }
@keyframes badgePulse { 0% { transform: scale(1); } 40% { transform: scale(1.2); } 100% { transform: scale(1); } }

@media (max-width: 620px) {
  .who h1 { font-size: 24px; }
  .avatar { width: 54px; height: 54px; }
  .rsub { display: none; }
}

.pager { display: flex; align-items: center; justify-content: center; gap: 16px; margin-top: 14px; }
.pager button:disabled { opacity: .4; cursor: default; }
.pager span { color: var(--muted); font-size: 13.5px; }

/* per-server standings */
.servers { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px,1fr)); gap: 12px; }
.server { background: var(--panel); border: 1px solid var(--line); border-radius: 13px; padding: 16px 18px; }
.srow { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.sname { display: flex; align-items: center; gap: 9px; font-weight: 600; font-size: 15px; }
.sicon { width: 24px; height: 24px; border-radius: 50%; flex: none; object-fit: cover; }
.sicon.blank { background: var(--panel-2); border: 1px solid var(--line); display: grid; place-items: center; font-size: 12px; color: var(--muted); }
.spos { font-weight: 700; font-variant-numeric: tabular-nums; }
.spos .of { font-weight: 400; font-size: 12px; color: var(--muted); }
.smeta { color: var(--muted); font-size: 13.5px; margin-top: 7px; }

/* curriculum */
.track { background: var(--panel); border: 1px solid var(--line); border-radius: 14px; padding: 22px; margin-bottom: 18px; }
.thead { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; margin-bottom: 16px; }
.thead h2 { font-size: 21px; margin: 0; }
.tlevel { flex: none; font-size: 12px; color: var(--muted); border: 1px solid var(--line); padding: 4px 11px; border-radius: 999px; }
.tprog { display: flex; align-items: center; gap: 12px; margin-bottom: 18px; }
.tprog .bar { flex: 1; }
.tprog span { font-size: 13px; color: var(--muted); white-space: nowrap; }

.llist { display: grid; gap: 7px; }
.lrow {
  display: flex; align-items: center; gap: 13px; width: 100%; text-align: left;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px;
  padding: 12px 15px; color: var(--text); font-size: 14.5px; font-family: inherit; cursor: pointer;
}
.lrow:hover:not(:disabled) { border-color: #3a4a78; }
.lrow.done { border-color: rgba(74,222,128,.35); }
.lnum { width: 24px; flex: none; text-align: center; color: var(--muted); font-size: 13px; display: inline-flex; align-items: center; justify-content: center; }
.lrow.done .lnum { color: var(--green); }
.ltitle { flex: 1; font-weight: 550; }
.lxp { font-size: 12.5px; color: var(--muted); }

/* Review rows sit in the same list as lessons and borrow .lrow, but they
   aren't lessons — accent-tinted so it's obvious the row does something
   different from the ones above it. */
.lreview {
  background: rgba(77,124,255,.10); border-color: rgba(77,124,255,.30);
  margin-top: 4px;
}
.lreview:hover:not(:disabled) { border-color: var(--accent-2); }
.lreview .lnum, .lreview .ltitle { color: var(--accent-2); }
/* the final review closes out a whole subject, so it sits apart from the
   chapters rather than inside the last one */
.lfinal { margin-top: 14px; }

/* lesson */
.lmeta { color: var(--muted); font-size: 13px; margin: 18px 0 4px; }
.lbody p { color: #c3cbe4; font-size: 16px; margin-bottom: 14px; }
.lbody code, .prompt code { background: var(--panel-2); border: 1px solid var(--line); padding: 1px 6px; border-radius: 5px; font-family: ui-monospace, Menlo, monospace; font-size: 14px; color: var(--accent-2); }
.egwrap { margin: 26px 0; border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
.eglabel { background: var(--panel); padding: 8px 16px; font-size: 11.5px; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); border-bottom: 1px solid var(--line); }
.eglabel.out { border-top: 1px solid var(--line); }
.eg { background: #0d1020; padding: 16px; font-family: ui-monospace, Menlo, monospace; font-size: 14px; line-height: 1.6; overflow-x: auto; color: #dbe3ff; margin: 0; }
.eg.out { color: var(--green); }

.checkbox { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; margin-bottom: 60px; }
.checkbox .eglabel { border-bottom: 1px solid var(--line); }
.prompt { padding: 16px 18px 0; color: var(--text); font-size: 15.5px; }
.checkbox .options { padding: 16px 18px; }
#c-code { padding: 16px 18px; }

/* CodeMirror editor — real syntax highlighting/auto-indent instead of a bare textarea */
#c-code .CodeMirror {
  height: auto;
  background: #0d1020; color: #dbe3ff; border: 1px solid var(--line);
  border-radius: 10px; font-family: ui-monospace, Menlo, monospace;
  font-size: 14px; line-height: 1.6; margin-bottom: 12px;
}
/* the wrapper's height is auto (fits content); the actual scrolling
   clip has to happen on the inner scroll element, not the wrapper */
#c-code .CodeMirror-scroll { min-height: 160px; max-height: 420px; }
#c-code .CodeMirror-focused { border-color: var(--accent); }
#c-code .CodeMirror-gutters { background: #0d1020; border-right: 1px solid var(--line); }
#c-code .CodeMirror-linenumber { color: var(--muted); }
#c-code .CodeMirror-cursor { border-left: 1.5px solid var(--accent-2); }
#c-code .CodeMirror-activeline-background { background: rgba(77,124,255,.08); }
#c-code .CodeMirror-matchingbracket { color: var(--green) !important; }
#c-code .cm-keyword { color: #ff9d5c; }
#c-code .cm-string { color: #a5e07a; }
#c-code .cm-comment { color: var(--muted); font-style: italic; }
#c-code .cm-number, #c-code .cm-atom { color: #f2a3ff; }
#c-code .cm-def, #c-code .cm-variable-2 { color: var(--accent-2); }
.verdict { margin: 0 18px 18px; padding: 13px 15px; border-radius: 10px; font-size: 14.5px; }
.verdict.ok { background: rgba(74,222,128,.12); border: 1px solid rgba(74,222,128,.3); color: #cdf6dd; }
.verdict.no { background: rgba(237,66,69,.1); border: 1px solid rgba(237,66,69,.3); color: #f7d3d4; }
.verdict pre { margin-top: 8px; background: #0d1020; padding: 9px 12px; border-radius: 7px; font-family: ui-monospace, Menlo, monospace; font-size: 13px; overflow-x: auto; }
#c-next { margin: 0; }

/* guest mode */
.guestbanner {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 12px;
  padding: 14px 18px; margin-bottom: 24px; font-size: 14.5px; color: var(--muted);
}
.btn-sm { padding: 8px 16px; font-size: 14px; flex: none; }
.avatar-fallback {
  display: flex; align-items: center; justify-content: center;
  background: var(--panel-2); color: var(--muted); font-weight: 700; font-size: 26px;
}
@media (max-width: 560px) {
  .guestbanner { flex-direction: column; align-items: flex-start; }
}

/* type mode — code typing practice */
/* always rendered (opacity-only .idle) so it can't shift the text down the
   moment someone starts typing */
.ct-live {
  display: flex; gap: 22px; margin-bottom: 16px; font-size: 14px;
  font-family: ui-monospace, Menlo, monospace; color: var(--muted);
  transition: opacity var(--dur-fast) var(--ease);
}
.ct-live.idle { opacity: 0; }
.ct-live b { color: var(--accent-2); font-weight: 700; font-variant-numeric: tabular-nums; }
/* fixed-height window, clipped — JS scrolls it internally to follow the
   caret so typing "reveals" more text instead of growing the page (which
   was what caused the whole document to scroll while typing). Only the
   bottom edge fades: that's the text still coming toward you, so it reads
   as revealing itself. The top stays at full strength — what you've
   already typed shouldn't dim out. */
.ct-textwindow {
  height: 200px; overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 38px), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 38px), transparent 100%);
}
/* always in the layout (opacity-only .idle) so zen mode, which has no end
   to progress toward, doesn't shift the rest of the stage */
.ct-progress {
  height: 3px; max-width: 920px; margin: 0 auto 44px;
  border-radius: 999px; overflow: hidden;
  background: var(--panel-2); border: 1px solid var(--line);
  transition: opacity var(--dur-fast) var(--ease);
}
.ct-progress.idle { opacity: 0; }
.ct-progress-fill {
  height: 100%; width: 0; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  transition: width .2s linear;
}
.type-display {
  font-family: ui-monospace, Menlo, monospace; font-size: 26px; line-height: 1.9;
  white-space: pre-wrap; word-break: break-word; cursor: text; user-select: none;
  letter-spacing: .03em;
  /* Shrink to the widest line and centre *that*, rather than filling the
     stage. The text stays left-aligned inside itself, so indentation still
     lines up — but a 24-character snippet no longer sits against the left
     edge of an 880px box with 500px of nothing beside it. Width is fixed for
     the life of one snippet, so nothing shifts while you're typing; it only
     re-centres when the next test loads. */
  width: max-content; max-width: 100%; margin-inline: auto;
  /* room to scroll the *last* line up to the eyeline. Without it a short
     snippet can't scroll at all, so its final line is stuck at the bottom
     of the window sitting in the fade, which is exactly where it's hardest
     to read. Window height minus one line. */
  padding-bottom: calc(200px - 1.9em);
}
.type-display .untyped { color: var(--muted); opacity: .5; }
.type-display .correct { color: var(--text); }
.type-display .incorrect { color: #f2777d; background: rgba(237,66,69,.18); border-radius: 2px; }
/* The next character to type. The highlight marks where you are, but the
   letter itself stays untyped-coloured — going white is what "you typed
   this correctly" means, and it hasn't been typed yet. */
.type-display .current {
  color: var(--muted); background: rgba(122,162,255,.38); border-radius: 3px;
  animation: caretPulse 1.1s ease-in-out infinite;
}
@keyframes caretPulse { 50% { background: rgba(122,162,255,.14); } }
.ty-input {
  position: absolute; opacity: 0; height: 1px; width: 1px; pointer-events: none;
}
.ct-meta { display: flex; align-items: center; gap: 18px; margin-top: 22px; font-size: 12.5px; color: var(--muted); }
.ct-restart {
  margin-left: auto; background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: 12.5px; font-family: inherit; padding: 4px 8px; border-radius: 6px;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.ct-restart:hover { color: var(--text); background: var(--panel); }

/* ---- CodeType result -------------------------------------------------
   Layout follows the typing tools this mode is modelled on: the two numbers
   that matter set large down the left, the run plotted beside them, and the
   secondary readings in a quiet row underneath. Scoped to .ct-page because
   .result is shared with the practice and lesson screens, which are centred. */
.ct-page .result { text-align: left; padding: 34px 0 60px; }
/* Three columns for two children. The empty third balances the headline column
   on the left, which puts the chart's centre on the page's centre — the same
   centre the toolbar above it is justified to. With `200px 1fr` the chart was
   shoved a whole headline-width right and overhung the toolbar's last chip. */
.rtop {
  display: grid; grid-template-columns: 200px minmax(0, 1fr) 200px;
  gap: 34px; align-items: start;
}
.rlabel {
  font-family: ui-monospace, Menlo, monospace; font-size: 15px;
  color: var(--muted); line-height: 1.2;
}
/* 50px, not 60: there are three of these stacked (wpm, lpm, acc) and at 60
   the column ran well past the bottom of the chart beside it. */
.rbig {
  font-family: ui-monospace, Menlo, monospace; font-size: 50px; font-weight: 700;
  color: var(--accent-2); line-height: 1.05; margin-bottom: 7px;
  font-variant-numeric: tabular-nums;
}
.rtypelabel { margin-top: 22px; }
.rtype {
  font-family: ui-monospace, Menlo, monospace; font-size: 16px;
  color: var(--accent-2); line-height: 1.5; margin-top: 2px;
}
.rchart { min-height: 200px; }
.rchart svg { width: 100%; height: 200px; display: block; }

/* Weekly pace on the dashboard. Boxed, unlike the results chart, because that
   one sits inside the result card already and this one stands on its own. */
.tychart {
  background: var(--panel); border: 1px solid var(--line); border-radius: 13px;
  padding: 14px 10px 6px;
}
.tychart svg { width: 100%; height: 210px; display: block; }
.tychart circle { transition: r var(--dur-fast) var(--ease); }
.tychart circle:hover { r: 5.5; }

.rrow {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px;
  margin-top: 30px;
}
.rmid {
  font-family: ui-monospace, Menlo, monospace; font-size: 26px; font-weight: 700;
  color: var(--accent-2); margin-top: 2px; font-variant-numeric: tabular-nums;
}
/* Your own history under the run that just ended. Muted by default — knowing
   your best is context, not news — and lit only when you actually beat it. */
.rpb {
  margin-top: 26px; font-size: 15px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.rpb-new { color: var(--green); font-weight: 600; }
.rpb-new::before { content: "★ "; }

.ractions { margin-top: 34px; }

/* Every headline number here is rounded — hovering one shows what it was
   rounded from, plus whatever it was computed out of. Costs nothing until
   someone asks, which is the right price for a detail most people don't want.
   No cursor change: the tooltip is a bonus for anyone who happens to hover,
   not a control advertising itself. */
.rtip {
  position: absolute; z-index: 60; pointer-events: none;
  background: #05070d; color: var(--text); border: 1px solid var(--line);
  border-radius: 7px; padding: 7px 10px;
  font-family: ui-monospace, Menlo, monospace; font-size: 12.5px; line-height: 1.5;
  white-space: pre-line;          /* the multi-line tips are plain \n */
  box-shadow: var(--shadow-md);
  opacity: 0; transition: opacity var(--dur-fast) var(--ease);
}
.rtip.on { opacity: 1; }

@media (max-width: 760px) {
  .rtop { grid-template-columns: 1fr; gap: 18px; }
  .rbig { font-size: 40px; }
  .rrow { grid-template-columns: repeat(2, 1fr); row-gap: 18px; }
  .rmid { font-size: 21px; }
}
@media (max-width: 620px) {
  .ct-page { padding-top: 24px; }
  .type-display { font-size: 20px; line-height: 1.8; padding-bottom: calc(155px - 1.8em); }
  .ct-textwindow { height: 155px; }
}
/* Where you are, said the way everything else on the site says a current
   choice — a filled pill. A 2px underline was carrying that on its own, which
   is the lightest possible mark for the most useful piece of state in the
   header. The underline goes with it; both at once is two answers to one
   question. */
header nav a.active {
  color: var(--accent-2); background: rgba(77,124,255,.15); font-weight: 620;
}
header nav a.active::after { display: none; }

/* first-visit onboarding overlay */
.onboard {
  position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center;
  padding: 24px; background: rgba(5,6,12,.72); backdrop-filter: blur(4px);
  opacity: 0; pointer-events: none; transition: opacity var(--dur-med) var(--ease);
}
.onboard.show { opacity: 1; pointer-events: auto; }
.onboard-card {
  width: 100%; max-width: 460px; background: var(--panel); border: 1px solid var(--line);
  border-radius: 18px; padding: 32px; box-shadow: var(--shadow-lg);
  transform: scale(.96) translateY(8px); transition: transform var(--dur-med) var(--ease);
}
.onboard.show .onboard-card { transform: scale(1) translateY(0); }
.onboard-rows { display: flex; flex-direction: column; gap: 16px; margin-top: 22px; }
.onboard-row { display: flex; align-items: flex-start; gap: 14px; }
.onboard-row .ic {
  width: 38px; height: 38px; border-radius: 10px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(140deg, rgba(77,124,255,.18), rgba(77,124,255,.05));
  border: 1px solid rgba(77,124,255,.25); color: var(--accent-2);
}
.onboard-row .ic svg { width: 19px; height: 19px; }
.onboard-row div { display: flex; flex-direction: column; }
.onboard-row strong { font-size: 15px; }
.onboard-row span { color: var(--muted); font-size: 13.5px; margin-top: 2px; }

@media (prefers-reduced-motion: reduce) {
  .onboard-card { transform: none; }
}

/* settings rows — label on the left, control on the right */
.setrow { justify-content: space-between; gap: 20px; align-items: center; }
.setrow .rname { flex: 1; }
.setrow input[type="checkbox"] {
  width: 20px; height: 20px; flex: none; accent-color: var(--accent); cursor: pointer;
}
.btn-danger { background: #b7343a; color: #fff; border-color: #b7343a; }
.btn-danger:hover { background: #cc3c43; text-decoration: none; }
.btn-danger:disabled { opacity: .45; cursor: not-allowed; transform: none; }
.confirm-input {
  width: 100%; margin-top: 12px; padding: 11px 14px; border-radius: 10px;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--text);
  font-family: ui-monospace, Menlo, monospace; font-size: 15px;
}
.confirm-input:focus { outline: none; border-color: var(--accent); }

/* opt-in from settings; the OS-level preference is handled separately in style.css */
.reduce-motion *, .reduce-motion *::before, .reduce-motion *::after {
  animation-duration: .001ms !important; animation-iteration-count: 1 !important;
  transition-duration: .001ms !important;
}

/* Learn: courses / practice tabs (practice used to be its own page) */
.learn-tabs { margin-bottom: 30px; }

/* Typing rows carry accuracy and recency in .rsub, which is the point of the
   row — so unlike leaderboard ranks it wraps on small screens instead of
   being hidden by the rule above. */
@media (max-width: 620px) {
  .tyrow { flex-wrap: wrap; row-gap: 3px; }
  .tyrow .rsub { display: block; width: 100%; }
}

/* A guest still needs to know nothing is being kept, but a full-width warning
   banner shouts it on every page view. One quiet line instead: present and
   legible, and easy to stop noticing once you've read it. */
/* Centred under the header rather than heading the page copy. It is a status
   line about the session, not the first thing this page has to say. */
.guestbar {
  display: flex; align-items: baseline; justify-content: center; gap: 8px;
  flex-wrap: wrap; margin: 0; font-size: 12.5px; color: var(--muted);
  text-align: center;
}
.guestbar .dot {
  width: 5px; height: 5px; border-radius: 50%; flex: none;
  background: var(--gold); opacity: .8; transform: translateY(-2px);
}
.guestbar a { color: var(--accent-2); }


/* Preference switches. A bare checkbox reads as a form control from 1997 next
   to everything else on the page. */
.setrow input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; margin: 0;
  width: 46px; height: 27px; border-radius: 999px; flex: none; cursor: pointer;
  position: relative; background: var(--panel-2); border: 1px solid var(--line);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.setrow input[type="checkbox"]::after {
  content: ""; position: absolute; top: 3px; left: 3px;
  width: 19px; height: 19px; border-radius: 50%;
  background: var(--muted);
  box-shadow: 0 1px 3px rgba(0,0,0,.45);
  transition: transform var(--dur-med) var(--ease), background var(--dur-fast) var(--ease);
}
.setrow input[type="checkbox"]:hover { border-color: #3a4a78; }
.setrow input[type="checkbox"]:checked {
  background: linear-gradient(140deg, #4d7cff, #2247b8); border-color: var(--accent);
}
.setrow input[type="checkbox"]:checked::after {
  transform: translateX(19px); background: #fff;
}
.setrow input[type="checkbox"]:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 3px; }

/* Locked for guests: visible, so they can see what an account adds, but
   clearly not theirs yet. Dimming beats hiding — you can't want something
   you were never shown. */
.tile.locked { opacity: .45; position: relative; }
.tile.locked::after {
  content: "Sign in"; position: absolute; inset: auto 0 8px 0;
  font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--accent-2);
}
.tile.locked .n { color: var(--muted); }

/* The guest's way out. Quiet, at the very bottom — it's an exit, not an
   action anyone should be nudged toward. */
.guest-exit {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  margin: 44px 0 70px; padding-top: 24px;
  border-top: 1px solid var(--line);
  color: var(--muted); font-size: 13.5px; line-height: 1.55;
}
.guest-exit span { max-width: 560px; }
.guest-exit form { margin: 0; }

/* Progress through the course, shown while you're inside a lesson. "Lesson 3
   of 10" says where you are; this says how much is left. */
.lprogress { display: flex; align-items: center; gap: 12px; margin: 6px 0 18px; }
.lprogress .bar { flex: 1; max-width: 320px; }
.lprogress span { font-size: 13px; color: var(--muted); white-space: nowrap; }

/* Course progress on the dashboard. Rows rather than cards: six courses as
   cards would swamp everything else on the page. */
.courselist { display: flex; flex-direction: column; gap: 10px; }
.course-row {
  display: flex; align-items: center; gap: 20px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 12px; padding: 14px 18px; color: var(--text);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.course-row:hover { background: var(--panel-2); border-color: rgba(77,124,255,.4); text-decoration: none; }
.course-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 9px; }
.course-top { display: flex; align-items: center; gap: 10px; }
.course-top strong { font-size: 15px; font-weight: 600; }
.course-level {
  font-size: 11px; color: var(--muted); border: 1px solid var(--line);
  border-radius: 999px; padding: 2px 9px; white-space: nowrap;
}
.course-side { display: flex; align-items: center; gap: 16px; flex: none; }
.course-count { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }
.course-verb { font-size: 13px; font-weight: 600; color: var(--accent-2); white-space: nowrap; }
/* A finished course stays listed but stops competing for attention. */
.course-row.done .course-count { color: var(--green); }
.course-row.done .bar-fill { background: var(--green); }

@media (max-width: 560px) {
  .course-row { gap: 12px; padding: 12px 14px; }
  .course-level { display: none; }
}

/* Learn page: subject -> chapter -> lesson.
   The subject is what someone wants ("learn Python"); the chapter is how far
   in they are. Chapters stay collapsed so the page lists chapters rather than
   every lesson in the catalogue. */
.fchips { display: flex; flex-wrap: wrap; gap: 8px; margin: 4px 0 26px; }
.fchip {
  font: inherit; font-size: 13.5px; cursor: pointer; white-space: nowrap;
  padding: 7px 15px; border-radius: 999px;
  background: var(--panel); border: 1px solid var(--line); color: var(--muted);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.fchip:hover { color: var(--text); border-color: rgba(77,124,255,.4); }
.fchip.on { background: var(--accent); border-color: var(--accent); color: #fff; }

/* It's a <section>, so the site-wide section padding (26px top, 72px bottom)
   applies and adds ~100px of dead air between every subject. */
/* The subject cards read as Practice's shelf does — same height, same
   title-and-a-line, no stat eyebrow and no arrow appearing on hover. */
.browse .shelf { --card-h: 356px; align-items: start; }
.browse .shelf .catbox { height: var(--card-h); }
.browse .shelf .catbox-title { font-size: 23px; }
.browse .shelf .catbox-blurb { font-size: 13px; }
.browse .shelf > :last-child:nth-child(odd) { grid-column: auto; }

.subject { padding: 0; margin-bottom: 34px; }
.subject-head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 12px; }
.subject-head h2 { font-size: 22px; margin: 0; }
.subject-count { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }

.chapter {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 13px; margin-bottom: 10px; overflow: hidden;
}
.chapter > summary {
  display: flex; align-items: center; gap: 14px; padding: 15px 18px;
  cursor: pointer; list-style: none; user-select: none;
}
.chapter > summary::-webkit-details-marker { display: none; }
.chapter > summary:hover { background: var(--panel-2); }
.ch-label {
  font-size: 11.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--muted); flex: none;
}
.ch-title { font-weight: 600; font-size: 15px; flex: none; }
.ch-level {
  font-size: 11px; color: var(--muted); border: 1px solid var(--line);
  border-radius: 999px; padding: 2px 9px; flex: none;
}
/* .bar was written for a <div>; used on a <span> it stays inline and ignores
   width, which collapsed the progress bar to a 2px sliver. */
.ch-bar { flex: 1; min-width: 60px; }
.ch-bar .bar, .ch-bar .bar-fill { display: block; }
.ch-count { font-size: 13px; color: var(--muted); flex: none; font-variant-numeric: tabular-nums; }
.chapter.done .ch-count { color: var(--green); }
.chapter.done .bar-fill { background: var(--green); }
.ch-blurb { margin: 0 18px 14px; }
.chapter .llist { padding: 0 10px 10px; }

@media (max-width: 620px) {
  .chapter > summary { flex-wrap: wrap; gap: 8px 12px; }
  .ch-bar { order: 5; flex-basis: 100%; }
  .ch-level { display: none; }
}

/* Category and subject cards — the two shelves above the chapter list. */
.crumbs { display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
          font-size: 13.5px; color: var(--muted); margin-bottom: 24px; }
.crumbs a { color: var(--muted); }
.crumbs a:hover { color: var(--text); }
.crumbs i { font-style: normal; opacity: .45; }
.crumbs span:last-child { color: var(--text); }

.catgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
.catcard {
  display: flex; flex-direction: column; gap: 8px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 14px; padding: 20px; color: var(--text);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
a.catcard:hover {
  background: var(--panel-2); border-color: rgba(77,124,255,.45);
  text-decoration: none; transform: translateY(-2px);
}
.cat-title { font-size: 17px; font-weight: 650; }
.cat-blurb { font-size: 13.5px; color: var(--muted); line-height: 1.5; flex: 1; }
.cat-foot { display: flex; justify-content: space-between; align-items: baseline;
            gap: 10px; font-size: 12.5px; color: var(--muted); margin-top: 4px; }
.cat-done { font-variant-numeric: tabular-nums; }
/* bar() returns spans, and .bar-fill never sets a display — an inline box
   ignores width, so these bars were rendering permanently empty at every
   completion level. .ch-bar above learned this the same way. */
.catcard .bar, .catcard .bar-fill { display: block; }
/* Listed but not clickable — the shape of what's coming, without pretending
   it's ready. */
.catcard.soon { opacity: .55; }
.cat-soon { font-size: 12px; color: var(--muted); border: 1px solid var(--line);
            border-radius: 999px; padding: 3px 11px; align-self: flex-start; margin-top: 4px; }

/* ── Pathways ──────────────────────────────────────────────────────────────
   Goal-shaped routes through the shelf, and the first thing on the page: an
   ordered answer for the visitor who doesn't yet know what to pick. The shelf
   follows underneath for anyone who came for one particular subject. */
.ways { margin-top: 26px; }
.ways-head { margin-bottom: 18px; }
.ways-head h2 { font-size: 23px; letter-spacing: -.018em; margin-bottom: 5px; }
.ways-head .section-sub { margin: 0; max-width: 74ch; }
/* The rail: seven destinations you scan down, detail pinned beside it. Rows
   are a fixed height so the list reads as one column of equal choices, and
   the rail is sticky — switching pathway then swaps only the right-hand side,
   so the thing you're reading never moves under you. */
.ways-rail { display: grid; grid-template-columns: 268px minmax(0, 1fr); gap: 20px; align-items: start; }
.raillist {
  display: flex; flex-direction: column; gap: 2px;
  position: sticky; top: 88px;
}
.railrow {
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  min-height: 56px; padding: 10px 13px; text-align: left; cursor: pointer;
  font-family: inherit; color: var(--text); background: none; border: none;
  border-left: 2px solid transparent; border-radius: 9px;
  transition: background var(--dur-fast) var(--ease);
}
.railrow:hover { background: var(--panel-2); }
.railrow.on {
  background: rgba(77,124,255,.15); border-left-color: transparent;
  border-radius: 9px;
}
.railrow.on .rail-name { color: var(--accent-2); }
.rail-name { font-size: 14px; font-weight: 620; }
.rail-sub { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* No panel. The pathway sits on the page the way a question does — a bordered
   box holding bordered chapter rows was two frames deep before the content,
   and the rail beside it already marks where this half of the page begins. */
.waycard { padding: 4px 2px 16px; }
.waycard-head {
  display: flex; gap: 28px; align-items: flex-start; flex-wrap: wrap;
  padding-bottom: 18px; margin-bottom: 6px; border-bottom: 1px solid var(--line);
}
.waycard-intro { flex: 1 1 340px; min-width: 0; }
.waycard-tag {
  display: block; font-size: 11.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--accent-2); font-weight: 650; margin-bottom: 7px;
}
.waycard-blurb { font-size: 14px; color: var(--text); line-height: 1.6; max-width: 62ch; }
/* Where a pathway admits what it doesn't cover. */
.waycard-note {
  font-size: 12.5px; color: var(--muted); line-height: 1.55; margin-top: 9px;
  padding-left: 11px; border-left: 2px solid var(--line); max-width: 62ch;
}
.waycard-action {
  flex: 0 0 250px; display: flex; flex-direction: column; gap: 9px; align-items: stretch;
}
.waycard-stat { font-size: 12.5px; color: var(--muted); }
.waycard-go { display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
.waycard-go svg { width: 15px; height: 15px; }
.waycard-done {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 14px; font-weight: 600; color: var(--green);
  border: 1px solid rgba(74,222,128,.35); border-radius: 10px; padding: 10px;
}
.waycard-action .bar, .waycard-action .bar-fill { display: block; }
.waycard-action .bar { height: 6px; }
.waycard-count { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }

.pathsteps { list-style: none; display: flex; flex-direction: column; }
.pathstep { display: flex; align-items: flex-start; gap: 14px; padding: 13px 4px; }
.pathstep + .pathstep { border-top: 1px solid var(--line); }
.pathstep-n {
  flex: none; width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center; font-size: 12.5px; font-weight: 650;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--muted);
}
.pathstep.done .pathstep-n { color: var(--green); border-color: rgba(74,222,128,.4); }
/* The one you're on, so the list has a "you are here" rather than just a tail
   of ticks and a tail of blanks. */
.pathstep.now .pathstep-n {
  color: var(--accent-2); border-color: rgba(122,162,255,.55);
  background: rgba(77,124,255,.12);
}
.pathstep-body { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pathstep-title { color: var(--text); font-weight: 600; font-size: 14.5px; }
.pathstep-title:hover { color: var(--accent-2); text-decoration: none; }
.pathstep-why { font-size: 13px; color: var(--muted); line-height: 1.5; }
.pathstep-count {
  flex: none; font-size: 12.5px; color: var(--muted);
  font-variant-numeric: tabular-nums; padding-top: 3px;
}
.pathstep.done .pathstep-count { color: var(--green); }

@media (max-width: 900px) {
  /* One column: the rail stops being a rail and becomes a strip above the
     detail, so it must not stick or it would cover what it opened. Laid on
     its side and scrolled sideways — stacked, eight destinations would be a
     whole screen of names standing between you and the route itself, and the
     route is what the page is now for. */
  .ways-rail { grid-template-columns: 1fr; }
  .raillist {
    position: static; flex-direction: row; gap: 6px;
    overflow-x: auto; overscroll-behavior-x: contain;
    scrollbar-width: none;
  }
  .raillist::-webkit-scrollbar { display: none; }
  .railrow {
    flex: 0 0 auto; min-height: 0; padding: 9px 14px;
    border-left: none; border-radius: 9px;
  }
  .railrow.on { border-radius: 9px; box-shadow: inset 0 0 0 1px rgba(77,124,255,.4); }
}
@media (max-width: 700px) {
  .ways { margin-top: 22px; }
  .browse { margin-top: 38px; padding-top: 26px; }
  .waycard { padding: 18px 16px 12px; }
  .waycard-action { flex: 1 1 100%; }
  .pathstep-count { display: none; }
}

/* ── Pick up where you left off ─────────────────────────────────────────
   Replaced a row of three shortcuts that never changed and a list of 48
   chapters that was mostly scrolling. The lead card is the one course you're
   actually part-way through and spans two columns; the rest are the other
   things worth doing today. */
#pickup { margin-bottom: 48px; }
#pickup h2 { font-size: 22px; letter-spacing: -.018em; margin-bottom: 4px; }
#pickup .section-sub { margin-bottom: 18px; }
.pickgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.pick-card {
  display: flex; flex-direction: column; gap: 5px; position: relative;
  padding: 20px 22px 18px; color: var(--text); overflow: hidden;
  background: linear-gradient(155deg, rgba(var(--hue), .10), transparent 60%), var(--panel);
  border: 1px solid var(--line); border-radius: 16px;
  transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.pick-card:hover {
  border-color: rgba(var(--hue), .5); transform: translateY(-2px); text-decoration: none;
}
.pick-card.lead { grid-column: 1 / -1; padding: 24px 26px 22px; }
.pick-ic {
  position: absolute; top: 18px; right: 20px; width: 38px; height: 38px;
  border-radius: 11px; display: grid; place-items: center;
  background: rgba(var(--hue), .12); border: 1px solid rgba(var(--hue), .28);
  color: rgb(var(--hue));
}
.pick-ic svg { width: 19px; height: 19px; }
.pick-kicker {
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  color: rgb(var(--hue)); font-weight: 650;
}
.pick-title { font-size: 18px; font-weight: 660; letter-spacing: -.015em; padding-right: 50px; }
.pick-card.lead .pick-title { font-size: 24px; }
.pick-sub { font-size: 13.5px; color: var(--muted); line-height: 1.5; padding-right: 50px; }
.pick-card .bar, .pick-card .bar-fill { display: block; }
.pick-card .bar { height: 6px; margin-top: 12px; }
.pick-card .bar-fill { background: linear-gradient(90deg, rgba(var(--hue), .75), rgb(var(--hue))); }
.pick-count { font-size: 12px; color: var(--muted); margin-top: 6px; font-variant-numeric: tabular-nums; }
.pick-go {
  margin-top: 12px; font-size: 13.5px; font-weight: 650; color: rgb(var(--hue));
}
@media (max-width: 720px) {
  .pickgrid { grid-template-columns: 1fr; }
  .pick-card.lead .pick-title { font-size: 20px; }
}

/* ── Code challenges ───────────────────────────────────────────────────── */
/* Flat, like every other game. The panel was a frame around a frame: the
   editor and the description already carry their own edges. */
#ch-card { max-width: 760px; }
.ch-head { display: flex; justify-content: space-between; align-items: flex-start;
           gap: 20px; margin-bottom: 14px; }
.ch-diff { font-size: 11.5px; letter-spacing: .08em; text-transform: uppercase;
           color: var(--accent-2); font-weight: 650; }
.ch-title { font-size: 22px; letter-spacing: -.015em; margin-top: 3px; }
/* A stopwatch, not a deadline — nothing fails when it runs on. */
.ch-timer { flex: none; font-family: ui-monospace, Menlo, monospace; font-size: 26px;
            font-weight: 700; color: var(--muted); font-variant-numeric: tabular-nums; }
.ch-desc { font-size: 14.5px; line-height: 1.65; margin-bottom: 16px; }
.ch-desc p { margin-bottom: 8px; }
.ch-desc pre.eg { margin: 8px 0; }
.ch-bar { display: flex; align-items: center; justify-content: space-between;
          gap: 16px; flex-wrap: wrap; margin-bottom: 8px; }
.ch-bar .chips { margin-bottom: 0; }
/* One rule for every card that holds a code box. This was three near-identical
   copies keyed on #ch-card / #db-card / #rv-card, which is why a fourth card
   added later rendered a raw browser textarea — grey, 20 characters wide, and
   nothing like the rest of the site. New cards use .gcard and are covered. */
/* The same window Find the bug and Guess the output use — same material, same
   radius, same hue-tinted edge — so every place you type on this site is
   recognisably the same object rather than three near-misses. */
#ch-card textarea, .gcard textarea, #wk-card textarea {
  width: 100%; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 14px;
  padding: 20px 22px; border-radius: 14px; background: rgba(6, 8, 15, .62);
  color: var(--text); border: 1px solid rgba(var(--hue, 77,124,255), .26);
  resize: vertical; line-height: 1.75; margin: 10px 0 16px;
}
#ch-card textarea:focus, .gcard textarea:focus { outline: none; border-color: rgba(var(--hue, 77,124,255), .6); }
#ch-card textarea:disabled, .gcard textarea:disabled { opacity: .6; }
/* The language row inside a challenge is the same control as the play bar's,
   so it wears the same colour rather than the site's generic accent. */
#ch-card .chips .chip.on, #wk-card .chips .chip.on {
  background: rgba(var(--hue, 77,124,255), .2);
  border-color: rgba(var(--hue, 77,124,255), .55);
  color: var(--text);
}
#ch-card .chips .chip:hover, #wk-card .chips .chip:hover {
  border-color: rgba(var(--hue, 77,124,255), .45);
}

.ch-actions, .ch-after { display: flex; gap: 10px; flex-wrap: wrap; }
.ch-after { margin-top: 18px; }

/* ── Find the bug ──────────────────────────────────────────────────────── */
/* No panel. This view is flat on the page like Guess the output and the quiz —
   a card around a code window around a program was three borders deep. */
#db-card textarea:disabled { opacity: .6; }

/* ── Review ────────────────────────────────────────────────────────────── */
.rv-stats { display: flex; flex-wrap: wrap; gap: 22px; margin: 20px 0 24px; }
.rv-stat { font-size: 13px; color: var(--muted); }
.rv-stat b { display: block; font-size: 24px; font-weight: 700; color: var(--text); }
#rv-card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 16px; padding: 22px 24px 20px;
}
.rv-head {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 16px; flex-wrap: wrap; margin-bottom: 10px;
}
.rv-from { font-size: 12px; letter-spacing: .07em; text-transform: uppercase;
           color: var(--accent-2); font-weight: 650; }
/* Which Leitner box this one is in — the honest version of a difficulty
   label, since it's derived from how often you've actually got it right. */
.rv-box { font-size: 11.5px; color: var(--muted); border: 1px solid var(--line);
          border-radius: 999px; padding: 3px 10px; }
.rv-title { font-size: 21px; letter-spacing: -.015em; margin-bottom: 10px; }
#rv-card .prompt { margin-bottom: 16px; }
#rv-card textarea {
  width: 100%; font-family: ui-monospace, Menlo, monospace; font-size: 14px;
  padding: 12px 14px; border-radius: 10px; background: var(--bg);
  color: var(--text); border: 1px solid var(--line); resize: vertical;
  margin-bottom: 12px; line-height: 1.6;
}
#rv-card textarea:focus { outline: none; border-color: var(--accent); }
#rv-cmd { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.rv-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }

/* Practice used to be duplicated onto the Learn page in a compact shelf. It
   lives at /practice now and only there — Learn is the curriculum, and the
   drills are becoming a page of their own. */

/* ── The two timed cards ───────────────────────────────────────────────────
   The question of the day and the challenge of the week. They lead Practice
   because they're the only things on the site that expire: a drill will still
   be there tomorrow, these won't. Claimed ones stay on the page rather than
   disappearing — "done" is information, and a row that empties as you use it
   makes the page look broken by evening. */
.questrow { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
.questcard {
  position: relative; overflow: hidden; isolation: isolate;
  display: flex; flex-direction: column; gap: 9px;
  padding: 22px 24px 20px; border-radius: 18px; color: var(--text);
  background: var(--panel); border: 1px solid rgba(var(--hue), .28);
  transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.questcard:hover {
  text-decoration: none; transform: translateY(-2px);
  border-color: rgba(var(--hue), .6);
  box-shadow: 0 22px 50px -28px rgba(var(--hue), .5), var(--shadow-md);
}
.quest-glow {
  position: absolute; right: -30%; top: -70%; width: 90%; aspect-ratio: 1; z-index: 0;
  pointer-events: none; border-radius: 50%;
  background: radial-gradient(circle, rgba(var(--hue), .22), transparent 68%);
}
.questcard > :not(.quest-glow) { position: relative; z-index: 1; }
.quest-top { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.quest-kicker {
  font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase;
  color: rgb(var(--hue)); font-weight: 600;
}
.quest-when { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.quest-title { font-size: 22px; font-weight: 660; letter-spacing: -.018em; line-height: 1.2; }
.quest-sub { font-size: 13.5px; color: var(--muted); line-height: 1.55; flex: 1; }
.quest-foot {
  display: flex; justify-content: space-between; align-items: center;
  gap: 12px; margin-top: 6px;
}
.quest-xp { font-size: 14px; font-weight: 680; color: rgb(var(--hue)); font-variant-numeric: tabular-nums; }
.quest-go { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; color: var(--muted); }
/* Claimed: drained of colour, but still legible and still a link back to it. */
.questcard.done { border-color: var(--line); }
.questcard.done .quest-glow { opacity: .25; }
.questcard.done .quest-kicker, .questcard.done .quest-xp { color: var(--muted); }
.questcard.done .quest-xp::before { content: "✓ "; color: var(--green); }

/* ── Styles Match ──────────────────────────────────────────────────────────
   A destination, not a drill: while you're on a level the site header is gone
   and the whole viewport is the game, the same way CodeType clears itself once
   a run starts. */
body.playing { overflow: hidden; }

.mbar {
  display: flex; align-items: center; gap: 22px;
  padding: 0 20px; height: 56px;
  border-bottom: 1px solid var(--line); background: var(--panel);
}
.mback { font-size: 13.5px; color: var(--muted); flex: none; }
.mback:hover { color: var(--text); text-decoration: none; }
.mname { font-size: 15px; font-weight: 650; flex: none; }
.mstat { display: flex; align-items: baseline; gap: 7px; flex: none; }
.mstat b {
  font-family: ui-monospace, Menlo, monospace; font-size: 18px; font-weight: 700;
  color: var(--text); font-variant-numeric: tabular-nums;
}
/* Green is the signal that a number has crossed a line — the 95% solve
   threshold, or par. Nothing else on this bar changes colour. */
.mstat b.good { color: var(--green); }
.mstat i { font-style: normal; font-size: 12px; color: var(--muted); }
.mspace { flex: 1; }
.mbtn {
  flex: none; font: inherit; font-size: 13px; cursor: pointer;
  background: none; color: var(--muted);
  border: 1px solid var(--line); border-radius: 8px; padding: 7px 14px;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
.mbtn:hover { color: var(--text); border-color: rgba(77,124,255,.45); }
.mbtn.on { color: var(--accent-2); border-color: rgba(77,124,255,.55); background: rgba(77,124,255,.12); }

.mgrid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 24px;
  padding: 22px; height: calc(100vh - 56px); overflow: auto;
}
.mpane { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.mlabel {
  font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
}
.mhint { text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--muted); }

/* The two renders share one box so the overlay is a class flip rather than a
   redraw. Checkerboard under them: without it a transparent render and a black
   one look identical, which would be a lie the player acts on. */
.mstack {
  position: relative; width: 400px; height: 300px; max-width: 100%;
  border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
  background-color: #05070d;
  background-image: linear-gradient(45deg, #0e1322 25%, transparent 25%, transparent 75%, #0e1322 75%),
                    linear-gradient(45deg, #0e1322 25%, transparent 25%, transparent 75%, #0e1322 75%);
  background-size: 16px 16px; background-position: 0 0, 8px 8px;
}
.mstack canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
#m-over { opacity: 0; transition: opacity var(--dur-fast) var(--ease); }
.mstack.overlaid #m-over { opacity: .5; }

.mpane-edit textarea {
  flex: 1; min-height: 260px; width: 100%; resize: none;
  font-family: ui-monospace, Menlo, monospace; font-size: 14px; line-height: 1.65;
  padding: 14px; border-radius: 10px; tab-size: 2;
  background: var(--bg); color: var(--text); border: 1px solid var(--line);
}
.mpane-edit textarea:focus { outline: none; border-color: var(--accent); }
.mfoot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; font-size: 13px; color: var(--muted);
}

/* Level picker */
.mlevels { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; margin-top: 22px; }
.mlevel {
  display: flex; align-items: center; gap: 14px; padding: 16px 18px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 13px;
  color: var(--text);
  transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.mlevel:hover { text-decoration: none; border-color: rgba(77,124,255,.5); transform: translateY(-2px); }
.mlevel-n {
  flex: none; font-family: ui-monospace, Menlo, monospace; font-size: 17px;
  font-weight: 700; color: var(--accent-2);
}
.mlevel-body { display: flex; flex-direction: column; gap: 2px; }
.mlevel-name { font-weight: 620; }
.mlevel-sub { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* The result panel. Deliberately not a modal — the second half of the game is
   beating the number it just gave you, so it sits under the editor and leaves
   the code where it is. */
.mresult {
  margin-top: 14px; padding: 16px 18px;
  background: var(--panel); border: 1px solid rgba(74,222,128,.4); border-radius: 13px;
}
.mresult-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; }
.mresult-badge {
  font-size: 11.5px; letter-spacing: .09em; text-transform: uppercase;
  font-weight: 650; color: var(--green);
}
.mresult-badge.par { color: #ffc63d; }
.mresult-row { display: flex; gap: 30px; margin: 12px 0 8px; }
.mresult-row span { display: flex; flex-direction: column; gap: 2px; }
.mresult-row b {
  font-family: ui-monospace, Menlo, monospace; font-size: 21px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.mresult-row b.good { color: #ffc63d; }
.mresult-row i { font-style: normal; font-size: 11.5px; color: var(--muted); }
.mresult-line { font-size: 13.5px; color: var(--muted); }
.mresult-xp { font-size: 13.5px; color: var(--green); font-weight: 600; margin-top: 5px; }

.mlevel-tick { margin-left: auto; flex: none; color: var(--green); font-size: 15px; }
.mlevel-tick.par { color: #ffc63d; }

/* Side by side needs the width for two 400px canvases and a usable editor.
   Below that the target goes above the code rather than both being cramped. */
@media (max-width: 1279px) {
  .mgrid { grid-template-columns: 1fr; height: auto; overflow: visible; }
  body.playing { overflow: auto; }
  .mbar { flex-wrap: wrap; height: auto; padding: 10px 16px; gap: 14px; }
  .mspace { display: none; }
}

/* ── Campaign ──────────────────────────────────────────────────────────────
   A vertical list of subject nodes, deliberately not a graph or a canvas. The
   structure is what matters at this stage and it's far easier to make a map
   look right once real content is filling it. */
.cnodes { display: grid; gap: 14px; margin-top: 22px; }
.cnode {
  display: flex; flex-direction: column; gap: 8px;
  padding: 20px 22px; border-radius: 15px; color: var(--text);
  background: var(--panel); border: 1px solid var(--line);
  transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
a.cnode:hover { text-decoration: none; border-color: rgba(77,124,255,.5); transform: translateY(-2px); }
.cnode.locked { opacity: .62; border-style: dashed; }
.cnode-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
.cnode-name { font-size: 18px; font-weight: 660; letter-spacing: -.012em; }
.cnode-sub { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }
.cnode-acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; }
.cact {
  font-size: 11.5px; color: var(--muted);
  border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px;
}
.cact.off { opacity: .5; }

/* The ladder at a glance: filled is cleared, ringed is where you are, hollow
   is ahead. Five of them say more than a sentence would. */
.pips { display: inline-flex; gap: 5px; flex: none; }
.pip {
  width: 9px; height: 9px; border-radius: 50%;
  border: 1px solid var(--line); background: transparent;
}
.pip.cleared { background: var(--green); border-color: var(--green); }
.pip.current { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(77,124,255,.25); }

.ctier {
  padding: 18px 20px; border-radius: 14px; margin-top: 14px;
  background: var(--panel); border: 1px solid var(--line);
}
.ctier.locked { opacity: .6; border-style: dashed; }
.ctier.current { border-color: rgba(77,124,255,.45); }
.ctier.cleared { border-color: rgba(74,222,128,.32); }
.ctier-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.ctier-name { font-size: 16px; font-weight: 650; }
.ctier-mult {
  font-size: 12px; font-weight: 650; color: var(--accent-2);
  font-variant-numeric: tabular-nums;
}
.ctier-state { margin-left: auto; font-size: 11.5px; letter-spacing: .08em;
               text-transform: uppercase; color: var(--muted); font-weight: 600; }
.ctier.cleared .ctier-state { color: var(--green); }
.ctier-clear { font-size: 13px; color: var(--muted); margin-top: 6px; line-height: 1.55; }
.ctier-acts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.cactbtn {
  font-size: 13px; color: var(--text); padding: 7px 14px; border-radius: 9px;
  background: var(--panel-2); border: 1px solid var(--line);
}
a.cactbtn:hover { text-decoration: none; border-color: rgba(77,124,255,.5); }
.cactbtn.dest { border-color: rgba(255,198,61,.4); color: #ffc63d; }
.cactbtn.off { opacity: .55; color: var(--muted); }

/* A tier run: the state you need while answering, and nothing else. */
.runbar {
  display: flex; align-items: center; gap: 26px; flex-wrap: wrap;
  margin: 18px 0 10px;
}
.runstat { display: flex; align-items: baseline; gap: 7px; }
.runstat b {
  font-family: ui-monospace, Menlo, monospace; font-size: 20px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.runstat i { font-style: normal; font-size: 12px; color: var(--muted); }
.runspace { flex: 1; }
.runprog { height: 4px; border-radius: 999px; background: var(--panel-2); overflow: hidden; margin-bottom: 18px; }
.runprog span {
  display: block; height: 100%; width: 0; border-radius: 999px;
  background: var(--accent); transition: width var(--dur-slow) var(--ease);
}
.runcard .options, .runcard .ch-title { margin-top: 10px; }
.runresult { border-color: rgba(74,222,128,.35); }

.cactbtn.run { border-color: rgba(77,124,255,.45); color: var(--accent-2); }

/* The open subjects on Home, each showing the tier you're on. One click from
   landing to a run — the map itself is a click further, for the rest. */
.campsec { margin-top: 30px; }
.campsec-head { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }
.campsec-head h2 { font-size: 23px; letter-spacing: -.018em; }
.campsec-all { font-size: 13px; color: var(--muted); flex: none; }
.campsec-all:hover { color: var(--accent-2); text-decoration: none; }
.campgrid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 14px; margin-top: 14px;
}
.campcard {
  display: flex; flex-direction: column; gap: 6px;
  padding: 18px 20px; border-radius: 15px; color: var(--text);
  background: linear-gradient(150deg, rgba(77,124,255,.12), transparent 60%), var(--panel);
  border: 1px solid rgba(77,124,255,.3);
  transition: border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.campcard:hover { text-decoration: none; border-color: rgba(77,124,255,.6); transform: translateY(-2px); }
.campcard-top { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.campcard-kicker {
  font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--accent-2); font-weight: 600;
}
/* .pip sets a width and height, which an inline element ignores — without a
   flex container here the five of them collapse into one smear. */
.campcard-pips { display: inline-flex; gap: 5px; flex: none; }
.campcard-title { font-size: 19px; font-weight: 660; letter-spacing: -.016em; }
.campcard-sub { font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }
.campcard-bar {
  display: block; height: 4px; border-radius: 999px; background: var(--panel-2);
  overflow: hidden; margin-top: 4px;
}
.campcard-bar span {
  display: block; height: 100%; border-radius: 999px; background: var(--accent);
  transition: width var(--dur-slow) var(--ease);
}
.campcard-go { font-size: 13px; color: var(--accent-2); font-weight: 600; margin-top: 6px; }

/* ── Today's quests ────────────────────────────────────────────────────────
   A checklist, not a set of cards: three short lines you scan in a second to
   see what today asks for. Progress is derived from what you've already done,
   so rows are routinely complete before you first look at them — the claim
   button is the only thing here that needs pressing. */
.qbox {
  margin-top: 22px; padding: 20px 22px 18px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 16px;
}
.qbox-head { display: flex; justify-content: space-between; align-items: baseline; gap: 14px; margin-bottom: 4px; }
.qbox-head h2 { font-size: 18px; letter-spacing: -.014em; }
.qbox-meta { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.qlist { list-style: none; margin: 12px 0 0; }
.qrow {
  display: flex; align-items: center; gap: 14px;
  padding: 11px 0; border-top: 1px solid var(--line);
}
.qtick {
  flex: none; width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid var(--line); color: var(--green);
}
.qrow.done .qtick { border-color: rgba(74,222,128,.45); background: rgba(74,222,128,.12); }
.qbody { flex: 1; display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.qname { font-size: 14.5px; font-weight: 550; }
.qrow.claimed .qname { color: var(--muted); }
.qbar { display: block; height: 5px; border-radius: 999px; background: var(--panel-2); overflow: hidden; }
.qbar span {
  display: block; height: 100%; border-radius: 999px; background: var(--accent);
  transition: width var(--dur-slow, .5s) var(--ease);
}
.qrow.done .qbar span { background: var(--green); }
.qcount { flex: none; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; min-width: 62px; text-align: right; }
.qpending { flex: none; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.qclaimed { flex: none; font-size: 12.5px; color: var(--green); font-variant-numeric: tabular-nums; }
.qclaimed::before { content: "✓ "; }
.qclaim { flex: none; white-space: nowrap; }
.qbonus {
  display: flex; justify-content: space-between; align-items: center; gap: 14px;
  margin-top: 4px; padding: 13px 0 0; border-top: 1px solid var(--line);
  font-size: 14px; color: var(--muted);
}
.qbonus.ready { color: var(--text); font-weight: 550; }

/* Says the number and goes. The block it belongs to redraws on claim, so there
   is no bar left standing for it to animate into. */
.qflash {
  position: fixed; left: 50%; bottom: 34px; transform: translate(-50%, 12px);
  z-index: 80; padding: 10px 20px; border-radius: 999px;
  background: rgba(74,222,128,.14); border: 1px solid rgba(74,222,128,.4);
  color: var(--green); font-weight: 650; font-variant-numeric: tabular-nums;
  opacity: 0; transition: opacity var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
  pointer-events: none;
}
.qflash.on { opacity: 1; transform: translate(-50%, 0); }

/* ── Guess the output ──────────────────────────────────────────────────────
   The four options are program output, not prose. They keep their line breaks
   and their monospace, because three of them differ from the right answer by
   a character or a newline and a proportional font hides exactly that. */
.o-code { margin: 4px 0 18px; font-size: 14.5px; line-height: 1.7; }
.options.outs { display: grid; gap: 10px; grid-template-columns: 1fr; max-width: 560px; }
.opt.out {
  font-family: ui-monospace, Menlo, monospace; font-size: 14px;
  white-space: pre-wrap; line-height: 1.55;
}
.o-why {
  max-width: 68ch; margin: 14px auto 0; font-size: 14px;
  color: var(--muted); line-height: 1.65;
  border-left: 2px solid var(--line); padding-left: 14px; text-align: left;
}

/* A locked card, which is the point of showing it at all: greyed, unclickable,
   and carrying the exact requirement with live progress under it. Hiding it
   entirely would remove the only reason to want the level. */
.questcard.locked {
  border-style: dashed; cursor: default; opacity: .72;
}
.questcard.locked:hover { transform: none; box-shadow: none; border-color: rgba(var(--hue), .28); }
.questcard.locked .quest-title, .questcard.locked .quest-kicker { color: var(--muted); }
.lockbar {
  flex: 1; height: 5px; border-radius: 999px; background: var(--panel-2);
  overflow: hidden; max-width: 190px;
}
.lockbar span { display: block; height: 100%; border-radius: 999px; background: var(--muted); }

.drills-head { font-size: 23px; letter-spacing: -.018em; margin: 46px 0 5px; }

/* The panel every single-item game view sits in — question of the day, the
   weekly, guess the output. Same shell as #ch-card so the four read as one
   family rather than four pages that happen to share a header. Capped, because
   a 1200px-wide question with four short answers under it is mostly emptiness. */
/* The daily and the weekly, same treatment: no card, just the question on the
   page in the colour that led you here. */
.gcard { max-width: 760px; }
.gcard .options { margin-top: 4px; }
.gcard .result { text-align: center; }
.hintrow { margin-top: 14px; }

/* The XP figure on a card head. Gold on the weekly (it's the big one), the
   site accent everywhere else. */
.wk-xp.accent { color: var(--accent-2); }

/* Weekly challenge: the code-challenge furniture, with the tally where the
   difficulty chip would be. */
.wk-diff { background: rgba(255,198,61,.14); color: #ffc63d; border-color: rgba(255,198,61,.3); }
.wk-diff.on { background: rgba(74,222,128,.14); color: var(--green); border-color: rgba(74,222,128,.34); }
.wk-meta { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.wk-xp { font-size: 17px; font-weight: 700; color: #ffc63d; font-variant-numeric: tabular-nums; }
.wk-solvers { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }

@media (max-width: 780px) {
  .questrow { grid-template-columns: 1fr; }
  .drills-head { margin-top: 34px; }
}
/* Must sit after .wk-meta above: a media query adds no specificity, so the
   plain rule would win on source order and this would silently do nothing. */
@media (max-width: 620px) {
  /* The title and its meta stop competing for a phone's width. Side by side,
     "Digit Sum" was squeezed into 91px and wrapped mid-name while the XP
     figure beside it kept 137. Applies to the code-challenge card too — they
     share this header and it was cramped there for the same reason. */
  .ch-head { flex-direction: column; gap: 10px; }
  .wk-meta { flex-direction: row; align-items: baseline; gap: 12px; }
  .gcard { padding: 18px 16px 16px; }
}

/* ── The category shelf ────────────────────────────────────────────────────
   Four cards, each its own colour, each with a motif that says what the area
   is. Everything that moves is transform or opacity, so four forever-loops
   stay on the compositor. --hue is set per card from the id.
   Second on the page now, under the pathways, so the separator hangs here. */
.browse { margin-top: 52px; padding-top: 34px; border-top: 1px solid var(--line); }
.shelf { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 22px; }
/* An odd number of games leaves the last one beside a dead column, which reads
   as an unfinished row rather than a deliberate one. Let it take the width. */
.shelf > :last-child:nth-child(odd) { grid-column: 1 / -1; }
.shelf > :last-child:nth-child(odd) .catbox-art > span { width: min(34%, 260px); }

.catbox {
  --mx: .5; --my: .5; --lift: 0;
  position: relative; display: block; height: 320px;
  border-radius: 22px; overflow: hidden; color: var(--text);
  background: var(--panel); border: 1px solid var(--line);
  isolation: isolate;
  transform: perspective(1100px)
             rotateY(calc((var(--mx) - .5) *  7deg))
             rotateX(calc((var(--my) - .5) * -6deg))
             translateY(calc(var(--lift) * -6px));
  transition: transform .55s var(--ease), border-color .4s var(--ease),
              box-shadow .5s var(--ease);
}
.catbox:hover { text-decoration: none; }
.catbox:where(:hover, :focus-visible) {
  --lift: 1;
  border-color: rgba(var(--hue), .5);
  box-shadow: 0 30px 70px -30px rgba(var(--hue), .45), var(--shadow-md);
}

/* The light. Drifts on its own, and leans toward the cursor. */
.catbox-glow {
  position: absolute; left: 50%; top: 42%; width: 130%; aspect-ratio: 1;
  margin: -65% 0 0 -65%; z-index: 0; pointer-events: none;
  background: radial-gradient(circle at 50% 50%,
              rgba(var(--hue), .30) 0%, rgba(var(--hue), .13) 34%, transparent 66%);
  transform: translate3d(calc((var(--mx) - .5) * 90px), calc((var(--my) - .5) * 70px), 0);
  transition: transform .9s var(--ease);
  animation: catboxDrift 22s ease-in-out infinite;
}
@keyframes catboxDrift {
  0%, 100% { translate: 0 0; }
  33%      { translate: 5% -4%; }
  66%      { translate: -4% 3%; }
}

/* ── Card motifs ───────────────────────────────────────────────────────────
   This layer was declared when the shelf was built and never had anything put
   in it, which is why every card was a coloured void with the words pushed to
   the bottom. The Courses cards still draw their motifs on this layer. The
   Practice ones moved to .gm below, which puts real code in the picture rather
   than bars standing in for it — but they still sit inside .catbox-art, so the
   drift and the cursor-lean here apply to both. */
.catbox-art {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  /* Anchored to the top rather than centred: a centred motif drifts down into
     the scrim as soon as it is a few pixels taller than the last one, and the
     bottom bar reads as clipped rather than faded. */
  display: grid; place-items: start center; padding-top: 32px;
  transform: translate3d(calc((var(--mx) - .5) * 22px), calc((var(--my) - .5) * 16px), 0);
  transition: transform .9s var(--ease);
}
.catbox-art > span {
  display: flex; flex-direction: column; gap: 9px;
  width: min(62%, 260px);
}
/* Every motif is built from these bars, so one rule sets the material. */
.catbox-art i {
  display: block; height: 10px; border-radius: 999px;
  background: rgba(var(--hue), .55);
}

/* The copy never shares pixels with anything moving. */
.catbox-scrim {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(to top,
    rgba(10,12,20,.97) 20%, rgba(10,12,20,.78) 34%, rgba(10,12,20,.16) 56%, transparent 76%);
}
.catbox-copy {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 3;
  display: flex; flex-direction: column; gap: 7px; padding: 0 26px 24px;
}
.catbox-eyebrow {
  font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase;
  color: rgb(var(--hue)); font-weight: 600;
}
.catbox-title { font-size: 27px; font-weight: 680; letter-spacing: -.022em; line-height: 1.15; }
.catbox-blurb { font-size: 13.5px; color: var(--muted); line-height: 1.55; max-width: 46ch; }
.catbox-foot {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 12px; font-size: 12.5px; color: var(--muted); margin-top: 8px;
}
.catbox-done { font-variant-numeric: tabular-nums; }
.catbox .bar, .catbox .bar-fill { display: block; }
.catbox .bar { margin-top: 7px; height: 6px; }
.catbox .bar-fill { background: linear-gradient(90deg, rgba(var(--hue), .75), rgb(var(--hue))); }

.catbox-go {
  position: absolute; top: 20px; right: 22px; z-index: 3;
  width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center;
  background: rgba(var(--hue), .14); border: 1px solid rgba(var(--hue), .34);
  color: rgb(var(--hue));
  opacity: 0; transform: translateX(-6px);
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}
.catbox:where(:hover, :focus-visible) .catbox-go { opacity: 1; transform: translateX(0); }

/* A category with nothing written yet is shown, not hidden — but it doesn't
   pretend to be clickable. */
.catbox.soon { opacity: .5; pointer-events: none; }
.catbox-soon {
  font-size: 12px; color: var(--muted); border: 1px solid var(--line);
  border-radius: 999px; padding: 3px 11px; align-self: flex-start; margin-top: 6px;
}

/* Data Science — the sample, rising and falling. */
.art-data { position: absolute; left: 8%; right: 8%; top: 13%; bottom: 44%;
            display: flex; align-items: flex-end; gap: 3.4%; }
.art-data i {
  flex: 1; height: 100%; display: block; border-radius: 5px 5px 2px 2px;
  background: linear-gradient(to top, rgba(var(--hue), .22), rgba(var(--hue), .78));
  transform-origin: bottom; transform: scaleY(var(--h));
  animation: dataWave 6.5s ease-in-out infinite;
}
@keyframes dataWave {
  0%, 100% { transform: scaleY(var(--h)); }
  50%      { transform: scaleY(calc(var(--h) * 1.42)); }
}

/* Coding Fundamentals — braces breathing around lines that run. */
.art-fund { position: absolute; inset: 0; }
.art-fund-brace {
  position: absolute; font: 300 148px/1 ui-monospace, Menlo, monospace;
  color: rgba(var(--hue), .5); top: 34%; transform: translateY(-50%);
  animation: fundBreathe 8s ease-in-out infinite;
}
.art-fund-brace.l { left: 15%; }
.art-fund-brace.r { right: 15%; animation-delay: .5s; }
@keyframes fundBreathe {
  0%, 100% { transform: translateY(-50%) scale(1);    opacity: .82; }
  50%      { transform: translateY(-50%) scale(1.07); opacity: 1; }
}
.art-fund-lines {
  position: absolute; top: 34%; left: 30%; right: 30%;
  transform: translateY(-50%); display: flex; flex-direction: column; gap: 11px;
}
.art-fund-lines i {
  display: block; height: 7px; border-radius: 999px; width: var(--w);
  background: rgba(var(--hue), .55);
  transform-origin: left; animation: fundRun 5.5s ease-in-out infinite;
}
.art-fund-lines i:nth-child(2) { animation-delay: .45s; }
.art-fund-lines i:nth-child(3) { animation-delay: .9s;  }
.art-fund-lines i:nth-child(4) { animation-delay: 1.35s; }
@keyframes fundRun {
  0%, 100% { transform: scaleX(1);   opacity: .55; }
  45%      { transform: scaleX(1.1); opacity: 1; }
}

/* Languages — three depths of extensions drifting past each other. The masks
   fade them at the edges so they read as passing through, not starting. */
.art-lang {
  position: absolute; inset: 0 0 50% 0; overflow: hidden;
  mask-image: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent),
              linear-gradient(to top, transparent 2%, #000 26%);
  mask-composite: intersect; -webkit-mask-composite: source-in;
}
.art-lang-row {
  position: absolute; left: 0; display: flex; gap: 11px; white-space: nowrap;
  font: 600 var(--fs)/1 ui-monospace, Menlo, Consolas, monospace;
  color: rgba(var(--hue), var(--a));
}
.art-lang-row span {
  padding: 7px 12px; border-radius: 9px;
  border: 1px solid rgba(var(--hue), calc(var(--a) * .45));
  background: rgba(var(--hue), .05);
}
.art-lang-row.a { top: 8%;  --fs: 15px; --a: .38; animation: langSlideL 38s linear infinite; }
.art-lang-row.b { top: 38%; --fs: 20px; --a: .92; animation: langSlideR 28s linear infinite; }
.art-lang-row.c { top: 68%; --fs: 13px; --a: .26; animation: langSlideL 48s linear infinite; }
@keyframes langSlideL { from { transform: translateX(0);    } to { transform: translateX(-50%); } }
@keyframes langSlideR { from { transform: translateX(-50%); } to { transform: translateX(0);    } }

/* AI — a network breathing, with light running the edges. */
.art-ai { position: absolute; inset: 0; }
.art-ai svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.art-ai-mesh { transform-origin: 50% 42%; animation: aiBreathe 15s ease-in-out infinite; }
@keyframes aiBreathe {
  0%, 100% { transform: scale(1)     rotate(0deg); }
  50%      { transform: scale(1.045) rotate(1.4deg); }
}
.art-ai-edge { fill: none; stroke: rgba(var(--hue), .28); stroke-width: 1.2; }
.art-ai-node { fill: rgba(10,12,20,.85); stroke: rgba(var(--hue), .75); stroke-width: 2; }
.art-ai-node.on { fill: rgba(var(--hue), .35); }
.art-ai-pulse {
  fill: none; stroke: rgb(var(--hue)); stroke-width: 2.4; stroke-linecap: round;
  stroke-dasharray: 26 320; opacity: .95;
  animation: aiPulse 4.6s linear infinite;
}
.art-ai-pulse.p2 { animation-delay: 1.5s; }
.art-ai-pulse.p3 { animation-delay: 3s; }
@keyframes aiPulse {
  0%   { stroke-dashoffset: 300; opacity: 0; }
  12%  { opacity: .95; }
  88%  { opacity: .95; }
  100% { stroke-dashoffset: -46; opacity: 0; }
}

/* Reduced motion kills transition-duration, so a tilt that's still being
   calculated snaps to a new angle every frame instead of easing — the card
   jitters under the cursor. Take the movement away entirely and let hover be
   a colour change, which is a state, not motion. Both switches, because the
   OS media query and the in-app toggle are independent. */
@media (prefers-reduced-motion: reduce) {
  .catbox, .catbox-glow, .catbox-art { transform: none !important; }
  .catbox:where(:hover, :focus-visible) { background: var(--panel-2); box-shadow: none; }
}
.reduce-motion .catbox,
.reduce-motion .catbox-glow,
.reduce-motion .catbox-art { transform: none !important; }
.reduce-motion .catbox:where(:hover, :focus-visible) {
  background: var(--panel-2); box-shadow: none;
}

@media (max-width: 1000px) {
  .shelf { grid-template-columns: 1fr; }
  /* No tilt on touch — there is no cursor to lean toward. */
  .catbox { height: 300px; transform: none; }
  /* Shorter card, so the copy sits further up into the art — the scrim has to
     come up with it or the title reads over moving bars. */
  .catbox-scrim { background: linear-gradient(to top,
    rgba(10,12,20,.98) 42%, rgba(10,12,20,.9) 58%, rgba(10,12,20,.3) 78%, transparent 94%); }
}

/* A git answer is one line, so it gets one line — a code editor would imply
   a program and invite one. */
#c-cmd { display: block; }
.cmd-input {
  flex: 1; min-width: 220px;
  font-family: ui-monospace, Menlo, monospace; font-size: 14.5px;
  padding: 12px 14px; border-radius: 10px;
  background: var(--bg); color: var(--text); border: 1px solid var(--line);
}
.cmd-input:focus { outline: none; border-color: var(--accent); }


/* ── The featured rail ──────────────────────────────────────────────────────
   The shelf used to be five equal cards, and equal weight is why it read as
   filler: nothing said where to start, so all five had to be weighed up every
   visit. The two games you sit down and *do* now take one wide card at the top
   and you swipe between them. It earns the width by carrying the difficulty
   buttons — with ?d= honoured on the way in, that is one click to a question
   instead of card, setup, difficulty, Start.

   .frail, not .rail: .railrow and .rail-name are already the Courses sidebar.
   .feat, not .hero: style.css owns .hero for the landing page and would add
   100px/70px of padding to every one of these. */
/* One height for every card on the page. The band was 252 and the tiles under
   it were 296, which is what made the top one read as slim — declared once
   here so the two cannot drift apart again. */
/* 296 -> 356: twenty percent more room. Still one value for the band and the
   tiles both, so the two cannot drift back out of alignment. */
.frail, .shelf-play { --card-h: 356px; }
.frail { position: relative; }

/* Thirds: the copy takes two, the motif sits centred in the last one. */
.feat {
  --mx: .5; --my: .5;
  position: relative; display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  min-height: var(--card-h); border-radius: 22px; overflow: hidden; isolation: isolate;
  cursor: pointer;
  background: var(--panel); border: 1px solid var(--line);
  transition: border-color .4s var(--ease), box-shadow .5s var(--ease);
}
.feat:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 3px; }
.feat:hover {
  border-color: rgba(var(--hue), .42);
  box-shadow: 0 34px 84px -40px rgba(var(--hue), .45), var(--shadow-md);
}
.feat-glow {
  position: absolute; left: 70%; top: 46%; width: 100%; aspect-ratio: 1;
  margin: -50% 0 0 -50%; z-index: 0; pointer-events: none;
  background: radial-gradient(circle at 50% 50%, rgba(var(--hue), .30) 0%,
              rgba(var(--hue), .12) 36%, transparent 66%);
  transform: translate3d(calc((var(--mx) - .5) * 64px), calc((var(--my) - .5) * 44px), 0);
  transition: transform .9s var(--ease);
  animation: catboxDrift 22s ease-in-out infinite;
}
.feat-copy {
  position: relative; z-index: 3; display: flex; flex-direction: column;
  align-items: flex-start; justify-content: flex-end; gap: 7px;
  padding: 30px 12px 40px 34px;
}
.feat-kind {
  font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
  color: rgb(var(--hue)); font-weight: 600;
}
.feat-title { font-size: 31px; font-weight: 700; letter-spacing: -.028em; line-height: 1.08; }
/* The tagline, not the blurb — three lines of prose is what makes this a
   billboard instead of a band. */
.feat-blurb { font-size: 13.5px; color: var(--muted); line-height: 1.55; max-width: 48ch; }
/* Set apart from the sentence above rather than tucked under it — these are
   the thing you came to press, not the last line of the blurb. */
.feat-play { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 26px; }

/* The card's one affordance now that the pickers are gone. Quiet, because the
   whole card is the target — this only has to say that it is one. */
.feat-go {
  display: inline-flex; align-items: center; margin-top: 22px;
  font-size: 13.5px; font-weight: 650; color: rgb(var(--hue));
  transition: transform .2s var(--ease);
}
.feat:hover .feat-go { transform: translateX(3px); }

.feat-art {
  position: relative; z-index: 1; overflow: hidden;
  display: grid; place-items: center; padding: 26px 18px;
  transform: translate3d(calc((var(--mx) - .5) * 16px), calc((var(--my) - .5) * 11px), 0);
  transition: transform .9s var(--ease);
}
/* Left-edge fade, so the motif dissolves into the copy instead of butting it.
   Stops track the 2:1 split — solid across the copy, clear before the motif. */
.feat-fade {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  background: linear-gradient(to right, var(--panel) 3%, rgba(10,12,20,.28) 46%, transparent 68%);
}

/* Two bars, and that is all. No prev/next buttons: they only ever worked with a
   mouse, and they were hidden on narrow screens — which is exactly where the
   one control announcing a second game needs to be. Swipe, drag, the bars and
   the arrow keys already cover every way in. */
.frail-nav { display: flex; align-items: center; justify-content: center; gap: 9px; margin-top: 14px; }
.frail-dot {
  width: 34px; height: 4px; border-radius: 99px; border: 0; padding: 0; cursor: pointer;
  background: var(--line); transition: background .25s var(--ease), width .25s var(--ease);
}
.frail-dot:hover { background: #3d4665; }
/* The bar reads the card's own --hue, which is mid-tween during a swap — so
   it recolours continuously with the card rather than flipping at the end. */
.frail { --hue: 77,124,255; }
.frail-dot.on { background: rgb(var(--hue)); width: 50px; }

/* The shelf under the rail. Shorter than the Courses one, because the rail is
   now the biggest thing on the page and these no longer need 320px to look
   substantial. Scoped so learn.html's shelf is untouched. */
/* The two timed things. They belong to Home — this is so that arriving straight
   here does not hide them — but a line of small text under a 356px card read as
   a footnote to it rather than as two things you can do. Cards, then, at about
   a third the height: enough to be a target, not enough to compete with the
   game above them. Each keeps its own colour, and a done one goes quiet rather
   than disappearing, because having done it is worth seeing. */
/* Above the rail, not below it: they are the things with a clock on them, so
   they are what you check first and stop thinking about. */
.timed { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 0 0 18px; }
.minicard {
  position: relative; display: flex; flex-direction: column; gap: 5px;
  padding: 16px 18px; border-radius: 14px; text-decoration: none;
  background: var(--panel); border: 1px solid var(--line); color: var(--text);
  overflow: hidden;
  transition: border-color .25s var(--ease), transform .25s var(--ease),
              box-shadow .3s var(--ease);
}
.minicard::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 90% at 100% 0%, rgba(var(--hue), .16), transparent 62%);
}
.minicard:hover {
  border-color: rgba(var(--hue), .5); transform: translateY(-2px);
  box-shadow: 0 16px 34px -22px rgba(var(--hue), .5); text-decoration: none;
}
.mini-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.mini-name { font-size: 15px; font-weight: 680; letter-spacing: -.01em; }
.mini-state {
  font-size: 11px; color: rgb(var(--hue)); font-weight: 650;
  font-variant-numeric: tabular-nums; flex: none;
}
.mini-blurb { font-size: 12.5px; color: var(--muted); line-height: 1.5; }
.mini-xp { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; margin-top: 2px; }
.minicard.done { opacity: .58; }
.minicard.done .mini-state { color: var(--green); }
@media (max-width: 700px) { .timed { grid-template-columns: 1fr; } }

.shelf-play { margin-top: 26px; align-items: start; }
.shelf-play .catbox { height: var(--card-h); }
.shelf-play .catbox-title { font-size: 23px; }
.shelf-play .catbox-blurb { font-size: 13px; }
.shelf-play .catbox-art { padding-top: 26px; }

/* ── Game motifs ────────────────────────────────────────────────────────────
   A small true picture of each game with real code in it. One grid unit, one
   type scale, five very different pictures — and each plays its own game rather
   than sharing a generic shimmer.

   Everything is transform and opacity except a few colour flips that *are* the
   beat, one element each. Loop lengths all differ and several start on a
   negative delay, so five motifs on one screen never fall into step and start
   pulsing as a unit. */
.gm {
  --k: #c792ea; --str: #c3e88d; --num: #f78c6c; --fn: #82aaff; --pun: #7d88a8;
  width: 252px !important; font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 11px; line-height: 1.65; display: block !important; letter-spacing: .01em;
}
/* The old motifs were built entirely from <i> bars, so .catbox-art i sets a
   height, a radius and a hue fill on every <i> in here. These use <i> for lines
   of code, which would arrive wearing that pill and render as a solid coloured
   slab. Strip the material off first; every rule below is more specific, so the
   pieces that do want a fill still get one. (The .catbox-art rules stay — the
   Courses motifs are still built on them.) */
.gm i, .gm b, .gm u, .gm em {
  background: none; height: auto; width: auto; border-radius: 0; box-shadow: none; border: 0;
}
/* Syntax colours. All five are unknown element names on purpose, so they are
   inline with no styling of their own — <p> for punctuation nests a block
   paragraph inside each line and makes one line of code 128px tall, and <s>
   would arrive struck through. Pinned inline anyway, so neither can come back. */
.gm k { color: var(--k); } .gm f { color: var(--fn); } .gm n { color: var(--num); }
.gm z { color: var(--str); } .gm c { color: var(--pun); }
.gm k, .gm f, .gm n, .gm z, .gm c {
  display: inline; margin: 0; padding: 0; font-style: normal; text-decoration: none;
}
.gm .win {
  background: rgba(6,8,15,.62); border: 1px solid rgba(var(--hue), .26);
  border-radius: 10px; overflow: hidden; display: block;
}
/* Just the filename. Three coloured dots are a picture of a window, not of
   this game, and they were the busiest thing in any of these motifs. */
.gm .chrome {
  display: flex; align-items: center; padding: 6px 11px;
  border-bottom: 1px solid rgba(var(--hue), .18); background: rgba(var(--hue), .08);
}
.gm .chrome em { margin-left: 0; font-style: normal; font-size: 9.5px; color: var(--muted); letter-spacing: .04em; }
.gm .code { display: block; padding: 9px 11px; white-space: nowrap; }
.gm .code i { display: block; font-style: normal; }
.gm .code i.t1 { padding-left: 14px; }

/* Quick quiz — a question and four answers, one of which gets ticked. */
.gm-quiz .ask { display: block; color: var(--text); font-size: 11.5px; margin-bottom: 9px; white-space: nowrap; }
.gm-quiz .opts { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.gm-quiz .opts i {
  position: relative; display: block; font-style: normal; padding: 7px 10px;
  border-radius: 8px; border: 1px solid rgba(var(--hue), .34);
  background: rgba(var(--hue), .11); color: var(--text);
}
.gm-quiz .opts i b {
  position: absolute; right: 9px; top: 50%; width: 8px; height: 4px; margin-top: -3px;
  border-left: 2px solid var(--green); border-bottom: 2px solid var(--green);
  transform: rotate(-45deg); opacity: 0;
}
.gm-quiz .opts i:not(.on) { animation: qRead 6s var(--ease) infinite; }
.gm-quiz .opts i:nth-child(2) { animation-delay: .28s; }
.gm-quiz .opts i:nth-child(4) { animation-delay: .84s; }
@keyframes qRead {
  0%, 100% { opacity: .62; transform: translateY(0); }
  5%       { opacity: 1;   transform: translateY(-2px); }
  14%      { opacity: .62; transform: translateY(0); }
  48%, 86% { opacity: .34; transform: translateY(0); }
}
.gm-quiz .opts i.on { animation: qPick 6s var(--ease) infinite; }
@keyframes qPick {
  0%, 40%  { background: rgba(var(--hue), .11); border-color: rgba(var(--hue), .34); transform: translateY(0); }
  47%      { background: rgba(74,222,128,.2); border-color: rgba(74,222,128,.75); transform: translateY(-3px); }
  55%, 88% { background: rgba(74,222,128,.2); border-color: rgba(74,222,128,.75); transform: translateY(0); }
  100%     { background: rgba(var(--hue), .11); border-color: rgba(var(--hue), .34); transform: translateY(0); }
}
.gm-quiz .opts i.on b { animation: qTick 6s var(--ease) infinite; }
@keyframes qTick {
  0%, 43%  { opacity: 0; transform: rotate(-45deg) scale(.3); }
  52%      { opacity: 1; transform: rotate(-45deg) scale(1.3); }
  58%, 88% { opacity: 1; transform: rotate(-45deg) scale(1); }
  100%     { opacity: 0; transform: rotate(-45deg) scale(.3); }
}

/* Guess the output — the program, then the line it printed. Bare lines rather
   than a window, so it does not read as Code challenges, and the printed value
   sits under a rule in the game's own colour, which is the part you are being
   asked about. An earlier pass showed only "?" turning into "4"; that was
   tidier and said nothing about what the question was. */
.gm-out { display: flex; flex-direction: column; gap: 0; }
.gm-out .ocode { display: flex; flex-direction: column; gap: 4px; }
.gm-out .ocode i {
  display: block; font-style: normal; white-space: nowrap; opacity: .62;
  transform-origin: left center; animation: oType 5.4s var(--ease) infinite;
}
.gm-out .ocode i:nth-child(2) { animation-delay: .13s; }
.gm-out .ocode i:nth-child(3) { animation-delay: .26s; }
.gm-out .ocode i:nth-child(4) { animation-delay: .39s; }
@keyframes oType {
  0%        { opacity: 0; transform: translateX(-6px); }
  12%, 84%  { opacity: .62; transform: none; }
  94%, 100% { opacity: 0; transform: translateX(-6px); }
}
.gm-out .oout {
  display: flex; align-items: baseline; gap: 10px;
  margin-top: 12px; padding-top: 11px;
  border-top: 1px solid rgba(var(--hue), .3);
}
.gm-out .oout em { font-style: normal; color: var(--muted); font-size: 14px; }
.gm-out .oout b {
  font-size: 26px; font-weight: 700; line-height: 1; letter-spacing: -.02em;
  color: rgb(var(--hue)); display: inline-block;
  animation: oPrint 5.4s var(--ease) infinite;
}
@keyframes oPrint {
  0%, 40%   { opacity: 0; transform: translateY(8px) scale(.78); }
  50%       { opacity: 1; transform: translateY(0) scale(1.18); }
  56%, 86%  { opacity: 1; transform: none; }
  94%, 100% { opacity: 0; transform: translateY(8px) scale(.78); }
}

/* Styles Match — a real little composition, and your dashed copy sliding onto
   it. The drift-and-snap is the whole game, so the motif does nothing else. */
.gm-match { position: relative; height: 140px; }
.gm-match .tgt, .gm-match .you {
  position: absolute; width: 138px; height: 100px; border-radius: 11px;
  overflow: hidden; display: block;
}
.gm-match .tgt {
  left: 4px; top: 0; background: rgba(var(--hue), .16); border: 1px solid rgba(var(--hue), .4);
  animation: mLit 6.6s var(--ease) infinite; animation-delay: -1.1s;
}
.gm-match .tgt b {
  position: absolute; left: 15px; top: 17px; width: 40px; height: 40px;
  border-radius: 50%; background: rgb(var(--hue)); display: block;
}
.gm-match .tgt u {
  position: absolute; left: 15px; bottom: 17px; height: 9px; width: 104px;
  border-radius: 99px; background: rgba(var(--hue), .6); display: block;
}
@keyframes mLit { 0%, 34% { opacity: .7; } 48%, 68% { opacity: 1; } 84%, 100% { opacity: .7; } }
.gm-match .you {
  left: 78px; top: 36px; border: 2px dashed rgba(var(--hue), .9); background: rgba(6,8,15,.5);
  animation: mSnap 6.6s var(--ease) infinite; animation-delay: -1.1s;
}
.gm-match .you b {
  position: absolute; left: 14px; top: 16px; width: 40px; height: 40px;
  border-radius: 50%; border: 2px dashed rgba(var(--hue), .75); display: block;
}
.gm-match .you u {
  position: absolute; left: 14px; bottom: 16px; height: 9px; width: 104px;
  border-radius: 99px; border: 2px dashed rgba(var(--hue), .75); display: block;
}
@keyframes mSnap {
  0%, 10%   { transform: translate(0, 0); border-color: rgba(var(--hue), .9); }
  44%, 70%  { transform: translate(-74px, -36px); border-color: rgba(74,222,128,.95); }
  96%, 100% { transform: translate(0, 0); border-color: rgba(var(--hue), .9); }
}

/* Code challenges — code being typed under a caret, then the tests pass. */
.gm-term .code i:not(.run) { transform-origin: left center; animation: tType 5.8s var(--ease) infinite; }
.gm-term .code i:nth-child(2) { animation-delay: .24s; }
.gm-term .code i:nth-child(3) { animation-delay: .48s; }
@keyframes tType {
  0%        { clip-path: inset(0 100% 0 0); }
  13%, 80%  { clip-path: inset(0 0 0 0); }
  93%, 100% { clip-path: inset(0 100% 0 0); }
}
.gm-term .caret {
  display: inline-block; width: 6px; height: 11px; vertical-align: -1px;
  background: rgb(var(--hue)); animation: tCaret 5.8s var(--ease) infinite;
}
@keyframes tCaret {
  0%, 16%       { opacity: 0; }
  22%, 64%      { opacity: 1; }
  30%, 44%, 58% { opacity: .15; }
  37%, 51%      { opacity: 1; }
  72%, 100%     { opacity: 0; }
}
.gm-term .run {
  display: block; font-style: normal; margin-top: 6px; padding-top: 7px;
  border-top: 1px solid rgba(var(--hue), .18); color: var(--green);
  animation: tPass 5.8s var(--ease) infinite;
}
@keyframes tPass {
  0%, 66%   { opacity: 0; transform: translateY(4px); }
  74%, 90%  { opacity: 1; transform: none; }
  97%, 100% { opacity: 0; transform: translateY(4px); }
}

/* Find the bug — no window either. Bare lines with a reading bar sweeping down
   them and stopping on the wrong one, which is what the game actually feels
   like: the search, not the code. */
.gm-bug { position: relative; display: flex; flex-direction: column; gap: 9px; padding: 4px 0; }
.gm-bug i { display: block; font-style: normal; white-space: nowrap; position: relative; z-index: 1; }
.gm-bug .bscan {
  position: absolute; left: -10px; right: -10px; height: 24px; top: -3px; z-index: 0;
  border-radius: 6px; background: rgba(var(--hue), .16);
  border-left: 2px solid rgb(var(--hue));
  animation: bScan 6.2s var(--ease) infinite; animation-delay: -.5s;
}
@keyframes bScan {
  0%       { transform: translateY(0); opacity: 0; }
  8%       { transform: translateY(0); opacity: 1; }
  20%      { transform: translateY(29px); opacity: 1; }
  32%      { transform: translateY(58px); opacity: 1; }
  44%, 86% { transform: translateY(0); opacity: 1; }
  96%, 100%{ transform: translateY(0); opacity: 0; }
}
.gm-bug .swap { position: relative; display: inline-block; min-width: 7px; }
.gm-bug .swap em { font-style: normal; display: inline-block; }
.gm-bug .swap em.was { color: #ff6b6e; animation: bWas 6.2s var(--ease) infinite; animation-delay: -.5s; }
.gm-bug .swap em.now {
  position: absolute; left: 0; top: 0; color: var(--green); opacity: 0;
  animation: bNow 6.2s var(--ease) infinite; animation-delay: -.5s;
}
@keyframes bWas { 0%, 62% { opacity: 1; } 70%, 92% { opacity: 0; } 100% { opacity: 1; } }
@keyframes bNow { 0%, 62% { opacity: 0; } 70%, 92% { opacity: 1; } 100% { opacity: 0; } }
/* The squiggle: a repeating-gradient zigzag that wipes in once the bar arrives. */
.gm-bug .sq {
  position: absolute; left: 0; right: 0; bottom: -3px; height: 3px; display: block;
  background: repeating-linear-gradient(135deg, #ff6b6e 0 2px, transparent 2px 4px);
  animation: bSq 6.2s var(--ease) infinite; animation-delay: -.5s;
}
@keyframes bSq {
  0%, 44%   { opacity: 0; transform: scaleX(0); }
  52%, 62%  { opacity: 1; transform: scaleX(1); }
  70%, 100% { opacity: 0; transform: scaleX(1); }
}

/* Order the lines — numbered slots with gaps between them, and one line lifting
   out and dropping into place. Rows rather than a block of code, because the
   game is about where a line sits, not what it says. */
/* Spacing via margin, not gap: .gm sets display:block !important and this
   element *is* a .gm, so the flex display never applied and gap did nothing —
   the rows sat flush against each other, which is what read as overlapping. */
.gm-order .oslot + .oslot { margin-top: 8px; }
.gm-order .oslot {
  display: flex; align-items: center; gap: 11px; font-style: normal;
  background: rgba(var(--hue), .08); border: 1px solid rgba(var(--hue), .22);
  border-radius: 8px; padding: 6px 11px; white-space: nowrap;
}
.gm-order .oslot u {
  text-decoration: none; font-size: 10px; color: var(--muted);
  width: 9px; flex: none; font-variant-numeric: tabular-nums;
}
/* Slots 3 and 4 trade places: the "return" that was too early drops below the
   loop that has to run first. */
/* --pitch is the distance between two rows: their height plus the margin above.
   The keyframes move by exactly this, so a change to either one cannot leave
   the swap landing between slots. It was translating 35px against a real pitch
   of 32, which is what put one row on top of another mid-move. */
.gm-order { --pitch: 40px; }
.gm-order .s3 { animation: ordA 5.6s var(--ease) infinite; animation-delay: -.4s; }
.gm-order .s4 { animation: ordB 5.6s var(--ease) infinite; animation-delay: -.4s; }
/* Two rows trading places have to cross — that is what a swap is. The moving one
   is lifted above the other while it travels: raised, shadowed and slightly
   larger, so the moment they overlap reads as one row dragged over another
   rather than two rows colliding. */
.gm-order .s3 { position: relative; z-index: 2; }
@keyframes ordA {
  0%, 18%   { transform: translateY(0) translateX(0) scale(1); opacity: 1;
              background: rgba(var(--hue), .08); border-color: rgba(var(--hue), .22);
              box-shadow: none; }
  26%       { transform: translateY(4px) translateX(7px) scale(1.03); opacity: 1;
              background: rgba(var(--hue), .22); border-color: rgba(var(--hue), .6);
              box-shadow: 0 10px 22px -8px rgba(0, 0, 0, .75); }
  60%       { transform: translateY(calc(var(--pitch) * .7)) translateX(7px) scale(1.03);
              opacity: 1; background: rgba(var(--hue), .22);
              border-color: rgba(var(--hue), .6);
              box-shadow: 0 10px 22px -8px rgba(0, 0, 0, .75); }
  72%, 84%  { transform: translateY(var(--pitch)) translateX(0) scale(1); opacity: 1;
              background: rgba(var(--hue), .08); border-color: rgba(var(--hue), .22);
              box-shadow: none; }
  90%, 94%  { transform: translateY(var(--pitch)) scale(1); opacity: 0; box-shadow: none; }
  95%, 100% { transform: translateY(0) scale(1); opacity: 1; box-shadow: none; }
}
/* The row underneath slides straight up into the slot the lifted one vacated,
   starting only once the lift is clear of it. */
@keyframes ordB {
  0%, 30%   { transform: translateY(0); opacity: 1; }
  50%, 84%  { transform: translateY(calc(var(--pitch) * -1)); opacity: 1; }
  90%, 94%  { transform: translateY(calc(var(--pitch) * -1)); opacity: 0; }
  95%, 100% { transform: translateY(0); opacity: 1; }
}

/* A motif that keeps performing while you are trying to read is worse than a
   still one, so all of it stops on request — both the OS setting and the
   in-app toggle nav.js puts on <html>. Each still frame is the state worth
   seeing: the answer ticked, the output printed, the tests passed, the bug
   still flagged rather than already fixed. */
@media (prefers-reduced-motion: reduce) {
  .gm *, .feat-glow, .feat-art { animation: none !important; }
  /* The frame worth seeing when nothing moves: the answer ticked, the value
     printed, the tests passed, the bug found but not yet fixed, the lines in
     the order they started in. */
  .gm-quiz .opts i.on b, .gm-term .run, .gm-out .oout b,
  .gm-bug .sq, .gm-bug .bscan { opacity: 1 !important; transform: none !important; }
  .gm-bug .swap em.now { opacity: 0 !important; }
  .gm-term .code i, .gm-out .ocode i { clip-path: none !important; opacity: .62 !important; }
  .gm-order .s3, .gm-order .s4 { transform: none !important; opacity: 1 !important; }

}
.reduce-motion .gm *,
.reduce-motion .feat-glow,
.reduce-motion .feat-art { animation: none !important; }
.reduce-motion .gm-quiz .opts i.on b, .reduce-motion .gm-term .run,
.reduce-motion .gm-out .oout b, .reduce-motion .gm-bug .sq,
.reduce-motion .gm-bug .bscan { opacity: 1 !important; transform: none !important; }
.reduce-motion .gm-bug .swap em.now { opacity: 0 !important; }
.reduce-motion .gm-term .code i { clip-path: none !important; }
.reduce-motion .gm-out .ocode i { opacity: .62 !important; transform: none !important; }
.reduce-motion .gm-order .s3, .reduce-motion .gm-order .s4 { transform: none !important; opacity: 1 !important; }

/* ── Home, on the same footing as Practice ─────────────────────────────────
   The page was four bordered cards stacked before any content — a sign-in
   prompt, four stat tiles, and a box around each quest block — on the one page
   whose whole job is to be read in three seconds. */

/* Who you are, as a line rather than a panel. */
.hwho { display: flex; align-items: center; gap: 16px; margin-bottom: 18px; background: none;
        border: 0; padding: 0; }
.hwho h1 { font-size: 30px; letter-spacing: -.025em; margin: 0; line-height: 1.1; }

/* A box around a number says the number is something you can act on. None of
   these are, so they are numbers. */
.hstats {
  display: flex; flex-wrap: wrap; gap: 34px;
  padding-bottom: 26px; margin-bottom: 30px; border-bottom: 1px solid var(--line);
}
.hstat b {
  display: block; font-size: 26px; font-weight: 700; letter-spacing: -.02em;
  line-height: 1; font-variant-numeric: tabular-nums;
}
.hstat span {
  display: block; font-size: 11px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); margin-top: 7px;
}
.hstat[data-locked] b { color: var(--muted); opacity: .5; }

/* The quest blocks lose their box; the heading takes the shelf's shape. */
.qbox { background: none; border: 0; padding: 0; margin: 38px 0 0; }
.qbox-head {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding-top: 22px; margin-bottom: 10px; border-top: 1px solid var(--line);
}
.qbox-head h2 { font-size: 15px; font-weight: 650; letter-spacing: -.01em; margin: 0; }
.qbox-meta { font-size: 12.5px; color: var(--muted); }
.qlist { list-style: none; margin: 0; padding: 0; }
.qrow {
  display: flex; align-items: center; gap: 14px; padding: 12px 0;
  border-bottom: 1px solid rgba(35, 42, 66, .55);
}
.qrow:last-child { border-bottom: 0; }
/* Every quest gets a bar, including the ones counted in one step — a row with
   a bar next to a row without one reads as two kinds of thing. */
.qbody { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.qname { font-size: 13.5px; }
.qrow:not(.done) .qname { color: var(--muted); }
.qbar { display: block; height: 4px; border-radius: 99px; background: var(--panel-2); overflow: hidden; }
.qbar span { display: block; height: 100%; border-radius: 99px; background: var(--accent); transition: width .4s var(--ease); }
.qrow.done .qbar span { background: var(--green); }
.qtick {
  width: 17px; height: 17px; border-radius: 50%; flex: none; display: grid;
  place-items: center; border: 1px solid var(--line); color: transparent;
}
.qrow.done .qtick { background: rgba(74,222,128,.16); border-color: rgba(74,222,128,.6); color: var(--green); }
.qcount { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; flex: none; }
.qclaimed { font-size: 11.5px; color: var(--muted); flex: none; }

/* The section head above the timed pair, matching the shelf's. */
.hhead {
  display: flex; align-items: baseline; justify-content: space-between;
  margin: 38px 0 14px; padding-top: 22px; border-top: 1px solid var(--line);
}
.hhead h2 { font-size: 15px; font-weight: 650; letter-spacing: -.01em; margin: 0; }
.hhead span { font-size: 12.5px; color: var(--muted); }

/* The guest notice sits under the header on every page, this one included. */

/* Said once, where it lands: a guest has just earned XP that is not being kept,
   and the number is on screen. Styled as a consequence of the result rather
   than a banner over it — the gold dot is the same one the old top bar used, so
   the meaning carries even though the placement changed. */
.savenudge {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 16px 0 0; padding: 11px 14px; border-radius: 10px;
  background: rgba(255, 198, 61, .07); border: 1px solid rgba(255, 198, 61, .28);
  font-size: 12.5px; color: var(--muted); line-height: 1.5;
}
.savenudge::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%; flex: none;
  background: var(--gold); transform: translateY(-2px);
}
.savenudge b { color: var(--gold); font-weight: 700; }
.savenudge a { color: var(--accent-2); font-weight: 600; }

/* The other door. Small, under the options, where every site puts it. */
.signin-switch {
  text-align: center; font-size: 13px; color: var(--muted);
  margin: 18px 0 0; display: flex; gap: 6px; justify-content: center; flex-wrap: wrap;
}
.signin-switch .linkbtn { font-size: 13px; font-weight: 600; }

/* The provider buttons, stacked and equal width. Only the first one offered is
   filled — a column of identical primary buttons gives no way in. */
.signin-provider { width: 100%; justify-content: center; margin-bottom: 10px; }
.signin-hint {
  font-size: 11.5px; color: var(--muted); line-height: 1.5;
  margin: -4px 0 14px; text-align: center;
}
.signin-hint code {
  font-family: ui-monospace, Menlo, monospace; font-size: 11px;
  background: var(--panel-2); padding: 1px 5px; border-radius: 5px;
}

/* The sign-in card, short enough to see at once. It was long enough to scroll,
   which put the guest option — the one most people take — below the fold. */
.signin-perks {
  margin: 20px 0 0; padding-top: 16px; border-top: 1px solid var(--line);
  font-size: 12px; line-height: 1.5;
}
.signin-perks li { margin-bottom: 5px; }
.signin-switch { margin: 14px 0 0; }
.signin-sep { margin: 16px 0; }
.signin-guest { width: 100%; justify-content: center; }
.signin-note { margin: 10px 0 0; font-size: 11.5px; }
/* Folded away: true, occasionally useful, and not part of signing up. */
.signin-more { margin-top: 14px; font-size: 11.5px; color: var(--muted); }
.signin-more summary { cursor: pointer; color: var(--accent-2); list-style: none; }
.signin-more summary::-webkit-details-marker { display: none; }
.signin-more summary::before { content: "+ "; opacity: .7; }
.signin-more[open] summary::before { content: "– "; }
.signin-more p { margin: 8px 0 0; line-height: 1.55; }
.signin-legal { margin-top: 16px; font-size: 11px; }
.signin-card .signin-mark { margin-bottom: 12px; }
.signin-card h1 { font-size: 25px; margin-bottom: 6px; }
.signin-card .section-sub { font-size: 13px; margin-bottom: 20px; max-width: 34ch; margin-inline: auto; }
.signin-provider { margin-bottom: 8px; }
.signin-hint { margin: -2px 0 12px; }
/* Sits with the button it describes, not at the end of the card. The button is
   inside the form, so the note is the form's sibling — not the button's. */
#guest-form + .signin-note { margin: 8px 0 0; }

/* An empty result slot should take no room. These are filled only after an
   answer, and while empty they were still holding 24px of margin apiece —
   which is most of the gap that made the result area look sprawling. */
.levelup-badge:empty, .verdict:empty, .bar-note:empty { display: none; }

/* Skip sits opposite the action it is an alternative to. */

/* One shape for every game, taken from the quiz because that is the one that
   reads cleanly:

     ←                          N left      what you are set to, and how many left
     languages / difficulty                 the settings, centred
     ─────────────────────────────────      the game's colour
     the question, and the thing you do to it
     [Primary] [Reset]           0:42       only for games you submit to
     Hint                         Skip      always, opposite ends

   Hint and Skip were in the action row on three games and the footer on two,
   which made every screen need re-reading. They are help with this question and
   leaving this question — neither is an action on your answer, so neither
   belongs beside the button that submits it. */

/* Section headings for the two halves of the shelf. Same weight and rule as
   Home's, so a heading means the same thing on both pages. */
.gsec {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin: 0 0 14px;
}
.gsec-rule { margin-top: 40px; padding-top: 22px; border-top: 1px solid var(--line); }
.gsec h2 {
  display: flex; align-items: center; gap: 8px;
  font-size: 15px; font-weight: 650; letter-spacing: -.01em; margin: 0;
}
.gsec h2 svg { color: #fff; opacity: .5; flex: none; }
.gsec span { font-size: 12.5px; color: var(--muted); }

/* ── the two rows every game shares ──────────────────────────────────────
   Top: back on the left, what is left of today on the right, Skip furthest
   right. Bottom: Hint on the left, and the one button that commits on the
   right, where a form's submit belongs and where the thumb already is. */
.playbar-top > .playbar-left { margin-left: auto; }


.qfoot-end { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.qfoot-end .ch-timer {
  font-variant-numeric: tabular-nums; font-size: 15px;
  color: var(--muted); letter-spacing: .02em;
}

/* The commit button wears the game's colour rather than the site's blue, so
   the screen reads as one thing. Tinted, not filled: a solid block that size
   pulled the eye off the question it is meant to be answering. */
.btn-go {
  background: rgba(var(--play-hue, var(--hue, 77,124,255)), .14);
  border: 1px solid rgba(var(--play-hue, var(--hue, 77,124,255)), .45);
  color: rgb(var(--play-hue, var(--hue, 77,124,255)));
  font-weight: 620;
}
.btn-go:hover:not(:disabled) {
  background: rgba(var(--play-hue, var(--hue, 77,124,255)), .22);
  border-color: rgba(var(--play-hue, var(--hue, 77,124,255)), .75);
}
.btn-go:disabled { opacity: .35; cursor: default; }
.btn-go:focus-visible {
  outline: 2px solid rgb(var(--play-hue, var(--hue, 77,124,255))); outline-offset: 3px;
}

/* Chosen, not yet answered — has to be legible next to .right and .wrong
   without being mistaken for either, so it is the game's own colour. */
.opt.sel {
  border-color: rgb(var(--play-hue, var(--hue, 77,124,255)));
  background: rgba(var(--play-hue, var(--hue, 77,124,255)), .20);
  box-shadow: 0 0 0 1px rgba(var(--play-hue, var(--hue, 77,124,255)), .45);
}
.opt.right, .opt.wrong { box-shadow: none; }

/* The daily and the weekly are not .qplay views, so they were missing the tint
   the other games' options carry and their XP figure was pinned to the site
   blue by .accent — the one number that belongs to the daily looked like it
   belonged to the header. --play-hue falls back to --hue, which is what the
   shelf cards set, so both contexts stay right. */
.opt {
  background: rgba(var(--play-hue, var(--hue)), .09);
  border-color: rgba(var(--play-hue, var(--hue)), .28);
}
.opt:hover:not(:disabled) {
  background: rgba(var(--play-hue, var(--hue)), .17);
  border-color: rgba(var(--play-hue, var(--hue)), .5);
}
.opt.sel:hover { background: rgba(var(--play-hue, var(--hue, 77,124,255)), .20); }
.wk-xp, .wk-xp.accent { color: rgb(var(--play-hue, 255,198,61)); }
/* Last of the blue: the topic eyebrow. Same treatment as the XP figure. */
.ch-diff { color: rgb(var(--play-hue, var(--hue, 122,162,255))); }

/* Skip sits under the rule rather than in the toolbar row: up there it read as
   another session control next to the back arrow, when it acts on the one
   question in front of you. Hard right, above the question it skips. */
.qskip { display: flex; justify-content: flex-end; margin: -14px 0 6px; }

/* ── Courses, brought in line with the games ─────────────────────────────
   The lesson screen had a bordered box around the example, another around
   "Your turn", and a bordered code editor inside that — three frames to read
   past before any content. The games dropped their panels for the same
   reason: the code block is the thing you are reading, so it carries the one
   border and everything else sits on the page. */
.egwrap { border: 0; border-radius: 0; overflow: visible; }
.checkbox { border: 0; border-radius: 0; overflow: visible; margin-bottom: 48px; }
.checkbox .eglabel { border-bottom: 0; }
.checkbox .options { padding: 0; }

.eglabel {
  background: none; border-bottom: 0; padding: 0 0 8px;
  font-size: 11px; letter-spacing: .09em;
}
.eglabel.out { border-top: 0; padding-top: 18px; }

/* The example and its output keep a border each, the way .codebox does on a
   game screen — they are the two things here you actually read as code. */
.eg {
  border: 1px solid var(--line); border-radius: 10px;
  background: #0d1020; padding: 14px 16px;
}
.eg.out { border-color: rgba(74,222,128,.28); }

.prompt { padding: 0 0 14px; }
.verdict { margin: 0 0 18px; }
.checkbox .rbar-wrap, .checkbox .levelup-badge { margin-left: 0; margin-right: 0; }

/* The toolbar's progress bar, on the row under the back arrow where the games
   put their language and difficulty chips. */
.lprogress { margin: 0; }
.lprogress .bar { max-width: none; }

.cmd-input { width: 100%; }

/* Every ending, right or wrong, offers the same three things in the same
   places: another one, back to the shelf, and out to Home. "Done for now"
   named a mood; these name destinations. */
.resnav {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  margin-top: 26px;
}
.resnav-out { display: inline-flex; align-items: center; gap: 4px; }
.resnav-out .btn-quiet { padding: 6px 10px; font-size: 13px; }
.resnav-out .btn-quiet + .btn-quiet::before {
  content: "·"; color: var(--muted); opacity: .5; margin-right: 12px;
}

/* Order keeps its puzzle on screen, so its way out sits at the far left of the
   footer row opposite the button that loads the next one. */
.qfoot-row > .resnav-out { margin-right: auto; }

/* An answered option is a record, not a control. It kept the border and the
   cursor of something you could press, which is what made the highlighted
   right answer read as the way onward once the question was over. */
.opt:disabled { cursor: default; opacity: .55; }
.opt.right:disabled, .opt.wrong:disabled { opacity: 1; }
.opt:disabled:hover { background: rgba(var(--play-hue, var(--hue)), .09);
                      border-color: rgba(var(--play-hue, var(--hue)), .28); }
.opt.right:disabled:hover { border-color: #2f9e5f; background: rgba(74,222,128,.13); }
/* Room under an ending so its buttons never sit flush against the fold. */
.resnav { padding-bottom: 28px; }
/* Belt and braces on the same problem: don't let the browser re-anchor the
   page while a result's XP bar is still animating into place. */
.result, .ch-after, #dq-result, #wk-after { overflow-anchor: none; }
