/* ============================================================
   Techtok PlayQuest — style.css
   Kid-first game UI in Techtok brand colors.
   ============================================================ */

/* ---------- Brand fonts (self-hosted, no external requests) ---------- */
@font-face {
  font-family: 'Neue Haas';
  src: url('../assets/fonts/NHaasGroteskTXPro-55Rg.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Neue Haas';
  src: url('../assets/fonts/NHaasGroteskTXPro-65Md.ttf') format('truetype');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Neue Haas';
  src: url('../assets/fonts/NHaasGroteskTXPro-75Bd.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ---------- Tokens ---------- */
:root {
  --ink: #0D065B;
  --ink-soft: #4A4680;
  --white: #FFFFFF;
  --yellow: #FAFF05;
  --yellow-dark: #C9CC00;
  --purple: #5F29FF;
  --purple-dark: #4318CC;
  --bg-yellow: #FFFFE6;
  --bg-purple: #EFEAFC;
  --bg-red: #FFDFDF;
  --bg-blue: #D6DCFF;
  --bg-green: #F1FFF9;
  --comp-yellow: #FCFF6C;
  --comp-purple: #AA8DFF;
  --comp-red: #FF8686;
  --comp-blue: #899CFF;
  --comp-green: #8CFFCF;
  --good: #14B86B;
  --good-dark: #0E8A50;
  --bad: #FF5C7A;

  --r-lg: 26px;
  --r-md: 18px;
  --r-sm: 12px;
  --r-pill: 999px;

  --shadow-sm: 0 3px 10px rgba(13, 6, 91, 0.10);
  --shadow-md: 0 10px 30px rgba(13, 6, 91, 0.18);

  --font: 'Neue Haas', 'Helvetica Neue', Helvetica, Arial, system-ui, sans-serif;

  /* ---------- Fluid type scale ----------
     Ten named steps instead of ad-hoc px, in two tiers that grow into
     different amounts of screen:

     Functional tier (xs-lg: labels, buttons, chips, body copy) caps at a
     620px-wide viewport, same as --appw's own ceiling — dense UI text
     doesn't need to keep growing just because a Chromebook has more room;
     past a point that only costs line length and makes rows wrap.

     Expressive tier (xl-hero: subheadings through hero/success titles)
     keeps growing all the way to a 1000px-wide viewport instead of
     stopping at 620 — these are short, punchy, emotional moments (page
     titles, "Helemaal juist!", the welcome screen), and a bigger screen
     genuinely earns bigger, bolder text there. It's a game; those moments
     are allowed to feel more fun with the extra room, not just sit frozen
     at phone size once the layout stops growing.

     Both tiers track viewport width continuously from a 360px small-phone
     floor, no breakpoint jumps, and every step keeps real separation from
     its neighbors at both ends so two adjacent steps are never the same
     size even at full scale. Not yet used anywhere — additive only until
     the existing font-size declarations are migrated onto it. */
  --fs-xs: clamp(10.5px, calc(9.81px + 0.19vw), 11px);
  --fs-sm: clamp(11.5px, calc(10.12px + 0.38vw), 12.5px);
  --fs-base: clamp(13px, calc(11.62px + 0.38vw), 14px);
  --fs-md: clamp(14.5px, calc(12.42px + 0.58vw), 16px);
  --fs-lg: clamp(16.5px, calc(14.42px + 0.58vw), 18px);
  --fs-xl: clamp(18.5px, calc(15.97px + 0.703vw), 23px);
  --fs-2xl: clamp(21px, calc(17.63px + 0.938vw), 27px);
  --fs-3xl: clamp(24px, calc(19.5px + 1.25vw), 32px);
  --fs-display: clamp(28px, calc(22.38px + 1.563vw), 38px);
  --fs-hero: clamp(33px, calc(24.56px + 2.344vw), 48px);

  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);

  --appw: 520px;
}

/* ---------- Reset ---------- */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  min-width: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  overflow-x: clip;
  /* touch-action:manipulation (the usual choice for "kill the 300ms tap
     delay") is actually shorthand for "pan-x pan-y pinch-zoom" — it still
     explicitly ALLOWS pinch-zoom, it only drops double-tap-to-zoom. iOS
     Safari also ignores the viewport meta's user-scalable=no/maximum-scale
     entirely as of iOS 10 (a deliberate Apple accessibility decision), so
     that's not a fallback either — touch-action is the only lever that
     actually works there. pan-x pan-y (no pinch-zoom) keeps normal
     scrolling and the same no-tap-delay benefit, minus native page zoom. */
  touch-action: pan-x pan-y;
  /* iOS Safari's elastic "rubber-band" bounce at the top/bottom of the page
     drags position:sticky/fixed bars along with it for a frame or two before
     they re-pin — that's what let map content show above the sticky top bar
     (.hud-wrap) in Safari. overscroll-behavior-y kills the bounce itself, so
     there's nothing for the bars to lag behind during. */
  overscroll-behavior-y: none;
  background:
    radial-gradient(circle at 20% 10%, #221591 0%, transparent 45%),
    radial-gradient(circle at 85% 85%, #3A1E9E 0%, transparent 40%),
    #0D065B;
}

body {
  font-family: var(--font);
  color: var(--ink);
  min-height: 100dvh;
  overflow-x: clip;
  overscroll-behavior-y: none;
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}

button {
  font-family: inherit;
  border: none;
  background: none;
  cursor: pointer;
  color: inherit;
}

img,
svg {
  max-width: 100%;
  display: block;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

#confetti-canvas {
  position: fixed;
  inset: 0;
  z-index: 900;
  pointer-events: none;
}

/* Cover for the safe-area-top/notch/status-bar region, ONLY while the map
   screen is active (body.screen-map-active, toggled in show() in app.js) —
   the map is the one screen whose top is navy (#0D065B, matching .hud);
   every other screen's top is a light pastel or a lighter blue-violet
   gradient, so showing this there would paint an unwanted dark strip over
   real content instead of blending in.
   While scrolling the map, Safari animates its own address bar from
   full-width down to a small pill, live-resizing how much of the page is
   visible — during that native animation, .hud/.hud::before (the map's own
   navy chrome) can lag a frame or two behind, briefly showing page content
   in the newly-exposed area (confirmed via screen recording: the map's
   pink CTA button flashing through where solid navy should be). This is a
   second, independent element for just that region, so it doesn't depend
   on .hud's own paint state catching up in time.
   Height matches .hud's own top padding (20px + safe-top, the iOS value)
   exactly — NOT taller — so this only ever covers the blank padding strip
   above where the HUD avatar actually starts. Taller than that and it
   paints over real content instead of just backstopping the empty margin
   above it. */
.status-bar-cover {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  max-width: var(--appw);
  margin: 0 auto;
  height: calc(20px + env(safe-area-inset-top, 0px));
  background: #0D065B;
  z-index: 9999;
  pointer-events: none;
}

body.screen-map-active .status-bar-cover {
  display: block;
}

/* ---------- App frame ---------- */
/* No max-width/shadow/card treatment here on purpose: the background
   (this element's own bg-purple, or whatever a screen overrides it with)
   fills the real browser viewport at every size, so an iPad/Chromebook/
   desktop never reads as "a phone floating in a dark void." --appw is
   still very much alive as a token, just applied one level down, to each
   screen's own content wrapper (.page-wrap, .mission-stage, etc. — see
   those rules) — so the CONTENT stays at a comfortable, centered reading
   width while the color behind it goes edge to edge. */
.app {
  min-height: 100dvh;
  position: relative;
  background: var(--bg-purple);
}

@media (min-width: 768px) {
  :root {
    --appw: 620px;
  }
}

body.full-viewport {
  --appw: 100vw;
  overflow: hidden !important;
}

body.full-viewport::-webkit-scrollbar,
body.full-viewport *::-webkit-scrollbar {
  display: none !important;
  width: 0 !important;
  height: 0 !important;
}

body.full-viewport .app {
  width: 100vw !important;
  max-width: 100vw !important;
  box-shadow: none !important;
  overflow: hidden !important;
}

body.full-viewport .bella-face {
  position: relative !important;
  background: #FFFFFF !important;
  border-radius: 50% !important;
  overflow: hidden !important;
}

body.full-viewport .bella-face::after {
  content: '' !important;
  position: absolute !important;
  inset: 0 !important;
  background: #FFFFFF url('../assets/bella-character.png') center / cover no-repeat !important;
  border-radius: 50% !important;
  z-index: 99 !important;
  pointer-events: none !important;
}

.screen {
  display: none;
}

.screen.active {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-weight: 700;
  font-size: var(--fs-lg);
  line-height: 1.2;
  padding: 16px 22px;
  border-radius: var(--r-md);
  transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
  user-select: none;
  -webkit-user-select: none;
}

.btn:active {
  transform: translateY(3px);
}

.btn-big {
  width: 100%;
  font-size: var(--fs-lg);
  padding: 18px 24px;
}

.btn-yellow {
  background: var(--yellow);
  color: var(--ink);
  box-shadow: 0 5px 0 var(--yellow-dark), var(--shadow-sm);
}

.btn-yellow:active {
  box-shadow: 0 2px 0 var(--yellow-dark);
}

.btn-purple {
  background: var(--purple);
  color: var(--white);
  box-shadow: 0 5px 0 var(--purple-dark), var(--shadow-sm);
}

.btn-purple:active {
  box-shadow: 0 2px 0 var(--purple-dark);
}

.btn-purple:disabled,
.btn-yellow:disabled {
  filter: grayscale(0.7) opacity(0.6);
  transform: none;
  cursor: default;
}

.btn-ghost {
  background: var(--bg-purple);
  color: var(--ink);
  box-shadow: 0 4px 0 #D5CCF2;
}

.btn-danger {
  background: var(--bad);
  color: #fff;
  box-shadow: 0 4px 0 #CC3A56;
}

.btn-dice {
  background: var(--white);
  color: var(--ink);
  width: 100%;
  box-shadow: 0 5px 0 #D5CCF2, var(--shadow-sm);
  margin-bottom: 14px;
  font-size: var(--fs-lg);
}

.btn-text {
  display: block;
  margin: 14px auto 0;
  font-size: var(--fs-md);
  font-weight: 500;
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 3px;
  padding: 8px;
}

.btn-row {
  display: flex;
  gap: 10px;
  align-items: stretch;
}

.btn-row .btn-big {
  width: auto;
  flex: 1;
}

.btn-back-row {
  flex: 0 0 auto;
  width: 52px;
  padding: 0;
  font-size: var(--fs-xl);
}

.btn-back-row-light {
  background: rgba(255, 255, 255, 0.16);
  color: var(--white);
  box-shadow: none;
  border: 1.5px solid rgba(255, 255, 255, 0.35);
}

.icon-btn {
  width: 42px;
  height: 42px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.92);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-sm);
  font-size: var(--fs-lg);
  font-weight: 700;
  flex-shrink: 0;
}

.icon-btn svg {
  width: 22px;
  height: 22px;
}

/* ---------- Brand ---------- */
.brand-mark {
  font-weight: 700;
  letter-spacing: 0.14em;
  font-size: var(--fs-base);
  color: var(--white);
}

.brand-mark span {
  color: var(--yellow);
}

/* ---------- Splash ---------- */
#screen-splash {
  background: var(--ink);
  align-items: center;
  justify-content: center;
  transition: opacity 0.45s ease;
}

#screen-splash.splash-out {
  opacity: 0;
}

.splash-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
}

.splash-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 92px;
  height: 92px;
  background: transparent !important;
  border-radius: 50% !important;
  border: none !important;
  outline: none !important;
  box-shadow: none !important;
  overflow: visible !important;
}

.splash-logo svg {
  width: 92px;
  height: 92px;
  border-radius: 50% !important;
  border: none !important;
  outline: none !important;
  display: block;
  background: transparent !important;
  box-shadow: none !important;
  filter: drop-shadow(0 0 25px rgba(250, 255, 5, 0.45));
}

.splash-dots {
  display: flex;
  gap: 8px;
}

.splash-dots span {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--yellow);
  animation: dotbounce 1s infinite ease-in-out;
}

.splash-dots span:nth-child(2) {
  animation-delay: 0.15s;
}

.splash-dots span:nth-child(3) {
  animation-delay: 0.3s;
}

@keyframes dotbounce {

  0%,
  60%,
  100% {
    transform: translateY(0);
    opacity: 0.5;
  }

  30% {
    transform: translateY(-8px);
    opacity: 1;
  }
}

/* ---------- Sky / clouds (welcome + map) ---------- */
.welcome-sky,
.map-sky {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 1;
}


.cloud,
.sky-bird,
.sky-butterfly,
.welcome-Buggie,
.ground-flower {
  pointer-events: auto;
  cursor: pointer;
  z-index: 1;
}

.anim-popped-out {
  transform: scale(0) !important;
  opacity: 0 !important;
  transition: transform 0.2s ease-in, opacity 0.2s ease-in !important;
}

.cloud {
  position: absolute;
  background: rgba(255, 255, 255, 0.85);
  border-radius: var(--r-pill);
  height: 26px;
  width: 90px;
  box-shadow: 22px -12px 0 6px rgba(255, 255, 255, 0.85), 48px -4px 0 2px rgba(255, 255, 255, 0.85);
  animation: drift 40s linear infinite;
  opacity: 0.9;
}

.cloud.c1 {
  top: 5%;
  left: -120px;
  animation-delay: -5s;
}

.cloud.c2 {
  top: 12%;
  left: -120px;
  animation-delay: -22s;
  scale: 0.75;
}

.cloud.c3 {
  top: 20%;
  left: -120px;
  animation-delay: -12s;
  scale: 1.15;
  opacity: 0.6;
}

.cloud.c4 {
  top: 28%;
  left: -120px;
  animation-delay: -32s;
  scale: 0.85;
  opacity: 0.75;
  animation-duration: 44s;
}

.cloud.c5 {
  top: 36%;
  left: -120px;
  animation-delay: -17s;
  scale: 1.25;
  opacity: 0.5;
  animation-duration: 36s;
}

.cloud.c6 {
  top: 44%;
  left: -120px;
  animation-delay: -28s;
  scale: 0.7;
  opacity: 0.8;
  animation-duration: 48s;
}

.sky-bird {
  position: absolute;
  width: 64px;
  left: -80px;
  filter: drop-shadow(0 2px 6px rgba(255, 255, 255, 0.35));
  animation: drift 32s linear infinite, flutter 1.8s ease-in-out infinite;
}

.sky-bird.b1 {
  top: 32%;
}

.sky-bird.b2 {
  top: 52%;
  width: 46px;
  animation-delay: -14s, -0.6s;
  animation-duration: 38s, 2.1s;
}

.sky-butterfly {
  position: absolute;
  width: 40px;
  filter: drop-shadow(0 2px 5px rgba(13, 6, 91, 0.28)) drop-shadow(0 0 3px rgba(255, 255, 255, 0.85));
}

.bfly-1 {
  left: -60px;
  top: 76%;
  /* Its own meandering path, not the straight-line drift clouds/birds use —
     a butterfly wanders and lingers before it eventually floats off. */
  animation: bfly-flight 24s ease-in-out infinite;
}

.bfly-2 {
  right: -60px;
  top: 83%;
  width: 30px;
  /* Opposite direction of bfly-1, its own timing and waypoints — not just a
     copy on a delay, an actually different butterfly. */
  animation: bfly-flight-rev 19s ease-in-out infinite;
  animation-delay: -7s;
}

.bfly-3 {
  left: -60px;
  top: 89%;
  width: 34px;
  animation: bfly-flight 31s ease-in-out infinite;
  animation-delay: -15s;
}

.bfly-grass {
  position: absolute;
  bottom: 180px;
  left: 25px;
  width: 32px;
  z-index: 2;
  animation: bfly-grass-hover 18s ease-in-out infinite alternate;
}

@keyframes bfly-grass-hover {
  0% {
    transform: translate(0, 0) rotate(-6deg);
  }

  25% {
    transform: translate(75px, -20px) rotate(8deg);
  }

  50% {
    transform: translate(165px, -8px) rotate(-4deg);
  }

  75% {
    transform: translate(235px, -26px) rotate(6deg);
  }

  100% {
    transform: translate(295px, -12px) rotate(-5deg);
  }
}

/* A wandering path, not a straight line: drifts on generally, but
   back-and-forth and up-and-down along the way, lingering a while before
   it finally floats off past the edge — closer to how a real butterfly
   actually moves than a steady glide. */
@keyframes bfly-flight {
  0% {
    translate: 0 0;
  }

  8% {
    translate: calc(var(--appw) * 0.10) -16px;
  }

  18% {
    translate: calc(var(--appw) * 0.16) 12px;
  }

  29% {
    translate: calc(var(--appw) * 0.30) -20px;
  }

  40% {
    translate: calc(var(--appw) * 0.26) 8px;
  }

  52% {
    translate: calc(var(--appw) * 0.46) -14px;
  }

  64% {
    translate: calc(var(--appw) * 0.58) 16px;
  }

  77% {
    translate: calc(var(--appw) * 0.74) -18px;
  }

  90% {
    translate: calc(var(--appw) * 0.90) 10px;
  }

  100% {
    translate: calc(var(--appw) + 260px) -6px;
  }
}

/* Mirror of bfly-flight — wanders right to left instead, with its own
   waypoints so it doesn't just look like the same flight in reverse. */
@keyframes bfly-flight-rev {
  0% {
    translate: 0 0;
  }

  10% {
    translate: calc(var(--appw) * -0.12) 10px;
  }

  22% {
    translate: calc(var(--appw) * -0.20) -14px;
  }

  35% {
    translate: calc(var(--appw) * -0.34) 18px;
  }

  47% {
    translate: calc(var(--appw) * -0.30) -10px;
  }

  60% {
    translate: calc(var(--appw) * -0.52) 14px;
  }

  73% {
    translate: calc(var(--appw) * -0.68) -16px;
  }

  86% {
    translate: calc(var(--appw) * -0.86) 8px;
  }

  100% {
    translate: calc((var(--appw) + 260px) * -1) -4px;
  }
}

/* Each wing is its own stroke pivoting from the point they meet, rotating
   in opposite directions — a real flap, not a rigid shape sliding by. */
.bird-wing-l,
.bird-wing-r {
  transform-origin: 16px 10px;
}

.bird-wing-l {
  animation: flap-wing-l 0.42s ease-in-out infinite;
}

.bird-wing-r {
  animation: flap-wing-r 0.42s ease-in-out infinite;
}

@keyframes flap-wing-l {

  0%,
  100% {
    rotate: 0deg;
  }

  50% {
    rotate: -22deg;
  }
}

@keyframes flap-wing-r {

  0%,
  100% {
    rotate: 0deg;
  }

  50% {
    rotate: 22deg;
  }
}

/* Wing pairs pivot from the body's centerline, scaling in toward it and
   back out — a flutter, not a static shape. */
.bfly-wing-l,
.bfly-wing-r {
  transform-origin: 14px 13px;
  animation: bfly-flap 0.6s ease-in-out infinite;
}

@keyframes bfly-flap {

  0%,
  100% {
    scale: 1 1;
  }

  50% {
    scale: 0.7 1;
  }
}

@keyframes flutter {

  0%,
  100% {
    translate: 0 0;
  }

  50% {
    translate: 0 -6px;
  }
}

/* transform (not the standalone translate property) so the large horizontal
   offset is reliably clipped by the sky container's overflow:hidden across
   browsers — the drift distance is wide enough that clipping quirks show. */
@keyframes drift {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(calc(var(--appw) + 260px));
  }
}

