/* ═══════════════════════════════════════════════════════════════════════
   pageContent.css — the look of every teaching page in units 2 and 3.

   ONE file. It used to be a <style> block inside each of the fourteen
   topic pages, 23 KB copied fourteen times, so changing a colour meant
   re-uploading fourteen files. Now it is here and they carry none.

   HOW IT IS SCOPED
   Every rule below starts with .tpage — the wrapper div around a topic
   page. That is deliberate: names like .ok, .on, .open and .done are also
   used by unit 1, the sims and the sidebar, and without the wrapper this
   file would repaint them. If you add a rule, keep the .tpage.

   WHERE TO CHANGE THINGS
   Almost everything worth turning is in THE DIALS, immediately below.
   Change a number there and it moves everywhere it is used. The rules
   after it are grouped under headings in the order they appear down the
   page, so you can also go straight to the thing you can see.

   AFTER YOU EDIT THIS FILE
   Bump the ?v= number on its <link> in index.html, or the browser will
   keep serving the old one.
   ═══════════════════════════════════════════════════════════════════ */

/* ── THE DIALS ─────────────────────────────────────────────────────────
   Change these, not the rules below. */
.tpage{
  --pc-title         : clamp(1.33rem,7vw,2.38rem);    /* the big page title (owner, 25 Aug 2026: white titles down 4px) */
  --pc-intro         : clamp(1rem,2.3vw,1.15rem);     /* the one paragraph under it */
  --pc-label         : .72rem;                        /* small caps labels: SECTION 1 */
  --pc-section-title : clamp(1.36rem,3.4vw,1.79rem);  /* the big section titles (same x.875) */
  --pc-section-sub   : 1.02rem;                       /* the line under a section title */
  --pc-box-title     : 1.24rem;                       /* box titles (the 2-4 word ones) */
  --pc-box-body      : .97rem;                        /* text inside a box */
  --pc-body          : 1rem;                          /* ordinary text outside boxes */
  --pc-box-pad       : .95rem 1rem;                   /* padding inside a box */
  --pc-gap           : 1rem;                          /* gap between boxes in a row */
  --pc-sec-1         : var(--c-purple);               /* section 1 title colour */
  --pc-sec-2         : var(--c-pink);                 /* section 2 */
  --pc-sec-3         : var(--c-cyan);                 /* section 3 */
  --pc-sec-4         : var(--c-gold);                 /* section 4 */
  --pc-sec-5         : var(--c-green);                /* section 5 */
  --pc-fold-1        : var(--c-cyan);                 /* fold 1 - Learn it: I'll show you mine */
  --pc-fold-2        : var(--c-gold);                 /* fold 2 - Learn it: now you show me yours */
  --pc-fold-3        : var(--c-pink);                 /* fold 3 - Try it */
  --pc-pill-rgb      : var(--c-pink-rgb);             /* the contents pills (r,g,b) */
}

/* The hero is styled by topic-hub.css / unit2-pages.css. These two restate
   the sizes at their current values so they are on a dial like everything
   else. Same numbers — nothing moves until you turn them. */
.tpage .topic-title{font-size:var(--pc-title)}
.tpage .topic-sub{font-size:var(--pc-intro)}
.tpage .card-grid{gap:var(--pc-gap)}


/* ── CONTENTS PILLS ────────────────────────────────────────────────
   The row of pink pills under the intro. They go green when a section is done. */

/* contents strip — what the type cards used to say, without the navigation */
.tpage .vchips{display:flex;gap:.5rem;flex-wrap:wrap;margin:1.6rem 0 .4rem}
.tpage .vchip{display:flex;align-items:center;gap:.5rem;padding:.5rem .9rem;border-radius:999px;
  border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.04);color:rgba(255,255,255,.78);
  font-size:.9rem;text-decoration:none;transition:all .14s}
