/* 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';
/* The palette carries a WCAG 2.1 AA floor (test/a11y-contrast.test.mjs computes
   every declared pairing). Two rules keep it there, and both are why some hues
   appear twice:

   1. A colour that CARRIES TEXT is darker than the same colour used as a fill.
      --amber paints the power bar, the counters, the arcs and the confetti and
      is unchanged; --amber-ink is the same hue at the lightness AA needs, and is
      the only amber allowed under or over words. --dino and --dino-dk are the
      same split for the greens. Nothing decorative moved, so no filled shape on
      the child's screen changed colour.
   2. A hover state may not be darker than the tint its label was measured on.
      --teal-tint-hover is the one deeper tint teal text still clears 4.5:1 on;
      --teal-line stays a border colour and is no longer a background under text. */
:root{
  --paper:#fffdf8; --panel:#ffffff;
  --ink:#1f2e36; --ink-soft:#586a72;
  --teal:#0c6d6c; --teal-tint:#e2f0ef; --teal-tint-hover:#d2e8e6; --teal-line:#9bc9c7;
  --amber:#e0940f; --amber-ink:#8a5a06; --amber-tint:#faecd2;
  --dino:#57b894; --dino-dk:#327c62; --dino-deep:#2a6a52;
  --danger:#c23f3f; --danger-ink:#a63f29;
  --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-tint-hover)}
.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}
/* Without this the rule above beats the browser's own [hidden] and the school year
   painted, greyed out, on the kid-facing edit that sets hidden on it. */
.profile-form label[hidden]{display:none}
/* A heading focused on a screen change (headingfocus.js). :focus-visible does not match
   programmatic focus so no ring is drawn in a current browser, and a tabindex="-1"
   element is not keyboard reachable, so suppressing the outline takes nothing from a
   keyboard user. Kept for browsers that still draw a plain :focus ring. */
h1[tabindex="-1"]:focus{outline:none}
.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}
/* The two halves of the create flow — the parent's step and the child's. Creating
   shows one at a time; editing shows both, which is why they space themselves the
   same way the form does rather than being one flex child each. */
.form-step{display:flex;flex-direction:column;gap:12px}
.form-step[hidden]{display:none}
/* The note under the school year question, shown only in July and August (app.js). */
.field-note{font-size:13px;color:var(--ink-soft);line-height:1.4;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}
/* Each chip is a button — tapping one opens the skill card — so it carries the reset a
   button needs to keep looking like the pill it was. */
.chip{display:flex;align-items:center;gap:6px;padding:5px 10px;border:none;border-radius:999px;background:var(--locked);font-family:'Fredoka',sans-serif;font-size:12px;color:#6a6152;text-align:left;cursor:pointer;transition:transform .15s,background .3s}
.chip:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.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:var(--amber-ink)}.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:#7d4305}.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:var(--teal-tint-hover);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-ink);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)}
/* TWO COMPONENTS SHARE THESE CLASS NAMES. .nl-line is the drawn line from
   models/numberline.js. .hop-line is the hand-built row of tappable stones in
   activities/counting.js and numberjumps.js, which reuses .numberline and .nl-tick for
   their type and centring only. .numberline and the bare .nl-tick therefore hold just
   what both want; everything a drawn line needs is under .nl-line, and everything a
   stone needs is under .hop-line (further down, in the games block). */
.numberline{display:flex;gap:0;max-width:100%}
.nl-tick{position:relative;display:flex;align-items:center;justify-content:center;font-family:'Fredoka',sans-serif;color:var(--ink-soft)}

/* A DRAWN LINE. One rule, from the first tick to the last, and every tick pinned at
   its true fraction of the span (--nl-pos, set per tick by numberline.js). This was
   once a second mode used only for off-grid marks; it is now the only one, because
   equal-width cells misplace ticks the moment the value gaps are unequal and run the
   rule half a step past each end. Spacing between ticks is capped off --nl-steps so a
   five-tick line does not stretch across a wide screen, and the line then centres. */
.nl-line{position:relative;display:block;height:38px;width:100%;margin-inline:auto;
  max-width:calc(var(--nl-steps) * 44px + 2 * var(--nl-inset,18px))}
