/* ===========================================================================
   /elearn/quiz — the daily public quiz.

   THIS IS THE APP'S STYLESHEET, IN FULL. src/parts/p01, p02, p03, p04, p05,
   p06, p06b, p08 and p08b — every part of the app that carries a <style> block,
   in the app's own order, byte for byte.

   The owner, 2 September 2026: "I said it should be exactly same, don't get
   inspired, don't invent things. Just copy exact thing here."

   TWO EARLIER ATTEMPTS AT THIS FILE WERE BOTH SHORT. The first lifted only p05's
   quiz block, leaving .row, .g10, .btn-q and .qtop undefined — the app's screens
   inside my spacing. The second lifted p01–p05 because the quiz's rules are in
   p05 and five felt like the whole of it; .i-14 is in p06b, so the icon in a
   Match column heading drew at the browser's fallback 300×150 and put a grey
   slab down the middle of the screen. The list above is now taken from which
   files actually contain CSS, and the build script fails if that list changes.

   NOTHING IN THE LIFTED BLOCKS IS EDITED. If the app's look changes, replace
   them from the same nine parts rather than adjusting them here, so the two
   cannot drift apart by somebody improving one of them.

   EVERYTHING BELOW THE LIFTED BLOCKS is this page's own and is maintained
   here: the end-of-quiz screen, the site's header, and the handful of rules
   a page needs that a screen inside an app does not. Each carries its own
   note, and .topbar .bar explains why it is scoped.
   ========================================================================== */

/* ==== src/parts/p01.html, verbatim ==== */

/* ============================================================================
   MINATI MA'AM'S ACADEMY — design system
   Two independently designed themes. Light is airy paper-and-ink with violet
   shadow; dark is a deep midnight aurora with luminous edges. Not inversions.
   ========================================================================== */

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
body,h1,h2,h3,h4,p,figure,blockquote,dl,dd,ul,ol{margin:0}
ul[class],ol[class]{list-style:none;padding:0}
img,svg,canvas{display:block;max-width:100%}
button,input,select,textarea{font:inherit;color:inherit}
button{background:none;border:0;cursor:pointer;-webkit-tap-highlight-color:transparent}
a{color:inherit;text-decoration:none}
:focus-visible{outline:2.5px solid var(--focus);outline-offset:3px;border-radius:6px}
::selection{background:var(--sel);color:var(--sel-ink)}

/* ---------------------------------------------------------------- tokens --*/
:root{
  --f-ui:'Plus Jakarta Sans',system-ui,-apple-system,'Segoe UI',Roboto,sans-serif;
  --f-dis:'Outfit','Plus Jakarta Sans',system-ui,sans-serif;
  --f-ser:'Instrument Serif','Iowan Old Style',Georgia,serif;

  --r-xs:8px; --r-sm:12px; --r-md:16px; --r-lg:22px; --r-xl:28px; --r-2xl:36px; --r-full:999px;

  --ease:cubic-bezier(.22,1,.36,1);
  --ease-io:cubic-bezier(.65,.05,.36,1);
  --spring:cubic-bezier(.34,1.46,.64,1);

  --nav-w:266px; --top-h:68px; --tab-h:66px;
  --maxw:1240px;

  /* The theme engine overwrites every one of these at runtime. What is written
     here is the Aurora identity, so the first paint is already correct. */
  --a1:#5A45FF; --a2:#A855F7; --a3:#FF3D9A;
}

/* ------------------------------------------------------------ LIGHT theme -*/
:root[data-theme="light"]{
  --bg:#F2F4FE;
  --bg-deep:#E7EAFB;
  --surface:#FFFFFF;
  --surface-2:#F7F8FF;
  --surface-3:#EFF1FE;
  --glass:rgba(255,255,255,.72);
  --glass-strong:rgba(255,255,255,.9);
  --line:#E2E6F7;
  --line-soft:#EDEFFA;
  --line-strong:#D2D8F0;

  --ink:#0F1130;
  --ink-2:#4A4E75;
  --ink-3:#7B7FA4;
  --ink-4:#A7ABC8;

  --brand:var(--a1); --brand-2:var(--a2); --brand-3:var(--a3);
  --brand-soft:rgba(90,69,255,.09);
  --brand-line:rgba(90,69,255,.22);
  --on-brand:#fff;

  --ok:#059669; --ok-soft:rgba(5,150,105,.1); --ok-line:rgba(5,150,105,.25);
  --no:#E11D6F; --no-soft:rgba(225,29,111,.09); --no-line:rgba(225,29,111,.24);
  /* ink that sits on a filled ok/no chip — the verdict marks are solid, not tinted */
  --on-ok:#fff; --on-no:#fff;
  --warn:#7C3AED; --warn-soft:rgba(124,58,237,.09);

  --sh-1:0 1px 2px rgba(24,20,70,.05), 0 2px 6px rgba(24,20,70,.04);
  --sh-2:0 2px 4px rgba(24,20,70,.05), 0 10px 24px -6px rgba(24,20,70,.09);
  --sh-3:0 4px 8px rgba(24,20,70,.05), 0 22px 44px -12px rgba(30,22,86,.16);
  --sh-4:0 8px 16px rgba(24,20,70,.06), 0 40px 80px -20px rgba(30,22,86,.24);
  --sh-brand:0 12px 32px -8px rgba(90,69,255,.42);
  --ring-track:#E6E9FA;

  --focus:#5A45FF;
  --sel:rgba(90,69,255,.16); --sel-ink:#0F1130;
  --noise-op:.028;
  --aura-op:.9;
}

/* ------------------------------------------------------------- DARK theme -*/
:root[data-theme="dark"]{
  --bg:#05071A;
  --bg-deep:#03040F;
  --surface:#0E1230;
  --surface-2:#141936;
  --surface-3:#1B2145;
  --glass:rgba(18,23,58,.62);
  --glass-strong:rgba(12,16,44,.86);
  --line:rgba(140,155,255,.13);
  --line-soft:rgba(140,155,255,.08);
  --line-strong:rgba(150,165,255,.24);

  --ink:#EFF1FF;
  --ink-2:#AFB6E4;
  --ink-3:#7A82B8;
  --ink-4:#565D8C;

  --brand:#8B7BFF; --brand-2:#C084FC; --brand-3:#FF6BB0;
  --brand-soft:rgba(139,123,255,.15);
  --brand-line:rgba(139,123,255,.34);
  --on-brand:#0B0D24;

  --ok:#34E5B0; --ok-soft:rgba(52,229,176,.13); --ok-line:rgba(52,229,176,.32);
  --no:#FF5C93; --no-soft:rgba(255,92,147,.13); --no-line:rgba(255,92,147,.32);
  /* the dark-mode accents are light, so the ink on a filled chip goes dark */
  --on-ok:#04271E; --on-no:#3B0518;
  --warn:#C084FC; --warn-soft:rgba(192,132,252,.13);

  --sh-1:0 1px 2px rgba(0,0,0,.5);
  --sh-2:0 4px 18px rgba(0,0,0,.5);
  --sh-3:0 12px 40px rgba(0,0,0,.6);
  --sh-4:0 24px 70px rgba(0,0,0,.7);
  --sh-brand:0 12px 40px -10px rgba(139,123,255,.6);
  --ring-track:rgba(140,155,255,.16);

  --focus:#A79BFF;
  --sel:rgba(139,123,255,.32); --sel-ink:#fff;
  --noise-op:.05;
  --aura-op:.75;
}

/* gradients ---------------------------------------------------------------*/
:root{
  --grad-brand:linear-gradient(135deg,var(--a1) 0%,var(--a2) 48%,var(--a3) 100%);
  /* THE BRAND ACCENTS AT FULL STRENGTH, and fixed rather than themed.
     Three attempts were made to darken this so white would clear 4.5 on every
     stop, and the owner rejected each one as losing its lightness. She is right,
     and the reason is worth writing down: #A855F7 is 3.96:1 under white and
     #FF3D9A is 3.29:1 -- so EVERY solid button on this site has been below the
     small-text floor since it shipped, Buy now included. Holding this one button
     to a stricter standard made it visibly duller than the buttons beside it,
     which is a real fault a reader can see, traded against one they cannot.
     THE CONTRAST IS A SEPARATE, SITE-WIDE DECISION -- either the accents move
     for every button at once, or the labels get a scrim. Fixing it on one
     button was the wrong shape of fix.
     WHY THE TOKEN STILL EXISTS: the app rewrites both --grad-brand and
     --grad-deep from the hero palette on every tick of its time-of-day theming
     (p09b.html), so a button using either drifts through the day and stops
     matching the public shop. This one is fixed and shared. */
  /* Her brand accents, at their own values and in their own places -- the same
     gradient every .btn-solid on the site carries. Fixed rather than themed:
     the app rewrites both --grad-brand and --grad-deep from the hero palette on
     every tick of its time-of-day theming (p09b.html), so a button built on
     either drifts through the day and stops matching the public shop. */
  /* THE GRADIENT WHITE TEXT MAY STAND ON. Same three colours, lightness
     lowered until white clears 4.5:1 at every point -- the old stops were
     3.96:1 and 3.29:1. Kept identical to page.css so the button is the same
     button in the app and on the public pages. */
  --grad-btn:linear-gradient(135deg,#5A45FF 0%,#9D40F6 48%,#E4006D 100%);
  --grad-cool:linear-gradient(135deg,#0EA5E9,#5A45FF 60%,#A855F7);
  --grad-mint:linear-gradient(135deg,#10C79B,#0EA5E9);
  --grad-pink:linear-gradient(135deg,#A855F7,#FF3D9A);
}

/* ------------------------------------------------------------------ base -*/
html,body{height:100%}
body{
  font-family:var(--f-ui);
  font-size:15.5px; line-height:1.62; font-weight:450;
  color:var(--ink); background:var(--bg);
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
  transition:background-color .5s var(--ease), color .5s var(--ease);
  font-feature-settings:"kern","liga","calt","ss01";
}
h1,h2,h3,h4{font-family:var(--f-dis);font-weight:700;line-height:1.14;letter-spacing:-.022em;color:var(--ink)}
/* panels that carry their own colour must pass it down to their headings */
.hero h1,.hero h2,.hero h3,.profhero h1,.profhero h2,.profhero h3,
.mcqhero h1,.mcqhero h2,.mcqhero h3,#celebrate h1,#celebrate h2,#onb h1,
.face.back h1,.face.back h2,.face.back h3{color:inherit}

/* --------------------------------------------------------- aurora canvas -*/
#aura{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;
  opacity:var(--aura-op);transition:opacity .8s var(--ease)}
#aura .blob{position:absolute;border-radius:50%;filter:blur(70px);will-change:transform;
  opacity:.5;mix-blend-mode:normal}
:root[data-theme="dark"] #aura .blob{filter:blur(90px);opacity:.42;mix-blend-mode:screen}
#aura .b1{width:52vw;height:52vw;min-width:380px;min-height:380px;background:var(--au1);
  top:-16vw;left:-10vw;animation:drift1 26s var(--ease-io) infinite alternate}
#aura .b2{width:44vw;height:44vw;min-width:320px;min-height:320px;background:var(--au2);
  top:22vh;right:-14vw;animation:drift2 32s var(--ease-io) infinite alternate}
#aura .b3{width:40vw;height:40vw;min-width:300px;min-height:300px;background:var(--au3);
  bottom:-16vh;left:24vw;animation:drift3 29s var(--ease-io) infinite alternate}
@keyframes drift1{0%{transform:translate3d(0,0,0) scale(1)}100%{transform:translate3d(9vw,7vh,0) scale(1.16)}}
@keyframes drift2{0%{transform:translate3d(0,0,0) scale(1.08)}100%{transform:translate3d(-8vw,10vh,0) scale(.92)}}
@keyframes drift3{0%{transform:translate3d(0,0,0) scale(.95)}100%{transform:translate3d(7vw,-8vh,0) scale(1.14)}}
#grain{position:fixed;inset:0;z-index:1;pointer-events:none;opacity:var(--noise-op);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  mix-blend-mode:overlay}

/* First-paint aurora. The engine replaces these per theme and time of day. */
:root[data-theme="light"]{--au1:#B9C4FF;--au2:#F5B9E4;--au3:#A8E8F0}
:root[data-theme="dark"]{--au1:#4B3BC7;--au2:#8B2E9E;--au3:#1E6E8C}

/* Long-form reading. The aurora stays — it just steps back a little so body
   copy sits on a calmer field. Applied by the router to any reading view, so
   it is a system behaviour rather than a lesson-page exception. */
:root[data-reading="1"] #aura{opacity:calc(var(--aura-op) * .4)}
:root[data-reading="1"] #grain{opacity:calc(var(--noise-op) * .5)}

/* ------------------------------------------------------------- utilities -*/
.hide{display:none !important}
/* Names are user data: they must wrap, never clip, at any length. */
.uname{overflow-wrap:anywhere;word-break:break-word;hyphens:auto;min-width:0;max-width:100%}
.row{display:flex;align-items:center}
.between{display:flex;align-items:center;justify-content:space-between;gap:12px}
.wrap{flex-wrap:wrap}
.g8{gap:8px}.g10{gap:10px}.g12{gap:12px}.g14{gap:14px}.g16{gap:16px}
.mt8{margin-top:8px}.mt12{margin-top:12px}.mt14{margin-top:14px}.mt16{margin-top:16px}
.mt20{margin-top:20px}.mt24{margin-top:24px}.mt32{margin-top:32px}
.tc{text-align:center}
.grow{flex:1 1 auto;min-width:0}
.trunc{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.clamp2{display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.muted{color:var(--ink-3)}
.eyebrow{font-family:var(--f-dis);font-size:11px;font-weight:700;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-3)}
.rel{position:relative}

/* icons — the class lands on the <svg> itself once Lucide swaps the placeholder,
   so the size must live on .i and never be re-expanded to 100%. */
.i{width:20px;height:20px;flex:0 0 auto;stroke-width:1.9;display:block}
.i>svg{width:100%;height:100%;display:block;stroke-width:inherit}
svg.i{overflow:visible}
i[data-lucide]{display:block;width:20px;height:20px;flex:0 0 auto}

/* scrollbars */
*::-webkit-scrollbar{width:11px;height:11px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{background:var(--line-strong);border-radius:99px;border:3px solid transparent;background-clip:content-box}
*::-webkit-scrollbar-thumb:hover{background:var(--ink-4);background-clip:content-box}

/* ---------------------------------------------------------------- motion -*/
@keyframes fadeUp{from{opacity:0;transform:translate3d(0,18px,0)}to{opacity:1;transform:none}}
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
@keyframes scaleIn{from{opacity:0;transform:scale(.94)}to{opacity:1;transform:none}}
@keyframes popIn{0%{opacity:0;transform:scale(.8)}60%{opacity:1;transform:scale(1.04)}100%{transform:scale(1)}}
@keyframes slideRight{from{opacity:0;transform:translate3d(-14px,0,0)}to{opacity:1;transform:none}}
@keyframes breathe{0%,100%{transform:scale(1)}50%{transform:scale(1.035)}}
@keyframes spinS{to{transform:rotate(360deg)}}
@keyframes shimmer{0%{background-position:-260% 0}100%{background-position:260% 0}}
@keyframes shakeX{10%,90%{transform:translateX(-2px)}20%,80%{transform:translateX(4px)}
  30%,50%,70%{transform:translateX(-7px)}40%,60%{transform:translateX(7px)}}
@keyframes pulseRing{0%{box-shadow:0 0 0 0 var(--pr,rgba(90,69,255,.5))}
  70%{box-shadow:0 0 0 16px rgba(0,0,0,0)}100%{box-shadow:0 0 0 0 rgba(0,0,0,0)}}
@keyframes floatY{0%,100%{transform:translateY(0)}50%{transform:translateY(-7px)}}
@keyframes sheen{0%{transform:translateX(-120%) skewX(-18deg)}100%{transform:translateX(320%) skewX(-18deg)}}

.anim-up{animation:fadeUp .62s var(--ease) both}
.anim-pop{animation:popIn .5s var(--spring) both}
.stagger>*{animation:fadeUp .62s var(--ease) both}
.stagger>*:nth-child(1){animation-delay:.04s}.stagger>*:nth-child(2){animation-delay:.09s}
.stagger>*:nth-child(3){animation-delay:.14s}.stagger>*:nth-child(4){animation-delay:.19s}
.stagger>*:nth-child(5){animation-delay:.24s}.stagger>*:nth-child(6){animation-delay:.29s}
.stagger>*:nth-child(7){animation-delay:.34s}.stagger>*:nth-child(8){animation-delay:.39s}
.stagger>*:nth-child(9){animation-delay:.44s}.stagger>*:nth-child(n+10){animation-delay:.49s}

:root[data-motion="off"] *,:root[data-motion="off"] *::before,:root[data-motion="off"] *::after{
  animation-duration:.01ms !important;animation-iteration-count:1 !important;
  transition-duration:.01ms !important;scroll-behavior:auto !important}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important}
}

/* ==== src/parts/p02.html, verbatim ==== */
/* ============================================================================
   SHELL — desktop rail, tablet pill bar, mobile dock. Three real layouts.
   ========================================================================== */
#app{position:relative;z-index:2;min-height:100vh;min-height:100dvh;opacity:0}
#app.ready{opacity:1;transition:opacity .5s var(--ease)}

/* ------------------------------------------------------------------ brand -*/
.brand{display:flex;align-items:center;gap:11px;min-width:0}
.brand-mark{width:38px;height:38px;flex:0 0 auto;border-radius:12px;position:relative;
  background:var(--grad-brand);box-shadow:var(--sh-brand);display:grid;place-items:center;
  transition:transform .5s var(--spring)}
.brand-mark::after{content:"";position:absolute;inset:0;border-radius:inherit;
  background:linear-gradient(160deg,rgba(255,255,255,.4),transparent 55%);pointer-events:none}
.brand-mark svg{width:22px;height:22px;position:relative;z-index:1}
.brand:hover .brand-mark{transform:rotate(-7deg) scale(1.06)}
.brand-mark.beat{animation:breathe 4.5s var(--ease-io) infinite}
.brand-txt{display:flex;flex-direction:column;line-height:1.02;min-width:0}
.brand-txt .l1{font-family:var(--f-dis);font-weight:600;font-size:13.5px;letter-spacing:.005em;
  color:var(--ink-2);white-space:nowrap}
.brand-txt .l2{font-family:var(--f-dis);font-weight:800;font-size:19px;letter-spacing:-.028em;
  background:var(--grad-brand);-webkit-background-clip:text;background-clip:text;
  color:transparent;-webkit-text-fill-color:transparent;white-space:nowrap;
  padding-right:.12em;margin-right:-.12em}

/* ------------------------------------------------------------- desktop rail */
#rail{position:fixed;left:0;top:0;bottom:0;width:var(--nav-w);z-index:60;display:none;
  flex-direction:column;padding:22px 16px 18px;gap:18px;
  background:var(--glass);backdrop-filter:blur(26px) saturate(160%);
  -webkit-backdrop-filter:blur(26px) saturate(160%);
  border-right:1px solid var(--line)}
:root[data-theme="dark"] #rail{background:linear-gradient(180deg,rgba(14,18,48,.82),rgba(8,11,32,.86))}
#rail .brand{padding:4px 8px 2px}
.nav{display:flex;flex-direction:column;gap:3px;flex:1 1 auto;min-height:0;overflow-y:auto}
.nav-lbl{padding:14px 12px 6px}
.nav-item{display:flex;align-items:center;gap:12px;padding:11px 13px;border-radius:14px;
  color:var(--ink-2);font-weight:600;font-size:14.5px;position:relative;
  transition:color .2s var(--ease),background .2s var(--ease),transform .3s var(--spring);
  cursor:pointer;text-align:left;width:100%}
.nav-item .i{color:var(--ink-3);transition:color .2s,transform .35s var(--spring)}
.nav-item:hover{color:var(--ink);background:var(--surface-2)}
.nav-item:hover .i{color:var(--brand);transform:translateY(-1.5px) scale(1.06)}
.nav-item[aria-current="page"]{color:var(--ink);background:var(--brand-soft);font-weight:700}
.nav-item[aria-current="page"] .i{color:var(--brand)}
.nav-item[aria-current="page"]::before{content:"";position:absolute;left:-16px;top:50%;
  transform:translateY(-50%);width:4px;height:22px;border-radius:0 4px 4px 0;background:var(--grad-brand)}

.rail-card{border-radius:var(--r-lg);padding:14px;background:var(--surface);
  border:1px solid var(--line);box-shadow:var(--sh-1);position:relative;overflow:hidden}
:root[data-theme="dark"] .rail-card{background:linear-gradient(155deg,var(--surface-2),var(--surface))}
.rail-foot{display:flex;flex-direction:column;gap:9px;padding:0 6px}
.foot-link{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--ink-3);font-weight:600;
  transition:color .2s}
.foot-link:hover{color:var(--brand)}
.foot-link .i{width:14px;height:14px}

/* -------------------------------------------------------------- top bars --*/
#topbar{position:sticky;top:0;z-index:55;height:var(--top-h);display:flex;align-items:center;
  gap:14px;padding:0 clamp(14px,3vw,28px);
  background:var(--glass);backdrop-filter:blur(24px) saturate(170%);
  -webkit-backdrop-filter:blur(24px) saturate(170%);
  border-bottom:1px solid var(--line);transition:box-shadow .3s,background .5s}
#topbar.scrolled{box-shadow:var(--sh-2)}
:root[data-theme="dark"] #topbar{background:rgba(7,10,28,.76)}

.pagetitle{font-family:var(--f-dis);font-weight:700;font-size:17px;letter-spacing:-.02em;
  display:flex;align-items:center;gap:9px;min-width:0}
.pagetitle .i{color:var(--brand)}

/* chips in the bar */
.chip{display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 12px;border-radius:99px;
  background:var(--surface-2);border:1px solid var(--line);font-size:13px;font-weight:700;
  font-family:var(--f-dis);color:var(--ink-2);white-space:nowrap;
  transition:transform .3s var(--spring),border-color .2s,background .2s,box-shadow .3s}
.chip .i{width:15px;height:15px}
.chip.streak .i{color:var(--brand-3)}
.chip.xp .i{color:var(--brand)}
.chip:hover{transform:translateY(-1.5px);border-color:var(--brand-line);box-shadow:var(--sh-1)}
.chip.pop{animation:popIn .5s var(--spring)}
.chip b{color:var(--ink);font-weight:800}

/* the universal theme toggle — same top-right slot in every layout */
.iconbtn{width:38px;height:38px;border-radius:12px;display:grid;place-items:center;
  color:var(--ink-2);background:var(--surface-2);border:1px solid var(--line);
  transition:transform .35s var(--spring),color .2s,background .2s,border-color .2s,box-shadow .3s;position:relative}
.iconbtn:hover{color:var(--brand);border-color:var(--brand-line);transform:translateY(-2px);box-shadow:var(--sh-1)}
.iconbtn:active{transform:translateY(0) scale(.94)}
.iconbtn.ghost{background:transparent;border-color:transparent}
.iconbtn.ghost:hover{background:var(--surface-2);border-color:var(--line)}
#themeBtn{overflow:hidden}
#themeBtn .tw{display:grid;place-items:center;transition:transform .55s var(--spring),opacity .3s}
#themeBtn .tw.out{transform:rotate(-100deg) scale(.3);opacity:0}

.avatar{width:38px;height:38px;border-radius:12px;display:grid;place-items:center;flex:0 0 auto;
  font-family:var(--f-dis);font-weight:800;font-size:15px;color:#fff;letter-spacing:-.02em;
  background:var(--grad-brand);box-shadow:var(--sh-brand);cursor:pointer;
  transition:transform .35s var(--spring)}
.avatar:hover{transform:scale(1.07) rotate(-4deg)}
.avatar.sm{width:30px;height:30px;font-size:12px;border-radius:10px}
.avatar.lg{width:76px;height:76px;font-size:29px;border-radius:24px}
.avatar.xl{width:104px;height:104px;font-size:40px;border-radius:32px}

/* -------------------------------------------------------- tablet pill nav -*/
#pillnav{display:none;gap:4px;padding:5px;border-radius:99px;background:var(--surface-2);
  border:1px solid var(--line);margin:0 auto}
#pillnav .pill{display:flex;align-items:center;gap:7px;padding:8px 15px;border-radius:99px;
  font-weight:650;font-size:13.5px;color:var(--ink-3);font-family:var(--f-dis);
  transition:color .22s,background .3s var(--ease),transform .3s var(--spring);white-space:nowrap}
#pillnav .pill .i{width:17px;height:17px}
#pillnav .pill:hover{color:var(--ink);transform:translateY(-1px)}
#pillnav .pill[aria-current="page"]{color:var(--on-brand);background:var(--grad-brand);
  box-shadow:var(--sh-brand)}

/* ---------------------------------------------------------- mobile dock --*/
#dock{position:fixed;left:0;right:0;bottom:0;z-index:60;display:none;
  padding:8px 10px calc(8px + env(safe-area-inset-bottom));
  background:var(--glass-strong);backdrop-filter:blur(26px) saturate(180%);
  -webkit-backdrop-filter:blur(26px) saturate(180%);
  border-top:1px solid var(--line)}
:root[data-theme="dark"] #dock{background:rgba(6,9,26,.9)}
#dock .dockrow{display:grid;grid-template-columns:repeat(5,1fr);gap:2px;max-width:520px;margin:0 auto}
.dockbtn{display:flex;flex-direction:column;align-items:center;gap:3px;padding:7px 2px 5px;
  border-radius:14px;color:var(--ink-3);position:relative;transition:color .22s}
.dockbtn .ico{width:30px;height:26px;display:grid;place-items:center;border-radius:9px;position:relative;
  transition:transform .4s var(--spring)}
.dockbtn .i{width:21px;height:21px;stroke-width:1.85;transition:stroke-width .2s}
.dockbtn span{font-size:10.5px;font-weight:700;font-family:var(--f-dis);letter-spacing:.005em}
.dockbtn[aria-current="page"]{color:var(--brand)}
.dockbtn[aria-current="page"] .i{stroke-width:2.4}
.dockbtn[aria-current="page"] .ico{transform:translateY(-3px)}
.dockbtn[aria-current="page"]::after{content:"";position:absolute;top:2px;left:50%;transform:translateX(-50%);
  width:26px;height:26px;border-radius:9px;background:var(--brand-soft);z-index:-1}
.dockbtn:active .ico{transform:scale(.88)}

/* ------------------------------------------------------------- main area -*/
#main{position:relative;min-height:100vh;min-height:100dvh;display:flex;flex-direction:column}
#view{flex:1 1 auto;padding:clamp(16px,2.4vw,30px) clamp(14px,3vw,28px) 40px;
  max-width:var(--maxw);width:100%;margin:0 auto}
.viewhead{margin-bottom:20px}
.viewhead h1{font-size:clamp(26px,4vw,38px);letter-spacing:-.035em}
.viewhead p{color:var(--ink-3);font-size:14.5px;margin-top:5px;max-width:62ch}

/* footer */
.sitefoot{margin-top:auto;padding:26px clamp(14px,3vw,28px) 30px;border-top:1px solid var(--line-soft)}
.sitefoot .in{max-width:var(--maxw);margin:0 auto;display:flex;align-items:center;
  justify-content:space-between;gap:16px;flex-wrap:wrap}