/* ---------- Welcome ---------- */
#screen-welcome {
  background: linear-gradient(180deg, #2A16A8 0%, #5F29FF 55%, #8F6BFF 100%);
  position: relative;
}

.welcome-Buggie {
  position: absolute;
  top: 9%;
  left: 50%;
  width: 250px;
  transform: translateX(-50%);
}

.welcome-Buggie svg {
  width: 100%;
}

.welcome-content {
  position: relative;
  z-index: 2;
  margin-top: auto;
  margin-left: auto;
  margin-right: auto;
  width: 100%;
  max-width: var(--appw);
  background: linear-gradient(180deg, transparent, rgba(13, 6, 91, 0.35) 30%);
  padding: 60px 24px calc(28px + var(--safe-bottom));
  display: flex;
  flex-direction: column;
  gap: 16px;
  color: var(--white);
}

.welcome-content h1 {
  font-size: var(--fs-hero);
  line-height: 1.08;
  font-weight: 700;
  text-wrap: balance;
}

.glitch-word {
  color: var(--yellow);
  display: inline-block;
  animation: glitchflicker 2.4s infinite steps(1);
}

@keyframes glitchflicker {

  0%,
  88%,
  100% {
    transform: none;
    opacity: 1;
  }

  90% {
    transform: translate(-2px, 1px) skewX(-8deg);
    opacity: 0.7;
  }

  93% {
    transform: translate(2px, -1px);
    opacity: 1;
  }

  96% {
    transform: skewX(6deg);
    opacity: 0.85;
  }
}

.welcome-content p {
  font-size: 1.3em;
  line-height: 1.5;
  opacity: 0.94;
  max-width: 100%;
  text-wrap: pretty;
}

.privacy-pill {
  display: inline-flex;
  width: fit-content;
  align-items: center;
  gap: 6px;
  background: rgba(255, 255, 255, 0.16);
  border: 1.5px solid rgba(255, 255, 255, 0.35);
  color: var(--white);
  font-size: var(--fs-base);
  font-weight: 500;
  padding: 8px 14px;
  border-radius: var(--r-pill);
}

/* ---------- Onboarding (outcome screen) ---------- */
#screen-onboarding {
  background: linear-gradient(180deg, #2A16A8 0%, #5F29FF 55%, #8F6BFF 100%);
  position: relative;
}

.onboarding-Buggie {
  left: auto;
  right: 6%;
  top: 4%;
  width: 90px;
  transform: scaleX(-1);
}

.accent-word {
  color: var(--yellow);
}

.mission-steps {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 10px 0 4px;
}

.mission-step {
  display: flex;
  align-items: center;
  gap: 12px;
  background: rgba(255, 255, 255, 0.14);
  border-radius: var(--r-md);
  padding: 12px 16px;
  color: var(--white);
}

.mission-step-num {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--yellow);
  color: var(--ink);
  font-weight: 700;
  font-size: var(--fs-md);
}

.mission-step-num svg {
  width: 16px;
  height: 16px;
}

.mission-step:nth-child(2) .mission-step-num {
  background: var(--comp-green);
}

.mission-step:nth-child(3) .mission-step-num {
  background: var(--comp-blue);
}

.mission-step-label {
  font-size: 1em;
  font-weight: 700;
}

/* ---------- Hero builder ---------- */
#screen-builder {
  background: linear-gradient(180deg, #EFEAFC 0%, #D6DCFF 100%);
}

.builder-wrap {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
  width: 100%;
  max-width: var(--appw);
  margin-left: auto;
  margin-right: auto;
  padding: calc(22px + var(--safe-top)) 22px 0;
}

.builder-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding-bottom: 12px;
}

.builder-actions {
  position: sticky;
  bottom: 0;
  z-index: 5;
  flex-shrink: 0;
  margin: 0 -22px;
  padding: 18px 22px calc(22px + var(--safe-bottom));
  background: linear-gradient(180deg, rgba(214, 220, 255, 0) 0%, #D6DCFF 35%);
}

.builder-top {
  display: flex;
  justify-content: center;
  margin-bottom: 6px;
  flex-shrink: 0;
  position: sticky;
  top: 0;
  z-index: 5;
}

.builder-steps {
  display: flex;
  gap: 8px;
}

.bstep {
  width: 26px;
  height: 8px;
  border-radius: var(--r-pill);
  background: rgba(13, 6, 91, 0.15);
  transition: background 0.2s;
}

.bstep.now {
  background: var(--purple);
}

.bstep.done {
  background: var(--comp-purple);
}

.builder-preview {
  position: relative;
  display: flex;
  justify-content: center;
  padding: 18px 0 6px;
}

.builder-podium {
  position: absolute;
  bottom: 2px;
  width: 150px;
  height: 26px;
  border-radius: 50%;
  background: rgba(13, 6, 91, 0.14);
  filter: blur(1px);
}

.builder-hero {
  width: 150px;
  position: relative;
  z-index: 1;
}

.builder-hero svg {
  width: 100%;
}

#screen-builder h2 {
  font-size: var(--fs-3xl);
  font-weight: 700;
  text-align: center;
  margin-top: 10px;
}

.builder-sub {
  text-align: center;
  color: var(--ink-soft);
  font-size: 1.3em;
  margin: 6px 0 16px;
  text-wrap: pretty;
}

.species-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-bottom: 18px;
}

.species-card {
  background: var(--white);
  border-radius: var(--r-md);
  padding: 10px 6px 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  border: 3px solid transparent;
  box-shadow: var(--shadow-sm);
  transition: transform 0.12s ease, border-color 0.12s ease;
}

.species-card:active {
  transform: scale(0.94);
}

.species-card.sel {
  border-color: var(--purple);
  background: var(--bg-yellow);
}

.species-card .hero-svg {
  width: 64px;
  height: 64px;
}

.species-name {
  font-weight: 700;
  font-size: var(--fs-base);
}

.species-flavor {
  font-size: var(--fs-xs);
  color: var(--ink-soft);
}

.palette-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 12px 14px;
  max-width: 270px;
  margin: 0 auto 16px;
  padding: 8px 0 14px;
}

.palette-row::-webkit-scrollbar {
  display: none;
}

.palette-dot {
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 4px solid var(--white);
  box-shadow: var(--shadow-sm);
  transition: transform 0.12s ease, border-color 0.12s;
}

.palette-dot:active {
  transform: scale(0.9);
}

.palette-dot.sel {
  border-color: var(--ink);
  transform: scale(1.12);
}

.name-card {
  background: var(--white);
  border-radius: var(--r-lg);
  padding: 22px;
  text-align: center;
  box-shadow: var(--shadow-md);
  margin-bottom: 14px;
}

.name-big {
  font-size: var(--fs-display);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.name-code {
  margin-top: 8px;
  font-size: var(--fs-base);
  color: var(--ink-soft);
}

.name-code b {
  background: var(--bg-yellow);
  padding: 3px 10px;
  border-radius: var(--r-pill);
  color: var(--ink);
  letter-spacing: 0.08em;
}

/* ---------- HUD / Map ---------- */
#screen-map {
  background: linear-gradient(180deg, #A9CBFF 0%, var(--bg-blue) 50%, var(--bg-green) 100%);
  position: relative;
}

/* Sits at the true bottom of the canvas, extending behind the fixed bottom
   nav — its own art is already solid dark green down there, so it continues
   the ground color seamlessly instead of needing a separate synthetic fill
   bar. Only the top portion (the actual hills) needs to be visible above
   the nav; the rest can safely sit hidden behind it. */
.map-skyline {
  position: absolute;
  bottom: 45px;
  left: 0;
  right: 0;
  opacity: 0.94;
}

/* Real illustrated rolling hills, colors remapped to the brand's green
   palette. Shown at its natural size (not cropped to a fixed-height box) so
   the transparent area above the hill silhouette stays transparent and the
   curved edge overlays the page background, instead of being cut into a
   flat rectangle. */
.grass-hills-img {
  display: block;
  width: 100%;
  height: auto;
  min-height: 130px;
  object-fit: cover;
}

/* Simple flowers along the ground, each swaying from its own stem base —
   staggered timing/size per flower so they don't all sway in lockstep. */
.ground-flower {
  position: absolute;
  bottom: 4px;
  width: 16px;
  transform-origin: 50% 100%;
  animation: flower-sway 3.2s ease-in-out infinite alternate;
}

@keyframes flower-sway {
  0% {
    rotate: -7deg;
  }

  100% {
    rotate: 7deg;
  }
}

.ground-flower.gf-1 {
  left: 6%;
  animation-delay: -0.4s;
}

.ground-flower.gf-2 {
  left: 18%;
  width: 13px;
  bottom: 10px;
  animation-duration: 2.6s;
  animation-delay: -1.6s;
}

.ground-flower.gf-3 {
  left: 30%;
  animation-duration: 3.6s;
  animation-delay: -2.2s;
}

.ground-flower.gf-4 {
  left: 43%;
  width: 14px;
  bottom: 8px;
  animation-duration: 2.9s;
  animation-delay: -0.8s;
}

.ground-flower.gf-5 {
  left: 57%;
  animation-duration: 3.4s;
  animation-delay: -1.1s;
}

.ground-flower.gf-6 {
  left: 71%;
  width: 13px;
  bottom: 11px;
  animation-duration: 2.7s;
  animation-delay: -2.6s;
}

.ground-flower.gf-7 {
  left: 84%;
  animation-duration: 3.1s;
  animation-delay: -1.9s;
}

.ground-flower.gf-8 {
  left: 94%;
  width: 14px;
  bottom: 7px;
  animation-duration: 2.8s;
  animation-delay: -0.2s;
}

/* position:fixed, not sticky — same reasoning as .bottom-nav (see that
   rule): sticky has to continuously compare the element's normal-flow
   position against the current scroll offset, and that comparison can
   lag during an abrupt JS-driven scroll (showMap's scroll-to-current-node
   jump on a cold load straight to the map) before Safari's own
   address-bar/viewport animation has settled — which is what let map
   content show above this bar. Fixed is pinned to the viewport on every
   frame regardless of scroll position, so there's nothing to lag behind.
   Centered with left:0/right:0/max-width/margin:auto, not
   left:50%/transform, for the same Android-jank reasons as .bottom-nav.
   Deliberately NOT forcing a compositing layer here (no transform/
   will-change) — that was tried and it broke .hud::before's upward
   overflow (the pseudo-element that extends the navy background above
   this box to cover the area Safari's address bar vacates as it collapses
   to a pill while scrolling): forcing a layer clips a compositing-layer's
   painted region to its own box in WebKit, so content a child paints
   outside that box via negative offset can get cut off. */
.hud-wrap {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  max-width: var(--appw);
  margin: 0 auto;
  z-index: 50;
  /* This wrapper's own box can end up a hair taller than the visible .hud
     bar (safe-area insets, sub-pixel rounding vs the banner's sticky top
     offset below) — without this, that empty sliver silently eats clicks
     meant for the chapter banner's button once it's docked underneath. */
  pointer-events: none;
}

.hud-wrap>* {
  pointer-events: auto;
}

.hud {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: calc(14px + var(--safe-top)) 14px 12px;
  background: #0D065B;
  box-shadow: var(--shadow-md);
}

@supports (-webkit-touch-callout: none) {
  .hud {
    padding-top: calc(20px + var(--safe-top));
  }
}

.hud::before {
  content: '';
  position: absolute;
  bottom: 100%;
  left: -20vw;
  right: -20vw;
  height: 50vh;
  background: #0D065B;
  z-index: 0;
  pointer-events: none;
}

.hud-hero {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  background: rgba(255, 255, 255, 0.12);
  border-radius: var(--r-pill);
  padding: 5px 14px 5px 5px;
}

.hud-hero:active {
  transform: scale(0.96);
}

.hud-face {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
}

.hud-face svg {
  width: 34px;
  height: 34px;
}

.hud-names {
  display: flex;
  flex-direction: column;
  min-width: 0;
  text-align: left;
}

.hud-names b {
  color: var(--white);
  font-size: var(--fs-base);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.hud-names span {
  color: var(--comp-yellow);
  font-size: var(--fs-xs);
  font-weight: 500;
}

.hud-right {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 10px;
}

/* A total, not a bar. The bar filled toward the next level — a number the
   kid never chose and can't act on. Real progress is the map itself and the
   Missielogboek; this is just a score.
   NB: .hud-xp-bar / .hud-xp-fill below are still used by the mission log. */
.hud-xp {
  display: flex;
  align-items: center;
  gap: 5px;
  background: rgba(255, 255, 255, 0.14);
  border-radius: var(--r-pill);
  padding: 6px 12px;
  color: var(--yellow);
  font-size: var(--fs-base);
  font-weight: 700;
}

.hud-xp svg {
  width: 15px;
  height: 15px;
}

/* opens the Missielogboek drawer */
.hud-map {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.14);
  color: var(--white);
  transition: background 0.15s ease, transform 0.12s ease;
}

.hud-map svg {
  width: 19px;
  height: 19px;
}

.hud-map:active {
  transform: scale(0.92);
}

.hud-map.open {
  background: var(--yellow);
  color: var(--ink);
}

.hud-xp-bar {
  width: 84px;
  height: 9px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.25);
  overflow: hidden;
}

.hud-xp-fill {
  height: 100%;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--yellow), #FFD34D);
  transition: width 0.6s ease;
}

.map-scroll {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: var(--appw);
  margin-left: auto;
  margin-right: auto;
  /* .hud-wrap floats over the content (position:fixed) rather than taking
     its own space in flow, so ~88px reserves room for the HUD bar itself
     (its padding + the 40px avatar, plus a hair of buffer) — then the
     existing 28px on top of that keeps chapter 1's banner from sitting
     flush against the HUD from the very first frame — same y-position as
     the sticky offset below — so the IntersectionObserver in
     watchStickyBanners() (app.js) can't call it "docked" before the user
     ever scrolls, skipping the hero state entirely. */
  padding-top: calc(88px + var(--safe-top) + 28px);
  /* .bottom-nav floats over the content (position:fixed) rather than
     taking its own space in flow, so this reserves room for it — plus the
     locate FAB that sits just above it — at the end of the chapter list. */
  padding-bottom: calc(170px + var(--safe-bottom));
}

/* The "Jouw Missie" tab that hung under the header is gone — it made the
   sticky top bar feel heavy. The drawer it opened now hangs off the map
   button in the hud instead. */

.mission-drawer {
  position: relative;
  z-index: 9;
  max-height: 0;
  overflow: hidden;
  opacity: 0;
  background: var(--white);
  margin: 0 14px;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  transition: max-height 0.4s ease, opacity 0.3s ease;
}

.mission-drawer.open {
  max-height: min(78vh, calc(100dvh - 200px));
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  opacity: 1;
  box-shadow: var(--shadow-md);
}

.mission-drawer-inner {
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 22px 20px calc(110px + var(--safe-bottom));
}

.mission-drawer-title {
  display: block;
  text-align: center;
  font-size: var(--fs-lg);
}

.spine {
  display: flex;
  flex-direction: column;
}

/* ---------- Chapter sections (the six-chapter spine) ---------- */
.chapter-section {
  position: relative;
}

/* Marks exactly where the sticky wrapper starts, for watchStickyBanners()
   (app.js) to detect via IntersectionObserver when it's actually docked —
   CSS alone has no way to select a sticky element's "currently stuck" state. */
.chapter-banner-sentinel {
  height: 1px;
}

/* Sticks right under the HUD, swapping for the next chapter's as its
   section scrolls into place, Duolingo-style. Holds both the banner AND its
   missielogboek drawer as one pinned unit — otherwise, once the banner is
   docked, an opened drawer would expand at the chapter's normal scroll
   position instead of staying reachable under the pinned banner. */
.chapter-sticky {
  position: sticky;
  top: calc(68px + var(--safe-top));
  z-index: 10;
}

@supports (-webkit-touch-callout: none) {
  .chapter-sticky {
    top: calc(76px + var(--safe-top));
  }
}

/* Chapter identity lives on this banner, not the full scroll background —
   keeps the sky/clouds continuous behind the whole path. A roomy hero card
   at rest (big title, ghost numeral) that visibly compresses down into the
   compact docked bar as .is-stuck lands on .chapter-sticky (set by app.js)
   — the padding/font-size transitions below are what make that a morph
   instead of a swap. Fully rounded at rest; once docked, the top corners
   square off flush against the HUD above it. */
.chapter-banner {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 50px;
  margin: 0 14px;
  padding: 26px 22px 20px;
  background: var(--chapter-tint, transparent);
  border-radius: var(--r-lg);
  /* A real floating shadow, not the hairline --shadow-card — this banner
     needs to read as a lifted card over the sky, not a flat color block. */
  box-shadow: var(--shadow-sm);
  transition: padding 0.32s cubic-bezier(.4, 0, .2, 1), margin 0.32s cubic-bezier(.4, 0, .2, 1), border-radius 0.15s ease, transform 0.1s ease;
}

/* Only unlocked chapters carry data-chapter (see app.js) — a locked one
   keeps the plain, unclickable banner rather than opening an intro for
   content the player can't play yet. */
.chapter-banner[data-chapter] {
  cursor: pointer;
}

.chapter-banner[data-chapter]:active {
  transform: scale(0.99);
}

.chapter-sticky.is-stuck .chapter-banner {
  margin: 0;
  padding: 20px 20px 12px;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}

/* A big pale numeral behind the title, purely decorative — it's what gives
   the resting banner presence, then shrinks away as the real icon button
   fades in once docked, so the two read as one continuous morph. */
.chapter-banner-ghost {
  position: absolute;
  right: 22px;
  top: 44%;
  transform: translateY(-44%);
  font-size: 88px;
  font-weight: 800;
  line-height: 1;
  color: var(--chapter-accent, var(--ink));
  opacity: 0.14;
  pointer-events: none;
  user-select: none;
  transition: opacity 0.26s ease, transform 0.32s cubic-bezier(.4, 0, .2, 1), font-size 0.32s cubic-bezier(.4, 0, .2, 1);
}

.chapter-sticky.is-stuck .chapter-banner-ghost {
  opacity: 0;
  font-size: 48px;
  transform: translateY(-44%) translateX(14px);
}

.chapter-banner-text {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.chapter-sticky.is-stuck .chapter-banner-text {
  gap: 1px;
}

/* Only makes sense to open once its chapter is the one actually pinned —
   opening it from a resting (not-yet-docked) banner felt disconnected, so
   it stays hidden (and scaled down, echoing the ghost numeral it
   visually replaces) until .chapter-sticky.is-stuck reveals it below. */
.chapter-banner-icon {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.9);
  color: var(--ink-soft);
  box-shadow: 0 3px 8px rgba(13, 6, 91, 0.14);
  opacity: 0;
  transform: scale(0.5);
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.3s cubic-bezier(.34, 1.56, .64, 1), background 0.15s ease, color 0.15s ease;
}

.chapter-sticky.is-stuck .chapter-banner-icon {
  opacity: 1;
  transform: scale(1);
  pointer-events: auto;
}

.chapter-banner-icon:active {
  transform: scale(0.9);
}

.chapter-banner-icon.open {
  background: var(--yellow);
  color: var(--ink);
}

.chapter-banner-icon svg {
  width: 18px;
  height: 18px;
}

.chapter-num {
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--chapter-accent, var(--ink-soft));
  transition: font-size 0.28s ease;
}

.chapter-sticky.is-stuck .chapter-num {
  font-size: var(--fs-xs);
  color: var(--ink-soft);
}

.chapter-title {
  font-size: var(--fs-2xl);
  font-weight: 800;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: font-size 0.28s cubic-bezier(.4, 0, .2, 1);
}

