* { box-sizing: border-box; margin: 0; padding: 0; }

html, body {
  width: 100%; height: 100%;
  background: #f8d8e0;
  overflow: hidden;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  user-select: none;
}

/* Background fills the whole viewport, behind everything */
.bg-cover {
  position: fixed; inset: 0;
  width: 100vw; height: 100vh;
  object-fit: cover;
  object-position: center;
  pointer-events: none;
  z-index: 0;
}

/* In portrait the background spans from the top down to where the drawer's
   rounded corners end; width scales proportionally with the height. JS sets
   --bg-h based on the current stage scale. */
@media (orientation: portrait) {
  .bg-cover {
    inset: auto;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
    height: var(--bg-h, 100vh);
    width: auto;
    max-width: none;
    object-fit: fill;
  }

  /* Battle mode in portrait: the bright bg fills the full screen height,
     width proportional (auto) and centred; the dimmed ::before copy below
     fills any side strips. */
  body.battle-mode .bg-cover {
    top: 0;
    height: 100vh;
  }
  /* Battle backdrop: the whole viewport goes black, then a 75 %-opacity
     copy of the bg image is laid over it so the empty strips above and
     below the cropped main bg read as a dim version of the room. */
  body.battle-mode {
    background: #000;
  }
  body.battle-mode::before {
    content: '';
    position: fixed;
    inset: 0;
    background-image: url('assets/bg.png');
    background-size: cover;
    background-position: center;
    opacity: 0.75;
    z-index: 0;
    pointer-events: none;
  }
}

/* Stage holds the UI; scaled by viewport height, anchored to the right edge */
.stage-wrap {
  position: fixed; inset: 0;
  overflow: hidden;
  z-index: 1;
}

.stage {
  position: absolute;
  top: 0; right: 0;
  width: 1920px;
  height: 1080px;
  transform-origin: top right;
  /* Keep 3D context for children: without this, Android Chrome flattens
     children translate3d back to 2D matrix, defeating GPU promotion. */
  transform-style: preserve-3d;
  -webkit-transform-style: preserve-3d;
}

/* ---------- Character ---------- */
/* Each .char is the native 1210x1920 canvas. Its position+scale comes from
   JS (character.setLayout) — anchored so the silhouette fills the available
   area regardless of how much padding the canvas has around the body. */
#chars { position: absolute; inset: 0; pointer-events: none; }

/* One slot per girl. Sized/positioned in stage coords by JS.
   Overflow stays visible everywhere so the body extends naturally behind
   the drawer / off-screen, and the floor shadow (which extends far past
   the slot to the left) doesn't get cropped in trio. */
.char-slot {
  position: absolute;
  pointer-events: none;
  z-index: 0;
  overflow: visible;
}

.char {
  position: absolute;
  top: 0; left: 0;
  /* width/height set by JS to CHAR_W/CHAR_H; transform comes from setLayout */
  transform-origin: top left;
  transition: opacity 0.25s ease;
}
/* Dim non-active girls in trio — semi-transparent (not tinted) so they
   read as "softer" copies of the active girl in the centre. Landscape
   default below; portrait override later disables this so the three
   mobile-mode girls all render at 100 % opacity. */
.char.dim .breath {
  filter: none;
  transition: filter 0.25s ease;
}
/* Inactive trio girls stay fully opaque on every screen — clicking to pick a
   girl must NOT fade the others. The .active-marker is the only "active" cue. */
.char.dim          { opacity: 1; }
/* No floor-shadow override — let --shadow-opacity (0.20) apply equally
   to active and dimmed girls, per design request. */
.char.dim .active-marker { display: none; }

/* ---------- Active-girl marker (trio mode) ----------
   Small pink diamond floating just above the active girl's head. Scales
   with the character because it sits inside .char (in canvas px). */
.char .active-marker {
  position: absolute;
  left: 605px;             /* centered on canvas */
  top: 40px;               /* ~head top (bbox top = 80) minus a little */
  width: 110px;
  height: 110px;
  transform: translate(-50%, -100%) rotate(45deg);
  background: linear-gradient(135deg, #ff7ab6 0%, #ff3a8d 100%);
  border: 6px solid #fff;
  box-shadow: 0 6px 18px rgba(243, 60, 130, 0.45);
  opacity: 0;
  transition: opacity 0.2s ease;
  pointer-events: none;
  z-index: 3;
}
#chars.trio .char.active .active-marker { opacity: 1; }

/* ---------- Floor cast shadow ----------
   Cloned silhouette laid flat on the floor, extending to the left of the
   character (light comes from the upper-right). Pivot is at the feet
   (bbox bottom = y=1810 of the 1920-tall canvas). The shadow lives
   inside .char so it inherits the figure's scale automatically — it
   shrinks/grows with the character.

   === Tweak these to adjust the shadow look ===
   All values are in the character's native canvas px (1210×1920) and
   scale with the rendered figure. */
.char .floor-shadow {
  --shadow-rotate:    -85.3deg;   /* -90° = flat horizontal; less negative = more upright */
  --shadow-squash:     0.45;    /* vertical compression: 1 = none, 0.3 = very flat */
  --shadow-offset-x:   120px;     /* horizontal nudge (negative = left) */
  --shadow-offset-y:   -180px;     /* vertical nudge (positive = down) */
  --shadow-opacity:    0.20;    /* 0 = invisible, 1 = solid black */
  --shadow-blur:       4px;     /* soften the silhouette edges (per-layer) */

  position: absolute;
  inset: 0;
  transform-origin: 50% 1810px;
  /* Order (matrix-order, right applied first):
       1. rotate to lay horizontally
       2. squash vertically
       3. translate (nudge) */
  transform:
    translate(var(--shadow-offset-x), var(--shadow-offset-y))
    scale(1, var(--shadow-squash))
    rotate(var(--shadow-rotate));
  /* NOTE: filter intentionally NOT here. A parent-level
     `filter: brightness(0) blur()` forces the browser to allocate a full
     intermediate buffer for all ~20 child layers (≈76 MB per character at
     high DPR), which crushes mobile and causes texture eviction → flicker.
     We apply filter on each .layer below instead — same visual, no parent
     compositing buffer needed. */
  opacity: var(--shadow-opacity);
  pointer-events: none;
  z-index: 0;
}
.char .breath { z-index: 1; }

/* ---------- Mobile lite-mode ----------
   On coarse-pointer devices (phones/tablets) the floor shadow is too
   expensive: it duplicates ~10 layered 1210×1920 PNGs per character with
   `filter: brightness(0) blur(6px)` on top of mask compositing. On iOS
   Safari that quickly overruns the texture budget and the browser starts
   evicting layers — symptoms are partial / flickering character rendering.

   We also drop `will-change: transform` on animated wrappers so each one
   stops promoting to its own GPU layer. Animations still run; they just
   share fewer composite layers. */
/* Mobile mode is triggered by ANY of these — some Android Chrome builds
   report (pointer: fine) even on touchscreens, so we need broader checks.
   `any-pointer: coarse` catches every device with a touch input, even
   touch-capable laptops. `max-width: 900px` is a viewport-size safety net
   for phones/tablets that fall through. */
@media (any-pointer: coarse), (hover: none), (max-width: 900px) {
  /* Mobile mode keeps the full visual now — shadow, animations and
     filters all stay. The previous "lite-mode" cuts traded look for
     stability; we get stability back by killing the actual memory hog
     (the parent-level shadow filter — see .floor-shadow above) instead
     of removing features.
     Only safe perf hint we keep is content-visibility on wardrobe cells
     so 40+ off-screen item PNGs don't all decode at once. */
  .cell {
    content-visibility: auto;
    contain-intrinsic-size: 211px 211px;
  }
}
/* The shadow is rendered as a single <canvas> per character (see
   character.js — game-engine style texture caching). The canvas backing
   store is persistent, so mobile image-cache eviction can't make the
   shadow flicker like it did with 19 cloned <img> layers. */
.char .floor-shadow > canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* brightness(0) is baked into the canvas pixels — only blur left. */
  filter: blur(var(--shadow-blur));
}

/* Body-sway wrapper. Slow lateral weight shift; sits OUTSIDE breath so it
   composes with the breathing scale without overriding it. Halved from the
   original amplitude for a more subtle, lifelike weight shift. */
.char .body-sway {
  position: absolute;
  inset: 0;
  animation: body-sway 9s ease-in-out infinite;
  transform-origin: 50% 92%;
}
@keyframes body-sway {
  0%, 100% { transform: translateX(-2.5px) rotate(-0.125deg); }
  50%      { transform: translateX( 2.5px) rotate( 0.125deg); }
}

/* Breathing wrapper. Holds all character layers and pulses vertically so the
   chest "breathes". Origin at feet (~92% of canvas height) so the feet stay
   planted instead of moving with the bottom of the padded canvas. */