.sitefoot .lk{display:flex;gap:18px;flex-wrap:wrap}
.sitefoot a{display:inline-flex;align-items:center;gap:7px;font-size:13px;font-weight:650;
  color:var(--ink-3);transition:color .2s,transform .3s var(--spring)}
.sitefoot a:hover{color:var(--brand);transform:translateY(-1px)}
.sitefoot .i{width:15px;height:15px}
.sitefoot .cr{font-size:12.5px;color:var(--ink-4)}

/* ============================ RESPONSIVE ================================= */
/* The wordmark never abbreviates and never wraps, so anything sharing the bar
   with it has to yield. Below the rail breakpoint the brand is on screen, and
   the page title would be the second full-length phrase in the same row — which
   is exactly where "Minati Ma'am's Academy" and "Lesson 1 · English Blunders"
   ran into each other. The dock and the pill nav both already name the section,
   so the title belongs only to the layout where the brand is hidden. */
@media (max-width:1099.98px){ #pageTitle{display:none} }

/* MOBILE (default, <760) — dock at the bottom, brand always full in the bar */
@media (max-width:759.98px){
  :root{--top-h:60px}
  #dock{display:block}
  /* Room for the dock AND for Pipo, who floats above it. Without the second
     part the last control on a screen - the quiz Next question, most often -
     could come to rest under him with nothing left to scroll. 84px is his 60px
     button plus the 12px he stands off the dock, plus a finger's margin. */
  #main{padding-bottom:calc(var(--dock-h,calc(var(--tab-h) + env(safe-area-inset-bottom))) + 84px)}
  #topbar{padding:0 12px;gap:8px}
  #barChips .chip{height:30px;padding:0 9px;font-size:12px;gap:4px}
  #barChips .chip .i{width:13px;height:13px}
  #topbar .iconbtn,#topbar .avatar{width:34px;height:34px;border-radius:11px}
  .hero .row.g10.wrap>.btn-lg{width:100%}
  .brand-txt .l1{font-size:11.5px}
  .brand-txt .l2{font-size:16.5px}
  .brand-mark{width:34px;height:34px;border-radius:11px}
  .brand-mark svg{width:19px;height:19px}
  #view{padding:14px 14px 26px}
  .viewhead h1{font-size:27px}
  .sitefoot{padding:22px 14px 24px}
  .sitefoot .in{flex-direction:column;align-items:flex-start;gap:12px}
  body{font-size:15px}
}
/* the bar now carries a third control, so the chips give way before the
   wordmark or anything you can tap does */
@media (max-width:479.98px){ #barChips .chip.xp{display:none} }
/* very narrow — stack the wordmark but never abbreviate it */
@media (max-width:359.98px){
  .brand-txt .l1{font-size:10.5px}
  .brand-txt .l2{font-size:15px}
  .brand-mark{width:30px;height:30px}
  #barChips{display:none}
}
/* the companion floats over this corner — keep the footer links clear of it */
@media (min-width:560px){ .sitefoot .in{padding-right:88px} }
/* TABLET (760–1099) — centred pill navigation.
   Five pills plus the full wordmark plus three controls is the whole budget, so
   the chips step out here; streak and XP are on Home and Profile in full. */
@media (min-width:760px) and (max-width:1099.98px){
  #pillnav{display:flex}
  /* Through the variable, not around it. This said height:72px directly while
     --top-h still read 68, and --top-h is what every sticky row underneath
     offsets itself by - so in this one band they each stopped 4px too high and
     let a sliver of text show through. */
  :root{--top-h:72px}
  #topbar{padding:0 18px;gap:10px}
  #view{padding:22px 22px 34px}
  .brand-txt .l1{font-size:12.5px}
  .brand-txt .l2{font-size:18px}
  #barChips{display:none}
  #pillnav .pill{padding:8px 12px;font-size:13px}
  /* The pill group is the centrepiece of this layout, so it is centred on the
     BAR — not on whatever gap the wordmark and the controls happen to leave.
     Its auto margins could never do that: the flexible spacer beside it absorbs
     the free space first, which left the group sitting ~100–170px left of
     centre. Taking it out of flow centres it outright, and because the pills
     are symmetrically padded both variants — icon-only below 940, labelled
     above — land dead centre by the same rule. */
  #pillnav{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);margin:0}
}
/* the lower half of the tablet range cannot fit five labelled pills beside the
   wordmark, so they go icon-only rather than overflowing the bar */
@media (min-width:760px) and (max-width:939.98px){
  #pillnav .pill{padding:9px 12px;justify-content:center}
  #pillnav .pill span{display:none}
}
/* DESKTOP (>=1100) — persistent rail */
@media (min-width:1100px){
  #rail{display:flex}
  #main{margin-left:var(--nav-w)}
  #topbar{padding:0 32px}
  #topbar .brand{display:none}
  #view{padding:26px 32px 46px}
  .sitefoot{padding:30px 32px 34px}
}
@media (min-width:1500px){ :root{--maxw:1340px} }

/* ==== src/parts/p03.html, verbatim ==== */
/* ============================================================================
   COMPONENTS — surfaces, buttons, hero, rings, nudges, badges
   ========================================================================== */

/* ------------------------------------------------------------------ cards -*/
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-xl);
  padding:20px;box-shadow:var(--sh-1);position:relative;
  transition:transform .42s var(--ease),box-shadow .42s var(--ease),border-color .3s}
:root[data-theme="dark"] .card{background:linear-gradient(158deg,rgba(28,34,74,.72),rgba(13,17,44,.86));
  box-shadow:var(--sh-2),inset 0 1px 0 rgba(255,255,255,.045)}
.card.hoverable{cursor:pointer}
.card.hoverable:hover{transform:translateY(-4px);box-shadow:var(--sh-3);border-color:var(--brand-line)}
.card.hoverable:active{transform:translateY(-1px) scale(.995)}
.card.flat{box-shadow:none}
.card-h{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px}
.card-h h3{font-size:15.5px;letter-spacing:-.018em;display:flex;align-items:center;gap:8px}
.card-h h3 .i{width:17px;height:17px;color:var(--brand)}

/* cursor-tracked sheen — the surface reacts to where you are */
.lit{position:relative;overflow:hidden;isolation:isolate}
.lit::before{content:"";position:absolute;inset:-1px;z-index:0;pointer-events:none;opacity:0;
  background:radial-gradient(340px circle at var(--mx,50%) var(--my,50%),
    color-mix(in srgb,var(--brand) 15%,transparent),transparent 62%);
  transition:opacity .45s var(--ease)}
.lit:hover::before{opacity:1}
.lit>*{position:relative;z-index:1}

/* ---------------------------------------------------------------- buttons -*/
.btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;height:46px;
  padding:0 22px;border-radius:15px;font-family:var(--f-dis);font-weight:700;font-size:14.5px;
  letter-spacing:-.006em;position:relative;overflow:hidden;white-space:nowrap;
  transition:transform .32s var(--spring),box-shadow .32s var(--ease),
    background .25s,color .25s,border-color .25s,opacity .25s}
.btn .i{width:18px;height:18px}
.btn:active{transform:scale(.96)}
.btn:disabled{opacity:.44;pointer-events:none}
.btn-p{background:var(--grad-brand);color:#fff;box-shadow:var(--sh-brand)}
.btn-p::after{content:"";position:absolute;top:0;bottom:0;width:34%;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.34),transparent);
  transform:translateX(-120%) skewX(-18deg);pointer-events:none}