.chapter-sticky.is-stuck .chapter-title {
  font-size: var(--fs-lg);
}

.chapter-meta {
  text-align: left;
  padding: 8px 24px 30px;
}

/* Centers the subtitle pill regardless of .chapter-meta's own (left)
   text alignment, which the lock ribbon and lesson banner below still rely on. */
.chapter-subtitle-wrap {
  display: flex;
  justify-content: center;
}

/* A small pill, not a second heading fighting the banner above it — closer
   to a caption/kicker than a headline, since the banner already carries the
   bold chapter identity. Carries its own chapter accent color so it still
   reads as "alive" without falling back to a flat dark bar. */
.chapter-section-title {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-base);
  font-weight: 700;
  font-family: inherit;
  letter-spacing: 0.01em;
  color: var(--ink-soft);
  /*background: var(--chapter-tint, var(--purple));
    box-shadow: var(--shadow-sm);
*/
  border: none;
  padding: 9px 20px;
  border-radius: var(--r-pill);
  margin: 2px 0 22px;
  transition: transform 0.1s ease;
}

/* A <button> when its chapter is unlocked (see app.js), a plain <h3>
   otherwise — only the button variant gets pointer/press affordance. */
button.chapter-section-title {
  cursor: pointer;
}

button.chapter-section-title:active {
  transform: scale(0.96);
}

.chapter-subtitle-icon {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
}

.chapter-lock-ribbon {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 8px;
  background: rgba(255, 255, 255, 0.85);
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: 5px 14px;
  border-radius: var(--r-pill);
  max-width: 260px;
}

.chapter-lock-ribbon svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.chapter-locked .chapter-title,
.chapter-locked .chapter-num {
  opacity: 0.55;
}

.chapter-connector {
  width: 0;
  height: 34px;
  margin: 0 auto;
  border-left: 5px dashed rgba(255, 255, 255, 0.65);
}

.chapter-connector.done {
  border-left-style: solid;
  border-left-color: var(--good);
}

.map-path {
  position: relative;
  height: var(--path-h, 220px);
  margin-top: 6px;
}

.map-path-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.map-step {
  position: absolute;
  left: var(--nx);
  top: var(--ny);
  transform: translate(-50%, -50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 150px;
}

.map-node {
  position: relative;
  width: 78px;
  height: 78px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 6px 0 rgba(13, 6, 91, 0.25), var(--shadow-md);
  transition: transform 0.12s ease;
}

.map-node:active:not(:disabled) {
  transform: scale(0.92);
}

.map-node-icon svg {
  width: 36px;
  height: 36px;
}

.node-open,
.node-done {
  background: var(--white);
  color: var(--purple);
}

.ncolor-ocean.node-open,
.ncolor-ocean.node-done {
  color: #2F6FD6;
}

.ncolor-coral.node-open,
.ncolor-coral.node-done {
  color: #E0503F;
}

.ncolor-sunset.node-open,
.ncolor-sunset.node-done {
  color: #E06A1B;
}

.ncolor-berry.node-open,
.ncolor-berry.node-done {
  color: #D9427F;
}

.ncolor-galaxy.node-open,
.ncolor-galaxy.node-done {
  color: #6A3FE0;
}

.ncolor-forest.node-open,
.ncolor-forest.node-done {
  color: #2E9A62;
}

.node-done .map-node-icon {
  opacity: 0.5;
}

.map-node-badge {
  position: absolute;
  right: -4px;
  bottom: -4px;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--good);
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 3px solid var(--white);
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
}

.map-node-badge svg {
  width: 15px;
  height: 15px;
}

.node-locked {
  background: rgba(255, 255, 255, 0.55);
  color: var(--ink-soft);
  cursor: default;
}

.node-auth-locked {
  cursor: pointer;
}

.map-node-lock {
  font-size: var(--fs-3xl);
  display: flex;
  align-items: center;
  justify-content: center;
}

.map-node-lock svg {
  width: 28px;
  height: 28px;
}

.node-trophy {
  background: var(--yellow);
  color: var(--ink);
  animation: nodepulse 1.6s infinite;
}

.map-node-ring {
  position: absolute;
  inset: -7px;
  border-radius: 50%;
  border: 4px solid var(--yellow);
  animation: nodepulse 1.6s infinite;
}

@keyframes nodepulse {

  0%,
  100% {
    transform: scale(1);
    opacity: 1;
  }

  50% {
    transform: scale(1.08);
    opacity: 0.75;
  }
}

.map-node-label {
  margin-top: 8px;
  text-align: center;
  background: rgba(255, 255, 255, 0.92);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: stretch;
  max-width: 170px;
}

.map-node-label-content {
  padding: 6px 12px;
  display: flex;
  flex-direction: column;
  gap: 1px;
  align-items: center;
  justify-content: center;
}

.map-node-label-content b {
  font-size: var(--fs-base);
}

.map-sub {
  font-size: var(--fs-xs);
  color: var(--ink-soft);
  text-wrap: pretty;
}

.map-stars {
  font-size: var(--fs-base);
  letter-spacing: 2px;
}

.star {
  color: #D8D4F0;
}

.star svg {
  fill: currentColor;
}

.star.on {
  color: #FFB300;
}

.map-here {
  position: absolute;
  top: -46px;
  left: 50%;
  transform: translateX(-50%);
  width: 52px;
  animation: bob 2s ease-in-out infinite;
  filter: drop-shadow(0 4px 6px rgba(13, 6, 91, 0.3));
}

@keyframes bob {

  0%,
  100% {
    translate: 0 0;
  }

  50% {
    translate: 0 -7px;
  }
}

/* ---------- Bottom nav ----------
   position:fixed, pinned to the true viewport — the standard, most
   broadly-supported technique for an app-shell bottom bar (every major
   mobile PWA uses this). Centered with plain left:0/right:0 + max-width +
   margin:0 auto, NOT left:50%/transform:translateX(-50%) — that transform
   combo on a fixed element is a well-documented source of detachment/jank
   on Android Chrome during scroll and address-bar show/hide, which is what
   broke this bar. margin:auto centering has none of that failure mode and
   is just as widely supported. */
.bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  max-width: var(--appw);
  margin: 0 auto;
  z-index: 50;
  display: flex;
  justify-content: space-around;
  background: var(--white);
  padding: 10px 8px calc(10px + var(--safe-bottom));
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: 0 -8px 30px rgba(13, 6, 91, 0.18);
}

.nav-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-soft);
  padding: 4px 18px;
  border-radius: var(--r-md);
}

.nav-item .nav-icon svg {
  width: 24px;
  height: 24px;
}

.nav-item.active {
  color: var(--purple);
  background: var(--bg-purple);
}

/* Floating "back to where I am" button — the wrapper is a full-width fixed
   strip (same left:0/right:0/max-width/margin:auto centering as
   .bottom-nav — no transform, see that rule for why) with
   pointer-events:none, so only the button itself — right-aligned inside
   it — is actually clickable; the rest of the strip lets map taps through.
   Hidden by default: bindMapLocate() in app.js toggles .visible once
   #map-node-current scrolls out of view. */
.map-locate-wrap {
  position: fixed;
  left: 0;
  right: 0;
  max-width: var(--appw);
  margin: 0 auto;
  bottom: calc(86px + var(--safe-bottom));
  display: flex;
  justify-content: flex-end;
  padding: 0 16px;
  pointer-events: none;
  z-index: 55;
}

.map-locate-btn {
  pointer-events: none;
  width: 48px;
  height: 48px;
  border-radius: var(--r-pill);
  background: var(--yellow);
  color: var(--ink);
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 0 var(--yellow-dark), var(--shadow-md);
  opacity: 0;
  transform: translateY(8px) scale(0.9);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.map-locate-btn svg {
  width: 22px;
  height: 22px;
}

.map-locate-btn.visible {
  opacity: 1;
  transform: translateY(0) scale(1);
  pointer-events: auto;
}

.map-locate-btn:active {
  transform: translateY(3px) scale(1);
  box-shadow: 0 1px 0 var(--yellow-dark);
}

@media (prefers-reduced-motion: reduce) {
  .map-locate-btn {
    transition: opacity 0.2s ease;
  }
}

/* ---------- Mission stage ---------- */
/* #screen-mission/#screen-reward deliberately NOT listed here: both always
   get a real mcolor-* class set via JS (setScreenColor in engine.js,
   inline in app.js) before their content ever renders, and an ID selector
   here would out-specificity .mcolor-*'s class selector and silently win
   every time, stuck on this flat fallback instead of the real mission
   color — exactly the bug that made the header's scrim (which reads
   --top-fade, a different property, unaffected by this fight) match a
   color the visible background wasn't actually showing. .app's own
   background is the same var(--bg-purple), so there's no visible gap
   before a color class is applied. #screen-hero never gets an mcolor
   class at all, so it keeps this flat fallback for real. */
#screen-hero {
  background: var(--bg-purple);
}

.mission-stage {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  width: 100%;
  max-width: var(--appw);
  margin-left: auto;
  margin-right: auto;
  padding: calc(10px + var(--safe-top)) 16px 0;
}

/* --top-fade is declared first and the gradient is built FROM it (not the
   other way around) — one hex value per color, not two copies that could
   drift apart. .mission-top's scrim inherits --top-fade directly, so it's
   always reading the exact color the background itself uses, never a
   restated duplicate of it. */
.mcolor-ocean {
  --top-fade: #D8E9FF;
  background: linear-gradient(180deg, var(--top-fade) 0%, #EFEAFC 70%);
}

.mcolor-sunset {
  --top-fade: #FFE3C9;
  background: linear-gradient(180deg, var(--top-fade) 0%, #FFF6EC 70%);
}

.mcolor-berry {
  --top-fade: #FFDCEA;
  background: linear-gradient(180deg, var(--top-fade) 0%, #FFF3F8 70%);
}

.mcolor-galaxy {
  --top-fade: #E8DFFF;
  background: linear-gradient(180deg, var(--top-fade) 0%, #F6F2FF 70%);
}

.mcolor-forest {
  --top-fade: #D9F7E6;
  background: linear-gradient(180deg, var(--top-fade) 0%, #F1FFF9 70%);
}

/* position:sticky has to live here, not on the close button nested inside
   — a sticky element can only stay stuck within the height of its OWN
   parent, and a button sticky inside just .mission-top (42px tall) runs
   out of room to stick in almost immediately and scrolls away with it.
   .mission-top's real parent, .mission-stage, spans the whole scrollable
   round, so making the row itself sticky is what actually keeps the close
   button (and everything else in this header — dots, XP chip) reachable
   the whole way down. No background added: .mission-top was already
   transparent, and .icon-btn/.mission-xp-chip/.rdot already carry their
   own solid pills, so this still reads as a few small floating elements,
   not a sticky bar. */
.mission-top {
  position: sticky;
  top: calc(10px + var(--safe-top));
  z-index: 45;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 42px;
  margin-bottom: 12px;
  width: 100%;
}

.mission-top>button,
.mission-top>.icon-btn {
  flex-shrink: 0;
}

/* Soft scrim behind the sticky header: without it, scrolling content runs
   straight into the floating button/dots/chip instead of settling behind
   them. --top-fade (set per .mcolor-* above, inherited down through the
   DOM) is that screen's own gradient color, so this always matches
   whatever mission/lesson/creation color is active — falls back to the
   plain screens' flat bg-purple where no mcolor is set (hero, blueprint).
   Bleeds past .mission-stage's own 16px side padding and up through the
   safe-area so the fade reaches the true top/edges of the screen, not
   just the header's own box. */
.mission-top::before {
  content: '';
  position: absolute;
  top: calc(-10px - var(--safe-top));
  left: -16px;
  right: -16px;
  bottom: -16px;
  z-index: -1;
  background: linear-gradient(to bottom, var(--top-fade, var(--bg-purple)) 55%, transparent 100%);
  pointer-events: none;
}

.mission-top-mid {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  text-align: center;
  pointer-events: none;
  z-index: 1;
}

/* Title-only headers have just one side button, so flex:1 text-align:center
   centers within the leftover space, not the true header center. Bar headers
   (rdots flanked by two buttons) don't hit this, since a full-width bar has
   no "center" to be off. */
.mission-top-mid:has(.mission-top-title) {
  position: absolute;
  left: 0;
  right: 0;
}

.mission-top-mid * {
  pointer-events: auto;
}

.mission-top-title {
  font-weight: 800;
  font-size: var(--fs-lg);
  color: var(--ink);
  letter-spacing: -0.01em;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.rdots {
  display: flex;
  gap: 4px;
  width: 100%;
  height: 8px;
}

.rdot {
  flex: 1;
  height: 100%;
  border-radius: var(--r-pill);
  background: rgba(13, 6, 91, 0.15);
  transition: background 0.2s;
}

.rdot.now {
  background: var(--purple);
}

.rdot.done {
  background: var(--good);
}

.mission-xp-chip {
  background: var(--ink);
  color: var(--yellow);
  font-weight: 700;
  font-size: var(--fs-base);
  padding: 8px 12px;
  border-radius: var(--r-pill);
  min-width: 58px;
  text-align: center;
  flex-shrink: 0;
}

/* ---------- Briefing ---------- */
.briefing {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 4px 2px 14px 2px;
}

.brief-header {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 24px;
  animation: slideup 0.35s ease both;
}

.brief-tag {
  display: inline-flex;
  align-self: flex-start;
  align-items: center;
  gap: 8px;
  background: rgba(255, 255, 255, 0.7);
  border-radius: var(--r-pill);
  padding: 4px 12px 4px 6px;
  margin-bottom: 6px;
  box-shadow: none;
}

.brief-tag-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  font-size: var(--fs-base);
  flex-shrink: 0;
}

.brief-tag-icon svg {
  width: 15px;
  height: 15px;
}

.brief-tag-text {
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--purple);
}

.brief-title {
  font-size: 1.8em;
  font-weight: 800;
  color: var(--ink);
  line-height: 1.2;
  margin: 0;
  text-wrap: pretty;
  letter-spacing: -0.01em;
}

.brief-sub {
  font-size: 1em;
  font-weight: 500;
  color: var(--ink-soft);
  margin: 0;
  line-height: 1.35;
  text-wrap: pretty;
}

.brief-avatar {
  width: 84px;
  flex-shrink: 0;
}

.brief-avatar svg {
  width: 100%;
}

.brief-villain,
.brief-hero {
  display: flex;
  align-items: flex-end;
  gap: 10px;
}

.brief-villain {
  animation: slideup 0.4s 0.1s ease both;
}

.brief-hero {
  flex-direction: row;
  justify-content: flex-end;
  animation: slideup 0.4s 0.7s ease both;
}

.bubble {
  position: relative;
  padding: 24px 26px;
  border-radius: var(--r-lg);
  font-size: 1em;
  line-height: 1.45;
  font-weight: 500;
  box-shadow: var(--shadow-sm);
  animation: popin 0.35s ease both;
  text-wrap: pretty;
}

.bubble-villain {
  background: var(--ink);
  color: var(--white);
  border-bottom-left-radius: 6px;
  animation-delay: 0.25s;
}

.bubble-hero {
  background: var(--white);
  border-bottom-right-radius: 6px;
  animation-delay: 0.85s;
}

.bubble-coach {
  background: var(--white);
  border-bottom-left-radius: 6px;
  animation-delay: 0.4s;
  color: var(--ink);
}

.brief-task {
  margin-top: auto;
  display: flex;
  gap: 14px;
  align-items: center;
  border-radius: var(--r-lg);
  padding: 16px;
  background: var(--white);
  box-shadow: var(--shadow-md);
  animation: slideup 0.4s 1.6s ease both;
}

.brief-task-intro {
  margin-top: auto;
  display: flex;
  gap: 14px;
  text-wrap: pretty;
  align-items: center;
  border-radius: var(--r-lg);
  padding: 16px;
  background: var(--white);
  box-shadow: var(--shadow-md);
  animation: slideup 0.4s 1.6s ease both;
}

.briefing-actions {
  animation: slideup 0.4s 0.2s ease both;
}

.brief-task strong {
  font-size: var(--fs-base);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.brief-task p {
  font-size: var(--fs-md);
  font-weight: 500;
  margin-top: 3px;
  text-wrap: pretty;
}

.brief-task-icon {
  width: 54px;
  height: 54px;
  border-radius: var(--r-md);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Minimalist instruction label above round interactive areas */
.round-instruction-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  text-align: center;
  margin: 14px auto 14px auto;
  line-height: 1.3;
}

.brief-task-icon svg {
  width: 30px;
  height: 30px;
}

.icolor-ocean {
  background: var(--bg-blue);
  color: #2F6FD6;
}

.icolor-sunset {
  background: #FFE3C9;
  color: #E06A1B;
}

.icolor-berry {
  background: #FFDCEA;
  color: #D9427F;
}

.icolor-galaxy {
  background: #E8DFFF;
  color: #6A3FE0;
}

.icolor-forest {
  background: #D9F7E6;
  color: #2E9A62;
}

.mission-actions {
  position: sticky;
  bottom: 0;
  padding: 16px 20px calc(20px + var(--safe-bottom, 0px));
  background: var(--white, #ffffff);
  z-index: 100;
  display: flex;
  gap: 12px;
  align-items: center;
  box-shadow: 0 -10px 20px rgba(0, 0, 0, 0.05);
  border-top-left-radius: var(--r-lg, 24px);
  border-top-right-radius: var(--r-lg, 24px);
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  margin-left: -16px;
  margin-right: -16px;
}

.mission-actions .btn-big {
  flex: 1;
}

/* ---------- Round ---------- */
/* padding-bottom here (not on .mission-stage, which .round-feedback's own
   negative-margin bleed math is tuned against — see that rule's comment)
   guarantees scroll slack below the last row of round content (e.g. a long
   order tray or answer grid). Without it, content taller than one viewport
   ends exactly at the document's bottom edge with nothing left to scroll
   into, so the last row sits behind Safari's floating bottom toolbar with
   no way to reach it. */
.round-area {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding-bottom: calc(24px + var(--safe-bottom, 0px));
}

/* ---- Bella asks (Duolingo-style: mentor + speech bubble on top) ----
   She holds the question for the whole round; reactions go to the
   feedback sheet so the question never gets overwritten. */
.round-ask {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  margin-bottom: 22px;
}

.bella-coach {
  flex-shrink: 0;
  position: relative;
  width: 92px;
  height: 92px;
}

.bella-face {
  position: relative;
  width: 92px;
  height: 92px;
  border-radius: 50%;
  background: linear-gradient(to bottom, rgba(214, 220, 255, 0.5), var(--white));
  border: 3px solid var(--white);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.bella-face .bella-zoom {
  --bella-shift: -23px;
}

.ask-bubble {
  position: relative;
  flex: 1;
  background: var(--white);
  border-radius: var(--r-lg);
  border-bottom-left-radius: 6px;
  padding: 14px 18px;
  box-shadow: var(--shadow-sm);
  min-height: 72px;
  display: flex;
  align-items: center;
}

/* tail, pointing back at Bella */
.ask-bubble::before {
  content: "";
  position: absolute;
  left: -7px;
  bottom: 12px;
  width: 14px;
  height: 14px;
  background: var(--white);
  clip-path: polygon(100% 0, 100% 100%, 0 100%);
  border-bottom-left-radius: 3px;
}

.ask-text {
  width: 100%;
  font-size: 1.4em;
  line-height: 1.35;
  font-weight: 600;
  color: var(--ink);
  margin: 0;
  text-wrap: balance;
}

.ask-speak {
  position: absolute;
  right: -4px;
  bottom: -2px;
  z-index: 100;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-purple);
  color: var(--purple);
  border: 2.5px solid var(--white);
  box-shadow: 0 2px 8px rgba(13, 6, 91, 0.18);
  cursor: pointer;
  transition: transform 0.12s ease, background 0.15s ease, color 0.15s ease;
}

.ask-speak svg {
  width: 16px;
  height: 16px;
}

.ask-speak:hover {
  transform: scale(1.08);
}

.ask-speak:active {
  transform: scale(0.92);
}

.ask-speak.is-playing {
  background: var(--purple);
  color: var(--white);
}

.ask-speak.is-loading {
  opacity: 0.5;
}

.ask-speak.is-playing svg {
  animation: talkbob 0.5s ease-in-out infinite;
}

.round-body {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* ---- feedback sheet (pinned under the options) ---- */
.round-feedback {
  position: sticky;
  bottom: 0;
  margin: 16px -16px calc(-20px - var(--safe-bottom, 0px));
  padding: 28px 24px calc(28px + var(--safe-bottom, 0px));
  display: flex;
  flex-direction: column;
  gap: 24px;
  border-top-left-radius: var(--r-lg);
  border-top-right-radius: var(--r-lg);
  box-shadow: 0 -10px 24px rgba(13, 6, 91, 0.1);
  z-index: 90;
  /* .fb-good/.fb-hint paint their own tone; a toneless bar (the compose
     round's sticky "send" button, before any answer) needs a background of
     its own so it doesn't read as a transparent floating button. */
  background: var(--white);
}

.round-feedback[hidden] {
  display: none;
}

.fb-good {
  background: #E4F8ED;
}

.fb-hint {
  background: #FFF1F5;
}

.fb-head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.fb-mark {
  flex-shrink: 0;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--white);
}

.fb-mark svg {
  width: 24px;
  height: 24px;
  stroke-width: 2.5;
}

/* Botto's own confused character, embedded in the compose round's wrong-try
   drawer (see .fb-mark above for the generic version every other layout
   uses) — full size, no colored circle badge, since he already has his own
   colors and reads oddly forced into a flat icon disc. */
.fb-botto {
  flex-shrink: 0;
  width: 64px;
}

/* The compose round's send bar and its wrong-try state share this row so
   Botto sits in the exact same spot in both — only his face and what's
   next to him change. */
.fb-compose-row {
  display: flex;
  align-items: center;
  gap: 14px;
}

.fb-compose-row .btn {
  flex: 1;
}

.fb-good .fb-mark {
  background: var(--good);
}

.fb-hint .fb-mark {
  background: var(--bad);
}

.fb-copy strong {
  display: block;
  font-size: var(--fs-2xl);
  font-weight: 800;
  letter-spacing: -0.01em;
  margin-bottom: 8px;
}

.fb-good .fb-copy strong {
  color: #0C7A45;
}

.fb-hint .fb-copy strong {
  color: #B3255C;
}

.fb-copy p {
  font-size: var(--fs-xl);
  line-height: 1.55;
  color: var(--ink-soft);
  text-wrap: pretty;
}

/* Names the setup a first-timer can't see. Sits with the buttons it
   describes, not up in Bella's bubble. */
.task-hint {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 4px;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink-soft);
  text-align: center;
}

/* generic option feedback */
.opt {
  transition: transform 0.12s ease, border-color 0.12s ease, box-shadow 0.12s ease;
}

.opt:active {
  transform: scale(0.95);
}

.opt-wrong {
  animation: shake 0.45s ease;
  border-color: var(--bad) !important;
}

.opt-right {
  border-color: var(--good) !important;
  box-shadow: 0 0 0 4px rgba(20, 184, 107, 0.25), var(--shadow-sm) !important;
}

/* icon grid */
.opt-grid-icons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

/* Reads as a physical key: outline + a solid bottom edge that the
   press "squashes". These options are deliberately label-free — the
   whole skill is reading the picture — so every hint that they're
   tappable has to come from the shape. */
.opt-icon {
  background: var(--white);
  border-radius: var(--r-md);
  border: 2px solid #DCD8F0;
  aspect-ratio: 1.35;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 0 #CBC5E6;
  color: var(--ink);
}

.opt-icon:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 #CBC5E6;
}

@media (hover: hover) {
  .opt-icon:hover {
    border-color: var(--comp-purple);
    background: #FBFAFF;
  }
}

.opt-icon-art>svg {
  width: 58px;
  height: 58px;
}

/* posters / answer cards / prompt cards */
.opt-col {
  display: flex;
  flex-direction: column;
  gap: 12px;
  counter-reset: opt-count;
}

.poster {
  border-radius: var(--r-md);
  border: 3px solid rgba(13, 6, 91, 0.1);
  padding: 22px 16px;
  font-weight: 700;
  font-size: var(--fs-2xl);
  letter-spacing: 0.01em;
  box-shadow: var(--shadow-sm);
  text-align: center;
  line-height: 1.25;
}

.poster-tiny {
  font-size: var(--fs-xs);
  font-weight: 500;
}

.poster-big {
  font-size: var(--fs-3xl);
}

.answers-card {
  background: var(--white);
  border-radius: var(--r-md);
  border: 3px solid transparent;
  padding: 14px 16px;
  box-shadow: var(--shadow-sm);
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.answers-title {
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.answers-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-md);
  font-weight: 500;
}

.ans-dot {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: 700;
  font-size: var(--fs-md);
}

.opt-prompt {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--white);
  border-radius: var(--r-md);
  border: 2px solid #DCD8F0;
  box-shadow: 0 4px 0 #CBC5E6;
  color: var(--ink);
  padding: 14px 16px;
  font-size: 1em;
  line-height: 1.45;
  font-weight: 500;
  text-align: left;
  text-wrap: pretty;
  transition: transform 0.1s, box-shadow 0.1s, border-color 0.12s, background 0.12s;
  cursor: pointer;
}

.opt-prompt::before {
  counter-increment: opt-count;
  content: counter(opt-count, upper-alpha);
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: var(--surface-alt);
  border: 1.5px solid #DCD8F0;
  color: var(--purple);
  font-size: var(--fs-base);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.12s, color 0.12s, border-color 0.12s;
}

.opt-prompt:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 #CBC5E6;
}

@media (hover: hover) {
  .opt-prompt:hover {
    border-color: var(--comp-purple);
    background: #FBFAFF;
  }

  .opt-prompt:hover::before {
    background: var(--purple);
    border-color: var(--purple);
    color: var(--white);
  }
}

.opt-right.opt-prompt::before {
  background: var(--good) !important;
  border-color: var(--good) !important;
  color: var(--white) !important;
}

.opt-wrong.opt-prompt::before {
  background: var(--bad) !important;
  border-color: var(--bad) !important;
  color: var(--white) !important;
}

.opt-prompt-quote {
  color: var(--comp-purple);
  font-weight: 700;
  font-size: var(--fs-lg);
}

/* Chef Botto */
.botto-stage {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 2px;
}

.botto {
  width: 88px;
  flex-shrink: 0;
}

.botto-out {
  flex: 1;
  background: var(--white);
  border-radius: var(--r-md);
  border-bottom-left-radius: 4px;
  padding: 12px 14px;
  font-size: 1.1em;
  line-height: 1.45;
  font-weight: 500;
  box-shadow: var(--shadow-sm);
  min-height: 58px;
  display: flex;
  align-items: center;
  transition: background 0.25s;
}

.botto-out-good {
  background: var(--bg-green);
}

.botto-out-bad {
  background: var(--bg-red);
}

/* fix-it lab */
.fixit-preview {
  border-radius: var(--r-lg);
  border: 3px solid rgba(13, 6, 91, 0.1);
  min-height: 130px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-sm);
}

.fixit-btn {
  font-size: var(--fs-3xl);
  font-weight: 700;
  letter-spacing: 0.12em;
  padding: 16px 42px;
  border-radius: var(--r-md);
  border: 3px solid currentColor;
  transition: color 0.3s ease;
  pointer-events: none;
}

.fixit-hint {
  text-align: center;
  font-size: var(--fs-base);
  color: var(--ink-soft);
  font-weight: 500;
}

.swatch-row {
  display: flex;
  justify-content: center;
  gap: 16px;
}

.swatch {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  border: 4px solid var(--white);
  box-shadow: var(--shadow-md);
}

/* swipe — one short card, judge it JA/NEE, it flies off on a correct guess */
.swipe-stage {
  display: flex;
  justify-content: center;
  padding: 12px 0 4px;
}

.swipe-card {
  background: var(--white);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
  padding: 30px 24px;
  width: 100%;
  max-width: 280px;
  min-height: 140px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  text-align: center;
  transition: transform 0.35s ease, opacity 0.35s ease;
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
}

.swipe-card.grabbing {
  cursor: grabbing;
  box-shadow: var(--shadow-sm);
}

.swipe-card.fly-left {
  transform: translateX(-160%) rotate(-18deg);
  opacity: 0;
}

.swipe-card.fly-right {
  transform: translateX(160%) rotate(18deg);
  opacity: 0;
}

.swipe-card-icon {
  width: 40px;
  height: 40px;
  color: var(--purple);
}

.swipe-card-text {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
  line-height: 1.35;
}

.swipe-controls {
  display: flex;
  justify-content: center;
  gap: 16px;
  margin-top: 20px;
}

/* Same neutral look for both directions — deliberately no red/green or
   check/x. Color-coding JA green and NEE red taught kids "tap green" by
   traffic-light instinct, independent of whether JA was actually correct
   that round. Correctness is only ever revealed after the tap (shake vs.
   fly-off), same as every other layout — the buttons themselves carry no
   hint, only the option's own text does. */
.swipe-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 700;
  font-size: var(--fs-md);
  padding: 14px 22px;
  border-radius: var(--r-pill);
  background: var(--white);
  color: var(--ink);
  border: 2px solid #DCD8F0;
  box-shadow: 0 4px 0 #CBC5E6;
}