.nl-line::before{content:'';position:absolute;top:0;left:var(--nl-inset,18px);right:var(--nl-inset,18px);border-top:2px solid var(--teal-line)}
/* max-content, not the available width: a tick near the right-hand end has almost no
   room between its left edge and the container, and shrink-to-fit would squeeze its
   label. The box hugs the label and translateX centres both on the value. */
.nl-line .nl-tick{position:absolute;top:0;height:38px;width:max-content;flex-direction:column;justify-content:flex-start;font-size:14px;
  left:calc(var(--nl-inset,18px) + var(--nl-pos,0) * (100% - 2 * var(--nl-inset,18px)));transform:translateX(-50%)}
/* The stroke IS the tick: it stands on the rule at the value, and it is what a child
   counts to read 7/5 off a line marked in fifths. Every tick draws one, labelled or
   not. On a line that shows parts, the wholes stand taller than the parts between. */
.nl-stroke{width:2px;height:7px;border-radius:1px;background:var(--teal-line);flex:0 0 auto}
.nl-whole .nl-stroke{width:3px;height:13px;background:var(--teal)}
.nl-minor .nl-label{display:none}
/* A marked value is an ARROW, with its apex on the rule, and the pill hangs under it.
   The pill is on the label, never on the tick box — a pill as wide as a tick cell
   covers half a step either side of the value it claims to name.
   IT IS AN ARROW AND NOT A STROKE because of the word for it. This line now draws
   eleven tick marks, and the item's own distractor codes "counted the tick marks rather
   than the steps between them" (activities/fractionlineitems.js). A prompt that called
   the target "the mark" would tell a child to count marks and then record the answer as
   the misconception the sentence produced. The prompt says "the arrow", nothing else on
   the line is an arrow, and an arrow has a point — which is what the marker is for. */
.nl-mark .nl-stroke{width:0;height:0;background:none;border-radius:0;
  border-left:6px solid transparent;border-right:6px solid transparent;
  border-bottom:10px solid var(--amber-ink)}
.nl-mark .nl-label{background:var(--amber-tint);color:var(--amber-ink);border-radius:8px;padding:1px 6px}
/* 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-start .nl-stroke{background:var(--amber)}
.nl-start .nl-label{background:transparent;box-shadow:inset 0 0 0 2px var(--amber);color:var(--ink)}
.nl-step{opacity:.55}
.nl-step .nl-label{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. */
.nl-line.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. nowrap so a thousands-separated label never breaks across two lines. */
.nl-label{margin-top:3px;white-space:nowrap;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)}
/* Jumps ("hops: true"): an amber arc over each step, so "4 jumps of 2" shows four
   visible hops. The arc spans two ticks, so it is placed on the line from one
   position to the next; margin leaves headroom for the arcs above the rule. */
.nl-jumps{margin-top:16px;max-width:calc(var(--nl-steps) * 64px + 2 * var(--nl-inset,18px))}
.nl-arc{position:absolute;top:-14px;height:14px;border:2.5px solid var(--amber);border-bottom:none;border-radius:50% 50% 0 0/100% 100% 0 0;
  left:calc(var(--nl-inset,18px) + var(--nl-from) * (100% - 2 * var(--nl-inset,18px)));
  width:calc((var(--nl-to) - var(--nl-from)) * (100% - 2 * var(--nl-inset,18px)))}
