  :root {
    --ink: #2b1140;
    --ink-soft: #5b3a7a;
    --accent: #ff6bb5;
    --accent-2: #ffd166;
    --good: #6ed398;
    --bad: #ff7b7b;
    --gold: #ffd166;
    --paper: #fff8ec;
    --locked: #c8bcd4;
    /* Game-area width and horizontal offset (Phase 2, v2).
       Adaptive stage sizing — fills viewport on phone, caps at a
       ~5:4 aspect ratio on wider screens so the playable area
       expands on desktop/tablet without becoming letterbox-tall on
       ultrawide monitors. The painted parallax layers already
       repeat-x and the battlers are positioned with %-based left,
       so widening the stage naturally spreads units out across more
       landscape — same sprites, more breathing room. */
    /* 100svh (small-viewport height) so the stage sizes to the space
       iOS Safari ACTUALLY shows under its toolbars; plain-vh fallback
       first for old browsers. */
    --gw: min(100vw, calc(100vh * 1.25));
    --gw: min(100vw, calc(100svh * 1.25));
    --gx: max(0px, (100vw - var(--gw)) / 2);

    /* ───────── Fluid type & spacing scale (Phase 1, v2 redesign) ─────────
       Every UI font/padding/radius pulls from these tokens so the layout
       scales smoothly from 320px phones to 4K monitors without per-screen
       hacks. Each token is clamp(min, vw-based preferred, max).
       Migration buckets:
         11-14 → --fs-xs   28-36 → --fs-lg    96-170 → --fs-hero
         15-19 → --fs-sm   38-50 → --fs-xl
         20-26 → --fs-md   52-72 → --fs-2xl                                */
    --fs-xs:   clamp(11px, 1.6vw + 0.4rem, 14px);
    --fs-sm:   clamp(13px, 2.0vw + 0.4rem, 18px);
    --fs-md:   clamp(15px, 2.4vw + 0.5rem, 22px);
    --fs-lg:   clamp(18px, 3.2vw + 0.5rem, 30px);
    --fs-xl:   clamp(22px, 4.0vw + 0.6rem, 40px);
    --fs-2xl:  clamp(26px, 5.2vw + 0.6rem, 52px);
    --fs-hero: clamp(32px, 7vw + 0.8rem, 72px);
    --sp-1: clamp(2px, 0.4vw, 4px);
    --sp-2: clamp(4px, 0.8vw, 8px);
    --sp-3: clamp(6px, 1.2vw, 12px);
    --sp-4: clamp(10px, 1.8vw, 18px);
    --sp-6: clamp(14px, 2.6vw, 28px);
    --radius-card: clamp(10px, 1.6vw, 20px);
  }
  * { box-sizing: border-box; }
  html, body {
    margin: 0; padding: 0; height: 100dvh; overflow: hidden;
    font-family: "Comic Sans MS", "Chalkboard SE", "Baloo", system-ui, sans-serif;
    color: var(--ink);
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
    background: linear-gradient(180deg, #cfeefe 0%, #d4e6ff 100%);
  }

  /* ---------- TOP BAR ---------- */
  #topbar {
    position: fixed; top: 0; left: var(--gx); right: var(--gx); height: 44px; z-index: 30;
    display: flex; align-items: center; justify-content: space-between;
    padding: 4px 8px;
    background: rgba(255,255,255,0.6);
    backdrop-filter: blur(4px);
    border-bottom: 2px solid var(--accent);
  }
  #topbar h1 {
    margin: 0; font-size: var(--fs-xl); color: var(--accent);
    text-shadow: 0 2px 0 #fff, 0 4px 4px rgba(0,0,0,0.1);
    letter-spacing: 1px;
  }
  .top-btns { display: flex; gap: var(--sp-2); }
  .top-btns a, .top-btns button {
    border: 2px solid var(--accent);
    background: #fff; color: var(--ink);
    border-radius: var(--radius-card); padding: var(--sp-1) var(--sp-3);
    font-family: inherit; font-size: var(--fs-lg); font-weight: 700;
    cursor: pointer; text-decoration: none;
  }
  .top-btns button.muted { opacity: 0.55; }

  /* ---------- WALLET ---------- */
  #wallet {
    position: fixed; top: 50px; left: calc(var(--gx) + 8px); z-index: 30;
    display: flex; gap: var(--sp-2); flex-wrap: wrap;
  }
  .pill {
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 999px;
    padding: var(--sp-1) var(--sp-3);
    font-weight: 700; font-size: var(--fs-lg);
    box-shadow: 0 2px 0 rgba(0,0,0,0.15);
  }
  .pill.coins { background: #fff4cc; }
  .pill.gems  { background: #e1d6ff; }

  /* ---------- STAGE ---------- */
  #stage {
    position: fixed; top: 44px; left: var(--gx); right: var(--gx); bottom: 0;
    display: flex; flex-direction: column;
  }

  /* Expedition zone — 50% by default; auto-shifts to 70% during active
     battles via body.battle-active so the action breathes. Smooth flex
     transition keeps the change non-jarring. */
  #expedition {
    position: relative; flex: 1 1 50%; min-height: 0;
    overflow: hidden;
    border-bottom: 4px solid #b88be0;
    /* Idle backdrop is just a soft gradient — the multi-layer painted
       parallax (layer-sky / layer-mountains / etc.) takes over once
       an expedition starts. */
    background: linear-gradient(180deg, #cfeefe 0%, #d8f0ff 60%, #cce6c8 100%);
    transition: flex-basis 0.6s ease, flex-grow 0.6s ease;
  }
  body.battle-active #expedition {
    flex: 1 1 70%;
  }
  #expedition .placard {
    position: absolute; left: 50%; top: 40%; transform: translate(-50%, -50%);
    background: rgba(255,255,255,0.92);
    border: 3px dashed var(--accent);
    border-radius: 18px;
    padding: 14px 18px;
    text-align: center;
    max-width: 80%;
    box-shadow: 0 6px 0 rgba(0,0,0,0.08);
    z-index: 8;
  }
  #expedition .placard h2 {
    margin: 0 0 6px 0; font-size: var(--fs-xl); color: var(--accent);
  }
  #expedition .placard p {
    margin: 0 0 8px 0; font-size: var(--fs-lg); color: var(--ink-soft);
  }
  .send-btn {
    background: linear-gradient(180deg, #ffd166, #ff9b3d);
    border: 2px solid var(--ink);
    border-radius: 999px;
    padding: var(--sp-2) var(--sp-4);
    font-family: inherit; font-weight: 900; font-size: var(--fs-xl);
    cursor: pointer; color: var(--ink);
    box-shadow: 0 4px 0 rgba(0,0,0,0.18);
  }
  .send-btn:hover, .send-btn:active { transform: translateY(-2px); }

  #expedition .cloud {
    position: absolute; font-size: var(--fs-2xl); line-height: 1;
    pointer-events: none; opacity: 0.7;
    animation: cloud-drift 60s linear infinite;
    z-index: 5;        /* Phase 7 — clouds drift ABOVE sun/moon (z 1), mountains (z 2), mid-hills (z 3), grass (z 4) */
  }
  #expedition .cloud.c1 { top: 12%; left: -10%; animation-delay: 0s; }
  #expedition .cloud.c2 { top: 28%; left: -10%; animation-delay: -22s; font-size: var(--fs-2xl); }
  #expedition .cloud.c3 { top: 8%;  left: -10%; animation-delay: -45s; font-size: var(--fs-xl); }
  @keyframes cloud-drift { from { transform: translateX(0); } to { transform: translateX(120vw); } }

  /* Battle units in expedition zone — classic JRPG diagonal column.
     Sprites are 20% of zone height by default, then scaled per-slot
     via the inline --depth-scale variable (1.0 front rank, ~0.70 rear).
     transform-origin: bottom centre keeps feet planted on the ground
     when the depth scale shrinks the back ranks. */
  .battler {
    position: absolute;
    height: 20%;
    width: auto;
    image-rendering: pixelated;
    pointer-events: none;
    z-index: 5;
    filter: drop-shadow(0 4px 3px rgba(0,0,0,0.3));
    transform-origin: 50% 100%;
    transform: scale(var(--depth-scale, 1));
    transition: left 0.5s ease-out, top 0.6s ease-out, bottom 0.5s ease-out, transform 0.4s ease-out;
  }
  .battler img {
    height: 100%; width: auto; display: block;
  }
  /* Allies march left→right along the trail. Walking = gentle bob. */
  .battler.ally.walking img {
    animation: walk-in-place 0.55s infinite ease-in-out;
  }
  @keyframes walk-in-place {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-4px); }
  }
  /* Enemies march in from the RIGHT side (FF-style line on the right) */
  .battler.enemy {
    transition: left 4s linear, top 0.5s ease-out, transform 0.6s ease-out, opacity 0.6s ease-out;
  }
  .battler.enemy.idle img,
  .battler.enemy.idle .enemy-emoji {
    animation: enemy-hover 1.4s infinite ease-in-out;
  }
  /* Emoji-only enemies (no painted sprite) — same vertical footprint.
     Font-size now 60px (was 76px) so the glyph fits inside the
     smaller 20 % battler box on shorter screens. */
  .battler.enemy .enemy-emoji {
    height: 100%; width: 100%;
    display: flex; align-items: center; justify-content: center;
    font-size: var(--fs-2xl);
    line-height: 1;
    user-select: none;
    filter: drop-shadow(0 4px 4px rgba(0,0,0,0.35));
  }
  @keyframes enemy-hover {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-3px); }
  }
  /* Approach phase: enemies start far (small + off-screen-right) and walk in */
  .battler.enemy.far {
    transform: scale(0.5);
    opacity: 0.85;
  }
  .battler.enemy.engaged {
    transform: scale(1);
    opacity: 1;
  }
  /* Attacker steps TOWARD opponent. Small horizontal nudge only — no
     extra scale boost (the previous 1.10 scale at transform-origin
     bottom made characters appear to "jump up" because they grew
     toward the head). Keeping the depth scale baseline makes the
     lunge a clean side-step. */
  /* Attack lunge — 3× further than before per the kid (was 12 px,
     now 36 px) so the unit visibly marches toward the middle of the
     battlescape before its strike. */
  .battler.ally.attacking  { transform: translateX( 36px) scale(var(--depth-scale, 1)); }
  .battler.enemy.attacking { transform: translateX(-36px) scale(var(--depth-scale, 1)); }

  /* Most hero sprites face RIGHT naturally — no flip needed since
     heroes on the LEFT face the enemy line on the right. A handful
     were drawn facing LEFT (foal / moonhorse / salamander / seahorse
     / unicorn), so those get a `.face-flip` class on the img to
     mirror them. The walk-in-place keyframes have a flipped variant
     so the bob doesn't un-mirror them. */
  .battler.ally img.face-flip { transform: scaleX(-1); }
  .battler.ally.walking img.face-flip {
    animation: ally-walk-flip 0.55s infinite ease-in-out;
  }
  @keyframes ally-walk-flip {
    0%, 100% { transform: scaleX(-1) translateY(0); }
    50%      { transform: scaleX(-1) translateY(-4px); }
  }
  .battler.hurt img { animation: hurt-shake 0.35s; }
  @keyframes hurt-shake {
    0%,100% { transform: translateX(0); filter: brightness(1); }
    25% { transform: translateX(-4px); filter: brightness(1.7) sepia(1) hue-rotate(-30deg); }
    75% { transform: translateX(4px); filter: brightness(1.7) sepia(1) hue-rotate(-30deg); }
  }
  .battler.dying img { animation: die-fade 0.6s forwards; }
  @keyframes die-fade {
    from { opacity: 1; transform: scale(1); }
    to   { opacity: 0; transform: scale(0.5) rotate(180deg); }
  }
  .hp-bar {
    position: absolute; top: -14px; left: 0; right: 0;
    height: 9px;
    background: rgba(0,0,0,0.55);
    border: 1.5px solid #fff;
    border-radius: 5px; overflow: hidden;
  }
  .hp-bar > i {
    display: block; height: 100%;
    background: linear-gradient(90deg, var(--good), #c5f0a4);
    transition: width 0.25s ease-out;
  }
  .hp-bar.low > i { background: linear-gradient(90deg, var(--bad), #ffaaaa); }
  .hp-text {
    position: absolute; top: -16px; left: 0; right: 0;
    height: 11px; line-height: 11px;
    text-align: center;
    font-size: var(--fs-md); font-weight: 800;
    color: #fff;
    text-shadow: 0 1px 1px rgba(0,0,0,0.9), 0 0 2px rgba(0,0,0,0.9);
    pointer-events: none;
    letter-spacing: 0.02em;
  }
  /* ATB gauge — DISABLED in the FF6 redesign per user request.
     The ATB system still drives turns internally, just no visible bar. */
  .atb-bar { display: none; }

  /* Status icons row above the HP bar — small badges showing poison,
     regen, defend, slow, taunt, etc. */
  .status-icons {
    position: absolute;
    top: -28px; left: 0; right: 0;
    display: flex; gap: 2px;
    justify-content: center;
    pointer-events: none;
    z-index: 6;
  }
  .status-icons .si {
    background: rgba(255,255,255,0.95);
    border: 1.5px solid var(--ink);
    border-radius: 999px;
    padding: 0 4px;
    font-size: var(--fs-md);
    line-height: 1.5;
    box-shadow: 0 1px 2px rgba(0,0,0,0.3);
  }
  /* Top-bar in the expedition zone — wave counter + retreat button */
  .exp-topbar {
    position: absolute;
    top: 8px; left: 8px; right: 8px;
    display: flex; justify-content: space-between; align-items: center;
    gap: 8px;
    z-index: 14;
    pointer-events: none;
  }
  .exp-wave-tag {
    pointer-events: auto;
    background: rgba(0,0,0,0.62);
    color: #ffe085;
    border: 2px solid #ffd86b;
    border-radius: 999px;
    padding: 4px 12px;
    font-weight: 800; font-size: var(--fs-md);
    box-shadow: 0 2px 6px rgba(0,0,0,0.35);
  }
  .exp-retreat-btn {
    pointer-events: auto;
    background: linear-gradient(180deg, #ff8b91, #d6494f);
    color: #fff;
    border: 2px solid #fff;
    border-radius: 999px;
    padding: 5px 14px;
    font-weight: 800; font-size: var(--fs-md);
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0,0,0,0.18);
  }
  .exp-retreat-btn:active { transform: translateY(2px); box-shadow: 0 1px 0 rgba(0,0,0,0.18); }

  /* Side-quest backdrop overlay — kicks in when the kid accepts an
     "Explore?" prompt and the scenery shifts to a special locale.
     Sits ABOVE the trail-layer (z=4) but below the time-overlay (z=6). */
  .quest-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 4;
    opacity: 0;
    transition: opacity 1.6s ease-in-out;
  }
  .quest-overlay.show { opacity: 1; }
  .quest-overlay.cave   {
    background: radial-gradient(ellipse at center,
      rgba(20,20,40,0.60) 0%,
      rgba(8,8,30,0.85) 60%,
      rgba(0,0,16,0.95) 100%);
  }
  .quest-overlay.house  {
    background: radial-gradient(ellipse at 30% 70%,
      rgba(255,180,90,0.18) 0%,
      rgba(220,140,80,0.12) 60%,
      rgba(180,80,40,0.08) 100%);
  }
  .quest-overlay.temple {
    background: radial-gradient(ellipse at center,
      rgba(255,240,160,0.30) 0%,
      rgba(255,220,140,0.20) 50%,
      rgba(220,170,80,0.10) 100%);
  }
  .quest-overlay.door   {
    background: radial-gradient(ellipse at center,
      rgba(140,80,200,0.30) 0%,
      rgba(80,40,140,0.25) 60%,
      rgba(40,20,80,0.15) 100%);
  }

  /* Side-quest banner (separate from existing battle-banner / city-event-banner) */
  .quest-banner {
    position: absolute;
    top: 18%; left: 50%; transform: translateX(-50%);
    background: linear-gradient(180deg, #fffaf2, #ffd86b);
    border: 3px solid var(--ink);
    border-radius: 16px;
    padding: 8px 18px;
    font-weight: 900; font-size: var(--fs-lg);
    color: var(--ink);
    z-index: 12;
    box-shadow: 0 4px 0 rgba(0,0,0,0.18), 0 0 24px rgba(255,200,80,0.6);
    animation: ce-banner-in 0.5s ease-out;
    pointer-events: none;
    white-space: nowrap;
  }
  .quest-banner .qb-star { font-size: var(--fs-xl); vertical-align: middle; }

  /* Day/night tinting overlay over the battle zone. Smooth 3.5s cross-fade
     so the time-of-day shift is gentle and storybook-feeling. */
  .time-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 6;          /* above battlers (z=5), below HUD (z=7) */
    transition: background 3.5s ease-in-out;
  }
  .time-overlay.time-day      { background: rgba(255,255,255,0); }
  .time-overlay.time-sunrise  {
    /* Soft pink-orange dawn — bridges night → day */
    background: linear-gradient(180deg,
      rgba(255,160,180,0.42) 0%,
      rgba(255,200,160,0.32) 30%,
      rgba(255,220,180,0.18) 60%,
      rgba(255,235,200,0.04) 100%);
  }
  .time-overlay.time-morning  {
    background: linear-gradient(180deg,
      rgba(255,180,140,0.22) 0%,
      rgba(255,210,170,0.14) 50%,
      rgba(255,235,200,0.04) 100%);
  }
  .time-overlay.time-sunset   {
    /* Bridge day → night with a deep orange-pink sky */
    background: linear-gradient(180deg,
      rgba(255,80,40,0.32)  0%,
      rgba(255,130,80,0.32) 40%,
      rgba(180,70,90,0.30)  75%,
      rgba(80,50,90,0.30)  100%);
  }
  .time-overlay.time-evening  {
    /* Deeper purple-orange just before night */
    background: linear-gradient(180deg,
      rgba(180,60,60,0.45)  0%,
      rgba(120,50,90,0.45) 50%,
      rgba(40,30,90,0.45) 100%);
  }
  .time-overlay.time-night    {
    background: linear-gradient(180deg,
      rgba(8,12,40,0.65)   0%,
      rgba(20,24,80,0.55) 60%,
      rgba(30,40,100,0.40) 100%);
  }
  /* The city's overlay is a touch lighter — kid still wants to see the city */
  .time-overlay.city-time.time-night    {
    background: linear-gradient(180deg,
      rgba(8,12,40,0.55)   0%,
      rgba(20,24,80,0.45) 60%,
      rgba(30,40,100,0.30) 100%);
  }
  .time-overlay.city-time.time-evening  {
    background: linear-gradient(180deg,
      rgba(180,60,60,0.32)  0%,
      rgba(120,50,90,0.32) 50%,
      rgba(40,30,90,0.32) 100%);
  }
  .time-overlay.city-time.time-sunset   {
    background: linear-gradient(180deg,
      rgba(255,90,50,0.22) 0%,
      rgba(255,140,90,0.22) 50%,
      rgba(120,60,90,0.22) 100%);
  }

  /* Twinkling stars layer that fades in during night */
  /* Painted sky-and-parallax layers — back to front:
      0  layer-sky        full painted sky, no scroll
      1  layer-mountains  painted mountain silhouette, scroll slow
      2  stars-layer      painted stars, fade in at night
      3  sky-decor        sun + moon (painted, time-of-day animated)
      4  layer-clouds     painted clouds drift IN FRONT of sun + mountains */
  .layer-sky {
    position: absolute;
    inset: 0 0 30% 0;
    z-index: 0;        /* base sky bg */
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    pointer-events: none;
  }
  #expedition[data-biome="forest"] .layer-sky { background-image: url('./assets/parallax/sky-forest.webp'); }
  #expedition[data-biome="crypt"]  .layer-sky { background-image: url('./assets/parallax/sky-crypt.webp'); }
  #expedition[data-biome="castle"] .layer-sky { background-image: url('./assets/parallax/sky-castle.webp'); }
  /* Mountains — far-distance band, slowest scroll (120s). Anchored
     LOWER (bottom 45% — was 55%) per Katherine: the mountain bases
     should be hidden BEHIND the hills layer (which sits at bottom
     44%) so the painted peaks tower over the hill silhouettes with
     no daylight gap between them. */
  .layer-mountains {
    position: absolute;
    bottom: 45%;
    left: 0; right: 0;
    height: 38%;
    width: 200%;
    z-index: 2;        /* Phase 7 — mountains in FRONT of sun/moon (z 1) */
    pointer-events: none;
    background-repeat: repeat-x;
    background-position: 0 100%;
    background-size: auto 100%;
    animation: trail-scroll 120s linear infinite;
  }
  #expedition[data-biome="forest"] .layer-mountains { background-image: url('./assets/parallax/mountains-forest.webp'); }
  #expedition[data-biome="crypt"]  .layer-mountains { background-image: url('./assets/parallax/mountains-crypt.webp'); }
  #expedition[data-biome="castle"] .layer-mountains { background-image: url('./assets/parallax/mountains-castle.webp'); }
  /* Clouds — kawaii cloud sprites drift across the upper sky region.
     z=4 puts them IN FRONT of the painted sun (z=3) and mountains
     (z=1), so they actually feel like clouds passing in front of the
     sun. Confined to the top 50% of the expedition zone so they never
     reach down past the painted hills/mountains. */
  .layer-clouds {
    position: absolute;
    inset: 0 0 50% 0;
    width: 200%;
    z-index: 6;        /* Phase 7 — parallax cloud band above all other sky layers */
    pointer-events: none;
    background-image: url('./assets/parallax/clouds.webp');
    background-repeat: repeat-x;
    background-position: 0 35%;
    background-size: auto 55%;
    animation: trail-scroll 90s linear infinite;
    opacity: 0.95;
  }
  /* Stars — painted, only show at night/sleep/evening. z=2 puts them
     IN FRONT of the mountains layer (z=1) so they're actually visible
     in the night sky instead of hidden behind the peaks. */
  .stars-layer {
    position: absolute;
    inset: 0 0 30% 0;
    z-index: 2;
    pointer-events: none;
    /* Phase 7 — Katherine asked to remove the dim painted starfield;
       the moon does the heavy lifting at night. Force-hide here so even
       if the .show class flips, no stars render. */
    opacity: 0 !important;
    background: none !important;
  }
  /* Phase rework: pause ALL scrolling parallax layers + the hero
     walk-bob when #expedition has .parallax-paused. setTrailWalking
     toggles the class so the WHOLE scene freezes when heroes aren't
     actively walking the trail (placard / battle / camp / retreat). */
  #expedition.parallax-paused .layer-mountains,
  #expedition.parallax-paused .layer-clouds,
  #expedition.parallax-paused .trail-track,
  #expedition.parallax-paused .trail-track-mid,
  #expedition.parallax-paused .trail-track-far,
  #expedition.parallax-paused .ground-band,
  #expedition.parallax-paused .battler.ally.walking img {
    animation-play-state: paused;
  }
  .stars-layer.show { opacity: 1; animation: stars-twinkle 4s ease-in-out infinite alternate; }
  @keyframes stars-twinkle {
    from { filter: brightness(0.8); }
    to   { filter: brightness(1.3); }
  }

  /* Sky decor — a sun + moon glyph that move position based on the
     current time-of-day phase. Drives off the data-phase attribute on
     #expedition (set by setTimeOfDay). Both glyphs are positioned in
     the upper sky region only, so they sit above the painted hills
     and never overlap battlers. */
  .sky-decor {
    position: absolute;
    inset: 0 0 50% 0;     /* upper half of the expedition zone */
    z-index: 1;           /* Phase 7 — BEHIND mountains (z 2) but above
                             the static sky (z 0); clouds at z 3-4 drift
                             across in front of the sun/moon. */
    pointer-events: none;
    overflow: hidden;
  }
  .sky-decor .sky-sun, .sky-decor .sky-moon {
    position: absolute;
    width: 22%;           /* big enough to see clearly during the day arc */
    height: auto;
    opacity: 0;
    transition: left 4.5s ease-in-out, top 4.5s ease-in-out, opacity 2.5s ease-in-out, filter 2.5s ease-in-out;
    will-change: left, top, opacity;
  }
  .sky-decor .sky-sun {
    filter: drop-shadow(0 0 24px rgba(255,216,107,1)) drop-shadow(0 0 40px rgba(255,180,80,0.7));
    animation: sun-pulse 3s ease-in-out infinite alternate;
  }
  .sky-decor .sky-moon {
    width: 30%;           /* Phase 7 — bigger moon, brighter halo */
    filter:
      brightness(1.6)
      drop-shadow(0 0 28px rgba(255,255,240,1))
      drop-shadow(0 0 60px rgba(255,250,180,0.95))
      drop-shadow(0 0 90px rgba(220,220,255,0.7));
  }
  @keyframes sun-pulse {
    from { transform: scale(1); }
    to   { transform: scale(1.05); }
  }
  /* Phase positions — sun moves left → top → right across day,
     moon stays in upper-right at night. */
  #expedition[data-phase="sunrise"] .sky-sun { opacity: 0.95; left: 8%;  top: 60%; filter: hue-rotate(-12deg); }
  #expedition[data-phase="morning"] .sky-sun { opacity: 1;    left: 28%; top: 22%; }
  #expedition[data-phase="day"]     .sky-sun { opacity: 1;    left: 50%; top: 12%; }
  #expedition[data-phase="sunset"]  .sky-sun { opacity: 0.95; left: 78%; top: 55%; filter: hue-rotate(-15deg); }
  #expedition[data-phase="evening"] .sky-sun { opacity: 0.4;  left: 92%; top: 70%; filter: hue-rotate(-22deg) brightness(0.7); }
  #expedition[data-phase="night"]   .sky-sun { opacity: 0; }
  #expedition[data-phase="sleep"]   .sky-sun { opacity: 0; }
  #expedition[data-phase="evening"] .sky-moon { opacity: 0.6; left: 16%; top: 16%; }
  #expedition[data-phase="night"]   .sky-moon { opacity: 1;   left: 76%; top: 14%; }
  #expedition[data-phase="sleep"]   .sky-moon { opacity: 1;   left: 76%; top: 14%; }
  #expedition[data-phase="sunrise"] .sky-moon { opacity: 0.45; left: 92%; top: 10%; }

  /* Sleep "Z" puffs that float up from sleeping heroes */
  .sleep-z {
    position: absolute;
    font-size: var(--fs-xl);
    font-weight: 900;
    color: #cbd5ff;
    text-shadow: 0 1px 2px rgba(0,0,0,0.5);
    pointer-events: none;
    z-index: 11;
    animation: sleep-float 3.6s ease-out forwards;
  }
  @keyframes sleep-float {
    0%   { opacity: 0; transform: translate(-50%, 0) scale(0.6); }
    20%  { opacity: 1; transform: translate(-50%, -10px) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -60px) scale(1.4); }
  }
  .battler.ally.sleeping img { animation: sleeping-bob 4s ease-in-out infinite; }
  @keyframes sleeping-bob {
    0%, 100% { transform: translateY(0) rotate(0deg); }
    50%      { transform: translateY(-3px) rotate(2deg); }
  }

  /* Heroes WALK between formation / camp / tent positions instead of
     teleporting. The transition is set inline at animation start so we
     can vary the duration per move. */
  .battler.ally.walking-to {
    transition: left 2.4s ease-in-out, bottom 2.4s ease-in-out;
  }

  /* Tents pitched at night around the campfire — one per hero */
  .camp-tent {
    position: absolute;
    z-index: 4;
    font-size: var(--fs-2xl);
    line-height: 1;
    pointer-events: none;
    transform-origin: center bottom;
    transform: scale(0);
    opacity: 0;
    filter: drop-shadow(0 4px 4px rgba(0,0,0,0.4));
    transition: transform 0.8s ease-out, opacity 0.8s ease-out;
  }
  .camp-tent.up {
    transform: scale(1);
    opacity: 1;
  }
  .camp-tent.glow::after {
    content: '';
    position: absolute;
    inset: 30% 20% 0 20%;
    background: radial-gradient(circle, rgba(255,200,80,0.35), transparent 60%);
    border-radius: 50%;
    pointer-events: none;
    animation: tent-glow 2.4s ease-in-out infinite;
  }
  @keyframes tent-glow {
    0%, 100% { opacity: 0.5; }
    50%      { opacity: 1; }
  }

  /* Big animated campfire that replaces the small dropSceneIcon.
     z-index 8 so it paints ABOVE the navy HP HUD (z 7); otherwise
     the fire spawns at bottom 18-22% and gets hidden behind the
     panel. Smaller font (52px) so it reads as a ground campfire,
     not a wall of flame. */
  .big-campfire {
    position: absolute;
    z-index: 8;
    font-size: var(--fs-2xl);
    line-height: 1;
    pointer-events: none;
    transform: scale(0);
    opacity: 0;
    transition: transform 0.6s ease-out, opacity 0.6s ease-out;
    filter: drop-shadow(0 4px 6px rgba(255,150,30,0.6));
    animation: fire-flicker 0.45s ease-in-out infinite alternate;
  }
  .big-campfire.up { transform: scale(1); opacity: 1; }
  @keyframes fire-flicker {
    0%   { filter: drop-shadow(0 4px 6px rgba(255,150,30,0.5)) brightness(0.95); }
    100% { filter: drop-shadow(0 6px 10px rgba(255,200,80,0.9)) brightness(1.1); }
  }
  /* Sparks drift up from the fire */
  .fire-spark {
    position: absolute;
    pointer-events: none;
    z-index: 6;
    font-size: var(--fs-lg);
    animation: spark-rise 1.6s ease-out forwards;
  }
  @keyframes spark-rise {
    0%   { opacity: 0; transform: translate(-50%, 0) scale(0.4); }
    20%  { opacity: 1; }
    100% { opacity: 0; transform: translate(-50%, -80px) scale(1.4); }
  }

  /* Patrol guard / wizard with torch in the city at night */
  .city-patrol {
    position: absolute;
    width: 9%; min-width: 48px; max-width: 84px;
    aspect-ratio: 1;
    display: flex; align-items: center; justify-content: center;
    font-size: var(--fs-xl);
    line-height: 1;
    pointer-events: none;
    z-index: 6;
    filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5));
    transition: left 6s linear, bottom 0.4s ease-in-out;
  }
  .city-patrol .torch {
    position: absolute;
    top: -10%; right: -8%;
    font-size: var(--fs-xl);
    animation: torch-flicker 0.4s ease-in-out infinite alternate;
    filter: drop-shadow(0 0 12px rgba(255,180,40,0.9));
  }
  @keyframes torch-flicker {
    from { filter: drop-shadow(0 0 8px rgba(255,180,40,0.6)) brightness(0.9); }
    to   { filter: drop-shadow(0 0 18px rgba(255,200,80,1.0)) brightness(1.15); }
  }

  /* Glowing windows on city buildings at night */
  .city-bldg.lit-up::after {
    content: '';
    position: absolute;
    inset: 30% 25% 30% 25%;
    background: radial-gradient(circle, rgba(255,220,120,0.55), transparent 60%);
    pointer-events: none;
    border-radius: 30%;
    animation: window-flicker 3.5s ease-in-out infinite;
  }
  @keyframes window-flicker {
    0%, 100% { opacity: 0.65; }
    50%      { opacity: 0.95; }
  }

  /* Lively-night per-district building glows (Slice 6) — applied
     ALWAYS for the bakery (the warm oven runs 24/7), and only at
     night for tavern (ambient lantern + music notes). The blacksmith's
     forge-fire ::before was removed at kid's request 2026-05-23. */
  #bldg-bakery::before {
    content: '';
    position: absolute;
    inset: 30% 22% 25% 22%;
    background: radial-gradient(circle, rgba(255,200,80,0.45), transparent 65%);
    border-radius: 40%;
    pointer-events: none;
    animation: bakery-warm 4.2s ease-in-out infinite;
    z-index: 1;
  }
  @keyframes bakery-warm {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 0.85; }
  }
  /* Tavern at night gets bouncing music-note + extra warm glow */
  body.city-night #bldg-tavern::after {
    content: '🎵';
    position: absolute;
    top: -16px;
    left: 50%;
    font-size: var(--fs-xl);
    pointer-events: none;
    animation: tavern-music 1.4s ease-in-out infinite;
    z-index: 7;
  }
  @keyframes tavern-music {
    0%, 100% { transform: translate(-50%, 0) rotate(-8deg); opacity: 0.85; }
    50%      { transform: translate(-50%, -8px) rotate(8deg); opacity: 1; }
  }

  /* Temple bell at night — one chime emoji that bobs once per cycle */
  body.city-night #bldg-temple::after {
    content: '🔔';
    position: absolute;
    top: -14px;
    left: 50%;
    font-size: var(--fs-xl);
    pointer-events: none;
    animation: temple-bell 6s ease-in-out infinite;
    z-index: 7;
  }
  @keyframes temple-bell {
    0%, 90%, 100% { transform: translate(-50%, 0) rotate(0deg); opacity: 0.6; }
    93%           { transform: translate(-50%, -4px) rotate(-15deg); opacity: 1; }
    96%           { transform: translate(-50%, -4px) rotate(15deg); opacity: 1; }
  }

  /* Nighttime ambient critter — flies/walks across the screen */
  .night-critter {
    position: absolute;
    z-index: 7;
    pointer-events: none;
    font-size: var(--fs-xl);
    line-height: 1;
    filter: drop-shadow(0 2px 3px rgba(0,0,0,0.5));
  }
  .night-critter.bat   { animation: bat-flap 8s linear forwards; bottom: 65%; }
  .night-critter.owl   { animation: owl-glide 11s linear forwards; bottom: 55%; }
  .night-critter.fox   { animation: fox-walk 12s linear forwards; bottom: 14%; }
  .night-critter.firefly { animation: firefly-drift 15s linear forwards; }
  @keyframes bat-flap {
    0%   { left: -8%; transform: scaleX(-1) translateY(0); }
    25%  { transform: scaleX(-1) translateY(-22px); }
    50%  { transform: scaleX(-1) translateY(0); }
    75%  { transform: scaleX(-1) translateY(-22px); }
    100% { left: 108%; transform: scaleX(-1) translateY(0); }
  }
  @keyframes owl-glide {
    0%   { left: 108%; transform: translateY(0); }
    50%  { transform: translateY(-12px); }
    100% { left: -8%; transform: translateY(0); }
  }
  @keyframes fox-walk {
    0%   { left: -10%; transform: scaleX(-1) translateY(0); }
    20%  { transform: scaleX(-1) translateY(-3px); }
    40%  { transform: scaleX(-1) translateY(0); }
    60%  { transform: scaleX(-1) translateY(-3px); }
    80%  { transform: scaleX(-1) translateY(0); }
    100% { left: 108%; transform: scaleX(-1) translateY(0); }
  }
  @keyframes firefly-drift {
    0%   { left: 0%; bottom: 25%; opacity: 0; }
    10%  { opacity: 1; }
    25%  { left: 25%; bottom: 35%; }
    50%  { left: 50%; bottom: 25%; }
    75%  { left: 75%; bottom: 40%; }
    90%  { opacity: 1; }
    100% { left: 100%; bottom: 30%; opacity: 0; }
  }
  .night-critter.firefly {
    color: #ffe085;
    text-shadow: 0 0 10px #ffe085, 0 0 18px #ffd45c;
  }

  /* End-of-run outcome card (Victory / Defeat / Retreat) and Obtained card.
     "Soft" popup that occupies just the battle area (above the HP HUD). */
  .battle-outcome {
    position: absolute;
    left: 50%; top: 36%;
    transform: translate(-50%, -50%);
    width: min(80%, 380px);
    background: linear-gradient(180deg, rgba(18,28,90,0.92), rgba(8,16,60,0.94));
    border: 3px solid #ffd86b;
    border-radius: 16px;
    padding: 18px 18px 16px;
    text-align: center;
    color: #fff;
    z-index: 30;
    box-shadow: 0 6px 0 rgba(0,0,0,0.3), 0 12px 26px rgba(0,0,0,0.45);
    animation: outcome-in 0.45s ease-out;
  }
  .battle-outcome .bo-banner {
    font-size: var(--fs-xl); font-weight: 900;
    letter-spacing: 0.04em;
    margin-bottom: 6px;
    text-shadow: 0 2px 0 rgba(0,0,0,0.5);
    line-height: 1.05;
  }
  .battle-outcome.win   .bo-banner { color: #ffd86b; }
  .battle-outcome.lose  .bo-banner { color: #ff8b91; }
  .battle-outcome.retreat .bo-banner { color: #c8eaff; }
  .battle-outcome .bo-sub {
    font-size: var(--fs-md); font-weight: 700;
    color: #cbd5ff;
    margin-bottom: 4px;
  }
  .battle-outcome .bo-obtained {
    margin-top: 10px;
    background: rgba(255,255,255,0.08);
    border: 2px solid rgba(255,216,107,0.4);
    border-radius: 10px;
    padding: 8px 10px;
    text-align: left;
    font-size: var(--fs-md);
    line-height: 1.45;
    font-weight: 700;
  }
  .battle-outcome .bo-line {
    display: flex; justify-content: space-between;
    border-bottom: 1px dashed rgba(255,255,255,0.18);
    padding: 3px 0;
  }
  .battle-outcome .bo-line:last-child { border-bottom: none; }
  .battle-outcome .bo-line .lbl { color: #cbd5ff; }
  .battle-outcome .bo-line .val { color: #ffd86b; font-weight: 900; }

  /* Results screen — per-hero XP bars + chest reveal grid. Lives
     inside the existing .battle-outcome card so it slots into the
     post-fight flow without new modals. */
  .battle-outcome .rs-screen {
    display: flex; flex-direction: column; gap: 10px;
    margin-top: 8px;
    text-align: left;
  }
  .battle-outcome .rs-totals {
    display: flex; justify-content: center; gap: 14px;
    font-size: var(--fs-lg); font-weight: 900;
  }
  .battle-outcome .rs-totals .rs-coin { color: #ffd86b; }
  .battle-outcome .rs-totals .rs-foes { color: #c8eaff; }
  .battle-outcome .rs-heroes {
    display: flex; flex-direction: column; gap: 4px;
  }
  .battle-outcome .rs-hero {
    display: flex; align-items: center; gap: 10px;
    padding: 4px 6px;
    background: rgba(255,255,255,0.08);
    border: 2px solid rgba(255,255,255,0.18);
    border-radius: 10px;
  }
  .battle-outcome .rs-hero.leveled {
    background: linear-gradient(90deg, rgba(255,216,107,0.3), rgba(255,255,255,0.06));
    border-color: rgba(255,216,107,0.6);
    box-shadow: 0 0 12px rgba(255,216,107,0.4);
  }
  .battle-outcome .rs-portrait {
    width: 40px; height: 40px;
    object-fit: contain;
    image-rendering: pixelated;
    flex-shrink: 0;
  }
  .battle-outcome .rs-portrait.face-flip { transform: scaleX(-1); }
  .battle-outcome .rs-info { flex: 1; min-width: 0; }
  .battle-outcome .rs-row {
    display: flex; justify-content: space-between; align-items: center;
    font-size: var(--fs-md); font-weight: 800;
    color: #fff;
  }
  .battle-outcome .rs-name { color: #fff; }
  .battle-outcome .rs-lvl {
    background: rgba(255,255,255,0.12);
    color: #cbd5ff;
    border-radius: 6px;
    padding: 1px 6px;
    font-size: var(--fs-md);
  }
  .battle-outcome .rs-lvl-up {
    background: #ffd86b;
    color: #6e3a00;
    border-radius: 6px;
    padding: 1px 6px;
    font-size: var(--fs-md);
    font-weight: 900;
    animation: rs-lvl-pulse 0.8s ease-in-out infinite alternate;
  }
  @keyframes rs-lvl-pulse {
    from { box-shadow: 0 0 0 0 rgba(255,216,107,0.7); }
    to   { box-shadow: 0 0 0 6px rgba(255,216,107,0); }
  }
  .battle-outcome .rs-xpbar {
    height: 8px;
    background: rgba(0,0,0,0.4);
    border: 1px solid rgba(255,255,255,0.3);
    border-radius: 4px;
    overflow: hidden;
    margin: 3px 0 2px 0;
  }
  .battle-outcome .rs-xpbar > i {
    display: block; height: 100%;
    background: linear-gradient(90deg, #58dd66, #ffd86b);
    transition: width 1.6s cubic-bezier(0.34, 1.2, 0.64, 1);
  }
  .battle-outcome .rs-xptext {
    font-size: var(--fs-md); font-weight: 700;
    color: #fff7c2;
  }
  /* Loot row — chests that pop in then can be tapped to reveal. */
  .battle-outcome .rs-loot-row { text-align: center; }
  .battle-outcome .rs-loot-title {
    font-size: var(--fs-md); font-weight: 800;
    color: #ffd86b;
    margin-bottom: 4px;
  }
  .battle-outcome .rs-loot-empty {
    font-size: var(--fs-md); color: #cbd5ff; font-style: italic;
  }
  /* Phase 7 — clean loot pill list (replaces cardboard chest grid). */
  .battle-outcome .rs-loot-list {
    display: flex; flex-wrap: wrap; justify-content: center; gap: 6px;
    margin-top: 4px;
  }
  .battle-outcome .rs-loot-pill {
    background: linear-gradient(180deg, #fff8ec 0%, #ffe9c4 100%);
    border: 2px solid var(--ink, #4a3268);
    border-radius: 999px;
    padding: 4px 10px;
    font-size: var(--fs-sm); font-weight: 800;
    color: var(--ink, #4a3268);
    box-shadow: 0 2px 0 rgba(0,0,0,0.18);
  }
  .battle-outcome .rs-loot-pill.r-rare      { background: linear-gradient(180deg, #e3edff, #aaccff); }
  .battle-outcome .rs-loot-pill.r-epic      { background: linear-gradient(180deg, #ffe1f7, #ffaaee); }
  .battle-outcome .rs-loot-pill.r-legendary { background: linear-gradient(180deg, #fff0d6, #ffd09a); }
  .battle-outcome .rs-loot-chests {
    display: flex; flex-wrap: wrap; justify-content: center; gap: 8px;
  }
  .battle-outcome .rs-chest {
    position: relative;
    width: 64px; height: 64px;
    background: linear-gradient(180deg, #ffe085 0%, #c97e00 100%);
    border: 3px solid var(--ink, #4a3268);
    border-radius: 10px;
    cursor: pointer;
    box-shadow: 0 4px 0 rgba(0,0,0,0.3), inset 0 -6px 0 rgba(0,0,0,0.15);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    overflow: hidden;
    animation: rs-chest-in 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) backwards,
               rs-chest-shake 1.6s ease-in-out 0.6s infinite;
    color: #fff;
    font-size: var(--fs-md); font-weight: 800;
    text-shadow: 0 1px 2px rgba(0,0,0,0.7);
  }
  .battle-outcome .rs-chest.r-rare {
    background: linear-gradient(180deg, #aaccff 0%, #4a78c8 100%);
  }
  .battle-outcome .rs-chest.r-epic {
    background: linear-gradient(180deg, #ffaaee 0%, #b455a4 100%);
  }
  .battle-outcome .rs-chest.r-legendary {
    background: linear-gradient(180deg, #ffd6a8 0%, #ff8a3a 100%);
  }
  .battle-outcome .rs-chest:hover, .battle-outcome .rs-chest:active {
    transform: translateY(-3px);
    filter: brightness(1.1);
  }
  .battle-outcome .rs-chest .rs-chest-lid {
    font-size: var(--fs-2xl);
    line-height: 1;
    transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  .battle-outcome .rs-chest .rs-chest-glow {
    position: absolute; inset: 0;
    background: radial-gradient(circle at 50% 50%, rgba(255,255,255,0.6), transparent 60%);
    opacity: 0; pointer-events: none;
    animation: rs-chest-pulse 1.4s ease-in-out infinite;
  }
  .battle-outcome .rs-chest.r-rare .rs-chest-glow { animation-duration: 1.0s; }
  .battle-outcome .rs-chest.r-epic .rs-chest-glow { animation-duration: 0.7s; }
  .battle-outcome .rs-chest.opened {
    animation: none;
    cursor: default;
  }
  .battle-outcome .rs-chest.opened .rs-chest-burst {
    position: absolute; top: -10px; left: 50%;
    transform: translateX(-50%);
    font-size: var(--fs-2xl);
    animation: rs-chest-burst 0.8s ease-out;
    pointer-events: none;
  }
  .battle-outcome .rs-chest.opened .rs-chest-item {
    font-size: var(--fs-xl);
    line-height: 1;
    margin-bottom: 1px;
    animation: rs-chest-pop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  .battle-outcome .rs-chest.opened .rs-chest-name {
    font-size: var(--fs-sm); font-weight: 800;
    text-align: center;
    line-height: 1.1;
    animation: rs-chest-fadein 0.4s ease-out 0.3s backwards;
  }
  @keyframes rs-chest-in {
    from { opacity: 0; transform: translateY(-30px) scale(0.5); }
    to   { opacity: 1; transform: translateY(0)     scale(1); }
  }
  @keyframes rs-chest-shake {
    0%, 100% { transform: rotate(-2deg); }
    50%      { transform: rotate(2deg); }
  }
  @keyframes rs-chest-pulse {
    0%, 100% { opacity: 0; }
    50%      { opacity: 0.7; }
  }
  @keyframes rs-chest-burst {
    0%   { opacity: 0; transform: translateX(-50%) translateY(0) scale(0.5); }
    50%  { opacity: 1; transform: translateX(-50%) translateY(-12px) scale(1.4); }
    100% { opacity: 0; transform: translateX(-50%) translateY(-24px) scale(1); }
  }
  @keyframes rs-chest-pop {
    0%   { transform: scale(0); }
    70%  { transform: scale(1.3); }
    100% { transform: scale(1); }
  }
  @keyframes rs-chest-fadein {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  @keyframes outcome-in {
    from { opacity: 0; transform: translate(-50%, -38%) scale(0.7); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  }
  @keyframes outcome-out {
    to { opacity: 0; transform: translate(-50%, -58%) scale(0.95); }
  }
  .battle-outcome.fade-out {
    animation: outcome-out 0.5s ease-in forwards;
  }

  /* FF6-style HP HUD across the BOTTOM of the expedition zone.
     One column per active hero slot, blue gradient panel with name + HP. */
  /* Bottom party HUD — classic JRPG menu panel: chunky double-border
     navy box with one cell per active slot showing name + HP + MP. */
  #party-hud-bottom {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 20%;
    min-height: 116px;
    background: linear-gradient(180deg, #2a3aa0 0%, #14206a 70%, #0a1450 100%);
    border-top: 4px solid #1a2470;
    box-shadow:
      inset 0 2px 0 #aac4ff,
      inset 0 -2px 0 rgba(0,0,0,0.5),
      inset 0 0 0 4px #14206a,
      0 -2px 8px rgba(0,0,0,0.4);
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 0;
    padding: 6px 8px;
    z-index: 7;
    pointer-events: none;
  }
  .phud-cell {
    display: flex; flex-direction: column; justify-content: center; gap: 4px;
    padding: 4px 8px;
    border-right: 2px solid rgba(170, 196, 255, 0.30);
    color: #fff;
    /* Lock the cell to its grid track (1fr each, repeat-4). min-width:0
       defeats grid's default 'auto' min-content sizing that would let
       a long name push the cell wider than its share. overflow:hidden
       clips any content that would otherwise spill into the next cell. */
    min-width: 0;
    overflow: hidden;
  }
  .phud-cell:last-child { border-right: none; }
  .phud-cell.empty { opacity: 0.4; }
  .phud-name {
    font-weight: 900; font-size: var(--fs-xl);
    color: #ffe085;
    text-shadow: 0 1px 0 rgba(0,0,0,0.7), 0 0 4px rgba(255,224,133,0.35);
    line-height: 1.05;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    display: flex; align-items: center; gap: 4px;
    letter-spacing: 0.4px;
  }
  .phud-limit {
    font-size: var(--fs-md); font-weight: 900;
    color: #ff2266;
    background: #ff0;
    padding: 0 4px; border-radius: 3px;
    animation: limit-blink 0.6s infinite alternate;
  }
  @keyframes limit-blink {
    from { box-shadow: 0 0 4px #ff0; }
    to   { box-shadow: 0 0 12px #ff0; }
  }
  .phud-statline {
    display: flex; align-items: center; gap: 6px;
    font-size: var(--fs-lg); font-weight: 800;
  }
  .phud-statline .stat-label {
    font-size: var(--fs-lg); font-weight: 900; letter-spacing: 0.5px;
    width: 30px;
    color: rgba(255,255,255,0.95);
    text-shadow: 0 1px 0 rgba(0,0,0,0.6);
  }
  .phud-statline .pb-bar {
    flex: 1; height: 12px;
    background: #11214b;
    border: 1px solid #4060a0;
    border-radius: 3px;
    overflow: hidden;
    box-shadow: inset 0 1px 0 rgba(0,0,0,0.5);
  }
  .phud-statline .pb-bar > i {
    display: block; height: 100%;
    transition: width 0.25s ease-out;
  }
  .phud-statline.hp .pb-bar > i {
    background: linear-gradient(90deg, #58dd66, #aae696);
  }
  .phud-statline.hp .pb-bar.low > i {
    background: linear-gradient(90deg, #ff5566, #ffaaaa);
  }
  .phud-statline.mp .pb-bar > i {
    background: linear-gradient(90deg, #66aaff, #aaccff);
  }
  .phud-statline .pb-num {
    color: #fff; min-width: 68px; text-align: right;
    text-shadow: 0 1px 0 rgba(0,0,0,0.7);
    font-size: var(--fs-lg);
    font-variant-numeric: tabular-nums;
  }

  /* Spell callout — slightly fancier than basic attack */
  .attack-callout.spell {
    background: linear-gradient(180deg, #fffaf2, #ffd86b);
    border-color: #c97e00;
    color: #6e3a00;
  }
  .battler-name {
    position: absolute; bottom: -20px; left: 50%; transform: translateX(-50%);
    font-size: var(--fs-lg); font-weight: 800;
    color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,0.85);
    white-space: nowrap;
  }

  /* Side-scrolling parallax trail. Spans the whole expedition zone.
     Three layers, all behind battlers (z=2+):
       z=0: far parallax (mountains/hills) — scrolls slow
       z=0: ground dirt path strip
       z=1: near props (trees/bushes/flowers) — scrolls medium-fast
       z=3: ambient critters (foreground, fast — go in FRONT of allies) */
  .trail-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
  }
  /* Painted ground band — sits at the bottom of the expedition zone,
     above the HP HUD, taking ~1/3 of the visible height. Gives a real
     stage for battlers instead of the squished dirt strip from before.
     Layered on top of the biome backdrop so the sky/mountains in the
     painted background show through the upper 2/3. */
  .ground-band {
    position: absolute;
    left: 0; right: 0;
    /* Grass band IS the floor heroes stand on. Twice as thick
       per Katherine (height 24%, was 12%) so the green stage feels
       like a real meadow. Sits above the HUD, below the trees. */
    bottom: 20%;
    height: 24%;
    z-index: 4;        /* Phase 7 — above mid-hills (z 3) */
    pointer-events: none;
    /* Painted grass tile (parallax/grass-tile.webp) tiles seamlessly
       across the strip, with the original gradient as a fallback so
       the band still has color if the asset fails to load. */
    background:
      url('./assets/parallax/grass-tile.webp') 0 50% / 220px auto repeat,
      linear-gradient(180deg, #92d36a 0%, #6dbf52 30%, #5fa844 70%, #4d8c39 100%);
    box-shadow: inset 0 6px 0 #a3d864, inset 0 -2px 4px rgba(0,0,0,0.15);
    /* Grass is the CLOSEST parallax layer (under the heroes' feet),
       so it scrolls fastest — 12 s/cycle vs bushes at 14 s. */
    animation: ground-scroll 12s linear infinite;
  }
  @keyframes ground-scroll {
    from { background-position: 0 50%, 0 0; }
    to   { background-position: -220px 50%, 0 0; }
  }
  .ground-band.paused {
    animation-play-state: paused;
  }

  /* Brown dirt-path strip — DISABLED. Was a thin band above the HP
     HUD that read as a "weird emoticon strip" because the trail-prop
     emoji used to overlap it. The painted ground-band carries the
     stage now. Kept the rule as no-op so future commits can re-enable
     a painted version cleanly. */
  .trail-layer::after { content: none; }
  /* Three-layer parallax — distant silhouettes (slow), mid-distance
     trees (medium), and foreground props (fast). Speed ratio 80:40:22
     gives a strong depth read like classic side-scrollers. */
  .trail-track-far {
    position: absolute;
    inset: 0;
    width: 200%;
    z-index: 0;
    animation: trail-scroll 80s linear infinite;
  }
  .trail-prop.farprop {
    bottom: 50%;
    font-size: var(--fs-xl);
    opacity: 0.45;
    filter: blur(1px) drop-shadow(0 1px 2px rgba(0,0,0,0.2));
  }
  .trail-track-mid {
    position: absolute;
    /* Trees + hills band — sits flush above the grass (bottom 44%)
       and runs up to where the mountains begin (62%) so there's NO
       daylight between grass / trees / mountains. Total height 18%. */
    bottom: 44%;
    left: 0; right: 0;
    height: 18%;
    width: 200%;
    z-index: 3;        /* Phase 7 — IN FRONT of mountains (z 2) per kid feedback "small hills on wrong layer" */
    /* Slowed from 40s → 70s — hills are FAR distance, should drift slow.
       Bushes (.trail-track at 14s) feel fast against this. */
    animation: trail-scroll 70s linear infinite;
    /* Painted mid-distance silhouette of pastel hills + distant pines,
       moved UP and made TALLER so the hill silhouette covers the
       lower painted-mountain area of the biome backdrop. The kid
       shouldn't see "grass" sticking up above the hill line. */
    background-image: url('./assets/parallax/mid-hills.webp');
    background-repeat: repeat-x;
    /* Track now has an explicit height (14%); fill the track fully
       with the silhouette art. */
    background-position: 0 100%;
    background-size: auto 100%;
  }
  .trail-prop.midprop {
    bottom: 42%;
    font-size: var(--fs-xl);
    opacity: 0.78;
    filter: blur(0.3px) drop-shadow(0 2px 3px rgba(0,0,0,0.25));
  }
  .trail-track {
    position: absolute;
    /* Foreground bushes sit at the VERY bottom of the scene, tucked
       slightly UNDER the HUD top edge so there's no daylight gap
       between the bush band and the navy HP panel. Per Katherine: "on
       the top layer (all else behind it)" — z=10 puts the band in
       front of heroes (z=5) too, so heroes' feet hide behind the
       front bushes for depth (Pokemon-style). */
    bottom: 18%;
    left: 0; right: 0;
    height: 26%;
    width: 200%;
    z-index: 10;
    /* Sped up from 18s → 14s — bushes are the closest layer, should
       blur past quickly while the trail is walking. */
    animation: trail-scroll 14s linear infinite;
    background-image: url('./assets/parallax/fg-bushes.webp');
    background-repeat: repeat-x;
    /* Track has explicit height (8%) — fill it with the silhouette art. */
    background-position: 0 100%;
    background-size: auto 100%;
  }
  .trail-track.paused,
  .trail-track-mid.paused,
  .trail-track-far.paused,
  .trail-layer.paused .trail-track,
  .trail-layer.paused .trail-track-mid,
  .trail-layer.paused .trail-track-far,
  .trail-layer.paused .trail-prop,
  .trail-layer.paused .ambient-critter {
    animation-play-state: paused;
  }
  /* Biome-specific parallax bands — set when buildTrail() tags the
     layer with data-biome="forest|crypt|castle". Each biome gets its
     own painted mid-hills silhouette and foreground bush band so the
     three regions feel visually distinct from each other. */
  .trail-layer[data-biome="forest"] .trail-track-mid {
    background-image: url('./assets/parallax/forest-mid-hills.webp');
  }
  .trail-layer[data-biome="forest"] .trail-track {
    background-image: url('./assets/parallax/forest-fg-bushes.webp');
  }
  .trail-layer[data-biome="crypt"] .trail-track-mid {
    background-image: url('./assets/parallax/crypt-mid-hills.webp');
  }
  .trail-layer[data-biome="crypt"] .trail-track {
    background-image: url('./assets/parallax/crypt-fg-bushes.webp');
  }
  .trail-layer[data-biome="castle"] .trail-track-mid {
    background-image: url('./assets/parallax/castle-mid-hills.webp');
  }
  .trail-layer[data-biome="castle"] .trail-track {
    background-image: url('./assets/parallax/castle-fg-bushes.webp');
  }
  @keyframes trail-scroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
  }
  .trail-prop {
    position: absolute;
    line-height: 1;
    filter: drop-shadow(0 3px 4px rgba(0,0,0,0.3));
    user-select: none;
  }
  /* Near-layer parallax props — anchored at the BASE of the painted
     ground band (just above the HP HUD at bottom 18%) so trees and
     bushes look like they're growing out of the grass, not floating. */
  .trail-prop.tree {
    bottom: 19%;
    font-size: var(--fs-2xl);
  }
  .trail-prop.tree.tall {
    bottom: 19%;
    font-size: var(--fs-2xl);
  }
  .trail-prop.bush {
    bottom: 19%;
    font-size: var(--fs-xl);
  }
  .trail-prop.flower {
    bottom: 19%;
    font-size: var(--fs-xl);
  }
  .trail-prop.rock {
    bottom: 18%;
    font-size: var(--fs-2xl);
  }
  .trail-prop.far {
    bottom: 36%;
    font-size: var(--fs-xl);
    opacity: 0.6;
    filter: drop-shadow(0 1px 2px rgba(0,0,0,0.2));
  }

  /* Ambient critters — bunnies hopping, butterflies flying, birds drifting */
  .ambient-critter {
    position: absolute;
    z-index: 3;       /* in FRONT of allies for fun foreground */
    font-size: var(--fs-xl);
    line-height: 1;
    pointer-events: none;
    filter: drop-shadow(0 2px 3px rgba(0,0,0,0.3));
    animation: critter-drift linear 1 forwards;
    user-select: none;
  }
  .ambient-critter.bunny  { animation: critter-hop 9s linear 1 forwards; bottom: 25%; }
  .ambient-critter.butter { animation: critter-flutter 11s linear 1 forwards; bottom: 50%; }
  .ambient-critter.bird   { animation: critter-glide 9s linear 1 forwards; bottom: 70%; }
  .ambient-critter.frog   { animation: critter-hop 12s linear 1 forwards; bottom: 25%; }
  .ambient-critter.hedge  { animation: critter-drift 13s linear 1 forwards; bottom: 25%; }
  @keyframes critter-drift {
    from { left: 105%; transform: scaleX(-1); }
    to   { left: -10%; transform: scaleX(-1); }
  }
  @keyframes critter-hop {
    0%   { left: 105%; transform: scaleX(-1) translateY(0); }
    10%  { transform: scaleX(-1) translateY(-14px); }
    20%  { transform: scaleX(-1) translateY(0); }
    30%  { transform: scaleX(-1) translateY(-14px); }
    40%  { transform: scaleX(-1) translateY(0); }
    50%  { transform: scaleX(-1) translateY(-14px); }
    60%  { transform: scaleX(-1) translateY(0); }
    70%  { transform: scaleX(-1) translateY(-14px); }
    80%  { transform: scaleX(-1) translateY(0); }
    90%  { transform: scaleX(-1) translateY(-14px); }
    100% { left: -10%; transform: scaleX(-1) translateY(0); }
  }
  @keyframes critter-flutter {
    0%   { left: 105%; transform: scaleX(-1) translateY(0); }
    25%  { transform: scaleX(-1) translateY(-22px); }
    50%  { transform: scaleX(-1) translateY(0); }
    75%  { transform: scaleX(-1) translateY(-22px); }
    100% { left: -10%; transform: scaleX(-1) translateY(0); }
  }
  @keyframes critter-glide {
    0%   { left: 105%; transform: scaleX(-1) translateY(0); }
    50%  { transform: scaleX(-1) translateY(-10px); }
    100% { left: -10%; transform: scaleX(-1) translateY(0); }
  }

  /* Encounter actor — a friendly sprite (merchant / healer / NPC / etc.)
     that walks in from the right just like a monster, but with an emoji
     for art instead of a sprite sheet. Sits on the ground line. */
  .encounter-actor {
    position: absolute;
    height: 22%;
    z-index: 5;
    display: flex; flex-direction: column;
    align-items: center; justify-content: flex-end;
    pointer-events: none;
    transition: left 1.4s ease-out, transform 1.4s ease-out, opacity 1.4s;
    filter: drop-shadow(0 4px 4px rgba(0,0,0,0.3));
  }
  .encounter-actor.far {
    transform: scale(0.5);
    opacity: 0.8;
  }
  .encounter-actor.engaged {
    transform: scale(1);
    opacity: 1;
  }
  .encounter-actor.going-home {
    transition: left 4.5s ease-in, top 4.5s ease-in, transform 4.5s ease-in, opacity 4.5s;
  }
  .encounter-actor .ea-emoji {
    font-size: var(--fs-2xl);
    line-height: 1;
    animation: encounter-bob 2.2s ease-in-out infinite;
  }
  /* Painted-sprite version of an encounter actor (used by recruit
     encounters so the kid sees the actual creature instead of a
     blank emoji-fallback). */
  .encounter-actor .ea-sprite {
    width: auto; height: 80%;
    image-rendering: pixelated;
    display: block;
    filter: drop-shadow(0 4px 4px rgba(0,0,0,0.3));
    animation: encounter-bob 2.2s ease-in-out infinite;
  }
  .encounter-actor.dancer .ea-emoji,
  .encounter-actor.dancer .ea-sprite {
    animation: dance-shake 0.5s ease-in-out infinite;
  }
  @keyframes encounter-bob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-6px); }
  }
  @keyframes dance-shake {
    0%, 100% { transform: rotate(-12deg) translateY(0); }
    25%      { transform: rotate(8deg) translateY(-6px); }
    50%      { transform: rotate(-6deg) translateY(0); }
    75%      { transform: rotate(10deg) translateY(-6px); }
  }
  .encounter-actor .ea-name {
    margin-top: 4px;
    font-size: var(--fs-md); font-weight: 800;
    color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,0.8);
    background: rgba(0,0,0,0.45);
    padding: 1px 7px;
    border-radius: 999px;
    white-space: nowrap;
  }

  /* Dance-party rainbow strobe across the screen */
  .dance-floor {
    position: absolute;
    inset: 14% 0 14% 0;
    pointer-events: none;
    z-index: 4;
    background:
      radial-gradient(circle at 30% 50%, rgba(255,80,150,0.18), transparent 50%),
      radial-gradient(circle at 50% 50%, rgba(120,200,255,0.18), transparent 55%),
      radial-gradient(circle at 70% 50%, rgba(255,220,80,0.18), transparent 50%);
    animation: dance-strobe 0.6s steps(4) infinite;
  }
  @keyframes dance-strobe {
    0%   { filter: hue-rotate(0deg); }
    100% { filter: hue-rotate(360deg); }
  }
  .dance-note {
    position: absolute;
    pointer-events: none;
    z-index: 6;
    font-size: var(--fs-xl);
    animation: note-float 2.2s ease-out forwards;
  }
  @keyframes note-float {
    0%   { opacity: 0; transform: translateY(0) scale(0.6); }
    20%  { opacity: 1; transform: translateY(-20px) scale(1); }
    100% { opacity: 0; transform: translateY(-90px) scale(1.2); }
  }

  /* City event banner — big, celebratory pop-in for "Surprise Festival",
     "Cupcake Party", etc. Now reads as a real "EVENT!" announcement
     with a sparkle ring around it instead of a tiny pill. */
  .city-event-banner {
    position: absolute;
    top: 8%; left: 50%; transform: translateX(-50%);
    background: linear-gradient(180deg, #fff8e0 0%, #ffd86b 60%, #ffae3a 100%);
    border: 4px solid var(--ink);
    border-radius: 22px;
    padding: 12px 28px;
    font-weight: 900; font-size: var(--fs-xl);
    line-height: 1.15;
    text-align: center;
    color: #6e3a00;
    text-shadow: 0 2px 0 #fff8e0, 0 0 12px rgba(255,222,120,0.6);
    z-index: 12;
    box-shadow:
      0 6px 0 rgba(0,0,0,0.22),
      0 0 0 6px rgba(255,255,255,0.6),
      0 0 0 10px rgba(255,200,80,0.45),
      0 0 24px rgba(255,200,80,0.6);
    animation:
      ce-banner-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1),
      ce-banner-shake 1.6s ease-in-out 0.5s infinite,
      ce-banner-out 0.5s ease-in 5.4s forwards;
    pointer-events: none;
    white-space: nowrap;
    max-width: 90%;
  }
  /* Sparkles spinning around the banner for extra celebration vibe */
  .city-event-banner::before,
  .city-event-banner::after {
    content: '✨';
    position: absolute;
    top: 50%;
    font-size: var(--fs-xl);
    animation: ce-sparkle-spin 2.5s linear infinite;
    text-shadow: 0 0 8px rgba(255,222,120,0.8);
  }
  .city-event-banner::before { left: -32px; transform: translateY(-50%); }
  .city-event-banner::after  { right: -32px; transform: translateY(-50%); animation-direction: reverse; }
  @keyframes ce-banner-in {
    0%   { opacity: 0; transform: translate(-50%, -16px) scale(0.4) rotate(-6deg); }
    70%  { opacity: 1; transform: translate(-50%, 4px)   scale(1.1) rotate(2deg); }
    100% { opacity: 1; transform: translate(-50%, 0)     scale(1)   rotate(0deg); }
  }
  @keyframes ce-banner-shake {
    0%, 100% { transform: translate(-50%, 0) rotate(-1deg); }
    50%      { transform: translate(-50%, -2px) rotate(1deg); }
  }
  @keyframes ce-banner-out {
    to { opacity: 0; transform: translate(-50%, -16px) scale(0.7); }
  }
  @keyframes ce-sparkle-spin {
    0%   { transform: translateY(-50%) rotate(0deg) scale(1); opacity: 0.8; }
    50%  { transform: translateY(-50%) rotate(180deg) scale(1.4); opacity: 1; }
    100% { transform: translateY(-50%) rotate(360deg) scale(1); opacity: 0.8; }
  }
  /* City event particles — confetti / balloons / rainbow */
  .city-confetti {
    position: absolute;
    top: -8%;
    pointer-events: none;
    font-size: var(--fs-lg);
    z-index: 11;
    animation: confetti-fall linear forwards;
  }
  @keyframes confetti-fall {
    0%   { opacity: 1; transform: translateY(0) rotate(0deg); }
    100% { opacity: 0; transform: translateY(120vh) rotate(720deg); }
  }
  .city-balloon {
    position: absolute;
    bottom: -8%;
    pointer-events: none;
    font-size: var(--fs-2xl);
    z-index: 11;
    animation: balloon-rise linear forwards;
  }
  @keyframes balloon-rise {
    0%   { opacity: 0; transform: translateY(0) translateX(0); }
    10%  { opacity: 1; }
    100% { opacity: 0; transform: translateY(-200%) translateX(20px); }
  }
  .city-rainbow {
    position: absolute;
    top: 8%; left: 0; right: 0;
    height: 60%;
    pointer-events: none;
    z-index: 5;
    background: radial-gradient(ellipse at 50% 100%,
      transparent 35%,
      rgba(255,80,80,0.30) 36%, rgba(255,80,80,0.30) 38%,
      rgba(255,180,80,0.30) 39%, rgba(255,180,80,0.30) 41%,
      rgba(255,240,80,0.30) 42%, rgba(255,240,80,0.30) 44%,
      rgba(120,220,80,0.30) 45%, rgba(120,220,80,0.30) 47%,
      rgba(80,180,255,0.30) 48%, rgba(80,180,255,0.30) 50%,
      rgba(180,80,220,0.30) 51%, rgba(180,80,220,0.30) 53%,
      transparent 54%);
    animation: rainbow-fade 8s ease-in-out forwards;
  }
  @keyframes rainbow-fade {
    0%   { opacity: 0; }
    20%  { opacity: 1; }
    80%  { opacity: 1; }
    100% { opacity: 0; }
  }

  /* Emoji-based city wanderer (rescued NPCs that came home from adventures) */
  .wander.emoji-wander {
    width: 9.75%; min-width: 48px; max-width: 84px;
    aspect-ratio: 1;
    display: flex; align-items: center; justify-content: center;
    font-size: var(--fs-2xl);
    line-height: 1;
    background: none;
    border: none;
    filter: drop-shadow(0 2px 2px rgba(0,0,0,0.25));
  }
  /* Phase 7 — sprite-painted wanderer (kitsune / unicorn / etc. rescued
     NPCs render as their painted portrait instead of the bare emoji). */
  .wander.sprite-wander {
    font-size: 0;            /* hide any leftover text glyph */
    overflow: hidden;
  }
  .wander.sprite-wander img {
    width: 100%; height: 100%;
    object-fit: contain;
    image-rendering: pixelated;
    pointer-events: none;
  }

  /* Scene icon dropped into the expedition zone (campfire, chest, etc.) */
  .scene-icon {
    position: absolute;
    left: 50%; top: 38%;
    transform: translate(-50%, -50%);
    display: flex; flex-direction: column; align-items: center;
    z-index: 8;
    animation: scene-in 0.5s ease-out;
    pointer-events: none;
  }
  .scene-icon .si-emoji {
    font-size: var(--fs-2xl);
    filter: drop-shadow(0 4px 6px rgba(0,0,0,0.4));
    animation: scene-bob 1.6s ease-in-out infinite;
  }
  /* Cupcake projectile — fired one at a time from baker → hero during
     the Cupcake Party encounter. Position is set inline in JS; the
     transition + transform handle the parabolic arc. */
  .cupcake-throw {
    position: absolute;
    z-index: 12;
    font-size: var(--fs-2xl);
    line-height: 1;
    pointer-events: none;
    transform: translate(0, 0);
    will-change: transform;
    filter: drop-shadow(0 3px 4px rgba(0,0,0,0.3));
  }

  /* Painted breakfast food (pancakes / fruit bowl / bread plate) — sit
     near the campfire during morning chat so the meal scene actually
     looks like breakfast instead of just heroes near a flame. */
  .breakfast-food {
    position: absolute;
    width: auto;
    height: 9%;
    min-height: 42px;
    max-height: 70px;
    image-rendering: pixelated;
    pointer-events: none;
    /* z 9 so plates render ABOVE the HP HUD (z 7); were hidden behind it. */
    z-index: 9;
    filter: drop-shadow(0 3px 3px rgba(0,0,0,0.25));
    animation: scene-in 0.45s ease-out backwards, scene-bob 2.4s ease-in-out infinite 0.4s;
  }
  .scene-icon .si-label {
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 10px;
    padding: 3px 9px;
    font-size: var(--fs-md); font-weight: 800;
    color: var(--ink);
    margin-top: 4px;
    box-shadow: 0 2px 0 rgba(0,0,0,0.18);
    white-space: nowrap;
  }
  @keyframes scene-in {
    from { opacity: 0; transform: translate(-50%, -30%) scale(0.4); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  }
  @keyframes scene-bob {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-6px); }
  }

  /* Choice card for interactive encounters (merchant, healer, rescue, etc.) */
  .encounter-card {
    position: absolute;
    left: 50%; top: 50%; transform: translate(-50%, -50%);
    width: min(86%, 380px);
    background: linear-gradient(180deg, #fffaf2, #ffe9c4);
    border: 3px solid var(--ink);
    border-radius: 18px;
    padding: 18px 16px 14px;
    text-align: center;
    box-shadow: 0 8px 0 rgba(0,0,0,0.25), 0 14px 28px rgba(0,0,0,0.35);
    z-index: 30;
    animation: ec-in 0.35s ease-out;
  }
  .encounter-card .ec-emoji {
    font-size: var(--fs-2xl);
    line-height: 1;
    margin-bottom: 6px;
    filter: drop-shadow(0 3px 4px rgba(0,0,0,0.35));
  }
  .encounter-card .ec-title {
    font-size: var(--fs-lg); font-weight: 900;
    color: var(--ink);
    margin-bottom: 8px;
  }
  .encounter-card .ec-body {
    font-size: var(--fs-md);
    color: var(--ink);
    margin-bottom: 14px;
    line-height: 1.4;
  }
  .encounter-card .ec-body p { margin: 0 0 6px; }
  .encounter-card .ec-actions {
    display: flex; gap: 10px; justify-content: center;
    flex-wrap: wrap;
  }
  .encounter-card .ec-actions button {
    flex: 1 1 120px;
    padding: 10px 14px;
    border: 2px solid var(--ink);
    border-radius: 10px;
    font-weight: 800; font-size: var(--fs-lg);
    cursor: pointer;
    background: var(--gold);
    color: var(--ink);
    box-shadow: 0 3px 0 rgba(0,0,0,0.18);
  }
  .encounter-card .ec-actions .ec-no {
    background: #efeaff;
  }
  .encounter-card .ec-actions button:active {
    transform: translateY(2px);
    box-shadow: 0 1px 0 rgba(0,0,0,0.18);
  }
  @keyframes ec-in {
    from { opacity: 0; transform: translate(-50%, -38%) scale(0.7); }
    to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  }

  /* Hero / NPC speech bubble during approach + encounters */
  .speech-bubble {
    position: absolute;
    bottom: 110%; left: 50%; transform: translateX(-50%);
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 16px;
    padding: 9px 14px;
    font-size: var(--fs-lg); font-weight: 700;
    color: var(--ink);
    z-index: 12;
    box-shadow: 0 4px 0 rgba(0,0,0,0.18);
    animation: bubble-in 0.35s ease-out, bubble-out 0.5s ease-in 4s forwards;
    pointer-events: none;
    max-width: 260px;
    min-width: 80px;
    line-height: 1.25;
    text-align: center;
    word-wrap: break-word;
  }
  .speech-bubble.long {
    /* Used for the longer arrival conversations — stays on screen 4.5s */
    animation: bubble-in 0.35s ease-out, bubble-out 0.5s ease-in 4s forwards;
  }
  .speech-bubble::after {
    content: '';
    position: absolute;
    bottom: -10px; left: 50%; transform: translateX(-50%);
    width: 0; height: 0;
    border-left: 8px solid transparent;
    border-right: 8px solid transparent;
    border-top: 11px solid var(--ink);
  }
  .speech-bubble::before {
    content: '';
    position: absolute;
    bottom: -7px; left: 50%; transform: translateX(-50%);
    width: 0; height: 0;
    border-left: 6px solid transparent;
    border-right: 6px solid transparent;
    border-top: 8px solid #fff;
    z-index: 1;
  }
  @keyframes bubble-in {
    from { opacity: 0; transform: translate(-50%, 6px) scale(0.7); }
    to   { opacity: 1; transform: translate(-50%, 0) scale(1); }
  }
  @keyframes bubble-out {
    to { opacity: 0; transform: translate(-50%, -8px) scale(0.95); }
  }

  /* JRPG-style attack-name callout that floats above the attacker.
     Total life is 2.5 s — fast pop-in (0.2 s), long readable hold
     (~1.8 s at full opacity), gentle fade-out (0.4 s). */
  .attack-callout {
    position: absolute;
    bottom: 105%; left: 50%; transform: translateX(-50%);
    background: linear-gradient(180deg, #fff, #ffe7a3);
    border: 2px solid var(--ink);
    border-radius: 8px;
    padding: 3px 10px;
    font-weight: 900; font-size: var(--fs-lg);
    color: var(--ink);
    white-space: nowrap;
    z-index: 11;
    box-shadow: 0 4px 0 rgba(0,0,0,0.18);
    animation: callout-pop 2.5s ease-out forwards;
    pointer-events: none;
  }
  @keyframes callout-pop {
    0%   { opacity: 0; transform: translate(-50%, 8px) scale(0.6); }
    8%   { opacity: 1; transform: translate(-50%, 0) scale(1.15); }
    14%  { transform: translate(-50%, 0) scale(1); }
    85%  { opacity: 1; transform: translate(-50%, 0) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -10px) scale(1); }
  }

  /* Flying attack projectile (emoji-based) */
  .vfx {
    position: absolute;
    font-size: var(--fs-2xl);
    z-index: 10;
    pointer-events: none;
    text-shadow: 0 0 8px currentColor, 0 0 14px currentColor;
    transition: left 0.5s ease-in, top 0.5s ease-in, transform 0.5s ease-in, opacity 0.3s;
    line-height: 1;
  }
  .vfx.spin { animation: vfx-spin 0.6s linear infinite; }
  @keyframes vfx-spin { to { transform: rotate(360deg); } }

  /* Burst on impact */
  .vfx-burst {
    position: absolute;
    z-index: 10;
    pointer-events: none;
    font-size: var(--fs-xl);
    animation: vfx-burst 0.5s ease-out forwards;
  }
  @keyframes vfx-burst {
    0%   { transform: scale(0.4); opacity: 1; }
    60%  { transform: scale(1.6); opacity: 1; }
    100% { transform: scale(2.2); opacity: 0; }
  }

  /* Damage numbers floating up */
  .dmg-num {
    position: absolute;
    font-weight: 900; font-size: var(--fs-2xl);
    color: #fff;
    text-shadow:
      -2px -2px 0 var(--bad), 2px -2px 0 var(--bad),
      -2px 2px 0 var(--bad),  2px 2px 0 var(--bad),
      0 0 8px rgba(255,80,80,0.9);
    pointer-events: none;
    z-index: 9;
    animation: dmg-rise 1.2s ease-out forwards;
    letter-spacing: -0.02em;
  }
  .dmg-num.crit {
    font-size: var(--fs-2xl);
    color: #fff200;
    text-shadow:
      -2px -2px 0 #c33, 2px -2px 0 #c33,
      -2px 2px 0 #c33,  2px 2px 0 #c33,
      0 0 12px #ff0, 0 0 22px #ff0;
    animation: dmg-rise-big 1.4s ease-out forwards;
  }
  .dmg-num.heal {
    color: #fff;
    text-shadow:
      -2px -2px 0 var(--good), 2px -2px 0 var(--good),
      -2px 2px 0 var(--good),  2px 2px 0 var(--good),
      0 0 8px rgba(120,220,120,0.9);
  }
  /* Element-resist labels — yellow WEAK! gets bigger + glows; blue RESIST is dimmer */
  .dmg-num.weak {
    font-size: var(--fs-2xl);
    color: #fff200;
    text-shadow:
      -2px -2px 0 #c33, 2px -2px 0 #c33,
      -2px 2px 0 #c33,  2px 2px 0 #c33,
      0 0 12px #ff0, 0 0 22px #ff0;
    animation: dmg-rise-big 1.4s ease-out forwards;
  }
  .dmg-num.resist {
    font-size: var(--fs-xl);
    color: #cce5ff;
    text-shadow:
      -2px -2px 0 #335, 2px -2px 0 #335,
      -2px 2px 0 #335,  2px 2px 0 #335,
      0 0 8px rgba(120,160,255,0.8);
    opacity: 0.85;
  }
  @keyframes dmg-rise {
    0%   { transform: translate(-50%, 0) scale(1.6); opacity: 1; }
    20%  { transform: translate(-50%, -8px) scale(1); opacity: 1; }
    100% { transform: translate(-50%, -54px) scale(0.85); opacity: 0; }
  }
  @keyframes dmg-rise-big {
    0%   { transform: translate(-50%, 0) scale(0.4); opacity: 0; }
    15%  { transform: translate(-50%, -4px) scale(2.2); opacity: 1; }
    35%  { transform: translate(-50%, -8px) scale(1.2); opacity: 1; }
    100% { transform: translate(-50%, -70px) scale(1); opacity: 0; }
  }
  /* Big-hit screen flash */
  .crit-flash {
    position: absolute; inset: 0;
    background: radial-gradient(ellipse at center, rgba(255,242,0,0.45), rgba(255,80,80,0.1) 60%, transparent 80%);
    pointer-events: none;
    z-index: 8;
    animation: crit-flash 0.6s ease-out forwards;
  }
  @keyframes crit-flash {
    0%   { opacity: 0; }
    20%  { opacity: 1; }
    100% { opacity: 0; }
  }

  /* ==================================================================
     FF6-style spell VFX (Slice C)
     Element-tinted full-screen wash + caster glow + target mark +
     element-specific particles. Each move's `anim` field picks the
     right combination.
     ================================================================== */

  .spell-tint {
    position: absolute; inset: 0;
    pointer-events: none;
    z-index: 8;
    opacity: 0;
    animation: spell-tint-flash 1.1s ease-out forwards;
  }
  .spell-tint.elem-fire    { background: linear-gradient(180deg, rgba(255,90,40,0.55),  rgba(180,50,20,0.35)); }
  .spell-tint.elem-ice     { background: linear-gradient(180deg, rgba(180,230,255,0.55),rgba(80,150,220,0.40)); }
  .spell-tint.elem-thunder { background: linear-gradient(180deg, rgba(255,235,80,0.55), rgba(180,150,40,0.30)); }
  .spell-tint.elem-holy    { background: linear-gradient(180deg, rgba(255,255,220,0.65),rgba(255,220,140,0.35)); }
  .spell-tint.elem-dark    { background: linear-gradient(180deg, rgba(80,40,140,0.55),  rgba(20,10,40,0.50)); }
  .spell-tint.elem-water   { background: linear-gradient(180deg, rgba(80,160,220,0.50), rgba(40,80,160,0.40)); }
  .spell-tint.elem-wind    { background: linear-gradient(180deg, rgba(220,255,240,0.45),rgba(140,220,200,0.30)); }
  .spell-tint.elem-earth   { background: linear-gradient(180deg, rgba(180,130,80,0.45), rgba(120,80,40,0.40)); }
  .spell-tint.elem-heal    { background: linear-gradient(180deg, rgba(180,255,180,0.40),rgba(100,200,140,0.30)); }
  .spell-tint.elem-nature  { background: linear-gradient(180deg, rgba(180,255,150,0.40),rgba(255,180,220,0.30)); }
  .spell-tint.elem-physical{ background: linear-gradient(180deg, rgba(255,255,255,0.20),rgba(220,220,220,0.10)); }
  @keyframes spell-tint-flash {
    0%   { opacity: 0; }
    20%  { opacity: 1; }
    70%  { opacity: 1; }
    100% { opacity: 0; }
  }

  /* Caster glow ring — pulses around the active actor for ~600ms */
  /* IMPORTANT — this rule used to set `position: relative` on the
     casting wrapper, which OVERRODE the `position: absolute` baseline
     and "teleported" the unit to the top of #expedition mid-attack.
     The pseudo-element ::before below positions itself against the
     wrapper just fine via the existing absolute positioning context,
     so we can drop the override entirely. */
  .battler.casting, .encounter-actor.casting { /* no override */ }
  .battler.casting::before, .encounter-actor.casting::before {
    content: '';
    position: absolute;
    inset: -10px;
    border-radius: 50%;
    border: 3px solid var(--glow, #ffe085);
    box-shadow: 0 0 14px var(--glow, #ffe085), inset 0 0 14px var(--glow, #ffe085);
    animation: caster-pulse 0.7s ease-out;
    pointer-events: none;
    z-index: 7;
  }
  @keyframes caster-pulse {
    0%   { transform: scale(0.7); opacity: 0; }
    30%  { transform: scale(1.15); opacity: 1; }
    100% { transform: scale(1.7); opacity: 0; }
  }

  /* Target crosshair mark — flashes briefly before impact */
  .battler.targeted::after, .encounter-actor.targeted::after {
    content: '';
    position: absolute;
    inset: -8px;
    border-radius: 50%;
    border: 3px dashed var(--mark, #ff5566);
    animation: target-mark-pulse 0.45s ease-out 2;
    pointer-events: none;
    z-index: 7;
  }
  @keyframes target-mark-pulse {
    0%   { transform: scale(0.5); opacity: 0; }
    40%  { transform: scale(1); opacity: 1; }
    100% { transform: scale(1.25); opacity: 0; }
  }

  /* Generic element particle — stream / fall / strike all use this */
  .elem-particle {
    position: absolute;
    font-size: var(--fs-2xl);
    pointer-events: none;
    z-index: 9;
    line-height: 1;
    filter: drop-shadow(0 2px 3px rgba(0,0,0,0.45));
    will-change: transform, left, top, opacity;
  }

  /* Slash arc — diagonal white cut across target (physical attacks) */
  .slash-arc {
    position: absolute;
    width: 90px; height: 6px;
    background: linear-gradient(90deg, transparent, #fff 30%, #fff 70%, transparent);
    box-shadow: 0 0 10px #fff, 0 0 20px rgba(255,255,255,0.6);
    z-index: 9;
    pointer-events: none;
    transform-origin: center;
    animation: slash-cut 0.45s ease-out forwards;
  }
  @keyframes slash-cut {
    0%   { opacity: 0; transform: rotate(-45deg) scaleX(0.2); }
    25%  { opacity: 1; transform: rotate(-45deg) scaleX(1.4); }
    100% { opacity: 0; transform: rotate(-45deg) scaleX(1.6); }
  }

  /* Lightning bolt — vertical zigzag SVG-style via stacked clip-path */
  .lightning-bolt {
    position: absolute;
    width: 16px;
    height: 80%;
    top: 0; bottom: 20%;
    background: linear-gradient(180deg, #ffff66, #fff, #ffff66);
    box-shadow: 0 0 18px #ffe085, 0 0 36px #ffff66;
    z-index: 10;
    pointer-events: none;
    clip-path: polygon(20% 0%, 80% 25%, 30% 45%, 80% 70%, 20% 100%, 60% 60%, 30% 50%, 60% 30%);
    animation: lightning-flicker 0.45s steps(3) forwards;
  }
  @keyframes lightning-flicker {
    0%, 100% { opacity: 1; }
    33%      { opacity: 0.4; }
    66%      { opacity: 1; }
  }

  /* Heal column — vertical green sparkle waterfall onto an ally */
  .heal-column {
    position: absolute;
    top: -40%; bottom: -10%;
    width: 70%;
    left: 15%;
    background: linear-gradient(180deg,
      rgba(180,255,180,0) 0%,
      rgba(180,255,180,0.55) 30%,
      rgba(120,220,120,0.65) 70%,
      rgba(180,255,180,0) 100%);
    z-index: 6;
    pointer-events: none;
    animation: heal-wash 1.4s ease-out forwards;
    border-radius: 50% 50% 30% 30% / 30% 30% 70% 70%;
    filter: blur(2px);
  }
  @keyframes heal-wash {
    0%   { opacity: 0; transform: scaleY(0.4) translateY(-30%); }
    25%  { opacity: 1; transform: scaleY(1) translateY(0); }
    100% { opacity: 0; transform: scaleY(1) translateY(20%); }
  }

  /* Buff aura — colored ring that pulses around caster after a self-buff */
  .buff-aura {
    position: absolute;
    inset: -12px;
    border-radius: 50%;
    border: 4px solid var(--aura, #ffe085);
    box-shadow: 0 0 16px var(--aura, #ffe085);
    z-index: 7;
    pointer-events: none;
    animation: buff-pulse 1.2s ease-out forwards;
  }
  @keyframes buff-pulse {
    0%   { transform: scale(0.6); opacity: 0; }
    30%  { transform: scale(1.1); opacity: 1; }
    100% { transform: scale(1.5); opacity: 0; }
  }
  /* Camera shake on crits */
  #expedition.shake { animation: cam-shake 0.4s; }
  @keyframes cam-shake {
    0%, 100% { transform: translate(0, 0); }
    20%      { transform: translate(-6px, 4px); }
    40%      { transform: translate(5px, -3px); }
    60%      { transform: translate(-4px, -3px); }
    80%      { transform: translate(3px, 5px); }
  }

  /* Battle banner */
  .battle-banner {
    position: absolute; top: 32px; left: 50%; transform: translateX(-50%);
    background: rgba(28, 8, 60, 0.7);
    border: 3px solid var(--gold);
    border-radius: 999px;
    padding: 8px 24px;
    color: #fff;
    font-weight: 800; font-size: var(--fs-2xl);
    z-index: 9;
    box-shadow: 0 3px 0 rgba(0,0,0,0.2);
  }
  .battle-banner.win { background: rgba(110,211,152,0.88); }
  .battle-banner.lose { background: rgba(255,123,123,0.88); }

  /* Walking-up-from-city dispatch */
  .dispatch {
    position: fixed;
    z-index: 25;
    pointer-events: none;
    image-rendering: pixelated;
    filter: drop-shadow(0 3px 3px rgba(0,0,0,0.3));
    width: 30px; height: auto;
    /* Slow walk-in animation; matches the leisurely SNES-FF arrival pacing */
    transition: left 2.4s ease-out, top 2.4s ease-out, width 2.4s ease-out;
  }

  /* ---------- CITY (bottom 50%, with 4 tabbed districts) ----------
     The kid asked for the city to be much bigger and easier to see, so it
     now occupies half the viewport. During active battle a body.battle-active
     class shrinks it to ~30% so combat has room; tabs remain visible. */
  #city {
    position: relative; flex: 0 0 50%; min-height: 320px;
    overflow: hidden;
    transition: flex-basis 0.6s ease, flex-grow 0.6s ease;
    /* Per-district background lives on .city-district panels now; #city itself
       just shows a fallback gradient if no district is active */
    background: linear-gradient(180deg, #ffe6d4, #d6f3c4);
  }
  body.battle-active #city {
    flex: 0 0 30%;
  }

  /* District tab bar — sits on top of the city, four stacked tabs */
  .city-tabs-bar {
    position: absolute;
    top: 0; left: 0; right: 0;
    display: flex;
    gap: 3px;
    padding: 4px 6px 0 6px;
    z-index: 8;
    background: linear-gradient(180deg, rgba(255,255,255,0.9), rgba(255,255,255,0.6) 70%, transparent);
    backdrop-filter: blur(2px);
    pointer-events: auto;
  }
  .city-tab {
    flex: 1;
    min-width: 0;
    background: rgba(255,255,255,0.85);
    border: 2px solid var(--ink);
    border-bottom: none;
    border-radius: 12px 12px 0 0;
    padding: 8px 4px;
    /* Tabs use a tighter font scale than the rest of the UI — at 5 tabs
       on a 768-1024px tablet, --fs-lg (max 30px) truncated "Main Square"
       and "Forest" to "Main S..." / "For..." Switching to a fluid token
       capped at 18px keeps every label whole down to phone width. */
    font-size: clamp(11px, 1.6vw + 0.4rem, 18px); font-weight: 800;
    color: var(--ink);
    cursor: pointer;
    transition: background 0.18s, transform 0.15s, color 0.18s;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .city-tab:hover, .city-tab:active {
    transform: translateY(-1px);
    filter: brightness(1.05);
  }
  .city-tab.active {
    background: var(--accent);
    color: white;
    transform: translateY(-1px);
    box-shadow: 0 -3px 0 rgba(0,0,0,0.12);
  }

  /* Floating Dress-Up button — top-right of the city panel, always
     accessible no matter which tab is in view. */
  .city-floating-btn {
    position: absolute;
    top: 8px; right: 12px;
    width: 56px; height: 56px;
    background: var(--accent);
    color: #fff;
    border: 3px solid var(--ink);
    border-radius: 50%;
    font-size: var(--fs-lg); line-height: 1;
    cursor: pointer;
    z-index: 11;
    box-shadow: 0 4px 0 var(--ink);
    transition: transform 0.15s, box-shadow 0.15s;
  }
  .city-floating-btn:active {
    transform: translateY(2px);
    box-shadow: 0 2px 0 var(--ink);
  }
  /* Wrapper that holds 4 districts side-by-side; only one visible at a
     time. Slides via translateX based on which tab is active. */
  .city-districts {
    position: absolute;
    top: 38px;       /* below the tab bar */
    left: 0; bottom: 0;
    height: calc(100% - 38px);
    display: flex;
    width: 500%;     /* 5 districts × 100% each, overflows #city */
    transition: transform 0.45s ease-in-out;
    will-change: transform;
  }
  .city-district {
    flex: 0 0 20%;   /* 20% of 500% = 100% of #city */
    position: relative;
    overflow: hidden;
  }
  .city-district[data-district="units"] {
    background: linear-gradient(180deg, #fff4e0 0%, #ffd6e8 60%, #f5b8d6 100%);
    overflow-y: auto;
  }
  /* Units roster panel — scrollable list of every hero with portrait,
     level, XP bar, HP/MP and the 3 equipment slots. Tap a card to
     open the hero detail modal (#heroov). */
  #units-roster {
    padding: 8px 10px;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 8px;
  }
  .unit-card {
    background: rgba(255,255,255,0.92);
    border: 3px solid var(--ink, #4a3268);
    border-radius: 14px;
    padding: 8px;
    cursor: pointer;
    text-align: center;
    box-shadow: 0 3px 0 rgba(0,0,0,0.15);
    transition: transform 0.12s, box-shadow 0.12s;
    display: flex; flex-direction: column; align-items: center; gap: 4px;
  }
  .unit-card:hover, .unit-card:active {
    transform: translateY(-2px);
    box-shadow: 0 5px 0 rgba(0,0,0,0.18);
  }
  .unit-card .uc-portrait {
    width: 64px; height: 64px;
    object-fit: contain;
    image-rendering: pixelated;
    filter: drop-shadow(0 2px 2px rgba(0,0,0,0.2));
  }
  .unit-card .uc-name {
    font-size: var(--fs-md); font-weight: 800;
    color: var(--ink);
    line-height: 1.1;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    max-width: 100%;
  }
  .unit-card .uc-class {
    font-size: var(--fs-md); font-weight: 700;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.6px;
  }
  .unit-card .uc-lvl {
    background: var(--accent);
    color: #fff;
    font-size: var(--fs-md); font-weight: 900;
    border-radius: 999px;
    padding: 1px 7px;
  }
  .unit-card .uc-xp {
    width: 100%;
    height: 5px;
    background: rgba(0,0,0,0.12);
    border-radius: 3px;
    overflow: hidden;
    margin-top: 1px;
  }
  .unit-card .uc-xp > i {
    display: block; height: 100%;
    background: linear-gradient(90deg, var(--good, #6ed398), var(--gold, #ffd166));
  }
  .unit-card .uc-bars {
    width: 100%;
    display: flex; flex-direction: column; gap: 2px;
    font-size: var(--fs-sm); font-weight: 700;
    color: var(--ink-soft);
    margin-top: 2px;
  }
  .unit-card .uc-bar {
    display: flex; align-items: center; gap: 4px;
  }
  .unit-card .uc-bar .uc-label { width: 18px; }
  .unit-card .uc-bar .uc-track {
    flex: 1; height: 4px;
    background: rgba(0,0,0,0.12);
    border-radius: 2px;
    overflow: hidden;
  }
  .unit-card .uc-bar .uc-track > i { display: block; height: 100%; }
  .unit-card .uc-bar.hp .uc-track > i { background: linear-gradient(90deg, #58dd66, #aae696); }
  .unit-card .uc-bar.mp .uc-track > i { background: linear-gradient(90deg, #66aaff, #aaccff); }
  .unit-card .uc-stats {
    width: 100%;
    display: flex; justify-content: space-around;
    font-size: var(--fs-sm); font-weight: 800;
    color: var(--ink);
    margin-top: 2px;
  }
  .unit-card .uc-gear {
    width: 100%;
    display: flex; justify-content: space-around;
    font-size: var(--fs-lg);
    margin-top: 2px;
  }
  .unit-card .uc-gear .uc-slot {
    background: rgba(0,0,0,0.06);
    border-radius: 6px;
    padding: 2px 4px;
    line-height: 1;
    min-width: 22px;
  }
  .unit-card .uc-gear .uc-slot.empty {
    color: rgba(0,0,0,0.25);
    font-size: var(--fs-md);
  }
  /* Phase 7 — rename pip on every unit card. Tap → opens rename dialog. */
  .unit-card .uc-rename {
    position: absolute;
    top: 4px; left: 4px;
    width: 28px; height: 28px;
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 50%;
    font-size: var(--fs-sm); line-height: 1;
    cursor: pointer;
    box-shadow: 0 2px 0 rgba(0,0,0,0.18);
    padding: 0;
    display: flex; align-items: center; justify-content: center;
    z-index: 2;
  }
  .unit-card .uc-rename:hover, .unit-card .uc-rename:active {
    transform: scale(1.1);
    background: var(--accent-2);
  }
  /* Phase 7 — rename dialog. Reuses .page-choice-ov + .encounter-card. */
  .rename-ov .rename-card {
    background: linear-gradient(180deg, #fff7e8 0%, #ffe6f2 100%);
    border: 4px solid var(--accent);
    box-shadow: 0 8px 0 var(--ink);
    max-width: 420px;
  }
  .rename-ov .ec-actions {
    display: flex; flex-direction: column; gap: 8px;
  }
  /* Status: deployed / on-expedition heroes show a small badge */
  .unit-card.deployed::after {
    content: "🗡 on quest";
    position: absolute;
    top: 4px; right: 4px;
    background: var(--accent);
    color: #fff;
    font-size: var(--fs-sm); font-weight: 800;
    padding: 1px 5px;
    border-radius: 6px;
  }
  #units-empty {
    grid-column: 1 / -1;
    text-align: center;
    color: var(--ink-soft);
    font-size: var(--fs-md);
    padding: 20px 10px;
  }
  .city-district[data-district="main-square"] {
    background:
      url('./assets/city-panorama.webp') center bottom / cover no-repeat,
      linear-gradient(180deg, #ffe6d4, #d6f3c4);
  }
  .city-district[data-district="market"] {
    background:
      url('./assets/district-market.webp') center bottom / cover no-repeat,
      linear-gradient(180deg, #fff4e0 0%, #ffe2c2 60%, #f4cba8 100%);
  }
  .city-district[data-district="castle"] {
    background:
      url('./assets/district-castle.webp') center bottom / cover no-repeat,
      linear-gradient(180deg, #e8e4ff 0%, #d6cfff 60%, #b9adff 100%);
  }
  .city-district[data-district="forest"] {
    background:
      url('./assets/district-forest.webp') center bottom / cover no-repeat,
      linear-gradient(180deg, #e8f4d6 0%, #c4e0a8 60%, #94c378 100%);
  }
  /* "Coming soon" centered placeholder for districts without buildings yet.
     Hidden once that district actually has any .city-bldg rendered. */
  .district-coming-soon {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center; justify-content: center;
    gap: 6px;
    pointer-events: none;
    text-align: center;
    padding: 10px;
  }
  .city-district:has(.city-bldg) .district-coming-soon {
    display: none;
  }
  .district-coming-soon .dcs-emoji {
    font-size: var(--fs-2xl);
    line-height: 1;
    filter: drop-shadow(0 4px 4px rgba(0,0,0,0.18));
  }
  .district-coming-soon .dcs-title {
    font-size: var(--fs-xl); font-weight: 900;
    color: var(--ink);
    text-shadow: 0 2px 0 rgba(255,255,255,0.7);
  }
  .district-coming-soon .dcs-sub {
    font-size: var(--fs-md);
    color: var(--ink-soft);
  }

  .city-bldg {
    position: absolute;
    width: 18%;
    bottom: 8%;
    cursor: pointer;
    transition: transform 0.15s, filter 0.2s;
    filter: drop-shadow(0 4px 4px rgba(0,0,0,0.15));
    z-index: 5;
    background: none; border: none; padding: 0;
  }
  .city-bldg img {
    width: 100%; height: auto; display: block;
    image-rendering: pixelated;
  }
  .city-bldg:hover, .city-bldg:active {
    transform: translateY(-3px);
    filter: drop-shadow(0 6px 8px rgba(0,0,0,0.25));
  }
  .city-bldg .tier-badge {
    position: absolute; top: -4px; right: -4px;
    background: var(--gold);
    border: 2px solid var(--ink);
    border-radius: 999px;
    font-size: var(--fs-md); font-weight: 900;
    padding: 2px 6px;
    color: var(--ink);
    box-shadow: 0 2px 0 rgba(0,0,0,0.15);
  }
  .city-bldg .unit-count {
    position: absolute; bottom: -6px; left: 50%; transform: translateX(-50%);
    background: var(--accent); color: #fff;
    border: 2px solid #fff;
    border-radius: 999px;
    font-size: var(--fs-md); font-weight: 900;
    padding: 1px 7px;
    white-space: nowrap;
    box-shadow: 0 2px 0 rgba(0,0,0,0.15);
  }
  .city-bldg.training::after {
    content: "⏱";
    position: absolute; top: -6px; left: -4px;
    background: var(--accent);
    color: #fff;
    border-radius: 999px;
    font-size: var(--fs-md);
    width: 22px; height: 22px;
    display: flex; align-items: center; justify-content: center;
    border: 2px solid #fff;
    animation: pulse 1.4s infinite ease-in-out;
  }
  /* Empty plot — dashed silhouette + "+ Build" badge */
  .city-bldg.empty-plot {
    background: rgba(255,255,255,0.55);
    border: 3px dashed rgba(0,0,0,0.45);
    border-radius: 12px;
    padding: 8px 4px 6px;
    aspect-ratio: 1 / 1;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    filter: none;
    box-shadow: inset 0 0 0 4px rgba(255,255,255,0.5);
  }
  .city-bldg.empty-plot:hover, .city-bldg.empty-plot:active {
    background: rgba(255,255,200,0.8);
    transform: translateY(-3px);
  }
  .city-bldg.empty-plot .plot-silhouette {
    font-size: var(--fs-xl);
    opacity: 0.35;
    line-height: 1;
  }
  .city-bldg.empty-plot .plot-label {
    font-size: var(--fs-md); font-weight: 800;
    color: var(--ink);
    background: var(--gold);
    padding: 2px 6px;
    border-radius: 6px;
    border: 1.5px solid var(--ink);
    margin-top: 4px;
  }
  .city-bldg.empty-plot .plot-cost {
    font-size: var(--fs-md); font-weight: 700;
    color: var(--ink);
    margin-top: 2px;
  }

  /* First-launch starter picker */
  #starterov {
    position: fixed; inset: 0; z-index: 80;
    background: rgba(28, 8, 60, 0.7);
    display: none;
    align-items: center; justify-content: center;
    padding: 12px;
  }
  #starterov.show { display: flex; }
  #starterov .ov-card {
    width: min(540px, 100%);
    max-height: 90vh; overflow-y: auto;
    background: linear-gradient(180deg, #fffaf2, #ffe9c4);
    border: 3px solid var(--ink);
    border-radius: 18px;
    padding: 18px 16px;
    box-shadow: 0 8px 0 rgba(0,0,0,0.25), 0 14px 28px rgba(0,0,0,0.4);
  }
  #starterov h2 {
    margin: 0 0 4px;
    text-align: center;
    font-size: var(--fs-xl);
    color: var(--ink);
  }
  #starterov p.intro {
    text-align: center;
    margin: 0 0 12px;
    font-size: var(--fs-md);
    color: var(--ink);
  }
  #starterov-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 10px;
  }
  .starter-card {
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 12px;
    padding: 10px 8px;
    text-align: center;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0,0,0,0.18);
  }
  .starter-card:hover, .starter-card:active {
    background: #fff8d8;
    transform: translateY(-2px);
    box-shadow: 0 5px 0 rgba(0,0,0,0.2);
  }
  .starter-card .sc-emoji { font-size: var(--fs-2xl); line-height: 1; }
  .starter-card .sc-name { font-weight: 900; font-size: var(--fs-md); margin: 4px 0 2px; }
  .starter-card .sc-creature { font-size: var(--fs-md); color: var(--ink-soft); margin-bottom: 6px; }
  .starter-card .sc-pick {
    background: var(--gold);
    border: 2px solid var(--ink);
    border-radius: 8px;
    padding: 4px 6px;
    font-weight: 800; font-size: var(--fs-md);
  }

  /* Page-level choice card overlay (for build-plot prompts) */
  .page-choice-ov {
    position: fixed; inset: 0; z-index: 75;
    background: rgba(28, 8, 60, 0.55);
    display: flex; align-items: center; justify-content: center;
    padding: 16px;
  }
  .page-choice-ov .encounter-card {
    position: static; transform: none;
    /* .encounter-card's base rule sets left/top: 50% for the absolute
       positioning it uses outside an .ov backdrop. Inside .page-choice-ov
       we flex-center instead, so those offsets must be cleared — otherwise
       any overlay that re-promotes the card to position: relative (e.g.
       .boutique-ov .boutique-card) inherits the 50%/50% offsets and the
       modal renders shifted halfway off-screen. */
    left: auto; top: auto;
    animation: ec-in 0.3s ease-out;
  }
  /* Phase 7 — King's "donate gold to the city" slider dialog. */
  .king-ov .king-card {
    background: linear-gradient(180deg, #fff8d6 0%, #ffe39a 60%, #ffd76a 100%);
    border: 4px solid var(--ink);
    box-shadow: 0 8px 0 rgba(0,0,0,0.25), 0 0 28px rgba(255, 200, 60, 0.55);
    max-width: 440px;
  }
  .king-ov .king-card .ec-title { color: #6a430a; }
  /* Phase 7 — actual painted king portrait at the top of the dialog
     (replaces the 🤴👑 emoji that was kid-feedback "doesn't look like
     the king who lives in the city"). */
  .king-ov .king-portrait {
    display: block;
    width: 128px; height: 128px;
    object-fit: contain;
    image-rendering: pixelated;
    margin: 8px auto 4px;
    filter: drop-shadow(0 4px 6px rgba(0,0,0,0.35));
  }
  .king-slider-wrap {
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 16px;
    padding: 12px 14px 10px;
    margin: 10px 0;
    box-shadow: inset 0 2px 0 rgba(0,0,0,0.06);
  }
  #king-slider {
    width: 100%;
    -webkit-appearance: none;
    appearance: none;
    height: 14px;
    background: linear-gradient(90deg, #ffd86b 0%, #c97e00 100%);
    border: 2px solid var(--ink);
    border-radius: 999px;
    outline: none;
    cursor: pointer;
  }
  #king-slider::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 32px; height: 32px;
    background: radial-gradient(circle at 35% 35%, #fff7d0 0%, #ffd166 60%, #c97e00 100%);
    border: 3px solid var(--ink);
    border-radius: 50%;
    box-shadow: 0 3px 0 rgba(0,0,0,0.25);
    cursor: grab;
  }
  #king-slider::-moz-range-thumb {
    width: 32px; height: 32px;
    background: radial-gradient(circle at 35% 35%, #fff7d0 0%, #ffd166 60%, #c97e00 100%);
    border: 3px solid var(--ink);
    border-radius: 50%;
    box-shadow: 0 3px 0 rgba(0,0,0,0.25);
    cursor: grab;
  }
  .king-slider-row {
    display: flex; align-items: center; justify-content: center; gap: 10px;
    margin-top: 8px;
    font-size: var(--fs-md); font-weight: 800; color: var(--ink);
  }
  .king-slider-pct   { color: var(--accent); font-size: var(--fs-lg); }
  .king-slider-eq    { color: var(--ink-soft); }
  .king-slider-coins { color: #c97e00; }
  .king-ov .ec-actions {
    display: flex; flex-direction: column; gap: 8px;
  }
  .king-ov .ec-yes:disabled {
    opacity: 0.55;
    cursor: not-allowed;
    background: linear-gradient(180deg, #ddd, #bbb);
  }
  /* Princess-Run-style full-bleed title screen — painted hero art that
     doubles as the asset-loading curtain. Replaces the small card-style
     splash with a proper hero scene. */
  .mr-title-screen {
    position: fixed; inset: 0;
    z-index: 9000;
    display: grid; place-items: center;
    transition: opacity 0.5s ease;
    background: #1a0a3a;
    overflow: hidden;
  }
  .mr-title-screen.hide {
    opacity: 0;
    pointer-events: none;
  }
  .mr-title-screen #title-art {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    object-position: center;
    z-index: 0;
    pointer-events: none;
  }
  .mr-title-screen #title-text {
    position: absolute; z-index: 1;
    left: 50%; top: 6vh;
    transform: translateX(-50%);
    text-align: center;
    pointer-events: none;
    user-select: none;
    line-height: 0.9;
    font-family: "Fredoka", "Comic Sans MS", system-ui, sans-serif;
    font-weight: 800;
    width: max-content;
  }
  .mr-title-screen #title-text .row1,
  .mr-title-screen #title-text .row2 {
    display: block;
    color: #fff7d6;
    letter-spacing: 2px;
    text-shadow:
      -3px 0 0 #3a1a5a, 3px 0 0 #3a1a5a,
      0 -3px 0 #3a1a5a, 0 3px 0 #3a1a5a,
      -3px -3px 0 #3a1a5a, 3px 3px 0 #3a1a5a,
      -3px 3px 0 #3a1a5a, 3px -3px 0 #3a1a5a,
      0 6px 0 rgba(0,0,0,0.35),
      0 12px 22px rgba(0,0,0,0.55);
    animation: mr-title-bob 2.4s ease-in-out infinite alternate;
  }
  .mr-title-screen #title-text .row1 {
    font-size: clamp(26px, 7vw, 84px);
    color: #ffd1f3;
    margin-bottom: 4px;
  }
  .mr-title-screen #title-text .row2 {
    font-size: clamp(44px, 12vw, 144px);
    color: #ffe18f;
    letter-spacing: 5px;
    animation-delay: 0.2s;
  }
  @keyframes mr-title-bob {
    from { transform: translateY(0)    rotate(-1deg); }
    to   { transform: translateY(-6px) rotate(1deg); }
  }
  .mr-title-screen .mr-title-actions {
    position: absolute; z-index: 1;
    left: 50%; bottom: 9vh;
    transform: translateX(-50%);
    display: flex; flex-direction: column;
    align-items: center; gap: 14px;
  }
  .mr-title-screen .wood-btn {
    appearance: none;
    border: none;
    cursor: pointer;
    font-family: "Georgia", "Times New Roman", serif;
    font-weight: 900;
    font-size: clamp(20px, 3.4vw, 34px);
    letter-spacing: 0.06em;
    color: #4a2a14;
    text-shadow: 0 1px 0 rgba(255, 240, 210, 0.7);
    padding: 0.5em 1.6em;
    border-radius: 18px;
    background: linear-gradient(180deg, #fbe6b1 0%, #f1c878 45%, #d99a3f 100%);
    box-shadow:
      0 0 0 4px #4a2a14,
      0 0 0 7px #b6803a,
      inset 0 4px 0 rgba(255, 250, 220, 0.65),
      inset 0 -6px 8px rgba(120, 60, 20, 0.45),
      0 8px 14px rgba(0, 0, 0, 0.45);
    transition: transform 0.12s ease;
  }
  .mr-title-screen .wood-btn:hover  { transform: translateY(-2px); }
  .mr-title-screen .wood-btn:active { transform: translateY(2px); }
  .mr-title-screen .wood-btn.primary {
    background: linear-gradient(180deg, #ffd86b 0%, #ffb320 50%, #c9780f 100%);
  }
  /* Phase 7 — Full-screen painted cutscene (throne room).
     Replaces the popup-style modal. Three layers:
       .cs-bg        — painted throne-room webp filling the viewport
       .cs-vignette  — soft dark vignette around the edges
       .cs-stage     — queen + princess sprites positioned left/right
       .cs-box       — RPG-style dialogue panel pinned to the bottom
  */
  .cutscene-scene {
    position: fixed; inset: 0;
    z-index: 200;
    overflow: hidden;
    background: #2b1140;
    animation: cs-fade-in 0.5s ease-out;
  }
  .cutscene-scene.fade-out { animation: cs-fade-out 0.5s ease-in forwards; }
  @keyframes cs-fade-in  { from { opacity: 0; } to { opacity: 1; } }
  @keyframes cs-fade-out { from { opacity: 1; } to { opacity: 0; } }
  .cutscene-scene .cs-bg {
    position: absolute; inset: 0;
    background: url('./assets/cutscene-throne.webp') center center / cover no-repeat;
    image-rendering: pixelated;
  }
  .cutscene-scene .cs-vignette {
    position: absolute; inset: 0;
    pointer-events: none;
    background:
      radial-gradient(ellipse at center, transparent 35%, rgba(20, 5, 50, 0.55) 100%);
  }
  .cutscene-scene .cs-stage {
    /* Phase 7 — full-viewport stage so we can drop the princess down
       to just above the dialogue box without dragging the queen with
       her. Each character anchors with its own explicit `bottom`. */
    position: absolute; inset: 0;
    pointer-events: none;
  }
  .cutscene-scene .cs-char {
    position: absolute;
    /* `bottom` is overridden per-character (cs-left vs cs-right) so the
       queen can stand on the throne while the princess kneels on the
       carpet below. */
    width: 32%;
    max-width: 220px;
    height: 36%;
    max-height: 280px;
    object-fit: contain;
    object-position: center bottom;
    image-rendering: pixelated;
    filter: drop-shadow(0 6px 6px rgba(0,0,0,0.35));
    transition: opacity 0.45s ease, transform 0.45s ease, filter 0.45s ease;
    opacity: 1;
  }
  .cutscene-scene .cs-char.active {
    /* Speaker spotlight — golden glow + tiny zoom. No opacity changes
       so the other character stays fully visible per kid feedback. */
    filter:
      drop-shadow(0 0 18px rgba(255, 220, 130, 0.85))
      drop-shadow(0 6px 6px rgba(0,0,0,0.35));
  }
  .cutscene-scene .cs-char.dim {
    /* Phase 7 — listener stays fully opaque (Katherine called the
       fade-out "weird"). Just a tiny dim filter so the speaker pops
       a bit more, but never any transparency. */
    filter: drop-shadow(0 4px 4px rgba(0,0,0,0.4)) brightness(0.95);
  }
  /* Phase 7 — queen anchored at the exact horizontal center, standing
     in front of the throne. Princess kneels MUCH lower (right above
     the dialogue box) and to the right. Princess on top layer.
  */
  .cutscene-scene .cs-left {
    left: 50%;
    bottom: 32%;      /* upper area, on the throne dais */
    transform: translateX(-50%);
    z-index: 1;
  }
  .cutscene-scene .cs-left.active { transform: translateX(-50%) scale(1.04); }
  .cutscene-scene .cs-left.dim    { transform: translateX(-50%) scale(0.98); }
  .cutscene-scene .cs-right {
    /* Phase 7 — princess is 2× the base cs-char size so she reads as
       the hero of the scene. Width drives display because the source
       sprite is 1:1 and `object-fit:contain` fits to the smaller
       container dimension. */
    width: 64%;
    max-width: 360px;
    height: 64%;
    max-height: 540px;
    left: 50%;
    bottom: 22%;      /* just above the dialogue box (which is min 180px ≈ 22%) */
    transform: translateX(-10%) scale(0.96);
    transform-origin: center bottom;
    z-index: 2;       /* princess always renders ABOVE the queen */
  }
  .cutscene-scene .cs-right.active { transform: translateX(-10%) scale(1.04); }
  .cutscene-scene .cs-right.dim    { transform: translateX(-10%) scale(0.94); }
  .cutscene-scene .cs-heroes-row[hidden] { display: none; }
  .cutscene-scene .cs-heroes-row {
    position: absolute;
    left: 50%; bottom: 0;
    transform: translateX(-50%);
    display: flex; gap: 16px;
  }
  .cutscene-scene .cs-hero {
    background: rgba(255,255,255,0.92);
    border: 3px solid var(--ink);
    border-radius: 18px;
    padding: 8px 6px 6px;
    width: 110px;
    box-shadow: 0 6px 0 rgba(0,0,0,0.25);
    text-align: center;
    animation: cs-hero-pop 0.55s cubic-bezier(.55,1.4,.4,1.0) backwards;
  }
  @keyframes cs-hero-pop {
    0%   { transform: translateY(40px) scale(0.6); opacity: 0; }
    70%  { transform: translateY(-4px) scale(1.08); opacity: 1; }
    100% { transform: translateY(0) scale(1); opacity: 1; }
  }
  .cutscene-scene .cs-hero-img {
    width: 72px; height: 72px;
    object-fit: contain;
    image-rendering: pixelated;
    display: block;
    margin: 0 auto;
    filter: drop-shadow(0 3px 3px rgba(0,0,0,0.25));
  }
  .cutscene-scene .cs-hero-emoji { font-size: var(--fs-2xl); line-height: 1; }
  .cutscene-scene .cs-hero-name { font-size: var(--fs-sm); font-weight: 800; margin-top: 4px; }
  .cutscene-scene .cs-hero-role { font-size: var(--fs-xs); color: var(--ink-soft); margin-top: 2px; }

  /* Dialogue box pinned to the bottom — JRPG textbox style. */
  .cutscene-scene .cs-box {
    position: absolute;
    left: 10px; right: 10px; bottom: 10px;
    background: linear-gradient(180deg, rgba(255,247,225,0.97) 0%, rgba(255,222,235,0.97) 100%);
    border: 5px solid var(--ink);
    border-radius: 22px;
    box-shadow: 0 8px 0 rgba(0,0,0,0.45);
    /* Phase 7 — extra-large dialogue text for the 7-year-old. Bottom
       padding leaves room for the advance button which sits in the
       lower-right corner (full-flow below the line text). */
    padding: 20px 22px 78px 22px;
    cursor: pointer;
    user-select: none;
    min-height: 180px;
    animation: cs-box-in 0.4s ease-out;
  }
  @keyframes cs-box-in {
    from { transform: translateY(20px); opacity: 0; }
    to   { transform: translateY(0); opacity: 1; }
  }
  .cutscene-scene .cs-name-tag {
    display: inline-block;
    background: var(--accent);
    color: #fff;
    padding: 4px 16px;
    border-radius: 999px;
    font-weight: 800;
    font-size: var(--fs-lg);
    margin-bottom: 8px;
    box-shadow: 0 3px 0 rgba(0,0,0,0.3);
  }
  .cutscene-scene .cs-line {
    margin: 6px 0 0;
    font-size: var(--fs-lg);
    color: var(--ink);
    line-height: 1.25;
    font-weight: 600;
    transition: opacity 0.35s ease;
  }
  .cutscene-scene .cs-advance {
    position: absolute;
    right: 16px; bottom: 14px;
    padding: 10px 20px;
    font-family: inherit;
    font-weight: 800;
    font-size: var(--fs-md);
    background: linear-gradient(180deg, #ffd166, #ffb347);
    color: var(--ink);
    border: 3px solid var(--ink);
    border-radius: 14px;
    cursor: pointer;
    box-shadow: 0 5px 0 rgba(0,0,0,0.22);
    transition: transform 0.12s;
    white-space: nowrap;
    max-width: calc(100% - 32px);
  }
  .cutscene-scene .cs-advance:hover, .cutscene-scene .cs-advance:active {
    transform: translateY(-2px);
  }
  .cutscene-scene .cs-skip {
    position: absolute;
    right: 12px; top: 12px;
    background: rgba(0,0,0,0.5);
    color: #fff;
    border: 2px solid rgba(255,255,255,0.65);
    border-radius: 12px;
    padding: 6px 14px;
    font-family: inherit;
    font-size: var(--fs-sm); font-weight: 700;
    cursor: pointer;
    opacity: 0.8;
  }
  .cutscene-scene .cs-skip:hover { opacity: 1; }
  /* Princess creator dialog — kid customizes name + hair on first load. */
  .princess-creator-ov .princess-creator-card {
    background: linear-gradient(180deg, #ffe6f2 0%, #ffd1e8 60%, #ffc1d8 100%);
    border: 4px solid var(--accent);
    box-shadow: 0 8px 0 var(--ink), 0 0 30px rgba(255, 107, 181, 0.6);
    max-width: 440px;
  }
  .princess-creator-ov .princess-creator-card .ec-title {
    color: #b53d80;
  }
  .princess-hair-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
    margin: 6px 0;
  }
  .princess-hair-swatch {
    border: 3px solid var(--ink);
    border-radius: 14px;
    padding: 0;
    aspect-ratio: 1 / 1;
    cursor: pointer;
    position: relative;
    box-shadow: 0 3px 0 var(--ink);
    transition: transform 0.15s, box-shadow 0.15s;
  }
  .princess-hair-swatch.selected {
    transform: translateY(-2px) scale(1.08);
    box-shadow: 0 5px 0 var(--ink), 0 0 18px gold;
    border-color: gold;
  }
  .princess-hair-label {
    position: absolute;
    bottom: 4px; left: 4px; right: 4px;
    font-size: var(--fs-xs); font-weight: 800;
    color: #fff;
    text-shadow: 0 0 4px #000, 0 1px 2px #000;
  }

  /* Phase 3 — Dress-Up Boutique modal */
  .boutique-ov .boutique-card {
    width: 96vw; max-width: 900px;
    /* Use dvh so iOS Safari's collapsible address bar doesn't push
       the modal past the visible viewport. Cap at 88% so the top
       (header + close X) is always comfortably reachable. */
    max-height: 88vh;
    max-height: 88dvh;
    padding: 0;
    background: linear-gradient(180deg, #fff7e8 0%, #ffe6f2 100%);
    border: 4px solid var(--accent);
    box-shadow: 0 8px 0 var(--ink);
    display: flex; flex-direction: column;
    /* Scroll the whole card vertically when the hair/outfit/accessory
       rows overflow the viewport (used to be `overflow: hidden`, which
       silently won over the .boutique-card-v2 override and trapped the
       kid on the top half of the dialog with no way to see Accessories
       on phone). */
    overflow-y: auto;
    position: relative;  /* anchor for the absolutely-positioned close X */
  }
  /* Phase 7 — kid-friendly v2 boutique. Big princess preview at top,
     hair + outfit + accessory rows scroll below. */
  .boutique-card-v2 { overflow-y: auto; }
  .boutique-stage {
    display: flex; align-items: center; justify-content: center; gap: 14px;
    padding: 14px;
    background: linear-gradient(180deg, #fff0f8 0%, #ffd6ec 100%);
    border-bottom: 3px dashed var(--accent);
  }
  .bstage-card {
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 18px;
    padding: 10px 14px;
    text-align: center;
    box-shadow: 0 4px 0 rgba(0,0,0,0.18);
  }
  .bstage-img {
    width: 140px; height: 140px;
    object-fit: contain;
    image-rendering: pixelated;
    transition: filter 0.25s;
  }
  .bstage-name { font-size: var(--fs-md); font-weight: 800; margin-top: 4px; }
  .bstage-sub  { font-size: var(--fs-sm); color: var(--ink-soft); margin-top: 2px; }
  .bstage-switcher {
    display: flex; flex-direction: column; gap: 6px;
    max-height: 180px; overflow-y: auto;
  }
  .bswitch {
    background: #fff;
    border: 2px solid var(--ink); border-radius: 12px;
    padding: 4px; cursor: pointer; font-family: inherit;
  }
  .bswitch.selected { background: var(--accent-2); box-shadow: 0 0 8px gold; }
  .bswitch img { width: 50px; height: 50px; object-fit: contain; }
  .boutique-section {
    padding: 10px 14px 4px;
    border-bottom: 2px dashed #f4cce0;
  }
  .bsec-title {
    font-size: var(--fs-md); font-weight: 800; color: var(--accent);
    margin-bottom: 6px;
  }
  .boutique-row {
    display: flex; gap: 8px; flex-wrap: wrap;
  }
  .boutique-hair-card {
    background: #fff;
    border: 3px solid var(--ink); border-radius: 14px;
    padding: 6px 10px;
    cursor: pointer; font-family: inherit;
    display: flex; align-items: center; gap: 6px;
    box-shadow: 0 3px 0 rgba(0,0,0,0.15);
  }
  .boutique-hair-card.selected { background: var(--accent-2); box-shadow: 0 0 10px gold; }
  .bh-swatch {
    width: 28px; height: 28px; border-radius: 50%;
    border: 2px solid var(--ink);
  }
  .bh-label { font-size: var(--fs-sm); font-weight: 800; }
  .boutique-outfit-card {
    background: #fff;
    border: 3px solid var(--ink); border-radius: 14px;
    padding: 6px;
    cursor: pointer; font-family: inherit;
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    width: 100px;
    box-shadow: 0 3px 0 rgba(0,0,0,0.15);
  }
  .boutique-outfit-card.selected { background: var(--accent-2); box-shadow: 0 0 10px gold; border-color: gold; }
  .bo-preview {
    width: 70px; height: 70px;
    object-fit: contain; image-rendering: pixelated;
  }
  .bo-label { font-size: var(--fs-xs); font-weight: 800; text-align: center; }
  .boutique-grid-v2 {
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 8px; padding: 8px 0 12px 0;
  }
  .boutique-header {
    /* Header sticks to the top of the scrollable card so the close X
       is always reachable, even after the user scrolls through the
       hair / outfit / accessory rows. */
    position: sticky; top: 0; z-index: 5;
    display: flex; align-items: center; justify-content: space-between;
    padding: 12px 56px 12px 16px; /* extra right-padding so title doesn't slip under the X */
    background: var(--accent); color: #fff;
    border-bottom: 3px solid var(--ink);
  }
  .boutique-title { font-size: var(--fs-md); font-weight: 800; line-height: 1.1; }
  .boutique-close {
    /* Bumped up: bigger, bright red, pinned to the top-right of the
       whole card (not just the header) so it never scrolls away even
       on tiny screens. */
    position: absolute;
    top: 8px; right: 8px;
    z-index: 50;
    background: #e63946; color: #fff;
    border: 3px solid #fff;
    box-shadow: 0 2px 0 var(--ink), 0 0 0 4px var(--ink);
    border-radius: 50%;
    width: 44px; height: 44px;
    font-size: var(--fs-md); font-weight: 800;
    cursor: pointer; line-height: 1;
    display: grid; place-items: center;
    padding: 0;
  }
  .boutique-close:hover { background: #b8232f; }
  .boutique-close:active { translate: 0 2px; box-shadow: 0 0 0 var(--ink), 0 0 0 4px var(--ink); }
  .boutique-body {
    display: flex; flex: 1; min-height: 0;
  }
  .boutique-heroes {
    width: 200px; min-width: 160px;
    overflow-y: auto;
    padding: 8px; background: #fff8f0;
    border-right: 3px solid var(--ink);
    display: flex; flex-direction: column; gap: 6px;
  }
  .boutique-hero {
    background: #fff;
    border: 3px solid var(--ink); border-radius: 12px;
    padding: 6px; cursor: pointer;
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    font-family: inherit;
  }
  .boutique-hero img { width: 56px; height: 56px; object-fit: contain; }
  .boutique-hero.selected {
    background: var(--accent-2);
    box-shadow: 0 0 12px gold;
  }
  .boutique-hero-name { font-size: var(--fs-xs); font-weight: 800; line-height: 1.1; text-align: center; }
  .boutique-hero-cosmetics { font-size: var(--fs-sm); }
  .boutique-right {
    flex: 1; display: flex; flex-direction: column; min-width: 0;
  }
  .boutique-slot-tabs {
    display: flex; gap: 4px;
    padding: 8px; background: #fff;
    border-bottom: 2px solid var(--ink);
    overflow-x: auto;
  }
  .boutique-slot-tab {
    background: #fde6f0; color: var(--ink);
    border: 2px solid var(--ink); border-radius: 10px;
    padding: 6px 10px; font-family: inherit; font-weight: 800;
    font-size: var(--fs-sm); cursor: pointer; white-space: nowrap;
  }
  .boutique-slot-tab.selected {
    background: var(--accent); color: #fff;
  }
  .boutique-grid {
    flex: 1; padding: 12px; overflow-y: auto;
    display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 10px;
  }
  .boutique-item {
    background: #fff;
    border: 3px solid var(--ink); border-radius: 14px;
    padding: 10px 8px; cursor: pointer;
    font-family: inherit;
    display: flex; flex-direction: column; align-items: center; gap: 4px;
  }
  .boutique-item.owned { background: #f4ffea; }
  .boutique-item.equipped {
    background: var(--accent-2);
    box-shadow: 0 0 16px gold;
    border-color: gold;
  }
  .boutique-item-emoji { font-size: var(--fs-xl); line-height: 1; }
  .boutique-item-name { font-size: var(--fs-sm); font-weight: 800; text-align: center; }
  .boutique-item-stats { font-size: var(--fs-xs); color: var(--ink-soft); }
  .boutique-item-action {
    margin-top: 4px; padding: 4px 8px;
    background: var(--accent); color: #fff;
    border-radius: 8px; font-size: var(--fs-xs); font-weight: 800;
  }
  .boutique-item.owned .boutique-item-action {
    background: #6cc06c;
  }
  .boutique-item.equipped .boutique-item-action {
    background: gold; color: var(--ink);
  }

  /* Phase 5b — Bakery + Farm auto-production cards */
  .production-header {
    grid-column: 1 / -1;
    margin-bottom: 8px;
    padding: 8px 12px;
    background: #fff8f0;
    border: 2px dashed var(--accent);
    border-radius: 12px;
    text-align: center;
  }
  .production-card.producing {
    background: #e6ffe6;
    border-color: #1c8b3c;
    box-shadow: 0 0 12px rgba(28, 139, 60, 0.4);
  }
  .production-card.producing .snack-emoji {
    animation: production-pulse 2s ease-in-out infinite alternate;
  }
  @keyframes production-pulse {
    from { transform: scale(1); }
    to   { transform: scale(1.08); }
  }
  /* Phase 5 — production shop modal (Alchemy + Workshop) */
  .prod-shop-list {
    display: flex; flex-direction: column; gap: 8px;
    max-height: 50vh; overflow-y: auto;
    margin: 8px 0;
  }
  .prod-shop-item {
    display: flex; gap: 12px; align-items: center;
    background: #fff;
    border: 3px solid var(--ink); border-radius: 14px;
    padding: 8px 10px;
  }
  .prod-shop-item.locked { opacity: 0.5; }
  .prod-shop-emoji { font-size: var(--fs-xl); line-height: 1; }
  .prod-shop-meta { flex: 1; text-align: left; }
  .prod-shop-name { font-size: var(--fs-sm); font-weight: 800; color: var(--ink); }
  .prod-shop-count {
    background: var(--accent-2); color: var(--ink);
    border: 2px solid var(--ink); border-radius: 12px;
    padding: 1px 6px; font-size: var(--fs-xs); margin-left: 4px;
  }
  .prod-shop-desc { font-size: var(--fs-xs); color: var(--ink-soft); margin: 2px 0; }
  .prod-shop-buy {
    background: var(--accent); color: #fff;
    border: 3px solid var(--ink); border-radius: 12px;
    padding: 6px 12px; font-family: inherit; font-weight: 800; font-size: var(--fs-sm);
    cursor: pointer;
  }
  .prod-shop-locked {
    background: #d4d0d8; color: var(--ink);
    border: 2px solid var(--ink); border-radius: 12px;
    padding: 4px 10px; display: inline-block;
    font-size: var(--fs-xs); font-weight: 800;
  }
  @keyframes pulse {
    0%,100% { transform: scale(1); }
    50% { transform: scale(1.18); }
  }

  /* Plot positions */
  /* Building positions inside the Main Square district panel. Phase 2c
     collapses 5 family halls into 3 class halls — Warriors / Healers /
     Mages — so 3 plot positions instead of 5, evenly spread across the
     panorama. Phase 4 will rewrite the city UI entirely; this is the
     interim layout. */
  /* Phase 7 — hero halls 2x bigger + dirt mound underneath each.
     Keep them spread across the panorama; the wider halls overlap a
     little but the mound base anchors them visually. */
  #bldg-warriors-hall    { left:  2%; width: 32%; bottom: 5%; }
  #bldg-healers-conclave { left: 34%; width: 32%; bottom: 5%; }
  #bldg-mages-tower      { left: 66%; width: 32%; bottom: 5%; }
  /* Painted earthy mound under each guild hall — a flat oval that
     hugs the bottom of the building. CSS-only; no new sprite art. */
  #bldg-warriors-hall::before,
  #bldg-healers-conclave::before,
  #bldg-mages-tower::before {
    content: "";
    position: absolute;
    left: -8%; right: -8%; bottom: -6%;
    height: 24%;
    background:
      radial-gradient(ellipse at 50% 28%,
        #a3744a 0%,
        #8b5e35 35%,
        rgba(110, 78, 45, 0.7) 65%,
        rgba(110, 78, 45, 0) 100%);
    z-index: -1;
    pointer-events: none;
    border-radius: 50%;
    filter: drop-shadow(0 4px 4px rgba(0,0,0,0.25));
  }
  /* Phase 7 — Boutique as a real painted city building. Sits in the
     foreground next to the three guild halls; uses the painted webp
     sprite (./assets/buildings/boutique.webp). */
  .city-boutique-bldg {
    position: absolute;
    /* moved further up + left out of the central square so it no
       longer overlaps the three hero halls. */
    left: 24%;
    bottom: 48%;
    width: 16%;
    transform: translateX(-50%);
    background: none;
    border: 0;
    box-shadow: none;
    cursor: pointer;
    z-index: 4;
    padding: 0;
    transition: transform 0.15s;
  }
  .city-boutique-bldg:hover, .city-boutique-bldg:active {
    transform: translateX(-50%) translateY(-3px) scale(1.05);
  }
  .city-boutique-bldg .boutique-bldg-art {
    width: 100%; height: auto;
    image-rendering: pixelated;
    filter: drop-shadow(0 4px 4px rgba(0,0,0,0.25));
  }
  .city-boutique-bldg .boutique-bldg-emoji {
    font-size: var(--fs-xl); line-height: 1;
    filter: drop-shadow(0 3px 3px rgba(0,0,0,0.3));
  }
  .city-boutique-bldg .boutique-bldg-sparkle {
    position: absolute; top: -4px; right: -4px;
    font-size: var(--fs-md);
    animation: boutiqueSparkle 1.6s ease-in-out infinite;
  }
  @keyframes boutiqueSparkle {
    0%, 100% { transform: rotate(-12deg) scale(0.9); opacity: 0.7; }
    50%      { transform: rotate(12deg) scale(1.15); opacity: 1; }
  }

  /* District buildings — emoji-based tile style (no painted art yet),
     so they use a flat colored card with a giant emoji icon. */
  .city-bldg.district-bldg {
    display: flex;
    flex-direction: column;
    align-items: center; justify-content: center;
    gap: 4px;
    aspect-ratio: 1 / 1;
    background: rgba(255, 255, 255, 0.94);
    border: 3px solid var(--ink);
    border-radius: 18px;
    padding: 10px 6px;
    box-shadow: 0 4px 0 rgba(0,0,0,0.18);
    text-align: center;
  }
  .city-bldg.district-bldg .dbldg-icon {
    font-size: var(--fs-xl);
    line-height: 1;
    filter: drop-shadow(0 3px 3px rgba(0,0,0,0.15));
  }
  /* Painted building sprite — replaces the giant emoji on each card. */
  .city-bldg.district-bldg .dbldg-art {
    width: auto;
    height: 70%;
    max-width: 95%;
    image-rendering: pixelated;
    filter: drop-shadow(0 3px 3px rgba(0,0,0,0.18));
  }
  .city-bldg.district-bldg .dbldg-name {
    font-size: var(--fs-md); font-weight: 800;
    color: var(--ink);
    line-height: 1.1;
  }
  .city-bldg.district-bldg.empty-plot {
    background: rgba(255, 255, 255, 0.65);
    border-style: dashed;
    border-color: var(--accent);
  }
  /* Empty-plot painted preview — desaturated + faded so the kid reads
     it as "this is what it'll look like when built". */
  .city-bldg.district-bldg.empty-plot .dbldg-art {
    opacity: 0.45;
    filter: grayscale(0.7) drop-shadow(0 2px 2px rgba(0,0,0,0.1));
  }

  .wander {
    position: absolute;
    width: 9.75%; min-width: 48px; max-width: 84px;
    height: auto;
    image-rendering: pixelated;
    pointer-events: none;
    /* z-index 6 so villagers walk IN FRONT of buildings (buildings sit
       at z-index 5). Previously they were at 4 and disappeared behind
       the building tiles whenever their path crossed one. */
    z-index: 6;
    transition: transform 0.4s linear, left 0.4s linear, bottom 0.4s linear;
    filter: drop-shadow(0 2px 2px rgba(0,0,0,0.2));
  }
  .wander.flip { transform: scaleX(-1); }
  .wander.flip.jump  { transform: scaleX(-1) translateY(-12px); }
  .wander.flip.twirl { transform: scaleX(-1) rotate(360deg); transition: transform 0.7s ease-in-out; }
  .wander.flip.nap   { transform: scaleX(-1) scale(0.8); }
  .wander.jump  { transform: translateY(-12px); }
  .wander.twirl { transform: rotate(360deg); transition: transform 0.7s ease-in-out; }
  .wander.nap   { transform: scale(0.8); }
  .wander.spawn-in {
    animation: creatureSpawn 0.6s ease-out;
  }
  @keyframes creatureSpawn {
    0%   { transform: scale(0) rotate(-180deg); opacity: 0; }
    60%  { transform: scale(1.2) rotate(20deg); opacity: 1; }
    100% { transform: scale(1) rotate(0deg); opacity: 1; }
  }
  /* "thought" emoji that pops above creature when it does something */
  .creature-bubble {
    position: absolute;
    font-size: var(--fs-lg);
    pointer-events: none;
    z-index: 4;
    animation: bubbleFloat 1.4s ease-out forwards;
  }
  @keyframes bubbleFloat {
    0%   { opacity: 0; transform: translateY(0) scale(0.3); }
    20%  { opacity: 1; transform: translateY(-4px) scale(1); }
    100% { opacity: 0; transform: translateY(-30px) scale(0.9); }
  }

  .city-hint {
    position: absolute; left: 50%; top: 6px; transform: translateX(-50%);
    background: rgba(255,255,255,0.92);
    border: 2px solid var(--accent);
    border-radius: 999px;
    padding: 4px 12px;
    font-size: var(--fs-md); font-weight: 700;
    color: var(--ink);
    box-shadow: 0 2px 0 rgba(0,0,0,0.15);
    z-index: 6;
    pointer-events: none;
    animation: hintFade 7s ease forwards;
  }
  @keyframes hintFade {
    0% { opacity: 0; transform: translate(-50%, -8px); }
    8% { opacity: 1; transform: translate(-50%, 0); }
    80% { opacity: 1; }
    100% { opacity: 0; transform: translate(-50%, -8px); }
  }

  /* ---------- ROSTER OVERLAY ---------- */
  #ov {
    position: fixed; inset: 0; z-index: 50;
    background: rgba(28, 8, 60, 0.55);
    display: none;
    align-items: flex-start; justify-content: center;
    padding: 8px;
    overflow-y: auto;
  }
  #ov.show { display: flex; }
  #expov {
    position: fixed; inset: 0; z-index: 50;
    background: rgba(28, 8, 60, 0.55);
    display: none;
    align-items: flex-start; justify-content: center;
    padding: 8px;
    overflow-y: auto;
  }
  #expov.show { display: flex; }

  /* Equip screen — opens after team-pick, before expedition starts */
  #equipov, #slotov {
    position: fixed; inset: 0; z-index: 60;
    background: rgba(28, 8, 60, 0.6);
    display: none;
    align-items: flex-start; justify-content: center;
    padding: 8px;
    overflow-y: auto;
  }
  #equipov.show, #slotov.show { display: flex; }
  .equip-card {
    width: min(100%, 560px);
  }
  .equip-stats-row {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 6px;
    margin-bottom: 10px;
  }
  .equip-stat-card {
    background: linear-gradient(180deg, #fffaf2, #ffe9c4);
    border: 2px solid var(--ink);
    border-radius: 10px;
    padding: 5px 6px;
    text-align: center;
    box-shadow: 0 2px 0 rgba(0,0,0,0.15);
  }
  .equip-stat-card .esn-emoji {
    font-size: var(--fs-xl); line-height: 1;
    margin-bottom: 2px;
  }
  .equip-stat-card .esn-name {
    font-weight: 900; font-size: var(--fs-md);
    color: var(--ink);
    line-height: 1.1;
    margin-bottom: 3px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .equip-stat-card .esn-line {
    font-size: var(--fs-md); font-weight: 700;
    color: var(--ink);
    margin: 1px 0;
  }
  .equip-stat-card .esn-line .lbl {
    color: var(--ink-soft);
    margin-right: 3px;
  }
  .equip-stat-card .esn-line.hp .val { color: #2a8a3a; }
  .equip-stat-card .esn-line.mp .val { color: #2a4ed0; }
  .equip-stat-card .esn-line.lvl .val { color: #c97e00; }

  .equip-dolls-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }
  .equip-doll {
    background: #fffaf2;
    border: 2px solid var(--ink);
    border-radius: 12px;
    padding: 8px 8px 10px;
    text-align: center;
    box-shadow: 0 3px 0 rgba(0,0,0,0.15);
  }
  .equip-doll .ed-name {
    font-weight: 900; font-size: var(--fs-md);
    color: var(--ink);
    margin-bottom: 5px;
    line-height: 1.1;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .equip-doll .ed-doll {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
  }
  .equip-doll .ed-portrait {
    width: 56px; height: 56px;
    background: rgba(0,0,0,0.05);
    border-radius: 8px;
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0;
    overflow: hidden;
  }
  .equip-doll .ed-portrait img {
    width: 90%; height: auto;
    image-rendering: pixelated;
  }
  .equip-doll .ed-slots {
    display: flex; flex-direction: column;
    gap: 4px;
    flex: 1;
    min-width: 0;
  }
  .equip-slot-btn {
    background: rgba(255, 255, 255, 0.85);
    border: 2px dashed rgba(0, 0, 0, 0.3);
    border-radius: 8px;
    padding: 4px 6px;
    font-size: var(--fs-md);
    font-weight: 700;
    color: var(--ink);
    cursor: pointer;
    text-align: left;
    display: flex;
    align-items: center;
    gap: 5px;
    line-height: 1.2;
  }
  .equip-slot-btn.filled {
    border: 2px solid var(--gold);
    background: #fff8d8;
  }
  .equip-slot-btn:hover, .equip-slot-btn:active {
    background: #ffe085;
    transform: translateY(-1px);
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.15);
  }
  .equip-slot-btn .esb-icon {
    font-size: var(--fs-lg);
    line-height: 1;
    flex-shrink: 0;
  }
  .equip-slot-btn .esb-text {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .equip-slot-btn .esb-empty { color: rgba(0,0,0,0.42); font-style: italic; }

  /* Slot picker — currently equipped + list of available items */
  .slot-card {
    width: min(100%, 460px);
  }
  .slot-card #slotov-current {
    margin-bottom: 12px;
  }
  .slot-card .slot-current-card {
    background: #fff8d8;
    border: 2px solid var(--gold);
    border-radius: 10px;
    padding: 8px 10px;
    display: flex;
    align-items: center;
    gap: 8px;
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.12);
  }
  .slot-card .slot-current-card .scc-emoji { font-size: var(--fs-2xl); }
  .slot-card .slot-current-card .scc-name {
    flex: 1; font-weight: 900; font-size: var(--fs-lg);
  }
  .slot-card .slot-current-card .scc-stats {
    font-size: var(--fs-md); color: var(--ink-soft);
    margin-top: 2px; font-weight: 700;
  }
  .slot-card .slot-remove-btn {
    background: linear-gradient(180deg, #ff8b91, #d6494f);
    color: #fff;
    border: 2px solid #fff;
    border-radius: 999px;
    padding: 5px 10px;
    font-weight: 800; font-size: var(--fs-md);
    cursor: pointer;
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.15);
  }
  .slot-card .slot-remove-btn:active {
    transform: translateY(2px);
    box-shadow: 0 0 0 rgba(0, 0, 0, 0.15);
  }
  .slot-card #slotov-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 6px;
  }
  .slot-card .slot-item-tile {
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 10px;
    padding: 6px 8px;
    text-align: left;
    cursor: pointer;
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.12);
    display: flex; align-items: center; gap: 6px;
  }
  .slot-card .slot-item-tile:hover, .slot-card .slot-item-tile:active {
    background: #fff8d8;
    transform: translateY(-1px);
  }
  .slot-card .slot-item-tile .sit-emoji { font-size: var(--fs-xl); }
  .slot-card .slot-item-tile .sit-meta {
    flex: 1; min-width: 0;
  }
  .slot-card .slot-item-tile .sit-name {
    font-weight: 800; font-size: var(--fs-md);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .slot-card .slot-item-tile .sit-stats {
    font-size: var(--fs-md); color: var(--ink-soft);
    font-weight: 700;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .slot-card .slot-empty-msg {
    text-align: center;
    padding: 16px 12px;
    color: var(--ink-soft);
    font-size: var(--fs-md);
  }
  .slot-card .slot-empty-msg b { color: var(--ink); }

  .unit-tile.picked {
    box-shadow: 0 0 0 3px var(--gold), 0 2px 0 rgba(0,0,0,0.15);
    background: #fff8d8;
  }
  .unit-tile.picked-bk {
    box-shadow: 0 0 0 3px #b8a4ff, 0 2px 0 rgba(0,0,0,0.15);
    background: #ede8ff;
  }
  .unit-tile .pick-badge {
    position: absolute; top: 2px; right: 4px;
    font-size: var(--fs-lg); pointer-events: none;
    text-shadow: 0 1px 2px rgba(255,255,255,0.9);
  }
  .unit-tile .pick-badge.bk { color: #6e57b8; }

  /* Party picker — Active / Backup rows */
  #expov-roster .section-h {
    margin: 10px 4px 4px;
    font-size: var(--fs-md);
    font-weight: 700;
    color: var(--ink);
    letter-spacing: 0.02em;
  }
  /* Redesigned in 2026-05-23: kid asked for one horizontal LINE of
     8 slots (Active → Backup, marching right) instead of two stacked
     rows of 4 with giant 🛡/🪖 silhouettes in the empties. A small
     right-pointing arrow separates the gold active slots from the
     purple backup slots so it reads as a single squad lining up to
     leave town. */
  .party-line {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr)) auto repeat(4, minmax(0, 1fr));
    gap: 4px;
    align-items: stretch;
    margin: 4px 0 10px 0;
  }
  .party-line-divider {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: var(--fs-lg);
    color: var(--accent);
    font-weight: 900;
    line-height: 1;
    user-select: none;
  }
  .party-line .party-slot {
    aspect-ratio: 1 / 1;
    border-radius: 10px;
    padding: 2px;
  }
  .party-line .party-slot .ps-name {
    font-size: var(--fs-xs);
    margin-top: 1px;
  }
  /* Slot number badge — subtle, replaces the giant helmet/shield
     emoji on empty slots so the row reads as a clean line of eight
     waiting boxes. */
  .party-slot .ps-slot-num {
    position: absolute;
    bottom: 2px;
    right: 4px;
    font-size: var(--fs-xs);
    font-weight: 700;
    color: rgba(0,0,0,0.28);
    pointer-events: none;
  }
  .party-line .party-slot.active.empty {
    border-color: rgba(255, 200, 80, 0.55);
    background: rgba(255, 248, 216, 0.65);
  }
  .party-line .party-slot.backup.empty {
    border-color: rgba(184, 164, 255, 0.55);
    background: rgba(237, 232, 255, 0.65);
  }
  .party-slot.empty .ps-empty {
    /* Subtle plus sign only — no helmet/shield */
    font-size: var(--fs-lg);
    opacity: 0.25;
  }
  .party-slot {
    position: relative;
    aspect-ratio: 1 / 1;
    border-radius: 12px;
    border: 2px dashed rgba(0,0,0,0.18);
    background: rgba(255,255,255,0.6);
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    overflow: hidden;
    transition: transform 0.18s;
  }
  .party-slot.filled.active {
    border: 2px solid var(--gold);
    background: #fff8d8;
  }
  .party-slot.filled.backup {
    border: 2px solid #b8a4ff;
    background: #ede8ff;
  }
  .party-slot img {
    width: 60%; height: auto;
    image-rendering: pixelated;
  }
  .party-slot .ps-name {
    font-size: var(--fs-md); line-height: 1.1;
    text-align: center;
    color: var(--ink);
    padding: 0 2px;
  }
  .party-slot .ps-empty {
    font-size: var(--fs-2xl); opacity: 0.35;
  }
  .party-slot .ps-x {
    position: absolute;
    top: 2px; right: 2px;
    width: 18px; height: 18px;
    border: none;
    border-radius: 50%;
    background: rgba(255,255,255,0.95);
    color: #c33;
    font-size: var(--fs-lg);
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 1px 2px rgba(0,0,0,0.15);
  }

  #biomeov {
    position: fixed; inset: 0; z-index: 50;
    background: rgba(28, 8, 60, 0.55);
    display: none;
    align-items: flex-start; justify-content: center;
    padding: 8px;
    overflow-y: auto;
  }
  #biomeov.show { display: flex; }
  #shopov, #bagov, #heroov, #capov,
  #bakeryov, #forgeov, #storeov, #libraryov,
  #throneov, #templeov, #academyov,
  #tavernov, #farmov, #stablesov, #hermitov,
  #prodov,
  #decoov, #pathov {
    position: fixed; inset: 0; z-index: 50;
    background: rgba(28, 8, 60, 0.55);
    display: none;
    align-items: flex-start; justify-content: center;
    padding: 8px;
    overflow-y: auto;
  }
  #shopov.show, #bagov.show, #heroov.show, #capov.show,
  #bakeryov.show, #forgeov.show, #storeov.show, #libraryov.show,
  #throneov.show, #templeov.show, #academyov.show,
  #tavernov.show, #farmov.show, #stablesov.show, #hermitov.show,
  #prodov.show,
  #decoov.show, #pathov.show { display: flex; }
  /* Phase 7 — production-line status row inside the universal modal */
  .prod-line-row {
    display: flex; align-items: center; gap: 8px;
    padding: 4px 8px;
    background: #f7f4ec;
    border: 2px solid var(--ink);
    border-radius: 10px;
    margin: 3px 0;
    font-size: var(--fs-md);
  }
  .prod-line-row.idle    { background: #ececec; opacity: 0.75; }
  .prod-line-row.busy    { background: linear-gradient(90deg, #fffaf2, #ffe8c4); }
  .prod-line-row .pl-emoji { font-size: var(--fs-lg); flex-shrink: 0; }
  .prod-line-row .pl-name  { flex: 1; font-weight: 700; }
  .prod-line-row .pl-time  { font-weight: 800; color: var(--accent); flex-shrink: 0; }
  .prod-recipe-card {
    background: #fff8ec;
    border: 2px solid var(--ink);
    border-radius: 12px;
    padding: 6px 8px;
    text-align: center;
    box-shadow: 0 2px 0 rgba(0,0,0,0.1);
  }
  .prod-recipe-card.locked  { background: #ececec; opacity: 0.7; }
  .prod-recipe-card.busy    { background: linear-gradient(180deg, #c8f0a8, #97cd6c); border-color: #4c8a4c; }
  .prod-recipe-card .prc-emoji { font-size: var(--fs-lg); line-height: 1; }
  .prod-recipe-card .prc-name  { font-size: var(--fs-sm); font-weight: 700; margin-top: 2px; }
  .prod-recipe-card .prc-time  { font-size: var(--fs-xs); color: var(--ink-soft); margin-top: 2px; }
  .prod-recipe-card .prc-status{ font-size: var(--fs-xs); font-weight: 700; margin-top: 2px; }
  .prod-recipe-card.busy .prc-status { color: #2c802c; }
  .prod-recipe-card.locked .prc-status { color: #888; }

  /* Path picker — three escalating-difficulty cards in a vertical
     stack. Each card shows diff stars, name, blurb, boss preview and
     reward multiplier. Bigger tap targets so the kid can pick easily. */
  .ov-card.path-card { max-width: 540px; }
  #path-grid {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin: 4px 0;
  }
  .path-row {
    display: flex; align-items: center; gap: 10px;
    background: linear-gradient(180deg, #fffaf2 0%, #ffe8c4 100%);
    border: 3px solid var(--ink, #4a3268);
    border-radius: 16px;
    padding: 8px 10px;
    cursor: pointer;
    transition: transform 0.12s, box-shadow 0.12s;
    box-shadow: 0 4px 0 rgba(0,0,0,0.15);
  }
  .path-row:hover, .path-row:active {
    transform: translateY(-2px);
    box-shadow: 0 6px 0 rgba(0,0,0,0.2);
  }
  .path-row.diff-2 {
    background: linear-gradient(180deg, #fff0e0 0%, #ffc8a0 100%);
  }
  .path-row.diff-3 {
    background: linear-gradient(180deg, #ffe2e8 0%, #ff9eb8 100%);
  }
  .path-row .pr-boss {
    width: 64px; height: 64px;
    flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    background: rgba(255,255,255,0.7);
    border: 2px solid var(--ink);
    border-radius: 50%;
    overflow: hidden;
  }
  .path-row .pr-boss img {
    width: 100%; height: 100%;
    object-fit: contain;
    image-rendering: pixelated;
  }
  .path-row .pr-info { flex: 1; min-width: 0; }
  .path-row .pr-stars {
    font-size: var(--fs-md); font-weight: 900;
    color: #c97e00;
    letter-spacing: 1px;
    margin-bottom: 1px;
  }
  .path-row .pr-name {
    font-size: var(--fs-lg); font-weight: 900;
    color: var(--ink);
    line-height: 1.1;
    margin-bottom: 2px;
  }
  .path-row .pr-blurb {
    font-size: var(--fs-md); color: var(--ink-soft);
    line-height: 1.2;
  }
  .path-row .pr-rewards {
    display: flex; align-items: center; gap: 4px;
    margin-top: 4px;
    font-size: var(--fs-md); font-weight: 800;
    color: #2a6a2a;
  }
  .path-row .pr-rewards .pr-loot {
    background: #fff;
    border: 1.5px solid var(--ink);
    border-radius: 999px;
    padding: 1px 6px;
    font-size: var(--fs-md);
    color: var(--ink);
  }
  .path-row .pr-rewards .pr-loot.r-rare  { background: #c8d8ff; color: #1a3082; }
  .path-row .pr-rewards .pr-loot.r-epic  { background: #ffd6f5; color: #7a2266; }

  /* Item grid + tile (used in shop and bag) */
  .item-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 6px;
  }
  .item-tile {
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 12px;
    padding: 5px 4px;
    text-align: center;
    position: relative;
    box-shadow: 0 2px 0 rgba(0,0,0,0.12);
  }
  .item-tile.r-common { background: #ecf6ff; }
  .item-tile.r-rare   { background: #f3ebff; }
  .item-tile.r-epic   { background: #fff5d6; }
  .item-tile.r-legendary {
    background: linear-gradient(135deg, #ffe4f3 0%, #fff4cc 50%, #d4ffe1 100%);
    border-color: #d83388;
    animation: legendary-glow 2s infinite ease-in-out;
    box-shadow: 0 2px 0 rgba(0,0,0,0.12), 0 0 12px rgba(255,170,210,0.6);
  }
  @keyframes legendary-glow {
    0%, 100% { box-shadow: 0 2px 0 rgba(0,0,0,0.12), 0 0 12px rgba(255,170,210,0.5); }
    50%      { box-shadow: 0 2px 0 rgba(0,0,0,0.12), 0 0 22px rgba(255,170,210,1.0); }
  }
  .item-tile .rarity-tag {
    position: absolute; top: 2px; right: 2px;
    font-size: var(--fs-sm); font-weight: 900;
    padding: 1px 5px;
    border-radius: 999px;
    color: var(--ink);
    background: #fff; border: 1px solid var(--ink);
  }
  .item-tile.r-legendary .rarity-tag {
    background: linear-gradient(90deg, #ff80b8, #ffce6e);
    color: #fff; text-shadow: 0 1px 1px rgba(0,0,0,0.4);
  }
  .item-tile .iemoji {
    font-size: var(--fs-xl); line-height: 1;
    display: block; margin: 2px 0 2px;
  }
  .item-tile .iname {
    font-size: var(--fs-md); font-weight: 700; line-height: 1.1;
  }
  .item-tile .istats {
    font-size: var(--fs-md); color: var(--ink-soft); line-height: 1.2;
    margin-top: 2px;
  }
  .item-tile .icost {
    font-size: var(--fs-md); font-weight: 800;
    margin: 2px 0 0;
  }
  .item-tile button {
    margin-top: 3px; width: 100%;
    background: linear-gradient(180deg, #ffd166, #ffb347);
    border: 2px solid var(--ink);
    border-radius: 8px;
    padding: 3px 0;
    font-family: inherit; font-weight: 800; font-size: var(--fs-md);
    cursor: pointer; color: var(--ink);
  }
  .item-tile button:disabled { opacity: 0.55; cursor: not-allowed; }

  /* Hero detail */
  .hero-summary {
    display: flex; gap: 10px; align-items: center;
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 14px;
    padding: 6px;
    margin-bottom: 6px;
  }
  .hero-summary img {
    width: 80px; height: 80px;
    border-radius: 10px;
    image-rendering: pixelated;
    background: #f0f0f0;
    flex-shrink: 0;
    object-fit: contain;
  }
  .hero-summary .h-meta {
    flex: 1; min-width: 0;
  }
  .hero-summary h3 {
    margin: 0 0 2px; font-size: var(--fs-lg); color: var(--accent);
  }
  .hero-summary .h-stats {
    font-size: var(--fs-md); color: var(--ink-soft); line-height: 1.3;
  }
  .hero-summary .h-stats span {
    background: #f4eaff; padding: 1px 5px;
    border-radius: 999px; margin-right: 3px;
    display: inline-block; margin-bottom: 2px;
  }
  /* Class badge in hero detail */
  .hero-summary .h-class {
    display: inline-block;
    background: linear-gradient(180deg, #ffe085, #ffd45c);
    border: 2px solid var(--ink);
    border-radius: 999px;
    padding: 2px 9px;
    font-weight: 900; font-size: var(--fs-md);
    color: var(--ink);
    margin: 2px 0 4px;
  }
  /* Element-affinity badges on gear cards (Slice D) */
  .el-badges {
    display: flex; flex-wrap: wrap; gap: 3px;
    margin-top: 3px;
    justify-content: center;
  }
  .el-badge {
    display: inline-block;
    font-size: var(--fs-sm); font-weight: 800;
    padding: 1px 5px;
    border-radius: 999px;
    border: 1.5px solid var(--ink);
    line-height: 1.4;
    white-space: nowrap;
  }
  .el-badge.el-bonus  { background: linear-gradient(180deg, #ffe085, #ffd45c); color: #5a3500; }
  .el-badge.el-resist { background: linear-gradient(180deg, #b0e0ff, #7cc6ff); color: #14406a; }
  .el-badge.el-weak   { background: linear-gradient(180deg, #ffaaaa, #ff7777); color: #5a0a0a; }

  /* Move list in hero detail */
  .moves-list {
    display: flex; flex-direction: column;
    gap: 3px;
    margin-bottom: 8px;
  }
  .move-row {
    display: grid;
    grid-template-columns: 26px 1fr auto auto;
    gap: 6px;
    align-items: center;
    background: #fff;
    border: 1.5px solid var(--ink);
    border-radius: 8px;
    padding: 4px 8px;
    font-size: var(--fs-md);
    box-shadow: 0 2px 0 rgba(0,0,0,0.1);
  }
  .move-row.locked {
    opacity: 0.45;
    background: #f0e8f0;
  }
  .move-row .mr-elem { font-size: var(--fs-lg); }
  .move-row .mr-name { font-weight: 800; color: var(--ink); }
  .move-row .mr-power { color: var(--accent); font-weight: 800; }
  .move-row .mr-mp { color: var(--ink-soft); font-size: var(--fs-md); }
  .gear-row {
    display: flex; gap: 6px; align-items: stretch;
    margin-top: 6px;
  }
  .gear-slot {
    flex: 1;
    background: #fff;
    border: 2px dashed var(--accent);
    border-radius: 10px;
    padding: 4px;
    text-align: center;
    cursor: pointer;
    min-height: 70px;
  }
  .gear-slot.equipped {
    background: #fff8e8; border-style: solid;
  }
  .gear-slot .gs-label {
    font-size: var(--fs-md); color: var(--ink-soft);
  }
  .gear-slot .gs-emoji {
    font-size: var(--fs-2xl); line-height: 1;
    display: block; margin: 2px 0;
  }
  .gear-slot .gs-name {
    font-size: var(--fs-md); font-weight: 700; line-height: 1.1;
  }
  .gear-slot .gs-empty {
    color: var(--locked); font-size: var(--fs-md);
    margin-top: 8px;
  }

  /* Sell-hero row in the hero detail modal */
  .hero-sell-row {
    margin-top: 18px;
    padding-top: 14px;
    border-top: 2px dashed var(--accent);
    text-align: center;
  }
  .hero-sell-btn {
    background: var(--bad);
    color: #fff;
    border: 3px solid var(--ink);
    border-radius: 14px;
    padding: 10px 18px;
    font-family: inherit;
    font-weight: 800;
    font-size: var(--fs-md);
    cursor: pointer;
    box-shadow: 0 3px 0 var(--ink);
  }
  .hero-sell-btn:active { transform: translateY(2px); box-shadow: 0 1px 0 var(--ink); }
  .hero-sell-hint {
    margin-top: 6px;
    color: var(--ink-soft);
    font-size: var(--fs-sm);
  }

  /* Boss-wave gold-chest reveal. Big centered emoji + coin-amount tag,
     pops in then floats up. Bigger size + glow on biome-boss waves. */
  .boss-chest {
    position: absolute;
    left: 50%;
    top: 38%;
    transform: translate(-50%, -50%);
    text-align: center;
    pointer-events: none;
    z-index: 15;
    animation: boss-chest-pop 2.4s ease-out forwards;
  }
  .boss-chest .bc-emoji {
    display: block;
    font-size: var(--fs-hero);
    line-height: 1;
    text-shadow: 0 0 22px gold, 0 0 40px gold, 0 0 60px rgba(255,210,80,0.8);
  }
  .boss-chest .bc-coins {
    display: inline-block;
    margin-top: 8px;
    padding: 7px 18px;
    background: #fff;
    border: 4px solid #c97e00;
    border-radius: 16px;
    font-size: var(--fs-lg);
    font-weight: 900;
    color: #6e3a00;
    box-shadow: 0 5px 0 #c97e00, 0 0 28px gold;
    white-space: nowrap;
  }
  .boss-chest.huge .bc-emoji { font-size: var(--fs-hero); }
  .boss-chest.huge .bc-coins { font-size: var(--fs-xl); padding: 9px 22px; }
  @keyframes boss-chest-pop {
    0%   { transform: translate(-50%, -50%) scale(0.2) rotate(-20deg); opacity: 0; }
    15%  { transform: translate(-50%, -50%) scale(1.4) rotate(10deg);  opacity: 1; }
    30%  { transform: translate(-50%, -50%) scale(1.1) rotate(-3deg);  opacity: 1; }
    80%  { transform: translate(-50%, -50%) scale(1.05) rotate(0deg);  opacity: 1; }
    100% { transform: translate(-50%, -130%) scale(0.9) rotate(0deg);  opacity: 0; }
  }

  /* Loot drop popup — Phase rework polish: bigger emoji, name label,
     longer hold so the kid actually reads what they got. */
  .loot-drop {
    position: absolute;
    z-index: 14;
    pointer-events: none;
    text-align: center;
    transform: translate(-50%, -50%);
    animation: loot-pop 2.6s ease-out forwards;
  }
  .loot-drop .loot-emoji {
    font-size: var(--fs-hero);
    line-height: 1;
    display: block;
    text-shadow: 0 0 18px gold, 0 0 32px gold, 0 0 50px rgba(255, 200, 80, 0.7);
  }
  .loot-drop .loot-name {
    display: inline-block;
    margin-top: 8px;
    padding: 6px 14px;
    background: #fff;
    border: 4px solid var(--ink);
    border-radius: 14px;
    font-size: var(--fs-md);
    font-weight: 800;
    color: var(--ink);
    box-shadow: 0 4px 0 var(--ink), 0 0 24px gold;
    white-space: nowrap;
  }
  .loot-drop.r-rare      .loot-name { border-color: #5b9aff; box-shadow: 0 4px 0 #5b9aff, 0 0 24px #5b9aff; }
  .loot-drop.r-epic      .loot-name { border-color: #c66bff; box-shadow: 0 4px 0 #c66bff, 0 0 24px #c66bff; }
  .loot-drop.r-legendary .loot-name { border-color: gold;    box-shadow: 0 4px 0 gold,    0 0 32px gold;    animation: loot-leg-shimmer 1.4s ease-in-out infinite; }
  @keyframes loot-leg-shimmer {
    0%, 100% { filter: brightness(1); }
    50%      { filter: brightness(1.3); }
  }
  @keyframes loot-pop {
    0%   { transform: translate(-50%, -50%) scale(0.2) rotate(-30deg); opacity: 0; }
    15%  { transform: translate(-50%, -50%) scale(1.5) rotate(15deg);  opacity: 1; }
    30%  { transform: translate(-50%, -50%) scale(1.2) rotate(-5deg);  opacity: 1; }
    80%  { transform: translate(-50%, -50%) scale(1.1) rotate(0deg);   opacity: 1; }
    100% { transform: translate(-50%, -180%) scale(0.85) rotate(0deg); opacity: 0; }
  }

  /* ── Scout Adventures panel + picker modal ───────────────────────── */
  #scout-panel {
    position: absolute;
    left: 50%;
    bottom: 12px;
    transform: translateX(-50%);
    z-index: 12;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    pointer-events: auto;
    max-width: 92%;
  }
  #scout-panel[hidden] { display: none; }
  .sp-open-btn {
    background: linear-gradient(180deg, #ffe9a8 0%, #ffc466 100%);
    border: 4px solid var(--ink);
    border-radius: 18px;
    padding: 10px 18px;
    font-size: var(--fs-md);
    font-weight: 800;
    color: var(--ink);
    box-shadow: 0 4px 0 var(--ink);
    cursor: pointer;
    animation: sp-bounce 1.4s ease-in-out infinite;
  }
  .sp-open-btn:disabled {
    opacity: 0.6;
    cursor: default;
    animation: none;
    background: #eee;
  }
  .sp-open-btn:active:not(:disabled) {
    transform: translateY(2px);
    box-shadow: 0 2px 0 var(--ink);
  }
  @keyframes sp-bounce {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-3px); }
  }
  /* Scout-status HUD lives top-right of #city. Sits above the
     city-tabs-bar (z-index 8) so it's never obscured by tabs. */
  #scout-status {
    position: absolute;
    top: 4px;
    right: 6px;
    z-index: 9;
    display: flex;
    flex-direction: column;
    gap: 3px;
    background: rgba(255, 255, 255, 0.94);
    border: 3px solid var(--ink);
    border-radius: 12px;
    padding: 5px 8px;
    box-shadow: 0 3px 0 var(--ink);
    max-width: 200px;
    pointer-events: none;
  }
  #scout-status[hidden] { display: none; }
  .ss-title {
    font-size: var(--fs-xs);
    font-weight: 800;
    color: var(--ink);
    text-align: center;
    margin-bottom: 2px;
  }
  .ss-row {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--ink);
    line-height: 1.1;
  }
  .ss-row .ss-emoji { font-size: var(--fs-xs); }
  .ss-row .ss-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ss-row .ss-count {
    background: #ffd86b;
    border: 2px solid var(--ink);
    border-radius: 999px;
    padding: 0 5px;
    font-size: var(--fs-xs);
    font-weight: 800;
  }
  .ss-row .ss-time { color: #666; font-weight: 600; font-size: var(--fs-xs); }

  /* Scout picker modal */
  .sp-modal-back {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 9000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    overflow-y: auto;
  }
  .sp-modal {
    background: #fff7e0;
    border: 5px solid var(--ink);
    border-radius: 22px;
    box-shadow: 0 8px 0 var(--ink);
    padding: 18px;
    max-width: 760px;
    width: 100%;
    max-height: 92vh;
    overflow-y: auto;
    position: relative;
  }
  .sp-close {
    position: absolute;
    top: 8px;
    right: 10px;
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 50%;
    width: 36px;
    height: 36px;
    font-size: var(--fs-sm);
    font-weight: 800;
    cursor: pointer;
    color: var(--ink);
  }
  .sp-title {
    text-align: center;
    margin: 4px 0 12px;
    font-size: var(--fs-lg);
    color: var(--ink);
  }
  .sp-hint {
    text-align: center;
    font-size: var(--fs-sm);
    color: var(--ink-soft, #555);
    margin: 4px 0 10px;
  }
  .sp-dest-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
  }
  @media (max-width: 600px) {
    .sp-dest-grid { grid-template-columns: repeat(2, 1fr); }
  }
  .sp-dest-card {
    background: #fff;
    border: 4px solid var(--ink);
    border-radius: 16px;
    box-shadow: 0 4px 0 var(--ink);
    padding: 10px 8px;
    cursor: pointer;
    text-align: center;
    transition: transform 0.1s;
  }
  .sp-dest-card:hover { transform: translateY(-2px); }
  .sp-dest-card:active { transform: translateY(2px); box-shadow: 0 2px 0 var(--ink); }
  .sp-dest-emoji { font-size: var(--fs-xl); line-height: 1; }
  .sp-dest-name { font-size: var(--fs-sm); font-weight: 800; margin: 4px 0 2px; color: var(--ink); }
  .sp-dest-flavor { font-size: var(--fs-xs); color: #666; min-height: 30px; }
  .sp-dest-bias {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 4px 8px;
    margin-top: 6px;
    font-size: var(--fs-xs);
    color: var(--ink);
    font-weight: 700;
  }

  .sp-dest-label { font-weight: 800; color: var(--ink); }
  .sp-unit-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
    max-height: 50vh;
    overflow-y: auto;
    padding: 4px;
  }
  .sp-unit-row {
    display: flex;
    align-items: center;
    gap: 10px;
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 12px;
    padding: 6px 10px;
    cursor: pointer;
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--ink);
  }
  .sp-unit-row input { width: 22px; height: 22px; }
  .sp-unit-portrait { width: 36px; height: 36px; object-fit: contain; }
  .sp-unit-name { flex: 1; }
  .sp-unit-lvl { color: #666; font-weight: 600; }
  .sp-empty {
    text-align: center;
    padding: 18px;
    color: #888;
    font-style: italic;
  }
  .sp-actions {
    display: flex;
    gap: 10px;
    justify-content: center;
    margin-top: 12px;
    /* Stick the Back / Send buttons to the bottom of the modal even
       when the unit list grows past the viewport — otherwise picking
       many scouts pushes Send below the fold and the kid can't tap it. */
    position: sticky;
    bottom: -18px;
    background: #fff7e0;
    padding: 10px 0 6px;
    z-index: 2;
  }
  .sp-back, .sp-confirm {
    border: 4px solid var(--ink);
    border-radius: 14px;
    padding: 8px 16px;
    font-size: var(--fs-sm);
    font-weight: 800;
    cursor: pointer;
    box-shadow: 0 4px 0 var(--ink);
    color: var(--ink);
  }
  .sp-back { background: #fff; }
  .sp-confirm { background: linear-gradient(180deg, #b6f0a8 0%, #66c466 100%); }
  .sp-confirm:disabled { background: #eee; opacity: 0.6; cursor: default; }

  /* Dedicated "🗺️ Scouts came back!" popup — fires 30s after the main
     party walks home from battle, so the kid gets a focused moment to
     see what each scouting group brought home (instead of having it
     mashed into the battle results screen). */
  .scout-return-back {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    z-index: 9100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    animation: sr-fade-in 0.35s ease-out;
  }
  @keyframes sr-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
  }
  .scout-return-card {
    background: #fff7e0;
    border: 5px solid var(--ink);
    border-radius: 22px;
    box-shadow: 0 8px 0 var(--ink);
    padding: 18px 16px 14px;
    max-width: 480px;
    width: 100%;
    max-height: 88vh;
    overflow-y: auto;
    text-align: center;
    animation: sr-pop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  @keyframes sr-pop {
    0%   { transform: scale(0.3) rotate(-8deg); opacity: 0; }
    60%  { transform: scale(1.05) rotate(2deg); opacity: 1; }
    100% { transform: scale(1) rotate(0); opacity: 1; }
  }
  .sr-banner {
    font-size: var(--fs-lg);
    font-weight: 800;
    color: var(--ink);
  }
  .sr-sub {
    font-size: var(--fs-sm);
    color: #666;
    margin-bottom: 12px;
  }
  .sr-dest {
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 14px;
    padding: 8px 12px;
    margin: 6px 0;
    text-align: left;
  }
  .sr-dest-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-sm);
    color: var(--ink);
  }
  .sr-dest-head .sr-emoji { font-size: var(--fs-md); }
  .sr-dest-head .sr-name { font-weight: 800; }
  .sr-dest-head .sr-gold { color: #b07a00; font-weight: 700; margin-left: auto; }
  .sr-dest-head .sr-items { font-size: var(--fs-sm); }
  .sr-heroes {
    margin-top: 4px;
    font-size: var(--fs-xs);
    color: #555;
  }
  .sr-ok {
    margin-top: 12px;
    background: linear-gradient(180deg, #b6f0a8 0%, #66c466 100%);
    border: 4px solid var(--ink);
    border-radius: 14px;
    padding: 8px 22px;
    font-size: var(--fs-sm);
    font-weight: 800;
    color: var(--ink);
    box-shadow: 0 4px 0 var(--ink);
    cursor: pointer;
  }
  .sr-ok:active {
    transform: translateY(2px);
    box-shadow: 0 2px 0 var(--ink);
  }
  .biome-card {
    display: flex; gap: 10px; align-items: center;
    background: #fff;
    border: 3px solid var(--ink);
    border-radius: 14px;
    padding: 6px;
    margin-bottom: 8px;
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0,0,0,0.15);
  }
  .biome-card.locked {
    opacity: 0.5; cursor: not-allowed;
    background: #ece5f4;
  }
  .biome-card .biome-thumb {
    width: 90px; height: 60px;
    border-radius: 8px;
    background-size: cover; background-position: center;
    flex-shrink: 0;
    image-rendering: pixelated;
  }
  .biome-card .biome-meta { flex: 1; min-width: 0; }
  .biome-diff {
    display: inline-block;
    margin-left: 8px;
    padding: 2px 10px;
    border: 2px solid var(--ink);
    border-radius: 999px;
    font-size: var(--fs-sm);
    font-weight: 800;
    color: var(--ink);
    vertical-align: middle;
  }
  .biome-diff.diff-easy   { background: #aee8a3; }
  .biome-diff.diff-medium { background: #ffd86b; }
  .biome-diff.diff-hard   { background: #ff8b91; color: #fff; }
  .biome-card .biome-name {
    font-weight: 800; font-size: var(--fs-lg); color: var(--accent);
    margin: 0;
  }
  .biome-card .biome-stars {
    font-size: var(--fs-md); color: var(--gold);
    margin: 2px 0;
  }
  .biome-card .biome-progress {
    height: 6px;
    background: rgba(0,0,0,0.12);
    border-radius: 3px; overflow: hidden;
    margin-top: 4px;
  }
  .biome-card .biome-progress > i {
    display: block; height: 100%;
    background: linear-gradient(90deg, var(--good), var(--gold));
  }
  .biome-card .biome-rewards {
    font-size: var(--fs-md); color: var(--ink-soft);
    margin-top: 2px;
  }
  .ov-card {
    background: var(--paper);
    border: 4px solid var(--ink);
    border-radius: 22px;
    padding: 10px;
    width: 100%; max-width: 500px;
    /* Cap card height to the viewport (minus the overlay's 8px
       padding on each side) and let the card itself scroll. Fixes
       "tight, can't see top and bottom" on shorter PC laptop
       screens where the recruit / heroes / capov modals are taller
       than the visible area. */
    max-height: calc(100vh - 32px);   /* fallback for old browsers */
    max-height: calc(100svh - 32px);  /* real visible height under iOS toolbars */
    overflow-y: auto;
    box-shadow: 0 8px 0 rgba(0,0,0,0.2);
    margin: auto;
  }
  .ov-card header {
    display: flex; align-items: center; gap: 8px;
    margin-bottom: 6px;
  }
  .ov-card header h2 {
    flex: 1; margin: 0; font-size: var(--fs-lg); color: var(--accent);
  }
  .close-x {
    background: var(--bad); color: #fff;
    border: 2px solid var(--ink);
    border-radius: 999px;
    width: 32px; height: 32px;
    font-size: var(--fs-lg); font-weight: 900;
    cursor: pointer;
  }

  .bldg-detail {
    display: flex; gap: 10px; align-items: center;
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 14px;
    padding: 6px;
  }
  .bldg-detail img {
    width: 100px; height: 100px;
    border-radius: 10px;
    image-rendering: pixelated;
    background: #f0f0f0;
    flex-shrink: 0;
    object-fit: contain;
  }
  .bldg-detail .meta { flex: 1; min-width: 0; }
  .bldg-detail .meta .perk {
    font-size: var(--fs-md); color: var(--ink-soft);
    margin-bottom: 4px;
  }
  .upgrade-btn {
    width: 100%;
    background: linear-gradient(180deg, #ffd166, #ffb347);
    border: 2px solid var(--ink);
    border-radius: 12px;
    padding: 6px;
    font-family: inherit; font-weight: 800; font-size: var(--fs-md);
    cursor: pointer; color: var(--ink);
    box-shadow: 0 3px 0 rgba(0,0,0,0.2);
    margin-top: 4px;
  }
  .upgrade-btn:disabled { opacity: 0.55; cursor: not-allowed; }
  .upgrade-btn.maxed { background: linear-gradient(180deg, #b8a4ff, #d4c2ff); }
  .upgrade-btn.alt   { background: linear-gradient(180deg, #c8e6ff, #a5cff5); }

  /* Unified upgrade-tree component — used by every district building's
     modal in place of the old single "⬆ Upgrade Foo" button + browser
     confirm() popup. Shows 5 (or 7 for Throne) tier rows with state:
       built  — green checkmark
       next   — gold highlight + "Build" or "Upgrade" button
       locked — grey, "🔒 Locked"
       avail  — neutral, "💰 cost" (faded for tiers further out) */
  .upgrade-tree {
    display: flex; flex-direction: column;
    gap: 4px;
    margin: 8px 0 4px 0;
  }
  .upgrade-tree h4 {
    font-size: var(--fs-md);
    font-weight: 800;
    color: var(--ink-soft);
    margin: 0 0 4px 2px;
    letter-spacing: 0.4px;
    text-transform: uppercase;
  }
  .tier-row {
    display: flex; align-items: center; gap: 8px;
    padding: 6px 10px;
    background: #f7f4ec;
    border: 2px solid var(--ink, #4a3268);
    border-radius: 12px;
    font-size: var(--fs-md);
    position: relative;
    box-shadow: 0 2px 0 rgba(0,0,0,0.1);
    transition: transform 0.15s, box-shadow 0.15s;
  }
  .tier-row.built {
    background: linear-gradient(90deg, #c8f0a8, #97cd6c);
    border-color: #4c8a4c;
  }
  .tier-row.next {
    background: linear-gradient(90deg, #fffaf2, #ffd86b);
    border-color: #c97e00;
    box-shadow: 0 2px 0 rgba(0,0,0,0.18), 0 0 0 3px rgba(255,179,71,0.35);
  }
  .tier-row.locked {
    background: #ececec;
    border-color: #aaa;
    color: #888;
    opacity: 0.85;
  }
  .tier-num {
    width: 26px; height: 26px;
    border-radius: 50%;
    background: var(--accent);
    color: #fff;
    display: flex; align-items: center; justify-content: center;
    font-weight: 900;
    font-size: var(--fs-md);
    flex-shrink: 0;
    box-shadow: inset 0 -2px 0 rgba(0,0,0,0.2);
  }
  .tier-row.built .tier-num   { background: #4c8a4c; }
  .tier-row.next .tier-num    { background: #c97e00; animation: tier-num-pulse 1.4s ease-in-out infinite; }
  .tier-row.locked .tier-num  { background: #999; }
  @keyframes tier-num-pulse {
    0%, 100% { box-shadow: inset 0 -2px 0 rgba(0,0,0,0.2), 0 0 0 0 rgba(201,126,0,0.6); }
    50%      { box-shadow: inset 0 -2px 0 rgba(0,0,0,0.2), 0 0 0 6px rgba(201,126,0,0); }
  }
  .tier-text {
    flex: 1;
    font-weight: 700;
    color: var(--ink);
    line-height: 1.2;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .tier-row.locked .tier-text { color: #888; font-style: italic; }
  .tier-status {
    font-size: var(--fs-md);
    font-weight: 800;
    flex-shrink: 0;
  }
  .tier-row.built .tier-status   { color: #2c802c; }
  .tier-row.locked .tier-status  { color: #888; }
  .tier-action-btn {
    background: linear-gradient(180deg, #ffd166, #ffb347);
    border: 2px solid var(--ink, #4a3268);
    border-radius: 10px;
    padding: 5px 10px;
    font-family: inherit; font-weight: 800; font-size: var(--fs-md);
    cursor: pointer; color: var(--ink);
    box-shadow: 0 2px 0 rgba(0,0,0,0.18);
    flex-shrink: 0;
    transition: transform 0.12s;
  }
  .tier-action-btn:hover, .tier-action-btn:active { transform: translateY(-1px); }
  .tier-action-btn:disabled {
    opacity: 0.55;
    cursor: not-allowed;
    background: linear-gradient(180deg, #ddd, #bbb);
  }

  /* Bakery / blacksmith / store / library / temple / academy / tavern / farm / deco grids */
  .bakery-grid, #forge-pick-grid, #store-grid, #library-grid,
  #bag-snacks-grid, #temple-grid, #academy-grid,
  #tavern-recruits, #farm-plots, #farm-seeds, #deco-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 8px;
    margin: 6px 0;
  }
  /* 🎨 Decorate button — sits in the city tab bar to the right */
  .city-deco-btn {
    background: linear-gradient(180deg, #ffd166, #ffb347);
    border: 2px solid var(--ink);
    border-radius: 50%;
    width: 30px; height: 30px;
    font-size: var(--fs-lg);
    cursor: pointer;
    align-self: center;
    box-shadow: 0 2px 0 rgba(0,0,0,0.18);
    margin-left: 4px;
  }
  .city-deco-btn:hover, .city-deco-btn:active { transform: scale(1.1); }
  /* Banner-color swatches in the deco modal */
  .banner-swatch {
    width: 36px; height: 36px;
    border-radius: 8px;
    border: 3px solid var(--ink);
    cursor: pointer;
    transition: transform 0.15s;
  }
  .banner-swatch:hover { transform: scale(1.1); }
  .banner-swatch.selected { box-shadow: 0 0 0 3px var(--accent); }
  .banner-swatch.red    { background: #ff6b6b; }
  .banner-swatch.blue   { background: #6bb8ff; }
  .banner-swatch.green  { background: #6cd66c; }
  .banner-swatch.yellow { background: #ffe066; }
  .banner-swatch.purple { background: #b96bff; }
  .banner-swatch.none   { background: repeating-linear-gradient(45deg, #ddd 0 4px, #f5f5f5 4px 8px); }
  /* Decorations placed in a district panel */
  .city-deco {
    position: absolute;
    font-size: var(--fs-xl);
    line-height: 1;
    pointer-events: none;
    z-index: 3;
    filter: drop-shadow(0 2px 2px rgba(0,0,0,0.25));
  }
  /* District banner — colored ribbon at top corner of the active district */
  .district-banner {
    position: absolute;
    top: 6px; right: 8px;
    width: 26px; height: 50px;
    z-index: 5;
    pointer-events: none;
    clip-path: polygon(0 0, 100% 0, 100% 100%, 50% 88%, 0 100%);
    box-shadow: 0 2px 4px rgba(0,0,0,0.3);
  }
  .district-banner.red    { background: #ff6b6b; }
  .district-banner.blue   { background: #6bb8ff; }
  .district-banner.green  { background: #6cd66c; }
  .district-banner.yellow { background: #ffe066; }
  .district-banner.purple { background: #b96bff; }
  /* Town fountain — placed centrally in Main Square once built */
  .town-fountain {
    position: absolute;
    left: 48%; bottom: 24%;
    transform: translateX(-50%);
    font-size: var(--fs-xl);
    line-height: 1;
    cursor: pointer;
    z-index: 4;
    pointer-events: auto;
    filter: drop-shadow(0 4px 4px rgba(0,0,0,0.2));
    animation: fountain-bob 3s ease-in-out infinite;
  }
  @keyframes fountain-bob {
    0%, 100% { transform: translateX(-50%) translateY(0); }
    50%      { transform: translateX(-50%) translateY(-3px); }
  }
  .town-fountain:hover, .town-fountain:active {
    transform: translateX(-50%) scale(1.1);
  }
  .farm-plot {
    background: #f5e9c8;
    border: 2px dashed var(--ink);
    border-radius: 12px;
    padding: 10px 6px;
    text-align: center;
    font-size: var(--fs-md);
    cursor: pointer;
    min-height: 80px;
    display: flex; flex-direction: column; gap: 4px;
    align-items: center; justify-content: center;
  }
  .farm-plot.empty {
    background: #e9e0c4;
    color: var(--ink-soft);
  }
  .farm-plot.ready {
    background: linear-gradient(180deg, #c8f0a8, #97cd6c);
    border-color: var(--good, #4ca64c);
    cursor: pointer;
  }
  .farm-plot .fp-emoji {
    font-size: var(--fs-xl); line-height: 1;
  }
  .farm-plot .fp-progress {
    font-size: var(--fs-md); color: var(--ink-soft);
  }
  .farm-plot.ready .fp-progress {
    color: var(--good, #2c802c); font-weight: 800;
  }

  /* Villager wanderer — painted chibi sprite that matches the panorama
     art. Renders as an <img>. Falls back to the emoji glyph if the
     image fails to load.
     SIZING is HEIGHT-driven so every adult villager renders at the
     same physical height regardless of their painted aspect ratio.
     Width is auto + min/max'd so very narrow figures don't squish. */
  .wander.villager {
    width: auto;
    height: 22%;
    min-height: 60px;
    max-height: 92px;
    min-width: 36px;
    max-width: 110px;
    font-size: var(--fs-xl);
    line-height: 1;
    text-align: center;
    transition: transform 0.15s, filter 0.2s;
    filter: drop-shadow(0 2px 3px rgba(0,0,0,0.3));
  }
  .wander.villager img {
    height: 100%; width: auto; display: block;
    pointer-events: none;
  }
  /* Kids render noticeably smaller so the cast reads as "grownups + kids"
     rather than a row of differently-shaped same-height adults. */
  .wander.villager.kid {
    height: 16%;
    min-height: 46px;
    max-height: 68px;
  }
  .wander.villager:hover, .wander.villager:active {
    transform: scale(1.15);
  }
  .wander.villager.flip img { transform: scaleX(-1); }

  /* Villager popup card — small floating dialogue box */
  #villager-card {
    position: fixed; inset: 0; z-index: 60;
    background: rgba(28, 8, 60, 0.55);
    display: none;
    align-items: center; justify-content: center;
    padding: 16px;
  }
  #villager-card.show { display: flex; }
  #villager-card .vc-inner {
    background: var(--paper);
    border: 4px solid var(--ink);
    border-radius: 22px;
    padding: 14px 12px;
    width: 100%; max-width: 340px;
    text-align: center;
    box-shadow: 0 8px 0 rgba(0,0,0,0.2);
    position: relative;
  }
  #villager-card .vc-emoji { font-size: var(--fs-2xl); line-height: 1; margin: 4px 0; }
  #villager-card .vc-portrait {
    display: block;
    width: 110px; height: auto;
    margin: 4px auto 6px auto;
    image-rendering: pixelated;
    filter: drop-shadow(0 3px 4px rgba(0,0,0,0.25));
  }
  #villager-card .vc-name { font-size: var(--fs-lg); font-weight: 900; color: var(--accent); }
  #villager-card .vc-role {
    font-size: var(--fs-md); font-weight: 700;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 1.5px;
    margin: 2px 0 10px 0;
  }
  #villager-card .vc-line {
    font-size: var(--fs-md); font-style: italic;
    color: var(--ink);
    margin: 6px 0 10px 0;
    padding: 8px 10px;
    background: #fff8e0;
    border-radius: 12px;
    border: 2px solid var(--gold, #ffd166);
  }
  #villager-card .close-x {
    position: absolute; top: 6px; right: 8px;
    background: rgba(255,255,255,0.85);
    border: 2px solid var(--ink);
    border-radius: 999px;
    width: 26px; height: 26px;
    font-weight: 900; font-size: var(--fs-lg);
    cursor: pointer;
  }
  .snack-card, .scroll-card, .forge-pick-card {
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 12px;
    padding: 8px 6px;
    text-align: center;
    cursor: pointer;
    transition: transform 0.15s, box-shadow 0.15s;
    box-shadow: 0 3px 0 rgba(0,0,0,0.12);
    display: flex; flex-direction: column; gap: 4px; align-items: center;
  }
  .snack-card:hover, .scroll-card:hover, .forge-pick-card:hover {
    transform: translateY(-2px);
    box-shadow: 0 5px 0 rgba(0,0,0,0.15);
  }
  .snack-card.locked, .scroll-card.locked {
    opacity: 0.45;
    cursor: not-allowed;
  }
  .snack-card .snack-emoji,
  .scroll-card .scroll-emoji,
  .forge-pick-card .fpc-emoji {
    font-size: var(--fs-2xl);
    line-height: 1;
  }
  /* Painted hero portrait on tavern recruit cards (falls back to the
     .snack-emoji span if the sprite 404s). */
  .snack-card .snack-hero {
    width: 64px; height: 64px;
    object-fit: contain;
    image-rendering: auto;
    margin: 0 auto;
    display: block;
  }
  .snack-card .snack-name,
  .scroll-card .scroll-name {
    font-size: var(--fs-md); font-weight: 800;
    color: var(--ink);
  }
  .snack-card .snack-desc,
  .scroll-card .scroll-desc {
    font-size: var(--fs-md); color: var(--ink-soft);
    line-height: 1.15;
  }
  .snack-card .snack-cost,
  .scroll-card .scroll-cost {
    font-size: var(--fs-md); font-weight: 800; color: var(--accent);
  }
  .snack-card .snack-count {
    background: var(--accent); color: #fff;
    border-radius: 10px;
    padding: 1px 6px;
    font-size: var(--fs-md); font-weight: 800;
  }
  .forge-pick-card.picked {
    background: var(--gold);
    transform: translateY(-2px);
    box-shadow: 0 5px 0 rgba(0,0,0,0.18);
  }

  .section-h {
    margin: 10px 0 4px 0;
    font-size: var(--fs-md);
    color: var(--ink-soft);
    font-weight: 700;
  }

  /* Training slots */
  .slot {
    background: #fff;
    border: 2px dashed var(--accent);
    border-radius: 12px;
    padding: 6px;
    margin-bottom: 6px;
    min-height: 52px;
    display: flex; align-items: center; gap: 8px;
  }
  .slot.busy { border-style: solid; background: #fff8e8; }
  .slot .slot-info { flex: 1; min-width: 0; font-size: var(--fs-md); }
  .slot button {
    background: var(--accent); color: #fff;
    border: 2px solid var(--ink);
    border-radius: 10px;
    padding: 5px 9px;
    font-family: inherit; font-weight: 800; font-size: var(--fs-md);
    cursor: pointer;
  }
  .slot button.cancel { background: var(--bad); }
  .slot .bar {
    flex: 1; height: 7px;
    background: rgba(0,0,0,0.12);
    border-radius: 4px; overflow: hidden;
  }
  .slot .bar > i {
    display: block; height: 100%;
    background: linear-gradient(90deg, var(--accent), var(--gold));
    transition: width 0.5s linear;
  }
  .slot .timeleft {
    flex-shrink: 0; font-weight: 800; font-size: var(--fs-md); min-width: 34px;
    text-align: right;
  }

  /* Hire grid — available creature types for this building */
  .hire-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
    gap: 6px;
    margin-bottom: 8px;
  }
  .hire-tile {
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 12px;
    padding: 4px;
    text-align: center;
    position: relative;
    box-shadow: 0 2px 0 rgba(0,0,0,0.12);
  }
  .hire-tile.locked {
    background: #f5f0fa;
    border-color: var(--locked);
    opacity: 0.72;
  }
  .hire-tile img {
    width: 100%; aspect-ratio: 1/1; object-fit: contain;
    image-rendering: pixelated;
    display: block;
  }
  .hire-tile .hname {
    font-size: var(--fs-md); font-weight: 700; line-height: 1.1;
    margin: 2px 0 1px;
  }
  .hire-tile .hcost {
    font-size: var(--fs-md); color: var(--ink-soft);
  }
  .hire-tile .hire-btn {
    margin-top: 3px; width: 100%;
    background: linear-gradient(180deg, #a8edaf, #6ed398);
    border: 2px solid var(--ink);
    border-radius: 8px;
    padding: 3px 0;
    font-family: inherit; font-weight: 800; font-size: var(--fs-md);
    cursor: pointer; color: var(--ink);
  }
  .hire-tile .hire-btn:disabled { opacity: 0.55; cursor: not-allowed; }
  .hire-tile .lock-badge {
    position: absolute; top: 2px; right: 2px;
    font-size: var(--fs-md);
  }
  .hire-tile .owned-badge {
    background: var(--accent); color: #fff;
    border: 1.5px solid #fff;
    border-radius: 999px;
    font-size: var(--fs-sm); font-weight: 900;
    padding: 1px 5px;
    display: inline-block;
    margin-bottom: 2px;
  }

  /* ── Expedition hero picker (rebuilt 2026-05-10) ─────────────
     Per Katherine: "3 columns and 4 rows, scrollable down if we
     need more. images of the heroes should be large and it should
     include some stats. there should also be a button at the top
     that says 'Let the Princess Decide'." */
  .princess-decide-btn {
    display: block;
    width: 100%;
    background: linear-gradient(180deg, #fff1a8, #ffd24a);
    color: var(--ink);
    border: 3px solid var(--ink);
    border-radius: 14px;
    padding: 10px;
    margin: 6px 0 10px;
    font-family: inherit;
    font-weight: 900;
    font-size: var(--fs-md);
    cursor: pointer;
    box-shadow: 0 3px 0 rgba(0,0,0,0.2);
  }
  .princess-decide-btn:active { transform: translateY(2px); box-shadow: 0 1px 0 rgba(0,0,0,0.2); }
  .princess-decide-btn:disabled { opacity: 0.55; cursor: not-allowed; }
  .expo-pick-grid {
    /* Available heroes grid. 4 cols by default so each card is small
       and the line of 8 party slots above stays compact. Switched OFF
       the inner-scroll (max-height + overflow-y) because it nested
       inside the .ov-card scroll and trapped the kid on phone — she
       couldn't reach the Start Adventure button. Now the whole modal
       scrolls as one and the sticky footer keeps the button reachable. */
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 6px;
    padding: 4px;
    background: rgba(0, 0, 0, 0.04);
    border-radius: 12px;
  }
  @media (max-width: 480px) {
    /* Phone unit-pick cards: horizontal pill layout — small square
       sprite on the LEFT, level + name + stats stacked on the RIGHT.
       Way shorter cards than the old image-on-top layout, so 6+ heroes
       are visible without scrolling and the Start Adventure button
       right under Princess Decide always stays in view. */
    .expo-pick-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
    .expo-pick-tile {
      flex-direction: row !important;
      align-items: center;
      padding: 4px;
      gap: 6px;
      border-radius: 10px;
      min-height: 56px;
    }
    .expo-pick-tile img {
      width: 44px !important;
      height: 44px !important;
      flex: 0 0 44px;
      aspect-ratio: 1 / 1;
    }
    .expo-pick-tile .ept-info {
      flex: 1 1 auto;
      min-width: 0;
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      gap: 1px;
    }
    .expo-pick-tile .ept-pick-badge { top: 2px; right: 4px; font-size: var(--fs-md); }
    .expo-pick-tile .ept-lvl {
      font-size: var(--fs-xs); padding: 0 5px;
      margin: 0;
    }
    .expo-pick-tile .ept-xp { display: none; }
    .expo-pick-tile .ept-name {
      font-size: var(--fs-xs);
      margin: 0;
      text-align: left;
      width: 100%;
    }
    .expo-pick-tile .ept-stats {
      font-size: var(--fs-xs);
      margin: 0;
      gap: 3px;
      width: 100%;
    }
    .expo-pick-tile .ept-stats span { padding: 1px 2px; }
  }
  /* In-forest "🗺 X adventures ready!" placard — h2 + blurb + button
     used --fs-xl/--fs-lg which felt huge on a 390px phone (placard ate
     half the visible expedition zone). Tighter sizes inside narrow
     viewports keep the call-to-action friendly, not overbearing. */
  @media (max-width: 480px) {
    #expedition .placard {
      padding: 8px 12px;
      max-width: 70%;
      border-radius: 14px;
    }
    #expedition .placard h2 { font-size: var(--fs-md); margin: 0 0 3px 0; }
    #expedition .placard p  { font-size: var(--fs-sm); margin: 0 0 6px 0; }
    #expedition .placard .send-btn {
      font-size: var(--fs-sm);
      padding: 5px 12px;
    }
  }
  .expo-pick-tile {
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 14px;
    padding: 5px;
    text-align: center;
    cursor: pointer;
    position: relative;
    box-shadow: 0 2px 0 rgba(0, 0, 0, 0.12);
    display: flex; flex-direction: column;
    font-family: inherit;
  }
  .expo-pick-tile.picked    { border-color: var(--gold); background: #fff8d8; }
  .expo-pick-tile.picked-bk { border-color: #b8a4ff;     background: #ede8ff; }
  .expo-pick-tile img {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: contain;
    image-rendering: pixelated;
  }
  .expo-pick-tile .ept-pick-badge {
    position: absolute; top: 3px; right: 5px;
    font-size: var(--fs-lg); pointer-events: none;
    text-shadow: 0 1px 2px rgba(255,255,255,0.9);
  }
  .expo-pick-tile .ept-pick-badge.bk { color: #6e57b8; }
  .expo-pick-tile .ept-lvl {
    background: var(--accent); color: #fff;
    border-radius: 999px;
    font-size: var(--fs-sm); font-weight: 900;
    padding: 1px 6px;
    display: inline-block;
    margin-top: 3px;
  }
  .expo-pick-tile .ept-xp {
    height: 4px; background: rgba(0,0,0,0.1);
    border-radius: 2px; overflow: hidden;
    margin-top: 3px;
  }
  .expo-pick-tile .ept-xp > i {
    display: block; height: 100%;
    background: var(--good);
  }
  .expo-pick-tile .ept-name {
    font-size: var(--fs-sm); font-weight: 800; line-height: 1.1;
    margin-top: 3px;
    color: var(--ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .expo-pick-tile .ept-stats {
    display: flex; justify-content: space-between;
    gap: 2px;
    margin-top: 4px;
    font-size: var(--fs-sm); font-weight: 700;
    line-height: 1;
  }
  .expo-pick-tile .ept-stats span {
    flex: 1;
    background: rgba(0,0,0,0.05);
    border-radius: 5px;
    padding: 3px 0;
  }
  /* Princess gets a subtle gold ring so she stands out in the grid */
  .expo-pick-tile.princess {
    border-color: var(--gold);
    box-shadow: 0 0 0 2px rgba(255,200,80,0.4), 0 2px 0 rgba(0,0,0,0.12);
  }

  /* Your creature list for training */
  .roster-grid {
    /* Hero picker tiles sized so ~3 columns fit on a phone screen
       (per Katherine: "make it 3 characters wide"). minmax 96px =
       3 cols at ~320px container, 4-5 cols at desktop widths. */
    display: grid; grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: 8px;
  }
  .unit-tile {
    background: #fff;
    border: 2px solid var(--ink);
    border-radius: 12px;
    padding: 4px;
    text-align: center;
    cursor: pointer;
    position: relative;
    box-shadow: 0 2px 0 rgba(0,0,0,0.12);
  }
  .unit-tile.busy { opacity: 0.5; cursor: not-allowed; }
  .unit-tile img {
    width: 100%; aspect-ratio: 1/1; object-fit: contain;
    image-rendering: pixelated;
  }
  .unit-tile .lvl {
    background: var(--accent); color: #fff;
    border-radius: 999px;
    font-size: var(--fs-sm); font-weight: 900;
    padding: 1px 5px;
    display: inline-block;
  }
  .unit-tile .xp {
    height: 4px; background: rgba(0,0,0,0.1);
    border-radius: 2px; overflow: hidden;
    margin-top: 2px;
  }
  .unit-tile .xp > i {
    display: block; height: 100%;
    background: var(--good);
  }
  .unit-tile .name {
    font-size: var(--fs-sm); font-weight: 700; line-height: 1.1;
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  /* Compact stat strip below the name — ❤️ HP / ⚔️ ATK / 💨 SPD */
  .unit-tile .ut-stats {
    display: flex; justify-content: space-between;
    gap: 2px;
    margin-top: 3px;
    font-size: var(--fs-sm); font-weight: 700;
    color: var(--ink);
    line-height: 1;
  }
  .unit-tile .ut-stats span {
    flex: 1;
    background: rgba(0,0,0,0.05);
    border-radius: 6px;
    padding: 2px 0;
  }

  /* ---------- TOAST / SPARKLE ---------- */
  #toasts {
    position: fixed; top: 50px; left: 50%;
    transform: translateX(-50%); z-index: 60;
    pointer-events: none;
    display: flex; flex-direction: column; align-items: center; gap: 4px;
  }
  .toast {
    background: var(--gold);
    border: 2px solid var(--ink);
    border-radius: 999px;
    padding: 7px 16px;
    font-weight: 800; font-size: var(--fs-lg);
    box-shadow: 0 3px 0 rgba(0,0,0,0.18);
    animation: toastIn 0.3s ease-out, toastOut 0.4s ease-in 1.6s forwards;
  }
  @keyframes toastIn {
    0% { transform: translateY(-12px); opacity: 0; }
    100% { transform: translateY(0); opacity: 1; }
  }
  @keyframes toastOut {
    0% { transform: translateY(0); opacity: 1; }
    100% { transform: translateY(-12px); opacity: 0; }
  }

  .sparkle-shower {
    position: fixed; inset: 0; z-index: 70;
    pointer-events: none; overflow: hidden;
  }
  .sparkle {
    position: absolute;
    font-size: var(--fs-xl);
    animation: sparkleFall 1.6s ease-out forwards;
  }
  @keyframes sparkleFall {
    0% { transform: translateY(-30px) rotate(0deg); opacity: 0; }
    20% { opacity: 1; }
    100% { transform: translateY(80vh) rotate(540deg); opacity: 0; }
  }

  .game-version {
    position: fixed; bottom: 2px; right: 4px;
    font-size: var(--fs-md); opacity: 0.55;
    color: #fff;
    pointer-events: none;
    z-index: 99999;
    text-shadow: 0 1px 1px rgba(0,0,0,0.5);
    font-family: system-ui, -apple-system, sans-serif;
  }

  /* Phase 7 — hide the bag/equipment button during adventures. Heroes
     auto-equip; the kid doesn't need to manage gear mid-fight. */
  body.adventuring #bag-btn { display: none !important; }

  /* ---------- PRODUCTION TICKER (Phase 7) ----------
     Subtle row of pills at the BOTTOM of the screen. Each pill is
     building-emoji + recipe-emoji + count. Old pills fade after a few
     seconds. Designed to be ignorable — minimal indicator only. */
  #prod-ticker {
    position: fixed;
    left: 50%;
    bottom: 6px;
    transform: translateX(-50%);
    z-index: 55;
    display: flex; flex-wrap: wrap-reverse; justify-content: center;
    gap: 4px;
    max-width: 92vw;
    pointer-events: none;
  }
  .prod-pill {
    display: inline-flex; align-items: center; gap: 2px;
    padding: 2px 8px;
    background: rgba(255, 255, 255, 0.82);
    border: 1.5px solid rgba(74, 50, 104, 0.55);
    border-radius: 999px;
    font-family: inherit;
    font-size: var(--fs-sm);
    font-weight: 700;
    color: var(--ink);
    box-shadow: 0 1px 0 rgba(0,0,0,0.18);
    backdrop-filter: blur(2px);
    animation: prodPillIn 0.32s cubic-bezier(.55,1.4,.4,1.0);
    transition: opacity 0.6s, transform 0.6s;
    line-height: 1;
  }
  .prod-pill .pp-bldg   { font-size: var(--fs-sm); opacity: 0.65; margin-right: 1px; }
  .prod-pill .pp-recipe { font-size: var(--fs-md); }
  .prod-pill .pp-count  { font-size: var(--fs-xs); font-weight: 800; color: var(--accent); margin-left: 2px; }
  .prod-pill.fading { opacity: 0; transform: translateY(6px); }
  @keyframes prodPillIn {
    0%   { transform: translateY(8px) scale(0.6); opacity: 0; }
    70%  { transform: translateY(-2px) scale(1.05); opacity: 1; }
    100% { transform: translateY(0) scale(1); opacity: 1; }
  }
  body.adventuring #prod-ticker { display: none; }   /* keep adventures uncluttered */

  /* ---------- HERO REQUESTS (Phase 7 speech bubbles) ---------- */
  #hero-requests {
    position: fixed; top: 56px; right: calc(var(--gx) + 6px);
    z-index: 65;
    display: flex; flex-direction: column; gap: 6px;
    max-width: min(80vw, calc(var(--gw) - 12px));
    pointer-events: none;
  }
  .hero-bubble {
    pointer-events: auto;
    display: flex; align-items: center; gap: 6px;
    background: linear-gradient(180deg, #fff0fa 0%, #ffd6ec 100%);
    border: 3px solid var(--accent);
    border-radius: 18px;
    padding: 6px 8px 6px 6px;
    box-shadow: 0 4px 0 rgba(0,0,0,0.18), 0 2px 8px rgba(255,107,181,0.35);
    font-family: inherit;
    color: var(--ink);
    max-width: 320px;
    animation: heroBubbleIn 0.45s cubic-bezier(.55,1.4,.4,1.0);
  }
  .hero-bubble.fade-out { animation: heroBubbleOut 0.4s ease-in forwards; }
  .hero-bubble-face {
    width: 56px; height: 56px;
    border-radius: 50%;
    background: #fff;
    border: 2px solid var(--ink);
    overflow: hidden;
    flex-shrink: 0;
    object-fit: cover;
    object-position: top center;
  }
  .hero-bubble-text {
    flex: 1;
    font-size: var(--fs-sm);
    line-height: 1.2;
    font-weight: 700;
  }
  .hero-bubble-text small {
    display: block;
    font-size: var(--fs-xs);
    color: var(--ink-soft);
    font-weight: 600;
    margin-top: 2px;
  }
  .hero-bubble-actions {
    display: flex; flex-direction: column; gap: 4px;
    flex-shrink: 0;
  }
  .hero-bubble-go, .hero-bubble-x {
    border: 2px solid var(--ink);
    border-radius: 10px;
    padding: 3px 8px;
    font-family: inherit;
    font-weight: 800; font-size: var(--fs-sm);
    cursor: pointer;
    box-shadow: 0 2px 0 rgba(0,0,0,0.18);
  }
  .hero-bubble-go {
    background: linear-gradient(180deg, #fff5b1, #ffd166);
    color: var(--ink);
  }
  .hero-bubble-x {
    background: #fff;
    color: var(--ink-soft);
  }
  @keyframes heroBubbleIn {
    0%   { transform: translateX(40px) scale(0.6); opacity: 0; }
    70%  { transform: translateX(-4px) scale(1.05); opacity: 1; }
    100% { transform: translateX(0) scale(1); opacity: 1; }
  }
  @keyframes heroBubbleOut {
    from { transform: translateX(0) scale(1); opacity: 1; }
    to   { transform: translateX(40px) scale(0.7); opacity: 0; }
  }

  /* Phase 2 (v2): wider-than-portrait styling. The 540px cap is gone
     — --gw at :root now grows fluidly to a 5:4-ratio stage on wide
     screens. We keep the themed background gradient and soft side
     box-shadows for the "card on screen" look (Phase 4 replaces the
     background with a painted scene). */
  @media (min-aspect-ratio: 5/8) {
    body {
      background:
        radial-gradient(circle at 20% 15%, #ffe4f1 0%, transparent 55%),
        radial-gradient(circle at 80% 85%, #e0d4ff 0%, transparent 55%),
        linear-gradient(135deg, #cfeefe 0%, #e9dcff 50%, #ffe1ee 100%);
    }
    #topbar { box-shadow: -1px 0 0 rgba(0,0,0,0.06), 1px 0 0 rgba(0,0,0,0.06); }
    #stage  { box-shadow: -1px 0 0 rgba(0,0,0,0.06), 1px 0 0 rgba(0,0,0,0.06); }
  }

  /* Phase 2 (v2): give the parallax/battle scene a bit more vertical
     real estate on desktop so the wider landscape breathes, and the
     4 phud cells in the HUD get more horizontal room. */
  @media (min-width: 900px) {
    #expedition { flex: 1 1 62%; }
    body.battle-active #expedition { flex: 1 1 72%; }
  }

  /* Phone-only — these rules used to fire via the html.mr-phone class
     added by the head bootstrap, which Phase 1 v2 disabled in favor of
     fluid clamp() tokens. But several elements (battle banner, wave
     pill, per-battler HP bars, wallet pills, topbar title) still need
     to shrink past the clamp() floor on real phones, so they're now
     wrapped in a 480px media query. */
  @media (max-width: 480px) {
    #topbar h1 { font-size: var(--fs-sm); letter-spacing: 0.5px; }
    .top-btns a,
    .top-btns button {
      font-size: var(--fs-xs);
      padding: 2px 6px;
      border-radius: 9px;
    }
    /* !important — the 3 wallet buttons (hero-cap, shop, bag) carry an
       inline font-size: var(--fs-lg) in their HTML that would win otherwise. */
    .pill { font-size: var(--fs-xs) !important; padding: 2px 6px !important; }
    #wallet { gap: 4px; }

    /* Battle banner ("Heading to the Forest Path…", "🌅 The sun is
       setting", "FIGHT!", "WAVE CLEAR!"), post-battle outcome card,
       mid-fight loot drops, toasts, and boss chest — explicitly
       shrink each (NOT `zoom: 0.5`, which iOS Safari doesn't honor)
       so they don't dominate the small phone viewport. */
    .battle-banner {
      top: 60px;
      font-size: var(--fs-md);
      padding: 4px 12px;
      border-width: 2px;
      max-width: 90vw;
    }
    .toast {
      font-size: var(--fs-sm);
      padding: 4px 10px;
      border-width: 1px;
      max-width: 88vw;
    }
    .battle-outcome { font-size: var(--fs-sm); padding: 8px 10px; }
    .battle-outcome .bo-banner { font-size: var(--fs-lg); }
    .battle-outcome .bo-sub    { font-size: var(--fs-xs); }
    .battle-outcome .bo-line   { font-size: var(--fs-xs); }
    .loot-drop { font-size: var(--fs-md); padding: 3px 8px; }
    .boss-chest { font-size: var(--fs-md); }

    /* End-of-run XP / loot card — with 8 heroes the rs-heroes column
       overflowed the bottom of the card. Drop the transform: scale
       wrapper (it scaled visuals but kept layout at full size, so
       overflow was actually hidden behind the parallax). Instead
       shrink every nested piece explicitly + cap the card to the
       visible expedition zone so the inside can scroll if it needs to. */
    .battle-outcome {
      top: 50%;
      width: min(94%, 360px);
      max-height: 88vh;
      overflow-y: auto;
      padding: 10px 12px 8px;
    }
    .battle-outcome .bo-banner { font-size: var(--fs-md); margin-bottom: 3px; }
    .battle-outcome .bo-sub    { font-size: var(--fs-xs); margin-bottom: 2px; }
    .battle-outcome .bo-obtained { font-size: var(--fs-sm); padding: 5px 7px; margin-top: 6px; }
    .battle-outcome .rs-screen   { gap: 5px; margin-top: 4px; }
    .battle-outcome .rs-totals   { font-size: var(--fs-sm); gap: 8px; }
    .battle-outcome .rs-heroes   { gap: 3px; }
    .battle-outcome .rs-hero     { padding: 2px 4px; gap: 6px; border-width: 1px; border-radius: 7px; }
    .battle-outcome .rs-portrait { width: 24px; height: 24px; }
    .battle-outcome .rs-row      { font-size: var(--fs-xs); }
    .battle-outcome .rs-lvl,
    .battle-outcome .rs-lvl-up   { font-size: 10px; padding: 0 4px; }
    .battle-outcome .rs-xpbar    { height: 5px; margin: 1px 0 0; }
    .battle-outcome .rs-xptext   { font-size: 10px; }
    .battle-outcome .rs-loot-title { font-size: var(--fs-xs); margin-bottom: 2px; }
    .battle-outcome .rs-loot-empty { font-size: var(--fs-xs); }
    .battle-outcome .rs-loot-pill  { font-size: 10px; padding: 2px 7px; }
    .battle-outcome .rs-chest      { width: 44px; height: 44px; font-size: var(--fs-xs); }
    .battle-outcome .rs-chest .rs-chest-lid { font-size: var(--fs-lg); }
    .battle-outcome .rs-chest.opened .rs-chest-item { font-size: var(--fs-md); }
    .battle-outcome .rs-chest.opened .rs-chest-name { font-size: 9px; }

    /* Scout-status pill in the top-right of #city — on phone it sat
       on top of the rightmost "Units" district tab. Push it down past
       the tab bar so both stay tappable. */
    #scout-status {
      top: 48px;
      right: 4px;
      padding: 4px 6px;
      max-width: 140px;
      border-width: 2px;
    }
    #scout-status .ss-title,
    #scout-status .ss-row { font-size: 10px; }

    /* King Braum donation dialog (and every other .encounter-card —
       merchant/healer/rescue choice popups) was too big on phone. The
       portrait, title, body and slider row were each scaling on their
       own clamp() tokens and stacking to ~500px tall. Force every
       piece a step smaller inside the phone breakpoint. */
    .encounter-card {
      padding: 10px 12px 10px;
      width: min(94%, 340px);
    }
    .encounter-card .ec-emoji { font-size: var(--fs-xl); margin-bottom: 3px; }
    .encounter-card .ec-title { font-size: var(--fs-md); margin-bottom: 5px; }
    .encounter-card .ec-body  { font-size: var(--fs-sm); margin-bottom: 8px; line-height: 1.3; }
    .encounter-card .ec-actions { gap: 6px; }
    .encounter-card .ec-actions button {
      padding: 6px 10px;
      font-size: var(--fs-sm);
      flex: 1 1 90px;
    }
    .king-ov .king-portrait { width: 72px; height: 72px; margin: 4px auto 2px; }
    .king-slider-wrap { padding: 8px 10px 6px; margin: 6px 0; }
    .king-slider-row { font-size: var(--fs-sm); gap: 6px; margin-top: 4px; }
    .king-slider-pct { font-size: var(--fs-md); }

    /* Generic shrink for every .ov-card-backed modal (shop, bag,
       hero detail, hero cap, tavern, blacksmith forge, bakery,
       store, library, throne, temple, academy, farm, stables,
       hermit, deco, biome picker, path picker). Header h2 + body
       paragraphs were sized for a 540px viewport. */
    .ov-card { padding: 8px; }
    .ov-card header h2 { font-size: var(--fs-md); }
    .ov-card > p { font-size: var(--fs-sm) !important; }
    .close-x { width: 26px; height: 26px; font-size: var(--fs-md); }

    /* Path picker — three big painted cards in a vertical stack. The
       64px boss portrait + --fs-lg name made each card ~120px tall,
       and three together overflowed the modal. */
    .path-row { padding: 6px 8px; gap: 8px; }
    .path-row .pr-boss { width: 48px; height: 48px; }
    .path-row .pr-stars { font-size: var(--fs-sm); }
    .path-row .pr-name { font-size: var(--fs-md); margin-bottom: 1px; }
    .path-row .pr-blurb { font-size: var(--fs-xs); line-height: 1.2; }
    .path-row .pr-rewards { font-size: var(--fs-xs); margin-top: 2px; }
    .path-row .pr-rewards .pr-loot { font-size: var(--fs-xs); padding: 1px 5px; }

    /* Starter creature picker cards. */
    .starter-card .sc-emoji { font-size: var(--fs-xl); }
    .starter-card .sc-name { font-size: var(--fs-sm); }
    .starter-card .sc-creature { font-size: var(--fs-xs); }

    /* Biome picker thumbs were 90x60 — fine, but the meta text used
       --fs-lg name + --fs-md stars/rewards which overflowed the row. */
    .biome-card { padding: 5px; gap: 8px; }
    .biome-card .biome-thumb { width: 64px; height: 48px; }
    .biome-card .biome-name { font-size: var(--fs-md); }
    .biome-card .biome-stars,
    .biome-card .biome-rewards { font-size: var(--fs-xs); }
    .biome-diff { font-size: var(--fs-xs); padding: 1px 6px; }

    /* Item-grid tiles (shop, bag, blacksmith forge, etc.) — shrink
       the min-track so 4-5 items fit per row on phone instead of 3. */
    .item-grid { grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 5px; }
    .item-tile { padding: 4px 3px; border-radius: 9px; }
    .item-tile .rarity-tag { font-size: var(--fs-xs); padding: 0 4px; }

    /* Boutique modal on phone — shrink every fixed pixel size so the
       hair/outfit/accessory rows fit in fewer screens of scroll. */
    .boutique-ov .boutique-card { width: 96vw; max-width: none; border-width: 3px; }
    .boutique-stage { padding: 8px; gap: 8px; }
    .bstage-card { padding: 6px 8px; border-radius: 12px; border-width: 2px; }
    .bstage-img { width: 80px; height: 80px; }
    .bstage-name { font-size: var(--fs-sm); margin-top: 2px; }
    .bstage-sub  { font-size: var(--fs-xs); }
    .bstage-switcher { max-height: 100px; gap: 4px; }
    .bswitch img { width: 32px; height: 32px; }
    .bswitch { padding: 2px; border-width: 2px; border-radius: 8px; }
    .boutique-section { padding: 6px 8px 2px; }
    .bsec-title { font-size: var(--fs-sm); margin-bottom: 3px; }
    .boutique-row { gap: 5px; }
    .boutique-hair-card { padding: 3px 7px; border-width: 2px; border-radius: 10px; gap: 4px; }
    .bh-swatch { width: 20px; height: 20px; border-width: 1px; }
    .bh-label { font-size: var(--fs-xs); }
    .boutique-outfit-card { width: 64px; padding: 3px; border-width: 2px; border-radius: 10px; }
    .bo-preview { width: 44px; height: 44px; }
    .bo-label { font-size: 9px; }
    .boutique-grid-v2 { grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: 5px; padding: 4px 0 6px; }
    .boutique-header { padding: 7px 40px 7px 10px; }
    .boutique-title { font-size: var(--fs-sm); }

    /* In-battle floating elements — kid said attacks + chat were too
       big on phone. Shrink each so it doesn't dominate the small
       battle area. */
    .speech-bubble {
      font-size: var(--fs-sm);
      padding: 5px 10px;
      border-width: 2px;
      border-radius: 12px;
      max-width: 180px;
      min-width: 50px;
    }
    .attack-callout {
      font-size: var(--fs-sm);
      padding: 2px 7px;
      border-width: 1px;
    }
    .dmg-num,
    .dmg-num.crit,
    .dmg-num.weak {
      font-size: var(--fs-lg);
    }
    .dmg-num.resist { font-size: var(--fs-md); }
    .creature-bubble { font-size: var(--fs-md); }
    .city-hint { font-size: var(--fs-sm); padding: 3px 9px; }
    .hero-bubble { font-size: var(--fs-sm); }
    .hero-bubble-face { width: 36px; height: 36px; }
    .hero-bubble-text { font-size: var(--fs-xs); }
    .hero-bubble-text small { font-size: 10px; }

    /* Wave indicator + retreat button top-bar — was being covered by
       the wrapped wallet pills. */
    .exp-topbar { top: 56px; }
    .exp-wave-tag {
      font-size: var(--fs-xs);
      padding: 2px 7px;
      border-width: 1px;
    }
    .exp-retreat-btn { font-size: var(--fs-xs); padding: 2px 7px; }

    /* Per-battler name + HP bar that sit on each ally/enemy sprite —
       60% size on phone (40% reduction). */
    .battler-name { font-size: var(--fs-sm); bottom: -12px; }
    .hp-text { font-size: var(--fs-xs); top: -10px; height: 7px; line-height: 7px; }
    .hp-bar { height: 5px; top: -8px; border-width: 1px; }
  }

  /* Bottom HP/MP HUD on phones — the desktop defaults (--fs-xl/--fs-lg
     fonts, min-height 116px, 30px stat labels, 68px num column) made
     the HUD eat ~30% of the visible expedition zone on a 390px screen,
     pushing the battle scene up so the grass parallax bled out the top
     edge of the HUD. Shrunk everything to half-size on phone so the
     HUD is a tidy ~60px strip the battle can breathe above. The old
     html.mr-phone class was removed in Phase 1 v2, so target via
     @media instead. */
  @media (max-width: 480px) {
    #party-hud-bottom { min-height: 60px; padding: 3px 4px; border-top-width: 2px; }
    .phud-cell { padding: 2px 4px; gap: 2px; }
    .phud-name { font-size: var(--fs-sm); gap: 2px; letter-spacing: 0.2px; }
    .phud-limit { font-size: var(--fs-xs); padding: 0 2px; }
    .phud-statline { font-size: var(--fs-sm); gap: 3px; }
    .phud-statline .stat-label { font-size: var(--fs-sm); width: 15px; }
    .phud-statline .pb-bar { height: 6px; }
    .phud-statline .pb-num { font-size: var(--fs-sm); min-width: 34px; }
  }

  /* ───────── Phase 3 (v2): desktop side panels ─────────
     Two glass-card asides occupy the wide-screen gutters (the empty
     space either side of the centered stage). Hidden on phone and
     anything narrower than the panels need to be useful. Show only
     when the viewport is BOTH wider than 5:4 (so --gx > 0 — there's
     actual gutter space) AND ≥1280px (so the gutters are at least
     ~140-280px each, big enough for icons + text). Content placeholders
     for now; the game-state wiring lands in a follow-up commit. */
  .mr-side {
    display: none;
    position: fixed;
    top: 44px;
    bottom: 0;
    width: calc(var(--gx) - var(--sp-3));
    z-index: 5;
    overflow-y: auto;
    padding: var(--sp-4);
    background: rgba(255, 255, 255, 0.78);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border-top: 2px solid rgba(255, 107, 181, 0.35);
    box-shadow: 0 8px 24px rgba(43, 17, 64, 0.08);
  }
  #mr-left  { left: 0; border-right: 2px solid rgba(255, 107, 181, 0.25); }
  #mr-right { right: 0; border-left:  2px solid rgba(255, 107, 181, 0.25); }
  .mr-side h2 {
    margin: 0 0 var(--sp-3) 0;
    font-size: var(--fs-lg);
    color: var(--accent);
    text-shadow: 0 1px 0 #fff;
  }
  .mr-side .placeholder {
    color: var(--ink-soft);
    font-size: var(--fs-sm);
    line-height: 1.4;
    padding: var(--sp-3);
    border: 2px dashed rgba(91, 58, 122, 0.25);
    border-radius: var(--radius-card);
    background: rgba(255, 255, 255, 0.5);
    text-align: center;
  }
  .mr-hero-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
    gap: var(--sp-2);
  }
  .mr-hero-card {
    background: rgba(255, 255, 255, 0.75);
    border: 2px solid rgba(255, 107, 181, 0.35);
    border-radius: var(--radius-card);
    padding: var(--sp-2);
    text-align: center;
    overflow: hidden;
  }
  .mr-hero-card img {
    width: 100%; aspect-ratio: 1 / 1;
    object-fit: contain;
    image-rendering: pixelated;
  }
  .mr-hero-name {
    font-size: var(--fs-xs);
    color: var(--ink);
    font-weight: 700;
    line-height: 1.1;
    margin-top: var(--sp-1);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .mr-hero-tier {
    font-size: var(--fs-xs);
    color: var(--accent);
    font-weight: 700;
  }
  .mr-hero-more {
    margin-top: var(--sp-2);
    font-size: var(--fs-xs);
    color: var(--ink-soft);
    text-align: center;
    font-style: italic;
  }
  .mr-realm-card {
    background: rgba(255, 255, 255, 0.75);
    border: 2px solid rgba(255, 107, 181, 0.35);
    border-radius: var(--radius-card);
    padding: var(--sp-3);
    margin-bottom: var(--sp-3);
  }
  .mr-realm-big {
    font-size: var(--fs-md);
    font-weight: 800;
    color: var(--ink);
    margin-bottom: var(--sp-1);
  }
  .mr-realm-status {
    font-size: var(--fs-sm);
    color: var(--ink-soft);
  }
  .mr-realm-scouts {
    margin-top: var(--sp-2);
    font-size: var(--fs-sm);
    color: var(--accent);
    font-weight: 700;
  }
  .mr-realm-mini {
    font-size: var(--fs-sm);
    color: var(--ink);
    padding: var(--sp-1) 0;
    display: flex; justify-content: space-between;
  }

  /* ───────── Phase 3.5 (v2): scout mini-adventures ─────────
     One little parallax diorama per active scout adventure. Heroes
     gently bob in place while the mountains + hills scroll past behind
     them, giving the feel of a peaceful walk through the landscape
     (no fighting — just exploring). A global setInterval in the
     script tail randomly pops 💰/⭐/🎁 emoji over the scenes to hint
     at the loot the scouts are gathering. Reuses the same parallax
     art that the expedition zone uses (assets/parallax/*) so no new
     asset generation is needed. */
  .mr-scout-scene {
    position: relative;
    width: 100%;
    aspect-ratio: 3 / 1;
    border-radius: var(--radius-card);
    overflow: hidden;
    margin-bottom: var(--sp-2);
    border: 2px solid rgba(255, 107, 181, 0.35);
    background: linear-gradient(180deg, #cfeefe 0%, #d8f0ff 60%, #cce6c8 100%);
  }
  .mr-scout-scene[data-biome="forest"] {
    background: linear-gradient(180deg, #cfeefe 0%, #d8f0ff 50%, #cce6c8 100%);
  }
  .mr-scout-scene[data-biome="crypt"] {
    background: linear-gradient(180deg, #4a3858 0%, #2c1d3a 100%);
  }
  .mr-scout-scene[data-biome="castle"] {
    background: linear-gradient(180deg, #f9e8e1 0%, #e5b7c1 100%);
  }
  .mss-mountains, .mss-hills {
    position: absolute;
    left: 0;
    width: 200%;
    background-size: auto 100%;
    background-repeat: repeat-x;
    pointer-events: none;
  }
  .mss-mountains {
    bottom: 18%;
    height: 60%;
    opacity: 0.85;
    animation: mss-scroll 70s linear infinite;
  }
  .mss-hills {
    bottom: 0;
    height: 35%;
    animation: mss-scroll 28s linear infinite;
  }
  @keyframes mss-scroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
  }
  .mr-scout-scene[data-biome="forest"] .mss-mountains { background-image: url('./assets/parallax/mountains-forest.webp'); }
  .mr-scout-scene[data-biome="forest"] .mss-hills     { background-image: url('./assets/parallax/forest-mid-hills.webp'); }
  .mr-scout-scene[data-biome="crypt"]  .mss-mountains { background-image: url('./assets/parallax/mountains-crypt.webp'); }
  .mr-scout-scene[data-biome="crypt"]  .mss-hills     { background-image: url('./assets/parallax/crypt-mid-hills.webp'); }
  .mr-scout-scene[data-biome="castle"] .mss-mountains { background-image: url('./assets/parallax/mountains-castle.webp'); }
  .mr-scout-scene[data-biome="castle"] .mss-hills     { background-image: url('./assets/parallax/castle-mid-hills.webp'); }
  .mss-hero {
    position: absolute;
    bottom: 4%;
    height: 70%;
    width: auto;
    z-index: 2;
    image-rendering: pixelated;
    filter: drop-shadow(0 2px 2px rgba(0,0,0,0.3));
    animation: mss-bob 0.6s ease-in-out infinite alternate;
  }
  .mss-hero-1 { left: 22%; }
  .mss-hero-2 { left: 46%; animation-delay: -0.3s; }
  .mss-hero-3 { left: 70%; animation-delay: -0.15s; }
  @keyframes mss-bob {
    from { transform: translateY(0); }
    to   { transform: translateY(-3px); }
  }
  .mss-label {
    position: absolute;
    top: var(--sp-1);
    right: var(--sp-1);
    background: rgba(255, 255, 255, 0.88);
    color: var(--ink);
    padding: 2px var(--sp-2);
    border-radius: 999px;
    font-size: var(--fs-xs);
    font-weight: 700;
    z-index: 3;
  }
  .mss-loot {
    position: absolute;
    z-index: 4;
    font-size: var(--fs-md);
    pointer-events: none;
    animation: mss-loot 1.8s ease-out forwards;
    text-shadow: 0 2px 4px rgba(0,0,0,0.35);
  }
  @keyframes mss-loot {
    0%   { transform: translateY(0)   scale(0.4); opacity: 0; }
    20%  { transform: translateY(-6px) scale(1.1); opacity: 1; }
    100% { transform: translateY(-30px) scale(0.7); opacity: 0; }
  }

  /* Scout-diorama enter / exit animations. When the kid sends a new
     scout group, the whole panel "expands open" (height + opacity) and
     the hero sprites walk in from the left edge. When the scouts return
     home, heroes walk off to the right and the panel collapses. The
     enter/exit modifier classes are added/removed by renderScoutScenes()
     in the JS tail. */
  .mr-scout-scene.entering {
    animation: mss-scene-in 700ms cubic-bezier(0.22, 1, 0.36, 1) both;
  }
  .mr-scout-scene.entering .mss-hero {
    animation: mss-hero-walkin 800ms cubic-bezier(0.34, 1.2, 0.64, 1) both,
               mss-bob 0.6s ease-in-out infinite alternate 800ms;
  }
  .mr-scout-scene.exiting {
    animation: mss-scene-out 600ms cubic-bezier(0.5, 0, 0.75, 0.2) forwards;
    pointer-events: none;
  }
  .mr-scout-scene.exiting .mss-hero {
    animation: mss-hero-walkout 600ms cubic-bezier(0.5, 0, 0.75, 0.2) forwards;
  }
  @keyframes mss-scene-in {
    0%   { opacity: 0; transform: translateY(-8px) scale(0.96);
           max-height: 0; margin-bottom: 0; padding-top: 0; padding-bottom: 0; }
    40%  { opacity: 1; max-height: 240px; margin-bottom: var(--sp-2); }
    100% { opacity: 1; transform: translateY(0) scale(1);
           max-height: 240px; margin-bottom: var(--sp-2); }
  }
  @keyframes mss-scene-out {
    0%   { opacity: 1; transform: translateY(0) scale(1);
           max-height: 240px; margin-bottom: var(--sp-2); }
    60%  { opacity: 0.4; transform: translateY(4px) scale(0.97); }
    100% { opacity: 0; transform: translateY(8px) scale(0.94);
           max-height: 0; margin-bottom: 0; padding-top: 0; padding-bottom: 0;
           border-width: 0; }
  }
  @keyframes mss-hero-walkin {
    0%   { transform: translateX(-90px) translateY(0); opacity: 0; }
    20%  { opacity: 1; }
    100% { transform: translateX(0) translateY(0); opacity: 1; }
  }
  @keyframes mss-hero-walkout {
    0%   { transform: translateX(0) translateY(0); opacity: 1; }
    80%  { opacity: 0.8; }
    100% { transform: translateX(90px) translateY(0); opacity: 0; }
  }

  @media (min-aspect-ratio: 5/4) and (min-width: 1280px) {
    .mr-side { display: block; }
  }