/* compare-choose — two whole reference designs, no correct/wrong; tap
   an attribute chip under either one to feed it into the live remix. */
.cc-variants {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 12px;
}

.cc-variant {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.cc-variant-label {
  align-self: flex-start;
  font-weight: 700;
  font-size: var(--fs-base);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* The artboard holds ONLY the design preview — the tappable chip legend
   lives outside it, so the bordered "this is the design" card never
   visually includes controls that aren't part of the design itself. */
.cc-artboard {
  width: 100%;
  background: var(--white);
  border-radius: var(--r-md);
  border: 2px solid #E7E2F5;
  padding: 14px;
  box-shadow: var(--shadow-sm);
  display: flex;
  justify-content: center;
}

.cc-btn-preview {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  max-width: 220px;
  padding: 13px 18px;
  border-radius: var(--r-md);
  font-weight: 700;
  font-size: var(--fs-md);
  box-shadow: var(--shadow-sm);
}

.cc-btn-icon svg {
  width: 18px;
  height: 18px;
}

/* Only the two reference designs get this — never the remix preview at
   the bottom, which is a display, not an input. The translucent patch
   is what tells a kid the icon/text are each their own tap target and
   not just decoration inside one big button. */
.cc-hot {
  border-radius: 8px;
  transition: background 0.12s ease;
}

.cc-btn-icon.cc-hot {
  background: rgba(255, 255, 255, 0.22);
  padding: 4px 6px;
}

.cc-btn-text.cc-hot {
  padding: 4px 6px;
}

@media (hover: hover) {
  .cc-hot:hover {
    background: rgba(255, 255, 255, 0.35);
  }
}

.cc-remix {
  background: var(--white);
  border-radius: var(--r-lg);
  border: 2.5px solid var(--purple);
  padding: 16px;
  box-shadow: var(--shadow-md);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

/* width:100% for the same reason .cc-remix-preview-wrap needs it below —
   .cc-remix's align-items:center shrinks a plain block child to fit its
   content otherwise, which would center this row instead of spreading
   the label and reset button to opposite edges. */
.cc-remix-head {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.cc-remix-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  font-size: var(--fs-base);
  letter-spacing: 0.03em;
  color: var(--purple-dark);
}

.cc-reset-btn {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(13, 6, 91, 0.06);
  color: var(--ink-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: opacity 0.12s ease;
}

.cc-reset-btn svg {
  width: 14px;
  height: 14px;
}

.cc-reset-btn:disabled {
  opacity: 0.35;
}

/* .cc-remix's align-items:center shrinks a plain block child to its
   content width — without this, #cc-remix-preview's button never gets
   the same full-width-to-expand-into that .cc-artboard gives the two
   reference designs, so it renders narrower even though both share the
   same max-width:220px cap. */
.cc-remix-preview-wrap {
  width: 100%;
  display: flex;
  justify-content: center;
}

.cc-remix-label svg {
  width: 15px;
  height: 15px;
}

/* Only shown before the very first pick — once anything is chosen the
   remix renders for real (picked pieces solid, untouched ones simply
   absent), so this dashed/placeholder look never mixes with real
   picked colors. */
.cc-btn-preview-empty {
  background: #F3EFFD;
  color: var(--ink-soft);
  border: 2px dashed var(--comp-purple);
  box-shadow: none;
}

/* pattern — tap-to-match pairs, no dragging */
.pattern-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
}

.pattern-card {
  display: flex;
  align-items: center;
  gap: 9px;
  background: var(--white);
  border-radius: var(--r-md);
  border: 2px solid #DCD8F0;
  box-shadow: 0 4px 0 #CBC5E6;
  padding: 12px;
  text-align: left;
}

.pattern-card-icon {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--bg-purple);
  color: var(--purple);
  display: flex;
  align-items: center;
  justify-content: center;
}

.pattern-card-icon svg {
  width: 15px;
  height: 15px;
}

.pattern-card-label {
  font-size: 1.2em;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.3;
  text-wrap: pretty;
}

/* Neutral "picked, not yet confirmed" state — purple, the app's
   generic selected-state color, never green/red (those stay reserved
   for the confirmed right/wrong result once the second card is tapped). */
.pattern-card-picked {
  border-color: var(--purple) !important;
  box-shadow: 0 0 0 3px rgba(95, 41, 255, 0.18), var(--shadow-sm) !important;
}

/* compose */
.compose-groups {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-bottom: 4px;
}

.compose-group {
  background: var(--white);
  border-radius: var(--r-md);
  padding: 12px 14px;
  box-shadow: var(--shadow-sm);
}

.compose-group-name {
  font-weight: 700;
  font-size: var(--fs-base);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 8px;
}

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  background: var(--bg-purple);
  border: 2.5px solid transparent;
  border-radius: var(--r-pill);
  padding: 9px 14px;
  font-size: 1em;
  font-weight: 500;
  transition: all 0.12s ease;
}

.chip:active {
  transform: scale(0.93);
}

.chip-on {
  background: var(--comp-yellow);
  border-color: var(--ink);
  font-weight: 700;
}

/* ---------- Phone mock ---------- */
.phone {
  background: var(--ink);
  border-radius: 34px;
  padding: 10px;
  box-shadow: var(--shadow-md);
  max-width: 340px;
  width: 100%;
  margin: 0 auto;
}

.phone-notch {
  width: 90px;
  height: 6px;
  background: rgba(255, 255, 255, 0.25);
  border-radius: var(--r-pill);
  margin: 2px auto 8px;
}

.phone-head {
  color: var(--white);
  font-weight: 700;
  font-size: var(--fs-base);
  text-align: center;
  padding-bottom: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.phone-app-emoji {
  font-size: var(--fs-md);
}

.phone-body {
  background: var(--white);
  border-radius: 24px;
  padding: 16px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-height: 300px;
}

.pp {
  border-radius: var(--r-sm);
  transition: all 0.25s ease;
  position: relative;
}

[data-part] {
  cursor: pointer;
}

.pp-title {
  font-weight: 700;
  font-size: var(--fs-lg);
  padding: 4px 2px;
}

.pp-row {
  font-size: var(--fs-base);
  color: var(--ink-soft);
  padding: 8px 10px;
  background: var(--bg-purple);
}

.pp-img {
  font-size: 44px;
  text-align: center;
  padding: 12px;
  background: var(--bg-blue);
}

.pp-banner {
  background: repeating-linear-gradient(45deg, var(--comp-yellow) 0 14px, var(--yellow) 14px 28px);
  font-weight: 700;
  text-align: center;
  padding: 14px 10px;
  font-size: var(--fs-md);
  border-radius: var(--r-sm);
}

.pp-field {
  border: 2.5px solid var(--bg-blue);
  padding: 11px 12px;
  font-size: var(--fs-base);
  color: var(--ink-soft);
  background: #FBFAFF;
}

.pp-ghost {
  color: #D9D7E8;
  font-size: var(--fs-xs);
  text-align: center;
  padding: 4px;
}

.pp-btn {
  font-weight: 700;
  font-size: var(--fs-md);
  padding: 12px 14px;
  border-radius: var(--r-sm);
  width: 100%;
}

.pp-btn-primary {
  background: var(--good);
  color: #fff;
  font-size: var(--fs-md);
  padding: 15px;
  box-shadow: 0 4px 0 var(--good-dark);
}

.pp-btn-loud {
  background: var(--comp-red);
  color: var(--ink);
  font-size: var(--fs-md);
  padding: 15px;
  box-shadow: 0 4px 0 #D96060;
}

.pp-btn-tiny {
  background: var(--bg-purple);
  color: var(--ink-soft);
  font-size: var(--fs-xs);
  padding: 6px;
  width: 55%;
  align-self: center;
}

.pp-btn-plain {
  background: var(--bg-purple);
  color: var(--ink);
}

.pp-btn-safe {
  background: var(--good);
  color: #fff;
  box-shadow: 0 4px 0 var(--good-dark);
}

.pp-btn-danger {
  background: var(--bad);
  color: #fff;
  width: 60%;
  align-self: center;
  font-size: var(--fs-sm);
  padding: 8px;
}

.pp-btn-calmed {
  background: var(--bg-purple) !important;
  color: var(--ink-soft) !important;
  font-size: var(--fs-sm) !important;
  padding: 8px !important;
  box-shadow: none !important;
}

.pp-btn-hero {
  background: var(--purple) !important;
  color: #fff !important;
  font-size: var(--fs-lg) !important;
  padding: 17px !important;
  width: 100% !important;
  box-shadow: 0 5px 0 var(--purple-dark) !important;
  animation: heropop 0.5s ease;
}

@keyframes heropop {
  0% {
    transform: scale(0.9);
  }

  55% {
    transform: scale(1.08);
  }

  100% {
    transform: scale(1);
  }
}

.pp-trapped {
  outline: 4px solid var(--bad);
  outline-offset: 2px;
}

.trap-tag {
  position: absolute;
  top: -12px;
  right: -6px;
  z-index: 3;
  background: var(--bad);
  color: #fff;
  font-size: var(--fs-xs);
  font-weight: 700;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-sm);
}

.trap-villain {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: center;
  font-size: var(--fs-base);
  font-weight: 700;
  color: var(--ink-soft);
  font-style: italic;
}

.trap-villain svg {
  width: 54px;
  height: 54px;
}

.hint-toggle {
  margin: 4px auto 0;
  font-size: var(--fs-base);
  font-weight: 700;
  color: var(--purple);
  background: var(--white);
  padding: 10px 18px;
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-sm);
  max-width: 100%;
}

.hint-open {
  color: var(--ink);
  font-weight: 500;
  font-style: italic;
}