.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-ink)}
.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:var(--amber-ink);font-weight:600}
.log-session_start{background:transparent;color:#b9b09c;text-align:center}
.log-lesson_shown{background:#f2eee2;font-style:italic}
.adult-actions{margin-top:16px;display:flex;flex-direction:column;gap:8px}
/* The one line a parent reads about whether this device's practice reached us
   (practicesaved.js). No box and no border: it is a sentence in the panel, not
   a readout. Device-level, so it has its own class and is not in the
   .adult-panel.no-child list — the grown-up button's attention dot points at
   it, and it has to exist in every state the dot can be on in. */
.practice-saved{font-size:13px;color:var(--ink-soft);line-height:1.4;margin:14px 0 8px}
.practice-saved p{margin:0 0 4px}
.practice-saved-alert{color:var(--danger-ink)}
/* The uploader's detailed status, now read only inside the developer block
   (devpanel.js) — ui.js renderSyncStatus draws it. */
.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}
/* Developer tools, built into the panel's foot only for a developer browser
   (devpanel.js). Ruled off above so it never reads as part of what a family is
   being offered. */
.devtools{margin-top:20px;padding-top:12px;border-top:1px dashed var(--rule);
  display:flex;flex-direction:column;gap:8px}
.devtools[hidden]{display:none}
.devtools-title{font-family:'Fredoka',sans-serif;font-size:13px;color:var(--ink-soft);
  margin:0;text-transform:uppercase;letter-spacing:.04em}
.ghost{background:var(--teal-tint);color:var(--teal);border:none;padding:10px;border-radius:12px;font-size:14px}
.ghost.danger{background:#f7e4e1;color:var(--danger-ink)}

/* 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:var(--amber-ink)}
.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:var(--amber-ink)}
.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-practised{background:#3fa07a}
.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:var(--danger-ink);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}
  /* 44px is the floor, not 38: this tier used to shrink the key to 38 and a second
     @media block further down the file put it back, which meant the file said two
     different things and only source order decided. One declaration now. */
  .key{width:clamp(44px,11vw,48px);height:clamp(44px,11vw,48px);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-ink)}
.equation .a{color:var(--amber-ink)}
.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:var(--teal-tint-hover)}
.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:var(--teal-tint-hover)}

/* 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-dk);color:#fff;border-color:var(--dino)}
.hop-line .nl-mark{background:var(--secure);color:#0b3f2c;border-color:#0b6b46}
.hop-line .nl-hop{background:var(--amber-ink);color:#fff;border-color:var(--amber);animation:pip-bounce .3s ease}
.hop-line .nl-ten{box-shadow:inset 0 0 0 2px var(--amber)}
.hop-line .nl-wrong{background:var(--danger);color:#fff;border-color:#e05a5a;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;min-height:44px;border-radius:14px;background:var(--dino-dk);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-ink)}
.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}

/* The referral card (referralcard.js). Its own classes rather than .adult-sub /
   .setting-hint, because it is family-level and must survive the no-child state
   that hides the per-child sections. */
