/* ============================================================
   Alpha Arcade Pocket — styles
   British English throughout (colour, centre, grey ...)
   ============================================================ */

:root {
  --font: "Lexend", "Baloo 2", "Quicksand", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Font-size scale, switched via [data-font] on <html> */
  --scale: 1;
  --base: calc(16px * var(--scale));

  /* Module accent colours (match the hub arrows) */
  --c-library: #f2b8d6;   /* up    — pink   */
  --c-balloon: #f4a259;   /* left  — orange */
  --c-spell:   #6fc4c9;   /* right — teal   */
  --c-trace:   #8fd17a;   /* down  — green  */
  --c-hub:     #bfe3f0;

  --ink: #41506b;
  --ink-soft: #6b7a96;
  --panel: rgba(255, 255, 255, 0.82);
  --shadow: 0 10px 30px rgba(70, 90, 130, 0.22);
  --radius: 22px;

  /* Collectible cards are cut to the artwork's own 298 × 444 proportions, so
     no part of the painted frame is ever cropped away. */
  --card-ratio: 149 / 222;
}

html[data-font="small"]  { --scale: 0.85; }
html[data-font="medium"] { --scale: 1; }
html[data-font="large"]  { --scale: 1.25; }

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  font-family: var(--font);
  font-size: var(--base);
  color: var(--ink);
  background: #cfeaf2;
  overscroll-behavior: none;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

button { font-family: inherit; cursor: pointer; }