/* ---------- Order (flow) ---------- */
.order-slots {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.order-slot {
  border: 3px dashed var(--comp-blue);
  border-radius: var(--r-md);
  min-height: 60px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  background: rgba(255, 255, 255, 0.5);
  transition: border-color 0.2s;
}

.order-flow-done .order-slot {
  border-color: var(--good);
  border-style: solid;
}

.order-slot-num {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--ink);
  color: var(--yellow);
  font-weight: 700;
  font-size: var(--fs-md);
  display: flex;
  align-items: center;
  justify-content: center;
}

.order-tray {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 12px;
  min-height: 64px;
}

.order-card {
  background: var(--white);
  border: 3px solid transparent;
  border-radius: var(--r-md);
  padding: 10px 12px;
  display: flex;
  align-items: center;
  gap: 10px;
  box-shadow: var(--shadow-sm);
  font-weight: 700;
  font-size: var(--fs-base);
  text-align: left;
  transition: transform 0.12s ease, opacity 0.15s ease, box-shadow 0.15s ease;
}

.order-card:active {
  transform: scale(0.94);
}

/* Placed into a slot — stays in its original tray spot (never reflows,
   so the remaining cards don't shift and a kid never has to re-scan for
   one that moved). Only its own content disappears; the box + shadow
   stay as a "this one's spoken for" placeholder. */
.order-card-used {
  box-shadow: none;
  border-style: dashed;
  border-color: #DCD8F0;
  background: rgba(255, 255, 255, 0.4);
  pointer-events: none;
}

.order-card-used .order-card-emoji,
.order-card-used .order-card-label {
  visibility: hidden;
}

.order-card-emoji {
  font-size: var(--fs-3xl);
}

/* The slot's own independent rendering of whichever card is placed —
   not the tray button itself, so the tray copy can stay put untouched. */
.order-slot-content {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 24px;
}

.order-slot-content:not(:empty) {
  background: var(--white);
  border: 3px solid transparent;
  border-radius: var(--r-sm);
  padding: 8px 10px;
  font-weight: 700;
  font-size: var(--fs-base);
  text-align: left;
}

/* !important: .order-slot-content:not(:empty) sets background/border too,
   and its two-part selector otherwise outranks this plain class — same
   reason .opt-right needs it elsewhere. */
.order-card-right {
  border-color: var(--good) !important;
  background: var(--bg-green) !important;
}

/* The teach card is now the .round-feedback sheet — see "feedback sheet". */

/* ---------- Reward ---------- */
/* No overflow:hidden here — that would make .reward-stage a scroll
   container of its own and break .reward-actions' position:sticky below
   (a sticky element only sticks against the nearest actual scrolling
   ancestor, which must stay the page). The burst graphic clips itself via
   .reward-burst-clip instead, so nothing needs the stage to do it. */
.reward-stage {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: var(--appw);
  margin-left: auto;
  margin-right: auto;
  /* Bottom padding is slack, not visual spacing: .reward-actions already
     pins itself to the true bottom via position:sticky, but when the
     badge/level-up rows above make this exactly (or barely under) one
     viewport tall, there's otherwise zero scroll distance available to
     correct for any mobile browser toolbar-height mismatch at first paint
     — same root cause as .round-area's own padding-bottom above. */
  padding: calc(30px + var(--safe-top)) 22px calc(20px + var(--safe-bottom, 0px));
  text-align: center;
  position: relative;
}

.reward-burst-clip {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}

.reward-burst {
  position: absolute;
  top: -140px;
  left: 50%;
  transform: translateX(-50%);
  width: 420px;
  height: 420px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(250, 255, 5, 0.5), transparent 65%);
  animation: nodepulse 2.4s infinite;
}

.reward-hero {
  width: 140px;
  position: relative;
  z-index: 2;
}

.reward-Buggie {
  display: flex;
  align-items: center;
  gap: 8px;
  z-index: 2;
  font-size: var(--fs-base);
  font-weight: 700;
  font-style: italic;
  color: var(--ink-soft);
  background: rgba(255, 255, 255, 0.75);
  border-radius: var(--r-pill);
  padding: 6px 14px 6px 6px;
  margin-top: 6px;
}

.reward-Buggie svg {
  width: 40px;
  height: 40px;
}

.reward-title {
  font-size: var(--fs-display);
  font-weight: 700;
  margin-top: 14px;
  z-index: 2;
}

.reward-stars {
  display: flex;
  gap: 10px;
  margin: 10px 0 16px;
  z-index: 2;
}

.star-big {
  font-size: 46px;
  color: #D8D4F0;
  transform: scale(0);
  animation: starpop 0.5s ease forwards;
}

.star-big svg {
  fill: currentColor;
}

.star-big.on {
  color: #FFB300;
  filter: drop-shadow(0 4px 0 rgba(13, 6, 91, 0.12));
}

@keyframes starpop {
  0% {
    transform: scale(0) rotate(-30deg);
  }

  70% {
    transform: scale(1.25) rotate(8deg);
  }

  100% {
    transform: scale(1) rotate(0);
  }
}

.reward-rows {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
  z-index: 2;
}

.reward-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: var(--white);
  border-radius: var(--r-md);
  padding: 14px 18px;
  font-size: var(--fs-md);
  font-weight: 500;
  box-shadow: var(--shadow-sm);
}

.reward-row b {
  font-size: var(--fs-lg);
}

.reward-badge {
  display: flex;
  align-items: center;
  gap: 14px;
  text-align: left;
  background: var(--ink);
  color: var(--white);
  border-radius: var(--r-lg);
  padding: 16px;
  box-shadow: var(--shadow-md);
}

.reward-badge small {
  display: block;
  letter-spacing: 0.14em;
  font-size: var(--fs-xs);
  color: var(--comp-yellow);
  font-weight: 700;
}

.reward-badge b {
  font-size: var(--fs-xl);
  display: block;
}

.reward-badge i {
  font-size: var(--fs-sm);
  font-style: normal;
  opacity: 0.8;
}

.reward-badge-icon {
  width: 58px;
  height: 58px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.reward-badge-icon svg {
  width: 30px;
  height: 30px;
}

.reward-level {
  background: var(--bg-yellow);
  color: var(--ink);
  font-weight: 700;
  font-size: var(--fs-md);
  border-radius: var(--r-md);
  padding: 14px;
  border: 2px dashed var(--yellow-dark);
  box-shadow: none;
}

/* Sticky like .mission-actions/.round-feedback, so it stays reachable when
   the badge/level-up/tip rows above push this taller than the viewport —
   margin-top:auto still pins it to the bottom on short reward screens,
   position:sticky keeps it pinned once the page actually scrolls.
   align-self:stretch (not width:100%) is what actually bleeds it to the
   stage's edges: .reward-stage centers its children (align-items:center),
   and centering an item's margin box is what the negative side margins
   below are relative to — with width:100% that box already matches the
   stage's content width, so centering a same-size box leaves it exactly
   where it started and the negative margins have nothing left to do.
   align-self:stretch instead lets those margins expand the box itself. */
.reward-actions {
  align-self: stretch;
  margin-top: auto;
  margin-left: -22px;
  margin-right: -22px;
  padding: 20px 22px calc(20px + var(--safe-bottom, 0px));
  position: sticky;
  bottom: 0;
  background: var(--white);
  box-shadow: 0 -10px 20px rgba(0, 0, 0, 0.05);
  border-top-left-radius: var(--r-lg);
  border-top-right-radius: var(--r-lg);
  z-index: 2;
}

/* Equal 50/50 split (the existing flex:1 from .btn-row .btn-big) is the
   default, same as "Stoppen" vs a short "Volgende →". white-space:nowrap
   is what lets a much longer CTA label ("Test je Prototype →") push past
   its half instead of wrapping — flex-shrink still applies, so "Stoppen"
   yields the extra room rather than the label breaking onto two lines. */
.reward-actions .btn-big {
  white-space: nowrap;
}

.reward-close {
  position: absolute;
  top: 16px;
  left: 16px;
  z-index: 10;
}

/* badge colors */
.bcolor-ocean {
  background: #2F6FD6;
  color: #fff;
}

.bcolor-sunset {
  background: #E06A1B;
  color: #fff;
}

.bcolor-berry {
  background: #D9427F;
  color: #fff;
}

.bcolor-galaxy {
  background: #6A3FE0;
  color: #fff;
}

.bcolor-forest {
  background: #2E9A62;
  color: #fff;
}

/* ---------- Pages (badges / me / summary) ---------- */
#screen-badges,
#screen-me {
  background: linear-gradient(180deg, #EFEAFC, #D6DCFF);
}

.page-wrap {
  position: relative;
  flex: 1;
  width: 100%;
  max-width: var(--appw);
  margin-left: auto;
  margin-right: auto;
  padding: calc(22px + var(--safe-top)) 22px calc(110px + var(--safe-bottom));
  display: flex;
  flex-direction: column;
}

/* ---- creator tips ----
   A tip is standalone creator content matched to the quest just finished.
   The card is enclosed and filled because it IS tappable — it opens the
   video overlay. */
.reward-tip {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: var(--white);
  border-radius: var(--r-md);
  padding: 10px 12px;
  box-shadow: var(--shadow-sm);
  transition: transform 0.12s ease;
}

.reward-tip:active {
  transform: scale(0.98);
}

.reward-tip-thumb {
  flex: 0 0 54px;
  height: 54px;
  border-radius: var(--r-sm);
  background-size: cover;
  background-position: center;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--white);
  background-color: var(--purple);
}

.reward-tip-thumb svg {
  width: 22px;
  height: 22px;
  filter: drop-shadow(0 1px 3px rgba(13, 6, 91, 0.6));
}

.reward-tip-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.reward-tip-body small {
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.06em;
  color: var(--purple);
}

.reward-tip-body b {
  font-size: var(--fs-md);
  color: var(--ink);
  line-height: 1.25;
}

.reward-tip>svg {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  color: var(--ink-soft);
}

.tip-overlay {
  z-index: 300;
}

.tip-player {
  width: min(92vw, 420px);
  background: var(--white);
  border-radius: var(--r-lg);
  overflow: hidden;
  position: relative;
}

/* openTip (app.js) plays this immediately in real fullscreen off the same
   tap that opened it — this inline sizing is only what's briefly visible
   during that hand-off, and the fallback for wherever fullscreen isn't
   available at all. Explicit aspect-ratio + object-fit:contain (every
   creator tip is a vertical short) rather than leaving the browser to
   guess a replaced element's size from a bare max-height, which is the
   ambiguous sizing that made this look letterboxed oddly before. */
.tip-player video {
  display: block;
  width: 100%;
  aspect-ratio: 9 / 16;
  max-height: 70vh;
  object-fit: contain;
  background: #000;
}

.tip-close {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 5;
  background: rgba(255, 255, 255, 0.92);
}

.tip-meta {
  padding: 14px 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.tip-meta b {
  font-size: var(--fs-lg);
}

.tip-meta span {
  font-size: var(--fs-base);
  color: var(--ink-soft);
  font-weight: 600;
}

.tip-meta p {
  margin-top: 6px;
  font-size: var(--fs-md);
  line-height: 1.45;
  color: var(--ink-soft);
}

#screen-more {
  background: linear-gradient(180deg, #EFEAFC, #D6DCFF);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* The "Meer" page — grouped lists, no separately-shadowed card styles: a
   settings page a kid actually wants to look at reads as a couple of quiet
   columns, not a stack of competing boxes. The account row is a plain row
   like every other one here (icon, text), just with a real button on the
   right instead of a chevron — logging in is an action, not a detail page
   to drill into. */
.more-list {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border-radius: var(--r-md);
  box-shadow: 0 3px 0 rgba(13, 6, 91, 0.05), 0 6px 16px rgba(13, 6, 91, 0.04);
  overflow: hidden;
}

.more-list+.more-list,
.more-list+.more-label {
  margin-top: 20px;
}

.more-label {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-soft);
  opacity: 0.75;
  margin: 0 0 8px 8px;
}

.more-row {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 16px 18px;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  text-align: left;
  border-bottom: 1px solid rgba(13, 6, 91, 0.07);
  transition: background-color 0.12s ease;
}

.more-list .more-row:last-child {
  border-bottom: none;
}

.more-row:active {
  background-color: rgba(13, 6, 91, 0.04);
}

.more-row>i:first-child,
.more-row>svg:first-child {
  width: 19px;
  height: 19px;
  color: var(--purple);
  flex-shrink: 0;
}

.more-row span {
  flex: 1;
  min-width: 0;
}

.more-row b {
  font-weight: 700;
  color: var(--ink-soft);
  font-size: var(--fs-base);
}

.more-row-btn {
  flex-shrink: 0;
  background: var(--purple);
  color: var(--white);
  font-size: var(--fs-base);
  font-weight: 700;
  padding: 8px 16px;
  border-radius: var(--r-pill);
  text-decoration: none;
  transition: transform 0.12s ease;
}

.more-row-btn:active {
  transform: scale(0.95);
}

.more-row-chevron {
  width: 17px;
  height: 17px;
  color: var(--ink-soft);
  opacity: 0.5;
  flex-shrink: 0;
}

.more-row-danger span,
.more-row-danger b {
  color: var(--bad);
}

.more-row-danger>i:first-child,
.more-row-danger>svg:first-child {
  color: var(--bad);
}

.more-footer {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  line-height: 1.5;
  padding: 0 6px;
  margin: 18px 0 0;
}

.more-footer i,
.more-footer svg {
  flex-shrink: 0;
  margin-top: 1px;
  color: var(--good);
  width: 15px;
  height: 15px;
}

/* the hero screen's own spacing */
#screen-hero .me-card {
  margin-bottom: 18px;
}

#screen-hero .btn-text {
  margin-top: 10px;
}

.page-title {
  font-size: var(--fs-3xl);
  font-weight: 700;
  text-align: center;
  margin-bottom: 18px;
}

.empty-state {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 8px;
}

.empty-art {
  width: 110px;
  opacity: 0.9;
}

.empty-state p b {
  font-size: var(--fs-xl);
}

.empty-sub {
  color: var(--ink-soft);
  font-size: var(--fs-md);
  line-height: 1.5;
  max-width: 30ch;
}

.empty-state .btn {
  margin-top: 14px;
  width: auto;
}

.badge-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.badge-tile {
  border-radius: var(--r-lg);
  padding: 18px 12px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  box-shadow: var(--shadow-sm);
}

.badge-tile b {
  font-size: var(--fs-md);
}

.badge-tile span:last-child {
  font-size: var(--fs-sm);
  opacity: 0.85;
}

.badge-tile-icon {
  font-size: var(--fs-display);
}

.badge-tile-icon svg {
  width: 40px;
  height: 40px;
}

.badge-locked {
  background: rgba(255, 255, 255, 0.6);
  color: var(--ink-soft);
}

.badge-count {
  text-align: center;
  margin-top: 16px;
  font-weight: 700;
  color: var(--ink-soft);
}

/* me card */
.me-card {
  border-radius: var(--r-lg);
  padding: 26px 20px;
  text-align: center;
  color: var(--white);
  box-shadow: var(--shadow-md);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}