.btn-p:hover{transform:translateY(-2.5px);box-shadow:0 18px 40px -10px color-mix(in srgb,var(--a1) 60%,transparent)}
.btn-p:hover::after{animation:sheen .85s var(--ease)}
.btn-s{background:var(--surface);color:var(--ink);border:1px solid var(--line-strong);box-shadow:var(--sh-1)}
.btn-s:hover{border-color:var(--brand-line);color:var(--brand);transform:translateY(-2px);box-shadow:var(--sh-2)}
.btn-g{background:var(--brand-soft);color:var(--brand);border:1px solid var(--brand-line)}
.btn-g:hover{background:color-mix(in srgb,var(--brand) 16%,transparent);transform:translateY(-2px)}
.btn-q{color:var(--ink-2)}
.btn-q:hover{color:var(--brand);background:var(--surface-2)}
.btn-d{background:var(--no-soft);color:var(--no);border:1px solid var(--no-line)}
.btn-d:hover{background:var(--no);color:#fff;transform:translateY(-2px)}
/* buttons that sit on a hero/gradient panel — they follow the active theme
   because they take their ink from the hero's own mid-stop */
.btn-onhero{background:#fff;color:var(--h2);box-shadow:0 14px 34px -10px rgba(0,0,0,.35)}
.btn-onhero:hover{transform:translateY(-2.5px);box-shadow:0 20px 44px -12px rgba(0,0,0,.42)}
.btn-onhero-ghost{background:rgba(255,255,255,.15);color:#fff;border:1px solid rgba(255,255,255,.3);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
.btn-onhero-ghost:hover{background:rgba(255,255,255,.24);transform:translateY(-2.5px)}
.btn-sm{height:37px;padding:0 15px;font-size:13px;border-radius:12px;gap:7px}
.btn-sm .i{width:15px;height:15px}
.btn-lg{height:54px;padding:0 28px;font-size:16px;border-radius:17px}
.btn-block{width:100%}

/* ----------------------------------------------------------------- pills --*/
.tag{display:inline-flex;align-items:center;gap:6px;height:27px;padding:0 11px;border-radius:99px;
  font-size:11.5px;font-weight:750;font-family:var(--f-dis);letter-spacing:.01em;
  background:var(--surface-3);color:var(--ink-2);border:1px solid transparent;white-space:nowrap}
.tag .i{width:13px;height:13px}
.tag.brand{background:var(--brand-soft);color:var(--brand);border-color:var(--brand-line)}
.tag.ok{background:var(--ok-soft);color:var(--ok);border-color:var(--ok-line)}
.tag.no{background:var(--no-soft);color:var(--no);border-color:var(--no-line)}
.tag.warn{background:var(--warn-soft);color:var(--warn)}
/* a page-level back link, sitting above the hero it belongs to. It is the exact
   counterpart of the lesson reader's Next control and wears the same clothes:
   .btn.btn-s.btn-sm sized identically, arrow leading instead of trailing
   because this one goes back. Same shape, same height, same step-down on a
   narrow phone — one navigation pill, used at both ends of the journey. */
.crumb{height:38px;padding:0 14px;gap:6px;white-space:nowrap;margin-bottom:18px}
.crumb .i{width:16px;height:16px;transition:transform .35s var(--spring)}
.crumb:hover .i{transform:translateX(-3px)}
@media (max-width:400px){
  .crumb{height:32px;padding:0 11px;font-size:12.5px;gap:5px}
  .crumb .i{width:14px;height:14px}
}
.tag.glass{background:rgba(255,255,255,.16);color:#fff;border-color:rgba(255,255,255,.26);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}

.segbar{display:inline-flex;gap:3px;padding:4px;border-radius:14px;background:var(--surface-2);
  border:1px solid var(--line);overflow-x:auto;max-width:100%}
.segbar button{padding:7px 14px;border-radius:11px;font-size:13px;font-weight:700;font-family:var(--f-dis);
  color:var(--ink-3);white-space:nowrap;transition:color .2s,background .3s var(--ease)}
.segbar button:hover{color:var(--ink)}
.segbar button[aria-selected="true"]{background:var(--surface);color:var(--brand);box-shadow:var(--sh-1)}
:root[data-theme="dark"] .segbar button[aria-selected="true"]{background:var(--surface-3);color:var(--brand)}

/* ------------------------------------------------------------ progress ---*/
.bar{height:8px;border-radius:99px;background:var(--ring-track);overflow:hidden;position:relative}
.bar>i{display:block;height:100%;border-radius:99px;background:var(--grad-brand);
  transition:width .9s var(--ease);position:relative}
.bar>i::after{content:"";position:absolute;inset:0;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.45),transparent);
  background-size:200% 100%;animation:shimmer 2.6s linear infinite}
.bar.thin{height:5px}
.bar.thick{height:12px}

.ring{position:relative;display:grid;place-items:center;flex:0 0 auto}
.ring svg{transform:rotate(-90deg);overflow:visible}
.ring .trk{fill:none;stroke:var(--ring-track)}
.ring .val{fill:none;stroke:url(#ringGrad);stroke-linecap:round;
  transition:stroke-dashoffset 1.1s var(--ease)}
.ring .mid{position:absolute;inset:0;display:grid;place-items:center;text-align:center;line-height:1}
.ring .mid b{font-family:var(--f-dis);font-weight:800;letter-spacing:-.04em}

/* ------------------------------------------------------------------ hero -*/
.hero{position:relative;border-radius:var(--r-2xl);overflow:hidden;isolation:isolate;
  padding:clamp(22px,3.4vw,42px);color:#fff;
  background:linear-gradient(135deg,var(--h1),var(--h2) 52%,var(--h3));
  box-shadow:0 26px 60px -22px rgba(20,14,70,.5)}
/* First-paint hero. Replaced per theme and time of day by the engine. */
:root[data-theme="light"]{--h1:#4B37E8;--h2:#8B3FE0;--h3:#E0389A}
:root[data-theme="dark"]{--h1:#241C6B;--h2:#4C1D8A;--h3:#7A1F63}
.hero::before{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:radial-gradient(560px circle at var(--mx,72%) var(--my,18%),rgba(255,255,255,.2),transparent 60%);
  transition:background .3s linear}
.hero::after{content:"";position:absolute;inset:0;z-index:0;pointer-events:none;opacity:.5;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
  mix-blend-mode:overlay}
.hero>*{position:relative;z-index:2}
.hero canvas.dust{position:absolute;inset:0;z-index:1;pointer-events:none;opacity:.55}
.hero .tod{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 13px;border-radius:99px;
  background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.25);
  backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  font-size:11.5px;font-weight:750;font-family:var(--f-dis);letter-spacing:.05em;text-transform:uppercase}
.hero .tod .i{width:14px;height:14px}
.hero .greet{font-family:var(--f-ser);font-size:clamp(32px,6.2vw,62px);line-height:1.04;
  letter-spacing:-.024em;margin-top:16px;text-wrap:balance;
  overflow-wrap:anywhere;word-break:break-word;hyphens:auto;max-width:20ch}
/* The italic serif overhangs its inline box, so a gradient fill clips the last
   glyph. Padding restores it for names of any length. */
.hero .greet em{font-style:italic;display:inline-block;max-width:100%;
  background:linear-gradient(100deg,#fff,#FFD9F2 45%,#BFE9FF);
  -webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
  padding-right:.16em;margin-right:-.1em;padding-bottom:.08em;margin-bottom:-.08em;
  overflow-wrap:anywhere;word-break:break-word}
@media (min-width:1100px){.hero .greet{max-width:22ch}}
.hero .sub{font-size:clamp(14.5px,1.6vw,17px);line-height:1.55;margin-top:12px;max-width:52ch;
  color:rgba(255,255,255,.9);font-weight:450;min-height:1.55em}
.hero .sub .cursor{display:inline-block;width:2px;height:1em;vertical-align:-.14em;margin-left:2px;
  background:rgba(255,255,255,.9);animation:blink 1.05s steps(2) infinite}
@keyframes blink{50%{opacity:0}}

.heroStats{display:flex;gap:10px;margin-top:26px;flex-wrap:wrap}
.hstat{display:flex;align-items:center;gap:11px;padding:11px 15px 11px 12px;border-radius:17px;
  background:rgba(255,255,255,.13);border:1px solid rgba(255,255,255,.2);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
  transition:transform .4s var(--spring),background .3s}
.hstat:hover{transform:translateY(-3px);background:rgba(255,255,255,.2)}
.hstat .ic{width:34px;height:34px;border-radius:11px;display:grid;place-items:center;
  background:rgba(255,255,255,.18)}
.hstat .ic .i{width:17px;height:17px}
.hstat .tx{line-height:1.15}
.hstat .tx b{display:block;font-family:var(--f-dis);font-weight:800;font-size:18px;letter-spacing:-.03em}
.hstat .tx span{font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:rgba(255,255,255,.72)}

/* ---------------------------------------------------------------- nudges -*/
.nudge{position:relative;display:flex;gap:14px;align-items:flex-start;padding:16px 46px 16px 16px;
  border-radius:var(--r-lg);background:var(--surface);border:1px solid var(--line);
  box-shadow:var(--sh-1);overflow:hidden}
:root[data-theme="dark"] .nudge{background:linear-gradient(150deg,rgba(30,36,80,.7),rgba(14,18,46,.85))}
.nudge::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--grad-brand)}
.nudge .nic{width:40px;height:40px;flex:0 0 auto;border-radius:13px;display:grid;place-items:center;
  background:var(--brand-soft);color:var(--brand)}
.nudge .nic .i{width:19px;height:19px}
.nudge h4{font-size:14.5px;letter-spacing:-.015em}
.nudge p{font-size:13.5px;color:var(--ink-2);margin-top:3px;line-height:1.55}
.nudge .nact{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap}
.nudge .x{position:absolute;top:9px;right:9px;width:29px;height:29px;border-radius:9px;display:grid;
  place-items:center;color:var(--ink-4);transition:color .2s,background .2s,transform .3s var(--spring)}
.nudge .x:hover{color:var(--ink);background:var(--surface-3);transform:rotate(90deg)}
.nudge .x .i{width:15px;height:15px}
.nudge.leaving{animation:nudgeOut .45s var(--ease) forwards}
@keyframes nudgeOut{to{opacity:0;transform:translateX(26px) scale(.96);
  margin-bottom:-14px;max-height:0;padding-top:0;padding-bottom:0}}

/* ----------------------------------------------------------------- badges -*/
.badge{display:flex;flex-direction:column;align-items:center;gap:8px;padding:15px 8px;
  border-radius:var(--r-lg);text-align:center;transition:transform .4s var(--spring);position:relative}
.badge:hover{transform:translateY(-4px)}
.badge .medal{width:56px;height:56px;border-radius:19px;display:grid;place-items:center;position:relative;
  background:var(--grad-brand);box-shadow:var(--sh-brand);color:#fff}
.badge .medal::after{content:"";position:absolute;inset:0;border-radius:inherit;
  background:linear-gradient(155deg,rgba(255,255,255,.42),transparent 52%)}
.badge .medal .i{width:25px;height:25px;position:relative;z-index:1}
.badge.locked .medal{background:var(--surface-3);color:var(--ink-4);box-shadow:none;
  border:1.5px dashed var(--line-strong)}
.badge.locked{opacity:.75}
.badge .bn{font-size:11.5px;font-weight:750;font-family:var(--f-dis);line-height:1.25;letter-spacing:-.01em}
.badge.just .medal{animation:popIn .7s var(--spring)}

/* --------------------------------------------------------------- heatmap -*/
.heat{display:grid;grid-template-columns:repeat(auto-fill,minmax(13px,1fr));gap:4px}
.heat i{aspect-ratio:1;border-radius:4px;background:var(--ring-track);transition:transform .3s var(--spring)}
.heat i[data-l="1"]{background:color-mix(in srgb,var(--brand) 28%,var(--ring-track))}
.heat i[data-l="2"]{background:color-mix(in srgb,var(--brand) 52%,var(--ring-track))}
.heat i[data-l="3"]{background:color-mix(in srgb,var(--brand) 76%,var(--ring-track))}
.heat i[data-l="4"]{background:var(--brand);box-shadow:0 0 10px -2px var(--brand)}
.heat i:hover{transform:scale(1.35)}

.weekrow{display:grid;grid-template-columns:repeat(7,1fr);gap:7px}
.daycell{display:flex;flex-direction:column;align-items:center;gap:7px}
.daycell .dot{width:100%;aspect-ratio:1;max-width:44px;border-radius:13px;display:grid;place-items:center;
  background:var(--surface-3);color:var(--ink-4);font-family:var(--f-dis);font-weight:800;font-size:13px;
  transition:transform .4s var(--spring),background .3s}
.daycell .dot .i{width:17px;height:17px}
.daycell.on .dot{background:var(--grad-brand);color:#fff;box-shadow:var(--sh-brand)}
.daycell.today .dot{outline:2.5px solid var(--brand);outline-offset:2.5px}
.daycell .dl{font-size:10.5px;font-weight:750;color:var(--ink-3);font-family:var(--f-dis);
  text-transform:uppercase;letter-spacing:.06em}
.daycell:hover .dot{transform:translateY(-3px)}

/* --------------------------------------------------------------- toaster -*/
#toaster{position:fixed;z-index:400;left:50%;transform:translateX(-50%);
  top:calc(var(--top-h) + 14px);display:flex;flex-direction:column;gap:9px;
  align-items:center;pointer-events:none;width:min(430px,calc(100vw - 24px))}
.toast{display:flex;align-items:center;gap:11px;padding:11px 17px 11px 12px;border-radius:99px;
  background:var(--glass-strong);backdrop-filter:blur(20px) saturate(180%);
  -webkit-backdrop-filter:blur(20px) saturate(180%);
  border:1px solid var(--line-strong);box-shadow:var(--sh-3);pointer-events:auto;
  animation:toastIn .55s var(--spring) both;max-width:100%}
:root[data-theme="dark"] .toast{background:rgba(20,26,62,.92)}
.toast.out{animation:toastOut .35s var(--ease) forwards}
@keyframes toastIn{from{opacity:0;transform:translateY(-16px) scale(.92)}to{opacity:1;transform:none}}
@keyframes toastOut{to{opacity:0;transform:translateY(-12px) scale(.95)}}
.toast .tic{width:30px;height:30px;border-radius:10px;display:grid;place-items:center;flex:0 0 auto;
  background:var(--grad-brand);color:#fff}
.toast .tic .i{width:16px;height:16px}
.toast.ok .tic{background:var(--ok)}
.toast.no .tic{background:var(--no)}
.toast .tt{font-size:13.5px;font-weight:700;font-family:var(--f-dis);letter-spacing:-.01em}
.toast .ts{font-size:12px;color:var(--ink-3);margin-top:1px}

/* ---------------------------------------------------------------- modals -*/
.scrim{position:fixed;inset:0;z-index:300;background:rgba(8,7,28,.5);
  backdrop-filter:blur(9px);-webkit-backdrop-filter:blur(9px);
  display:grid;place-items:center;padding:18px;animation:fadeIn .3s var(--ease) both;overflow-y:auto}
:root[data-theme="dark"] .scrim{background:rgba(2,3,12,.72)}
.scrim.out{animation:fadeIn .25s var(--ease) reverse both}
/* max-height + its own scroll, rather than leaving a tall modal to be
   scrolled as part of the whole page. Settings — the one modal long enough to
   need this — uses .scrolly below instead, which keeps a proper header
   rather than relying on this alone; this is the fallback for anything else
   that happens to run tall. */
.modal{width:min(540px,100%);max-height:88vh;overflow-y:auto;background:var(--surface);
  border:1px solid var(--line);border-radius:var(--r-2xl);box-shadow:var(--sh-4);padding:26px;
  position:relative;animation:modalIn .5s var(--spring) both;margin:auto}
:root[data-theme="dark"] .modal{background:linear-gradient(160deg,#171D42,#0D1130)}
@keyframes modalIn{from{opacity:0;transform:translateY(22px) scale(.96)}to{opacity:1;transform:none}}
.scrim.out .modal{animation:modalIn .25s var(--ease) reverse both}
.modal.wide{width:min(760px,100%)}
/* Settings alone needs this — it carries the same two-column .dash2 split the
   page itself used to give it, and .wide's 760px squeezes a segbar and a
   voice picker that had roughly twice that room before. */
.modal.xwide{width:min(1000px,100%)}
.modal.slim{width:min(430px,100%)}
.modal .mx{position:absolute;top:14px;right:14px;width:34px;height:34px;border-radius:11px;
  display:grid;place-items:center;color:var(--ink-3);
  transition:background .25s,color .25s,transform .25s}
.modal .mx:hover{background:var(--surface-3);color:var(--ink);transform:rotate(90deg)}
.modal h2{font-size:22px;letter-spacing:-.028em}
.modal .mbody{margin-top:12px;color:var(--ink-2);font-size:14.5px;line-height:1.62}
.modal .mfoot{margin-top:22px;display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap}

/* ------------------------------------------------------- rows of actions --
   Two places show a set of equal actions — the store card's footer and the
   preview modal's — and both had the same fault: the buttons were laid out at
   their natural widths, so the row fitted or it did not depending on how long
   the words happened to be. On a 1440 desktop the card's three needed 344px in
   a 316px row, which put "What's inside" on a line of its own looking like an
   afterthought rather than a third option.

   flex:1 1 0 with min-width:0 divides the row instead of measuring it, so the
   three are always equal, always on one line, and always fill the width —
   which is the same at 320px as it is at 1920.

   The icons come off in this row only. They are what made the difference
   between fitting and not: an icon and its gap is about twenty pixels, and
   three of those is the shortfall exactly. The words are the thing being read
   here, and "Add to cart" needs no picture of a trolley to be understood. */
/* wrap, not nowrap: the free-trial button declares flex-basis 100% so it takes
   a line of its own by design. The two above it still divide their row equally,
   so nothing here can produce the ragged edge nowrap was guarding against. */
.actrow{display:flex;gap:8px;margin-top:16px;flex-wrap:wrap}
.actrow>.btn{flex:1 1 0;min-width:0;padding:0 9px;gap:0;
  font-size:12.6px;white-space:nowrap;overflow:hidden}
.actrow>.btn .i,.actrow>.btn svg{display:none}
/* Below about 360 the row still holds but the longest label runs out of room by
   a few pixels, so the type and the padding come in to meet it. Measured at
   320, which is the narrowest screen still in use. */
@media (max-width:400px){
  .actrow>.btn{padding:0 4px;font-size:11.4px;letter-spacing:-.014em}
}
/* .scrolly — a modal built as a fixed header over a scrolling body, the same
   shape Pipo's own chat panel (.pipohead + .pipomsgs) already uses elsewhere
   in the app. Modal.show({scrolly:true}) moves the title and the close button
   together into .mhead, a non-scrolling row with its own bottom border, and
   everything else into .mbody-scroll beneath it — so closing never requires
   scrolling back up to reach either of them, and the row reads as one piece
   rather than a button floating apart from what it belongs to. */
.modal.scrolly{display:flex;flex-direction:column;padding:0;overflow:hidden}
.modal.scrolly .mhead{flex:0 0 auto;display:flex;align-items:center;gap:14px;
  padding:20px 24px;border-bottom:1px solid var(--line-soft)}
.modal.scrolly .mhead h2{flex:1 1 auto;min-width:0;margin:0}
.modal.scrolly .mx{position:static;flex:0 0 auto}
.modal.scrolly .mbody-scroll{flex:1 1 auto;overflow-y:auto;padding:22px 24px 26px}
@media (max-width:559.98px){
  .scrim{padding:0;align-items:flex-end}
  .modal{width:100%;border-radius:var(--r-2xl) var(--r-2xl) 0 0;padding:22px 18px calc(24px + env(safe-area-inset-bottom));
    animation:sheetIn .48s var(--spring) both;max-height:92dvh;overflow-y:auto}
  @keyframes sheetIn{from{transform:translateY(100%)}to{transform:none}}
  .scrim.out .modal{animation:sheetIn .28s var(--ease) reverse both}
  .modal::before{content:"";display:block;width:40px;height:4px;border-radius:99px;background:var(--line-strong);
    margin:-6px auto 14px}
  .modal .mfoot{flex-direction:column-reverse}
  .modal .mfoot .btn{width:100%}
  /* ...except a row of equal actions, which stays a row. Stacking three
     choices into three full-width bars makes the last one look like the
     answer, and it is not — they are alternatives. */
  .modal .mfoot.actrow{flex-direction:row}
  .modal .mfoot.actrow .btn{width:auto}
  .modal.scrolly{padding:0;max-height:92dvh}
  .modal.scrolly::before{margin:8px auto 0;flex:0 0 auto}
  .modal.scrolly .mhead{padding:12px 18px 16px}
  .modal.scrolly .mbody-scroll{padding:18px 18px calc(24px + env(safe-area-inset-bottom))}
}

/* --------------------------------------------------------------- inputs --*/
.field{display:flex;flex-direction:column;gap:7px}
.field label{font-size:12.5px;font-weight:750;font-family:var(--f-dis);color:var(--ink-2);
  letter-spacing:.005em}
.inp{height:46px;padding:0 15px;border-radius:14px;background:var(--surface-2);
  border:1.5px solid var(--line);color:var(--ink);font-size:14.5px;font-weight:500;width:100%;
  transition:border-color .25s,background .25s,box-shadow .25s}
.inp:hover{border-color:var(--line-strong)}
/* One place for "that did not work" inside a modal. Every account flow can
   fail for a reason only the server knows — an address already in use, a wrong
   current password, a code that has expired — and each of those has to be
   readable where the student is looking, not in a toast behind the modal. */
.merr{margin-top:12px;padding:11px 13px;border-radius:13px;background:var(--no-soft);
  border:1px solid var(--no-line);color:var(--no);font-size:13.4px;line-height:1.5;
  font-weight:600}
.merr[hidden]{display:none}

/* --------------------------------------------------------------- free trial */
/* A row of its own, always. The two actions that cost money share the row above;
   this one spans the width beneath them, at every screen size, because a layout
   that is two rows here and three there is the thing that looks broken.

   The colour is the positive one — free is good news, not the option left over
   after the two that charge. The second line carries "no sign-up needed"
   because that is the objection somebody actually has: free usually means an
   email address and a password first, and saying otherwise removes the excuse
   more effectively than any adjective. */
/* THE BRAND var(--grad-brand)IENT AS AN OUTLINE, not as a fill.

   It was green. The only green in this palette is --ok, which means "your
   answer was correct" - so a green button was borrowing the marking language to
   sell something, and that is why it read as foreign however it was arranged.
   Nothing here is green now. The gradient this whole product is built on draws
   the edge and the words, and the inside stays the surface it sits on: lighter
   than the two buttons above it, and unmistakably the same family.

   Two backgrounds do the border: the surface colour clipped to the padding box
   over the gradient clipped to the border box. That is the only way to put a
   gradient on a border, and it needs no image and no extra element.

   It is finished WITHOUT a mouse, which the old one was not - it spent its
   whole effect on hover, so on a phone it was a flat pale rectangle. The edge
   and the lettering are there at rest; hover fills it in. */
.btn-free{flex:1 1 100% !important;width:100%;
  height:auto !important;min-height:48px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:1px !important;padding:8px 16px !important;text-align:center;
  color:#fff;border:0;
  position:relative;overflow:hidden;isolation:isolate;
  /* SOLID, AND THE LOUDEST THING ON THE CARD. It was a near-white outline
     between a white Add to cart and a solid Buy now -- the palest button on the
     card carrying the best offer on it. The weights are swapped: this one is
     filled and Buy now is outlined below.
     A DEEPER GRADIENT THAN --grad-brand, and that is not taste. White on #A855F7 is
     3.96:1 and on #FF3D9A 3.29:1 -- both under the 4.5 a 14px label needs, and
     Buy now has been carrying that fault all along. #9E42F6 and #E4006D are the
     same two colours taken down until white clears 4.6 at every stop; only the
     pink moves much, and it is still plainly the brand pink. */
  /* AN ORDINARY SOLID BUTTON, AND THEN SOMETHING MOVES ACROSS IT. The gradient
     used to slide inside the button and the shadow used to breathe, so the
     button itself was never still -- it read as a special effect rather than as
     a button. It sits exactly like Buy now or Sign in now: one static gradient,
     one steady shadow. The only thing that moves is the shine crossing it,
     which is a layer on top and changes nothing underneath. */
  /* THE ORIGINAL GRADIENT, ONE STEP LESS BRIGHT. A flat 10% veil over it, which
     is the only way to take brightness off a fill without touching what it is
     made of: every hue keeps its own value and the three stay in exactly the
     relationship they had. Lightness, saturation and stop positions were each
     tried on the colours themselves and each one changed the gradient into a
     different gradient.
     ON TOP OF --grad-btn, NOT INSTEAD OF IT, so the token stays the site's own
     and Sign in still reads from the same definition. */
  /* NO VEIL. The 10% black over the old stops was how "less bright" was
     delivered; the new stops already are that much deeper, and measured
     against what the owner approved this sits 1.2% off it while keeping
     both would be 21.6% darker. */
  background:var(--grad-btn);
  box-shadow:var(--sh-brand)}
/* AND SOMETHING CROSSES IT, left to right, over and over. White now, because
   the fill is saturated -- the tinted stripe that showed up on a near-white
   button is invisible on this one. Narrow, with long feathered edges, so at any
   instant it is over a fraction of the button and gone in half a second. */
.btn-free::after{content:'';position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:linear-gradient(100deg,
    transparent 38%,
    rgba(255,255,255,.30) 47%,
    rgba(255,255,255,.42) 50%,
    rgba(255,255,255,.30) 53%,
    transparent 62%);
  transform:translateX(-115%);
  animation:freePass 2.8s cubic-bezier(.42,0,.3,1) infinite}
@keyframes freePass{
  0%,6%   {transform:translateX(-115%)}
  60%,100%{transform:translateX(115%)}
}
/* Anybody who has asked the system not to move things gets a button that does
   not move: the gradient sits where it starts and nothing crosses it. */
/* Anybody who has asked the system not to move things gets a plain solid
   button: the shine does not run and nothing else ever moved. */
@media (prefers-reduced-motion:reduce){
  .btn-free::after{animation:none;opacity:0}
}
.btn-free b{font-family:var(--f-dis);font-weight:800;font-size:15px;
  letter-spacing:-.012em;line-height:1.25;color:#fff}
/* The objection, answered on the button: free usually means an email address
   and a password first. It was --ink-3 on a white fill, about 3.7:1, which made
   the whole offer the faintest thing on the button making it. On the fill it is
   white at .92 -- 4.6:1 against the palest stop underneath. One line at every
   width, so two cards side by side are never different heights. */
.btn-free small{font-size:11.6px;font-weight:600;line-height:1.3;
  /* FULL WHITE, not .92 of it. Measured on the fill: .92 comes out 4.05:1
     against the palest stop, and this is 11.6px text, which needs 4.5. The
     eight per cent bought nothing and cost the line its legibility. */
  color:#fff;
  max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.btn-free:hover{transform:translateY(-2px);
  box-shadow:0 14px 34px -8px rgba(90,69,255,.55)}
@media (prefers-reduced-motion:reduce){ .btn-free:hover{transform:none} }

/* BUY NOW STEPS BACK SO START FREE TRIAL CAN LEAD. On this card the free entry
   is the offer worth shouting, and two solid buttons in one row shout over each
   other -- so this one is outlined in the brand colour instead of filled with
   it. Three weights in one family: Add to cart plain, Buy now outlined, Start
   Free Trial filled.
   BRAND TEXT ON WHITE IS 5.63:1, better than the 3.96 white-on-gradient it had.
   The swap costs it nothing in legibility; it only stops it competing.
   Scoped to the card's action row -- .btn-solid is the site's primary button
   everywhere else and is not touched. */
/* A REAL COLOURED OUTLINE, not --brand-line. That token is brand at 22% --
   #DAD6FF on a white button, 1.39:1, which is an edge you have to look for.
   Full brand is 5.63:1 and matches the button's own lettering, so the outline
   and the words read as one deliberate object rather than a box that happens to
   be near some purple text. It is also what keeps this distinct from Add to
   cart beside it, which keeps the grey --line-strong. */
/* .btn-p, NOT .btn-solid. The app and the public pages name their buttons
   differently -- primary is .btn-p here and .btn-solid there, secondary is
   .btn-s here and .btn-ghost there -- so the rule copied across from page.css
   matched nothing and Buy now stayed filled. Two filled buttons on one card,
   and the app's was the one at 3.29:1.
   This is the one place the two files cannot be identical, and it is worth
   saying out loud: identical rules are not the same as identical results when
   the markup underneath uses different names. */
.actrow .btn-p{background:var(--brand-soft);color:var(--brand);
  border:1.5px solid var(--brand);box-shadow:none}
/* .13, not .17. Brand text on a 17% tint is 4.37:1 -- a hover state is
   still text somebody reads, and it must not fall under 4.5 the moment a
   pointer touches it. */
/* MORE THAN SUBTLE, WITHOUT BECOMING A PURPLE SLAB. A 13% tint was a
   hover you had to look for; filling it solid brand was the other
   extreme and she said so -- "buy now hover is now too purple".
   What changes instead is the EDGE and the LIFT: the tint goes to 15%,
   which is as far as it can go with brand lettering on it (17% is
   4.37:1), the border doubles, and a brand shadow puts it above the
   card. Three things move at once, so it is unmistakable, and none of
   them is more colour. */
.actrow .btn-p:hover{background:rgba(90,69,255,.15);
  border-color:var(--a1);color:var(--brand);transform:translateY(-2px);
  /* an inset ring rather than a thicker border: these buttons are
     border-box, so growing the border eats 1.5px of content on each
     side and the label shifts under the pointer. A shadow thickens the
     edge and moves nothing. */
  box-shadow:inset 0 0 0 1.5px var(--a1),0 8px 20px -8px rgba(90,69,255,.55)}

/* .btn-sm shrinks the shared button to 38px; this one carries two lines, so it
   keeps its own floor rather than being squeezed under its own text. */
.btn-free.btn-sm{min-height:46px;font-size:inherit}

/* --------------------------------------------------------------- in cart --
   A STATE, NOT AN ACTION, and it was painted like an action: soft violet beside
   the solid violet of Buy now, two strengths of one colour arguing. Once the
   course is in the cart there is nothing left to do here, so it goes quiet and
   the eye carries on to Checkout. Its own class rather than a repaint of
   .btn-g, which the certificate link and the read-aloud toggles also wear. */
.btn.incart{background:var(--surface-2);border:1px solid var(--line);
  color:var(--ink-3);box-shadow:none}
.btn.incart:hover{background:var(--surface-3);color:var(--ink-2);
  border-color:var(--line-strong);transform:none;box-shadow:none}

/* ---------------------------------------------------------------- mini cart */
/* The same bar the public store raises, so the shop behaves the same on both
   sides of the sign-in. It is only ever present when something is in the cart:
   an empty bar is a permanent strip of nothing across the bottom of a phone. */
.minicart{position:fixed;left:0;right:0;bottom:0;z-index:70;
  background:var(--glass);backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);
  border-top:1px solid var(--line-strong);box-shadow:0 -10px 34px -14px rgba(24,20,70,.3);
  transform:translateY(115%);transition:transform .34s var(--spring);
  padding-bottom:env(safe-area-inset-bottom,0)}
.minicart.up{transform:none}
.mc-in{max-width:1180px;margin:0 auto;display:flex;align-items:center;gap:12px;
  padding:11px clamp(14px,4vw,28px)}
.mc-list{flex:1 1 auto;display:flex;align-items:center;gap:11px;min-width:0;text-align:left}
.mc-n{flex:0 0 auto;width:30px;height:30px;border-radius:10px;display:grid;place-items:center;
  background:var(--grad-brand);color:#fff;font-family:var(--f-dis);font-weight:800;font-size:14px;
  box-shadow:var(--sh-brand)}
.mc-t{min-width:0;display:block}
.mc-t b{display:block;font-family:var(--f-dis);font-weight:800;font-size:17px;
  letter-spacing:-.03em;line-height:1.1}
.mc-t i{display:block;font-style:normal;color:var(--ink-3);font-size:12.4px;margin-top:1px}
.mc-go{flex:0 0 auto}
/* The dock sits at the bottom on a phone, so the bar rides above it rather than
   over it. */
/* --dock-h is the dock's measured height, written by syncBottomChrome(). The
   fallback is the nominal calculation, which is a few pixels short - it only
   applies for the one frame before the measurement lands. */
@media (max-width:759.98px){
  .minicart{bottom:var(--dock-h,calc(var(--tab-h) + env(safe-area-inset-bottom)))}
}
/* IT STOPS WHERE THE RAIL STOPS. The bar itself is unchanged - same glass, same
   single top edge, same shelf sitting on the bottom of the screen - it simply no
   longer starts at the left edge of the window. It was spanning the whole width
   at z-index 70 over a rail at 60, so the cart was drawn across the bottom of
   the navigation, which is not its to cover. */
@media (min-width:1100px){ .minicart{left:var(--nav-w)} }

/* A student's own picture, once there is one. The initial stays underneath as
   the markup it always was — this only covers it — so a picture that fails to
   load, or a student who has never had one, is the screen that shipped before
   any of this existed. */
.avatar.hasimg{background-size:cover;background-position:center;color:transparent;
  text-shadow:none}

/* ------------------------------------------------------------- the cropper */
/* A square stage with a round window cut into it. The picture underneath is a
   plain positioned <img> the drag handler moves; the ring is drawn on top with
   one enormous box-shadow, which dims everything outside the circle without a
   second element and without an SVG mask. */
.cropstage{position:relative;width:100%;max-width:280px;aspect-ratio:1;margin:16px auto 0;
  border-radius:18px;overflow:hidden;background:var(--surface-3);cursor:grab;
  touch-action:none;user-select:none}
.cropstage.grabbing{cursor:grabbing}
.cropstage img{position:absolute;max-width:none;pointer-events:none;
  -webkit-user-drag:none}
.cropring{position:absolute;inset:0;border-radius:50%;pointer-events:none;
  box-shadow:0 0 0 999px rgba(6,8,30,.55);border:2px solid rgba(255,255,255,.85)}
.inp:focus{outline:none;border-color:var(--brand);background:var(--surface);
  box-shadow:0 0 0 4px var(--brand-soft)}
.inp::placeholder{color:var(--ink-4)}
select.inp{appearance:none;cursor:pointer;padding-right:38px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%237B7FA4' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right 13px center}
textarea.inp{height:auto;padding:12px 15px;resize:vertical;line-height:1.6}

/* ------------------------------------------------------------ checkout --
   Three small things the payment modal needs and nothing else uses. Kept with
   the other form styles rather than inlined, so the modal is built from the
   same tokens as every other surface in the app. */
.hintline{font-size:12.3px;color:var(--ink-3);line-height:1.5;margin-top:6px}
.payerr{margin-top:14px;padding:11px 13px;border-radius:14px;text-align:left;
  background:var(--no-soft);border:1px solid var(--no-line);color:var(--no);
  font-size:13.4px;font-weight:600;line-height:1.5}
.payerr[hidden]{display:none}
/* The button keeps its size while it works, so the modal does not jump. */
.mini-spin{width:15px;height:15px;flex:0 0 auto;border-radius:50%;
  border:2px solid rgba(255,255,255,.35);border-top-color:#fff;
  animation:spinS .7s linear infinite;margin-right:9px;display:inline-block;
  vertical-align:-2px}

.switch{position:relative;width:50px;height:29px;border-radius:99px;background:var(--ring-track);
  border:1px solid var(--line);flex:0 0 auto;cursor:pointer;transition:background .35s var(--ease)}
.switch::after{content:"";position:absolute;top:2px;left:2px;width:23px;height:23px;border-radius:50%;
  background:#fff;box-shadow:0 2px 6px rgba(0,0,0,.22);transition:transform .38s var(--spring)}
.switch[aria-checked="true"]{background:var(--grad-brand);border-color:transparent}
.switch[aria-checked="true"]::after{transform:translateX(21px)}
.switch:active::after{width:27px}

input[type=range]{-webkit-appearance:none;appearance:none;width:100%;height:6px;border-radius:99px;
  background:linear-gradient(90deg,var(--brand) var(--p,50%),var(--ring-track) var(--p,50%));
  outline:none;cursor:pointer}
input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:20px;height:20px;border-radius:50%;
  background:#fff;border:3px solid var(--brand);cursor:pointer;box-shadow:var(--sh-2);
  transition:transform .28s var(--spring)}
input[type=range]::-webkit-slider-thumb:hover{transform:scale(1.22)}
input[type=range]::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:#fff;
  border:3px solid var(--brand);cursor:pointer;box-shadow:var(--sh-2)}

.setrow{display:flex;align-items:center;gap:14px;padding:14px 2px;border-bottom:1px solid var(--line-soft)}
.setrow:last-child{border-bottom:0}
.setrow .sic{width:38px;height:38px;flex:0 0 auto;border-radius:12px;display:grid;place-items:center;
  background:var(--surface-3);color:var(--ink-2)}
.setrow .sic .i{width:18px;height:18px}
.setrow .st{flex:1 1 auto;min-width:0}
.setrow .st b{display:block;font-size:14px;font-weight:700;font-family:var(--f-dis);letter-spacing:-.012em}
.setrow .st span{display:block;font-size:12.5px;color:var(--ink-3);margin-top:2px;line-height:1.5}

/* theme picker — each option previews the identity it will apply.
   minmax(0,1fr), not plain 1fr: a bare 1fr track is really minmax(auto,1fr),
   so its floor is the widest min-content of whatever lands in that column —
   here, Marine and Orchid happening to fall in column 2 with a couple of the
   longer subtitles among the five. Below ~640px that floor exceeded the
   column's actual share of the grid, and the grid does not clip its own
   overflow, so those two cards were rendered wider than the track and spilled
   past the grid's own right edge. minmax(0,…) removes the content-based floor
   so the track — and the card filling it — shrinks with the grid instead. */
.themegrid{display:grid;gap:10px;grid-template-columns:minmax(0,1fr)}
@media (min-width:520px) and (max-width:1099.98px){.themegrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:1240px){.themegrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
.themecard{display:flex;align-items:center;gap:13px;padding:11px;border-radius:16px;text-align:left;
  background:var(--surface-2);border:1.5px solid var(--line);width:100%;
  transition:transform .35s var(--spring),border-color .22s,background .22s,box-shadow .3s}
.themecard:hover{transform:translateY(-2px);border-color:var(--brand-line);box-shadow:var(--sh-1)}
.themecard.on{border-color:var(--brand);background:var(--brand-soft)}
.tc-art{width:58px;height:44px;flex:0 0 auto;border-radius:12px;position:relative;overflow:hidden;
  display:flex;align-items:flex-end;gap:3px;padding:6px;box-shadow:var(--sh-1)}
.tc-art i{width:9px;height:9px;border-radius:50%;box-shadow:0 0 0 1.5px rgba(255,255,255,.5)}
.tc-tx{flex:1 1 auto;min-width:0}
.tc-tx b{display:block;font-family:var(--f-dis);font-weight:750;font-size:13.8px;letter-spacing:-.014em}
.tc-tx span{display:block;font-size:11.8px;color:var(--ink-3);margin-top:1px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tc-check{width:22px;height:22px;flex:0 0 auto;border-radius:50%;display:grid;place-items:center;
  background:var(--brand);color:#fff;opacity:0;transform:scale(.5);
  transition:all .32s var(--spring)}
.tc-check .i{width:13px;height:13px;stroke-width:3}
.themecard.on .tc-check{opacity:1;transform:none}

/* ==== src/parts/p04.html, verbatim ==== */

/* ============================================================================
   ONBOARDING — an entrance, not a form
   ========================================================================== */
#onb{position:fixed;inset:0;z-index:500;display:grid;place-items:center;padding:24px;overflow:hidden;
  background:linear-gradient(150deg,#0A0A26,
    color-mix(in srgb,var(--h1) 82%,#000) 40%,
    color-mix(in srgb,var(--h3) 62%,#12082A) 74%, #0B1140)}
#onb .ob-aura{position:absolute;inset:-20%;z-index:0}
#onb .ob-aura i{position:absolute;border-radius:50%;filter:blur(80px);opacity:.55;mix-blend-mode:screen}
#onb .ob-aura i:nth-child(1){width:60vmax;height:60vmax;background:var(--a1);top:-18vmax;left:-14vmax;
  animation:drift1 18s var(--ease-io) infinite alternate}
#onb .ob-aura i:nth-child(2){width:48vmax;height:48vmax;background:var(--a3);bottom:-14vmax;right:-10vmax;
  animation:drift2 22s var(--ease-io) infinite alternate}
#onb .ob-aura i:nth-child(3){width:42vmax;height:42vmax;background:var(--a2);top:34%;left:38%;
  animation:drift3 20s var(--ease-io) infinite alternate}
#onb canvas{position:absolute;inset:0;z-index:1;pointer-events:none}
#onb .ob-in{position:relative;z-index:2;width:min(560px,100%);text-align:center;color:#fff}
#onb .ob-mark{width:82px;height:82px;margin:0 auto;border-radius:26px;display:grid;place-items:center;
  background:linear-gradient(140deg,rgba(255,255,255,.22),rgba(255,255,255,.06));
  border:1px solid rgba(255,255,255,.28);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
  box-shadow:0 22px 60px -18px rgba(0,0,0,.6);animation:popIn .9s var(--spring) both}
#onb .ob-mark svg{width:44px;height:44px}
#onb .ob-mark path.dr{stroke-dasharray:120;stroke-dashoffset:120;animation:draw 1.5s var(--ease) .3s forwards}
#onb .ob-mark circle.dr{opacity:0;animation:fadeIn .5s var(--ease) 1.4s forwards}
@keyframes draw{to{stroke-dashoffset:0}}
#onb .ob-brand{margin-top:22px;font-family:var(--f-dis);font-weight:500;font-size:14px;
  letter-spacing:.34em;text-transform:uppercase;color:rgba(255,255,255,.7);
  animation:fadeUp .9s var(--ease) .5s both}
#onb h1{font-family:var(--f-ser);font-size:clamp(38px,8.4vw,72px);line-height:1;letter-spacing:-.03em;
  margin-top:12px;animation:fadeUp 1s var(--ease) .68s both;text-wrap:balance}
#onb h1 em{font-style:italic;background:linear-gradient(100deg,#FFD9F2,#BFE9FF 60%,#fff);
  -webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent}
#onb .ob-say{font-size:clamp(15px,2.2vw,17.5px);color:rgba(255,255,255,.78);margin-top:16px;
  line-height:1.6;max-width:44ch;margin-inline:auto;animation:fadeUp 1s var(--ease) .86s both}
#onb .ob-form{margin-top:38px;animation:fadeUp 1s var(--ease) 1.04s both}
#onb .ob-wrap{position:relative;max-width:420px;margin:0 auto}
#onb input{width:100%;height:64px;background:transparent;border:0;border-bottom:2px solid rgba(255,255,255,.26);
  color:#fff;font-family:var(--f-dis);font-size:clamp(24px,5vw,32px);font-weight:600;text-align:center;
  letter-spacing:-.02em;transition:border-color .3s;padding:0 52px 0 12px}
#onb input:focus{outline:none;border-bottom-color:transparent}
#onb input::placeholder{color:rgba(255,255,255,.32);font-weight:400}
#onb .ob-underline{position:absolute;left:0;right:0;bottom:0;height:2px;overflow:hidden;border-radius:2px}
#onb .ob-underline i{display:block;height:100%;width:0;background:linear-gradient(90deg,#8B5CF6,#FF3D9A,#22D3EE);
  transition:width .55s var(--ease)}
#onb .ob-go{position:absolute;right:2px;bottom:11px;width:44px;height:44px;border-radius:14px;
  display:grid;place-items:center;background:#fff;color:#2A1470;opacity:0;transform:scale(.5) translateX(10px);
  transition:all .45s var(--spring);pointer-events:none;box-shadow:0 10px 26px -6px rgba(0,0,0,.5)}
#onb .ob-go.on{opacity:1;transform:none;pointer-events:auto}
#onb .ob-go:hover{transform:translateX(3px) scale(1.06)}
#onb .ob-hint{margin-top:20px;font-size:12.5px;color:rgba(255,255,255,.5);display:flex;
  align-items:center;justify-content:center;gap:7px}
#onb .ob-hint .i{width:14px;height:14px}
/* The entry animations use fill-mode "both", which holds opacity:1 and beats a
   plain declaration — so the form has to be told to stop animating before it
   can clear the stage for the welcome. */
#onb.stage2 .ob-in>*:not(.ob-final){
  animation:none !important;opacity:0;transform:translateY(-18px) scale(.985);
  transition:opacity .45s var(--ease),transform .6s var(--ease);pointer-events:none}
#onb .ob-final{position:fixed;inset:0;display:grid;place-items:center;padding:26px;
  opacity:0;pointer-events:none;text-align:center}
/* Arrive quickly — the hold happens after this lands, not during it. */
#onb.stage2 .ob-final{opacity:1;animation:fadeUp .62s var(--ease) .14s both}
#onb .ob-final .fn{font-family:var(--f-ser);font-size:clamp(34px,7.4vw,64px);line-height:1.06;
  letter-spacing:-.03em;text-wrap:balance;max-width:16ch;margin-inline:auto;
  overflow-wrap:anywhere;word-break:break-word}
#onb .ob-final .fn em{font-style:italic;display:inline-block;
  background:linear-gradient(100deg,#FFD9F2,#BFE9FF 60%,#fff);
  -webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
  padding-right:.16em;margin-right:-.1em;padding-bottom:.08em;margin-bottom:-.08em}
#onb .ob-final .fs{font-size:15.5px;color:rgba(255,255,255,.74);margin-top:16px;
  display:flex;align-items:center;justify-content:center;gap:9px}
#onb .ob-final .fs i{width:6px;height:6px;border-radius:50%;background:rgba(255,255,255,.7);
  animation:tdot 1.2s infinite}
#onb .ob-final .fs i:nth-child(2){animation-delay:.16s}
#onb .ob-final .fs i:nth-child(3){animation-delay:.32s}
#onb.done{animation:obOut 1.05s var(--ease) forwards}
@keyframes obOut{0%{opacity:1;transform:scale(1)}55%{opacity:1;transform:scale(1.03)}
  100%{opacity:0;transform:scale(1.14);visibility:hidden}}

/* ============================================================================
   DASHBOARD
   ========================================================================== */