.referral-card{margin:14px 0;padding:10px 12px;background:var(--teal-tint,#e6f4f3);border-radius:12px}
.referral-title{font-family:'Fredoka',sans-serif;font-size:13px;color:var(--ink-soft);margin:0 0 6px;text-transform:uppercase;letter-spacing:.04em}
.referral-note{font-size:13px;color:var(--ink-soft);line-height:1.4;margin:2px 0 8px}
.referral-actions{display:flex;flex-wrap:wrap;gap:8px}
a.referral-share{display:inline-block;text-decoration:none;text-align:center}
.referral-share{background:#fff}
.referral-url{font-size:12px;color:var(--ink-soft);word-break:break-all;margin:8px 0 0}

/* The skill card: what a tapped pill opens (pillcard.js). It sits above the grown-up
   panel (z-index 15) and its backdrop (14), and rises from the bottom edge, which is
   where a thumb already is on a phone. A peek, not a screen: it is as tall as its own
   words, and the map is still there when it closes. */
.skill-card-layer{position:fixed;inset:0;z-index:16;background:rgba(31,46,54,.3);
  display:flex;align-items:flex-end;justify-content:center}
.skill-card{position:relative;width:100%;max-width:420px;background:var(--panel);
  border-radius:18px 18px 0 0;box-shadow:0 -6px 30px rgba(31,46,54,.18);
  padding:18px 18px calc(20px + env(safe-area-inset-bottom,0px));
  max-height:80vh;overflow-y:auto;animation:skill-card-rise .18s ease-out}
@keyframes skill-card-rise{from{transform:translateY(14px);opacity:.6}to{transform:none;opacity:1}}
.skill-card:focus{outline:none}
.skill-card-close{position:absolute;top:8px;right:8px;width:40px;height:40px;border:none;
  background:transparent;color:var(--ink-soft);font-size:17px;cursor:pointer}
.skill-card-heading{font-family:'Fredoka',sans-serif;font-size:18px;color:var(--teal);
  margin:0 44px 8px 0}
.skill-card-sentence{margin:0 0 10px;font-size:15px;line-height:1.45}
.skill-card-state{margin:0;font-size:15px;font-weight:600;background:var(--teal-tint);
  border-radius:12px;padding:10px 12px}
.skill-card-link{display:block;margin-top:12px;color:var(--teal);font-size:14px;font-weight:600}
/* Once the panel is a column beside the page rather than most of a phone screen, the
   card belongs to that column: same width, same edge. The wash still covers the page,
   because the card is modal — the panel behind it is inert while it is open. */
@media (min-width:700px){
  .skill-card-layer{justify-content:flex-end}
  .skill-card{max-width:320px}
}

/* 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-ink)}
.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-ink);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-ink);background:var(--amber-tint);border-radius:14px;padding:9px 14px;margin-top:6px;max-width:300px}
/* The collection strip: the sentence first, then the four things, each drawn on
   its own and filling its space so a child can tell them apart at phone width
   (worn art is what the shelf shows; at 58px the scarf vanished into the body).
   No count of what is left, and no state for a day not played. The sentence
   carries the whole strip for a 4-11 year old, so it is sized as a line to
   read, not as a caption under a picture. */
.ending-collect{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:12px}
.ending-collect-line{font-family:'Fredoka',sans-serif;font-size:17px;line-height:1.35;color:var(--ink);max-width:300px}
.ending-collect-row{display:flex;gap:14px;justify-content:center}
/* No border, no plate, no inset: a bordered empty cell reads as a hole in a
   sticker album, and a child reads a hole as something missing far more sharply
   than a faded picture. The slot is the picture.

   Which ones are mine is answered by marking the OWNED piece, never the
   unowned: a warm halo with no edge to it, a slightly bigger drawing and full
   ink. A child sees one state only and has nothing to compare against, so the
   owned piece has to look positively picked out rather than merely less faded —
   and the unowned pieces must never be pushed fainter to manufacture the
   difference, because that is the deficit reading again. Pinned by
   endingcard.style.test.js, which fails anything that styles the unowned. */
.ending-collect-slot{display:flex;align-items:center;justify-content:center;width:60px;height:52px;opacity:.6}
.ending-collect-slot.got{opacity:1;transform:scale(1.1);
  background:radial-gradient(circle,rgba(224,148,15,.28) 0%,rgba(224,148,15,0) 70%)}
.ending-collect-slot.got .ending-collect-art{filter:drop-shadow(0 1px 3px rgba(201,122,12,.5))}
.ending-collect-art{width:100%;height:100%}
.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-ink)}.sm-title.sm-quicker::before{background:var(--amber)}
.sm-title.sm-practise{color:var(--danger-ink)}.sm-title.sm-practise::before{background:var(--danger-ink)}
.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:var(--amber-ink)}
.sm-chip.sm-practise{background:#f7e4e1;color:var(--danger-ink)}

/* 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-ink);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-tint-hover)}
/* 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:var(--danger);color:#fff;border-color:#e05a5a}

/* shared number-strand model components (src/app/models/) */
/* 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-ink)}
.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-ink);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-dk);color:#fff}
.digit-pad .key:last-child:hover,.digit-entry .key:last-child:hover{background:var(--dino-deep)}
/* 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-ink)}

/* 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-ink)}
/* 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-ink);opacity:.9}
.wm-mark{color:var(--amber-ink)}
.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}


/* 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}

/* 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)}
/* The chosen segment carries a white label, so its fill is the ink amber rather
   than the decorative one: white on --amber is 2.50:1. */
.focus-chip.focus-pause.on{background:var(--amber-ink)}
.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-ink);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}
/* The sign-in requirement, set above the Start button rather than under it, and
   in the body ink rather than the soft grey the notes use: a parent has to read
   it before they tap, so it is not styled as small print. */
.value-disclosure{font-family:'Fredoka',sans-serif;font-weight:600;font-size:15px;color:var(--ink);
  text-align:center;margin:16px auto 0;max-width:32ch;line-height:1.4}
