/* Hone, the whole visual system.
 *
 * Lifted out of index.html unchanged. It lived inline because the page was
 * one file and that was honest at the time; at ~300 lines it had become the
 * thing that made every other change slow to find.
 *
 * The design notes below are load-bearing. Read them before 'improving'
 * anything here.
 */
/* ─────────────────────────────────────────────────────────────────────────
   DESIGN NOTES, read before "improving" any of this.

   The audience is developers who can ship with AI help and privately suspect
   they don't understand what they shipped. A page that tries hard to look
   futuristic reads, to that person, as a product with something to hide. So
   every effect here earns its place by making something easier to read or
   easier to trust:

     · ONE accent color, and it only ever means truth, correct, progress,
       the mark. Never decoration.
     · Monospace for code and data ONLY. A whole page in terminal font is
       costume, not craft.
     · Motion under 220ms, none of it decorative.
     · Depth comes from light, not drop shadows, so it holds up on OLED.
   ──────────────────────────────────────────────────────────────────────── */
:root{
  --bg:#080a0d; --bg2:#0c1014; --panel:#11161c; --line:#1b222b;
  --line2:#242d38; --ink:#eaf0f7; --dim:#8d9aab; --faint:#7d8a9a;
  --accent:#6ee7a8; --accent-dk:#2f6b4d; --warn:#e6bd6d; --bad:#f08b8b;
  /* TWO TOKENS THAT WERE READ AND NEVER DEFINED, which is not a fallback,
     it is nothing: `background:var(--card)` on an undefined property makes
     the element TRANSPARENT. The track menu was the visible cost -- it
     overlays the page, so every track name landed on top of the day's
     question and both were unreadable. --dimline was quieter: a hover
     border that never appeared. Same value as --panel and --line2 by
     intent; they keep separate names because a card is a raised chip or
     menu and a panel is a section of the page. tests/test_css_tokens.py
     now fails on any property read without a value. */
  --card:#11161c; --dimline:#2c3644;
  --mono:ui-monospace,"SF Mono",SFMono-Regular,Menlo,monospace;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,system-ui,sans-serif;
}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);
  font-size:16px;line-height:1.55;-webkit-font-smoothing:antialiased}
/* ONE TYPOGRAPHIC BASELINE, everywhere. A heading is short enough that the
   browser can even out its lines instead of leaving one word on the second
   row; a paragraph is long enough that the thing to prevent is a single
   orphaned word on the last row. Both properties are progressive: a browser
   that does not know them lays the text out exactly as it did before, so
   nothing here is load-bearing.

   Nothing is said about <pre> on purpose. text-wrap is a longhand of
   white-space, so writing text-wrap:wrap on a <pre> would override the
   nowrap that white-space:pre gives it and start folding code at the
   column edge. The rules below never touch it, and white-space on a
   <pre> keeps beating what it would otherwise inherit. */
h1,h2,h3,h4{text-wrap:balance}
p,li,dd,figcaption,blockquote{text-wrap:pretty}
body::before{content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
  background:radial-gradient(1000px 460px at 50% -12%,rgba(110,231,168,.05),transparent 68%)}
.wrap{position:relative;z-index:1;max-width:660px;margin:0 auto;
  padding:24px 18px calc(80px + env(safe-area-inset-bottom))}
.hidden{display:none!important}

header{display:flex;align-items:center;gap:10px;margin-bottom:18px}
.mark{font-family:var(--mono);font-size:13.5px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--accent)}
/* The wordmark is a link home on every page; it should look like a mark,
   not like a link. */
a.mark,a.brand{text-decoration:none}
a.brand{display:inline-block}
.hdr-right{margin-left:auto;display:flex;align-items:center;gap:12px;
  font-size:12.5px;color:var(--faint);min-width:0}
/* A FLEX ITEM WILL NOT SHRINK BELOW ITS CONTENT unless you say so: the
   default min-width:auto is what let the account chip run 69px past the
   right edge of a 390px phone, cutting the name and the gear clean off.
   Nothing here is allowed to do that again -- the chips truncate, which
   is what the ellipsis on the name was always for and never got. */
header{min-width:0}
.hdr-right > *{min-width:0}
.pts{font-family:var(--mono);color:var(--accent);font-size:13px;min-width:2ch;display:inline-block;font-variant-numeric:tabular-nums}
.ptswrap{display:inline-flex;align-items:center}
.gdef{font-size:13px;color:var(--dim);line-height:1.55;margin:7px 0}
.gdef b{color:var(--ink);font-weight:600}
.iconbtn{background:none;border:0;color:var(--faint);font:inherit;cursor:pointer;
  padding:5px 6px;min-width:24px;min-height:24px;font-size:14px;line-height:1}
/* A LINK IN THE ICON ROW. The Calendar link sits beside the sound button and
   takes its look, but an anchor gets none of a button's box, so on its own
   it measured 24px: under a fingertip (tests/test_tap_targets.py). The box
   is the law's 44px; the glyph stays the row's size. */
a.iconbtn{display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;text-decoration:none}

/* SECTION CARDS. Each section is a panel, and it has to READ as one, or a
   long stacked page (the day view during extra questions, especially on a
   phone) turns into one blur where nobody can tell where a section ends.
   Depth comes from light, not shadows, per the design notes at the top: a
   1px lit top edge and a slightly stronger border lift the card off the
   near-black page without a single drop shadow, so it holds on OLED. */
section{background:linear-gradient(180deg,var(--panel),var(--bg2));
  border:1px solid var(--line2);border-radius:14px;padding:20px;margin-bottom:18px;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
/* SECTION HEADER, the anchor that says which section you are in. A short
   quiet bar starts it and a rule finishes it, so every header reads as one
   scannable band; brighter than before so it does not vanish into the card.
   The bar is line-coloured, never the accent, which stays reserved for
   truth and progress. */
.label{font-family:var(--mono);font-size:11px;letter-spacing:.15em;
  text-transform:uppercase;color:var(--dim);margin-bottom:14px;font-weight:500;
  display:flex;align-items:center;gap:9px}
.label::before{content:"";width:3px;height:12px;border-radius:2px;
  background:var(--line2);flex:none}
.label::after{content:"";flex:1;height:1px;background:var(--line)}
/* KIND CHIP + SUBHEAD. Every workspace zone says, in words, what kind of
   thing it is and why it is there, so the day reads as distinct areas at a
   glance instead of one long identical stack. Words do the differentiating,
   never colour: the accent stays reserved for truth and progress. The chip
   is a quiet neutral pill in the header band; the subhead is one plain line
   under it. */
.kind{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--faint);border:1px solid var(--line2);
  border-radius:999px;padding:2px 8px;font-weight:500;flex:none;letter-spacing:.08em}
.subhead{font-size:13px;color:var(--faint);line-height:1.55;margin:-6px 0 15px;
  max-width:60ch}
.subhead b{color:var(--dim);font-weight:600}
/* The hands-on challenge is the day's main event. A slightly brighter frame
   (still a neutral, never the accent) sets that one card apart from the
   quick-question card above it, so which is which is obvious at a glance. */
