/* Pip's world — the shared design system from the games: rounded Fredoka, teal/amber,
   warm paper, no punitive signals. Touch-first for phone, tablet and Chromebook. */
@import './fonts.css';
:root{
  --paper:#fffdf8; --panel:#ffffff;
  --ink:#1f2e36; --ink-soft:#5d6f78;
  --teal:#0e7c7b; --teal-tint:#e2f0ef; --teal-line:#9bc9c7;
  --amber:#e0940f; --amber-tint:#faecd2;
  --dino:#57b894; --dino-dk:#3f9a79;
  --rule:#ece7db;
  --locked:#e7e2d6; --ready:#cfe7e5; --learning:#ffe6b8; --secure:#7cc9a6; --review:#f3c99a;
  /* manipulative cell size scales with the viewport so a double ten-frame fits a phone */
  --cell:clamp(20px, 6.8vw, 34px);
}
*{box-sizing:border-box}
html,body{margin:0;height:100%}
body{
  background:var(--paper); color:var(--ink);
  font-family:'Inter',system-ui,sans-serif;
  min-height:100%; display:flex; flex-direction:column;
  -webkit-tap-highlight-color:transparent;
}
/* Installed on a phone with a notch, the page owns the whole screen (the viewport
   meta says viewport-fit=cover), so the chrome at the top — Home, the child's dino,
   the grown-up button — would sit under the notch and the rounded corners would eat
   the edges. The insets are added to the padding rather than replacing it, and each
   carries a 0px fallback so a browser that has never heard of env() keeps the
   padding it always had. */
.wrap{max-width:560px;margin:0 auto;width:100%;
  padding:calc(14px + env(safe-area-inset-top,0px)) calc(18px + env(safe-area-inset-right,0px))
    calc(28px + env(safe-area-inset-bottom,0px)) calc(18px + env(safe-area-inset-left,0px));
  flex:1;display:flex;flex-direction:column;gap:12px}

.topbar{display:flex;align-items:center;justify-content:space-between}
.topbar-left{display:flex;align-items:center;gap:10px}
/* Home: the discoverable way out of play, back to the hub. Hidden except during play. */
.home-btn{display:inline-flex;align-items:center;gap:6px;border:none;background:var(--teal-tint);color:var(--teal);font-family:'Fredoka',sans-serif;font-weight:500;font-size:14px;padding:6px 12px 6px 10px;border-radius:999px;cursor:pointer}
.home-btn[hidden]{display:none}
.home-btn:hover{background:var(--teal-line)}
.home-icon{font-size:16px;line-height:1}
.brand{font-family:'Fredoka',sans-serif;font-weight:600;font-size:20px;color:var(--teal);letter-spacing:-.01em}
.brand span{color:var(--dino-dk)}
.topbar-right{display:flex;align-items:center;gap:10px}
.who{font-family:'Fredoka',sans-serif;color:var(--ink-soft);font-size:14px}
.adult-toggle{position:relative;border:none;background:var(--teal-tint);color:var(--teal);font-family:'Fredoka',sans-serif;font-size:13px;font-weight:500;padding:6px 12px;border-radius:999px;cursor:pointer}
/* Sync trouble shows here as a quiet dot, not as anything child-facing. */
.adult-toggle.attention::after{content:'';position:absolute;top:-2px;right:-2px;width:9px;height:9px;border-radius:50%;background:var(--amber);border:2px solid var(--paper)}
/* The chrome stays visually small — it must not compete with the game — but a
   small button is still a small target, and these three are pressed by children.
   Each carries an invisible 44px pad centred inside itself: the thumb gets the
   accessible target, the eye gets the same quiet strip, and nothing moves. */
.home-btn,.adult-toggle,.who{position:relative}
.home-btn::before,.adult-toggle::before,.who::before{content:'';position:absolute;
  top:50%;left:50%;transform:translate(-50%,-50%);
  width:100%;height:100%;min-width:44px;min-height:44px}

.card{background:var(--panel);border:1px solid var(--rule);border-radius:22px;padding:20px 18px;box-shadow:0 6px 20px rgba(31,46,54,.05)}
h1{font-family:'Fredoka',sans-serif;font-weight:600;font-size:26px;margin:0 0 6px;text-align:center}
.sub{text-align:center;color:var(--ink-soft);margin:0 0 16px;font-size:15px}

/* screens */
.screen{display:none}
.screen.active{display:block}