/* The three-question taster, given a card of its own. It is the only part of the
   product a parent can see before signing in, so it reads as an offer rather
   than as a footnote to the example above it. */
.value-taster{border:1.5px solid var(--teal-line);border-radius:16px;background:var(--paper);
  padding:14px 14px 12px;margin:18px auto 0;max-width:34ch}
[data-screen="value"] .value-taster .ghost{margin-top:0;font-size:16px;padding:12px 16px}
.value-taster-note{font-size:13px;color:var(--ink-soft);text-align:center;margin:10px auto 0;
  max-width:30ch;line-height:1.45}
/* 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}
/* The status line of the sign-in the Start tap mounts. It sits between the
   button and the note that follows it on every ordinary visit, so while it has
   nothing to say it takes no room: a blank paragraph's margin would open a gap
   in the landing for nothing. */
#value-start-status:empty{margin:0}

/* 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:var(--amber-ink)}
/* 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:var(--amber-ink);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}
/* The overdue-payment card (handbackoverdue.js). Amber rather than danger:
   nothing has been taken away from the child, and the parent is being asked to
   do something, not warned that they have lost something. Bordered like the
   add-to-home-screen card so the screen has one shape for "a thing to do". */
.handback-overdue{margin:0 0 4px;padding:12px;border:1.5px solid var(--amber);
  border-radius:12px;background:var(--amber-tint)}
.handback-overdue .handback-sub{margin-top:0;color:var(--amber-ink)}
/* An anchor wearing the primary button's clothes, so it needs the underline
   taking off and the text centring that a <button> gets for free. */
.handback-overdue-action{display:block;width:100%;margin-top:4px;
  text-align:center;text-decoration:none}
/* 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 add-to-home-screen invitation (homescreencard.js), on the confirmation
   after sign-in. Outlined rather than filled: the screen already has one filled
   button — Back to playing — and this is the smaller of the two things to do.
   Empty and hidden on every device that gets no invitation, so it takes no
   space there. */
.homescreen-card{margin:16px 0 0;padding:12px;border:1.5px solid var(--teal-line);border-radius:12px}
.homescreen-card .handback-sub{margin-top:0}
.homescreen-card .handback-note{margin-bottom:10px}
.homescreen-add{display:block;width:100%}
.homescreen-decline{font-size:13px;color:var(--ink-soft)}
/* The standing entry in the grown-up panel (homescreenentry.js). No box, no
   border and no tint: it is a section of the panel like Screen time and Watch
   from a distance, and these two rules carry the same declarations as
   .adult-sub and .setting-hint so it reads as one of them. It has its own class
   names rather than theirs because .adult-panel.no-child hides those, and this
   entry is about the device rather than about a child. */
.homescreen-entry-title{font-family:'Fredoka',sans-serif;font-size:13px;color:var(--ink-soft);
  margin:14px 0 6px;text-transform:uppercase;letter-spacing:.04em}
.homescreen-entry-note{font-size:13px;color:var(--ink-soft);line-height:1.4;margin:2px 0 8px}
/* The Players section (playerlistcard.js). Its own class names rather than
   .adult-sub and .setting-hint, for the same reason the home-screen entry has
   its own: .adult-panel.no-child hides those, and this section has to stand
   when the panel is opened from the who's-playing screen with nobody selected.
   The two title rules carry the same declarations as .adult-sub so it reads as
   one of the panel's sections. */
.player-list-title{font-family:'Fredoka',sans-serif;font-size:13px;color:var(--ink-soft);
  margin:14px 0 6px;text-transform:uppercase;letter-spacing:.04em}
.player-list-hint{font-size:13px;color:var(--ink-soft);line-height:1.4;margin:2px 0 8px}
.player-list{display:flex;flex-direction:column;gap:6px}
/* Name on the left, controls on the right; the row wraps rather than squeezing
   the buttons, because a long name on a 390px phone would otherwise shrink the
   targets below the floor. */