.pcolor-sunset {
  background: linear-gradient(160deg, #FF9950, #FF5C7A);
}

.pcolor-berry {
  background: linear-gradient(160deg, #FF7BAC, #8C4DFF);
}

.pcolor-ocean {
  background: linear-gradient(160deg, #5FA8FF, #22D3B6);
}

.pcolor-forest {
  background: linear-gradient(160deg, #5ECC8F, #2E9A62);
}

.pcolor-galaxy {
  background: linear-gradient(160deg, #9D7BFF, #FF8FE1);
}

.me-face {
  width: 110px;
  background: rgba(255, 255, 255, 0.25);
  border-radius: 50%;
  padding: 8px;
}

.me-card h1 {
  font-size: var(--fs-3xl);
}

.me-code {
  font-size: var(--fs-sm);
  letter-spacing: 0.1em;
  font-weight: 700;
  opacity: 0.9;
}

.me-code b {
  background: rgba(13, 6, 91, 0.35);
  padding: 4px 12px;
  border-radius: var(--r-pill);
  margin-left: 4px;
}

.me-lvl {
  font-size: var(--fs-base);
  font-weight: 700;
  background: rgba(255, 255, 255, 0.2);
  padding: 6px 16px;
  border-radius: var(--r-pill);
}

.me-stats {
  display: flex;
  gap: 10px;
  margin-top: 12px;
  width: 100%;
}

.me-stats>div {
  flex: 1;
  background: rgba(255, 255, 255, 0.92);
  color: var(--ink);
  border-radius: var(--r-md);
  padding: 12px 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.me-stats b {
  font-size: var(--fs-xl);
}

.me-stats span {
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  font-weight: 700;
}

.me-note {
  font-size: var(--fs-base);
  color: var(--ink-soft);
  line-height: 1.5;
  text-align: center;
  margin: 16px 4px 18px;
}

/* ---------- Grade level section in hero drawer ---------- */
.me-grade {
  width: 100%;
  margin: 4px 0 18px;
}

.me-grade-label {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 8px;
}

.me-grade-btns {
  display: flex;
  gap: 8px;
}

.me-grade-btn {
  flex: 1;
  background: var(--surface);
  border: 2px solid transparent;
  border-radius: var(--r-md);
  padding: 10px 6px;
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  transition: border-color .15s, background .15s, transform .1s;
  line-height: 1.3;
}

.me-grade-btn:hover {
  border-color: var(--accent);
  transform: translateY(-1px);
}

.me-grade-btn.is-active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, white);
}

/* ---------- Level picker screen ---------- */
#screen-level-picker {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100dvh;
  background: linear-gradient(160deg, #1a0a4a 0%, #3a1a8a 60%, #6c2fc0 100%);
  padding: 32px 20px;
  overflow-y: auto;
}

.level-picker-wrap {
  width: 100%;
  max-width: 420px;
  display: flex;
  flex-direction: column;
  align-items: center;
}

.level-picker-title {
  font-size: var(--fs-hero);
  font-weight: 900;
  color: #fff;
  text-align: center;
  margin: 0 0 10px;
}

.level-picker-sub {
  font-size: var(--fs-md);
  color: rgba(255, 255, 255, .7);
  text-align: center;
  margin: 0 0 28px;
  line-height: 1.5;
}

.level-picker-cards {
  display: flex;
  flex-direction: column;
  gap: 14px;
  width: 100%;
}

.level-card {
  display: flex;
  align-items: center;
  gap: 16px;
  background: rgba(255, 255, 255, .08);
  border: 2px solid rgba(255, 255, 255, .15);
  border-radius: 18px;
  padding: 18px 20px;
  cursor: pointer;
  transition: border-color .15s, background .15s, transform .12s;
  text-align: left;
  color: #fff;
}

.level-card:hover {
  background: rgba(255, 255, 255, .15);
  transform: translateY(-2px);
}

.level-card.is-selected {
  border-color: #ffd700;
  background: rgba(255, 215, 0, .15);
  box-shadow: 0 0 0 4px rgba(255, 215, 0, .15);
}

.level-card-emoji {
  font-size: var(--fs-display);
  flex-shrink: 0;
}

.level-card-name {
  font-size: var(--fs-lg);
  font-weight: 800;
  display: block;
}

.level-card-sub {
  font-size: var(--fs-base);
  color: rgba(255, 255, 255, .7);
  display: block;
  margin-top: 2px;
}


/* ---------- Summary ---------- */
#screen-summary {
  background: linear-gradient(180deg, #2A16A8, #5F29FF);
}

.summary-wrap {
  flex: 1;
  width: 100%;
  max-width: var(--appw);
  margin-left: auto;
  margin-right: auto;
  padding: calc(26px + var(--safe-top)) 20px calc(26px + var(--safe-bottom));
  display: flex;
  flex-direction: column;
  gap: 16px;
  color: var(--white);
}

.summary-fireworks {
  font-size: 44px;
  text-align: center;
  animation: bob 1.6s infinite;
}

.summary-big {
  font-size: var(--fs-hero);
  text-align: center;
  font-weight: 700;
  color: var(--yellow);
  text-wrap: balance;
}

.summary-Buggie {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: center;
  font-size: var(--fs-base);
  font-weight: 500;
  font-style: italic;
  opacity: 0.95;
}

.summary-Buggie svg {
  width: 52px;
  height: 52px;
  flex-shrink: 0;
}

.summary-card {
  background: var(--white);
  color: var(--ink);
  border-radius: var(--r-lg);
  padding: 20px 18px;
  box-shadow: var(--shadow-md);
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.summary-head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.summary-face {
  width: 62px;
  flex-shrink: 0;
  background: var(--bg-purple);
  border-radius: 50%;
  padding: 4px;
}

.summary-head b {
  font-size: var(--fs-xl);
}

.summary-head small {
  color: var(--ink-soft);
  font-weight: 500;
}

.summary-line {
  font-size: var(--fs-md);
  line-height: 1.5;
  background: var(--bg-yellow);
  border-radius: var(--r-md);
  padding: 12px 14px;
}

.summary-skills {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.summary-skill {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 2px solid var(--bg-purple);
  border-radius: var(--r-md);
  padding: 10px 12px;
}

.summary-skill>span:nth-child(2) {
  flex: 1;
}

.summary-skill b {
  font-size: var(--fs-md);
}

.summary-skill small {
  color: var(--ink-soft);
  font-size: var(--fs-sm);
  line-height: 1.35;
  display: block;
  margin-top: 2px;
}

.summary-skill-icon {
  width: 42px;
  height: 42px;
  border-radius: 50%;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.summary-skill-icon svg {
  width: 22px;
  height: 22px;
}

.summary-skill-stars {
  font-size: var(--fs-sm);
  letter-spacing: 1px;
  flex-shrink: 0;
}

.summary-parent-note {
  font-size: var(--fs-base);
  line-height: 1.55;
  background: var(--bg-green);
  border-radius: var(--r-md);
  padding: 14px;
}

/* ---------- Sheets & modals ---------- */
.sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 800;
  background: rgba(13, 6, 91, 0.55);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.sheet {
  width: min(100%, var(--appw));
  background: var(--white);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding: 14px 20px calc(22px + var(--safe-bottom));
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.sheet-handle {
  width: 44px;
  height: 5px;
  border-radius: var(--r-pill);
  background: #D8D4F0;
  margin: 0 auto 4px;
}

.sheet h2 {
  font-size: var(--fs-xl);
  text-align: center;
}

.sheet-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: var(--bg-purple);
  border-radius: var(--r-md);
  padding: 15px 16px;
  font-size: var(--fs-md);
  font-weight: 500;
  color: var(--ink);
  text-decoration: none;
  border: none;
  cursor: pointer;
  box-sizing: border-box;
  width: 100%;
}

.sheet-row span {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.sheet-row b {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--purple);
  font-weight: 600;
}

.sheet-privacy {
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  line-height: 1.5;
  text-align: left;
  padding: 2px 6px;
}

/* ---------- Mission Log ---------- */
.missionlog-story {
  font-size: var(--fs-base);
  color: var(--ink-soft);
  line-height: 1.5;
}

.missionlog-progress-label {
  font-size: var(--fs-base);
  margin-bottom: 6px;
}

.missionlog-bar {
  width: 100%;
  height: 10px;
  background: rgba(13, 6, 91, 0.1);
}

.missionlog-bar .hud-xp-fill {
  background: linear-gradient(90deg, var(--good), #3FD98A);
}

.missionlog-chapters {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* These always looked enclosed, and now they act it: each one jumps to its
   chapter on the map. Same rule as everywhere else — an enclosed, filled
   shape is something you can tap. */
.missionlog-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  background: var(--bg-purple);
  border-radius: var(--r-md);
  padding: 9px 14px;
  font-size: var(--fs-base);
  font-weight: 600;
  text-align: left;
  color: var(--ink);
  transition: transform 0.12s ease, background 0.15s ease;
}

.missionlog-row:active {
  transform: scale(0.98);
}

@media (hover: hover) {
  .missionlog-row:hover {
    background: var(--comp-purple);
  }

  .missionlog-row.ml-done:hover {
    background: rgba(20, 184, 107, 0.28);
  }
}

.missionlog-row-num {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--row-color, var(--ink));
  color: var(--white);
  font-size: var(--fs-xs);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}

.missionlog-row-title {
  flex: 1;
}

.missionlog-row-icon {
  display: flex;
  color: var(--ink-soft);
}

.missionlog-row-icon svg {
  width: 16px;
  height: 16px;
}

.missionlog-row.ml-done {
  background: rgba(20, 184, 107, 0.14);
}

.missionlog-row.ml-done .missionlog-row-icon {
  color: var(--good);
}

/* The chapter you actually opened this log from — its own tint plus a ring
   in its own color, so "you are here" reads at a glance. Comes after
   .ml-done so it wins if a chapter is both current and already completed. */
.missionlog-row.ml-current {
  background: var(--row-tint, var(--bg-purple));
  box-shadow: 0 0 0 2px var(--row-color, var(--purple)) inset;
}

.missionlog-row-current {
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--row-color, var(--purple));
}

.missionlog-row.ml-locked {
  opacity: 0.55;
}

.modal {
  background: var(--white);
  border-radius: var(--r-lg);
  padding: 24px 20px;
  width: min(92%, 360px);
  margin: auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-shadow: var(--shadow-md);
}

.modal h2 {
  font-size: var(--fs-xl);
}

.modal p {
  font-size: var(--fs-md);
  color: var(--ink-soft);
  line-height: 1.5;
}

.modal-actions {
  display: flex;
  gap: 10px;
  margin-top: 8px;
}

.modal-actions .btn {
  flex: 1;
  font-size: var(--fs-md);
  padding: 13px 10px;
}

/* ---------- Paywall ---------- */
.paywall-backdrop {
  position: fixed;
  inset: 0;
  z-index: 950;
  background: rgba(13, 6, 91, 0.7);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.paywall-card {
  position: relative;
  background: var(--white);
  border-radius: var(--r-lg);
  padding: 36px 26px 28px;
  width: min(92%, 400px);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  box-shadow: var(--shadow-md);
}

.paywall-close {
  position: absolute;
  top: -14px;
  right: -14px;
  width: 38px;
  height: 38px;
  border-radius: var(--r-pill);
  background: var(--white);
  box-shadow: var(--shadow-md);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
}

.paywall-close svg {
  width: 18px;
  height: 18px;
}

.paywall-icon {
  width: 58px;
  height: 58px;
  border-radius: 50%;
  background: var(--yellow);
  box-shadow: 0 5px 0 var(--yellow-dark);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  margin-bottom: 4px;
}

.paywall-icon svg {
  width: 28px;
  height: 28px;
}

.paywall-card h2 {
  font-size: var(--fs-2xl);
  line-height: 1.25;
}

.paywall-price {
  display: inline-block;
  background: var(--bg-purple);
  color: var(--purple);
  font-size: var(--fs-xl);
  font-weight: 800;
  border-radius: var(--r-pill);
  padding: 8px 18px;
}

.paywall-card p {
  font-size: var(--fs-base);
  color: var(--ink-soft);
  line-height: 1.55;
}

.paywall-card .btn {
  width: 100%;
  margin-top: 6px;
}

.paywall-login-row {
  font-size: var(--fs-base);
  color: var(--ink-soft);
}

.paywall-login-link {
  color: var(--purple);
  font-weight: 700;
  text-decoration: underline;
  padding: 2px;
}

/* ---------- Degraded storage banner ---------- */
.degraded-banner {
  position: fixed;
  top: calc(8px + var(--safe-top));
  left: 50%;
  transform: translateX(-50%);
  width: min(94%, calc(var(--appw) - 20px));
  z-index: 950;
  background: var(--ink);
  color: var(--white);
  border: 2px solid var(--yellow);
  border-radius: var(--r-md);
  padding: 12px 14px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-base);
  line-height: 1.45;
  font-weight: 500;
  box-shadow: var(--shadow-md);
  animation: slideup 0.4s ease both;
}

.degraded-banner button {
  color: var(--yellow);
  font-size: var(--fs-md);
  font-weight: 700;
  padding: 4px 6px;
  flex-shrink: 0;
}

/* ---------- XP float ---------- */
.xp-float {
  position: fixed;
  z-index: 920;
  pointer-events: none;
  background: var(--ink);
  color: var(--yellow);
  font-weight: 700;
  font-size: var(--fs-md);
  padding: 6px 12px;
  border-radius: var(--r-pill);
  transform: translate(-50%, -50%);
  animation: xpfloat 1.25s ease-out forwards;
  box-shadow: var(--shadow-md);
}

@keyframes xpfloat {
  0% {
    opacity: 0;
    translate: 0 10px;
    scale: 0.6;
  }

  20% {
    opacity: 1;
    scale: 1.1;
  }

  100% {
    opacity: 0;
    translate: 0 -70px;
    scale: 1;
  }
}

/* ---------- Animations ---------- */
.anim-float {
  animation: bob 3s ease-in-out infinite;
}

.anim-bounce-in {
  animation: bouncein 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

@keyframes bouncein {
  0% {
    transform: scale(0.3);
    opacity: 0;
  }

  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.anim-pop {
  animation: popk 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}

@keyframes popk {
  0% {
    transform: scale(0.6);
    opacity: 0;
  }

  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.anim-popin {
  animation: popin 0.3s ease both;
}

@keyframes popin {
  0% {
    transform: translateY(6px);
    opacity: 0;
  }

  100% {
    transform: none;
    opacity: 1;
  }
}

.anim-slideup {
  animation: slideup 0.4s ease both;
}

@keyframes slideup {
  0% {
    transform: translateY(24px);
    opacity: 0;
  }

  100% {
    transform: none;
    opacity: 1;
  }
}

.anim-shake {
  animation: shake 0.45s ease;
}

@keyframes shake {

  0%,
  100% {
    transform: translateX(0);
  }

  20% {
    transform: translateX(-8px);
  }

  40% {
    transform: translateX(8px);
  }

  60% {
    transform: translateX(-5px);
  }

  80% {
    transform: translateX(5px);
  }
}

.anim-pulse {
  animation: pulsek 0.4s ease;
}

@keyframes pulsek {
  0% {
    transform: scale(1);
  }

  50% {
    transform: scale(1.18);
  }

  100% {
    transform: scale(1);
  }
}

/* ---------- Reduced motion ---------- */
@media (prefers-reduced-motion: reduce) {

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------- Wider screens ---------- */
@media (min-width: 600px) {
  :root {
    --appw: 560px;
  }

  /* opt-grid-icons stays 2x2 on desktop as requested */

  .species-grid {
    grid-template-columns: repeat(6, 1fr);
  }

  .species-card .hero-svg {
    width: 52px;
    height: 52px;
  }
}

/* Inline lucide icon sizing to match text */
.lucide {
  width: 1.2em;
  height: 1.2em;
  vertical-align: middle;
  display: inline-block;
}

/* ---------- Creation Mode (Icon Studio) ---------- */
/* Locked to the viewport (height, not min-height) and never scrolls —
   unlike quiz/blueprint screens where content length varies and page
   scroll is expected, an artboard tool should never need a scroll to
   reach its own toolbar: opening the tray shrinks the canvas instead
   (see .creation-canvas below), so every part of the tool stays
   reachable and the artboard stays "in view" no matter what's open. */
.creation-stage {
  display: flex;
  flex-direction: column;
  height: 100dvh;
  overflow: hidden;
  width: 100%;
  max-width: var(--appw);
  margin-left: auto;
  margin-right: auto;
  padding: calc(10px + var(--safe-top)) 16px 0;
  box-sizing: border-box;
}

.creation-area {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 12px;
  min-height: 0;
}

/* Instruction card: the toggle for it lives in the topbar (see
   .creation-hint-toggle, replacing where Wissen used to sit) — so
   collapsing it removes this card from the layout entirely, no leftover
   row just to hold a tap target. Opens on entry to a screen, collapses
   itself the moment a kid places their first element on it. */
.creation-prompt {
  flex-shrink: 0;
  background: var(--white);
  border-radius: var(--r-lg);
  padding: 14px 16px;
  box-shadow: var(--shadow-sm);
}

.creation-goal {
  font-weight: 700;
  font-size: var(--fs-md);
  color: var(--ink);
  line-height: 1.3;
}

.creation-hint {
  margin-top: 4px;
  font-size: var(--fs-base);
  font-weight: 500;
  color: var(--ink-soft);
  line-height: 1.35;
}

.creation-hint b {
  color: var(--purple);
  font-weight: 700;
}

.creation-hint .hint-ic {
  width: 15px;
  height: 15px;
  vertical-align: -3px;
}

/* Topbar avatar — doubles as the show/hide toggle for .creation-prompt
   above, and as Buggie's expression (studioHint's mood) at a glance. */
/* .icon-btn already centers its child and sizes any svg to 22px
   (.icon-btn svg) — .coach-avatar-mini just needs to not fight that. */
.coach-avatar-mini {
  display: flex;
}

.creation-canvas-wrap {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.4);
  border-radius: var(--r-lg);
  padding: 16px;
  box-sizing: border-box;
  margin-bottom: 16px;
  /* A 2nd finger landing here opens the full-screen preview (makerij.js) —
     without this, the browser can start its own scroll/gesture recognition
     on the 2nd touch before our capture-phase listener gets to it. */
  touch-action: none;
}

/* Sized by BOTH constraints at once (height:100% of whatever vertical
   space is left, max-width:100% of the wrap) — the browser picks
   whichever is tighter, so the 3:4 artboard always fits fully in view,
   shrinking automatically when the tray/hint above take more room and
   growing back the moment they don't, instead of ever scrolling.
   .creation-canvas-frame carries that sizing so Wissen (.creation-canvas-clear)
   can hang off its corner, outside the canvas's own overflow:hidden — the
   same "hanging badge" trick as .placed-del, just anchored to the whole
   artboard instead of one shape, so it reads as acting on the canvas
   instead of floating in the topbar with nothing near it. */
.creation-canvas-frame {
  position: relative;
  height: 100%;
  max-width: 100%;
  aspect-ratio: 3/4;
}

.creation-canvas {
  position: absolute;
  inset: 0;
  background: var(--white);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  /* Own the whole touch stream (drag/resize are custom pointer-event
     handling in makerij.js) — without this, a single-finger drag can
     start a native scroll attempt underneath our gesture handling. */
  touch-action: none;
}

.creation-canvas-clear {
  position: absolute;
  top: -12px;
  right: -12px;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--white);
  color: var(--ink-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-sm);
  border: 2px solid rgba(13, 6, 91, 0.06);
  z-index: 6;
}

.creation-canvas-clear svg {
  width: 16px;
  height: 16px;
}

.creation-canvas-clear:active {
  transform: scale(0.9);
}

.creation-canvas.is-editing {
  box-shadow: var(--shadow-md), 0 0 0 3px var(--comp-purple);
}

.creation-canvas.shake {
  animation: shake 0.4s;
}

.creation-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  color: var(--ink-soft);
  opacity: 0.55;
  pointer-events: none;
}

.creation-empty svg {
  width: 32px;
  height: 32px;
  animation: nudge 1.4s ease-in-out infinite;
}

.creation-empty span {
  font-size: var(--fs-base);
  font-weight: 700;
}

@keyframes nudge {

  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(5px);
  }
}

/* Placed shapes: independent draggable layers */
.placed {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  border-radius: 10px;
  transition: box-shadow 0.15s;
  /* width and height are now set inline by app.js */
}

.placed .creation-placed-shape {
  position: static;
  width: 100%;
  height: 100%;
  display: block;
}

.placed .creation-placed-shape * {
  vector-effect: non-scaling-stroke;
}

.placed .creation-placed-text {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 2rem;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
}

/* No z-index boost here on purpose — selecting a shape used to force it
   to z-index:10, which meant selecting anything that sat BEHIND another
   shape (e.g. a large background shape under a smaller accent) made that
   other shape vanish behind it while selected: nothing was deleted, it
   was just hidden by the now-elevated selection, but it read as data
   loss. The selection ring below is visible enough on its own without
   needing to reorder the stack. */
.placed.selected {
  box-shadow: 0 0 0 3px var(--white), 0 0 0 6px var(--purple);
  animation: pop 0.25s ease;
}

.placed-del {
  position: absolute;
  top: -14px;
  right: -14px;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--bad);
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-sm);
  border: 2.5px solid var(--white);
  z-index: 5;
}

.placed-del svg {
  width: 15px;
  height: 15px;
}

.placed-del:active {
  transform: scale(0.88);
}

.placed-resize-handle {
  position: absolute;
  bottom: -12px;
  right: -12px;
  width: 28px;
  height: 28px;
  background: var(--white);
  border: 3px solid var(--purple);
  border-radius: 50%;
  cursor: nwse-resize;
  z-index: 20;
  box-shadow: var(--shadow-sm);
  touch-action: none;
}

.creation-canvas-mini {
  width: 60px;
  height: 60px;
  background: var(--white);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-sm);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  margin-top: 6px;
}

.creation-placed-shape {
  position: absolute;
  width: 60%;
  height: 60%;
  transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.creation-placed-shape:nth-child(1) {
  width: 90%;
  height: 90%;
}

.creation-placed-shape:nth-child(2) {
  width: 70%;
  height: 70%;
}

.creation-placed-shape:nth-child(3) {
  width: 50%;
  height: 50%;
}

.creation-placed-shape:nth-child(4) {
  width: 30%;
  height: 30%;
}

.creation-placed-shape:nth-child(5) {
  width: 20%;
  height: 20%;
}

/* Bottom dock — one sticky container (not several stacked ones) holding,
   top to bottom: the expandable tray (or the selected-shape quick-actions
   bar in its place), the slim icon toolbar, and the Terug/Volgende/Klaar
   row. Closed by default, so the "always on screen" chrome stays as thin
   as possible and the artboard above gets the rest of the viewport. */
.studio-bottom-dock {
  position: sticky;
  bottom: 0;
  z-index: 100;
  margin: 0 -16px;
  background: var(--white);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: 0 -4px 12px rgba(13, 6, 91, 0.05), 0 8px 24px rgba(13, 6, 91, 0.15);
  padding: 10px 16px calc(10px + var(--safe-bottom, 0px));
}

.makerij-tray {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.22s cubic-bezier(0.4, 0, 0.2, 1);
}

.makerij-tray.open {
  max-height: 150px;
  overflow-y: auto;
  padding-bottom: 12px;
  padding-top: 6px;
}

.makerij-toolbar-row {
  display: flex;
  justify-content: center;
  gap: 6px;
}

.mk-tool-btn {
  flex: 1;
  max-width: 100px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  padding: 8px 6px;
  border-radius: var(--r-md);
  background: transparent;
  color: var(--ink-soft);
  font-weight: 700;
  font-size: var(--fs-xs);
  transition: background 0.15s, color 0.15s;
}

.mk-tool-btn svg {
  width: 20px;
  height: 20px;
}

.mk-tool-btn.active {
  background: var(--bg-purple);
  color: var(--purple);
}

/* Quick actions for the currently-selected shape — recolour or delete
   right there, replacing the tray/toolbar row while a shape is selected,
   so recolouring never needs a detour through the Kleuren tab. */
.mk-context-bar {
  display: flex;
  align-items: center;
  gap: 10px;
}

.mk-context-swatches {
  flex: 1;
  flex-wrap: nowrap;
  overflow-x: auto;
  justify-content: flex-start;
  gap: 8px;
  padding-bottom: 2px;
}

.mk-context-swatches .swatch {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
}

.mk-context-del {
  background: var(--bg-red);
  color: var(--bad);
  flex-shrink: 0;
}

.mk-context-done {
  background: var(--good);
  color: var(--white);
  flex-shrink: 0;
}

/* Full-screen read-only preview (makerij.js openFullscreenPreview) — a 2nd
   finger anywhere near the artboard pops the whole artwork in here, large,
   over a dimmed backdrop. Pinch/wheel zoom further and drag to pan once
   past 1x; a plain tap (no pan) or the X closes it. */
.mk-fullscreen-preview {
  position: fixed;
  inset: 0;
  z-index: 400;
  background: rgba(13, 6, 91, 0.92);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  box-sizing: border-box;
  touch-action: none;
  animation: mkFullscreenIn 0.18s ease;
}

@keyframes mkFullscreenIn {
  from {
    opacity: 0;
  }

  to {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .mk-fullscreen-preview {
    animation: none;
  }
}

.mk-fullscreen-canvas {
  position: relative;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  aspect-ratio: 3/4;
  background: var(--white);
  border-radius: var(--r-lg);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
  overflow: hidden;
}

.mk-fullscreen-content {
  position: absolute;
  inset: 0;
}

.mk-fullscreen-close {
  position: absolute;
  top: calc(16px + var(--safe-top, 0px));
  right: 16px;
  z-index: 2;
}

/* A real gap + hairline, not just spacing — kids have less precise
   taps than adults, and this button sits right below tools that change
   constantly (tray/context-bar), so it needs to read as a clearly
   separate zone, not "one more button in the row above." */
.makerij-actions-row {
  padding-top: 18px;
  margin-top: 6px;
  border-top: 2px solid rgba(13, 6, 91, 0.08);
}

.mk-text-row {
  display: flex;
  gap: 8px;
}

.mk-text-input {
  flex: 1;
  min-width: 0;
  border: 2px solid #DCD8F0;
  border-radius: var(--r-sm);
  padding: 10px 12px;
  font-size: var(--fs-md);
  font-family: inherit;
  color: var(--ink);
  outline: none;
  background: var(--white);
  box-sizing: border-box;
}

.mk-text-input:focus {
  border-color: var(--purple);
}

.mk-text-add {
  width: auto;
  flex-shrink: 0;
  padding: 0 18px;
}

.shape-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}

.swatch-strip {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
  padding: 8px 0;
}

.swatch {
  width: 46px;
  height: 46px;
  border-radius: 50%;
  border: 3px solid rgba(13, 6, 91, 0.12);
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.12s;
  position: relative;
}

.swatch:active {
  transform: scale(0.9);
}

.swatch svg {
  width: 20px;
  height: 20px;
  opacity: 0;
  color: var(--white);
  stroke-width: 4;
  transition: opacity 0.1s;
}

.swatch.sel {
  transform: scale(1.14);
  border-color: var(--ink);
  box-shadow: 0 0 0 4px var(--comp-yellow), var(--shadow-md);
}

.swatch.sel svg {
  opacity: 1;
}

/* Yellow / white swatches need a dark check for contrast */
.swatch[data-color="#FAFF05"] svg,
.swatch[data-color="#FFFFFF"] svg {
  color: var(--ink);
}

.btn-shape {
  width: 48px;
  height: 48px;
  border-radius: var(--r-sm);
  background: var(--bg-purple);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.1s;
}

.btn-shape:active {
  transform: scale(0.9);
}

.btn-shape svg {
  width: 28px;
  height: 28px;
}

@keyframes shake {

  0%,
  100% {
    transform: translateX(0);
  }

  20%,
  60% {
    transform: translateX(-7px);
  }

  40%,
  80% {
    transform: translateX(7px);
  }
}

.creation-celebration {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(13, 6, 91, 0.85);
  backdrop-filter: blur(8px);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 24px;
}

.creation-celeb-burst {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 300px;
  height: 300px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(250, 255, 5, 0.4), transparent 70%);
  animation: nodepulse 2s infinite;
  pointer-events: none;
}

.creation-celeb-art {
  /* .creation-canvas is position:absolute now (it needs a containing
     block it can hang its corner Wissen button off of in the studio) —
     this reused-elsewhere usage has no .creation-canvas-frame around it,
     so it needs its own containing block instead. */
  position: relative;
}

.creation-celeb-art .creation-canvas {
  position: relative;
  inset: auto;
  width: 200px;
  height: 200px;
  border-radius: 30px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
}

.creation-celeb-dialog {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  margin-top: 40px;
}

.creation-celeb-dialog .coach-avatar {
  width: 70px;
}

/* First-run coachmarks — scoped to the makerij (Makerij.showCoachmarks in
   makerij.js). A dim layer with one cut-out "spotlight" box positioned
   over a real on-screen element, plus a small callout card; always
   skippable, never blocks the tool underneath. */
.mk-coach-overlay {
  position: fixed;
  inset: 0;
  z-index: 500;
  pointer-events: none;
}

.mk-coach-spot {
  position: fixed;
  border-radius: var(--r-md);
  box-shadow: 0 0 0 2000px rgba(13, 6, 91, 0.6), 0 0 0 3px var(--yellow);
  transition: top 0.25s ease, left 0.25s ease, width 0.25s ease, height 0.25s ease;
  pointer-events: none;
}

.mk-coach-card {
  position: fixed;
  left: 16px;
  right: 16px;
  max-width: 360px;
  margin: 0 auto;
  background: var(--white);
  border-radius: var(--r-lg);
  padding: 16px 18px;
  box-shadow: var(--shadow-md);
  pointer-events: auto;
}

.mk-coach-text {
  margin: 0 0 12px;
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
}

.mk-coach-text b {
  color: var(--purple);
}

.mk-coach-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.mk-coach-actions .btn {
  padding: 10px 20px;
  width: auto;
}

.summary-creation {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px dashed rgba(13, 6, 91, 0.1);
}

.summary-creation-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.map-node-creation {
  background: var(--ink);
  color: var(--yellow);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  flex-shrink: 0;
  border-radius: 0 var(--r-md) var(--r-md) 0;
  transition: background 0.15s;
}

.map-node-blueprint {
  background: var(--purple);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  flex-shrink: 0;
  border-radius: var(--r-md) 0 0 var(--r-md);
  transition: background 0.15s;
}

.map-node-creation:active,
.map-node-blueprint:active {
  background: var(--ink-soft);
}

.map-node-creation svg,
.map-node-blueprint svg {
  width: 22px;
  height: 22px;
}

/* ---------- Chapter-intro Lesson banner (on the map) ---------- */
.chapter-lesson {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  margin-top: 14px;
  margin-bottom: 40px;
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: var(--white);
  box-shadow: var(--shadow-sm);
  border: 2.5px solid var(--chapter-accent, var(--purple));
  position: relative;
  z-index: 3;
  transition: transform 0.12s ease, box-shadow 0.15s ease;
}

.chapter-lesson:active {
  transform: scale(0.98);
}

.chapter-lesson.is-done {
  border-style: dashed;
  opacity: 0.92;
}

.chapter-lesson-ico {
  flex: 0 0 auto;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  background: var(--chapter-accent, var(--purple));
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
}

.chapter-lesson-ico svg {
  width: 22px;
  height: 22px;
}

.chapter-lesson-text {
  flex: 1;
  min-width: 0;
  color: var(--ink);
}

.chapter-lesson-text b {
  display: block;
  font-size: var(--fs-md);
  line-height: 1.15;
}

.chapter-lesson-text span {
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  font-weight: 500;
}

.chapter-lesson-cta {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--bg-purple);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
}

.chapter-lesson:not(.is-done) .chapter-lesson-cta {
  background: var(--yellow);
}

.chapter-lesson-cta svg {
  width: 17px;
  height: 17px;
}

/* ---------- Lesson screen (chapter-intro module player) ---------- */
.lesson-stage {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  width: 100%;
  max-width: var(--appw);
  margin-left: auto;
  margin-right: auto;
  padding: calc(10px + var(--safe-top)) 16px 0;
}

.lesson-progress {
  height: 8px;
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.35);
  overflow: hidden;
  margin: 8px 0 14px;
}

.lesson-progress-fill {
  height: 100%;
  background: var(--yellow);
  border-radius: var(--r-pill);
  transition: width 0.35s ease;
}

/* Big centered avatar + floating control pill (matches /games/trail) */
.lesson-avatar-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  margin: 6px 0 16px;
}