/* profile */
.profile-form{display:flex;flex-direction:column;gap:12px;max-width:320px;margin:0 auto}
.profile-form label{display:flex;flex-direction:column;gap:6px;font-family:'Fredoka',sans-serif;color:var(--ink-soft);font-size:14px}
.profile-form input,.profile-form select{font-size:18px;padding:12px 14px;border:1.5px solid var(--teal-line);border-radius:14px;font-family:'Inter',sans-serif}
.primary,.ghost{font-family:'Fredoka',sans-serif;font-weight:600;cursor:pointer}
.primary{background:var(--teal);color:#fff;border:none;font-size:20px;padding:14px;border-radius:16px}

/* progress map */
.map{display:flex;flex-wrap:wrap;gap:6px;justify-content:center}
.chip{display:flex;align-items:center;gap:6px;padding:5px 10px;border-radius:999px;background:var(--locked);font-family:'Fredoka',sans-serif;font-size:12px;color:#6a6152;transition:transform .15s,background .3s}
.chip-dot{width:9px;height:9px;border-radius:50%;background:#b9b09c}
.chip-ready{background:var(--ready);color:#0e6b6a}.chip-ready .chip-dot{background:var(--teal)}
.chip-learning{background:var(--learning);color:#8a5a06}.chip-learning .chip-dot{background:var(--amber)}
.chip-practised{background:#d9efe4;color:#0b5a44}.chip-practised .chip-dot{background:#3fa07a}
.chip-secure{background:var(--secure);color:#0b3f2c}.chip-secure .chip-dot{background:#0b6b46}
.chip-review{background:var(--review);color:#8a4a06}.chip-review .chip-dot{background:#c9701a}
.chip-focus{transform:scale(1.08);box-shadow:0 0 0 2px var(--teal)}

/* The activity's one-line instruction. The strings are written in sentence case
   ("Count the eggs"); it used to be upper-cased here, which is the hardest case
   for an early reader and the one thing on the play screen written at them. */
.goal{text-align:center;font-family:'Fredoka',sans-serif;color:var(--ink-soft);font-size:14px;letter-spacing:.01em;margin-top:4px}

/* play */
/* Top-anchored with a stable min-height so swapping games or revealing a scaffold grows
   downward instead of re-centring the whole screen (no vertical jump). The min() caps
   that floor against the dynamic viewport (100dvh tracks the space browser chrome
   leaves) so a short phone screen is never forced to scroll to reach the answers. */
.play-area{min-height:min(400px,calc(100dvh - 230px));display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:16px;padding-top:10px}
/* Probe surface (app.js markProbe): a bare question + tiles + skip swims in the round
   floor above — probes size to content with a modest floor instead. Height may change
   between consecutive probes; only mid-question movement is the enemy. */
.play-area.play-compact{min-height:min(180px,calc(100dvh - 230px))}
.question{font-family:'Fredoka',sans-serif;font-weight:600;font-size:52px;text-align:center;letter-spacing:.01em}
/* Reserve room for the ten-frame so its fade-in doesn't push the pad down mid-question. */
/* width:100% so percentage-sized models (the bar model is width:min(100%,320px)) have a
   real width to resolve against — in a centred flex column the slot would otherwise shrink
   to content and collapse the bar to ~30px. Intrinsically-sized models (arrays, frames)
   stay centred. */
.model-slot{min-height:86px;width:100%;max-width:100%;overflow-x:auto;display:flex;align-items:center;justify-content:center}
/* Reserved-but-empty disclosure space: a faint crossed-out eye marks "held for a
   helper that only appears if needed" (stall scaffolds, rescue models), so the
   blank slot reads intentional rather than broken. Runners keep unrevealed slots
   truly childless (they clear with replaceChildren()), so :empty holds and the
   glyph vanishes the moment real content lands — no runner code involved; that
   contract is pinned in activities/emptyslot.test.js. present-slot collapses to
   zero height when empty and slot-collapsed is the lesson's animated close —
   neither may gain the glyph's height. Single colour via currentColor through an
   inline SVG mask (CSP allows no external assets); static, so nothing to quiet
   under prefers-reduced-motion. */
.model-slot:empty:not(.present-slot):not(.slot-collapsed)::before{
  content:'';width:30px;height:30px;flex:0 0 auto;
  color:var(--ink-soft);opacity:.16;background:currentColor;
  -webkit-mask:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'><path d='M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6z'/><circle cx='12' cy='12' r='3'/><path d='M4.5 19.5 19.5 4.5'/></svg>") center/contain no-repeat;
  mask:url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'><path d='M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6z'/><circle cx='12' cy='12' r='3'/><path d='M4.5 19.5 19.5 4.5'/></svg>") center/contain no-repeat;
}
.tiles{display:flex;flex-wrap:wrap;gap:12px;justify-content:center}
/* Auto width with a square minimum: single digits stay square, but four-digit products
   and expression tiles ("2 × 9") grow instead of overflowing (broken on placement). */
.tile{font-family:'Fredoka',sans-serif;font-weight:600;font-size:clamp(20px,6vw,30px);min-width:clamp(58px,18vw,82px);width:auto;padding:0 12px;white-space:nowrap;height:clamp(58px,18vw,82px);border-radius:18px;cursor:pointer;background:var(--teal-tint);color:var(--teal);border:2px solid transparent;transition:transform .1s,background .15s}
.tile:hover{background:#d2e8e6;transform:translateY(-2px)}
.tile:disabled{cursor:default}
.tile-right{background:var(--secure);color:#0b3f2c;border-color:#0b6b46}
.tile-wrong{background:var(--amber-tint);color:var(--amber);animation:nudge .3s}
@keyframes nudge{0%,100%{transform:translateX(0)}25%{transform:translateX(-5px)}75%{transform:translateX(5px)}}
.coach{color:var(--ink-soft);font-size:15px;text-align:center;margin:0;min-height:20px}

/* models */
.model{display:grid;gap:5px}
.tenframe{grid-template-columns:repeat(5,var(--cell));grid-template-rows:repeat(2,var(--cell));padding:5px;background:#fff;border:2px solid var(--teal-line);border-radius:10px}
/* Two frames side by side outgrow a 390px phone at the root cell size by a few pixels
   (and centred overflow clips at the left edge, unreachably) — a slightly smaller cell
   keeps the pair inside the card on the narrowest phones we support. */
.model-double{display:flex;gap:8px;flex-wrap:nowrap;max-width:100%;--cell:clamp(18px,6vw,34px)}
.tf-cell{border:1.5px solid var(--teal-line);border-radius:7px;background:#fbfbf7}
.tf-filled{background:var(--teal)}
.tf-added{background:var(--amber)}
.tf-removed{opacity:.35}
.dotgrid{grid-template-columns:repeat(3,var(--cell));grid-template-rows:repeat(3,var(--cell))}
.dot-cell{display:flex;align-items:center;justify-content:center}
.dot-on{position:relative}
.dot-on::after{content:'';width:72%;aspect-ratio:1;border-radius:50%;background:var(--teal)}
.numberline{display:flex;gap:0;max-width:100%}
.nl-tick{position:relative;width:var(--cell);height:38px;display:flex;align-items:center;justify-content:center;border-top:2px solid var(--teal-line);font-family:'Fredoka',sans-serif;color:var(--ink-soft);font-size:14px;flex:0 0 auto}
.nl-mark{background:var(--amber-tint);color:var(--amber);border-radius:8px}
/* Mark roles (numberline.js): a counted run's start is a hollow outline — shape, not
   colour, carries the cue — its steps are soft (faded and smaller), and the landing,
   a bare mark, keeps the strong filled style above. */
.nl-tick.nl-start{background:transparent;box-shadow:inset 0 0 0 2px var(--amber);color:var(--ink)}
.nl-tick.nl-step{opacity:.55;font-size:12px}
/* A dense line (numberline.js: a counting span labelling every tick, e.g. −10..10 on
   a phone) compacts its labels so 21 fit across a small screen; the pixel pass still
   thins any that genuinely cannot. */
.numberline.nl-dense .nl-tick{font-size:11px}
/* The pixel-aware label pass (numberline.js placeLabels): a label poking past the
   container is clamped back inside (--nl-dx, set per label by thinRenderedLabels);
   overlapping step labels hide; a crowded mark/endpoint drops to a second row so it
   still reads. */
.nl-label{transform:translateX(var(--nl-dx,0px))}
.nl-thinned .nl-label{visibility:hidden}
/* Clear of the 38px tick box entirely, so a staggered label never tucks under a
   neighbouring mark pill. */
.nl-stagger .nl-label{transform:translate(var(--nl-dx,0px),27px)}
/* In a model slot the line fills the row: short lines get wide, readable ticks (capped
   so five ticks don't stretch comically); long lines shrink to fit with no sideways
   clipping. The interactive hop-line sizes itself. */
.model-slot>.numberline:not(.hop-line){width:100%;justify-content:center;padding:0 18px}
.model-slot>.numberline:not(.hop-line) .nl-tick{flex:1 1 var(--cell);max-width:44px}
/* A line with an off-grid mark (numberline.js renderNumberLine adds nl-prop) can't use
   equal flex cells — pixel position would lie about value position. Each tick is pinned
   at its true fraction of the span (--nl-pos), and the connecting line the cells' top
   borders would have formed is drawn once across the inset width. */
.numberline.nl-prop{position:relative;display:block;height:38px}
.nl-prop::before{content:'';position:absolute;top:0;left:var(--nl-inset,18px);right:var(--nl-inset,18px);border-top:2px solid var(--teal-line)}
.nl-prop .nl-tick{position:absolute;top:0;left:calc(var(--nl-inset,18px) + var(--nl-pos,0)*(100% - 2*var(--nl-inset,18px)));transform:translateX(-50%)}
/* Jumps ("hops: true"): an amber arc lands on every multiple, so "4 jumps of 2" shows
   four visible hops. Margin leaves headroom for the arcs above the line. */
.nl-jumps{margin-top:16px}
.model-slot>.numberline.nl-jumps .nl-tick{flex:1 1 0;width:auto;max-width:64px}
.nl-arc{position:absolute;top:-14px;left:-50%;width:100%;height:14px;border:2.5px solid var(--amber);border-bottom:none;border-radius:50% 50% 0 0/100% 100% 0 0}
.fade-in{animation:fade .5s ease}
@keyframes fade{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}

/* celebration */
.celebrate-layer{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;opacity:0;transition:opacity .25s;z-index:20}
.celebrate-layer.show{opacity:1}
.celebrate{background:#fff;border-radius:24px;padding:26px 34px;box-shadow:0 20px 60px rgba(31,46,54,.2);text-align:center;transform:scale(.9);animation:pop .4s forwards}
.celebrate-big{padding:38px 52px}
.celebrate-pip{font-size:64px;animation:bounce .6s infinite alternate}
.celebrate-big .celebrate-pip{font-size:92px}
.celebrate-text{font-family:'Fredoka',sans-serif;font-weight:600;font-size:26px;color:var(--teal)}
.celebrate-big .celebrate-text{font-size:34px;color:var(--amber)}
.celebrate-sub{color:var(--ink-soft);font-size:15px;margin-top:2px}
@keyframes pop{to{transform:scale(1)}}
@keyframes bounce{to{transform:translateY(-10px)}}

.done{text-align:center;color:var(--ink-soft)}
.done-pip{font-size:72px}

/* adult panel */
.adult-panel{position:fixed;right:0;top:0;bottom:0;width:320px;max-width:88vw;background:#fff;border-left:1px solid var(--rule);box-shadow:-8px 0 30px rgba(31,46,54,.1);padding:20px;transform:translateX(102%);transition:transform .28s;overflow-y:auto;z-index:15}
.adult-panel.open{transform:none}
.adult-panel h2{font-family:'Fredoka',sans-serif;font-size:18px;margin:0 0 4px;color:var(--teal)}
.adult-note{color:var(--ink-soft);font-size:13px;margin:0 0 12px}
.adult-log{display:flex;flex-direction:column;gap:3px;font-size:13px;font-family:'Fredoka',sans-serif}
.log-row{padding:5px 9px;border-radius:8px;background:#f8f6ef;color:var(--ink-soft)}
.log-attempt{background:#f8f6ef}
.log-mastered{background:var(--secure);color:#0b3f2c}
.log-gap_located{background:var(--amber-tint);color:#8a5209;font-weight:600}
.log-session_start{background:transparent;color:#b9b09c;text-align:center}
.adult-actions{margin-top:16px;display:flex;flex-direction:column;gap:8px}
.sync-status{padding:10px 12px;border:1px solid var(--rule);border-radius:12px;background:var(--paper);font-size:12px;color:var(--ink-soft)}
.sync-state{font-family:'Fredoka',sans-serif;font-size:13px;font-weight:600;color:var(--teal);margin-bottom:3px}
.sync-error{color:#a33}
.sync-detail{margin-top:2px;line-height:1.35}
.sync-now{width:100%;margin-top:7px}
.ghost{background:var(--teal-tint);color:var(--teal);border:none;padding:10px;border-radius:12px;font-size:14px}
.ghost.danger{background:#f7e4e1;color:#b4472f}

/* Collapsed run of attempts on one node in Recent Activity */
.log-attempts{background:#f8f6ef}

/* The longitudinal record — the grown-up panel's lead, and the watch page's idle state.
   Phone-first: the week row and its count sit on one band, each gap is one line, and the
   hands-on prompt is the only thing allowed to run to a paragraph. */
.record{display:flex;flex-direction:column;gap:10px;margin:10px 0 4px}
.record-week{display:flex;flex-direction:column;gap:4px}
.record-week .week-row{gap:6px}
.record-week .week-dot{width:12px;height:12px}
.record-week-count{font-family:'Fredoka',sans-serif;font-size:17px;color:var(--ink)}
.record-longrun{font-size:12px;color:var(--ink-soft)}
.record-empty{font-size:13px;color:var(--ink-soft);line-height:1.4}
.record-gaps{display:flex;flex-direction:column;gap:6px}
.record-gap{padding:8px 11px;border-radius:11px;font-size:13px;line-height:1.35}
.record-gap-closed{background:var(--secure);color:#0b3f2c}
.record-gap-open{background:var(--amber-tint);border:1px solid #e8cf9a;color:#7a5209}
.record-handson{padding:10px 12px;border-radius:12px;background:var(--teal-tint);border:1px solid var(--teal-line)}
.record-handson-title{font-family:'Fredoka',sans-serif;font-size:12px;color:var(--ink-soft);text-transform:uppercase;letter-spacing:.04em}
.record-handson-skill{font-family:'Fredoka',sans-serif;font-weight:600;color:var(--teal);margin:2px 0 3px}
.record-handson-tip{font-size:14px;color:var(--ink-soft);line-height:1.4}

/* "What we found": the found-and-fixed gap story, in parent language */
.gap-summary{display:flex;flex-direction:column;gap:8px;margin-bottom:4px}
.gap-card{padding:10px 12px;border-radius:12px;font-size:13px;line-height:1.4}
.gap-open{background:var(--amber-tint);border:1px solid #e8cf9a;color:#7a5209}
.gap-closed{background:var(--secure);color:#0b3f2c}
.gap-history-title{font-family:'Fredoka',sans-serif;font-size:12px;color:var(--ink-soft);text-transform:uppercase;letter-spacing:.04em;margin-top:2px}
.gap-empty{font-size:13px;color:var(--ink-soft);margin:0}

/* Skill-map state legend */
.map-legend{display:flex;flex-wrap:wrap;gap:8px 12px;margin:8px 0 2px;font-family:'Fredoka',sans-serif;font-size:11px;color:var(--ink-soft)}
.legend-item{display:inline-flex;align-items:center;gap:5px}
.legend-locked{background:#b9b09c}
.legend-ready{background:var(--teal)}
.legend-learning{background:var(--amber)}
.legend-secure{background:#0b6b46}
.legend-review{background:#c9701a}

/* Wordless "let's try something a bit easier" beat on the child's screen at a gap
   descent — no words, cleared at the next round. */
.easier-cue{display:flex;align-items:center;justify-content:center;gap:6px;margin:6px auto;padding:6px 14px;background:var(--teal-tint);border-radius:999px;width:max-content;animation:easier-in .4s ease-out}
.easier-cue-pip{font-size:22px;line-height:1}
.easier-cue-arrow{font-size:18px;color:var(--teal);animation:easier-nudge .9s ease-in-out infinite}
@keyframes easier-in{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
@keyframes easier-nudge{0%,100%{transform:translateY(0)}50%{transform:translateY(3px)}}

/* Grown-up gate: a full-screen scrim with a small sum to answer before the panel opens */
.adult-gate{position:fixed;inset:0;z-index:20;background:rgba(31,46,54,.4);display:flex;align-items:center;justify-content:center;padding:24px}
.adult-gate[hidden]{display:none}
.adult-gate-box{background:#fff;border-radius:20px;padding:22px 20px;max-width:320px;width:100%;text-align:center;box-shadow:0 10px 34px rgba(31,46,54,.2)}
.adult-gate-title{font-family:'Fredoka',sans-serif;font-weight:600;font-size:20px;color:var(--ink);margin:0 0 4px}
.adult-gate-note{color:var(--ink-soft);font-size:14px;margin:0 0 12px}
.adult-gate-q{font-family:'Fredoka',sans-serif;font-size:17px;color:var(--ink);margin:0 0 14px}
.adult-gate-form{display:flex;gap:8px;justify-content:center;align-items:stretch}
.adult-gate-form input{font-size:20px;padding:10px 12px;width:90px;text-align:center;border:1.5px solid var(--teal-line);border-radius:14px;font-family:'Inter',sans-serif}
.adult-gate-form .primary{font-size:16px;padding:10px 18px}
.adult-gate-hint{min-height:18px;font-size:13px;color:#b4472f;margin:8px 0}
.adult-gate .ghost{width:100%}

/* Narrow phones: reclaim horizontal room so the manipulatives sit comfortably */
@media (max-width:400px){
  /* Still clear of the rounded corners: this tier trims the padding, not the inset. */
  .wrap{padding-left:calc(12px + env(safe-area-inset-left,0px));
    padding-right:calc(12px + env(safe-area-inset-right,0px))}
  .card{padding-left:14px;padding-right:14px}
  .question{font-size:44px}
}

/* Short viewports (phones in landscape-ish ratios, small phones with browser chrome):
   compact everything above the answers so question, model and every option — the "20"
   key included — share one screen with no scrolling. */
@media (max-height:740px){
  /* The notch does not shrink with the viewport: the trimmed top padding still
     starts below it, or the top bar ends up underneath it on exactly the phones
     this tier is for. */
  .wrap{padding-top:calc(8px + env(safe-area-inset-top,0px));gap:8px}
  .question{font-size:38px}
  .equation{font-size:clamp(26px,8vw,38px)}
  .play-area{gap:10px;padding-top:4px}
  .model-slot{min-height:64px}
  .key{width:clamp(38px,11vw,46px);height:clamp(38px,11vw,46px);font-size:19px}
  /* min-width, not width: this compact override once fixed the tile's width, which
     clipped five-digit placement answers inside their boxes on phones (launch night).
     The square minimum keeps short answers square; long ones grow and the row wraps. */
  .tile{min-width:clamp(52px,15vw,68px);width:auto;height:clamp(52px,15vw,68px);font-size:26px}
  .pip{width:44px;height:44px}
  .hop-line .nl-tick{height:54px}
  .dot-stage{min-height:96px}
}

/* ============ games ============ */
.game-head{display:flex;align-items:center;justify-content:space-between;width:100%;gap:14px;max-width:360px}
.pip{width:60px;height:60px;flex:0 0 auto}
.bounce{animation:pip-bounce .5s ease}
@keyframes pip-bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-12px)}}
.wobble{animation:wobble-x .34s ease}
@keyframes wobble-x{0%,100%{transform:translateX(0)}25%{transform:translateX(-6px)}75%{transform:translateX(6px)}}

.power{flex:1;max-width:220px}
/* Sentence case for the same reason as .goal: it sits on the child's play screen. */
.power-lab{font-family:'Fredoka',sans-serif;font-size:11px;color:var(--ink-soft);letter-spacing:.02em;margin-bottom:3px}
.power-track{height:12px;background:var(--teal-tint);border-radius:999px;overflow:hidden}
.power-fill{display:block;height:100%;width:0;background:var(--amber);border-radius:999px;transition:width .4s ease}

.equation{font-family:'Fredoka',sans-serif;font-weight:600;font-size:clamp(30px,9.5vw,48px);text-align:center;letter-spacing:.01em}
.equation .slot{display:inline-block;min-width:.6em;color:var(--teal-line)}
.equation .slot.filled{color:var(--amber)}
.equation .a{color:var(--amber)}
.equation .b{color:var(--teal)}
.big-ask{font-size:clamp(24px,7vw,34px);color:var(--ink-soft)}
.say{min-height:22px;color:var(--ink-soft);font-size:15px;text-align:center;font-family:'Fredoka',sans-serif}

.pad{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;max-width:340px}
.key{font-family:'Fredoka',sans-serif;font-weight:600;font-size:22px;width:clamp(44px,13vw,54px);height:clamp(44px,13vw,54px);border-radius:14px;background:var(--teal-tint);color:var(--teal);border:2px solid transparent;cursor:pointer;transition:transform .1s,background .12s}
.key:hover{background:#d2e8e6}
.key:active{transform:translateY(1px)}
.key:disabled{opacity:.45;cursor:default}
.choices{display:flex;flex-wrap:wrap;gap:12px;justify-content:center}
.choice{font-family:'Fredoka',sans-serif;font-weight:600;font-size:26px;padding:14px 22px;border-radius:16px;background:var(--teal-tint);color:var(--teal);border:2px solid transparent;cursor:pointer}
.choice:hover{background:#d2e8e6}

/* subitise */
.dot-stage{min-height:118px;align-items:center}
.flash-in{animation:fade .28s ease}
.dimmed{opacity:.12;transition:opacity .45s ease}

/* counting/jumps number line — big tappable stones that fill the width. min-width:0
   lets up to eight stones share the row on a 375px phone without horizontal overflow;
   the tall height keeps each stone an easy touch target as it narrows. */
.hop-line{gap:5px;width:100%;justify-content:center;flex-wrap:nowrap}
.hop-line .nl-tick{flex:1 1 0;min-width:0;width:auto;height:66px;padding:0;border:2px solid var(--teal-line);border-radius:16px;background:var(--panel);color:var(--ink);cursor:pointer;font-family:'Fredoka',sans-serif;font-weight:600;font-size:clamp(16px,4.5vw,24px);transition:transform .1s,background .12s}
.hop-line .nl-tick:hover{background:var(--teal-tint);transform:translateY(-2px)}
.hop-line .nl-tick:active{transform:scale(.94)}
.hop-line .nl-here{background:var(--dino);color:#fff;border-color:var(--dino-dk)}
.hop-line .nl-mark{background:var(--secure);color:#0b3f2c;border-color:#0b6b46}
.hop-line .nl-hop{background:var(--amber);color:#fff;border-color:#c97a0c;animation:pip-bounce .3s ease}
.hop-line .nl-ten{box-shadow:inset 0 0 0 2px var(--amber)}
.hop-line .nl-wrong{background:#e05a5a;color:#fff;border-color:#c23f3f;animation:wobble-x .34s ease}
.hop-line .nl-hint{animation:hint-pulse 1s ease infinite}
@keyframes hint-pulse{0%,100%{box-shadow:0 0 0 0 rgba(14,124,123,.5)}50%{box-shadow:0 0 0 6px rgba(14,124,123,0)}}

/* bridge make-ten */
/* Fixed like .scaffold-slot (see that rule's comment): the pad may never move when the
   rescue fades in. Two frames at the model-double cell (≤87px) plus the counter tray
   (one row on most phones, two on the narrowest) peak near 128px; odder content scrolls
   within the box instead of pushing the pad. */
.bridge-slot{flex-direction:column;gap:10px;align-items:center;height:132px;min-height:132px;overflow-y:auto}
.tray{display:flex;gap:6px;justify-content:center;flex-wrap:wrap;min-height:22px}
.loose{width:26px;height:26px;border-radius:50%;background:var(--amber);border:none;cursor:pointer;padding:0;box-shadow:0 2px 4px rgba(224,148,15,.35);transition:transform .1s}
.loose:hover{transform:translateY(-2px)}
.loose:active{transform:scale(.9)}
.make-ten{font-family:'Fredoka',sans-serif;font-weight:600;font-size:18px;padding:10px 20px;border-radius:14px;background:var(--dino);color:#fff;border:none;cursor:pointer}
.make-ten:active{transform:translateY(1px)}

/* flip rod */
.flip-stage{display:flex;flex-direction:column;align-items:center;gap:16px}
.rod{display:flex;gap:3px;transition:transform .38s ease;transform-origin:center}
.rod.flipped{transform:rotate(180deg)}
.cube{width:26px;height:26px;border-radius:6px}
.cube.ca{background:var(--amber)}
.cube.cb{background:var(--teal)}

/* fact-family triangle */
.triangle{display:flex;flex-direction:column;align-items:center;gap:8px}
.triangle-legs{width:96px;height:22px;border-left:2px solid var(--teal-line);border-right:2px solid var(--teal-line);border-bottom:2px solid var(--teal-line);border-radius:0 0 40px 40px}
.parts{display:flex;gap:48px}
.tri-node{width:60px;height:60px;border-radius:16px;display:flex;align-items:center;justify-content:center;font-family:'Fredoka',sans-serif;font-weight:600;font-size:28px}
.tri-whole{background:var(--amber-tint);color:var(--amber)}
.tri-part{background:var(--teal-tint);color:var(--teal)}

.hidden{display:none!important}

/* grown-up panel additions */
.adult-sub{font-family:'Fredoka',sans-serif;font-size:13px;color:var(--ink-soft);margin:14px 0 6px;text-transform:uppercase;letter-spacing:.04em}
.map-adult{justify-content:flex-start}

/* round-end reward */
.reward{display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;padding:16px 10px}
.reward-pip{width:96px;height:96px}
.reward-big .reward-pip{width:128px;height:128px}
/* A .reward-pip holding characters rather than a drawn dino: the 96px box was
   holding a 16px emoji in its top-left corner. Fill it — 46px so the two-emoji
   cards (🦕👋) still fit across, and larger for the single-dino fallback
   rewardbadge.js renders when no child is chosen. */
.reward-pip:not(svg){display:flex;align-items:center;justify-content:center;
  font-size:46px;line-height:1}
.reward-pip.reward-pip-plain{font-size:64px}
.reward-h{font-family:'Fredoka',sans-serif;font-weight:600;font-size:26px;color:var(--teal)}
.reward-big .reward-h{font-size:32px;color:var(--amber)}
.reward-score{font-family:'Fredoka',sans-serif;font-size:18px;color:var(--ink-soft)}
.reward-sub{color:var(--ink-soft);font-size:14px;margin-bottom:10px}
/* "It's yours now" — the line that turns a reward into a possession. */
.reward-kept{font-family:'Fredoka',sans-serif;font-size:16px;color:var(--amber);margin:-6px 0 8px}

/* The end of a sitting, as the child gets it: what they did, the week, and the
   one thing waiting tomorrow. The grown-up handoff sits under all of it. */
.ending-worked{font-size:15px;color:var(--ink);max-width:280px}
.ending-week{display:flex;flex-direction:column;align-items:center;gap:4px;margin:10px 0 2px;padding:12px 16px;border-radius:16px;background:var(--teal-tint)}
.ending-week .week-row{gap:6px}
.ending-week .week-dot{width:12px;height:12px}
.ending-week-count{font-family:'Fredoka',sans-serif;font-weight:600;font-size:19px;color:var(--teal)}
.ending-week .reward-sub{margin-bottom:0}
.ending-tomorrow{font-family:'Fredoka',sans-serif;font-size:16px;color:var(--amber);background:var(--amber-tint);border-radius:14px;padding:9px 14px;margin-top:6px;max-width:300px}
.ending-handoff{font-size:14px;color:var(--ink-soft);margin:12px 0 2px}

/* speed map on the reward screen */
.speedmap{display:flex;flex-direction:column;gap:8px;width:100%;max-width:320px;margin-top:4px}
.sm-group{text-align:left}
.sm-title{font-family:'Fredoka',sans-serif;font-size:12px;font-weight:600;margin-bottom:4px;display:flex;align-items:center;gap:6px}
.sm-title::before{content:'';width:9px;height:9px;border-radius:50%;display:inline-block}
.sm-title.sm-fast{color:#0b6b46}.sm-title.sm-fast::before{background:#0b6b46}
.sm-title.sm-quicker{color:var(--amber)}.sm-title.sm-quicker::before{background:var(--amber)}
.sm-title.sm-practise{color:#b4472f}.sm-title.sm-practise::before{background:#b4472f}
.sm-chips{display:flex;flex-wrap:wrap;gap:5px}
.sm-chip{font-family:'Fredoka',sans-serif;font-size:13px;padding:3px 9px;border-radius:999px}
.sm-chip.sm-fast{background:var(--secure);color:#0b3f2c}
.sm-chip.sm-quicker{background:var(--amber-tint);color:#8a5209}
.sm-chip.sm-practise{background:#f7e4e1;color:#b4472f}

/* reward polish — stars, sparkles, and quieter grown-up detail */
.stars{display:flex;gap:6px;justify-content:center;margin:2px 0}
.star{font-size:26px;color:var(--rule);line-height:1}
.star.on{color:var(--amber);animation:star-pop .4s ease}
@keyframes star-pop{0%{transform:scale(0) rotate(-30deg)}70%{transform:scale(1.3)}100%{transform:scale(1)}}
.sparkles{display:flex;gap:14px;justify-content:center;font-size:22px;margin-top:-6px}
.sparkle{animation:twinkle 1s ease infinite alternate}
.sparkle:nth-child(2){animation-delay:.3s;font-size:28px}
@keyframes twinkle{from{opacity:.4;transform:translateY(2px) scale(.9)}to{opacity:1;transform:translateY(-2px) scale(1.1)}}
.reward .speedmap{margin-top:10px;opacity:.85}
.reward .sm-chip{font-size:12px;padding:2px 8px}

/* game delight — the make-ten pulse and counters dropping in */
.frame-ten{animation:ten-pulse .5s ease}
@keyframes ten-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.06);box-shadow:0 0 0 3px var(--secure)}}
.tf-added{animation:cell-pop .22s ease}
@keyframes cell-pop{0%{transform:scale(.2);opacity:.3}100%{transform:scale(1);opacity:1}}

/* mastery confetti */
.confetti{position:absolute;inset:0;overflow:hidden;pointer-events:none;border-radius:24px}
.confetto{position:absolute;top:-12px;width:9px;height:9px;border-radius:2px;opacity:0;animation:fall 1.4s ease-in forwards}
.confetto.c0{background:var(--teal)}.confetto.c1{background:var(--amber)}
.confetto.c2{background:var(--dino)}.confetto.c3{background:#e0607a}
.confetto:nth-child(4n){left:12%}.confetto:nth-child(4n+1){left:34%}
.confetto:nth-child(4n+2){left:58%}.confetto:nth-child(4n+3){left:80%}
.confetto:nth-child(2n){animation-delay:.15s}.confetto:nth-child(3n){animation-delay:.3s}
.confetto:nth-child(5n){animation-delay:.45s}
@keyframes fall{0%{opacity:1;transform:translateY(0) rotate(0)}100%{opacity:0;transform:translateY(180px) rotate(320deg)}}

/* ============ profiles: picker, create, topbar avatar ============ */
.who{display:flex;align-items:center;gap:6px;border:none;background:var(--teal-tint);border-radius:999px;padding:3px 12px 3px 4px;cursor:pointer;font-family:'Fredoka',sans-serif;color:var(--teal);font-size:14px}
.who:empty{display:none}
.who-dino{width:30px;height:30px;flex:0 0 auto}

/* picker / hub */
.picker-list{display:flex;flex-wrap:wrap;gap:14px;justify-content:center;margin:8px 0 18px}
.player-tile{position:relative;display:flex}
.player{display:flex;flex-direction:column;align-items:center;gap:4px;width:112px;padding:14px 8px;border:2px solid var(--rule);border-radius:20px;background:var(--panel);cursor:pointer;transition:transform .12s,border-color .12s}
.player:hover{transform:translateY(-3px);border-color:var(--teal-line)}
.player-dino{width:76px;height:76px}
.player-name{font-family:'Fredoka',sans-serif;font-weight:600;font-size:17px;color:var(--ink)}
/* Days practised this week: decoration under the name, not a banner. A filled dot
   is a day played, the ringed dot is today, and an empty week reads as a fresh
   page — there is no "missed" state to draw. */
.week-row{display:flex;gap:4px;align-items:center;margin-top:2px}
.week-dot{display:block;width:8px;height:8px;border-radius:50%;background:var(--rule)}
.week-dot.on{background:var(--teal)}
.week-dot.today{box-shadow:0 0 0 2px var(--teal-line)}
/* Restyle a dino: kid-facing, small, tucked in the tile's corner so a tap on the
   tile still means "play as me". */
.player-edit{position:absolute;top:6px;right:6px;width:30px;height:30px;border:none;border-radius:50%;background:var(--teal-tint);color:var(--teal);font-size:14px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center}
.player-edit:hover{background:var(--teal-line)}
/* The child's own shelf, in the opposite corner from the pencil: a bag of the
   things they have won. No reading required to find it. */
.player-shelf{position:absolute;top:6px;left:6px;width:30px;height:30px;border:none;border-radius:50%;background:var(--amber-tint);font-size:15px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center}

/* shelf screen */
.shelf-preview{display:flex;justify-content:center;margin-bottom:6px}
.shelf-dino{width:128px;height:128px}
.shelf-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;max-width:320px;margin:10px auto 18px}
.shelf-slot{display:flex;align-items:center;justify-content:center;aspect-ratio:1;border-radius:18px;border:2px solid var(--rule);background:var(--panel)}
.shelf-earned{cursor:pointer;padding:2px}
.shelf-earned.on{border-color:var(--teal);background:var(--teal-tint)}
.shelf-slot-dino{width:100%;height:100%}
/* Not found yet: a shape with a question mark in it, and no count of how many
   are left — the empty slot is the invitation, not a target. */
.shelf-locked{background:var(--rule);color:var(--panel);font-family:'Fredoka',sans-serif;font-weight:600;font-size:26px}
.shelf-back{display:block;margin:0 auto;min-width:160px}
.hub-actions{display:flex;flex-wrap:wrap;gap:10px;justify-content:center}
.hub-actions .ghost{min-width:150px}
.add-child{max-width:220px}

/* create */
.dino-preview{display:flex;justify-content:center;margin:2px 0 10px}
.dino-preview .pip{width:100px;height:100px}
.picker-group{display:flex;flex-direction:column;gap:8px}
.picker-label{font-family:'Fredoka',sans-serif;color:var(--ink-soft);font-size:14px}
.swatch-row{display:flex;flex-wrap:wrap;gap:10px}
.swatch{border:2px solid var(--rule);background:var(--panel);border-radius:14px;padding:6px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:border-color .12s,transform .1s}
.swatch:hover{transform:translateY(-2px)}
.swatch.selected{border-color:var(--teal)}
.swatch .pip{width:48px;height:48px;display:block}
.colour-dot{width:38px;height:38px;border-radius:50%;display:block}

/* duplicate-name prompt — a gentle question above the Start button, not a warning */
.duplicate-prompt{background:var(--teal-tint);border:1px solid var(--teal-line);border-radius:14px;padding:12px 14px;display:flex;flex-direction:column;gap:10px}
.duplicate-message{margin:0;font-size:15px;color:var(--ink);font-family:'Fredoka',sans-serif}
.duplicate-actions{display:flex;flex-direction:column;gap:8px}

/* flip concept-check choices */
.choice-right{background:var(--secure);color:#0b3f2c;border-color:#0b6b46}
.choice-wrong{background:#e05a5a;color:#fff;border-color:#c23f3f}

/* shared number-strand model components (src/app/models/) */
.nl-minor{font-size:0;border-top-width:4px}
/* Place-value chart: one pill per place. The pills stretch to the tallest column
   and the counters take the slack, so the digits sit on one baseline whatever the
   number is — 8 tens beside 0 ones used to leave the 0 floating halfway up. */
.pvchart{display:flex;gap:8px;align-items:stretch;width:100%;max-width:340px;margin-inline:auto}
.pv-col{display:flex;flex-direction:column;align-items:center;gap:4px;min-width:0;flex:1 1 0;max-width:96px;padding:6px 4px;background:#fff;border:2px solid var(--teal-line);border-radius:12px}
.pv-head{font-family:'Fredoka',sans-serif;font-weight:600;color:var(--teal);font-size:13px;line-height:1.1;text-align:center;overflow-wrap:anywhere}
.pv-counters{display:flex;flex-direction:column-reverse;align-items:center;gap:3px;flex:1 1 auto;min-height:calc(9 * (var(--cell) / 2.6))}
.pv-counter{width:calc(var(--cell) / 1.6);height:calc(var(--cell) / 2.9);border-radius:50%;background:var(--teal)}
.pv-digit{font-family:'Fredoka',sans-serif;font-weight:600;color:var(--ink)}
.barmodel{display:flex;flex-direction:column;gap:4px;width:min(100%,320px)}
.bar-whole{border:2px solid var(--teal-line);border-radius:8px;background:var(--teal-tint);text-align:center;font-family:'Fredoka',sans-serif;font-weight:600;padding:3px 0}
.bar-parts{display:flex;width:100%}
.bar-part{border:2px solid var(--teal-line);background:#fff;text-align:center;font-family:'Fredoka',sans-serif;padding:3px 0;overflow:hidden}
.bar-part:not(:first-child){border-left-width:1px}
.bar-unknown{background:var(--amber-tint);color:var(--amber)}
.arraygrid{display:grid;gap:4px}
.arr-dot{width:calc(var(--cell) / 1.6);height:calc(var(--cell) / 1.6);border-radius:50%;background:var(--teal);justify-self:center}
.fractionbar{display:flex;width:min(100%,320px);border:2px solid var(--teal-line);border-radius:8px;overflow:hidden}
.fb-part{flex:1 1 0;height:34px;background:#fff}
.fb-part:not(:first-child){border-left:2px solid var(--teal-line)}
.fb-shaded{background:var(--teal)}
/* The split board (models/splitboard.js): four lettered candidate bars stacked. The
   parts slim from 34px to 24px so the stack fits the 160px scaffold slot at phone
   width — the smallest change that keeps four bars readable — and each letter sits
   left of its bar, pairing it with the letter tile below. */
.splitboard{display:flex;flex-direction:column;gap:8px;width:min(100%,330px)}
.sb-row{display:flex;align-items:center;gap:10px}
.sb-letter{flex:0 0 20px;text-align:center;font-family:'Fredoka',sans-serif;font-weight:600;font-size:18px;color:var(--ink)}
.sb-row .fractionbar,.sb-row .barmodel{flex:1 1 0;min-width:0;width:auto}
.splitboard .fb-part{height:24px}
.splitboard .bar-part{height:24px;padding:0}

/* ============ wave-2 activity surfaces ============ */

/* typed-answer entry (multdivkit digitEntry): the answer builds in an amber pill above
   a 0–9 pad, echoing the equation's filled-slot colour */
.digit-entry{display:flex;flex-direction:column;align-items:center;gap:10px;width:100%}
.digit-display{min-width:2.2em;padding:2px 16px;border-radius:16px;background:var(--amber-tint);color:var(--amber);text-align:center}
.entry-slot,.input-slot{display:flex;justify-content:center;width:100%}
/* the ✓ confirm key on digit pads — green like make-ten, so "done" reads at a glance */
.digit-pad .key:last-child,.digit-entry .key:last-child{background:var(--dino);color:#fff}
.digit-pad .key:last-child:hover,.digit-entry .key:last-child:hover{background:var(--dino-dk)}
/* an empty given line would otherwise still claim a flex gap */
.equation.given:empty{display:none}

/* working lines — partial products, a multiples list — as a soft card in the scaffold slot */
.steps{display:flex;flex-direction:column;gap:4px;padding:10px 16px;background:var(--teal-tint);border-radius:14px;max-width:100%}
.steps .say{min-height:0;text-align:left;color:var(--ink)}

/* sentence prompts read at sentence size, not equation size, and wrap to a readable measure */
.choice-prompt{font-size:clamp(20px,5.8vw,28px);line-height:1.3;max-width:26ch}
/* mental-strategies word problems (the surface with an .input-slot): same treatment.
   Sibling :has() keeps this off the pure-equation surfaces; older engines just show big text. */
.equation:has(+ .model-slot + .say + .input-slot){font-size:clamp(20px,5.8vw,28px);line-height:1.35;max-width:30ch}

/* "Hear it again" — a quiet pill, clearly tappable but not competing with the answers */
.replay{border:1.5px solid var(--teal-line);background:var(--panel);color:var(--teal);font-family:'Fredoka',sans-serif;font-weight:500;font-size:14px;padding:8px 16px;min-height:44px;border-radius:999px;cursor:pointer;transition:background .12s}
.replay::before{content:'🔊 ' / ''}
.replay:hover{background:var(--teal-tint)}
.replay:active{transform:translateY(1px)}

/* answer reveal: the way out lights up green and breathes — an invitation, not an alarm */
.choice.reveal,.key.reveal{background:var(--secure);color:#0b3f2c;border-color:#0b6b46;animation:hint-pulse 1s ease infinite}
/* spent wrong choices step back quietly (right/wrong/reveal states keep their own look) */
.choice:disabled:not(.choice-right):not(.choice-wrong):not(.reveal){opacity:.45;cursor:default}

/* Roman numeral breakdown: one card per token, letters over their value (XL over 40) */
.roman-breakdown{display:flex;flex-wrap:wrap;gap:8px;justify-content:center}
.roman-part{display:flex;flex-direction:column;align-items:center;gap:2px;padding:8px 12px;min-width:52px;background:#fff;border:2px solid var(--teal-line);border-radius:12px}
.roman-letters{font-family:'Fredoka',sans-serif;font-weight:600;font-size:24px;color:var(--teal)}
.roman-value{font-family:'Fredoka',sans-serif;font-weight:600;font-size:15px;color:var(--amber)}

/* count-the-eggs: big tappable objects; a counted one turns quiet green with its ✓
   (or its rescue number), so what's left to count stays obvious */
.object-field{display:flex;flex-wrap:wrap;gap:12px;justify-content:center;max-width:340px}
.count-object{width:clamp(52px,15vw,64px);height:clamp(52px,15vw,64px);font-size:clamp(26px,8vw,32px);padding:0;display:flex;align-items:center;justify-content:center;background:var(--panel);border:2px solid var(--teal-line);border-radius:18px;cursor:pointer;transition:transform .1s,background .15s,border-color .15s}
.count-object:hover{transform:translateY(-2px);background:var(--teal-tint)}
.count-object.counted{background:var(--secure);border-color:#0b6b46;color:#0b3f2c;cursor:default;font-family:'Fredoka',sans-serif;font-weight:600}

/* foundation rounds: the question model collapses when an item has none, so the
   scaffold slot below keeps its reserved height without doubling the gap */
.model-slot.present-slot{min-height:0}

/* choice buttons that carry a dot-grid model instead of text: sized so the model reads
   as the choice, with a slightly smaller cell so two fit a phone side by side */
.choice.model-choice{display:flex;align-items:center;justify-content:center;padding:10px;min-width:64px;min-height:64px}
.model-choice .model{--cell:clamp(16px,5vw,26px)}

/* two subitising part-groups side by side, far enough apart to read as two parts */
.parts-stage{gap:clamp(14px,5vw,26px);align-items:center}

/* commutativity rescue: two labelled arrays with small dots, side by side while they
   fit a phone and wrapping to a stack when they don't — never sideways page scroll */
.rescue-pair{flex-wrap:wrap;gap:8px 20px}
.rescue-pair .model{--cell:clamp(9px,2.9vw,14px)}
.rescue-figure{display:flex;flex-direction:column;align-items:center;gap:3px}
.rescue-figure .say{min-height:0}

/* column method: the stacked sum's grid is inline in columnmethod.js; type and scroll live here */
.column-sum{font-family:'Fredoka',sans-serif;color:var(--ink);max-width:100%;overflow-x:auto}
/* the placement probe's board sits where a probe model would, centred in the play area */
.probe-board{display:flex;justify-content:center;width:100%;max-width:100%;margin:6px 0 2px}

/* Written multiplication/division is intentionally scoped away from the established
   column-method selectors. Six product columns still fit at 390px without shrinking
   the tap targets, while the board itself may never widen the activity shell. */
.wm-board-slot{display:flex;justify-content:center;width:100%;max-width:100%;min-height:150px}
.wm-board{font-family:'Fredoka',sans-serif;color:var(--ink);font-weight:700;font-variant-numeric:tabular-nums;max-width:100%}
.wm-mult-board{display:flex;flex-direction:column;justify-content:center;font-size:clamp(1.22rem,6vw,1.55rem);width:min(100%,340px);min-width:0}
.wm-grid-row{display:grid;grid-template-columns:1em repeat(var(--wm-cols),minmax(1.05em,1.35em));justify-content:end;gap:1px .18em;min-height:1.2em}
.wm-cell{display:inline-flex;align-items:center;justify-content:center;min-width:0;min-height:1.15em}
.wm-operator{display:inline-flex;align-items:center;justify-content:center;min-height:1.15em}
.wm-carries{min-height:.8em;opacity:.76}
.wm-carries .wm-mark{font-size:.62em}
.wm-answer{color:var(--teal)}
.wm-partial{color:var(--ink-soft)}
.wm-total{color:var(--amber)}
/* align-content:start keeps the quotient row sitting on the bus stop: the board slot's
   min-height stretches the grid, and distributed row tracks would float the quotient
   ~40px above the dividend. */
.wm-division-board{--wm-div-prefix:.8em;--wm-div-digit:.8em;--wm-div-cell:calc(var(--wm-div-prefix) + var(--wm-div-digit));display:grid;grid-template-columns:auto 1fr auto;grid-template-rows:auto auto;align-content:start;align-items:center;font-size:clamp(1.25rem,6.3vw,1.6rem);width:min(100%,330px);min-width:0;padding-top:18px}
.wm-div-quotient{grid-column:1/3;display:grid;grid-template-columns:minmax(1.5em,auto) repeat(var(--wm-div-cols,4),var(--wm-div-cell));justify-content:start;color:var(--teal)}
.wm-div-quotient .wm-answer{width:var(--wm-div-digit);justify-self:end}
.wm-div-working{grid-column:1/3;display:flex;align-items:flex-start}
.wm-divisor{padding:.12em .35em 0 0;min-width:1.5em;text-align:right}
.wm-div-inside{display:grid;grid-template-columns:repeat(var(--wm-div-cols,4),var(--wm-div-cell));border-top:3px solid currentColor;border-left:3px solid currentColor;border-radius:.35em 0 0 0;padding:.16em 0 0}
.wm-dividend-cell{display:grid;grid-template-columns:var(--wm-div-prefix) var(--wm-div-digit);align-items:baseline;width:var(--wm-div-cell)}
.wm-div-carry{font-size:.52em;line-height:1;text-align:right;padding-right:.12em;white-space:nowrap;color:var(--amber);opacity:.9}
.wm-mark{color:var(--amber)}
.wm-remainder{grid-column:3;grid-row:1/3;display:inline-flex;align-items:baseline;gap:.2em;margin-left:.45em;padding:.08em .18em;white-space:nowrap;color:var(--teal)}
.wm-final-carry{font-size:.62em}
.wm-scaffold{height:142px;min-height:142px}
.wm-scaffold .steps{display:grid;grid-template-columns:repeat(3,auto);gap:2px 14px;max-width:100%}
.wm-scaffold .steps .say{min-height:0;font-size:clamp(.72rem,3.4vw,.9rem);white-space:nowrap}
.wm-say{padding:0 8px;min-height:38px;display:flex;align-items:center;justify-content:center}
.wm-input .pad{max-width:330px}

/* short screens: compact keys may not shrink below a 44px touch target (review flag) */
@media (max-height:740px){
  .key{width:clamp(44px,11vw,48px);height:clamp(44px,11vw,48px)}
}

/* first-visit onboarding (value → try-it → hand over) */
.value-lead{font-size:16px;line-height:1.5;color:var(--ink-soft);max-width:34ch;margin:0 auto 16px;text-align:center}
.value-facts{list-style:none;padding:0;margin:0 auto 18px;max-width:30ch;display:flex;flex-direction:column;gap:8px}
.value-facts li{position:relative;padding-left:22px;font-size:14px;color:var(--ink-soft);line-height:1.4}
.value-facts li::before{content:'✓';position:absolute;left:0;color:var(--dino-dk);font-weight:700}
.value-note{font-size:13px;color:var(--ink-soft);text-align:center;margin:10px auto 0;max-width:32ch;line-height:1.4;min-height:18px}
.tryit-caption{font-size:14px;color:var(--ink-soft);text-align:center;margin:0 auto 12px;max-width:32ch;line-height:1.4}

/* floor-and-tell hands-on prompt: reuses gap-card; the skill name leads, the tip follows */
.handson-card .handson-skill{font-family:'Fredoka',sans-serif;font-weight:600;color:var(--teal);margin-bottom:3px}
.handson-card .handson-tip{font-size:14px;color:var(--ink-soft);line-height:1.4}

/* entry-round scaffold: reserve the space the model + hint will occupy so the answer pad
   holds still when they fade in on a stall (jumping content was jarring on the phone).
   The array is capped and scrolls within the reserved box rather than pushing the layout. */
/* Fixed height (not a min/max range): the slot never grows when the scaffold arrives, so
   the answer pad never moves. Tall scaffolds (the division multiples panel, two stacked
   place-value charts) scroll within it rather than pushing the layout. */
.scaffold-slot{height:160px;min-height:160px;overflow-y:auto;align-items:center;transition:height .3s ease,min-height .3s ease}
/* Centre via auto margins, not the flex axis: auto vertical margins centre a short
   scaffold exactly as align-items did, but collapse to zero when a tall panel (eight
   partial products plus "now add them", a nine-line multiples list) outgrows the slot —
   so line one anchors at the scroll top instead of clipping above an unreachable region. */
.scaffold-slot>*{margin-top:auto;margin-bottom:auto}
/* Lesson beats without a model collapse the slot smoothly instead of leaving a fixed
   160px hole mid-card (activities never toggle this). */
.scaffold-slot.slot-collapsed{height:0;min-height:0}
.scaffold-slot .model{--cell:clamp(12px,3.8vw,20px)}
/* The floor is an exact two lines (2 × 15px × 1.4) — 40px with an unpinned line-height
   under-reserved a wrapped line by ~2px, nudging the tiles when the hint landed. */
.scaffold-say{min-height:42px;line-height:1.4;display:flex;align-items:center;justify-content:center;text-align:center}
.scaffold-say:not(:empty){animation:fade .4s ease}
/* choiceround's per-item say reservation: the current item's scaffold hint and reveal
   line sit in the say as hidden sizers, stacked with the visible line in one grid cell,
   so the say is as tall as the tallest line it might show from first paint. No fixed
   floor fits every item — the wordy hints (fractions, skip counting) wrap to three
   phone lines while most items need one. The runner also aria-hides the sizers. */
.say-stack{display:grid;place-items:center}
.say-stack>*{grid-area:1/1;max-width:100%}
.say-reserve{visibility:hidden}
/* The visible line keeps the fade the say itself loses (its sizers make it never :empty). */
.say-line:not(:empty){animation:fade .4s ease}

/* parental controls in the grown-up panel */
.setting-row{display:flex;align-items:center;justify-content:space-between;gap:12px;font-family:'Fredoka',sans-serif;color:var(--ink-soft);font-size:14px}
.setting-row select{font-family:'Inter',sans-serif;font-size:15px;padding:8px 10px;border:1.5px solid var(--teal-line);border-radius:12px;background:#fff}
.setting-hint{font-size:13px;color:var(--ink-soft);line-height:1.4;margin:2px 0 8px}
.watch-link{display:inline-block;text-decoration:none;text-align:center}
/* Learning focus: per-strand curation rows with a Boost/Normal/Pause segmented dial. */
.focus-list{display:flex;flex-direction:column;gap:10px}
.focus-row{border:1px solid var(--rule);border-radius:14px;padding:10px 12px;background:var(--paper)}
.focus-strand{font-family:'Fredoka',sans-serif;font-weight:600;font-size:14px;color:var(--ink)}
.focus-plan{font-size:12px;color:var(--ink-soft);margin:2px 0 8px;line-height:1.35}
.focus-dial{display:flex;gap:0;border:1.5px solid var(--teal-line);border-radius:999px;overflow:hidden;background:#fff}
.focus-chip{flex:1;border:none;background:transparent;font-family:'Fredoka',sans-serif;font-weight:500;font-size:12px;color:var(--ink-soft);padding:6px 4px;cursor:pointer;transition:background .15s,color .15s}
.focus-chip+.focus-chip{border-left:1px solid var(--teal-line)}
.focus-chip.on{color:#fff}
.focus-chip.focus-boost.on{background:var(--teal)}
.focus-chip.focus-normal.on{background:var(--ink-soft)}
.focus-chip.focus-pause.on{background:var(--amber)}
.account-link-btn{display:inline-block;text-decoration:none;text-align:center;margin:6px 0 4px}
/* the gentle "nearly time for a rest" heads-up between rounds */
.nearly-done-cue{text-align:center;font-family:'Fredoka',sans-serif;color:var(--amber);background:var(--amber-tint);border-radius:12px;padding:6px 12px;font-size:14px;animation:fade .4s ease}

/* what a shared link gives, noted on arrival: a quiet teal chip above the start
   button, in the same tinted family as the other calm cues (never an alert) */
.shared-code-note{font-size:13px;line-height:1.4;text-align:center;color:var(--teal);background:var(--teal-tint);border-radius:14px;padding:8px 12px;margin:0 auto;max-width:34ch}
.shared-code-note[hidden]{display:none}

/* One real finding on the landing, set as the quoted thing it is: the same
   tinted card family as the grown-up view's gap cards, at landing scale. */
.value-example{font-size:13px;line-height:1.45;text-align:left;color:var(--ink-soft);
  background:var(--teal-tint);border-radius:14px;padding:10px 12px;margin:12px auto 0;max-width:34ch}
/* landing small print + the quiet "already have a player?" text link */
.value-smallprint{font-size:12px;color:var(--ink-soft);text-align:center;margin:14px auto 0;max-width:34ch;line-height:1.5}
/* .linky is shaped for a button; as an anchor it needs its text centred too. */
.value-learn{color:var(--teal);text-align:center}
.linky{display:block;margin:8px auto 0;border:none;background:none;color:var(--teal);font-family:'Fredoka',sans-serif;font-size:14px;text-decoration:underline;cursor:pointer}
/* the returning-family sign-in, held to the landing screen's text column so the
   email field is not the full width of the card */
.value-signin{max-width:32ch;margin:0 auto}

/* landing CTAs centred under the pitch (the buttons are otherwise left-aligned blocks) */
[data-screen="value"] .primary,[data-screen="value"] .ghost{display:block;width:fit-content;min-width:180px;margin:16px auto 0;text-align:center}
[data-screen="value"] .value-note{margin-top:8px}

/* fluency reward — the points score line and the neutral chip groups. sm-steady
   (1 point) and sm-later (next time) are deliberately calm: no failure red anywhere
   on the reward screen. */
.reward-score-big{font-family:'Fredoka',sans-serif;font-weight:600;font-size:20px;color:var(--teal)}
.sm-title.sm-steady{color:var(--ink-soft)}.sm-title.sm-steady::before{background:var(--ink-soft)}
.sm-chip.sm-steady{background:var(--teal-tint);color:var(--teal)}
.sm-title.sm-later{color:var(--ink-soft)}.sm-title.sm-later::before{background:var(--ink-soft)}
.sm-chip.sm-later{background:var(--rule);color:var(--ink-soft)}

/* fluency countdown — a thin quiet drain under the question. Fainter on the second
   window, gone at the floor; it never flashes or turns a warning colour. */
.fluency-bar{width:100%;max-width:200px;height:3px;margin:6px auto 0;border-radius:2px;background:rgba(31,46,54,.07);overflow:hidden;transition:opacity .6s}
.fluency-fill{display:block;height:100%;border-radius:2px;background:var(--teal);opacity:.35}
.fluency-bar.fluency-tier2 .fluency-fill{opacity:.2}
.fluency-bar.fluency-done{opacity:0}

/* scenes: the play area is either a lesson (learning with Pip — a warm amber wash) or
   the game (the default teal/paper). Each names itself in the mode chip, in a fixed spot
   above the play area, so the pair reads as two modes of one world. */
.scene{display:flex;flex-direction:column;gap:12px}
.scene-chip{align-self:center;font-family:'Fredoka',sans-serif;font-weight:600;font-size:13px;padding:5px 14px;border-radius:999px;letter-spacing:.01em;transition:background .3s,color .3s}
.scene-chip:empty{display:none}
.scene-game .scene-chip{background:var(--teal-tint);color:var(--teal)}
.scene-lesson .scene-chip{background:var(--amber-tint);color:#8a5a06}
/* the lesson backdrop wash — amber-leaning, so a lesson feels like a distinct place. The
   game scene leaves the play area on the plain paper it has always used. */
.play-area{transition:background .4s ease}
.scene-lesson .play-area{background:linear-gradient(180deg,rgba(224,148,15,.10),rgba(224,148,15,.03));border:1px solid var(--amber-tint);border-radius:20px;padding:14px 12px 18px}

/* lessons: the watch → together mini-lesson before a new skill is played */
/* Pip at the board: bigger than the game head and centred above it, so the lesson has a
   visible teacher. Sized off the viewport (higher specificity than the short-viewport
   .pip rule, so it holds) and space is reserved so the beat's empty board doesn't jump. */
.lesson-head{flex-direction:column;justify-content:center;gap:8px;max-width:100%}
.lesson-head .pip{width:clamp(60px,18vw,80px);height:clamp(60px,18vw,80px)}
.dino-in{animation:dino-in .5s cubic-bezier(.2,1.2,.4,1)}
@keyframes dino-in{0%{transform:translateY(18px) scale(.7);opacity:0}60%{transform:translateY(-6px) scale(1.05)}100%{transform:translateY(0) scale(1);opacity:1}}
/* the phase copy, reframed from a bare uppercase label into a soft pill inside the scene */
.lesson-phase{text-align:center;font-family:'Fredoka',sans-serif;font-weight:600;color:#8a5a06;background:var(--amber-tint);font-size:13px;padding:4px 12px;border-radius:999px}
.lesson-phase:empty{display:none} /* no bare pill during the entry beat, before the first phase */
/* Monospace so stacked column working lines up; the runner shrinks .board-lines'
   font to fit the card when a generated line is long (lesson.js fitBoardFont). */
.lesson-board{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:76px;max-width:100%;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:1.4rem;font-weight:700;white-space:pre;line-height:1.5;text-align:center}
.board-lines{display:inline-block}
.lesson-say{color:var(--ink-soft);font-size:16px;text-align:center;max-width:30ch;margin:0 auto;line-height:1.45;min-height:44px;display:flex;align-items:center;justify-content:center}
.lesson-controls{display:flex;gap:10px;justify-content:center;margin-top:8px}
.lesson-next{background:var(--teal);color:#fff;border:none;font-family:'Fredoka',sans-serif;font-weight:600;font-size:17px;padding:11px 26px;border-radius:14px;cursor:pointer}
.lesson-again{border:1.5px solid var(--teal-line);background:none;color:var(--teal);font-family:'Fredoka',sans-serif;font-size:14px;padding:9px 16px;border-radius:12px;cursor:pointer}
/* The same control on the subitising flash, where the dots are gone in under a
   second and asking again is the only alternative to guessing. */
.parts-again{display:block;margin:6px auto 0;min-height:44px}

/* placement's quiet escape, and the same escape on every activity round: counts as a
   miss, moves on. .not-sure-skip mirrors .probe-skip; it sits in its own slot below the
   activity so the activity clearing #play-area never wipes it. */
.probe-skip,.not-sure-skip{display:block;margin:18px auto 0;font-size:14px;padding:9px 18px;border:1.5px solid var(--teal-line);border-radius:12px;background:none;color:var(--teal)}
.probe-skip:disabled,.not-sure-skip:disabled{opacity:.5}
.not-sure-slot:empty{display:none}

/* --- the placement surface: greeting, warm-up ramp, probes (probeview.js) ---
   The child's own dino sits over every question here, centred (a round's head puts
   the power bar beside it; a search of unknown length has no bar to fill). The say
   line is the activities' own, so it already holds its height and nothing moves when
   the feedback lands. */
.probe-head{justify-content:center}
/* A question made of words rather than digits (probeview.js sets the class): 52px
   type wraps a word problem to four lines and pushes the model and tiles off the
   phone. Same treatment the activities give their wordy prompts. */
.question-wordy{font-size:clamp(22px,6.4vw,32px);line-height:1.25;max-width:22ch}
/* The hello between Start and the first question (greeting.js): the reward card's
   shape, with room for a dino that bounds in at 96px. */
.greeting{gap:16px;padding-top:22px}
.greeting .reward-pip{width:128px;height:128px}
.greeting .greeting-line{max-width:18ch}

/* Handback: the parent-facing end of the discovery burst — findings, pitch,
   paywall, code redemption (handback.js renders it all into #handback-area) */
.handback-heading h1{margin:0 0 4px}
.handback-sub{font-family:'Fredoka',sans-serif;font-weight:600;font-size:16px;color:var(--teal);margin:18px 0 8px}
.handback-note{font-size:14px;color:var(--ink-soft);line-height:1.45;margin:0 0 10px}
.handback-findings .gap-card{margin-bottom:8px}
.plan-buttons{display:flex;flex-direction:column;gap:10px;margin:10px 0 0}
.plan-button{width:100%}
.handback-status{font-size:13px;color:var(--ink-soft);min-height:18px;margin:6px 0 0}
/* The two words beside the sign-in card's heading. Inline so it wraps with the
   heading rather than claiming a line of its own on a narrow phone. */
.handback-nocard{display:inline-block;margin-left:8px;padding:2px 9px;border-radius:999px;
  background:var(--teal-tint);color:var(--teal);font-size:12px;font-weight:600;
  vertical-align:middle;white-space:nowrap}
/* The Practice Promise line belongs to the ask below it rather than to the
   paragraph above it, so it sits closer to the form — which is also what keeps
   both ways in above the fold on a 390x844 phone. */
.handback-promise{margin-bottom:4px}
/* Who is asking for the address, and where the policy is. Quiet, and present. */
.handback-fineprint{font-size:11px;color:var(--ink-soft);margin:8px 0 0;text-align:center}
.handback-fineprint a{color:inherit}
.code-form{display:flex;gap:8px;margin-top:8px}
.code-form input{flex:1;min-width:0;font-family:'Inter',sans-serif;font-size:15px;padding:10px;border:1.5px solid var(--teal-line);border-radius:12px;text-transform:uppercase}
/* The code path is a minority arrival, and it sits below the ask and the pitch:
   a plain text link, smaller than the copy above it. */
.handback-code .linky{margin:16px auto 0;font-size:13px;color:var(--ink-soft)}
.handback-playmore{display:block;margin:18px auto 0}
/* The child's own control at the top of a return visit (handbackplay.js): their
   dino, one word, and a target a small thumb cannot miss. Big on purpose — it is
   the one thing on this screen addressed to a reader who cannot read. */
.child-play{display:flex;align-items:center;justify-content:center;gap:10px;
  width:100%;min-height:72px;font-size:26px;margin:0 0 4px}
.child-play .pip{width:52px;height:52px;flex:0 0 auto}
/* The one line on this screen addressed to the child. Bigger than the grown-up's
   notes below it and centred under their own button, so it reads as theirs. */
.child-play-note{margin:0 0 4px;text-align:center;font-size:16px;color:var(--ink-soft)}

/* closing the grown-up panel: an X, and a tap anywhere outside */
.adult-backdrop{position:fixed;inset:0;background:rgba(31,46,54,.25);z-index:14}
.adult-backdrop[hidden]{display:none}
.adult-close{position:absolute;top:12px;right:14px;width:36px;height:36px;border:none;border-radius:50%;background:var(--teal-tint);color:var(--teal);font-size:16px;cursor:pointer;line-height:1}
/* opened with no child selected: just the note (and sync below), not stale sections */
.adult-panel.no-child .adult-sub,.adult-panel.no-child .gap-summary,.adult-panel.no-child .map,
.adult-panel.no-child .map-legend,.adult-panel.no-child .record,.adult-panel.no-child .setting-row,
.adult-panel.no-child .setting-hint,.adult-panel.no-child .watch-link,.adult-panel.no-child .adult-log,
.adult-panel.no-child .focus-list,.adult-panel.no-child .adult-actions{display:none}

/* --- magic-link sign-in form (auth-flow) ------------------------------------
   Shaped like .code-form but for an address: stacked so a long email stays
   readable on a phone, and with none of the code field's upper-casing. The
   progress bar shows only while a request is in flight (signinform.js toggles
   .is-sending), so a parent on a slow connection can see it working. */
.signin-form{display:flex;flex-direction:column;gap:8px;margin-top:8px}
.signin-form input{width:100%;min-width:0;font-family:'Inter',sans-serif;font-size:16px;padding:10px;
  border:1.5px solid var(--teal-line);border-radius:12px;background:#fff}
.signin-emails-note{font-size:11px;color:var(--ink-soft);margin:0;text-align:center}
.signin-progress{display:none;height:6px;border-radius:999px;background:var(--teal-tint)}
.signin-form.is-sending .signin-progress{display:block;animation:signin-breathe 1.4s ease-in-out infinite}
@keyframes signin-breathe{0%,100%{opacity:.3}50%{opacity:1}}
@media (prefers-reduced-motion:reduce){
  .signin-form.is-sending .signin-progress{animation:none;opacity:.75}
}

/* --- provider sign-in above the email field (signinproviders.js) ------------
   Google's guidelines want their own mark on a white/bordered control, so this
   is .primary's shape with the app's colours taken out. It is an anchor, laid
   out as a full-width button; the separator below it names the alternative
   underneath it rather than reading as a bare "or".

   Of the two ways in this is the one carrying the weight — live SES is still
   sandboxed, so it is the one that reaches a stranger's inbox. It cannot take
   the app's solid teal (a Google button must keep its own light/neutral/dark
   treatment), so the weight comes from size and lift instead, and the email
   button below it drops to .ghost (signinform.js swaps the class when a
   provider is actually offered). */
.signin-providers{display:flex;flex-direction:column;gap:8px;margin-top:8px}
/* Scoped through the row so it outranks the value screen's own .primary rule,
   which would otherwise lay the button out as a block and centre it. */
.signin-providers .provider-button{display:flex;align-items:center;justify-content:center;gap:10px;
  width:auto;margin:0;padding:15px 14px;background:#fff;color:#3c4043;
  border:1.5px solid var(--teal-line);border-radius:16px;
  box-shadow:0 3px 10px rgba(31,46,54,.12);
  text-decoration:none;font-size:18px}
.provider-mark{width:22px;height:22px;flex:none}
.signin-or{display:flex;align-items:center;gap:10px;margin:4px 0 0;color:var(--ink-soft);font-size:13px}
.signin-or::before,.signin-or::after{content:"";flex:1;height:1px;background:var(--teal-line)}
/* The email path, once it is the secondary one: still a full-width control a
   parent can find and hit, in the quiet treatment. */
.signin-form .signin-send{width:100%;padding:12px}
.signin-form .signin-send.ghost{font-size:15px;padding:10px;background:transparent;
  border:1px solid var(--teal-line)}

/* The one line a failed provider sign-in leaves behind (signinnotice.js), on
   the landing and the picker. Dismissible, and out of the way until there is
   something to say. */
.signin-notice{display:flex;align-items:center;gap:8px;margin:0 auto 12px;max-width:44ch;
  padding:8px 12px;border:1.5px solid var(--teal-line);border-radius:12px;background:var(--teal-tint);
  font-size:14px;text-align:left}
.signin-notice[hidden]{display:none}
.signin-notice-line{flex:1}
.signin-notice-dismiss{flex:none;border:none;background:none;cursor:pointer;
  font-size:18px;line-height:1;padding:4px;color:inherit}

/* --- waiting for the entitlement grant after payment (auth-flow) ------------
   Same breathing bar as the sign-in form, on the post-payment wait. It shows
   only while the poll is running; the timed-out view replaces it with the
   check-again button. */
.handback-wait{display:flex;flex-direction:column;gap:10px;margin-top:6px}
.wait-line{margin:0}
.wait-progress{display:block;height:6px;border-radius:999px;background:var(--teal-tint);
  animation:signin-breathe 1.4s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){.wait-progress{animation:none;opacity:.75}}

/* --- turn the phone back round ---------------------------------------------
   A phone held sideways gives the activities a viewport ~350px tall: the
   question, the answer tiles and the pad cannot all fit, so a child playing
   sideways meets a screen that half works. The manifest asks an installed app
   for portrait; a browser tab honours nothing of the sort, so this covers it
   instead — Pip asking to be turned round, which is a thing to do rather than a
   failure to read about.

   The card is fully styled here and hidden; the media query below only turns it
   on. Keeping the look outside the query means what shows can never be a
   half-styled card. */
.rotate-card{display:none;position:fixed;inset:0;z-index:900;background:var(--paper);
  align-items:center;justify-content:center;padding:16px;text-align:center}
.rotate-card-box{max-width:340px;display:flex;flex-direction:column;align-items:center;gap:6px}
.rotate-pip{font-size:56px;line-height:1;display:block;
  transform:rotate(90deg);animation:rotate-nudge 2.2s ease-in-out infinite}
.rotate-h{font-family:'Fredoka',sans-serif;font-weight:600;font-size:22px;color:var(--teal);margin:0}
.rotate-note{color:var(--ink-soft);font-size:15px;margin:0}
@keyframes rotate-nudge{0%,55%{transform:rotate(90deg)}80%,100%{transform:rotate(0deg)}}
@media (prefers-reduced-motion:reduce){.rotate-pip{animation:none;transform:rotate(0deg)}}
/* Phones only: landscape AND a viewport too short to play in AND a touch device,
   so a laptop (landscape, tall enough, mouse) and a tablet (landscape, tall
   enough) never see it. */
@media (orientation:landscape) and (max-height:520px) and (pointer:coarse){
  .rotate-card{display:flex}
}