.player-row{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:8px;padding:8px 10px;border:1px solid var(--rule);border-radius:12px;background:var(--paper)}
.player-row-name{font-family:'Fredoka',sans-serif;font-size:15px;color:var(--ink)}
.player-row-actions{display:flex;gap:8px}
.player-row-action{min-height:44px;padding:10px 14px;font-size:14px}
/* The delete form, opened inside the player's own row. It takes the whole row
   width so the warning reads as a paragraph rather than a squeezed column, and
   it replaces that row's buttons while it is open. Tinted like .ghost.danger so
   the row it is in looks different from every other row on the screen. */
.player-delete{flex:1 1 100%;display:flex;flex-direction:column;gap:8px;
  margin-top:4px;padding:10px;border-radius:12px;background:#f7e4e1}
.player-delete-warning{margin:0;font-size:13px;line-height:1.4;color:var(--danger-ink)}
.player-delete-label{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--danger-ink)}
/* 16px so iOS does not zoom the panel when the box takes focus. */
.player-delete-input{min-height:44px;padding:10px;font-family:'Inter',sans-serif;font-size:16px;
  border:1px solid var(--rule);border-radius:12px;background:var(--paper);width:100%;min-width:0}
.player-delete-note{margin:0;font-size:13px;line-height:1.4;color:var(--danger-ink);font-weight:600}
.player-delete-note[hidden]{display:none}
/* The line on the who's-playing screen when every player is hidden. */
.picker-note{font-size:14px;color:var(--ink-soft);line-height:1.4;margin:8px 0;text-align:center}
.picker-note[hidden]{display:none}
/* 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: the note that says where to choose one, the
   family-level and device-level sections, and nothing that would be blank or
   another child's leftovers.
   .adult-rhythm and .gap-intro joined this list on 2026-08-07. Both are
   .adult-note, which is NOT in the list — #adult-nochild is an .adult-note too
   and is the one note this state exists to show — so each per-child note needs
   a class of its own here. Until then a parent with nobody selected read "The
   gap-walk runs quietly while your child plays. Here's what it found." over the
   hidden section it introduces. */
.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,
.adult-panel.no-child .adult-rhythm,.adult-panel.no-child .gap-intro{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}
}

/* --- target size -----------------------------------------------------------
   Last in the file so it wins on order over the layout rules above, which set
   `display` on several of these and would otherwise leave a label stuck to the
   top of a taller box.

   Two floors, both from WCAG: 24x24 is the AA minimum (2.2, 2.5.8) and 44x44 is
   what 2.5.5 asks. The answer tiles, the keypad, the hop-line stones and the
   counting objects were already at or above 44 and are untouched. These were
   not: the panel's buttons and the round's escape were 37-38px high, and the
   footer's policy links were 15px.

   The tile corner buttons (.player-edit, .player-shelf) are deliberately left at
   30x30. They clear the AA floor, and a 44px target in the corner of a 112px
   tile would take taps from the dino under it, which is the control a child who
   cannot read is aiming for. */
.ghost,.primary,.probe-skip,.not-sure-skip{min-height:44px}
/* A <button> centres its own content in a taller box; an anchor laid out as one
   does not, so the anchors say so. */
a.ghost,a.primary{display:inline-flex;align-items:center;justify-content:center}
a.ghost.homescreen-add,a.ghost.shelf-back{display:flex}
.probe-skip,.not-sure-skip{display:flex;align-items:center;justify-content:center;
  width:max-content}
/* The X on the grown-up panel keeps its 36px circle and gains the invisible 44px
   pad the topbar buttons use, so the drawing does not grow. */
.adult-close{position:absolute}
.adult-close::before{content:'';position:absolute;top:50%;left:50%;
  transform:translate(-50%,-50%);width:100%;height:100%;min-width:44px;min-height:44px}
/* Text links: a line of 12-14px type is 15-19px tall, under the 24px AA floor.
   Padding rather than a pad element, so wrapped footer links keep clear of each
   other rather than overlapping into one ambiguous target. */
.linky{padding:5px 4px;min-height:24px}
.value-smallprint a{display:inline-block;padding:5px 3px}
.setting-row select,.profile-form input,.profile-form select,.signin-form input,
.code-form input{min-height:44px}