.bella-big {
  position: relative;
  width: 156px;
  height: 156px;
  border-radius: 50%;
  background: linear-gradient(to bottom, rgba(214, 220, 255, 0.5), var(--white));
  border: 4px solid var(--white);
  box-shadow: var(--shadow-md);
  overflow: hidden;
}

/* Crops the full-body rig down to a head-shot. The shift is per-size:
   it scales with the frame, so a smaller frame needs a smaller nudge. */
.bella-zoom {
  position: absolute;
  inset: 0;
  transform: scale(2) translateY(var(--bella-shift, -40px));
  transform-origin: center;
}

.bella-zoom canvas {
  width: 100%;
  height: 100%;
  display: block;
}

/* Shown only if the Rive avatar can't load — Lucide, like every other icon. */
.bella-fallback {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--purple);
}

.bella-fallback svg {
  width: 46%;
  height: 46%;
  stroke-width: 1.8;
}

.bella-face .bella-fallback svg {
  width: 52%;
  height: 52%;
}

.bella-big.speaking .bella-fallback {
  animation: talkbob 0.4s ease-in-out infinite;
}

@keyframes talkbob {

  0%,
  100% {
    transform: translateY(0) scale(1);
  }

  50% {
    transform: translateY(-3px) scale(1.06);
  }
}

.voice-pill {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 8px 5px 5px;
  background: var(--white);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-md);
}

.voice-play {
  position: relative;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.voice-play.is-disabled {
  opacity: 0.4;
  pointer-events: none;
}

.voice-ring-svg {
  position: absolute;
  inset: 0;
  width: 44px;
  height: 44px;
  transform: rotate(-90deg);
  pointer-events: none;
}

.voice-ring-bg {
  fill: none;
  stroke: rgba(13, 6, 91, 0.08);
  stroke-width: 3;
}

.voice-ring {
  fill: none;
  stroke: var(--purple);
  stroke-width: 3;
  stroke-linecap: round;
  transition: stroke-dashoffset 0.15s linear;
}

.voice-play-ico {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--purple);
  color: var(--white);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}

.voice-play-ico svg {
  width: 17px;
  height: 17px;
}

.voice-play:active .voice-play-ico {
  transform: scale(0.92);
}

.voice-sep {
  width: 1px;
  height: 18px;
  background: rgba(13, 6, 91, 0.1);
}

.voice-mini {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  color: var(--ink-soft);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* display:flex above would otherwise beat the UA's [hidden] rule */
.voice-mini[hidden],
.voice-sep[hidden] {
  display: none;
}

.voice-mini svg {
  width: 16px;
  height: 16px;
}

.voice-mini:active {
  transform: scale(0.9);
  color: var(--purple);
}

.voice-cc.on {
  background: var(--purple);
  color: var(--white);
}

/* muted: the one control that says "she is off right now" */
.voice-mute.is-off {
  background: var(--bg-red);
  color: var(--bad);
}

.spin {
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

/* Karaoke captions — one cue at a time, words light up as spoken */
.lesson-caption {
  max-width: 100%;
  margin: 0 auto 14px;
  padding: 11px 16px;
  background: var(--ink);
  border-radius: var(--r-md);
  text-align: center;
  font-weight: 700;
  font-size: var(--fs-md);
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.5);
  animation: popin 0.25s ease both;
}

.lesson-caption[hidden] {
  display: none;
}

.lesson-caption span {
  transition: color 0.1s ease;
}

.lesson-caption span.spoken {
  color: var(--white);
}

.lesson-caption span.now {
  color: var(--ink);
  background: var(--yellow);
  border-radius: 6px;
  padding: 1px 5px;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/* padding-bottom for the same reason as .round-area: without it, a long
   lesson can end exactly at the document's bottom edge with no slack to
   scroll clear of the mobile browser's floating bottom toolbar. */
.lesson-body {
  flex: 1;
  padding-bottom: calc(24px + var(--safe-bottom, 0px));
}

.lesson-loading {
  color: var(--white);
  text-align: center;
  padding: 40px 0;
  font-weight: 500;
  opacity: 0.85;
}

.lesson-card {
  background: var(--white);
  border-radius: var(--r-lg);
  padding: 20px 18px;
  box-shadow: var(--shadow-md);
}

.lesson-h {
  font-size: var(--fs-2xl);
  font-weight: 700;
  line-height: 1.2;
}

.lesson-p {
  margin-top: 8px;
  color: var(--ink-soft);
  font-weight: 500;
  line-height: 1.4;
}

/* tools/achievements/tips are now key points — see "EXPLANATION STYLE" below.
   They were tinted boxes, which read as buttons (and the yellow tip box read
   as the primary button exactly). */

/* ============================================================
   EXPLANATION STYLE — "key points"
   ------------------------------------------------------------
   The rule this encodes: in PlayQuest an enclosed, filled shape
   means "tap me" — .btn, .opt-icon, .quiz-opt and .pp-btn are
   all a filled box with a hard bottom edge. So teaching content
   must never be a filled box, or a kid reads a list of facts as
   a row of dead buttons and waits for something to happen.

   Explanation is OPEN instead: no fill, no border, no elevation.
   Points are grouped by a shared marker column and steady rhythm
   rather than by boxes — one visible set, obviously un-tappable.
   Only the marker changes with the content: a dot for plain
   points or contrast pairs, a number (plus a connecting rail)
   for ordered steps. That's the extension point — any new kind
   of grouped content gets a marker, not a new box.
   ============================================================ */
.lesson-points {
  margin: 14px 0 2px;
}

.lesson-reveal[hidden] {
  display: none;
}

/* the one line that frames the whole slide */
.lp-lead {
  border-left: 3px solid var(--comp-purple);
  padding-left: 12px;
  margin-bottom: 18px;
  font-size: var(--fs-lg);
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
  white-space: pre-line;
}

.lp-list {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.lp-item {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

/* The rail is a claim about ORDER, so only an ordered run gets one. Drawn
   per item from just under one marker to the next, which keeps it exact
   whatever height the copy runs to — a single line down the whole list
   leaves stubs above the first marker and below the last. */
.lp-list.is-ordered .lp-item:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 12px;
  top: 30px;
  bottom: -22px;
  width: 2px;
  border-radius: 1px;
  background: var(--bg-purple);
}

/* ---- markers: the one thing that varies by content type ----
   dot  = a plain point or a contrast pair
   num  = an ordered step (gets the rail)
   icon = a named thing (a tool, an achievement, a tip)
   Themes tint the marker. That's where an author's colour goes now —
   tinting a box would put the enclosure straight back. */
.lp-marker {
  --mk: var(--purple);
  /* the point's accent; themes reassign it */
  position: relative;
  flex: 0 0 26px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--mk);
}

.lp-dot::after {
  content: "";
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--mk);
}

.lp-num {
  margin-top: -1px;
  background: var(--mk);
  color: var(--white);
  /* the digit, not the disc */
  font-size: var(--fs-base);
  font-weight: 700;
}

.lp-icon {
  background: color-mix(in srgb, var(--mk) 14%, transparent);
}

.lp-icon svg {
  width: 15px;
  height: 15px;
  stroke-width: 2.4;
}

/* theme tints — authored per tool/tip, applied to the marker only */
.lp-t-purple {
  --mk: var(--purple);
}

.lp-t-blue {
  --mk: #3D5AFE;
}

.lp-t-green {
  --mk: var(--good-dark);
}

.lp-t-red {
  --mk: #D9427F;
}

.lp-t-yellow {
  --mk: #B08900;
}

.lp-t-tip {
  --mk: #B08900;
}

.lp-body {
  flex: 1;
  padding-top: 1px;
  min-width: 0;
}

.lp-title {
  font-size: var(--fs-lg);
  font-weight: 700;
  line-height: 1.3;
}

.lp-desc {
  margin-top: 2px;
  font-size: var(--fs-md);
  font-weight: 500;
  line-height: 1.45;
  color: var(--ink-soft);
  white-space: pre-line;
}

/* a point with no title carries its copy at full strength */
.lp-body>.lp-desc:first-child {
  color: var(--ink);
  margin-top: 3px;
}

/* nested detail under a point (a tool's observations) */
.lp-sub {
  margin: 7px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.lp-sub li {
  position: relative;
  padding-left: 14px;
  font-size: var(--fs-md);
  font-weight: 500;
  line-height: 1.4;
  color: var(--ink-soft);
}

.lp-sub li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 9px;
  width: 6px;
  height: 2px;
  border-radius: 1px;
  background: rgba(13, 6, 91, 0.25);
}

.lesson-done-ico {
  position: relative;
  z-index: 1;
  width: 86px;
  height: 86px;
  margin: 4px auto 6px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--purple);
}

.lesson-done-ico svg {
  width: 44px;
  height: 44px;
  stroke-width: 2.6;
}

/* Kicker introducing the achievement list below, not a heading itself —
   the big icon + .lesson-h already carry that weight. */
.lesson-done-label {
  margin-top: 14px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* ---- "the one Bella is on" (highlightAtSrtIndex) ----
   The authored design filled the live item with a solid purple block. That
   shape is this app's "tap me" (.btn, .opt-icon, .quiz-opt), so a kid would
   sit waiting for three facts to do something. Same meaning, carried the way
   the rest of the system carries it: the live item comes forward — full-ink
   text, marker up — and its neighbours step back. The marker doing the work
   also matches .rdot.now on the mission top bar, which is already how this
   game says "this is the one, now".

   showAtSrtIndex hides an item until its cue. Only ever set by the player
   once it knows it can reveal it again. */
.lp-item[hidden],
.lp-sub li[hidden] {
  display: none;
}

.lp-item,
.lp-sub li {
  transition: opacity 0.25s ease, color 0.25s ease;
}

.lp-marker {
  transition: transform 0.25s ease;
}

.lp-list.has-now .lp-item:not(.is-now),
.lp-sub.has-now li:not(.is-now) {
  opacity: 0.4;
}

.lp-item.is-now .lp-title,
.lp-item.is-now .lp-desc {
  color: var(--ink);
}

.lp-item.is-now .lp-marker {
  transform: scale(1.18);
}

.lp-sub li.is-now {
  color: var(--ink);
  font-weight: 600;
}

.lp-sub li.is-now::before {
  background: var(--purple);
  width: 10px;
}

/* a heading over a run of points (text columns) */
.lp-heading {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 12px;
}

/* Bringing it to life: stages are cumulative, so the only genuinely new
   thing on screen is the last point — animate that one alone. Animating
   them all would re-run the whole list on every cue and read as a flicker.
   The global prefers-reduced-motion rule neutralises both. */
.lesson-reveal:not([hidden]) .lp-item:last-child {
  animation: point-in 0.4s cubic-bezier(0.2, 0.8, 0.3, 1) backwards;
}

.lesson-reveal:not([hidden]) .lp-item:last-child .lp-marker {
  animation: marker-pop 0.45s cubic-bezier(0.2, 0.8, 0.3, 1) backwards;
}

@keyframes point-in {
  from {
    opacity: 0;
    transform: translateY(9px);
  }
}

@keyframes marker-pop {
  0% {
    transform: scale(0.4);
  }

  60% {
    transform: scale(1.15);
  }

  100% {
    transform: scale(1);
  }
}

/* Link tile → opens another tool (optionally a new tab).
   align-self: stretch makes tiles on the same wrapped row share the tallest
   height instead of each being its own content height. */
.lesson-tool-link {
  display: flex;
  align-items: center;
  gap: 12px;
  align-self: stretch;
  text-decoration: none;
  color: var(--ink);
  background: var(--bg-blue);
  border-radius: var(--r-md);
  padding: 12px 14px;
  border: 2.5px solid transparent;
  transition: transform 0.12s ease, border-color 0.15s ease;
}

.lesson-tool-link:active {
  transform: scale(0.98);
}

.lesson-tool-link:hover {
  border-color: var(--purple);
}

.lesson-tool-link img {
  width: 52px;
  height: 52px;
  border-radius: var(--r-sm);
  object-fit: cover;
  flex: 0 0 auto;
  box-shadow: none;
}

.tool-link-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.tool-link-title {
  font-weight: 700;
  font-size: var(--fs-md);
  line-height: 1.15;
}

.tool-link-sub {
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  font-weight: 500;
}

.tool-link-go {
  flex: 0 0 auto;
  color: var(--purple);
  display: flex;
}

.tool-link-go svg {
  width: 18px;
  height: 18px;
}

/* Downloadable file (PDF / template) */
.lesson-download {
  text-align: center;
}

.dl-preview {
  max-width: 100%;
  max-height: 300px;
  border-radius: var(--r-md);
  box-shadow: var(--shadow-sm);
  margin-bottom: 12px;
}

.dl-desc {
  margin-bottom: 12px;
  margin-top: 0;
}

.dl-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: auto;
  text-decoration: none;
}

