*{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
body{background:var(--ground);color:var(--ink);font-family:var(--ui);font-size:17px;line-height:1.5;min-height:100dvh}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
[hidden]{display:none!important}
:focus-visible{outline:2px solid var(--sea);outline-offset:3px;border-radius:var(--r-s)}
.wrap{max-width:33rem;margin:0 auto;padding:0 1rem}

/* ── top bar: lives + progress ── */
.top{position:sticky;top:0;z-index:5;background:var(--ground);padding-top:calc(.6rem + var(--safe-t));padding-bottom:.6rem}
.top-in{display:flex;align-items:center;gap:.75rem;max-width:33rem;margin:0 auto;padding:0 1rem}
.quit{font-size:1.5rem;line-height:1;color:var(--ink-2);padding:.2rem .4rem}
.bar{flex:1;height:14px;background:var(--sea-soft);border-radius:var(--r-pill);overflow:hidden}
.bar i{display:block;height:100%;background:var(--accent);box-shadow:inset 0 -3px 0 color-mix(in srgb,var(--accent) 50%,var(--edge-mix));border-radius:var(--r-pill);transition:width .35s cubic-bezier(.4,0,.2,1)}
.lives{display:flex;gap:.2rem;font-size:1.05rem;letter-spacing:-.05em}
.lives .gone{opacity:.25;filter:grayscale(1)}

/* ── home ── */
/* padding-block only: the shorthand would reset .wrap's side padding
   and the screen would run edge to edge on any phone under 528px. */
.home{padding-block:1.5rem 5rem}
.brand{font-family:var(--display);font-size:2.6rem;font-weight:700;letter-spacing:-.02em;line-height:1}
.tag{color:var(--ink-2);font-size:1rem;margin:.3rem 0 1.5rem}
.hero{display:flex;align-items:center;gap:1rem;margin-bottom:1.25rem}
.hero .brand{font-size:2rem} .hero .tag{margin:.2rem 0 0}
.hero-img{width:6.5rem;height:6.5rem;border-radius:var(--r-l);object-fit:cover;flex:none;background:var(--card)}
.intro{min-height:100dvh;padding:3rem 1rem;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.6rem}
.intro .hero-img{width:11rem;height:11rem;border-radius:var(--r-xl);margin-bottom:.5rem}
.intro-q{font-family:var(--display);font-size:1.35rem;font-weight:700;margin-top:1.2rem}
.intro .typed{max-width:18rem;text-align:center;font-size:1.15rem}
.intro .cta{max-width:18rem;margin-top:.4rem}
.next-k{display:block;font-size:.72rem;text-transform:uppercase;letter-spacing:.08em;opacity:.8}
/* margin-top was -.6rem, to tuck this under the old home screen's heading.
   On its own tab that pulls it into the title (Xavi, 2026-09-19). */
.review{display:block;width:100%;text-align:left;background:var(--gold-soft);border:var(--bw) solid var(--gold);color:var(--ink);border-radius:var(--r-m);padding:.9rem 1.1rem;margin:var(--sp-3) 0 var(--sp-5)}
.review .next-k{color:var(--gold-ink);opacity:1}
.review b{display:block;font-family:var(--display);font-size:1.1rem;font-weight:700;margin:.15rem 0}
.review span.w{display:block;font-size:.88rem;color:var(--ink-2)}
.lvl{font-family:var(--display);font-size:1.3rem;font-weight:700;margin:1.9rem 0 .1rem;display:flex;align-items:baseline;gap:.6rem}
.lvl em{font-style:normal;font-family:var(--ui);font-size:.72rem;font-weight:700;color:var(--ground);background:var(--sea);padding:.2rem .5rem;border-radius:var(--r-pill);letter-spacing:.06em}
.stats{display:flex;gap:.6rem;margin-bottom:1.75rem}
.stat{flex:1;background:var(--card);border:1px solid var(--line);border-radius:var(--r-m);padding:.7rem .8rem}
.stat b{display:block;font-family:var(--display);font-size:1.5rem;font-weight:700;font-variant-numeric:tabular-nums;line-height:1.1}
.stat span{font-size:.78rem;color:var(--ink-2);text-transform:uppercase;letter-spacing:.06em}
.unit-h{font-family:var(--display);font-size:1.05rem;font-weight:700;margin:1.5rem 0 .2rem;display:flex;align-items:baseline;gap:.5rem}
.unit-h em{font-style:normal;font-family:var(--ui);font-size:.72rem;font-weight:600;color:var(--sea);background:var(--sea-soft);padding:.15rem .45rem;border-radius:var(--r-pill);letter-spacing:.04em}
.unit-h .i{align-self:center;font-size:var(--t1);color:var(--ink-2)}
.unit-d{color:var(--ink-2);font-size:.9rem;margin-bottom:.8rem}
.phases{display:flex;flex-direction:column;gap:.5rem}
.phase{display:flex;align-items:center;gap:.8rem;background:var(--card);border:1px solid var(--line);border-radius:var(--r-m);padding:.8rem .9rem;text-align:left;width:100%;box-shadow:var(--sink);transition:transform .08s,box-shadow .08s,border-color .12s}
.phase:active:not(:disabled){transform:translateY(var(--sink-d));box-shadow:var(--sink-down)}
.phase:hover:not(:disabled){transform:translateY(-1px);border-color:var(--sea)}
.phase:disabled{opacity:.5;cursor:not-allowed}
.dot{width:2.3rem;height:2.3rem;border-radius:50%;display:grid;place-items:center;font-weight:700;font-size:.95rem;background:var(--sea-soft);color:var(--on-sea-soft);flex:none}
.phase.on{border-color:var(--accent);background:var(--accent-soft)}
.phase.on:hover:not(:disabled){border-color:var(--accent)}
.phase.on .dot{background:var(--accent);color:var(--accent-ink)}
.board{display:flex;flex-direction:column;gap:.5rem;margin:.4rem 0 1.75rem}
/* Quiet for sign-out; the ONE use of red, for forget and leave (4g). */
.btn-quiet{display:block;width:100%;text-align:left;padding:.8rem .9rem;border-radius:var(--r-m);background:var(--card);border:1px solid var(--line);color:var(--ink-2);box-shadow:var(--sink)}
.btn-quiet:active{transform:translateY(var(--sink-d));box-shadow:var(--sink-down)}
.btn-danger{display:block;width:100%;text-align:left;padding:.8rem .9rem;border-radius:var(--r-m);border:1.5px solid var(--danger-soft);background:var(--danger-soft);color:var(--on-c);color:var(--on-danger-soft);font-weight:700}
.phase .t{flex:1;min-width:0}
.phase .t b{display:block;font-weight:600;font-size:.98rem}
.phase .t span{font-size:.82rem;color:var(--ink-2)}
.chev{color:var(--ink-2);font-size:1.1rem}
.pts{font-weight:700;font-variant-numeric:tabular-nums;flex:none}

/* ── exercise ── */
.ex{padding-block:.5rem 11rem}
.q{font-family:var(--display);font-size:1.4rem;font-weight:700;line-height:1.25;margin-bottom:.35rem;text-wrap:balance}
.hint{color:var(--ink-2);font-size:.92rem;margin-bottom:1.1rem}
.opts{display:flex;flex-direction:column;gap:.55rem}
.opt{background:var(--card);border:1.5px solid var(--line);border-radius:var(--r-m);padding:.85rem 1rem;text-align:left;box-shadow:var(--sink);transition:transform .08s,box-shadow .08s,border-color .12s,background .12s}
.opt:active{transform:translateY(var(--sink-d));box-shadow:var(--sink-down)}
.opt:hover{border-color:var(--sea)}
.opt[aria-checked=true]{border-color:var(--sea);background:var(--sea-soft)}
.opt.ok{border-color:var(--on-ok-soft);background:var(--ok-soft)}
.opt.no{border-color:var(--on-no-soft);background:var(--no-soft)}

/* tap-tokens: the pronoms-febles mechanic */
.text{background:var(--card);border:1px solid var(--line);border-radius:var(--r-m);padding:1rem;font-size:1.13rem;line-height:2.1}
.tok{display:inline;border-radius:var(--r-s);padding:.18em .1em;transition:background .12s,box-shadow .12s}
.tok.on{background:var(--sea-soft);box-shadow:inset 0 0 0 1.5px var(--sea)}
.tok.hit{background:var(--ok-soft);box-shadow:inset 0 0 0 2px var(--on-ok-soft)}
.tok.miss{background:var(--no-soft);box-shadow:inset 0 0 0 2px var(--on-no-soft)}
.tok.missed{box-shadow:inset 0 0 0 2px var(--gold-ink);background:var(--gold-soft)}
.tok[data-w]{cursor:pointer}

/* buckets: gender sorting */
.buckets{display:flex;gap:.6rem;margin-bottom:.9rem}
.bucket{flex:1;min-height:6.5rem;border:1.5px dashed var(--line);border-radius:var(--r-m);padding:.5rem;transition:border-color .12s}
.bucket.live{border-color:var(--sea);border-style:solid}
.bucket h4{font-family:var(--display);font-size:1rem;text-align:center;color:var(--sea-ink);margin-bottom:.4rem}
.bucket .in{display:flex;flex-wrap:wrap;gap:.35rem;justify-content:center}
.pool{display:flex;flex-wrap:wrap;gap:.45rem;justify-content:center;min-height:2.6rem}
.chip{background:var(--card);border:1.5px solid var(--line);border-radius:var(--r-pill);padding:.4rem .85rem;font-size:.97rem;box-shadow:var(--sink);transition:transform .08s,box-shadow .08s,border-color .12s}
.chip:active{transform:translateY(var(--sink-d));box-shadow:var(--sink-down)}
.chip:hover{border-color:var(--sea)}
.chip.sel{border-color:var(--sea);background:var(--sea-soft)}
.chip.ok{border-color:var(--on-ok-soft);background:var(--ok-soft)}
.chip.no{border-color:var(--on-no-soft);background:var(--no-soft)}
.chip.used{opacity:.3;pointer-events:none}

/* build a sentence from tiles */
.slots{min-height:3.6rem;border-bottom:2px solid var(--line);padding:.5rem 0;display:flex;flex-wrap:wrap;gap:.4rem;margin-bottom:1.2rem;align-content:flex-start}

/* fill in the blank */
.gap{display:inline-block;min-width:5.5rem;border-bottom:2.5px solid var(--sea);text-align:center;font-weight:600;color:var(--on-sea-soft);padding:0 .3rem}
.typed{width:100%;background:var(--card);border:1.5px solid var(--line);border-radius:var(--r-m);padding:.85rem 1rem;font:inherit;color:inherit}
.typed:focus{border-color:var(--sea);outline:0}

/* distribute: N confusable forms into N gaps */
.gapbtn{display:inline-block;min-width:4.2rem;border:0;border-bottom:2.5px solid var(--sea);
        background:var(--sea-soft);border-radius:6px 6px 0 0;padding:.05rem .45rem;margin:0 .1rem;
        font-weight:600;color:var(--on-sea-soft);font-size:.97em}
.gapbtn.ok{border-bottom-color:var(--ok);background:var(--ok-soft);color:var(--on-ok-soft)}
.gapbtn.no{border-bottom-color:var(--no);background:var(--no-soft);color:var(--on-no-soft)}

/* match pairs */
.pairs{display:grid;grid-template-columns:1fr 1fr;gap:.5rem}

/* listen */
.play{display:flex;align-items:center;justify-content:center;gap:.6rem;width:100%;background:var(--sea);color:var(--on-c);color:color-mix(in srgb,var(--sea) 25%,#000);border-radius:var(--r-m);padding:1.4rem;font-size:1.05rem;font-weight:600;margin-bottom:1rem}
.play:active{transform:translateY(var(--sink-d));box-shadow:var(--sink-down)}

/* ── footer verdict ── */
.foot{position:fixed;left:0;right:0;bottom:0;z-index:6;border-top:1px solid var(--line);background:var(--ground);padding:.9rem 0 calc(.9rem + var(--safe-b));transition:background .2s}
.foot.ok{background:var(--ok-soft);border-top-color:var(--ok)}
.foot.no{background:var(--no-soft);border-top-color:var(--no)}
.foot.save{background:var(--gold-soft);border-top-color:var(--gold)}
.foot-in{max-width:33rem;margin:0 auto;padding:0 1rem;display:flex;flex-direction:column;gap:.7rem}
.verdict{display:flex;align-items:flex-start;gap:.6rem}
.verdict .ic{font-size:1.5rem;line-height:1.1}
.verdict b{display:block;font-family:var(--display);font-size:1.1rem}
.verdict p{font-size:.92rem;color:var(--ink);margin-top:.15rem}
.verdict .sol{color:var(--ink);font-weight:600}
.verdict .why{font-weight:600}
.cta{--btn:var(--accent);background:var(--accent);color:var(--accent-ink);border-radius:var(--r-m);padding:.95rem;font-weight:800;font-size:1.05rem;width:100%;letter-spacing:.01em;box-shadow:var(--sink);transition:transform .08s,box-shadow .08s}
.cta:active:not(:disabled){transform:translateY(var(--sink-d));box-shadow:var(--sink-down)}
.cta:disabled{opacity:.35}
.foot.ok .cta{--btn:var(--ok);background:var(--ok);color:var(--on-c);color:color-mix(in srgb,var(--ok) 30%,#000)}
.foot.no .cta{--btn:var(--no);background:var(--no);color:var(--on-c);color:color-mix(in srgb,var(--no) 30%,#000)}
.foot.save .cta{--btn:var(--gold);background:var(--gold);color:var(--on-c);color:color-mix(in srgb,var(--gold) 30%,#000)}

/* ── end of lesson ── */
.done{text-align:center;padding-block:2rem 3rem}
.done h2{font-family:var(--display);font-size:2.1rem;font-weight:900;line-height:1.1;margin-bottom:.3rem}
.done.win h2{color:var(--gold-ink)}
.done p{color:var(--ink-2);margin-bottom:1.6rem}
.score{display:flex;gap:.6rem;margin-bottom:1.8rem;flex-wrap:wrap} .score .stat{flex:1 1 calc(50% - .3rem)}
.weak{background:var(--card);border:1px solid var(--line);border-radius:var(--r-m);padding:.9rem;text-align:left;margin-bottom:1.5rem}
.weak b{font-size:.78rem;text-transform:uppercase;letter-spacing:.06em;color:var(--ink-2)}
.weak ul{margin:.5rem 0 0;padding-left:1.1rem;font-size:.95rem}

/* ── language picker ── */
/* Icons size from font-size and colour from currentColor, so they drop into
   every rule that previously sized a glyph without touching it. */
.i{display:inline-flex;align-items:center;justify-content:center;line-height:0}
.i svg{width:1em;height:1em;display:block}
.i.fill svg{fill:currentColor;stroke:none}

.langs{display:flex;gap:.4rem;justify-content:center;flex-wrap:wrap;margin:.2rem 0 1.25rem}
/* The line above the pills. It says WHICH language is being chosen - the one
   the explanations are written in, not the one being learned - because on a
   Catalan course a bare picker reads as "what do you want to study?". */
.lang-q{color:var(--ink-2);font-size:var(--t-2);text-align:center;margin-bottom:var(--sp-2)}
/* A REAL SURFACE. This was transparent, so the shared cartoon edge at the
   end of this file mixed an edge from --card and hung it under a button
   that had no card - a shadow with nothing above it. Values from the
   scales, like every rule added since the design system landed. */
.lang{background:var(--card);border:var(--bw) solid var(--line);border-radius:var(--r-pill);
      padding:var(--sp-2) var(--sp-4);font-size:var(--t-1);font-weight:700;
      color:var(--ink-2);transition:border-color .12s,color .12s,background .12s}
.lang:hover{border-color:var(--sea);color:var(--ink)}
.lang.on{border-color:var(--accent);background:var(--accent-soft);color:var(--on-accent-soft);font-weight:700}

/* ══ the path ══
   Nodes are absolutely positioned; the connector is an SVG drawn through the
   same coordinates, so the road is always exactly where the nodes are rather
   than a guess made in CSS. */
.app{padding-bottom:5.5rem}
.path{position:relative;margin:.4rem 0 2rem}
/* svg.road, not svg: the node icons are svgs inside .path (a descendant
   selector ate them once), and the mascot is an svg that is a DIRECT CHILD of
   .path (a direct-child selector ate it too - the giant oval of 2026-09-19).
   Only the road wears the class, so only the road gets these rules. */
/* A fixed 300px box centred under the nodes, viewBox == box, so the road is
   never scaled: scaling a thick stroke non-uniformly is what smeared it into
   an oval on Android, and it misaligned the road from the discs everywhere. */
.path > svg.road{position:absolute;top:0;bottom:0;left:50%;width:300px;margin-left:-150px;height:100%;pointer-events:none}
.path > svg.road path{fill:none;stroke:var(--road);stroke-width:var(--road-w);
  stroke-opacity:var(--road-o);stroke-linecap:round;stroke-linejoin:round;vector-effect:non-scaling-stroke}
/* A disc with a bottom edge in a darker shade of itself. Two box-shadow
   declarations on purpose: the first is the fallback, the second wins where
   color-mix exists. Glyphs are ink, not white: see --on-c. */
.node{position:absolute;width:66px;height:66px;margin-left:-33px;border-radius:50%;display:grid;place-items:center;
      font-weight:900;font-size:1.4rem;color:var(--on-c);color:color-mix(in srgb,var(--c,var(--sea)) 42%,#000);background:var(--c,var(--sea));
      box-shadow:0 6px 0 var(--edge);
      box-shadow:0 6px 0 color-mix(in srgb,var(--c,var(--sea)) 72%,var(--edge-mix));
      transition:transform .1s,box-shadow .1s}
.node .i{font-size:1.75rem}
.node:active:not(:disabled){transform:translateY(4px);
      box-shadow:0 2px 0 var(--edge);
      box-shadow:0 2px 0 color-mix(in srgb,var(--c,var(--sea)) 72%,var(--edge-mix))}
.node:disabled{background:var(--lock);color:var(--lock-ink);box-shadow:0 6px 0 var(--lock-edge);cursor:not-allowed}
.node.now{z-index:1}
.node.now::after{content:'';position:absolute;top:-5px;right:-9px;bottom:-13px;left:-9px;border-radius:50%;
                 border:4px solid var(--c,var(--sea));opacity:.4;animation:halo 1.6s ease-in-out infinite}
@keyframes halo{0%,100%{transform:scale(1);opacity:.4}50%{transform:scale(1.07);opacity:.7}}
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}
@media (prefers-reduced-motion:reduce){.node.now::after,.start,.path-pet,.spark{animation:none}}
/* A chip, not bare text: the label hangs at the midpoint of a road segment,
   and only a VISIBLE occluder reads as "the road carries on behind this". */
.node-l{position:absolute;text-align:center;font-size:.76rem;font-weight:800;color:var(--ink-2);
        width:max-content;max-width:150px;transform:translateX(-50%);line-height:1.25;
        background:var(--card);padding:.3rem .6rem;border-radius:var(--r-m);box-shadow:var(--sink-s)}
.node-l small{display:block;font-weight:600;font-size:.68rem;margin-top:.1rem}

/* ══ bottom tabs ══ */
.tabs{position:fixed;left:0;right:0;bottom:0;z-index:7;background:var(--card);
      border-top:2px solid var(--line);display:flex;
      padding-bottom:var(--safe-b)}
.tab{flex:1;padding:.4rem .2rem .45rem;display:flex;flex-direction:column;align-items:center;gap:.1rem;
     font-size:.66rem;font-weight:800;color:var(--ink-2);letter-spacing:.03em;text-transform:uppercase}
.tab .i{font-size:1.55rem;width:3.1rem;height:2.3rem;border-radius:var(--r-m);border:2px solid transparent;
        transition:opacity .15s,background .15s,border-color .15s,transform .15s}
.tab:active .i{transform:scale(.92)}
/* The label stays ink (the level colours are too light for 10px text on the
   card); the tab's own colour lives on the icon and its tinted box. */
.tab.on{color:var(--ink)}
.tab.on .i{opacity:1;color:var(--ink);color:color-mix(in srgb,var(--tc,var(--accent)) 70%,var(--ink));border-color:var(--tc,var(--accent));background:var(--accent-soft);
           background:color-mix(in srgb,var(--tc,var(--sea)) 14%,transparent)}

/* ══ empty states ══ */
.empty{text-align:center;color:var(--ink-2);background:var(--card);border:2px dashed var(--line);
       border-radius:var(--r-l);padding:2rem 1.2rem;margin:1rem 0}

/* ══ the rules card, before the drill ══ */
.teach{padding-block:.5rem 7rem}
.teach h2{font-size:1.5rem;font-weight:900;line-height:1.2;margin-bottom:.2rem}
.teach .kick{font-size:.72rem;font-weight:800;letter-spacing:.09em;text-transform:uppercase;
             color:var(--sea)}
.rule{background:var(--card);border:2px solid var(--sea-soft);border-left:6px solid var(--sea);
      border-radius:var(--r-m);padding:1rem 1.1rem;margin:1rem 0 1.2rem;font-size:1rem;line-height:1.55}
.egs{display:flex;flex-direction:column;gap:.5rem}
.eg{display:flex;align-items:baseline;gap:.7rem;background:var(--card);border:1px solid var(--line);
    border-radius:var(--r-m);padding:.7rem .9rem}
.eg b{font-size:1.08rem;font-weight:800;color:var(--sea-ink);white-space:nowrap}
.eg span{font-size:.88rem;color:var(--ink-2);text-align:right;flex:1}
.empty b{display:block;color:var(--ink);font-size:1.05rem;font-weight:800;margin-bottom:.3rem}

/* ── game chrome, synthesised 2026-09-18 ── */
/* ══ game chrome (2026-09-18) ══
   The Duolingo path structure in our palette. Every saturated surface uses
   --on-c for its text and a color-mix edge with an --edge fallback. */
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* status row: three counters. The flame is lit or grey; it never burns down. */
.status{position:sticky;top:0;z-index:5;display:flex;gap:.5rem;background:var(--ground);
        padding-block:calc(.6rem + var(--safe-t)) .7rem;margin-bottom:.2rem}
.pill{display:inline-flex;align-items:center;gap:.35rem;background:var(--card);border:2px solid var(--line);
      border-radius:var(--r-pill);padding:.28rem .75rem .28rem .6rem;font-weight:900;font-size:.95rem;color:var(--ink);
      font-variant-numeric:tabular-nums;position:relative}
.pill .i{font-size:1.2rem}
.pill.flame .i{color:var(--flame)}
.pill.xp .i{color:var(--gold-ink)}
.pill.save .i{color:var(--sea-2)}
.pill.off .i{color:var(--ink-2)}
.app .hero{margin:.3rem 0 .4rem}

/* section sign: one card per level, in that level's colour. The mosaic row
   is the trencadis motif as a progress readout: a piece per phase, lit when
   finished, and pieces only ever get added. */
.sec{display:flex;align-items:center;gap:.8rem;background:var(--c,var(--sea));color:var(--on-c);color:color-mix(in srgb,var(--c,var(--sea)) 25%,#000);
     border-radius:var(--r-l);padding:1rem 1.1rem;margin:1.5rem 0 .2rem;
     box-shadow:0 5px 0 var(--edge);
     box-shadow:0 5px 0 color-mix(in srgb,var(--c,var(--sea)) 72%,var(--edge-mix))}
.sec-t{flex:1;min-width:0}
.sec-t em{display:block;font-style:normal;font-size:.7rem;font-weight:900;letter-spacing:.09em;text-transform:uppercase}
.sec-t b{display:block;font-family:var(--display);font-size:1.3rem;font-weight:900;line-height:1.15;margin:.1rem 0}
.sec-t span{display:flex;align-items:center;flex-wrap:wrap;font-size:.82rem;font-weight:800}
.mosaic{display:inline-flex;flex-wrap:wrap;gap:3px;margin-right:.45rem;font-size:.9rem}
.mosaic .i{opacity:.6}
.mosaic .i.on{opacity:1}
.sec-go{flex:none;display:inline-flex;align-items:center;gap:.4rem;background:var(--paper);color:var(--on-c);color:color-mix(in srgb,var(--c,var(--sea)) 25%,#000);
        border:2px solid var(--paper);border-radius:var(--r-m);padding:.55rem .85rem;font-size:.8rem;font-weight:900;
        letter-spacing:.06em;text-transform:uppercase;transition:transform .08s;position:relative}
.sec-go .i{font-size:1.1rem}
button.sec-go:active{transform:translateY(2px)}

/* the dragon beside the current node, and the start bubble under it */
.path-pet{position:absolute;width:64px;height:64px;margin-left:-32px;border-radius:50%;object-fit:cover;
          border:3px solid var(--card);box-shadow:var(--sink);background:var(--card);pointer-events:none;
          animation:bob 2.2s ease-in-out infinite .4s}
.start{position:absolute;width:200px;margin-left:-100px;z-index:1;background:var(--card);border:2px solid var(--line);
       border-radius:var(--r-m);padding:.7rem .8rem .75rem;text-align:center;box-shadow:var(--sink);
       animation:bob 1.6s ease-in-out infinite}
.start::before{content:'';position:absolute;top:-9px;left:50%;width:14px;height:14px;margin-left:-7px;
               background:var(--card);border-left:2px solid var(--line);border-top:2px solid var(--line);transform:rotate(45deg)}
.start b{display:block;font-family:var(--display);font-size:.95rem;font-weight:800;line-height:1.2;margin-bottom:.15rem;color:var(--ink)}
.start small{display:block;font-size:.72rem;font-weight:700;color:var(--ink-2);margin-bottom:.55rem}
.start .go{display:block;width:100%;background:var(--c,var(--sea));color:var(--on-c);color:color-mix(in srgb,var(--c,var(--sea)) 25%,#000);border-radius:var(--r-m);padding:.6rem;
           font-size:.82rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase;
           box-shadow:0 4px 0 var(--edge);
           box-shadow:0 4px 0 color-mix(in srgb,var(--c,var(--sea)) 72%,var(--edge-mix));transition:transform .08s,box-shadow .08s}
.start .go:active{transform:translateY(3px);box-shadow:none}

/* teach header: the level colour carries from the road into the lesson */
.teach-h{display:flex;align-items:center;gap:.8rem;background:var(--c,var(--sea));color:var(--on-c);color:color-mix(in srgb,var(--c,var(--sea)) 25%,#000);
         border-radius:var(--r-l);padding:1rem 1.1rem;margin:.4rem 0 1rem;
         box-shadow:0 5px 0 var(--edge);
         box-shadow:0 5px 0 color-mix(in srgb,var(--c,var(--sea)) 72%,var(--edge-mix))}
.teach-t{flex:1;min-width:0}
.teach-h .kick{color:var(--on-c);color:color-mix(in srgb,var(--c,var(--sea)) 25%,#000)}
.teach-h .tag-ph{display:inline-block;margin-top:.45rem;background:var(--glass);border-radius:var(--r-pill);padding:.18rem .6rem;
                 font-size:.7rem;font-weight:900;letter-spacing:.06em;text-transform:uppercase}
.teach-pet{width:4.6rem;height:4.6rem;border-radius:var(--r-l);object-fit:cover;flex:none;background:var(--card);
           border:3px solid var(--glass)}

/* lesson complete: the celebrate pose, two sparkles, three tally chips.
   Never a sad pose: a fail shows the stage art unadorned. */
.done-pet{position:relative;width:9.5rem;height:9.5rem;margin:0 auto 1rem}
.done-pet .mascot{width:100%;height:100%;border-radius:var(--r-xl);object-fit:cover;background:var(--card);box-shadow:var(--sink)}
.spark{position:absolute;color:var(--gold);font-size:2rem;animation:twinkle 1.4s ease-in-out infinite}
.spark.a{top:-.6rem;left:-1.2rem}
.spark.b{top:.6rem;right:-1.4rem;font-size:1.4rem;animation-delay:.5s}
@keyframes twinkle{0%,100%{transform:scale(.8) rotate(-8deg);opacity:.6}50%{transform:scale(1.1) rotate(8deg);opacity:1}}
.tally{display:flex;gap:.6rem;justify-content:center;margin:1.2rem 0 1.6rem}
.chip-t{flex:1 1 0;max-width:8.5rem;background:var(--tc,var(--sea));border-radius:var(--r-m);padding:3px;
        box-shadow:0 4px 0 var(--edge);
        box-shadow:0 4px 0 color-mix(in srgb,var(--tc,var(--sea)) 72%,var(--edge-mix))}
.chip-t em{display:block;font-style:normal;font-size:.62rem;font-weight:900;letter-spacing:.08em;text-transform:uppercase;
           color:var(--on-c);color:color-mix(in srgb,var(--tc,var(--sea)) 30%,#000);padding:.3rem 0 .25rem}
.chip-t b{display:flex;align-items:center;justify-content:center;gap:.3rem;background:var(--card);border-radius:var(--r-m);
          padding:.6rem .4rem;font-family:var(--display);font-size:1.25rem;font-weight:900;color:var(--ink);
          font-variant-numeric:tabular-nums}
.chip-t b .i{font-size:1.1rem;opacity:.8}

/* Thin overlay-style scrollbar, like macOS (Xavi, 2026-09-18). Phones already
   overlay theirs; this is for desktop and the harness. Firefox via
   scrollbar-width, Chromium/Safari via ::-webkit-scrollbar. */
html{scrollbar-width:thin;scrollbar-color:var(--line) transparent}
::-webkit-scrollbar{width:6px;height:6px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--line);border-radius:var(--r-pill);border:1px solid transparent;background-clip:padding-box}
::-webkit-scrollbar-thumb:hover{background:var(--ink-2);background-clip:padding-box}

/* ── the mascot: blink and bob, both off under reduced motion ── */
.mascot{display:block}
.m-blinks{transform-origin:50px 51px;animation:m-blink 5.2s ease-in-out infinite}
@keyframes m-blink{0%,93%,100%{transform:scaleY(1)}96%{transform:scaleY(.08)}}
.hero-img.mascot,.done-pet .mascot{animation:bob 2.6s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){.m-blinks,.hero-img.mascot,.done-pet .mascot{animation:none}}

/* ── level test result: one bar per level, in that level's colour.
      Built only from tokens - the template for any rule added from here on. ── */
.meters{display:flex;flex-direction:column;gap:var(--sp-2);margin:0 0 var(--sp-5);text-align:left}
.meter{display:flex;align-items:center;gap:var(--sp-3);background:var(--card);border:var(--bw) solid var(--line);border-radius:var(--r-m);padding:var(--sp-2) var(--sp-3)}
.meter b{flex:none;width:2rem;font-size:var(--t-1);font-weight:800}
.meter .bar{background:var(--line)}
.meter .bar i{background:var(--c,var(--sea));box-shadow:none}
.meter em{flex:none;font-style:normal;font-size:var(--t-2);font-weight:800;color:var(--ink-2);font-variant-numeric:tabular-nums}
.weak .sol{display:block;font-weight:800;font-size:var(--t-1)}
.weak li{margin-bottom:var(--sp-3);color:var(--ink-2);font-size:var(--t-1)}
.weak li .sol{color:var(--ink)}

/* ── game feel (2026-09-19): the movement a moment makes ──
   Every keyframe here is a REACTION to something the learner did. Nothing
   loops, nothing draws attention to itself while they are reading, and the
   whole block is switched off under prefers-reduced-motion at the end. */
@keyframes pop{0%{transform:scale(1)}40%{transform:scale(1.06)}100%{transform:scale(1)}}
@keyframes shake{0%,100%{transform:translateX(0)}15%{transform:translateX(-7px)}30%{transform:translateX(6px)}
                 45%{transform:translateX(-4px)}60%{transform:translateX(3px)}80%{transform:translateX(-2px)}}
@keyframes rise{0%{transform:translateY(10px);opacity:0}100%{transform:translateY(0);opacity:1}}
@keyframes chipin{0%{transform:scale(.7) translateY(8px);opacity:0}60%{transform:scale(1.05) translateY(0)}100%{transform:scale(1);opacity:1}}
@keyframes petpop{0%{transform:scale(.5) rotate(-8deg);opacity:0}55%{transform:scale(1.08) rotate(3deg)}100%{transform:scale(1) rotate(0);opacity:1}}
@keyframes fall{0%{transform:translate3d(0,-12vh,0) rotate(0);opacity:1}
                100%{transform:translate3d(var(--x,0),92vh,0) rotate(var(--r,180deg));opacity:0}}
@keyframes glowgold{0%,100%{box-shadow:0 0 0 0 rgba(255,179,86,0)}45%{box-shadow:0 0 0 7px rgba(255,179,86,.32)}}

/* the answer itself, at the moment it is marked */
.opt.ok,.chip.ok,.gapbtn.ok,.tok.ok{animation:pop .32s ease-out}
.ex.shake{animation:shake .4s ease-in-out}
.verdict{animation:rise .22s ease-out}
.foot.save .cta{animation:glowgold .9s ease-out}

/* the done screen, assembled rather than just present */
.done-pet{animation:petpop .5s cubic-bezier(.34,1.4,.5,1) both}
.done .chip-t{animation:chipin .38s ease-out both}
.done .chip-t:nth-child(1){animation-delay:.16s}
.done .chip-t:nth-child(2){animation-delay:.26s}
.done .chip-t:nth-child(3){animation-delay:.36s}
.done h2{animation:rise .32s ease-out both;animation-delay:.06s}

/* confetti: shards owned by the screen that spawned them, removed after 3.2s */
.confetti{position:fixed;inset:0;pointer-events:none;z-index:20;overflow:hidden}
.confetti i{position:absolute;top:0;width:9px;height:14px;border-radius:2px;animation:fall linear both}

@media (prefers-reduced-motion:reduce){
  .opt.ok,.chip.ok,.gapbtn.ok,.tok.ok,.ex.shake,.verdict,.foot.save .cta,
  .done-pet,.done .chip-t,.done h2,.confetti i{animation:none}
  .done .chip-t,.done h2,.done-pet{opacity:1;transform:none}
}

/* ── mascot expressions (2026-09-19) ──
   One class per animated face, set by mascot() as .m-a-<anim>. Each is a
   REACTION and each runs once; nothing here loops in the learner's peripheral
   vision while they are reading. Switched off under reduced motion at the end. */
@keyframes m-cheer{0%{transform:scale(1)}30%{transform:scale(1.12) translateY(-4px)}55%{transform:scale(.97)}100%{transform:scale(1)}}
@keyframes m-nod{0%,100%{transform:rotate(0)}35%{transform:rotate(-9deg)}70%{transform:rotate(4deg)}}
@keyframes m-oops{0%,100%{transform:translateX(0)}20%{transform:translateX(-4px)}45%{transform:translateX(4px)}70%{transform:translateX(-2px)}}
@keyframes m-drip{0%{transform:translateY(-4px);opacity:0}30%{opacity:1}100%{transform:translateY(9px);opacity:0}}
@keyframes m-spin{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}
.m-a-cheer{animation:m-cheer .62s cubic-bezier(.34,1.5,.5,1)}
.m-a-nod .m-head{animation:m-nod .55s ease-in-out;transform-origin:50px 60px}
.m-a-oops{animation:m-oops .45s ease-in-out}
.m-sweat{animation:m-drip 1.5s ease-in .35s infinite}
.m-sparks{animation:twinkle 1.5s ease-in-out infinite}
/* The dizzy dot orbits INSIDE its own eyeball, so each side needs its own
   origin in user units. A circle spun about its own centre shows nothing,
   which is why the dot is off centre in the first place. */
.m-orb{transform-box:view-box;animation:m-spin 1.6s linear infinite}
.m-orb-l{transform-origin:37px 51px}
.m-orb-r{transform-origin:63px 51px}
/* the reacting face in the exercise footer, beside the verdict text */
.verdict .ic .mascot{display:block;width:2.5rem;height:2.5rem;margin-top:-.15rem}
@media (prefers-reduced-motion:reduce){
  .m-a-cheer,.m-a-oops,.m-a-nod .m-head,.m-sweat,.m-sparks,.m-orb{animation:none}
  .m-sweat{opacity:1}
}

/* ══ DRAG AND DROP (2026-09-20) ══
   touch-action:none is REQUIRED on anything draggable or the browser pans
   the page instead of sending the move events. It is scoped to the chips
   and tiles themselves, never to a container, so every screen still
   scrolls normally everywhere else. */
.drag{touch-action:none;-webkit-user-select:none;user-select:none}
body.dnd-on{cursor:grabbing}
/* The ghost is a clone: fixed, above everything, and pointer-events:none so
   that elementFromPoint returns what is UNDER it rather than the ghost. */
.dnd-ghost{position:fixed;top:0;left:0;z-index:30;pointer-events:none;margin:0;
           opacity:.96;box-shadow:0 12px 24px rgba(21,48,63,.3)}
.dnd-src{opacity:.3}
/* The target says yes before you let go. Tinted, not just outlined: an
   outline alone is invisible under a thumb. */
.dnd-over{border-color:var(--sea)!important;background:var(--sea-soft)!important}
.bucket.dnd-over{border-style:solid}
.slots.dnd-over,.pool.dnd-over{border-radius:var(--r-m);outline:2px dashed var(--sea);outline-offset:3px}
.gap.dnd-over,.gapbtn.dnd-over{color:var(--on-sea-soft)}
@media (prefers-reduced-motion:reduce){.dnd-ghost{box-shadow:none}}

/* ══ THE CARTOON EDGE (2026-09-19) ══
   Xavi: "all buttons should have the same shadow on the bottom, cartooney
   like the mascot, this should be in the design system."

   A button's edge is a darker shade of ITS OWN background, never a grey drop
   shadow, so the colour stays in the family - the same relationship the
   mascot's --m-shade has to --m-body. 72% against --edge-mix is not a new
   number: .node, .sec-go, .start .go and .chip-t already used it.

   TO GIVE A NEW BUTTON AN EDGE: add it to these selectors and set --btn to
   its background. Never write a box-shadow by hand.
   The formula is repeated in the two rules below rather than held in a token
   because a :root custom property containing var(--btn) resolves against
   :root's --btn, not the button's - so every button would share one edge. */
.cta,.opt,.chip,.phase,.btn-quiet,.btn-danger,.lang,.play,.review,.gapbtn,.consent-btn,.country-btn,.share,.upd-go{
  box-shadow:0 var(--sink-d) 0 color-mix(in srgb,var(--btn) 72%,var(--edge-mix));
  transition:transform .08s,box-shadow .08s,border-color .12s,background .12s}
.cta:active:not(:disabled),.opt:active,.chip:active,.phase:active:not(:disabled),
.btn-quiet:active,.btn-danger:active,.lang:active,.play:active,.review:active,.gapbtn:active,.consent-btn:active,.country-btn:active,.share:active,.upd-go:active{
  transform:translateY(var(--sink-d));
  box-shadow:0 1px 0 color-mix(in srgb,var(--btn) 72%,var(--edge-mix))}
/* each raised control names its own surface; everything else inherits --card */
.cta{--btn:var(--accent)}
.foot.ok .cta{--btn:var(--ok)} .foot.no .cta{--btn:var(--no)} .foot.save .cta{--btn:var(--gold)}
.play{--btn:var(--sea)}
.btn-danger{--btn:var(--danger-soft)}
.lang.on{--btn:var(--accent-soft)}
.review{--btn:var(--gold-soft)}
.gapbtn.ok{--btn:var(--ok-soft)} .gapbtn.no{--btn:var(--no-soft)}
/* Deliberately FLAT, and this is the rule rather than an omission: the raised
   edge means "this performs an action". The tabs change where you are, the
   pills report a number, the quit/rules glyphs are bare icons. Xavi asked
   whether the bottom tabs should be game-styled too - this is the argued no. */
.tab,.pill,.quit{box-shadow:none}

/* ── cookie consent (app/consent.js) ──────────────────────────────────
   Only ever on screen while CONFIG.ga4 is set AND nobody has answered yet.
   Sits ABOVE the tab bar but does not cover it: the course stays usable with
   the question open, because a consent given to clear a blocking wall is not
   freely given. */
/* The scrim: this is what disables the page. Pointer events land here, not on
   the course behind it. It does not dismiss on click - there is no silent
   third answer - and it blocks until ANSWERED, never until accepted, which is
   the difference between a modal and an unlawful cookie wall. */
.consent-wrap{position:fixed;inset:0;z-index:60;display:flex;
  align-items:flex-end;justify-content:center}
.consent-scrim{position:absolute;inset:0;background:rgba(8,18,26,.62);
  backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px)}
@media (prefers-reduced-motion:reduce){.consent-scrim{backdrop-filter:none;-webkit-backdrop-filter:none}}
.consent{position:relative;z-index:1;width:100%;
  background:var(--card);border-top:1px solid var(--line);
  padding:var(--sp-4) 1rem calc(var(--sp-4) + env(safe-area-inset-bottom));
  box-shadow:0 -8px 24px rgba(0,0,0,.28);
  max-width:33rem;margin:0 auto;border-radius:var(--r-l) var(--r-l) 0 0}
.consent-t{font-weight:800;font-size:var(--t1);margin:0 0 .35rem;color:var(--ink)}
.consent-b{font-size:var(--t0);line-height:1.5;margin:0 0 var(--sp-3);color:var(--ink-2)}
/* Side by side and equal. Wrapping to one column on a narrow screen keeps them
   equal too - never stack accept above reject, which makes one the default. */
.consent-row{display:flex;gap:.6rem;flex-wrap:wrap}
.consent-btn{flex:1 1 9rem;border:0;border-radius:var(--r-m);
  padding:.85rem 1rem;font:800 var(--t0)/1.2 inherit;cursor:pointer}
.consent-yes{--btn:var(--accent);background:var(--accent);color:var(--accent-ink)}
/* The refusing button is a real button on a real surface, the same size and
   weight as the accepting one. Only the colour differs. */
.consent-no{--btn:var(--card);background:var(--card);color:var(--ink);
  border:1px solid var(--line)}
.consent-link{display:inline-block;margin-top:var(--sp-3);
  font-size:var(--t0);color:var(--sea-ink)}

/* ── sign-in on the first screen (home.js drawIntro) ──────────────────
   Below the start button and quieter than it: an account is an offer, never
   a gate. Absent entirely when app/config.js is unconfigured. */
.intro-or{margin:var(--sp-4) 0 var(--sp-2);color:var(--ink-2);font-size:var(--t0)}
/* 18rem, matching .intro .typed and .intro .cta above. Without it .btn-quiet
   keeps its full-width block layout and runs edge to edge on a desktop while
   the button above it sits at 18rem - the intro constrains its children one by
   one, so a new child has to opt in. */
.intro .btn-quiet{max-width:18rem;width:100%;margin-top:0}
.intro-auth{display:flex;justify-content:center;min-height:44px;
  max-width:18rem;width:100%}


/* ── country picker (app/country.js) ─────────────────────────────────
   The trigger is an ACTION, so it wears the shared edge like every other
   button. The rows inside the panel deliberately do NOT: they are a list, and
   250 raised slabs scrolling past would fight everything else on screen. The
   rule in the design system is that a raised edge means "this performs an
   action" - opening the picker is one, scrolling a list is not. */
.flag{width:1.75rem;height:1.75rem;border-radius:50%;flex:none;
      display:inline-block;vertical-align:middle;background:var(--line)}
/* Beside the learner's own name, where it is an accent rather than a label. */
.flag-name{width:1.25rem;height:1.25rem;margin-left:.5rem}
.flag-none{background:var(--line);opacity:.5}
.country{position:relative;width:100%}
.country-btn{--btn:var(--card);display:flex;align-items:center;gap:.6rem;
  width:100%;text-align:left;padding:.7rem .9rem;border-radius:var(--r-m);
  background:var(--card);border:1px solid var(--line);color:var(--ink);
  font:700 var(--t0)/1.2 inherit}
.country-btn .i{margin-left:auto;flex:none;transition:transform .12s}
/* One glyph, turned, rather than a second icon meaning the same thing. */
.country-btn[aria-expanded="true"] .i{transform:rotate(90deg)}
.country-l{display:flex;align-items:center;gap:.6rem;min-width:0}
.country-l>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.country-panel{margin-top:var(--sp-2);background:var(--card);
  border:1px solid var(--line);border-radius:var(--r-m);overflow:hidden}
.country-q{margin:.6rem;width:calc(100% - 1.2rem)}
.country-list{max-height:15rem;overflow-y:auto}
.country-row{display:flex;align-items:center;gap:.6rem;width:100%;
  text-align:left;padding:.55rem .9rem;background:none;border:0;
  color:var(--ink);font:600 var(--t-1)/1.3 inherit}
.country-row:hover,.country-row:focus-visible{background:var(--sea-soft)}
.country-row.on{background:var(--accent-soft);color:var(--on-accent-soft);font-weight:800}
/* .intro constrains its children ONE BY ONE - a new child runs edge to edge
   on a desktop unless it opts in, which is the trap CLAUDE.md records costing
   a round on the sign-in button. 18rem matches .typed and .cta above it. */
.intro .country{max-width:18rem}


/* ── share (app/share.js) ────────────────────────────────────────────
   Secondary to the CTA above it and quieter than it: continuing the course is
   the primary action on this screen, sharing is an offer. It still wears the
   shared edge, because it performs an action - that is the rule the design
   system states, and "quieter" is carried by colour and weight instead. */
.share{display:inline-flex;align-items:center;justify-content:center;gap:.5rem;
  --btn:var(--card);background:var(--card);border:1px solid var(--line);
  color:var(--ink-2);border-radius:var(--r-m);padding:.7rem 1.3rem;
  margin-top:var(--sp-3);font:800 var(--t-1)/1.2 inherit}
.share .i{font-size:1.05rem}


/* ── the update bar (app/pwa.js) ─────────────────────────────────────
   NOT the consent banner. No scrim, nothing blocked, dismissible, and
   ignoring it costs nothing because the update lands anyway once every tab
   is closed. Consent had to be answered before anything could lawfully
   happen; an update is an offer, and a second blocking modal in the same app
   would teach people to dismiss both without reading either.

   Top rather than bottom: the tab bar owns the bottom edge, and a bar above
   it would either cover the tabs or shift the whole layout at the exact
   moment someone is reaching for one. */
.upd{position:fixed;top:0;left:0;right:0;z-index:50;
  display:flex;align-items:center;gap:.6rem;
  padding:calc(env(safe-area-inset-top) + .6rem) 1rem .6rem;
  background:var(--accent);color:var(--accent-ink);
  box-shadow:0 2px 10px rgba(0,0,0,.18);animation:updin .28s ease-out}
.upd-t{flex:1;min-width:0;font-weight:800;font-size:var(--t-1)}
.upd-go{--btn:var(--card);background:var(--card);color:var(--ink);border:0;
  border-radius:var(--r-pill);padding:.45rem 1.1rem;font:900 var(--t-2)/1.2 inherit}
.upd-go:disabled{opacity:.5}
/* Bare glyph, no raised edge: dismissing is not the action being offered. */
.upd-x{background:none;border:0;color:var(--accent-ink);padding:.3rem;
  opacity:.7;line-height:0}
@keyframes updin{from{transform:translateY(-100%)}to{transform:none}}
@media (prefers-reduced-motion:reduce){.upd{animation:none}}