.dashgrid{display:grid;gap:16px;margin-top:16px}
@media (min-width:760px){.dashgrid{gap:18px;grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:1100px){.dashgrid{grid-template-columns:minmax(0,1.62fr) minmax(0,1fr);gap:20px}}
.nudgestack{display:flex;flex-direction:column;gap:11px;margin-top:16px}

.contcard{position:relative;overflow:hidden;padding:0}
.contcard .art{height:104px;position:relative;background:var(--grad-cool);overflow:hidden}
.contcard .art::after{content:"";position:absolute;inset:0;
  background:radial-gradient(300px circle at 78% 20%,rgba(255,255,255,.3),transparent 60%)}
.contcard .art .lnum{position:absolute;right:16px;bottom:-14px;font-family:var(--f-dis);font-weight:800;
  font-size:82px;color:rgba(255,255,255,.16);letter-spacing:-.06em;line-height:1}
.contcard .art .tag{position:absolute;left:16px;top:16px}
.contcard .bd{padding:18px}
.sentpair{display:flex;flex-direction:column;gap:8px}
/* The verdict marks run 10% down the brightness scale from the shared ok/no
   accents. Redefining the two tokens on .sline itself means the chip fill and
   the strike-through move together and every sentence pair matches, wherever it
   appears — while quiz feedback, toasts and tags keep the accents at full. */
.sline{--ok:#058760; --no:#CB1A64;
  display:flex;align-items:flex-start;gap:9px;font-size:14.5px;line-height:1.5}
:root[data-theme="dark"] .sline{--ok:#2FCE9E; --no:#E65384}
/* The cross and the tick carry the whole point of this course, so they are solid
   chips rather than 9%-alpha tints, and the wrong sentence stays readable ink
   rather than dropping to the muted tertiary grey. One rule set, so every place
   a sentence pair appears — lesson hero, lesson prose, the dashboard's
   "pick up where you left off" card, the quiz prompt — reads the same. */
.sline .mk{width:21px;height:21px;flex:0 0 auto;border-radius:7px;display:grid;place-items:center;
  margin-top:1px;box-shadow:0 1px 3px rgba(24,20,70,.16)}
.sline .mk .i{width:13px;height:13px;stroke-width:3}
.sline.bad .mk{background:var(--no);color:var(--on-no)}
.sline.bad .stx{color:var(--ink-2);font-weight:500;text-decoration:line-through;
  text-decoration-color:var(--no);text-decoration-thickness:1.5px;text-underline-offset:2px}
.sline.good .mk{background:var(--ok);color:var(--on-ok)}
.sline.good .stx{color:var(--ink);font-weight:650}
.sline.tick .mk{background:var(--brand);color:var(--on-brand)}
.sline.tick .stx{color:var(--ink)}
.sline.arrow .mk{background:var(--surface-3);color:var(--ink-2);box-shadow:none}

.mini{display:flex;align-items:center;gap:12px;padding:12px;border-radius:16px;
  transition:background .25s,transform .35s var(--spring);cursor:pointer;width:100%;text-align:left}
.mini:hover{background:var(--surface-2);transform:translateX(3px)}
.mini .mn{width:38px;height:38px;flex:0 0 auto;border-radius:12px;display:grid;place-items:center;
  font-family:var(--f-dis);font-weight:800;font-size:13.5px;background:var(--surface-3);color:var(--ink-2)}
.mini .mn.on{background:var(--grad-brand);color:#fff}
.mini .mt{flex:1 1 auto;min-width:0}
.mini .mt b{display:block;font-size:13.8px;font-weight:700;letter-spacing:-.012em;font-family:var(--f-dis);
  max-width:100%}
.mini .mt span{display:block;font-size:12.5px;color:var(--ink-3);margin-top:1px}
.mini .mt .bar{display:block;width:100%}
.mini .mn .i{width:17px;height:17px}
.mini>.i{color:var(--ink-4);transition:transform .3s var(--spring),color .2s}
.mini:hover>.i{color:var(--brand);transform:translateX(3px)}

.insight{display:flex;gap:12px;align-items:flex-start;padding:12px 0;border-bottom:1px solid var(--line-soft)}
.insight:last-child{border-bottom:0}
.insight .iic{width:32px;height:32px;flex:0 0 auto;border-radius:10px;display:grid;place-items:center;
  background:var(--brand-soft);color:var(--brand)}
.insight .iic .i{width:16px;height:16px}
.insight b{font-size:13.5px;font-family:var(--f-dis);letter-spacing:-.012em}
.insight p{font-size:12.8px;color:var(--ink-3);margin-top:2px;line-height:1.5}

.qa{display:grid;grid-template-columns:repeat(2,1fr);gap:11px}
.qabtn{display:flex;flex-direction:column;gap:9px;padding:15px;border-radius:18px;text-align:left;
  background:var(--surface-2);border:1px solid var(--line);
  transition:transform .38s var(--spring),box-shadow .35s,border-color .25s,background .25s}
.qabtn:hover{transform:translateY(-4px);box-shadow:var(--sh-2);border-color:var(--brand-line);
  background:var(--surface)}
.qabtn .qic{width:38px;height:38px;border-radius:13px;display:grid;place-items:center;color:#fff}
.qabtn .qic .i{width:19px;height:19px}
.qabtn b{font-size:13.8px;font-family:var(--f-dis);letter-spacing:-.014em}
.qabtn span{font-size:12px;color:var(--ink-3);line-height:1.42}

/* ============================================================================
   COURSES
   ========================================================================== */
/* ------------------------------------------------------------ my library --*/
/* The courses already paid for, and the switch between them. Deliberately
   compact: a library is a short list of things a student already has, and
   giving each one a full catalogue card is exactly what pushed the Store off
   the first screen. These say where you are, then get out of the way. */
/* the same track sizes as .catalogue below it, so a library card and a store
   card are exactly as wide as each other at every width and the two sections
   read as one shelf rather than two grids that happen to be stacked */
/* 16px was enough when a card opened with its own quiet padding: the heading
   had 36px of nothing under it before anything was drawn. A card wearing the
   Active band starts with a saturated colour at its very top edge, so the same
   16px put "My Library" and "ACTIVE" within a few pixels of each other with a
   block of brand in between - two labels fighting over one small space. The
   grid takes the air instead, so it is right whichever card is active and
   whether any of them is. */
.libgrid{display:grid;gap:16px;margin-top:26px}
@media (min-width:640px){.libgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:1100px){.libgrid{grid-template-columns:repeat(3,minmax(0,1fr));gap:18px}}
/* Art and title share the top row, progress spans the next, Open and the Active
   marker take the last. Every card in the library is built and painted
   identically — which one is active is reported by the marker alone, never by
   the card. The sheen is a background layer rather than an overlay so it stays
   under the content without any stacking work. */
.libcard{position:relative;display:grid;grid-template-columns:auto minmax(0,1fr);
  column-gap:17px;row-gap:17px;align-items:center;padding:20px;border-radius:var(--r-lg);
  overflow:hidden;border:1px solid var(--line);box-shadow:var(--sh-1);
  background:radial-gradient(340px circle at 94% -12%,var(--brand-soft),transparent 62%),var(--surface);
  transition:transform .4s var(--spring),box-shadow .35s,border-color .3s}
:root[data-theme="dark"] .libcard{
  background:radial-gradient(340px circle at 94% -12%,var(--brand-soft),transparent 62%),
             linear-gradient(150deg,rgba(30,36,80,.62),rgba(14,18,46,.82))}
.libcard:hover{transform:translateY(-3px);box-shadow:var(--sh-2);border-color:var(--brand-line)}
.libcard .lc-art{width:74px;height:74px;border-radius:20px;overflow:hidden;position:relative;
  box-shadow:var(--sh-2);transition:transform .45s var(--spring)}
.libcard .lc-art canvas{position:absolute;inset:0;width:100%;height:100%}
/* a glazed edge over the generated art — it sits above the canvas, which an
   inset shadow on the tile itself could not */
.libcard .lc-art::after{content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  box-shadow:inset 0 1px 1px rgba(255,255,255,.42),inset 0 0 0 1px rgba(255,255,255,.16)}
.libcard:hover .lc-art{transform:scale(1.05)}
/* the card is a grid, so its columns are blockified for free — but the text
   column's own children are not, and .bar would collapse to nothing as an
   inline box */
.libcard .lc-main{display:block;min-width:0}
/* the whole course name, wrapped rather than cut — this is the label the
   student is choosing by, and half of it is not enough to choose on */
.libcard .lc-t{font-family:var(--f-dis);font-weight:750;font-size:16px;
  line-height:1.28;letter-spacing:-.022em;color:var(--ink)}
.libcard .lc-lv{display:block;font-size:12.2px;font-weight:650;color:var(--ink-3);margin-top:6px}
.lc-prog{grid-column:1/-1;display:block}
.lc-pn{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:9px;
  font-size:12px;font-weight:700;color:var(--ink-3)}
.lc-pn b{font-family:var(--f-dis);font-size:13.2px;font-weight:800;letter-spacing:-.02em;color:var(--ink-2)}
.libcard .bar{display:block;width:100%}
/* Open takes whatever width the row has left rather than sitting pinned to the
   right edge — stranded against the corner it read as an afterthought, and on
   the card with no marker beside it there was nothing to balance against. It
   still always finishes flush right; it just starts where the content does. */
.lc-side{grid-column:1/-1;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.lc-open{flex:1 1 auto}
/* Unlock full course keeps its own width - it is a sentence, and squeezing a
   sentence to fit beside Open is how a button ends up reading "Unlock full
   cou...". The row wraps instead: on a wide card nothing moves, and on a narrow
   one the two buttons take a line of their own rather than being crushed onto
   the marker's. */
.lc-buy{flex:0 0 auto}
/* Buy now appears only on a course being tried, and keeps its own width so the
   card reads the same with it and without it: Open still finishes flush right
   and still starts where the content does. */
.lc-buy{flex:0 0 auto}
/* ACTIVE — a status, so it is built like one: a lit dot with its own halo and a
   micro-label in the same uppercase, letter-spaced voice the Coming Soon chip
   already speaks in. Soft-filled rather than gradient, so it reports alongside
   Open without competing with it. */
/* THE HAT. Full-bleed across the top of the card - the negative margins undo
   the card's own 20px padding, and the card's overflow:hidden trims the ends to
   its corner radius, so the band takes the shape of the card rather than sitting
   on it. Left padding matches the card's, so the word starts on the same line
   the artwork and title start on.

   Filled, not tinted. A wash of brand-soft was too quiet to do a hat's job -
   from a shelf's distance it read as a slightly warmer stripe and the card
   still had to be examined. This is the same --grad-brand the primary buttons
   and the current-lesson chip carry, so the loudest thing on the shelf is a
   colour the app already owns rather than a new one invented for a badge. */
.libcard .lc-live{grid-column:1/-1;margin:-20px -20px 8px;height:36px;padding:0 20px;
  border:0;border-radius:0;letter-spacing:.13em;justify-content:flex-start;
  background:var(--grad-brand);color:#fff;
  box-shadow:0 1px 0 rgba(0,0,0,.06)}
/* White on the band, and pulsing with the ring the current lesson already uses,
   so "this is the one the app is working from" is said the same way in both
   places. The global reduced-motion rule stops it for anybody who asked. */
.libcard .lc-live .lc-dot{background:#fff;--pr:rgba(255,255,255,.6);
  box-shadow:0 0 0 3px rgba(255,255,255,.28);animation:pulseRing 2.4s infinite}
.lc-live{flex:0 0 auto;display:inline-flex;align-items:center;gap:9px;height:31px;
  padding:0 14px 0 11px;border-radius:99px;
  background:var(--brand-soft);border:1px solid var(--brand-line);color:var(--brand);
  font-family:var(--f-dis);font-size:10.6px;font-weight:800;letter-spacing:.1em;
  text-transform:uppercase;white-space:nowrap}
.lc-dot{position:relative;width:8px;height:8px;flex:0 0 auto;border-radius:50%;
  background:var(--brand);box-shadow:0 0 0 3px var(--brand-soft)}
/* one slow ring outward — the difference between "this is the one" and "this
   one is live right now". It rides the halo, never the card. */
.lc-dot::after{content:"";position:absolute;inset:0;border-radius:50%;
  border:1.5px solid var(--brand);animation:lcPing 2.6s var(--ease-io) infinite}
@keyframes lcPing{
  0%{transform:scale(1);opacity:.6}
  70%,100%{transform:scale(2.7);opacity:0}
}
:root[data-motion="off"] .lc-dot::after{display:none}
@media (prefers-reduced-motion:reduce){.lc-dot::after{display:none}}


.catalogue{display:grid;gap:16px;margin-top:16px}
@media (min-width:640px){.catalogue{grid-template-columns:repeat(2,1fr)}}
@media (min-width:1100px){.catalogue{grid-template-columns:repeat(3,1fr);gap:18px}}

.ccard{position:relative;overflow:hidden;border-radius:var(--r-2xl);border:1px solid var(--line);
  background:var(--surface);box-shadow:var(--sh-1);
  transition:transform .45s var(--ease),box-shadow .45s var(--ease),border-color .3s}
:root[data-theme="dark"] .ccard{background:linear-gradient(158deg,rgba(28,34,74,.7),rgba(13,17,44,.88))}
.ccard:hover{transform:translateY(-5px);box-shadow:var(--sh-3);border-color:var(--brand-line)}
/* The hat keeps the banner's own 3:1 so no part of a course picture is ever
   cropped away. A fixed 128px against a card 273-482 wide took up to 14.5%
   off each side, and three of the six drawn banners lost the first letter of
   their own title on a narrow phone. The public page.css carries the same
   pair of rules, because these two cards are meant to be one card.
   @supports: without aspect-ratio the fixed height must stand, or the hat
   collapses. */
.ccard .cart{height:128px;position:relative;overflow:hidden}
@supports (aspect-ratio: 3 / 1){ .ccard .cart{height:auto;aspect-ratio:3 / 1} }
.ccard .cart canvas{position:absolute;inset:0;width:100%;height:100%}
/* THE COURSE PICTURE, over the generated art. It is inserted before the
   glow, so the light still falls across the photograph the way it fell
   across the gradient, and the state chip still sits above both. If the
   file is not there the <img> removes itself and the canvas below is
   what it always was -- so no list of which courses have pictures has to
   be kept anywhere. object-fit:cover matches the public page, which
   paints the same file as a background-size:cover. */
.ccard .cart .cpic{position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:center}
.ccard .cart .glow{position:absolute;inset:0;
  background:radial-gradient(320px circle at 22% 24%,rgba(255,255,255,.3),transparent 62%)}
.ccard .cbody{padding:18px}
.ccard h3{font-size:17.5px;letter-spacing:-.026em}
/* TWO LINES, ALWAYS. min-height floors a short blurb; it does nothing to a
   long one, so a three-line blurb made its card 23px taller than the card
   beside it and the grid stretched every other card in the row to match.
   The clamp caps it at the same two lines the floor guarantees, which is
   what page.css does on the public shop's copy of this card. */
.ccard .cdesc{font-size:13.2px;color:var(--ink-3);margin-top:6px;line-height:1.55;
  min-height:2.9em;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;
  -webkit-box-orient:vertical;overflow:hidden}
/* A BUNDLE'S LAST BUTTON TAKES A ROW OF ITS OWN. A course card runs to two
   rows because Start Free Trial claims one; a bundle cannot be tried, so
   its three buttons sat on a single row and the card came out 58px shorter
   than every card beside it. */
.ccard.bundle .actrow > .btn:last-child{flex:1 1 100%}
.ccard .cmeta{display:flex;gap:14px;margin-top:14px;flex-wrap:wrap}
.ccard .cmeta i{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:650;
  color:var(--ink-3);font-style:normal}
.ccard .cmeta .i{width:14px;height:14px}
/* THE OLD PRICE, quieter than the new one and beside it. --ink-2 rather
   than --ink-3: a struck-through price is the number a saving is measured
   against, so it has to stay readable rather than become decoration.
   page.css draws the same pair on the public shop's card, and the two
   cards are meant to be one card. */
.ccard .cwas{color:var(--ink-2);font-weight:650;margin-right:1px;
  text-decoration-color:var(--ink-2);text-decoration-thickness:1.5px}
/* A course on sale. It used to be dimmed hard - saturate(.42) - which read as
   unavailable, and beside the word "Available" it would have contradicted
   itself. Held back only enough that an owned course still looks different. */
.ccard.avail{opacity:1}
.ccard.avail .cart{filter:saturate(.86)}
.ccard.avail:hover .cart{filter:saturate(1)}
/* A course still in production. It is not dimmed the way a locked-but-buyable
   course is — there is nothing to unlock yet, and greying out the very thing
   the Store exists to advertise would defeat the point. It stays fully lit and
   simply says when, so the art keeps doing its job. */
.ccard.soon{opacity:1}
.ccard.soon .cart{filter:none}
.statechip{position:absolute;top:14px;right:14px;display:inline-flex;align-items:center;gap:6px;
  height:29px;padding:0 12px;border-radius:99px;background:rgba(10,8,32,.5);color:#fff;
  border:1px solid rgba(255,255,255,.22);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
  font-size:11px;font-weight:750;font-family:var(--f-dis);letter-spacing:.06em;text-transform:uppercase}
.statechip .i{width:13px;height:13px}

/* -------------------------------------------------------------------- cart */
/* The cart is a bar control, so it borrows .iconbtn wholesale and adds nothing
   but a count — and the count only exists when there is something to count. */
#cartBtn{flex:0 0 auto}
.cartbadge{position:absolute;top:-6px;right:-6px;min-width:19px;height:19px;padding:0 5px;
  border-radius:99px;display:grid;place-items:center;pointer-events:none;
  background:var(--grad-brand);color:#fff;font-family:var(--f-dis);
  font-size:10.5px;line-height:1;font-weight:800;font-variant-numeric:tabular-nums;
  box-shadow:var(--sh-brand);border:2px solid var(--bg)}
.cartbadge.empty{display:none}
.cartbadge.pop{animation:popIn .45s var(--spring)}

.cartlist{display:flex;flex-direction:column;gap:9px;margin-top:14px}
.cartrow{display:flex;align-items:center;gap:12px;padding:12px;border-radius:16px;
  background:var(--surface-2);border:1px solid var(--line)}
.cartrow.leaving{animation:nudgeOut .3s var(--ease) forwards}
.cartrow .cr-ic{width:40px;height:40px;flex:0 0 auto;border-radius:13px;display:grid;place-items:center;
  background:var(--grad-brand);color:#fff;box-shadow:var(--sh-brand)}
.cartrow .cr-ic .i{width:19px;height:19px}
.cartrow b{display:block;font-family:var(--f-dis);font-size:14px;letter-spacing:-.016em;line-height:1.3}
.cartrow>div>span{display:block;font-size:12.2px;color:var(--ink-3);margin-top:2px}
.cartrow .cr-p{font-family:var(--f-dis);font-weight:800;font-size:14.5px;white-space:nowrap}
/* A COURSE A BUNDLE IN THIS CART ALREADY PAYS FOR. Shown rather than removed:
   the price is struck through and the row says which bundle covers it, so the
   total below adds up in front of the customer. Not dimmed to the point of
   looking unavailable — it is not unavailable, it is included. */
.cartrow.covered{background:var(--surface);border-style:dashed}
.cartrow.covered .cr-ic{background:var(--surface-3);color:var(--ink-3);box-shadow:none}
.cartrow .cr-p.was{text-decoration:line-through;color:var(--ink-4);font-weight:650}
.cartrow .cr-x{width:32px;height:32px;flex:0 0 auto;border-radius:10px;display:grid;place-items:center;
  color:var(--ink-4);transition:all .22s}
.cartrow .cr-x:hover{background:var(--no-soft);color:var(--no)}
.cartrow .cr-x .i{width:15px;height:15px}
.carttotal{display:flex;align-items:center;justify-content:space-between;margin-top:14px;padding-top:14px;
  border-top:1px solid var(--line)}
.carttotal span{font-size:13px;color:var(--ink-3);font-weight:650}
.carttotal b{font-family:var(--f-dis);font-weight:800;font-size:24px;letter-spacing:-.03em}

/* course preview sheet */
.prevhead{position:relative;height:150px;margin:-26px -26px 0;overflow:hidden;
  border-radius:var(--r-2xl) var(--r-2xl) 0 0}
.prevhead canvas{position:absolute;inset:0;width:100%;height:100%}
.prevhead .pvin{position:absolute;inset:auto 22px 18px;color:#fff;z-index:1}
.prevhead .pvin h2{color:#fff;font-size:clamp(20px,3.6vw,26px);text-shadow:0 2px 18px rgba(0,0,0,.35)}
@media (max-width:559.98px){ .prevhead{margin:-22px -18px 0;height:128px} }

.partacc{border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;background:var(--surface)}
:root[data-theme="dark"] .partacc{background:rgba(20,25,58,.55)}
.parthead{display:flex;align-items:center;gap:13px;padding:15px 17px;width:100%;text-align:left;
  transition:background .25s}
.parthead:hover{background:var(--surface-2)}
.parthead .pn{width:40px;height:40px;border-radius:13px;display:grid;place-items:center;flex:0 0 auto;
  background:var(--surface-3);font-family:var(--f-dis);font-weight:800;font-size:12.5px;color:var(--ink-2)}
.parthead .pn.done{background:var(--grad-mint);color:#fff}
.parthead .pt{flex:1 1 auto;min-width:0}
.parthead .pt b{display:block;font-size:14.5px;font-family:var(--f-dis);letter-spacing:-.016em}
.parthead .pt span{display:block;font-size:12.2px;color:var(--ink-3);margin-top:2px}
.parthead .cv{transition:transform .38s var(--spring);color:var(--ink-4)}
.parthead[aria-expanded="true"] .cv{transform:rotate(180deg)}
.partbody{display:grid;grid-template-rows:0fr;transition:grid-template-rows .45s var(--ease)}
.partbody>div{overflow:hidden}
.partacc[data-open="1"] .partbody{grid-template-rows:1fr}
.lrow{display:flex;align-items:center;gap:13px;padding:12px 17px 12px 15px;width:100%;text-align:left;
  border-top:1px solid var(--line-soft);transition:background .22s,padding-left .3s var(--ease)}
.lrow:hover{background:var(--surface-2);padding-left:20px}
.lrow .ls{width:30px;height:30px;flex:0 0 auto;border-radius:10px;display:grid;place-items:center;
  background:var(--surface-3);color:var(--ink-3);font-family:var(--f-dis);font-weight:800;font-size:12px}
.lrow .ls.done{background:var(--ok-soft);color:var(--ok)}
.lrow .ls.cur{background:var(--grad-brand);color:#fff;animation:pulseRing 2.4s infinite}
.lrow .ls .i{width:15px;height:15px}
.lrow .lt{flex:1 1 auto;min-width:0}
.lrow .lt b{display:block;font-size:13.6px;font-weight:650;letter-spacing:-.008em;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lrow .lt span{display:block;font-size:11.6px;color:var(--ink-4);margin-top:1px;font-weight:700;
  font-family:var(--f-dis);letter-spacing:.04em;text-transform:uppercase}
.lrow .lg{display:flex;align-items:center;gap:6px;flex:0 0 auto}
.lrow .lg .i{width:15px;height:15px;color:var(--ink-4)}

/* ============================================================================
   LESSON READER
   ========================================================================== */
/* ---------------------------------------------------------------------------
   READING SURFACE ("paper")
   The same card language used everywhere, tuned for long-form text: a calmer
   field behind the words, a measured line length and a firmer ink. Used by any
   reading view so the treatment is systemic, not a lesson-only exception.
   ------------------------------------------------------------------------- */
.paper{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-2xl);
  box-shadow:var(--sh-2);padding:clamp(20px,3.4vw,40px);position:relative}
:root[data-theme="dark"] .paper{
  background:linear-gradient(168deg,color-mix(in srgb,var(--surface-2) 82%,transparent),
    color-mix(in srgb,var(--surface) 94%,transparent));
  box-shadow:var(--sh-2),inset 0 1px 0 rgba(255,255,255,.045)}
.paper+.paper{margin-top:16px}

.lessongrid{display:grid;gap:22px;align-items:start}
.lessonwrap{max-width:100%;min-width:0}
@media (min-width:1180px){
  .lessongrid{grid-template-columns:minmax(0,1fr) 244px;gap:30px}
  .lessonwrap{max-width:none}
}
@media (max-width:1179.98px){ .lessongrid{grid-template-columns:minmax(0,1fr)} }
@media (min-width:760px) and (max-width:1179.98px){ .lessonwrap{max-width:760px;margin:0 auto} }

/* In This Lesson --------------------------------------------------------- */
.toc{display:none;position:sticky;top:calc(var(--top-h) + 22px);
  max-height:calc(100dvh - var(--top-h) - 46px);overflow-y:auto;padding:2px}
@media (min-width:1180px){ .toc{display:block} }
.toc .tochead{font-family:var(--f-dis);font-size:11px;font-weight:800;letter-spacing:.14em;
  text-transform:uppercase;color:var(--ink-3);display:flex;align-items:center;gap:8px;
  padding:0 12px 12px}
.toc .tochead .i{width:14px;height:14px;color:var(--brand)}
.toc ol{list-style:none;padding:0;margin:0;position:relative}
.toc ol::before{content:"";position:absolute;left:15px;top:6px;bottom:6px;width:2px;
  border-radius:2px;background:var(--line)}
.toc li{position:relative}
.toc a{display:flex;gap:11px;align-items:flex-start;padding:8px 12px;border-radius:12px;
  font-size:12.9px;line-height:1.42;font-weight:600;color:var(--ink-3);
  transition:color .22s,background .25s var(--ease)}
.toc a .dot{width:8px;height:8px;flex:0 0 auto;border-radius:50%;margin-top:5px;margin-left:3px;
  background:var(--surface);border:2px solid var(--line-strong);position:relative;z-index:1;
  transition:all .3s var(--spring)}
.toc a:hover{color:var(--ink);background:var(--surface-2)}
.toc a[aria-current="true"]{color:var(--ink);font-weight:750;background:var(--brand-soft)}
.toc a[aria-current="true"] .dot{background:var(--brand);border-color:var(--brand);
  transform:scale(1.32);box-shadow:0 0 0 4px var(--brand-soft)}
.toc .tocfoot{margin-top:14px;padding:12px;border-radius:14px;background:var(--surface-2);
  border:1px solid var(--line)}
.toc .tocfoot .bar{margin-top:9px}
.toc .tocfoot b{font-family:var(--f-dis);font-size:12px;letter-spacing:-.01em}
.toc .tocfoot span{font-size:11.5px;color:var(--ink-3)}

.lessonbar{position:sticky;top:var(--top-h);z-index:40;margin:-8px -4px 14px;padding:9px 4px;
  background:linear-gradient(var(--bg) 72%,transparent);display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.lessonbar .lbtitle{font-family:var(--f-dis);font-weight:750;font-size:13.5px;letter-spacing:-.01em;
  color:var(--ink-2);display:flex;align-items:center;gap:8px;min-width:0}
/* previous / next, mirroring the pair at the foot of the lesson. The ends of the
   course keep the slot occupied rather than letting the row shift sideways.
   These live in the lesson's title heading (.lname), at its right edge — the
   title is the one thing on the page that is unambiguously about this lesson,
   so the way to the next one belongs beside it rather than in the toolbar,
   which is about what you can do to the lesson you are already on. */
.lnav{display:flex;align-items:center;gap:6px;flex:0 0 auto}
/* Hard against the far end of the toolbar, opposite the lesson number - the
   same relationship it had with the title it travelled from. */
.lessonbar .lnav.in-bar{margin-left:auto}
.lnav .off{opacity:.26;pointer-events:none}
/* Previous stays the plain square icon button it always was. Next carries its
   own label — the one lesson-to-lesson action worth naming outright — so it
   gets the compact pill treatment instead, sized to match the icon button's
   height at every width. */
.lnav-next{height:38px;padding:0 14px;gap:6px;white-space:nowrap}
.lnav-next .i{width:16px;height:16px}
@media (max-width:400px){
  .lnav .iconbtn{width:32px;height:32px}
  .lnav-next{height:32px;padding:0 11px;font-size:12.5px;gap:5px}
  .lnav-next .i{width:14px;height:14px}
}
/* named actions — the icon stays, the word removes the guesswork.
   The three of them are one row at every width: below the tablet breakpoint they
   take the bar's second line together and share it evenly, and the labels step
   down to short forms rather than ever wrapping or stacking. */
.lessontools{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;min-width:0}
.ltool{min-width:0}
.ltool .lt-l{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ltool .lt-s{display:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@media (max-width:759.98px){
  .lessonbar{row-gap:10px}
  /* The bar wraps to two rows here: back arrow and title on the first, the
     three named actions sharing the second between them. */
  .lbtitle{order:1}
  /* The travelling nav belongs on the first row, at its right-hand end. Without
     an order it inherited 0, the same as the back arrow, and being second in
     the DOM among the order-0 items it landed BETWEEN the arrow and "Lesson 1 /
     50" - to the left of the number it is meant to sit opposite. Sharing the
     title's order puts it after the title, which grows to fill the row and so
     carries it out to the edge. */
  .lnav.in-bar{order:1}
  .lessontools{order:2;flex:1 1 100%}
  .ltool{flex:1 1 0;justify-content:center;padding:0 10px;gap:6px}
  .ltool .lt-l{font-size:12.5px}
}
/* below this the full phrase cannot share a row with two other buttons, so the
   middle action drops to its short label instead of the row breaking apart */
@media (max-width:539.98px){
  .ltool .lt-l.long{display:none}
  .ltool .lt-s{display:inline}
}
@media (max-width:400px){
  .ltool{padding:0 8px;gap:5px}
  .ltool .lt-l,.ltool .lt-s{font-size:12px}
  .ltool .i{width:16px;height:16px}
}
@media (max-width:339.98px){
  .ltool .lt-l,.ltool .lt-s{display:none}
  .ltool{padding:0;gap:0}
}
.readprog{position:fixed;left:0;right:0;top:var(--top-h);height:3px;z-index:45;background:transparent}
.readprog i{display:block;height:100%;width:0;background:var(--grad-brand);transition:width .12s linear}
@media (min-width:1100px){.readprog{left:var(--nav-w)}}

.lhero{overflow:hidden}
.lhero::before{content:"";position:absolute;top:0;left:0;right:0;height:4px;background:var(--grad-brand)}
.lhero .lnum{font-family:var(--f-dis);font-size:11.5px;font-weight:800;letter-spacing:.15em;
  text-transform:uppercase;color:var(--brand)}
/* A course may give each lesson a name of its own — "Present Perfect
   Continuous" — where another course is happy to be known by the mistake it
   corrects. Rendered only when the course provides one, so a course without
   names looks exactly as it always did. */
/* The lesson's name is the page's title, so it sits above the first card
   rather than inside it. Only the name: the eyebrow and the unit chip stay on
   the card, where they introduce the example beneath them. */
.lname{font-family:var(--f-dis);font-weight:800;
  font-size:clamp(21px,3.4vw,27px);letter-spacing:-.026em;line-height:1.18;
  color:var(--ink);text-wrap:balance;
  /* A header, not another content card. Tinted rather than white so it does not
     read as a second surface stacked on the first, and carrying an icon and the
     lesson's place in the course so it has a reason to be a card at all — the
     name alone in a plain box looked like an empty one. */
  display:flex;align-items:center;gap:14px;
  margin:0 0 14px;padding:15px 20px;
  border-radius:var(--r-xl);
  /* The quiet inset surface rather than a branded one. The card below it is
     already doing the shouting with its gradient edge, and two saturated blocks
     stacked read as two competing headings instead of a heading and its
     content. */
  background:var(--surface-2);
  border:1px solid var(--line)}
.lname .ln-ic{flex:0 0 auto;width:38px;height:38px;border-radius:12px;
  display:grid;place-items:center;color:#fff;background:var(--grad-brand);
  box-shadow:0 8px 18px -8px rgba(90,69,255,.6)}
.lname .ln-ic .i{width:19px;height:19px}
.lname .ln-t{flex:1 1 auto;min-width:0}
@media (max-width:559.98px){
  .lname{gap:11px;padding:12px 14px;margin-bottom:12px}
  .lname .ln-ic{width:32px;height:32px;border-radius:10px}
  .lname .ln-ic .i{width:16px;height:16px}
}
/* .ln-c and .lunit lived here: a chip beside the lesson title that repeated the
   unit number. Removed when the lesson bar was simplified — the heading already
   carried the number and the toolbar carried it again, so the chip said the
   same thing a third time. Nothing has rendered either class since. */
.lhero .pair{margin-top:16px;display:flex;flex-direction:column;gap:12px}
.lhero .pair .sline{font-size:clamp(16px,2.3vw,20px);align-items:center}
.lhero .pair .sline .mk{width:27px;height:27px;border-radius:9px}
.lhero .pair .sline .mk .i{width:15px;height:15px}

/* One gap value drives the whole reading column, so the space under the lesson
   hero matches the space between every section below it. */
:root{--sec-gap:16px}
#lbody{margin-top:var(--sec-gap)}
.sec{scroll-margin-top:calc(var(--top-h) + 78px)}
.sec+.sec{margin-top:var(--sec-gap)}
.sec-h{display:flex;align-items:center;gap:12px;margin-bottom:18px;
  padding-bottom:16px;border-bottom:1px solid var(--line-soft)}
.sec-h .sn{width:34px;height:34px;border-radius:12px;display:grid;place-items:center;flex:0 0 auto;
  background:var(--brand-soft);color:var(--brand)}
.sec-h .sn .i{width:17px;height:17px}
.sec-h h2{font-size:19px;letter-spacing:-.026em}
.sec-h .num{margin-left:auto;font-family:var(--f-dis);font-size:11.5px;font-weight:800;
  color:var(--ink-4);letter-spacing:.06em}
/* Reading ink is a step firmer than UI secondary text — the same palette, more
   contrast, because this is the only place with paragraphs to get through. */
.prose{font-size:16.4px;line-height:1.78;color:var(--read-ink);max-width:68ch;
  letter-spacing:-.003em}
:root{--read-ink:#2B2E52}
:root[data-theme="dark"]{--read-ink:#C9CFF2}
.prose>*+*{margin-top:14px}
.prose b{color:var(--ink);font-weight:750}
.prose .ph{font-family:var(--f-dis);font-weight:750;font-size:15px;color:var(--ink);letter-spacing:-.014em;
  margin-top:26px;display:flex;align-items:center;gap:9px}
.prose .ph::before{content:"";width:16px;height:2.5px;border-radius:2px;background:var(--brand);flex:0 0 auto}
.prose ul{display:flex;flex-direction:column;gap:9px;margin-top:12px}
.prose ul li{display:flex;gap:11px;align-items:flex-start}
.prose ul li::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--brand);
  margin-top:.66em;flex:0 0 auto}
.prose .sline{margin-top:9px;font-size:15.6px}
.prose table{width:100%;border-collapse:separate;border-spacing:0;margin-top:14px;font-size:14px;
  border:1px solid var(--line);border-radius:var(--r-md);overflow:hidden}
.prose th{background:var(--surface-3);font-family:var(--f-dis);font-weight:750;font-size:12px;
  letter-spacing:.05em;text-transform:uppercase;color:var(--ink-2);text-align:left;padding:11px 14px}
.prose td{padding:11px 14px;border-top:1px solid var(--line-soft);color:var(--ink)}
.prose tbody tr:nth-child(even) td{background:var(--surface-2)}
.prose .tipbox{border-radius:var(--r-lg);padding:18px;background:var(--brand-soft);
  border:1px solid var(--brand-line);margin-top:14px}
.prose .tipbox .prose{color:var(--ink-2)}

/* the block currently being read aloud */
.readmark{background:var(--brand-soft);border-radius:5px;box-shadow:0 0 0 3px var(--brand-soft)}

/* The reading player that used to float here is gone: nothing covers the lesson
   while it is read. Its equaliser now lives on Pipo (see #pipoBtn .pipowave),
   which is where the sound was always coming from. */

/* ==== src/parts/p05.html, verbatim ==== */

/* ============================================================================
   HUB HERO — the one big card at the top of Quiz and Flashcards. Same shell,
   same rhythm, same brand-gradient treatment everywhere it is used, so the two
   sections read as one product rather than two differently-designed screens.
   ========================================================================== */
.mcqhero{position:relative;overflow:hidden;border-radius:var(--r-2xl);padding:0;
  background:linear-gradient(135deg,var(--a1),var(--a2) 50%,var(--a3));color:#fff;
  box-shadow:0 30px 70px -26px color-mix(in srgb,var(--a1) 70%,transparent);
  transition:transform .5s var(--ease),box-shadow .5s var(--ease)}
.mcqhero:hover{transform:translateY(-6px);box-shadow:0 40px 90px -28px color-mix(in srgb,var(--a1) 78%,transparent)}
.mcqhero canvas{position:absolute;inset:0;width:100%;height:100%;opacity:.6;pointer-events:none}
/* No eyebrow above the title: the padding comes in a step, the title starts at
   the top of its column, and the rhythm underneath tightens by the same
   proportion, so the card reads as designed at this height. */
.mcqhero .in{position:relative;z-index:2;padding:clamp(22px,3.1vw,34px);
  display:grid;gap:22px;align-items:center}
@media (min-width:820px){.mcqhero .in{grid-template-columns:minmax(0,1fr) 260px}}
.mcqhero h2{font-size:clamp(28px,5vw,44px);letter-spacing:-.036em;margin-top:0;line-height:1.03}
.mcqhero p{font-size:15px;color:rgba(255,255,255,.86);margin-top:10px;max-width:46ch;line-height:1.6}
.mcqhero .stats{display:flex;gap:9px;margin-top:18px;flex-wrap:wrap}
.mcqhero .acts{display:flex;gap:10px;margin-top:22px;flex-wrap:wrap}
.mcqhero .btn-w{background:#fff;color:var(--a1);box-shadow:0 14px 34px -10px rgba(0,0,0,.4)}
.mcqhero .btn-w:hover{transform:translateY(-2.5px)}
.mcqhero .btn-o{background:rgba(255,255,255,.14);color:#fff;border:1px solid rgba(255,255,255,.3)}
.mcqhero .btn-o:hover{background:rgba(255,255,255,.24);transform:translateY(-2.5px)}
.mcqhero .viz{display:none}
@media (min-width:820px){.mcqhero .viz{display:grid;place-items:center}}
.mcqviz{width:230px;display:flex;flex-direction:column;gap:10px}
.mcqviz .op{display:flex;align-items:center;gap:11px;padding:12px 14px;border-radius:15px;
  background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.2);font-size:13px;font-weight:650;
  animation:opFloat 3.6s var(--ease-io) infinite}
.mcqviz .op:nth-child(2){animation-delay:.4s}.mcqviz .op:nth-child(3){animation-delay:.8s}
.mcqviz .op:nth-child(4){animation-delay:1.2s}
.mcqviz .op b{width:24px;height:24px;border-radius:8px;display:grid;place-items:center;
  background:rgba(255,255,255,.2);font-family:var(--f-dis);font-size:11.5px;flex:0 0 auto}
.mcqviz .op.sel{background:#fff;color:var(--a1);border-color:#fff;box-shadow:0 10px 26px -8px rgba(0,0,0,.4)}
.mcqviz .op.sel b{background:var(--a1);color:#fff}
@keyframes opFloat{0%,100%{transform:translateX(0)}50%{transform:translateX(6px)}}
/* Flashcards' deck choices. Four equal cards: three scopes plus the lesson
   picker. auto-fill left a ragged empty cell at desktop width, so the count is
   fixed per breakpoint — 1 up, 2 up, then a full 4-across row that squares off
   against the four-stat strip above it and leaves no dead space. */
.deckgrid{display:grid;gap:13px;margin-top:16px;grid-template-columns:1fr}
@media (min-width:600px){.deckgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (min-width:1100px){.deckgrid{grid-template-columns:repeat(4,minmax(0,1fr))}}
/* a four-stat strip balances two-up then four-up; the six-column default
   belongs to Profile, which actually has six */
.statgrid.s4{grid-template-columns:repeat(2,1fr)}
@media (min-width:760px){.statgrid.s4{grid-template-columns:repeat(4,1fr)}}

/* Which course am I practising — answerable from every practice screen.

   Three things and no more: the course's own icon, its name, and the word that
   changes it. Everything else this row has carried at one time or another — an
   uppercase label, a progress figure, a filled pill, a dropdown — was another
   element competing for the same 40px, and a thin strip holding five things
   reads as clutter however each one is styled. */
.cswitch{display:flex;align-items:center;gap:10px;padding:8px 15px;
  border-radius:var(--r-full);
  background:var(--surface);border:1px solid var(--line);box-shadow:var(--sh-1);margin:14px 0}
:root[data-theme="dark"] .cswitch{background:var(--surface-2)}
.cswitch .cs-ic{width:22px;height:22px;flex:0 0 auto;display:grid;place-items:center;
  color:var(--brand)}
.cswitch .cs-ic .i{width:18px;height:18px}
.cswitch .cs-name{flex:1 1 auto;min-width:0;font-family:var(--f-dis);font-weight:600;
  font-size:14px;letter-spacing:-.014em;color:var(--ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* A text link, not a button — no fill, no border, no height of its own. Its
   character comes from motion rather than from a shape: the underline warms and
   the chevron springs right, which is the same gesture every .mini row in the
   app makes when you hover it. Borrowed rather than invented, so it feels like
   the rest of the product without adding another filled thing to the row. */
.cswitch .cs-change{flex:0 0 auto;display:inline-flex;align-items:center;gap:5px;
  font-family:var(--f-dis);font-weight:700;font-size:13.5px;letter-spacing:-.012em;
  color:var(--brand);transition:color .2s var(--ease)}
.cswitch .cs-change:hover{color:var(--brand-2)}
.cswitch .cs-change .i{width:15px;height:15px;transition:transform .35s var(--spring)}
.cswitch .cs-change:hover .i{transform:translateX(3px)}
.cswitch .cs-change:active .i{transform:translateX(5px)}
/* "Change course" says what it changes; on a phone the row has no room for the
   second word and "Change" beside a course name is unambiguous anyway */
@media (max-width:559.98px){ .cswitch .cs-change .cs-more{display:none} }

.fmtgrid{display:grid;gap:13px;margin-top:16px;grid-template-columns:repeat(auto-fill,minmax(210px,1fr))}
.fmtcard{position:relative;overflow:hidden;padding:18px;border-radius:var(--r-xl);text-align:left;
  background:var(--surface);border:1px solid var(--line);box-shadow:var(--sh-1);
  transition:transform .42s var(--spring),box-shadow .4s,border-color .3s}
:root[data-theme="dark"] .fmtcard{background:linear-gradient(158deg,rgba(28,34,74,.7),rgba(13,17,44,.86))}
.fmtcard:hover{transform:translateY(-5px);box-shadow:var(--sh-3);border-color:var(--brand-line)}
.fmtcard .fic{width:42px;height:42px;border-radius:14px;display:grid;place-items:center;color:#fff;
  box-shadow:var(--sh-2)}
.fmtcard .fic .i{width:21px;height:21px}
.fmtcard b{display:block;font-size:15px;font-family:var(--f-dis);letter-spacing:-.02em;margin-top:13px}
.fmtcard span{display:block;font-size:12.6px;color:var(--ink-3);margin-top:5px;line-height:1.48}
.fmtcard .tag{display:inline-flex;width:auto;font-size:11.5px;margin-top:12px}
.fmtcard .tag .i{width:13px;height:13px}
.fmtcard .go{position:absolute;right:16px;top:18px;color:var(--ink-4);transition:transform .35s var(--spring),color .2s}
.fmtcard:hover .go{color:var(--brand);transform:translate(3px,-3px)}

/* Choosing a lesson: numbers, nothing else. The grid auto-fills so it is five
   or six across on a phone and ten-plus on a desktop without a breakpoint, and
   it scrolls inside the dialog rather than pushing the footer off screen. */
.lespick{display:grid;grid-template-columns:repeat(auto-fill,minmax(46px,1fr));gap:8px;
  max-height:min(46vh,300px);overflow-y:auto;padding:3px;margin-inline:-3px}
.lp{position:relative;aspect-ratio:1;border-radius:13px;display:grid;place-items:center;
  background:var(--surface-2);border:1.5px solid var(--line);color:var(--ink-2);
  font-family:var(--f-dis);font-weight:750;font-size:15px;letter-spacing:-.02em;
  transition:transform .28s var(--spring),border-color .2s,background .2s,color .2s,box-shadow .3s}
.lp:hover{transform:translateY(-2px);border-color:var(--brand-line);color:var(--brand);
  background:var(--surface);box-shadow:var(--sh-1)}
.lp:active{transform:scale(.94)}
.lp.done{background:var(--ok-soft);border-color:var(--ok-line);color:var(--ok)}
.lp.next{background:var(--grad-brand);border-color:transparent;color:#fff;box-shadow:var(--sh-brand)}
.lp.next:hover{color:#fff}
.lp .lp-t{position:absolute;top:-4px;right:-4px;width:16px;height:16px;border-radius:50%;
  display:grid;place-items:center;background:var(--ok);color:var(--on-ok);border:2px solid var(--surface)}
.lp .lp-t .i{width:9px;height:9px;stroke-width:3.4}
.lpkey{display:flex;gap:16px;justify-content:center;margin-top:12px;font-size:11.5px;
  font-weight:650;color:var(--ink-3)}
.lpkey span{display:inline-flex;align-items:center;gap:6px}
.lpkey i{width:11px;height:11px;border-radius:4px;display:block}
.lpkey .k-next{background:var(--grad-brand)}
.lpkey .k-done{background:var(--ok-soft);border:1.5px solid var(--ok-line)}
@media (max-width:379.98px){ .lespick{grid-template-columns:repeat(auto-fill,minmax(42px,1fr));gap:7px} }

/* the same format vocabulary in a row, for choosing one inside a dialog */
.fmtpick{display:flex;align-items:center;gap:13px;width:100%;padding:12px;border-radius:16px;text-align:left;
  background:var(--surface-2);border:1px solid var(--line);color:var(--ink);
  transition:transform .3s var(--spring),border-color .22s,background .22s,box-shadow .3s}
.fmtpick:hover{transform:translateX(3px);border-color:var(--brand-line);background:var(--surface);box-shadow:var(--sh-1)}
.fmtpick:active{transform:scale(.985)}
.fmtpick .fic{width:40px;height:40px;flex:0 0 auto;border-radius:14px;display:grid;place-items:center;
  color:#fff;box-shadow:var(--sh-2)}
.fmtpick .fic .i{width:20px;height:20px}
.fmtpick .ft{flex:1 1 auto;min-width:0}
.fmtpick .ft b{display:block;font-family:var(--f-dis);font-weight:750;font-size:14.5px;letter-spacing:-.018em}
.fmtpick .ft span{display:block;font-size:12.4px;color:var(--ink-3);line-height:1.45;margin-top:2px}
.fmtpick>.i{color:var(--ink-4);flex:0 0 auto}
.fmtpick .tag{flex:0 0 auto}
.fmtpick .tag .i{width:12px;height:12px}
.fmtpick.lead{background:var(--brand-soft);border-color:var(--brand-line);padding:14px}
.fmtpick.lead .ft b{font-size:16px}
.fmtpicks{display:flex;flex-direction:column;gap:8px}
@media (max-width:479.98px){
  .fmtpick .ft span{display:none}
  .fmtpick.lead .ft span{display:block}
  .fmtpick .tag{display:none}
}

/* runner */
.qrun{max-width:720px;margin:0 auto}
.qtop{display:flex;align-items:center;gap:12px;margin-bottom:18px}
.qtop .qcount{font-family:var(--f-dis);font-weight:800;font-size:13px;color:var(--ink-3);
  letter-spacing:.02em;white-space:nowrap;font-variant-numeric:tabular-nums}
.qcard{border-radius:var(--r-2xl);padding:clamp(20px,3vw,30px);background:var(--surface);
  border:1px solid var(--line);box-shadow:var(--sh-2);animation:qIn .5s var(--ease) both}
:root[data-theme="dark"] .qcard{background:linear-gradient(158deg,rgba(28,34,74,.78),rgba(13,17,44,.9))}
@keyframes qIn{from{opacity:0;transform:translateY(16px) scale(.985)}to{opacity:1;transform:none}}
.qcard.leave{animation:qOut .3s var(--ease) forwards}
@keyframes qOut{to{opacity:0;transform:translateY(-14px) scale(.985)}}
.qprompt{font-size:clamp(17px,2.6vw,22px);font-family:var(--f-dis);font-weight:700;letter-spacing:-.024em;
  line-height:1.35;color:var(--ink)}
.qsrc{font-size:11.5px;font-weight:750;font-family:var(--f-dis);letter-spacing:.09em;text-transform:uppercase;
  color:var(--brand);margin-bottom:10px}
.opts{display:flex;flex-direction:column;gap:10px;margin-top:20px}
.opt{display:flex;align-items:center;gap:13px;padding:14px 15px;border-radius:16px;text-align:left;
  background:var(--surface-2);border:1.5px solid var(--line);font-size:15px;line-height:1.45;
  transition:transform .3s var(--spring),border-color .22s,background .22s,box-shadow .3s;width:100%}
.opt .k{width:29px;height:29px;flex:0 0 auto;border-radius:9px;display:grid;place-items:center;
  background:var(--surface);border:1px solid var(--line);font-family:var(--f-dis);font-weight:800;
  font-size:12.5px;color:var(--ink-3);transition:all .22s;text-transform:uppercase}
.opt .ot{flex:1 1 auto;min-width:0}
.opt .rs{width:22px;height:22px;flex:0 0 auto;opacity:0;transition:opacity .25s}
.opt:not(.dis):hover{border-color:var(--brand-line);background:var(--surface);transform:translateX(4px)}
.opt:not(.dis):hover .k{border-color:var(--brand-line);color:var(--brand)}
.opt.sel{border-color:var(--brand);background:var(--brand-soft)}
.opt.sel .k{background:var(--grad-brand);color:#fff;border-color:transparent}
.opt.ok{border-color:var(--ok);background:var(--ok-soft)}
.opt.ok .k{background:var(--ok);color:#fff;border-color:transparent}
.opt.ok .rs{opacity:1;color:var(--ok)}
.opt.no{border-color:var(--no);background:var(--no-soft);animation:shakeX .5s var(--ease)}
.opt.no .k{background:var(--no);color:#fff;border-color:transparent}
.opt.no .rs{opacity:1;color:var(--no)}
.opt.dis{cursor:default}
.opt.fade{opacity:.5}

.fb{margin-top:18px;border-radius:var(--r-lg);padding:15px;display:flex;gap:12px;align-items:flex-start;
  animation:fadeUp .45s var(--ease) both}
.fb.ok{background:var(--ok-soft);border:1px solid var(--ok-line)}
.fb.no{background:var(--no-soft);border:1px solid var(--no-line)}
.fb .fi{width:32px;height:32px;flex:0 0 auto;border-radius:11px;display:grid;place-items:center;color:#fff}
.fb.ok .fi{background:var(--ok)}
.fb.no .fi{background:var(--no)}
.fb b{font-size:13.8px;font-family:var(--f-dis);letter-spacing:-.014em}
.fb p{font-size:13.6px;color:var(--ink-2);margin-top:4px;line-height:1.58}

/* auto-advance ring on every "next" button ------------------------------- */
.qnext{padding-left:14px}
/* PIPO OWNS THE BOTTOM-RIGHT CORNER OF A PHONE, and this button is the one
   thing on the screen you have to press to carry on - right-aligned, into that
   exact corner, so at some scroll positions his head was over it. #main already
   reserves the height to scroll it clear; this reserves the width so it never
   has to be scrolled at all. 72px is his 60px button plus the 14px he stands
   off the edge. The button keeps its full width: the row has the room. */
@media (max-width:759.98px){ .qnext{margin-right:72px} }
.qtimer{position:relative;width:24px;height:24px;flex:0 0 auto;display:grid;place-items:center;
  border-radius:50%;cursor:pointer;color:#fff;
  transition:transform .3s var(--spring),background .25s}
.qtimer:hover{background:rgba(255,255,255,.18);transform:scale(1.1)}
.qtimer:active{transform:scale(.92)}
.qtimer:focus-visible{outline:2px solid #fff;outline-offset:2px}
.qtimer svg{width:24px;height:24px;transform:rotate(-90deg);overflow:visible}
.qtimer circle{fill:none;stroke-width:3}
.qtimer .trk{stroke:rgba(255,255,255,.32)}
.qtimer .run{stroke:#fff;stroke-linecap:round;stroke-dasharray:94.25;stroke-dashoffset:0;
  animation:qcount 5s linear forwards}
@keyframes qcount{from{stroke-dashoffset:0}to{stroke-dashoffset:94.25}}
/* the ring is a countdown, not decoration — it keeps its real duration */
:root[data-motion="off"] .qtimer .run{animation-duration:5s !important}
@media (prefers-reduced-motion:reduce){ .qtimer .run{animation-duration:5s !important} }
.qtimer.paused .run{animation-play-state:paused;opacity:.4}
/* pause bars while it counts, a play triangle once it is held */
.qtimer .qglyph{position:absolute;width:6px;height:8px;
  border-left:2px solid currentColor;border-right:2px solid currentColor;opacity:.95}
.qtimer.paused .qglyph{width:0;height:0;border:none;margin-left:2px;
  border-left:7px solid currentColor;border-top:4.5px solid transparent;border-bottom:4.5px solid transparent}
@media (max-width:420px){ .qnext .qnext-t{font-size:13.5px} }
/* The ring as its own button over the Next button (quiz.js, nextBtn): the space
   it sits on is kept inside the button, and the ring is laid over it. */
.qnextwrap{position:relative;display:inline-flex}
.qtimer-space{width:24px;height:24px;flex:0 0 auto}
.qnextwrap .qtimer{position:absolute;left:14px;top:50%;margin-top:-12px;
  border:0;padding:0;background:transparent;font:inherit}

/* fill in the blank */
.blankline{font-size:clamp(18px,2.8vw,24px);font-family:var(--f-dis);font-weight:600;line-height:1.75;
  letter-spacing:-.018em;color:var(--ink);display:flex;flex-wrap:wrap;align-items:baseline;gap:6px}
.blankin{display:inline-block;min-width:130px;border:0;border-bottom:2.5px solid var(--brand);
  background:var(--brand-soft);border-radius:8px 8px 0 0;padding:2px 12px;font-family:inherit;
  font-size:inherit;font-weight:700;color:var(--brand);text-align:center;transition:all .25s}
.blankin:focus{outline:none;box-shadow:0 6px 18px -6px var(--brand)}
.blankin.ok{border-color:var(--ok);color:var(--ok);background:var(--ok-soft)}
.blankin.no{border-color:var(--no);color:var(--no);background:var(--no-soft);animation:shakeX .5s}
/* Progressive hint: the shape of the word first, then letters — never a list
   of candidate answers, which would give the exercise away. */
.shapehint{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:18px;
  opacity:0;max-height:0;overflow:hidden;transition:opacity .35s var(--ease),max-height .4s var(--ease)}
.shapehint.on{opacity:1;max-height:70px}
.shapehint .shl{font-size:11px;font-weight:800;font-family:var(--f-dis);letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-3);margin-right:6px}
.shapehint span:not(.shl){width:26px;height:34px;border-radius:8px;display:grid;place-items:center;
  background:var(--surface-2);border:1.5px solid var(--line);font-family:var(--f-dis);font-weight:800;
  font-size:16px;color:var(--ink-4);transition:all .3s var(--spring)}
.shapehint span.on{background:var(--brand-soft);border-color:var(--brand-line);color:var(--brand)}
.shapehint span.pun{width:16px;background:transparent;border-color:transparent;color:var(--ink-3)}
.shapehint.done span:not(.shl){opacity:.55}

/* sentence builder */
.slots{min-height:78px;display:flex;flex-wrap:wrap;gap:8px;padding:14px;border-radius:var(--r-lg);
  background:var(--surface-2);border:2px dashed var(--line-strong);align-content:flex-start;
  transition:border-color .3s,background .3s}
.slots.filled{border-style:solid;border-color:var(--brand-line);background:var(--brand-soft)}
.slots.ok{border-color:var(--ok);background:var(--ok-soft)}
.slots.no{border-color:var(--no);background:var(--no-soft);animation:shakeX .5s}
.slots:empty::after{content:attr(data-ph);color:var(--ink-4);font-size:13.5px;padding:6px 4px}
.bank{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px;min-height:44px}
.word{max-width:100%;padding:9px 14px;border-radius:12px;background:var(--surface);border:1.5px solid var(--line);
  font-family:var(--f-dis);font-weight:650;font-size:15px;color:var(--ink);cursor:grab;
  box-shadow:var(--sh-1);transition:transform .3s var(--spring),box-shadow .3s,border-color .2s;
  user-select:none;-webkit-user-select:none;touch-action:none;
  overflow-wrap:break-word;word-break:break-word;box-sizing:border-box}
.word:hover{transform:translateY(-3px);border-color:var(--brand-line);box-shadow:var(--sh-2)}
.word:active{cursor:grabbing;transform:scale(.95)}
.word.placed{background:var(--grad-brand);color:#fff;border-color:transparent;box-shadow:var(--sh-brand)}
.word.drag{opacity:.4}
.word.ghost{opacity:0;pointer-events:none}
.word.fly{position:fixed;z-index:200;pointer-events:none;margin:0}
/* narrow phones — the same tiles, sized so a full row of them never has to
   fight the edge of the screen */
@media (max-width:479.98px){
  .slots{padding:11px;gap:6px}
  .bank{gap:6px}
  .word{padding:7px 11px;font-size:13.5px;border-radius:10px}
}

/* true / false */
.tfstage{text-align:center;padding:14px 0}
.tfsent{font-family:var(--f-ser);font-size:clamp(24px,4.6vw,38px);line-height:1.24;letter-spacing:-.02em;
  color:var(--ink);text-wrap:balance;margin:8px auto 0;max-width:20ch}
.tfbtns{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:26px}
.tfbtn{padding:20px 14px;border-radius:var(--r-xl);border:2px solid var(--line);background:var(--surface-2);
  display:flex;flex-direction:column;align-items:center;gap:9px;
  transition:transform .35s var(--spring),border-color .22s,background .22s,box-shadow .3s}
.tfbtn .i{width:28px;height:28px}
.tfbtn b{font-family:var(--f-dis);font-weight:750;font-size:15px;letter-spacing:-.018em}
.tfbtn.t{color:var(--ok)}.tfbtn.f{color:var(--no)}
.tfbtn:not(.dis):hover{transform:translateY(-5px);box-shadow:var(--sh-3);background:var(--surface)}
.tfbtn.t:not(.dis):hover{border-color:var(--ok-line)}
.tfbtn.f:not(.dis):hover{border-color:var(--no-line)}
.tfbtn.picked.t{border-color:var(--ok);background:var(--ok-soft)}
.tfbtn.picked.f{border-color:var(--no);background:var(--no-soft)}
.tfbtn.wrongpick{animation:shakeX .5s}

/* match pairs */
.matchgrid{display:grid;grid-template-columns:1fr;gap:20px;margin-top:6px}
@media (min-width:680px){.matchgrid{grid-template-columns:1fr 1fr}}
.mcol{display:flex;flex-direction:column;gap:9px}
.mcol .clbl{font-size:11px;font-weight:800;font-family:var(--f-dis);letter-spacing:.11em;
  text-transform:uppercase;color:var(--ink-3);margin-bottom:2px}
.mitem{padding:13px 14px;border-radius:14px;text-align:left;background:var(--surface-2);
  border:1.5px solid var(--line);font-size:14px;line-height:1.42;
  transition:transform .3s var(--spring),border-color .22s,background .22s,opacity .3s,box-shadow .3s}
.mitem:not(.dis):hover{transform:translateX(4px);border-color:var(--brand-line);box-shadow:var(--sh-1)}
.mitem.sel{border-color:var(--brand);background:var(--brand-soft);transform:scale(1.015)}
.mitem.done{border-color:var(--ok-line);background:var(--ok-soft);opacity:.62;pointer-events:none}
.mitem.bad{border-color:var(--no);background:var(--no-soft);animation:shakeX .45s}
.mitem .mn{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;
  border-radius:6px;background:var(--surface);border:1px solid var(--line);font-size:10.5px;font-weight:800;
  font-family:var(--f-dis);color:var(--ink-3);margin-right:8px;vertical-align:1px}
.mitem.done .mn{background:var(--ok);color:#fff;border-color:transparent}

/* odd one out */
.oddgrid{display:grid;gap:11px;margin-top:18px}
@media (min-width:640px){.oddgrid{grid-template-columns:1fr 1fr}}

/* results */
.resultwrap{max-width:680px;margin:0 auto;text-align:center}
/* The results actions. Above the phone breakpoint this is the centred wrapping
   row it has always been, untouched. Below it, a two-up grid: the primary spans
   the full width and the rest pair off, so four buttons never leave one stranded
   on its own line, and .btn's nowrap label gets room by trimming the padding
   rather than by letting the text clip. */
/* One decision first, four options second. The primary is wide and alone so it
   reads as the way forward; the rest sit under it as a quieter row of equals. */
.qacts{display:flex;flex-direction:column;align-items:center;gap:14px}
.qacts .qact-go{width:100%;max-width:420px}
/* the supporting four are deliberately smaller than the primary — it reinforces
   the hierarchy and it is what lets all four share one row on a desktop */
.qact-rest{display:flex;flex-wrap:wrap;gap:9px;justify-content:center;width:100%}
.qact-rest>.btn{height:42px;padding:0 15px;font-size:13.6px;gap:8px}
.qact-rest>.btn .i{width:16px;height:16px}
@media (max-width:539.98px){
  .qacts{gap:11px}
  .qacts .qact-go{max-width:none;height:50px}
  .qact-rest{display:grid;grid-template-columns:1fr 1fr;gap:9px;align-items:stretch}
  .qact-rest>.btn{width:100%;padding:0 10px;font-size:13.5px;gap:7px;height:44px;min-width:0}
  .qact-rest>.btn .i{width:16px;height:16px}
  /* an odd count would leave a half-width button stranded beside an empty cell */
  .qact-rest>.btn:last-child:nth-child(odd){grid-column:1/-1}
}
@media (max-width:399.98px){
  .qact-rest{grid-template-columns:1fr}
  .qact-rest>.btn{grid-column:1/-1}
}
/* a status line on the results page, not a nag: quiet surface, no accent fill,
   and it stacks rather than squeezes when the row runs out of room */
.lessonnote{display:flex;align-items:center;gap:13px;text-align:left;padding:13px 14px;
  border-radius:16px;background:var(--surface-2);border:1px solid var(--line);
  animation:fadeUp .5s var(--ease) both}
.lessonnote .ni{width:36px;height:36px;flex:0 0 auto;border-radius:12px;display:grid;place-items:center;
  background:var(--brand-soft);color:var(--brand)}
.lessonnote .ni .i{width:18px;height:18px}
.lessonnote b{display:block;font-family:var(--f-dis);font-weight:700;font-size:13.6px;letter-spacing:-.014em}
.lessonnote span.grow,.lessonnote .grow>span{display:block;font-size:12.4px;color:var(--ink-3);
  line-height:1.45;margin-top:2px}
.lessonnote .btn{flex:0 0 auto}
@media (max-width:539.98px){
  .lessonnote{flex-wrap:wrap}
  .lessonnote .grow{min-width:0;flex:1 1 60%}
  .lessonnote .btn{width:100%;justify-content:center}
}
.rvlist{display:flex;flex-direction:column;gap:9px;margin-top:18px;text-align:left}
.rv{display:flex;gap:12px;align-items:flex-start;padding:14px;border-radius:16px;background:var(--surface-2);
  border:1px solid var(--line)}
.rv .rvi{width:26px;height:26px;flex:0 0 auto;border-radius:8px;display:grid;place-items:center;color:#fff}
.rv.ok .rvi{background:var(--ok)}.rv.no .rvi{background:var(--no)}
.rv .rvi .i{width:14px;height:14px}
.rv b{font-size:13.8px;display:block;line-height:1.45}
.rv p{font-size:12.8px;color:var(--ink-3);margin-top:4px;line-height:1.5}
.rv .ans{font-size:12.8px;color:var(--ok);font-weight:700;margin-top:3px}

/* ==== src/parts/p06.html, verbatim ==== */

/* ============================================================================
   FLASHCARDS — the reveal is its own deliberate step
   ========================================================================== */
.fcstage{max-width:600px;margin:0 auto;perspective:1600px;-webkit-perspective:1600px}
.fcdots{display:flex;gap:5px;justify-content:center;margin-bottom:18px;flex-wrap:wrap}
.fcdots i{width:22px;height:4px;border-radius:99px;background:var(--ring-track);transition:all .4s var(--ease)}
.fcdots i.on{background:var(--grad-brand);width:30px}
.fcdots i.past{background:var(--ok)}
/* The wrapper handles entering and leaving; the card itself only ever rotates.
   Keeping the two transforms on separate elements is what stops the flip from
   fighting the slide and flashing both faces mid-animation. */
.fcslide{position:relative;will-change:transform,opacity}
.fcslide.in-right{animation:slideInR .42s var(--ease) both}
.fcslide.in-left{animation:slideInL .42s var(--ease) both}
.fcslide.out-left{animation:slideOutL .26s var(--ease-io) both}
@keyframes slideInR{from{opacity:0;transform:translateX(38px) scale(.965)}to{opacity:1;transform:none}}
@keyframes slideInL{from{opacity:0;transform:translateX(-38px) scale(.965)}to{opacity:1;transform:none}}
@keyframes slideOutL{from{opacity:1;transform:none}to{opacity:0;transform:translateX(-34px) scale(.96)}}

.flip{position:relative;width:100%;aspect-ratio:1/1.06;max-height:66vh;
  transform-style:preserve-3d;-webkit-transform-style:preserve-3d;
  transition:transform .8s cubic-bezier(.34,.02,.2,1);cursor:pointer;will-change:transform}
@media (min-width:640px){.flip{aspect-ratio:1.32/1}}
.flip.on{transform:rotateY(180deg)}
.flip:focus-visible{outline:none}
.flip:focus-visible .face.front{outline:2.5px solid var(--focus);outline-offset:3px}
.face{position:absolute;inset:0;backface-visibility:hidden;-webkit-backface-visibility:hidden;
  border-radius:var(--r-2xl);padding:clamp(22px,4vw,34px);display:flex;flex-direction:column;
  box-shadow:var(--sh-3)}
/* No overflow:hidden here, deliberately. Clipping a backface-hidden element
   inside a preserve-3d parent makes WebKit rasterise it into its own surface
   and then compose that surface without re-applying backface-visibility — the
   hidden face comes back as a mirrored ghost under the one you are meant to be
   reading. It reproduces on every iPhone browser (they are all WebKit) and on
   none of the Blink ones, which is exactly the split reported. The rounded
   corners never needed it: a background is clipped by border-radius on its own,
   the sheen inherits the radius, and the only child that could ever outgrow the
   card clips itself instead. */
.face .fbody{overflow:hidden}
/* backface-visibility:hidden reliably stops the hidden face from being PAINTED,
   but hit-testing a 3D-rotated element is a separate code path in every renderer
   and is not consistently excluded the same way — on some engines the hidden
   front face keeps intercepting taps after the flip, which is why "Hear it"
   could sit directly under a real finger and still never fire. Segregating
   pointer-events explicitly removes the ambiguity: whichever face is not the
   one currently facing the viewer is inert, full stop, regardless of how any
   given browser handles backface hit-testing. */
.face.front{pointer-events:auto}
.face.back{pointer-events:none}
.flip.on .face.front{pointer-events:none}
.flip.on .face.back{pointer-events:auto}
/* And the same reasoning applied to painting. Rather than trust that every
   engine honours backface-visibility, the face that is turned away is also
   simply not drawn. The swap is a hard cut, timed to the instant the card
   passes edge-on: the flip runs .8s on cubic-bezier(.34,.02,.2,1), which is
   front-loaded, so 90deg falls at .2594s rather than halfway. At that moment
   the card is exactly perpendicular and one pixel wide, so nothing about the
   cut is visible — and with the turned-away face genuinely absent there is no
   ghost left for any renderer to mirror. Where backface-visibility already
   works this changes nothing; where it does not, it is the whole fix. */
.face{opacity:1;transition:opacity 0s linear .2594s}
.face.back{opacity:0}
.flip.on .face.front{opacity:0}
.flip.on .face.back{opacity:1}
/* Reduced motion collapses transition-duration but says nothing about delay, so
   the rotation would finish instantly while the faces waited a quarter second
   to swap — the exact ghost this is here to prevent. With no animation to stay
   in step with, the cut belongs at zero. */
:root[data-motion="off"] .face{transition-delay:0s}
@media (prefers-reduced-motion:reduce){.face{transition-delay:0s}}
.face.front{background:var(--surface);border:1px solid var(--line)}
:root[data-theme="dark"] .face.front{background:linear-gradient(158deg,#1B2149,#0D1130)}
.face.back{transform:rotateY(180deg);background:linear-gradient(140deg,var(--a1),var(--a2) 55%,var(--a3));
  color:#fff;border:1px solid transparent}
.face.back::after{content:"";position:absolute;inset:0;border-radius:inherit;
  background:radial-gradient(420px circle at 78% 12%,rgba(255,255,255,.24),transparent 60%);pointer-events:none}
.face .fk{font-size:11px;font-weight:800;font-family:var(--f-dis);letter-spacing:.14em;text-transform:uppercase;
  display:flex;align-items:center;gap:7px}
.face.front .fk{color:var(--brand)}
.face.back .fk{color:rgba(255,255,255,.8)}
.face .fk .i{width:14px;height:14px}
.face .fbody{flex:1 1 auto;display:flex;flex-direction:column;justify-content:center;
  gap:14px;position:relative;z-index:1;min-height:0;overflow-y:auto;padding:8px 0}
.face .fq{font-family:var(--f-ser);font-size:clamp(23px,4.4vw,34px);line-height:1.24;letter-spacing:-.02em;
  text-wrap:balance}
.face.front .fq{color:var(--ink)}
.face .fa{font-family:var(--f-ser);font-size:clamp(24px,4.6vw,36px);line-height:1.22;letter-spacing:-.02em}
.face .fnote{font-size:13.6px;line-height:1.6;color:rgba(255,255,255,.86);max-width:52ch}
.face .ffoot{position:relative;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:12px}
.face .hintflip{display:inline-flex;align-items:center;gap:8px;font-size:12.5px;font-weight:700;
  font-family:var(--f-dis);color:var(--ink-3);animation:floatY 2.6s var(--ease-io) infinite}
.face .hintflip .i{width:15px;height:15px}
.fcactions{display:flex;gap:10px;margin-top:18px;justify-content:center;flex-wrap:wrap}
.fcactions .btn{flex:1 1 150px;max-width:220px}
.lockmsg{text-align:center;font-size:12.6px;color:var(--ink-4);margin-top:12px;display:flex;
  align-items:center;justify-content:center;gap:7px}
.lockmsg .i{width:14px;height:14px}

/* ============================================================================
   PROFILE
   ========================================================================== */
.profhero{position:relative;overflow:hidden;border-radius:var(--r-2xl);padding:clamp(22px,3.4vw,34px);
  background:linear-gradient(140deg,var(--h1),var(--h2) 55%,var(--h3));color:#fff;
  box-shadow:0 26px 60px -24px rgba(20,14,70,.55)}
.profhero::after{content:"";position:absolute;inset:0;
  background:radial-gradient(520px circle at var(--mx,80%) var(--my,10%),rgba(255,255,255,.22),transparent 62%)}
.profhero>*{position:relative;z-index:1}
.profhero .ptop{display:flex;align-items:center;gap:18px;flex-wrap:wrap}
.profhero .pname{font-family:var(--f-ser);font-size:clamp(28px,5.4vw,44px);line-height:1.04;letter-spacing:-.028em}
.profhero .ppersona{display:inline-flex;align-items:center;gap:7px;margin-top:9px;height:29px;padding:0 13px;
  border-radius:99px;background:rgba(255,255,255,.16);border:1px solid rgba(255,255,255,.26);
  font-size:12px;font-weight:750;font-family:var(--f-dis)}
.profhero .ppersona .i{width:14px;height:14px}
.statgrid{display:grid;grid-template-columns:repeat(2,1fr);gap:11px;margin-top:16px}
@media (min-width:560px){.statgrid{grid-template-columns:repeat(3,1fr)}}
@media (min-width:1100px){.statgrid{grid-template-columns:repeat(6,1fr)}}
.stat{padding:15px;border-radius:var(--r-lg);background:var(--surface);border:1px solid var(--line);
  transition:transform .38s var(--spring),box-shadow .3s,border-color .25s}
:root[data-theme="dark"] .stat{background:linear-gradient(158deg,rgba(28,34,74,.66),rgba(13,17,44,.84))}
.stat:hover{transform:translateY(-4px);box-shadow:var(--sh-2);border-color:var(--brand-line)}
.stat .si{width:32px;height:32px;border-radius:11px;display:grid;place-items:center;
  background:var(--brand-soft);color:var(--brand)}
.stat .si .i{width:16px;height:16px}
.stat .sv{font-family:var(--f-dis);font-weight:800;font-size:clamp(21px,3vw,27px);letter-spacing:-.038em;
  margin-top:11px;line-height:1;font-variant-numeric:tabular-nums}
.stat .sl{font-size:11.5px;font-weight:700;color:var(--ink-3);margin-top:5px;letter-spacing:.03em}
.badgegrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(88px,1fr));gap:6px}
.posterrow{display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
.posterthumb{position:relative;aspect-ratio:3/4;border-radius:var(--r-lg);overflow:hidden;
  border:1px solid var(--line);box-shadow:var(--sh-1);cursor:pointer;
  transition:transform .42s var(--spring),box-shadow .4s}
.posterthumb:hover{transform:translateY(-5px) rotate(-1.2deg);box-shadow:var(--sh-3)}
.posterthumb canvas{width:100%;height:100%;object-fit:cover}
.posterthumb .pl{position:absolute;left:0;right:0;bottom:0;padding:22px 11px 10px;color:#fff;
  background:linear-gradient(transparent,rgba(6,4,24,.82));font-size:12px;font-weight:750;
  font-family:var(--f-dis);letter-spacing:-.01em}
.posterthumb .lockw{position:absolute;inset:0;display:grid;place-items:center;background:rgba(8,6,26,.62);
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);color:#fff;text-align:center;padding:12px}
.posterthumb .lockw span{font-size:11.5px;font-weight:700;line-height:1.4;margin-top:7px;display:block;
  opacity:.9}

.posterview{display:flex;flex-direction:column;align-items:center;gap:16px}
.posterview .frame{width:min(320px,72vw);aspect-ratio:3/4;border-radius:var(--r-lg);overflow:hidden;
  box-shadow:var(--sh-4);animation:popIn .6s var(--spring) both}
.posterview .frame canvas{width:100%;height:100%}

/* ---------------------------------------------------------- certificate --*/
.certwrap{display:grid;gap:20px;margin-top:8px}
@media (min-width:1000px){.certwrap{grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);align-items:start}}
.certframe{border-radius:var(--r-xl);overflow:hidden;box-shadow:var(--sh-4);
  border:1px solid var(--line);background:var(--surface);animation:popIn .6s var(--spring) both}
.certframe canvas{width:100%;height:auto;display:block}
.certside{display:flex;flex-direction:column;gap:16px;min-width:0}

/* ============================================================================
   CELEBRATION
   ========================================================================== */
#celebrate{position:fixed;inset:0;z-index:450;display:grid;place-items:center;padding:22px;
  background:radial-gradient(circle at 50% 40%,
    color-mix(in srgb,var(--h2) 78%,#000 22%), color-mix(in srgb,var(--h1) 26%,#05040F 74%) 72%);
  overflow:hidden}
#celebrate canvas{position:absolute;inset:0;z-index:0}
#celebrate .cin{position:relative;z-index:2;text-align:center;color:#fff;max-width:560px;width:100%}
#celebrate .cring{width:132px;height:132px;margin:0 auto}
#celebrate .cring circle.v{stroke-dasharray:396;stroke-dashoffset:396;
  animation:draw2 1.5s var(--ease) .2s forwards}
@keyframes draw2{to{stroke-dashoffset:0}}
#celebrate .cring .cmid{position:absolute;inset:0;display:grid;place-items:center}
#celebrate .cring .cmid .i{width:52px;height:52px;color:#fff;opacity:0;
  animation:popIn .7s var(--spring) 1.1s forwards}
#celebrate .ck{font-family:var(--f-dis);font-size:11.5px;font-weight:800;letter-spacing:.28em;
  text-transform:uppercase;color:rgba(255,255,255,.66);margin-top:26px;
  animation:fadeUp .8s var(--ease) 1.3s both}
#celebrate h2{font-family:var(--f-ser);font-size:clamp(34px,7.6vw,60px);line-height:1.04;letter-spacing:-.03em;
  margin-top:10px;animation:fadeUp .9s var(--ease) 1.45s both;text-wrap:balance}
#celebrate h2 em{font-style:italic;background:linear-gradient(100deg,#FFD9F2,#BFE9FF);
  -webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent}
#celebrate .cs{font-size:15.5px;color:rgba(255,255,255,.78);margin-top:14px;line-height:1.6;
  animation:fadeUp .9s var(--ease) 1.6s both;max-width:42ch;margin-inline:auto}
#celebrate .cstats{display:flex;gap:11px;justify-content:center;margin-top:26px;flex-wrap:wrap;
  animation:fadeUp .9s var(--ease) 1.75s both}
#celebrate .cstat{padding:13px 18px;border-radius:17px;background:rgba(255,255,255,.11);
  border:1px solid rgba(255,255,255,.2);min-width:96px}
#celebrate .cstat b{display:block;font-family:var(--f-dis);font-weight:800;font-size:24px;letter-spacing:-.035em}
#celebrate .cstat span{font-size:10.5px;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:rgba(255,255,255,.66)}
#celebrate .cacts{display:flex;gap:11px;justify-content:center;margin-top:30px;flex-wrap:wrap;
  animation:fadeUp .9s var(--ease) 1.9s both}
#celebrate .btn-w{background:#fff;color:#2A1470}
#celebrate .btn-o{background:rgba(255,255,255,.12);color:#fff;border:1px solid rgba(255,255,255,.28)}
#celebrate.out{animation:fadeIn .4s var(--ease) reverse both}

/* xp burst */
.xpfly{position:fixed;z-index:420;pointer-events:none;font-family:var(--f-dis);font-weight:800;
  font-size:19px;color:var(--brand);text-shadow:0 2px 12px var(--brand-soft);
  animation:xpFly 1.25s var(--ease) forwards}
@keyframes xpFly{0%{opacity:0;transform:translateY(0) scale(.6)}
  18%{opacity:1;transform:translateY(-10px) scale(1.16)}
  100%{opacity:0;transform:translateY(-84px) scale(.94)}}

/* ============================================================================
   PIPO — the companion
   ========================================================================== */
#pipo{position:fixed;right:18px;bottom:18px;z-index:120;
  bottom:calc(18px + env(safe-area-inset-bottom))}
@media (max-width:759.98px){
  #pipo{right:14px;bottom:calc(var(--dock-h,calc(var(--tab-h) + env(safe-area-inset-bottom))) + 12px)}
}
/* PIPO YIELDS TO THE CART. He floats bottom-right at z-index 120, which is
   exactly where the mini cart puts Checkout - so on a phone the one button that
   takes money sat underneath a cartoon. Sitting him down inside the bar instead
   was tried and dropped: a 60px face in a 65px bar has 2px of air around it and
   reads as wedged in. He stands above it. */
/* When the bar is up he stands on top of it, and --mc-clear already says how
   far up the screen it reaches - so this is one line whether the cart is a bar
   resting on a dock or a card in the corner of a desktop. */
body.has-mc #pipo{bottom:calc(var(--mc-clear,0px) + 14px)}
#pipoBtn{width:60px;height:60px;border-radius:22px;position:relative;display:grid;place-items:center;
  background:linear-gradient(145deg,var(--a1),var(--a2) 55%,var(--a3));box-shadow:var(--sh-brand);
  transition:transform .45s var(--spring),box-shadow .4s;overflow:visible}
#pipoBtn:hover{transform:translateY(-4px) scale(1.06)}
#pipoBtn:active{transform:scale(.93)}
/* Pipo sits still. No breathing, no idle pulse — hover, the celebration hop and
   the blink are all deliberate reactions, and the equaliser badge plus the mouth
   carry "speaking" on their own. */
#pipoBtn.hop{animation:pipoHop .8s var(--spring)}
@keyframes pipoHop{0%,100%{transform:translateY(0) scale(1)}
  28%{transform:translateY(-16px) scale(1.09)}52%{transform:translateY(0) scale(.94)}
  74%{transform:translateY(-6px) scale(1.03)}}
#pipoBtn .aur{position:absolute;inset:-7px;border-radius:26px;background:inherit;filter:blur(13px);
  opacity:.5;z-index:-1}
#pipoBtn svg{width:40px;height:40px;position:relative;z-index:1;overflow:visible}
#pipoBtn .eye{transition:transform .16s linear}
#pipoBtn .lid{transform-origin:center;transform:scaleY(0)}
#pipoBtn.blink .lid{animation:blinkLid .2s ease}
@keyframes blinkLid{0%,100%{transform:scaleY(0)}50%{transform:scaleY(1)}}
#pipoBtn .sparks{position:absolute;inset:-16px;pointer-events:none;opacity:0}
#pipoBtn.celebrate .sparks{opacity:1;animation:fadeIn .2s}
#pipoBtn .sparks i{position:absolute;width:5px;height:5px;border-radius:50%;background:#fff;
  animation:sparkOut .9s var(--ease) forwards}
@keyframes sparkOut{0%{opacity:1;transform:translate(0,0) scale(1)}
  100%{opacity:0;transform:translate(var(--dx),var(--dy)) scale(0)}}
#pipoDot{position:absolute;top:-2px;right:-2px;width:14px;height:14px;border-radius:50%;
  background:var(--a3);border:2.5px solid var(--bg);opacity:0;transform:scale(0);
  transition:all .35s var(--spring)}
#pipoDot.on{opacity:1;transform:scale(1)}

/* ------------------------------------------------------- Pipo's voice ------
   Reading aloud and Tap a word are Pipo doing the talking, not a separate audio
   feature running beside it. The signal is the equaliser badge that used to sit
   in the reading player — the same rhythm, moved to the thing actually making
   the sound — plus the mouth cycling through its talk shapes. No halo, and the
   button itself never scales: the badge and the mouth are the whole signal. */
#pipoBtn #pipoMouth{transition:d .13s linear}

.pipowave{position:absolute;top:-8px;right:-9px;z-index:2;display:none;align-items:center;
  gap:2.5px;height:21px;padding:0 7px;border-radius:99px;
  background:var(--surface);border:1.5px solid var(--line);box-shadow:var(--sh-1)}
:root[data-theme="dark"] .pipowave{background:var(--surface-2)}
.pipowave i{width:2.5px;height:5px;border-radius:2px;background:var(--brand);
  animation:wv .9s ease-in-out infinite}
/* three bars, so the delays spread across the same beat rather than the first
   three of a five-bar stagger — otherwise the wave would look truncated */
.pipowave i:nth-child(2){animation-delay:.18s}.pipowave i:nth-child(3){animation-delay:.36s}
@keyframes wv{0%,100%{height:5px}50%{height:15px}}
#pipoBtn.speaking .pipowave,#pipoBtn.listening .pipowave{display:flex;animation:popIn .3s var(--spring)}
/* listening is the same badge held steady — awake and waiting, not mid-sentence */
#pipoBtn.listening .pipowave i{animation:wvIdle 2.1s var(--ease-io) infinite;animation-delay:0s}
@keyframes wvIdle{0%,100%{height:5px;opacity:.5}50%{height:9px;opacity:1}}
/* one badge in that corner at a time */
#pipoBtn.speaking #pipoDot,#pipoBtn.listening #pipoDot{opacity:0;transform:scale(0)}
:root[data-motion="off"] .pipowave i{animation:none;height:11px}

/* the bubble needs its own containing block — inside #pipo it would be clamped
   to the 60px button and collapse to a sliver */
#pipoBubbleHost{position:fixed;right:92px;bottom:calc(26px + env(safe-area-inset-bottom));
  width:min(272px,calc(100vw - 130px));display:flex;justify-content:flex-end;
  pointer-events:none;z-index:119}
@media (max-width:759.98px){
  #pipoBubbleHost{right:88px;bottom:calc(var(--tab-h) + env(safe-area-inset-bottom) + 20px)}
}
@media (max-width:479.98px){
  #pipoBubbleHost{right:14px;left:auto;width:min(280px,74vw);
    bottom:calc(var(--tab-h) + env(safe-area-inset-bottom) + 84px)}
}
/* WITH THE CART UP, EVERYTHING RISES BY THE HEIGHT OF THE CART. These are all
   measured off the bottom of the screen and the bar is at the bottom of the
   screen, so without this the bubble opened across it - its far end over the
   left of Checkout, at z-index 119 against the bar's 70, swallowing taps meant
   for the till. --mc-clear is the bar's reach and is 0 when it is down, so each
   line below is simply the position it always had plus whatever now sits under
   it. The bubble still opens to Pipo's left; it is only higher. */
body.has-mc #pipoBubbleHost{bottom:calc(var(--mc-clear,0px) + 26px)}
@media (max-width:759.98px){
  body.has-mc #pipoBubbleHost{bottom:calc(var(--mc-clear,0px) + 20px)}
}
@media (max-width:479.98px){
  body.has-mc #pipoBubbleHost{bottom:calc(var(--mc-clear,0px) + 84px)}
}
#pipoBubble{position:relative;max-width:100%;padding:12px 34px 12px 15px;pointer-events:auto;
  border-radius:18px 18px 4px 18px;background:var(--surface);border:1px solid var(--line);
  box-shadow:var(--sh-3);font-size:13.2px;line-height:1.5;color:var(--ink);
  animation:bubIn .5s var(--spring) both;transform-origin:bottom right}
:root[data-theme="dark"] #pipoBubble{background:linear-gradient(150deg,#1D2450,#111535)}
@keyframes bubIn{from{opacity:0;transform:scale(.7) translateX(14px)}to{opacity:1;transform:none}}
#pipoBubble.out{animation:bubIn .3s var(--ease) reverse both}
#pipoBubble .bx{position:absolute;top:5px;right:5px;width:22px;height:22px;border-radius:7px;display:grid;
  place-items:center;color:var(--ink-4);transition:all .2s}
#pipoBubble .bx:hover{background:var(--surface-3);color:var(--ink)}
#pipoBubble .bx .i{width:12px;height:12px}
@media (max-width:479.98px){#pipoBubble{border-radius:18px 18px 18px 4px}}

#pipoPanel{position:fixed;right:18px;bottom:88px;z-index:130;width:min(384px,calc(100vw - 32px));
  height:min(548px,calc(100dvh - 150px));display:flex;flex-direction:column;overflow:hidden;
  border-radius:var(--r-2xl);background:var(--surface);border:1px solid var(--line);
  box-shadow:var(--sh-4);animation:panelIn .5s var(--spring) both;transform-origin:bottom right}
:root[data-theme="dark"] #pipoPanel{background:linear-gradient(165deg,#161C42,#0B0E2A)}
@keyframes panelIn{from{opacity:0;transform:translateY(20px) scale(.9)}to{opacity:1;transform:none}}
#pipoPanel.out{animation:panelIn .28s var(--ease) reverse both}
@media (max-width:559.98px){
  #pipoPanel{right:0;left:0;bottom:0;width:100%;height:min(86dvh,660px);
    border-radius:var(--r-2xl) var(--r-2xl) 0 0;animation:sheetIn .45s var(--spring) both}
  #pipoPanel.out{animation:sheetIn .26s var(--ease) reverse both}
}
.pipohead{display:flex;align-items:center;gap:11px;padding:14px 15px;border-bottom:1px solid var(--line);
  background:var(--surface-2);flex:0 0 auto}
:root[data-theme="dark"] .pipohead{background:rgba(255,255,255,.03)}
.pipohead .pav{width:38px;height:38px;border-radius:13px;display:grid;place-items:center;
  background:linear-gradient(145deg,var(--a1),var(--a3));box-shadow:var(--sh-brand)}
.pipohead .pav svg{width:24px;height:24px}
.pipohead .ph b{display:block;font-family:var(--f-dis);font-weight:750;font-size:14.5px;letter-spacing:-.016em}
.pipohead .ph span{font-size:11.5px;color:var(--ok);font-weight:650;display:flex;
  align-items:center;gap:5px}
.pipohead .ph span::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--ok);
  animation:breathe 2s infinite}
.pipomsgs{flex:1 1 auto;overflow-y:auto;padding:15px;display:flex;flex-direction:column;gap:11px;
  scroll-behavior:smooth}
.pm{max-width:86%;padding:11px 14px;border-radius:17px;font-size:13.6px;line-height:1.56;
  animation:fadeUp .4s var(--ease) both;word-wrap:break-word;overflow-wrap:anywhere}
.pm.bot{align-self:flex-start;background:var(--surface-2);border:1px solid var(--line);
  border-bottom-left-radius:5px;color:var(--ink)}
:root[data-theme="dark"] .pm.bot{background:rgba(255,255,255,.05)}
.pm.me{align-self:flex-end;background:var(--grad-brand);color:#fff;border-bottom-right-radius:5px;
  box-shadow:var(--sh-brand)}
.pm b{font-weight:750}
/* a word Pipo pronounced, with a quiet speaker to replay it */
.pm .sayw{display:inline-flex;align-items:center;gap:5px;padding:2px 4px 2px 8px;border-radius:99px;
  background:var(--brand-soft);border:1px solid var(--brand-line);vertical-align:baseline;
  max-width:100%;line-height:1.35}
.pm .sayw b{font-weight:750;color:var(--brand);overflow-wrap:anywhere}
.pm.me .sayw{background:rgba(255,255,255,.2);border-color:rgba(255,255,255,.3)}
.pm.me .sayw b{color:#fff}
.sayw-b{width:22px;height:22px;flex:0 0 auto;border-radius:50%;display:grid;place-items:center;
  color:var(--brand);background:transparent;
  transition:background .22s,transform .3s var(--spring),color .2s}
.sayw-b .i{width:13px;height:13px}
.sayw-b:hover{background:var(--brand);color:#fff;transform:scale(1.1)}
.sayw-b:active{transform:scale(.9)}
.sayw-b.saying{background:var(--brand);color:#fff;animation:breathe 1.1s var(--ease-io) infinite}
.pm.me .sayw-b{color:#fff}
.pm.me .sayw-b:hover{background:rgba(255,255,255,.3)}

.pm .qref{display:inline-flex;align-items:center;gap:6px;margin-top:9px;padding:6px 11px;border-radius:99px;
  background:var(--brand-soft);color:var(--brand);font-size:11.8px;font-weight:750;font-family:var(--f-dis);
  transition:all .25s}
.pm .qref:hover{background:var(--brand);color:#fff}
.pm .qref .i{width:12px;height:12px}
.pm.typing{display:flex;gap:4px;padding:14px 15px;align-items:center}
.pm.typing i{width:6px;height:6px;border-radius:50%;background:var(--ink-4);animation:tdot 1.1s infinite}
.pm.typing i:nth-child(2){animation-delay:.16s}.pm.typing i:nth-child(3){animation-delay:.32s}
@keyframes tdot{0%,60%,100%{opacity:.3;transform:translateY(0)}30%{opacity:1;transform:translateY(-4px)}}
.pipochips{display:flex;gap:7px;padding:0 15px 11px;flex-wrap:wrap;flex:0 0 auto}
.pipochip{height:31px;padding:0 12px;border-radius:99px;background:var(--surface-2);border:1px solid var(--line);
  font-size:12px;font-weight:700;color:var(--ink-2);font-family:var(--f-dis);
  transition:all .3s var(--spring);white-space:nowrap}
.pipochip:hover{border-color:var(--brand-line);color:var(--brand);transform:translateY(-2px)}
.pipoform{display:flex;gap:9px;padding:12px 14px calc(12px + env(safe-area-inset-bottom));
  border-top:1px solid var(--line);flex:0 0 auto;align-items:flex-end}
.pipoform textarea{flex:1 1 auto;height:42px;max-height:110px;padding:10px 14px;border-radius:14px;
  background:var(--surface-2);border:1.5px solid var(--line);resize:none;font-size:14px;line-height:1.4;
  transition:border-color .25s,box-shadow .25s}
.pipoform textarea:focus{outline:none;border-color:var(--brand);box-shadow:0 0 0 3.5px var(--brand-soft)}
.pipoform .send{width:42px;height:42px;flex:0 0 auto;border-radius:14px;display:grid;place-items:center;
  background:var(--grad-brand);color:#fff;box-shadow:var(--sh-brand);
  transition:transform .35s var(--spring),opacity .25s}
.pipoform .send:hover{transform:scale(1.07)}
.pipoform .send:disabled{opacity:.4;pointer-events:none}

/* ---------------------------------------------------------------- empties -*/
.empty{text-align:center;padding:52px 20px}
.empty .ei{width:64px;height:64px;margin:0 auto 16px;border-radius:22px;display:grid;place-items:center;
  background:var(--brand-soft);color:var(--brand)}
.empty .ei .i{width:29px;height:29px}
.empty h3{font-size:18px;letter-spacing:-.024em}
.empty p{font-size:14px;color:var(--ink-3);margin-top:7px;max-width:38ch;margin-inline:auto;line-height:1.6}

/* print fallback for PDF */
@media print{
  #rail,#topbar,#dock,#pipo,#pipoPanel,#aura,#grain,.sitefoot,#toaster{display:none !important}
  #main{margin:0}
  body{background:#fff;color:#000}
}

/* ==== src/parts/p06b.html, verbatim ==== */
/* ============================================================================
   DASHBOARD COLUMN SYSTEM — a different arrangement on each device, not one
   layout squeezed through breakpoints.
   ========================================================================== */
.dash2{display:grid;gap:16px;margin-top:16px}
.dcol{display:flex;flex-direction:column;gap:16px;min-width:0}
/* mobile: one honest column, ordered by what matters on a small screen */
@media (max-width:759.98px){
  .dash2{grid-template-columns:1fr}
  .dcol{gap:14px}
}
/* tablet: single stack, but the side rail becomes a two-up card grid */
@media (min-width:760px) and (max-width:1099.98px){
  .dash2{grid-template-columns:1fr;gap:18px}
  .dcol{gap:18px}
  .dash2>.dcol:last-child{display:grid;grid-template-columns:1fr 1fr;align-items:start}
  .dash2>.dcol:last-child>*:nth-child(3){grid-column:span 2}
  #dMain,#pMain{display:grid;grid-template-columns:1fr 1fr;align-items:start}
  #dMain>*:first-child,#pMain>*:first-child{grid-column:span 2}
}
/* desktop: true two-column composition */
@media (min-width:1100px){
  .dash2{grid-template-columns:minmax(0,1.58fr) minmax(0,1fr);gap:20px;align-items:start}
  .dcol{gap:20px}
}

/* icon size helpers used inline */
.i22{width:22px;height:22px;flex:0 0 auto}
.i32{width:32px;height:32px;flex:0 0 auto}
.i-14{width:14px;height:14px;flex:0 0 auto}

/* per-sentence speak affordance */
.sline{position:relative;padding-right:30px}
.sline .stx{flex:1 1 auto;min-width:0}
/* A template a student can open and keep. Shut by default so it reads as one
   quiet line in the lesson rather than a wall of text. */
.tmpl{border:1px solid var(--line);border-radius:14px;margin:14px 0;overflow:hidden;
  background:var(--surface-2,var(--surface))}
.tmpl-h{display:flex;align-items:center;gap:9px;padding:11px 13px}
.tmpl-h .i{width:16px;height:16px;color:var(--brand);flex:0 0 auto}
.tmpl-n{font-weight:600;flex:1 1 auto;min-width:0}
.tmpl-b{padding:0 13px 13px}
.tmpl-t{margin:0;padding:12px;border-radius:10px;background:var(--bg);
  border:1px solid var(--line);white-space:pre-wrap;word-break:break-word;
  font:500 13.5px/1.65 var(--mono,ui-monospace,SFMono-Regular,Menlo,monospace)}
.tmpl-h .tmpl-cp{flex:0 0 auto;margin-left:auto}
/* SAY IT - the block with the microphones. Built to look like the template
   card, because it is the same kind of thing: something the lesson hands you
   rather than something it says. */
.sayb{border:1px solid var(--line);border-radius:14px;margin:14px 0;overflow:hidden;
  background:var(--surface-2,var(--surface))}
.say-b{padding:0 13px 13px}
/* No header above it, so the body carries its own top padding. */
.say-b.bare{padding:13px}
.say-note{margin:0 0 11px;color:var(--ink-3);font-size:13.4px;line-height:1.55}
/* The one line a browser without a microphone gets. Smallest type on the card
   and no colour of its own, because it is an explanation and not a warning -
   nothing here has gone wrong. */
.say-why{display:flex;gap:7px;align-items:flex-start;margin:0 0 11px;
  color:var(--ink-4);font-size:12.4px;line-height:1.5}
.say-why .i{width:14px;height:14px;flex:0 0 auto;margin-top:2px}
/* The way back, after the microphone has been refused. A link rather than a
   button, because it undoes something rather than doing something. */
.say-again{background:none;border:0;padding:0;font:inherit;font-size:13.4px;
  color:var(--brand);font-weight:650;cursor:pointer;text-decoration:underline;
  text-underline-offset:2px}
/* Button and line on one row, at every width. The button is a column the eye
   can follow; the sentence starts where the sentence above it started. */
.say-row{display:flex;align-items:center;gap:11px;padding:8px 0;
  border-top:1px solid var(--line)}
.say-row:first-of-type{border-top:0}
/* A BUTTON WITH A WORD ON IT. It used to be a grey icon square, which sat in
   the same place as the plain speaker mark the no-microphone version shows -
   so the one thing on the card that could be tapped looked exactly like the
   thing that could not. Brand colour and the word Speak, because this is the
   one action the card exists for. */
.say-go{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  gap:6px;width:82px;height:34px;padding:0;border-radius:10px;white-space:nowrap;
  background:var(--brand-soft);border:1px solid var(--brand);color:var(--brand);
  font:inherit;font-size:12.5px;font-weight:650;letter-spacing:-.01em;
  cursor:pointer;transition:box-shadow .18s,transform .12s}
/* A FIXED WIDTH, so Speak, Listening and Again do not make the column jump
   about as somebody works down the card. Listening is the longest of the
   three and it sets the number. */
.say-go .i{width:15px;height:15px}
.say-go:hover{box-shadow:var(--sh-brand,0 4px 14px rgba(0,0,0,.10))}
.say-go:active{transform:scale(.97)}
/* No microphone here: a quiet mark instead of a button, so the row still reads
   as a line to be said rather than a control that failed to load. */
.say-dot{flex:0 0 auto;width:40px;height:40px;display:grid;place-items:center;
  color:var(--ink-4)}
.say-dot .i{width:16px;height:16px}
.say-row.plain{align-items:center}
.say-t{min-width:0;flex:1 1 auto}
.say-line{display:block;font-weight:600;line-height:1.45;font-size:14px}
.say-heard{display:flex;gap:5px;align-items:baseline;margin-top:3px;
  font-size:12.8px;color:var(--ink-4);line-height:1.5;overflow-wrap:anywhere}
.say-heard:empty{display:none}
.say-heard .i{width:13px;height:13px;flex:0 0 auto;transform:translateY(2px)}
/* Listening: the button pulses so it is obvious the microphone is open, which
   matters more than it looks - nobody should have to wonder. */
/* Listening: it pulses, so there is never a doubt that the microphone is
   open. Smaller than it was, because the button is wider now and a big pulse
   on a wide element reads as the page breathing. */
.say-row.hearing .say-go{animation:sayPulse 1.2s ease-in-out infinite}
@keyframes sayPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.03)}}

/* Narrow screens keep the same shape and simply give the button less room.
   Stacking it under the line was tried and measured: four outlined boxes down
   the page, rows of ninety and a hundred and fourteen pixels, and a card half
   as tall again as the one it replaced. */
@media (max-width:560px){
  .say-go{width:74px;height:32px;font-size:12px}
  .say-go .i{width:14px;height:14px}
  .say-line{font-size:13.6px}
}
/* Green, and the same green as a right answer everywhere else in this
   product. The button carries it too, so the column of buttons doubles as a
   list of what is left to do. */
.say-row.ok .say-go{background:var(--ok-soft);border-color:var(--ok);color:var(--ok)}
.say-row.ok .say-heard{color:var(--ok);font-weight:600}
/* AND RED WHEN IT DID NOT, the owner's decision and the right one. This was
   grey, on the reasoning that the recogniser is generous with accents and
   strict with noise - but a student looking at four lines needs to see which
   ones are still to do, and grey against green reads as not-yet-tried rather
   than try-again. The same --no the quiz uses, so one colour means one thing
   everywhere in the product.

   What keeps it honest is the wording rather than the colour: the line still
   says what was heard and which words were missing, so anybody can see whether
   it was the sentence or the room that failed. */
.say-row.miss .say-go{background:var(--no-soft);border-color:var(--no);color:var(--no)}
.say-row.miss .say-heard{color:var(--no);font-weight:600}
/* A booking line: prose with a link in it, at the end of a section. Not a card
   and not a colour - a box in the middle of a lesson reads as an advert, and
   somebody re-reading lesson nine for the fourth time should not have to see
   one. It is hidden until the server says the session is real. */
.bookline{margin:16px 0 0;color:var(--ink-3);font-size:14.6px;line-height:1.7}
.bookline[hidden]{display:none}
.bookline .bl-go{color:var(--brand);font-weight:700;text-decoration:none;
  border-bottom:1.5px solid var(--brand-line)}
.bookline .bl-go:hover{border-bottom-color:var(--brand)}
.bookline .bl-meta{color:var(--ink-4)}
/* 32px, and not 26 shrunk to 21. It is an extra rather than a control, but a
   thing worth tapping is a thing worth being able to tap. */
.sayline{position:absolute;right:0;top:50%;transform:translateY(-50%) scale(.86);
  width:32px;height:32px;border-radius:10px;display:grid;place-items:center;color:var(--ink-4);
  opacity:0;transition:opacity .22s,color .2s,background .2s,transform .3s var(--spring)}
.sayline .i{width:14px;height:14px}
.sline:hover .sayline,.sline:focus-within .sayline{opacity:1;transform:translateY(-50%) scale(1)}
.sayline:hover{color:var(--brand);background:var(--brand-soft)}
.sline.saying{background:var(--brand-soft);border-radius:8px}
@media (hover:none){ .sayline{opacity:.55;transform:translateY(-50%) scale(1)} }

/* tap-to-pronounce mode */
.lessonwrap.pron .prose,.lessonwrap.pron .lhero .stx{cursor:pointer}
.lessonwrap.pron .prose p:hover,.lessonwrap.pron .prose li:hover,
.lessonwrap.pron .prose .stx:hover{background:var(--brand-soft);border-radius:6px}
.lessonwrap.pron::after{content:"";position:fixed;inset:auto 0 0 0;height:2px;background:var(--grad-brand);
  z-index:30;animation:fadeIn .3s}

/* quiz hub visual balance on tablet */
@media (min-width:760px) and (max-width:1099.98px){
  .fmtgrid{grid-template-columns:repeat(2,1fr)}
}
/* profile settings columns */
@media (min-width:760px) and (max-width:1099.98px){
  #setA,#setB{display:flex}
}

/* reader typography scales with the device, never below comfortable */
@media (min-width:1100px){ .prose{font-size:16.8px;line-height:1.8} }
@media (max-width:759.98px){
  :root{--sec-gap:13px}
  .prose{font-size:16px;line-height:1.75}
  .paper{padding:19px 17px;border-radius:var(--r-xl)}
  .statgrid{gap:9px}
  .stat{padding:13px}
  .qa{gap:9px}
}

/* onboarding on small screens */
@media (max-width:559.98px){
  #onb{padding:20px 18px}
  #onb .ob-mark{width:68px;height:68px;border-radius:22px}
  #onb .ob-mark svg{width:36px;height:36px}
  #onb .ob-brand{font-size:11.5px;letter-spacing:.28em;margin-top:18px}
  #onb .ob-say{font-size:14.5px;margin-top:13px}
  #onb .ob-form{margin-top:30px}
  #onb input{height:56px}
}

/* the hero never lets its greeting collide with the stats on short screens */
@media (max-height:620px) and (max-width:759.98px){
  .hero{padding:18px}
  .hero .greet{font-size:30px;margin-top:12px}
  .heroStats{margin-top:18px}
}

/* wide desktop: give the hero more presence */
@media (min-width:1400px){
  .hero{padding:48px}
  .hero .greet{font-size:66px}
}

/* ==== src/parts/p08.html, verbatim ==== */
/* ============================================================================
   BOOT SKELETON
   The split build paints from cache and then waits for the course content to
   arrive. That gap is short, but a blank page for even a few hundred
   milliseconds reads as a broken app, so the shape of the product goes up
   immediately and the real thing replaces it.

   It draws the whole screen, chrome included, because #app is display:none
   until the app starts — the rail and the top bar are not there to be seen yet.
   Deliberately a silhouette and not a spinner: the student sees where things
   will be before they are there.

   The standalone build has nothing to wait for and never shows this.
   ========================================================================== */
#boot{
  position:fixed; inset:0; z-index:3;
  display:flex;
  background:var(--bg);
  animation:bootIn .28s var(--ease) both;
}
#boot[hidden]{display:none}

/* one shimmering surface, reused at every size */
.bk{
  position:relative; overflow:hidden;
  border-radius:var(--r-lg);
  background:var(--surface-2);
  border:1px solid var(--line);
}
.bk::after{
  content:''; position:absolute; inset:0;
  transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,var(--surface-3),transparent);
  animation:bootSheen 1.25s var(--ease) infinite;
}

/* ---- the rail, desktop only ---------------------------------------------- */
.bk-rail{
  flex:0 0 var(--nav-w); display:none;
  flex-direction:column; gap:12px;
  padding:22px 20px;
  border-right:1px solid var(--line);
}
.bk-brand{height:46px; border-radius:var(--r-md)}
.bk-rail .bk-nav{height:44px; border-radius:var(--r-md)}
.bk-rail .bk-spacer{flex:1 1 auto}
.bk-rail .bk-card{height:104px}

/* ---- top bar + content --------------------------------------------------- */
.bk-main{flex:1 1 auto; min-width:0; display:flex; flex-direction:column}
.bk-top{
  height:var(--top-h); flex:0 0 auto;
  display:flex; align-items:center; justify-content:flex-end; gap:10px;
  padding:0 clamp(14px,3vw,28px);
  border-bottom:1px solid var(--line-soft);
}
.bk-top .bk{height:36px; width:62px; border-radius:var(--r-full)}
.bk-top .bk-av{width:38px}

.bk-body{
  flex:1 1 auto; min-height:0;
  display:flex; flex-direction:column; gap:18px;
  padding:24px clamp(14px,3vw,28px);
  max-width:var(--maxw); width:100%;
}
.bk-hero{height:210px; flex:0 0 auto}
.bk-row{display:flex; gap:16px; flex:0 0 auto}
.bk-row .bk{flex:1 1 0; height:120px}
.bk-wide{height:150px; flex:0 0 auto}

/* the dock takes the rail's place on a phone */
.bk-dock{
  position:fixed; left:0; right:0; bottom:0; display:none;
  height:calc(var(--tab-h) + env(safe-area-inset-bottom));
  padding:9px 14px calc(9px + env(safe-area-inset-bottom));
  gap:10px;
  border-top:1px solid var(--line-soft);
  background:var(--surface);
}
.bk-dock .bk{flex:1 1 0; border-radius:var(--r-md)}

@media (min-width:1100px){
  .bk-rail{display:flex}
  .bk-hero{height:240px}
}
@media (max-width:759.98px){
  .bk-body{padding:16px; gap:14px}
  .bk-hero{height:180px}
  .bk-row .bk{height:96px}
  .bk-top .bk{width:52px}
  .bk-dock{display:flex}
}

@keyframes bootIn{from{opacity:0}to{opacity:1}}
@keyframes bootSheen{to{transform:translateX(100%)}}

/* ---- content could not be fetched ---------------------------------------- */
/* Says what happened and offers the one action that helps, rather than leaving
   a half-painted shell up forever. */
#bootErr{
  position:fixed; inset:0; z-index:4;
  display:flex; align-items:center; justify-content:center; padding:24px;
  background:var(--bg);
}
#bootErr[hidden]{display:none}
#bootErr .be-card{
  max-width:420px; text-align:center;
  padding:32px 28px;
  border-radius:var(--r-lg);
  background:var(--surface);
  border:1px solid var(--line);
  box-shadow:var(--sh-2);
}
#bootErr h2{
  font-family:var(--f-dis); font-weight:800; font-size:20px;
  letter-spacing:-.02em; color:var(--ink); margin:0 0 8px;
}
#bootErr p{
  font-size:14.5px; line-height:1.6; color:var(--ink-3); margin:0 0 20px;
}
#bootErr button{
  font-family:var(--f-dis); font-weight:700; font-size:14px;
  padding:11px 22px; border:0; border-radius:var(--r-full);
  color:#fff; background:var(--grad-brand); cursor:pointer;
}

@media (prefers-reduced-motion: reduce){
  #boot{animation:none}
  .bk::after{animation:none; opacity:0}
}

/* ==== src/parts/p08b.html, verbatim ==== */
/* ============================================================================
   PREVIEW & UNLOCK
   A course held on preview opens its first lessons and shows the rest, shut.
   Everything here is inert unless that is the case — nothing in this file
   applies to a course the student owns outright.

   Two shapes for the one action, because one shape cannot do both jobs:
     >=760px  a labelled button in the top bar, beside the theme toggle
     <760px   a full-width strip stuck under the bar, with the price on it
   Both disappear on a lesson, a certificate and a running quiz. Somebody who
   is reading is already being persuaded; interrupting that with a price is
   the surest way to end the session.
   ========================================================================== */

/* ------------------------------------------------------- top-bar button --- */
#unlockBtn{
  flex:0 0 auto;
  white-space:nowrap;
  gap:7px;
}
#unlockBtn[hidden]{display:none}
#unlockBtn .ub-i{width:15px;height:15px;flex:0 0 auto}

/* ------------------------------------------------------------ the strip --- */
#unlockBar{
  display:none;
  position:sticky;
  /* the bar above is sticky at 0 and this height, so this one sits exactly
     under it at every breakpoint without a hard-coded offset anywhere */
  top:var(--top-h);
  z-index:54;
  width:100%;
  align-items:center;
  justify-content:space-between;
  gap:10px;
  height:44px;
  padding:0 14px;
  border:0;
  color:#fff;
  font-family:var(--f-dis);
  text-align:left;
  cursor:pointer;
  background:var(--grad-brand);
  box-shadow:0 6px 18px -8px rgba(90,69,255,.55);
}
#unlockBar[hidden]{display:none}
#unlockBar .ul-l{display:flex; align-items:center; gap:9px; min-width:0}
#unlockBar .ul-i{width:15px;height:15px;flex:0 0 auto}
#unlockBar b{
  font-weight:750; font-size:13.6px; letter-spacing:-.01em;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
#unlockBar .ul-r{
  display:flex; align-items:center; gap:5px; flex:0 0 auto;
  font-weight:800; font-size:13px; opacity:.95;
}
#unlockBar .ul-c{width:14px;height:14px;flex:0 0 auto}
#unlockBar:active{filter:brightness(.96)}

@media (max-width:759.98px){
  #unlockBtn{display:none !important}
  #unlockBar:not([hidden]){display:flex}
  /* The strip and the lesson toolbar are both sticky at --top-h, so with the
     strip up they landed on the same line and the strip, higher up the stack,
     simply covered the toolbar. 44px is the strip's own height, declared eleven
     lines above this one. Only in this query: above 760 the way out of a
     preview is a button in the top bar and there is no strip to clear. */
  body.unlocking .lessonbar{top:calc(var(--top-h) + 44px)}
}
/* Under 340 the price is the first thing to go: the label is the ask, the
   number is a detail, and the tap target must not shrink for either. */
@media (max-width:339.98px){
  #unlockBar .ul-p{display:none}
}

/* ------------------------------------------------------ the mid-lesson wall */
/* Where the free reading stops, two sections into the first locked lesson.
 *
 * IT IS DELIBERATELY NOT A MODAL AND NOT A BANNER. It is a card in the flow of
 * the page, in the place the next section would have been, because the point is
 * that the lesson CONTINUES -- a dialog over the top says "you have hit a
 * paywall", and a card in the flow says "there is more of this just here". */
.wallcard{
  margin:22px 0 8px; padding:clamp(20px,4vw,30px);
  border:1px solid var(--brand-line); border-radius:20px;
  background:linear-gradient(180deg,var(--brand-soft),var(--surface));
  text-align:center;
}
.wallcard .wc-ic{
  width:46px;height:46px;margin:0 auto 12px;border-radius:15px;
  display:grid;place-items:center;background:var(--surface);
  color:var(--brand);box-shadow:0 1px 0 var(--line)
}
.wallcard .wc-ic .i{width:22px;height:22px}
.wallcard h2{
  font-family:var(--f-dis);font-size:clamp(19px,2.7vw,23px);letter-spacing:-.02em;
  margin:0 0 6px
}
.wallcard > p{color:var(--ink-2);font-size:15px;line-height:1.6;margin:0 auto 18px;max-width:46ch}
.wallcard > p b{color:var(--ink)}
/* The buy button is the loud one and stands alone; see renderWall for why. */
.wallcard .wc-buy{min-width:min(20rem,100%)}
.wc-form{max-width:24rem;margin:0 auto}
.wc-lab{
  display:block;font-size:12px;letter-spacing:.09em;text-transform:uppercase;
  color:var(--ink-3);font-weight:700;margin-bottom:7px
}
/* Stacks under 420px. A 44px-tall button beside a shrinking input is how an
   email field ends up eight characters wide on a phone. */
.wc-row{display:flex;gap:8px;flex-wrap:wrap}
.wc-in{
  flex:1 1 11rem;min-width:0;height:44px;padding:0 13px;
  border:1px solid var(--line-strong);border-radius:12px;
  background:var(--surface);color:var(--ink);font:inherit;font-size:15px
}
.wc-in:focus{outline:2px solid var(--brand);outline-offset:1px;border-color:var(--brand)}
.wc-row .btn{height:44px;flex:0 0 auto}
/* CONTINUE WITH GOOGLE, above the email row. Full width and 44px, the same
   height as the field and the button beside it, so the two ways in read as one
   pair rather than as a button and an afterthought.
   The mark keeps Google's own four colours -- their brand guidelines require it
   and a recoloured one is worse than none, because a wrong-coloured Google
   button is the shape a phishing page takes. */
.wc-goog{width:100%;height:44px;gap:10px;margin-bottom:12px}
.wc-goog svg{flex:0 0 auto}
/* NO DIVIDER BETWEEN THE TWO WAYS IN. There was one -- "or leave your email" --
   and it put a second rule across a card that already had "not yet?" above it.
   Two rules, three buttons, a label and a line of fine print in 589px of a
   694px screen is what "the whole wall looks messy" was.
   .wc-lab covers both: "Finish just this lesson, free" is as true of one tap as
   of an email address, so the two controls sit under it as a pair and need
   nothing between them. */
.wc-fine{color:var(--ink-4);font-size:12.5px;line-height:1.5;margin:9px 0 0}
.wc-msg{font-size:13.5px;line-height:1.5;margin:10px 0 0;color:var(--ink-2)}
.wc-msg.ok{color:var(--ok)}
.wc-msg.bad{color:var(--no)}
.wc-or{
  display:flex;align-items:center;gap:12px;margin:20px auto 14px;max-width:24rem;
  color:var(--ink-4);font-size:12px;letter-spacing:.1em;text-transform:uppercase
}
.wc-or::before,.wc-or::after{content:"";flex:1;height:1px;background:var(--line)}

/* --------------------------------------------------- locked lesson rows --- */
/* Shown, never hidden. The list is the argument: fifty numbered mistakes, two
   of them open, and the student can read every title they are not buying. */
.lrow.locked .ls{color:var(--ink-4)}
.lrow.locked .lt b{color:var(--ink-3)}
/* the row still lifts on hover — it is not disabled, it leads somewhere */
.lr-lock{
  display:inline-flex; align-items:center; gap:5px;
  flex:0 0 auto;
  padding:4px 9px;
  border-radius:var(--r-full);
  background:var(--surface-3);
  border:1px solid var(--line);
  color:var(--ink-3);
  font-family:var(--f-dis); font-weight:700; font-size:11px;
  letter-spacing:.01em;
}
.lr-lock .i{width:12px;height:12px}

/* ---------------------------------------------------- the locked screen --- */
/* Where a locked lesson would have been. States plainly what is behind it and
   offers the one way through, rather than bouncing the student to Home. */
.lockwrap{
  max-width:560px; margin:0 auto; text-align:center;
  padding:44px 24px;
}
.lockwrap .lw-ic{
  width:66px;height:66px;margin:0 auto 20px;
  display:grid;place-items:center;
  border-radius:var(--r-xl);
  background:var(--brand-soft);
  color:var(--brand);
}
.lockwrap .lw-ic .i{width:28px;height:28px}
.lockwrap h2{
  font-family:var(--f-dis); font-weight:800; font-size:23px;
  letter-spacing:-.022em; color:var(--ink); margin:0 0 10px;
}
.lockwrap p{font-size:15px; line-height:1.65; color:var(--ink-3); margin:0 0 8px}
.lockwrap .lw-peek{
  margin:22px 0;
  padding:18px 20px;
  border-radius:var(--r-lg);
  background:var(--surface-2);
  border:1px solid var(--line);
  text-align:left;
}
.lockwrap .lw-peek .lw-k{
  font-family:var(--f-dis); font-weight:800; font-size:10.5px;
  letter-spacing:.1em; text-transform:uppercase; color:var(--ink-4);
  margin-bottom:9px;
}
.lockwrap .lw-acts{
  display:flex; gap:10px; justify-content:center; flex-wrap:wrap;
  margin-top:24px;
}

/* ------------------------------------------------- after a preview quiz --- */
/* Two different cards on purpose. The first quiz has only just proved the
   format works, so it says what is there and gets out of the way. The second
   is the last free thing in the course, and that is the moment to ask. */
.pvnote{
  margin-top:18px;
  padding:18px 20px;
  border-radius:var(--r-lg);
  border:1px solid var(--line);
  background:var(--surface-2);
  text-align:left;
}
.pvnote.ask{
  border-color:var(--brand-line);
  background:var(--brand-soft);
}
.pvnote .pv-t{
  font-family:var(--f-dis); font-weight:800; font-size:15.5px;
  letter-spacing:-.015em; color:var(--ink); margin-bottom:6px;
  display:flex; align-items:center; gap:8px;
}
.pvnote .pv-t .i{width:16px;height:16px;color:var(--brand);flex:0 0 auto}
.pvnote p{font-size:13.8px; line-height:1.6; color:var(--ink-3); margin:0}
.pvnote .pv-act{margin-top:14px; display:flex; gap:9px; flex-wrap:wrap}

@media (prefers-reduced-motion: reduce){
  #unlockBar:active{filter:none}
}

/* ===========================================================================
   THE END-OF-QUIZ SCREEN, and only the parts the app has no equivalent for.

   Designed with the owner over 1 September 2026 and approved block for block:
   the diagnosis box in the app's wrong-answer pink, the course card with its own
   picture at her store's 1:3 proportion, the rule that runs from the end of
   "What you got wrong" to the right edge, and the quiet "see all ten" button.

   WHAT IS NOT RESTATED HERE, deliberately: .resultwrap, .qacts, .rvlist, .rv,
   .ring, .tag and .eyebrow. The app defines all of those and the app's versions
   are the ones that must win.
   ========================================================================== */

/* ---- OPAQUE OVER THE AURORA ----------------------------------------------
   The app draws these on a flat --bg. This page has the aurora behind it, and
   a translucent fill lets a drifting blob straight through: the diagnosis box
   is --no-soft, which is rgba(225,29,111,.09), so nine per cent pink over
   whatever colour happened to be under it. It read as washed out because it
   was. The same tint composited over --surface first is the same colour with
   nothing showing through -- the token is not restated, it is layered.

   The review rows are the other half of it. --surface-2 is opaque, but #F7F8FF
   against an aurora-tinted ground has almost no edge, so ten of them read as
   ten faint smudges rather than ten cards. White, with the line the app already
   gives them, is what makes them cards. Overridden here rather than edited in
   the lifted block. */

.diag{margin-top:26px;text-align:left;padding:18px 18px 17px;border-radius:var(--r-lg);
  background:var(--no-soft);border:1px solid var(--no-line)}

.diag .eyebrow{color:var(--no)}

.diag h2{font-size:19px;margin-top:6px;letter-spacing:-.02em}

.diag p{margin-top:7px;font-size:14px;color:var(--ink-2)}

.diag b{color:var(--ink);font-weight:800}

.diag{background:linear-gradient(var(--no-soft),var(--no-soft)),var(--surface)}
.resultwrap .rv{background:var(--surface);box-shadow:var(--sh-1)}

/* The score card. .qcard is the app's own white panel -- the one every question
   was asked in -- so the round ends on the surface it was played on. It only
   needs its top margin removed, because it is the first thing on the screen. */
.scorecard{margin-top:0;padding-top:26px;padding-bottom:24px}

.rvhead{margin-top:32px;text-align:left;display:flex;align-items:center;gap:13px}

.rvhead::after{content:'';flex:1 1 auto;height:1px;background:var(--line)}

.seeall{width:100%;margin-top:3px;height:42px;font-size:13.5px;color:var(--ink-2)}

.off{display:inline-block;margin-left:6px;padding:1px 7px;border-radius:var(--r-full);
  background:var(--ok-soft);border:1px solid var(--ok-line);color:var(--ok);
  font-size:11.5px;font-weight:800;letter-spacing:.01em}

.offer{margin-top:24px;border-radius:var(--r-lg);overflow:hidden;text-align:left;
  background:var(--surface);border:1px solid var(--line-strong);box-shadow:var(--sh-2)}

/* NO PICTURE IN THIS RULE. It arrived carrying one course's photograph as a
   99 KB base64 data URI -- residue from the mockup this block was lifted
   from, where there was only ever one course to show. quiz.js sets
   background-image per course from the real /img/courses/<id>.jpg, so the
   stylesheet was shipping a picture that is always replaced, on every load,
   to every visitor. Only the shape of the band belongs here. */
.offer .pic{aspect-ratio:3/1;background-size:cover;background-position:center;
  border-bottom:1px solid var(--line)}

.offer .in{padding:15px}

.offer .lvl{font-family:var(--f-dis);font-size:10.5px;font-weight:800;letter-spacing:.13em;
  text-transform:uppercase;color:var(--ink-3)}

.offer h3{font-family:var(--f-dis);font-weight:800;font-size:17.5px;letter-spacing:-.025em;
  line-height:1.2;margin-top:4px}

.offer .blurb{font-size:13px;color:var(--ink-2);line-height:1.45;margin-top:6px;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}

.offer .stats{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;margin-top:12px;
  background:var(--line);border:1px solid var(--line);border-radius:var(--r-md);
  overflow:hidden}

.offer .st{background:var(--surface-2);padding:8px 5px;text-align:center}

.offer .st b{display:block;font-family:var(--f-dis);font-weight:800;font-size:15px;
  letter-spacing:-.03em;color:var(--ink);line-height:1.1}

.offer .st span{display:block;font-size:10.5px;color:var(--ink-3);margin-top:3px;
  font-weight:600;line-height:1.25}

.offer .buy{margin-top:13px;padding-top:12px;border-top:1px solid var(--line)}

.offer .prow{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap}

.offer .now{font-family:var(--f-dis);font-weight:800;font-size:24px;letter-spacing:-.035em}

.offer .was{font-size:14px;color:var(--ink-4);text-decoration:line-through}

.offer .once{font-size:11.5px;color:var(--ink-3);margin-top:4px}

.offer .buy .btn{width:100%;margin-top:11px;height:48px;
  font-family:var(--f-dis);font-weight:800;font-size:15.5px;letter-spacing:-.012em;
  color:#fff;border:0;position:relative;overflow:hidden;
  background:var(--grad-btn);box-shadow:var(--sh-brand)}

.offer .buy .btn::after{content:'';position:absolute;inset:0;z-index:1;
  pointer-events:none;
  background:linear-gradient(100deg,transparent 38%,rgba(255,255,255,.30) 47%,
    rgba(255,255,255,.42) 50%,rgba(255,255,255,.30) 53%,transparent 62%);
  transform:translateX(-115%);
  animation:freePass 2.8s cubic-bezier(.42,0,.3,1) infinite}

.offer .buy .btn span{position:relative;z-index:2}

.offer .buy .btn:hover{transform:translateY(-2px);
  box-shadow:0 14px 34px -8px rgba(90,69,255,.55)}

.offer .free{display:block;text-align:center;font-size:12px;font-weight:700;
  color:var(--brand);text-decoration:none;margin-top:9px}

.offer .free:hover{text-decoration:underline}

@keyframes freePass{
  0%,6%   {transform:translateX(-115%)}
  60%,100%{transform:translateX(115%)}
}

/* TWO BUTTONS, SIDE BY SIDE, and this is the one place the app's rule is
   overridden rather than followed. Below 400px the app sends every button in
   this row to grid-column:1/-1 — right where the row can hold four buttons and
   two of them are long, and wrong here, where there are exactly two. She raised
   the stacking as a fault on 1 September ("also try again and share button
   becomes 2 rows in narrow mobiles") and approved the pair side by side.
   Scoped to this page's result screen; the app is untouched. */
/* THE SAME WIDTH AS THE PAIR BELOW IT. The app caps .qact-go at 420px, which
   is right in a content area beside the rail and wrong here: on a 720px column
   the gradient button sat at 420 while Try again and Share spanned 680 under
   it, so the one that matters was the narrower of the two rows. On a phone the
   cap never applied anyway -- the column is smaller than 420.
   Scoped to the result screen; the app is untouched. */
.resultwrap .qact-go{max-width:none}

.resultwrap .qact-rest{display:grid;grid-template-columns:1fr 1fr;gap:9px;width:100%}
.resultwrap .qact-rest>.btn{height:44px;font-size:13.6px;width:100%;min-width:0;
  padding:0 12px;grid-column:auto}
@media (max-width:399.98px){
  .resultwrap .qact-rest{grid-template-columns:1fr 1fr}
}
@media (max-width:520px){
  .resultwrap .qact-rest>.btn{padding:0 8px;font-size:12.8px;gap:6px}
  .resultwrap .qact-rest>.btn span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
}

/* ===========================================================================
   THE ONLY OTHER THING THAT IS NOT THE APP'S, and it is here because the app
   draws its quiz inside a shell that already has a header, a rail and page
   padding, and this page has none of that.
   ========================================================================== */
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--f-ui);
  font-size:15px;line-height:1.55;-webkit-font-smoothing:antialiased}

/* ABOVE THE AURORA, the way #app is in the app: position:relative and
   z-index:2, because #aura is fixed at 0 and #grain is fixed at 1. Without
   it #quiz stays static, the grain layer paints OVER the quiz instead of
   under it, and its mix-blend-mode:overlay washes colour across every word
   on the result screen. The app puts every screen inside one #app; this
   page has a header and a main, so both get it. */
#quiz{position:relative;z-index:2}

/* A LIGHTER AURORA THAN THE APP'S. --aura-op is .9 in p01, which is right
   behind an app whose chrome covers most of the screen -- a rail down one side,
   a top bar, a dock. This page is one card on an open field, so far more of the
   aurora is actually visible and .9 reads as heavy. Half of it is still clearly
   the same background, drifting the same way.
   The token is overridden rather than the rules edited, so the lift stays a
   copy and this is one number in each theme.

   ON THE THEME SELECTORS, NOT ON BARE :root. p01 declares it as
   `:root[data-theme="light"]{--aura-op:.9}`, which is (0,1,1); a plain `:root`
   is (0,0,1) and loses however far down the file it sits. The first attempt at
   this did nothing at all and looked like a caching problem. The app gives the
   two themes different values -- .9 and .75 -- so both are halved rather than
   flattened to one number. */
:root[data-theme="light"]{--aura-op:.45}
:root[data-theme="dark"]{--aura-op:.38}

/* THE SITE'S OWN HEADER, from page.css lines 232 and 246-249, verbatim.
   The logo sat hard against the left edge of a desktop screen because .qhead
   was a full-bleed flex row with 16px of padding: the mark was copied and the
   .wrap that holds it to --maxw and centres it was not. The same fault as the
   missing utility classes -- a piece taken without its container. */
:root{--maxw:1140px;--glass:rgba(255,255,255,.78)}
:root[data-theme="dark"]{--glass:rgba(10,14,38,.78)}
/* .barwrap, NOT page.css's .wrap, and this is the second collision of
   exactly the same kind as .bar. `.wrap` means the page container on the
   site and flex-wrap in the app -- `.wrap{flex-wrap:wrap}`, up at line 238.
   The app writes its chip rows as `class="row g10 wrap mt20"`, so giving
   the site's container that name put `margin:0 auto` and a 1140px cap on
   every one of them: the missing gap above "+8 XP  49s" was this rule's
   margin:0 cancelling the mt20 beside it.
   Two stylesheets, two vocabularies. Where they clash the page's own half
   renames, because the lifted half is a copy. */
.barwrap{max-width:var(--maxw);margin:0 auto;padding:0 clamp(16px,4vw,32px)}
.topbar{position:sticky;top:0;z-index:80;background:var(--glass);
  backdrop-filter:blur(20px) saturate(160%);-webkit-backdrop-filter:blur(20px) saturate(160%);
  border-bottom:1px solid var(--line)}
/* SCOPED TO .topbar, AND IT HAS TO BE. `.bar` means two different things in
   the two stylesheets this page now carries: page.css's header row, and the
   app's progress track inside .qtop — <div class="bar"><i id="qBar"></i></div>.
   Unscoped, the header rule made the progress bar 70px tall and laid its fill
   out as a flex row. page.css can use the bare selector because it never loads
   the app's; this file loads both. */
/* .topbar-row, NOT .bar, and the rename is the whole point.
   `.bar` means two different things in the two stylesheets this page
   carries: page.css's header row, and the app's progress track --
   `.bar{height:8px;border-radius:99px;background:var(--ring-track)}`,
   which is up at line 633 and cannot be edited, because the lifted blocks
   are a copy. Giving the header row that class handed it a 99px pill
   background: the "weird half circle" at each end of the menu bar was the
   progress track's rounded ends, drawn 1140px wide behind the wordmark.
   Scoping the header rule was not enough -- the app's rule is unscoped and
   still matched. The name has to differ. */
.topbar .topbar-row{display:flex;align-items:center;justify-content:space-between;gap:16px;height:70px}
@media (max-width:560px){ .topbar .topbar-row{height:62px} }

/* The quiz's own two items on the right of that bar. */
.topbar .daily{margin-left:auto;font-size:11px;font-weight:700;
  color:var(--ink-3);letter-spacing:.03em}
.topbar .iconbtn{flex:0 0 auto}

/* THE SITE'S OWN WORDMARK, from page.css lines 250-258 and 328-329, verbatim.
   This page had a 26px rounded square and a single line of 13.5px text — near
   enough to look right beside the real one and not the same thing. Somebody
   arriving here from the store should be looking at the identical mark, because
   it is the identical mark.
   --grad is page.css's token for it. Same three stops and same angle as the
   app's --grad-brand, but the rules below are copied and a copy that renames
   what it references is not a copy. */
:root{--grad:linear-gradient(135deg,var(--a1) 0%,var(--a2) 48%,var(--a3) 100%)}
.brand{display:flex;align-items:center;gap:10px;min-width:0}
.mark{flex:0 0 auto;border-radius:10px;overflow:hidden;box-shadow:var(--sh-brand);line-height:0}
.wordmark{display:flex;flex-direction:column;line-height:1.03;min-width:0}
.wordmark b{font-family:var(--f-dis);font-weight:600;font-size:12px;color:var(--ink-2);
  white-space:nowrap}
.wordmark i{font-family:var(--f-dis);font-weight:800;font-size:17.5px;font-style:normal;
  letter-spacing:-.03em;background:var(--grad);-webkit-background-clip:text;background-clip:text;
  color:transparent;white-space:nowrap;padding-right:.1em;margin-right:-.1em}
@media (max-width:560px){
  .wordmark b{font-size:10.5px}
  .wordmark i{font-size:15.5px}
}


/* THE LIGHT/DARK BUTTON, the same one every other public page carries. Both
   icons are in the markup and CSS shows whichever the current theme calls for,
   so switching never waits for anything to load -- page.css says exactly this
   and these are its two rules, copied.
   .iconbtn itself comes from the app's own stylesheet above. */
.i-sun{display:none}
:root[data-theme="dark"] .i-sun{display:block}
:root[data-theme="dark"] .i-moon{display:none}

/* Page padding, which #view provides in the app. */
/* The quiz sits in the same .wrap the header does, so the card lines up
   under the wordmark instead of running to the edges of a desktop screen. */
.qwrap{max-width:var(--maxw);margin:0 auto;padding:24px clamp(16px,4vw,32px) 44px}
@media (max-width:520px){ .qwrap{padding:18px 14px 34px} }

/* ---- THE PAGE IS THE WHOLE WINDOW HERE ------------------------------------
   In the app the quiz sits in a content area beside the rail and fills it. On
   this page there is no rail, so a 720px column pinned to the top of a 1440x900
   screen left the card ending at 555px with 350px of nothing under it -- a phone
   layout on a desktop.
   Centred in what is left below the header, from tablet width up. `safe center`
   is doing real work: the result screen is far taller than the window, and plain
   centring would push its top off the screen where it cannot be scrolled back
   to. safe falls back to start the moment the content does not fit. */
@media (min-width:768px){
  /* grid-template-columns is not optional: a grid track defaults to auto, which
     sizes to the content, so .qrun's max-width:720px collapsed to whatever the
     longest option happened to be -- 331px. minmax(0,1fr) gives it the row. */
  .qwrap{min-height:calc(100dvh - 70px);display:grid;
    grid-template-columns:minmax(0,1fr);align-content:safe center}
  /* AND THE COLUMN NEEDS A WIDTH. .qrun is `max-width:720px;margin:0 auto`,
     which centres perfectly in a block container -- but an auto margin on a
     grid item switches off stretch, so it shrank to its longest option: 331px.
     width:100% gives it the track back, and the 720 cap and the auto margins
     then do what they always did. */
  .qwrap>.qrun{width:100%}
}

/* Shown only while the ten are being read. Straight into question one was her
   instruction, so there is no start screen for this to be mistaken for. */
.qload{padding:70px 22px;text-align:center;color:var(--ink-3);font-size:14px}