.tpage .vchip:hover{background:rgba(255,255,255,.09);color:#fff}
.tpage .vchip .d{width:18px;height:18px;border-radius:50%;display:grid;place-items:center;font-size:.62rem;font-weight:800;
  background:rgba(255,255,255,.1);color:rgba(255,255,255,.55)}
.tpage .vchip.done{border-color:var(--c-green);color:var(--c-green)}
.tpage .vchip.done .d{background:var(--c-green);color:#04231a}
.tpage .vchips{justify-content:center}

/* The contents pills at the top of the page, in pink. They were grey on
   grey, which made the loudest page on the site open with its quietest
   thing. */
.tpage .vpage .vchip{border-color:rgba(var(--pc-pill-rgb),.55);
  background:rgba(var(--pc-pill-rgb),.10);color:rgb(var(--pc-pill-rgb));font-weight:700}
.tpage .vpage .vchip:hover{background:rgba(var(--pc-pill-rgb),.20);color:#fff;
  border-color:rgb(var(--pc-pill-rgb))}
.tpage .vpage .vchip .d{background:rgba(var(--pc-pill-rgb),.85);color:#12000a;font-weight:900}

/* Done still goes green — that is feedback, and feedback colours are
   reserved. */
.tpage .vpage .vchip.done{border-color:var(--c-green);color:var(--c-green);
  background:rgba(var(--c-green-rgb),.10)}
.tpage .vpage .vchip.done .d{background:var(--c-green);color:#04231a}


/* ── SECTION HEADERS ───────────────────────────────────────────────
   SECTION n label, the big coloured title, the line underneath. */

/* ── Single-page topic layout ────────────────────────────────────────
   Replaces the old five-tab overlay. Everything a student needs for one
   concept lives on one scroll: an overview, a section per question type,
   the trace and code tools once each, then a mixed exam. */

/* full width — this is the page itself now, not a panel inside one */
.tpage .vpage{width:100%;max-width:none;margin:0}
.tpage .vpage .sec{scroll-margin-top:14px;margin:3.4rem 0 0}
.tpage .sec-head{display:flex;align-items:baseline;gap:.85rem;flex-wrap:wrap;
  border-top:1px solid rgba(255,255,255,.12);padding-top:1.5rem;margin-bottom:.35rem}
.tpage .sec-num{font-family:var(--mono);font-size:var(--pc-label);letter-spacing:2px;text-transform:uppercase;
  color:var(--c-cyan);white-space:nowrap}
.tpage .sec-h1{font-size:clamp(1.9rem,5vw,2.7rem);line-height:1.05;font-weight:800;color:#fff;margin:0;letter-spacing:-.02em}
.tpage .sec-sub{color:var(--dim,rgba(255,255,255,.58));font-size:var(--pc-section-sub);line-height:1.5;margin:.45rem 0 1.6rem;max-width:74ch}
.tpage .sec-part{font-family:var(--mono);font-size:.7rem;letter-spacing:2px;text-transform:uppercase;
  color:rgba(255,255,255,.4);margin:2.4rem 0 .8rem;display:flex;align-items:center;gap:.7rem}
.tpage .sec-part::after{content:"";flex:1;height:1px;background:rgba(255,255,255,.1)}

/* ── Template rule ───────────────────────────────────────────────────
   If it is not inside a boxed container, it is centred. Titles, sub-titles,
   standalone paragraphs and the anchor pills all sit on the centre line;
   anything with a border around it keeps its own alignment. */
.tpage .vpage > .content-block{text-align:center}
.tpage .vpage > .content-block p{margin-left:auto;margin-right:auto;max-width:70ch}
.tpage .sec-head{justify-content:center;text-align:center}
.tpage .sec-sub{text-align:center;margin-left:auto;margin-right:auto}
.tpage .sec-part{justify-content:center;text-align:center}
.tpage .sec-part::before, .tpage .sec-part::after{content:"";flex:1;height:1px;background:rgba(255,255,255,.1)}

/* ══ CHECKLIST PASS, 3 Aug 2026 ═══════════════════════════════════════
   Section header, type scale and section-title colour. Everything below
   this line is the same on all fourteen pages — it is generated from one
   block in restyle.py, so if it needs changing it changes in one place.
   ══════════════════════════════════════════════════════════════════ */

/* The lime, ready if the sequence ever wants a sixth. The section colours
   themselves come straight out of palette.css — the site has exactly seven
   colours and inventing a pastel purple to sit among them was wrong. */
.tpage .vpage{--c-lime:#dcfe68}

/* The SECTION n label goes ABOVE the title, the way the page header does
   it, instead of sitting on the same baseline beside it. */
.tpage .sec-head{display:block !important;text-align:center}
.tpage .sec-head .sec-num{display:block;margin:0 0 .35rem}
.tpage .sec-h1{font-size:var(--pc-section-title)}

/* Section titles run purple, pink, cyan, yellow, green down the page.
   nth-of-type, so no page markup has to know its own number. */
.tpage .vpage section.sec:nth-of-type(1) .sec-h1{color:var(--pc-sec-1)}
.tpage .vpage section.sec:nth-of-type(2) .sec-h1{color:var(--pc-sec-2)}
.tpage .vpage section.sec:nth-of-type(3) .sec-h1{color:var(--pc-sec-3)}
.tpage .vpage section.sec:nth-of-type(4) .sec-h1{color:var(--pc-sec-4)}
.tpage .vpage section.sec:nth-of-type(5) .sec-h1{color:var(--pc-sec-5)}


/* ── TEACHING BLOCK ────────────────────────────────────────────────
   Everything the U21 renderer draws: sample, cards, decision, clues, remember. */
.tpage .u21-kicker{font-size:1.4rem;font-weight:800;letter-spacing:normal;text-transform:none;color:#fff;margin:1.8rem 0 .3rem;line-height:1.25}
.tpage .u21-kicker-sub{font-size:.98rem;color:var(--dim);margin:0 0 1rem;line-height:1.5}
.tpage .u21-sample{border-radius:16px;padding:1.1rem 1.3rem;margin:0 0 1.2rem;background:rgba(255,255,255,.035);border:1px solid rgba(255,255,255,.14)}
.tpage .u21-sample-label{font-weight:800;letter-spacing:.08em;text-transform:uppercase;font-size:.76rem;color:var(--faint);margin-bottom:.5rem}
.tpage .u21-sample-stem{font-size:1.02rem;color:#eee;line-height:1.6;white-space:pre-line}
.tpage .u21-sample-note{margin:.7rem 0 0;font-size:.9rem;color:var(--faint)}
.tpage .u21-gap{margin-bottom:1.4rem}
.tpage .u21-qblock{border-radius:16px;padding:1.3rem 1.4rem;margin:0 0 1.4rem;text-align:center;background:rgba(255,255,255,.035);border:1px solid rgba(var(--c-gold-rgb),.3)}
.tpage .u21-ask{font-weight:800;color:var(--c-gold);letter-spacing:.06em;text-transform:uppercase;font-size:.82rem;margin-bottom:.5rem}
.tpage .u21-paths{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:.9rem}
@media(max-width:600px){.tpage .u21-paths{grid-template-columns:1fr}}
.tpage .u21-path{border-radius:13px;padding:12px;font-size:1rem;border:1.5px solid;text-align:left}
.tpage .u21-path-a{display:block;font-size:.76rem;font-weight:800;letter-spacing:.04em;margin-bottom:.35rem;text-transform:uppercase}
.tpage .u21-path-body{font-weight:400;color:rgba(255,255,255,.93);line-height:1.5}
.tpage .u21-rule{margin:0}
.tpage .u21-pills{display:flex;flex-wrap:wrap;gap:7px;margin-top:.3rem}
.tpage .u21-remember{margin-bottom:1.2rem}
.tpage .u21-fact-row{display:flex;gap:11px;align-items:flex-start;padding:11px 0;border-bottom:1px solid rgba(255,255,255,.07)}
.tpage .u21-fact-row:last-child{border-bottom:none}
.tpage .u21-fact-row p{margin:0;font-size:1.02rem;color:rgba(255,255,255,.9);line-height:1.6}
.tpage .u21-fact-tag{flex:none;font-weight:900;font-size:.8rem;padding:5px 11px;border-radius:9px;margin-top:.05rem;white-space:nowrap}
.tpage .u21-trap{margin:0 0 1.2rem}
.tpage .qte-optblocks{display:block}
.tpage .qte-blocks{margin:.2rem 0 .7rem}
.tpage .qte-code{font-family:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;font-size:1.12rem;line-height:1.6;white-space:pre;overflow-x:auto;background:rgba(0,0,0,.42);border:1px solid rgba(255,255,255,.14);border-radius:10px;padding:.8rem 1rem;margin:.2rem 0 1rem;color:#dce3ee}
.tpage .qte-optcode{display:block;font-family:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;font-size:1.02rem;line-height:1.5;white-space:pre;overflow-x:auto;margin:.05rem 0}
@media(max-width:600px){.tpage .qte-optcode{font-size:.92rem;line-height:1.42}}
@media(max-width:600px){.tpage .qte-code{font-size:1rem;line-height:1.5;padding:.7rem .8rem}}
.tpage .u21-lead{font-weight:800;font-size:clamp(1.3rem,4vw,1.6rem);color:#fff;text-align:center;margin:2.2rem 0 .3rem}
.tpage .qte-lead-sub{color:var(--faint);text-align:center;max-width:52ch;margin:0 auto 1rem;font-size:.98rem}
.tpage .qte-drill{border:1px solid rgba(255,255,255,.1);border-radius:15px;padding:16px;margin-bottom:12px;background:rgba(255,255,255,.03)}
.tpage .qte-drill.done{border-color:rgba(var(--c-green-rgb),.5)}
.tpage .qte-drill-n{font-weight:800;color:var(--c-cyan);font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;margin-bottom:.4rem}
.tpage .qte-scn{font-size:1.2rem;margin:0 0 .7rem;color:#eee;line-height:1.6;white-space:pre-line}
.tpage .qte-drill-opts{display:grid;gap:.5rem;margin:.8rem 0 1rem;padding:.8rem;border:1px solid rgba(255,255,255,.12);border-radius:11px;background:rgba(255,255,255,.025)}
.tpage .qte-drill-opt{display:grid;grid-template-columns:1.5rem minmax(0,1fr);gap:.35rem;align-items:start;color:#eee;line-height:1.45}
.tpage .qte-drill-opt b{color:var(--c-cyan)}
.tpage .qte-drill-opt span{white-space:pre-wrap;overflow-wrap:anywhere}
.tpage .qte-qlab{font-weight:400;font-size:1.2rem;color:#eee;margin:.6rem 0 .45rem;line-height:1.6}
.tpage .qte-chips{display:flex;gap:9px;flex-wrap:wrap}
.tpage .qte-chips.qte-hidden{display:none}
.tpage .qte-chip{font-weight:800;font-size:.96rem;padding:9px 16px;border-radius:11px;cursor:pointer;border:1.5px solid rgba(255,255,255,.15);background:rgba(255,255,255,.03);color:#eee;font-family:inherit}
.tpage .qte-chip:hover:not(:disabled){border-color:#fff}
.tpage .qte-chip.good{border-color:var(--c-green);background:rgba(var(--c-green-rgb),.14);color:var(--c-green)}
.tpage .qte-chip.bad{border-color:var(--c-orange);background:rgba(var(--c-orange-rgb),.14);color:var(--c-orange)}
.tpage .qte-chip:disabled{cursor:default}
.tpage .qte-fb{margin-top:11px;font-size:.95rem;border-radius:11px;padding:11px 14px;display:none}
.tpage .qte-fb.show{display:block}
.tpage .qte-fb.win{background:rgba(var(--c-green-rgb),.1);border:1px solid rgba(var(--c-green-rgb),.4);color:var(--c-green)}
.tpage .qte-fb.nudge{background:rgba(var(--c-orange-rgb),.1);border:1px solid rgba(var(--c-orange-rgb),.4);color:var(--c-orange)}

/* walkthrough */
.tpage .qte-wt{padding:1.05rem 0 1.15rem;margin:0;border:0;border-top:1px solid rgba(255,255,255,.11);background:none;transition:box-shadow .3s}
.tpage .qte-wt.flash{box-shadow:inset 2px 0 0 var(--row)}
.tpage .qte-wt-title{margin-bottom:.55rem;color:var(--row);font-family:var(--mono);font-size:.68rem;font-weight:800;letter-spacing:.11em;text-transform:uppercase}
/* ── Grid tracing (Trace it, unit 1) ──────────────────────────────
   The grid is drawn as SVG by the engine and takes the section's hue
   from the ramp, so it belongs to whatever section it opens from. Edges
   stay grey like every other border on the site; the robot and the
   chosen answer are the only marks that carry colour. */
.tpage .qte-grid{display:block;max-width:100%;height:auto}
.tpage .qte-tracetop{display:flex;gap:1.4rem;flex-wrap:wrap;align-items:flex-start;margin-bottom:1rem}
.tpage .qte-gridcap{display:block;font-size:.66rem;font-weight:800;letter-spacing:.1em;
  text-transform:uppercase;color:var(--hue-3,rgba(255,255,255,.6));margin-bottom:.4rem}
.tpage .qte-cmds{list-style:none;counter-reset:cmd;padding:0;margin:0}
.tpage .qte-cmds li{counter-increment:cmd;position:relative;padding-left:2rem;margin:.35rem 0;
  font-family:var(--mono,monospace);font-size:.92rem;color:#eee}
.tpage .qte-cmds li::before{content:counter(cmd);position:absolute;left:0;top:.05em;
  width:1.3rem;height:1.3rem;border-radius:50%;display:grid;place-items:center;
  font-size:.68rem;font-weight:800;color:var(--hue);border:1px solid rgba(var(--hue-rgb),.38)}
/* the answer grid: click a square to place the robot, click it again to turn */
.tpage .qte-answergrid{display:flex;gap:1.2rem;flex-wrap:wrap;align-items:center}
.tpage .qte-pickgrid{cursor:pointer;border-radius:10px;transition:box-shadow .16s}
.tpage .qte-cell{fill:rgba(255,255,255,.012);stroke:rgba(var(--hue-rgb),.38);stroke-width:1;
  transition:fill .12s}
.tpage .qte-pickgrid:not(.right):not(.wrong) .qte-cell:hover{fill:rgba(255,255,255,.09)}
.tpage .qte-cell.on{fill:rgba(255,255,255,.06)}
.tpage .qte-pickgrid.right .qte-cell.on{fill:rgba(var(--c-green-rgb),.16)}
.tpage .qte-pickgrid.wrong .qte-cell.on{fill:rgba(255,255,255,.03)}
.tpage .qte-picknote{flex:1 1 12rem;font-size:.9rem;line-height:1.5;
  color:rgba(255,255,255,.76);display:flex;flex-direction:column;
  align-items:flex-start;gap:.7rem;max-width:16rem}
.tpage .qte-tracecheck:disabled{opacity:.4;cursor:default}
/* the streak line — the only number on screen, so it carries the hue */
.tpage .qte-streak{display:flex;gap:1.2rem;align-items:baseline;margin:0 0 1rem;
  font-size:.72rem;font-weight:800;letter-spacing:.1em;text-transform:uppercase}
.tpage .qte-streaknow{color:var(--hue)}
.tpage .qte-streakbest,.tpage .qte-tracecount{color:var(--hue)}
/* "12 of 25" over a practice question. Same register as the streak line
   above — small, uppercase, the section hue — because it is the same
   kind of thing: a number telling you where you are. The lap note is
   deliberately quieter than the count; it is reassurance, not a score. */
.tpage .qte-pvcount{margin:0 0 .9rem;font-size:.72rem;font-weight:800;
  letter-spacing:.1em;text-transform:uppercase;color:var(--hue)}
.tpage .qte-pvlap{margin-left:.5rem;font-weight:600;letter-spacing:.04em;
  text-transform:none;color:var(--faint)}
.tpage .qte-tracefb{min-height:1.4em;margin-top:.5rem}
.tpage .qte-trace{margin:0 0 2.4rem}

.tpage .qte-wq{font-size:1.2rem;margin:.1rem 0 .8rem;color:#eee;line-height:1.6;white-space:pre-line}
.tpage .qte-wopts{display:flex;flex-direction:column;gap:.45rem;margin:.75rem 0}
.tpage .qte-wopt{padding:.65rem .8rem;border-radius:9px;border:1px solid rgba(255,255,255,.12);font-size:1rem;line-height:1.45;color:rgba(255,255,255,.92);background:rgba(255,255,255,.018)}
.tpage .qte-wopt.hit{border-color:var(--c-green);background:rgba(var(--c-green-rgb),.1);color:#fff;font-weight:700}
.tpage .qte-wstep{display:flex;gap:.7rem;align-items:flex-start;margin-top:.65rem;padding:.7rem .8rem;border-radius:9px;background:rgba(var(--row-rgb),.045);border-left:2px solid var(--row)}
.tpage .qte-sn{flex:none;width:23px;height:23px;border-radius:50%;background:rgba(var(--row-rgb),.16);color:var(--row);font-weight:800;display:grid;place-items:center;font-size:.72rem}
.tpage .qte-btn{font-family:inherit;font-weight:800;font-size:.9rem;padding:.52rem .9rem;border-radius:10px;cursor:pointer;border:1px solid var(--c-blue-new);background:var(--c-blue-new);color:#fff}
.tpage .qte-btn:disabled{opacity:.4;cursor:default}
.tpage .qte-mt{margin-top:.85rem}

/* Your-turn questions remain collapsible, but read as a sequence rather
   than a stack of competing cards. */
.tpage .qte-ya{margin:.55rem 0;border:1px solid rgba(255,255,255,.12);border-radius:11px;overflow:hidden;background:rgba(255,255,255,.014)}
.tpage .qte-ya.done{border-color:rgba(var(--c-green-rgb),.52)}
.tpage .qte-ya-head{display:flex;align-items:center;gap:.6rem;padding:.75rem .85rem;cursor:pointer;font-weight:800;font-size:.98rem;color:#fff}
.tpage .qte-ya-head:hover{background:rgba(255,255,255,.025)}
.tpage .qte-chk{margin-left:auto;color:var(--c-green);font-weight:900;opacity:0}
.tpage .qte-ya.done .qte-chk{opacity:1}
.tpage .qte-arw{color:rgba(255,255,255,.45);transition:transform .2s;flex:none}
.tpage .qte-ya.open .qte-arw{transform:rotate(180deg)}
.tpage .qte-ya-body{display:none;padding:0 .85rem 1rem}
.tpage .qte-ya.open .qte-ya-body{display:block}
.tpage .qte-inrow{display:flex;gap:.5rem;flex-wrap:wrap;align-items:center}
.tpage .qte-tin{flex:1;min-width:150px;font-family:inherit;font-size:1rem;padding:.55rem .75rem;border-radius:9px;border:1px solid rgba(255,255,255,.18);background:rgba(0,0,0,.22);color:#fff}
.tpage .qte-tin:focus{outline:none;border-color:var(--row)}
.tpage .qte-tin.good{border-color:var(--c-green)}
.tpage .qte-tin.bad{border-color:var(--c-orange)}
.tpage .qte-numin{width:92px;min-width:0;text-align:center;font-family:'JetBrains Mono',monospace;font-weight:700}
.tpage .qte-hintline{font-size:.88rem;color:var(--faint);margin-top:.4rem}
.tpage .qte-kfb{font-weight:800}
.tpage .qte-cn,.tpage .qte-snum{font-weight:700;color:#fff;margin:.7rem 0 .4rem;font-size:1rem;line-height:1.5}
.tpage .qte-conv{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center;margin:.3rem 0;font-size:.97rem;color:#eee}
.tpage .qte-step2{max-height:0;overflow:hidden;transition:max-height .3s ease}
.tpage .qte-step2.live{max-height:820px}
.tpage .qte-opts{display:flex;flex-direction:column;gap:.45rem;margin-top:.5rem}
.tpage .qte-opt{display:flex;gap:.6rem;align-items:flex-start;text-align:left;width:100%;font-family:inherit;font-size:1rem;line-height:1.45;padding:.65rem .75rem;border-radius:9px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.018);color:#eee;cursor:pointer}
.tpage .qte-opt:hover:not(:disabled){border-color:rgba(var(--row-rgb),.62);background:rgba(var(--row-rgb),.05)}
.tpage .qte-ltr{flex:none;width:22px;height:22px;border-radius:6px;display:grid;place-items:center;font-weight:800;font-size:.75rem;background:rgba(255,255,255,.09);color:rgba(255,255,255,.62)}
.tpage .qte-opt.correct{border-color:var(--c-green);background:rgba(var(--c-green-rgb),.1);color:#fff}
.tpage .qte-opt.correct .qte-ltr{background:var(--c-green);color:#04231a}
.tpage .qte-opt.wrong{border-color:var(--c-orange);background:rgba(var(--c-orange-rgb),.1);color:#fff}
.tpage .qte-opt.wrong .qte-ltr{background:var(--c-orange);color:#3d1c00}
.tpage .qte-opt:disabled{cursor:default}
.tpage .u21-take{margin-left:.5rem;font-family:inherit;font-weight:800;font-size:.88rem;padding:.35rem .8rem;border-radius:9px;cursor:pointer;border:1px solid var(--c-blue-new);background:transparent;color:var(--c-blue-new)}

/* ── Charts ──────────────────────────────────────────────────────────
   The SVG carries its own colours and sizing; these two rules only give
   it somewhere to sit and stop it running to the full width of a desktop
   screen, where a scatter plot becomes unreadable. */
.tpage .u21-fig, .tpage .vex-fig{margin:1rem auto;max-width:520px}
.tpage .u21-fig svg, .tpage .vex-fig svg{width:100%;height:auto;display:block}
.tpage .u21-kicker, .tpage .u21-kicker-sub, .tpage .qte-lead-sub{text-align:center}
.tpage .u21-kicker-sub, .tpage .qte-lead-sub{margin-left:auto;margin-right:auto;max-width:72ch}

/* Every title bigger than the words underneath it. These four were the
   ones that were not: the tag on a remember card, the question heading a
   decision block, the label on a path, and the fold titles. Eyebrows —
   SECTION n, WHAT THIS QUESTION LOOKS LIKE — are labels, not titles, and
   deliberately stay small. */
.tpage .u21-fact-tag{font-size:1.06rem !important}
.tpage .u21-ask{font-size:1.12rem !important;letter-spacing:.02em;text-transform:none}
.tpage .u21-path-a{font-size:1.04rem !important;font-weight:800;text-transform:none;letter-spacing:0}
.tpage .u21-sample-stem, .tpage .u21-fact-row p, .tpage .u21-path-body{font-size:var(--pc-body)}
.tpage .vpage .u21-path{padding:11px 12px}
.tpage .vpage .u21-path-a{font-size:1.14rem !important;line-height:1.2}


/* ── BOXES ─────────────────────────────────────────────────────────
   The reusable boxes the teaching block fills: cards, compare columns, callouts. */
.tpage .compare-box h4{font-size:1.16rem;margin-bottom:.5rem}

/* Three boxes go side by side, three across — they hold two lines each and
   were being laid out two-then-one, which left a hole in the row and
   dropped the third box below the fold for no reason. The paths grid was
   hardcoded to two columns at every width; it now follows the number of
   paths. Boxes shrink a little to make room. */
@media (min-width:820px){.tpage .vpage .card-grid.three{grid-template-columns:repeat(3,1fr) !important}.tpage .vpage .u21-paths.n3{grid-template-columns:repeat(3,1fr)}.tpage .vpage .u21-paths.n4{grid-template-columns:repeat(4,1fr)}.tpage .vpage .u21-paths.n5{grid-template-columns:repeat(3,1fr)}}
.tpage .vpage .card-grid.three .ccard{padding:var(--pc-box-pad)}
.tpage .vpage .card-grid.three .cc-body{font-size:var(--pc-box-body);line-height:1.5}

/* Box titles are 2-4 words, so they can be big. */
.tpage .vpage .ccard .cc-title{font-size:var(--pc-box-title) !important;line-height:1.2;
  letter-spacing:-.01em;margin-bottom:.45rem}


/* ── FOLDS ─────────────────────────────────────────────────────────
   The three collapsible folds under each section, in their fixed colour order. */

/* Collapsible parts. The teaching stays open; the worked examples and the
   practice fold away, so the whole concept fits on one screen as a contents
   list you open into. Native <details>, so it still works with no JS and the
   keyboard gets it for free. */
.tpage .acc{border:1px solid rgba(255,255,255,.12);border-radius:12px;background:rgba(255,255,255,.02);
  margin:.5rem 0 0;overflow:hidden}
.tpage .acc+.acc{margin-top:.7rem}
.tpage .acc>summary{list-style:none;cursor:pointer;padding:.7rem .9rem;display:flex;align-items:center;
  gap:.7rem;user-select:none}
.tpage .acc>summary::-webkit-details-marker{display:none}
.tpage .acc>summary:hover{background:rgba(255,255,255,.04)}
.tpage .acc .acc-caret{flex:0 0 auto;width:19px;height:19px;border-radius:6px;display:grid;place-items:center;
  background:rgba(255,255,255,.08);color:rgba(255,255,255,.6);font-size:.64rem;transition:transform .16s,background .16s,color .16s}
.tpage .acc[open] .acc-caret{transform:rotate(90deg);background:rgba(255,255,255,.18);color:#fff}
.tpage .acc .acc-t{font-size:.88rem;font-weight:700;color:#fff}
.tpage .acc .acc-n{font-family:var(--mono);font-size:.64rem;color:rgba(255,255,255,.42);margin-left:auto;
  white-space:nowrap}
.tpage .acc.done .acc-n{color:var(--c-green)}
.tpage .acc .acc-body{padding:.2rem 1.1rem 1.3rem}
@media(max-width:640px){.tpage .acc .acc-body{padding:.2rem .7rem 1.1rem}}

/* ── THE CARD ─────────────────────────────────────────────────────
   The accordion set is ONE card, and each row is a divider inside it —
   the index-card box, not five loose bars. The coloured tab down the
   left of each row is part of the card rather than an icon sitting on
   it, and it carries the row's number.

   Numbering is a pure CSS counter, so rows renumber themselves if any
   are added, removed or reordered — nothing to keep in sync.

   Colour is one hue per row, walked down the brand palette, green held
   back for Prove it. Six hues; a page tops out at six non-exam rows.

   Only one row opens at a time — see the name="qte" attribute the
   engine now puts on each <details>. */
/* ── UNIT 2 REBUILD: ONE READING RAIL ───────────────────────────────
   The hero is the page entrance, so it may be centred. From the first
   section onward, every teaching object shares one 760px rail and reads
   from the left edge. This is easier to scan, especially when a student
   returns from an interaction or a practice modal. */
/* THE RAIL IS FLUSH LEFT, NOT CENTRED (22 Aug 2026). It used to be
   `max-width:760px; margin:auto`, which centres a 760 rail inside an 860
   section — so unit 2's section TITLES sat 50px right of their own
   prose, which the [class*="-lead"] rule had already pulled flush left.
   A title indented from the paragraph under it is the "left alignment
   hell" in unit 2. One edge for everything; see the block below. */
.tpage.u21-rebuild .tb-section-header,
.tpage.u22-rebuild .tb-section-header,
.tpage.u23-rebuild .tb-section-header,
.tpage.u24-rebuild .tb-section-header{
  max-width:none;
  margin-left:0;
  margin-right:0;
}
.tpage.u21-rebuild .u21-lead,
.tpage.u22-rebuild .u22-lead,
.tpage.u23-rebuild .u23-lead,
.tpage.u24-rebuild .u24-lead{
  text-align:left;
}
.tpage.u21-rebuild .u21-formula{
  text-align:left;
}
.tpage.u21-rebuild .u21-lab-top{
  align-items:flex-start;
  justify-content:flex-start;
  flex-direction:column;
}
/* ── QUESTION-TYPE LAUNCHERS AND MODALS ───────────────────────────
   A launcher is a quiet hand-off from the lesson to focused practice.
   It is not another card system. The modal uses the same dark ground as
   the lesson, with one accent colour used only to orient the student. */
.tpage .qte-types{position:relative;max-width:760px;margin:1rem auto 0;background:none;border:0;overflow:visible}
.tpage .qte-types.qte-inline{margin:1rem auto 1.7rem}
.tpage .qte-row+.qte-row{margin-top:.55rem}
.tpage .qte-open{position:relative;display:flex;align-items:center;gap:.8rem;width:100%;
  padding:.78rem .85rem .78rem 1.55rem;border:1px solid rgba(255,255,255,.16);
  border-radius:12px;background:rgba(255,255,255,.018);text-align:left;cursor:pointer;
  font-family:inherit;color:#fff;transition:border-color .14s,background .14s,transform .14s}
.tpage .qte-open:hover{border-color:rgba(var(--row-rgb),.62);background:rgba(var(--row-rgb),.055);transform:translateY(-1px)}
.tpage .qte-open:focus-visible{outline:2px solid var(--row);outline-offset:2px}
.tpage .qte-flag{position:absolute;left:.7rem;top:50%;width:.38rem;height:.38rem;transform:translateY(-50%);
  border-radius:50%;background:var(--row);box-shadow:0 0 11px rgba(var(--row-rgb),.45)}
.tpage .qte-open-txt{flex:1;min-width:0}
.tpage .qte-row .qte-t{display:block;font-size:1rem;font-weight:800;line-height:1.25;color:#fff;letter-spacing:-.01em}
.tpage .qte-row .qte-n{display:block;font-size:.82rem;line-height:1.42;color:rgba(255,255,255,.58);margin-top:.12rem}
.tpage .qte-go{flex:none;border:1px solid rgba(var(--row-rgb),.45);border-radius:999px;
  padding:.3rem .62rem;color:var(--row);font-family:var(--mono);font-size:.61rem;font-weight:800;
  letter-spacing:.1em;text-transform:uppercase;transition:background .14s,color .14s}
.tpage .qte-open:hover .qte-go{background:var(--row);border-color:var(--row);color:#fff}
.tpage .qte-row.done .qte-t{opacity:.62}
.tpage .qte-row.done .qte-n{color:rgba(255,255,255,.34)}

/* The modal is a focused workspace, not a floating dashboard. */
.tpage .qte-modal{width:min(940px,94vw);max-height:90vh;margin:auto;padding:0;overflow:hidden;
  border:1px solid rgba(var(--row-rgb),.3);border-radius:18px;background:#0b0d12;color:#fff;
  box-shadow:0 30px 90px rgba(0,0,0,.74)}
.tpage .qte-modal::backdrop{background:rgba(4,4,8,.82);backdrop-filter:blur(4px)}
.tpage .qte-modal-head{display:flex;align-items:center;gap:1rem;padding:.9rem 1.15rem;
  border-bottom:1px solid rgba(255,255,255,.11);background:linear-gradient(90deg,rgba(var(--row-rgb),.1),transparent 42%)}
.tpage .qte-modal-t{flex:1;min-width:0;margin:0;font-size:1.1rem;font-weight:800;line-height:1.25;color:#fff;letter-spacing:-.01em}
.tpage .qte-modal-t::before{content:'PRACTICE';display:block;margin-bottom:.2rem;color:var(--row);
  font-family:var(--mono);font-size:.6rem;font-weight:800;letter-spacing:.13em}
.tpage .qte-close{flex:none;font-family:var(--mono);font-size:.62rem;font-weight:800;letter-spacing:.1em;
  text-transform:uppercase;cursor:pointer;color:rgba(255,255,255,.81);background:transparent;
  border:1px solid rgba(255,255,255,.16);border-radius:999px;padding:.42rem .7rem;transition:all .14s}
.tpage .qte-close:hover{background:rgba(var(--row-rgb),.14);border-color:var(--row);color:#fff}
.tpage .qte-modal-body{padding:1.15rem 1.25rem 1.6rem;overflow-y:auto;max-height:calc(90vh - 4.15rem)}
.tpage .qte-modal-body .toolwrap iframe{height:min(68vh,760px)}

/* one hue per row, green reserved for the exam */
.tpage .qte-row{--row:var(--c-cyan);--row-rgb:var(--c-cyan-rgb)}
/* A row that lives INSIDE a section is part of that section, so it wears
   the section's colour — Robin: "aren't they supposed to always be the
   same color as their section?" The six fixed row hues below only apply
   to rows still sitting in the shared mount (units 6-7 pages). */
.tpage .tb-section .qte-row,
.tpage .tb-section .qte-row[data-qtype]{--row:var(--hue);--row-rgb:var(--hue-rgb)}
.tpage .qte-row[data-qtype="0"]{--row:var(--c-cyan);--row-rgb:var(--c-cyan-rgb)}
.tpage .qte-row[data-qtype="1"]{--row:var(--c-purple);--row-rgb:var(--c-purple-rgb)}
.tpage .qte-row[data-qtype="2"]{--row:var(--c-gold);--row-rgb:var(--c-gold-rgb)}
.tpage .qte-row[data-qtype="3"]{--row:var(--c-pink);--row-rgb:var(--c-pink-rgb)}
.tpage .qte-row[data-qtype="4"]{--row:var(--c-blue-new);--row-rgb:var(--c-blue-new-rgb)}
.tpage .qte-row[data-qtype="5"]{--row:var(--c-orange);--row-rgb:var(--c-orange-rgb)}
.tpage #qte-trace{--row:var(--c-blue-new);--row-rgb:var(--c-blue-new-rgb)}
.tpage #qte-code{--row:var(--c-orange);--row-rgb:var(--c-orange-rgb)}
.tpage #qte-prove{--row:var(--c-green);--row-rgb:var(--c-green-rgb)}
.tpage #qte-prove .qte-t::before{content:'THE EXAM';display:block;
  font-family:var(--mono);font-size:.6rem;letter-spacing:.14em;font-weight:800;
  color:rgba(var(--c-green-rgb),.7);margin-bottom:.2rem}

@media(max-width:640px){
  .tpage .qte-open{padding:.9rem .8rem .9rem 2.5rem;gap:.6rem}
  .tpage .qte-flag{width:1.7rem}
  .tpage .qte-row .qte-t{font-size:1.02rem}
  .tpage .qte-go{display:none}
  .tpage .qte-modal{width:100vw;max-width:100vw;max-height:100vh;border-radius:0;border:0}
  .tpage .qte-modal-body{padding:1rem .8rem 1.4rem;max-height:calc(100vh - 4.2rem)}
  .tpage .qte-modal-body .toolwrap iframe{height:72vh}
}

/* Accordions look like the qte-tab buttons inside them — coloured outline
   + coloured text — but that look stays fixed whether open or closed, no
   solid-fill flip on open. Cycles cyan/gold/pink every 3 so Trace it/Code
   it/Prove it keep going. */
.tpage .acc.c1{border-color:rgba(var(--c-cyan-rgb),.35)}
.tpage .acc.c1 .acc-t{color:var(--c-cyan)}
.tpage .acc.c1 .acc-caret{background:rgba(var(--c-cyan-rgb),.16);color:var(--c-cyan)}

.tpage .acc.c2{border-color:rgba(var(--c-gold-rgb),.35)}
.tpage .acc.c2 .acc-t{color:var(--c-gold)}
.tpage .acc.c2 .acc-caret{background:rgba(var(--c-gold-rgb),.16);color:var(--c-gold)}

.tpage .acc.c3{border-color:rgba(var(--c-pink-rgb),.35)}
.tpage .acc.c3 .acc-t{color:var(--c-pink)}
.tpage .acc.c3 .acc-caret{background:rgba(var(--c-pink-rgb),.16);color:var(--c-pink)}

/* QType Engine — the 3-tab switcher inside each question-type accordion
   (qtype-engine.js). Reuses .codetabs/.codetab verbatim for the tab row;
   this is the one new rule needed, for the tab-panel show/hide itself. */
.tpage .qte-panel{display:none}
.tpage .qte-panel.on{display:block}
/* Each of the 3 tabs keeps its own colour identity, on or off — matches
   the old fold colours: cyan = watch me, gold = your go, pink = practise. */
.tpage .qte-tab[data-tab="ln"]{--tab:var(--c-cyan); --tab-rgb:var(--c-cyan-rgb)}
.tpage .qte-tab[data-tab="dr"]{--tab:var(--c-gold); --tab-rgb:var(--c-gold-rgb)}

/* The modal keeps the old learning sequence, but its headings should
   guide the eye rather than become a second page hero. */
.tpage .qte-foldhead{margin:1.45rem 0 .9rem;padding-left:.9rem;border-left:2px solid var(--row);text-align:left}
.tpage .qte-foldhead h3{margin:0 0 .22rem;font-size:1.25rem;line-height:1.25;font-weight:800;color:#fff;letter-spacing:-.01em}
.tpage .qte-foldhead p{max-width:62ch;margin:0;font-size:.94rem;line-height:1.5;color:rgba(255,255,255,.64)}
.tpage .qte-panel[data-panel="ln"] > .qte-foldhead:first-child{margin-top:.25rem}
.tpage .tb-section .qte-foldhead h3{color:#fff!important}

/* "The Skills We Need" — the header 2.1 carried by hand, now emitted for
   every topic. Same look: white with the purple wrapping words. */
.tpage .qte-skillhead{margin:2.4rem 0 1.4rem;text-align:center}
.tpage .qte-skillhead h3,
.tpage .tb-section .qte-skillhead h3{
  font-family:'Outfit',sans-serif;font-weight:900;color:#fff!important;
  line-height:1.15;letter-spacing:-.01em;font-size:clamp(1.25rem,3.4vw,1.8rem);margin:0;
}
.tpage .qte-skillhead h3 span{color:var(--c-purple)}
.tpage .qte-skillhead p{
  font-size:1.02rem;line-height:1.6;color:rgba(255,255,255,.65);
  margin:.6rem auto 0;max-width:64ch;
}

/* Trace it / Code it / Prove it are now their own accordions too — trim
   the exam CTA's page-level top margin since it's nested in an acc-body. */
.tpage .acc-body .examcta{margin-top:0}


/* ── TABLES AND CHARTS ─────────────────────────────────────────────
   Data grids and the inline SVG charts, in the page and in the exam. */

/* data table (2.3's "read the table first" archetype) — sits above a qs/quiz stem */
.tpage .t-wrap{overflow-x:auto;border:1px solid rgba(255,255,255,.12);border-radius:14px;margin:0 0 1rem;background:rgba(255,255,255,.02)}
.tpage .t-grid{width:100%;border-collapse:collapse;font-size:.9rem}
.tpage .t-grid th.t-head{background:rgba(var(--c-cyan-rgb),.14);color:#fff;font-weight:800;text-align:center;padding:.6rem .7rem;white-space:normal;line-height:1.3;border-bottom:1px solid rgba(255,255,255,.14)}
.tpage .t-grid th.t-head:first-child{text-align:left}
.tpage .t-grid td.t-cell{padding:.55rem .7rem;text-align:center;color:#eee;border-bottom:1px solid rgba(255,255,255,.06);white-space:nowrap}
.tpage .t-grid tr.t-row:last-child td.t-cell{border-bottom:none}
.tpage .t-grid td.t-cell-label{text-align:left;color:#fff;font-weight:600}
.tpage .t-grid tr.t-row:nth-child(even){background:rgba(255,255,255,.025)}
.tpage .t-grid tr.t-row:hover{background:rgba(var(--c-cyan-rgb),.08)}

/* The exam sheet is fixed-position and lives outside .thub, so the site's
   table rules never reach it. Restate them here, scoped to the modal. */
.tpage .vex .qtable-wrap{overflow-x:auto;margin:0 0 .9rem}
/* Un-scoped from .vex on 11 Aug 2026. These rules only ever applied inside
   the exam renderer, so a table written into a teaching page came out with
   no borders and no padding — the header row ran into itself. A table is a
   table wherever it sits. */
.tpage .lz-tbl{border-collapse:collapse;font-size:.92rem;min-width:100%}
.tpage .lz-tbl th, .tpage .lz-tbl td{border:1px solid rgba(255,255,255,.14);
  padding:.45rem .65rem;text-align:left;color:rgba(255,255,255,.78)}
.tpage .lz-tbl th{color:#fff;background:rgba(255,255,255,.05);font-weight:700}
.tpage .tb-section .qtable-wrap{margin:.9rem 0 1.1rem;overflow-x:auto}


/* ── CODE ──────────────────────────────────────────────────────────
   AP pseudocode blocks and the block renderer. */
.tpage .qte-optblocks .apblocks{font-size:.84rem}
.tpage .qte-optblocks .block-control, .tpage .qte-optblocks .block-procedure{margin-right:0}
.tpage .qte-blocks .apblocks{font-size:.88rem}


/* ── TOOLS ─────────────────────────────────────────────────────────
   The Trace It / Code It / Play With It strip and its iframe. */
.tpage .toolwrap{border:1px solid rgba(255,255,255,.12);border-radius:14px;overflow:hidden;background:rgba(0,0,0,.25)}
.tpage .toolwrap iframe{display:block;width:100%;height:760px;border:0}
.tpage .codetabs{display:flex;gap:.4rem;margin:0 0 .7rem}
.tpage .codetab{padding:.45rem .9rem;border-radius:8px;border:1px solid rgba(255,255,255,.14);
  background:rgba(255,255,255,.04);color:rgba(255,255,255,.76);font-size:.88rem;cursor:pointer;font-family:inherit}
/* NEAR-BLACK ON A BRIGHT FILL, IN EVERY THEME (15 Sep 2026). Robin, on
   the grid quiz: "I have cyan buttons and white text." Measured across
   both notebooks and every theme, white on --c-cyan runs 1.21 to 2.35;
   near-black runs 8.41 to 16.39. The fill is right - the text colour
   was simply never asked to follow it.

   The noir block further down already fixed exactly these two, for
   noir only, on the same reasoning. The bug was never noir's: cyan is
   the LIGHT end of the palette in every theme we have, so every theme
   had it and only noir got looked at. Fixed at the base now, which
   makes that override redundant rather than wrong. */
.tpage .codetab.on{background:var(--c-cyan);border-color:var(--c-cyan);color:#0a0a0f;font-weight:700}
@media(max-width:640px){.tpage .toolwrap iframe{height:520px}.tpage .vex-sheet{padding:16px 14px 22px}}


/* ── PRACTICE EXAM ─────────────────────────────────────────────────
   The full-screen exam sheet. It is fixed-position, so it restates a few rules. */
/* NO BOX. Robin, 13 Sep 2026: "remove the bg and border around this
   site-wide." The exam card was the last panel left on a page whose own
   standard says sections are boxless and end on a pill — it had a cyan
   ring, a purple radial wash, an inset highlight and a drop shadow, all
   drawing a container around four things that were already centred.
   The kicker, the title, the sub-line and the green pill say "this is
   the door" on their own; the ring was saying it a second time. */
.tpage .examcta{width:min(calc(100% - 2rem),760px);box-sizing:border-box;margin:4rem auto 2.2rem;padding:clamp(1.7rem,4vw,2.5rem) 1.25rem;
  border:0;text-align:center;background:none;box-shadow:none}
.tpage .examcta::before{content:"PRACTICE EXAM";display:block;margin:0 0 .6rem;color:var(--c-cyan);
  font-family:var(--mono,ui-monospace,monospace);font-size:.7rem;font-weight:800;letter-spacing:.16em}
.tpage .examcta h2{font-size:clamp(1.55rem,4vw,2.05rem);color:#fff;margin:0 0 .45rem;font-weight:800}
.tpage .examcta p{color:rgba(255,255,255,.65);margin:0 auto 1.3rem;max-width:48ch;line-height:1.5}
.tpage .exambtn{min-width:190px;padding:.78rem 1.65rem;border:1px solid var(--c-green);border-radius:999px;background:var(--c-green);color:#04231a;
  box-shadow:0 0 20px rgba(57,217,138,.18);font-weight:800;font-size:1rem;cursor:pointer;font-family:inherit;transition:transform .15s ease,box-shadow .15s ease}
.tpage .exambtn:hover{transform:translateY(-2px);box-shadow:0 0 28px rgba(57,217,138,.34)}
.tpage .exambtn:focus-visible{outline:2px solid #fff;outline-offset:3px}
/* the quiz (QUIZ_NOT_EXAM): the label above the title, and the verdict that
   appears under the options the moment a pick is judged */
.tpage .examcta[data-quiz]::before{content:"QUIZ"}
.tpage .vex-verdict{margin:.9rem 0 .35rem;font-weight:800;font-size:1.05rem}
.tpage .vex-verdict.ok{color:var(--c-green)}
.tpage .vex-verdict.no{color:var(--c-orange)}
.tpage .vex-why{margin:0 0 .9rem;color:rgba(255,255,255,.72);line-height:1.5}
.tpage .vex-opt:disabled{cursor:default}
/* the exam lock: a locked button is plainly a locked button, not a grey
   version of the live one - no fill, no hover lift, a padlock in front */
.tpage .exambtn.exambtn-locked, .tpage .exambtn[disabled]{background:transparent;color:rgba(255,255,255,.55);
  border-color:rgba(255,255,255,.28);cursor:not-allowed;box-shadow:none}
.tpage .exambtn.exambtn-locked:hover{transform:none;box-shadow:none}
.tpage .exambtn.exambtn-locked::before{content:"\1F512  ";font-size:.9em}

/* exam modal */
.tpage .vex{position:fixed;inset:0;background:rgba(4,4,8,.86);backdrop-filter:blur(6px);z-index:9000;
  display:none;overflow-y:auto;padding:24px 16px 60px}
.tpage .vex.open{display:block}
.tpage .vex-sheet{max-width:840px;margin:0 auto;background:#0d0d14;border:1px solid rgba(255,255,255,.12);
  border-radius:16px;padding:22px 24px 28px}
.tpage .vex-top{display:flex;justify-content:space-between;align-items:center;gap:1rem;margin-bottom:1.2rem}
.tpage .vex-top h2{font-size:1.4rem;margin:0;color:#fff}
.tpage .vex-x{background:rgba(255,255,255,.08);border:0;color:#fff;width:34px;height:34px;border-radius:50%;
  cursor:pointer;font-size:1rem}
.tpage .vex-prog{font-family:var(--mono);font-size:.72rem;letter-spacing:1.5px;text-transform:uppercase;
  color:rgba(255,255,255,.42);margin-bottom:.6rem}
.tpage .vex-ctx{color:rgba(255,255,255,.8);font-size:1.12rem;line-height:1.6;margin:0 0 .7rem;white-space:pre-line}
.tpage .vex-q{color:#fff;font-size:1.2rem;line-height:1.5;margin:0 0 .9rem}
.tpage .vex-two{display:inline-block;font-size:.78rem;font-weight:700;color:var(--c-gold);
  border:1px dashed var(--c-gold);border-radius:8px;padding:.25rem .6rem;margin:0 0 .8rem}
.tpage .vex-opts{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.5rem}
.tpage .vex-opt{display:flex;align-items:flex-start;gap:.65rem;width:100%;text-align:left;cursor:pointer;
  padding:.65rem .8rem;border-radius:10px;border:1px solid rgba(255,255,255,.12);
  background:rgba(255,255,255,.03);color:rgba(255,255,255,.66);font-family:inherit;font-size:.96rem}
.tpage .vex-opt:hover:not(:disabled){background:rgba(255,255,255,.07);color:#fff}
.tpage .vex-opt .l{flex:0 0 auto;width:22px;height:22px;border-radius:6px;display:grid;place-items:center;
  font-size:.7rem;font-weight:800;background:rgba(255,255,255,.09)}
.tpage .vex-opt .b{flex:1 1 auto;min-width:0}
.tpage .vex-opt.right{border-color:var(--c-green);background:rgba(var(--c-green-rgb),.1);color:var(--c-green)}
.tpage .vex-opt.wrong{border-color:var(--c-orange);background:rgba(var(--c-orange-rgb),.12);color:var(--c-orange)}
.tpage .vex-fb{margin:.9rem 0 0;padding:.8rem .9rem;border-radius:10px;font-size:.94rem;line-height:1.5}
.tpage .vex-fb.ok{background:rgba(var(--c-green-rgb),.08);border:1px solid rgba(var(--c-green-rgb),.3);color:#dff5e6}
.tpage .vex-fb.no{background:rgba(var(--c-orange-rgb),.08);border:1px solid rgba(var(--c-orange-rgb),.3);color:#ffe6d2}
.tpage .vex-next{margin-top:1rem;padding:.6rem 1.4rem;border-radius:9px;border:0;background:var(--c-cyan);
  color:#0a0a0f;font-weight:800;cursor:pointer;font-family:inherit}
.tpage .vex-done{text-align:center;padding:1.4rem 0}
.tpage .vex-score{font-size:2.63rem;font-weight:800;color:#fff;line-height:1}
.tpage .vex-done p{color:rgba(255,255,255,.65);margin:.7rem auto 1.2rem;max-width:46ch;line-height:1.5}
.tpage .vex-pt{font-size:.9rem;font-weight:700;color:var(--c-green,#39FF14);margin:0 auto 1.1rem;max-width:46ch}
.tpage .vex-pt:empty{display:none}
.tpage .vex-pt.muted{color:rgba(255,255,255,.45);font-weight:500}

/* roman-numeral lists sometimes sit in the stem, not the context */
.tpage .vex-q{white-space:pre-line}


/* ── ODDS AND ENDS ─────────────────────────────────────────────────
   States, and anything that did not belong above. Back-to-top links are
   GONE (12 Aug 2026) — Robin: "never asked for them." If one sneaks back
   in via an old template, there is deliberately no CSS to dress it.

   The removal itself left two orphaned declaration lines here, and THAT
   broke the whole site: a stray "margin:...}" at rule level makes the
   parser treat everything up to the NEXT brace as a bad selector — which
   swallowed the entire .tb-section block below, taking --tint, --edge and
   --stroke with it. Every border that read those variables fell back to
   currentColor: solid white boxes everywhere. Robin caught it within
   hours. The lesson is at the bottom of this file: lint the braces. */

/* ══ THE SECTION ═══════════════════════════════════════════════════════
   No box. A section is the run from its title to its button, and that is
   the whole definition — the title with its rule line opens it, the pill
   closes it, and the space between is the content.

   ── COLOUR, REWRITTEN 12 Aug 2026 ─────────────────────────────────────
   Robin: "restore the color love… find a way to visually separate the
   types without making it monochrome hell. The notebook is predicated
   upon bright, rainbow, neon heaven."

   He is right and this was mine. Two rules were doing the damage together:

   ONE HUE PER SECTION is gone. It sounded disciplined and it painted every
   card, kicker, bullet and bold word in a section the same single colour.
   On 2.3 the only two coloured things on the whole page were the vocab
   cards in the header — the ones that sit OUTSIDE a section and so escaped
   the rule. Everything below them was one flat cyan.

   THE ALPHA RAMP is gone too — Robin: "didn't do much." It graded marks
   at .82 / .62 / .44 of the palette colour, so almost nothing on a page
   was ever at full neon; it read as dimmed rather than as hierarchy.

   What replaces them: every section carries THREE colours, not one. The
   first is its own — the title, the rule, the button, the bold words in
   its prose — so a section is still identifiable at a glance and the
   rotation down the page still means something. Cards then cycle through
   all three, and callouts and pills take the second and third. A row of
   three cards is three different colours again.

   The triples are set per hue in ONE table below. Nothing else in this
   file names a colour, so re-pairing the whole site is editing that table
   and nothing more.
   ═══════════════════════════════════════════════════════════════════ */
.tpage .tb-section{
  /* ── THE COLOUR TABLE — the only place colours are named ──────────
     --hue-a is the section's OWN colour, the one the rotation gave it.
     --hue-b and --hue-c are its partners. Pick each triple so that no
     two of them sit next to each other on the colour wheel: they have
     to stay apart at a glance, in a row of cards, at small size.
     Re-pair the site by editing these seven lines.                   */
  --hue-a:var(--c-cyan);   --hue-a-rgb:var(--c-cyan-rgb);
  --hue-b:var(--c-pink);   --hue-b-rgb:var(--c-pink-rgb);
  --hue-c:var(--c-gold);   --hue-c-rgb:var(--c-gold-rgb);

  /* The alpha ramp is gone — Robin: "didn't do much." Its five steps
     (--hue-1 … --hue-5) are gone with it, and every mark now reads --hue
     directly, at full palette strength.

     That deletion is worth more than the brightness it bought back. The
     steps were declared HERE, on the section, so they were substituted
     here — a card further down could set --hue to pink and its ground
     would go pink while its title stayed the section's cyan, because the
     title read --hue-2, which had already been resolved. That is exactly
     what happened to 3.2's concept card. With one pair of variables an
     override is all-or-nothing and cannot half-apply. */
  --hue:var(--hue-a); --hue-rgb:var(--hue-a-rgb);

  /* How much colour an element's own edge and ground carry. A ground at
     .05 is not a coloured panel — it is the faint bloom a neon mark
     throws onto what it sits on, and it is most of why the palette reads
     as neon rather than as text that happens to be bright. Zero these
     two and the boxes go back to plain grey-on-black. */
  --tint:.055;
  --edge:.30;

  --stroke:rgba(255,255,255,.09);      /* the grey stroke, still the default */
  background:none;border:0;box-shadow:none;border-radius:0;
  padding:0;margin:0 0 3.6rem;
}

/* ── THE OLD RAMP NAMES, KEPT ALIVE ─────────────────────────────────
   Killing the alpha ramp deleted --hue-1 … --hue-5, and ~530 references
   to them still live in content HTML, unit-6 bank items, the robot-grid
   SVGs in qtype-engine and the network diagrams. 1.2's grid drew itself
   with an undefined stroke and vanished — Robin caught it within a day.

   So the names live on as aliases, declared on EVERY element rather than
   on the section. That per-element declaration is the important part: it
   re-resolves from whatever --hue the element itself is wearing, so a
   card the cycle painted pink gets pink marks — the section-level version
   of these was exactly the half-applied-override bug that flattened 3.2's
   concept card. 1-3 are the flat hue now; 4 and 5 keep a little of their
   old restraint because they are hairlines and grid strokes, not words. */
.tpage, .tpage *{
  --hue-1:var(--hue); --hue-2:var(--hue); --hue-3:var(--hue);
  --hue-4:rgba(var(--hue-rgb),.55);
  --hue-5:rgba(var(--hue-rgb),.38);
}

/* The seven triples. First colour is always the section's own, so the
   page's cyan → purple → gold → pink → blue → orange → green rotation
   still reads exactly as it did. */
.tpage .tb-section[data-hue="cyan"]{
  --hue-a:var(--c-cyan);     --hue-a-rgb:var(--c-cyan-rgb);
  --hue-b:var(--c-pink);     --hue-b-rgb:var(--c-pink-rgb);
  --hue-c:var(--c-gold);     --hue-c-rgb:var(--c-gold-rgb)}
.tpage .tb-section[data-hue="purple"]{
  --hue-a:var(--c-purple);   --hue-a-rgb:var(--c-purple-rgb);
  --hue-b:var(--c-green);    --hue-b-rgb:var(--c-green-rgb);
  --hue-c:var(--c-cyan);     --hue-c-rgb:var(--c-cyan-rgb)}
.tpage .tb-section[data-hue="gold"]{
  --hue-a:var(--c-gold);     --hue-a-rgb:var(--c-gold-rgb);
  --hue-b:var(--c-blue-new); --hue-b-rgb:var(--c-blue-new-rgb);
  --hue-c:var(--c-pink);     --hue-c-rgb:var(--c-pink-rgb)}
.tpage .tb-section[data-hue="pink"]{
  --hue-a:var(--c-pink);     --hue-a-rgb:var(--c-pink-rgb);
  --hue-b:var(--c-cyan);     --hue-b-rgb:var(--c-cyan-rgb);
  --hue-c:var(--c-green);    --hue-c-rgb:var(--c-green-rgb)}
.tpage .tb-section[data-hue="blue-new"]{
  --hue-a:var(--c-blue-new); --hue-a-rgb:var(--c-blue-new-rgb);
  --hue-b:var(--c-gold);     --hue-b-rgb:var(--c-gold-rgb);
  --hue-c:var(--c-pink);     --hue-c-rgb:var(--c-pink-rgb)}
.tpage .tb-section[data-hue="orange"]{
  --hue-a:var(--c-orange);   --hue-a-rgb:var(--c-orange-rgb);
  --hue-b:var(--c-cyan);     --hue-b-rgb:var(--c-cyan-rgb);
  --hue-c:var(--c-purple);   --hue-c-rgb:var(--c-purple-rgb)}
.tpage .tb-section[data-hue="green"]{
  --hue-a:var(--c-green);    --hue-a-rgb:var(--c-green-rgb);
  --hue-b:var(--c-purple);   --hue-b-rgb:var(--c-purple-rgb);
  --hue-c:var(--c-gold);     --hue-c-rgb:var(--c-gold-rgb)}

/* normalizeSections writes --hue / --hue-rgb inline from the rotation, and
   an inline style beats everything here — which would pin all three of a
   section's colours back to one again. The [data-hue] tables above are the
   authority now, so take the inline pair back. */
.tpage .tb-section{--hue:var(--hue-a)!important; --hue-rgb:var(--hue-a-rgb)!important}

/* ── THE CYCLE ────────────────────────────────────────────────────────
   Cards in a row take the three colours in turn, and that is what brings
   the rainbow back. It colours by POSITION, not by a class somebody
   remembered to type, so it needs no page edits anywhere — the sixty-odd
   card rows already written across units 1-4 light up as they are. */
.tpage .tb-section .ccard:nth-child(3n+2),
.tpage .tb-section .vocab-card:nth-child(3n+2),
.tpage .tb-section .capped-card:nth-child(3n+2),
.tpage .tb-section .numtext-card:nth-child(3n+2){
  --hue:var(--hue-b); --hue-rgb:var(--hue-b-rgb)}
.tpage .tb-section .ccard:nth-child(3n+3),
.tpage .tb-section .vocab-card:nth-child(3n+3),
.tpage .tb-section .capped-card:nth-child(3n+3),
.tpage .tb-section .numtext-card:nth-child(3n+3){
  --hue:var(--hue-c); --hue-rgb:var(--hue-c-rgb)}

/* A callout breaks the prose, so it takes the third colour — it has to
   stand away from both the paragraph it interrupts and the cards near it.
   Every callout in a section is the same colour on purpose: the first
   version alternated on :nth-of-type, which counts DIVS rather than
   callouts, so the colour depended on how many unrelated divs happened to
   sit above it. */
.tpage .tb-section .callout{--hue:var(--hue-c); --hue-rgb:var(--hue-c-rgb)}

/* Most of unit 3 is not cards. Those pages are two-column notes blocks and
   code panels, and cycling only cards left them exactly as flat as before —
   the sweep showed four sections on 3.5 with ONE colour between them. So
   the same treatment reaches the blocks they are actually built from. */
.tpage .tb-section .tb-columns > .tb-col:nth-child(2){
  --hue:var(--hue-b); --hue-rgb:var(--hue-b-rgb)}
.tpage .tb-section .tb-columns > .tb-col:nth-child(3){
  --hue:var(--hue-c); --hue-rgb:var(--hue-c-rgb)}

/* ── THE REBUILT PAGES JOIN THE CYCLE (26 Aug 2026) ──────────────────
   The unit-3 rebuilds (and unit 4's steps rows) came over from the old
   notebook carrying private palettes: cards under local names —
   u31-card, u32-step, u33-card, bare .card, .lab — drawn grey, and note
   boxes hard-wired to ONE colour for a whole page. The rotation could
   not reach any of it, so whole sections rendered monochrome (owner,
   26 Aug 2026: "something reverted" — this was it). These aliases hand
   every such box to the same three-colour cycle ccard rides. A page
   that wants a box out of the cycle should use ccard with a named
   colour, not a new local class. */
.tpage .tb-section .u31-card:nth-child(3n+2),
.tpage .tb-section .u32-step:nth-child(3n+2),
.tpage .tb-section .u33-card:nth-child(3n+2),
.tpage .tb-section .card:nth-child(3n+2),
.tpage .tb-section .lab:nth-child(3n+2){
  --hue:var(--hue-b); --hue-rgb:var(--hue-b-rgb)}
.tpage .tb-section .u31-card:nth-child(3n+3),
.tpage .tb-section .u32-step:nth-child(3n+3),
.tpage .tb-section .u33-card:nth-child(3n+3),
.tpage .tb-section .card:nth-child(3n+3),
.tpage .tb-section .lab:nth-child(3n+3){
  --hue:var(--hue-c); --hue-rgb:var(--hue-c-rgb)}
/* same ground and edge as ccard, opaque over near-black */
.tpage .tb-section .u31-card,
.tpage .tb-section .u32-step,
.tpage .tb-section .u33-card,
.tpage .tb-section .card,
.tpage .tb-section .lab{
  border:1px solid rgba(var(--hue-rgb),var(--edge))!important;
  background:linear-gradient(rgba(var(--hue-rgb),var(--tint)),rgba(var(--hue-rgb),var(--tint))) var(--c-ground)!important}
/* the tiny caps naming each box follow the box's own colour */
.tpage .tb-section :is(.u31-card,.u32-step,.u33-card,.card,.lab) :is(small,.eyebrow){
  color:var(--hue)!important}
/* the pages' note boxes take the callout treatment — second and third
   colour, never one hard-wired hue per page */
.tpage .tb-section :is(.u31-do,.u32-warn){
  --hue:var(--hue-b); --hue-rgb:var(--hue-b-rgb)}
.tpage .tb-section :is(.u31-takeaway,.u32-note,.u33-note,.u35-note,.u36-note,.u37-note,.u38-note,.u39-note){
  --hue:var(--hue-c); --hue-rgb:var(--hue-c-rgb)}
.tpage .tb-section :is(.u31-do,.u31-takeaway,.u32-warn,.u32-note,.u33-note,.u35-note,.u36-note,.u37-note,.u38-note,.u39-note){
  border-left-color:var(--hue)!important;
  background:linear-gradient(rgba(var(--hue-rgb),var(--tint)),rgba(var(--hue-rgb),var(--tint))) var(--c-ground)!important}

/* Two code panels side by side are nearly always a pair — BROKEN and
   FIXED, before and after. Giving the second one its own colour is the
   one place in here where the colour is doing a job rather than a
   decoration: the header strips stop being two identical labels. The
   frames stay grey either way; only the named strip changes. */
.tpage .tb-section .cb-panelgrid > *:nth-child(2){
  --hue:var(--hue-b); --hue-rgb:var(--hue-b-rgb)}

/* Numbered steps cycle, so a list of five is not five identical badges. */
.tpage .tb-section .step-row:nth-child(3n+2){
  --hue:var(--hue-b); --hue-rgb:var(--hue-b-rgb)}
.tpage .tb-section .step-row:nth-child(3n+3){
  --hue:var(--hue-c); --hue-rgb:var(--hue-c-rgb)}

/* A card that names its own colour keeps it. Somebody who typed
   "ccard gold" meant gold, and the cycle must not overrule that. */
.tpage .tb-section .ccard.cyan,   .tpage .tb-section .vocab-card.cyan-card{
  --hue:var(--c-cyan);  --hue-rgb:var(--c-cyan-rgb)}
.tpage .tb-section .ccard.gold,   .tpage .tb-section .vocab-card.gold-card{
  --hue:var(--c-gold);  --hue-rgb:var(--c-gold-rgb)}
.tpage .tb-section .ccard.pink,   .tpage .tb-section .vocab-card.pink-card{
  --hue:var(--c-pink);  --hue-rgb:var(--c-pink-rgb)}
.tpage .tb-section .ccard.green,  .tpage .tb-section .vocab-card.green-card{
  --hue:var(--c-green); --hue-rgb:var(--c-green-rgb)}
.tpage .tb-section .ccard.purple, .tpage .tb-section .vocab-card.purple-card{
  --hue:var(--c-purple);--hue-rgb:var(--c-purple-rgb)}
.tpage .tb-section .ccard.orange{
  --hue:var(--c-orange);--hue-rgb:var(--c-orange-rgb)}
@media(max-width:640px){.tpage .tb-section{margin-bottom:2.6rem}}


/* ── ONE ELEMENT, ANY COLOUR ──────────────────────────────────────────
   Element Studio's custom-colour box. The element carries two values and
   nothing else - style="--hex:#ff9900;--hex-rgb:255,153,0" - and every
   redirection happens here, so exported page markup stays readable and
   the ramp's alphas are still defined in exactly one place (just above).

   Why the slots are restated as well as the ramp: inside a section a mark
   reads --hue-N, but the same element pasted anywhere else reads its
   palette slot direct (.callout.exam-tip is gold in style.css, and no
   swatch anywhere says "gold"). Overriding only one of the two makes the
   box work on some elements and silently do nothing on others.

   All slots, one colour: a custom hex paints the WHOLE element. An element
   built to wear two colours on purpose goes monochrome while a hex is set.
   Remove the attribute and the palette is back - nothing here is lossy.
   White stays white: it is body text, not a mark.                      */
[style*="--hex"]{
  --hue:var(--hex); --hue-rgb:var(--hex-rgb);
  --hue-1:var(--hex);
  --hue-2:rgba(var(--hex-rgb),.82);
  --hue-3:rgba(var(--hex-rgb),.62);
  --hue-4:rgba(var(--hex-rgb),.44);
  --hue-5:rgba(var(--hex-rgb),.28);
  --c-cyan:var(--hex);     --c-cyan-rgb:var(--hex-rgb);
  --c-gold:var(--hex);     --c-gold-rgb:var(--hex-rgb);
  --c-pink:var(--hex);     --c-pink-rgb:var(--hex-rgb);
  --c-green:var(--hex);    --c-green-rgb:var(--hex-rgb);
  --c-purple:var(--hex);   --c-purple-rgb:var(--hex-rgb);
  --c-orange:var(--hex);   --c-orange-rgb:var(--hex-rgb);
  --c-blue:var(--hex);     --c-blue-rgb:var(--hex-rgb);
  --c-blue-new:var(--hex); --c-blue-new-rgb:var(--hex-rgb);
}

/* the title + line: the section's opening move */
.tpage .tb-section .tb-section-header{margin-bottom:1.2rem}

/* A data table inside a section. .t-grid's header band and hover tint were
   written as a fixed cyan back when tables only appeared on pages with no
   sections at all; in a gold section that band was the only cyan on screen. */
.tpage .tb-section .t-wrap{border-color:var(--stroke)!important}
.tpage .tb-section .t-grid th.t-head{background:rgba(var(--hue-rgb),.14)!important}
.tpage .tb-section .t-grid tr.t-row:hover{background:rgba(var(--hue-rgb),.08)!important}

/* Prose written straight into a section. style.css zeroes every margin, so
   two paragraphs in a row ran together as one wall of text - fine while
   every section held exactly one. Direct children only, so this cannot
   reach inside a callout, a column or an Element Studio block. */
.tpage .tb-section > p{margin:0 0 .9rem;font-size:1rem;line-height:1.7;
  color:rgba(255,255,255,.9)}
.tpage .tb-section .tb-section-label,
.tpage .tb-section .tb-section-label *{color:var(--hue)!important}
/* The rule line runs the section's three colours, in order, and fades out.
   It is the one place all three appear together, so it reads as a key to
   the section underneath it — and it is the logo's move: colour IS the
   identity, so the thing that names a section should be made of colour
   rather than merely tinted with one. */
.tpage .tb-section .tb-section-line{background:linear-gradient(90deg,
  var(--hue-a) 0%, var(--hue-b) 34%, var(--hue-c) 62%,
  rgba(var(--hue-c-rgb),.06) 100%)!important}

/* ── EDGES AND GROUNDS ───────────────────────────────────────────────
   These used to be flat grey on flat near-white, on the rule that a
   background is never coloured and a border never either. That rule is
   most of why the page went dead: it left a card with nothing but its
   title carrying any colour at all, so three cards in a row were three
   identical grey boxes with three differently-worded headings.

   They now carry their OWN colour — the one the cycle above gave them —
   at --tint on the ground and --edge on the stroke. Both are single
   numbers at the top of the section block. A ground at .055 is not a
   coloured panel; it is the bloom the mark throws onto what it sits on.
   Set --tint:0 and --edge:0 and this is exactly the old flat look.

   Since 25 Aug 2026 every tint is composed over solid var(--c-ground) (the
   gradient-plus-color trick), because the year background is no longer
   black: an open tint let the aurora bleed through box interiors and
   mix with the card's own hue. Boxes are opaque surfaces; the pattern
   lives between them. */
.tpage .tb-section .cb-pane,
.tpage .tb-section .numtext-card,
.tpage .tb-section .ccard,
.tpage .tb-section .dg-stack,
.tpage .tb-section .dgrid{
  border-color:rgba(var(--hue-rgb),var(--edge))!important;box-shadow:none!important;
  background:linear-gradient(rgba(var(--hue-rgb),var(--tint)),rgba(var(--hue-rgb),var(--tint))) var(--c-ground)!important}
.tpage .tb-section .callout{
  background:linear-gradient(rgba(var(--hue-rgb),var(--tint)),rgba(var(--hue-rgb),var(--tint))) var(--c-ground)!important;border-radius:14px;box-shadow:none;
  border:1px solid rgba(var(--hue-rgb),var(--edge))!important;
  border-left:3px solid var(--hue)!important}  /* the accent bar is a mark, not an outline */
.tpage .tb-section .vocab-card{
  background:linear-gradient(rgba(var(--hue-rgb),var(--tint)),rgba(var(--hue-rgb),var(--tint))) var(--c-ground)!important;box-shadow:none;
  border:1px solid rgba(var(--hue-rgb),var(--edge))!important;
  border-left:3px solid var(--hue)!important}
/* Code keeps its grey frame. Syntax highlighting means "what kind of word
   is this", so a coloured ring around it would start competing with the
   one place on the page where colour already carries meaning. */
.tpage .tb-section .cb-pane{
  border-color:var(--stroke)!important;background:linear-gradient(rgba(255,255,255,.022),rgba(255,255,255,.022)) var(--c-ground)!important}

/* ── MARKS: the section's hue, everywhere ────────────────────────── */
/* step 1 — the title carries the section on its own */
.tpage .tb-section h3{color:var(--hue)!important}
/* step 2 — sub-heads, and the words prose leans on */
.tpage .tb-section h4,
.tpage .tb-section .cc-title,
.tpage .tb-section .vocab-card .term,
.tpage .tb-section strong,
.tpage .tb-section b{color:var(--hue)!important}
/* step 3 — the small caps that name a thing */
.tpage .tb-section .cb-pane-label,
.tpage .tb-section .cb-pane::before,
.tpage .tb-section .numtext-label,
.tpage .tb-section .callout-head,
.tpage .tb-section .cc-label{color:var(--hue)!important}
/* step 4 — marks rather than words */
.tpage .tb-section .color-tile,
.tpage .tb-section .vocab-pill,
.tpage .tb-section .dg-arrow{color:var(--hue)!important}
.tpage .tb-section .cb-pane-label,
.tpage .tb-section .cb-pane::before{background:rgba(255,255,255,.022)!important}
.tpage .tb-section .color-tile,
.tpage .tb-section .vocab-pill,
.tpage .tb-section .dg-arrow{background:rgba(255,255,255,.03)!important;
  border-color:var(--stroke)!important}
/* numbered bullets and step counters — the ones that used to be pink no
   matter what section they were sitting in */
.tpage .tb-section ol{list-style:none;counter-reset:secli;padding-left:0}
.tpage .tb-section ol>li{counter-increment:secli;position:relative;padding-left:2rem;margin:.5rem 0}
.tpage .tb-section ol>li::before{content:counter(secli);position:absolute;left:0;top:.05em;
  width:1.35rem;height:1.35rem;border-radius:50%;display:grid;place-items:center;
  font-family:var(--mono);font-size:.72rem;font-weight:800;
  color:var(--hue);border:1px solid rgba(var(--hue-rgb),.38)}
.tpage .tb-section ul>li::marker{color:var(--hue)}
.tpage .tb-section .qte-sn,
.tpage .tb-section .st-num,
.tpage .tb-section .step-num{color:var(--hue)!important;
  border-color:rgba(var(--hue-rgb),.38)!important;background:none!important}

/* ── THE BUTTON ────────────────────────────────────────────────────
   Ends every section, and wears that section's colour — it is part of
   the section, not an exception to it. Thin outline, neutral fill, no
   bloom. Seeing one means this idea is finished. */
.tpage .tb-section .qte-types.qte-inline{
  width:min(100%,760px);background:none;border:0;box-shadow:none;overflow:visible;
  margin:1.8rem auto 0;display:flex;justify-content:flex-start}
.tpage .tb-section .qte-types.qte-inline .qte-row{border:0;width:auto}
.tpage .tb-section .qte-types.qte-inline .qte-flag,
.tpage .tb-section .qte-types.qte-inline .qte-n,
.tpage .tb-section .qte-types.qte-inline .qte-go{display:none}
.tpage .tb-section .qte-types.qte-inline .qte-open{
  width:auto;padding:.7rem 1.9rem;border-radius:999px;
  border:1px solid var(--hue);background:rgba(255,255,255,.025);
  color:var(--hue);box-shadow:none;
  transition:background .16s,color .16s}
.tpage .tb-section .qte-types.qte-inline .qte-open:hover{background:rgba(255,255,255,.075)}
.tpage .tb-section .qte-types.qte-inline .qte-open-txt{flex:none}
.tpage .tb-section .qte-types.qte-inline .qte-t{
  font-size:.95rem;font-weight:800;letter-spacing:.02em;color:inherit}

/* ── THE THREE TOOLS AT THE FOOT ───────────────────────────────────
   Trace it / Code it / Prove it belong to the page, not to a section, so
   they get their own closing header and sit in a row beneath it — the
   same title-then-buttons shape as everything above, which is what stops
   them hanging loose at the bottom of the page. White, because they are
   not any one section's. */
.tpage .vpage .qte-types{background:none;border:0;box-shadow:none;overflow:visible;
  display:flex;flex-wrap:wrap;justify-content:center;gap:.8rem;margin:.4rem 0 1rem}
.tpage .vpage .qte-types::before{content:"Practice all of it";flex:0 0 100%;
  font-family:'Outfit',sans-serif;font-size:.72rem;font-weight:800;letter-spacing:.16em;
  text-transform:uppercase;color:rgba(255,255,255,.42);text-align:center;
  padding-bottom:.9rem;margin-bottom:.2rem;border-bottom:1px solid rgba(255,255,255,.10)}
.tpage .vpage .qte-types .qte-row{border:0;width:auto}
.tpage .vpage .qte-types .qte-flag,
.tpage .vpage .qte-types .qte-n,
.tpage .vpage .qte-types .qte-go{display:none}
.tpage .vpage .qte-types .qte-open{
  width:auto;padding:.7rem 1.7rem;border-radius:999px;
  border:1px solid rgba(255,255,255,.34);background:rgba(255,255,255,.025);
  color:rgba(255,255,255,.9);box-shadow:none;transition:background .16s,border-color .16s}
.tpage .vpage .qte-types .qte-open:hover{background:rgba(255,255,255,.08);
  border-color:rgba(255,255,255,.6);color:#fff}
.tpage .vpage .qte-types .qte-open-txt{flex:none}
.tpage .vpage .qte-types .qte-t{font-size:.92rem;font-weight:800;letter-spacing:.02em;color:inherit}
.tpage .vpage .qte-types .qte-t::before{content:none!important}

/* text mode: the same panel, its source shown plainly. The chips are the
   only thing that changes — frame, label and line rhythm stay put, so a
   page does not reflow when you flip the whole site to text. */
pre.cb-plain{margin:0;padding:.85rem 1rem 1rem;
  font-family:'JetBrains Mono',Monaco,Menlo,Consolas,ui-monospace,monospace;
  font-size:.95rem;line-height:1.85;color:#e8ecf3;overflow-x:auto;
  background:none;border:0;white-space:normal}

/* LINE NUMBERS ARE A GUTTER, NEVER TEXT — Robin, 16 Aug 2026: "some have
   line nums". Forty questions had "Line 01: " typed into the front of the
   code itself, so the numbers were syntax-highlighted as if they were
   pseudocode, they broke the blocks view, and every other panel on the
   site had none. Stripped from the source and drawn here instead, off a
   CSS counter, so a student can never select or mis-read them as code and
   a panel looks the same whether it is numbered or not. */
pre.cb-plain.lineno{counter-reset:psline;padding-left:3.5rem}
pre.cb-plain.lineno>span{position:relative;counter-increment:psline}
pre.cb-plain.lineno>span::before{content:counter(psline);
  position:absolute;left:-2.9rem;width:2rem;text-align:right;
  color:rgba(255,255,255,.28);font-variant-numeric:tabular-nums;
  -webkit-user-select:none;user-select:none;pointer-events:none}

/* A section with no title is a continuation of the one above (see
   normalizeSections). Its empty header would otherwise draw a full-width
   rule under nothing, which reads as a new section starting untitled. */
.tpage .tb-section[data-continues] > .tb-section-header{display:none}
.tpage .tb-section[data-continues]{margin-top:-2.2rem}

/* The flip button, sitting in the panel's header strip. .cb-pane is already
   a containing block for it (container-type:inline-size implies
   contain:layout) — say it out loud rather than depend on that. */
.tpage .cb-pane{position:relative}
/* THE PANEL'S FLIP. The button is part of the box the one renderer builds
   now (.toggle-view-btn.is-icon), not a separate .cb-mode appended to the
   pane — but a teaching panel already carries its own label strip above the
   code, so a second header row underneath it would be an empty bar with an
   icon floating in it. The header is lifted out of the flow and parked in
   the corner instead, which is exactly where the old button sat. */
.tpage .cb-pane .q-code-area{position:relative;border:0;background:none;margin:0}
.tpage .cb-pane .q-code-area > .q-code-header{
  position:absolute;top:.2rem;right:.4rem;z-index:2;
  padding:0;border:0;background:none;min-height:0}
.tpage .cb-pane .q-code-area > .q-code-header .q-code-label{display:none}
.tpage .cb-pane .toggle-view-btn.is-icon{
  display:grid;place-items:center;width:27px;height:23px;padding:0;
  background:none;border:0;border-radius:6px;cursor:pointer;
  color:var(--hue,rgba(255,255,255,.55));opacity:.4;
  transition:opacity .14s,background .14s}
.tpage .cb-pane .toggle-view-btn.is-icon:hover{opacity:1;background:rgba(255,255,255,.07)}
.tpage .cb-pane .toggle-view-btn.is-icon:focus-visible{opacity:1;
  outline:1px solid var(--hue,rgba(255,255,255,.5));outline-offset:1px}
.tpage .cb-pane .toggle-view-btn.is-icon svg{display:block;pointer-events:none}

/* Scoped to the CLASS, not to .cb-pane inside .tpage: the same text view is
   now used by questions, quizzes and exams, and the whole point is that code
   looks identical wherever a student meets it (owner, 11 Aug 2026).

   Each line is its own block, so the two kinds can behave differently.
   Code must not wrap — a line broken in the middle is a different program —
   so it keeps white-space:pre and the panel scrolls if it has to. */
pre.cb-plain .cb-txt{display:block;white-space:pre}

/* A comment is a sentence in English, and sentences wrap. Grey and italic
   so the eye can skip them, which the chips did for free in blocks view;
   the -2ch pulls the first line back out of the padding, so a comment
   starts level with its code and its runover sits two in from it. */
pre.cb-plain .cb-cmt{display:block;white-space:pre-wrap;
  text-indent:-2ch;color:rgba(255,255,255,.36);font-style:italic}

/* Syntax colours, same tokenizer and same roles as the editor and the
   static teach-blocks — a keyword is a keyword everywhere on the site.
   This is the one place a section's single-hue rule is off: colour here
   carries meaning (what kind of thing this word is), so it can't be
   recoloured to match the section without saying something false. */
pre.cb-plain .hl-keyword{color:var(--c-blue-new)}
pre.cb-plain .hl-proc   {color:var(--c-purple)}
pre.cb-plain .hl-io     {color:var(--c-green)}
pre.cb-plain .hl-builtin{color:var(--c-pink)}
pre.cb-plain .hl-string {color:var(--c-gold)}
pre.cb-plain .hl-number {color:var(--c-orange)}
pre.cb-plain .hl-arrow  {color:var(--c-pink)}
pre.cb-plain .hl-literal{color:var(--c-purple)}
pre.cb-plain .hl-boolop {color:#969593}
pre.cb-plain .hl-comment{color:rgba(255,255,255,.36);font-style:italic}

/* ── Type-modal tab strip (10 Aug 2026, second pass on Robin's screenshot):
   six tabs, six UNIQUE colours, one format. The first version reused cyan
   and pink from the learning tabs and styled the tool tabs differently,
   which read as two systems. Every tab now follows the learning tabs' own
   idiom — dim coloured outline at rest, solid fill when active — with
   Prove purple, Trace blue, Code orange. Green stays the exam's. */
.tpage .qte-tabs{justify-content:center}
.tpage .qte-tabs .qte-tab[data-tab="pr"]{--tab:var(--c-purple); --tab-rgb:var(--c-purple-rgb)}
.tpage .qte-tabs .qte-tab[data-tab="tr"]{--tab:var(--c-blue-new); --tab-rgb:var(--c-blue-new-rgb)}
.tpage .qte-tabs .qte-tab[data-tab="cd"]{--tab:var(--c-orange); --tab-rgb:var(--c-orange-rgb)}
.tpage .qte-codepick{justify-content:center;margin-bottom:.6rem}
.tpage .qte-codepick .codetab{font-size:.72rem;padding:.25rem .7rem;opacity:.75;border-style:dashed}
.tpage .qte-codepick .codetab.on{opacity:1}
.tpage .qte-praczone .vex-opt.right{border-color:var(--c-green,#39d98a)}
.tpage .qte-praczone .vex-opt.wrong{border-color:#f0665a}
/* Sim embeds — the guided shape (owner, 24 Aug 2026: "large headers and
   proper visuals to guide the students"): kicker, big title, one line of
   instruction, then the sim. One look everywhere. */
.tpage .sim-embed{max-width:var(--content-w);margin:1.5rem 0}
.tpage .sim-embed-kicker{margin:0 0 .1rem;color:var(--c-green,#54f22d);font-size:.72rem;
  font-weight:900;letter-spacing:.16em;text-transform:uppercase}
.tpage .sim-embed-title{margin:0 0 .3rem;color:var(--hue);font-size:1.45rem;font-weight:800}
.tpage .sim-embed-guide{margin:0 0 .75rem;color:rgba(255,255,255,.85);font-size:1.02rem;line-height:1.6}
.tpage .sim-embed iframe{width:100%;height:560px;border:1px solid rgba(255,255,255,.18);
  border-radius:14px;background:var(--c-ground)}
@media(max-width:680px){.tpage .sim-embed iframe{height:480px}}

/* ── A DRILL GAME SITS ON THE PAGE, IT IS NOT AN EMBED ────────────────
   Robin, 14 Sep 2026, holding his own mockup next to my first go:
   "no bg of any kind so it looks like it fits in, no box around it, no
   space above and below... in other words, cool."

   .sim-embed was the wrong container and it is worth saying why, since
   the next drill will want this one. A SIM is a thing you go and look
   at - a diagram, a machine, a demo - so a frame around it is correct;
   it says "this is a separate object, here it is". A DRILL is the page
   asking you a question. Put a border and a titled header on that and
   it stops being the page talking and becomes a widget bolted on, which
   is exactly how it looked.

   So: no border, no ground, no radius, no header. The sim paints
   nothing either (html.inframe in the game), so the page's own
   background runs straight through it and it changes with the theme
   for free. Margins are tight top and bottom because the teaching
   above it IS the context - there is nothing to introduce. */
.tpage .arcade{margin:.5rem 0 .55rem;max-width:var(--content-w)}
/* THE GAME HAS A NAME (Robin, 14 Sep 2026): "Lucky Streak", and
   under it "The more you do it the more you know it...". It is an
   H3 by the header rules - a move WITHIN the skill being taught,
   not a break between ideas - so it takes the same look as every
   other H3 on the site rather than inventing one. It sits tight to
   the game on purpose: a title with air under it reads as a new
   section starting, which is the boxed-in look we just took off. */
/* THE BTRACE HEADER (Robin, 15 Sep 2026: "Header needs to come down w
   some margin at top. Let's actually center it."). It sat hard against
   whatever ended above it and read as a caption on that, rather than as
   the title of the game underneath. Centred it belongs to the game,
   which is centred too. */
.tpage .arcade-h{margin:1.6rem 0 .5rem;text-align:center}
.tpage .arcade-h .tbh3-t{margin:0 0 .12rem}
.tpage .arcade-h .tbh3-s{margin:0}
/* 340, not 560. Tall enough that the vh half of the game's own type
   clamps is not the limiter, and short enough that the stage - which
   centres whatever it holds - is not centring one line in a field of
   nothing. Getting there took both halves: the first go raised the
   frame to fix the type, which fixed the type and doubled the empty
   space. The game's clamps lean on width now, so the frame can be
   short AND the question big. */
.tpage .arcade iframe{
  display:block;width:100%;height:340px;border:0;background:none;border-radius:0;
}
@media(max-width:680px){.tpage .arcade iframe{height:320px}}


/* ── A SIM THAT EARNS MORE WIDTH THAN THE TEXT COLUMN (14 Sep 2026) ──
   Robin: "let's use the width we have and make them bigger."

   Prose stops being comfortable past about 908px, which is what
   --content-w is for. A side-by-side comparison has the opposite need:
   squeeze it to the paragraph width and neither side is readable from
   the back of a classroom.

   So this bleeds into .main-content's own 64px side padding rather
   than trying to reach the viewport - the nav is on the left and a
   vw-based breakout would run under it. It only bleeds where there is
   room to, so on a laptop or a phone it is simply the normal width.

   BOTH RULES SIT AFTER the plain .sim-embed ones on purpose. The first
   go at this put them above, where they had equal specificity and lost
   the later-wins tiebreak - the class was on the element, the rules
   were in the file, and nothing changed on screen. Opt in per embed. */
.tpage .sim-embed.sim-embed--wide{max-width:none}
/* NO FRAME EITHER. Robin: "Two Bases, One Machine - remove the
   border around it, it makes it look foreign." Same finding as the
   arcade box: a rule drawn around something that is already part of
   the page turns it into a visitor. This one can lose it safely
   because two-bases.html links css/palette.css, so its ground IS
   the page's ground and moves with the theme. The other sims carry
   hardcoded colours, so their border is still doing real work -
   separating a slightly-different dark from this one - and this
   stays opt-in until they are on the tokens. */
.tpage .sim-embed.sim-embed--wide iframe{border:0;background:none;border-radius:0}
@media(min-width:1180px){
  .tpage .sim-embed.sim-embed--wide{width:calc(100% + 120px);margin-inline:-60px}
}

/* The skill intro — the owner's paragraph under every skill header:
   what the question is, what it is really testing, why it is asked that
   way (owner, 24 Aug 2026: "without that, it is just slapping a question
   at them with no context"). */
.tpage .skill-intro{max-width:var(--content-w);margin:0 0 1.15rem;color:rgba(255,255,255,.9);
  font-size:1.07rem;line-height:1.68}
.tpage .skill-intro b{color:var(--hue)}
.tpage .skill-intro code{font-family:'JetBrains Mono',ui-monospace,monospace;font-size:.92em;
  background:rgba(255,255,255,.07);border-radius:5px;padding:.05em .35em}
.tpage .qte-pracfb{text-align:center;margin-top:.8rem}
.tpage .qte-praczone .exambtn{margin-top:.4rem}
/* Unit 3 Prove it — the ten-question test (owner call, 22 Aug 2026) */
.tpage .qte-progress{display:flex;justify-content:space-between;align-items:center;margin:0 0 .8rem;
  font:800 .82rem 'Outfit',sans-serif;letter-spacing:.06em;text-transform:uppercase;color:var(--hue)}
.tpage .qte-progress-dots i,.tpage .qte-summary-dots i{display:inline-block;width:10px;height:10px;border-radius:50%;
  margin-left:5px;background:rgba(255,255,255,.14)}
.tpage .qte-progress-dots i.now{background:var(--hue)}
.tpage .qte-progress-dots i.hit,.tpage .qte-summary-dots i.hit{background:var(--c-green,#39d98a)}
.tpage .qte-progress-dots i.miss,.tpage .qte-summary-dots i.miss{background:#f0665a}
.tpage .qte-summary{text-align:center;padding:1.2rem 0}
.tpage .qte-summary-score{font:900 3.5rem/1 'Outfit',sans-serif;color:var(--hue)}
.tpage .qte-summary-score span{font-size:1.6rem;color:rgba(255,255,255,.55)}
.tpage .qte-summary-dots{margin:.7rem 0 1rem}
.tpage .qte-summary-dots i{width:14px;height:14px;margin:0 3px}
/* Unit 3 Trace it — the streak, small and in the corner (owner, 23 Aug 2026:
   "that giant streak needs to be less so and in the corner") */
.tpage [data-tool="trace"]{position:relative}
.tpage .qte-bigstreak{position:absolute;top:-2px;right:8px;display:flex;flex-direction:row;align-items:baseline;gap:.4rem;margin:0}
.tpage .qte-bigstreak-n{font:900 1.5rem/1 'Outfit',sans-serif;color:rgba(255,255,255,.35)}
.tpage .qte-bigstreak.lit .qte-bigstreak-n{color:var(--hue)}
.tpage .qte-bigstreak-l{font:800 .68rem 'Outfit',sans-serif;letter-spacing:.12em;text-transform:uppercase;
  color:rgba(255,255,255,.55)}
/* Ask mode: a prose question ends on "click the answer" */
.tpage .qte-wopt.is-pick{cursor:pointer;border-color:var(--hue)}
.tpage .qte-wopt.is-pick:hover{background:rgba(255,255,255,.06)}
.tpage .qte-wopt.is-bad{border-color:#f0665a}
.tpage .qte-traceline{display:flex;gap:.6rem;justify-content:center;align-items:center;margin-top:.6rem}
.tpage .qte-tracein{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.16);border-radius:10px;
  padding:.55rem .9rem;color:#fff;font:inherit;font-size:1rem;width:min(240px,60%)}
.tpage .qte-tracein:focus{outline:none;border-color:var(--c-blue-new,#0096FF)}
.tpage .qte-tracein.right{border-color:var(--c-green,#39d98a)}
.tpage .qte-tracein.wrong{border-color:#f0665a}

/* Charts inside a type modal / practice zone — the exam sheet and the
   Prove it panel both show inline SVG now. */
.tpage .qsvg-wrap{margin:0 0 1.2rem}
.tpage .qsvg-wrap svg,.tpage .qsvg-wrap img{width:100%;height:auto;max-width:460px;display:block;margin:0 auto}

/* MORE THAN ONE FIGURE IS A ROW OF SMALL ONES, NOT A COLUMN OF BIG ONES.
   Robin, 16 Aug 2026: "make these grids much smaller- in a grid maybe".
   3.6's four-grid questions stretched each 220px robot grid to 460 and
   stacked them, so one question ran 2,187px — you scrolled past Grid A to
   read the code that Grid A was about. A run of adjacent figures now sits
   inline and small enough to compare at a glance, which is the whole point
   of a question that asks "for which of the following grids". A lone
   figure is untouched: a single network diagram still wants the width.

   Deliberately NOT scoped to .tpage like the rules above it: the exam
   runner drops its questions straight into #quiz-area with no .tpage
   wrapper, so a .tpage-scoped rule would have tidied the type modal and
   left the exam — the place Robin actually met this — untouched. */
.qsvg-wrap:has(+ .qsvg-wrap),
.qsvg-wrap:has(+ .qsvg-wrap) ~ .qsvg-wrap{
  display:inline-block;vertical-align:top;width:auto;margin:0 1rem 1rem 0}
.qsvg-wrap:has(+ .qsvg-wrap) svg,
.qsvg-wrap:has(+ .qsvg-wrap) ~ .qsvg-wrap svg,
.qsvg-wrap:has(+ .qsvg-wrap) img,
.qsvg-wrap:has(+ .qsvg-wrap) ~ .qsvg-wrap img{max-width:152px;width:100%;height:auto}

/* Inline pseudocode in an option — see qtype-engine looksLikeCode. */
.tpage code.qcode{font-family:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.94em;
  background:rgba(0,0,0,.35);border:1px solid rgba(255,255,255,.12);border-radius:6px;
  padding:.12em .45em;color:#dce3ee;white-space:pre-wrap}

/* ── Exam: chosen, not marked ─────────────────────────────────────
   During the paper an option only shows that it is SELECTED. No green,
   no red, no explanation — the marking all happens in the report at the
   end (owner, 11 Aug 2026). */
.vex-opt.picked{border-color:var(--c-cyan)!important;background:rgba(var(--c-cyan-rgb),.10)!important}
.vex-pickhint{margin-left:.9rem;font-size:.85rem;color:rgba(255,255,255,.55)}
.vex-next:disabled{opacity:.4;cursor:default}
.vex-review{margin-top:2rem;text-align:left}
.vex-review h3{font-size:1.05rem;margin:0 0 1rem;color:#fff}
.vex-rev{border:1px solid rgba(255,255,255,.09);border-radius:14px;
  padding:1rem 1.2rem;margin:0 0 1rem;background:rgba(255,255,255,.022)}
.vex-rev-you{color:var(--c-orange);margin:.5rem 0 .2rem}
.vex-rev-ans{color:var(--c-green);margin:0 0 .4rem}

/* ── WALKTHROUGH TABLE MARKS (11 Aug 2026) ─────────────────────────
   A "How to Read a Table" walkthrough has to point AT the table, or it is
   just a paragraph about one. Each revealed step can mark a row, a column,
   or the single cell where they cross — set with capMarks on the item, and
   applied by markTable() in qtype-engine. Marks are alpha over the page
   ground in the section's own hue, so they darken rather than pastel out,
   and the crossing cell is the only thing that gets a solid edge. */
.tpage .t-grid .mark-row{background:rgba(var(--hue-rgb),.13)}
.tpage .t-grid .mark-col{background:rgba(var(--hue-rgb),.13)}
.tpage .t-grid td.mark-row.mark-col{background:rgba(var(--hue-rgb),.22)}
.tpage .t-grid .mark-cell{
  background:rgba(var(--hue-rgb),.30);
  box-shadow:inset 0 0 0 2px var(--hue);
  color:#fff;font-weight:700}
.tpage .t-grid th.mark-col{color:var(--hue)}
.tpage .t-grid .t-row,.tpage .t-grid .t-cell,.tpage .t-grid .t-head{
  transition:background .18s ease,color .18s ease}
@media (prefers-reduced-motion:reduce){
  .tpage .t-grid .t-row,.tpage .t-grid .t-cell,.tpage .t-grid .t-head{transition:none}}

/* ── COLUMNED CONTENT: ONE ROW, ALWAYS (12 Aug 2026) ─────────────────
   Robin: "they need to be in a single row and just resizing width-wise."
   style.css pins card-grid at two columns (three with a class somebody
   has to remember), so three cards wrapped 2+1 with a hole under the
   third. auto-fit makes N children N equal columns, whatever N is.
   Phones still stack to one column, same breakpoint style.css used. */
.tpage .card-grid,
.tpage .card-grid.three,
.tpage .card-grid.four,
.tpage .tb-columns{
  display:grid;
  /* WIDTH DECIDES THE COLUMN COUNT, not a class and not the window.
     (Robin, 15 Aug 2026: "collapse to mobile much sooner — this is a bad
     look".) It was grid-auto-flow:column, so N children meant N columns
     however narrow they got: four cards at 210px each with "Data & Digital
     Info" stacked over three lines. A @media query could not save it,
     because the window was 1664px — the SIDEBAR was eating half of it, and
     @media measures the window.

     auto-fit fits as many 15rem tracks as the space really allows and wraps
     the rest, so four cards become 2x2 and then 1, on any window, with no
     breakpoint to keep in step. min(100%,15rem) is the guard that stops the
     floor overflowing a container narrower than 15rem. */
  grid-template-columns:repeat(auto-fit,minmax(min(100%,15rem),1fr));
  gap:1rem}
/* A prose+code pair and the flattened card grids keep explicit counts —
   they are two or three specific things side by side, not a row of equals. */

/* FOUR CARDS ARE 2x2. (Robin, 15 Aug 2026: "go to 2 x 2 on the cards if
   need be.") Not a breakpoint, because there is no width at which four
   across is the better answer: the content column is capped at 1000px, so
   four cards can never be wider than ~240px each — which is the layout in
   the screenshot, "Data & Digital Info" stacked over three lines. At 2x2
   they get ~490px. auto-fit alone would have gone 4 -> 3 -> 2 and left one
   card orphaned on its own row, so the count is read off the children with
   :has() rather than guessed from the width.

   A row of three or two is left to auto-fit above; only four was broken. */
.tpage{container-type:inline-size}
.tpage .card-grid:has(> :nth-child(4)):not(:has(> :nth-child(5))),
.tpage .card-grid.four{grid-template-columns:repeat(2,minmax(0,1fr))}
@container (max-width:34rem){
  .tpage .card-grid:has(> :nth-child(4)):not(:has(> :nth-child(5))),
  .tpage .card-grid.four{grid-template-columns:1fr}}

@media (max-width:768px){
  .tpage .card-grid,.tpage .card-grid.three,.tpage .card-grid.four,
  .tpage .tb-columns{grid-auto-flow:row}}
/* narrow columns meet long words — "Programming" painted over its
   neighbour rather than wrap, because a word only breaks when told */
.tpage .ccard .cc-title,.tpage .ccard .cc-label{overflow-wrap:break-word;hyphens:auto}
/* and the title gives up a little size before it ever has to break */
.tpage .ccard .cc-title{font-size:clamp(1.02rem,.62rem + .9vw,1.35rem)}

/* ── EQUAL HEIGHTS IN A ROW (12 Aug 2026) ────────────────────────────
   Robin: "content boxes always occupy the same height, regardless of
   content." The grid already stretches each CELL to the row height — what
   it never did was make the card INSIDE the cell fill it, so a short card
   ended above its neighbours (the 1.0 "How This Room Works" pair). The
   column becomes a flex stack and a lone card grows to the bottom.
   :only-child keeps prose-plus-code columns stacking exactly as before. */
.tpage .tb-columns>.tb-col{display:flex;flex-direction:column}
.tpage .tb-columns>.tb-col>.ccard:only-child,
.tpage .tb-columns>.ccard{flex:1 1 auto;box-sizing:border-box}
/* Stacked card columns get FLATTENED by normalizeSections into one
   row-major grid (tb-flat-N), because no amount of flex can shrink a card
   below its own content — equal heights in a row have to come from the
   cards sharing a grid ROW, whose height is the tallest card in it. */
.tpage .tb-columns.tb-flat-2,.tpage .tb-columns.tb-flat-3{grid-auto-flow:row}
.tpage .tb-columns.tb-flat-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.tpage .tb-columns.tb-flat-3{grid-template-columns:repeat(3,minmax(0,1fr))}
@media (max-width:768px){
  .tpage .tb-columns.tb-flat-2,.tpage .tb-columns.tb-flat-3{grid-template-columns:1fr}}
.tpage .card-grid>.ccard{box-sizing:border-box}

/* A multi-line option is a whole code segment: keep its line breaks and
   its indentation, and let it sit left-aligned inside the option button.
   Added 13 Aug 2026 when hand-made banks stopped shipping options
   pre-wrapped in <code> HTML (three renderers escaped that and showed the
   markup on screen). */
.qcode-seg{white-space:pre-wrap; display:inline-block; text-align:left; line-height:1.65}
/* multi-line PROSE options — the "missing steps" families. Same line
   keeping, but the page's own font: a monospace frame around plain English
   reads as pseudocode when it is not. */
.qprose-seg{white-space:pre-wrap; display:inline-block; text-align:left; line-height:1.6}


/* ── EXPLANATION, THEN CODE ───────────────────────────────────────────
   A prose-and-code pair is a single teaching move. The explanation goes
   across the page first. Code then gets room to be read; a genuine pair of
   programs sits side by side, not in a narrow vertical stack. */
.tpage .tb-columns.tb-code-lesson{
  grid-template-columns:minmax(0,1fr);
  gap:1.1rem;
  align-items:start;
  margin:1rem 0 1.45rem;
}
.tpage .tb-columns.tb-code-lesson > .tb-col:first-child{
  width:100%;max-width:74ch;margin:0 auto;gap:.7rem;
}
.tpage .tb-columns.tb-code-lesson > .tb-col:first-child > p{margin:0;line-height:1.7}
.tpage .tb-columns.tb-code-lesson > .tb-code-deck{display:block;width:100%;min-width:0}
.tpage .tb-columns.tb-code-lesson > .tb-code-deck > .cb-wrap:has(> .cb-wrap){
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,24rem),1fr));
  gap:1rem;align-items:start;
}
.tpage .tb-columns.tb-code-lesson > .tb-code-deck > .cb-wrap > .cb-wrap{min-width:0;margin:0}
.tpage .tb-columns.tb-code-lesson .cb-pane{
  align-self:start;font-size:clamp(.9rem,.82rem + .28vw,1rem)!important;
}
@container (max-width:32rem){
  .tpage .tb-columns.tb-code-lesson > .tb-code-deck > .cb-wrap:has(> .cb-wrap){grid-template-columns:1fr}
}

/* Individual non-semantic boxes receive --hue inline in normalizeSections.
   Reset the old positional inheritance so prose stays tied to its section,
   while a box's own independent accent wins. */
.tpage .tb-section .tb-columns > .tb-col:nth-child(2),
.tpage .tb-section .tb-columns > .tb-col:nth-child(3){
  --hue:var(--hue-a)!important;--hue-rgb:var(--hue-a-rgb)!important;
}


/* A brief purpose line is part of every prose-and-code lesson. It is not a
   callout: it belongs to the code directly below and tells students exactly
   what they are meant to inspect. */
.tpage .tb-code-focus{max-width:74ch;margin:.25rem auto .8rem;text-align:left}
.tpage .tb-code-focus-label{
  color:var(--hue);font-family:var(--mono,ui-monospace,monospace);font-size:.72rem;
  font-weight:800;letter-spacing:.12em;text-transform:uppercase;margin:0 0 .25rem;
}
.tpage .tb-code-focus p{margin:0;color:rgba(255,255,255,.87);font-size:1rem;line-height:1.6}

/* Unit 3: responsive six-column lesson grid
   Pages opt in with data-lesson-grid="six". Two related ideas can sit side
   by side on a laptop, while every item returns to a generous single column
   on student phones. */
.tpage[data-lesson-grid="six"] .tb-columns{
  display:grid;
  grid-template-columns:repeat(6,minmax(0,1fr));
  gap:clamp(1rem,2vw,1.5rem);
  align-items:start;
}
.tpage[data-lesson-grid="six"] .tb-columns > .tb-col{
  min-width:0;
  grid-column:span 3;
}
.tpage[data-lesson-grid="six"] .tb-columns:has(> .tb-col:nth-child(3)) > .tb-col{
  grid-column:span 2;
}
.tpage[data-lesson-grid="six"] .tb-columns.tb-code-lesson{
  grid-template-columns:repeat(6,minmax(0,1fr));
  gap:clamp(1rem,2vw,1.5rem);
}
.tpage[data-lesson-grid="six"] .tb-columns.tb-code-lesson > .tb-col:first-child,
.tpage[data-lesson-grid="six"] .tb-columns.tb-code-lesson > .tb-code-deck{
  grid-column:span 3;
  width:auto;
  max-width:none;
  margin:0;
}
.tpage[data-lesson-grid="six"] .tb-columns.tb-code-lesson > .tb-code-focus{
  grid-column:1 / -1;
  width:auto;
  max-width:72ch;
  margin:0;
  text-align:left;
}
@media (max-width:900px){
  .tpage[data-lesson-grid="six"] .tb-columns,
  .tpage[data-lesson-grid="six"] .tb-columns.tb-code-lesson{
    display:grid;
    grid-template-columns:minmax(0,1fr);
    gap:1rem;
  }
  .tpage[data-lesson-grid="six"] .tb-columns > .tb-col,
  .tpage[data-lesson-grid="six"] .tb-columns:has(> .tb-col:nth-child(3)) > .tb-col,
  .tpage[data-lesson-grid="six"] .tb-columns.tb-code-lesson > .tb-col:first-child,
  .tpage[data-lesson-grid="six"] .tb-columns.tb-code-lesson > .tb-code-deck,
  .tpage[data-lesson-grid="six"] .tb-columns.tb-code-lesson > .tb-code-focus{
    grid-column:1;
    width:100%;
    max-width:none;
  }
}


/* ── ONE STUDENT-FACING SECTION TITLE SYSTEM ─────────────────────────
   Lesson headings are navigation, not code. Use the lighter Unit 2
   treatment everywhere: Outfit for readable student language and the
   thin multi-colour rule. JetBrains Mono stays reserved for code and tiny
   technical labels. This intentionally overrides one-off lesson CSS. */
.tpage .tb-section-header > .tb-section-label,
.tpage .tb-section-header > .tb-section-label *{
  font-family:Outfit,system-ui,sans-serif!important;
  font-size:clamp(1.2rem,3.1vw,1.5rem)!important;
  font-weight:500!important;
  letter-spacing:.19rem!important;
  line-height:1.2!important;
  text-transform:none!important;
}
.tpage .tb-section-header > .tb-section-line{
  /* Robin: "Use the current font for 'Number Systems on the Test', left
     aligned- line that matches the tab color each time." H1 took the
     centred bold, so H2 keeps this lighter letterspaced face — and its
     rule stops being a rainbow and becomes the one colour that tab owns,
     which is what ties a section to the tab it lives in. */
  height:1px!important;
  background:linear-gradient(90deg,
    var(--hue) 0%,rgba(var(--hue-rgb),.10) 100%)!important;
}
/* ── Skill headers, redesigned (owner, 24 Aug 2026: "the subsection
   should be smaller versions of that [the big top header] ... much more
   clearly separate things"). The number is a colored chip; the title is
   the hero's own family and weight, scaled down; the rainbow hairline
   gives way to a short solid bar in the skill's hue. Scoped to numbered
   labels so unnumbered pages keep the old dress. ── */
.tpage .tb-section-header > .tb-section-label[data-sv-numbered]{display:block}
.tpage .tb-section-header > .tb-section-label[data-sv-numbered] .sv-skillnum{
  display:inline-block;margin:0 0 .45rem;padding:.22rem .6rem;border-radius:7px;
  border:1px solid rgba(var(--hue-rgb),.45)!important;background:rgba(var(--hue-rgb),.1)!important;
  color:var(--hue)!important;font-size:.74rem!important;font-weight:900!important;
  letter-spacing:.14em!important;text-transform:uppercase!important}
.tpage .tb-section-header > .tb-section-label[data-sv-numbered] .sv-skilltitle{
  display:block;color:#fff!important;font-size:clamp(1.25rem,3.6vw,1.85rem)!important;
  font-weight:800!important;letter-spacing:0!important;line-height:1.15!important}
/* the title ends on a blinking terminal cursor in the skill's hue —
   hard on/off, no fade (owner, 25 Aug 2026; replaced the short solid
   bar, which read as "noon half struck") */
.tpage .tb-section-header > .tb-section-label[data-sv-numbered] .sv-skilltitle::after{
  content:"_";margin-left:.16em;color:var(--hue);font-weight:800;font-size:.8em;
  animation:svCursor 1.1s step-end infinite}
@keyframes svCursor{0%{opacity:1}50%{opacity:0}}
@media(prefers-reduced-motion:reduce){
  .tpage .tb-section-header > .tb-section-label[data-sv-numbered] .sv-skilltitle::after{animation:none}}
.tpage .tb-section-header:has(> .tb-section-label[data-sv-numbered]) > .tb-section-line{
  display:none!important}
.tpage .tb-section-header:has(> .tb-section-label[data-sv-numbered]){align-items:flex-end;gap:1rem}
/* ── topic header art (owner, 25 Aug 2026: "every page top is so damn
   boring... bright, horizontal, conveyed enough of the concept that even
   an I'm-not-reading kid can see what we're working on"). One doodle
   scene per topic, drawn as inline SVG in the hero — neon marker on the
   dark ground, the spirit of the old hand-drawn banners. ── */
.tpage .thub-art{max-width:820px;margin:1.15rem auto 0}
.tpage .thub-art svg{width:100%;height:auto;display:block;overflow:visible}
@media(prefers-reduced-motion:no-preference){
  .tpage .thub-art [data-float]{animation:artfloat 5.5s ease-in-out infinite}
  .tpage .thub-art [data-float="2"]{animation-delay:-2.75s}
}
@keyframes artfloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-6px)}}

/* a card label that opens with a step number gives the number its own
   line, so the rest of the label never wraps mid-phrase */
.tpage .ccard .cc-label .cc-step{display:block;margin-bottom:.18rem;opacity:.7}
/* ── Scroll mode: the pinned jump strip and the arrow seams ── */
.tpage .sv-strip{position:sticky;top:0;z-index:8;display:flex;flex-wrap:wrap;gap:.45rem;
  max-width:var(--content-w);margin:0 auto 1.4rem;padding:.6rem 0;
  background:rgba(var(--c-ground-rgb), .92);backdrop-filter:blur(8px)}
.tpage .sv-strip-chip{cursor:pointer;display:inline-flex;align-items:center;gap:.45rem;
  padding:.4rem .7rem;border:1px solid rgba(255,255,255,.18);border-radius:999px;
  background:transparent;color:rgba(255,255,255,.8);font:700 .82rem Outfit,sans-serif}
.tpage .sv-strip-chip b{display:grid;place-items:center;width:1.25rem;height:1.25rem;
  border-radius:50%;background:rgba(255,255,255,.1);font-size:.7rem;color:rgba(255,255,255,.65)}
.tpage .sv-strip-chip:hover{border-color:rgba(255,255,255,.4);color:#fff}
.tpage .sv-strip-chip.on{border-color:var(--c-green,#54f22d);color:#fff}
.tpage .sv-strip-chip.on b{background:var(--c-green,#54f22d);color:#062000}
[data-skill-flow="scroll"] .tb-section{scroll-margin-top:8rem}
[data-skill-flow="scroll"] .tb-section-header{scroll-margin-top:8rem}
.tpage .sv-flowdiv{display:flex;justify-content:center;margin:2.2rem 0}
.tpage .sv-flowdiv span{display:grid;place-items:center;width:2.2rem;height:2.2rem;
  border:1px solid rgba(255,255,255,.2);border-radius:50%;
  color:rgba(255,255,255,.55);font-size:1.1rem}
@media(max-width:560px){
  .tpage .tb-section-header > .tb-section-label,
  .tpage .tb-section-header > .tb-section-label *{
    letter-spacing:.115rem!important;
  }
}
/* When a lesson ends with several related skills, they remain distinct
   practice targets but share one calm, responsive row. */
/* THE OLD TRIGGER-CARD GRID. These five rules laid several "open this
   type" buttons out as an auto-fit row of equal-height cards. Skill
   view has no trigger buttons — a type is a full-width card — but the
   rules still matched, and :has() carries its argument's specificity,
   so they measured 0,5,0 and 0,7,0 and quietly beat every override:
   the wrapper stayed a two-row grid, the first card was stretched to
   the taller row, and the two cards overlapped. Excluded by name
   instead of outranked. */
.tpage .qte-types.qte-inline:not(.sv-open):has(.qte-row:nth-child(2)){
  display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,14.5rem),1fr));
  gap:.6rem;
  align-items:stretch;
}
.tpage .qte-types.qte-inline:not(.sv-open):has(.qte-row:nth-child(2)) .qte-row+.qte-row{margin-top:0}
.tpage .qte-types.qte-inline:not(.sv-open):has(.qte-row:nth-child(2)) .qte-row,
.tpage .qte-types.qte-inline:not(.sv-open):has(.qte-row:nth-child(2)) .qte-open{height:100%}
.tpage .qte-types.qte-inline:not(.sv-open):has(.qte-row:nth-child(2)) .qte-open{padding-left:1.25rem}
.tpage .qte-types.qte-inline:not(.sv-open):has(.qte-row:nth-child(2)) .qte-flag{left:.48rem}
@media(max-width:520px){
  .tpage .qte-types.qte-inline:has(.qte-row:nth-child(2)){grid-template-columns:1fr}
}

/* ONE PAGE-TITLE SCALE — units should introduce topics at the same volume.
   Local Unit 3 rebuilds used an oversized hero; the shared dial now wins. */
.tpage .topic-title{
  font-size:var(--pc-title)!important;
  line-height:1.03!important;
}

/* Unit 3's rebuilt topic hubs used their own oversized top padding.
   Keep their introduction compact like Units 2 and 4.

   21 Aug: this rule was itself the last offender. It was written to CUT
   unit 3's padding down and it did — from 70px to 39.2px — but 39.2 is
   still not the 0 that units 1 and 2 use, so those five pages kept their
   titles 41px lower than every other page on the site. The !important
   also meant the per-page fix could not reach them. Same variable as
   everywhere else now; the sides and the bottom are unchanged. */
.tpage.u35 > .thub,
.tpage.u36 > .thub,
.tpage.u37 > .thub,
.tpage.u38 > .thub,
.tpage.u39 > .thub{
  padding:var(--hero-pad-top) 1.25rem 1.65rem!important;
}


/* ── UNIT 3 VIRTUAL CONSOLE ───────────────────────────────────────────
   The explanation and code stay where they are. The companion Try it box
   is the code's console: Play reveals the run automatically, Step lets a
   student control every line, and a prediction pause happens before output. */
.tpage .cw-code{overflow:auto;padding:1rem 1.1rem;background:var(--ink,#080a0e)}
.tpage .cw-code .cw-line{display:block;min-height:1.78em;opacity:.16;transform:translateX(-.28rem);transition:opacity .18s ease,transform .18s ease,background .18s ease;border-radius:5px;white-space:pre}
.tpage .cw-code .cw-line.is-visible{opacity:1;transform:none}
.tpage .cw-code .cw-line.is-active{background:rgba(0,232,239,.1);box-shadow:inset 3px 0 0 var(--c-cyan,#00e8ef)}
.tpage .cw-code .cw-line.is-ran{opacity:.78}
.tpage .cw-code .cw-indent{visibility:hidden;display:inline-block}
.tpage .cw-console-ready{display:block!important}
.tpage .cw-console-ready .cw-console-source{display:none!important}
.tpage .cw-console-ready .cw-console-source-out{display:none!important}
.tpage .cw-console-shell{min-width:0}
.tpage .cw-console-head{display:flex;align-items:center;justify-content:space-between;gap:.6rem;margin-bottom:.62rem;color:var(--c-cyan,#00e8ef);font:800 .69rem/1 var(--mono,"JetBrains Mono",monospace);letter-spacing:.13em;text-transform:uppercase}
.tpage .cw-status{color:rgba(235,241,255,.52);font-size:.61rem;letter-spacing:.08em}
.tpage .cw-console-title{margin:.1rem 0 .32rem!important;color:#fff!important;font-size:1.15rem!important;line-height:1.12}
.tpage .cw-console-prompt{margin:.1rem 0 .78rem!important;color:#bec5cf!important;font-size:.93rem!important;line-height:1.45!important}
.tpage .cw-controls{display:flex;flex-wrap:wrap;gap:.42rem;margin:.2rem 0 .72rem}
.tpage .cw-controls button,.tpage .cw-predict-actions button{min-height:34px;padding:.44rem .68rem;font-size:.76rem}
.tpage .cw-controls .cw-step{border-color:rgba(255,230,0,.72);color:var(--c-gold,#ffe600)}
.tpage .cw-controls .cw-step:hover{background:var(--c-gold,#ffe600);color:#101006}
.tpage .cw-controls .cw-reset{border-color:rgba(235,241,255,.26);color:rgba(235,241,255,.64)}
.tpage .cw-log{max-height:12rem;overflow:auto;border:1px solid rgba(235,241,255,.11);border-radius:10px;background:rgba(0,0,0,.34);padding:.52rem .6rem}
.tpage .cw-log-row{padding:.26rem .08rem;color:#d7dbe3;font-size:.86rem;line-height:1.42}
.tpage .cw-log-row + .cw-log-row{border-top:1px solid rgba(255,255,255,.07)}
.tpage .cw-log-row.is-system{color:rgba(235,241,255,.58);font-style:italic}
.tpage .cw-log-row.is-output{color:var(--c-green,#39ff14)}
.tpage .cw-log-row.is-input{color:#e7e9ee}
.tpage .cw-input-result{color:var(--c-cyan,#00e8ef);font:800 .72rem var(--mono,"JetBrains Mono",monospace);letter-spacing:.06em;text-transform:uppercase}
.tpage .cw-predict{margin-top:.65rem;padding:.68rem .74rem;border-left:3px solid var(--c-gold,#ffe600);background:rgba(255,230,0,.055)}
.tpage .cw-predict-question{color:#fff;font-weight:800;line-height:1.4}
.tpage .cw-predict-actions{display:flex;flex-wrap:wrap;gap:.42rem;margin-top:.55rem}
.tpage .cw-predict-feedback{margin-top:.45rem;font-size:.86rem;line-height:1.4}
.tpage .cw-predict-feedback.is-correct{color:var(--c-green,#39ff14)}
.tpage .cw-predict-feedback.is-retry{color:var(--c-gold,#ffe600)}
.tpage .cw-input-label{margin:.9rem 0 .42rem;color:rgba(235,241,255,.55);font:800 .65rem var(--mono,"JetBrains Mono",monospace);letter-spacing:.1em;text-transform:uppercase}
.tpage .cw-console-ready .cw-console-inputs{margin-top:0}
.tpage .cw-standalone-console{margin-top:1rem;padding:1rem;border:1px solid rgba(235,241,255,.2);border-radius:14px;background:linear-gradient(145deg,rgba(255,255,255,.035),rgba(255,255,255,.01))}
@media (max-width:650px){
  .tpage .cw-code{font-size:.84rem;padding:.82rem .88rem}
  .tpage .cw-log{max-height:10rem}
}


/* Console pass 2 — one control bar, real terminal log, restrained neon chrome. */
/* The terminal sits UNDER the code, at every width (owner, 23 Aug 2026:
   "cleaner and collapses much better responsively") — so any split that
   pairs code with a lab or console stacks instead of sitting side by side. */
.tpage .split:has(.lab),
.tpage .split:has(.cw-console-shell),
.tpage .split:has(.cw-console-ready){grid-template-columns:1fr!important}
.tpage .code-card:has(.cw-code)>header,
.tpage .u32-code:has(.cw-code)>.u32-code-head,
.tpage .u33-code:has(.cw-code)>h4{display:flex;align-items:center;gap:.7rem}
.tpage .cw-code-nav{margin-left:auto;display:flex;align-items:center;gap:.34rem;flex-wrap:wrap;justify-content:flex-end}
.tpage .cw-nav-label{color:rgba(235,241,255,.42);font:800 .57rem var(--mono,"JetBrains Mono",monospace);letter-spacing:.1em;text-transform:uppercase;white-space:nowrap}
.tpage .cw-code-nav button{min-height:0;padding:.3rem .52rem;border-radius:6px;background:rgba(6,12,20,.78);font-size:.68rem;line-height:1.1;box-shadow:none}
.tpage .cw-code-nav .cw-play{border-color:rgba(0,232,239,.62);color:var(--c-cyan,#00e8ef)}
.tpage .cw-code-nav .cw-play:hover{background:var(--c-cyan,#00e8ef);color:#061015}
.tpage .cw-code-nav .cw-step{border-color:rgba(255,20,147,.7);color:#ff65bd}
.tpage .cw-code-nav .cw-step:hover{background:#ff1493;color:#18000d}
.tpage .cw-code-nav .cw-reset{border-color:rgba(235,241,255,.22);color:rgba(235,241,255,.6)}
.tpage .cw-console-ready{padding:1.15rem 1.28rem!important;background:
  linear-gradient(90deg,rgba(0,232,239,.78),rgba(255,20,147,.64)) top left/100% 1px no-repeat,
  radial-gradient(circle at 93% 7%,rgba(255,20,147,.11),transparent 28%),
  radial-gradient(circle at 5% 100%,rgba(0,232,239,.08),transparent 30%),
  linear-gradient(145deg,rgba(8,14,22,.95),rgba(4,7,12,.98))!important}
.tpage .cw-console-shell{position:relative;min-width:0}
.tpage .cw-console-head{position:relative;padding-bottom:.58rem;margin-bottom:.7rem;border-bottom:1px solid rgba(0,232,239,.26);color:var(--c-cyan,#00e8ef)}
/* The two neon dashes on the rule. They were absolute with nothing
   positioned to hold them, so they measured off .cw-console-shell instead
   and landed at ITS top right corner: the pink one printed straight over
   the word READY, and the cyan one — a 34px box-shadow, so further right
   again — hung outside the terminal box altogether. Anchored to the head
   now, and moved to the left end of the rule where the status text is
   not. */
.tpage .cw-console-head:after{content:"";width:28px;height:2px;background:#ff1493;box-shadow:34px 0 0 rgba(0,232,239,.8);position:absolute;left:0;bottom:-1px}
.tpage .cw-status{color:#f3a5d2}
.tpage .cw-console-title{font-size:1.22rem!important;color:var(--c-cyan,#00e8ef)!important}
.tpage .cw-console-prompt{max-width:34rem;margin-bottom:.95rem!important}
.tpage .cw-log{max-height:17rem;overflow:auto;border:0!important;border-radius:0!important;background:transparent!important;padding:0!important}
.tpage .cw-log-row{padding:.6rem .08rem;color:#dce1e9;font-size:.92rem;line-height:1.45}
.tpage .cw-log-row + .cw-log-row{border-top:1px solid rgba(0,232,239,.12)}
.tpage .cw-log-row.is-system{color:rgba(235,241,255,.58)}
.tpage .cw-log-row.is-output{color:#c5ffbd}
.tpage .cw-log-row.is-prompt{padding:.72rem .76rem;margin:.15rem 0;background:linear-gradient(90deg,rgba(255,20,147,.12),rgba(255,20,147,.025));border-left:2px solid #ff1493}
.tpage .cw-terminal-label,.tpage .cw-pause-label{display:block;margin-bottom:.22rem;color:var(--c-cyan,#00e8ef);font:800 .68rem var(--mono,"JetBrains Mono",monospace);letter-spacing:.11em;text-transform:uppercase}
.tpage .cw-pause-label{color:#ff65bd}
.tpage .cw-pause-reason{margin:.15rem 0 .55rem!important;color:#c9d1dd!important;font-size:.84rem!important;line-height:1.42!important}
.tpage .cw-predict-question{font-size:.96rem}
.tpage .cw-predict-actions{margin-top:.6rem}
.tpage .cw-predict-actions button{min-height:30px;padding:.36rem .6rem;border-radius:6px;border-color:rgba(255,20,147,.66);color:#ff9dd3;background:rgba(255,20,147,.08);font-size:.72rem}
.tpage .cw-predict-actions button:hover{background:#ff1493;color:#18000d}
.tpage .cw-input-dock{margin-top:.82rem;padding-top:.15rem;border-top:1px solid rgba(0,232,239,.12)}
.tpage .cw-input-label{margin:.72rem 0 .42rem;color:rgba(235,241,255,.54)}
.tpage .cw-console-ready .cw-console-inputs{margin:0}
.tpage .cw-code .cw-line.is-ran{opacity:1}
@media(max-width:650px){
  .tpage .cw-console-ready{padding:1rem!important}
  .tpage .cw-code-nav{gap:.26rem}
  .tpage .cw-nav-label{display:none}
  .tpage .cw-log{max-height:14rem}
}

/* ═══════════════════════════════════════════════════════════════════
   ONE CONTENT WIDTH — 21 Aug 2026

   Measured at 1440, 1920 and 2560, and identical at all three, so this
   was never a breakpoint problem:

       unit 1 (1.0, 1.2, 1.3)   hero+section 1000   prose 1000
       unit 2 (2.1, 2.2, 2.4)   hero+section 1000   prose  760
       3.1                      hero+section 1000   prose  760, +120 in
       3.3                      hero+section  760   prose  640
       3.6 / 3.7 / 3.9          hero+section  860   prose  735

   Three different section widths inside unit 3 alone. Each page set its
   own in its own <style> block, and a fragment's <style> lands in <head>
   AFTER every linked stylesheet — which is why a shared width rule could
   never win before. tools/one-width.py repointed all eleven of those
   declarations at the variable below, so the pages keep their rules and
   the rules all read one number.

   860 because it is what most of unit 3 already used and it is the
   proportion that has been pointed at as right. Change this ONE value to
   move every unit together.

   The prose cap is in ch, not px, because the constraint is reading, not
   layout: 1000px of 14px text is about 140 characters a line, and
   comfortable is 45-75. It is flush LEFT inside the section — a
   paragraph starts where its own section title starts, which is the
   +120px indent on 3.1. */
:root{
  --content-w:908px;  /* widened 23 Aug 2026 with the narrower menu */

  /* THE HERO'S TOP PADDING — step two, same disease as the width.
     Measured at 1440: units 1 and 2, plus 3.1 and 3.10, had padding-top
     0 and put their title at y=60. The rest of unit 3 wrote its own
     `padding: clamp(2.4rem,7vw,5rem) …` on its hero, which put the same
     title at y=101, y=131 or y=137 depending on the page — so a unit-3
     title sat up to 77px lower than the identical element in unit 1.
     Zero, because that is what units 1 and 2 already do and the hero
     already carries 42px of margin above it. */
  --hero-pad-top:0px;
}

.tpage .thub,
.tpage .tb-section,
.tpage .tb-section-header{
  max-width:var(--content-w);
  margin-left:auto;
  margin-right:auto;
}
.tpage .tb-section > p{
  max-width:72ch;
  margin-left:0;
  margin-right:0;
}

/* The 18px that units 1 and 2 did not have. Unit 3's pages each set
   `padding: … 1.25rem …` on their own hero and sections; units 1 and 2
   set none, so text began 18px further left there — small, but it is
   exactly the kind of per-unit drift that makes two pages feel like two
   sites. One padding, declared with the width it belongs to. */
.tpage .thub,
.tpage .tb-section{
  padding-left:1.25rem;
  padding-right:1.25rem;
}

/* ═══════════════════════════════════════════════════════════════════
   THE TERMINAL — the seven parts the ported player adds, 21 Aug 2026

   code-walkthrough.js came across from the HUD build for its BEHAVIOUR:
   a real execution trace, so a loop body steps once per trip; typed
   answers instead of yes/no buttons; a question about a variable's value
   on a second pass; robot position; raw console output; and a content
   gate so a pane holding a diagram does not get a dead Play button.

   None of its look came with it — no hudframes, no `hudx__head`, no
   theme ramp. But it emits seven classes this stylesheet had never seen,
   and an unstyled input in the middle of a console is worse than the
   yes/no buttons it replaced. They are dressed here, in the console's
   own existing idiom: cyan structure, pink state, JetBrains Mono, the
   same weights and sizes as .cw-log-row above.

     cw-pre        the # and $ prefix on a log row
     cw-q          the question text
     cw-type-row   input and escape hatch on one line
     cw-type       what the student types
     cw-type-show  the [ show me ] escape
     cw-caret      the blinking block on the ready line
     cw-out-text   a printed value, as the console would show it
   ═══════════════════════════════════════════════════════════════════ */
.tpage .cw-pre{
  display:inline-block;min-width:1.1em;margin-right:.45rem;
  color:var(--c-cyan,#00e8ef);
  font:800 .82rem var(--mono,"JetBrains Mono",monospace);
  user-select:none;
}
.tpage .cw-log-row.is-note .cw-pre{color:rgba(235,241,255,.42)}
.tpage .cw-q{color:#e7e9ee;font-size:.92rem;line-height:1.45}

.tpage .cw-type-row{
  display:inline-flex;align-items:center;gap:.5rem;
  margin-left:.55rem;vertical-align:middle;
}
/* A terminal input should look like the line you are already typing on,
   not like a form field dropped into one: no box, one rule under it, and
   the caret colour the console uses everywhere else. */
.tpage .cw-type{
  width:11rem;padding:.16rem .1rem;
  border:0;border-bottom:1px solid rgba(0,232,239,.45);
  border-radius:0;background:transparent;
  color:#fff;caret-color:var(--c-cyan,#00e8ef);
  font:700 .92rem var(--mono,"JetBrains Mono",monospace);
}
.tpage .cw-type:focus{outline:none;border-bottom-color:var(--c-cyan,#00e8ef)}
.tpage .cw-type:disabled{color:rgba(235,241,255,.55);border-bottom-style:dotted}

.tpage .cw-type-show{
  padding:0;border:0;background:none;cursor:pointer;
  color:rgba(235,241,255,.42);
  font:700 .72rem var(--mono,"JetBrains Mono",monospace);
  letter-spacing:.04em;
}
.tpage .cw-type-show:hover{color:var(--c-gold,#ffe600)}
.tpage .cw-type-show:disabled{opacity:.32;cursor:default}

/* the block cursor on the ready line — the one piece of motion, and it
   is what makes the panel read as a terminal rather than a log */
.tpage .cw-caret{
  display:inline-block;width:.55em;height:1.05em;
  margin-left:.15rem;vertical-align:-.16em;
  background:var(--c-green,#39ff14);
  /* HARD ON/OFF, and steps(2) was not it (owner, 31 Aug 2026: "the
     blinking cursor comes in in stages — I suspect to give it a fade
     effect, but it just looks janky").

     It was not trying to fade; steps() is applied BETWEEN EACH PAIR of
     keyframes, not across the whole animation. With one keyframe at 50%
     the caret ran 0%→50% in two steps (1, then 0.5) and 50%→100% in two
     more (0, then 0.5) — four visible states, which reads exactly like a
     bad fade. step-end holds each keyframe's value until the next one,
     so it is lit for half the cycle and dark for the other half and
     nothing in between. The skill-title cursor two hundred lines up has
     always used step-end; this one was the odd one out. */
  animation:cw-caret-blink 1.1s step-end infinite;
}
@keyframes cw-caret-blink{0%{opacity:1}50%{opacity:0}}
@media (prefers-reduced-motion:reduce){.tpage .cw-caret{animation:none}}

.tpage .cw-out-text{
  color:#c5ffbd;
  font:700 .92rem var(--mono,"JetBrains Mono",monospace);
}


/* ═══ ONE COLUMN, ONE LEFT EDGE (22 Aug 2026) ══════════════════════════
   Measured across all 25 live pages before this rule existed: the left
   edge of a block inside a section landed on FOURTEEN different values —
   448, 462, 463, 464, 465, 466, 469, 480, 495, 496, 497, 498, 564, 566.
   Every one of them came from the same two habits, repeated page by page
   in each fragment's own <style>:

     · `max-width:760px; margin:0 auto` on a block inside an 860 section,
       which centres it and indents it by 50px
     · a per-page rail narrower than the section it sits in

   Neither is a decision anyone made twice; they are the same line copied
   forward. So the column is declared once, here, and a fragment cannot
   move a block off it by accident any more.

   PROSE STILL GETS A MEASURE. Reading width is a real constraint — it is
   the only reason those 760s were ever written — so paragraphs keep a cap.
   They just take it on the RIGHT, flush left with everything else, rather
   than by being centred.

   `> *` on purpose: this governs what a section is built from, not what
   is nested inside those blocks. A card's own padding, a callout's stripe
   inset, a code panel's gutter are all untouched. */
html body .tpage .tb-section > *{
  max-width:none;
  margin-left:0;
  margin-right:0;
}
/* .qte-types is the skill-button row the engine drops into a section. It
   carries its own `max-width:760px; margin:1rem auto` — the same habit as
   the fragments, and the last block still sitting 32px off the rail. Its
   selector is more specific than `> *`, so it has to be named. */
html body .tpage .tb-section > .qte-types,
html body .tpage .tb-section > .qte-types.qte-inline{
  max-width:none;
  margin-left:0;
  margin-right:0;
}
html body .tpage .tb-section > p,
html body .tpage .tb-section > [class*="-lead"],
html body .tpage .tb-section > [class*="-intro"],
html body .tpage .tb-section > .lead{
  max-width:52rem;
}


/* ═══ THE TAB ROW: NO SOLID STATES (22 Aug 2026) ═══════════════════════
   Owner: "cyan has white text (can't be read) and every single button has
   a cyan hover... no solid hover or active, instead gentle part
   translucent fill same as several content areas — in the right colour of
   course."

   Both faults had one cause: the states were painted as SOLID fills, so a
   tab's own colour became its BACKGROUND and the label had to find ink
   that worked on it. That ink was #fff on every one of them — white on
   #00ffff measures about 1.1:1, which is why Learn it could not be read —
   and the hover was written once, in cyan, for all five tabs.

   Every state is a translucent wash of the tab's OWN colour now, at the
   weights the content areas already use: .07 on hover, .14 selected. The
   LABEL keeps its colour in all three states, so a tab is identified by
   the colour of its word — which never changes — and what changes is how
   much of that colour sits behind it.

   --tab is set per tab above and is the only thing that differs between
   them, so a sixth tab is one line rather than five. */
/* `html body` IS LOAD-BEARING HERE, and this is the second half of the
   bug. Every unit-3 fragment carries a blanket rule in its own <style> —
   `.u36 button:hover, .u36 button.on{background:var(--cyan);color:#071014}`
   — written for that page's lab buttons and landing on EVERY button in
   the page, tab row included. Fragment styles are appended to <head>
   after the linked sheets, so at equal specificity they win: that single
   line is why every tab went solid cyan with near-black text the moment
   you pointed at it, whatever colour the tab itself was. `.u36
   button:hover` measures 0,2,1; this measures 0,3,2 and beats it without
   an !important anywhere. */
html body .tpage .qte-tab{
  --tab:var(--c-cyan); --tab-rgb:var(--c-cyan-rgb);
  color:var(--tab);
  border-color:rgba(var(--tab-rgb),.35);
  background:rgba(255,255,255,.03);
  transition:background-color .14s,border-color .14s}
html body .tpage .qte-tab:hover{
  background:rgba(var(--tab-rgb),.07);
  border-color:rgba(var(--tab-rgb),.55);
  color:var(--tab)}
html body .tpage .qte-tab.on,
html body .tpage .qte-tab.on:hover{
  background:rgba(var(--tab-rgb),.14);
  border-color:var(--tab);
  color:var(--tab);
  font-weight:700}
html body .tpage .qte-tab:focus-visible{outline:2px solid var(--tab);outline-offset:2px}

/* the code-picker chips inside Code it, same rule, same reason */
html body .tpage .qte-codepick .codetab{--tab:var(--c-orange); --tab-rgb:var(--c-orange-rgb);
  color:var(--tab);border-color:rgba(var(--tab-rgb),.35)}
html body .tpage .qte-codepick .codetab:hover{background:rgba(var(--tab-rgb),.07);color:var(--tab)}
html body .tpage .qte-codepick .codetab.on,
html body .tpage .qte-codepick .codetab.on:hover{background:rgba(var(--tab-rgb),.14);
  border-color:var(--tab);color:var(--tab)}

/* ═══ SKILL VIEW — one skill per screen (22 Aug 2026) ══════════════════
   The layout half of skill-view.js. Everything here is chrome around a
   section; not one rule reaches inside a section, so a page looks exactly
   as it always did once you are on it. */

/* THE PARAGRAPH. Reading width rather than column width, because it is
   prose and it is the first thing on the page anyone actually reads. It
   sits between the title and the teaching, so it gets air on both sides. */
.tpage .sv-prose{
  max-width:52rem;margin:.2rem 0 1.9rem;
  font-size:1.06rem;line-height:1.7;color:rgba(255,255,255,.88)}
.tpage .sv-prose b{color:#fff;font-weight:700}
.tpage .sv-prose code{
  padding:.08em .4em;border-radius:5px;
  background:rgba(var(--hue-rgb),.14);color:var(--hue);
  font:600 .92em 'JetBrains Mono',ui-monospace,monospace}

/* a type, inline. It used to be a dialog, so it needs the breathing room
   the dialog gave it and a top edge to say where it starts. */
/* FULL WIDTH, ALWAYS STACKED. Several pages put their section body in a
   two-column lesson grid, and a type dropped into one of those columns
   came out half a screen wide with the next type beside it — which is the
   opposite of "clearly separate them visually". A type is a whole block of
   teaching; it gets the whole column. */
/* THE MODAL'S OWN BOX, BACK ON THE PAGE (owner, 22 Aug 2026: "maybe use a
   similar container we used when it was in modal so it looks
   self-contained"). Same 18px radius, same edge in the row's colour, same
   #0b0d12 ground — the thing that made a type feel like one object is that
   it had its own walls, and inline it had lost them. The header is the lid
   and the whole card is the click target's frame. */
/* the two rules that strip a row's frame inside a section are 0,4,0 and
   0,4,0; this has to outrank them or the card has no walls at all */
html body .tpage .tb-section .qte-types.qte-inline .sv-type.sv-type,
html body .tpage .vpage .qte-types .sv-type.sv-type,
html body .tpage .sv-type.sv-type{grid-column:1/-1;width:100%;
  margin:1.15rem 0 0;padding:0;overflow:hidden;
  border:1px solid rgba(var(--row-rgb),.34);border-radius:18px;
  background:#0b0d12;
  transition:border-color .16s}
html body .tpage .sv-type.sv-type:first-of-type{margin-top:2.6rem}
html body .tpage .sv-type:hover{border-color:rgba(var(--row-rgb),.5)}
/* Two cards in one skill were the same colour, because a row inside a
   section wears the section's hue and both rows are in the same section.
   The site's rule is that repeated elements cycle the section's THREE
   hues by position, so these do too: two cards, two colours, and it is
   the position that decides — nobody has to remember to type a class. */
html body .tpage .tb-section .sv-type:nth-of-type(3n+1){--row:var(--hue-a);--row-rgb:var(--hue-a-rgb)}
html body .tpage .tb-section .sv-type:nth-of-type(3n+2){--row:var(--hue-b);--row-rgb:var(--hue-b-rgb)}
html body .tpage .tb-section .sv-type:nth-of-type(3n+3){--row:var(--hue-c);--row-rgb:var(--hue-c-rgb)}
/* THE SHEET STOPS BEING A SHEET.
   .qte-modal-body is a scrolling panel inside a dialog: overflow-y:auto and
   max-height:90vh, so it fits the screen with its own scrollbar. Inline on
   a page that is completely wrong — one type here holds 3,689px of worked
   examples inside an 842px box, so the first question type was a small
   scroll-window in the middle of the page and everything past option C was
   simply not there. The page scrolls now; the type does not. */
html body .tpage .sv-type .qte-modal-body{
  padding:1.5rem 1.5rem 1.9rem;max-height:none;height:auto;overflow:visible}
@media(max-width:640px){
  html body .tpage .sv-type .qte-modal-body{padding:1.1rem .95rem 1.4rem}}
/* the two embedded tools keep a fixed frame — they are apps, not prose,
   and an iframe with no height is an iframe with no content */
html body .tpage .sv-type .qte-modal-body .toolwrap iframe{height:min(72vh,780px)}

/* THE HINGE, and it has to be sized like one.
   This header is where a skill stops explaining and starts asking, and
   where a second question type starts. It was set as a caption — a .62rem
   kicker and a .98rem name — so the two biggest transitions on the page
   were the two smallest things on it. It is a header now: its own rule
   across the column, an eyebrow that counts the types, and a name at
   heading weight. */
html body .tpage .sv-part{
  display:flex;align-items:center;gap:1.1rem;width:100%;
  max-width:none;margin:0;padding:1.15rem 1.45rem;cursor:pointer;
  border:0;border-bottom:1px solid rgba(255,255,255,.11);border-radius:0;
  background:linear-gradient(90deg,rgba(var(--row-rgb),.13),transparent 48%);
  box-shadow:none;font-family:inherit;text-align:left;min-height:0;
  transition:background .16s,border-color .16s}
html body .tpage .sv-part .sv-part-txt{flex:1 1 auto;min-width:0}
html body .tpage .sv-part .sv-part-k{
  display:block;margin:0 0 .38rem;
  font:800 .66rem Outfit,sans-serif;letter-spacing:.16em;text-transform:uppercase;
  color:var(--row)}
html body .tpage .sv-part .sv-part-t{
  display:block;max-width:none;margin:0;
  font:600 clamp(1.18rem,2.3vw,1.4rem)/1.25 Outfit,system-ui,sans-serif;
  letter-spacing:.02em;text-transform:none;color:#fff}
html body .tpage .sv-part:hover{
  background:linear-gradient(90deg,rgba(var(--row-rgb),.2),transparent 56%)}

/* the fold control */
html body .tpage .sv-part .sv-part-arw{
  flex:0 0 auto;display:grid;place-items:center;width:2.25rem;height:2.25rem;
  border:1px solid rgba(var(--row-rgb),.45);border-radius:50%;
  font-size:.95rem;line-height:1;color:var(--row);
  transition:transform .18s,background .18s}
html body .tpage .sv-part:hover .sv-part-arw{background:rgba(var(--row-rgb),.16)}
html body .tpage .sv-part:focus-visible{outline:2px solid var(--row);outline-offset:-2px}

/* SHUT: the card is just its lid, so the next one is right underneath
   instead of seven thousand pixels down. No bottom rule on a lid with
   nothing under it — that line was drawing the floor of an empty room. */
html body .tpage .sv-type.sv-shut .sv-part{border-bottom-color:transparent}
html body .tpage .sv-type.sv-shut .sv-part-arw{transform:rotate(-90deg)}
html body .tpage .sv-type.sv-shut{background:rgba(255,255,255,.018)}
@media(prefers-reduced-motion:reduce){
  html body .tpage .sv-part .sv-part-arw{transition:none}}
@media(max-width:640px){
  html body .tpage .sv-part{padding:1rem 1rem;gap:.8rem}}

/* EVERY OTHER TRANSITION INSIDE A TYPE, at page scale.
   All of this was sized for a modal, where the whole type was one small
   sheet and a .68rem kicker still read as a heading because there was
   nothing bigger near it. Inline on a full page, the same kicker is dust:
   "Worked example 2" was 9.5px, which is smaller than the body text it is
   supposed to introduce. Each of these marks a real "something new starts
   here", so each is sized to be seen from a scroll. */
html body .tpage .sv-type .qte-foldhead{
  margin:2.6rem 0 1.2rem;padding-left:1.05rem;border-left-width:3px}
html body .tpage .sv-type .qte-foldhead h3{
  margin:0 0 .35rem;font-size:clamp(1.34rem,2.4vw,1.6rem)}
html body .tpage .sv-type .qte-foldhead p{font-size:1rem}
html body .tpage .sv-type .qte-panel[data-panel="ln"] > .qte-foldhead:first-child{margin-top:0}

/* a worked example is a whole question — it gets a rule and a real label */
html body .tpage .sv-type .qte-wt{
  margin-top:2.2rem;padding-top:1.4rem;border-top:1px solid rgba(255,255,255,.09)}
html body .tpage .sv-type .qte-wt:first-of-type{border-top:0;padding-top:0;margin-top:1.2rem}
html body .tpage .sv-type .qte-wt-title{
  margin-bottom:.85rem;font-size:.84rem;letter-spacing:.16em}

/* the you-show-me items: their header is the click target as well as the
   transition, so it was doing two jobs at half the size for both */
html body .tpage .sv-type .qte-ya-head{padding-top:.85rem;padding-bottom:.85rem}
html body .tpage .sv-type .qte-ya-head > span:first-child{
  font-size:1.12rem;font-weight:700;letter-spacing:.01em}

/* the one-line brief above a tab's content */
html body .tpage .sv-type .qte-lead-sub{font-size:1.02rem;margin-bottom:1.4rem}

/* the tab row is the type's own control strip — give it room to read as
   one, and space before what it switches */
html body .tpage .sv-type .qte-tabs{margin:0 0 1.9rem}
html body .tpage .sv-type .codetab{font-size:.86rem;padding:.62rem 1.1rem}
/* The card the engine wrapped the row in has no frame to draw any more —
   and it is the node the page's lesson grid is laying out, so the
   full-width rule belongs HERE, not on .sv-type inside it. Two types in a
   two-column section were coming out side by side at half a screen each,
   which is the opposite of separating them. flex-basis covers the pages
   whose section body is a flex row rather than a grid. */
/* .sv-open is written TWICE on purpose, and it is not a typo.
   The rule this has to beat is `.tpage .qte-types.qte-inline:has(.qte-row:
   nth-child(2))`, and :has() contributes the specificity of its argument —
   so that selector measures 0,5,0, one class MORE than the plain
   .sv-open version. It was quietly winning: the wrapper stayed a two-row
   grid, the first card was stretched to the taller row, and the two cards
   overlapped by 7px. Repeating the class is the smallest honest way to
   outrank it without !important. */
html body .tpage .qte-types.qte-inline.sv-open.sv-open{
  display:block;                      /* it is a flex ROW by default, which
                                         is what put two types side by side
                                         at half a screen each */
  max-width:none;margin:0 0 .4rem;padding:0;background:none;border:0;
  grid-column:1/-1;flex:1 1 100%;width:100%;gap:0}

/* ── THE RAILS ───────────────────────────────────────────────────────
   ONE LAYOUT: two pills at the foot of the skill, in the flow.

   They were photo-gallery chevrons pinned to the window edges for a day.
   Owner, 22 Aug 2026, once the accordions landed: "we no longer need the
   chevron buttons at the side- move them to bottom of page." The
   accordions are why — a skill now ENDS somewhere, so there is a foot to
   arrive at, where before it ran on and a floating control was the only
   way out of the middle of it. The edge version also cost real geometry:
   it printed over the nav menu on the left, ran off the screen on the
   right, and needed the gutters measured on every resize.

   Quiet at rest, since this sits under reading, and lit under the
   pointer. */
html body .sv-nav{
  display:flex;align-items:stretch;gap:1rem;flex-wrap:wrap;
  margin:2.8rem 0 .6rem;padding-top:1.6rem;
  border-top:1px solid rgba(255,255,255,.07)}
html body .sv-nav .sv-rail.next{margin-left:auto}

html body .sv-rail{
  display:inline-flex;align-items:center;gap:.7rem;
  max-width:min(100%,22rem);padding:.72rem 1.05rem;cursor:pointer;
  border:1px solid rgba(255,255,255,.1);border-radius:14px;
  background:rgba(255,255,255,.035);color:inherit;font-family:inherit;
  box-shadow:none;letter-spacing:normal;text-transform:none;min-height:0;
  text-align:left;transition:background .16s,border-color .16s}
html body .sv-rail.next{flex-direction:row-reverse;text-align:right}
html body .sv-rail .sv-txt{display:flex;flex-direction:column;gap:.16rem;min-width:0}
html body .sv-rail .sv-chev{
  flex:0 0 auto;
  font:200 1.8rem/1 Outfit,sans-serif;
  color:rgba(255,255,255,.4);transition:color .16s,transform .16s}
html body .sv-rail small{
  font:800 .56rem Outfit,sans-serif;letter-spacing:.15em;text-transform:uppercase;
  color:rgba(255,255,255,.3);transition:color .16s}
html body .sv-rail .sv-lbl{
  font-size:.9rem;line-height:1.3;color:rgba(255,255,255,.62);
  overflow-wrap:break-word;transition:color .16s}
html body .sv-rail:hover{background:rgba(0,232,239,.08);border-color:rgba(0,232,239,.34)}
html body .sv-rail:hover .sv-chev{color:var(--c-cyan)}
html body .sv-rail.prev:hover .sv-chev{transform:translateX(-3px)}
html body .sv-rail.next:hover .sv-chev{transform:translateX(3px)}
html body .sv-rail:hover small{color:rgba(255,255,255,.5)}
html body .sv-rail:hover .sv-lbl{color:#fff}
html body .sv-rail:focus-visible{outline:2px solid var(--c-cyan);outline-offset:3px}
@media(prefers-reduced-motion:reduce){.sv-rail .sv-chev{transition:color .16s}}

/* the arriving skill, so a move reads as a move */
@keyframes sv-in{from{opacity:0;transform:translateY(9px)}to{opacity:1;transform:none}}
.tpage .sv-anim{animation:sv-in .2s ease-out}
@media(prefers-reduced-motion:reduce){.tpage .sv-anim{animation:none}}

/* ═══ LEARN IT — MODEL 4 ════════════════════════════════════════════
   The code stays on screen and the steps arrive in a full-width terminal
   directly under it. Owner, 22 Aug 2026: "a full width terminal under
   each segment", and the step's line "ringed" above it.

   The ring is drawn as an outline on the LINE, not a box around it, so
   nothing reflows when it turns on — code that jumps a pixel when you
   press Next is worse than no highlight. */
html body .tpage .qte-w4{
  margin:1.2rem 0 1.4rem;border:1px solid rgba(var(--hue-rgb),.3);border-radius:16px;
  background:#0b0d12;overflow:hidden}
html body .tpage .qte-w4 .q-code-area{border:0;border-radius:0;margin:0;background:none}
html body .tpage .qte-w4 .q-code-header{border-radius:0}

html body .tpage .cb-txt.is-ring{
  position:relative;display:block;
  background:rgba(var(--hue-rgb),.12);
  box-shadow:inset 3px 0 0 var(--hue)}
html body .tpage .cb-txt.is-ring::after{
  content:"";position:absolute;inset:0 .4rem;
  border:1.5px solid rgba(var(--hue-rgb),.8);border-radius:9px;
  pointer-events:none}
@media(prefers-reduced-motion:no-preference){
  html body .tpage .cb-txt.is-ring{transition:background .18s}}

/* the terminal */
html body .tpage .qte-w4term:empty{display:none}
html body .tpage .qte-w4 .qte-w4term{
  border-top:1px solid rgba(255,255,255,.11);background:rgba(0,0,0,.4)}
html body .tpage .qte-w4loose{
  margin:1.1rem 0 1.3rem;border:1px solid rgba(var(--hue-rgb),.3);
  border-radius:14px;background:rgba(0,0,0,.3);overflow:hidden}
html body .tpage .qte-w4row{
  display:flex;gap:.85rem;padding:.8rem 1.1rem;
  font-size:1rem;line-height:1.55;color:#dfe4ec}
html body .tpage .qte-w4row + .qte-w4row{border-top:1px solid rgba(255,255,255,.07)}
html body .tpage .qte-w4row > div{min-width:0}
html body .tpage .qte-w4row b{color:#fff}
html body .tpage .qte-w4tag{
  flex:none;min-width:3.6rem;
  font:800 .66rem/1.6 'JetBrains Mono',ui-monospace,monospace;
  letter-spacing:.1em;text-transform:uppercase;color:var(--hue)}
/* ── ONE STEP ON SCREEN, AND A ONE-LINE TRAIL ─────────────────────────
   Owner, 1 Sep 2026: "that is how long a single question gets with the
   walkthrough… I think there is just too much going on. Having these
   questions/steps only appear one at a time."

   In a ladder the host carries .is-solo: only the live row is shown, and
   the rungs already answered shrink into the trail line above it. The
   walkthrough goes from twelve stacked paragraphs to one question and a
   row of four small badges. On the last step the host takes .is-review and
   every row comes back, so the whole line of reasoning can be read at once
   after the work is done. A code trace never takes .is-solo — its terminal
   is meant to accumulate. */
html body .tpage .qte-w4term.is-solo > .qte-w4row:not(.is-live){display:none}
html body .tpage .qte-w4term.is-solo.is-review > .qte-w4row{display:flex}
html body .tpage .qte-w4trail{
  display:flex;flex-wrap:wrap;gap:.4rem;padding:.6rem 1.1rem;
  border-bottom:1px solid rgba(255,255,255,.07)}
html body .tpage .qte-w4trail:empty{display:none}
html body .tpage .qte-w4mark{
  display:inline-flex;align-items:center;gap:.3rem;
  padding:.2rem .55rem;border-radius:999px;
  border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.04);
  font:700 .78rem/1.3 'JetBrains Mono',ui-monospace,monospace;
  color:rgba(255,255,255,.66)}
html body .tpage .qte-w4mark b{color:#fff}
html body .tpage .qte-w4mark.is-ok{
  border-color:rgba(var(--c-ok-rgb),.6);background:rgba(var(--c-ok-rgb),.14);color:var(--c-ok)}
html body .tpage .qte-w4mark.is-no{
  border-color:rgba(var(--c-no-rgb),.6);background:rgba(var(--c-no-rgb),.14);color:var(--c-no)}
html body .tpage .qte-w4mark.is-ok b,html body .tpage .qte-w4mark.is-no b{color:inherit}
/* in review the trail has done its job — the full rows are back */
html body .tpage .qte-w4term.is-review .qte-w4trail{opacity:.55}

/* ── RIGHT AND WRONG WEAR THE RESERVED PAIR ───────────────────────────
   Deep green and true red, defined in palette.css, used here and nowhere
   else in the notebook. Never colour alone: the mark and the word carry
   the same message for a student who cannot tell the two hues apart. */
html body .tpage .qte-askfb.is-good{color:var(--c-ok)}
html body .tpage .qte-askfb.is-bad{color:var(--c-no)}
html body .tpage .qte-askmark{font-weight:900;margin-right:.1rem}
html body .tpage .qte-askchip.picked.is-ok{
  background:var(--c-ok);border-color:var(--c-ok);color:#03130a}
html body .tpage .qte-askchip.picked.is-no{
  background:var(--c-no);border-color:var(--c-no);color:#1a0405}

/* ── A, B, C AND D EACH OWN A COLOUR ──────────────────────────────────
   Owner, 1 Sep 2026, on a modal that had come out entirely orange: "we've
   talked a dozen times about text color constantly coming in as groups or
   sections all in a single color… what do we do to somehow differentiate
   this? Only other thing I can think of is that we reserve the colors red
   and green."

   So: the option letters are the thing that gets differentiated, because
   there the colour carries a fact rather than decorating one. A is cyan, B
   is gold, C is purple, D is blue — the same four in the option list, on
   the judging rung that talks about that option, and in the verdict that
   names it. A student following "so C is out" can find C without reading.

   RED AND GREEN ARE RESERVED, which is his own suggestion and is now real:
   green means this is right, pink means this is wrong, and nothing else in
   a walkthrough is allowed either of them — which is why the four option
   colours are drawn from the other end of the palette.

   Set as the element's own --hue so every rule already keyed to --hue (the
   letter, the pick border, the tint) follows without being restated. */
.tpage .qte-wopt[data-j="0"]{--hue:var(--c-cyan);     --hue-rgb:var(--c-cyan-rgb)}
.tpage .qte-wopt[data-j="1"]{--hue:var(--c-gold);     --hue-rgb:var(--c-gold-rgb)}
.tpage .qte-wopt[data-j="2"]{--hue:var(--c-purple);   --hue-rgb:var(--c-purple-rgb)}
.tpage .qte-wopt[data-j="3"]{--hue:var(--c-blue-new); --hue-rgb:var(--c-blue-new-rgb)}
.tpage .qte-wopt[data-j="4"]{--hue:var(--c-orange);   --hue-rgb:var(--c-orange-rgb)}
/* the letter wears it; the option text stays white, because the text is
   what they are reading and four coloured sentences is the monochrome
   problem again with extra steps */
html body .tpage .qte-wopt > b{color:var(--hue)!important}
/* the segment tabs and scoreboard cells on a code question are the same
   four options, so they take the same four colours */
.tpage .qte-segtab[data-seg="0"],.tpage .qte-segcell[data-seg="0"]{--hue:var(--c-cyan);     --hue-rgb:var(--c-cyan-rgb)}
.tpage .qte-segtab[data-seg="1"],.tpage .qte-segcell[data-seg="1"]{--hue:var(--c-gold);     --hue-rgb:var(--c-gold-rgb)}
.tpage .qte-segtab[data-seg="2"],.tpage .qte-segcell[data-seg="2"]{--hue:var(--c-purple);   --hue-rgb:var(--c-purple-rgb)}
.tpage .qte-segtab[data-seg="3"],.tpage .qte-segcell[data-seg="3"]{--hue:var(--c-blue-new); --hue-rgb:var(--c-blue-new-rgb)}

/* ── THE LIVE ROW ─────────────────────────────────────────────────────
   Owner, 1 Sep 2026: "the text of each line explanation as I step through
   is basically invisible. Same text color, same orange, it's just not
   there." Every row drew identically, so pressing Next changed nothing a
   student could see — the new sentence arrived at the same weight as the
   four already sitting above it, and .is-cap's whole distinguishing mark
   was a 2.8% white wash.

   So the newest row is the loud one — full-white text, a hue rail down its
   left edge, and one short fade so the eye catches the arrival — and every
   row above it steps back to .62. Read down the stack and it reads as
   history; the thing you are meant to be reading is the bright one at the
   bottom. Nothing here changes what the rows SAY, only which one is
   currently speaking. */
html body .tpage .qte-w4row{transition:color .18s, background .18s, opacity .18s}
html body .tpage .qte-w4row:not(.is-live){color:rgba(223,228,236,.62)}
html body .tpage .qte-w4row:not(.is-live) .qte-w4tag{opacity:.5}
html body .tpage .qte-w4row.is-live{
  color:#fff;box-shadow:inset 2px 0 0 var(--hue);
  background:rgba(var(--hue-rgb),.05);animation:qteLive .38s ease-out}
@keyframes qteLive{from{opacity:.25;transform:translateY(-3px)}to{opacity:1;transform:none}}
@media(prefers-reduced-motion:reduce){
  html body .tpage .qte-w4row.is-live{animation:none}
}
/* a verdict keeps its own colour when it is live — green means right, and
   that outranks "this is the newest row" */
html body .tpage .qte-w4row.is-live.is-ans{color:var(--c-green)}
html body .tpage .qte-w4row.is-live.is-bad{color:#ff86c4}
html body .tpage .qte-w4row.is-live.is-out{color:var(--c-green)}

/* step one is the method, and it says so */
html body .tpage .qte-w4row.is-method{background:rgba(var(--hue-rgb),.07)}
html body .tpage .qte-w4row.is-ans{color:var(--c-green);font-weight:600}
html body .tpage .qte-w4row.is-ans .qte-w4tag{color:var(--c-green)}

html body .tpage .qte-wbar{
  display:flex;align-items:center;gap:.9rem;margin:1.2rem 0 0}
html body .tpage .qte-wcount{
  margin-left:auto;font:800 .72rem 'JetBrains Mono',ui-monospace,monospace;
  letter-spacing:.1em;color:rgba(255,255,255,.36)}
/* what the program showed — the same green as a terminal's output */
html body .tpage .qte-w4row.is-out{color:var(--c-green)}
html body .tpage .qte-w4row.is-out .qte-w4tag{color:var(--c-green)}
/* a conclusion drawn from the run, as opposed to the run itself */
html body .tpage .qte-w4row.is-cap{background:rgba(255,255,255,.028)}
/* a long run scrolls inside the terminal rather than pushing the code
   off the top of the screen — the whole point is that they stay together */
html body .tpage .qte-w4 .qte-w4term{max-height:22rem;overflow-y:auto}

/* ── LEARN IT — the choose-a-segment walkthrough ──────────────────────
   Tabs across the top, one segment on screen, and a scoreboard under it
   that fills in as each is run. Every number in it was computed by
   running that segment, so nothing here can drift from the code. */
html body .tpage .qte-segtabs{
  display:flex;flex-wrap:wrap;gap:.4rem;padding:.7rem .9rem;
  border-bottom:1px solid rgba(255,255,255,.11)}
html body .tpage .qte-segtab{
  min-width:2.5rem;padding:.4rem .8rem;min-height:0;
  border:1px solid rgba(255,255,255,.18);border-radius:999px;
  background:rgba(255,255,255,.035);color:rgba(255,255,255,.62);
  font:800 .82rem Outfit,system-ui,sans-serif;letter-spacing:.02em;
  text-transform:none;cursor:pointer;box-shadow:none;transition:all .15s}
html body .tpage .qte-segtab:hover{color:#fff;border-color:rgba(var(--hue-rgb),.5)}
html body .tpage .qte-segtab.on{
  background:rgba(var(--hue-rgb),.16);border-color:var(--hue);color:#fff}
html body .tpage .qte-segtab.is-good{border-color:rgba(57,217,138,.6);color:#7ef0b6}
html body .tpage .qte-segtab.is-bad{border-color:rgba(255,20,147,.45);color:#ff86c4}
html body .tpage .qte-segpanel .q-code-area{border:0;border-radius:0;margin:0;background:none}

html body .tpage .qte-segboard{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,9rem),1fr));
  gap:.6rem;margin:1rem 0 0}
html body .tpage .qte-segcell{
  display:flex;flex-direction:column;gap:.2rem;padding:.75rem .85rem;
  border:1px solid rgba(255,255,255,.14);border-radius:12px;
  background:rgba(255,255,255,.025)}
html body .tpage .qte-segcell .k{
  font:800 .62rem Outfit,system-ui,sans-serif;letter-spacing:.13em;
  text-transform:uppercase;color:rgba(255,255,255,.42)}
html body .tpage .qte-segcell .v{
  font:600 1.25rem Outfit,system-ui,sans-serif;color:#fff;font-variant-numeric:tabular-nums}
html body .tpage .qte-segcell .n{font-size:.8rem;color:rgba(255,255,255,.5)}
html body .tpage .qte-segcell.is-good{border-color:rgba(57,217,138,.55);background:rgba(57,217,138,.09)}
html body .tpage .qte-segcell.is-good .v{color:var(--c-green)}
html body .tpage .qte-segcell.is-bad{border-color:rgba(255,20,147,.45);background:rgba(255,20,147,.06)}
html body .tpage .qte-segcell.is-bad .v{color:var(--c-pink)}
html body .tpage .qte-w4row.is-bad{color:#ff86c4}
html body .tpage .qte-w4row.is-bad .qte-w4tag{color:var(--c-pink)}
/* a segment question's options are already in the tabs — the row below is
   just the letters, so the answer has something to be marked on */
html body .tpage .qte-wopts-thin{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:1rem}
html body .tpage .qte-wopts-thin .qte-wopt{
  display:grid;place-items:center;width:2.4rem;height:2.4rem;padding:0;
  border-radius:50%;font:800 .9rem Outfit,system-ui,sans-serif}

/* ═══ QVIEW — the one question view (Learn it / Try it / Trace it) ═══ */
html body .tpage .qv-intro{margin:0 0 .8rem;color:rgba(255,255,255,.78)}
html body .tpage .qv-stem{margin:0 0 1rem;font-size:1.05rem;color:#fff}
html body .tpage .qv-panel{
  margin:0 0 1rem;border:1px solid rgba(var(--hue-rgb),.3);border-radius:16px;
  background:#0b0d12;overflow:hidden}
html body .tpage .qv-panel .q-code-area{border:0;border-radius:0;margin:0;background:none}
html body .tpage .qv-segtabs{
  display:flex;flex-wrap:wrap;gap:.4rem;padding:.7rem .9rem;
  border-bottom:1px solid rgba(255,255,255,.11)}
html body .tpage .qv-segtab{
  min-width:2.5rem;padding:.4rem .8rem;min-height:0;cursor:pointer;
  border:1px solid rgba(255,255,255,.18);border-radius:999px;
  background:rgba(255,255,255,.035);color:rgba(255,255,255,.62);
  font:800 .82rem Outfit,system-ui,sans-serif;text-transform:none;box-shadow:none}
html body .tpage .qv-segtab.on{background:rgba(var(--hue-rgb),.16);border-color:var(--hue);color:#fff}
html body .tpage .qv-segtab.is-good{border-color:rgba(57,217,138,.6);color:#7ef0b6}
html body .tpage .qv-segtab.is-bad{border-color:rgba(255,20,147,.45);color:#ff86c4}

html body .tpage .qv-term{max-height:22rem;overflow-y:auto;
  border-top:1px solid rgba(255,255,255,.11);background:rgba(0,0,0,.4)}
html body .tpage .qv-term:empty{display:none}
html body .tpage .qv-term-loose{
  margin:1rem 0;border:1px solid rgba(var(--hue-rgb),.3);border-radius:14px;
  background:rgba(0,0,0,.3);overflow:hidden}
html body .tpage .qv-row{display:flex;gap:.85rem;padding:.8rem 1.1rem;
  font-size:1rem;line-height:1.55;color:#dfe4ec}
html body .tpage .qv-row + .qv-row{border-top:1px solid rgba(255,255,255,.07)}
html body .tpage .qv-row b{color:#fff}
html body .tpage .qv-tag{flex:none;min-width:3.6rem;
  font:800 .66rem/1.6 'JetBrains Mono',ui-monospace,monospace;
  letter-spacing:.1em;text-transform:uppercase;color:var(--hue)}
html body .tpage .qv-row.is-method{background:rgba(var(--hue-rgb),.07)}
html body .tpage .qv-row.is-cap{background:rgba(255,255,255,.028)}
html body .tpage .qv-row.is-out{color:var(--c-green)}
html body .tpage .qv-row.is-out .qv-tag{color:var(--c-green)}
html body .tpage .qv-row.is-bad{color:#ff86c4}
html body .tpage .qv-row.is-bad .qv-tag{color:var(--c-pink)}
html body .tpage .qv-row.is-ans{color:var(--c-green);font-weight:600}
html body .tpage .qv-row.is-ans .qv-tag{color:var(--c-green)}

html body .tpage .qv-board{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,9rem),1fr));
  gap:.6rem;margin:0 0 1rem}
html body .tpage .qv-cell{display:flex;flex-direction:column;gap:.2rem;
  padding:.75rem .85rem;border:1px solid rgba(255,255,255,.14);border-radius:12px;
  background:rgba(255,255,255,.025)}
html body .tpage .qv-cell .k{font:800 .62rem Outfit,system-ui,sans-serif;
  letter-spacing:.13em;text-transform:uppercase;color:rgba(255,255,255,.42)}
html body .tpage .qv-cell .v{font:600 1.25rem Outfit,system-ui,sans-serif;color:#fff}
html body .tpage .qv-cell .n{font-size:.8rem;color:rgba(255,255,255,.5)}
html body .tpage .qv-cell.is-good{border-color:rgba(57,217,138,.55);background:rgba(57,217,138,.09)}
html body .tpage .qv-cell.is-good .v{color:var(--c-green)}
html body .tpage .qv-cell.is-bad{border-color:rgba(255,20,147,.45);background:rgba(255,20,147,.06)}
html body .tpage .qv-cell.is-bad .v{color:var(--c-pink)}

html body .tpage .qv-ask{margin:0 0 1.1rem}
html body .tpage .qv-asklead{margin:0 0 .7rem;color:rgba(255,255,255,.76)}
html body .tpage .qv-chips{display:flex;flex-wrap:wrap;gap:.5rem}
html body .tpage .qv-chip{padding:.55rem 1rem;min-height:0;cursor:pointer;
  border:1px solid rgba(255,255,255,.2);border-radius:999px;
  background:rgba(255,255,255,.04);color:#dfe4ec;
  font:700 .92rem Outfit,system-ui,sans-serif;text-transform:none;box-shadow:none}
html body .tpage .qv-chip:hover:not(:disabled){border-color:var(--hue);color:#fff}
html body .tpage .qv-chip.good{border-color:var(--c-green);background:rgba(57,217,138,.16);color:#fff}
html body .tpage .qv-chip.bad{border-color:var(--c-pink);background:rgba(255,20,147,.14);color:#fff}
html body .tpage .qv-inrow{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}
html body .tpage .qv-in{padding:.6rem .85rem;min-width:14rem;
  border:1px solid rgba(255,255,255,.2);border-radius:10px;
  background:rgba(0,0,0,.35);color:#fff;font:600 1rem 'JetBrains Mono',monospace}
html body .tpage .qv-in.good{border-color:var(--c-green)}
html body .tpage .qv-in.bad{border-color:var(--c-pink)}
html body .tpage .qv-go{padding:.6rem 1.1rem;min-height:0;cursor:pointer;
  border:1px solid var(--hue);border-radius:999px;background:rgba(var(--hue-rgb),.12);
  color:var(--hue);font:800 .88rem Outfit,system-ui,sans-serif;text-transform:none;box-shadow:none}
html body .tpage .qv-askfb{margin-top:.6rem;font-size:.98rem}
html body .tpage .qv-askfb.is-good{color:var(--c-green)}
html body .tpage .qv-askfb.is-bad{color:#ff86c4}

html body .tpage .qv-opts{display:flex;flex-direction:column;gap:.5rem;margin:1rem 0 0}
html body .tpage .qv-opts-thin{flex-direction:row;flex-wrap:wrap}
html body .tpage .qv-opt{padding:.7rem .9rem;border:1px solid rgba(255,255,255,.13);
  border-radius:12px;background:rgba(255,255,255,.02);color:#dfe4ec}
html body .tpage .qv-opts-thin .qv-opt{display:grid;place-items:center;
  width:2.4rem;height:2.4rem;padding:0;border-radius:50%}
html body .tpage .qv-opt.hit{border-color:var(--c-green);background:rgba(57,217,138,.1)}
html body .tpage .qv-bar{display:flex;align-items:center;gap:.9rem;margin:1.1rem 0 0}
html body .tpage .qv-next{padding:.66rem 1.25rem;min-height:0;cursor:pointer;
  border:1px solid var(--hue);border-radius:999px;background:rgba(var(--hue-rgb),.12);
  color:var(--hue);font:800 .88rem Outfit,system-ui,sans-serif;text-transform:none;box-shadow:none}
html body .tpage .qv-next:disabled{opacity:.4;cursor:default}
html body .tpage .qv-count{margin-left:auto;
  font:800 .72rem 'JetBrains Mono',monospace;letter-spacing:.1em;color:rgba(255,255,255,.36)}

/* ═══ STAGE 2 — "now you show me yours", the same problem ═══════════ */
html body .tpage .qte-stage2-one{
  margin:1.6rem 0 0;padding:1.4rem 0 0;
  border-top:1px solid rgba(255,255,255,.08)}
html body .tpage .qte-stage2-one:first-child{border-top:0;padding-top:0;margin-top:.6rem}
html body .tpage .qv2-lead{margin:0 0 1rem;color:rgba(255,255,255,.8)}
html body .tpage .qv2-lead b{color:var(--hue)}
html body .tpage .qv2-code{
  margin:0 0 1.1rem;border:1px solid rgba(var(--hue-rgb),.3);
  border-radius:16px;background:#0b0d12;overflow:hidden}
html body .tpage .qv2-code .q-code-area{border:0;border-radius:0;margin:0;background:none}
html body .tpage .qv2-ask{margin:0 0 1.1rem}
html body .tpage .qv2-q{display:flex;gap:.7rem;align-items:flex-start;margin:0 0 .6rem;
  font-size:1.02rem;color:#fff}
html body .tpage .qv2-n{
  flex:none;display:grid;place-items:center;width:1.7rem;height:1.7rem;border-radius:50%;
  border:1px solid rgba(var(--hue-rgb),.5);color:var(--hue);
  font:800 .76rem Outfit,system-ui,sans-serif}
html body .tpage .qv2-fb{margin-top:.45rem;font-size:.95rem}
html body .tpage .qv2-fb.is-good{color:var(--c-green)}
html body .tpage .qv2-fb.is-bad{color:#ff86c4}
html body .tpage .qv2-score{
  margin:.4rem 0 0;padding:.85rem 1rem;border-radius:12px;
  background:rgba(var(--hue-rgb),.08);color:#fff;font-weight:600}

/* ── "Now you show me yours": the same terminal, with blanks ────────── */
html body .tpage .qte-w4row.is-ask{background:rgba(var(--hue-rgb),.09);align-items:flex-start}
html body .tpage .qte-askq{margin:0 0 .55rem;color:#fff;font-size:1rem}
html body .tpage .qte-askq code{
  padding:.06em .35em;border-radius:5px;background:rgba(var(--hue-rgb),.16);
  color:var(--hue);font:600 .92em 'JetBrains Mono',ui-monospace,monospace}
html body .tpage .qte-askrow{display:flex;flex-wrap:wrap;gap:.5rem;align-items:center}
html body .tpage .qte-askin{
  width:9rem;padding:.5rem .75rem;min-height:0;
  border:1px solid rgba(255,255,255,.22);border-radius:9px;
  background:rgba(0,0,0,.4);color:#fff;font:600 .98rem 'JetBrains Mono',monospace}
html body .tpage .qte-askin.good{border-color:var(--c-green)}
html body .tpage .qte-askin.bad{border-color:var(--c-pink)}
html body .tpage .qte-askgo,html body .tpage .qte-askchip{
  padding:.5rem 1rem;min-height:0;cursor:pointer;
  border:1px solid rgba(var(--hue-rgb),.55);border-radius:999px;
  background:rgba(var(--hue-rgb),.12);color:var(--hue);
  font:800 .85rem Outfit,system-ui,sans-serif;text-transform:none;box-shadow:none}
html body .tpage .qte-askchip.picked{background:var(--hue);color:#08090d}
html body .tpage .qte-askgo:disabled,html body .tpage .qte-askchip:disabled{opacity:.45;cursor:default}
/* the one they actually chose stays readable after the row locks — a
   student re-reading the step needs to see what they said */
html body .tpage .qte-askchip.picked:disabled{opacity:1}
/* NAMED CHOICES (the prose ladder's first rung) are whole sentences, not
   the word "true" — they need to sit on their own lines and read left */
html body .tpage .qte-askrow.is-choices .qte-askchip{
  text-align:left;line-height:1.4;padding:.55rem .95rem;white-space:normal}
@media(max-width:700px){
  html body .tpage .qte-askrow.is-choices{flex-direction:column;align-items:stretch}
}
html body .tpage .qte-askfb{margin:.5rem 0 0;font-size:.95rem}
html body .tpage .qte-askfb.is-good{color:var(--c-green)}
html body .tpage .qte-askfb.is-bad{color:#ff86c4}
html body .tpage .qte-you .qte-wt-title{color:var(--hue)}

/* ── the segment walkthrough, as the sample has it ─────────────────── */
html body .tpage .qte-seghead{
  display:flex;align-items:center;gap:1rem;padding:.75rem 1.05rem;
  border-bottom:1px solid rgba(255,255,255,.11)}
html body .tpage .qte-segname{
  font:800 .72rem 'JetBrains Mono',ui-monospace,monospace;
  letter-spacing:.14em;text-transform:uppercase;color:var(--hue)}
html body .tpage .qte-segverdict{
  margin-left:auto;font:800 .72rem 'JetBrains Mono',ui-monospace,monospace;
  letter-spacing:.12em;text-transform:uppercase}
html body .tpage .qte-segverdict.is-good{color:var(--c-green)}
html body .tpage .qte-segverdict.is-bad{color:var(--c-pink)}
/* a disproved segment, struck through */
html body .tpage .qte-segpanel .cb-txt.is-out{
  opacity:.4;text-decoration:line-through;
  text-decoration-color:rgba(255,20,147,.75)}
/* the sentence row and the arithmetic row read differently on purpose */
html body .tpage .qte-w4row.is-said{color:#eef1f6}
html body .tpage .qte-w4row.is-work{
  font-family:'JetBrains Mono',ui-monospace,monospace;font-size:.92rem;
  color:rgba(235,241,255,.72)}
html body .tpage .qte-w4row.is-work .qte-w4tag{color:rgba(255,255,255,.3)}

/* ═══════════════════════════════════════════════════════════════════
   NOIR content — REWRITTEN 30 Aug 2026. Read this before touching it.

   The first version had one idea ("crimson stays in the pink slot and
   nowhere else") and it was wrong in three ways at once, all of which
   the owner named:

     "The colors do not appear right."
     "The colors do not appear randomly assigned individually — classes
      or something like before — not what I want."
     "In most of the unit(s) the crimson is largely absent."

   All three were the same bug wearing three faces. Below this block sat
   about a dozen rules that forced --c-gold onto structural chrome with
   !important — panels, kickers, lab buttons, tables, the section rule,
   the page title, and an odd/even rule that painted skill titles brass
   and crimson by POSITION IN THE DOM. Every one of them overrode the
   site's three-colour positional cycle. So: brass read as the theme
   (wrong colours), the cycle could not show through (assigned by class,
   not distributed), and crimson — reachable only through the pink slot,
   which a triple names maybe twice in seven — nearly never appeared.

   What replaces it: noir gets its OWN triple table, and then gets out
   of the way. Every triple contains crimson, so crimson lands in every
   section on the site rather than waiting for a pink one. The leads
   rotate cream → crimson → brass → crimson → cream → brass → stone, so
   consecutive sections differ and no page is one colour. The overrides
   that remain point at var(--hue…) instead of at brass, which is what
   makes them join the cycle instead of flattening it.

   Two other things the owner asked for, both here:
   — "less heavy approach to all titles/sub-titles… more than just a
     colour theme." Noir drops the display weights and opens the
     letter-spacing. Neon wants an 800; a noir page wants a 300 with air
     around it. That is the half a palette swap cannot say.
   — "maybe some [grounds] for some of the boxes, crimson/beige with
     complimentary colors inside." --tint goes from .055 to .11 in noir,
     so a box actually carries its colour instead of hinting at it.
   ═══════════════════════════════════════════════════════════════════ */

/* ── THE NOIR TRIPLE TABLE ────────────────────────────────────────
   Same shape as the neon table near the top of this file, same rules:
   --hue-a is the section's own, b and c are its partners, cards cycle
   through all three by position. The only difference is the values.
   EVERY ROW CONTAINS CRIMSON — that is the fix for "crimson is largely
   absent", and it is not negotiable if a later hand re-pairs these. */
html[data-theme="noir"] .tpage .tb-section,
html[data-theme="noir"] .tpage .tb-section[data-hue="cyan"]{
  --hue-a:var(--n-cream);       --hue-a-rgb:var(--n-cream-rgb);
  --hue-b:var(--n-crimson-lit); --hue-b-rgb:var(--n-crimson-lit-rgb);
  --hue-c:var(--n-brass);       --hue-c-rgb:var(--n-brass-rgb)}
html[data-theme="noir"] .tpage .tb-section[data-hue="purple"]{
  --hue-a:var(--n-crimson-lit); --hue-a-rgb:var(--n-crimson-lit-rgb);
  --hue-b:var(--n-cream);       --hue-b-rgb:var(--n-cream-rgb);
  --hue-c:var(--n-stone);       --hue-c-rgb:var(--n-stone-rgb)}
html[data-theme="noir"] .tpage .tb-section[data-hue="gold"]{
  --hue-a:var(--n-brass);       --hue-a-rgb:var(--n-brass-rgb);
  --hue-b:var(--n-crimson-lit); --hue-b-rgb:var(--n-crimson-lit-rgb);
  --hue-c:var(--n-cream);       --hue-c-rgb:var(--n-cream-rgb)}
html[data-theme="noir"] .tpage .tb-section[data-hue="pink"]{
  --hue-a:var(--n-crimson-lit); --hue-a-rgb:var(--n-crimson-lit-rgb);
  --hue-b:var(--n-brass);       --hue-b-rgb:var(--n-brass-rgb);
  --hue-c:var(--n-cream);       --hue-c-rgb:var(--n-cream-rgb)}
html[data-theme="noir"] .tpage .tb-section[data-hue="blue-new"]{
  --hue-a:var(--n-stone);       --hue-a-rgb:var(--n-stone-rgb);
  --hue-b:var(--n-crimson-lit); --hue-b-rgb:var(--n-crimson-lit-rgb);
  --hue-c:var(--n-cream);       --hue-c-rgb:var(--n-cream-rgb)}
html[data-theme="noir"] .tpage .tb-section[data-hue="orange"]{
  --hue-a:var(--n-crimson-lit); --hue-a-rgb:var(--n-crimson-lit-rgb);
  --hue-b:var(--n-cream);       --hue-b-rgb:var(--n-cream-rgb);
  --hue-c:var(--n-brass);       --hue-c-rgb:var(--n-brass-rgb)}
html[data-theme="noir"] .tpage .tb-section[data-hue="green"]{
  --hue-a:var(--n-brass);       --hue-a-rgb:var(--n-brass-rgb);
  --hue-b:var(--n-crimson-lit); --hue-b-rgb:var(--n-crimson-lit-rgb);
  --hue-c:var(--n-cream);       --hue-c-rgb:var(--n-cream-rgb)}

/* WHY THE LEADS ARE ORDERED THE WAY THEY ARE. Sections take their hue
   from the site's seven-step rotation — cyan, purple, gold, pink,
   blue-new, orange, green — with each page starting somewhere different.
   So the leads above, read in that order, are:

       cream · CRIMSON · brass · CRIMSON · stone · CRIMSON · brass

   Three of seven lead crimson, and — the part that matters — no two
   consecutive positions are both non-crimson except green→cyan, which
   is brass then cream. Any page with three or more sections therefore
   gets a crimson section, wherever its rotation happens to start. A
   first pass at this table left 2.2, 4.2 and 4.6 with no crimson lead
   at all, which is the exact complaint this rewrite exists to answer,
   so check that property before re-pairing anything here. */

/* A box should read as coloured, not as grey with a rumour of colour.
   Neon throws its own bloom onto what it sits on and .055 is enough;
   these tones do not, so noir doubles it. */
html[data-theme="noir"] .tpage .tb-section{--tint:.11; --edge:.42}

html[data-theme="noir"] .tpage .tb-section-header > .tb-section-line{
  background:var(--hue-a,var(--n-cream))!important;opacity:.5}
html[data-theme="noir"] .thub-art,
html[data-theme="noir"] .flow-wrap svg{filter:grayscale(1) sepia(.4) saturate(.6) brightness(1.02)}
html[data-theme="noir"] .tpage :is(.u21-panel,.u22-panel,.u23-panel,.u24-panel,.u23-pipe){
  border-color:rgba(var(--hue-rgb),.4)!important;
  background:linear-gradient(rgba(var(--hue-rgb),var(--tint,.055)),rgba(var(--hue-rgb),var(--tint,.055))) var(--c-ground)!important}
html[data-theme="noir"] .tpage :is(.u21-kicker,.u22-kicker,.u23-kicker,.u24-kicker,.u23-projkick,.u32-kicker,.u33-k,.eyebrow){
  color:var(--hue,var(--n-cream))!important}
html[data-theme="noir"] .tpage .topic-eyebrow{color:var(--n-crimson-lit)!important}
/* The interactive chrome takes the SECTION's colour, not brass. This is
   the difference between "every button on the site is brass" and "the
   buttons in this section belong to this section". */
html[data-theme="noir"] :is(.u31-rebuild,.u32-rebuild,.u33-rebuild,.u35,.u36,.u37,.u38,.u39) button{
  border-color:rgba(var(--hue-rgb),.55)!important;
  background:rgba(var(--hue-rgb),.08)!important;color:var(--hue)!important}
html[data-theme="noir"] :is(.u31-rebuild,.u32-rebuild,.u33-rebuild,.u35,.u36,.u37,.u38,.u39) button:is(:hover,.on,.is-active,.is-selected){
  background:var(--hue)!important;color:var(--c-ground)!important;border-color:var(--hue)!important}
/* the labs and value chips that carry their own hard-coded neon */
html[data-theme="noir"] .tpage :is(.u31-lab,.u32-lab,.u33-lab,.u32-value,.u32-picker button,.u32-answer){
  border-color:rgba(var(--hue-rgb),var(--edge,.42))!important;
  background:linear-gradient(rgba(var(--hue-rgb),var(--tint,.11)),rgba(var(--hue-rgb),var(--tint,.11))) var(--c-ground)!important}
/* the exam invitation is the one thing that does NOT ride the cycle: it
   is the same door on every page, so it wants to look the same on every
   page. Crimson, because it is the one call to action on the page. */
/* Noir used to paint this card crimson and give it a ring. The card has
   no box at all now (Robin, 13 Sep, site-wide), and "site-wide" includes
   the themes — a theme that kept its own background would have been the
   one place the box survived. The cream headings below stay: that is
   typography, not a container. */
html[data-theme="noir"] .tpage .examcta{background:none!important;border:0!important}
html[data-theme="noir"] .tpage .examcta :is(h3,h2,strong,b){color:var(--n-cream)!important}
/* 1.1's tool doors write their colour inline; the theme takes it back —
   crimson, cream, brass, so the row reads as the theme's own three */
html[data-theme="noir"] .ct-door{--d-rgb:var(--n-crimson-lit-rgb)!important}
html[data-theme="noir"] .ct-door:nth-child(2){--d-rgb:var(--n-cream-rgb)!important}
html[data-theme="noir"] .ct-door:nth-child(3){--d-rgb:var(--n-brass-rgb)!important}
html[data-theme="noir"] .ct-glyph svg{filter:grayscale(1) sepia(.4) brightness(1.12)}
/* EVERY embedded sim at once: sims are separate documents with their own
   palettes, so the theme cannot reach inside — instead the frame itself
   is developed like film. One rule noirs all of them. */
html[data-theme="noir"] .tpage iframe,
html[data-theme="noir"] #content-container iframe{
  filter:grayscale(1) sepia(.4) saturate(.7) brightness(1.02)}
/* aria's rainbow bubble goes cream */
html[data-theme="noir"] .aria-bubble .aria-mark{filter:grayscale(1) sepia(.4) brightness(1.1)}
/* Every in-body SVG figure develops like the header art — EXCEPT inside
   .thub-art, whose container already filters (a double bath over-darkens).
   And 2.1's color wheel keeps its real colors on purpose: there the color
   IS the lesson (colors as numbers), so noir leaves it meaningful. */
html[data-theme="noir"] .tpage svg{filter:grayscale(1) sepia(.4) saturate(.7) brightness(1.05)}
html[data-theme="noir"] .tpage .thub-art svg{filter:none}
/* A drawing whose shapes have been repainted in the theme's own inks
   (normalizeSections, noir branch) must lose the filter — grayscale(1)
   would strip exactly the colour that was just put in. */
html[data-theme="noir"] .tpage svg.is-noir-inked,
html[data-theme="noir"] .thub-art:has(svg.is-noir-inked),
html[data-theme="noir"] .flow-wrap svg.is-noir-inked{filter:none!important}
html[data-theme="noir"] .tpage :is(.u21-wheel,.u21-wheel-disc,.u21-wheel-pad) svg{filter:none}
html[data-theme="noir"] .computers-think-h{color:var(--hue,var(--n-cream))!important}
/* 2.1's widget suite carries its own hard neon; structural chrome pulls
   onto the section's colour. The color wheel and RGB build-a-color bits
   are exempt — semantic color, see above. */
html[data-theme="noir"] .tpage :is(.u21-system,.u21-place,.u21-sd-sym,.u21-sd-nav,.u21-switch,.u21-bit-lab,.u21-mode,.u21-mode-tabs,.u21-step,.u21-build,.u21-formula,.u21-lab-button,.u21-meaning-button,.u21-meaning-answer,.u21-readin){
  border-color:rgba(var(--hue-rgb),.38)!important;
  background:linear-gradient(rgba(var(--hue-rgb),.07),rgba(var(--hue-rgb),.07)) var(--c-ground)!important}
html[data-theme="noir"] .tpage .u21-sd-sym{color:var(--hue)!important}
html[data-theme="noir"] .tpage .u21-pattern-table :is(th,td,tr,thead,tbody){border-color:rgba(var(--hue-rgb),.3)!important}
html[data-theme="noir"] .tpage .u21-pattern-table th{background:rgba(var(--hue-rgb),.12)!important;color:var(--hue)!important}
/* the code player's Play/Step/Reset: Play is the section's, Step is
   crimson — a real pairing rather than two identical brass buttons */
html[data-theme="noir"] .tpage :is(.cw-controls,.cw-code-nav) .cw-play{
  border-color:rgba(var(--hue-rgb),.6)!important;color:var(--hue)!important}
html[data-theme="noir"] .tpage :is(.cw-controls,.cw-code-nav) .cw-play:hover{
  background:var(--hue)!important;color:var(--c-ground)!important}
html[data-theme="noir"] .tpage :is(.cw-controls,.cw-code-nav) .cw-step{
  border-color:rgba(var(--n-crimson-lit-rgb),.7)!important;color:var(--n-crimson-lit)!important}
html[data-theme="noir"] .tpage :is(.cw-controls,.cw-code-nav) .cw-step:hover{
  background:var(--n-crimson)!important;color:var(--n-cream)!important}

/* ── THE NOIR CODE THEME (owner, 30 Aug 2026: "any reason it would look
   much better in unit 2 than in unit 3? … maybe some colors leaking
   through") ───────────────────────────────────────────────────────────
   He was right, and the answer is almost entirely SYNTAX HIGHLIGHTING.
   Unit 2 is prose and tables; unit 3 is wall-to-wall code. A scan of
   every computed colour on every page found 2-10 saturated non-theme
   colours per unit-2 page and HUNDREDS per unit-3 page — 348 instances
   of one hot orange on Conditionals alone. Every one of them was an
   .hl-* token span inside a code panel.

   Two faults fed it. The first is fixed at the source: five rebuilt
   unit-3 pages each declared a PRIVATE palette in their own <style>
   block — --cyan:#00e8ef, --blue:#14a4ff, --pink:#ff1493, and so on —
   none of which are the site's values, so no theme could reach them.
   Those now point at the real tokens (and unit 3 stops running a
   parallel palette, which is worth more than the theme fix).

   The second is this block. Token substitution alone is not enough:
   --c-blue in noir is slate and --c-green is stone, so a code panel
   would come out muddy and its four token kinds would stop being
   distinguishable — and syntax colour is SEMANTIC here, the same
   argument that keeps 2.1's colour wheel in full colour. So noir gets a
   real code theme of its own, in its own register.

   How it is pitched: everything is warm and from the theme's families,
   and the four kinds separate by VALUE as well as hue. NUMBERS ARE THE
   BRIGHTEST THING IN THE PANEL, deliberately — tracing is watching a
   number change, so the number should be what your eye lands on.
   Keywords take crimson because they are the structure. DISPLAY and
   INPUT take a lighter crimson: same family as the keywords, because
   they are also language words, but not competing with them.

   One selector list, deliberately broad, because .hl-* spans are
   produced by five different writers (code-highlight.js, apblocks,
   the code player, the bank renderer, and hand-written page markup)
   and land in as many containers. The page-local rules it overrides
   are two classes deep, so this wins on specificity without
   !important. */
html[data-theme="noir"] .tpage :is(pre,code,.cw-code-text,.cb-txt,.apblocks){
  color:#BDB6A8}
html[data-theme="noir"] .tpage .hl-keyword{color:#D4444C}
html[data-theme="noir"] .tpage .hl-io     {color:#D8737A}
html[data-theme="noir"] .tpage .hl-string {color:var(--n-brass)}
html[data-theme="noir"] .tpage .hl-number {color:var(--n-cream)}
html[data-theme="noir"] .tpage .hl-bool   {color:#E0B75A}
html[data-theme="noir"] .tpage .hl-proc   {color:#D8737A}
html[data-theme="noir"] .tpage .hl-boolop {color:#9A968C}
html[data-theme="noir"] .tpage .hl-comment{color:#75706A;font-style:italic}
/* the panel's own chrome: the label strip on a hand-built code card was
   reading page-local cyan, which is the single loudest leak left once
   the tokens are repointed */
html[data-theme="noir"] .tpage :is(.code-card > header,.cb-label,.cw-code-title){
  color:var(--n-cream)}

/* ── THE CODE PLAYER'S TERMINAL ───────────────────────────────────────
   The last raw neon left standing in noir, and worth a note in the neon
   theme too: this whole .cw-console-* family names its colours by hand —
   #ff1493 and #00e8ef — and neither is a site colour. The palette's pink
   is #FF007F and its cyan is #00FFFF. So the terminal has never actually
   been on the palette in EITHER theme; it just looked close enough in
   neon to pass. Noir is where it stopped passing: two hot neon dashes on
   the rule under the word TERMINAL, on an otherwise crimson-and-cream
   page. Retuned here rather than at the source, because the neon values
   are the owner's to re-pick and this is a theme fix, not a repaint. */
html[data-theme="noir"] .tpage .cw-console-ready{
  background:linear-gradient(90deg,rgba(var(--n-crimson-lit-rgb),.85),rgba(var(--n-cream-rgb),.5)) top left/100% 1px no-repeat,
             var(--c-ground)!important}
html[data-theme="noir"] .tpage .cw-console-head{
  border-bottom-color:rgba(var(--n-cream-rgb),.22)!important;color:var(--n-cream)!important}
html[data-theme="noir"] .tpage .cw-console-head:after{
  background:var(--n-crimson-lit)!important;
  box-shadow:34px 0 0 rgba(var(--n-cream-rgb),.75)!important}
html[data-theme="noir"] .tpage :is(.cw-console-title,.cw-terminal-label){color:var(--n-cream)!important}
html[data-theme="noir"] .tpage :is(.cw-status,.cw-pause-label){color:var(--n-crimson-lit)!important}
html[data-theme="noir"] .tpage .cw-log-row + .cw-log-row{
  border-top-color:rgba(var(--n-cream-rgb),.12)!important}
html[data-theme="noir"] .tpage .cw-log-row.is-output{color:var(--n-stone)!important}
html[data-theme="noir"] .tpage .cw-log-row.is-prompt{
  background:rgba(var(--n-crimson-rgb),.12)!important;border-left-color:var(--n-crimson-lit)!important}
html[data-theme="noir"] .tpage .cw-predict-actions button{
  border-color:rgba(var(--n-crimson-lit-rgb),.6)!important;color:var(--n-crimson-lit)!important;
  background:rgba(var(--n-crimson-rgb),.10)!important}

/* ── THE TYPE (owner, 30 Aug 2026: "the noir mode should take a less
   heavy approach to all titles/sub-titles… more than just a color
   theme") ─────────────────────────────────────────────────────────────
   Neon carries an 800 because the colour is doing the shouting and the
   weight has to keep up. Noir has no shouting to keep up with, so the
   same 800 just reads as heavy. These four rules are the whole of it:
   drop the weight, open the tracking, let the ground do the work.

   The page title and the skill titles now take var(--hue) — i.e. the
   section's own colour off the table above — instead of the old odd/even
   brass-crimson rule, which is precisely the "assigned by class" the
   owner objected to. Crimson still lands on a beat; it lands on the
   rotation's beat now rather than on a parity trick. */
/* The page title draws crimson or brass at random each load (owner, 31 Aug
   2026). The colour itself is set inline by normalizeSections, which is
   the only thing that knows this load's seed; the weight and tracking
   stay here. --n-cream is the fallback for the fragment being viewed
   outside the app, where that script never runs. */
html[data-theme="noir"] .tpage .topic-title{
  color:var(--n-cream);font-weight:300!important;letter-spacing:.01em!important}
html[data-theme="noir"] .tpage .topic-sub{
  font-weight:300!important;color:rgba(239,227,211,.62)!important}
html[data-theme="noir"] .tpage .tb-section-header > .tb-section-label[data-sv-numbered] .sv-skilltitle{
  color:var(--hue)!important;font-weight:300!important;letter-spacing:.012em!important}
/* THE SKILL CHIP IS ALWAYS CRIMSON, on every page, whatever the section's
   colour is. This is what finally makes the accent unconditional: the
   rotation can start anywhere and a two-section page can draw two quiet
   hues, but the chips are structural, they repeat once per section, and
   they are small — which is exactly the dose a deep red wants. Crimson
   now carries the page's SKELETON (eyebrow, skill chips, callouts, the
   exam button) while cream, brass and stone carry its CONTENT. Losing
   this rule is how 2.2 and 3.9 end up with no red on them at all. */
html[data-theme="noir"] .tpage .tb-section-header > .tb-section-label[data-sv-numbered] .sv-skillnum{
  font-weight:600!important;letter-spacing:.2em!important;
  color:var(--n-crimson-lit)!important;
  border-color:rgba(var(--n-crimson-lit-rgb),.5)!important;
  background:rgba(var(--n-crimson-rgb),.14)!important}
html[data-theme="noir"] .tpage :is(.cc-title,.cc-label,.tb-section-label){
  font-weight:500!important;letter-spacing:.02em}
html[data-theme="noir"] .tpage .cc-label{letter-spacing:.16em!important}

/* ── GROUNDS (owner: "maybe some [grounds] for some of the boxes,
   crimson/beige with complimentary colors inside") ────────────────────
   The card cycle already tints every card in its own colour. What was
   missing is a box that commits — a real crimson or cream field with the
   type set against it rather than beside it. Every THIRD card in a row
   takes one, so it arrives as a beat down the page and not as wallpaper. */
html[data-theme="noir"] .tpage .tb-section .ccard:nth-child(3n+3){
  background:linear-gradient(rgba(var(--hue-rgb),.16),rgba(var(--hue-rgb),.16)) var(--c-ground)!important;
  border-color:rgba(var(--hue-rgb),.5)!important}
html[data-theme="noir"] .tpage .callout{
  background:linear-gradient(rgba(var(--n-crimson-rgb),.14),rgba(var(--n-crimson-rgb),.14)) var(--c-ground)!important;
  border-left-color:var(--n-crimson-lit)!important}
html[data-theme="noir"] .tpage .callout :is(.callout-head,b,strong){color:var(--n-cream)!important}

/* ── NAMED CARDS OFF THE DEAD GREYS ───────────────────────────────────
   "A card that names its own colour keeps it" is the right rule and it
   stays. But two of the eight slots those names resolve to are smoke and
   slate — greys that exist to be quiet — so a card someone wrote as
   `ccard purple` landed on nothing at all, and a page could carry four
   of them. Inside a page, every name is mapped onto the theme's live
   tones instead; the card still keeps ITS OWN choice, that choice just
   resolves to something that reads. The nav is untouched, which is where
   --c-purple and --c-blue are still doing quiet work on purpose. */
html[data-theme="noir"] .tpage .tb-section :is(.ccard.cyan,.vocab-card.cyan-card){
  --hue:var(--n-cream);       --hue-rgb:var(--n-cream-rgb)}
html[data-theme="noir"] .tpage .tb-section :is(.ccard.gold,.vocab-card.gold-card){
  --hue:var(--n-brass);       --hue-rgb:var(--n-brass-rgb)}
html[data-theme="noir"] .tpage .tb-section :is(.ccard.pink,.vocab-card.pink-card,.ccard.orange){
  --hue:var(--n-crimson-lit); --hue-rgb:var(--n-crimson-lit-rgb)}
html[data-theme="noir"] .tpage .tb-section :is(.ccard.green,.vocab-card.green-card){
  --hue:var(--n-stone);       --hue-rgb:var(--n-stone-rgb)}
html[data-theme="noir"] .tpage .tb-section :is(.ccard.purple,.vocab-card.purple-card){
  --hue:var(--n-brass);       --hue-rgb:var(--n-brass-rgb)}

/* the desk's theme picker */
.dk-theme-row{display:flex;gap:.6rem;margin-top:.6rem}
.dk-theme{min-width:7rem}
.dk-theme.on{border-color:var(--c-gold)!important;color:var(--c-gold)!important}

/* ═══ OPAQUE BOXES, THE MISSED HALF (owner, 27 Aug 2026: "did we not do
   anything about it?"). The 26 Aug opaque-boxes rule covered the shared
   components; the unit-2 pages' LOCAL surfaces — panels, labs, the 2.3
   story band, every guided box — still painted their tints over
   transparency, so the aurora washed through them (2.3's band read
   army-green). Same cure, same trick: each keeps its exact tint,
   composed over near-black. */
.tpage :is(.u21-panel,.u21-wheel){
  background:linear-gradient(rgba(255,255,255,.025),rgba(255,255,255,.025)) var(--c-ground)!important}
.tpage .u22-panel{
  background:linear-gradient(rgba(255,255,255,.022),rgba(255,255,255,.022)) var(--c-ground)!important}
.tpage :is(.u23-panel,.u24-panel){
  background:linear-gradient(rgba(255,255,255,.02),rgba(255,255,255,.02)) var(--c-ground)!important}
.tpage .u21-system{
  background:linear-gradient(rgba(255,255,255,.018),rgba(255,255,255,.018)) var(--c-ground)!important}
.tpage .u21-readin{
  background:linear-gradient(rgba(255,255,255,.05),rgba(255,255,255,.05)) var(--c-ground)!important}
.tpage .u21-place{background:#0d0d11!important}
.tpage :is(.u21-panel--cyan,.u21-bit-lab){
  background:linear-gradient(rgba(0,231,240,.045),rgba(0,231,240,.045)) var(--c-ground)!important}
.tpage :is(.u22-panel--cyan,.u23-panel--cyan,.u24-panel--cyan){
  background:linear-gradient(rgba(0,231,240,.04),rgba(0,231,240,.04)) var(--c-ground)!important}
.tpage .u21-panel--lime{
  background:linear-gradient(rgba(84,242,45,.045),rgba(84,242,45,.045)) var(--c-ground)!important}
.tpage .u21-build{
  background:linear-gradient(rgba(84,242,45,.035),rgba(84,242,45,.035)) var(--c-ground)!important}
.tpage .u21-panel--pink{
  background:linear-gradient(rgba(255,35,141,.05),rgba(255,35,141,.05)) var(--c-ground)!important}
.tpage .u22-panel--pink{
  background:linear-gradient(rgba(255,35,141,.04),rgba(255,35,141,.04)) var(--c-ground)!important}
.tpage .u21-panel--violet{
  background:linear-gradient(rgba(176,38,255,.045),rgba(176,38,255,.045)) var(--c-ground)!important}
.tpage .u23-proj{
  background:linear-gradient(rgba(255,230,0,.028),rgba(255,230,0,.028)) var(--c-ground)!important}
.tpage :is(.u21-do,.u22-do,.u23-do,.u24-do,.u23-pipe){
  background:linear-gradient(rgba(var(--hue-rgb),.05),rgba(var(--hue-rgb),.05)) var(--c-ground)!important}
.tpage :is(.u22-rule,.u23-rule,.u24-rule){
  background:linear-gradient(rgba(var(--hue-c-rgb),.05),rgba(var(--hue-c-rgb),.05)) var(--c-ground)!important}
.tpage :is(.u21-takeaway,.u23-take,.u24-take){
  background:linear-gradient(rgba(var(--hue-b-rgb),.05),rgba(var(--hue-b-rgb),.05)) var(--c-ground)!important}
.tpage .u23-field{
  background:linear-gradient(rgba(255,255,255,.018),rgba(255,255,255,.018)) var(--c-ground)!important}

/* ── WHAT THE PROGRAM DID TO THIS OPTION ──────────────────────────────
   The evidence line under a ladder rung when the options are the data:
   one program, run once per option, by the same interpreter the code
   panels use. Never authored — if it cannot be computed it is not shown. */
html body .tpage .qte-runline{
  margin:.5rem 0 0;font:600 .92rem/1.6 'JetBrains Mono',ui-monospace,monospace}
html body .tpage .qte-runline code{
  padding:.1em .4em;border-radius:5px;
  background:rgba(var(--hue-rgb),.14);color:var(--hue)}
html body .tpage .qte-runline code.is-same{
  background:rgba(255,255,255,.06);color:rgba(255,255,255,.6)}
html body .tpage .qte-runarrow{color:rgba(255,255,255,.4);margin:0 .15rem}
html body .tpage .qte-runouts{color:var(--c-ok);font-size:.86rem}

/* an option the student has picked but has not been marked yet — a
   select-two needs both before anything is right or wrong, so there has to
   be a state that means "chosen" without meaning "correct" */
html body .tpage .qte-wopt.is-chosen{
  border-color:rgba(255,255,255,.55);background:rgba(255,255,255,.07)}

/* ── NOIR: A LIGHT BUTTON TAKES DARK TEXT ─────────────────────────────
   Owner, 2 Sep 2026: "some of the text, especially inside tables/sims,
   can't be read because it's either red on red or white on super light
   grey. We may need to standardize on black there."

   Three controls fill themselves from a palette token and then hard-code
   `color:#fff`. In neon those tokens are saturated and white sits on them
   fine. In noir the same tokens are the LIGHT end of the theme — cyan is
   cream, blue-new is ash — so the fill goes pale and the white text stays
   white:

     .codetab.on   white on cream  1.27   invisible
     .vex-next     white on cream  1.27   invisible, and it is the button
                                          that moves you through the exam
     .qte-btn      white on ash    3.68   readable, under the line

   Near-black on cream is 15.61 and on ash is 5.37. The fill is left alone
   — the theme is right, the text colour was simply not asked to follow
   it. Measured across all 28 topics; below-3.0 nodes went from 99 to 0
   with the crimson change and these were what the stricter pass turned
   up next.

   ONE SCOPE NOTE, learned by breaking it: the question-type tabs (Learn
   it / Prove it) carry BOTH .qte-tab and .codetab, and their `on` state
   is a dark tinted ground, not a cream fill. A blanket .codetab.on rule
   put near-black on near-black across 26 of them. It is the cream-filled
   one only. */
html[data-theme="noir"] .tpage .qte-btn,
html[data-theme="noir"] .tpage .codetab.on:not(.qte-tab),
html[data-theme="noir"] .tpage .vex-next,
html[data-theme="noir"] .tpage .qte-askchip.picked,
html[data-theme="noir"] .tpage .qte-segtab.on,
html[data-theme="noir"] .tpage .qte-w4mark.is-ok,
html[data-theme="noir"] .tpage .qte-w4mark.is-no{color:var(--n-ink)}


/* ═══════════════════════════════════════════════════════════════════
   HOW A ROW OF CARDS FOLDS  (Robin, 4 Sep 2026)
   "The 3 column across content collapses way too soon and looks
   terrible. The remaining 2 column boxes are allowed to get much
   smaller before folding up into mobile."

   Both measured, and both were true:

     width   3-up row              2-up row
     1180    3 columns             2 columns
     1024    2 columns + ORPHAN    2 columns
      900    1 column              2 columns at 232px each

   Two separate faults. A three-card row passing through a two-column
   stage leaves the third card alone on its own row, and 1024 is iPad
   landscape, so that is a device half the class actually holds. And the
   two-up boxes had no floor at all, so they just kept narrowing.

   THE RULE: a row of equal cards goes N to 1. It never passes through a
   stage that orphans one, and no column is allowed below about 17rem.
   Robin: "I favor a collapse to mobile sooner than later."

   Counted with :has() rather than by class, because the same grids are
   used with two, three and four children and only the COUNT decides
   whether folding leaves an orphan. Container queries, not media
   queries, because the sidebar takes 252px out of the window — what
   matters is the space the cards actually have, not the size of the
   screen. .tpage already declares container-type:inline-size. */

/* exactly three: hold three, then go straight to one */
@container (max-width: 42rem) {
  .tpage .card-grid:has(> :nth-child(3)):not(:has(> :nth-child(4))),
  .tpage .tb-columns:has(> :nth-child(3)):not(:has(> :nth-child(4))) {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* exactly two, or a 2x2 of four: fold before a column gets thin */
@container (max-width: 35rem) {
  .tpage .card-grid:has(> :nth-child(2)):not(:has(> :nth-child(3))),
  .tpage .tb-columns:has(> :nth-child(2)):not(:has(> :nth-child(3))),
  .tpage .card-grid.four,
  .tpage .tb-columns.tb-flat-2 {
    grid-template-columns: minmax(0, 1fr);
  }
}


/* ═══════════════════════════════════════════════════════════════════
   THE SIMS, IN THE MATRIX THEME
   Robin, 5 Sep 2026, on the matrix background: "for it to really work -
   the colors in sims will all need to be members of any color family
   they like, as long as it's green..."

   THE SIMS CANNOT SEE THE THEME. Each one is a separate iframe document
   with its own stylesheet and no link to palette.css — the palette file
   says so itself: "Standalone sims in /sims are separate iframe
   documents and carry their own copy — they don't see these." So no
   amount of tokenising the PAGES turns a sim green; the sim is a
   different document.

   Doing it properly means passing the theme into ten iframes and then
   moving their hardcoded colour onto tokens — and the pages alone carry
   904 hardcoded values. That is a large job whose payoff shows only in
   two themes, on the course whose prose Robin has already said he is
   about to rewrite.

   So: a filter, which is a hack and worth naming as one. grayscale
   flattens whatever colours a sim chose, sepia gives a single hue to
   push, and hue-rotate lands it on green. Nothing inside any sim is
   edited and deleting these four lines reverts it completely.

   WHAT IT COSTS, honestly: it flattens DISTINCTIONS. A sim using pink
   and cyan to mean two different things now says both in green. Where
   that actually costs meaning — the compression and number-systems labs
   are the likely ones — that sim gets the real fix instead, and an
   opt-out class here.

   Only in matrix. Every other theme leaves the sims exactly as they are.
   ═══════════════════════════════════════════════════════════════════ */
html[data-theme="matrix"] .sim-embed iframe:not(.sim-keep-color){
  filter: grayscale(1) sepia(1) hue-rotate(75deg) saturate(3.2) brightness(.95);
}

/* the end of a finite practice run — finished, not broken */
.tpage .qte-pvend{text-align:center;padding:1.6rem 1rem;
  border:1px solid rgba(var(--c-ok-rgb),.4);border-radius:14px;
  background:rgba(var(--c-ok-rgb),.07)}
.tpage .qte-pvend-t{margin:0 0 .4rem;font-size:1.1rem;font-weight:800;color:var(--c-ok)}
.tpage .qte-pvend-s{margin:0 auto;max-width:46ch;font-size:.92rem;line-height:1.55;
  color:rgba(255,255,255,.62)}

/* ═══════════════════════════════════════════════════════════════════
   THE THREE HEADERS — locked site-wide, 12 Sep 2026

   Robin, setting the template before the shell rolls out: "the header
   rules need to be locked in site-wide." Three levels, and which one a
   thing gets is decided by WHAT KIND OF BREAK it is, never by how
   important it feels:

   H1  .tbh1        BIG WHITE. The highest-contrast break on the page.
                    Used between whole modes — the teaching and the
                    questions — and for anything that has to read as
                    "something different starts here". Every topic page
                    carries one above the type tabs.

   H2  .tb-section-label + .tb-section-line
                    WHITE, lighter weight, rainbow rule. A TOPIC break:
                    a new idea, a shift in content, and before any sim.
                    The text used to be the section's hue; it is white
                    now, because colour was doing a job the rule already
                    does and it made H2 compete with H3.

   H3  .tbh3        COLOURED BOLD with a white sub-line, over a small
                    kicker. A shift WITHIN a topic. The trap Robin named:
                    "Add a Bit" was an H3 separating how-binary-works
                    from the bits/values sim — that is a whole idea
                    changing, so it is an H2. H3 is for moves inside one
                    idea, not between two.

   .sim-embed-* are the same thing under their old names, kept so the
   sixty existing sims do not need editing.

   AND THE RULE THAT OUTRANKS ALL THREE — every title is LITERAL.
   Robin, 12 Sep 2026: "all titles MUST be literal. We have talked about
   this many times- which is more ell friendly 'Start With One Bit' or
   'How Bits Work'?"

   The second one, and the reason is what a student does with a heading:
   they scan it to find out what the section is ABOUT. "Start With One
   Bit" is a narrative instruction — it tells them what to do, not what
   they are looking at, so an ELL reader has to read the whole section to
   learn what the heading should have told them. A title names its
   content. No invitations, no verbs aimed at the reader, no wordplay.
   ═══════════════════════════════════════════════════════════════════ */

/* ── H2: white, not the section's hue ─────────────────────────────── */
.tpage .tb-section .tb-section-label,
.tpage .tb-section .tb-section-label *{color:#fff!important}
/* the skill chip is the one thing in a label that keeps its colour */
.tpage .tb-section-header > .tb-section-label[data-sv-numbered] .sv-skillnum,
.tpage .tb-section-header > .tb-section-label[data-sv-numbered] .sv-skillnum *{
  color:var(--hue)!important}

/* ── H1: heavier bold, centred, no rules ─────────────────────────────
   Robin, 12 Sep 2026, after seeing it: "I misspoke- seeing it is
   different. Full section titles- heavier bold centered, no rainbow
   lines, sub title below... That rainbow double line is horrible."

   Which is the right call for a reason the drawing made obvious: two
   rules pointing inward framed the title like a certificate, and the eye
   read the LINES before the words. Weight and air do the same job
   without adding furniture. */
.tpage .tbh1{
  max-width:var(--content-w);margin:3.4rem auto 1.6rem;
  padding:0 1.25rem;text-align:center;
}
/* THE H1 DROPS THE TOPIC NAME ALTOGETHER. Robin, 12 Sep 2026: do
   "WHAT COLLEGE BOARD ASKS:" without the topic itself.

   It took three passes to get here and the last one is the right one.
   "How Computers Think on the Test" read like the computer administers
   the AP test. Naming the topic AND the qualifier fixed the misreading
   but made a long line in the display face: "it ends up visually
   dominating everything else." Splitting it across a kicker and the big
   type fixed the length — and left the topic name printed twice on one
   screen, since the hero says it thirty lines above.

   So the topic name comes out. What is left is short, in the display
   face, and is the only thing this header ever had to say: the mode is
   changing, and the other party is College Board. The colon hands
   straight off to the sub-line under it. No kicker — there is nothing
   left for one to carry. */
.tpage .tbh1-t{
  display:block;color:#fff;
  font:800 clamp(1.5rem,3.8vw,2.1rem)/1.15 Outfit,system-ui,sans-serif;
  letter-spacing:-.005em;
}
.tpage .tbh1-s{
  margin:.8rem auto 0;max-width:62ch;
  font-size:clamp(.98rem,2.2vw,1.12rem);line-height:1.6;
  color:rgba(255,255,255,.78);
}
.tpage .tbh1-s b{color:#fff}

/* ── H3: coloured bold, white sub-line ────────────────────────────── */
.tpage .tbh3{margin:1.8rem 0 .9rem}
.tpage :is(.tbh3-k,.sim-embed-kicker){
  margin:0 0 .1rem;color:var(--c-green,#54f22d);font-size:.72rem;
  font-weight:800;letter-spacing:.16em;text-transform:uppercase;
}
.tpage :is(.tbh3-t,.sim-embed-title){
  margin:0 0 .3rem;color:var(--hue);font-size:1.45rem;font-weight:800;
}
.tpage :is(.tbh3-s,.sim-embed-guide){
  margin:0 0 .75rem;color:rgba(255,255,255,.85);
  font-size:1.02rem;line-height:1.6;
}

/* ═══ THE TAB PANEL IS ONE THING ═════════════════════════════════════
   Robin: "the whole tab content (including modal button) needs to have a
   very subtle border to clearly show it is all one, and all related to
   one tab. Finally- If we are on the red tab- the primary color in there
   needs to be red, border too. We should def use other complimentary
   pairs, it should be just 1 complimentary color."

   So the panel takes the tab's own colour as its PRIMARY and exactly one
   partner, rather than the page's seven-hue rotation. A tab and its
   contents are then the same colour idea, and the border says where that
   idea stops. This is the one place a box earns its keep: it is not
   decorating content, it is saying "all of this belongs to that tab".
   ═══════════════════════════════════════════════════════════════════ */
/* ═══ TAB SHELL — THE KNOBS ══════════════════════════════════════════
   Robin, 12 Sep 2026, giving the word to roll the shell out to every
   page: "keep it as tweak able as possible - in terms of look/feel."

   So every look-and-feel number the shell uses is a named value HERE,
   and nowhere else. Want the tabs smaller, the panel edge fainter, the
   OPEN label quieter, more air between two buttons? One line, one file,
   every page moves. Nothing below this table carries a raw number that
   you would ever want to argue with.

   The structural numbers stay where they are used — a scaleX(0), a
   1px hairline, a keyframe — because those are not taste. */
.tpage{
  /* the tab strip */
  /* -2px on Robin's word, 23 Sep 2026. A flat calc rather than new
     clamp bounds, because the middle term is a vw and 2px is not a
     fixed fraction of the viewport -- this takes exactly two pixels
     off at every width, which is what was asked for. */
  --ts-tab-size:calc(clamp(.8rem,1.45vw,1.02rem) - 2px);  /* the tab's own label */
  --ts-tab-pill:.58rem;                        /* the SKILL n pill */
  --ts-tab-dim:.38;                            /* inactive tab, 0-1 */
  --ts-tab-dim-hover:.7;
  --ts-tab-rule:3px;                           /* underline joining tab to panel */
  --ts-tab-pad:.6rem .7rem .7rem;
  --ts-tab-gap:.15rem;

  /* the panel the tabs open */
  --ts-panel-edge:.20;                         /* border alpha on the tab colour */
  --ts-panel-tint:.035;                        /* ground tint at the top */
  --ts-panel-radius:16px;
  --ts-panel-pad:.4rem clamp(.6rem,2vw,1.4rem) 1.6rem;
  --ts-panel-air:2.1rem;                       /* air under the tab strip */

  /* the destination button at the foot of a panel */
  --ts-btn-pad:.8rem 1rem;
  --ts-btn-radius:13px;
  --ts-btn-title:1.1rem;                       /* the type's name */
  --ts-btn-sub:.88rem;                         /* the line under it */
  --ts-btn-kick:.66rem;                        /* AP QUESTION */
  --ts-btn-open:.68rem;                        /* the OPEN label */
  --ts-btn-gap:.6rem;                          /* between two stacked buttons */
  --ts-btn-edge:.42;                           /* border alpha */
  --ts-btn-fill:.07;                           /* ground alpha */
  --ts-btn-fill-hover:.15;
}
.tpage .n12-panel{
  border:1px solid rgba(var(--tc-rgb),var(--ts-panel-edge));
  border-radius:var(--ts-panel-radius);
  padding:var(--ts-panel-pad);
  background:linear-gradient(180deg,rgba(var(--tc-rgb),var(--ts-panel-tint)),transparent 42%);
}
/* primary = the tab's colour, and ONE complement, alternating by
   position the way the page's own cards already do */
/* EVERY --tc HERE CARRIES A FALLBACK, AND THAT IS NOT BELT-AND-BRACES.
   Robin, 12 Sep 2026, on live 1.2: "Complete white throughout... if I
   was a white girl, I'd say eeewwww!"

   1.2's TAB BUTTONS carried --tc and its PANELS did not — 2.1's carry it
   on both, which is why 2.1 looked right and 1.2 did not. Every rule in
   this block reads the panel's colour through var(--tc), so on 1.2 they
   all resolved to nothing: white section titles, white card edges, no
   panel border. Pure silent divergence — no error, no warning, and the
   page it was authored against looked perfect the whole time.

   The default is the page's own rotation, so a panel that never declares
   a tab colour now falls back to exactly what it looked like before any
   of this existed, instead of falling off a cliff. */
.tpage .n12-panel{--tc:var(--hue,var(--c-cyan));--tc-rgb:var(--hue-rgb,var(--c-cyan-rgb));
  --tx:var(--c-gold);--tx-rgb:var(--c-gold-rgb)}
/* A MAIN-PAGE SECTION IS NOT A PANEL, and only the parts that should
   travel do. 2.1 keeps three types above the tabs, and their buttons
   looked nothing like the ones inside a tab because every rule here was
   scoped to .n12-panel. The BUTTON rules now match .n12-main as well;
   the border, the ground tint, the top padding and the hidden state do
   NOT, because a section on the main page should not grow a box. */
.tpage .n12-main{--hue-a:var(--tc)!important;--hue-a-rgb:var(--tc-rgb)!important;
  --hue-b:var(--tx)!important;--hue-b-rgb:var(--tx-rgb)!important;
  --hue-c:var(--tc)!important;--hue-c-rgb:var(--tc-rgb)!important}
.tpage .n12-panel .tb-section{
  --hue-a:var(--tc)!important;      --hue-a-rgb:var(--tc-rgb)!important;
  --hue-b:var(--tx)!important;      --hue-b-rgb:var(--tx-rgb)!important;
  --hue-c:var(--tc)!important;      --hue-c-rgb:var(--tc-rgb)!important;
}
/* the modal button is part of the tab, so it spans it. Three things were
   holding it to 196px of an 860px panel and all three had to go: the
   inline card is a GRID with more than one column, the row inside it is
   not full width, and the button's own width comes from a more specific
   rule than a plain `width:100%`. */
/* THE BUTTON MATCHES THE CONTENT COLUMN. Robin, 12 Sep 2026: "several of
   them the modal launch btn doesn't go matching width as content."

   Every one of them, in fact — all 18 tabs, by the same amount. The card
   carries `width:min(100%,760px)` from the inline-card rule, and 760 is a
   number from a page whose column was 760. Inside a panel the column is
   whatever the panel leaves (773 on 2.1, 813 on 2.4), so the button came
   up short by 13px or 53px depending on the page and never looked
   deliberate anywhere. The section's own header measures the column
   exactly; width:100% makes the button measure it the same way. */
.tpage :is(.n12-panel,.n12-main) .qte-types.qte-inline{
  display:block!important;
  width:100%!important;max-width:none!important;
}
.tpage :is(.n12-panel,.n12-main) .qte-row{width:100%!important;max-width:none!important}
/* Robin, 12 Sep 2026: "your demo had a little space between the buttons."
   It did, and this rule was already here doing nothing. The multi-type
   grid rule above (`:has(.qte-row:nth-child(2))`) sets margin-top:0 at
   0,5,0 and carries its :has() argument's specificity on top of that, so
   a plain 0,3,0 rule was being outranked on exactly the one property
   that mattered — the same shape as the .qte-n bug in this same block.
   Same fix: stop trying to outrank it, say !important. */
.tpage :is(.n12-panel,.n12-main) .qte-row + .qte-row{margin-top:var(--ts-btn-gap)!important}
.tpage :is(.n12-panel,.n12-main) .qte-open{
  width:100%!important;max-width:none!important;
  border-color:rgba(var(--tc-rgb),var(--ts-btn-edge))!important;
  background:rgba(var(--tc-rgb),var(--ts-btn-fill))!important;
}
/* ═══ THE MODAL BUTTON IS A DESTINATION, NOT A SWITCH ════════════════
   Robin picked idea 3 off docs/mock/exam-button.html: "it takes us
   somewhere special." So it stops looking like a control — a kicker when
   there is more than one in the tab, the type's name at real size, its
   own sub-line under it, and an arrow that leans on hover.

   The title was the tab's colour ON the tab's colour ("lacks contrast-
   color on color"). White reads on every one of the seven.

   THE MOCK HAD A SIX-SEGMENT PROGRESS METER AND THIS DOES NOT. The
   engine's only real signal is binary — a row gets .done when the
   student finishes that type's drill — and a meter with six segments
   would be drawing five states nothing measures. It shows the one state
   that is true instead. A real meter is possible later off the Firebase
   per-question log; it is not possible today. */
.tpage :is(.n12-panel,.n12-main) .qte-open{
  display:grid!important;grid-template-columns:1fr auto;
  align-items:center;gap:.15rem .9rem;
  padding:var(--ts-btn-pad)!important;border-radius:var(--ts-btn-radius)!important;
}
.tpage :is(.n12-panel,.n12-main) .qte-open .qte-flag{display:none}
.tpage :is(.n12-panel,.n12-main) .qte-open .qte-kick{
  grid-column:1/-1;font:900 var(--ts-btn-kick)/1 Outfit,system-ui,sans-serif;
  letter-spacing:.18em;text-transform:uppercase;color:var(--tc);
  margin-bottom:.35rem;
}
.tpage :is(.n12-panel,.n12-main) .qte-open .qte-open-txt{min-width:0}
.tpage :is(.n12-panel,.n12-main) .qte-open .qte-t{
  color:#fff!important;font-size:var(--ts-btn-title)!important;line-height:1.2!important;
}
.tpage :is(.n12-panel,.n12-main) .qte-open .qte-n{
  /* two earlier rules hide the sub-line and the arrow in inline mode.
     Styling them was not enough — display had to be put back, which is
     the same shape as every other bug today: a rule that looked applied
     and was being outranked on the one property that mattered. */
  display:block!important;margin-top:.2rem;font-size:var(--ts-btn-sub);font-weight:500;
  line-height:1.45;color:rgba(255,255,255,.6)!important;
}
/* the mock's arrow was a bare chevron at 1.3rem. The engine does not
   render a chevron — it renders the WORD "Open" — so 1.3rem made a
   headline out of it ("almost cartoonishly large"). It goes back to
   being a label: small, uppercase, letterspaced, with the chevron added
   after it so the lean-on-hover still reads. */
.tpage :is(.n12-panel,.n12-main) .qte-open .qte-go{
  display:inline-flex!important;align-items:center;gap:.32em;
  color:var(--tc)!important;
  font:800 var(--ts-btn-open)/1 Outfit,system-ui,sans-serif;
  letter-spacing:.14em;text-transform:uppercase;
  transition:transform .16s;
}
.tpage :is(.n12-panel,.n12-main) .qte-open .qte-go::after{
  content:'\203A';font-size:1.15rem;line-height:0;font-weight:700;
  letter-spacing:0;
}
/* HOVER HAD THE LABEL THE SAME COLOUR AS ITS OWN FILL. Robin, 12 Sep
   2026: "Hover on buttons- check colors... they can't be seen against
   some bg colors." On gold it was a solid gold pill with gold writing in
   it — completely blank.

   The old pill rule (line ~424) fills .qte-go with the row colour on
   hover and sets color:#fff to stay legible. That was correct until this
   block set color:var(--tc) with an !important, which beat it and left
   the text the same colour as the new fill. An !important that wins an
   argument it was not in.

   The fix is not a better ink colour, because there isn't one: measured
   against a solid fill, near-black clears 16:1 on gold and only 4.3:1 on
   purple, and white is the other way round. No single value wins on all
   seven, which is the tell that the SOLID FILL is the problem.

   Robin already ruled on this, 22 Aug 2026, a few hundred lines below in
   THE TAB ROW: NO SOLID STATES — "cyan has white text (can't be read)
   and every single button has a cyan hover... no solid hover or active,
   instead gentle part translucent fill same as several content areas —
   in the right colour of course." The pill was simply never brought into
   line with it. It is now a translucent tint and a brighter edge.

   The LABEL goes white, and keeping it the tab's colour was measured and
   rejected rather than argued about: hue text on a hue tint gets DIMMER
   on hover, not brighter — purple 4.13:1 at rest falls to 3.21:1, which
   is a control that gets harder to read the moment you point at it.
   White over the same tint measures 8.5:1 on gold, 12.9 on blue and 14.7
   on purple. So the old rule's colour:#fff was right all along; the
   solid fill under it was the whole bug. */
.tpage :is(.n12-panel,.n12-main) .qte-open:hover .qte-go{
  transform:translateX(5px);
  background:rgba(var(--tc-rgb),.18);
  border-color:rgba(var(--tc-rgb),.75);
  color:#fff!important;
}
/* the one state the engine actually knows */
.tpage :is(.n12-panel,.n12-main) .qte-row.done .qte-open{
  border-color:rgba(var(--c-green-rgb),.5)!important;
}
.tpage :is(.n12-panel,.n12-main) .qte-row.done .qte-kick::after{
  content:' \2713 done';color:var(--c-green);
}
.tpage :is(.n12-panel,.n12-main) .qte-open:hover{background:rgba(var(--tc-rgb),var(--ts-btn-fill-hover))!important}
.tpage :is(.n12-panel,.n12-main) .qte-types{margin-top:.4rem}

/* ═══ TAB SHELL — THE STRIP ══════════════════════════════════════════
   Lived inside 1.2 and again inside 2.1, byte for byte, with a comment
   in both saying "if it survives, both copies go and it moves into the
   engine." It survived, and six pages were about to carry six copies.

   Centred, and the tabs grow with their own words rather than sitting
   in a fixed track — Robin: "Centered tabs (expand as needed)."

   ONE ROW, ALWAYS. Robin, 11 Sep 2026: "the collapsing type lines. It
   is a tribute to inefficiency that we should have so little space when
   there is so damn much of it." Wrapping was the strip giving up:
   flex-wrap:wrap pushes the last tab onto a second row the moment the
   labels outgrow the column, which is the exact failure that killed
   horizontal tabs the first time. nowrap plus min-width:0 makes the
   TABS shrink instead, and a long label wraps inside its own tab. The
   row survives any width; only the tabs get narrower.

   Numbers come from the knob table above. ═════════════════════════ */
.tpage .n12-tabs{
  display:flex;justify-content:center;align-items:stretch;flex-wrap:nowrap;
  gap:var(--ts-tab-gap);max-width:var(--content-w,72rem);margin:1.5rem auto .2rem;
  padding:0 .5rem;
}
/* NO BOX. Robin, 11 Sep 2026: "no boxes around my tabs are there? Just
   clean simple tabs. Inactive one dimmed out." The first cut gave the
   active tab a tinted ground and a border, which turned it into a panel
   with a lid — the same boxed look the page standard threw out. The only
   things separating active from inactive are opacity and the rule
   underneath, which is all it needs. */
.tpage .n12-tab{
  flex:0 1 auto;min-width:0;text-align:left;cursor:pointer;background:none;
  border:0;padding:var(--ts-tab-pad);position:relative;
  opacity:var(--ts-tab-dim);transition:opacity .16s;
}
.tpage .n12-tab:hover{opacity:var(--ts-tab-dim-hover)}
.tpage .n12-tab.on{opacity:1}
/* the underline that joins the tab to its panel */
.tpage .n12-tab::after{
  content:'';position:absolute;left:.9rem;right:.9rem;bottom:-1px;
  height:var(--ts-tab-rule);
  border-radius:3px 3px 0 0;background:var(--tc);
  transform:scaleX(0);transition:transform .18s;
}
.tpage .n12-tab.on::after{transform:scaleX(1)}
/* a hairline between the tabs, sitting in the gap and stopping short
   at both ends so it separates without enclosing anything */
.tpage .n12-tab + .n12-tab::before{
  content:'';position:absolute;left:-.08rem;top:.7rem;bottom:.75rem;width:1px;
  background:rgba(255,255,255,.14);
}
.tpage .n12-tab:focus-visible{outline:2px solid var(--tc);outline-offset:3px}

/* pill + title: skill-view's own shapes, so a tab reads as the section
   header it replaced */
.tpage .n12-tnum{
  display:block;width:fit-content;margin:0 auto .4rem;padding:.22rem .6rem;border-radius:7px;
  border:1px solid rgba(var(--tc-rgb),.45);background:rgba(var(--tc-rgb),.1);
  color:var(--tc);font-size:var(--ts-tab-pill);font-weight:900;
  letter-spacing:.1em;text-transform:uppercase;white-space:nowrap;
}
/* CENTRED UNDER THE PILL (Robin, 15 Sep 2026: "if we are forced to 2
   lines for tab titles, lets center them below the pill"). Five lessons
   plus Other Uses means most titles wrap, and left-aligned two-line
   titles under a centred pill made the strip look ragged. */
.tpage .n12-ttl{
  display:block;color:#fff;font-size:var(--ts-tab-size);
  font-weight:800;line-height:1.18;white-space:normal;
  overflow-wrap:break-word;text-align:center;
}
.tpage .n12-ttl::after{
  content:"_";margin-left:.14em;color:var(--tc);font-weight:800;font-size:.8em;
  animation:n12cur 1.1s steps(1,end) infinite;
}
.tpage .n12-tab:not(.on) .n12-ttl::after{animation:none;opacity:.5}
@keyframes n12cur{0%,50%{opacity:1}51%,100%{opacity:0}}

/* No rule under the tabs. It was an adjacent-sibling match, so it drew
   under panel 0 and never under panel 1 — a line that blinked in and out
   with the tab. Nothing was holding it up but habit. */
.tpage .n12-panel[hidden]{display:none}

/* The tab already says what this section is called, so the header inside
   the panel would print the same words a second time. */
.tpage .n12-panel > .tb-section:first-child:not([data-n12-keephead]) > .tb-section-header{display:none}
/* ...but that header was also the only thing holding the content off the
   tabs. Robin: "I ain't got no margin!!" Put the air back on the panel,
   where it belongs to the tab strip rather than to a hidden element. */
.tpage .n12-panel{padding-top:var(--ts-panel-air)}

/* "Process" is a wider word than "DO" — the old 2.5rem track cropped it. */
.tpage .n12-item .ln .tag{min-width:4.7rem;text-align:center}

@media(max-width:620px){
  .tpage .n12-tabs{gap:.1rem;padding:0 .35rem}
  .tpage .n12-tab{padding:.55rem .45rem .6rem}
  .tpage .n12-ttl{font-size:.74rem}
  .tpage .n12-tnum{font-size:.52rem;margin-bottom:.25rem}
  .tpage .n12-item .ln .tag{min-width:4.2rem;font-size:.72rem}
}

/* ═══ 2.1's SIX SIMILARITIES ══════════════════════════════════════════
   Robin, 14 Sep 2026, on what the base-10/base-2 comparison has to be:
   "it needed to be more thorough - split page, and working down all of
   the ways they were similar, and thus all the reasons they shouldn't be
   intimidated."

   So the list IS the argument rather than decoration, and it is numbered
   because a student should be able to say "row 4" out loud in class.
   The numbers cycle the section's three hues by position, the same way
   every other row of things on this site does. */
.tpage .u21-samelist{display:flex;flex-direction:column;gap:.1rem;margin:1.1rem 0 1.4rem}
.tpage .u21-same{display:grid;grid-template-columns:auto 1fr;gap:.9rem;
  align-items:start;padding:.85rem 0;border-top:1px solid rgba(255,255,255,.07)}
.tpage .u21-same:first-child{border-top:0}
.tpage .u21-same-n{display:grid;place-items:center;width:1.85rem;height:1.85rem;
  border-radius:50%;font:900 .85rem/1 Outfit,system-ui,sans-serif;
  border:1px solid rgba(var(--hue-a-rgb),.45);color:var(--hue-a);
  background:rgba(var(--hue-a-rgb),.08)}
.tpage .u21-same:nth-child(3n+2) .u21-same-n{border-color:rgba(var(--hue-b-rgb),.45);
  color:var(--hue-b);background:rgba(var(--hue-b-rgb),.08)}
.tpage .u21-same:nth-child(3n) .u21-same-n{border-color:rgba(var(--hue-c-rgb),.45);
  color:var(--hue-c);background:rgba(var(--hue-c-rgb),.08)}
.tpage .u21-same h4{margin:0 0 .2rem;color:#fff;font-size:1rem;font-weight:800;line-height:1.3}
.tpage .u21-same p{margin:0;color:rgba(255,255,255,.74);font-size:.92rem;line-height:1.6}
.tpage .u21-same p b{color:var(--hue-a)}
.tpage .u21-same:nth-child(3n+2) p b{color:var(--hue-b)}
.tpage .u21-same:nth-child(3n) p b{color:var(--hue-c)}
@media(max-width:560px){
  .tpage .u21-same{grid-template-columns:1fr;gap:.35rem}
  .tpage .u21-same-n{width:1.6rem;height:1.6rem;font-size:.75rem}
}

/* ═══ LEARN IT v2 (16 Sep 2026) ═══════════════════════════════════════
   One question, three steps: Read it (College Board's words, tap-words
   with a bubble), Translate it (their sentence beside ours, the shared
   pieces lit), Answer it (aria works the choices one at a time).
   Every size is in em off .lrn so the fullscreen rule at the bottom
   scales the whole lesson for the projector. */
.tpage .lrn{--lrn:var(--row);--lrn-rgb:var(--row-rgb);font-size:1rem;color:#fff}
.tpage .lrn-top{display:flex;align-items:center;gap:.8em;flex-wrap:wrap;margin:0 0 1.1em;padding:0 0 .8em;
  border-bottom:1px solid rgba(255,255,255,.1)}
.tpage .lrn-pager{display:flex;align-items:center;gap:.35em;font-family:var(--mono);font-size:.7em;font-weight:800;
  letter-spacing:.1em;text-transform:uppercase;color:rgba(255,255,255,.7)}
.tpage .lrn-pager b{color:#fff}
.tpage .lrn-pg{width:2.1em;height:2.1em;border-radius:50%;border:1px solid rgba(255,255,255,.2);background:transparent;
  color:#fff;font-size:1.3em;line-height:1;cursor:pointer;font-family:inherit}
.tpage .lrn-pg:disabled{opacity:.25;cursor:default}
.tpage .lrn-pg:not(:disabled):hover{border-color:var(--lrn);background:rgba(var(--lrn-rgb),.12)}
.tpage .lrn-steps{display:flex;gap:.4em;margin-left:auto}
.tpage .lrn-stp{display:inline-flex;align-items:center;gap:.5em;padding:.4em .85em .4em .45em;border-radius:999px;
  border:1px solid rgba(255,255,255,.14);background:transparent;color:rgba(255,255,255,.66);font-family:inherit;
  font-size:.82em;font-weight:700;cursor:pointer;transition:all .15s}
.tpage .lrn-stp i{display:grid;place-items:center;width:1.55em;height:1.55em;border-radius:50%;font-style:normal;
  font-family:var(--mono);font-size:.8em;font-weight:800;background:rgba(255,255,255,.1);color:#fff}
.tpage .lrn-stp.on{border-color:var(--lrn);background:rgba(var(--lrn-rgb),.14);color:#fff}
.tpage .lrn-stp.on i{background:var(--lrn);color:#0b0d12}
.tpage .lrn-full{padding:.45em .8em;border-radius:999px;border:1px solid rgba(255,255,255,.14);background:transparent;
  color:rgba(255,255,255,.66);font-family:var(--mono);font-size:.66em;font-weight:800;letter-spacing:.08em;
  text-transform:uppercase;cursor:pointer}
.tpage .lrn-full:hover{border-color:var(--c-cyan);color:var(--c-cyan)}
.tpage .lrn-q,.tpage .lrn-stage{display:none}
.tpage .lrn-q.on,.tpage .lrn-stage.on{display:block;animation:lrn-in .28s ease-out}
@keyframes lrn-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
.tpage .lrn-head{margin:0 0 1em}
.tpage .lrn-kick{display:block;margin:0 0 .3em;color:var(--lrn);font-family:var(--mono);font-size:.68em;font-weight:800;
  letter-spacing:.14em;text-transform:uppercase}
.tpage .lrn-h{margin:0 0 .25em;font-size:1.7em;line-height:1.15;font-weight:800;color:#fff;letter-spacing:-.015em;text-wrap:balance}
.tpage .lrn-sub{margin:0;font-size:.95em;color:rgba(255,255,255,.62)}

/* 1 · READ IT — the question as printed, and the words that glow */
.tpage .lrn-cb{position:relative;padding:1.1em 1.25em 1em;border-radius:14px;border:1px solid rgba(255,255,255,.12);
  background:rgba(255,255,255,.03)}
/* CB's roman-numeral lists ("I. To determine ... II. To estimate ...")
   are authored one per line, and a <p> collapses those newlines into one
   running paragraph - which is exactly the wall of text this tab exists to
   take apart. pre-line keeps the authored line breaks and still collapses
   ordinary runs of spaces. */
.tpage .lrn-intro,.tpage .lrn-stem{margin:0 0 .7em;font-size:1.22em;line-height:1.6;color:#fff;white-space:pre-line}
.tpage .lrn-stem{font-weight:600}
.tpage .lrn-term{display:inline;padding:0;margin:0;border:0;background:none;color:var(--c-pink);font:inherit;font-weight:700;
  cursor:pointer;text-decoration:underline dotted rgba(var(--c-pink-rgb),.7);text-decoration-thickness:1.5px;
  text-underline-offset:.2em;transition:color .15s}
.tpage .lrn-term:hover,.tpage .lrn-term.on{color:var(--c-cyan);text-decoration-color:rgba(var(--c-cyan-rgb),.8)}
.tpage .lrn-opts{list-style:none;margin:.4em 0 0;padding:0;display:grid;gap:.35em}
.tpage .lrn-opts li{display:flex;gap:.7em;align-items:baseline;padding:.45em .7em;border-radius:9px;
  border:1px solid rgba(255,255,255,.09);font-size:1.05em;line-height:1.45}
.tpage .lrn-opts li b{flex:none;font-family:var(--mono);color:var(--lrn)}
.tpage .lrn-bubble{position:relative;margin:.2em 0 1em;padding:.8em 1em .85em;border-radius:12px;
  background:var(--c-pink);color:#fff;font-size:1.02em;line-height:1.45;box-shadow:0 14px 40px rgba(0,0,0,.45);
  animation:lrn-in .18s ease-out}
.tpage .lrn-bubble::before{content:'';position:absolute;top:-8px;left:var(--tip,20px);border:8px solid transparent;
  border-top:0;border-bottom-color:var(--c-pink)}
.tpage .lrn-bubble b{display:block;margin:0 0 .2em;font-size:1.05em;text-transform:lowercase}
.tpage .lrn-bar{display:flex;justify-content:space-between;align-items:center;gap:.6em;margin:1.2em 0 0;flex-wrap:wrap}
.tpage .lrn-go{padding:.7em 1.3em;border-radius:999px;border:1px solid var(--lrn);background:var(--lrn);color:#0b0d12;
  font-family:inherit;font-size:1em;font-weight:800;cursor:pointer;transition:transform .12s,box-shadow .12s}
.tpage .lrn-go:hover{transform:translateY(-1px);box-shadow:0 8px 24px rgba(var(--lrn-rgb),.35)}
.tpage .lrn-go.lrn-back{background:transparent;color:rgba(255,255,255,.8);border-color:rgba(255,255,255,.2)}
.tpage .lrn-go.lrn-back:hover{box-shadow:none;border-color:#fff;color:#fff}
.tpage .lrn-go.lrn-done{background:var(--c-green);border-color:var(--c-green)}

/* 2 · TRANSLATE IT — theirs beside ours, shared pieces lit */
.tpage .lrn-twin{display:grid;grid-template-columns:1fr 1fr;gap:.9em}
.tpage .lrn-pane{padding:1em 1.15em 1.1em;border-radius:14px;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.03)}
.tpage .lrn-pane p{margin:0;font-size:1.12em;line-height:1.6;white-space:pre-line}
.tpage .lrn-plabel{display:block;margin:0 0 .6em;font-family:var(--mono);font-size:.66em;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:rgba(255,255,255,.55)}
.tpage .lrn-ours{border-color:rgba(var(--lrn-rgb),.55);background:rgba(var(--lrn-rgb),.06)}
.tpage .lrn-ours .lrn-plabel{color:var(--lrn)}
.tpage .lrn-ours p{font-weight:600}
.tpage mark.lrn-same{background:rgba(var(--c-gold-rgb),.18);color:var(--c-gold);padding:0 .12em;border-radius:4px;
  box-shadow:inset 0 -2px 0 rgba(var(--c-gold-rgb),.7)}
.tpage .lrn-ask{margin:1.1em 0 0;display:flex;align-items:flex-end;gap:.7em}
.tpage .lrn-ask img{flex:none;width:3.2em;height:3.2em;border-radius:50%;object-fit:contain}
.tpage .lrn-ask .lrn-msg{max-width:none;padding:.85em 1.2em .95em}
.tpage .lrn-ask span{display:block;margin:0 0 .15em;font-family:var(--mono);font-size:.66em;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:rgba(255,255,255,.75)}
.tpage .lrn-ask strong{display:block;font-size:1.4em;line-height:1.25;font-weight:800;color:#fff;letter-spacing:-.01em}

/* 3 · ANSWER IT — the aria window, scripted */
.tpage .lrn-aria{border-radius:18px;border:1px solid rgba(255,255,255,.12);background:rgba(10,10,18,.72);
  box-shadow:0 24px 80px rgba(0,0,0,.45),0 0 40px rgba(var(--c-pink-rgb),.07),inset 0 1px 0 rgba(255,255,255,.08);overflow:hidden}
.tpage .lrn-ahead{display:flex;align-items:center;gap:.8em;padding:.8em 1.1em;border-bottom:1px solid rgba(255,255,255,.08)}
.tpage .lrn-ahead img{width:3.2em;height:3.2em;border-radius:50%;object-fit:contain}
.tpage .lrn-ahead b{display:block;font-size:.95em;letter-spacing:.5px;color:#fff}
.tpage .lrn-ahead span{display:block;font-size:.72em;font-weight:600;letter-spacing:.08em;text-transform:uppercase;color:rgba(255,255,255,.45)}
.tpage .lrn-msgs{display:flex;flex-direction:column;gap:.6em;padding:1em 1.1em;min-height:7em;max-height:24em;overflow-y:auto;
  border-top:1px solid rgba(255,255,255,.08)}
/* .tb-section b is !important-hued; inside the lesson bold is white on a
   coloured bubble, and the tapped word in the pink bubble is white too */
html body .tpage .lrn-msg b,html body .tpage .lrn-choice b,html body .tpage .lrn-bubble b,
html body .tpage .lrn-ask strong,html body .tpage .lrn-ahead b{color:inherit!important}
html body .tpage .lrn-msg.aria.ok .lrn-v{color:#0b0d12!important}
html body .tpage .lrn-opts li b{color:var(--lrn)!important}
html body .tpage .lrn-ahead b{color:var(--c-pink)!important}
.tpage .lrn-msg{max-width:85%;padding:.7em 1em;border-radius:14px;font-size:1em;line-height:1.5;animation:lrn-in .25s ease-out}
.tpage .lrn-msg.aria{align-self:flex-start;background:var(--c-pink);color:#fff;border-bottom-left-radius:6px}
.tpage .lrn-msg.you{align-self:flex-end;background:var(--c-purple);color:#fff;border-bottom-right-radius:6px}
.tpage .lrn-msg .lrn-v{display:inline-block;margin:0 .35em .1em 0;padding:.05em .55em;border-radius:999px;font-weight:800;
  background:rgba(255,255,255,.22);color:#fff}
.tpage .lrn-msg.aria.ok .lrn-v{background:var(--c-green);color:#0b0d12}
.tpage .lrn-typing{display:flex;gap:.3em;padding:.8em 1em}
.tpage .lrn-typing i{width:.5em;height:.5em;border-radius:50%;background:rgba(255,255,255,.85);animation:lrn-dot 1s infinite}
.tpage .lrn-typing i:nth-child(2){animation-delay:.15s}
.tpage .lrn-typing i:nth-child(3){animation-delay:.3s}
@keyframes lrn-dot{0%,80%,100%{opacity:.3;transform:translateY(0)}40%{opacity:1;transform:translateY(-.2em)}}
.tpage .lrn-aq{padding:1em 1.1em 0}
.tpage .lrn-aq p{margin:0 0 .5em;font-size:1.12em;line-height:1.55;color:#fff;white-space:pre-line}
.tpage .lrn-aq .lrn-aq-stem{font-weight:700}
.tpage .lrn-choices{display:grid;grid-template-columns:repeat(auto-fit,minmax(11em,1fr));gap:.5em;padding:.4em 1.1em 1em}
.tpage .lrn-choice{display:flex;align-items:center;gap:.6em;padding:.65em .8em;border-radius:11px;text-align:left;
  border:1px solid rgba(255,255,255,.16);background:rgba(255,255,255,.04);color:#fff;font-family:inherit;font-size:1em;
  cursor:pointer;transition:all .15s;opacity:.45;pointer-events:none}
.tpage .lrn-ready .lrn-choice{opacity:1;pointer-events:auto}
.tpage .lrn-choice b{flex:none;width:1.7em;height:1.7em;border-radius:50%;display:grid;place-items:center;
  font-family:var(--mono);font-size:.85em;background:rgba(255,255,255,.1)}
.tpage .lrn-choice span{flex:1;line-height:1.35}
.tpage .lrn-choice i{flex:none;width:1.2em;text-align:center;font-style:normal;font-weight:800}
.tpage .lrn-choice:not(.done):hover{border-color:var(--c-purple);background:rgba(var(--c-purple-rgb),.12)}
.tpage .lrn-choice.done{cursor:default}
.tpage .lrn-choice.ok{border-color:var(--c-green);background:rgba(var(--c-green-rgb),.14)}
.tpage .lrn-choice.ok b{background:var(--c-green);color:#0b0d12!important}
.tpage .lrn-choice.ok i::before{content:'\2713';color:var(--c-green)}
.tpage .lrn-choice.no{border-color:rgba(240,102,90,.6);opacity:.6}
.tpage .lrn-choice.no i::before{content:'\2717';color:#ff8a80}
.tpage .lrn-nextq,.tpage .lrn-done{opacity:.5}
.tpage .lrn-finished ~ .lrn-bar .lrn-nextq,.tpage .lrn-finished ~ .lrn-bar .lrn-done,
.tpage .lrn-q.lrn-finished .lrn-nextq,.tpage .lrn-q.lrn-finished .lrn-done{opacity:1}

/* projected: the dialog fills the screen and the lesson grows with it */
.tpage .qte-modal:fullscreen,.tpage .qte-modal.lrn-fs{width:100vw;max-width:100vw;max-height:100vh;border-radius:0;border:0}
.tpage .qte-modal:fullscreen .qte-modal-body,.tpage .qte-modal.lrn-fs .qte-modal-body{max-height:calc(100vh - 4.15rem);padding:1.6rem 3rem 2.4rem}
.tpage .qte-modal:fullscreen .lrn,.tpage .qte-modal.lrn-fs .lrn{font-size:1.45rem;max-width:1400px;margin:0 auto}
.tpage .qte-modal:fullscreen .lrn-msgs,.tpage .qte-modal.lrn-fs .lrn-msgs{max-height:40vh}
@media(max-width:700px){
  .tpage .lrn-twin{grid-template-columns:1fr}
  .tpage .lrn-steps{margin-left:0;width:100%;order:3}
  .tpage .lrn-stp{font-size:.74em;padding:.35em .6em .35em .35em}
  .tpage .lrn-h{font-size:1.4em}
  .tpage .lrn-intro,.tpage .lrn-stem{font-size:1.08em}
}

/* TEST IT — the graded check on one type (17 Sep 2026). It reuses .examcta,
   so it already looks like the exam it is; it only needs to sit inside a
   modal panel rather than at the foot of a page. */
.tpage .qte-panel[data-panel="tq"] .examcta.qte-typebar{margin:.4rem 0 0;width:100%}
.tpage .qte-tabs .qte-tab[data-tab="tq"]{--tab:var(--c-green); --tab-rgb:var(--c-green-rgb)}


/* ── OPEN IT LARGER ────────────────────────────────────────────────
   Robin, 21 Sep 2026: "2. Click to open larger." A sim embedded at 560px
   is fine to meet and hard to teach from, so an embed can carry a button
   that throws the WRAPPER full screen - wrapper, not iframe, so the sim
   never reloads and a student halfway through a set of questions does not
   lose it. Restored 22 Sep with the sim; this is the only CSS the embed
   needs, and nothing else from that day came back with it. */
.tpage .sim-expand-wrap{position:relative}
.tpage .sim-expand-wrap iframe{display:block}
.tpage .sim-expand{position:absolute;top:10px;right:10px;z-index:2;
  border:2px solid rgba(255,255,255,.28);border-radius:999px;
  background:rgba(10,10,15,.82);color:#fff;
  font:800 .82rem/1 inherit;letter-spacing:.04em;padding:.5rem .85rem;cursor:pointer;
  display:inline-flex;align-items:center;gap:.4em;opacity:.55;transition:opacity .15s,border-color .15s}
.tpage .sim-expand:hover,.tpage .sim-expand:focus-visible{opacity:1;border-color:var(--hue,#0ff)}
.tpage .sim-expand-wrap:hover .sim-expand{opacity:1}
.tpage .sim-expand-wrap:fullscreen{background:#0a0a0f}
.tpage .sim-expand-wrap:fullscreen iframe{width:100%;height:100%;border:0;border-radius:0}
@media(max-width:680px){.tpage .sim-expand{font-size:.74rem;padding:.42rem .7rem}}


/* ── THE TWO "HOW MANY" SIMS SIT ON THE PAGE, NOT IN A BOX ──────────
   Robin, 22 Sep 2026: "delete border and bg from both sims (2.1.3 and
   2.1.4)." Same finding as the arcade in September - a rule drawn around
   something that is already part of the page turns it into a visitor.

   Safe to do for these two because their ground IS the page's ground:
   both set html,body{background:var(--c-ground,#0a0a0f)} from the same
   palette token the page uses, so with the border and radius gone there
   is no seam to see. They are NOT made transparent - that is what turned
   a flicker into a permanent white box on 21 Sep, and the note at the
   foot of this file says why. */
html body .tpage .sim-embed .sim-expand-wrap iframe{
  border:0;border-radius:0;background:var(--c-ground,#0a0a0f)}

/* ══════════════════════════════════════════════════════════════════════
   MODALS: UNBOLD EVERYTHING BUT THE TITLES
   Robin, 22 Sep 2026: "Modals- all. Unbold all text in modals except
   titles, looks like it was placed there by walt disney."

   He is right and the cause is cumulative rather than any one rule -
   thirty-odd separate declarations in this file set 700, 800 or 900 on
   something inside a modal, so a student opens one and every line is
   shouting at the same volume. Nothing reads as more important than
   anything else, which is the same as nothing being important.

   This is one scoped override rather than thirty edits, on purpose: the
   individual rules still say what they were written to say, and lifting
   this block restores the old look exactly. Everything here is inside
   `.qte-modal`, so nothing on a page changes.

   WHAT STAYS BOLD, and the rule behind it: a title is a thing you scan
   to find out what you are looking at. The modal's own title, a section
   heading, a worked-example heading, and the small uppercase kickers
   that label a step. Those are the map. Everything else is the reading.

   Buttons and chips drop to 600 rather than 400. A control has to read
   as a control - at 400 a chip stops looking tappable - but 800 was what
   made a row of four options look like four headlines.

   Inline <b> and <strong> inside modal prose drop to 600 too. They mark
   the word that matters in a sentence, which still works at 600; at 800
   inside a 400 paragraph they were the loudest thing on screen.
   ══════════════════════════════════════════════════════════════════════ */
html body .tpage .qte-modal,
html body .tpage .qte-modal p,
html body .tpage .qte-modal li,
html body .tpage .qte-modal span,
html body .tpage .qte-modal div,
html body .tpage .qte-modal td,
html body .tpage .qte-modal th,
html body .tpage .qte-modal label{font-weight:400}

/* the reading: prose, options, explanations, verdicts, scores */
html body .tpage .qte-modal .qte-qlab,
html body .tpage .qte-modal .qte-cn,
html body .tpage .qte-modal .qte-snum,
html body .tpage .qte-modal .qte-kfb,
html body .tpage .qte-modal .qte-wopt,
html body .tpage .qte-modal .qte-wopt.hit,
html body .tpage .qte-modal .qte-pvcount,
html body .tpage .qte-modal .qte-pvlap,
html body .tpage .qte-modal .vex-verdict,
html body .tpage .qte-modal .vex-two,
html body .tpage .qte-modal .vex-pt,
html body .tpage .qte-modal .vex-score,
html body .tpage .qte-modal .qte-ltr,
html body .tpage .qte-modal .qte-sn,
html body .tpage .qte-modal .qte-numin,
html body .tpage .qte-modal .qte-w4row.is-ans,
html body .tpage .qte-modal .qte-askmark{font-weight:400}

/* the marked word in a sentence - present, not shouting */
html body .tpage .qte-modal b,
html body .tpage .qte-modal strong,
html body .tpage .qte-modal em{font-weight:600}

/* controls stay readable as controls */
html body .tpage .qte-modal .qte-btn,
html body .tpage .qte-modal .qte-chip,
html body .tpage .qte-modal button{font-weight:600}

/* THE TITLES, and only these */
html body .tpage .qte-modal .qte-modal-t,
html body .tpage .qte-modal .qte-foldhead h3,
html body .tpage .qte-modal .qte-wt-title,
html body .tpage .qte-modal .qte-drill-n,
html body .tpage .qte-modal .qte-gridcap,
html body .tpage .qte-modal .qte-pvend-t,
html body .tpage .qte-modal h1,
html body .tpage .qte-modal h2,
html body .tpage .qte-modal h3,
html body .tpage .qte-modal h4{font-weight:800}

/* ══════════════════════════════════════════════════════════════════════
   A STEM'S LINE BREAKS ARE PART OF THE QUESTION
   Found 22 Sep 2026 while putting CB's overflow paper in. CB prints its
   Roman-numeral questions with I, II and III on their own lines, and the
   banks store them that way - 2.2's lossy-compression question has used
   "\n  I. ..." since it was written. Nothing on the page honoured it, so
   every Roman-numeral question on the site rendered as one run-on
   paragraph: "...representation? I. The operation 4 + 8 will result in an
   overflow error. II. The operation 7 + 10 will..."

   A student has to hold three separate statements apart to answer one of
   these, and we were handing them a wall. `pre-line` keeps the newlines
   the bank already stores and still collapses the indent spaces, so no
   bank item has to change and nothing that has no newline moves at all.
   ══════════════════════════════════════════════════════════════════════ */
html body .tpage .qte-qlab,
html body .tpage .qte-cn,
html body .tpage .vex-q,
html body .tpage .lrn-q,
html body .tpage .qte-modal .qte-qlab,
html body .tpage .qte-modal .lrn-q,
html body .q-question,
html body .quiz-q{white-space:pre-line}

/* ══════════════════════════════════════════════════════════════════════
   THE SCENARIO READS AT THE SAME SIZE AS THE CHOICES
   Robin, 22 Sep 2026: "Reduce size of just scenario text to same size as
   response text."

   The Learn-it stem was 1.22em against options at 1.05em - about 19.5px
   against 16.8px on a laptop. On a projector that gap makes the scenario
   look like the headline and the four choices look like the small print,
   which is backwards: the choices are what a student has to compare
   word by word.

   Only the SIZE moves. The stem keeps its weight and its glossed words,
   so it still reads as the question rather than a fifth option.
   ══════════════════════════════════════════════════════════════════════ */
html body .tpage .lrn-intro,
html body .tpage .lrn-stem{font-size:1.05em}
@media(max-width:560px){
  html body .tpage .lrn-intro,
  html body .tpage .lrn-stem{font-size:1em}
}

/* ══════════════════════════════════════════════════════════════════════
   UNIT REVIEW — the modal. See unit-review.js for why it is a modal and
   not a tab. Every section carries its own --uh, set inline from the
   section's hue, so the rotation reads down the scroll the same way a
   page's sections do.
   ══════════════════════════════════════════════════════════════════════ */
/* ══════════════════════════════════════════════════════════════════════
   THE SHELL STANDS ON ITS OWN, and the first cut of this did not.
   Every modal rule in this file is scoped `.tpage .qte-modal`, and this
   dialog is appended to document.body so it can open over the nav as
   well as over a page - so it matched none of them. What rendered was a
   browser-default dialog: white canvas, no padding, body text invisible
   on white, and the embedded sims blown up across the whole screen.
   Nothing errored. It took a screenshot to see.

   So the shell is written out in full here and inherits nothing.
   ══════════════════════════════════════════════════════════════════════ */
dialog.urv-modal{
  width:min(980px,96vw);max-height:92vh;margin:auto;padding:0;overflow:hidden;
  border:1px solid rgba(255,255,255,.12);border-radius:16px;
  background:#0c0c12;color:rgba(255,255,255,.88);
  font-family:'Outfit',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  box-shadow:0 24px 80px rgba(0,0,0,.6);
  display:flex;flex-direction:column}
dialog.urv-modal::backdrop{background:rgba(4,4,8,.84);backdrop-filter:blur(4px)}
dialog.urv-modal .urv-head-bar{
  display:flex;align-items:flex-start;gap:1rem;padding:.9rem 1.15rem;flex:none;
  border-bottom:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.02)}
dialog.urv-modal .qte-modal-t{
  flex:1;min-width:0;margin:0;font-size:1.1rem;font-weight:800;line-height:1.25;
  color:#fff;letter-spacing:-.01em}
dialog.urv-modal .urv-close{
  flex:none;font-family:'JetBrains Mono',ui-monospace,monospace;font-size:.62rem;
  font-weight:800;letter-spacing:.1em;padding:.4rem .7rem;border-radius:8px;cursor:pointer;
  border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.05);
  color:rgba(255,255,255,.66)}
dialog.urv-modal .urv-close:hover{color:#fff;border-color:rgba(255,255,255,.34)}
dialog.urv-modal .urv-body{flex:1;min-height:0;overflow:auto}
.urv-kick{display:block;font-family:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;font-size:.6rem;font-weight:800;
  letter-spacing:.18em;text-transform:uppercase;color:var(--c-green);margin-bottom:.15rem}
dialog.urv-modal .urv-body{padding:1.1rem 1.25rem 2rem}
.urv-lead{margin:0 0 1rem;font-size:.95rem;line-height:1.6;color:rgba(255,255,255,.72);max-width:64ch}

.urv-jumps{display:flex;flex-wrap:wrap;gap:.35rem;margin:0 0 1.6rem;
  padding-bottom:1rem;border-bottom:1px solid rgba(255,255,255,.08)}
.urv-jump{font-family:inherit;font-size:.76rem;font-weight:700;letter-spacing:.02em;
  padding:.32rem .72rem;border-radius:999px;cursor:pointer;
  border:1px solid rgba(var(--uh-rgb),.42);background:rgba(var(--uh-rgb),.08);color:var(--uh)}
.urv-jump:hover{background:rgba(var(--uh-rgb),.18)}

.urv-sec{margin:0 0 2.2rem;scroll-margin-top:.5rem}
.urv-head{margin:0 0 .9rem}
.urv-eye{display:block;font-family:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;font-size:.62rem;font-weight:800;
  letter-spacing:.18em;text-transform:uppercase;color:var(--uh);margin-bottom:.2rem}
.urv-title{margin:0 0 .25rem;font-size:1.32rem;line-height:1.25;font-weight:800;color:#fff;
  letter-spacing:-.01em}
.urv-sub{margin:0;font-size:.88rem;line-height:1.55;color:rgba(255,255,255,.6);max-width:62ch}

/* place-value rail */
.urv-rail{margin:0 0 .8rem}
.urv-rail-l{display:block;font-size:.62rem;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:rgba(255,255,255,.4);margin-bottom:.4rem}
.urv-rail-cells{display:flex;gap:.35rem;flex-wrap:wrap}
.urv-cell{min-width:2.6rem;text-align:center;padding:.4rem .5rem;border-radius:9px;
  font-family:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;font-weight:800;font-size:.95rem;color:var(--uh);
  border:1px solid rgba(var(--uh-rgb),.4);background:rgba(var(--uh-rgb),.08)}

/* powers of two */
.urv-pow{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:.35rem;margin:0 0 .8rem}
.urv-pow-row{display:flex;align-items:baseline;gap:.3rem;padding:.4rem .6rem;border-radius:9px;
  border:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.02);font-size:.8rem;
  color:rgba(255,255,255,.55)}
.urv-pow-row b{font-family:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;font-size:1rem;color:#fff}
.urv-pow-row b.v{color:var(--uh)}
.urv-pow-row i{font-style:normal;color:rgba(255,255,255,.3);margin:0 .15rem}

/* fact rows */
.urv-facts{display:grid;gap:.35rem;margin:0 0 .8rem}
.urv-fact{display:grid;grid-template-columns:minmax(7.5rem,10rem) 1fr;gap:.8rem;
  padding:.5rem .75rem;border-radius:10px;border:1px solid rgba(255,255,255,.08);
  background:rgba(255,255,255,.02)}
@media(max-width:560px){.urv-fact{grid-template-columns:1fr;gap:.15rem}}
.urv-fact-k{font-size:.74rem;font-weight:800;letter-spacing:.04em;color:var(--uh);
  text-transform:uppercase;line-height:1.5}
.urv-fact-v{font-size:.88rem;line-height:1.55;color:rgba(255,255,255,.78)}
.urv-fact-v b{color:#fff;font-weight:600}

/* the two-column compare */
.urv-two{display:grid;grid-template-columns:1fr 1fr;gap:.6rem;margin:0 0 .8rem}
@media(max-width:560px){.urv-two{grid-template-columns:1fr}}
.urv-half{padding:.75rem .9rem;border-radius:12px;border:1px solid rgba(var(--uh-rgb),.35);
  background:rgba(var(--uh-rgb),.06)}
.urv-half.b{border-color:rgba(255,255,255,.14);background:rgba(255,255,255,.03)}
.urv-half-n{display:block;font-size:.95rem;font-weight:800;color:var(--uh);margin-bottom:.25rem}
.urv-half.b .urv-half-n{color:#fff}
.urv-half p{margin:0;font-size:.85rem;line-height:1.55;color:rgba(255,255,255,.72)}

/* the trap box */
.urv-trap{border-left:3px solid var(--c-gold);background:rgba(var(--c-gold-rgb),.06);
  border-radius:10px;padding:.6rem .85rem;margin:0 0 .8rem}
.urv-trap-k{display:block;font-size:.62rem;font-weight:800;letter-spacing:.16em;
  text-transform:uppercase;color:var(--c-gold);margin-bottom:.2rem}
.urv-trap p{margin:0;font-size:.85rem;line-height:1.55;color:rgba(255,255,255,.78)}
.urv-trap b{color:#fff;font-weight:600}

/* the small streak */
.urv-streak{margin:.6rem 0 0}
.urv-streak-l{display:block;font-size:.62rem;font-weight:800;letter-spacing:.16em;
  text-transform:uppercase;color:rgba(255,255,255,.38);margin-bottom:.35rem}
/* THE ARCADE SIZES ITSELF OFF ITS OWN VIEWPORT, so a wider frame makes
   it bigger, not roomier - clamp(4rem,15vw,7rem) on a 940px frame is a
   147px number, and at 250px tall the question overlapped its own
   scoreboard. The page embeds it at content width in a 340px box; this
   gives it the same deal rather than the full width of the modal. */
.urv-streak iframe{display:block;width:100%;max-width:680px;height:330px;
  margin:0 auto;border:0;background:none;border-radius:0}
@media(max-width:560px){.urv-streak iframe{height:320px}}

/* the final full-question streak */
.urv-final{border:1px solid rgba(var(--uh-rgb),.3);border-radius:14px;
  background:rgba(var(--uh-rgb),.04);padding:1rem 1.1rem;min-height:200px}
.urv-final-start{text-align:center;padding:1.4rem 0}
.urv-final-note{margin:.6rem 0 0;font-size:.8rem;color:rgba(255,255,255,.45)}
.urv-go{font-family:inherit;font-size:.9rem;font-weight:700;padding:.6rem 1.4rem;
  border-radius:11px;cursor:pointer;border:1px solid var(--c-green);
  background:rgba(var(--c-green-rgb),.12);color:var(--c-green)}
.urv-go:hover{background:rgba(var(--c-green-rgb),.22)}
.urv-scorebar{display:flex;gap:1.1rem;align-items:baseline;margin:0 0 .8rem;
  font-size:.68rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase;
  color:rgba(255,255,255,.42)}
.urv-scorebar b{font-family:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;font-size:1.05rem;color:var(--c-green);margin-left:.3rem}
.urv-scorebar .urv-count{margin-left:auto}
.urv-ctx{margin:0 0 .5rem;font-size:.9rem;line-height:1.6;color:rgba(255,255,255,.66);
  white-space:pre-line}
.urv-q{margin:0 0 .7rem;font-size:.95rem;line-height:1.6;color:#fff;white-space:pre-line}
.urv-opts{display:grid;gap:.35rem}
.urv-opt{display:flex;gap:.6rem;align-items:baseline;text-align:left;width:100%;
  font-family:inherit;font-size:.9rem;line-height:1.5;padding:.55rem .8rem;border-radius:10px;
  cursor:pointer;border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.02);
  color:rgba(255,255,255,.82)}
.urv-opt:hover{border-color:rgba(255,255,255,.26)}
.urv-opt-l{flex:none;font-family:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;font-weight:800;color:rgba(255,255,255,.45)}
.urv-opt.right{border-color:var(--c-green);background:rgba(var(--c-green-rgb),.12)}
.urv-opt.right .urv-opt-l{color:var(--c-green)}
.urv-opt.wrong{border-color:#EF722A;background:rgba(239,114,42,.12)}
.urv-opt.wrong .urv-opt-l{color:#EF722A}
.urv-fb-in{margin-top:.8rem;padding:.7rem .9rem;border-radius:11px;
  border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.03)}
.urv-fb-in.ok{border-color:rgba(var(--c-green-rgb),.35);background:rgba(var(--c-green-rgb),.07)}
.urv-fb-in.no{border-color:rgba(239,114,42,.35);background:rgba(239,114,42,.07)}
.urv-fb-v{display:block;font-size:.78rem;font-weight:800;letter-spacing:.06em;
  text-transform:uppercase;margin-bottom:.25rem}
.urv-fb-in.ok .urv-fb-v{color:var(--c-green)}
.urv-fb-in.no .urv-fb-v{color:#EF722A}
.urv-fb-in p{margin:0 0 .6rem;font-size:.85rem;line-height:1.55;color:rgba(255,255,255,.72)}

/* the nav row that opens it */
.nav-item.nav-unit-review .nav-num{color:var(--c-green);border-color:rgba(var(--c-green-rgb),.4)}
/* tbqRender's own wrapper inside the review's streak */
dialog.urv-modal .urv-body-q{margin:0 0 .8rem}
dialog.urv-modal .urv-body-q p{margin:0 0 .5rem;font-size:.92rem;line-height:1.6;
  color:rgba(255,255,255,.82);white-space:pre-line}
dialog.urv-modal .urv-body-q table{border-collapse:collapse;margin:.5rem 0;font-size:.85rem}
dialog.urv-modal .urv-body-q th,dialog.urv-modal .urv-body-q td{
  border:1px solid rgba(255,255,255,.16);padding:.35rem .6rem;text-align:left}
dialog.urv-modal .urv-body-q th{background:rgba(255,255,255,.05);color:#fff;font-weight:700}

/* ── LOOK BACK (23 Sep 2026) ───────────────────────────────────────
   The retrospective rung. It reuses dialog.urv-modal and every .urv-*
   question rule above — only the MAP is new, so this block is small on
   purpose. See look-back.js for why the map opens first. */
.lbk-actions{display:flex;flex-wrap:wrap;gap:.5rem;margin:0 0 1rem}
.lbk-go-2{
  border-color:rgba(var(--c-orange-rgb),.55) !important;
  background:rgba(var(--c-orange-rgb),.10) !important;
  color:var(--c-orange) !important;
}
.lbk-go-2:hover{background:rgba(var(--c-orange-rgb),.20) !important}

.lbk-verdict{border-left:3px solid var(--c-orange);background:rgba(var(--c-orange-rgb),.06);
  padding:.65rem .85rem;border-radius:0 11px 11px 0;margin:0 0 1.2rem}
.lbk-verdict.ok{border-left-color:var(--c-green);background:rgba(var(--c-green-rgb),.06)}
.lbk-verdict-k{display:block;font-size:.62rem;font-weight:800;letter-spacing:.16em;
  text-transform:uppercase;color:var(--c-orange);margin-bottom:.25rem}
.lbk-verdict.ok .lbk-verdict-k{color:var(--c-green)}
.lbk-verdict p{margin:0;font-size:.9rem;line-height:1.55;color:#fff}

.lbk-maphead{margin:0 0 .65rem}
.lbk-maphead-k{display:block;font-family:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;
  font-size:.62rem;font-weight:800;letter-spacing:.16em;text-transform:uppercase;
  color:rgba(255,255,255,.5);margin-bottom:.2rem}
.lbk-maphead-s{display:block;font-size:.82rem;line-height:1.5;color:rgba(255,255,255,.55);max-width:58ch}

.lbk-map{display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));gap:.4rem}
.lbk-chip{
  display:flex;align-items:baseline;gap:.5rem;text-align:left;width:100%;
  font-family:inherit;font-size:.85rem;font-weight:600;color:rgba(255,255,255,.8);
  padding:.55rem .7rem;border-radius:11px;cursor:pointer;
  border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.03);
  transition:border-color .15s,background .15s,color .15s;
}
.lbk-chip:hover{border-color:rgba(255,255,255,.34);color:#fff}
.lbk-chip-t{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lbk-chip-n{flex:none;font-family:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;
  font-size:.66rem;color:rgba(255,255,255,.35)}
/* lit = in this rung's focus, so the check will ask about it. It is a
   hint, never a restriction — every chip is tappable from the first
   second, which is the whole point of the screen. */
.lbk-chip.lit{border-color:rgba(var(--c-orange-rgb),.42);background:rgba(var(--c-orange-rgb),.05)}
.lbk-chip.ok{border-color:var(--c-green);background:rgba(var(--c-green-rgb),.10);color:#fff}
.lbk-chip.ok .lbk-chip-n{color:var(--c-green)}
.lbk-chip.miss{border-color:var(--c-orange);background:rgba(var(--c-orange-rgb),.14);color:#fff}
.lbk-chip.miss .lbk-chip-n{color:var(--c-orange)}

.lbk-bar{display:flex;align-items:center;gap:.7rem;margin:0 0 .9rem}
.lbk-back{font-family:inherit;font-size:.76rem;font-weight:700;cursor:pointer;
  padding:.3rem .7rem;border-radius:8px;color:rgba(255,255,255,.7);
  border:1px solid rgba(255,255,255,.18);background:transparent}
.lbk-back:hover{color:#fff;border-color:rgba(255,255,255,.36)}
.lbk-where{font-family:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;
  font-size:.66rem;font-weight:800;letter-spacing:.12em;text-transform:uppercase;
  color:rgba(255,255,255,.45)}
@media (prefers-reduced-motion: reduce){ .lbk-chip{transition:none} }