.zone-code{border-color:#33404e}
h2{font-size:17.5px;font-weight:600;margin:0 0 15px;line-height:1.42}

/* white-space:pre-wrap for the same reason #q got it in #116, on the other
   half of the screen. A choice reaches the button through textContent, so
   HTML collapsed every run of spaces and every newline inside it -- and 24
   of these questions ask what a program PRINTS, where the line structure is
   the answer: 'one\ntwo' arrived as "one two", git status --short lost the
   column that tells staged from unstaged, and f'{42:>5}' could not show
   five columns. Prose choices are unaffected; single spaces render the
   same either way. */
.choice{display:block;width:100%;text-align:left;background:#0a0e12;color:var(--ink);
  border:1px solid var(--line);border-radius:10px;padding:14px 15px;margin-bottom:9px;
  font-size:15px;font-family:inherit;cursor:pointer;white-space:pre-wrap;
  transition:border-color .14s,background .14s,transform .1s}
/* A LETTER ON EVERY ANSWER. A multiple-choice row is a wide box with, quite
   often, three characters in it ('x', 42, True), and an empty box the width
   of the card is what an unfinished page looks like. Every serious testing
   product (and every exam paper ever printed) letters its options: it fills
   the row, gives the eye an anchor to scan down, and lets somebody say "I
   picked B" out loud. It is a CSS counter, so the button's text is untouched
   and a screen reader still reads only the answer.

   Scoped to the four containers that hold real answers, and never to the
   retry buttons or the "I am not sure" escape hatch that share the .choice
   look but are not options to weigh. */
#pchoices,#dchoices,#choices,.demo-choices{counter-reset:optn}
#pchoices>.choice:not(.retry):not(.idk):not(.unsure),
#dchoices>.choice:not(.retry),
#choices>.choice:not(.retry):not(.idk):not(.unsure),
.demo-choices>.choice:not(.retry){display:flex;align-items:baseline;gap:12px;
  counter-increment:optn}
#pchoices>.choice:not(.retry):not(.idk):not(.unsure)::before,
#dchoices>.choice:not(.retry)::before,
#choices>.choice:not(.retry):not(.idk):not(.unsure)::before,
.demo-choices>.choice:not(.retry)::before{
  content:counter(optn,upper-alpha);font-family:var(--mono);font-size:11.5px;
  color:var(--faint);letter-spacing:.06em;flex:0 0 auto;min-width:1.2em}
.choice.right::before{color:var(--accent)}
.choice.wrong::before{color:var(--bad)}
.choice:active:not(:disabled){transform:scale(.994)}
.choice:hover:not(:disabled){border-color:var(--line2);background:#0d1319}
.choice:disabled{cursor:default;opacity:.5}
.choice.right{border-color:var(--accent);background:rgba(110,231,168,.07);opacity:1;
  box-shadow:0 0 0 1px rgba(110,231,168,.16)}
.choice.wrong{border-color:var(--bad);background:rgba(240,139,139,.06);opacity:1}
.why{font-size:14px;color:var(--dim);margin-top:13px;padding-top:13px;
  border-top:1px solid var(--line);animation:rise .22s ease}
@keyframes rise{from{opacity:0;transform:translateY(-3px)}to{opacity:1}}

.prompt{font-size:15px;color:var(--dim);margin-bottom:15px}
textarea{width:100%;min-height:170px;background:#0a0e12;color:var(--ink);
  border:1px solid var(--line);border-radius:10px;padding:14px;
  font-family:var(--mono);font-size:13.5px;line-height:1.65;resize:vertical;tab-size:4}
textarea:focus:not(:focus-visible){outline:none}
textarea:focus{border-color:var(--accent-dk)}

.row{display:flex;gap:11px;align-items:center;margin-top:13px;flex-wrap:wrap}
button.go{background:var(--accent);color:#06120c;border:0;border-radius:10px;
  padding:12px 20px;font-size:14.5px;font-weight:650;font-family:inherit;cursor:pointer;
  transition:filter .14s,transform .1s}
button.go:hover{filter:brightness(1.06)}
button.go:active{transform:scale(.98)}
button.go:disabled{opacity:.4;cursor:default}
button.ghost{background:none;border:1px solid var(--line2);color:var(--dim);
  border-radius:10px;padding:11px 16px;font-size:14px;font-family:inherit;cursor:pointer}
button.ghost:hover{border-color:var(--dim);color:var(--ink)}
.hint{font-size:13px;color:var(--faint);flex:1;min-width:170px}

.rail{display:flex;gap:5px;margin-bottom:16px}
.rail i{flex:1;height:3px;border-radius:2px;background:var(--line);transition:background .25s}
.rail i.on{background:var(--accent)} .rail i.now{background:var(--accent-dk)}

.trow{display:flex;align-items:center;gap:12px;padding:10px 0;
  border-bottom:1px solid #141a21;font-size:14.5px}
.trow:last-child{border-bottom:0}
/* fixed column: a comparison chart whose baselines don't align can't be scanned */
.tleft{display:flex;align-items:center;gap:7px;flex:1 1 150px;min-width:0}
.tname{font-family:var(--mono);font-size:13px;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.peek{font-size:9.5px;letter-spacing:.07em;text-transform:uppercase;color:var(--warn);
  border:1px solid #3d3216;border-radius:4px;padding:1px 5px;flex:none}
.duetag{font-size:9.5px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--accent);border:1px solid var(--accent-dk);border-radius:4px;
  padding:1px 5px;flex:none}
.bar{flex:1;height:5px;background:#161d25;border-radius:3px;overflow:hidden;min-width:46px}
.bar i{display:block;height:100%;border-radius:3px;
  background:linear-gradient(90deg,var(--accent-dk),var(--accent));
  transition:width .55s cubic-bezier(.4,0,.2,1)}
.tnum{font-family:var(--mono);font-size:12px;color:var(--faint);
  min-width:40px;text-align:right;flex:none}
.streak{font-size:13px;color:var(--dim);margin-top:15px;padding-top:13px;
  border-top:1px solid var(--line)}
.empty{font-size:14px;color:var(--faint);padding:4px 0}
.askbar{position:fixed;z-index:50;display:flex;gap:1px;background:#0f151b;
  border:1px solid var(--line2);border-radius:9px;overflow:hidden;
  box-shadow:0 6px 20px rgba(0,0,0,.5);animation:rise .14s ease}
.askbar button{background:none;border:0;color:var(--ink);font:inherit;
  font-size:12.5px;padding:9px 12px;cursor:pointer;white-space:nowrap}
.askbar button:hover{background:#182029;color:var(--accent)}
/* The keyboard sheet. Centered over a dim, not a black-out: the code you
   were looking at stays faintly visible, so opening the sheet never feels
   like leaving the page you were working on. */
.sheet{position:fixed;inset:0;z-index:100;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:1px;
  background:rgba(4,6,9,.72);-webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);
  animation:rise .16s ease;padding:24px}
.sheet h3{font-family:var(--mono);font-size:11px;letter-spacing:.18em;
  text-transform:uppercase;color:var(--faint);margin:0 0 14px;font-weight:500}
.krow{display:flex;align-items:center;gap:16px;width:min(400px,100%);
  padding:7px 0;font-size:13.5px;color:var(--dim)}
.krow kbd{font-family:var(--mono);font-size:11.5px;color:var(--ink);
  background:#121820;border:1px solid var(--line2);border-bottom-width:2px;
  border-radius:6px;padding:3px 8px;min-width:92px;text-align:center;flex:none}
.kfoot{font-size:12px;color:var(--faint);margin:16px 0 0}
.tracenote{font-size:12px;color:var(--faint);margin-top:11px;
  padding-top:10px;border-top:1px solid var(--line)}
.keyhint{font-family:var(--mono);font-size:11px;color:var(--faint);
  background:none;border:1px solid var(--line);border-radius:5px;
  padding:4px 7px;min-height:24px;cursor:pointer}
.keyhint:hover{border-color:var(--line2);color:var(--dim)}
.askout{margin-top:12px;padding:12px 14px;border-radius:9px;font-size:13.5px;
  border:1px solid var(--line);background:#0a0e12;color:var(--dim);
  animation:rise .2s ease}
.askout.no{border-color:#3d3216;color:var(--warn)}
.askscope{font-family:var(--mono);font-size:10px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--faint);margin-bottom:5px}
.results{margin-top:14px;padding-top:14px;border-top:1px solid var(--line);
  animation:rise .22s ease}
.tcase{display:flex;align-items:center;gap:10px;padding:7px 0;
  font-family:var(--mono);font-size:12.5px}
.dot{width:7px;height:7px;border-radius:50%;flex:none}
.dot.p{background:var(--accent)} .dot.f{background:var(--bad)}
.tcall{color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* This was flex:none, so "expected X, got Y" sized to its own content and
   could not shrink. One ordinary failing test then made the entire page
   scroll sideways. The type-error row had already been fixed this way; the
   common case had not. */
.tgot{margin-left:auto;color:var(--faint);flex:1 1 auto;min-width:0;
      white-space:normal;word-break:break-word}
.verdict{font-size:14px;margin-bottom:10px;font-weight:600}
.verdict.p{color:var(--accent)} .verdict.f{color:var(--warn)}
.stdout{font-family:var(--mono);font-size:12px;color:var(--dim);
  background:#080b0e;border:1px solid var(--line);border-radius:8px;
  padding:10px 12px;margin-top:10px;white-space:pre-wrap;max-height:160px;
  overflow:auto}

.gate{max-width:360px;margin:0 auto;min-height:100dvh;display:flex;
  flex-direction:column;justify-content:center;padding:32px 24px;text-align:center}
.step{animation:stepIn .26s cubic-bezier(.22,.9,.3,1)}
@keyframes stepIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.brand{font-family:var(--mono);font-size:26px;letter-spacing:.24em;
  text-transform:uppercase;color:var(--accent);margin-bottom:18px}
.tag{font-size:15px;color:var(--dim);line-height:1.6;margin:0 0 34px}
.stepnum{font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--faint);margin-bottom:14px}
.gate h1{font-size:22px;font-weight:600;margin:0 0 8px;letter-spacing:-.01em;
  line-height:1.3}
.sub{font-size:14px;color:var(--dim);margin:0 0 26px}
button.big{width:100%;padding:15px;font-size:15.5px;border-radius:12px;
  margin-bottom:10px}
button.link{background:none;border:0;color:var(--faint);font:inherit;
  font-size:14px;padding:11px;cursor:pointer;width:100%}
button.link:hover{color:var(--ink)}
.field{width:100%;background:#0a0e12;border:1px solid var(--line2);
  border-radius:12px;padding:15px 16px;color:var(--ink);font:inherit;
  font-size:16px;margin-bottom:12px;text-align:center}
.field:focus:not(:focus-visible){outline:none}
.field:focus{border-color:var(--accent-dk)}
.pin{display:flex;gap:14px;justify-content:center;margin-bottom:30px}
.pin span{width:12px;height:12px;border-radius:50%;background:transparent;
  border:1.5px solid var(--line2);transition:transform .12s,background .12s,border-color .12s}
.pin span.on{background:var(--accent);border-color:var(--accent);transform:scale(1.18)}
.pad{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}
.pad button{aspect-ratio:1.35;background:#0e141a;border:1px solid var(--line);
  color:var(--ink);border-radius:14px;font-family:var(--mono);font-size:22px;
  cursor:pointer;transition:background .1s,transform .06s;
  display:flex;align-items:center;justify-content:center}
.pad button:active{transform:scale(.94);background:#1a232c}
.pad button.g{background:none;border-color:transparent;color:var(--faint);
  font-size:15px;font-family:var(--sans)}
.err{color:var(--bad);font-size:13.5px;min-height:20px;margin-top:14px}
.langs{display:flex;flex-direction:column;gap:10px}
.langs button{background:#0e141a;border:1px solid var(--line);color:var(--ink);
  border-radius:12px;padding:15px;font:inherit;font-size:15.5px;cursor:pointer;
  transition:border-color .14s,background .1s,transform .06s}
.langs button:hover{border-color:var(--line2)}
.langs button:active{transform:scale(.985)}
.langs button.on{border-color:var(--accent);color:var(--accent)}
.foot{font-size:12px;color:var(--faint);line-height:1.7;margin-top:38px}

.done{text-align:center;padding:8px 0 4px}
.done .big{font-family:var(--mono);font-size:30px;color:var(--accent);
  animation:pop .35s cubic-bezier(.2,1.4,.4,1)}
@keyframes pop{from{transform:scale(.7);opacity:0}to{transform:scale(1);opacity:1}}
footer{margin-top:28px;font-size:12.5px;color:var(--faint);text-align:center;line-height:1.75}
@media(max-width:520px){
  .wrap{padding:18px 14px 60px} section{padding:16px;border-radius:12px}
  .tleft{width:120px} .tname{font-size:12.5px}
}

/* ── command palette ──────────────────────────────────────────────────────
   Anchored high rather than centered: it opens under your eye, not over the
   thing you were reading, and it does not jump when the list length changes.
   Every palette worth copying does this. */
.pal{position:fixed;inset:0;z-index:120;display:flex;justify-content:center;
  align-items:flex-start;padding:12vh 20px 20px;
  background:rgba(4,6,9,.62);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);animation:rise .12s ease}
.palbox{width:min(560px,100%);background:#0d1218;border:1px solid var(--line2);
  border-radius:13px;overflow:hidden;box-shadow:0 24px 70px rgba(0,0,0,.6)}
.palin{width:100%;background:none;border:0;border-bottom:1px solid var(--line);
  color:var(--ink);font-family:inherit;font-size:15.5px;padding:16px 18px}
.palin:focus:not(:focus-visible){outline:none}
.palin::placeholder{color:var(--faint)}
.pallist{max-height:min(46vh,380px);overflow-y:auto;padding:6px}
.pitem{display:flex;align-items:center;gap:14px;padding:10px 12px;
  border-radius:8px;cursor:pointer;font-size:14px;color:var(--dim)}
/* Highlight follows the KEYBOARD, not the mouse. If hover also highlighted,
   a stationary cursor would silently steal the row Enter is about to run. */
.pitem.on{background:#16202b;color:var(--ink)}
.plabel{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.phint{font-family:var(--mono);font-size:11px;color:var(--faint);flex:none}
.pempty{padding:16px 12px;font-size:13.5px;color:var(--faint)}

/* ── attempt history ─────────────────────────────────────────────────────
   Rows are buttons, not divs with handlers: keyboard reachable and
   announced correctly for free. */
.history{margin-top:14px;padding-top:14px;border-top:1px solid var(--line);
  animation:rise .2s ease}
.hrow{display:flex;align-items:center;gap:11px;width:100%;text-align:left;
  background:none;border:0;border-radius:8px;padding:8px 9px;cursor:pointer;
  font-family:inherit;font-size:13px;color:var(--dim)}
.hrow:hover{background:#0f151b;color:var(--ink)}
/* The attempt where it went green sits a shade brighter, so the eye lands on
   the moment that mattered without a loud colour. */
.hrow.green{color:var(--ink)}
.hsum{font-size:12px;color:var(--dim);margin:2px 0 10px;line-height:1.5}
.hwhen{font-family:var(--mono);font-size:12px}
.hmeta{font-family:var(--mono);font-size:11.5px;color:var(--faint);
  margin-left:auto}

/* Where someone is starting from. Full width targets with a second line,
   because the choice matters and a row of terse chips would make a beginner
   guess which one they are allowed to pick. */
.whos{display:flex;flex-direction:column;gap:9px;margin-bottom:6px}
.whobtn{display:flex;flex-direction:column;gap:3px;text-align:left;width:100%;
  background:#0a0e12;border:1px solid var(--line);border-radius:11px;
  padding:14px 16px;cursor:pointer;font-family:inherit;
  transition:border-color .14s,background .14s,transform .1s}
.whobtn:hover{border-color:var(--line2);background:#0d1319}
.whobtn:active{transform:scale(.994)}
.whot{font-size:15px;color:var(--ink);font-weight:550}
.whos2{font-size:12.5px;color:var(--faint)}

/* The lesson. Reading material, so it gets a measure and line height meant
   for prose rather than the dense layout the rest of the page uses. */
.lesson{margin-top:15px;padding:18px 19px;border-radius:12px;
  border:1px solid var(--line);background:#0a0e12;animation:rise .2s ease}
.lt{font-size:16.5px;margin:0 0 12px}
.lesson p{font-size:14.5px;line-height:1.62;color:var(--dim);margin:0 0 12px}
.lesson b{color:var(--ink);font-weight:600}
.lidea{color:var(--ink)!important;font-size:15px!important}
.lwork{margin:14px 0;border-left:2px solid var(--accent-dk);padding-left:14px}
.lstep{margin-bottom:11px}
/* INDENTATION IS PART OF THE CODE, AND THIS THREW IT AWAY. With the
   default white-space, the browser strips leading spaces, so every worked
   example that shows a line inside a loop, an if, a function or a class
   rendered flat against the margin. 71 lessons and 189 lines, most of them
   Python, where the indentation IS the syntax: a learner reading "Choosing
   between two paths" was shown an if/else that would not run. Measured in
   Chromium, not guessed: the data holds "    if email in banned:" and the
   screen showed "if email in banned:". pre-wrap keeps the spaces and still
   wraps a long line rather than pushing the panel sideways. */
.lstep code{display:block;font-family:var(--mono);font-size:13px;
  color:var(--ink);margin-bottom:3px;white-space:pre-wrap}
.lnote{font-size:13px;color:var(--faint);line-height:1.55}
/* The two doors out of a lesson, side by side: it worked, or it did not.
   Both are 44px so a thumb can hit either one. */
.lend{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.lend .ghost{min-height:44px}
.lhuh{color:var(--dim)}
.lhelp{margin:10px 0 0;padding:11px 13px;border-radius:9px;background:#0d1319;
  border:1px solid var(--line)}
.lhelp .lsaid{margin:0 0 6px;font-size:14px;color:var(--ink)}
.lhelp p{margin:6px 0 0}
/* The first door is a button, not a link, because it does something here
   rather than taking you away. 44px so a thumb can hit it. */
.lhelp .lsmall{min-height:44px;margin:8px 0 0}
.lhelp .lsmallnote{margin:5px 0 0}
.lhelp .lref{color:var(--faint)}
.lhelp .lref a{color:var(--faint)}
.lfade{margin:14px 0;padding:13px 14px;border-radius:9px;background:#0d1319;
  border:1px solid var(--line);font-size:14px;color:var(--dim)}
.lfade pre{font-family:var(--mono);font-size:13px;color:var(--ink);
  margin:9px 0;white-space:pre-wrap}
.ltrap{border-top:1px solid var(--line);padding-top:12px}
.lopen{margin-top:13px}

/* Draft controls. Reset is quiet and hidden until there is something to
   discard, because a destructive button on a blank editor is only a way to
   make someone nervous. */
button.ghost.small{padding:7px 12px;font-size:12.5px}
.saved{font-family:var(--mono);font-size:11px;color:var(--accent-dk);
  animation:rise .16s ease}

/* ── phones ───────────────────────────────────────────────────────────────
 *
 * Everything built in the last session was laid out at desktop width and
 * nothing below governed it on a small screen. These rules cover that gap.
 *
 * NOT VERIFIED ON A REAL DEVICE. The browser harness reported a successful
 * resize and left the viewport at 1394px, so this is a considered fix and
 * not a checked one. It must be looked at on an actual phone before anyone
 * claims mobile works, and the two things most likely to still be wrong are
 * text selection inside the editor, which fights the operating system's own
 * selection handles, and the inline trace values, which assume horizontal
 * room a phone does not have.
 */
@media(max-width:520px){
  /* The command palette is a full sheet on a phone. A centered box with
     margins wastes the only screen dimension there is. */
  .pal{padding:0;align-items:stretch}
  .palbox{width:100%;border-radius:0;border-left:0;border-right:0;
    display:flex;flex-direction:column;max-height:100%}
  .palin{font-size:16px;padding:18px 16px}   /* 16px stops iOS zooming in */
  .pallist{max-height:none;flex:1}
  .pitem{padding:14px 12px;font-size:15px}   /* a finger, not a cursor */
  .phint{display:none}                        /* no room, and no keyboard */

  /* The keyboard sheet is meaningless without a physical keyboard, so it
     does not belong in the way on a touch device. */
  .sheet{display:none!important}
  .keyhint{display:none}

  .krow{width:100%}

  /* Reading material gets room to breathe rather than desktop density. */
  .lesson{padding:15px 14px}
  .lstep code{font-size:12.5px;word-break:break-word}
  .lfade pre{font-size:12.5px}

  /* Full width choices. Everything tappable clears the 44px minimum. */
  .whobtn{padding:15px 15px}
  .choice{padding:15px 14px;font-size:15px}
  button.go{padding:14px 20px;font-size:15px;width:100%}
  .row{gap:9px}
  .row .hint{min-width:0;flex-basis:100%;order:9}
  button.ghost{padding:13px 16px}
  .hrow{padding:12px 9px}

  /* The trace assumes horizontal room a phone does not have. Wrapping it
     under its line is better than clipping it off the edge, and far better
     than pretending the values are complete. */
  .cm-trace{padding-left:12px;font-size:11px}
}

/* ── side pane ────────────────────────────────────────────────────────────
   Slides from the right over a scrim. Fixed rather than pushing the page:
   reflowing the whole layout to open a panel is what makes a site feel slow,
   and the editor would relayout underneath you. */
.pane{position:fixed;top:0;right:0;bottom:0;width:min(320px,86vw);z-index:90;
  background:#0b1015;border-left:1px solid var(--line);overflow-y:auto;
  padding:22px 20px calc(22px + env(safe-area-inset-bottom));
  animation:slidein .18s cubic-bezier(.4,0,.2,1)}
@keyframes slidein{from{transform:translateX(14px);opacity:0}to{opacity:1}}
.panescrim{position:fixed;inset:0;z-index:89;background:rgba(4,6,9,.5);touch-action:none}
.pstat{display:flex;align-items:center;gap:9px;margin-bottom:14px;
  font-size:15px;color:var(--ink)}
.ptag{font-size:10.5px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--accent);border:1px solid var(--accent-dk);border-radius:4px;
  padding:2px 6px}
.prow{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:9px 0;border-bottom:1px solid #141a21;font-size:13.5px;
  color:var(--dim)}
.prow b{font-family:var(--mono);font-size:13px;color:var(--ink);font-weight:500}
/* Nothing due is progress, so it gets the one accent colour, quietly. */
.caughtup{color:var(--accent);font-family:var(--sans,inherit);font-size:12.5px}
/* The plan rows. Buttons, because every line is something you can do right
   now, and the reason sits under the name in words rather than as a score. */
.planrow{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;
  background:none;border:0;border-bottom:1px solid #141a21;padding:9px 0;
  cursor:pointer;text-align:left;color:var(--ink);font:inherit}
.planrow .tname{white-space:normal;overflow:visible;text-overflow:clip}
.planrow i{font-style:normal;font-size:12px;color:var(--faint);line-height:1.4}
.planrow:hover .tname,.planrow:focus-visible .tname{color:var(--accent)}
/* THE PATH. Five stops down a single rail; the rail and each node fill with
   the accent only as real progress earns it, so the colour is truth, never
   decoration. One accent, depth from a light ring not a shadow. */
.path{margin:2px 0 4px}
.pstop{position:relative;display:flex;gap:13px;padding-bottom:15px}
.pstop:not(:last-child)::before{content:"";position:absolute;left:6px;top:15px;
  bottom:0;width:2px;background:var(--line)}
.pstop.done:not(:last-child)::before{background:var(--accent-dk)}
.pnode{position:relative;z-index:1;flex:none;width:14px;height:14px;margin-top:2px;
  border-radius:50%;border:2px solid var(--line2);background:var(--bg2);
  transition:border-color .22s ease,background .22s ease}
.pstop.active .pnode{border-color:var(--accent-dk)}
.pstop.done .pnode{background:var(--accent);border-color:var(--accent)}
.pstop.here .pnode{border-color:var(--accent);box-shadow:0 0 0 4px rgba(110,231,168,.13)}
.pbody{flex:1;min-width:0}
.phead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.phead b{font-size:14px;font-weight:600;color:var(--ink)}
.pstop.new .phead b{color:var(--dim)}
.pcount{font-family:var(--mono);font-size:11px;color:var(--dim);margin-left:auto}
.phere{font-family:var(--mono);font-size:10px;letter-spacing:.07em;text-transform:uppercase;
  color:var(--accent);border:1px solid var(--accent-dk);border-radius:4px;padding:1px 6px}
.pbar{height:3px;background:var(--line);border-radius:2px;margin:8px 0;overflow:hidden}
.pbar i{display:block;height:100%;border-radius:2px;
  background:linear-gradient(90deg,var(--accent-dk),var(--accent));transition:width .3s ease}
.pblurb{margin:0 0 8px;font-size:12.5px;color:var(--faint);line-height:1.5}
.pchips{display:flex;flex-wrap:wrap;gap:5px;align-items:center}
.pchip{font-size:11px;padding:2px 8px;border-radius:20px;border:1px solid var(--line2);
  color:var(--dim);background:none;white-space:nowrap;max-width:100%;overflow:hidden;
  text-overflow:ellipsis}
.pchip.strong{background:rgba(110,231,168,.1);border-color:var(--accent-dk);color:var(--accent)}
.pchip.practised{color:var(--ink);border-color:var(--line2)}
.pchip.new{color:var(--faint);border-style:dashed;border-color:var(--line)}
.pmore{font-size:11px;color:var(--faint);padding:2px 2px}
/* HOW FAR YOU'VE COME. One row per turned-around topic: a title, the gain, and
   two bars — a dim "then" and an accent "now", because progress is the one
   thing the accent is for. Words carry the counts; colour only marks the
   direction of travel. */
.grows{display:flex;flex-direction:column;gap:14px}
.grow-h{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
.grow-h .tname{font-size:13.5px;color:var(--ink)}
.grow-gain{font-size:12px;color:var(--accent);font-weight:600;flex:none;
  font-variant-numeric:tabular-nums}
.grow-r{display:flex;align-items:center;gap:8px;margin-top:6px}
.grow-k{width:32px;flex:none;font-size:11px;text-transform:uppercase;
  letter-spacing:.04em;color:var(--faint)}
.grow-n{width:46px;flex:none;text-align:right;font-size:12px;color:var(--dim);
  font-variant-numeric:tabular-nums}
.grow-bar{flex:1;height:6px;background:var(--line);border-radius:3px;overflow:hidden}
.grow-bar>i{display:block;height:100%;border-radius:3px;background:var(--line2);
  transition:width .34s cubic-bezier(.22,1,.36,1)}
.grow-bar.acc>i{background:var(--accent)}
/* The level links to the page that explains it. Dotted, so it reads as
   "there is more here" rather than as navigation chrome. */
.lvlink{color:inherit;text-decoration:underline dotted;text-underline-offset:3px}
.lvlink:hover{color:var(--accent)}
.pacts{display:flex;flex-direction:column;gap:8px;margin-top:18px}
.pacts .ghost{width:100%}

/* Friends in the pane. A row per person: name and good facts on the left,
   one action on the right. Applause only, never a ranking or a deficit. */
/* THE FIELD KEEPS ITS ROOM, AND THE BUTTON MOVES.
   A row of [field][Send request] with nothing to wrap it gives the
   button its natural width and lets the field take what is left:
   measured at 117px on a 320px phone, narrower than the placeholder
   and barely wider than the word being typed into it. The basis is
   what the field asks for before anything shrinks, so when the pair
   will not fit on one line the button drops to the next and the
   field gets the whole width.

   THE NUMBER IS MEASURED, not chosen. The placeholder is 207px of text
   and the field carries 11px of padding each side, so it needs 229px to
   say what it is for; 230 is that, rounded. The button is 133px and the
   gap 6, so the pair asks for 369px: the pane is 350px wide on a 390px
   phone and 279 on a desktop, and it wraps in both, while a 430px phone
   has 390px and keeps them side by side with room to spare. */
.fradd{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px}
.fradd .field{margin:0;flex:1 1 230px;min-width:0}
.fradd .go{white-space:nowrap;flex:0 0 auto}
.frrow{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:8px 0;border-bottom:1px solid #141a21}
.frname{display:flex;flex-direction:column;font-size:13.5px;color:var(--ink);min-width:0}
.frname i{font-style:normal;font-size:11.5px;color:var(--faint);margin-top:2px}
.fracts,.frcheer{white-space:nowrap;flex:0 0 auto}
/* The daily cooperative line. The shared-win state glows; the nudges stay
   quiet, because a prod should never shout. */
.cotag{display:inline-block;font-size:11px;margin-top:4px;color:var(--dim)}
.cotag.on{color:var(--accent);font-weight:600}

/* ── bottom bar, phones only ──────────────────────────────────────────────
 *
 * A hamburger in the top right is the least reachable point on a phone and
 * asks someone to decode a symbol as well as stretch for it. Thumbs sit at
 * the bottom, so navigation does too.
 *
 * Words, not icons. An icon has to be learned; a word does not, and this
 * product is for people who may never have used a coding site before.
 */
.tabbar{position:fixed;left:0;right:0;bottom:0;z-index:91;display:flex;
  background:rgba(9,13,17,.94);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
  border-top:1px solid var(--line);
  padding:6px 6px calc(6px + env(safe-area-inset-bottom))}
.tab{flex:1;background:none;border:0;color:var(--faint);font-family:inherit;
  font-size:12.5px;padding:11px 4px;cursor:pointer;border-radius:9px;
  min-height:48px;transition:color .16s ease,background .16s ease}
.tab.on{color:var(--accent)}
/* THE UNREAD DOT. It rides the account button, which on a phone is the You
   tab at the bottom and on a desktop is the button at the top right: the
   same control at two sizes, so there is one place to look either way and
   nobody is asked to stretch for a corner. Colour is never the only signal,
   the button's own label says the count too. */
.ndot{position:absolute;top:6px;right:calc(50% - 22px);width:8px;height:8px;border-radius:50%;
  background:var(--accent);box-shadow:0 0 0 2px rgba(9,13,17,.94)}
.tab{position:relative}
.avatar{position:relative}
.avatar .ndot{top:2px;right:2px;box-shadow:0 0 0 2px #0d1319}
@media(prefers-reduced-motion:no-preference){
  .ndot{animation:ndotIn .34s ease-out both}
}
@keyframes ndotIn{from{transform:scale(0)}to{transform:scale(1)}}
.tab:active{background:#131a22}
/* DESKTOP / TABLET: the same four tabs become a segmented control at the top
   of the single focused column, so exactly one view shows at a time on every
   screen size — the day stops being one long pile of everything at once. */
@media(min-width:521px){
  .tabbar{position:sticky;top:10px;bottom:auto;left:auto;right:auto;z-index:20;
    max-width:720px;margin:0 auto 18px;gap:4px;padding:5px;
    border:1px solid var(--line2);border-radius:12px;border-top:1px solid var(--line2);
    background:linear-gradient(180deg,var(--panel),var(--bg2));
    -webkit-backdrop-filter:none;backdrop-filter:none;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
  .tab{min-height:38px;font-size:13px;border-radius:8px;padding:8px 4px}
  .tab.on{background:#131a22;color:var(--accent)}
}
@media(max-width:520px){
  .tabbar{display:flex}
  /* Room for the bar so it never covers the last thing on the page. */
  .wrap{padding-bottom:calc(96px + env(safe-area-inset-bottom))}
  /* The header keeps the identity and loses the clutter. Points, sound and
     language all live in the pane, which is now one thumb away. */
  /* The track chip STAYS on a phone. It was hidden here while it was
     only a label; now it is the only way to change track, and hiding
     the control from the smaller screen is where that bias starts. */
  /* The rank meter goes too. It is the widest thing in the row at 106px --
     a word, a number and a 44px bar -- and all three are already in the You
     pane and on /level. The STREAK stays: it is 52px, and showing up is the
     one thing this product is actually about. */
  .hdr-right .ptswrap, .hdr-right #sound, .hdr-right .rank{display:none}
  .hdr-right .langbtn{max-width:34vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .hdr-right .avatar{max-width:min(170px,42vw)}
  /* The avatar stays: who you are is worth the space; the You tab also opens it. */
  /* The pane becomes a bottom sheet, because it is opened from the bottom
     and a panel that flies in from the opposite side of the screen to the
     finger that summoned it feels wrong even when people cannot say why. */
  /* dvh where the browser has it: vh on iOS means the URL-bar-collapsed
     viewport, so a 78vh sheet changed height as the bar came and went. */
  .pane{top:auto;left:0;width:100%;max-height:78vh;max-height:78dvh;border-left:0;
    border-top:1px solid var(--line2);border-radius:16px 16px 0 0;
    bottom:calc(60px + env(safe-area-inset-bottom));
    /* none, not contain: contain still lets iOS rubber-band the sheet at its
       top, and the bounce would run underneath the pull-to-close drag in
       app.js, moving the sheet twice for one finger. With the bounce off,
       pulling past the top has exactly one meaning. */
    overscroll-behavior-y:none;
    animation:sheetup .2s cubic-bezier(.4,0,.2,1)}
  @keyframes sheetup{from{transform:translateY(18px);opacity:0}to{opacity:1}}
  /* The grab handle. Phones taught everyone that a sheet with a small bar
     at the top can be pulled down to close; the bar makes the promise and
     the drag code in app.js keeps it. A pseudo-element on purpose: the
     sheet's contents are rewritten on every open, and a real element would
     be thrown away with them. */
  .pane::before{content:"";display:block;width:44px;height:5px;border-radius:3px;
    background:#2b3542;margin:-10px auto 12px}
}

/* ── rank ─────────────────────────────────────────────────────────────────
   A number and a bar that fills. Small, always visible, and it moves every
   time you work, which is the whole reason it exists. Restrained on purpose:
   this audience reads a loud progress meter as a product trying to distract
   them from something. */
.rank{display:inline-flex;align-items:center;gap:7px}
.rank b{font-family:var(--mono);font-size:13px;color:var(--accent);
  font-weight:600;min-width:16px;text-align:right;
  transition:transform .3s cubic-bezier(.2,1.5,.4,1)}
.rank.bump b{transform:scale(1.5)}
.rbar{display:block;width:44px;height:4px;border-radius:2px;
  background:#182029;overflow:hidden}
.rbar u{display:block;height:100%;width:0;border-radius:2px;
  background:linear-gradient(90deg,var(--accent-dk),var(--accent));
  transition:width .7s cubic-bezier(.2,.8,.2,1)}

/* Experience floating up from where it was earned. Brief, and it leaves.
   A reward that lingers becomes clutter you have to look past. */
.xpfloat{position:fixed;z-index:110;pointer-events:none;font-family:var(--mono);
  font-size:clamp(13px,1.8vmin,26px);color:var(--accent);font-weight:600;
  animation:xpup 1.1s cubic-bezier(.2,.7,.3,1) forwards}
@keyframes xpup{
  0%{opacity:0;transform:translateY(4px) scale(.9)}
  18%{opacity:1;transform:translateY(-6px) scale(1)}
  100%{opacity:0;transform:translateY(-42px) scale(1)}}

/* The run. "3 in a row!" bursts off the answer that extended it: a gold
   badge with a shine sweeping across and stars twinkling at its ends,
   popping in with a wiggle before it floats away. Called too lame in its
   plain-text form by the person the celebrating is for, and he was right:
   a run is the one thing worth real noise. */
.runfloat{position:fixed;z-index:110;pointer-events:none;transform:translateX(-50%);
  font-family:var(--mono);font-weight:800;letter-spacing:.06em;text-transform:uppercase;
  font-size:clamp(13px,1.9vmin,26px);color:#221a04;white-space:nowrap;
  background:linear-gradient(100deg,#ffe9a8 0%,#ffd166 38%,#fff3c9 50%,#ffd166 62%,#eab547 100%);
  background-size:220% 100%;
  border:1px solid #fff3c9;border-radius:999px;padding:.38em 1.05em;
  box-shadow:0 6px 24px rgba(232,176,75,.5),0 0 0 3px rgba(255,209,102,.25);
  animation:runup 1.6s cubic-bezier(.2,.7,.3,1) forwards,
            runshine 1.1s linear 1}
.runfloat::before,.runfloat::after{content:"\2726";color:#fff;font-size:.85em;
  display:inline-block;margin:0 .45em;
  animation:runtwinkle .55s ease infinite alternate;
  text-shadow:0 0 8px rgba(255,255,255,.9)}
.runfloat::after{animation-delay:.28s}
.runfloat.big{font-size:clamp(17px,2.7vmin,36px);
  box-shadow:0 10px 36px rgba(232,176,75,.65),0 0 0 4px rgba(255,209,102,.35),
             0 0 48px rgba(255,209,102,.4)}
@keyframes runup{
  0%{opacity:0;transform:translateX(-50%) translateY(10px) scale(.45) rotate(-4deg)}
  13%{opacity:1;transform:translateX(-50%) translateY(-12px) scale(1.22) rotate(2.5deg)}
  24%{transform:translateX(-50%) translateY(-16px) scale(.98) rotate(-1deg)}
  32%{transform:translateX(-50%) translateY(-17px) scale(1)}
  78%{opacity:1}
  100%{opacity:0;transform:translateX(-50%) translateY(-68px) scale(.95)}}
@keyframes runshine{from{background-position:120% 0}to{background-position:-60% 0}}
@keyframes runtwinkle{from{transform:scale(.55) rotate(0deg);opacity:.55}
  to{transform:scale(1.2) rotate(45deg);opacity:1}}

/* All green: the rows land one by one under their rising ticks. */
.tcase.reveal{animation:tcin .3s cubic-bezier(.2,.9,.3,1) backwards}
@keyframes tcin{from{opacity:0;transform:translateY(5px) scale(.98)}to{opacity:1;transform:none}}

/* Ranking up. Noticeable, then gone. It says what changed and why, because
   a celebration that does not tell you what you did is just noise. */
/* Celebration sizes ride vmin with a floor at the old phone values, so on
   a laptop they grow a little and on a TV across the room they land like
   they mean it. Asked for from a couch, watching a level-up card the size
   of a stamp. */
.levelup{position:fixed;left:50%;top:26%;transform:translateX(-50%);z-index:120;
  background:#0d1620;border:1px solid var(--accent-dk);border-radius:14px;
  padding:clamp(20px,3vmin,44px) clamp(26px,4vmin,64px);text-align:center;
  min-width:min(max(300px,34vmin),88vw);
  box-shadow:0 20px 60px rgba(0,0,0,.6),0 0 0 1px rgba(110,231,168,.1);
  animation:lvlin .42s cubic-bezier(.2,1.3,.4,1)}
@keyframes lvlin{
  from{opacity:0;transform:translateX(-50%) translateY(10px) scale(.96)}
  to{opacity:1;transform:translateX(-50%) translateY(0) scale(1)}}
.levelup .lvn{font-family:var(--mono);font-size:clamp(34px,7vmin,96px);color:var(--accent);
  line-height:1;margin-bottom:6px}
.levelup .lvt{font-size:clamp(15px,2.4vmin,32px);color:var(--ink);margin-bottom:4px}
.levelup .lvb{font-size:clamp(12.5px,1.9vmin,25px);color:var(--faint)}
/* A drop is information, not an event. Same card, none of the glow. */
.levelup.down{border-color:var(--line);box-shadow:0 20px 60px rgba(0,0,0,.6)}

/* Correct answers settle in rather than snapping. Under 220ms, so it reads
   as responsiveness and never as waiting. */
.choice.right{animation:settle .2s cubic-bezier(.2,1.4,.4,1)}
@keyframes settle{from{transform:scale(.985)}to{transform:scale(1)}}
@media(prefers-reduced-motion:reduce){
  .xpfloat,.levelup,.choice.right,.rank b,.rbar u,.runfloat,.tcase.reveal{animation:none;transition:none}
  .grow-bar>i,.tab{transition:none}
}
/* Append to style.css. Landing demo block. */
.demo{text-align:left;margin:0 0 26px;animation:stepIn .26s cubic-bezier(.22,.9,.3,1)}
.demo-kicker{font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--faint);margin-bottom:10px}
.demo-q{font-size:19px;font-weight:600;line-height:1.4;letter-spacing:-.01em;margin:0 0 14px}
.demo-line{font-size:14px;color:var(--dim);margin:14px 0 0}
.gate .tag{margin-bottom:22px}
.foot a{color:var(--faint);text-decoration:none}.foot a:hover{color:var(--dim)}
/* The day view's own footer links, to the homepage, about and the legal
   pages. Muted like the rest of the footer; they are a way back, not a
   call to action. */
.footnav{display:block;margin-top:12px;font-size:12px}
.footnav a{color:var(--faint);text-decoration:none}
.footnav a:hover{color:var(--dim)}

/* Google sign-in slot. Centered, one line, never wider than the primary button. */
.gsi{display:flex;justify-content:center;margin:12px 0 2px;min-height:0;
  /* The page is color-scheme: dark; Google's iframe is not. A browser paints an
     opaque white canvas behind a cross-scheme iframe. Match the iframe to
     itself and the canvas is transparent again. */
  color-scheme:light}
.gsi iframe{color-scheme:light;background:transparent}
.gsi:empty{margin:0}
.pane .gsi{justify-content:flex-start;margin:10px 0}
.plinked{font-size:12.5px;color:var(--faint);margin:8px 0 0}

/* Code keys: phones only, one row, scrolls sideways. */
.codekeys{display:none;gap:6px;overflow-x:auto;padding:8px 0 2px;margin-top:-4px;
  scrollbar-width:none}
.codekeys::-webkit-scrollbar{display:none}
.codekeys button{flex:0 0 auto;min-width:40px;height:36px;border-radius:8px;
  background:#0d1319;border:1px solid var(--line2);color:var(--ink);
  font-family:var(--mono);font-size:15px;cursor:pointer}
.codekeys button:active{background:#182029;border-color:var(--accent-dk)}
@media (pointer:coarse) and (max-width:820px){ .codekeys{display:flex} }
/* Session mode */
button.go.small{padding:9px 14px;font-size:14px;width:auto}
.session{display:flex;align-items:center;justify-content:space-between;gap:12px;
  margin-top:12px;padding:10px 12px;border:1px solid var(--accent-dk);border-radius:10px;
  font-family:var(--mono);font-size:12.5px;color:var(--accent)}
.session .link{margin:0;font-family:var(--sans)}
/* Ledger names wrap instead of truncating; the bar yields. */
.tname{white-space:normal;overflow:visible;text-overflow:clip;line-height:1.3}
.trow .bar{min-width:60px}
/* Contrast: body-level faint text was ~3.6:1. */
/* --faint lives with the other colours at the top of this file. It was
   declared twice, and the second one silently won, so anyone reading the
   first would have been reading a value the browser never used. */
.keyhint{font-family:var(--mono);font-size:12px;letter-spacing:.06em}
/* A CHECKBOX ROW IS A TAP TARGET. This label was 19px tall -- the text's
   own height with a 13px box beside it -- in a pane a phone opens with a
   thumb. The floor applies to it like any other control. */
.keeprow{display:flex;gap:8px;flex-wrap:wrap;margin:6px 0 4px}
.keeprow .ghost.small{flex:1 1 auto}
.ptoggle{display:flex;gap:10px;align-items:center;cursor:pointer;min-height:44px}
.ptoggle input[type=checkbox]{width:20px;height:20px;margin:0;accent-color:var(--accent);flex:0 0 auto}

/* Account button: the name you are signed in as, and the gear that opens
   settings. One control, so identity and control live in the same place. */
.avatar{display:inline-flex;align-items:center;gap:7px;background:#0d1319;
  border:1px solid var(--line2);border-radius:999px;padding:5px 9px 5px 12px;
  color:var(--ink);font:inherit;font-size:13px;cursor:pointer;max-width:170px}
.avatar #whoName{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.avatar .gear{color:var(--dim);font-size:15px;line-height:1}
.avatar:hover{border-color:var(--dim)}
.acct{margin-top:14px;border-top:1px solid var(--line);padding-top:12px}
/* WHAT HAPPENED WHILE YOU WERE AWAY. Above the friends list, because the
   whole point is that it is the first thing read, and gone once it is. */
.frnew{border:1px solid var(--accent-dk);border-radius:10px;padding:8px 10px;margin:0 0 10px}
.frnew .nrow{font-size:13px;color:var(--ink);padding:3px 0}
.frnew .nrow i{color:var(--dim);font-style:normal;font-size:12px;margin-left:6px}
.acct .label{margin-bottom:8px}
.ptoggle i{display:block;font-style:normal;font-size:11.5px;color:var(--faint);
  margin-top:2px;padding-left:24px}
.acct .field{width:100%;background:#0a0e12;color:var(--ink);border:1px solid var(--line);
  border-radius:8px;padding:9px 11px;font:inherit;font-size:14px;margin:4px 0 10px}
.acct select.field{appearance:none}
.acct .who{font-size:13px;color:var(--dim);margin:0 0 6px}
.acct .who b{color:var(--ink);font-weight:600}
.pacts{flex-wrap:wrap}
button.ghost.danger{border-color:#5a2a2a;color:var(--bad)}

/* How it works: quiet, numbered because it is a sequence. */
.how{text-align:left;margin:0 0 22px;padding:0 0 0 20px;font-size:14px;color:var(--dim);line-height:1.55}
.how li{margin:0 0 7px}
.how b{color:var(--ink);font-weight:600}
.how i{color:var(--accent)}
/* Explore chips */
.chips{display:block}
.chip{background:#0a0e12;border:1px solid var(--line);color:var(--ink);border-radius:999px;
  padding:7px 12px;font-family:var(--mono);font-size:12.5px;cursor:pointer;display:inline-flex;gap:7px;align-items:center}
.chip:hover{border-color:var(--line2)}
.chip .cd{color:var(--faint);font-size:11px}
.chip.done{border-color:var(--accent-dk);color:var(--accent)}
.chip .cc{color:var(--warn);font-size:11px}

.xlevel{font-family:var(--mono);font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--faint);margin:12px 0 8px}
.xlevel:first-child{margin-top:0}
.xrow{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:6px}
.chip.miss{border-color:#4a3636}
#chips>button.ghost{margin-top:12px}
/* Desktop: a 360px column in a wide window read as unfinished. */
@media(min-width:700px){ .gate{max-width:440px} }

/* Notes: one line, bottom-centre above the tab bar, gone in under three
   seconds — and every one carries its own dismiss, because a message that
   can only be waited out is a message that gets in the way. */
.note{position:fixed;left:50%;bottom:calc(84px + env(safe-area-inset-bottom) + var(--stack,0)*46px);transform:translateX(-50%) translateY(8px);
  background:#11161c;border:1px solid var(--line2);color:var(--ink);border-radius:999px;
  padding:6px 6px 6px 16px;font-size:13.5px;z-index:120;opacity:0;
  transition:opacity .22s,transform .22s,bottom .22s;
  max-width:88vw;display:flex;align-items:center;gap:8px}
.note .nt{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
/* The dismiss. Quiet enough to ignore, big enough to hit with a thumb:
   the 30px circle is the visible part, and it is the whole tap target. */
.note .nx{flex:0 0 auto;width:30px;height:30px;border-radius:50%;border:0;
  background:transparent;color:var(--faint);font:inherit;font-size:19px;line-height:1;
  cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center;
  transition:background .14s,color .14s}
/* No outline:none here. The global :focus-visible ring is a real outline
   on purpose: an outline survives forced-colors and high-contrast mode,
   and a box-shadow substitute quietly does not. The accessibility law
   caught exactly that swap. */
.note .nx:hover,.note .nx:focus-visible{background:#1b232c;color:var(--ink)}
.note.in{opacity:1;transform:translateX(-50%) translateY(0)}
.note.good{border-color:var(--accent-dk);color:var(--accent)}
.note.rank{border-color:var(--accent)}
.note.streak{border-color:var(--warn);color:var(--warn)}
/* Clear all: only once there are two, sitting just above the stack. */
.noteclear{position:fixed;left:50%;bottom:calc(84px + env(safe-area-inset-bottom) + var(--stack,0)*46px);
  transform:translateX(-50%) translateY(8px);
  background:#0a0e12;border:1px solid var(--line);color:var(--dim);
  border-radius:999px;padding:6px 14px;font:inherit;font-size:12px;cursor:pointer;
  z-index:121;opacity:0;transition:opacity .22s,transform .22s,bottom .22s;min-height:32px}
.noteclear.in{opacity:1;transform:translateX(-50%) translateY(0)}
.noteclear:hover,.noteclear:focus-visible{border-color:var(--dim);color:var(--ink)}
@media(min-width:521px){
  .note{bottom:calc(28px + var(--stack,0)*46px)}
  .noteclear{bottom:calc(28px + var(--stack,0)*46px)}
}
/* A TOAST MUST NEVER SWALLOW A TAP MEANT FOR WHAT IS UNDER IT. The page
   scrolls beneath a fixed message, so sooner or later one lands over a
   button — over the code keys above a phone keyboard, say, which is the
   exact moment somebody is reaching for them. On a touch screen the body
   of the message is therefore transparent to touches and only the dismiss
   takes them; the tap goes through to the button underneath.

   On a pointer device it stays live, because hovering to hold it open
   while you read is worth more there and a mouse can simply go around. */
@media (hover: none){
  .note{pointer-events:none}
  .note .nx{pointer-events:auto}
}
@media (prefers-reduced-motion: reduce){ .note,.noteclear{transition:none} }

/* ── Pebble (floating companion) ─────────────────────────────────────── */
.pebble-float{position:fixed;z-index:90;display:flex;flex-direction:row-reverse;align-items:flex-end;gap:10px;
  right:12px;left:auto;bottom:calc(68px + env(safe-area-inset-bottom));
  transition:transform .35s cubic-bezier(.22,.9,.3,1),opacity .25s;will-change:transform}
.pebble-float.pb-hidden{opacity:0;pointer-events:none;transform:translateY(12px)}
/* A SOFT GROUND UNDER HER. She is fixed, so on a narrow screen she floats
   over whatever happens to be scrolled beneath her, and a lioness sitting
   directly on a sentence looks like a collision rather than a companion.
   This is a static radial on the button itself, not a filter on the parts
   that breathe and blink, so it costs nothing per frame: it separates her
   from the text without dimming or moving anything. */
.pb-fig{width:48px;height:48px;padding:0;border:0;cursor:pointer;flex:0 0 auto;
  transform-origin:50% 90%;border-radius:50%;
  background:radial-gradient(closest-side,rgba(8,10,13,.92) 46%,rgba(8,10,13,.6) 72%,transparent 100%)}
/* No CSS filter on Pebble: a drop-shadow on an element that breathes and
   blinks forced a full repaint every frame and made phones drag. The
   shadow is a static ellipse in the SVG instead. */
.pb-fig svg{display:block;width:48px;height:48px}
.pb-fig:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:50%}
.pb-say{display:none;max-width:min(72vw,320px);font-size:14px;color:var(--ink);line-height:1.45;
  background:#11161c;border:1px solid var(--line2);border-radius:14px;padding:9px 12px;position:relative;
  margin-bottom:6px;cursor:pointer;box-shadow:0 6px 20px rgba(0,0,0,.4)}
.pb-say::before{content:"";position:absolute;right:-6px;bottom:14px;width:10px;height:10px;background:#11161c;
  border-right:1px solid var(--line2);border-top:1px solid var(--line2);transform:rotate(45deg)}
.pb-talk .pb-say{display:block;animation:pbIn .22s ease}
@keyframes pbIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
/* The pane is a bottom sheet of at most 78vh; Pebble sits just above its edge. */
.pb-at-pane{bottom:calc(78vh + 8px);bottom:calc(78dvh + 8px)}
@media(min-width:521px){ .pebble-float{bottom:20px;right:20px} .pb-at-pane{bottom:20px} }
/* Content never runs under Pebble: the lane is reserved at the bottom of every view. */
.wrap{padding-bottom:calc(150px + env(safe-area-inset-bottom))}
.gate{padding-bottom:calc(120px + env(safe-area-inset-bottom))}
@media(max-width:520px){ .wrap{padding-bottom:calc(160px + env(safe-area-inset-bottom))} }
/* moods */
.pb-idle .pb-body{animation:pbBreathe 3.4s ease-in-out infinite;transform-origin:50% 90%}
.pb-idle .pb-eye{animation:pbBlink 5s infinite}
@keyframes pbBreathe{0%,100%{transform:scaleY(1)}50%{transform:scaleY(.97)}}
@keyframes pbBlink{0%,93%,100%{transform:scaleY(1)}96%{transform:scaleY(.1)}}
.pb-eye{transform-origin:center;transform-box:fill-box}
.pb-happy .pb-body{animation:pbBob .5s ease}
@keyframes pbBob{0%{transform:translateY(0)}35%{transform:translateY(-6px) scaleX(1.04)}70%{transform:translateY(0) scaleY(.96)}100%{transform:none}}
.pb-cheer .pb-body{animation:pbJump .6s cubic-bezier(.22,.9,.3,1)}
.pb-cheer .pb-sparks{animation:pbSpark .7s ease forwards}
@keyframes pbJump{0%{transform:translateY(0)}40%{transform:translateY(-12px) rotate(-4deg)}70%{transform:translateY(0) scaleY(.92)}100%{transform:none}}
@keyframes pbSpark{0%{opacity:0;transform:scale(.6)}40%{opacity:1}100%{opacity:0;transform:scale(1.3) translateY(-6px)}}
.pb-kind .pb-body{animation:pbNod .5s ease}
.pb-kind .pb-brow-l{transform:translateY(-2px) rotate(8deg)} .pb-kind .pb-brow-r{transform:translateY(-2px) rotate(-8deg)}
@keyframes pbNod{0%{transform:rotate(0)}30%{transform:rotate(-5deg)}70%{transform:rotate(3deg)}100%{transform:none}}
.pb-think .pb-body{transform:rotate(-4deg)}
.pb-think .pb-brow-l{transform:rotate(12deg) translateY(1px)} .pb-think .pb-brow-r{transform:rotate(-12deg) translateY(1px)}
.pb-wave .pb-body{animation:pbWave .9s ease}
@keyframes pbWave{0%,100%{transform:rotate(0)}25%{transform:rotate(-8deg)}50%{transform:rotate(6deg)}75%{transform:rotate(-4deg)}}
.pb-brow,.pb-mouth{transition:transform .2s,d .2s;transform-box:fill-box;transform-origin:center}
@media (prefers-reduced-motion: reduce){ .pb-fig *{animation:none!important} .pebble-float{transition:none} }
/* streak in the header */
.spark{font-family:var(--mono);font-size:12.5px;color:var(--warn);display:inline-flex;align-items:center;gap:3px}
.spark.zero{color:var(--faint)}
/* session summary card */
.sumcard{margin-top:12px;padding:14px;border:1px solid var(--accent-dk);border-radius:12px;background:rgba(110,231,168,.05)}
.sumcard b{font-family:var(--mono);color:var(--accent)}

/* Walking, kicking, toppling. Transforms only. */
.pebble-float.pb-offset{transform:translate(var(--wx,0px),var(--wy,0px))}
.pebble-float.pb-hidden.pb-offset{transform:translate(var(--wx,0px),calc(var(--wy,0px) + 12px))}
.pb-face-left .pb-fig{transform:scaleX(-1)}
.pb-walking .pb-body{animation:pbStep .32s ease-in-out infinite;transform-origin:50% 90%}
.pb-walking .pb-feet{animation:pbFeet .32s ease-in-out infinite;transform-origin:50% 100%}
@keyframes pbStep{0%,100%{transform:translateY(0) rotate(-3deg)}50%{transform:translateY(-3px) rotate(3deg)}}
@keyframes pbFeet{0%,100%{transform:scaleX(1)}50%{transform:scaleX(.85)}}
.pb-kick .pb-body{animation:pbKick .32s ease-out}
.pb-kick .pb-foot-r{animation:pbKickFoot .32s ease-out;transform-origin:40% 50%}
@keyframes pbKick{0%{transform:rotate(0)}40%{transform:rotate(-14deg) translateX(-4px)}100%{transform:rotate(6deg) translateX(6px)}}
@keyframes pbKickFoot{0%{transform:translate(0,0)}60%{transform:translate(16px,-10px) rotate(-30deg)}100%{transform:translate(0,0)}}
.pb-shove .pb-body{animation:pbShove .5s ease}
@keyframes pbShove{0%{transform:translateX(0)}40%{transform:translateX(10px) rotate(4deg)}100%{transform:none}}
.pb-toppled{transform:rotate(-6deg) translateY(14px) translateX(6px)!important;transition:transform .5s cubic-bezier(.34,1.56,.64,1);
  opacity:.85}
.pb-restored{transform:none!important;transition:transform .45s cubic-bezier(.34,1.56,.64,1)}
.tcase{position:relative;transform-origin:left bottom}
@media (prefers-reduced-motion: reduce){ .pebble-float.pb-offset{transform:none} }

.tcase.flipped .dot.p{animation:flipDot .6s ease}
@keyframes flipDot{0%{transform:scale(1)}40%{transform:scale(1.9);box-shadow:0 0 0 6px rgba(110,231,168,.25)}100%{transform:scale(1)}}
.xpfloat{will-change:transform}

input[type=checkbox]{accent-color:var(--accent)}

.rlabel{font-family:var(--sans);font-size:11.5px;color:var(--faint);margin-right:4px;letter-spacing:0;text-transform:none}
@media(max-width:360px){ .rlabel{display:none} }

.legend{font-family:var(--mono);font-size:12px;line-height:1.6;color:var(--dim);background:#0a0e12;
  border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin:0 0 12px;white-space:pre-wrap}

/* Visualisations under the results */
.viz{margin-top:12px;padding-top:12px;border-top:1px solid var(--line)}
.vizh{font-size:12.5px;color:var(--dim);margin-bottom:8px}
.vizn{font-size:12px;color:var(--faint);margin-top:8px}
.tgrid{display:grid;gap:4px;overflow-x:auto;padding-bottom:4px}
.trowh,.trowd{display:grid;grid-template-columns:96px repeat(var(--n),22px);gap:3px;align-items:center}
.tlab{font-family:var(--mono);font-size:11.5px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tc{height:16px;border-radius:3px;background:#161d25;font-family:var(--mono);font-size:10px;color:var(--faint);text-align:center;line-height:16px;
  transform:scaleX(0);transform-origin:left;transition:transform .35s ease,background .2s}
.tgrid.in .tc{transform:scaleX(1)}
.trowh .tc{background:none}
.tc.on{background:var(--accent)}
.tc.off{background:#1a222b}
.trowd.exp .tlab{color:var(--warn)}
.tc.bad{box-shadow:inset 0 0 0 2px var(--bad)}
.tgrid .trowd:nth-child(2) .tc{transition-delay:.05s}.tgrid .trowd:nth-child(3) .tc{transition-delay:.1s}.tgrid .trowd:nth-child(4) .tc{transition-delay:.15s}.tgrid .trowd:nth-child(5) .tc{transition-delay:.2s}
.sqlt table,.difft{border-collapse:collapse;font-family:var(--mono);font-size:12px;margin-bottom:10px;max-width:100%;display:block;overflow-x:auto}
.sqlt td,.difft td,.difft th{border:1px solid var(--line);padding:5px 9px;color:var(--ink);white-space:nowrap}
.difft th{color:var(--faint);font-weight:500;text-align:left}
.sqlt tr.bad td,.difft tr.bad td{background:rgba(240,139,139,.08);border-color:#4a3636}
@media (prefers-reduced-motion: reduce){ .tc{transition:none;transform:none} }

/* ── Landing ───────────────────────────────────────────────────────────── */
/* MEASURE, THEN SCALE, THEN RHYTHM. This page is 760px wide on a desktop,
   which is right for the card grids and far too wide for a sentence: body
   copy was running to 89 characters and the hero deck to 95, when a line
   is comfortable to read at 45 to 75. Long lines are the single reason a
   page reads as a grey slab instead of a document somebody laid out, so
   every run of prose here is capped in `ch` (which tracks the font, not
   the viewport) and centred inside the wider column. The grids keep the
   full 760px; only the reading does not. A ch is the width of a ZERO,
   which is wider than average prose, so the caps read low: 58ch of body
   copy lands at about 64 characters in a normal interface font and about
   74 in a wide one. tests/test_first_impression.py counts the characters
   that actually reach the line rather than trusting the unit.

   The scale was flat too: a 22px headline (see the specificity note below),
   a 15px deck and a 15px card title, so nothing announced itself as more
   important than anything else. It now steps 44 / 17.5 / 15.5 / 13.5 with
   an 11px mono kicker, which is a scale a reader can feel without being
   able to name.

   text-wrap:balance evens the lines of short centred things (headlines,
   kickers, card titles); text-wrap:pretty keeps a paragraph from ending
   on a single orphaned word. Both are ignored by browsers that lack them,
   so nothing depends on either. */
/* The two rules below widen and un-centre the gate FOR THE LANDING, which
   also lives in it. They were landing on every other step too, so the
   sign-in and onboarding screens had a 712px-wide username field pinned to
   the top of a mostly empty desktop page: a one-question form rendered at
   the width of a document. The landing keeps the wide column; every other
   step gets a form's column back, and the vertical centring the base .gate
   rule always gave it. */
.gate{display:block;min-height:0;max-width:560px}
.step.landing{display:block;text-align:left}
.gate .step:not(.landing){max-width:400px;margin:0 auto;
  min-height:64dvh;display:flex;flex-direction:column;justify-content:center}
.lhero{display:block}
.lhero{text-align:center;padding:22px 0 10px}
/* .gate h1 (0,1,1) was quietly beating .lh1 (0,1,0), so the homepage
   headline had been rendering at the sign-in step's 22px and weight 600
   for as long as the .lh1 rule has existed: smaller than the sentence
   under it and the same weight as a card title. Every "no hierarchy"
   symptom on this page came from that one line of specificity. Scoped to
   .gate .lh1 (0,2,0) so the rule it was always meant to be actually wins. */
.gate .lh1{font-size:clamp(30px,4.4vw,44px);line-height:1.08;letter-spacing:-.028em;
  font-weight:700;margin:16px auto 28px;max-width:20ch;text-wrap:balance}
.lhero .tag{font-size:clamp(15.5px,1.5vw,17.5px);line-height:1.5;color:var(--ink);
  max-width:43ch;margin:0 auto 10px;text-wrap:pretty}
.lhero .tagsub{font-size:14.5px;line-height:1.6;color:var(--dim);
  max-width:50ch;margin:0 auto 26px;text-wrap:pretty}
.lcta{display:flex;flex-direction:column;align-items:center;gap:4px}
.lcta .go.big{max-width:360px}
/* "Signed in as …" above the resume button on the homepage, for a visitor
   who is already logged in. Quiet: it confirms state, it does not shout. */
.wback{margin:0 0 6px;font-size:13px;color:var(--dim)}
.wback b{color:var(--ink);font-weight:600}
/* A quiet, warm doorway to the kids playground, under the hero CTA. */
.kidline{margin:18px 0 0;font-size:13.5px}
.kidline a{color:var(--dim);text-decoration:none;border-bottom:1px solid var(--line2);padding-bottom:1px}
.kidline a:hover{color:var(--accent);border-color:var(--accent)}
/* Every one of these is a <section>, which the base rule at the top of this
   file already draws as a card: panel gradient, 1px --line2 border, and an
   inset lit top edge. The old `border-top:1px solid var(--line)` overrode
   only the top edge with a darker colour, so each card had three bright
   sides and one dead one and the stack read as ragged. Removed, so the
   cards are uniform and the rhythm between them is a single gutter. */
.lsec{margin:16px 0 0;padding:20px}
.lsec .label{margin-bottom:14px;text-wrap:balance}
.lp{font-size:15px;color:var(--dim);line-height:1.65;margin:0 0 13px;
  max-width:58ch;text-wrap:pretty}
/* ch resolves against the element's OWN font-size, so a cap on the <ul>
   was being measured in the list's inherited 16px while the rows render at
   14.5px, and every row came out wider than intended. Setting the size on
   the list and inheriting it down makes the cap mean what it says. */
.plinked,.ldiff li,.how li{text-wrap:pretty}
.plinked{max-width:60ch}
.ldiff{font-size:14.5px;max-width:60ch}
.how{max-width:60ch}
@media(min-width:700px){
  .lhero{padding:34px 0 12px}
  .lsec{margin-top:20px;padding:26px 28px}
}
.lp b{color:var(--ink);font-weight:600}
.tracks{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px}
.track{background:#0a0e12;border:1px solid var(--line2);color:var(--dim);border-radius:999px;padding:8px 13px;font:inherit;font-size:13.5px;cursor:pointer}
.track.on{border-color:var(--accent);color:var(--accent)}
.tgrid2{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.tcard{text-align:left;background:#0a0e12;border:1px solid var(--line);border-radius:12px;padding:12px;cursor:pointer;color:var(--ink);font:inherit;
  transition:border-color .16s ease,transform .16s ease,background .16s ease}
@media(hover:hover){ .tcard:hover{border-color:var(--line2);background:#0b1017;transform:translateY(-2px)} }
.tcard b{display:block;font-size:15px;margin-bottom:4px;text-wrap:balance}
.tcard span{font-size:13px;color:var(--dim);line-height:1.5;text-wrap:pretty}
.ldiff{margin:0;padding:0;list-style:none}
.ldiff li{font-size:14.5px;color:var(--dim);line-height:1.55;padding:8px 0;border-bottom:1px solid var(--line)}
.ldiff li:last-child{border-bottom:0}
.ldiff b{color:var(--ink);font-weight:600}
.ldiff i{color:var(--accent)}
#signin .gsi{margin:8px 0 10px}
.landing .demo{margin:0 0 6px}
.demo-kicker{display:none}
/* The headline sizes itself with clamp(); the old 28px override here never
   won and is gone rather than left lying about what happens. */
@media(max-width:400px){ .tgrid2{grid-template-columns:1fr} }

/* ── Landing motion ──────────────────────────────────────────────────────
   The one deliberate exception to the "motion under 220ms" rule at the top
   of this file. That rule governs the practice app, where motion must never
   slow a task down. The homepage has no task to slow: it is the pitch, read
   top to bottom, so its sections rise into view at reading pace as you
   scroll. This is communicative, not costume — it gives a long page rhythm
   and tells you there is more below.

   OFF BY DEFAULT. The page renders fully visible; app.js adds `anim` to
   <html> only when JavaScript is running AND the visitor has not asked for
   reduced motion. So a reduced-motion visitor, or anyone whose script
   failed, gets the whole page at once and never a blank one. Everything
   below is scoped to html.anim #s1 for that reason.
   ──────────────────────────────────────────────────────────────────────── */
html.anim #s1 .reveal{opacity:0;transform:translateY(18px);
  transition:opacity .6s cubic-bezier(.22,.7,.2,1) var(--rd,0ms),
             transform .6s cubic-bezier(.22,.7,.2,1) var(--rd,0ms)}
html.anim #s1 .reveal.in{opacity:1;transform:none}

/* Hero entrance: the mark, the promise, the sentence, the buttons, in that
   order, so the eye is led down to the one thing to do. */
html.anim .lhero .brand{--rd:40ms}
html.anim .lhero .lh1{--rd:130ms}
html.anim .lhero .tag{--rd:240ms}
html.anim .lhero .tagsub{--rd:300ms}
html.anim .lhero .lcta{--rd:380ms}
html.anim .lhero .kidline{--rd:460ms}

/* The mark under the headline draws itself once, left to right, finishing as
   the promise settles. Accent is only ever the mark or the truth on this
   site; a line that completes under the headline is that, not decoration. */
html.anim .lh1{position:relative}
html.anim .lh1::after{content:"";position:absolute;left:50%;margin-left:-95px;
  bottom:-14px;height:2px;width:190px;border-radius:2px;
  background:linear-gradient(90deg,transparent,var(--accent),transparent);
  transform:scaleX(0);transform-origin:center;
  transition:transform .75s cubic-bezier(.2,.7,.2,1) .45s}
html.anim .lh1.in::after{transform:scaleX(1)}

/* Cards and list rows fade in one after another as their section rises.
   Opacity only, so no transform ever nests inside the section's own rise. */
html.anim #s1 .reveal .tcard,
html.anim #s1 .reveal .ldiff li{opacity:0}
html.anim #s1 .reveal.in .tcard,
html.anim #s1 .reveal.in .ldiff li{animation:lfade .5s ease both}
@keyframes lfade{to{opacity:1}}
html.anim #s1 .reveal.in .tcard:nth-child(1),
html.anim #s1 .reveal.in .ldiff li:nth-child(1){animation-delay:.12s}
html.anim #s1 .reveal.in .tcard:nth-child(2),
html.anim #s1 .reveal.in .ldiff li:nth-child(2){animation-delay:.18s}
html.anim #s1 .reveal.in .tcard:nth-child(3),
html.anim #s1 .reveal.in .ldiff li:nth-child(3){animation-delay:.24s}
html.anim #s1 .reveal.in .tcard:nth-child(4),
html.anim #s1 .reveal.in .ldiff li:nth-child(4){animation-delay:.30s}
html.anim #s1 .reveal.in .tcard:nth-child(5),
html.anim #s1 .reveal.in .ldiff li:nth-child(5){animation-delay:.36s}
html.anim #s1 .reveal.in .tcard:nth-child(6),
html.anim #s1 .reveal.in .ldiff li:nth-child(6){animation-delay:.42s}

/* If the setting flips to reduced mid-session, force everything visible and
   still, whatever state the reveal was left in. */
@media(prefers-reduced-motion:reduce){
  html.anim #s1 .reveal{opacity:1!important;transform:none!important;transition:none!important}
  html.anim #s1 .reveal .tcard,
  html.anim #s1 .reveal .ldiff li{opacity:1!important;animation:none!important}
  html.anim .lh1::after{transition:none!important;transform:scaleX(1)!important}
}

/* Phones: a blur behind the tab bar re-rendered on every scroll. Opaque there. */
@media(max-width:520px){ #tabbar{-webkit-backdrop-filter:none;backdrop-filter:none;background:#0b0f14} }
/* Only breathe while on screen and the tab is visible. */
.pb-idle .pb-body,.pb-idle .pb-eye{will-change:transform}
html.hidden-tab .pb-fig *{animation-play-state:paused}

.coachtext p{margin:0 0 8px;line-height:1.5}
.coachtext p:last-child{margin-bottom:0}
.coachtext code{font-family:var(--mono);font-size:12.5px;background:#0a0e12;border:1px solid var(--line);border-radius:5px;padding:1px 5px;color:#f0a1a1}
.coachtext b{color:var(--ink)}

.applies{margin:0 0 12px;padding:10px 12px;border-left:3px solid var(--accent);background:#0a0e12;border-radius:0 10px 10px 0;font-size:13.5px;line-height:1.5;color:var(--dim)}
.applies b{display:block;color:var(--accent);font-size:11px;letter-spacing:.08em;text-transform:uppercase;margin-bottom:4px}
.prow.sig{flex-wrap:wrap} .prow.sig i{flex-basis:100%;font-style:normal;font-size:12px;color:var(--faint);margin-top:2px}

/* ── Desktop: two columns. The question and the editor get the room; Explore
   and the ledger sit beside them instead of a mile below.
   The sidebar used to be sticky with its own capped, scrollbarless inner
   scroller, which split the page into two scroll worlds: the page itself had
   almost nothing below the fold, the wheel went dead after one flick, and
   the rest of Explore sat cut off with no hint it could move. Reported by
   the first person to try it on a desktop, in exactly those words. One page,
   one scroll. ── */
/* ONE VIEW AT A TIME, EVERY SIZE. The old two-column desktop grid put four
   cards on screen at once; now the tabs pick one section and the rest are
   hidden, in a single centered column. The wrappers become display:contents
   so their sections flow directly and can be toggled one at a time. */
.cols{display:block}
.colmain,.colside{display:contents}
@media(min-width:521px){
  .cols{max-width:720px;margin:0 auto}
  .cm-editor{min-height:320px}
}
@media(min-width:1000px){ .wrap{max-width:1180px} }
@media(min-width:1400px){ .wrap{max-width:1320px} }
.cmeta{display:flex;gap:8px;flex-wrap:wrap;margin:-4px 0 10px}
.cmeta span{font-size:11.5px;color:var(--dim);background:#0a0e12;border:1px solid var(--line);border-radius:999px;padding:3px 9px}
.cmeta span.iv{color:var(--warn);border-color:#4a3a16}
.cmeta span.rep{color:var(--accent);border-color:#1f4a34}
.example{font-family:var(--mono);font-size:12.5px;color:var(--dim);background:#0a0e12;border:1px solid var(--line);border-radius:8px;padding:8px 11px;margin:-4px 0 12px;white-space:pre-wrap}
.example b{color:var(--faint);font-weight:500;font-family:var(--sans);font-size:11px;letter-spacing:.06em;text-transform:uppercase;display:block;margin-bottom:3px}

/* Pebble 3: eyes, tricks, sleep, carry, coach delivery */
.pb-pupils{transition:transform .12s ease}
.pb-left{left:12px;right:auto;flex-direction:row}
.pb-left .pb-say::before{left:-6px;right:auto;transform:rotate(-135deg)}
@media(min-width:521px){ .pb-left{left:20px} }
.pb-carried{transition:none!important;z-index:95}
.pb-carried .pb-fig{transform:scale(1.08,.92) rotate(-6deg);cursor:grabbing}
/* Carried, his feet paddle at the air. */
.pb-carried .pb-feet{animation:pbDangle .4s ease-in-out infinite;transform-origin:50% 0}
@keyframes pbDangle{0%,100%{transform:rotate(5deg)}50%{transform:rotate(-5deg)}}

/* ── reactions: the answer flow moves him, on the ROOT so a mood change a
   beat later cannot wipe the class mid-air ── */
#pebble.pb-r-jump .pb-fig{animation:pbrJump .55s cubic-bezier(.22,.9,.3,1)}
@keyframes pbrJump{0%{transform:none}30%{transform:translateY(-13px)}
  55%{transform:translateY(0) scale(1.06,.94)}75%{transform:scale(.98,1.02)}100%{transform:none}}
#pebble.pb-r-droop .pb-fig{animation:pbrDroop .8s ease}
@keyframes pbrDroop{0%{transform:none}30%{transform:translateY(3px) scale(.985,.97)}
  70%{transform:translateY(3px) scale(.985,.97)}100%{transform:none}}
#pebble.pb-r-hype .pb-fig{animation:pbrHype .8s cubic-bezier(.22,.9,.3,1)}
#pebble.pb-r-hype .pb-sparks{animation:pbSpark .8s ease forwards}
@keyframes pbrHype{0%{transform:none}18%{transform:translateY(-12px) rotate(-5deg)}
  36%{transform:translateY(0) scale(1.07,.93)}52%{transform:translateY(-8px) rotate(5deg)}
  70%{transform:translateY(0) scale(1.05,.95)}100%{transform:none}}

/* ── a lioness has a tail and ears, and they are never quite still ── */
.pb-tail{transform-box:view-box;transform-origin:72% 83%}
.pb-idle .pb-tail{animation:pbTail 3.4s ease-in-out infinite}
#pebble.pb-r-hype .pb-tail,.pb-cheer .pb-tail{animation:pbTail .7s ease-in-out infinite}
@keyframes pbTail{0%,100%{transform:rotate(0)}50%{transform:rotate(-9deg)}}
.pb-ear-l,.pb-ear-r{transform-box:fill-box;transform-origin:50% 90%}
#pebble.pb-twitch .pb-ear-l{animation:pbEar .35s ease}
#pebble.pb-twitch .pb-ear-r{animation:pbEar .35s ease .1s}
@keyframes pbEar{0%,100%{transform:rotate(0)}40%{transform:rotate(-12deg)}}
/* a live head: the slow idle sway, and pieces for the bigger moves */
.pb-head{transform-box:fill-box;transform-origin:50% 72%}
.pb-idle .pb-head{animation:pbHead 7s ease-in-out infinite}
@keyframes pbHead{0%,100%{transform:rotate(0)}30%{transform:rotate(-1.6deg)}70%{transform:rotate(1.4deg)}}
/* grooming: one paw up, head down to meet it */
.pb-leg-l{transform-box:fill-box;transform-origin:50% 10%}
#pebble.pb-groom .pb-leg-l{animation:pbGroomPaw 1.9s ease-in-out}
@keyframes pbGroomPaw{0%,100%{transform:none}25%,75%{transform:rotate(-34deg) translate(3px,-5px)}
  40%{transform:rotate(-30deg) translate(3px,-4px)}60%{transform:rotate(-36deg) translate(3px,-6px)}}
#pebble.pb-groom .pb-head{animation:pbGroomHead 1.9s ease-in-out}
@keyframes pbGroomHead{0%,100%{transform:none}30%,70%{transform:rotate(-7deg) translate(-2px,2px)}}
/* the ready-crouch while tests run: low, coiled, tail quick */
#pebble.pb-crouch .pb-fig{transform:translateY(3px) scale(1.05,.9)}
#pebble.pb-crouch .pb-tail{animation:pbTail .5s ease-in-out infinite}
/* the pounce, for a solve: coil, leap toward the page, land with a squash */
#pebble.pb-r-pounce .pb-fig{animation:pbrPounce .85s cubic-bezier(.3,.8,.4,1)}
@keyframes pbrPounce{0%{transform:none}22%{transform:translateY(4px) scale(1.08,.85)}
  50%{transform:translate(-20px,-24px) rotate(-8deg) scale(1.04,.98)}
  72%{transform:translate(-6px,0) scale(1.12,.86)}88%{transform:scale(.97,1.04)}100%{transform:none}}
#pebble.pb-left.pb-r-pounce .pb-fig{animation:pbrPounceR .85s cubic-bezier(.3,.8,.4,1)}
@keyframes pbrPounceR{0%{transform:none}22%{transform:translateY(4px) scale(1.08,.85)}
  50%{transform:translate(20px,-24px) rotate(8deg) scale(1.04,.98)}
  72%{transform:translate(6px,0) scale(1.12,.86)}88%{transform:scale(.97,1.04)}100%{transform:none}}
/* the roar: a cub-sized one. Head back, sparks out. */
#pebble.pb-roar .pb-head{animation:pbRoarHead .95s cubic-bezier(.3,.9,.35,1)}
@keyframes pbRoarHead{0%{transform:none}28%{transform:rotate(-8deg) translateY(-3px)}
  60%{transform:rotate(-6deg) translateY(-2px)}100%{transform:none}}
#pebble.pb-roar .pb-sparks{animation:pbSpark .9s ease forwards}
#pebble.pb-roar .pb-fig{animation:pbrJump .95s cubic-bezier(.22,.9,.3,1)}
/* depth: she leans toward the pointer; the gradients sell the roundness */
.pb-fig svg{transform:rotate(var(--tilt,0deg));transition:transform .35s ease;will-change:transform}
.pb-shadow{transform-box:fill-box;transform-origin:center;transition:opacity .3s,transform .3s}
/* hopping between places: the walk with air in it */
#pebble.pb-hopping .pb-fig{animation:pbHopMove .3s ease-in-out infinite}
@keyframes pbHopMove{0%,100%{transform:translateY(0)}50%{transform:translateY(-9px)}}
/* perched on a ledge: the ground is close, so the shadow tightens */
#pebble.pb-perched .pb-shadow{opacity:.2;transform:scaleX(.72)}
/* peering down at the code below her */
#pebble.pb-peer .pb-head{animation:none;transform:rotate(9deg) translateY(3px)}
/* pretend-typing: paws alternating on invisible keys */
.pb-leg-r{transform-box:fill-box;transform-origin:50% 10%}
#pebble.pb-typing .pb-leg-l{animation:pbTypeTap .16s ease-in-out infinite}
#pebble.pb-typing .pb-leg-r{animation:pbTypeTap .16s ease-in-out infinite .08s}
@keyframes pbTypeTap{0%,100%{transform:none}50%{transform:translateY(-3px) rotate(-6deg)}}
/* the paw swipe at a pointer that lingers on her */
#pebble.pb-swipe .pb-leg-r{animation:pbSwipe .65s ease}
@keyframes pbSwipe{0%,100%{transform:none}40%{transform:rotate(38deg) translate(-2px,-9px)}60%{transform:rotate(24deg) translate(-1px,-6px)}}
/* what pretend-typing produces */
.fx-glyph{position:fixed;z-index:96;pointer-events:none;font-family:var(--mono);
  font-size:11px;color:#9fe8c1;opacity:0;animation:glyphUp 1.4s ease-out forwards}
@keyframes glyphUp{0%{opacity:0;transform:translateY(4px)}20%{opacity:.9}
  100%{opacity:0;transform:translateY(-34px) rotate(6deg)}}

/* Her lines type on, visually: the invisible full copy holds the bubble's
   final size, the typed copy paints over it, the screen-reader copy is the
   only one assistive tech ever hears. */
.pb-tw{position:relative;display:block}
.pb-ghost{visibility:hidden}
.pb-typed{position:absolute;left:0;top:0;right:0}
.pb-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}

/* First meeting with an idea. Violet, so it reads as "different", not as
   praise or warning; the title says why a miss here is nothing. */
.newchip{font-size:9.5px;letter-spacing:.07em;text-transform:uppercase;color:#c4b5fd;
  border:1px solid #4c3f7a;border-radius:4px;padding:1px 5px;margin-left:8px;
  display:inline-block;vertical-align:1px}

/* The run's standing scoreboard: a gold chip under the header that climbs
   with the run and swells from five up. Gone the moment the run is. */
/* In the header band, not over the words, and it steps back to a ghost a
   moment after each change: fixed elements do not scroll away, so the only
   polite overlay is one that gets out of the way by itself. */
.combochip{position:fixed;top:8px;left:50%;transform:translateX(-50%);z-index:105;
  pointer-events:none;font-family:var(--mono);font-weight:700;
  font-size:clamp(12px,1.7vmin,24px);
  letter-spacing:.12em;color:#0b1015;background:linear-gradient(180deg,#ffd166,#e8b04b);
  border-radius:999px;padding:clamp(4px,.6vmin,9px) clamp(14px,2vmin,30px);
  box-shadow:0 4px 18px rgba(232,176,75,.35);
  transition:opacity .45s ease,transform .45s ease}
.combochip.quiet{opacity:.16;transform:translateX(-50%) scale(.82)}
.combochip.hot{font-size:clamp(14px,2.1vmin,30px);padding:clamp(5px,.8vmin,11px) clamp(18px,2.5vmin,38px)}
.combochip.pulse{animation:comboPulse .4s cubic-bezier(.2,1.4,.4,1)}
@keyframes comboPulse{0%{transform:translateX(-50%) scale(.85)}
  60%{transform:translateX(-50%) scale(1.12)}100%{transform:translateX(-50%) scale(1)}}
/* ten in a row: one wash of her gold across everything, then gone */
.fx-flash{position:fixed;inset:0;z-index:190;pointer-events:none;
  background:radial-gradient(circle at 50% 40%,rgba(255,209,102,.42),rgba(232,176,75,.12) 55%,transparent 80%);
  animation:flashUp .6s ease-out forwards}
@keyframes flashUp{0%{opacity:0}25%{opacity:1}100%{opacity:0}}
/* waking up: the long cat stretch before anything else happens */
#pebble.pb-stretch .pb-fig{animation:pbStretch .9s cubic-bezier(.34,1.2,.4,1)}
@keyframes pbStretch{0%{transform:none}35%{transform:scale(1.12,.86) translateY(4px)}
  70%{transform:scale(.94,1.1) translateY(-3px)}100%{transform:none}}
/* the hint dash: same walk, twice the legs, a lean into it */
#pebble.pb-dash .pb-body{animation:pbStep .16s ease-in-out infinite;transform-origin:50% 90%}
#pebble.pb-dash .pb-feet{animation:pbFeet .16s ease-in-out infinite;transform-origin:50% 100%}
#pebble.pb-dash .pb-fig{transform:rotate(-4deg)}
/* watching you type: attentive, leaning toward the work */
#pebble.pb-watch .pb-fig{transform:rotate(-3deg) translateY(-1px)}
#pebble.pb-watch .pb-tail{animation:pbTail 1.6s ease-in-out infinite}
.pb-walking .pb-tail,#pebble.pb-dash .pb-tail{animation:pbTail .8s ease-in-out infinite}

/* The coach box is Hafsa talking: her name on it, her gold on its edge. */
.askout.hafsa{border-left:3px solid #d9a441}
.coachwho{font-size:10px;letter-spacing:.12em;text-transform:uppercase;
  color:#d9a441;margin-bottom:6px;font-weight:700}

/* ── petting: eyes close happy, blush shows, hearts drift up ── */
.pb-petted .pb-lids rect{height:14px}
.pb-petted .pb-fig{transform:scale(1.03,.97)}
.pb-heart{position:absolute;z-index:96;pointer-events:none;font-size:13px;color:#f9a8d4;
  animation:pbHeart 1.3s ease-out forwards}
@keyframes pbHeart{0%{opacity:0;transform:translateY(2px) scale(.7)}
  20%{opacity:1;transform:translateY(-8px) scale(1.1)}
  100%{opacity:0;transform:translateY(-34px) scale(.9)}}
.pb-fig{cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none}
.pb-land .pb-body{animation:pbLand .55s cubic-bezier(.34,1.56,.64,1)}
@keyframes pbLand{0%{transform:scaleY(.7) scaleX(1.25)}60%{transform:scaleY(1.08) scaleX(.96)}100%{transform:none}}
.pb-spin .pb-body{animation:pbSpin .8s ease}
@keyframes pbSpin{to{transform:rotateY(360deg)}}
.pb-hop .pb-body{animation:pbJump .6s cubic-bezier(.22,.9,.3,1) 2}
.pb-wink .pb-lids rect:first-child{animation:pbWink .7s ease}
@keyframes pbWink{30%,60%{height:16px}}
.pb-wiggle .pb-body{animation:pbWave .6s ease 2}
.pb-peek .pb-pupils{animation:pbPeek 1s ease}
@keyframes pbPeek{25%{transform:translate(-3px,-1px)}75%{transform:translate(3px,-1px)}}
.pb-lids rect{transition:height .3s ease}
.pb-asleep .pb-lids rect{height:16px}
.pb-asleep .pb-body{animation:pbBreathe 4.5s ease-in-out infinite}
.pb-asleep .pb-zz{animation:pbZ 2.2s ease-in-out infinite}
@keyframes pbZ{0%{opacity:0;transform:translate(0,0)}30%{opacity:1}100%{opacity:0;transform:translate(6px,-14px)}}
.pb-asleep .pb-eye{animation:none}

.pebble-float.pb-flip{flex-direction:row}
.pebble-float.pb-flip .pb-say::before{left:-6px;right:auto;transform:rotate(-135deg)}
.pebble-float.pb-left.pb-flip{flex-direction:row-reverse}
.pebble-float.pb-left.pb-flip .pb-say::before{right:-6px;left:auto;transform:rotate(45deg)}

.rxrow{display:flex;gap:10px;align-items:baseline;padding:6px 0;border-bottom:1px solid var(--line);font-size:13px}
.rxrow:last-child{border-bottom:0}
.rxlab{color:var(--accent);width:14px;flex:0 0 auto} .rxrow.bad .rxlab{color:var(--bad)}
.rxrow code{font-family:var(--mono);white-space:pre-wrap;word-break:break-all;color:var(--dim)}
.rxrow mark{background:rgba(110,231,168,.22);color:var(--ink);border-bottom:2px solid var(--accent);border-radius:2px;padding:0 1px}

/* UX pass */
@media(min-width:1000px){ .gate{max-width:760px} .tgrid2{grid-template-columns:1fr 1fr 1fr} }
.tcase .tcall{white-space:normal;overflow:visible;text-overflow:clip}

.tcase.terr{cursor:pointer;background:rgba(240,139,139,.05);border-radius:6px;margin:2px -6px;padding:6px}
.tcase.terr .tgot{color:var(--warn);white-space:normal;text-align:left;flex:1 1 60%}
.tcase.terr .tcall{color:var(--dim)}


/* FOCUS YOU CAN SEE, EVERYWHERE. Three rules removed the outline and put back
   a border colour at 2.5:1, which is under the 3:1 a focus indicator needs,
   and it was a colour-only change. Everything else in the app fell back to
   whatever the browser did. One rule now covers every control. */
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* iOS ZOOMS THE PAGE WHEN A FOCUSED FIELD IS UNDER 16px, and the page it
   zooms into is wider than the screen — so the phone starts scrolling
   sideways and the only way to see the whole thing is to turn it. That is
   the "I have to hold my phone landscape to type" bug, and it is a FONT
   SIZE, not a layout: the layout measures clean at every width down to
   320px.

   This rule used to name two fields by hand, and every field added since
   escaped it: the landing search, the Explore search, and the film's own
   answer box and its two selects. Naming them one at a time is how the
   list rots, so this covers EVERY field, and it wins on purpose. A later
   rule setting 13px is not a preference to respect; it is this bug coming
   back.

   THE FIX PEOPLE USUALLY REACH FOR IS WORSE THAN THE BUG: maximum-scale=1
   or user-scalable=no in the viewport meta stops the zoom by taking
   pinch-zoom away from everybody who needs it to read at all. Never that.
   A law holds both halves. */
@media(pointer:coarse){
  input:not([type=range]):not([type=checkbox]):not([type=radio]),
  textarea, select, [contenteditable="true"], .cm-content{
    font-size:16px!important;
  }
  .avatar,.chip,.track,.askbar button{min-height:44px}
  .codekeys button{height:44px}
}

/* Three long labels at ~440px could not fit any phone, so the third button
   sat past the right edge on every one of them. Stack instead. */
@media(max-width:520px){
  .askbar{left:8px!important;right:8px;width:auto;flex-direction:column}
  .askbar button{padding:13px 12px;text-align:left}
  .tcase{flex-wrap:wrap}
}

/* "I am not sure yet" during placement. Deliberately not styled as a wrong
   answer: it is an honest report, and the staircase treats it as a miss
   without the screen turning red at someone who already told us they are
   new to this. */
.choice.idk{color:var(--dim);font-style:italic}
.choice.unsure{border-color:var(--line);color:var(--dim)}


/* SCROLLING THAT BEHAVES, OPEN OR CLOSED.
   The bar sat at z-index 80 under a scrim at 89, so while the pane was open
   the tabs were fully visible, looked live, and did nothing: tapping one
   closed the pane instead of going anywhere. Visible and dead is worse than
   hidden. It now sits above the scrim so it keeps working, and tapping "You"
   a second time closes the pane like it looks like it should.

   overscroll-behavior stops scroll chaining. Reaching the end of the pane or
   the results and continuing to swipe used to start dragging the page behind
   it, which reads as the page fighting you. Each scrolling surface now keeps
   its own gesture. */
.pane,.results,.sheet,.pal{overscroll-behavior:contain}
/* `contain` on the viewport, never `none`: contain keeps the Mac bounce at
   the ends of the page (whose absence reads as broken to anyone with a
   trackpad) while still blocking pull-to-refresh and back-swipe chaining
   on phones. The old `none` on body killed the bounce everywhere. */
html{overscroll-behavior-y:contain}

/* THE WHEEL BUG THAT SURVIVED EVERY OTHER FIX LIVED ON THIS LINE. It used
   to say `html,body{overflow-x:hidden}`, and when BOTH html and body carry
   a non-visible overflow, body stops propagating to the viewport and
   becomes ITS OWN scroll container: document scrolling splits across two
   boxes. On Chromium on a Mac that split makes wheel scrolling sticky,
   laggy or dead depending on where the pointer sits, while native
   scrolling stays smooth, which is exactly how it was reported: dragging
   a text selection scrolled beautifully, the wheel did not, on a machine
   this environment's headless browser could not imitate. `clip` forbids
   sideways overflow WITHOUT creating a scroll container, which is the
   entire difference between it and `hidden`. Engines too old for clip
   get `hidden` on html alone, which never split anything. */
html,body{overflow-x:clip}
@supports not (overflow-x: clip){
  html{overflow-x:hidden}
  body{overflow-x:visible}
}

/* ── badges ──────────────────────────────────────────────────────────────
   The claim screen a win deserves: one badge at a time, big enough for a
   TV, gone with one tap. The overlay owns the screen briefly because the
   moment is the product's thank-you; reduced motion keeps the card and
   loses the theatre. */
.badgeover{position:fixed;inset:0;z-index:220;display:flex;align-items:center;
  justify-content:center;background:rgba(4,6,9,.78);
  -webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);
  animation:rise .18s ease}
.badgeover.bye{opacity:0;transition:opacity .25s}
.badgecard{text-align:center;padding:clamp(28px,5vmin,64px) clamp(30px,6vmin,80px);
  background:#0d1620;border:1px solid var(--accent-dk);border-radius:20px;
  box-shadow:0 30px 90px rgba(0,0,0,.7),0 0 0 1px rgba(110,231,168,.12),
             0 0 80px rgba(255,209,102,.12);
  animation:badgein .5s cubic-bezier(.2,1.4,.4,1)}
@keyframes badgein{from{transform:scale(.7) translateY(16px);opacity:0}
  to{transform:none;opacity:1}}
.badgecard .bicon{width:clamp(96px,16vmin,180px);height:clamp(96px,16vmin,180px);
  margin:0 auto 6px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-size:clamp(52px,9vmin,104px);
  background:radial-gradient(circle at 38% 30%,#ffe9a8,#e8b04b 70%);
  box-shadow:0 10px 34px rgba(232,176,75,.55),inset 0 -6px 14px rgba(0,0,0,.18)}
.badgecard .btier{font-family:var(--mono);font-weight:800;color:#ffd166;
  font-size:clamp(18px,3vmin,34px);margin-bottom:10px}
.badgecard .btitle{font-size:clamp(20px,3.4vmin,40px);font-weight:700;color:var(--ink)}
.badgecard .bhow{font-size:clamp(13px,2vmin,22px);color:var(--dim);margin:8px 0 4px;
  max-width:44ch}
.badgecard .bwhen{font-family:var(--mono);font-size:clamp(11px,1.6vmin,17px);
  color:var(--faint);letter-spacing:.08em;text-transform:uppercase;margin-bottom:20px}
.badgecard .bclaim{min-width:min(260px,60vw)}
@media (prefers-reduced-motion: reduce){
  .badgeover,.badgecard{animation:none}
}

/* ── the editor's chrome ────────────────────────────────────────────────
   True facts only, dressed the way an editor dresses them. */
.idebar{display:flex;align-items:center;gap:14px;background:#0a0e13;
  border:1px solid var(--line);border-bottom:0;border-radius:10px 10px 0 0;
  padding:0 10px;margin-top:10px}
.idebar .idetab{display:inline-flex;align-items:center;gap:7px;
  font-family:var(--mono);font-size:12px;color:var(--ink);
  background:#11161c;border:1px solid var(--line);border-bottom-color:#11161c;
  border-radius:8px 8px 0 0;padding:7px 13px;margin-top:6px;position:relative;top:1px}
.idebar .idedot{width:8px;height:8px;border-radius:50%;background:var(--accent);
  box-shadow:0 0 6px rgba(110,231,168,.75)}
.idebar .idecrumb{font-family:var(--mono);font-size:11px;color:var(--faint);
  letter-spacing:.04em}
.idebar + #editor,.idebar + .cm-editor,.idebar + .edwrap{border-top-left-radius:0;border-top-right-radius:0;margin-top:0}
.idebar + .edwrap > #editor,.idebar + .edwrap > .edhl{border-top-left-radius:0;border-top-right-radius:0}

/* THE FOOTER, JOINED RATHER THAN TUCKED.
   It used to be pulled up with margin:-10px so it sat under the editor's
   rounded bottom corner, and a learner photographed the result with the top
   of every letter sliced off. A negative margin tuned to one font size at
   one zoom is not a joint: at 125% browser zoom, or with a larger system
   font, the overlap grows until it eats the text. Two other things sit
   between the editor and this bar in the DOM (the code keys and the web
   preview), so the pull also had to reach back over them, and on a phone —
   where the code keys are not zero-height — it landed on top of those
   instead.
   Now the editor simply has no bottom corners and no bottom border, and
   this sits flush underneath with its own top border. Nothing overlaps
   anything, at any size. */
.idestatus{display:flex;gap:0;flex-wrap:wrap;align-items:center;background:#0a0e13;
  border:1px solid var(--line);border-top:1px solid #131a22;border-radius:0 0 10px 10px;
  padding:5px 8px;margin:0 0 12px;font-family:var(--mono);font-size:11px;
  line-height:1.5;color:var(--faint)}
.idestatus span{padding:2px 9px;border-right:1px solid #141a21}
.idestatus span:last-child{border-right:0}
.idestatus span:first-child{color:var(--accent);font-weight:600}
.idestatus span.tests{color:var(--dim)}
/* The editor gives up its bottom edge to the footer that follows it. */
.hasfoot > #editor,.hasfoot > .edhl{border-bottom-left-radius:0;border-bottom-right-radius:0}
.edwrap.hasfoot,.cm-editor.hasfoot{border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom:0}

/* ── your journey (You pane) ───────────────────────────────────────────── */
.journey .jtop{display:flex;align-items:center;gap:16px;margin:10px 0 4px;flex-wrap:wrap}
.jring{position:relative;width:104px;height:104px;flex:0 0 auto}
.jring svg{display:block;transform:rotate(-90deg)}
.jmid{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}
.jmid b{font-family:var(--mono);font-size:22px;color:var(--ink);letter-spacing:-.02em;line-height:1}
.jmid span{font-family:var(--mono);font-size:10px;letter-spacing:.12em;color:var(--faint);margin-top:3px}
.jinfo{flex:1 1 180px;min-width:180px}
.jrank{display:flex;align-items:baseline;gap:8px}
.jrank b{font-family:var(--mono);font-size:28px;color:var(--accent);letter-spacing:-.02em}
.jnext{margin:2px 0 0;font-size:13px;color:var(--dim)}
.jlock{margin:6px 0 0;font-size:13px;color:var(--ink)}
.jlock span{color:var(--faint)}
.jwords{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin:12px 0 4px}
.jword{border:1px solid var(--line);border-radius:10px;padding:9px 11px 9px 22px;position:relative}
.jword::before{content:'';position:absolute;left:9px;top:14px;width:6px;height:6px;border-radius:50%;background:var(--faint)}
.jword.lvl::before{background:var(--accent)}
.jword.eff::before{background:var(--warn)}
.jword b{display:block;font-size:13px;color:var(--ink);font-weight:600}
.jword span{display:block;font-size:11.5px;color:var(--faint);line-height:1.4;margin-top:2px}
@media(max-width:380px){ .jwords{grid-template-columns:1fr} }
/* the moment work lands: this topic's rung on the review ladder */
.rungs{margin:10px 0 0}
.rungs .rl{display:flex;gap:4px;height:8px}
.rungs .rl i{flex:1;border-radius:3px;background:var(--line);transform-origin:center bottom;display:block}
.rungs .rl i.on{background:var(--accent)}
.rungs.miss .rl i.on{background:var(--warn)}
.rungs .rt{margin:7px 0 0;font-size:13px;color:var(--dim)}
/* the two truest moments: a turnaround (full) and a lock-in (warm) */
.note.turn{border-color:var(--accent);color:var(--accent);font-weight:600}
.note.lock{border-color:var(--accent-dk);color:var(--ink)}
/* ── job readiness (You pane) + the interview drill timer ─────────────── */
.ready .rtop{display:flex;align-items:center;gap:12px;margin:8px 0 2px}
.rscore{font-family:var(--mono);font-size:26px;color:var(--accent);letter-spacing:-.02em;flex:0 0 auto}
.rscore span{font-size:13px;color:var(--faint);margin-left:1px}
.rmeter{flex:1;height:8px;border-radius:4px;background:var(--line);overflow:hidden}
.rmeter i{display:block;height:100%;background:var(--accent);border-radius:4px;transition:width .6s cubic-bezier(.4,0,.2,1)}
.rband{margin:4px 0 0;font-size:14px;color:var(--ink);font-weight:600}
.rline{margin:5px 0 0;font-size:13px;color:var(--dim)}
.rline.faint{color:var(--faint);font-size:12px}
#drillBtn{border-color:var(--accent);color:var(--accent)}
.drill{font-family:var(--mono);font-size:11px;color:var(--accent);border:1px solid var(--accent);border-radius:999px;padding:2px 8px}
@media (prefers-reduced-motion: reduce){ .rmeter i{transition:none} }
/* ── explain it: the interview step after a drill pass ─────────────────── */
.explain{margin:12px 0 0;padding:14px 16px;border:1px solid var(--accent);border-radius:12px;background:rgba(110,231,168,.04)}
.explain textarea{width:100%;box-sizing:border-box;font:inherit;font-size:14px;line-height:1.5;color:var(--ink);background:var(--bg);
  border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin:10px 0 8px;resize:vertical;min-height:72px}
.explain textarea:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent)}
.explain .row{align-items:center;gap:10px;flex-wrap:wrap}
.rubric{margin:12px 0 0;font-size:13px;color:var(--dim)}
.rubric b{color:var(--ink)}
.rubric label{display:block;margin:6px 0 0;color:var(--ink);cursor:pointer}
.rubric input{margin-right:8px}

/* START HERE: the doors. Five answers to "which one is you", each one a
   track, a starting level and a path in one tap. Same skin as the track
   cards so the landing reads as one system; the kicker line under each
   names exactly what the door opens, so nothing is a surprise. */
.doors{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.door{display:flex;flex-direction:column;gap:5px;text-align:left;background:#0a0e12;border:1px solid var(--line);
  border-radius:12px;padding:13px 14px;cursor:pointer;color:var(--ink);font:inherit;text-decoration:none;
  transition:border-color .14s,background .14s,transform .1s}
.door:hover{border-color:var(--line2);background:#0d1319}
.door:active{transform:scale(.994)}
.door.on{border-color:var(--accent)}
.door b{font-size:15.5px;line-height:1.3;text-wrap:balance}
@media(min-width:401px){ .door b{min-height:2.6em} }
.door span{font-size:13px;color:var(--dim);line-height:1.5;text-wrap:pretty}
/* margin-top:auto pins the track line to the floor of the card, so the six
   meta lines sit on one baseline across the row however long the blurbs
   above them run. Without it every card ended at a different height and
   the grid read as ragged, which is what "generated" looks like. */
.door i{font-style:normal;font-family:var(--mono);font-size:10px;color:var(--faint);
  letter-spacing:0;line-height:1.4;margin-top:auto;padding-top:10px}
.door.on i{color:var(--accent)}
@media(max-width:400px){ .doors{grid-template-columns:1fr} }
@media(min-width:1000px){ .doors{grid-template-columns:1fr 1fr 1fr} }
/* The sign-up step a door already answered: one line, one way to change it. */
.startas{font-size:12.5px;color:var(--faint);margin:-6px 0 12px}
.startas b{color:var(--dim);font-weight:500}
.startas button{background:none;border:0;padding:0;margin-left:6px;color:var(--accent);font:inherit;font-size:12.5px;cursor:pointer;text-decoration:underline;text-underline-offset:2px}

/* PATHS in Explore: a route to follow, with progress counted, never
   invented. A card per path; the open one shows its stages and the topic
   chips Explore already uses, so a path is a view of the same content. */
.paths{display:flex;flex-direction:column;gap:8px;margin-bottom:6px}
.pathcard{display:flex;flex-direction:column;gap:5px;text-align:left;width:100%;background:#0a0e12;border:1px solid var(--line);
  border-radius:11px;padding:11px 13px;cursor:pointer;color:var(--ink);font:inherit;transition:border-color .14s}
.pathcard:hover{border-color:var(--line2)}
.pathcard.on{border-color:var(--accent-dk)}
.pathcard .ph{display:flex;justify-content:space-between;gap:10px;align-items:baseline}
.pathcard .ph b{font-size:14px}
.pathcard .ph small{font-family:var(--mono);font-size:11px;color:var(--faint);white-space:nowrap}
.pathcard .pw{font-size:12.5px;color:var(--dim);line-height:1.4}
.pathcard .pbar{margin:4px 0 0}
.pathcard .following{font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--accent)}
.pathopen{border:1px solid var(--line);border-top:0;border-radius:0 0 11px 11px;margin:-9px 0 2px;padding:6px 13px 10px;background:#090c10}
.pathopen .pdesc{font-size:13px;color:var(--dim);line-height:1.5;margin:6px 0 4px}
.pathopen .row{margin:6px 0 4px}
.pstage{padding:9px 0 4px;border-top:1px solid var(--line)}
.pstage .sh{display:flex;gap:8px;align-items:baseline;font-size:13px;flex-wrap:wrap}
.pstage .sh b{font-weight:600}
.pstage .sh small{font-family:var(--mono);font-size:11px;color:var(--faint)}
.pstage.done .sh b{color:var(--accent)}
.pstage .sh .phere{font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--accent);white-space:nowrap}
.pstage .xrow{margin-top:7px}
.pproject{display:flex;align-items:center;gap:10px;margin:8px 0 4px;padding:9px 12px;min-height:44px;
  border:1px solid var(--line2);border-radius:10px;background:#0a0e12;text-decoration:none;color:var(--ink);text-align:left}
.pproject .pj{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);white-space:nowrap}
.pproject b{font-size:13px;font-weight:600;flex:1;min-width:0}
.pproject small{font-family:var(--mono);font-size:11px;color:var(--faint);white-space:nowrap}
.pproject.ready{border-color:var(--accent-dk)}
.pproject.ready .pj,.pproject.ready small{color:var(--accent)}
.pproject.done b,.pproject.done small{color:var(--accent)}
.pproject.locked{opacity:.72}
.pproject:hover{border-color:var(--line2);background:#0c1116}
@media (max-width:480px){ .pproject{flex-wrap:wrap} .pproject b{flex-basis:100%} }
.chip.strong{border-color:var(--accent-dk);color:var(--accent)}
.chip.practised{border-color:#4a4636}

/* The weekly note, previewed in the pane: the email's own plain text. */
.digestprev{white-space:pre-wrap;font-family:var(--mono);font-size:12px;color:var(--dim);background:#0a0e12;
  border:1px solid var(--line);border-radius:10px;padding:10px 12px;margin:8px 0 0;line-height:1.5;max-height:340px;overflow:auto}
.link.small{font-size:12.5px;padding:4px 0;margin:2px 0 0}

/* RELIABILITY PASS. Screen-reader-only text, the skip link, and space
   reserved so the first paint does not jump when the day arrives. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.skip{position:absolute;left:12px;top:-60px;z-index:300;background:var(--accent);color:#06130c;padding:10px 14px;border-radius:8px;
  font-weight:600;text-decoration:none;transition:top .12s}
.skip:focus{top:12px;outline:2px solid #fff;outline-offset:2px}
#q{min-height:2.4em}
.rank b{min-width:2ch;display:inline-block;text-align:right;font-variant-numeric:tabular-nums}

/* LOOK IT UP: the search box at the top of Explore and its hits. */
.srch{margin:6px 0 16px}
.srch input{width:100%;box-sizing:border-box;background:#0a0e12;border:1px solid var(--line);color:var(--ink);
  border-radius:10px;padding:10px 12px;font:inherit;font-size:14px;-webkit-appearance:none;appearance:none}
.srch input::placeholder{color:var(--faint)}
.srch input:focus{border-color:var(--accent-dk);box-shadow:0 0 0 3px rgba(110,231,168,.10)}
.srch input:focus:not(:focus-visible){outline:none}
.srchout{display:flex;flex-direction:column;gap:6px;margin-top:8px}
.hit{display:flex;flex-direction:column;gap:3px;text-align:left;width:100%;background:#0a0e12;border:1px solid var(--line);
  border-radius:10px;padding:9px 11px;color:var(--ink);cursor:pointer;font:inherit}
.hit:hover,.hit:focus-visible{border-color:var(--accent-dk)}
.hit .ht{font-size:14px;line-height:1.3}
.hit .hs{font-family:var(--mono);font-size:11px;color:var(--faint);line-height:1.4}
.srchnone{font-size:12.5px;color:var(--faint);margin:6px 2px 0}

/* RÉSUMÉ LINES in the You pane: the sentences, the profile line, the next line to earn. */
.resume{margin:8px 0 4px;padding:10px 12px;background:#0a0e12;border:1px solid var(--line);border-radius:10px;font-size:13px;color:var(--dim);line-height:1.5}
.resume ul{margin:0 0 8px;padding-left:18px}
.resume li{margin:0 0 6px;color:var(--ink)}
.resume .rline{margin:0 0 8px}
.resume .rline b{color:var(--ink);font-weight:500}
.resume .rnext{margin:8px 0 4px;font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint)}
.resume .rnextl li{color:var(--dim)}

/* THE REFERENCE APPROACH, shown after you have said it yourself. */
.refap{margin:10px 0 0;padding:10px 12px;border:1px solid var(--line);border-radius:10px;font-size:13.5px;color:var(--dim);line-height:1.5}
.refap b{color:var(--ink);font-weight:500}
.refap p{margin:6px 0 0}

/* THE JOB-READY CHECKLIST in the You pane. */
.ck .ckbar{height:6px;border-radius:3px;background:#0a0e12;border:1px solid var(--line);margin:8px 0 10px;overflow:hidden}
.ck .ckbar i{display:block;height:100%;background:var(--accent);transition:width .22s}
.ckrow{display:grid;grid-template-columns:18px 1fr;column-gap:9px;row-gap:1px;align-items:center;width:100%;text-align:left;background:none;border:0;border-top:1px solid var(--line);padding:8px 2px;color:var(--ink);cursor:pointer;font:inherit;font-size:13.5px}
.ckrow:first-of-type{border-top:0}
.ckrow i{font-style:normal;color:var(--accent);font-family:var(--mono);font-size:12px;width:18px;height:18px;border:1px solid var(--line2);border-radius:5px;display:inline-flex;align-items:center;justify-content:center;grid-row:span 2}
.ckrow.done i{border-color:var(--accent-dk);background:rgba(110,231,168,.08)}
.ckrow small{grid-column:2;font-family:var(--mono);font-size:11px;color:var(--faint)}
.ckrow.done span{color:var(--dim)}
.ckrow:hover span,.ckrow:focus-visible span{color:var(--accent)}

/* The visitor's search box on the landing: the app's own hit rows, as links. */
#lookUp .srch{max-width:560px;margin:10px 0 8px}
#lookUp a.hit{text-decoration:none;display:flex}
#lookUp .lp a{color:var(--accent);text-decoration:none}

/* THE WEEK, ONCE: the recap card above the question on the first visit of a new week. */
/* TODAY'S PUZZLE. Squares big enough to read on a phone and to survive a
   screenshot, in a row that wraps rather than scrolls. */
/* THE SHAPE. Deliberately quiet: it is a reading of the tests, not the
   answer, and it should feel like a margin note rather than a verdict. */
.shape{border:1px dashed var(--line2);border-radius:10px;padding:12px 14px;margin:12px 0 0;
       background:#0a0e12}
.shape h4{margin:0 0 8px;font-size:11.5px;letter-spacing:.09em;text-transform:uppercase;
          color:var(--dim);font-weight:500}
.shape ul{margin:0;padding-left:18px}
.shape li{font-size:13.5px;color:var(--dim);line-height:1.6;margin:3px 0}
.shape li:first-child{font-family:var(--mono);color:var(--ink);font-size:12.5px}
.shape .sfoot{font-size:12px;color:var(--faint);margin:9px 0 0}
.daily{border-color:var(--accent-dk)}
.daily h3{font-size:17px;font-weight:600;margin:10px 0 10px;line-height:1.45}
.daily .subhead{margin-bottom:2px}
.dgrid{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:12px}
.dsquares{font-size:20px;letter-spacing:2px;line-height:1}
.dsum{font-size:13.5px;color:var(--dim)}
.daily .done{opacity:.75}
.recap{border-color:var(--accent-dk)}
.recap .rnums{font-size:15px;color:var(--ink);margin:6px 0 4px;line-height:1.5}
.recap .rnext{align-items:center;gap:10px;flex-wrap:wrap;margin:8px 0 6px}

/* THE WEB PREVIEW: the learner's page under the editor, white because a page
   is white until they say otherwise, framed like the editor it mirrors. */
.preview{margin-top:10px}
.preview iframe{display:block;width:100%;height:320px;border:1px solid var(--line);border-radius:10px;background:#fff}
@media(max-width:640px){ .preview iframe{height:240px} }

/* ── WATCH IT RUN ─────────────────────────────────────────────────────────
   A film of the learner's own code, sitting directly under it. The design
   job here is one thing: make the eye follow the machine. Everything that
   moves is a thing that actually changed, and nothing else moves at all. */
.watch{border:1px solid var(--line2);border-radius:12px;background:#0b1015;
  padding:12px 13px 11px;margin:12px 0 0}
/* The panel takes focus programmatically (tabindex -1) so Escape and the
   arrows work from anywhere inside it. That focus should not paint a ring —
   but a KEYBOARD focus still must, so this is scoped to :not(:focus-visible)
   rather than killing the outline outright. */
.watch:focus:not(:focus-visible){outline:none}
.whead{display:flex;align-items:center;gap:9px;margin-bottom:9px}
.whead b{font-size:13.5px;color:var(--ink)}
.whead .wcall{font-family:var(--mono);font-size:11.5px;color:var(--faint);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
.watch .wx{flex:0 0 auto;width:30px;height:30px;border-radius:50%;border:0;
  background:transparent;color:var(--faint);font:inherit;font-size:19px;line-height:1;
  cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center}
.watch .wx:hover,.watch .wx:focus-visible{background:#161e26;color:var(--ink)}

/* THE WORDS. The largest text in the panel on purpose: it is the teaching,
   and the numbers below it are the evidence for what it says. */
.wsay{font-size:15px;color:var(--ink);min-height:22px;margin:0 0 10px;
  font-weight:500;line-height:1.35}
.wsay.bad{color:var(--warn)}

.wstack{display:flex;flex-wrap:wrap;align-items:center;gap:5px;margin:0 0 9px;
  font-family:var(--mono);font-size:11px}
.wfr{color:var(--faint)}
.wfr.on{color:var(--accent)}
.warr{color:var(--line2)}

/* THE VARIABLES. Cards rather than a list, because a value that changes
   should be able to flash without the whole row reflowing. */
.wvars{display:flex;flex-wrap:wrap;gap:7px;margin:0 0 10px;min-height:34px}
.wvar{background:#11161c;border:1px solid var(--line);border-radius:8px;
  padding:5px 9px;font-family:var(--mono);font-size:12px;display:flex;gap:7px;
  align-items:baseline;max-width:100%}
.wvar b{color:var(--dim);font-weight:500}
.wvar span{color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wvar.lit{border-color:var(--accent);background:#0f1a16}
.wvar.lit span{color:var(--accent)}
@media(prefers-reduced-motion:no-preference){
  .wvar.lit{animation:wpulse .5s ease-out}
}
@keyframes wpulse{ from{transform:scale(1.06)} to{transform:scale(1)} }
.wempty{color:var(--faint);font-size:12px;margin:6px 0 0}

.wout{background:#080c10;border:1px solid var(--line);border-radius:8px;
  padding:7px 9px;margin:0 0 10px;font-family:var(--mono);font-size:11.5px;
  color:var(--dim);max-height:96px;overflow:auto;white-space:pre-wrap}

/* THE TRANSPORT. A scrubber you can drag is the difference between a demo
   and an instrument: it makes the run something you can interrogate. */
.wbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}

/* THE TRANSPORT FOLLOWS YOU WHILE IT PLAYS.
   The film's whole point is that it lights a line in the editor ABOVE this
   panel, so watching it means looking up — and on a 390-wide phone that put
   Pause 55px below the fold, the scrubber 161px and the results 295px. The
   thing stepped through her code with every way of stopping it off screen,
   which is exactly how it was reported: "line by line is going but no
   results and no way to stop it".

   So while it is running the bar pins itself to the bottom of the window,
   the way a video scrubber does. The moment it stops it drops back into the
   panel where it belongs — a control that floats when there is nothing to
   stop is just clutter. The panel keeps the bar's height in reserve so
   nothing below it jumps when the bar leaves the flow. */
.watch.playing{padding-bottom:64px}
.watch.playing .wbar{position:fixed;left:12px;right:12px;
  bottom:calc(12px + env(safe-area-inset-bottom));z-index:95;
  background:rgba(13,19,25,.96);-webkit-backdrop-filter:blur(12px);
  backdrop-filter:blur(12px);
  border:1px solid var(--line2);border-radius:14px;padding:8px 10px;
  box-shadow:0 8px 28px rgba(0,0,0,.45)}
/* On a phone the tab bar already owns the bottom edge, so sit above it. */
@media(max-width:520px){
  .watch.playing .wbar{bottom:calc(76px + env(safe-area-inset-bottom))}
}
@media(prefers-reduced-motion:reduce){
  .watch.playing .wbar{-webkit-backdrop-filter:none;backdrop-filter:none}
}
.wbar .go.small,.wbar .ghost.small{min-height:34px;padding:6px 12px}
.wbar #wScrub{flex:1;min-width:120px;accent-color:var(--accent);height:34px;cursor:pointer}
.wpos{font-family:var(--mono);font-size:11.5px;color:var(--faint);
  min-width:62px;text-align:right}
.wbar select{background:#0a0e12;color:var(--ink);border:1px solid var(--line);
  border-radius:7px;padding:6px 7px;font:inherit;font-size:12px;min-height:34px}
.wtrunc{font-size:11.5px;color:var(--warn);margin:8px 0 0}
.whow{font-size:11.5px;color:var(--faint);margin:8px 0 0}

/* Phones: the scrubber gets its own row so it is wide enough to aim at,
   and the variable cards scroll sideways instead of stacking into a wall. */
@media(max-width:520px){
  .wsay{font-size:14.5px}
  .wvars{flex-wrap:nowrap;overflow-x:auto;padding-bottom:3px;
    -webkit-overflow-scrolling:touch}
  .wvar{flex:0 0 auto}
  .wbar #wScrub{order:5;flex:1 0 100%;min-width:0;margin-top:2px}
  .wpos{order:4}
}

/* THE ASK. The film stops and puts a question where the answer would be.
   Given the accent border because it is the one moment the film wants
   something FROM you rather than the other way round. */
.wsay.ask{color:var(--accent)}
.wsay.good{color:var(--accent)}
.wsay.miss{color:var(--warn)}
.wask{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin:0 0 10px}
.wask .field{background:#0a0e12;color:var(--ink);border:1px solid var(--accent-dk);
  border-radius:8px;padding:8px 11px;font-family:var(--mono);font-size:13px;
  min-height:38px;flex:1;min-width:120px;max-width:260px}
.wask .field:focus{border-color:var(--accent)}
.wask .go.small{min-height:38px;padding:8px 14px}
.wask .link.small{background:none;border:0;color:var(--faint);font:inherit;
  font-size:12px;cursor:pointer;padding:6px 4px;text-decoration:underline;
  min-height:38px}
.wask .link.small:hover{color:var(--ink)}
.wtally{font-family:var(--mono);font-size:11.5px;color:var(--faint);margin-left:auto}
/* The value being asked about, wearing a question mark instead of itself. */
.wvar.asking{border-color:var(--accent);border-style:dashed}
.wvar.asking span{color:var(--accent);font-weight:600}

/* THE LIT LINE, in the editor itself. The film's current position, painted
   on the learner's own code rather than on a copy of it. */
.cm-nowline{background:rgba(110,231,168,.10);
  box-shadow:inset 2px 0 0 var(--accent)}

/* THE ROWS, for a SQL film. A real table, scrollable in its own box so a
   wide result never widens the page — the one rule a phone cannot forgive. */
.wtabwrap{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%;
  border:1px solid var(--line);border-radius:8px;background:#0a0e12}
.wtab{border-collapse:collapse;font-family:var(--mono);font-size:12px;min-width:100%}
.wtab th,.wtab td{text-align:left;padding:5px 9px;border-bottom:1px solid var(--line);
  white-space:nowrap}
.wtab th{color:var(--faint);font-weight:500;background:#11161c;position:sticky;top:0}
.wtab td{color:var(--ink)}
.wtab tr:last-child td{border-bottom:0}
.wnull{color:var(--faint);font-style:italic}
.wcount{font-family:var(--mono);font-size:11.5px;color:var(--faint);margin:6px 0 0}

/* THE PATTERN IN PIECES, for a regex film. Each piece is a chip so it reads
   as one thing: "\\d{3}" is "three digits", not six characters. Fitted
   pieces are green, the one that stopped it is amber — the same two colours
   this product uses for "yes" and "look here" everywhere else. */
.wstack.wpieces{gap:4px;font-size:12.5px}
.wpc{border:1px solid var(--line);border-radius:6px;padding:3px 7px;
  background:#11161c;color:var(--faint);white-space:pre}
.wpc.fit{color:var(--accent);border-color:var(--accent-dk);
  background:rgba(110,231,168,.10)}
.wpc.stuck{color:var(--warn);border-color:var(--warn);
  background:rgba(230,189,109,.12)}

/* THE TEXT, WITH THE SCAN STANDING IN IT. Wraps rather than scrolls: a
   sentence cut off at the right edge of a phone is a sentence you cannot
   read, and unlike a table there is no column to preserve. break-all
   because the text is data, not prose, and a URL or a long token must not
   push the panel wider than the screen. */
.wtextwrap{border:1px solid var(--line);border-radius:8px;background:#0a0e12;
  padding:9px 10px;max-width:100%}
.wtext{font-family:var(--mono);font-size:13px;line-height:1.7;color:var(--ink);
  white-space:pre-wrap;word-break:break-all}
.wpast{color:var(--dim);text-decoration:line-through;
  text-decoration-color:var(--line2)}
.whit{color:#04140c;background:var(--accent);border-radius:3px;padding:1px 2px;
  font-weight:600}
.wzero{padding:0 1px;min-width:3px;display:inline-block}
.wcur{color:var(--ink);box-shadow:inset 0 -2px 0 var(--warn);
  background:rgba(230,189,109,.16);border-radius:2px}
.wcur.wend{display:inline-block;width:6px}

/* THE LIT CLAUSE. A SQL film moves clause by clause, so it marks a range of
   the query rather than a row of it. */
.cm-nowclause{background:rgba(110,231,168,.14);border-radius:3px;
  box-shadow:0 0 0 1px var(--accent-dk)}

/* The pointer from Explain It down to the scaling panel. It is a real
   button, not a note, because the useful thing it does is TAKE you there:
   on a 1440x775 MacBook viewport the panel is about 300px below the fold
   after a pass, and the step above it asks for complexity in its opening
   sentence. */
.toscale{display:block;width:100%;margin:-4px 0 2px;padding:11px 14px;min-height:44px;
  text-align:left;font:inherit;font-size:13.5px;line-height:1.5;color:var(--dim);
  background:var(--surface,rgba(255,255,255,.02));border:1px dashed var(--line);
  border-radius:10px;cursor:pointer;transition:border-color .12s,color .12s}
.toscale b{color:var(--ink);font-weight:600}
.toscale:hover{border-color:var(--accent);color:var(--ink)}
.toscale:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* ── how does it scale: guess, then the numbers ──────────────────────────
   Sits under Explain It and reads as its second half. The guess buttons are
   full fingertips on a phone; the evidence is a bar per input size, because
   "each doubling quadrupled it" is a shape before it is a sentence. */
.scale{border-color:var(--line);background:rgba(255,255,255,.02)}
/* The heading is two mono phrases and a rule. On a narrow phone the chip
   squeezed the words into a four-line stack, so below 520px the chip drops
   to its own line rather than wringing out the title. */
.scale .label{flex-wrap:wrap;row-gap:6px}
.scale .label>span{white-space:nowrap}
.scale .row{gap:8px;flex-wrap:wrap;margin:2px 0 0}
.sg{font:inherit;font-size:14px;font-variant-numeric:tabular-nums;color:var(--ink);
  background:var(--bg);border:1px solid var(--line);border-radius:10px;
  padding:9px 13px;min-height:44px;cursor:pointer;transition:border-color .12s,background .12s}
.sg:hover:not(:disabled){border-color:var(--accent);background:rgba(110,231,168,.06)}
.sg:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.sg:disabled{opacity:.42;cursor:default}
.sg.picked{opacity:1;border-color:var(--accent);color:var(--accent);background:rgba(110,231,168,.10);font-weight:600}
/* A 32px hit area on a phone is the exact thing the floor at the end of
   this file exists to stop, and "What do these mean?" is the control a
   beginner reaches for first. Padding, not height, so it stays a text link
   that happens to be easy to hit. */
.scale .link{display:inline-block;margin:8px 0 0;padding:12px 0;min-height:44px;
  background:none;border:0;color:var(--dim);font:inherit;font-size:13px;
  text-decoration:underline;text-underline-offset:3px;cursor:pointer}
.scale .link:hover{color:var(--accent)}
.scalemeans{margin:8px 0 0;font-size:13px;line-height:1.5;color:var(--dim)}
.scalemeans div{margin:8px 0 0;padding:0 0 0 11px;border-left:2px solid var(--line)}
.scalemeans b{color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums}
.scalemeans i{color:var(--faint);font-style:normal;font-size:12px;margin-left:7px}
.scalemeans span{display:block;margin:2px 0 0}
.scalesaid{margin:12px 0 0;padding:12px 14px;border:1px solid var(--line);border-radius:10px;
  font-size:13.5px;line-height:1.55;color:var(--dim)}
.scalesaid.yes{border-color:var(--accent);background:rgba(110,231,168,.06)}
.scalesaid.no{border-color:var(--warn,#e8a33d);background:rgba(232,163,61,.06)}
.scalesaid p{margin:8px 0 0}
.scalesaid p:first-child{margin:0}
.scalesaid .sv{color:var(--ink);font-weight:600;font-size:14.5px}
.scalesaid .sm{margin-right:7px}
.scalesaid.yes .sm{color:var(--accent)}
.scalesaid.no .sm{color:var(--warn,#e8a33d)}
.scalesaid .sp{margin-top:10px;padding-top:10px;border-top:1px solid var(--line)}
.scalepts{margin:11px 0 0}
.sgrid{margin:6px 0 0}
.srow{display:flex;align-items:center;gap:9px;margin:3px 0 0}
.srow i{flex:0 0 46px;text-align:right;font-style:normal;font-size:12px;
  color:var(--faint);font-variant-numeric:tabular-nums}
.srow u{height:9px;border-radius:5px;text-decoration:none;
  background:linear-gradient(90deg,var(--accent),rgba(110,231,168,.45));min-width:2px}
.srow b{font-weight:400;font-size:12px;color:var(--dim);font-variant-numeric:tabular-nums;white-space:nowrap}
.srow s{text-decoration:none;font-size:11.5px;color:var(--faint);font-variant-numeric:tabular-nums;
  white-space:nowrap;margin-left:auto;padding-left:8px}
@media(max-width:520px){
  .sg{flex:1 1 calc(33.333% - 6px);padding:9px 4px;text-align:center}
  .srow i{flex:0 0 40px}
  .scale .label::after{display:none}
  .scale .label>.kind{order:3;flex-basis:100%;margin-left:12px}
}

/* ── the painted editor ──────────────────────────────────────────────────
   A textarea cannot colour its own text, so a coloured copy of the code is
   painted directly behind it and the real text is made transparent. The
   caret, the selection, undo, the keyboard and every mobile behaviour stay
   the browser's own; only the pixels are ours.

   EVERY METRIC BELOW MUST MATCH THE TEXTAREA EXACTLY. Font, size, line
   height, padding, wrapping, tab size: anything off by half a pixel shows
   up as colour sliding away from the code underneath, which is worse than
   no colour at all. They are declared together here for that reason, and a
   browser law measures the two boxes rather than trusting this comment. */
.edwrap{position:relative;display:block;background:#0a0e12;
  border:1px solid var(--line);border-radius:10px;overflow:hidden}
.edwrap:focus-within{border-color:var(--accent-dk)}
.edwrap > #editor,.edwrap > .edhl{
  font-family:var(--mono);font-size:13.5px;line-height:1.65;
  padding:14px 14px 14px 52px;tab-size:4;
  white-space:pre-wrap;overflow-wrap:break-word;word-break:normal;
  border:0;border-radius:0;margin:0;letter-spacing:normal}
/* NO SCROLLBAR INSIDE THE EDITOR, and the box grows to fit instead.
   A textarea's scrollbar is drawn INSIDE its content box, so the moment the
   code got long enough to scroll, the real text had a few pixels less width
   than the painted copy and wrapped a row earlier — every line below the
   first wrap then sat one row off its colour. Measured: eight painted rows
   against nine typed ones. Rather than chase the scrollbar's width across
   browsers, the editor simply never has one: it sizes itself to the code,
   and the page scrolls, which is the better behaviour on a phone anyway.
   Beyond the cap it scrolls with the overlay kept in step. */
.edwrap > #editor{position:relative;z-index:1;display:block;width:100%;
  min-height:170px;max-height:none;background:transparent;color:transparent;
  caret-color:var(--accent);resize:none;overflow:hidden;scrollbar-width:none}
.edwrap > #editor::-webkit-scrollbar{width:0;height:0}
.edwrap > #editor::selection{background:#1c3a52;color:transparent}
.edwrap > .edhl{position:absolute;inset:0;z-index:0;overflow:hidden;
  pointer-events:none;color:var(--ink);background:transparent}
/* The gutter. Line numbers are the single cheapest signal that a box is an
   editor rather than a comment field, and the error messages the runner
   prints are all line numbers. */
.edgutter{position:absolute;left:0;top:0;bottom:0;width:44px;z-index:2;
  overflow:hidden;pointer-events:none;user-select:none;
  background:#080b0f;border-right:1px solid #141c26;
  font-family:var(--mono);font-size:13.5px;line-height:1.65;
  padding:14px 0;color:#3d4a5a;text-align:right}
.edgutter span{display:block;padding-right:11px}
/* The film's current line, painted behind the code. Measured, not
   calculated: a wrapped line is more than one row tall. */
.edline{position:absolute;left:44px;right:0;z-index:0;display:none;
  background:rgba(110,231,168,.09);
  border-left:2px solid var(--accent);pointer-events:none}
.edwrap:focus-within .edgutter{color:#4d5c6e}
@media(pointer:coarse){
  /* 16px or iOS zooms the page the moment the editor is focused. */
  .edwrap > #editor,.edwrap > .edhl,.edgutter{font-size:16px}
}
/* ── the code palette ────────────────────────────────────────────────────
   Generated from Syntax.PALETTE in syntax.js, which is the single place
   these colours are decided. They live here rather than in an injected
   <style> because this site sends a strict Content-Security-Policy and an
   inline stylesheet would be refused — and because colour that arrives one
   frame late is a flash of white code on every load.

   A law re-reads both files and fails if they ever disagree, and re-computes
   every contrast ratio against the editor ground: all twelve clear WCAG AA
   at 4.5:1. Comments came out at 4.27 on the first pass and were lifted
   rather than waved through, because a comment nobody can read is a comment
   a beginner skips. */
.tkw{color:#c9a2f5}   /* keyword */
.tbi{color:#e6bd6d}   /* builtin constant */
.tstr{color:#6ee7a8}   /* string */
.tnum{color:#f0b482}   /* number */
.tcom{color:#6b7d92;font-style:italic}   /* comment */
.tfn{color:#7fd4f5}   /* function */
.tty{color:#9fe6c8}   /* type or tag */
.top{color:#a8b6c6}   /* operator */
.tpun{color:#8d9aab}   /* punctuation */
.tnm{color:#eaf0f7}   /* plain name */
.tat{color:#e6bd6d}   /* decorator or attribute */
.tbad{color:#f08b8b}   /* invalid */

/* ── THE QUESTION, WHEN THE QUESTION IS A PROGRAM ────────────────────────
   Seventy-eight questions carry a whole program, and 384 more set code
   apart inline with two spaces. Both were being flattened: HTML collapses
   newlines and runs of spaces, so a question about what a LOOP BODY does
   arrived with the indentation that defines the loop body deleted.

   Two rules fix it. `white-space:pre-wrap` on the question elements gives
   the text back the spacing its author wrote, which is the whole of the
   inline fix and involves guessing nothing (qtext.js records the classifier
   that was built for that job and thrown away). And qtext.js splits the
   lead-in from the program, which lands here as .qlead + pre.qcode.

   .why is on the list for the same reason and no other: two of the 1,715
   explanations set something apart with two spaces, and the rule that gives
   a question its spacing back should not stop at the question.

   The block sits on #0a0e12, the editor's own ground, on purpose: the
   twelve token colours above were audited for WCAG AA against exactly that
   colour, so reusing it carries the audit over instead of inviting a new
   one. */
#q,#pq,#dq,.demo-q,.why,#cprompt,#chint{white-space:pre-wrap}
/* #cprompt and #chint join the list for the reason .why did: they are
   authored text arriving through textContent, and one challenge prompt
   already uses the two-space convention (`a log line like  2026-09-04
   12:31:07 ERROR Disk full  capture...`). The choices are handled on
   .choice itself, where the note explains what was being lost. */
.qlead{display:block}
pre.qcode{margin:12px 0 0;padding:13px 15px;background:#0a0e12;
  border:1px solid var(--line);border-radius:10px;
  font-family:var(--mono);font-size:13.5px;line-height:1.55;font-weight:400;
  letter-spacing:0;color:var(--ink);
  white-space:pre;overflow-x:auto;-webkit-overflow-scrolling:touch}
pre.qcode code{font:inherit;color:inherit;background:none;padding:0}
@media(max-width:520px){ pre.qcode{font-size:13px;padding:11px 12px} }

/* ── TOUCH MINIMUMS, DELIBERATELY THE LAST WORD IN THIS FILE ─────────────
   44px is a fingertip. Anything smaller is a control you aim at rather
   than press.

   These live at the very END on purpose. The first attempt put them beside
   the font-size fix in the middle of the file, and every later rule that
   set a smaller min-height quietly won on order — the film's transport
   buttons stayed at 34px and the toast dismiss at 30px while a rule
   claiming to fix them sat a thousand lines above. A floor that anything
   can step over is not a floor. Written once here, after everything, so
   the last word belongs to the thumb. */
@media(pointer:coarse){
  button,.go,.ghost,.link{min-height:44px}

  /* NAMED, BECAUSE A BARE ELEMENT SELECTOR LOSES ON SPECIFICITY. The floor
     above is `button` (0,0,1) and these are classes and ids, so on a TABLET
     — where the phone rules stop applying but the pointer is still a finger
     — they quietly won and shrank themselves back under the line:

       .tab       38px   the segmented control above 520px wide
       #sound     24px   the header's sound toggle, hidden on a phone
       #keysBtn   24px   the code-keys toggle

     #154 fixed this same class of bug for cascade ORDER and left the
     SPECIFICITY half of it standing, on the one screen size nobody had
     measured. A floor has to out-rank what it is a floor over. */
  .tab{min-height:44px}
  #sound,#keysBtn{min-height:44px;min-width:44px}
  .wbar .go.small,.wbar .ghost.small,.wbar select,
  .wask .go.small,.wask .link.small,.wask .field{min-height:44px}
  .note .nx,.watch .wx{width:44px;height:44px}

  /* THE SCRUBBER IS THE MOST-USED CONTROL IN THE FILM and it was the one
     under the floor: 34px tall with a native thumb around 16px, which is a
     control you aim at rather than press. The floor above could not reach
     it, because a range input is not a button and the sweep that measures
     buttons never looked at it.

     Drawn by hand ONLY on a coarse pointer, so a mouse keeps the native
     control: 44px of hit area, a 6px track through the middle of it, and a
     24px thumb with a ring the colour of the page so it reads as a grip
     rather than a dot. Both engines' pseudo-elements are given, because
     turning the native appearance off in one and not styling the other
     would leave an invisible slider. */
  .wbar #wScrub{height:44px;-webkit-appearance:none;appearance:none;
    background:transparent}
  .wbar #wScrub::-webkit-slider-runnable-track{height:6px;border-radius:3px;
    background:var(--line2)}
  .wbar #wScrub::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
    width:24px;height:24px;margin-top:-9px;border-radius:50%;
    background:var(--accent);border:2px solid var(--bg)}
  .wbar #wScrub::-moz-range-track{height:6px;border-radius:3px;
    background:var(--line2)}
  .wbar #wScrub::-moz-range-thumb{width:24px;height:24px;border-radius:50%;
    background:var(--accent);border:2px solid var(--bg)}

  /* ── THE TAP-TARGET FLOOR, MEASURED RATHER THAN DECLARED ─────────────────
     #154 put a floor under buttons and #158 put one under the scrubber. Both
     were written as heights in this file, and the law that held them READ
     this file. A control that declares no height at all is invisible to
     both: it renders however tall its text happens to be, and nothing
     notices. That is how the chip that switches your track shipped at 34px,
     in a block appended BELOW the floor it was meant to obey, and how the
     wordmark has been a 16px target in every header since the beginning.

     So this block is last in the file, it names controls rather than a list
     of ids, and tests/test_tap_targets.py measures what Chromium actually
     draws on a phone and on a tablet, signed in, instead of trusting a word
     of it. Signed in matters: the first version of that law measured a day
     view that was not on the screen and let the 34px chip through. */

  .langbtn{min-height:44px}
  a.mark,.khead a.home{display:inline-block;padding:12px 0}
  a.brand{display:inline-flex;align-items:center;min-height:44px}
  a.winhome{display:inline-block;padding:13px 0}
  /* Every link in a nav is a control rather than prose, so the floor is
     stated for navs as a class rather than for the four it was noticed on.
     THE ONE PLACE !important IS EARNED. #154 fixed the cascade ORDER and
     the floor still leaked, because the quiet-nav-link style is copied into
     six files and sets padding-bottom:1px from sheets that load after this
     one and win on specificity. A floor any decorative rule can lower is
     not a floor.
     The border-bottom goes with it and a real underline takes its place:
     a border draws at the bottom of the BOX, so in a 44px box it floats
     away from the words, while text-decoration follows the text. */
  nav a,.footnav a{display:inline-flex!important;align-items:center;
    min-height:44px!important;border-bottom:0!important;
    text-decoration:underline!important;text-underline-offset:4px;
    text-decoration-color:var(--line2);text-decoration-thickness:1px}
  /* The lesson index is a list of links, one to a row, so the row is the
     target rather than the words in it. */
  .learn.index li a{display:block;padding-top:12px;padding-bottom:12px}
}

/* ── SWITCHING TRACK FROM THE HEADER ─────────────────────────────────────
   The chip that names your language is now the control that changes it.
   The menu is anchored to it, every row is a fingertip, and a row says
   where you stand on that track so switching is an informed move rather
   than a guess. */
.langwrap{position:relative;display:inline-block}
.langbtn{min-height:44px;padding:5px 10px;border-radius:8px;background:var(--card);
  border:1px solid var(--line);color:var(--dim);font:inherit;font-size:12.5px;cursor:pointer}
.langbtn:hover{color:var(--ink);border-color:var(--dimline)}
.langbtn::after{content:" \25BE";color:var(--faint)}
.langmenu{position:absolute;right:0;top:calc(100% + 6px);z-index:70;min-width:246px;
  max-height:70vh;overflow-y:auto;background:var(--card);border:1px solid var(--line);
  border-radius:12px;padding:6px;box-shadow:0 18px 44px rgba(0,0,0,.5)}
.langmenu .lmrow{display:block;width:100%;text-align:left;min-height:44px;padding:8px 10px;
  border:0;border-radius:8px;background:none;color:var(--ink);font:inherit;cursor:pointer}
.langmenu .lmrow:hover{background:var(--surface2, #141c23)}
.langmenu .lmrow[aria-selected="true"]{background:var(--surface2, #141c23)}
.langmenu .lmrow b{display:block;font-size:14px;font-weight:600}
.langmenu .lmrow span{display:block;font-size:12px;color:var(--faint);margin-top:1px}
.langmenu .lmhead{padding:6px 10px 4px;font-size:11.5px;color:var(--faint)}

/* ── A SCREEN THIS BIG HAS ROOM ───────────────────────────────────────────
   Measured on a television, from a sofa, because that is where it was wrong.
   The layout was frozen: 760px of content and 13px card text at EVERY screen
   size, so a 4K panel used a fifth of its width and printed body copy at
   thirteen physical pixels on eighty inches of glass. The only fix available
   to the person watching was to remember to press ctrl-plus, and people do
   not remember.

   SCALE, DO NOT SPREAD. The instinct is to let the text run out to the edges,
   and it is the wrong one: a line of 200 characters is harder to read than a
   line of 65, not easier. So the page grows instead. The same measure, the
   same shape, the same three cards to a row; all of it bigger, which is what
   sitting further away actually calls for.

   Each step lands the effective layout around 1400 to 1500 CSS pixels, the
   width the design was drawn at, so nothing below this line has to know that
   any of it is happening. zoom rather than transform, deliberately: zoom
   re-runs layout, so text re-wraps and stays sharp, where a transform would
   scale a picture of the page and blur it.

   A page cannot know how far away you are sitting. It can know that a screen
   this wide has room it is not using, and that is the part being fixed. */
@media (min-width:1600px){ :root{ zoom:1.15 } }
@media (min-width:1760px){ :root{ zoom:1.25 } }
@media (min-width:1920px){ :root{ zoom:1.35 } }
@media (min-width:2150px){ :root{ zoom:1.5 } }
@media (min-width:2400px){ :root{ zoom:1.65 } }
@media (min-width:2700px){ :root{ zoom:1.85 } }
@media (min-width:3000px){ :root{ zoom:2.05 } }
@media (min-width:3300px){ :root{ zoom:2.25 } }
@media (min-width:3600px){ :root{ zoom:2.45 } }

/* AND THE EDGES. Every border on the site sits at about 1.3:1 against the
   background. Close up that reads as a hairline and looks calm, which is why
   it was chosen. Across a room it is not there at all, and the cards stop
   being cards: the photograph that started this shows six panels with no
   visible boundary between them. Raised only where the distance problem is,
   so the desk keeps the design it has. */
@media (min-width:1600px){
  :root{ --line:#344552; --line2:#515965 }
}

/* ── A PROBLEM YOU TYPE ──────────────────────────────────────────────────
   Git and the terminal get a prompt where every other track gets an editor.
   The list comes FIRST, above the terminal, on purpose: it is the brief in
   plain words, and knowing exactly what is being asked before you type is
   the difference between practice and a guessing game. Each row goes green
   the moment it becomes true, so progress is visible mid-task rather than
   only at the end. Same panel language as the rest of the site: one card,
   one hairline, no new colours. */
.cmd{margin:14px 0 4px}
.cmdgoal{border:1px solid var(--line2);border-radius:12px;background:var(--bg2);
         padding:12px 14px;margin-bottom:12px}
.cmdgoal h3{margin:0 0 8px;text-align:left;font-size:13.5px;font-weight:600;color:var(--dim);
            letter-spacing:.01em}
.cmdgoal ul{list-style:none;margin:0;padding:0;display:grid;gap:7px}
/* text-align:left, explicitly. The zone this lives in centres its rows, and
   a checklist that walks in and out from the middle is unreadable as a
   list: the eye has no left edge to run down. */
.cmdgoal li{display:grid;grid-template-columns:18px 1fr;gap:8px;align-items:baseline;
            font-size:14px;color:var(--dim);text-align:left;justify-items:start}
.cmdgoal li b{font-weight:500;color:var(--ink)}
/* grid-column:2 deliberately. The row is a two column grid and this is its
   third child, so without it the "what it is right now" line dropped into
   the tick column and read as a second, narrower list. */
.cmdgoal li i{grid-column:2;display:block;font-style:normal;font-size:12.5px;
              color:var(--faint);margin-top:2px}
.cmdgoal .tick{color:var(--line2);font-size:13px;line-height:1.5}
.cmdgoal li.done .tick{color:var(--accent)}
.cmdgoal li.done b{color:var(--accent)}
.cmd.won .cmdgoal{border-color:var(--accent-dk)}
.cmd.won .cmdgoal h3{color:var(--accent)}

.cmdterm{border:1px solid var(--line2);border-radius:12px;overflow:hidden;background:var(--panel)}
.cmdbar{display:flex;align-items:center;gap:10px;padding:8px 12px;background:var(--bg2);
        border-bottom:1px solid var(--line)}
.cmdbar #cmdWhere,.cmdbar #swcWhere{font-family:var(--mono);font-size:11.5px;color:var(--faint);
                  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cmdbar .cmdtag{margin-left:auto;font-size:11.5px;color:var(--faint);white-space:nowrap}
@media (max-width:520px){ .cmdbar .cmdtag{display:none} }
.cmdscroll{overflow-y:auto;text-align:left;padding:12px 14px;font-family:var(--mono);font-size:13px;
           line-height:1.55;min-height:120px;max-height:40vh;-webkit-overflow-scrolling:touch}
.cmdscroll .said{margin:0 0 2px;color:var(--ink);white-space:pre-wrap;word-break:break-word}
.cmdscroll .said .ps{color:var(--accent);user-select:none}
.cmdscroll .out{color:var(--dim);white-space:pre-wrap;word-break:break-word;margin:2px 0 10px}
.cmdscroll .oops{color:var(--bad);white-space:pre-wrap;word-break:break-word;margin:2px 0 10px}
.cmdscroll .hello{color:var(--faint);margin:0 0 12px}
#cmdForm,#swcForm{display:flex;align-items:center;gap:8px;border-top:1px solid var(--line);
         padding:6px 12px;background:var(--card,var(--bg2))}
#cmdForm .cmdps,#swcForm .cmdps{font-family:var(--mono);font-size:13px;color:var(--accent)}
#cmdIn,#swcIn{flex:1;min-width:0;background:transparent;border:0;color:var(--ink);
       font-family:var(--mono);font-size:16px;padding:8px 2px;min-height:44px}
/* The ring goes only where a mouse put the caret. A keyboard focus keeps
   it, always: this is the one border somebody navigating by tab has. */
#cmdIn:not(:focus-visible),#swcIn:not(:focus-visible){outline:none}
#cmdIn::placeholder,#swcIn::placeholder{color:var(--faint)}
#cmdIn:focus-visible,#swcIn:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}

/* What that command just did, in the words a colleague would use. */
.cmdsaid{margin:10px 0 2px;display:grid;gap:6px}
.cmdsaid p{margin:0;text-align:left;font-size:13.5px;color:var(--dim);border-left:2px solid var(--line2);
           padding-left:10px}

/* ── "YOUR TURN", WITH A BOX ───────────────────────────────────────────────
   The blank is a field in the middle of the line, sized to the answer's
   shape rather than stretched across the card, so the code still reads as
   code. 16px in the field, because under that a phone zooms the page the
   moment it is tapped. */
.lfade .lcode{white-space:pre-wrap;word-break:break-word}
.lgap{font-family:var(--mono);font-size:16px;color:var(--ink);background:#0a0f14;
      border:1px solid var(--accent-dk);border-radius:6px;padding:2px 8px;margin:0 2px;
      min-height:32px;vertical-align:baseline}
/* The ring goes only where a mouse put the caret. A keyboard focus keeps it
   always: it is the one border somebody navigating by tab has. */
.lgap:not(:focus-visible){outline:none}
.lgap:focus-visible{border-color:var(--accent);box-shadow:0 0 0 2px rgba(110,231,168,.22)}
.lgap.got{border-color:var(--accent);color:var(--accent);background:rgba(110,231,168,.07)}
.lfade .lrow{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px}
.lfade .lmark{font-size:13.5px;color:var(--dim);flex:1 1 240px;min-width:0}
.lfade .lmark.hit{color:var(--accent)}
.lfade .lmark.miss{color:var(--warn)}
.lfade .lmark.shown{color:var(--dim)}
.lfade.won{border-color:var(--accent-dk)}
/* Where no answer has been written down yet the block says so plainly and
   draws no box: a mark that grades nothing would be the same bug wearing a
   tick. */
.lfade-open .lnote{display:block;margin-top:8px}
@media (max-width:420px){ .lgap{min-height:36px} }

/* ── TWO FAST TAPS ARE NOT A ZOOM ─────────────────────────────────────────
   Reported from a phone: tapping the same block twice in the kids playground
   zoomed the page in and cut half the controls off. A phone waits ~300ms
   after a tap to see whether a second one is coming, and treats the pair as
   "zoom here" — which collides with every interface where tapping the same
   control twice is the point: a path of arrows, a number pad, +/- steppers,
   a chip you press again.

   touch-action:manipulation turns off double-tap-to-zoom ON THESE ELEMENTS
   and nothing else. Pinch to zoom still works on the whole page, including
   over these controls, which is the accessibility line that must not move
   and is checked by its own law. It also removes the 300ms wait before the
   tap registers, so every button on the site answers faster. */
button, [role="button"], .chip, .door, .tab, .kbtn, .blk,
input[type="button"], input[type="submit"], summary, label.tap {
  touch-action: manipulation;
}
.lfade .ldoor{display:inline-block;margin-top:10px;font-size:14px;color:var(--accent);
              text-decoration:none;border-bottom:1px solid var(--accent-dk);padding-bottom:1px}
.lfade .ldoor:hover{border-bottom-color:var(--accent)}
/* Width comes from `size`, so the code line stays a line. */
.lgap{width:auto;max-width:100%}

/* THE CREDENTIAL ID IN THE PANE. Set apart from the prose because it is the
   one string somebody copies out of this card and types somewhere else. */
.certbox .certid{display:flex;justify-content:space-between;align-items:center;gap:12px;
  margin:8px 0 4px;padding:9px 12px;border:1px dashed var(--line2);border-radius:10px;flex-wrap:wrap}
.certbox .certid span{font-family:var(--mono);font-size:10px;letter-spacing:.16em;
  text-transform:uppercase;color:var(--faint)}
.certbox .certid b{font-family:var(--mono);font-size:14px;letter-spacing:.1em;color:var(--ink);
  font-weight:600;word-break:break-all}
.certbox .warnline{color:var(--warn)}

/* THE CREDENTIAL GRID on the landing page. Six flat cards rather than a
   paragraph, because somebody deciding whether a certificate is worth
   earning is scanning for the one fact they care about, and six of them are
   different facts. The last card is the one that says what it is NOT, which
   is why the grid exists rather than a bulleted list: it has to be as
   visible as the five above it. */
.credgrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(232px,1fr));gap:12px;margin-top:14px}
.ccard{border:1px solid var(--line);border-radius:14px;padding:15px 16px;
  background:linear-gradient(180deg,rgba(110,231,168,.035),rgba(255,255,255,.008))}
.ccard b{display:block;font-size:14.5px;margin-bottom:6px;color:var(--ink);text-wrap:balance}
.ccard span{display:block;font-size:13.5px;line-height:1.6;color:var(--dim);text-wrap:pretty}
.ccard code{font-family:var(--mono);font-size:12.5px;letter-spacing:.05em;color:var(--accent);
  background:rgba(110,231,168,.09);border-radius:5px;padding:1px 5px;white-space:nowrap}