.char .breath {
  position: absolute;
  inset: 0;
  transform-origin: 50% 92%;
  animation: breathe 4s ease-in-out infinite;
  will-change: transform;
}
@keyframes breathe {
  0%, 100% { transform: scaleY(1.000); }
  50%      { transform: scaleY(1.008); }
}

/* ---------- Idle micro-animations on individual layers ----------
   Each is scoped to .breath so the floor-shadow clones don't double the
   motion. The shadow stays planted. */

/* Closed-eyes canvas — the eyelid PNG pre-tinted in character.js with
   the same skin-multiply formula as the body, so the colour matches
   exactly. Default opacity 0; the .blinking class plays a one-shot
   keyframe animation that fades it in and back out.

   Keyframe (not transition) on purpose: a CSS transition on `opacity`
   under our composited setup (the .breath/.body-sway parents each run
   their own transform animations) could leave the transition stuck at
   `currentTime: 0` in some Chrome versions. A keyframe animation just
   plays its timeline; no transition tick required. */
.char .eye-closed-canvas {
  opacity: 0;
}
.char .eye-closed-canvas.blinking {
  animation: eyelid-blink 220ms ease-in-out forwards;
}
@keyframes eyelid-blink {
  0%   { opacity: 0; }
  40%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { opacity: 0; }
}

/* Hair sway — only when a hair item is equipped. Pivot near the scalp. */
.char .breath .layer[data-slot="hair"] {
  transform-origin: 50% 18%;
  animation: hair-sway 5.5s ease-in-out infinite;
  will-change: transform;
}
@keyframes hair-sway {
  0%, 100% { transform: rotate(-0.6deg); }
  50%      { transform: rotate( 0.6deg); }
}

/* Earrings — very subtle dangle (small angle). */
.char .breath .layer[data-slot="earrings"] {
  transform-origin: 50% 24%;
  animation: jewel-swing 3.2s ease-in-out infinite;
}
@keyframes jewel-swing {
  0%, 100% { transform: rotate(-0.35deg); }
  50%      { transform: rotate( 0.35deg); }
}

/* Light beam reaction — character brightens a touch while the beam is mid-
   sweep, as if the lamp swung onto her. Parent-level filter; composes with
   child filters (drop-shadow / saturate) since they're on different elems. */
body.beam-active .char {
  filter: brightness(1.05);
  transition: filter 0.6s ease-out;
}

/* ---------- Sparkles ----------
   Spawned by JS on outfit changes. Four-point star SVG, in canvas-pixel
   coordinates inside .char so they scale with the figure. Each one fades
   in, twirls, then fades out — no DOM left behind (JS removes after). */
.char .sparkle {
  position: absolute;
  pointer-events: none;
  z-index: 5;
  background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40"><path d="M20 1 L22.5 17.5 L39 20 L22.5 22.5 L20 39 L17.5 22.5 L1 20 L17.5 17.5 Z" fill="white"/></svg>');
  background-size: contain;
  background-repeat: no-repeat;
  filter: drop-shadow(0 0 8px rgba(255, 225, 245, 0.95)) drop-shadow(0 0 18px rgba(255, 180, 220, 0.55));
  opacity: 0;
  animation: sparkle 900ms ease-out forwards;
  --spark-rot: 0deg;
}
@keyframes sparkle {
  0%   { transform: scale(0)   rotate(calc(var(--spark-rot) - 90deg)); opacity: 0; }
  20%  { transform: scale(1)   rotate(var(--spark-rot));               opacity: 1; }
  60%  { transform: scale(1.2) rotate(calc(var(--spark-rot) + 80deg)); opacity: 0.9; }
  100% { transform: scale(0.3) rotate(calc(var(--spark-rot) + 200deg)); opacity: 0; }
}

/* In face zoom the slot is sized to the head bbox, but we let the body
   extend naturally: behind the drawer in portrait, off the bottom in
   landscape. (Was overflow:hidden; that cropped the figure too hard.) */
#chars.face .char-slot { overflow: visible; }

/* ---------- Light beam (event-driven sweep) ----------
   .beam-clip is sized to the scene area (above the drawer in portrait,
   left of the UI panel in landscape) and clips with overflow:hidden so
   the beam can never bleed onto the UI.

   .light-beam itself is far larger than the wrapper (8× each axis) so
   the gradient's transparent edges always sit way outside the visible
   area — the user never sees where the stripe begins or ends.

   Inactive by default. A click on .beam-toggle adds .beam-active +
   .beam-from-* to body, which plays one of four directional sweeps
   (from bottom/top/left/right) with JS-randomized duration and width.
   animationend clears the classes so the next click can re-fire. */
.beam-clip {
  /* Light beam removed: it's an 8x-viewport animated layer that costs GPU
     fill-rate on weak devices for purely decorative value. display:none drops
     the layer entirely; the (harmless) beam timer in main.js just toggles
     classes on a hidden element. */
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right:  calc(100vw - var(--scene-right-vp,  100vw));
  bottom: calc(100vh - var(--scene-bottom-vp, 100vh));
  overflow: hidden;
  pointer-events: none;
  z-index: 45;
}
.light-beam {
  --beam-half-w: 50px;       /* half-width of the bright core */
  position: absolute;
  top: -350%;
  left: -350%;
  width: 800%;
  height: 800%;
  background: linear-gradient(135deg,
    transparent 0%,
    transparent calc(50% - var(--beam-half-w) - 50px),
    rgba(255, 255, 255, 0.05) calc(50% - var(--beam-half-w)),
    rgba(255, 255, 255, 0.44) 50%,
    rgba(255, 255, 255, 0.05) calc(50% + var(--beam-half-w)),
    transparent calc(50% + var(--beam-half-w) + 50px),
    transparent 100%);
  mix-blend-mode: overlay;
  opacity: 0;
  will-change: transform, opacity;
}
/* JS sets --beam-duration per-click for ±30% speed jitter. */
body.beam-active.beam-from-bottom .light-beam {
  animation: beam-from-bottom var(--beam-duration, 50s) ease-in-out forwards;
}
body.beam-active.beam-from-top .light-beam {
  animation: beam-from-top    var(--beam-duration, 50s) ease-in-out forwards;
}
body.beam-active.beam-from-left .light-beam {
  animation: beam-from-left   var(--beam-duration, 50s) ease-in-out forwards;
}
body.beam-active.beam-from-right .light-beam {
  animation: beam-from-right  var(--beam-duration, 50s) ease-in-out forwards;
}
@keyframes beam-from-bottom {
  0%   { opacity: 0; transform: translate3d(0,  130vh, 0); }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(0, -130vh, 0); }
}
@keyframes beam-from-top {
  0%   { opacity: 0; transform: translate3d(0, -130vh, 0); }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(0,  130vh, 0); }
}
@keyframes beam-from-left {
  0%   { opacity: 0; transform: translate3d(-130vw, 0, 0); }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d( 130vw, 0, 0); }
}
@keyframes beam-from-right {
  0%   { opacity: 0; transform: translate3d( 130vw, 0, 0); }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(-130vw, 0, 0); }
}

/* ---------- Back-to-menu button (in-game) ----------
   Figma 58-174: hot-pink circular gradient with white outline and a
   subtle inset highlight. Larger than before (76px) to read on phones.
   Position varies by orientation — top-left in landscape, top-right
   in portrait (matches the second Figma layout). */