/* ---------------- Persistent drifting skybox ---------------- */
.sky {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}
.sky__gradient {
  position: absolute;
  inset: -10%;
  background:
    radial-gradient(120% 90% at 50% 18%, #fff6dd 0%, #d7f0ec 32%, #cfe6f6 60%, #e7d6f0 100%);
  animation: skyHue 60s ease-in-out infinite alternate;
}
.sky__orb {
  position: absolute;
  left: 50%;
  top: 42%;
  width: 160vmax;
  height: 160vmax;
  transform: translate(-50%, -50%);
  background: url("../Assets/Background/Alpha_Arcade_Pocket_Dynamic_Background.png") center / contain no-repeat;
  opacity: 0.5;
  filter: saturate(1.05);
  animation: skyDrift 90s ease-in-out infinite alternate;
}
.sky__cloud {
  position: absolute;
  width: 46vmax;
  height: 22vmax;
  background: radial-gradient(closest-side, rgba(255,255,255,0.9), rgba(255,255,255,0) 72%);
  filter: blur(2px);
}
.sky__cloud--1 { top: 12%; left: -10%; animation: cloud1 80s linear infinite; }
.sky__cloud--2 { top: 38%; left: 60%; animation: cloud2 120s linear infinite; opacity: .8; }
.sky__cloud--3 { top: 66%; left: 20%; animation: cloud3 100s linear infinite; opacity: .7; }

@keyframes skyDrift {
  0%   { transform: translate(-52%, -48%) scale(1.02) rotate(0deg); }
  100% { transform: translate(-48%, -52%) scale(1.08) rotate(3deg); }
}
@keyframes skyHue { 0% { filter: hue-rotate(0deg); } 100% { filter: hue-rotate(-12deg); } }
@keyframes cloud1 { from { transform: translateX(-30vmax); } to { transform: translateX(120vw); } }
@keyframes cloud2 { from { transform: translateX(60vw); } to { transform: translateX(-60vmax); } }
@keyframes cloud3 { from { transform: translateX(-40vmax); } to { transform: translateX(120vw); } }

@media (prefers-reduced-motion: reduce) {
  .sky__orb, .sky__cloud, .sky__gradient { animation: none; }
}

/* ---------------- 3x3 navigation grid ---------------- */
.grid {
  position: fixed;
  inset: 0;
  z-index: 1;
  width: 300vw;
  height: 300dvh;
  display: grid;
  grid-template-columns: repeat(3, 100vw);
  grid-template-rows: repeat(3, 100dvh);
  transform: translate3d(-100vw, -100dvh, 0); /* start on hub; JS overrides */
  transition: transform 520ms cubic-bezier(.7,.05,.25,1);
  will-change: transform;
}
.grid.is-dragging { transition: none; }

/* Place screens into the cross */
.screen { position: relative; width: 100vw; height: 100dvh; overflow: hidden; }
[data-screen="library"] { grid-column: 2; grid-row: 1; }
[data-screen="balloon"] { grid-column: 1; grid-row: 2; }
[data-screen="hub"]     { grid-column: 2; grid-row: 2; }
[data-screen="spell"]   { grid-column: 3; grid-row: 2; }
[data-screen="trace"]   { grid-column: 2; grid-row: 3; }

/* ---------------- Generic UI ---------------- */
.btn {
  border: none;
  border-radius: 999px;
  padding: 0.7em 1.4em;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
  background: #fff;
  box-shadow: var(--shadow);
}
.btn--primary { background: var(--c-spell); color: #08343a; }
.btn:active { transform: translateY(1px) scale(.98); }

.home-btn {
  position: absolute;
  top: max(14px, env(safe-area-inset-top));
  left: 14px;
  z-index: 5;
  width: 2.6em;
  height: 2.6em;
  border-radius: 50%;
  border: none;
  font-size: 1rem;
  background: var(--panel);
  box-shadow: var(--shadow);
  display: grid;
  place-items: center;
  backdrop-filter: blur(6px);
}

.screen-title {
  position: absolute;
  top: max(16px, env(safe-area-inset-top));
  left: 0; right: 0;
  text-align: center;
  font-weight: 800;
  font-size: 1.4rem;
  letter-spacing: .5px;
  color: var(--ink);
  text-shadow: 0 2px 8px rgba(255,255,255,.6);
  pointer-events: none;
  z-index: 4;
}

/* ---------------- Global nav + settings chrome ---------------- */
/* Bottom-right cluster of pixel-art buttons, present on every screen so
   settings and hub-return are always one tap away, whatever the swipe. The
   icons carry their own art (shape, colour, border), so the buttons are just
   transparent hit targets sized generously for small fingers. */
.chrome {
  position: fixed;
  right: max(10px, env(safe-area-inset-right));
  bottom: max(10px, env(safe-area-inset-bottom));
  z-index: 40;
  display: flex;
  align-items: center;
  gap: .35rem;
}
.chrome-btn {
  border: none;
  background: transparent;
  padding: .25em;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: transform .15s ease;
  -webkit-tap-highlight-color: transparent;
}
.chrome-btn:active { transform: translateY(2px) scale(.95); }
.chrome-btn img {
  display: block;
  height: 34px;
  width: auto;
  filter: drop-shadow(0 4px 8px rgba(70, 90, 130, .3));
}
.chrome-btn[aria-label="Settings"] img { height: 42px; }   /* near-square gear */
/* HOME is the way back to the hub from anywhere — every game, and the
   Information / LEVELS / Coming Soon page. It is hidden only on the hub itself,
   where there is nowhere to return to. */
body[data-active="hub"]:not([data-home="true"]) .chrome-btn--home { display: none; }
/* MENU leads OUT of the learning environment, so it appears on the hub alone,
   inside the centre circle (.hub__btn). The corner copy is never shown; it is
   the handler the circle's button drives. */
.chrome-btn--menu { display: none; }
/* On the hub the corner cluster would only duplicate the centre circle. It
   returns over the Coming Soon page, where HOME leads back to the hub. */
body[data-active="hub"]:not([data-home="true"]) .chrome { display: none; }

.modal {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  background: rgba(40, 55, 80, 0.4);
  backdrop-filter: blur(4px);
}
.modal[hidden] { display: none; }
.modal__panel {
  width: min(92vw, 380px);
  background: #fff;
  border-radius: var(--radius);
  padding: 1.4rem;
  box-shadow: var(--shadow);
  display: grid;
  gap: 1.1rem;
}
.modal__panel h2 { margin: 0; font-size: 1.4rem; }
.field { display: grid; gap: .5rem; border: none; padding: 0; margin: 0; }
.field > span, .field > legend { font-weight: 700; color: var(--ink-soft); }
select {
  font: inherit;
  padding: .6em .8em;
  border-radius: 12px;
  border: 2px solid #e4ebf3;
  background: #f7fafc;
}
.segmented { display: flex; gap: .4rem; }
.segmented button {
  flex: 1;
  border: 2px solid #e4ebf3;
  background: #f7fafc;
  border-radius: 12px;
  padding: .6em;
  font-weight: 700;
  color: var(--ink-soft);
}
.segmented button[aria-checked="true"] {
  background: var(--c-spell);
  border-color: var(--c-spell);
  color: #08343a;
}

/* ============================================================
   HUB
   ============================================================ */
.hub {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}
.hub__logo {
  position: absolute;
  top: max(26px, calc(env(safe-area-inset-top) + 14px));
  left: 50%;
  transform: translateX(-50%);
  width: min(74vw, 360px);
  filter: drop-shadow(0 6px 14px rgba(70,90,130,.3));
  pointer-events: none;
  z-index: 3;
}
.hub__arrows {
  position: relative;
  width: min(86vw, 86dvh);
  height: min(86vw, 86dvh);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  grid-template-rows: 1fr auto 1fr;
  place-items: center;
}
.hub__centre {
  grid-column: 2; grid-row: 2;
  width: 7em; height: 7em;
  border-radius: 50%;
  background: rgba(255,255,255,0.45);
  box-shadow: inset 0 0 30px rgba(255,255,255,.7), var(--shadow);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .2em;
  backdrop-filter: blur(3px);
}
/* MENU and SETTINGS live in the hub's centre circle. HOME used to sit here, but
   it pointed at the screen you were already on. These drive the chrome buttons
   (see hub.js), which are hidden on the hub so there is only ever one of each. */
.hub__btn {
  border: none;
  background: transparent;
  padding: .1em;
  cursor: pointer;
  display: grid;
  place-items: center;
  transition: transform .15s ease;
  -webkit-tap-highlight-color: transparent;
}
.hub__btn:active { transform: translateY(2px) scale(.95); }
/* Sized so the pair sits WITHIN the 7em circle rather than straddling it: MENU
   is a wide bar (~1.9:1), so it drives the width, and the two together have to
   clear the circle's edge on the diagonal, not just left and right. */
.hub__btn img {
  display: block;
  height: 28px;
  width: auto;
  filter: drop-shadow(0 4px 8px rgba(70, 90, 130, .3));
}
.hub__btn[aria-label="Settings"] img { height: 30px; }   /* near-square gear */
.arrow {
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  display: grid;
  place-items: center;
  gap: .55em;
  font-weight: 800;
  color: var(--ink);
  animation: bob 2.4s ease-in-out infinite;
}

/* Arrow-shaped bubble: a rounded tile with an iridescent fill tinted to the
   module's accent colour and a triangular pointer indicating the swipe
   direction. The square placeholder image (supplied later) sits inside. */
.bubble {
  position: relative;
  /* Enlarged from the old 5.4em, but capped in vw so the left/centre/right row
     never overflows the narrowest phones. */
  width: min(6.8em, 22vw);
  height: min(6.8em, 22vw);
  border-radius: 28%;
  display: grid;
  place-items: center;
  /* Base iridescent gradient built from the per-arrow accent (--accent). */
  background:
    linear-gradient(135deg,
      color-mix(in srgb, var(--accent) 55%, white) 0%,
      var(--accent) 42%,
      color-mix(in srgb, var(--accent) 65%, #b388ff) 78%,
      color-mix(in srgb, var(--accent) 55%, #7ce0ff) 100%);
  background-size: 200% 200%;
  box-shadow:
    inset 0 2px 9px rgba(255,255,255,.75),
    inset 0 -7px 16px color-mix(in srgb, var(--accent) 55%, #5a3fa0),
    0 9px 20px rgba(70,90,130,.28);
  animation: iridescent 6s ease-in-out infinite;
  isolation: isolate;
}
/* Moving rainbow sheen for the iridescent shimmer. */
.bubble::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(115deg,
    transparent 18%,
    rgba(255,255,255,.6) 38%,
    rgba(180,140,255,.4) 50%,
    rgba(120,224,255,.4) 62%,
    transparent 82%);
  background-size: 250% 250%;
  mix-blend-mode: screen;
  /* `alternate` sweeps the sheen down-right, then back up-left, so the loop
     never hard-resets mid-shimmer. Each sweep still takes the full 5s. */
  animation: sheen 5s ease-in-out infinite alternate;
  pointer-events: none;
}
/* Triangular pointer — direction set per arrow below. Enlarged and given a
   directional pulse so it clearly signals the swipe direction. */
.bubble::after {
  content: "";
  position: absolute;
  width: 0;
  height: 0;
  filter: drop-shadow(0 3px 5px rgba(70,90,130,.4));
}
/* The module's mascot icon sits inside the bubble, sized to fit fully (contain)
   with a soft drop-shadow for depth. */
.bubble__img {
  position: relative;
  z-index: 1;
  width: 86%;
  height: 86%;
  object-fit: contain;
  filter: drop-shadow(0 3px 5px rgba(70,90,130,.35));
}

.arrow span.label { font-size: .95rem; font-weight: 800; text-shadow: 0 1px 6px rgba(255,255,255,.85); }
/* Trace points downward, so its pointer occupies the space under the bubble —
   the label goes above instead, where Library's label sits below its own
   upward pointer. */
.arrow--down span.label { order: -1; }
/* Direction indicators use the shared difficulty-tier palette (red / green /
   blue) so the hub reads the same as every game's level chooser. Balloon keeps
   its orange accent. */
.arrow--up    { grid-column: 2; grid-row: 1; --accent: #f0805f; color: #f0805f; }
.arrow--left  { grid-column: 1; grid-row: 2; --accent: var(--c-balloon); color: var(--c-balloon); animation-delay: .3s; }
.arrow--right { grid-column: 3; grid-row: 2; --accent: #5f9bf0; color: #5f9bf0; animation-delay: .6s; }
.arrow--down  { grid-column: 2; grid-row: 3; --accent: #54c4a0; color: #54c4a0; animation-delay: .9s; }

/* Pointer placement per direction (tip coloured to the accent). Each pointer
   sits a little clear of the bubble and pulses outward to imply the swipe. The
   centring translate is baked into the keyframes so the pulse never drops it. */
.arrow--up .bubble::after {
  bottom: calc(100% + .28em); left: 50%;
  border-left: 1.15em solid transparent;
  border-right: 1.15em solid transparent;
  border-bottom: 1.25em solid var(--accent);
  animation: point-y 1.5s ease-in-out infinite;
}
.arrow--down .bubble::after {
  top: calc(100% + .28em); left: 50%;
  border-left: 1.15em solid transparent;
  border-right: 1.15em solid transparent;
  border-top: 1.25em solid var(--accent);
  animation: point-y-rev 1.5s ease-in-out infinite;
}
.arrow--left .bubble::after {
  right: calc(100% + .28em); top: 50%;
  border-top: 1.15em solid transparent;
  border-bottom: 1.15em solid transparent;
  border-right: 1.25em solid var(--accent);
  animation: point-x 1.5s ease-in-out infinite;
}
.arrow--right .bubble::after {
  left: calc(100% + .28em); top: 50%;
  border-top: 1.15em solid transparent;
  border-bottom: 1.15em solid transparent;
  border-left: 1.25em solid var(--accent);
  animation: point-x-rev 1.5s ease-in-out infinite;
}

/* Directional nudge — the pointer edges outward and back, hinting the swipe. */
@keyframes point-y     { 0%,100% { transform: translate(-50%, 0); } 50% { transform: translate(-50%, -5px); } }
@keyframes point-y-rev { 0%,100% { transform: translate(-50%, 0); } 50% { transform: translate(-50%, 5px); } }
@keyframes point-x     { 0%,100% { transform: translate(0, -50%); } 50% { transform: translate(-5px, -50%); } }
@keyframes point-x-rev { 0%,100% { transform: translate(0, -50%); } 50% { transform: translate(5px, -50%); } }

@media (prefers-reduced-motion: reduce) {
  .arrow--up .bubble::after    { transform: translate(-50%, 0); animation: none; }
  .arrow--down .bubble::after  { transform: translate(-50%, 0); animation: none; }
  .arrow--left .bubble::after  { transform: translate(0, -50%); animation: none; }
  .arrow--right .bubble::after { transform: translate(0, -50%); animation: none; }
}

@keyframes bob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
@keyframes iridescent {
  0%,100% { background-position: 0% 50%; }
  50%     { background-position: 100% 50%; }
}
/* Falling background-position slides the oversized gradient down and to the
   right — the reverse of the old 0%→100% sweep. */
@keyframes sheen {
  0%   { background-position: 100% 100%; }
  100% { background-position: 0% 0%; }
}

/* ============================================================
   BALLOON
   ============================================================ */
.balloon-game { position: absolute; inset: 0; }
.balloon-layer { position: absolute; inset: 0; overflow: hidden; }
.balloon {
  position: absolute;
  width: 4.4em;
  height: 5.6em;
  will-change: transform;
  touch-action: none;
}
.balloon__body {
  position: absolute;
  inset: 0 0 12% 0;
  border-radius: 50% 50% 48% 48% / 55% 55% 45% 45%;
  background:
    radial-gradient(circle at 32% 28%, rgba(255,255,255,.85), rgba(255,255,255,0) 42%),
    var(--balloon-c, #f4a259);
  box-shadow: inset -6px -8px 14px rgba(0,0,0,.12);
  display: grid;
  place-items: center;
}
/* Letter dominates the balloon (~70%) so it reads instantly. */
.balloon__letter {
  font-weight: 800;
  font-size: 2.9em;
  line-height: 1;
  color: #fff;
  text-shadow: 0 3px 5px rgba(0,0,0,.28);
}
.balloon__knot {
  position: absolute;
  bottom: 8%;
  left: 50%;
  width: .5em; height: .5em;
  transform: translateX(-50%) rotate(45deg);
  background: var(--balloon-c, #f4a259);
}
.balloon__string {
  position: absolute;
  bottom: -38%;
  left: 50%;
  width: 2px;
  height: 40%;
  background: rgba(255,255,255,.7);
  transform: translateX(-50%);
}

/* Intentional colour system: vowels yellow, consonants blue. */
.balloon--vowel .balloon__body {
  background:
    radial-gradient(circle at 32% 28%, rgba(255,255,255,.9), rgba(255,255,255,0) 46%),
    linear-gradient(160deg, #ffe27a, #f5b820);
}
.balloon--vowel .balloon__knot { background: #f5b820; }
.balloon--consonant .balloon__body {
  background:
    radial-gradient(circle at 32% 28%, rgba(255,255,255,.9), rgba(255,255,255,0) 46%),
    linear-gradient(160deg, #7db8ff, #2f7be0);
}
.balloon--consonant .balloon__knot { background: #2f7be0; }

.balloon.pop { animation: pop 320ms ease-out forwards; }
@keyframes pop {
  0%   { transform: scale(1); opacity: 1; }
  40%  { transform: scale(1.3); opacity: 1; }
  100% { transform: scale(.2); opacity: 0; }
}
/* Wrong tap — a soft jiggle on the body (outer transform stays under JS). */
.balloon__body.jiggle { animation: jiggle 360ms ease; }
@keyframes jiggle {
  0%,100% { transform: translateX(0) rotate(0); }
  20% { transform: translateX(-6px) rotate(-5deg); }
  40% { transform: translateX(6px) rotate(5deg); }
  60% { transform: translateX(-4px) rotate(-3deg); }
  80% { transform: translateX(4px) rotate(3deg); }
}
/* Gentle hint pulse on the answer when attempts run out. */
.balloon__body.hint { animation: hintPulse 900ms ease-in-out 2; box-shadow: 0 0 0 4px rgba(255,255,255,.8), inset -6px -8px 14px rgba(0,0,0,.12); }
@keyframes hintPulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.12); } }

.confetti-piece {
  position: absolute;
  width: 10px; height: 14px;
  border-radius: 3px;
  pointer-events: none;
  animation: confettiFly .95s ease-out forwards;
}
@keyframes confettiFly {
  from { transform: translate(0,0) rotate(0); opacity: 1; }
  to   { transform: translate(var(--dx), var(--dy)) rotate(560deg); opacity: 0; }
}

/* ---- Balloon HUD ---- */
.b-hud {
  position: absolute;
  top: max(12px, env(safe-area-inset-top));
  left: 10px; right: 10px;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  font-weight: 800;
  color: var(--ink);
}
.b-hud[hidden] { display: none; }
.b-hud__left, .b-hud__find, .b-hearts {
  background: var(--panel);
  border-radius: 14px;
  padding: .35em .7em;
  box-shadow: var(--shadow);
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  gap: .35em;
  white-space: nowrap;
}
.b-hud__find .b-find {
  font-size: 1.5em;
  line-height: 1;
  color: var(--c-balloon);
}
.b-back {
  border: none;
  background: transparent;
  padding: 0;
  display: grid; place-items: center;
  cursor: pointer;
}
.b-back img { display: block; height: 22px; width: auto; }
.b-replay {
  border: none;
  background: transparent;
  font-size: 1.1rem;
  padding: 0 0 0 .1em;
}
.b-hearts { gap: .15em; min-height: 22px; }

/* ---- Heart meter (Balloon / Spell / Trace) ----------------------------- */
/* Only the hearts actually held are drawn — see hearts.js. A gain pops in, a
   loss floats up and fades; neither leaves a greyed-out slot behind. */
.b-hearts, .sp-hearts, .tr-hearts {
  display: flex;
  align-items: center;
  gap: .15em;
}
.heart {
  display: block;
  width: 22px; height: 22px;
  object-fit: contain;
  filter: drop-shadow(0 2px 3px rgba(70, 90, 130, .35));
}
.heart.is-new { animation: heart-pop .42s cubic-bezier(.2, 1.6, .4, 1) both; }
.heart.is-lost { animation: heart-lost .42s ease-in forwards; pointer-events: none; }

@keyframes heart-pop {
  from { transform: scale(0) rotate(-25deg); opacity: 0; }
  60%  { transform: scale(1.25) rotate(6deg); opacity: 1; }
  to   { transform: scale(1) rotate(0); opacity: 1; }
}
@keyframes heart-lost {
  from { transform: scale(1) translateY(0); opacity: 1; }
  to   { transform: scale(1.5) translateY(-14px); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .heart.is-new, .heart.is-lost { animation-duration: .01ms; }
}

/* ---- Mascot coach ---- */
.b-mascot {
  position: absolute;
  left: max(8px, env(safe-area-inset-left));
  bottom: max(10px, env(safe-area-inset-bottom));
  z-index: 6;
  display: flex;
  align-items: flex-end;
  gap: .4rem;
  pointer-events: none;
}
.b-mascot[hidden] { display: none; }
.b-mascot__img {
  width: clamp(64px, 18vw, 104px);
  height: auto;
  filter: drop-shadow(0 6px 10px rgba(70,90,130,.3));
  animation: mascotBob 2.6s ease-in-out infinite;
}
@keyframes mascotBob { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
.b-bubble {
  max-width: 46vw;
  background: #fff;
  color: var(--ink);
  font-weight: 800;
  font-size: .95rem;
  border-radius: 16px 16px 16px 4px;
  padding: .5em .8em;
  box-shadow: var(--shadow);
  opacity: 0;
  transform: translateY(8px) scale(.9);
}
.b-bubble.show { animation: bubblePop 2.6s ease forwards; }
@keyframes bubblePop {
  0% { opacity: 0; transform: translateY(8px) scale(.9); }
  10%,82% { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(-4px) scale(.96); }
}

/* ---- Difficulty start screen ---- */
.b-start {
  position: absolute;
  inset: 0;
  z-index: 7;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .8rem;
  padding: 1.2rem;
  text-align: center;
}
.b-start[hidden] { display: none; }
.b-start__logo { width: min(72vw, 340px); filter: drop-shadow(0 6px 14px rgba(70,90,130,.3)); }
/* The square Mascot_Icons put Alpha Ant beside a scene (a lettered balloon), so
   the character is only half the frame — they need roughly double the width of
   the old full-height cut-out to read at the same size. */
.b-start__mascot { width: clamp(180px, 54vw, 260px); filter: drop-shadow(0 8px 12px rgba(70,90,130,.3)); }
.b-start__title { margin: .2rem 0 0; font-size: 1.6rem; }

/* "Choose a level" — one rule for all three games, so the prompt reads
   identically whichever start screen the child lands on. Balloon's bold treatment
   is the standard; keep this shared rather than repeating it per game, or the
   three drift apart again (Spell and Trace had gone light-weight and faded). */
.b-start__sub,
.sp-start__sub,
.tr-start__sub { margin: 0; color: var(--ink-soft); font-weight: 700; }
.b-levels { display: flex; flex-direction: column; gap: .7rem; width: min(80vw, 320px); margin-top: .4rem; }
.b-level {
  border: none;
  border-radius: 18px;
  padding: .85em 1em;
  font-size: 1.2rem;
  font-weight: 800;
  color: var(--ink);
  box-shadow: var(--shadow);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.b-level small { font-weight: 700; opacity: .85; font-size: .9rem; }
.b-level:active { transform: translateY(2px) scale(.99); }
.b-level--easy   { background: linear-gradient(160deg, #a6e8d0, #54c4a0); }
.b-level--medium { background: linear-gradient(160deg, #aacdff, #5f9bf0); }
.b-level--hard   { background: linear-gradient(160deg, #ffc9a6, #f0805f); }

/* ============================================================
   SPELL  —  Hear → Blend → Choose
   ============================================================ */
/* Use the same friendly library font (Lexend) for letters — warmer for young learners. */
[data-screen="spell"] { background:
  linear-gradient(180deg, rgba(190,230,240,0) 35%, rgba(111,196,201,.22) 100%); }

.spell {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  padding: max(12px, env(safe-area-inset-top)) 14px calc(14px + env(safe-area-inset-bottom));
  gap: .5rem;
  --font-letter: "Lexend", "Baloo 2", "Quicksand", system-ui, sans-serif;
  --c-vowel: #ffd86b;       /* vowels  — yellow (matches Balloon) */
  --c-cons:  #8fc0ff;       /* consonants — blue */
  --ink-vowel: #8a6400;
  --ink-cons:  #1d4a86;
}
.spell[hidden] { display: none; }
.sp-play { flex: 1; display: flex; flex-direction: column; gap: .5rem; min-height: 0; width: 100%; }
.sp-play[hidden] { display: none; }

/* ---- HUD ---- */
.sp-hud {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  font-weight: 800;
  font-size: 1.15rem;
  min-height: 2.4rem;
}
.sp-back {
  border: none; background: transparent; padding: 0;
  display: grid; place-items: center; cursor: pointer;
}
.sp-back img { display: block; height: 26px; width: auto; }
.sp-hearts { min-height: 22px; }

/* ---- Stage: picture, caption, word slots, listen ---- */
.sp-stage {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .8rem;
  min-height: 0;
}
.sp-pic {
  width: min(64vw, 240px);
  height: min(26vh, 200px);
  border-radius: 28px;
  border: 4px dashed #b9ccdd;
  background: linear-gradient(160deg, #eef6f8, #dcebf0);
  display: grid;
  /* Definite single cell that fills the frame, so the illustration's
     height:100% resolves to the frame (not its intrinsic square) and
     object-fit:contain letterboxes inside instead of overflowing. */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  place-items: center;
  overflow: hidden;
  color: #8aa3b6;
  text-align: center;
}
.sp-pic__icon { font-size: 2.6rem; opacity: .8; }
/* Word illustration: fills the frame, keeping its aspect ratio. When present,
   the dashed placeholder frame gives way to a clean white card. */
.sp-pic__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 24px;
  padding: 6px;
}
.sp-pic__img[hidden] { display: none; }
.sp-pic.has-img {
  border-style: solid;
  border-color: #e3edf3;
  background: #fff;
}
.sp-pic.has-img .sp-pic__icon { display: none; }
.sp-caption {
  font-family: var(--font-letter);
  font-weight: 700;
  font-size: clamp(1.6rem, 6vh, 2.4rem);
  letter-spacing: 2px;
  color: #2a3f55;
  text-transform: uppercase;
}
.sp-caption[hidden] { display: none; }

/* The picture and the word are tappable to hear the word said aloud. */
.sp-pic.is-tappable, .sp-caption.is-tappable { cursor: pointer; }
.sp-pic.is-tappable:active { transform: scale(.97); }
.sp-caption.is-tappable:active { opacity: .7; }

/* Every letter shown in the word is tappable to hear its sound on its own. */
.sp-cell, .sp-slot--box.filled { cursor: pointer; }
.sp-cell:active, .sp-slot--box.filled:active { transform: scale(.92); }

/* The word with one blank (fill stages) or empty boxes (build stage). */
.sp-slots { display: flex; gap: .45rem; justify-content: center; align-items: flex-end; }
.sp-cell {
  font-family: var(--font-letter);
  font-weight: 700;
  font-size: clamp(2.2rem, 9vh, 3.2rem);
  min-width: 1.1em;
  text-align: center;
  color: #2a3f55;
  line-height: 1.1;
}
/* Word-display letters take the tile BACKGROUND tones — vowels the tile yellow,
   consonants the tile blue — so they read as the same colour family as the
   letter tiles below, not a darker ink. */
.sp-cell.vowel { color: var(--c-vowel); }
.sp-cell.cons  { color: var(--c-cons); }
.sp-slot {
  min-width: 1.3em;
  height: .22em;
  align-self: flex-end;
  margin-bottom: .18em;
  border-radius: 6px;
  background: #9fb3c8;
}
.sp-slot.filled { background: transparent; }

/* Build-stage drop boxes */
.sp-slot--box {
  width: min(18vh, 18vw, 5rem);
  height: min(18vh, 18vw, 5rem);
  min-width: 64px; min-height: 64px;
  border: 4px dashed #9fb3c8;
  border-radius: 18px;
  display: grid; place-items: center;
  font-family: var(--font-letter);
  font-weight: 700;
  font-size: clamp(2rem, 8vh, 3rem);
  color: #2a3f55;
  transition: border-color .12s, background .12s;
}
.sp-slot--box.over { border-color: #6fc4c9; background: rgba(111,196,201,.18); }
.sp-slot--box.filled { border-style: solid; }
.sp-slot--box.filled.vowel { color: var(--ink-vowel); border-color: var(--c-vowel); background: #fff7e0; }
.sp-slot--box.filled.cons  { color: var(--ink-cons);  border-color: var(--c-cons);  background: #eaf3ff; }

.sp-listen {
  display: inline-flex; align-items: center; gap: .4rem;
  border: none; background: #fff; box-shadow: var(--shadow);
  border-radius: 999px; padding: .55rem 1.1rem;
  font-weight: 700; color: #0c4a52; font-size: 1rem;
}
.sp-listen:active { transform: translateY(2px); }

/* ---- Choices / drag tray (lower third) ---- */
.sp-choices {
  display: flex; flex-wrap: wrap;
  gap: .7rem; justify-content: center; align-items: center;
  min-height: 22vh;
  padding-bottom: .4rem;
}
.sp-tile {
  width: min(20vh, 20vw, 6rem);
  height: min(20vh, 20vw, 6rem);
  min-width: 80px; min-height: 80px;     /* ≥80px touch target */
  border: none; border-radius: 22px;
  font-family: var(--font-letter);
  font-weight: 700;
  font-size: clamp(2.2rem, 9vh, 3.4rem);
  color: var(--ink-cons);
  background: var(--c-cons);
  box-shadow: 0 7px 0 rgba(0,0,0,.14), var(--shadow);
  touch-action: none;
  transition: transform .1s;
}
.sp-tile.vowel { background: var(--c-vowel); color: var(--ink-vowel); }
.sp-tile.cons  { background: var(--c-cons);  color: var(--ink-cons); }
.sp-tile:active { transform: translateY(3px); box-shadow: 0 4px 0 rgba(0,0,0,.14); }
.sp-tile.spent   { visibility: hidden; }     /* placed — keeps tray layout steady */
.sp-tile.picked  { opacity: .3; }            /* lifted out as a drag clone */
.sp-tile.hint  { animation: spHint 1s ease-in-out infinite; }

/* Digraph block (sh / ch / th) — two letters, one sound, so it reads as a
   single wide rectangular block rather than two square letter tiles. Only the
   width grows; the height matches the neighbouring single-letter blocks. */
.sp-slot--box.is-digraph {
  width: min(30vh, 30vw, 8.5rem);
  min-width: 112px;
  padding-inline: .3em;
}
.sp-tile.is-digraph {
  width: min(34vh, 34vw, 10rem);
  min-width: 132px;
  padding-inline: .3em;
  letter-spacing: 1px;
}

/* The letter that follows the finger while dragging. Mounted on <body> so it
   sits above (and outside the transform of) the swipe grid. */
.sp-ghost {
  position: fixed;
  z-index: 70;
  margin: 0;
  pointer-events: none;                       /* so elementFromPoint sees slots */
  transform: scale(1.06);
  box-shadow: 0 16px 26px rgba(0,0,0,.3);
}

@keyframes spHint { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }

/* Correct: the letter jumps upward. */
.sp-jump { animation: spJump .5s ease; }
@keyframes spJump {
  0% { transform: translateY(0); }
  35% { transform: translateY(-26px); }
  70% { transform: translateY(0); }
  85% { transform: translateY(-7px); }
  100% { transform: translateY(0); }
}
/* Read-back: on completion each letter lights in turn as its sound plays, then
   the whole word is spoken — a gold glow confirming the order was right. */
.sp-lit { animation: spLit 1.3s ease; }
@keyframes spLit {
  0%, 100% { box-shadow: none; text-shadow: none; filter: none; }
  25% {
    box-shadow: 0 0 0 4px rgba(255, 209, 79, .5), 0 0 20px 5px rgba(255, 209, 79, .75);
    text-shadow: 0 0 16px rgba(255, 209, 79, .95);
    filter: brightness(1.1);
  }
}

/* Incorrect: a gentle shake — never a red X or harsh cue. */
.sp-shake { animation: spShake .4s ease; }
@keyframes spShake {
  0%,100% { transform: translateX(0); }
  25% { transform: translateX(-7px); }
  50% { transform: translateX(7px); }
  75% { transform: translateX(-4px); }
}

/* Praise / "try again" pill */
.sp-toast {
  position: absolute;
  top: 16%; left: 50%;
  transform: translateX(-50%) scale(.7);
  background: #fff; box-shadow: var(--shadow);
  padding: .5rem 1.2rem; border-radius: 999px;
  font-weight: 800; color: #1f5b62;
  opacity: 0; pointer-events: none; z-index: 6;
}
.sp-toast.show { animation: spToast 1.5s ease; }
@keyframes spToast {
  0% { opacity: 0; transform: translateX(-50%) scale(.7); }
  18% { opacity: 1; transform: translateX(-50%) scale(1.06); }
  78% { opacity: 1; transform: translateX(-50%) scale(1); }
  100% { opacity: 0; transform: translateX(-50%) scale(1); }
}

/* Sparkle particles on a correct tap */
.sp-spark { position: absolute; font-size: 1.3rem; pointer-events: none; z-index: 6;
  animation: spSpark .85s ease-out forwards; }
@keyframes spSpark {
  0% { opacity: 1; transform: translate(0,0) scale(.6); }
  100% { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(1.2); }
}

/* ---- Start / level-select (mirrors the Balloon start screen) ---- */
.sp-start {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .8rem; padding: 1.4rem; text-align: center;
}
.sp-start[hidden] { display: none; }
.sp-start__logo { width: min(70vw, 320px); filter: drop-shadow(0 6px 14px rgba(70, 90, 130, .3)); }
.sp-start__mascot { width: clamp(180px, 54vw, 260px); filter: drop-shadow(0 8px 12px rgba(70, 90, 130, .3)); }
.sp-start__title { font-size: 1.8rem; margin: 0; }

/* Encouragement Alpha Ant that pops in on a correct answer (bottom-left,
   opposite the nav chrome). */
.sp-mascot, .tr-mascot {
  position: absolute;
  left: max(8px, env(safe-area-inset-left));
  bottom: max(10px, env(safe-area-inset-bottom));
  width: clamp(64px, 18vw, 104px);
  height: auto;
  z-index: 6;
  pointer-events: none;
  filter: drop-shadow(0 6px 10px rgba(70, 90, 130, .3));
  opacity: 0;
}
.sp-mascot[hidden], .tr-mascot[hidden] { display: none; }
.sp-mascot.show, .tr-mascot.show { animation: mascotCheer 1.6s ease forwards; }
@keyframes mascotCheer {
  0% { opacity: 0; transform: translateY(16px) scale(.8); }
  18%, 76% { opacity: 1; transform: translateY(0) scale(1); }
  100% { opacity: 0; transform: translateY(6px) scale(.94); }
}
.sp-levels { display: flex; flex-direction: column; gap: .9rem; width: min(82vw, 340px); }
.sp-level {
  border: none; border-radius: 18px; padding: 1rem 1.2rem;
  color: var(--ink); font-weight: 800; font-size: 1.2rem;
  box-shadow: var(--shadow);
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
}
.sp-level small { font-weight: 700; opacity: .85; font-size: .95rem; font-family: var(--font-letter); }
.sp-level:active { transform: translateY(2px) scale(.99); }
/* Shared difficulty-tier palette (green → blue → red), matched across
   Balloon, Spell and Trace so every game's level chooser reads the same. */
.sp-level--vowel     { background: linear-gradient(160deg, #a6e8d0, #54c4a0); }
.sp-level--consonant { background: linear-gradient(160deg, #aacdff, #5f9bf0); }
.sp-level--build     { background: linear-gradient(160deg, #ffc9a6, #f0805f); }

.confetti { position: absolute; top: -10px; font-size: 1.4rem; animation: fall 1.4s ease-in forwards; }
@keyframes fall { to { transform: translateY(110dvh) rotate(360deg); opacity: 0; } }

/* ============================================================
   TRACE
   ============================================================ */
[data-screen="trace"] { background:
   linear-gradient(180deg, rgba(190,230,240,.0) 40%, rgba(120,190,150,.35) 100%); }
/* Keep attention on the tracing exercise: gently desaturate the shared sky. */
body[data-active="trace"] .sky { filter: saturate(.75); transition: filter .4s ease; }

.trace { position: absolute; inset: 0; }

/* ---- Difficulty start screen (mirrors the Spell / Balloon level pickers) ---- */
.tr-start {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .8rem; padding: 1.4rem; text-align: center;
}
.tr-start[hidden] { display: none; }
.tr-start__logo { width: min(70vw, 320px); filter: drop-shadow(0 6px 14px rgba(70, 90, 130, .3)); }
.tr-start__mascot { width: clamp(180px, 54vw, 260px); filter: drop-shadow(0 8px 12px rgba(70, 90, 130, .3)); }
.tr-start__title { font-size: 1.8rem; margin: 0; }
.tr-levels { display: flex; flex-direction: column; gap: .9rem; width: min(82vw, 340px); }
.tr-level {
  border: none; border-radius: 18px; padding: 1rem 1.2rem;
  color: var(--ink); font-weight: 800; font-size: 1.2rem; box-shadow: var(--shadow);
  display: flex; align-items: center; justify-content: space-between; gap: .6rem;
}
.tr-level small { font-weight: 700; opacity: .85; font-size: .95rem; }
.tr-level:active { transform: translateY(2px) scale(.99); }
/* Same shared difficulty-tier palette (green → blue → red) as Balloon/Spell. */
.tr-level--beg { background: linear-gradient(160deg, #a6e8d0, #54c4a0); }
.tr-level--int { background: linear-gradient(160deg, #aacdff, #5f9bf0); }
.tr-level--adv { background: linear-gradient(160deg, #ffc9a6, #f0805f); }

/* ---- Play screen ---- */
.tr-play { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; }
.tr-play[hidden] { display: none; }

.tr-hud {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: .5rem; z-index: 4;
  padding: max(12px, env(safe-area-inset-top)) 1rem 0;
}
/* Lower / UPPER case toggle for the trace map */
.tr-case { flex: 0 0 auto; gap: .25rem; }
.tr-case button {
  flex: 0 0 auto;
  width: 2em; padding: .25em 0;
  font-size: 1.1rem; font-weight: 800;
  border-radius: 10px;
  line-height: 1;
}
.tr-case button[aria-checked="true"] {
  background: var(--c-trace); border-color: var(--c-trace); color: #14431a;
}
.tr-hearts { min-height: 22px; flex: 0 1 auto; overflow: hidden; }
.tr-replay {
  width: 2.6em; height: 2.6em; border-radius: 50%; border: none;
  font-size: 1.2rem; background: var(--panel); box-shadow: var(--shadow);
}
.tr-back {
  border: none; background: transparent; padding: 0;
  display: grid; place-items: center; cursor: pointer;
}
.tr-back img { display: block; height: 28px; width: auto; }

.trace__stage { position: relative; flex: 1; width: 100%; display: grid; place-items: center; touch-action: none; }
.tr-art { position: relative; transform-origin: center; transition: transform .5s ease;
  /* A soft halo lifts the letter off the busy sky without a hard "box". */
  background: radial-gradient(closest-side, rgba(255,255,255,.42), rgba(255,255,255,0) 74%); }
.tr-guide, .tr-ink { position: absolute; inset: 0; width: 100%; height: 100%; }
.tr-guide { overflow: visible; }
.tr-ink { border-radius: 28px; touch-action: none; }

/* Thick letter outline the child never loses sight of. */
.tr-outline__p { fill: none; stroke: rgba(56,72,108,.30); stroke-width: 12; stroke-linecap: round; stroke-linejoin: round; }
/* The "watch me" demonstration stroke (magic-ink gradient). */
.tr-demo__p { fill: none; stroke: url(#trInkGrad); stroke-width: 7; stroke-linecap: round; stroke-linejoin: round;
  filter: drop-shadow(0 0 3px rgba(130,120,255,.7)); }
.tr-arrow { fill: rgba(60,80,120,.55); }

.tr-startdot { fill: #2fc65f; transform-box: fill-box; transform-origin: center;
  filter: drop-shadow(0 0 4px rgba(60,200,110,.9)); animation: trDot 1.1s ease-in-out infinite; }
.tr-startdot[hidden] { display: none; }

/* Ordered "racing-track" checkpoints (green start → brown finish). The next one
   to reach pulses; upcoming ones sit faint; a reached one bursts and is gone —
   it must not linger over the ink the child just drew through it. */
.tr-cp__dot {
  stroke: #fff; stroke-width: 1.4;
  transform-box: fill-box; transform-origin: center;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,.3));
}
.tr-cp__dot.is-idle   { opacity: .45; }
.tr-cp__dot.is-active { opacity: 1; animation: trCpPulse 1s ease-in-out infinite; }
.tr-cp__dot.is-done   { opacity: 0; animation: trCpPop .3s ease-out; }
@keyframes trCpPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.5); } }
@keyframes trCpPop {
  0%   { opacity: 1; transform: scale(1); }
  55%  { opacity: .7; transform: scale(1.9); }
  100% { opacity: 0; transform: scale(2.3); }
}
@media (prefers-reduced-motion: reduce) {
  .tr-cp__dot.is-active { animation: none; }
  .tr-cp__dot.is-done   { animation: none; }
}
@keyframes trDot { 0%,100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.35); opacity: .7; } }

.tr-fairy { fill: #fff; filter: drop-shadow(0 0 6px #ffe27a) drop-shadow(0 0 11px #ffd34d); }
.tr-fairy[hidden] { display: none; }

/* Letter intro (glow) and hear-the-sound (pulse) accents. */
.tr-intro .tr-art { animation: trGlow 1.3s ease-in-out; }
@keyframes trGlow {
  0% { filter: none; opacity: .35; }
  50% { filter: drop-shadow(0 0 16px rgba(120,180,255,.95)); opacity: 1; }
  100% { filter: none; opacity: 1; }
}
.tr-pulse .tr-art { animation: trPulse 1.9s ease-in-out; }
@keyframes trPulse { 0%,100% { transform: scale(1); } 25% { transform: scale(1.05); } 60% { transform: scale(1.03); } }

.tr-spark {
  position: absolute; font-size: 1rem; pointer-events: none; z-index: 6;
  transform: translate(-50%, -50%); animation: trSpark .7s ease-out forwards;
}
@keyframes trSpark {
  0% { opacity: 1; transform: translate(-50%, -50%) scale(.5); }
  100% { opacity: 0; transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(1.1); }
}

.trace__tick {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: 8rem; color: #3cbf6b; opacity: 0; pointer-events: none;
  text-shadow: 0 6px 14px rgba(0,0,0,.2); z-index: 5;
}
.trace__tick.show { animation: tickPop 900ms ease-out forwards; }
@keyframes tickPop {
  0% { opacity: 0; transform: scale(.3); }
  40% { opacity: 1; transform: scale(1.15); }
  100% { opacity: 1; transform: scale(1); }
}

/* Celebrate: the completed letter shrinks up, making room for the picture. */
.tr-celebrating .tr-art { transform: translateY(-16%) scale(.5); }
.tr-celebrate {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: flex-end; gap: .5rem;
  /* The letter-nav has moved to the top, so the picture + word no longer have to
     duck under it — they get the bottom of the stage back. */
  padding-bottom: calc(.75rem + env(safe-area-inset-bottom)); pointer-events: none; z-index: 5;
}
.tr-celebrate[hidden] { display: none; }
.tr-celebrate__img { width: 38%; max-width: 190px; border-radius: 18px; box-shadow: var(--shadow); animation: trPicPop .5s ease-out; }
.tr-celebrate__img[hidden] { display: none; }
/* No text-transform: the case is the lesson. The word is written in the case the
   child just traced ("bat" after b, "BAT" after B), so forcing a capital here
   would have shown "Bat" straight after a lower-case b — see win() in trace.js. */
.tr-celebrate__cap { font-weight: 800; font-size: 1.6rem; font-family: var(--font-letter); }
@keyframes trPicPop { 0% { opacity: 0; transform: scale(.5); } 100% { opacity: 1; transform: scale(1); } }

/* Letter navigation sits at the TOP, just under the HUD, and is part of the
   column's flow rather than floating over it. Down at the bottom it was absolutely
   positioned, so it overlapped whatever the stage put there — the descender of a
   g or y, and the celebration's picture and word. In the flow it reserves its own
   band and the stage simply gets what's left, so nothing can collide with it. */
.trace__nav {
  flex: 0 0 auto;
  width: 100%;
  display: flex;
  justify-content: center;
  gap: 1.2rem;
  padding: .5rem 1rem 0;
  z-index: 4;
}
.trace__nav button {
  width: 3em; height: 3em; border-radius: 50%; border: none;
  font-size: 1.3rem; background: var(--panel); box-shadow: var(--shadow);
}
.shake { animation: shake 360ms; }
@keyframes shake {
  0%,100% { transform: translate(0,0); }
  20% { transform: translate(-8px, 2px); }
  40% { transform: translate(8px, -2px); }
  60% { transform: translate(-6px, 1px); }
  80% { transform: translate(6px, -1px); }
}

/* ============================================================
   LIBRARY
   ============================================================ */
.library {
  position: absolute;
  inset: 0;
  overflow-y: auto;
  overflow-x: hidden;
  touch-action: pan-y;
  -webkit-overflow-scrolling: touch;
}

/* ---- Tabs (Letters / Words / Cards) ---- */
.lib-tabs {
  position: sticky;
  top: 0;
  z-index: 5;
  display: flex;
  gap: .4rem;
  padding: max(50px, calc(env(safe-area-inset-top) + 42px)) .8rem .55rem;
  background: linear-gradient(180deg, rgba(255,255,255,.92), rgba(255,255,255,.55));
  backdrop-filter: blur(8px);
}
.lib-tab {
  flex: 1;
  border: none;
  border-radius: 14px;
  padding: .5rem .3rem;
  background: rgba(255,255,255,.55);
  color: var(--ink-soft);
  font-weight: 800;
  font-size: .95rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .1rem;
  transition: background .2s, color .2s, box-shadow .2s;
}
.lib-tab__count { font-size: .68rem; font-weight: 700; opacity: .85; }
.lib-tab.is-active { background: var(--c-library); color: #6b3a53; box-shadow: var(--shadow); }

.lib-panel { padding: .5rem 1rem 2.6rem; }
.lib-subhead {
  margin: .9rem 0 .55rem;
  font-size: .95rem;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 1px;
  display: flex;
  align-items: baseline;
  gap: .5rem;
}
.lib-count {
  font-size: .78rem;
  letter-spacing: 0;
  color: var(--ink);
  background: rgba(255,255,255,.6);
  padding: .05rem .55rem;
  border-radius: 999px;
}

/* ---- Letter tiles ---- */
.lib-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.6em, 1fr));
  gap: .45rem;
}
.lib-tile {
  aspect-ratio: 1;
  border: none;
  border-radius: 12px;
  font-weight: 800;
  font-size: 1.3rem;
  display: grid;
  place-items: center;
  position: relative;
  overflow: hidden;
}
.lib-tile.is-unlocked {
  background: var(--c);
  color: #fff;
  box-shadow: var(--shadow);
  text-shadow: 0 1px 3px rgba(0,0,0,.25);
}
/* sparkle sweep across unlocked tiles */
.lib-tile.is-unlocked::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, transparent 40%, rgba(255,255,255,.75) 50%, transparent 60%);
  transform: translateX(-120%);
  animation: libSparkle 3.6s ease-in-out infinite;
}
/* frosted "crystallised" locked tiles */
.lib-tile.is-locked {
  color: #9fb3cf;
  background:
    linear-gradient(135deg, rgba(214,229,244,.95), rgba(188,207,228,.95)),
    repeating-linear-gradient(60deg, rgba(255,255,255,.5) 0 6px, rgba(255,255,255,0) 6px 12px);
  background-blend-mode: screen;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.6);
}
.lib-tile.is-locked::before {
  content: "❄";
  position: absolute;
  top: 2px;
  right: 4px;
  font-size: .6rem;
  color: rgba(255,255,255,.9);
}
@keyframes libSparkle {
  0%, 62% { transform: translateX(-120%); }
  82%, 100% { transform: translateX(120%); }
}

.shake { animation: libShake .32s ease; }
@keyframes libShake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-4px); }
  80% { transform: translateX(3px); }
}
@media (prefers-reduced-motion: reduce) {
  .lib-tile.is-unlocked::after, .shake { animation: none; }
}

/* ---- Word collection ---- */
.lib-cat { margin-bottom: 1.1rem; }
.lib-cat__title {
  margin: .7rem 0 .5rem;
  font-size: 1rem;
  color: var(--ink);
  display: flex;
  align-items: baseline;
  gap: .5rem;
}
.lib-cat__title b {
  font-size: .72rem;
  font-weight: 700;
  color: var(--ink-soft);
  background: rgba(255,255,255,.6);
  padding: .05rem .5rem;
  border-radius: 999px;
}
.lib-wgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  gap: .5rem;
}
.lib-wtile {
  border: none;
  border-radius: 14px;
  overflow: hidden;
  background: var(--panel);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  aspect-ratio: 1 / 1.16;
}
.lib-wtile__img {
  width: 100%; flex: 1; min-height: 0; object-fit: cover; background: #fff;
  margin-top: 7px;               /* sit the picture inside the card, clear of its top edge */
  border-radius: 9px 9px 0 0;    /* round the top to follow the container's corners */
}
.lib-wtile__label {
  font-weight: 800;
  font-size: .85rem;
  color: var(--ink);
  padding: .25rem;
  text-align: center;
}
.lib-wtile.no-img { justify-content: center; }
.lib-wtile.is-locked {
  background: rgba(255,255,255,.4);
  box-shadow: none;
  border: 2px dashed rgba(120,140,170,.35);
  justify-content: center;
}
.lib-wtile.is-locked .lib-wtile__label { color: #aebacd; }

/* The word a failed "Build the word" round sent the learner here to look at.
   Temporarily shown as though collected (see library.js), enlarged, and flashing
   a rainbow so it is impossible to miss in a long scrolling grid. */
.lib-wtile.is-spotlight {
  position: relative;
  z-index: 1;
  transform: scale(1.18);
  animation: wtile-rainbow 2.4s linear infinite;
}
.lib-wtile.is-spotlight .lib-wtile__label {
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .45);
}
@keyframes wtile-rainbow {
  0%   { box-shadow: 0 0 0 4px #ff5d5d, 0 6px 22px rgba(255, 93, 93, .55);  background: #ff5d5d; }
  20%  { box-shadow: 0 0 0 4px #ffb43d, 0 6px 22px rgba(255, 180, 61, .55); background: #ffb43d; }
  40%  { box-shadow: 0 0 0 4px #8fd17a, 0 6px 22px rgba(143, 209, 122, .55); background: #8fd17a; }
  60%  { box-shadow: 0 0 0 4px #6fc4c9, 0 6px 22px rgba(111, 196, 201, .55); background: #6fc4c9; }
  80%  { box-shadow: 0 0 0 4px #7db8ff, 0 6px 22px rgba(125, 184, 255, .55); background: #7db8ff; }
  100% { box-shadow: 0 0 0 4px #ff5d5d, 0 6px 22px rgba(255, 93, 93, .55);  background: #ff5d5d; }
}
@media (prefers-reduced-motion: reduce) {
  /* Still unmistakable, just not flashing. */
  .lib-wtile.is-spotlight {
    animation: none;
    background: #7db8ff;
    box-shadow: 0 0 0 4px #7db8ff, 0 6px 22px rgba(125, 184, 255, .55);
  }
}

/* ---- Library review overlays (tapped letter / word) ---- */
.lib-overlay {
  position: fixed;
  inset: 0;
  z-index: 82;
  display: grid;
  place-items: center;
  background: rgba(30,40,60,.72);
  backdrop-filter: blur(6px);
}
.lib-overlay[hidden] { display: none; }
.lib-ov-close {
  position: absolute;
  top: max(14px, env(safe-area-inset-top));
  right: 16px;
  width: 2.4em;
  height: 2.4em;
  border-radius: 50%;
  border: none;
  background: rgba(255,255,255,.9);
  color: var(--ink);
  font-size: 1.05rem;
  font-weight: 800;
  box-shadow: var(--shadow);
}
.lib-bigletter {
  font-size: min(60vw, 46vh);
  font-weight: 800;
  line-height: 1;
  color: var(--c);
  text-shadow: 0 8px 30px rgba(0,0,0,.35);
  animation: libPop .4s cubic-bezier(.2,.8,.2,1);
  cursor: pointer;
  user-select: none;
}
@keyframes libPop { from { transform: scale(.4); opacity: 0; } to { transform: scale(1); opacity: 1; } }

.lib-wordview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1rem;
  width: min(88vw, 420px);
}
.lib-wordview__img {
  width: min(64vw, 260px);
  aspect-ratio: 1;
  object-fit: contain;
  border-radius: 20px;
  background: #fff;
  box-shadow: 0 20px 50px rgba(0,0,0,.4);
  cursor: pointer;
}
.lib-wordview__letters { display: flex; gap: .5rem; flex-wrap: wrap; justify-content: center; }
.lib-lettercell {
  min-width: 1.7em;
  padding: .18em .34em;
  border: none;
  border-radius: 12px;
  font-size: 2rem;
  font-weight: 800;
  color: #fff;
  box-shadow: var(--shadow);
}
.lib-lettercell.vowel { background: #f3c34a; color: #6b5210; }
.lib-lettercell.cons { background: #5aa9d6; }

.lib-toggle { display: flex; align-items: center; gap: .7rem; color: #fff; font-weight: 700; }
.lib-toggle__side { text-shadow: 0 1px 4px rgba(0,0,0,.4); }
.lib-switch {
  width: 3.2em;
  height: 1.7em;
  border-radius: 999px;
  border: none;
  background: rgba(255,255,255,.35);
  position: relative;
  transition: background .2s;
}
.lib-switch.is-on { background: #8fd17a; }
.lib-switch__knob {
  position: absolute;
  top: 50%;
  left: .2em;
  width: 1.3em;
  height: 1.3em;
  border-radius: 50%;
  background: #fff;
  transform: translateY(-50%);
  transition: left .2s;
  box-shadow: 0 2px 6px rgba(0,0,0,.3);
}
.lib-switch.is-on .lib-switch__knob { left: calc(100% - 1.5em); }

.card-slots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
  gap: .6rem;
  margin-top: .6rem;
  padding-bottom: 2rem;
}
.card-slot {
  aspect-ratio: var(--card-ratio);
  border-radius: 10px;
  border: 2px dashed rgba(120,140,170,.4);
  background: rgba(255,255,255,.35);
  display: grid;
  place-items: center;
  font-weight: 800;
  color: #b9c4d6;
  overflow: hidden;
  position: relative;
}
.card-slot { cursor: pointer; }
.card-slot.unlocked {
  border-style: solid;
  box-shadow: var(--shadow);
}
.card-slot.tier-silver { border-color: #a9b6c9; }
.card-slot.tier-gold   { border-color: #e9c45e; }
/* Little corner medal so tiers read at a glance in the grid. */
.card-slot__badge {
  position: absolute;
  top: 2px; right: 3px;
  font-size: .8rem;
  line-height: 1;
  filter: drop-shadow(0 1px 1px rgba(0,0,0,.35));
  pointer-events: none;
}
.card-slot img { width: 100%; height: 100%; object-fit: cover; }

/* ---- Card faces ----
   The same art carries all three states, so a child sees one character grow
   from a padlocked mystery into a full-colour Gold card:
     locked — blurred and grey, a tease only;
     silver — cooler and quieter than the finished card;
     gold   — the artwork at full strength, with a warm halo. */
.card-face { position: relative; width: 100%; height: 100%; display: block; }
.card-face img { width: 100%; height: 100%; object-fit: contain; display: block; }
.card-face--locked img { filter: grayscale(.75) brightness(1.7) contrast(.45) blur(5px); }
.card-face--silver img { filter: saturate(.5) brightness(1.04) contrast(.96); }
.card-face--gold img   { filter: saturate(1.06); }
.card-face--gold { box-shadow: inset 0 0 0 1px rgba(233,196,94,.5); }
.card-face__lock {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 2.4rem;
  filter: drop-shadow(0 3px 8px rgba(0,0,0,.5));
  pointer-events: none;
}

.card-slot__placeholder {
  width: 100%; height: 100%;
  display: grid; place-items: center; gap: .1em;
  background: linear-gradient(160deg, #fdf3cf, #e7c45e);
  color: #6b4e12;
  text-align: center;
}
.card-slot__placeholder--silver { background: linear-gradient(160deg, #f2f5f9, #b6c2d3); color: #465468; }
.card-slot__placeholder--gold   { background: linear-gradient(160deg, #fff6d6, #e7bf4c); color: #6b4e12; }
.card-slot__placeholder b { font-size: 1.8rem; }
.card-slot__placeholder small { font-size: .5rem; letter-spacing: .5px; }

/* ---- "How to earn this card" checklist sheet ---- */
.card-checklist {
  position: relative;
  width: min(92vw, 420px);
  max-height: 88vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: #fff;
  border-radius: 18px;
  padding: 1.2rem 1.1rem 1.4rem;
  box-shadow: 0 30px 70px rgba(0,0,0,.5);
  text-align: center;
}
.cl-close {
  position: absolute;
  top: .5rem; right: .5rem;
  width: 2rem; height: 2rem;
  border: none; border-radius: 50%;
  background: rgba(0,0,0,.06);
  color: #465468; font-weight: 800; font-size: 1rem;
  cursor: pointer;
}
.cl-hero {
  width: 42%;
  max-width: 150px;
  margin: .2rem auto .6rem;
  box-shadow: 0 10px 24px rgba(0,0,0,.25);
}
.cl-title { margin: 0; font-size: 1.3rem; color: #33405a; }
.cl-sub { margin: .1rem 0 1rem; font-size: .8rem; color: #7a8699; }

.cl-tier {
  border-radius: 14px;
  padding: .7rem .8rem;
  margin-bottom: .8rem;
  text-align: left;
  border: 2px solid transparent;
}
.cl-tier--silver { background: #f3f6fa; }
.cl-tier--gold   { background: #fdf6e2; }
.cl-tier.is-earned { border-color: #8fd17a; }
.cl-tier__head { margin: 0 0 .5rem; font-size: 1rem; color: #33405a; }

.cl-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.cl-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: .6rem;
}
.cl-check {
  width: 1.4rem; height: 1.4rem;
  border-radius: 50%;
  border: 2px solid #c2ccdb;
  display: grid; place-items: center;
  font-size: .85rem; font-weight: 800; color: #fff;
}
.cl-check.is-done { background: #8fd17a; border-color: #8fd17a; }
.cl-row__text { display: flex; flex-direction: column; line-height: 1.15; }
.cl-row__label { font-weight: 700; color: #33405a; font-size: .92rem; }
.cl-row__instr { color: #7a8699; font-size: .72rem; }
.cl-row__count { font-weight: 800; color: #556683; font-variant-numeric: tabular-nums; }
.cl-row.is-done .cl-row__count { color: #4a9e35; }

.cl-actions { display: flex; justify-content: center; margin-top: .3rem; }

/* ---- Card reward / detail overlay ---- */
.card-overlay {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  background: rgba(30, 40, 60, 0.7);
  backdrop-filter: blur(6px);
}
.card-overlay[hidden] { display: none; }
.card-3d {
  width: min(80vw, 320px);
  aspect-ratio: var(--card-ratio);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 30px 70px rgba(0,0,0,.5);
  transform-style: preserve-3d;
}
.card-3d.spin { animation: cardSpin 1.6s cubic-bezier(.2,.7,.2,1) forwards; }
@keyframes cardSpin {
  0%   { transform: perspective(900px) rotateY(0deg) scale(.4); opacity: 0; }
  20%  { opacity: 1; }
  100% { transform: perspective(900px) rotateY(1440deg) scale(1); opacity: 1; }
}
.card-3d img { width: 100%; height: 100%; object-fit: contain; display: block; }
.card-overlay .card-actions {
  position: absolute;
  bottom: 8%;
  display: flex;
  gap: .8rem;
}

/* Toast */
.toast {
  position: fixed;
  left: 50%;
  bottom: 12%;
  transform: translateX(-50%);
  z-index: 90;
  background: rgba(40,55,80,.9);
  color: #fff;
  padding: .7em 1.2em;
  border-radius: 999px;
  font-weight: 700;
  box-shadow: var(--shadow);
  animation: toast 2.4s ease forwards;
}
@keyframes toast {
  0% { opacity: 0; transform: translate(-50%, 12px); }
  12%,80% { opacity: 1; transform: translate(-50%, 0); }
  100% { opacity: 0; transform: translate(-50%, -10px); }
}

/* ============================================================
   HOME / LEVEL SELECT  (opened from the MENU button)
   Full-screen over the shared sky; the game grid is hidden while open so only
   the drifting sky shows behind. Sits below the nav chrome (z-40) so MENU stays
   tappable to toggle the page closed.
   ============================================================ */
.home {
  position: fixed;
  inset: 0;
  z-index: 35;
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}
.home[hidden] { display: none; }
/* Reveal only the sky behind the home page. HOME stays on show here — it is what
   returns the learner to the central directions page. */
body[data-home="true"] .grid { visibility: hidden; }

.home__scroll {
  box-sizing: border-box;
  min-height: 100%;
  width: min(92vw, 460px);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  text-align: center;
  padding: max(18px, env(safe-area-inset-top)) 16px
           calc(76px + env(safe-area-inset-bottom));   /* clear the nav chrome */
}
.home__logo {
  width: min(90vw, 440px);   /* enlarged banner logo */
  margin-top: .3rem;
  filter: drop-shadow(0 8px 18px rgba(70, 90, 130, .35));
}
.home__info {
  margin: 0;
  color: var(--ink-soft);
  font-weight: 600;
  line-height: 1.5;
  font-size: 1.02rem;
  text-shadow: 0 1px 4px rgba(255, 255, 255, .6);
}

.home__levels { display: flex; flex-direction: column; gap: .9rem; width: 100%; }

/* Level tiers — same shape and palette as the in-game level tabs. */
.home-level {
  border: none;
  border-radius: 18px;
  padding: .95rem 1.25rem;
  min-height: 66px;
  color: var(--ink);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: .1rem;
  text-align: left;
  cursor: pointer;
  transition: transform .1s;
}
.home-level:active { transform: translateY(2px) scale(.99); }
.home-level__title { font-weight: 800; font-size: 1.3rem; }
.home-level__sub { font-weight: 700; font-size: .9rem; opacity: .82; }

.home-level--1 { background: linear-gradient(160deg, #a6e8d0, #54c4a0); }
.home-level--2 { background: linear-gradient(160deg, #aacdff, #5f9bf0); }
.home-level--3 { background: linear-gradient(160deg, #ffc9a6, #f0805f); }

/* Locked mystery tier — grey gradient scattered with question marks. */
.home-level--locked {
  background: linear-gradient(160deg, #b8bcc4, #6b717b);
  align-items: center;
  justify-content: center;
  min-height: 84px;
  overflow: hidden;
}
.home-level__q {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .1rem .55rem;
  color: #fff;
  font-weight: 800;
  line-height: 1;
}
.home-level__q > span:nth-child(1)  { font-size: 1.4rem; opacity: .68; }
.home-level__q > span:nth-child(2)  { font-size: 2.0rem; opacity: .52; }
.home-level__q > span:nth-child(3)  { font-size: 1.1rem; opacity: .8;  }
.home-level__q > span:nth-child(4)  { font-size: 2.3rem; opacity: .58; }
.home-level__q > span:nth-child(5)  { font-size: 1.5rem; opacity: .5;  }
.home-level__q > span:nth-child(6)  { font-size: 1.2rem; opacity: .74; }
.home-level__q > span:nth-child(7)  { font-size: 2.1rem; opacity: .5;  }
.home-level__q > span:nth-child(8)  { font-size: 1.5rem; opacity: .7;  }
.home-level__q > span:nth-child(9)  { font-size: 1.1rem; opacity: .84; }
.home-level__q > span:nth-child(10) { font-size: 1.9rem; opacity: .55; }
.home-level__q > span:nth-child(11) { font-size: 1.3rem; opacity: .72; }

.home-level[aria-disabled="true"] .home-level__sub { opacity: .9; }

/* ==== Shared UI: floating frame + card-goal counter ======================= */
/* The soft translucent-white "oval frame" first used behind the Balloon HUD is
   the app's shared chrome for any control that floats over the drifting sky.
   Placed at the end of the sheet so it wins on source order over the earlier
   transparent backgrounds of .chrome-btn / .tr-back / .sp-back. */
.chrome-btn,
.tr-back, .tr-hearts, .tr-goal,
.sp-back, .sp-hearts, .sp-goal {
  background: var(--panel);
  border-radius: 14px;
  box-shadow: var(--shadow);
  backdrop-filter: blur(6px);
}
/* Icon buttons want a square-ish frame with even breathing room. */
.chrome-btn { padding: .4em; }
.tr-back, .sp-back { padding: .35em; }
/* Text / meter chips get a pill of horizontal padding. */
.tr-goal, .sp-goal { padding: .3em .55em; }
.tr-hearts, .sp-hearts { padding: .3em .5em; }

/* ---- Card-goal counter (Balloon / Trace / Spell) ------------------------
   Replaces the old ⭐ tally: a mini letter-card + "done / target" showing how
   close the letter in play is to unlocking its collectible card. The games'
   internal play counters live on for later interaction recording — this is
   only what the child sees. */
.hud-goal {
  display: inline-flex;
  align-items: center;
  gap: .35em;
  font-weight: 800;
  font-size: 1.05rem;
  line-height: 1;
  color: var(--ink);
}
.hud-goal:empty { display: none; }          /* before the first round sets a target */
.hud-goal__card {
  display: grid;
  place-items: center;
  width: 1.35em;
  height: 1.75em;
  border-radius: .3em;
  color: #fff;
  font-size: .82em;
  font-weight: 800;
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, .75), 0 1px 2px rgba(70, 90, 130, .35);
}
.hud-goal__count { font-variant-numeric: tabular-nums; white-space: nowrap; }
.hud-goal.is-met .hud-goal__count { color: #c8860b; }   /* gold once the card is complete */

/* ============================================================
   Daily Adventure — Journey Map, launch pop-up, recap, locks
   ============================================================ */

/* ---- Journey Map ribbon (top, tap to hide/show) ---- */
.journey {
  position: fixed;
  top: max(6px, env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  width: min(92vw, 440px);
  z-index: 45;
  padding: 6px 14px;
  background: var(--panel);
  border-radius: 999px;
  box-shadow: var(--shadow);
  backdrop-filter: blur(4px);
}
.journey[hidden] { display: none; }
.journey__track {
  position: relative;
  height: 30px;
  border-radius: 999px;
  cursor: pointer;
  /* whitened rainbow base — the "path not yet travelled" */
  background:
    linear-gradient(90deg, rgba(255,255,255,.62), rgba(255,255,255,.62)),
    linear-gradient(90deg, #ff6b6b, #ffd166, #06d6a0, #4d96ff, #b892ff);
  background-size: 100% 100%, min(92vw, 412px) 100%;
  background-repeat: no-repeat;
  transition: height .3s ease;
}
.journey__fill {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 0;
  border-radius: 999px;
  /* full-saturation rainbow, sized to the whole track so it stays aligned */
  background: linear-gradient(90deg, #ff6b6b, #ffd166, #06d6a0, #4d96ff, #b892ff);
  background-size: min(92vw, 412px) 100%;
  background-repeat: no-repeat;
  transition: width .5s ease;
}
.journey__marker {
  position: absolute;
  top: 50%;
  width: 24px; height: 24px;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 2px 3px rgba(70,90,130,.4));
  transition: opacity .5s ease, transform .5s ease;
  pointer-events: none;
}
.journey__marker.passed { opacity: 0; transform: translate(-50%, -50%) scale(1.7); }
.journey__ant {
  position: absolute;
  top: 50%; left: 0;
  width: 39.75px; height: auto;
  transform: translate(-50%, -55%);
  filter: drop-shadow(0 3px 4px rgba(70,90,130,.4));
  transition: left .5s ease;
  pointer-events: none;
}
/* Collapsed: a slim, still-tappable line with the icons tucked away. */
.journey--collapsed .journey__track { height: 7px; }
.journey--collapsed .journey__marker,
.journey--collapsed .journey__ant { opacity: 0; }
/* Used for one frame while a flight measures where the Ant is due to land: the
   marker must be AT its final spot to be measured, not easing towards it. */
.journey--nofx .journey__ant,
.journey--nofx .journey__fill { transition: none; }

/* ---- Idle Alpha Ant (lower left, tap to start / resume) ----
   The resting state of the same head that rides the ribbon. It sits opposite the
   HOME/SETTINGS cluster (bottom right) so the two never collide. Flights between
   here and the ribbon are driven from js/session.js with the Web Animations API,
   which owns the wrapper's transform — hence the bob living on the inner image,
   where the two cannot fight over the same property. */
.ant-idle {
  position: fixed;
  left: max(14px, env(safe-area-inset-left));
  bottom: max(14px, env(safe-area-inset-bottom));
  z-index: 46;                 /* just above the ribbon, below modals */
  width: 80px;                 /* ~2x the 39.75px it shrinks to on the ribbon */
  padding: 0;
  border: none;
  background: transparent;
  line-height: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ant-idle[hidden] { display: none; }
.ant-idle__img {
  width: 100%;
  height: auto;
  display: block;
  filter: drop-shadow(0 6px 10px rgba(70, 90, 130, .38));
  animation: antFloat 3.2s ease-in-out infinite;
}
@keyframes antFloat {
  0%, 100% { transform: translateY(0) rotate(-3deg); }
  50%      { transform: translateY(-10px) rotate(3deg); }
}
.ant-idle:active .ant-idle__img { animation-play-state: paused; transform: scale(.92); }

/* The Ant's invitation to go again. It pops in a beat after the Ant settles, so
   it reads as the Ant speaking rather than as part of the furniture. Hiding the
   Ant sets display:none on this too, which restarts the animation next time it
   appears — no JS timer needed to re-arm it. */
.ant-idle__bubble {
  position: absolute;
  left: 76%;
  bottom: 62%;
  width: max-content;
  max-width: 46vw;
  padding: .42em .7em;
  border-radius: 14px;
  /* Solid, not the translucent --panel: the tail overlaps the body, and two
     translucent layers would show a seam where they cross. */
  background: #fff;
  box-shadow: 0 4px 10px rgba(70, 90, 130, .28);
  color: var(--ink);
  font-size: .82rem;
  font-weight: 800;
  line-height: 1.25;
  text-align: left;
  white-space: normal;
  animation: bubblePop .45s cubic-bezier(.2, 1.3, .4, 1) .7s both;
}
/* Tail, pointing back down at the Ant's head. */
.ant-idle__bubble::after {
  content: "";
  position: absolute;
  left: 12px;
  bottom: -6px;
  width: 14px; height: 14px;
  background: inherit;
  border-radius: 3px;
  transform: rotate(45deg);
  box-shadow: 3px 3px 6px rgba(70, 90, 130, .12);
}
@keyframes bubblePop {
  from { opacity: 0; transform: translateY(6px) scale(.8); }
  to   { opacity: 1; transform: none; }
}
.ant-idle__bubble:empty { display: none; }

/* Mid-flight the wrapper is animated, so the bob stands down, the bubble stands
   down with it, and taps are ignored until the Ant has landed. */
.ant-idle--flying { pointer-events: none; }
.ant-idle--flying .ant-idle__img { animation: none; transform: none; }
.ant-idle--flying .ant-idle__bubble { display: none; }

@media (prefers-reduced-motion: reduce) {
  .ant-idle__img { animation: none; }
  .ant-idle__bubble { animation: none; }
}

/* ---- Age range picker (4 options, tighter than the 2/3-up segmented) ---- */
.segmented--age button { padding: .55em .2em; font-size: .95rem; }

/* ---- Today's Adventure! pop-up ---- */
.adv-panel { text-align: center; }
.adv-mascot { width: 84px; height: auto; margin: -.3rem auto 0; display: block; }
.adv-goals { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }
.adv-goals li {
  display: flex; align-items: center; justify-content: center; gap: .5rem;
  font-weight: 700; font-size: 1.1rem;
  background: #f4f9fc; border-radius: 14px; padding: .55rem .8rem;
}
.adv-goal__verb { color: var(--ink-soft); }
.adv-goal__n { color: var(--c-spell); font-size: 1.4rem; font-variant-numeric: tabular-nums; }
.adv-reward { margin: 0; font-weight: 700; color: #c8860b; }
.adv-actions { display: flex; gap: .6rem; }
.adv-actions .btn { flex: 1; }
.btn--ghost { background: #eef3f8; color: var(--ink-soft); box-shadow: none; }

/* ---- Adventure Complete recap ---- */
.recap {
  position: fixed; inset: 0; z-index: 62;
  display: grid; place-items: center;
  background: rgba(40, 55, 80, .45);
  backdrop-filter: blur(5px);
}
.recap[hidden] { display: none; }
.recap__panel {
  width: min(92vw, 400px);
  background: #fff;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.recap__scroll {
  max-height: 86dvh; overflow-y: auto;
  padding: 1.3rem; display: grid; gap: .9rem; text-align: center;
  -webkit-overflow-scrolling: touch;
}
.recap__mascot { width: 96px; height: auto; margin: 0 auto; }
.recap__scroll h2 { margin: 0; font-size: 1.5rem; }
.recap__lead { margin: 0; color: var(--ink-soft); font-weight: 700; }
.recap-row { display: grid; gap: .4rem; }
.recap-row h3 {
  margin: 0; font-size: .85rem; color: var(--ink-soft);
  text-transform: uppercase; letter-spacing: .05em;
}
.recap-chips { display: flex; flex-wrap: wrap; gap: .4rem; justify-content: center; }
.recap-chip { background: #f0f6fb; border-radius: 12px; padding: .3em .7em; font-weight: 700; }
.recap-chip--none { color: #b7c3d4; }
.recap-chip--card { background: #fff3d6; }
.recap-mission {
  display: flex; gap: .7rem; align-items: center; text-align: left;
  background: linear-gradient(135deg, #eaf7ff, #f5ecff);
  border-radius: 16px; padding: .8rem;
}
.recap-mission__mascot { width: 56px; height: auto; flex: none; }
.recap-mission h3 { margin: 0 0 .2rem; font-size: .85rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: .05em; }
.recap-mission__text { margin: 0; font-weight: 700; }
.recap__go { justify-self: center; margin-top: .3rem; }

/* ---- Locked level tiers (progressive unlock) ---- */
.b-level, .tr-level, .sp-level { position: relative; }
.lvl-lock { position: absolute; top: 6px; right: 8px; font-size: 1rem; }
.b-level.locked, .tr-level.locked, .sp-level.locked { opacity: .55; filter: saturate(.55); }

/* ============================================================
   PRIVACY — consent gate, disclosure, Settings block
   ============================================================ */

/* Settings grew a Privacy block, so the panel can now outgrow a short screen. */
#settingsModal .modal__panel,
.privacy-panel {
  max-height: 88dvh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.field--privacy { border-top: 2px solid #eef3f8; padding-top: 1rem; }
.privacy-note {
  margin: 0;
  font-size: .85rem;
  line-height: 1.45;
  color: var(--ink-soft);
}
.privacy-links { display: flex; flex-wrap: wrap; gap: .9rem; }
.linkish {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-size: .85rem;
  font-weight: 700;
  color: var(--c-spell);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.linkish--warn { color: #b4483f; }

/* Sharing toggle reads as a state, not a switch: the label says what is true
   now, and pressing it opens the grown-up gate rather than flipping anything. */
#sharingToggle { justify-self: start; font-size: .95rem; padding: .55em 1.1em; }
#sharingToggle[aria-pressed="true"] {
  background: var(--c-spell);
  color: #08343a;
}

/* ---- The grown-up gate ---- */
.gate-panel { text-align: center; }
.gate-hint { margin: 0; color: var(--ink-soft); font-size: .9rem; }
.gate-sum {
  margin: 0;
  font-size: 2rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  letter-spacing: .02em;
}
.gate-choices {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .6rem;
}
.gate-choice {
  border: 2px solid #e4ebf3;
  background: #f7fafc;
  border-radius: 14px;
  padding: .75em;
  font: inherit;
  font-size: 1.25rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.gate-choice:active { transform: translateY(1px) scale(.98); }

/* ---- The disclosure ---- */
.privacy-intro, .privacy-never, .privacy-foot { margin: 0; font-size: .92rem; }
.privacy-never { font-weight: 800; color: var(--ink-soft); }
.privacy-foot { font-size: .82rem; color: var(--ink-soft); line-height: 1.45; }
.privacy-list {
  margin: 0;
  padding-left: 1.15rem;
  display: grid;
  gap: .35rem;
  font-size: .88rem;
  line-height: 1.4;
}
.privacy-list--yes li::marker { color: var(--c-spell); }
.privacy-list--no li::marker { color: #b4483f; }
.privacy-actions { display: flex; gap: .6rem; }
.privacy-actions .btn { flex: 1; }

/* Build identity at the foot of Settings — for bug reports, never on a play
   screen. A non-production build is tinted so a screenshot is self-identifying. */
.build-line {
  margin: 0;
  text-align: center;
  font-size: .72rem;
  letter-spacing: .04em;
  color: #a8b4c4;
  font-variant-numeric: tabular-nums;
}
.build-line[data-env] {
  color: #9a5b00;
  font-weight: 700;
}
/* Only the environment tag is shouted — the version and sha stay readable. */
.build-line[data-env]::before {
  content: attr(data-env) " · ";
  text-transform: uppercase;
  letter-spacing: .08em;
}