.dl-btn svg {
  width: 18px;
  height: 18px;
}

/* Rich content: image + text columns and multi-part steps */
.lesson-cols {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 14px;
  align-items: stretch;
}

@media (min-width: 600px) {
  .lesson-cols {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
  }
}

.lesson-col {
  flex: 1;
  min-width: 0;
}

.lesson-cols.cols-img {
  justify-content: center;
}

.lesson-col-img {
  margin: 0;
  text-align: center;
}

.lesson-col-img img {
  max-width: 100%;
  max-height: 380px;
  border-radius: var(--r-md);
  object-fit: contain;
  box-shadow: var(--shadow-sm);
}

.lesson-col-img figcaption {
  font-size: var(--fs-sm);
  color: var(--ink-soft);
  margin-top: 8px;
  font-weight: 500;
}

/* text columns render as key points now (.lp-heading + .lp-list). */
.lesson-part {
  margin-top: 16px;
}

.lesson-part+.lesson-part {
  border-top: 1px dashed rgba(13, 6, 91, 0.12);
  padding-top: 14px;
}

.lesson-part-title {
  font-weight: 700;
  font-size: var(--fs-lg);
  margin-bottom: 4px;
  color: var(--ink);
}

/* tips render as key points now (amber lightbulb marker). */

/* ---------- Video content (local mp4/webm/ogg, Vimeo, YouTube, Visla) ----------
   One definition, not three — this used to be three separate, later-wins
   .lesson-video rules scattered across the file (a 16:9 relative box, a
   9:16 block box, and a flex/min-height box), each overriding a few of the
   previous one's properties and leaving the rest, so the real computed box
   was whichever Frankenstein merge of all three the cascade produced. That
   inconsistent shape is what made the play button (centered via
   position:absolute on this box) look "oddly positioned" — it was
   centering correctly on a box that wasn't the one actually visible.

   16/9 is just the default before a clip's real shape is known. --video-ar
   is set from the video's own videoWidth/videoHeight once metadata loads
   (bindLessonVideo, lesson.js) — always exact, unlike a guess. is-portrait-
   hint (from the fullHeightVideo content flag) only picks a better-looking
   *default* for the brief window before that, so a portrait clip doesn't
   flash in a landscape-shaped box first. max-height keeps a portrait clip
   from ever dominating the screen; object-fit:contain plus the black
   background turns any mismatch into ordinary letterboxing instead of a
   stretched or cropped picture. */
.lesson-video {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 70vh;
  margin: 14px auto 0;
  border-radius: var(--r-md);
  overflow: hidden;
  background: #000;
}

.lesson-video.is-portrait-hint {
  aspect-ratio: 9 / 16;
}

.lesson-video.has-real-ar {
  aspect-ratio: var(--video-ar);
}

.lesson-video iframe,
.lesson-video video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: contain;
  background: #000;
}

/* Full-cover, not just a small centered circle — the whole frame is the
   tap target (easier to hit than a hand-rolled play icon ever was), and
   the gradient scrim is what makes a plain white button read clearly
   against any poster frame, light or dark. Hides once, on first play, and
   stays hidden — see bindLessonVideo's comment in lesson.js for why this
   is no longer wired to every play/pause/ended event. */
.video-play-overlay {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: linear-gradient(180deg, rgba(13, 6, 91, 0.1), rgba(13, 6, 91, 0.4));
  cursor: pointer;
  transition: opacity 0.2s ease;
}

.video-play-overlay-icon {
  width: 68px;
  height: 68px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--white);
  box-shadow: var(--shadow-md);
  transition: transform 0.15s ease;
}

.video-play-overlay:active .video-play-overlay-icon {
  transform: scale(0.92);
}

.video-play-overlay-icon svg {
  width: 30px;
  height: 30px;
  color: var(--ink);
  margin-left: 3px;
  /* optical centering for a play triangle */
  fill: var(--ink);
}

.lesson-video.is-playing .video-play-overlay {
  opacity: 0;
  pointer-events: none;
}

/* Lesson stops on the map path */
.map-node.is-lesson.node-open,
.map-node.is-lesson.node-done {
  background: var(--ink);
}

.map-node.is-lesson .map-node-icon {
  color: var(--yellow);
}

/* Tip stops: same lesson node underneath, distinct color/icon on the map.
   Reuses #B08900, the same amber already used for tip callouts in
   Lessons (.lp-t-tip's --mk, line 5519). */
.map-node.is-tip.node-open,
.map-node.is-tip.node-done {
  background: #4318CC;
}

.map-node.is-tip .map-node-icon {
  color: var(--white);
}

.quiz-q {
  font-size: var(--fs-lg);
  font-weight: 700;
  margin: 14px 0;
  line-height: 1.25;
}

.quiz-opt {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: var(--white);
  border: 2px solid #DCD8F0;
  border-radius: var(--r-md);
  padding: 13px 14px;
  margin-bottom: 14px;
  font-weight: 500;
  transition: all 0.12s ease;
  box-shadow: 0 4px 0 #CBC5E6;
  color: var(--ink);
}

.quiz-opt .qo-ico {
  flex: 0 0 auto;
  display: flex;
  color: var(--purple);
}

.quiz-opt .qo-ico svg {
  width: 20px;
  height: 20px;
}

.quiz-opt:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 #CBC5E6;
}

.quiz-opt.correct {
  background: var(--comp-green);
  border-color: var(--good);
  box-shadow: 0 4px 0 var(--good-dark);
}

.quiz-opt.correct:active {
  box-shadow: 0 1px 0 var(--good-dark);
}

.quiz-opt.wrong {
  background: var(--bg-red);
  border-color: var(--bad);
  box-shadow: 0 4px 0 #D96060;
}

.quiz-opt.wrong:active {
  box-shadow: 0 1px 0 #D96060;
}

/* ---- lesson footer ----
   Quiz feedback used to appear inline under the options, in its own colours,
   while a mission put it in a sheet on the bottom bar. Same moment, two
   different answers. Now the lesson reuses the mission's .round-feedback
   sheet, and it shares one sticky unit with the Terug/Verder bar so the
   result lands attached to the buttons rather than floating mid-card. */
.lesson-foot {
  position: sticky;
  bottom: 0;
  z-index: 100;
}

.lesson-foot .round-feedback {
  position: static;
  margin: 0 -16px;
  padding: 14px 20px 12px;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: 0 -10px 24px rgba(13, 6, 91, 0.10);
}

.lesson-foot .mission-actions {
  position: static;
}

/* the sheet already separates the footer from the content behind it */
.lesson-foot:has(.round-feedback:not([hidden])) .mission-actions {
  box-shadow: none;
}

/* Quiz feedback is the sheet's only copy (no heading above it), so it reads
   at full strength rather than as a sub-line under a title. */
.fb-lead {
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
}

.lesson-done {
  text-align: center;
  position: relative;
  overflow: hidden;
}

/* The icon/heading/kicker above read fine centered, but a list of
   achievements reads as a list — .lp-item is already a left-flowing
   icon+text row everywhere else it's used, so undo the inherited center
   alignment just for this section instead of fighting it item by item. */
.lesson-done .lesson-points {
  text-align: left;
}

.lesson-done-burst {
  position: absolute;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  width: 220px;
  height: 220px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(250, 255, 5, 0.35), transparent 70%);
  animation: nodepulse 2s infinite;
  pointer-events: none;
}

/* ---------- Chapter intro ("trailer") ----------
   Full-screen hype-up for one chapter — tapped from the map's banner/
   subtitle chip, or shown automatically right after finishing the chapter
   before it (see returnToMapOrChapterIntro in app.js). Deliberately built
   like a small movie-trailer beat (mascot face-off, one-line taunt/comeback,
   a peek at what's inside) rather than a form: this is the screen doing
   marketing for the chapter, not just routing to it. --ci-dark/--ci-main
   come from the chapter's own PALETTES color, set inline per screen. */
/* No overflow:hidden here — same reason as .reward-stage: it would make this
   the scrolling ancestor and break .chintro-actions' position:sticky below. */
/* The gradient lives on the screen element (full viewport width, never
   capped) so it bleeds edge to edge; .chintro-stage below is just the
   centered, width-capped content column sitting on top of it — same
   split as .app/.page-wrap elsewhere. */
#screen-chapter-intro {
  background: linear-gradient(180deg, var(--ci-dark) 0%, var(--ci-main) 60%, var(--ci-dark) 100%);
}

.chintro-stage {
  position: relative;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  width: 100%;
  max-width: var(--appw);
  margin-left: auto;
  margin-right: auto;
  padding: calc(16px + var(--safe-top)) 22px 0;
  color: var(--white);
}

/* A big pale numeral behind the header, echoing .chapter-banner-ghost on the
   map — ties this screen back to the banner it was opened from, and gives
   the upper half presence instead of leaving it looking sparse. */
.chintro-ghost-num {
  position: absolute;
  top: calc(30px + var(--safe-top));
  left: 50%;
  transform: translateX(-50%);
  font-size: 220px;
  font-weight: 800;
  line-height: 1;
  color: var(--white);
  opacity: 0.08;
  pointer-events: none;
  user-select: none;
  z-index: 0;
}

/* Close button + status pill live in normal flow now, not absolutely
   positioned off env(safe-area-inset-top) directly — same pattern as
   .mission-top elsewhere in this app. iOS Safari has been seen reporting a
   stale (too-small) safe-area-inset-top on the very first screen shown in a
   session, before anything has forced it to settle (see the map's own fight
   with this class of bug, commits 4318655/a91e1cb/3b0b6e2) — an absolutely
   positioned element computed off that value can end up rendered too high,
   under the notch. In flow, this bar just gets pushed down by whatever
   .chintro-stage's own padding-top resolves to, in lockstep with everything
   else on the screen, so it can't end up wrong on its own. */
.chintro-topbar {
  position: relative;
  z-index: 5;
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.chintro-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: rgba(255, 255, 255, 0.16);
  border: 1.5px solid rgba(255, 255, 255, 0.32);
  color: var(--white);
  font-size: var(--fs-sm);
  font-weight: 700;
  padding: 7px 12px;
  border-radius: var(--r-pill);
}

.chintro-status-pill svg {
  width: 13px;
  height: 13px;
}

.chintro-status-done {
  background: var(--good);
  border-color: transparent;
  color: var(--white);
}

.chintro-scene {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 320px;
  height: 190px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.chintro-burst {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 320px;
  height: 320px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 255, 255, 0.35), transparent 65%);
  animation: nodepulse 2.4s infinite;
}

/* Entrance (bouncein, transform: scale+opacity) + a "talking" nod timed to
   its speech bubble popping in (standalone scale:) + a settled idle bob
   (standalone translate:) once the exchange is done — three different CSS
   properties so they layer instead of one animation clobbering another.
   Defined as one rule (not the shared .anim-bounce-in utility class) so the
   `animation` shorthand isn't split across two competing selectors. */
.chintro-hero {
  position: relative;
  z-index: 2;
  width: 156px;
  animation:
    bouncein 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) both,
    chintro-hero-talk 0.5s ease 1.15s both,
    chintro-hero-idle 2.8s ease-in-out 1.75s infinite;
}

.chintro-hero svg {
  width: 100%;
  display: block;
  filter: drop-shadow(0 10px 16px rgba(13, 6, 91, 0.25));
}

@keyframes chintro-hero-talk {

  0%,
  100% {
    scale: 1;
  }

  35% {
    scale: 1.1;
  }

  65% {
    scale: 0.96;
  }
}

@keyframes chintro-hero-idle {

  0%,
  100% {
    translate: 0 0;
  }

  50% {
    translate: 0 -8px;
  }
}

/* Same layering trick, mirrored: entrance (popk, transform) + a shake timed
   to Buggie's own bubble (standalone rotate:) + an idle shifty sway (also
   rotate:, but never active at the same time as the shake, so no conflict).
   The static horizontal flip moves to the standalone scale: property (was
   transform: scaleX(-1)) so it can't get clobbered by popk's transform. */
.chintro-villain {
  position: relative;
  z-index: 3;
  width: 92px;
  margin-left: -24px;
  scale: -1 1;
  animation:
    popk 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) 0.2s both,
    chintro-villain-talk 0.7s ease 0.6s both,
    chintro-villain-idle 3.2s ease-in-out 1.75s infinite;
}

.chintro-villain svg {
  width: 100%;
  display: block;
  filter: drop-shadow(0 8px 12px rgba(13, 6, 91, 0.25));
}

@keyframes chintro-villain-talk {

  0%,
  100% {
    rotate: 0deg;
  }

  20% {
    rotate: -9deg;
  }

  40% {
    rotate: 7deg;
  }

  60% {
    rotate: -5deg;
  }

  80% {
    rotate: 3deg;
  }
}

@keyframes chintro-villain-idle {

  0%,
  100% {
    rotate: -4deg;
  }

  50% {
    rotate: 4deg;
  }
}

/* Buggie's eyes (see the .buggie-eyes <g> in data.js's BuggieSVG) blink on
   their own loop — scoped here only, so every other place Buggie's face
   renders across the app stays exactly as it was. */
.chintro-villain .buggie-eyes {
  transform-box: fill-box;
  transform-origin: center;
  animation: chintro-blink 4.2s ease-in-out 2.4s infinite;
}

@keyframes chintro-blink {

  0%,
  92%,
  100% {
    scale: 1 1;
  }

  96% {
    scale: 1 0.12;
  }
}

.chintro-captions {
  position: relative;
  z-index: 1;
  margin-top: -6px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 100%;
  max-width: 300px;
}

/* Real chat bubbles, not italic caption lines — .bubble-villain/.bubble-hero
   (mission briefings) already established "dark bubble = villain, white
   bubble = hero"; reused here so a returning player reads it instantly. A
   small tail on each one, pointing back up at the character it belongs to
   (Buggie's on the right, the hero's on the left, matching the scene above),
   is what makes these read as speech instead of captions underneath a
   photo — full side-by-side "bubble next to character" comic panels don't
   fit here without shrinking the big dual-character hero shot, which is the
   screen's best screenshot moment. Buggie's line lands first, the hero's a
   beat later (see animation-delay below), each popping in right as its
   character does its talk animation. */
.chintro-bubble {
  position: relative;
  max-width: 84%;
  padding: 10px 14px;
  border-radius: var(--r-md);
  font-size: var(--fs-base);
  line-height: 1.42;
  font-weight: 500;
  text-align: left;
  text-wrap: pretty;
  box-shadow: var(--shadow-md);
  animation: popin 0.35s ease both;
}

.chintro-bubble::after {
  content: '';
  position: absolute;
  top: -7px;
  width: 0;
  height: 0;
  border-left: 7px solid transparent;
  border-right: 7px solid transparent;
}

.chintro-bubble b {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  margin-bottom: 3px;
  opacity: 0.75;
}

.chintro-bubble-villain {
  align-self: flex-end;
  background: rgba(13, 6, 91, 0.85);
  color: var(--white);
  animation-delay: 0.6s;
}

.chintro-bubble-villain::after {
  right: 22px;
  border-bottom: 8px solid rgba(13, 6, 91, 0.85);
}

.chintro-bubble-villain b {
  color: var(--yellow);
  opacity: 0.95;
}

.chintro-bubble-hero {
  align-self: flex-start;
  background: var(--white);
  color: var(--ink);
  animation-delay: 1.15s;
}

.chintro-bubble-hero::after {
  left: 22px;
  border-bottom: 8px solid var(--white);
}

.chintro-bubble-hero b {
  color: var(--purple);
}

/* Leads the screen — chapter number, title and tagline answer "what is
   this" before the mascots get their moment below. A player (or a
   screenshot) glancing at just the top of this screen should already know
   exactly which chapter this is. */
.chintro-header {
  position: relative;
  z-index: 1;
  margin-top: 18px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 100%;
}

.chintro-kicker {
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  opacity: 0.8;
}

.chintro-title {
  font-size: var(--fs-hero);
  font-weight: 800;
  line-height: 1.12;
  text-wrap: balance;
}

.chintro-tagline {
  font-size: var(--fs-md);
  line-height: 1.5;
  opacity: 0.92;
  max-width: 320px;
  text-wrap: pretty;
}

.chintro-preview {
  width: 100%;
  margin-top: 34px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.chintro-preview-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.65;
}

/* Wraps and centers rather than scrolling sideways — a chapter's whole
   curriculum should read at a glance (this screen doubles as a shareable
   summary), not hide behind a swipe with a cut-off chip as the only hint
   there's more. */
.chintro-preview-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  width: 100%;
  justify-content: center;
  padding: 2px 4px;
}

.chintro-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(255, 255, 255, 0.14);
  border: 1.5px solid rgba(255, 255, 255, 0.24);
  border-radius: var(--r-pill);
  padding: 8px 14px 8px 10px;
  font-size: var(--fs-sm);
  font-weight: 600;
  white-space: nowrap;
}

.chintro-chip svg {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

.chintro-chip-more {
  opacity: 0.7;
  padding: 8px 12px;
}

/* Same sticky-bottom-drawer treatment as .reward-actions/.mission-actions —
   pinned, edge-to-edge, safe-area aware — so the CTA sits in the same place
   and behaves the same way a player already knows from the rest of the app,
   on every device regardless of how tall its content ends up. align-self:
   stretch (not width:100%) is what bleeds it to the stage's edges: see
   .reward-actions' own note above for why. */
.chintro-actions {
  align-self: stretch;
  margin-top: auto;
  margin-left: -22px;
  margin-right: -22px;
  padding: 18px 22px calc(18px + var(--safe-bottom, 0px));
  position: sticky;
  bottom: 0;
  background: var(--ci-dark);
  box-shadow: 0 -10px 20px rgba(0, 0, 0, 0.14);
  border-top-left-radius: var(--r-lg);
  border-top-right-radius: var(--r-lg);
  z-index: 4;
}