.menu-back-btn {
  position: fixed;
  left: 14px; top: 14px;
  width: 38px; height: 38px;
  border-radius: 50%;
  border: 3px solid #fff;
  background: linear-gradient(135deg, #ff7ab6 0%, #ff3a8d 100%);
  /* Layered shadows: drop + soft pink glow halo around the white ring,
     matches the lit look of Figma 58-174 / 58-7. */
  box-shadow:
    0 6px 16px rgba(243, 60, 130, 0.45),
    0 0 28px rgba(255, 122, 182, 0.5),
    inset 0 0 0 4px rgba(255, 255, 255, 0.25);
  cursor: pointer;
  padding: 0;
  z-index: 50;
  display: grid; place-items: center;
  transition: transform 0.15s;
}
.menu-back-btn:hover  { transform: scale(1.08); }
.menu-back-btn:active { transform: scale(0.94); }
.menu-back-btn:focus-visible {
  outline: 3px solid #c8517c;
  outline-offset: 2px;
}
.menu-back-btn[hidden] { display: none; }
.menu-back-glyph {
  display: block;
  width: 14px; height: 14px;
  border-left: 4px solid #fff;
  border-top:  4px solid #fff;
  transform: rotate(-45deg) translate(2px, 2px);
  border-radius: 2px;
}

/* ---------- SUBMIT button (Figma 58-141 / 58-46) ----------
   Large gradient pill at the bottom of the wardrobe panel in landscape
   and the bottom of the drawer in portrait. In portrait there is ALSO a
   smaller pill ✓ floating above the drawer (Figma 58-75) — both trigger
   the same battle flow. UX-wise SUBMIT == "I'm done dressing, judge me".

   .submit-btn-mini is the smaller floating pill (portrait only); CSS
   below hides it in landscape so we don't ship two buttons on desktop.
   !important needed because the later `.submit-btn { display:flex }`
   has equal specificity and would otherwise win. */
.submit-btn-mini { display: none !important; }
.submit-btn {
  position: absolute;
  left: 1395px; top: 890px;
  width: 460px; height: 130px;
  border-radius: 999px;
  border: 4px solid #fff;
  background: linear-gradient(-42deg, #ff0765 0%, #ff8fc1 91%);
  color: #fff;
  font-family: 'Arial Rounded MT Bold', system-ui, sans-serif;
  font-size: 56px;
  font-weight: 800;
  letter-spacing: 4px;
  cursor: pointer;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 18px;
  box-shadow:
    0 8px 20px rgba(255, 20, 111, 0.35),
    inset 0 0 0 6px rgba(255, 20, 111, 0.3);
  transition: transform 0.15s, filter 0.15s;
}
.submit-btn:hover  { transform: scale(1.02); filter: brightness(1.05); }
.submit-btn:active { transform: scale(0.97); }
.submit-btn:focus-visible {
  outline: 3px solid #c8517c;
  outline-offset: 4px;
}
.submit-btn[hidden] { display: none; }
/* Same 3D forward push as .grid-title — keeps SUBMIT in front of the
   panel-back wash on Android Chrome 145. */
.submit-btn { transform: translate3d(0, 0, 2px); backface-visibility: hidden; }
.submit-btn:hover  { transform: translate3d(0, 0, 2px) scale(1.02); }
.submit-btn:active { transform: translate3d(0, 0, 2px) scale(0.97); }
/* SUBMIT label — !important on everything that any later rule (or
   browser default) might still override. Pure system font, big size,
   white fill, dark outline for maximum visibility on the pink pill. */
.submit-btn .submit-label {
  position: relative !important;
  display: inline-block !important;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif !important;
  font-size: 56px !important;
  font-weight: 900 !important;
  color: #fff !important;
  letter-spacing: 4px !important;
  -webkit-text-stroke: 2px #c8517c;
          text-stroke: 2px #c8517c;
  text-shadow: 0 3px 6px rgba(196, 12, 76, 0.55);
  line-height: 1 !important;
  z-index: 2 !important;
  transform: translate3d(0, 0, 4px);
  white-space: nowrap !important;
  pointer-events: none;
  opacity: 1 !important;
  visibility: visible !important;
}
.submit-check {
  position: relative;
  width: 50px; height: 50px;
  flex: 0 0 auto;
}
.submit-check::before,
.submit-check::after {
  content: '';
  position: absolute;
  background: #fff;
  border-radius: 4px;
}
.submit-check::before {
  width: 8px; height: 32px;
  transform: rotate(45deg);
  top: 6px; left: 26px;
}
.submit-check::after {
  width: 8px; height: 18px;
  transform: rotate(-45deg);
  top: 16px; left: 8px;
}

/* ---------- Action bar (random / reset / photo) ----------
   Vertical stack on the top-left under the menu-back button (landscape).
   In portrait it stays in the same spot — small screens have plenty of bg
   real-estate above the drawer where these three buttons fit comfortably. */
.action-bar {
  position: fixed;
  left: 14px;
  top: 86px;                 /* below the 56px back button + 16 gap */
  display: flex;
  flex-direction: column;
  gap: 12px;
  z-index: 50;
}
.action-bar[hidden] { display: none; }
.action-btn {
  width: 56px; height: 56px;
  border-radius: 50%;
  border: 3px solid #fff;
  background: linear-gradient(180deg, #ffbbc8 0%, #ff8fc1 100%);
  box-shadow: 0 4px 10px rgba(243, 100, 160, 0.30);
  cursor: pointer;
  padding: 0;
  display: grid; place-items: center;
  transition: transform 0.15s, filter 0.15s;
}
.action-btn:hover  { transform: scale(1.08); filter: brightness(1.05); }
.action-btn:active { transform: scale(0.94); }
.action-btn:focus-visible {
  outline: 3px solid #c8517c;
  outline-offset: 2px;
}
.action-btn.busy { opacity: 0.65; cursor: progress; pointer-events: none; }

/* All three glyphs share size + colour; only the shape differs. We use
   inline CSS instead of icon assets because UI/add|photo|cancel.png are
   styled for grid cells (different scale + drop-shadow) — drawing the
   icon in CSS keeps the action bar pixel-crisp at any DPR. */
.action-glyph {
  display: block;
  width: 26px; height: 26px;
  pointer-events: none;
}

/* Random: two staggered dots evoke "dice / chance". */
.action-glyph-random {
  background: #fff;
  -webkit-mask:
    radial-gradient(circle at 30% 30%, #000 3.5px, transparent 4px) 0 0 / 100% 100%,
    radial-gradient(circle at 70% 70%, #000 3.5px, transparent 4px) 0 0 / 100% 100%;
          mask:
    radial-gradient(circle at 30% 30%, #000 3.5px, transparent 4px) 0 0 / 100% 100%,
    radial-gradient(circle at 70% 70%, #000 3.5px, transparent 4px) 0 0 / 100% 100%;
  -webkit-mask-composite: source-over;
          mask-composite: add;
  border: 2px solid #fff;
  border-radius: 6px;
}

/* Reset: a circular-arrow shape made from a partial border + arrow tip. */
.action-glyph-reset {
  width: 24px; height: 24px;
  border: 3px solid #fff;
  border-top-color: transparent;
  border-right-color: transparent;
  border-radius: 50%;
  position: relative;
  transform: rotate(-45deg);
}
.action-glyph-reset::after {
  content: '';
  position: absolute;
  left: -3px; top: -2px;
  width: 0; height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-bottom: 7px solid #fff;
  transform: rotate(-45deg);
}

/* Photo: rounded rectangle camera body + circular lens. */
.action-glyph-photo {
  width: 28px; height: 22px;
  border: 3px solid #fff;
  border-radius: 5px;
  position: relative;
  background: transparent;
}
.action-glyph-photo::before {
  content: '';
  position: absolute;
  top: -7px; left: 50%;
  width: 10px; height: 5px;
  background: #fff;
  border-radius: 3px 3px 0 0;
  transform: translateX(-50%);
}
.action-glyph-photo::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 9px; height: 9px;
  background: #fff;
  border-radius: 50%;
  transform: translate(-50%, -50%);
}

/* Battle button glyph: crossed swords drawn with two rotated bars. */
.action-glyph-battle {
  width: 26px; height: 26px;
  position: relative;
}
.action-glyph-battle::before,
.action-glyph-battle::after {
  content: '';
  position: absolute;
  left: 50%; top: 50%;
  width: 4px; height: 22px;
  background: #fff;
  border-radius: 2px;
  transform-origin: 50% 50%;
}
.action-glyph-battle::before { transform: translate(-50%, -50%) rotate( 45deg); }
.action-glyph-battle::after  { transform: translate(-50%, -50%) rotate(-45deg); }
.action-btn-battle {
  /* Slightly hotter gradient to draw the eye — this is the "main attraction". */
  background: linear-gradient(180deg, #ff9bb6 0%, #ff6aa3 100%);
}

/* While battle is up, hide the regular game UI without unmounting it —
   restoreChars/exitBattle just removes the body class and everything
   reappears in place. visibility lets layout stay measured so chars get
   placed correctly even before the overlay closes. */
body.battle-mode .cat-column,
body.battle-mode .panel,
body.battle-mode .cat-strip,
body.battle-mode .grid-title,
body.battle-mode .submit-btn,
body.battle-mode .submit-btn .submit-label,
body.battle-mode .scroll-track,
body.battle-mode .cat-list-track,
body.battle-mode .grid,
body.battle-mode .menu-back-btn,
body.battle-mode .action-bar {
  visibility: hidden !important;
}

/* Battle slots tile the whole scene area — no overlap, no face-zoom marker. */
.char-slot.battle-slot { overflow: visible; }

/* In battle the floor shadows become a compact, flat pool directly
   UNDER each girl's feet (offset 0, fully horizontal, heavy squash)
   instead of the long sideways sweep — that sweep spilled onto the
   neighbouring girl in 3v3. Faint 10 % so it never reads as "on top of"
   another character. (A per-character transform on .char traps the
   shadow in its own stacking context, so a global z-order can't push
   all shadows under all bodies — keeping each shadow tucked under its
   own feet is the robust fix.) */
body.battle-mode .char .floor-shadow {
  --shadow-opacity: 0.10;
  --shadow-rotate: -90deg;
  --shadow-squash: 0.18;
  --shadow-offset-x: 0px;
  --shadow-offset-y: 0px;
  --shadow-blur: 8px;
  z-index: -1;
}

/* ---------- Battle overlay (searching + result) ---------- */
.battle-overlay {
  position: fixed;
  inset: 0;
  z-index: 110;
  background: rgba(35, 12, 25, 0.78);
  color: #fff;
  display: grid;
  place-items: center;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
}
.battle-overlay[hidden] { display: none; }
.battle-stage {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  padding: 24px;
  max-width: 88vw;
  text-align: center;
}
.battle-stage[hidden] { display: none; }

.battle-spinner {
  width: 96px; height: 96px;
  border-radius: 50%;
  /* Conic gradient with a transparent slice — rotates to read as a spinner. */
  background: conic-gradient(from 0deg, #ff8fc1 0deg, #ff8fc1 240deg, transparent 240deg, transparent 360deg);
  -webkit-mask: radial-gradient(circle, transparent 30px, #000 31px);
          mask: radial-gradient(circle, transparent 30px, #000 31px);
  animation: battle-spin 1s linear infinite;
}
@keyframes battle-spin {
  to { transform: rotate(360deg); }
}
.battle-search-title {
  font-size: 28px;
  font-weight: 800;
  letter-spacing: 1px;
}
.battle-search-sub {
  font-size: 16px;
  opacity: 0.85;
  min-height: 1.4em;
}

/* Result title — colour swap signals win vs lose. */
.battle-result-title {
  font-size: clamp(40px, 9vw, 80px);
  font-weight: 900;
  letter-spacing: 3px;
  margin: 0;
  text-shadow: 0 4px 18px rgba(0, 0, 0, 0.45);
}
.battle-overlay.won  .battle-result-title { color: #ffd7e5; }
.battle-overlay.lost .battle-result-title { color: #ffb5b5; }
.battle-result-sub {
  font-size: 18px;
  opacity: 0.9;
  max-width: 32ch;
}

/* ---------- Scoreboard (during fight) ----------
   Two independent fixed-position cells. main.js sets `left` on each so
   the cell is centred above its team; `transform: translateX(-50%)`
   pins the horizontal centre. The .battle-scoreboard wrapper only
   exists to flip both cells on/off together via [hidden]. */
.battle-scoreboard { pointer-events: none; }
.battle-scoreboard[hidden] .battle-score { display: none; }

.battle-score {
  position: fixed;
  z-index: 80;
  transform: translateX(-50%);
  background: rgba(255, 255, 255, 0.92);
  color: #c8517c;
  font-weight: 800;
  padding: 8px 18px;
  border-radius: 999px;
  display: flex;
  align-items: center;
  gap: 8px;
  box-shadow: 0 6px 18px rgba(180, 60, 110, 0.35);
  transition: background 0.2s ease, color 0.2s ease, transform 0.2s ease;
  min-width: 110px;
  justify-content: center;
  white-space: nowrap;
}
.battle-score.leader {
  background: linear-gradient(180deg, #ffbbc8 0%, #ff5fa6 100%);
  color: #fff;
  /* Keep the translateX(-50%) when scaling so the cell stays centred. */
  transform: translateX(-50%) scale(1.10);
}
.battle-heart {
  font-size: 20px;
  line-height: 1;
}
.battle-num {
  font-variant-numeric: tabular-nums;
  font-size: 22px;
  letter-spacing: 0.5px;
}

/* Middle girl in a 3-girl team needs to draw over her sisters so the
   overlap looks like a group photo, not a sprite collision. */
.char-slot.battle-mid { z-index: 2; }

/* Central VS badge. Sits dead-centre of the viewport (X + Y) between
   the two team-score cells. */
.battle-vs-big {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 72px; height: 72px;
  border-radius: 50%;
  background: linear-gradient(135deg, #ff7ab6 0%, #ff3a8d 100%);
  color: #fff;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 26px;
  font-weight: 900;
  letter-spacing: 1px;
  display: grid;
  place-items: center;
  box-shadow:
    0 6px 16px rgba(243, 60, 130, 0.45),
    inset 0 0 0 4px #fff;
  z-index: 81;
}

/* Result banner — title at the top of the viewport, OK button at the
   bottom. No full-screen overlay so the win/lose animation behind stays
   visible. */
.battle-result-banner {
  position: fixed;
  top: 20px;
  left: 0; right: 0;
  z-index: 120;
  text-align: center;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  pointer-events: none;
  color: #fff;
}
.battle-result-banner[hidden] { display: none; }
#battleResultBannerTitle {
  font-size: clamp(48px, 9vw, 90px);
  font-weight: 900;
  letter-spacing: 4px;
  line-height: 1;
  margin: 0;
  color: #ff3a8d;
  text-shadow:
    0 0 2px #fff,
    0 4px 10px rgba(196, 12, 76, 0.55),
    0 0 30px rgba(255, 122, 182, 0.6);
  animation: result-pop 0.45s cubic-bezier(.5, 1.6, .5, 1) backwards;
}
#battleResultBannerSub {
  margin: 6px 0 0;
  font-size: 18px;
  color: #fff;
  text-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
}
@keyframes result-pop {
  0%   { transform: scale(0); opacity: 0; }
  60%  { transform: scale(1.15); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
/* Portrait (mobile): push the ПОБЕДА banner to the very top. */
@media (orientation: portrait) {
  .battle-result-banner { top: 4px; }
}

/* OK button — pinned to the bottom. Larger tap target on phones. */
.battle-result-ok {
  position: fixed;
  left: 50%;
  bottom: 32px;
  transform: translateX(-50%);
  z-index: 120;
  padding: 16px 56px;
  border-radius: 999px;
  border: 4px solid #fff;
  background: linear-gradient(-42deg, #ff0765 0%, #ff8fc1 91%);
  color: #fff;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: 1px;
  cursor: pointer;
  box-shadow:
    0 8px 20px rgba(255, 20, 111, 0.45),
    inset 0 0 0 3px rgba(255, 20, 111, 0.3);
}
.battle-result-ok[hidden] { display: none; }
.battle-result-ok:hover  { filter: brightness(1.05); transform: translateX(-50%) scale(1.04); }
.battle-result-ok:active { transform: translateX(-50%) scale(0.97); }

/* Confetti container fills the screen; particles spawned by JS. */
.battle-confetti {
  position: fixed;
  inset: 0;
  z-index: 115;
  pointer-events: none;
  overflow: hidden;
}
.battle-confetti[hidden] { display: none; }
.battle-confetti span {
  position: absolute;
  top: -20px;
  width: 12px; height: 18px;
  border-radius: 2px;
  animation: confetti-fall linear forwards;
}
@keyframes confetti-fall {
  0%   { transform: translate3d(0, -20px, 0) rotate(0deg); opacity: 1; }
  100% { transform: translate3d(var(--confetti-x), 110vh, 0) rotate(720deg); opacity: 0.85; }
}

/* ---------- Battle particle FX (hearts/stars) ----------
   Floating ambient particles around the girls + click/tap bursts. Sits
   above the girls but below the scoreboard/banner so counters stay readable.
   Each particle flies by (--fx-dx,--fx-dy), spins (--fx-rot), over --fx-dur. */
.battle-fx {
  position: fixed;
  inset: 0;
  z-index: 70;
  pointer-events: none;
  overflow: hidden;
}
.battle-fx[hidden] { display: none; }
.fx-particle {
  position: fixed;
  left: 0; top: 0;
  line-height: 1;
  text-shadow: 0 0 6px rgba(255, 255, 255, 0.7);
  will-change: transform, opacity;
  animation: fx-pop var(--fx-dur, 1.2s) ease-out forwards;
}
@keyframes fx-pop {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.3) rotate(0deg); }
  18%  { opacity: 1; transform: translate(-50%, -50%) scale(1.05) rotate(calc(var(--fx-rot) * 0.2)); }
  100% { opacity: 0;
         transform: translate(calc(-50% + var(--fx-dx)), calc(-50% + var(--fx-dy)))
                    scale(0.9) rotate(var(--fx-rot)); }
}

/* Win / lose poses. The .battle-winner slot transitions to its target
   centre-of-screen position via JS-set --battle-shift (px) on the slot.
   .battle-loser slides off-screen in the opposite direction.
   The .breath element inside the winner runs a 2x jump animation. */
.char-slot.battle-winner,
.char-slot.battle-loser {
  transition: transform 0.6s ease-out, opacity 0.6s ease-out;
}
.char-slot.battle-winner {
  /* Centre + optional victory zoom. Scale pivots at the feet (bottom
     centre) so the girl grows upward staying planted; --battle-win-y then
     drops her feet onto the floor line. All three vars are set by main.js
     (zoomWinnersPortrait) on a portrait win and default to no-op, so
     landscape just centres via --battle-shift. */
  transform: translateX(var(--battle-shift, 0)) translateY(var(--battle-win-y, 0)) scale(var(--battle-win-scale, 1));
  transform-origin: bottom center;
}
.char-slot.battle-loser {
  transform: translateX(var(--battle-shift, 0));
  opacity: 0;
}
.char-slot.battle-winner .breath {
  animation: winner-jump 0.6s ease-out 2;
}
@keyframes winner-jump {
  0%   { transform: translateY(0)    scale(1); }
  35%  { transform: translateY(-40px) scale(1.05); }
  100% { transform: translateY(0)    scale(1); }
}

/* Portrait (mobile): big counters + big VS pinned to the top, over the
   heads — there's empty space up there anyway. (JS sets each cell's left.) */
@media (orientation: portrait) {
  .battle-score { padding: 9px 20px; min-width: 116px; border-radius: 999px; gap: 9px; }
  .battle-num   { font-size: 30px; }
  .battle-heart { font-size: 26px; }
  .battle-vs-big { width: 78px; height: 78px; font-size: 28px; }
}

/* Landscape (PC): BIG counters stacked (heart over number) pinned to the
   left & right screen edges at mid-height, well off the girls' heads. */
@media (orientation: landscape) {
  .battle-score {
    flex-direction: column;
    gap: 2px;
    top: 40%;
    transform: translateY(-50%);
    padding: 16px 30px;
    min-width: 0;
    border-radius: 30px;
  }
  .battle-score[data-side="player"] { left: 32px;  right: auto; }
  .battle-score[data-side="bots"]   { right: 32px; left: auto; }
  .battle-heart { font-size: 48px; }
  .battle-num   { font-size: 46px; }
  .battle-score.leader { transform: translateY(-50%) scale(1.07); }
}

/* Toast — short-lived status text near the action bar. Shown after photo
   download so the user has feedback that the file is on its way. */
.toast {
  position: fixed;
  left: 14px;
  bottom: 14px;
  padding: 10px 16px;
  background: rgba(50, 20, 35, 0.85);
  color: #fff;
  font: 500 14px/1.3 system-ui, sans-serif;
  border-radius: 999px;
  box-shadow: 0 6px 16px rgba(0,0,0,0.25);
  pointer-events: none;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.18s ease, transform 0.18s ease;
  z-index: 60;
}
.toast.visible { opacity: 1; transform: translateY(0); }

/* ---------- Main menu overlay ---------- */
/* Main menu: full-screen street backdrop, the player's girl large and
   centred, settings gear + likes counter on top, two big mode buttons
   (СОЛО / ТРИО) at the bottom that start the chosen mode directly. */
.main-menu {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  background: linear-gradient(180deg, #ffd7e5 0%, #ffb5d0 100%);
  user-select: none;
  overflow: hidden;
}
.main-menu[hidden] { display: none; }
.main-menu-bg {
  position: absolute;
  inset: 0;
  /* bg.png deferred: applied only after the Poki SDK is up (html.assets-ready,
     set in main.js once the asset gate opens) so it doesn't compete with the
     SDK download. Until then the menu shows its gradient (.main-menu) — and the
     loader covers everything anyway, so the swap-in is invisible. */
  background-image: none;
  background-size: cover;
  background-position: center;
  pointer-events: none;
}
html.assets-ready .main-menu-bg { background-image: url("assets/bg.png"); }

/* Top bar: gear (left) + likes (right). */
.menu-topbar {
  position: relative;
  z-index: 2;
  width: 100%;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px;
}
.menu-gear {
  width: 58px; height: 58px;
  border-radius: 50%;
  border: 4px solid #fff;
  background: linear-gradient(180deg, #ffc24d 0%, #ff8a00 100%);
  display: grid; place-items: center;
  cursor: pointer;
  box-shadow: 0 5px 14px rgba(200, 120, 0, 0.35);
  transition: transform 0.12s, filter 0.12s;
}
.menu-gear:hover  { filter: brightness(1.06); }
.menu-gear:active { transform: scale(0.94); }
.menu-gear-glyph::before { content: '\2699'; font-size: 32px; color: #fff; line-height: 1; }
.menu-likes {
  display: flex; align-items: center; gap: 8px;
  background: linear-gradient(180deg, #ff7ab6 0%, #ff3a8d 100%);
  border: 4px solid #fff;
  border-radius: 999px;
  padding: 6px 20px 6px 16px;
  color: #fff; font-weight: 800; font-size: 26px;
  box-shadow: 0 5px 14px rgba(243, 60, 130, 0.35);
}
.menu-likes-heart { font-size: 24px; line-height: 1; }

/* Girl fills the space between the top bar and the buttons. The
   .menu-char-host becomes a native 1210×1920 .char scaled by setLayout;
   overflow:hidden clips the empty canvas padding. */
.menu-char-frame {
  position: relative;
  z-index: 1;
  flex: 1 1 auto;
  min-height: 0;
  width: min(94vw, 560px);
  /* Visible so the floor shadow (just below the feet, outside the bbox)
     isn't clipped. The canvas padding around it is transparent, and the
     frame sits below the top bar / buttons (z-index 1), so nothing leaks. */
  overflow: visible;
}
.menu-char-host {
  position: absolute;
  top: 0; left: 0;
  pointer-events: none;
}

/* Two chunky bottom buttons. */
.menu-mode-row {
  position: relative;
  z-index: 2;
  display: flex;
  gap: 18px;
  padding: 0 16px 28px;
}
.mode-btn {
  border: 5px solid #fff;
  border-radius: 30px;
  font: 900 clamp(26px, 6vw, 38px)/1 system-ui, -apple-system, "Segoe UI", sans-serif;
  letter-spacing: 2px;
  color: #fff;
  cursor: pointer;
  padding: 16px clamp(28px, 8vw, 56px);
  text-shadow: 0 3px 0 rgba(0, 0, 0, 0.18);
  box-shadow: 0 8px 0 rgba(0, 0, 0, 0.12), 0 14px 24px rgba(0, 0, 0, 0.2);
  transition: transform 0.12s, filter 0.12s, box-shadow 0.12s;
}
.mode-btn:hover  { filter: brightness(1.05); }
.mode-btn:active { transform: translateY(4px); box-shadow: 0 4px 0 rgba(0, 0, 0, 0.12), 0 8px 14px rgba(0, 0, 0, 0.2); }
.mode-btn-solo { background: linear-gradient(180deg, #ffc24d 0%, #ff8a00 100%); }
.mode-btn-trio { background: linear-gradient(180deg, #ff8fd0 0%, #ff3a8d 100%); }

.menu-btn {
  border: 4px solid #fff;
  background: linear-gradient(180deg, #ffbbc8 0%, #ff8fc1 100%);
  color: #fff;
  font-size: 22px;
  font-weight: 700;
  padding: 16px 36px;
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 6px 16px rgba(243, 100, 160, 0.30);
  transition: transform 0.15s, filter 0.15s;
}
.menu-btn:hover  { transform: scale(1.03); filter: brightness(1.05); }
.menu-btn:active { transform: scale(0.97); }
.menu-btn:focus-visible {
  outline: 3px solid #c8517c;
  outline-offset: 3px;
}
.menu-btn-primary {
  font-size: 30px;
  padding: 22px 56px;
}

/* ---------- Loading screen ---------- */
.app-loader {
  position: fixed; inset: 0;
  z-index: 9999;
  display: grid; place-items: center;
  background: linear-gradient(180deg, #ffd7e5 0%, #ff9ec9 100%);
  transition: opacity 0.4s ease;
}
.app-loader.hide { opacity: 0; pointer-events: none; }
.app-loader[hidden] { display: none; }
.app-loader-inner { display: flex; flex-direction: column; align-items: center; gap: 26px; }
.app-loader-title {
  font: 900 clamp(40px, 9vw, 72px)/1 system-ui, -apple-system, "Segoe UI", sans-serif;
  letter-spacing: 2px;
  color: #ff3a8d;
  text-shadow: 0 0 2px #fff, 0 4px 12px rgba(196, 12, 76, 0.45);
}
.app-loader-spin {
  width: 64px; height: 64px;
  border-radius: 50%;
  border: 7px solid rgba(255, 255, 255, 0.6);
  border-top-color: #ff3a8d;
  animation: app-loader-spin 0.9s linear infinite;
}
@keyframes app-loader-spin { to { transform: rotate(360deg); } }

/* ---------- Settings panel ---------- */
.settings-overlay {
  position: fixed; inset: 0;
  z-index: 200;
  display: grid; place-items: center;
  background: rgba(40, 10, 30, 0.55);
}
.settings-overlay[hidden] { display: none; }
.settings-card {
  background: linear-gradient(180deg, #fff 0%, #ffeef5 100%);
  border: 4px solid #ff9ed1;
  border-radius: 28px;
  padding: 26px 28px 22px;
  width: min(90vw, 420px);
  max-height: 86vh; overflow: auto;
  box-shadow: 0 18px 50px rgba(180, 40, 100, 0.4);
  text-align: center;
}
.settings-title {
  margin: 0 0 18px;
  font-size: 30px; font-weight: 900; color: #c8517c;
}
.settings-rows { display: flex; flex-direction: column; gap: 14px; margin-bottom: 18px; }
.settings-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  font-weight: 700; color: #b8487a; font-size: 17px;
}
.settings-row-label { white-space: nowrap; }
.settings-select {
  flex: 1 1 auto; max-width: 58%;
  border: 2px solid #ff9ed1; border-radius: 12px;
  padding: 8px 10px; font-size: 16px; color: #b8487a; background: #fff;
}
.settings-slider { flex: 1 1 auto; max-width: 58%; accent-color: #ff3a8d; height: 26px; }

/* Menu top-left button cluster (gear + trophy). */
.menu-topbar-left { display: flex; align-items: center; gap: 10px; }
.menu-ach { background: linear-gradient(180deg, #ffd24d 0%, #ff8a00 100%); }
.menu-ach-glyph { font-size: 28px; line-height: 1; }

/* Achievements panel list. */
.ach-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; text-align: left; }
.ach-row {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; border-radius: 16px;
  background: #fff5fa; border: 2px solid #ffd6e8;
  opacity: 0.55;
}
.ach-row.done { opacity: 1; border-color: #ff7ab6; background: linear-gradient(180deg, #fff 0%, #ffe6f1 100%); }
.ach-row-icon  { font-size: 28px; line-height: 1; }
.ach-row-name  { flex: 1 1 auto; font-weight: 700; color: #b8487a; font-size: 16px; }
.ach-row-check { font-size: 20px; }

/* Achievement-unlocked toast — top centre, slides in on any screen. */
.ach-toast {
  position: fixed;
  top: 14px; left: 50%;
  transform: translate(-50%, -150%);
  z-index: 300;
  display: flex; align-items: center; gap: 12px;
  background: linear-gradient(180deg, #ff7ab6 0%, #ff3a8d 100%);
  color: #fff;
  border: 3px solid #fff; border-radius: 999px;
  padding: 10px 22px 10px 16px;
  box-shadow: 0 10px 26px rgba(243, 60, 130, 0.5);
  opacity: 0;
  transition: transform 0.4s cubic-bezier(.4, 1.5, .5, 1), opacity 0.4s ease;
  pointer-events: none;
  max-width: 92vw;
}
.ach-toast[hidden] { display: none; }
.ach-toast.show { transform: translate(-50%, 0); opacity: 1; }
.ach-toast-icon  { font-size: 34px; line-height: 1; }
.ach-toast-text  { display: flex; flex-direction: column; line-height: 1.15; }
.ach-toast-label { font-size: 13px; opacity: 0.9; font-weight: 700; }
.ach-toast-name  { font-size: 18px; font-weight: 800; }

/* Pretty pink scrollbar for the scrollable panels (settings / achievements)
   instead of the default chunky grey one. */
.settings-card { scrollbar-width: thin; scrollbar-color: #ff5fa6 transparent; }
.settings-card::-webkit-scrollbar { width: 10px; }
.settings-card::-webkit-scrollbar-track { background: transparent; margin: 10px 0; }
.settings-card::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, #ff9ed1 0%, #ff5fa6 100%);
  border-radius: 999px;
  border: 2px solid #fff;
}

/* ---------- Submit confirmation ---------- */
.submit-confirm {
  position: fixed; inset: 0;
  z-index: 210;
  display: grid; place-items: center;
  background: rgba(40, 10, 30, 0.55);
}
.submit-confirm[hidden] { display: none; }
.submit-confirm-card {
  background: linear-gradient(180deg, #fff 0%, #ffeef5 100%);
  border: 4px solid #ff9ed1;
  border-radius: 30px;
  padding: 28px 30px 24px;
  width: min(90vw, 440px);
  text-align: center;
  box-shadow: 0 18px 50px rgba(180, 40, 100, 0.4);
  animation: result-pop 0.4s cubic-bezier(.5, 1.6, .5, 1);
}
.submit-confirm-emoji { font-size: 46px; line-height: 1; }
.submit-confirm-text {
  margin: 14px 0 22px;
  font-size: 22px; font-weight: 700; color: #b8487a;
}
.submit-confirm-actions { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
.submit-confirm-no {
  background: linear-gradient(180deg, #e9e2e6 0%, #cfc3ca 100%);
  color: #6f5560;
}

/* ---------- First-run tutorial ---------- */
/* Lives INSIDE .main-menu so the highlighted button can sit above the dim
   layer within the menu's own stacking context. */
.tutorial {
  position: absolute; inset: 0;
  z-index: 40;
  background: rgba(20, 5, 18, 0.6);
}
.tutorial[hidden] { display: none; }
/* During the tutorial the whole mode row is lifted above the dim layer (a
   child can't escape the row's own stacking context), then the NON-target
   button is dimmed + disabled so only the highlighted one stands out. */
/* Lift the row above the dim AND raise it off the bottom so the pointing
   hand fits below the highlighted button. */
.main-menu.tut-active .menu-mode-row { z-index: 50; padding-bottom: 104px; }
.main-menu.tut-active .mode-btn:not(.tut-target) {
  opacity: 0.4;
  pointer-events: none;
}
.mode-btn.tut-target {
  position: relative;
  animation: tut-pulse 1.1s ease-in-out infinite;
}
@keyframes tut-pulse {
  0%, 100% { box-shadow: 0 8px 0 rgba(0,0,0,0.12), 0 0 0 0 rgba(255,255,255,0.85); }
  50%      { box-shadow: 0 8px 0 rgba(0,0,0,0.12), 0 0 0 16px rgba(255,255,255,0); }
}
.tutorial-hand {
  position: absolute;
  z-index: 60;
  font-size: 60px;
  transform: translate(-50%, 0);
  filter: drop-shadow(0 4px 6px rgba(0,0,0,0.4));
  animation: tut-bob 0.9s ease-in-out infinite;
  pointer-events: none;
}
@keyframes tut-bob {
  0%, 100% { transform: translate(-50%, 0); }
  50%      { transform: translate(-50%, -12px); }   /* bob up toward the button */
}

/* ---------- Active-slot lift in trio ----------
   In trio with overlapping face zoom slots, the active girl needs to draw
   ON TOP of the others so neither her body nor the marker get hidden. */
#chars.trio .char-slot.active-slot { z-index: 1; }
.char .layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  -webkit-user-drag: none;
}
.char .skin-tint {
  mix-blend-mode: multiply;
  -webkit-mask-image: url("assets/girl.png");
          mask-image: url("assets/girl.png");
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

/* ---------- Panels ---------- */
.panel {
  position: absolute;
  border: 5px solid #fff;
  border-radius: 50px;
  opacity: 0.95;
  pointer-events: none;
  z-index: 3;
  /* GPU promotion to dodge an Android Chrome 145 paint bug where children
     of a transform: scale() parent silently aren't painted. translateZ(0)
     alone was getting optimised back to a 2D identity matrix on that
     browser, so we use a stronger combo: will-change hints the compositor
     up-front, translate3d with non-zero Z forces an actual 3D path, and
     backface-visibility cements the dedicated layer. */
  will-change: transform;
  transform: translate3d(0, 0, 1px);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
/* Figma 58-97: big rounded panel that wraps both the category column
   and the grid. Soft cream/rose tint + a strong inset white highlight,
   plus a translucent 4px white border. */
.panel-back {
  left: 1209px; top: 30px;
  width: 682px; height: 1020px;
  background: rgba(253, 240, 242, 0.6);
  border: 4px solid rgba(255, 255, 255, 0.5);
  border-radius: 50px;
  box-shadow: inset 0 4px 60px 15px rgba(255, 255, 255, 0.55);
}
/* Figma 58-99: lighter overlay on the grid half — kept separate from
   panel-back so the cat column reads slightly warmer than the items. */
.panel-front {
  left: 1354px; top: 30px;
  width: 537px; height: 1020px;
  background: rgba(255, 249, 242, 0.6);
  border-radius: 0 50px 50px 0;
}

/* Pink gradient stripe behind the category icons. Hidden in landscape —
   only the active category gets a pink pill (.cat-item.active). The
   mobile drawer still uses .cat-strip as a soft pink card; the portrait
   media query overrides display + position below. */
.cat-strip { display: none; }

/* Big category title centred over the grid (Figma 58-139). */
.grid-title {
  position: absolute;
  left: 1370px;
  top: 60px;
  width: 480px;
  text-align: center;
  font-family: 'Arial Rounded MT Bold', system-ui, sans-serif;
  font-weight: 800;
  font-size: 60px;
  color: #ff428d;
  letter-spacing: 2px;
  z-index: 7;
  pointer-events: none;
  text-shadow: 0 2px 0 rgba(255, 255, 255, 0.6);
  text-transform: uppercase;
  transform: translate3d(0, 0, 2px);
  backface-visibility: hidden;
}

/* Mobile-only elements — hidden by default (landscape) */
.cat-list-track { display: none; }

/* ---------- Scrollbar ----------
   Mobile-style: bright pink gradient thumb, narrower track. */
.scroll-track {
  position: absolute;
  left: 1845px; top: 160px;
  width: 18px; height: 710px;
  background: #ffdee3;
  border: 3px solid #fff;
  border-radius: 999px;
  z-index: 5;
  box-sizing: border-box;
  transform: translate3d(0, 0, 3px);
  backface-visibility: hidden;
}
.scroll-thumb {
  position: absolute;
  left: 2px; top: 2px;
  width: 8px; height: 160px;
  background: linear-gradient(180deg, #ff8fc1 0%, #ff3a8d 100%);
  border-radius: 999px;
  box-shadow: 0 3px 8px rgba(196, 70, 120, 0.45);
  transition: top 0.15s ease-out;
}

/* ---------- Side category column ---------- */
.cat-column {
  position: absolute;
  left: 1209px;
  top: 30px;
  width: 150px;
  height: 1020px;
  z-index: 4;
  display: flex; flex-direction: column;
  align-items: center;
  /* Android Chrome 145 paint workaround — see .panel. */
  will-change: transform;
  transform: translate3d(0, 0, 1px);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.cat-arrow {
  position: absolute;
  width: 88px; height: 41px;
  left: 28px;
  background: transparent;
  border: 0;
  cursor: pointer;
  padding: 0;
  z-index: 6;
  display: grid;
  place-items: center;
}
.cat-arrow .chevron {
  display: block;
  width: 36px; height: 36px;
  border-left: 8px solid #c8517c;
  border-top: 8px solid #c8517c;
  transform: rotate(45deg);    /* points up */
  border-radius: 4px;
}
.cat-arrow-up   { top: 30px; }
.cat-arrow-down { top: 953px; }
.cat-arrow-down .chevron { transform: rotate(225deg); }
.cat-arrow:hover .chevron { filter: brightness(1.15); }
.cat-arrow:active { transform: translateY(1px) scale(0.97); }
.cat-arrow:focus-visible {
  outline: 3px solid #c8517c;
  outline-offset: 2px;
  border-radius: 8px;
}

/* Category list — flat scrollable column in both orientations.
   In landscape the column is 1020 tall with arrows at top/bottom, so the
   scrollable area is between them. */
.cat-list {
  position: absolute;
  left: 0;
  top: 80px;
  width: 150px;
  height: 870px;
  overflow-y: auto;
  overflow-x: hidden;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  scrollbar-width: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 6px 0;
}
.cat-list::-webkit-scrollbar { display: none; }
.cat-list.dragging, .grid.dragging { cursor: grabbing; }
.cat-list.dragging *, .grid.dragging * { pointer-events: none; }
/* Live drag must NOT animate the scroll: scroll-behavior:smooth makes each
   scrollTop write smoothly animate to its new target, so during a drag the
   visible scroll constantly chases the pointer instead of pinning to it. */
.cat-list.dragging, .grid.dragging { scroll-behavior: auto; }
/* Same idea for the custom thumbs: their `transition: top` would make the
   bar lag behind the content during drag/momentum. */
body.is-dragging .scroll-thumb,
body.is-dragging .cat-list-thumb { transition: none; }

.cat-item {
  position: relative;
  width: 130px;
  height: 110px;
  flex: 0 0 110px;
  display: grid;
  place-items: center;
  cursor: pointer;
  border-radius: 26px;
  transition: background-color 0.18s, transform 0.15s;
  z-index: 7;
}
.cat-item:hover { background: rgba(255, 145, 195, 0.10); }
.cat-item:focus-visible {
  outline: 3px solid #c8517c;
  outline-offset: 2px;
  background: rgba(255, 145, 195, 0.16);
}
/* Active category sits inside an opaque pink pill (Figma 58-138).
   The colour pops harder than before so the player can spot the current
   slot at a glance against the stripe behind. */
.cat-item.active {
  background: linear-gradient(180deg, rgba(254, 192, 203, 0.95) 0%, rgba(254, 160, 198, 0.95) 100%);
  box-shadow: 0 4px 14px rgba(243, 100, 160, 0.35);
}
.cat-item img {
  max-width: 86px; max-height: 86px;
  width: auto; height: auto;
  pointer-events: none;
  filter: drop-shadow(0 2px 3px rgba(196, 70, 120, 0.25));
  transition: transform 0.18s ease;
}
.cat-item:hover img { transform: scale(1.08); }
.cat-item.active img { transform: scale(1.06); }

/* ---------- Grid ----------
   Bottom mask-image gradient so the last row of cells dissolves into
   the panel edge instead of being chopped by a hard horizontal line. */
.grid {
  position: absolute;
  left: 1381px; top: 160px;
  width: 477px;
  height: 710px;
  display: grid;
  grid-template-columns: repeat(2, 211px);
  grid-auto-rows: 211px;
  gap: 15px;
  -webkit-mask-image: linear-gradient(to bottom,
    #000 0%,
    #000 calc(100% - 70px),
    transparent 100%);
          mask-image: linear-gradient(to bottom,
    #000 0%,
    #000 calc(100% - 70px),
    transparent 100%);
  overflow-y: auto;
  overflow-x: hidden;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  scrollbar-width: none;
  padding: 0 0 15px 0;
  z-index: 5;
  /* Android Chrome 145 paint workaround — see .panel. */
  will-change: transform;
  transform: translate3d(0, 0, 1px);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
.grid::-webkit-scrollbar { display: none; }

/* Wardrobe cell — Figma node 58-104 (desktop) / 58-21 (mobile). */
.cell {
  position: relative;
  width: 211px; height: 211px;
  border: 4px solid #f8ccd9;
  border-radius: 40px;
  background: linear-gradient(132.8deg, #fff3d7 9%, #ffe1d4 100%);
  box-shadow: 0 4px 11px rgba(203, 114, 134, 0.25);
  cursor: pointer;
  display: grid; place-items: center;
  overflow: hidden;
  transition: transform 0.15s ease, box-shadow 0.15s ease, border-color 0.15s;
}
.cell:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 14px rgba(203, 114, 134, 0.35);
}
.cell:active { transform: translateY(0) scale(0.98); }
.cell:focus-visible {
  outline: 3px solid #c8517c;
  outline-offset: 3px;
}

/* Loading state: a centred ring spinner overlays the empty cell while
   the asset queue resolves both the preview icon AND the cloth source
   PNG. We only flip `.loading` off once both are decoded — so a click
   on a tile that just appeared is guaranteed to apply instantly,
   without a per-click decode stall.
   The spinner is pure CSS (no extra DOM) — keeps the grid cheap to
   maintain when scrolling through 40+ cells. */
.cell.loading::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 38px; height: 38px;
  border: 4px solid rgba(196, 70, 120, 0.18);
  border-top-color: #c8517c;
  border-radius: 50%;
  animation: cell-spin 0.75s linear infinite;
  pointer-events: none;
}
@keyframes cell-spin {
  to { transform: rotate(360deg); }
}
@media (orientation: portrait) {
  .cell.loading::after { width: 60px; height: 60px; border-width: 6px; }
}

.cell.selected {
  background: linear-gradient(132.8deg, #ffbbc8 9%, #ff8fc1 100%);
  border: 5px solid #fc7ebc;
  box-shadow: 0 4px 11px rgba(203, 114, 134, 0.35);
}

/* The "_0" none-preview fills the cell like a normal item icon (dedicated
   icons use background-size:contain centred, so we match with object-fit). */
.cell.cancel > img:not(.done) {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: contain;
  pointer-events: none;
}
/* The generic crossed-circle fallback stays small + centred. */
.cell.cancel > img.cancel-fallback {
  position: static;
  inset: auto;
  width: 130px; height: 130px;
}

/* Done checkmark — bottom-right pink button with a white ✓.
   Drawn in CSS (no .png asset) so the size scales cleanly with the cell
   in both landscape (40px) and portrait (60px). */
/* Done checkmark — bottom-right pink circle with white ✓. */
.cell .done {
  position: absolute;
  right: 6px; bottom: 6px;
  width: 40px; height: 40px;
  border-radius: 50%;
  background: linear-gradient(135deg, #ff7ab6 0%, #fc7ebc 100%);
  box-shadow:
    0 2px 6px rgba(196, 70, 120, 0.45),
    inset 0 0 0 3px #fff;
  opacity: 0;
  transform: scale(0.4);
  transition: opacity 0.2s, transform 0.25s cubic-bezier(.4,1.5,.5,1);
  pointer-events: none;
  display: grid;
  place-items: center;
}
.cell .done::before,
.cell .done::after {
  content: '';
  position: absolute;
  background: #fff;
  border-radius: 2px;
}
.cell .done::before {
  width: 5px; height: 16px;
  transform: rotate(45deg) translate(-1px, 1px);
  top: 9px; left: 18px;
}
.cell .done::after {
  width: 5px; height: 10px;
  transform: rotate(-45deg) translate(-1px, 1px);
  top: 15px; left: 11px;
}
.cell.selected .done { opacity: 1; transform: scale(1); }
@media (orientation: portrait) {
  .cell .done { width: 60px; height: 60px; right: 10px; bottom: 10px; }
  .cell .done::before { width: 7px; height: 24px; top: 14px; left: 27px; }
  .cell .done::after  { width: 7px; height: 14px; top: 24px; left: 16px; }
}

/* Item thumbnail — fills the cell; the cell's border-radius + overflow:hidden
   clips it into the rounded shape, so no separate mask asset needed. */
.item-icon {
  position: absolute;
  inset: 0;
  background-repeat: no-repeat;
  pointer-events: none;
}

/* Skin-tone swatch */
.skin-swatch {
  width: 70%; height: 70%;
  border-radius: 50%;
  box-shadow: inset 0 6px 10px rgba(0,0,0,0.08), 0 3px 6px rgba(0,0,0,0.15);
  pointer-events: none;
}

/* =========================================================================
   MOBILE / PORTRAIT LAYOUT  (design: 1080 x 1920)
   ========================================================================= */
@media (orientation: portrait) {
  .stage {
    width: 1080px;
    height: 1920px;
    top: auto;
    right: auto;
    bottom: 0;
    left: 0;
    transform-origin: bottom left;
  }

  /* Character layout: JS sets .char transform from CHAR_AREAS.portrait. */

  /* Trio mobile: all three girls render at full opacity — the ◇ active
     marker is the only "selected" cue. Floor shadows already share the
     20 % opacity from the default --shadow-opacity. */
  .char.dim {
    opacity: 1;
  }
  .char.dim .breath {
    filter: none;
  }

  /* Repurpose the two panel rectangles for the bottom drawer.
     Figma 58-14 wraps everything in a soft-pink wash with a strong
     inset white highlight; cat-strip provides the deeper pink stripe
     behind the category column. */
  .panel-back,
  .panel-front {
    opacity: 1;
  }
  .panel-front {
    /* Inner cream drawer body. */
    left: 0; top: 1304px;
    width: 1080px; height: 616px;
    background: #fff4f4;
    border: none;
    border-radius: 60px 60px 0 0;
    box-shadow: none;
    z-index: 1;
  }
  .panel-back {
    /* Solid pink wash with inset highlight — sharp top edge as before. */
    left: 0; top: 1304px;
    width: 1080px; height: 616px;
    background: #ffdee3;
    border: 4px solid rgba(255, 255, 255, 0.5);
    border-radius: 50px 50px 0 0;
    box-shadow: inset 0 4px 60px 15px rgba(255, 255, 255, 0.55);
    z-index: 2;
  }
  /* Lighter pink card behind the category column. */
  .cat-strip {
    display: block;
    position: absolute;
    left: 24px; top: 1360px;
    width: 145px; height: 520px;
    background: rgba(255, 187, 200, 0.55);
    border-radius: 50px;
    z-index: 3;
    pointer-events: none;
  }

  /* No HAIR title in mobile (Figma 58-2 doesn't have one). */
  .grid-title { display: none; }

  /* No big SUBMIT pill in mobile — only the mini ✓ above the drawer. */
  .submit-btn-main { display: none; }

  /* Category column → narrow strip on the left of the drawer. */
  .cat-column {
    left: 0; top: 1304px;
    width: 195px; height: 616px;
    z-index: 3;
  }

  /* Arrows: small chevrons top/bottom of the strip. */
  .cat-arrow {
    width: 70px; height: 35px;
    left: 60px;
  }
  .cat-arrow .chevron {
    width: 28px; height: 28px;
    border-left-width: 8px;
    border-top-width: 8px;
  }
  .cat-arrow-up   { top: 14px; }
  .cat-arrow-down { top: 575px; }

  /* Category list — flex column, scrollable. */
  .cat-list {
    left: 15px;
    top: 60px;
    width: 170px;
    height: 500px;
    gap: 12px;
    padding: 4px 0;
  }

  .cat-item {
    width: 165px;
    height: 150px;
    flex: 0 0 150px;
    border-radius: 32px;
  }
  .cat-item img {
    max-width: 130px;
    max-height: 130px;
    filter: drop-shadow(0 3px 4px rgba(196, 70, 120, 0.3));
  }

  /* Custom category scrollbar — between strip and grid. */
  .cat-list-track {
    display: block;
    position: absolute;
    left: 178px; top: 1360px;
    width: 10px; height: 520px;
    background: rgba(255, 245, 250, 0.9);
    border-radius: 6px;
    z-index: 3;
    pointer-events: none;
  }
  .cat-list-thumb {
    position: absolute;
    left: 0; top: 0;
    width: 10px; height: 100px;
    background: linear-gradient(180deg, #ffbbc8 0%, #ff8fc1 100%);
    border-radius: 6px;
    transition: top 0.15s ease-out;
  }

  /* Grid scrollbar — half the previous width (30 stage-px vs 60).
     translate3d keeps it above panel-back (Android paint fix). */
  .scroll-track {
    left: 1030px; top: 1360px;
    width: 30px; height: 510px;
    border-radius: 999px;
    border: 4px solid #fff;
    background: #ffdee3;
    z-index: 5;
    box-sizing: border-box;
    transform: translate3d(0, 0, 3px);
    backface-visibility: hidden;
  }
  .scroll-thumb {
    left: 3px; top: 3px;
    width: 16px; height: 220px;
    background: linear-gradient(180deg, #ff8fc1 0%, #ff3a8d 100%);
    border-radius: 999px;
    box-shadow: 0 4px 10px rgba(196, 70, 120, 0.45);
  }

  /* Grid extends to the drawer's bottom edge so cells fade right at the
     screen edge (not 50 px inside the drawer). mask-image gradient sits
     in the last 80 px of the grid box, so the last cell row dissolves
     into the screen edge instead of being chopped off. */
  .grid {
    left: 210px; top: 1360px;
    width: 790px;
    height: 560px;
    grid-template-columns: 381px 381px;
    grid-auto-rows: 381px;
    gap: 28px;
    padding-bottom: 28px;
    z-index: 3;
    -webkit-mask-image: linear-gradient(to bottom,
      #000 0%,
      #000 calc(100% - 80px),
      transparent 100%);
            mask-image: linear-gradient(to bottom,
      #000 0%,
      #000 calc(100% - 80px),
      transparent 100%);
  }

  .cell {
    width: 381px; height: 381px;
    border-radius: 40px;
  }
  /* The "_0" none-preview fills the bigger portrait cell too. */
  .cell.cancel > img:not(.done) {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: contain;
  }
  .cell.cancel > img.cancel-fallback {
    position: static; inset: auto;
    width: 230px; height: 230px;
  }

  /* Mini ✓ pill floating above the drawer (Figma 58-75). Sits ~50 px
     higher than before so the gap between pill and drawer reads as
     intentional whitespace, not a near-touch.
     !important overrides the landscape `display:none !important`. */
  .submit-btn-mini { display: flex !important; }
  .submit-btn-mini {
    left: auto;
    right: 40px;
    top: 1120px;
    width: 200px;
    height: 130px;
    border-radius: 999px;
    font-size: 0;
    letter-spacing: 0;
    padding: 0;
    gap: 0;
  }
  .submit-btn-mini .submit-label { display: none; }
  .submit-btn-mini .submit-check { width: 70px; height: 70px; }
  .submit-btn-mini .submit-check::before { width: 12px; height: 44px; top: 10px; left: 34px; }
  .submit-btn-mini .submit-check::after  { width: 12px; height: 24px; top: 24px; left: 12px; }

  /* Back button moves to the top-right corner in portrait (Figma 58-7),
     and is half the landscape size (38 vs 76). */
  .menu-back-btn {
    left: auto;
    right: 14px;
    top: 14px;
    width: 38px;
    height: 38px;
    border-width: 3px;
  }
  .menu-back-glyph {
    width: 14px; height: 14px;
    border-left-width: 4px;
    border-top-width: 4px;
    transform: rotate(-45deg) translate(2px, 2px);
  }

}
