@font-face{
  font-family:'ByteBounce';
  src:url('assets/fonts/ByteBounce.woff2') format('woff2'),
      url('assets/fonts/ByteBounce.ttf') format('truetype');
  font-display:swap;
}

:root{
  /* ── macOS-STYLE LIGHT THEME ──
     Whole UI reskinned to sit inside a pale-yellow/cream "window",
     like the app is a Mac window on a desktop, per direct request.
     Every surface/text/edge token below keeps its ORIGINAL NAME (so
     the hundreds of existing var(--x) references throughout the file
     don't need touching) but points at a light-mode value instead —
     dark bg -> pale cream, light text -> dark charcoal, etc. Accents
     (cyan/gold/green/red/...) are re-tuned so they stay legible on a
     light surface instead of a dark one (a bright cyan that popped on
     navy reads as washed-out on cream, so it's deepened). */
  --bg:#fbf5e0;
  --bg2:#f6edd0;
  --panel:linear-gradient(165deg,#fffef9,#fbf6e8);
  --panel-solid:#fffef9;
  --panel2:linear-gradient(165deg,#fdf9ec,#f7f0da);
  --panel3:#f3ead0;

  /* ── EDGES ──
     Two-tone borders: a bright top-left rim and a soft bottom-right
     shadow give every panel a lifted, physical "card" feel. */
  --line:rgba(150,120,40,.16);
  --line2:rgba(120,95,30,.35);
  --rim:rgba(255,255,255,.9);
  --shade:rgba(120,100,60,.18);

  /* ── ACCENTS ── deepened so they hold contrast on a pale surface */
  --cyan:#0091b3;
  --magenta:#d6289e;
  --violet:#7a4de0;
  --green:#1ea660;
  --gold:#c98a12;
  --red:#e0334f;
  --orange:#d9691f;

  /* ── ATTRIBUTE COLOURS (gameplay-meaningful — left as-is) ── */
  --attr-red:#ff6a2b;
  --attr-green:#3ddc84;
  --attr-yellow:#e0a020;
  --attr-white:#f6ecd8;

  --txt:#2b2515;
  --muted:#8a7d55;

  --pixel:'ByteBounce','Press Start 2P',monospace;
  --mono:'ByteBounce','Share Tech Mono',monospace;

  --glow-cyan:0 0 14px rgba(0,145,179,.30);
  --glow-gold:0 0 14px rgba(201,138,18,.30);

  /* ── macOS WINDOW CHROME ── (traffic-light colors are Apple's own
     values and stay identical in both light and dark mode) */
  --mac-red:#ff5f57;   --mac-red-edge:#e0443e;
  --mac-yellow:#febc2e; --mac-yellow-edge:#dea123;
  --mac-green:#28c840; --mac-green-edge:#1aab29;
  --mac-titlebar:#f2e6bd;
  --mac-desktop:linear-gradient(160deg,#fdeecb 0%,#f7dfa8 45%,#f3d488 100%);
  --topbar-bg:rgba(255,253,246,.92);
  --btn-bg:linear-gradient(180deg,#fffef9,#f0e4bc);
  --btn-hover-bg:linear-gradient(180deg,#fffef9,#e8d9a0);
  --knob-bg:linear-gradient(90deg,#fdf6df,#f5e5b8);
  --knob-bg-active:linear-gradient(90deg,#f5e5b8,#eeda98);
  --drawer-panel-bg:linear-gradient(180deg,#fffef9,#faf1d8);
}

/* ── DARK MODE ──
   Switched automatically by time of day in Thailand (wireDayNightTheme()
   in game.js — 19:00-06:00 local = dark) via data-theme on <html>, same
   idea as a Mac auto-switching between Light/Dark Appearance. This is
   effectively the game's original indigo-violet cyberpunk palette,
   preserved as the "night" look rather than discarded, since it already
   reads well after dark — only the macOS window-chrome tokens change to
   their own dark-appearance equivalents. */
:root[data-theme="dark"]{
  --bg:#0a0716;
  --bg2:#120b26;
  --panel:linear-gradient(165deg,rgba(32,22,60,.94),rgba(16,11,32,.96));
  --panel-solid:#1a1130;
  --panel2:linear-gradient(165deg,rgba(44,30,78,.92),rgba(24,16,46,.95));
  --panel3:#241640;

  --line:rgba(150,110,255,.24);
  --line2:rgba(190,150,255,.5);
  --rim:rgba(255,255,255,.14);
  --shade:rgba(0,0,0,.55);

  --cyan:#3df0ff;
  --magenta:#ff5ce0;
  --violet:#a56bff;
  --green:#4dff9e;
  --gold:#ffcf3f;
  --red:#ff4d6a;
  --orange:#ff8a3d;

  --txt:#efe6ff;
  --muted:#9c8ac4;

  --glow-cyan:0 0 18px rgba(61,240,255,.45);
  --glow-gold:0 0 18px rgba(255,207,63,.45);

  --mac-titlebar:#241a3d;
  --mac-desktop:linear-gradient(160deg,#0a0716 0%,#150d28 50%,#1c1236 100%);
  --topbar-bg:rgba(6,4,16,.94);
  --btn-bg:linear-gradient(180deg,rgba(70,46,120,.95),rgba(34,22,62,.95));
  --btn-hover-bg:linear-gradient(180deg,rgba(88,60,150,.95),rgba(44,28,80,.95));
  --knob-bg:linear-gradient(90deg,rgba(40,26,70,.85),rgba(60,38,100,.95));
  --knob-bg-active:linear-gradient(90deg,rgba(60,38,100,.9),rgba(80,50,130,.98));
  --drawer-panel-bg:linear-gradient(180deg,rgba(26,16,50,.98),rgba(14,9,28,.99));
}
:root[data-theme="dark"] body::before{ background:var(--mac-desktop); }
:root[data-theme="dark"] body::after{
  background:
    radial-gradient(ellipse 60% 40% at 15% 0%, rgba(120,60,220,.30), transparent 70%),
    radial-gradient(ellipse 50% 35% at 85% 12%, rgba(255,60,180,.18), transparent 70%);
  opacity:1;
}

/* touch-action:manipulation drops double-tap-to-zoom (still allows
   panning/pinch-zoom) so a quick double-tap on a buy/sell button or
   anywhere else doesn't accidentally zoom the page — see the matching
   touchend guard in game.js for browsers that ignore this. */
*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
html,body{height:100%}
/* Kill text-selection and the iOS long-press "select/copy/save image"
   callout everywhere in the game — a held finger anywhere (a drag gesture
   on the throw-potion wheel, a long tap on a pet sprite, ...) should never
   fall through to the browser's native selection/callout UI, which was
   cancelling mid-drag gestures (a pointercancel fires the instant the
   browser takes over for its own text-selection). The one real text
   input (#player-name) is explicitly exempted back to normal behavior. */
*{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;-webkit-user-drag:none}
input,textarea{-webkit-touch-callout:default;-webkit-user-select:text;user-select:text}
body{
  background:var(--bg); color:var(--txt);
  font-family:var(--mono); font-size:20px; letter-spacing:.3px;
  overflow-x:hidden; -webkit-font-smoothing:none;
}
/* Soft pale-yellow "desktop" backdrop behind the windowed app (see
   #app's window framing below) — a warm gradient rather than the old
   cyberpunk glow-pools + CRT scanline layers, since those read as
   dark-theme-specific effects. */
body::before{
  content:'';position:fixed;inset:0;z-index:-2;pointer-events:none;
  background:var(--mac-desktop);
}
body::after{
  content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;opacity:.4;
  background:radial-gradient(ellipse 70% 50% at 50% 0%, rgba(255,255,255,.5), transparent 70%);
}
img{image-rendering:pixelated}
button{font-family:inherit;color:inherit;cursor:pointer;border:none;background:none}

/* ── SHARED SURFACE TREATMENT ──
   Every panel gets a bright top rim and dark bottom shade so it looks
   like a lit physical plate rather than a flat rectangle. */
.section,.pet-card,.shop-card,.care-card,.loot-card,.raid-card,
.zb-mon,.bench-chip,.swap-card,.br-card,.hack-card{
  box-shadow:inset 0 1px 0 var(--rim),
             inset 0 -2px 0 var(--shade),
             0 4px 14px rgba(0,0,0,.45);
}

/* ── SHELL ── */
/* Capped to a large-phone width, not full desktop/tablet width — every
   screen here (fixed-size pixel-art cards, the merchant portrait, the
   battle stage) was authored for a phone-narrow column. Letting #app
   grow toward 1100px on a landscape phone/tablet or a desktop browser
   stretched all of that to oversized, sparse-looking proportions. The
   body::before atmospheric background below exists specifically to
   fill the resulting side margins, so this was always the intended
   shape — 1100px was just too generous a cap. */
/* Each .screen inside is now its own position:absolute floating
   window (see the SCREENS section further down), so #app itself no
   longer needs to be draggable as a whole unit — deliberately plain
   position:relative (just an anchor for its absolutely-positioned
   .screen children), not a transformed/offset element, so it never
   becomes the containing block for any position:fixed descendant
   (the map screen's video layer, the evolution overlay, etc. all
   expect the real viewport). */
#app{position:relative;min-height:100vh;max-width:480px;margin:0 auto}

/* ── PER-WINDOW CHROME ──
   #win-main (the single shared main-scene window) and the drawer each
   hand-author one of these — a real draggable, closable, minimizable,
   maximizable Mac window. Shared .win-dot-* colors intentionally reuse
   the --mac-red/yellow/green tokens (Apple's own traffic-light colors),
   matching the (now-undecorated) menu bar. */
.win-titlebar{
  position:relative;flex:0 0 auto;height:32px;
  display:flex;align-items:center;justify-content:center;
  background:var(--mac-titlebar);border-bottom:1px solid var(--line2);
  border-radius:11px 11px 0 0;
}
.win-traffic-lights{position:absolute;left:10px;display:flex;gap:8px;z-index:1}
.win-dot{width:12px;height:12px;border-radius:50%;padding:0;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.12);flex:0 0 auto}
.win-dot:active{filter:brightness(.82)}
.win-dot-red{background:var(--mac-red)}
.win-dot-yellow{background:var(--mac-yellow)}
.win-dot-green{background:var(--mac-green)}
/* Browser-style "back" button — only #win-main has one, since it's the
   only window whose content changes pages in place (see
   MAIN_SCENE_IDS/goMainBack() in game.js). Disabled/dimmed once there's
   nowhere to revert to (e.g. fresh boot showing the map). */
.win-back-btn{
  position:absolute;left:74px;top:50%;transform:translateY(-50%);
  width:22px;height:22px;border-radius:6px;padding:0;
  background:transparent;border:none;color:var(--txt);
  font-size:20px;line-height:1;opacity:.75;
}
.win-back-btn:active{background:rgba(127,127,127,.2)}
.win-back-btn:disabled{opacity:.25;pointer-events:none}
.win-title{font-family:var(--pixel);font-size:13px;color:var(--txt);
  opacity:.75;letter-spacing:.5px;cursor:grab;user-select:none;
  -webkit-user-select:none;touch-action:none;padding:4px 16px}
.win-title:active{cursor:grabbing}
/* position:relative so any position:absolute;inset:0 content inside
   (the map pane's video layer, in particular) fills just the body
   area below the title bar, not the whole (unpositioned-parent-
   skipping) window box. */
.win-body{position:relative;flex:1;overflow-y:auto;padding:12px;-webkit-overflow-scrolling:touch}
/* The map and battle panes want their video/arena backgrounds flush to
   the window edge — same no-padding treatment the old per-screen
   .win-body rules gave them, now keyed off which pane is showing since
   there's only one shared .win-body. */
#win-main[data-pane="map"] .win-body,
#win-main[data-pane="battle"] .win-body{padding:0}

/* ── HUD ── */
#topbar{
  position:sticky;top:0;z-index:70;display:flex;align-items:center;
  justify-content:space-between;gap:8px;padding:6px 10px;
  background:var(--topbar-bg);border-bottom:1px solid var(--line2);
  backdrop-filter:blur(6px);font-size:15px;color:var(--txt);
  min-height:28px;
}
#topbar.hidden{display:none}

/* ── MINIMIZED-WINDOW DOCK ── */
#win-dock{
  position:sticky;top:40px;z-index:65;
  max-width:480px;margin:0 auto;
  display:flex;gap:6px;overflow-x:auto;padding:0;
  background:var(--topbar-bg);border-bottom:1px solid var(--line);
}
#win-dock:empty{display:none;border:none}
.win-dock-chip{flex:0 0 auto;display:flex;align-items:center;gap:5px;
  padding:5px 10px;font-family:var(--pixel);font-size:13px;color:var(--txt);
  border-right:1px solid var(--line)}
.win-dock-chip .wdc-dot{width:8px;height:8px;border-radius:50%;background:var(--mac-yellow);flex:0 0 auto}

.topbar-left{display:flex;align-items:center;gap:8px;min-width:0}
#tb-clock{font-family:var(--pixel);font-size:15px;color:var(--cyan);flex:0 0 auto}
.tb-activity{display:flex;align-items:center;gap:5px;font-size:14px;color:var(--gold);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.tb-activity .tb-act-icon{flex:0 0 auto}
.topbar-right{display:flex;align-items:center;gap:6px;flex:0 0 auto;font-size:14px}
.tb-sep{color:var(--muted)}
#tb-name{color:var(--txt);max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tb-bitz b, .tb-pets b{color:var(--gold);font-weight:400}
.tb-quicknav{display:flex;align-items:center;gap:4px;flex:0 0 auto}
.tb-qn-btn{width:22px;height:22px;display:flex;align-items:center;justify-content:center;
  font-size:14px;line-height:1;background:transparent;border:none;color:var(--txt);
  opacity:.75;filter:grayscale(1) brightness(1.5)}
.tb-qn-btn:active{opacity:1;filter:none;transform:scale(.9)}
:root[data-theme="dark"] .tb-qn-btn{filter:grayscale(1) brightness(2.2)}

#hud{
  position:sticky;top:0;z-index:60;display:flex;align-items:center;gap:10px;
  padding:9px 12px;
  background:linear-gradient(180deg,rgba(26,15,52,.98),rgba(12,8,26,.92));
  border-bottom:2px solid rgba(0,0,0,.6);
  box-shadow:0 2px 0 rgba(165,107,255,.25),0 8px 24px rgba(0,0,0,.5);
  backdrop-filter:blur(10px);
}
#hud .brand{font-family:var(--pixel);font-size:30px;letter-spacing:1px;line-height:1;
  background:linear-gradient(90deg,var(--cyan),var(--magenta),var(--gold));
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
  filter:drop-shadow(0 2px 0 rgba(0,0,0,.6)) drop-shadow(0 0 10px rgba(255,92,224,.4))}
#hud .spacer{flex:1}
.chip{
  display:flex;align-items:center;gap:5px;font-size:20px;line-height:1;
  padding:6px 11px;background:var(--panel2);
  border:1px solid var(--line);
  box-shadow:inset 0 1px 0 var(--rim), inset 0 -2px 0 var(--shade),
             0 2px 6px rgba(0,0,0,.4);
  clip-path:polygon(0 0,calc(100% - 7px) 0,100% 7px,100% 100%,0 100%);
}
.chip b{color:var(--gold);font-weight:400}
.chip.pw b{color:var(--cyan)}

/* ── HACK TERMINAL (Fallout-style) ── */
#screen-hack, #screen-steal{background:#020a02}
.term-frame{max-width:520px;margin:0 auto;padding:12px;
  font-family:'Courier New',monospace;color:#33ff66;
  text-shadow:0 0 6px rgba(51,255,102,.6);
  background:radial-gradient(ellipse at 50% 40%,rgba(20,60,25,.5),rgba(2,10,2,.9));
  border:1px solid rgba(51,255,102,.3);position:relative;overflow:hidden}
.term-frame::after{content:'';position:absolute;inset:0;pointer-events:none;
  background:repeating-linear-gradient(0deg,transparent 0 2px,rgba(0,0,0,.18) 2px 4px)}
.term-head{display:flex;justify-content:space-between;align-items:center;
  font-size:15px;letter-spacing:1px;margin-bottom:10px;position:relative;z-index:1}
.term-x{background:transparent;border:1px solid rgba(51,255,102,.4);
  color:#33ff66;width:24px;height:24px;font-size:15px}
.term-status{display:flex;justify-content:space-between;font-size:20px;
  margin-bottom:10px;position:relative;z-index:1}
.atk-block{display:inline-block;width:11px;height:13px;margin-left:3px;
  background:#33ff66;box-shadow:0 0 6px rgba(51,255,102,.8);vertical-align:middle}
.atk-block.spent{background:transparent;border:1px solid rgba(51,255,102,.35);box-shadow:none}
.term-grid{font-size:15px;line-height:1.5;position:relative;z-index:1;
  letter-spacing:1px;margin-bottom:10px}
.hg-row{display:flex;gap:8px;white-space:nowrap}
.hg-addr{color:rgba(51,255,102,.45)}
.hg-cells{}
.hg-ch{display:inline-block;width:0.72em;text-align:center}
.hg-ch.word{cursor:pointer}
.hg-ch.word.hot{background:#33ff66;color:#020a02;text-shadow:none}
.hg-ch.word.used{color:rgba(51,255,102,.25);cursor:default}
.hg-ch.word.used.hot{background:transparent;color:rgba(51,255,102,.25)}
.term-readout{border-top:1px solid rgba(51,255,102,.25);padding-top:8px;
  font-size:20px;min-height:70px;max-height:110px;overflow-y:auto;
  position:relative;z-index:1}
.hr-line{padding:1px 0}
.hr-line.ok{color:#8fffa8;font-weight:bold}
.hr-line.warn{color:#c8ff33}
.hr-line.bad{color:#ff6a4a;text-shadow:0 0 6px rgba(255,80,60,.6)}

/* steal menu */
.steal-lab{font-size:15px;margin:10px 0 8px;position:relative;z-index:1}
.steal-petrow{display:flex;gap:8px;flex-wrap:wrap;position:relative;z-index:1}
.steal-pet{background:rgba(10,30,12,.6);border:1px solid rgba(51,255,102,.3);
  color:#33ff66;padding:8px;width:88px;text-align:center;
  font-family:'Courier New',monospace}
.steal-pet.on{background:rgba(51,255,102,.15);border-color:#33ff66;
  box-shadow:0 0 12px rgba(51,255,102,.4)}
.sp-art{width:56px;height:56px;display:block;margin:0 auto}
.steal-pet span{display:block;font-size:18px;margin-top:3px}
.steal-pet i{font-size:16px;color:rgba(51,255,102,.6);font-style:normal}
.steal-lootgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));
  gap:10px;position:relative;z-index:1}
.loot-card{background:rgba(10,30,12,.6);border:1px solid rgba(51,255,102,.3);
  color:#33ff66;padding:12px 8px;text-align:center;
  font-family:'Courier New',monospace;transition:.12s}
.loot-card:hover{background:rgba(51,255,102,.12);border-color:#33ff66;
  box-shadow:0 0 14px rgba(51,255,102,.4);transform:translateY(-2px)}
.lc-icon{font-size:30px;margin-bottom:4px}
.lc-name{font-size:20px;margin-bottom:4px}
.lc-chance{font-size:15px;font-weight:bold}
.lc-chance.auto{color:#8fffa8}
.lc-warn{font-size:16px;color:#c8ff33;margin-top:3px}

/* ── SKILL TREE SCREEN ── */
.tree-mode-toggle{display:inline-flex;gap:6px;margin-left:10px;vertical-align:middle}
.chip-btn{font-size:14px;padding:4px 10px;border-radius:20px;border:1px solid var(--line);
  background:var(--panel2);color:var(--muted);cursor:pointer}
.chip-btn.on{border-color:var(--cyan);color:var(--cyan);box-shadow:0 0 10px rgba(55,230,255,.25)}
.tree-bar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:8px 10px;background:var(--panel);border:1px solid var(--line);margin-bottom:10px}
.tree-pts{font-size:20px;color:var(--muted)}
.tree-pts b{color:var(--gold);font-size:17px}

#tree-canvas{position:relative;width:100%;aspect-ratio:3/4;max-height:66vh;
  background:#06080f;
  border:1px solid var(--line2);overflow:hidden}
/* Cathedral stained-glass window art, themed per attribute/mutation —
   see EQ_SOCKET_LAYOUT-style comment at renderTree()'s bgKey. A dark
   scrim sits between the art and the node graph so small node icons
   and lines stay legible against a busy, richly colored window. */
.tree-canvas-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0}
.tree-canvas-scrim{position:absolute;inset:0;z-index:0;
  background:linear-gradient(180deg,rgba(6,8,18,.55) 0%,rgba(6,8,18,.68) 50%,rgba(6,8,18,.85) 100%)}
.tree-lines{position:absolute;inset:0;width:100%;height:100%;z-index:1}
.tree-line{stroke:rgba(255,255,255,.12);stroke-width:.8;vector-effect:non-scaling-stroke}
.tree-line.on{stroke:var(--tree-color,#3ddc84);stroke-width:2.4;
  filter:drop-shadow(0 0 4px var(--tree-color,#3ddc84))}

.tree-node{position:absolute;transform:translate(-50%,-50%);
  width:38px;height:38px;border-radius:50%;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:0;
  background:rgba(10,14,28,.95);border:2px solid rgba(255,255,255,.18);
  transition:.14s;z-index:2;padding:0}
.tree-node.skill{width:50px;height:50px;
  background-size:cover;background-position:center;background-repeat:no-repeat;
  border-color:rgba(220,60,60,.5)}
.tree-node.skill.open,.tree-node.skill.part{border-color:#ff4444;
  box-shadow:0 0 14px rgba(255,68,68,.4)}
.tree-node.skill.maxed{border-color:#ff4444;
  background-color:rgba(60,10,10,.6);box-shadow:0 0 18px rgba(255,68,68,.6)}
.tree-node.skill.maxed .tn-icon{filter:none}
.tree-node.skill .tn-icon{text-shadow:0 0 6px #000,0 0 3px #000}
.tree-node.open{border-color:var(--tree-color,#3ddc84);
  box-shadow:0 0 12px rgba(255,255,255,.14)}
.tree-node.part{border-color:var(--tree-color,#3ddc84);
  background:rgba(20,40,30,.95)}
.tree-node.maxed{border-color:var(--tree-color,#3ddc84);
  background:var(--tree-color,#3ddc84);
  box-shadow:0 0 16px var(--tree-color,#3ddc84)}
.tree-node.maxed .tn-icon{filter:brightness(0) saturate(0)}
.tree-node.locked{opacity:.4}
.tn-icon{font-size:17px;line-height:1}
.tree-node.skill .tn-icon{font-size:21px;color:var(--gold)}
.tn-rank{position:absolute;bottom:-13px;font-size:15px;color:var(--muted);
  background:rgba(6,8,18,.9);padding:0 3px;border-radius:2px;white-space:nowrap}
.tree-node.maxed .tn-rank{color:var(--tree-color,#3ddc84)}
.tn-tip{position:absolute;bottom:120%;left:50%;transform:translateX(-50%);
  background:rgba(6,10,20,.96);border:1px solid var(--line2);padding:4px 7px;
  font-size:16px;white-space:nowrap;opacity:0;pointer-events:none;transition:.12s;
  z-index:5;color:var(--txt);line-height:1.4}
.tn-tip i{color:var(--muted);font-style:normal;font-size:15px}
.tree-node:hover .tn-tip{opacity:1}

/* ── SPECIAL SKILL BAR ── */
#skill-bar{display:flex;gap:6px;overflow-x:auto;padding:8px 10px;
  background:var(--panel);border-top:1px solid var(--line)}
.sk-btn{flex:0 0 auto;position:relative;display:flex;flex-direction:column;
  align-items:flex-start;gap:1px;overflow:hidden;
  padding:6px 10px;background:var(--panel2);border:1px solid var(--line);
  transition:.12s;min-width:92px;
  clip-path:polygon(0 0,calc(100% - 6px) 0,100% 6px,100% 100%,0 100%)}
.sk-btn.on{border-color:var(--gold);box-shadow:0 0 12px rgba(255,213,74,.3)}
.sk-btn.poor{opacity:.45}
.sk-name{font-family:var(--pixel);font-size:21px;line-height:1;color:var(--txt)}
.sk-cost-cd{font-size:15px;color:#7fd4ff;line-height:1.2}
.sk-auto{font-size:15px;color:var(--muted);line-height:1.2}
.sk-btn.on .sk-auto{color:var(--gold)}
.sk-empty{font-size:18px;color:var(--muted);padding:4px}

/* Cooldown overlay: darkens the whole button and shows a live
   countdown from the skill's full cooldown down to 0s, per the user's
   spec — "darken with animation showing large number of time delay
   from full time to 0s then back to normal again". Opacity is toggled
   by refreshSkillCooldownUI() every 100ms while a battle is active. */
.sk-btn.on-cooldown{pointer-events:none}
.sk-cd-overlay{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  background:rgba(4,3,10,.82);opacity:0;transition:opacity .25s ease;
  z-index:2;
}
.sk-cd-num{
  font-family:var(--pixel);font-size:25px;color:#fff;
  text-shadow:0 0 10px rgba(255,213,74,.6),0 2px 3px rgba(0,0,0,.8);
}

/* ── BATTLE ENTRANCE ── */
/* Hide plates/bench until the fighters have landed, so nothing reads
   before the reveal. */
#battle-stage.entrance-lock .name-plate,
#battle-stage.entrance-lock ~ #battle-bench,
#battle-stage.entrance-lock ~ #skill-bar,
#battle-stage.entrance-lock ~ #hit-btn-row,
#battle-stage.entrance-lock ~ #potion-bar{opacity:0;pointer-events:none;
  transition:opacity .2s}

/* ── MANUAL "HIT!" BUTTON ──
   Appears once the active ally's crit gauge is full (see
   refreshUnitVitals()/renderBattleSide() toggling #hit-btn-row's `show`
   class) — tapping it fires the same guaranteed-crit bonus strike as
   tapping the ally's own sprite (requestBonusCrit(), see
   wireCritGaugeTap()), then hides again once the gauge is spent. */
#hit-btn-row{display:flex;justify-content:flex-start;padding:2px 12px 0;
  min-height:0}
.hit-btn{display:none;font-family:var(--pixel);font-size:18px;letter-spacing:1px;
  padding:8px 20px;color:#fff;background:linear-gradient(180deg,#ff5a76,#c81c33);
  border:2px solid #ff2f56;border-radius:6px;
  box-shadow:0 0 14px rgba(255,47,86,.7);animation:hitBtnPulse 1s infinite}
.hit-btn.show{display:block}
@keyframes hitBtnPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.06)}}

/* Same idea but scoped to just the foe's plate — used when the next
   enemy in a multi-character wave steps up mid-fight. The ally side
   (plate/bench/skill/potion bar) stays fully visible and usable. */
#battle-stage.foe-entrance-lock #plate-foe{opacity:0;pointer-events:none;
  transition:opacity .2s}

.bunit.enter-from-l{
  animation:enterFromL .5s steps(6) forwards;
  image-rendering:pixelated;
}
.bunit.enter-from-r{
  animation:enterFromR .5s steps(6) forwards;
  image-rendering:pixelated;
}
@keyframes enterFromL{
  0%{opacity:0;transform:translateX(-140px) scale(.7);filter:contrast(1.3) saturate(0)}
  40%{opacity:1;filter:contrast(1.15) saturate(.4)}
  100%{opacity:1;transform:translateX(0) scale(1);filter:none}}
@keyframes enterFromR{
  0%{opacity:0;transform:translateX(140px) scale(.7);filter:contrast(1.3) saturate(0)}
  40%{opacity:1;filter:contrast(1.15) saturate(.4)}
  100%{opacity:1;transform:translateX(0) scale(1);filter:none}}

/* Landing stomp: quick squash-and-recover */
.bunit.enter-land{animation:enterLand .28s cubic-bezier(.3,1.6,.4,1) forwards}
@keyframes enterLand{
  0%{transform:scale(1,1)}
  35%{transform:scale(1.18,.8)}
  70%{transform:scale(.94,1.07)}
  100%{transform:scale(1,1)}}

/* ── ENEMY DEATH: pixel-shard explosion ──
   .exploding hides the real sprite the instant death starts; the
   shards (clones of the sprite, each windowed to one grid cell via
   overflow:hidden) fly outward and fade, then the unit is removed. */
.bunit.exploding .bu-sprite-wrap,
.bunit.exploding .ail-badges{opacity:0 !important}
.explode-burst{position:absolute;pointer-events:none;z-index:11}
.explode-shard{position:absolute;overflow:hidden;image-rendering:pixelated;
  animation:shardBlast .6s cubic-bezier(.2,.6,.3,1) forwards}
.explode-clone{position:absolute;pointer-events:none}
@keyframes shardBlast{
  0%{transform:translate(0,0) rotate(0deg);opacity:1}
  65%{opacity:1}
  100%{transform:translate(var(--ex),var(--ey)) rotate(var(--er));opacity:0}}

/* Dust puff at the feet on landing */
.dust-puff{position:absolute;z-index:8;transform:translate(-50%,-50%);
  width:0;height:0;pointer-events:none}
.dust-puff i{position:absolute;left:0;top:0;width:7px;height:7px;border-radius:1px;
  background:#cbb99a;opacity:0;image-rendering:pixelated;
  animation:dustBit .42s ease-out forwards;animation-delay:var(--pdel,0s);
  transform:rotate(var(--pa)) translateX(0)}
@keyframes dustBit{
  0%{opacity:.9;transform:rotate(var(--pa)) translateX(2px) scale(1)}
  100%{opacity:0;transform:rotate(var(--pa)) translateX(var(--pd,24px)) scale(.4)}}

/* VS slam — two halves crash in from opposite sides and lock together */
.vs-slam{position:absolute;left:50%;top:38%;z-index:15;
  transform:translate(-50%,-50%);
  font-family:var(--pixel);font-size:44px;line-height:1;color:#fff;
  -webkit-text-stroke:6px #1a0a24;paint-order:stroke fill;
  text-shadow:0 0 22px rgba(255,92,224,.7),0 4px 0 rgba(0,0,0,.5);
  animation:vsSlam .3s cubic-bezier(.2,1.8,.3,1) both}
.vs-slam.out{animation:vsOut .2s ease-in both}
@keyframes vsSlam{
  0%{opacity:0;transform:translate(-50%,-50%) scale(2.6) rotate(-8deg)}
  60%{opacity:1;transform:translate(-50%,-50%) scale(.92) rotate(3deg)}
  100%{opacity:1;transform:translate(-50%,-50%) scale(1) rotate(0)}}
@keyframes vsOut{to{opacity:0;transform:translate(-50%,-50%) scale(1.3)}}

/* ── SPELL VFX ── */
.vfx{position:absolute;transform:translate(-50%,-50%);z-index:9;pointer-events:none}
.vfx b,.vfx i{position:absolute;left:0;top:0;display:block}

/* fire */
.vfx-fire .flame{width:14px;height:22px;margin:-11px 0 0 -7px;border-radius:50% 50% 45% 45%;
  background:linear-gradient(180deg,#fff3a0,#ff8a2b 45%,#e0341c);
  animation:flameUp .8s ease-out forwards;animation-delay:var(--fd,0s);opacity:0}
@keyframes flameUp{0%{opacity:0;transform:translate(0,10px) scale(.4)}
  25%{opacity:1}100%{opacity:0;transform:translate(var(--fx,0),-70px) scale(1.25)}}

/* ice */
.vfx-ice .shard{width:8px;height:26px;margin:-13px 0 0 -4px;
  background:linear-gradient(180deg,#eafaff,#7fd6ff);clip-path:polygon(50% 0,100% 100%,0 100%);
  transform:rotate(var(--sa)) translateY(-34px);opacity:0;
  animation:shardIn .5s ease-out forwards;animation-delay:var(--sd,0s)}
@keyframes shardIn{0%{opacity:0;transform:rotate(var(--sa)) translateY(-70px) scale(.4)}
  60%{opacity:1}100%{opacity:0;transform:rotate(var(--sa)) translateY(-20px) scale(1)}}
.frost-ring{width:110px;height:110px;margin:-55px 0 0 -55px;border-radius:50%;
  border:3px solid #9fe4ff;animation:ringPop .55s ease-out forwards}

/* heal */
.vfx-heal .heal-ring{width:96px;height:96px;margin:-48px 0 0 -48px;border-radius:50%;
  border:3px solid #4ee88a;box-shadow:0 0 22px rgba(78,232,138,.75) inset;
  animation:ringPop .7s ease-out forwards}
.vfx-heal .sparkle{width:8px;height:8px;margin:-4px 0 0 -4px;border-radius:50%;
  background:#b6ffcf;box-shadow:0 0 10px #4ee88a;opacity:0;
  animation:sparkUp 1s ease-out forwards;animation-delay:var(--sd,0s)}
@keyframes sparkUp{0%{opacity:0;transform:translate(var(--sx,0),18px) scale(.4)}
  30%{opacity:1}100%{opacity:0;transform:translate(var(--sx,0),-58px) scale(1.1)}}

/* holy / bless */
.vfx-holy .holy-beam{width:56px;height:200px;margin:-160px 0 0 -28px;
  background:linear-gradient(180deg,rgba(255,255,220,0),rgba(255,246,170,.9),rgba(255,255,220,0));
  filter:blur(1px);animation:beamDrop .6s ease-out forwards}
@keyframes beamDrop{0%{opacity:0;transform:scaleY(.2)}40%{opacity:1}100%{opacity:0;transform:scaleY(1.1)}}
.vfx-holy .holy-ring{width:120px;height:34px;margin:-17px 0 0 -60px;border-radius:50%;
  border:3px solid #ffe98a;animation:ringPop .7s ease-out forwards}

/* shield */
.vfx-shield .shield-hex{width:104px;height:104px;margin:-52px 0 0 -52px;
  clip-path:polygon(50% 0,95% 25%,95% 75%,50% 100%,5% 75%,5% 25%);
  border:3px solid #7fd4ff;background:rgba(127,212,255,.16);
  animation:hexPop .8s ease-out forwards}
@keyframes hexPop{0%{opacity:0;transform:scale(.5)}35%{opacity:1;transform:scale(1.08)}
  70%{opacity:.9;transform:scale(1)}100%{opacity:0;transform:scale(1.05)}}

/* poison */
.vfx-poison .bubble{width:12px;height:12px;margin:-6px 0 0 -6px;border-radius:50%;
  background:radial-gradient(circle at 35% 30%,#d8f36a,#4a9c3f);opacity:0;
  animation:bubbleUp .95s ease-out forwards;animation-delay:var(--bd,0s)}
@keyframes bubbleUp{0%{opacity:0;transform:translate(var(--bx,0),14px) scale(.3)}
  30%{opacity:1}100%{opacity:0;transform:translate(var(--bx,0),-54px) scale(1.2)}}

/* charm */
.vfx-charm .heartp{margin:-10px 0 0 -8px;font-size:21px;color:#ff6ab0;opacity:0;
  text-shadow:0 0 10px rgba(255,106,176,.8);
  animation:heartUp 1s ease-out forwards;animation-delay:var(--hd,0s)}
@keyframes heartUp{0%{opacity:0;transform:translate(var(--hx,0),16px) scale(.5)}
  30%{opacity:1}100%{opacity:0;transform:translate(var(--hx,0),-62px) scale(1.15)}}

/* wind */
.vfx-wind .gust{width:90px;height:4px;margin:0 0 0 -45px;border-radius:2px;
  background:linear-gradient(90deg,transparent,#d6fff0,transparent);opacity:0;
  animation:gustGo .5s ease-out forwards;animation-delay:var(--gd,0s)}
@keyframes gustGo{0%{opacity:0;transform:translate(-60px,var(--gy,0)) scaleX(.4)}
  40%{opacity:1}100%{opacity:0;transform:translate(60px,var(--gy,0)) scaleX(1.2)}}

/* meteor / phoenix / pierce / impact */
.vfx-meteor .rock{width:26px;height:26px;margin:-13px 0 0 -13px;border-radius:50%;
  background:radial-gradient(circle at 35% 30%,#ffd08a,#a33a12);
  box-shadow:0 0 26px rgba(255,140,60,.9);animation:rockFall .45s ease-in forwards}
@keyframes rockFall{0%{opacity:0;transform:translate(90px,-190px) scale(.6)}
  60%{opacity:1}100%{opacity:1;transform:translate(0,0) scale(1)}}
.boom{width:20px;height:20px;margin:-10px 0 0 -10px;border-radius:50%;
  background:radial-gradient(circle,#fff8d0,#ffb02e 45%,rgba(255,90,30,0));
  animation:boomOut .55s .38s ease-out forwards;opacity:0}
@keyframes boomOut{0%{opacity:1;transform:scale(.4)}100%{opacity:0;transform:scale(7)}}
.vfx-phoenix .wing{width:70px;height:96px;margin:-48px 0 0 -35px;
  background:radial-gradient(ellipse at 50% 60%,#ffd98a,#ff6a2b 55%,rgba(255,60,20,0));
  animation:wingFlare .7s ease-out forwards}
.vfx-phoenix .wing.left{transform-origin:100% 60%;animation-name:wingL}
.vfx-phoenix .wing.right{transform-origin:0 60%;animation-name:wingR}
@keyframes wingL{0%{opacity:0;transform:rotate(20deg) scale(.4)}40%{opacity:1}
  100%{opacity:0;transform:rotate(-38deg) scale(1.25)}}
@keyframes wingR{0%{opacity:0;transform:rotate(-20deg) scale(.4)}40%{opacity:1}
  100%{opacity:0;transform:rotate(38deg) scale(1.25)}}
.vfx-pierce .lance{width:150px;height:8px;margin:-4px 0 0 -75px;border-radius:4px;
  background:linear-gradient(90deg,rgba(255,255,255,0),#fff,#7fd4ff);
  animation:lanceGo .4s ease-out forwards}
@keyframes lanceGo{0%{opacity:0;transform:translateX(-90px) scaleX(.3)}
  40%{opacity:1}100%{opacity:0;transform:translateX(70px) scaleX(1.2)}}
.vfx-aura .aura-ring{width:80px;height:80px;margin:-40px 0 0 -40px;border-radius:50%;
  border:3px solid #ff8a4a;animation:ringPop .8s ease-out forwards}
.vfx-aura .aura-ring.d2{animation-delay:.18s;border-color:#ffd23f}
@keyframes ringPop{0%{opacity:0;transform:scale(.35)}35%{opacity:1}
  100%{opacity:0;transform:scale(1.6)}}

/* poison tick number */
.poison-pop{position:absolute;transform:translate(-50%,-50%);z-index:9;
  font-family:var(--pixel);font-size:25px;color:#a8e04a;
  -webkit-text-stroke:4px #16300c;paint-order:stroke fill;
  animation:healRise 1s ease-out forwards;pointer-events:none}

/* ailment badges on units */
.ail-badges{position:absolute;top:-6px;left:50%;transform:translateX(-50%);
  display:flex;gap:3px;z-index:6}
.ail-badge{font-size:15px;filter:drop-shadow(0 1px 2px rgba(0,0,0,.8));
  animation:ailPulse 1.4s infinite}
@keyframes ailPulse{0%,100%{transform:scale(1)}50%{transform:scale(1.18)}}

/* MP gauge on name plates — see the shared .vital rules further down
   (near .np-hp) for how the heart/circle liquid-fill mechanism works. */
.np-mp{display:flex;align-items:center;margin-top:4px}
.name-plate.right .np-mp{justify-content:flex-end}

/* speed / crit gauges — vertical liquid-fill bars, same clip-path-from-
   the-top mechanism as .vital (see updateGaugeBar()/gaugeBarHtml() in
   game.js), just a tall narrow bar instead of a heart/circle: SPD fills
   yellow, CRIT fills orange, both rising from the bottom as they charge. */
.gauge-bars{display:flex;gap:6px;margin-top:3px;align-items:flex-end}
.gauge-bar{position:relative;width:9px;height:34px;border-radius:3px;
  overflow:hidden;border:1px solid rgba(255,255,255,.3)}
.gauge-bar-bg,.gauge-bar-fill{position:absolute;inset:0}
.gauge-bar-bg{background:rgba(0,0,0,.5)}
.gauge-bar.spd .gauge-bar-fill{background:linear-gradient(0deg,#ffd23f,#ffb300);
  filter:drop-shadow(0 0 3px rgba(255,210,63,.7))}
.gauge-bar.crit .gauge-bar-fill{background:linear-gradient(0deg,#ff8a3d,#ff4a1e);
  filter:drop-shadow(0 0 3px rgba(255,90,30,.7))}
.gauge-bar-fill{transition:clip-path .35s ease}
.gauge-bar.crit.ready{border-color:#ff2f56;
  animation:critPipPulse 1s infinite}
.gauge-bar.crit.ready .gauge-bar-fill{background:linear-gradient(0deg,#ff2f56,#ff2f56);
  box-shadow:0 0 8px rgba(255,47,86,.9)}
@keyframes critPipPulse{0%,100%{opacity:1}50%{opacity:.6}}
.turn-banner.speed{background:rgba(180,140,20,.95);border-color:#ffd23f}
.turn-banner.ail{background:rgba(120,40,140,.95);border-color:#d88aff}
.turn-banner.miss{background:rgba(70,78,92,.95)}

/* Tappable "bonus crit ready" glow on an ally's own sprite — see
   wireCritGaugeTap()/fireBonusCrit() in game.js. Only ever added to the
   ally slot (a foe auto-fires the instant it's ready, no tap needed). */
.bunit.crit-ready{cursor:pointer}
.bunit.crit-ready .bu-sprite-wrap{animation:critReadyGlow 1.1s infinite}
@keyframes critReadyGlow{
  0%,100%{filter:drop-shadow(0 0 0 rgba(255,47,86,0))}
  50%{filter:drop-shadow(0 0 14px rgba(255,47,86,.85))}
}

/* Stoned — a bigger, more prominent overlay than a normal .ail-badge,
   placeholder emoji until a real gif lands at assets/fx/stoned.gif (at
   which point swap the `content`/emoji below for a plain background-image
   on this rule, e.g. background:url(assets/fx/stoned.gif) center/contain
   no-repeat, and remove the font-size/line-height text styling). */
.stoned-overlay{position:absolute;top:-30px;left:50%;transform:translateX(-50%);
  font-size:30px;line-height:1;z-index:7;
  filter:drop-shadow(0 2px 4px rgba(0,0,0,.7));
  animation:stonedBob 1.6s ease-in-out infinite}
@keyframes stonedBob{0%,100%{transform:translateX(-50%) translateY(0)}50%{transform:translateX(-50%) translateY(-4px)}}

/* ── BATTLE RESULTS PANEL ── */
#battle-results{position:fixed;inset:0;z-index:80;display:none;
  align-items:center;justify-content:center;padding:20px;
  background:rgba(4,6,14,.82);backdrop-filter:blur(4px)}
#battle-results.on{display:flex;animation:brFade .25s ease}
@keyframes brFade{from{opacity:0}to{opacity:1}}
.br-card{width:100%;max-width:420px;background:var(--panel);
  border:1px solid var(--line2);padding:18px 16px;
  clip-path:polygon(0 0,calc(100% - 12px) 0,100% 12px,100% 100%,12px 100%,0 calc(100% - 12px))}
.br-card.win{box-shadow:0 0 30px rgba(61,220,132,.2)}
.br-card.lose{box-shadow:0 0 30px rgba(255,77,94,.2)}
.br-title{font-family:var(--pixel);font-size:34px;text-align:center;margin-bottom:6px}
.br-card.win .br-title{color:var(--green)}
.br-card.lose .br-title{color:#ff6a7a}
.br-loot{text-align:center;font-size:15px;color:var(--gold);margin-bottom:14px}
.br-rows{display:flex;flex-direction:column;gap:10px;margin-bottom:16px}
.br-row{display:flex;gap:10px;align-items:center}
.br-art{flex:0 0 auto}
.br-sprite{width:52px;height:52px;display:block}
.br-info{flex:1;min-width:0}
.br-name{font-size:15px;font-weight:bold;margin-bottom:3px;display:flex;
  align-items:center;gap:6px;flex-wrap:wrap}
.br-lvup{font-size:18px;color:var(--gold);font-weight:normal}
.br-xpbar{height:9px;background:rgba(0,0,0,.5);border:1px solid var(--line);
  overflow:hidden;border-radius:2px}
.br-xpbar i{display:block;height:100%;width:0;
  background:linear-gradient(90deg,#ffcf4a,#ffb02e);
  transition:width .6s cubic-bezier(.3,1,.5,1)}
.br-xptext{font-size:16px;color:var(--muted);margin-top:2px;
  display:flex;justify-content:space-between}
.br-gain{color:var(--gold)}
.br-extra{margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}
.br-skill{font-size:16px;color:#ffd23f}
.br-loy{font-size:16px;color:#ff8a9a}
.br-empty{text-align:center;color:var(--muted);font-size:20px;padding:8px}

/* ── PROCESS FEED ── */
#process-panel{margin-top:10px;border:1px solid var(--line2);background:var(--panel)}
.process-head{display:flex;align-items:baseline;gap:8px;padding:7px 10px;
  border-bottom:1px solid var(--line);background:var(--panel2)}
.proc-title{font-family:var(--pixel);font-size:25px;color:var(--cyan);letter-spacing:1px}
.proc-sub{font-size:16px;color:var(--muted)}
#process-feed{max-height:180px;overflow-y:auto;padding:4px 0}
.pf-line{display:flex;gap:8px;padding:4px 10px;font-size:20px;
  border-bottom:1px solid rgba(255,255,255,.03);line-height:1.4}
.pf-time{flex:0 0 auto;color:var(--muted);font-size:16px;padding-top:1px}
.pf-msg{flex:1;color:var(--txt)}
.pf-line.win .pf-msg{color:var(--green)}
.pf-line.lose .pf-msg{color:#ff7a8a}
.pf-line.buff .pf-msg{color:var(--gold)}
.pf-empty{padding:14px;text-align:center;color:var(--muted);font-size:20px}

/* ── QUICK-ACCESS BAR ──
   The old #hud header bar and #quickbar are now hidden EVERYWHERE —
   replaced by the persistent #topbar (time/activity left, name/bitz/
   pet-count right) that shows on every screen, plus the global drawer
   knob for navigation. Team status now lives inside the drawer
   (see .drawer-team) instead of a dedicated top strip. */
#hud, #quickbar{display:none !important}
#quickbar{position:sticky;top:41px;z-index:59;gap:2px;
  padding:4px 6px;background:rgba(5,6,15,.92);border-bottom:1px solid var(--line);
  backdrop-filter:blur(8px);overflow-x:auto}
.qb{flex:1 0 auto;display:flex;flex-direction:column;align-items:center;gap:2px;
  min-width:56px;padding:6px 4px;font-size:23px;background:transparent;
  border:1px solid transparent;color:var(--txt);transition:.12s;border-radius:6px}
.qb span{font-size:17px;color:var(--muted);line-height:1}
.qb:hover{background:rgba(90,60,160,.35)}
.qb.on{background:linear-gradient(180deg,rgba(90,60,170,.6),rgba(50,32,100,.5));
  border-color:var(--cyan);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 0 14px rgba(61,240,255,.3)}
.qb.on span{color:var(--cyan)}

/* ── PERSISTENT TEAM STRIP ── */
/* Team status now lives in the drawer as a plain vertical list (see
   renderDrawerTeam) instead of a persistent swipeable top strip —
   the per-pet row styling (.ts-*) is unchanged, just stacked instead
   of paged one-at-a-time. */
.drawer-team{display:flex;flex-direction:column;gap:10px;margin-bottom:4px}
.ts-cell{display:flex;gap:10px;align-items:center;
  border-left:4px solid var(--attr);padding-left:9px;
  filter:drop-shadow(0 0 8px color-mix(in srgb,var(--attr) 40%,transparent))}
.ts-sprite{flex:0 0 auto}
.ts-art{width:60px;height:60px;display:block}
.ts-body{flex:1;min-width:0}
.ts-name{font-family:var(--pixel);font-size:25px;line-height:1;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;text-shadow:0 2px 0 rgba(0,0,0,.6)}
.ts-attr{font-size:20px}
.ts-lv{font-size:16px;color:var(--txt);margin:1px 0 3px}
.ts-baropts{display:flex;align-items:center;gap:5px;margin-bottom:2px}
.ts-lab{font-size:15px;color:var(--muted);width:16px}
.ts-bar{flex:1;height:7px;background:rgba(0,0,0,.6);border:1px solid var(--line);
  overflow:hidden;box-shadow:inset 0 1px 2px rgba(0,0,0,.6)}
.ts-bar i{display:block;height:100%;box-shadow:0 0 8px currentColor}
.ts-bar.hp i{background:linear-gradient(180deg,#7fffb8,#2fbf6a);color:#4dff9e}
.ts-bar.xp i{background:linear-gradient(180deg,#ffe27a,#e8a91c);color:#ffcf3f}
.ts-val{font-size:15px;color:var(--muted);width:56px;text-align:right}
.ts-stats{font-size:18px;color:var(--cyan);margin-top:2px}


/* ── MAIN SCENE + DRAWER AS WINDOWS ──
   .screen is the Mac-style floating window shell (see wireMainWindow()/
   wireDrawer() in game.js) — now just TWO instances ever exist, #win-main
   (map, warp gate, clinic, and everything else off the map menu) and the
   drawer. "windowed" (the default open state) leaves a small margin so
   the desktop peeks through and the window can be dragged; "maximized"
   fills #app edge to edge. z-index is set inline from stack position
   (restackWindowsZ()). Navigating between the map-menu pages doesn't
   open a new .screen — it swaps which .pane is visible inside #win-main,
   like following a link in the same browser tab (see MAIN_SCENE_IDS/
   showMainPane()/goMainBack() in game.js). */
.screen{
  display:none;flex-direction:column;
  position:absolute;inset:26px 6px 6px 6px;
  background:var(--panel-solid);border:1px solid var(--line2);
  border-radius:12px;box-shadow:0 14px 40px rgba(0,0,0,.22);
  overflow:hidden;
}
:root[data-theme="dark"] .screen{box-shadow:0 14px 40px rgba(0,0,0,.5)}
.screen.on{display:flex}
.screen.maximized{inset:0;border-radius:0;border:none}
.screen.win-pop-in{animation:winPopIn .18s cubic-bezier(.2,.8,.3,1)}
@keyframes winPopIn{0%{opacity:0;transform:scale(.97)}100%{opacity:1;transform:scale(1)}}

/* One page of the main scene — plain content, no window chrome of its
   own; only one is ever .on at a time inside #win-main's shared
   .win-body. */
.pane{display:none}
.pane.on{display:block}

.screen-head{display:flex;align-items:center;gap:10px;margin-bottom:12px}
.screen-head h2{font-family:var(--pixel);font-size:28px;letter-spacing:1px;color:var(--cyan)}
.back-btn{
  font-family:var(--pixel);font-size:23px;padding:7px 11px;
  border:1px solid var(--line2);background:var(--panel);color:var(--txt);
  clip-path:polygon(0 0,calc(100% - 6px) 0,100% 6px,100% 100%,0 100%);
}
.back-btn:hover{border-color:var(--cyan);color:var(--cyan)}

/* ── INTRO ── */
#screen-intro{display:none;text-align:center;padding:40px 16px}
#screen-intro.on{display:block}
.intro-title{font-family:var(--pixel);font-size:23px;letter-spacing:2px;margin-bottom:8px;
  background:linear-gradient(90deg,var(--cyan),var(--magenta));
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;
  filter:drop-shadow(0 0 18px rgba(55,230,255,.4))}
.intro-sub{color:var(--muted);font-size:17px;margin-bottom:26px;letter-spacing:1px}
#starter-grid{display:flex;flex-wrap:wrap;gap:12px;justify-content:center;margin-bottom:22px}
.starter-card{
  width:150px;padding:12px;border:1px solid var(--line);background:var(--panel);
  transition:.15s;clip-path:polygon(0 0,calc(100% - 10px) 0,100% 10px,100% 100%,10px 100%,0 calc(100% - 10px));
}
.starter-card:hover,.starter-card.sel{border-color:var(--cyan);
  box-shadow:0 0 20px rgba(55,230,255,.25);transform:translateY(-3px)}
.starter-sprite{width:96px;height:96px;display:block;margin:0 auto 6px;overflow:visible}
.starter-name{font-family:var(--pixel);font-size:23px;margin-bottom:4px}
.starter-hint{font-size:20px;color:var(--muted)}
#player-name{
  font-family:var(--mono);font-size:17px;padding:9px 12px;width:200px;
  background:var(--panel);border:1px solid var(--cyan);color:var(--cyan);outline:none;
}
.btn{
  font-family:var(--pixel);font-size:25px;line-height:1;padding:11px 16px;
  letter-spacing:1px;position:relative;
  border:2px solid var(--line2);color:var(--txt);
  background:var(--btn-bg);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.22),
             inset 0 -3px 0 rgba(0,0,0,.18),
             0 3px 0 rgba(0,0,0,.12), 0 5px 12px rgba(0,0,0,.12);
  clip-path:polygon(0 0,calc(100% - 8px) 0,100% 8px,100% 100%,8px 100%,0 calc(100% - 8px));
  transition:.1s;
  text-shadow:none;
}
:root[data-theme="dark"] .btn{
  box-shadow:inset 0 1px 0 rgba(255,255,255,.22),
             inset 0 -3px 0 rgba(0,0,0,.45),
             0 3px 0 rgba(0,0,0,.5), 0 5px 12px rgba(0,0,0,.4);
  text-shadow:0 2px 0 rgba(0,0,0,.5);
}
.btn:hover:not(:disabled){border-color:var(--cyan);color:var(--cyan);
  background:var(--btn-hover-bg);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.3),inset 0 -3px 0 rgba(0,0,0,.18),
             0 3px 0 rgba(0,0,0,.12),0 0 20px rgba(0,145,179,.25)}
:root[data-theme="dark"] .btn:hover:not(:disabled){
  box-shadow:inset 0 1px 0 rgba(255,255,255,.3),inset 0 -3px 0 rgba(0,0,0,.45),
             0 3px 0 rgba(0,0,0,.5),0 0 20px rgba(61,240,255,.4)}
/* press-down: the button physically sinks */
.btn:active:not(:disabled){transform:translateY(3px);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.18),inset 0 -1px 0 rgba(0,0,0,.4),
             0 0 12px rgba(61,240,255,.3)}

/* Click ripple — a ring expands from the tap point and fades. Added by
   JS on pointerdown for .btn, .qb, .zone-pin, .care-card and similar
   tappable elements so every interaction gets a confirming beat. */
.click-ripple{position:fixed;z-index:9999;pointer-events:none;
  width:10px;height:10px;border-radius:50%;
  background:radial-gradient(circle,rgba(255,255,255,.9),rgba(120,200,255,.4) 60%,transparent 72%);
  transform:translate(-50%,-50%) scale(.3);
  animation:rippleGo .45s ease-out forwards}
@keyframes rippleGo{
  0%{opacity:.9;transform:translate(-50%,-50%) scale(.3)}
  100%{opacity:0;transform:translate(-50%,-50%) scale(6)}}
.btn:disabled{opacity:.3;cursor:not-allowed;box-shadow:none;transform:none}
.btn.wide{width:100%;margin-top:8px}
.btn.small{font-size:21px;padding:8px 12px}
.btn.primary{border-color:var(--gold);color:#231602;
  background:linear-gradient(180deg,#ffd95c,#e8a91c);
  text-shadow:0 1px 0 rgba(255,255,255,.4);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.5),inset 0 -3px 0 rgba(120,70,0,.5),
             0 3px 0 rgba(80,45,0,.6),0 5px 14px rgba(255,180,40,.3)}
.btn.primary:hover:not(:disabled){color:#1a1000;border-color:#fff0a8;
  background:linear-gradient(180deg,#ffe98a,#f4bb2a);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.6),inset 0 -3px 0 rgba(120,70,0,.5),
             0 3px 0 rgba(80,45,0,.6),0 0 24px rgba(255,200,60,.55)}

/* ── MAP ── */
/* Map is a pane inside the shared #win-main window (position/inset/
   maximize all come from the generic .screen rule on #win-main) rather
   than permanently pinned to the true fullscreen viewport —
   object-fit:cover on the video re-crops to whatever box it's actually
   given, so the pin % coordinates (measured against a portrait frame)
   still line up at the near-fullscreen "windowed" default or fully
   maximized; only an extreme drag/resize would visibly skew them. */
#map-stage{position:absolute;inset:0;overflow:hidden;background:#05060f}
#bg-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
#map-scrim{position:absolute;inset:0;
  background:radial-gradient(ellipse at 50% 55%,transparent 35%,rgba(5,6,15,.5) 100%)}
#map-nodes{position:absolute;inset:0;z-index:2}

/* ── KNOB + DRAWER-AS-WINDOW ──
   The knob opens the drawer as a real floating window now (same
   openWindow()/closeWindow()/minimizeWindow()/toggleMaximize() as
   every .screen — see wireWindowManager() in game.js), not a slide-
   from-the-edge panel, so its own head carries the same traffic-light
   trio as everything else. */
#drawer-knob{
  position:fixed;right:6px;top:50%;transform:translateY(-50%);
  /* Above every open window's z-index range (10-30ish, see
     restackWindowsZ() in game.js) so it's always reachable — like a
     real Dock icon staying clickable under full-screen windows. */
  z-index:60;width:36px;height:36px;
  background:var(--knob-bg);
  border:1px solid var(--line2);border-radius:9px;
  box-shadow:0 3px 12px rgba(0,0,0,.18);
  display:flex;align-items:center;justify-content:center;
  color:var(--cyan);font-size:18px;transition:.2s;
}
:root[data-theme="dark"] #drawer-knob{box-shadow:0 3px 12px rgba(0,0,0,.4)}
#drawer-knob:active{background:var(--knob-bg-active)}
/* Knob is global — visible on every screen (town, dungeon, battle,
   everywhere), hidden only on the intro screen since there's no game
   state to show yet. */
#drawer-knob{display:flex}
#app[data-screen="intro"] #drawer-knob{display:none}

#drawer-panel{
  display:none;flex-direction:column;
  position:fixed;inset:60px 8px 8px 8px;max-width:420px;margin:0 auto;
  background:var(--drawer-panel-bg);border:1px solid var(--line2);
  border-radius:12px;box-shadow:0 14px 40px rgba(0,0,0,.25);
  /* Overridden by restackWindowsZ() (game.js) the moment it's opened —
     it's a real member of windowStack like any .screen, same 10-30ish
     range. This is just the harmless pre-open resting value. */
  z-index:10;overflow:hidden;
}
:root[data-theme="dark"] #drawer-panel{box-shadow:0 14px 40px rgba(0,0,0,.5)}
#drawer-panel.on{display:flex}
#drawer-panel.maximized{inset:0;border-radius:0;border:none;max-width:none}
#drawer-panel.win-pop-in{animation:winPopIn .18s cubic-bezier(.2,.8,.3,1)}
.drawer-head{position:relative;flex:0 0 auto;display:flex;align-items:center;justify-content:center;
  height:32px;padding:0;border-bottom:1px solid var(--line2);
  background:var(--mac-titlebar);border-radius:11px 11px 0 0}
#drawer-panel.maximized .drawer-head{border-radius:0}
.drawer-head .proc-title{font-family:var(--pixel);font-size:13px;color:var(--txt);
  opacity:.75;letter-spacing:.5px;cursor:grab;user-select:none;-webkit-user-select:none;touch-action:none}
.drawer-head .proc-title:active{cursor:grabbing}
.drawer-body{flex:1;overflow-y:auto;padding:14px;-webkit-overflow-scrolling:touch}
.drawer-hud{display:flex;gap:8px;margin-bottom:14px}
.drawer-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:16px}
.drawer-tab{display:flex;flex-direction:column;align-items:center;gap:4px;
  padding:12px 6px;background:var(--panel2);border:1px solid var(--line);
  border-radius:8px}
.dt-icon{font-size:25px}
.dt-text{font-size:14px;color:var(--txt);text-align:center}
.drawer-section-title{font-family:var(--pixel);font-size:16px;color:var(--cyan);
  margin-bottom:8px;padding-bottom:5px;border-bottom:1px solid var(--line)}
.drawer-feed{max-height:none}
/* The DOT is anchored exactly at the landmark's measured x/y. The text
   label is now a SEPARATE element (.map-node-label) positioned at its
   own independently-measured coordinate — see buildMapNodes() — since
   the two aren't a fixed offset apart. No background box behind the
   text anymore (that dark rectangle was the complaint); readability
   comes from a heavy text-shadow instead, same treatment as zone
   labels already used. */
.map-node{position:absolute;transform:translate(-50%,-50%);background:none;border:none}

.map-node-label{
  position:absolute;transform:translate(-50%,-50%);z-index:3;
  font-family:var(--pixel);font-size:18px;letter-spacing:.4px;
  color:#fff;white-space:nowrap;pointer-events:auto;cursor:pointer;
  text-shadow:0 0 6px rgba(0,0,0,.95),0 0 12px rgba(0,0,0,.9),0 2px 3px rgba(0,0,0,.8);
}

/* Every node's click target IS the whole invisible circle, sized to
   the landmark (see zoneR in MAP_NODES) — a transparent tap area, no
   dot or box drawn over the art. Labels are separate .map-node-label
   elements positioned independently via textX/textY. */
.map-node.zone{
  border-radius:50%;display:flex;align-items:center;justify-content:center;
  transition:.15s;
}
.map-node.zone:hover{background:rgba(55,230,255,.08)}
.map-node.zone:active{background:rgba(55,230,255,.16)}
#map-actions{display:flex;gap:8px;flex-wrap:wrap;padding:12px}
#map-log{margin:0 12px 12px}

/* ── LOG ── */
.log-panel{background:rgba(4,6,14,.8);border:1px solid var(--line);
  padding:8px;height:120px;overflow-y:auto;font-size:15px;line-height:1.55}
.log-panel::-webkit-scrollbar{width:4px}
.log-panel::-webkit-scrollbar-thumb{background:var(--line2)}
.log-line.win{color:var(--gold)} .log-line.lose{color:var(--red)}
.log-line.heal{color:var(--green)} .log-line.sys{color:var(--cyan)}
.log-line.info{color:var(--muted)}

/* ── PET CARD ── */
.pet-card{
  width:146px;padding:11px 10px 10px;position:relative;transition:.15s;
  background:var(--panel2);
  border:2px solid var(--rar,var(--line));
  clip-path:polygon(0 0,calc(100% - 11px) 0,100% 11px,100% 100%,11px 100%,0 calc(100% - 11px));
}
/* rarity ribbon across the top edge */
.pet-card::after{content:'';position:absolute;left:0;right:0;top:0;height:3px;
  background:var(--rar,var(--line2));opacity:.9}
.pet-card:hover{transform:translateY(-4px);
  box-shadow:inset 0 1px 0 var(--rim),0 0 26px var(--glow,rgba(165,107,255,.4)),
             0 8px 20px rgba(0,0,0,.5)}
.pet-card.sel{border-color:var(--attr);
  box-shadow:inset 0 1px 0 var(--rim),0 0 26px var(--glow),0 6px 18px rgba(0,0,0,.5)}
.pet-card.down{opacity:.45;filter:grayscale(.7)}
/* team-slot card (city team roster) — cyan ring marks it as an active fighter */
.pet-card.in-team{border-color:var(--cyan);
  box-shadow:inset 0 1px 0 var(--rim),0 0 22px rgba(61,240,255,.45),0 6px 18px rgba(0,0,0,.5)}
.pet-card.in-team::after{background:var(--cyan)}
/* bench-slot card (reserve, swappable in any time) — violet ring */
.pet-card.in-bench{border-color:var(--violet);
  box-shadow:inset 0 1px 0 var(--rim),0 0 22px rgba(165,107,255,.45),0 6px 18px rgba(0,0,0,.5)}
.pet-card.in-bench::after{background:var(--violet)}
/* defense-slot card (base guard) — gold ring marks it as guarding the base */
.pet-card.in-def{border-color:var(--gold);
  box-shadow:inset 0 1px 0 var(--rim),0 0 22px rgba(255,207,63,.45),0 6px 18px rgba(0,0,0,.5)}
.pet-card.in-def::after{background:var(--gold)}
.pet-card.empty{display:flex;flex-direction:column;align-items:center;justify-content:center;
  min-height:180px;border-style:dashed;color:var(--muted)}
/* ── CHARACTER SCREEN DRAG-TO-SWAP ── see wireCharSlotDrag() in game.js.
   touch-action:none on the draggable cards is load-bearing: without it
   a touch-drag can get reinterpreted as a page scroll mid-gesture,
   which cancels our pointermove stream and makes the ghost stop
   tracking the finger while the page scrolls out from under it. */
#char-team-slots .pet-card, #char-bench-slots .pet-card{touch-action:none}
.pet-card.char-drag-source{opacity:.35}
.pet-card.char-drop-hover{border-color:var(--gold);
  box-shadow:inset 0 1px 0 var(--rim),0 0 22px rgba(255,207,63,.6),0 6px 18px rgba(0,0,0,.5)}
/* Just the creature sprite, enlarged and centered directly on the
   pointer -- same idea as the battle potion/poison wheel's carried
   icon (.throw-carry), not a scaled clone of the whole stat card. */
.char-drag-ghost{position:fixed;z-index:70;pointer-events:none;
  width:86px;height:86px;display:flex;align-items:center;justify-content:center;
  transform:translate(-50%,-50%) scale(1.5);
  transition:none;filter:drop-shadow(0 8px 16px rgba(0,0,0,.6)) drop-shadow(0 0 10px rgba(255,255,255,.55))}
.pet-card.empty.small{min-height:120px}
.empty-mark{font-size:30px;opacity:.5}
.empty-t{font-size:20px;margin-top:4px}
.pc-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}
.pc-attr{font-size:20px}
.pc-rar{font-family:var(--pixel);font-size:17px;color:var(--rar);letter-spacing:.5px;line-height:1}
.pc-sprite-wrap{position:relative;width:86px;height:86px;margin:0 auto 4px}
.pc-sprite{width:86px;height:86px;display:block;overflow:visible}
/* Down: a grey wipe grows counter-clockwise (mirrored conic-gradient)
   around the sprite over the real 5-minute window, transparent ->
   fully grey, driven by --frac (0-1) set inline at render time. */
.ps-wipe{position:absolute;inset:-4px;border-radius:50%;pointer-events:none;
  background:conic-gradient(rgba(110,110,120,.9) calc(var(--frac,0)*360deg),transparent 0);
  transform:scaleX(-1)}
/* Error: a glitchy RGB-split/jitter filter over the sprite itself. */
.ps-glitch{position:absolute;inset:0;pointer-events:none;
  background:repeating-linear-gradient(0deg,rgba(255,60,220,.18) 0 2px,rgba(60,220,255,.18) 2px 4px,transparent 4px 6px);
  mix-blend-mode:screen;animation:psGlitchJitter .25s steps(2) infinite}
@keyframes psGlitchJitter{
  0%,100%{clip-path:inset(0 0 0 0);transform:translate(0,0)}
  30%{clip-path:inset(10% 0 55% 0);transform:translate(-2px,1px)}
  60%{clip-path:inset(55% 0 8% 0);transform:translate(2px,-1px)}
  80%{clip-path:inset(30% 0 35% 0);transform:translate(-1px,2px)}}
.pet-card.ps-error .pc-sprite{animation:psGlitchJitter .25s steps(2) infinite}
.pc-state{font-size:14px;text-align:center;margin:2px 0;font-weight:bold}
.pc-state.down{color:#9fb0c8}
.pc-state.error{color:#ff5a5a}
.pc-state.incubating{color:#5fd6ff}
.pc-state.ready{color:var(--green)}
.pc-name{font-family:var(--pixel);font-size:26px;line-height:1.05;text-align:center;
  margin-bottom:4px;text-shadow:0 2px 0 rgba(0,0,0,.6)}
.pc-trait{font-size:16px}
.pc-evo-ready{display:inline-block;color:var(--green);font-size:18px;font-weight:bold;
  animation:evoArrowBob 1s ease-in-out infinite alternate;
  text-shadow:0 0 8px rgba(92,214,92,.8)}
@keyframes evoArrowBob{from{transform:translateY(0)}to{transform:translateY(-3px)}}
.pc-lv{font-size:17px;color:var(--txt);text-align:center;margin-bottom:5px;line-height:1.2}
.pc-bar{height:6px;background:rgba(0,0,0,.55);border:1px solid var(--line);overflow:hidden}
.pc-bar i{display:block;height:100%;background:var(--green);transition:width .3s}
.pc-hp{font-size:16px;color:var(--muted);text-align:center;margin:3px 0 4px;line-height:1.2}
.pc-stats{display:flex;justify-content:space-between;font-size:17px;color:var(--cyan);
  margin-top:4px;line-height:1.2}

/* ── HOME ── */
.section{margin-bottom:16px;padding:14px 13px;background:var(--panel);
  border:1px solid var(--line);position:relative;
  clip-path:polygon(0 0,calc(100% - 14px) 0,100% 14px,100% 100%,14px 100%,0 calc(100% - 14px))}
/* corner accent notch */
.section::before{content:'';position:absolute;top:0;right:0;
  width:14px;height:14px;background:linear-gradient(225deg,var(--violet),transparent);
  opacity:.6}
.section-title{font-family:var(--pixel);font-size:24px;line-height:1;color:var(--cyan);
  letter-spacing:1.5px;margin-bottom:10px;padding-bottom:7px;
  border-bottom:1px solid var(--line);
  text-shadow:0 0 12px rgba(61,240,255,.5),0 2px 0 rgba(0,0,0,.6)}
.card-row{display:flex;gap:10px;flex-wrap:wrap}
.syn-banner{padding:9px 11px;border:1px solid var(--line);background:var(--panel);
  font-size:15px;color:var(--muted);margin-bottom:8px}
.syn-banner.active{border-color:var(--attr);color:var(--txt);
  box-shadow:inset 0 0 24px color-mix(in srgb,var(--attr) 12%,transparent)}
.syn-banner b{color:var(--attr);font-weight:400}
#support-line{font-size:15px;color:var(--green);margin-bottom:8px}
.grid{display:flex;gap:10px;flex-wrap:wrap}
.muted{color:var(--muted);font-size:15px;padding:8px}

/* ── INVENTORY — swipeable sub-tabs of dense icon-box grids ──
   Same tab+track+swipe pattern as the pet detail modal (.pd-tabs/
   .pd-viewport/.pd-track), just under an inv- prefix since this lives
   on a free-scrolling page rather than a fixed-height modal — the
   track's height is simply whichever sub-tab is tallest, with
   #inv-viewport clipping the other two off-screen horizontally. */
.inv-tabs{display:flex;gap:6px;margin:8px 0}
.inv-tab{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:2px;padding:8px 4px 7px;font-size:12px;line-height:1.15;text-align:center;
  background:var(--panel2);border:1px solid var(--line2);color:var(--muted);
  border-radius:16px;transition:background .15s,box-shadow .15s}
.inv-tab .it-ic{font-size:19px;line-height:1}
.inv-tab.on{background:linear-gradient(160deg,var(--magenta),#8a3ce0);color:#fff;
  border-color:transparent;box-shadow:0 3px 12px rgba(214,40,158,.4)}
.inv-page{display:none;padding:2px}
.inv-page.on{display:block}
.inv-subtitle{font-family:var(--pixel);font-size:16px;color:var(--cyan);margin:10px 0 6px}
.inv-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:8px}
.inv-box{position:relative;aspect-ratio:1;background:var(--panel2);
  border:2px solid var(--grade,var(--line));border-radius:12px;
  box-shadow:0 0 9px var(--grade,transparent),inset 0 0 12px rgba(0,0,0,.15);
  display:flex;align-items:center;justify-content:center;cursor:pointer}
.inv-box:active{transform:scale(.93)}
.inv-box-icon{font-size:30px;line-height:1;display:flex;align-items:center;
  justify-content:center;width:100%;height:100%}
.inv-box-icon img{width:72%;height:72%;object-fit:contain;image-rendering:pixelated;
  filter:drop-shadow(0 0 3px var(--grade,transparent))}
.inv-box-num{position:absolute;bottom:2px;right:3px;font-size:12px;color:var(--gold);
  background:rgba(0,0,0,.65);border-radius:4px;padding:0 4px;line-height:1.4}
.inv-box-lock{position:absolute;top:2px;left:3px;font-size:11px;line-height:1;
  background:rgba(0,0,0,.65);border-radius:4px;padding:2px 3px}
.inv-empty-msg{grid-column:1/-1;color:var(--muted);font-size:15px;padding:14px;text-align:center}

/* ── EQUIPMENT DETAIL CARD ── see showEquipDetail() in
   src/screens/inventory.js. A richer replacement for the plain
   showItemDetail() popup, just for equipment: graded header block,
   a rough power total, a per-stat breakdown, and a lock toggle. */
.eqd-head{display:flex;align-items:center;gap:10px;margin-bottom:10px}
.eqd-icon-box{flex:0 0 auto;width:56px;height:56px;display:flex;align-items:center;justify-content:center;
  background:var(--panel2);border:2px solid var(--grade,var(--line));border-radius:10px;
  box-shadow:0 0 10px var(--grade,transparent)}
.eqd-icon-box img{width:78%;height:78%;object-fit:contain;image-rendering:pixelated}
.eqd-headinfo{flex:1;min-width:0}
.eqd-name{font-family:var(--pixel);font-size:18px;line-height:1.25}
.eqd-slot{font-size:14px;color:var(--muted)}
.eqd-grade{font-size:14px;font-weight:bold}
.eqd-lock{flex:0 0 auto;width:34px;height:34px;font-size:16px;border-radius:50%;
  background:var(--panel2);border:1px solid var(--line);cursor:pointer}
.eqd-lock.on{border-color:var(--gold);box-shadow:0 0 10px rgba(255,207,63,.4)}
.eqd-power{text-align:center;font-size:14px;color:var(--muted);margin-bottom:8px}
.eqd-power b{color:var(--cyan);font-size:17px}
.eqd-stats{display:flex;flex-direction:column;gap:2px;padding:8px 10px;
  background:var(--panel2);border:1px solid var(--line);margin-bottom:8px}
.eqd-stat-row{display:flex;justify-content:space-between;font-size:15px}
.eqd-stat-row b{color:var(--cyan)}
.eqd-effect{font-size:14px;padding:8px 10px;background:var(--panel2);border:1px solid var(--line);margin-bottom:8px}

/* Equipment tab — sort chips + sell-all row (see renderEquipBag()/
   sellAllEquip() in game.js). "on" chip toggles off back to the
   default sort (rarity desc, then level desc) when tapped again. */
.eq-sort-row{display:flex;align-items:center;gap:6px;margin:0 0 8px;flex-wrap:wrap}
.eq-sort-label{font-size:14px;color:var(--muted)}
.eq-sellall-row{display:flex;align-items:center;justify-content:space-between;
  gap:8px;margin:10px 0 4px;flex-wrap:wrap}

/* ── WHEEL LOADOUT (Inventory tab) ──
   Tap to add/remove an item from that pouch's up-to-5 wheel — the
   numbered badge is the order it'll sit around the wheel. See
   renderInvThrowLoadout()/throwLoadout() in game.js. */
.throw-loadout-grid{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:6px}
.throw-loadout-chip{position:relative;display:flex;align-items:center;gap:6px;
  padding:6px 10px;background:var(--panel2);border:1px solid var(--line);
  border-radius:6px;cursor:pointer;opacity:.75}
.throw-loadout-chip.on{opacity:1;border-color:var(--cyan);box-shadow:0 0 8px rgba(55,230,255,.25)}
.throw-loadout-chip:active{transform:scale(.96)}
.tlc-icon{font-size:19px}
.tlc-name{font-size:14px;color:var(--txt);font-weight:500}
.throw-loadout-chip.on .tlc-name{color:var(--cyan)}
.tlc-n{position:absolute;top:-6px;right:-6px;width:17px;height:17px;border-radius:50%;
  background:var(--cyan);color:#031018;font-size:11px;font-weight:bold;
  display:flex;align-items:center;justify-content:center}
.bot-chip{display:inline-flex;align-items:center;gap:6px;font-size:20px;
  padding:5px 9px;border:1px solid var(--line);background:var(--panel);margin:0 6px 6px 0}
.bot-chip small{color:var(--muted)}
.stat-line{display:flex;gap:14px;font-size:15px;color:var(--muted);margin-bottom:8px}
.stat-line b{color:var(--cyan);font-weight:400}

/* ── SHOP / CLINIC ── */
.shop-card{
  width:158px;padding:11px;background:var(--panel);border:1px solid var(--line);
  text-align:center;transition:.15s;
  clip-path:polygon(0 0,calc(100% - 9px) 0,100% 9px,100% 100%,9px 100%,0 calc(100% - 9px));
}
.shop-card:hover{border-color:var(--magenta);box-shadow:0 0 16px rgba(255,79,216,.2)}
.shop-card.disabled{opacity:.35;pointer-events:none}
.sc-icon{font-size:30px;margin-bottom:5px}
.sc-icon .eq-icon-img{width:36px;height:36px;display:block;margin:0 auto}
.sc-name{font-family:var(--pixel);font-size:21px;margin-bottom:5px}
.sc-desc{font-size:20px;color:var(--muted);line-height:1.45;margin-bottom:6px;min-height:34px}
.sc-cost{font-size:15px;color:var(--gold);margin-bottom:7px}
.guard-icon{height:64px;display:flex;align-items:flex-end;justify-content:center}
.guard-icon-sprite{max-width:100%;max-height:64px;width:auto;height:auto;image-rendering:pixelated;object-fit:contain}
.guard-card.owned{border-color:var(--green);box-shadow:0 0 14px rgba(77,255,158,.25)}

/* ── TECH LAB ── */
.techlab-section{border-color:rgba(150,255,150,.25)}
.tl-status{font-family:var(--pixel);font-size:16px;padding:8px 10px;margin-bottom:10px;
  border:1px solid var(--line);background:var(--panel2)}
.tl-status.ok{color:var(--green);border-color:var(--green)}
.tl-status.bad{color:#ff8a5a;border-color:rgba(255,138,90,.5)}
.tl-slots{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:12px}
.tl-slot{width:78px;height:88px;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:4px;background:var(--panel);border:2px dashed var(--line);
  color:var(--muted);padding:4px}
.tl-slot.filled{border-style:solid;border-color:var(--cyan);background:var(--panel2);color:var(--txt)}
.tl-slot-plus{font-size:30px;color:var(--line2)}
.tl-slot-icon{font-size:30px}
.tl-slot-label{font-size:13px;text-align:center;line-height:1.2}
.tl-slot-count{font-size:12px;color:var(--gold)}
/* ── SYNCHRONIZE (Tech Lab section) ── see renderSynchronize() in
   src/screens/shop.js. Reuses .tl-slot's base look, just wider/taller
   to fit a name + rarity + level line instead of one icon+label. */
.sync-slots{display:flex;align-items:center;gap:10px;margin-bottom:12px}
.sync-slot{width:120px;height:120px;flex:0 0 auto}
.sync-slot-sprite{width:52px;height:52px;display:block;margin:0 auto 2px}
.sync-plus{font-size:22px;color:var(--muted);flex:0 0 auto}
.sync-base-label{font-size:14px;color:var(--muted);margin-bottom:6px}
.sync-base-pick{display:flex;gap:10px;margin-bottom:12px}
.sync-base-btn{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;
  padding:8px 4px;background:var(--panel2);border:2px solid var(--line);color:var(--muted)}
.sync-base-btn.on{border-color:var(--cyan);color:var(--txt);box-shadow:0 0 12px rgba(55,230,255,.3)}
.sync-base-sprite{width:44px;height:44px;display:block}
.tl-odds{margin-bottom:12px;padding:8px 10px;background:var(--panel);border:1px solid var(--line)}
.tl-odds-title{font-size:14px;color:var(--muted);margin-bottom:5px}
.tl-odds-row{display:flex;justify-content:space-between;font-size:16px;padding:2px 0}

/* ── EVOLVE ANIMATION ── */
.evo-overlay{position:fixed;inset:0;z-index:999;background:rgba(4,6,14,.92);
  display:flex;align-items:center;justify-content:center}
.evo-stage{position:relative;display:flex;flex-direction:column;align-items:center;gap:14px}
.evo-sprite-wrap{position:relative;width:200px;height:200px;display:flex;align-items:center;justify-content:center}
.evo-sprite-wrap img,.evo-sprite-wrap svg{max-width:180px;max-height:180px}
.evo-sprite-wrap.evo-pulse{animation:evoPulse .95s ease-in-out}
.evo-sprite-wrap.evo-pulse::after{content:'';position:absolute;inset:-20px;border-radius:50%;
  background:radial-gradient(circle,rgba(255,255,255,.95) 0%,rgba(255,255,255,0) 70%);
  animation:evoPulseGlow .95s ease-in-out}
@keyframes evoPulse{0%,100%{filter:brightness(1)}45%{filter:brightness(3) saturate(0)}}
@keyframes evoPulseGlow{0%,100%{opacity:0}45%{opacity:1}}
.evo-sprite-wrap.evo-reveal{animation:evoReveal .5s ease-out}
@keyframes evoReveal{from{transform:scale(.4);opacity:0}to{transform:scale(1);opacity:1}}
.evo-shards{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);pointer-events:none}
.evo-shard{position:absolute;background-repeat:no-repeat;opacity:1}
.evo-shards.go .evo-shard{animation:evoShardFly .8s ease-out forwards}
@keyframes evoShardFly{
  0%{transform:translate(0,0) rotate(0deg);opacity:1}
  100%{transform:translate(var(--dx),var(--dy)) rotate(var(--rot));opacity:0}}
.evo-label{font-family:var(--pixel);font-size:23px;color:#eaf4ff;letter-spacing:1px;
  text-shadow:0 0 12px rgba(120,200,255,.6)}

.clinic-row{display:flex;align-items:center;gap:10px;padding:8px;
  border:1px solid var(--line);background:var(--panel);margin-bottom:7px}
.clinic-row.down{border-color:rgba(159,176,200,.5)}
.clinic-row.error{border-color:rgba(255,90,90,.6);box-shadow:0 0 12px rgba(255,60,60,.15)}
.clinic-row.incubating{border-color:rgba(95,214,255,.5)}
.clinic-row.ready{border-color:var(--green);box-shadow:0 0 12px rgba(77,255,158,.2)}
.cr-sprite{width:44px;height:44px;display:block;overflow:visible}
.cr-info{flex:1;display:flex;flex-direction:column;gap:2px}
.cr-info b{font-family:var(--pixel);font-size:21px}
.cr-info span{font-size:20px;color:var(--muted)}
.btn.danger{border-color:var(--red);color:var(--red)}

/* ── HACK ── */
.hack-card{
  padding:12px;background:var(--panel);border:1px solid var(--line);
  border-left:3px solid var(--tier);margin-bottom:10px;transition:.15s;
}
.hack-card:hover{box-shadow:0 0 18px color-mix(in srgb,var(--tier) 22%,transparent)}
.hc-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:5px}
.hc-name{font-family:var(--pixel);font-size:25px}
.hc-tier{font-family:var(--pixel);font-size:21px;color:var(--tier);
  border:1px solid var(--tier);padding:3px 7px}
.hc-desc{font-size:15px;color:var(--muted);margin-bottom:6px}
.hc-meta{display:flex;gap:12px;font-size:20px;color:var(--muted)}

/* ── PET CARD EXTRAS (exp, loyalty, signature) ── */
.pc-cap{color:var(--muted);font-size:15px}
.pc-xpbar{height:3px;background:rgba(0,0,0,.55);border:1px solid var(--line);
  overflow:hidden;margin:3px 0 1px}
.pc-xpbar i{display:block;height:100%;background:var(--gold);transition:width .3s}
.pc-xp{font-size:15px;color:var(--muted);margin-bottom:4px;line-height:1.2}
.pc-loy{font-size:16px;color:var(--txt);display:flex;align-items:center;gap:5px;line-height:1.2;
  margin-top:4px;padding-top:4px;border-top:1px solid var(--line)}
.pc-loy-bar{flex:1;height:3px;background:rgba(0,0,0,.55);border:1px solid var(--line);
  overflow:hidden}
.pc-loy-bar i{display:block;height:100%;
  background:linear-gradient(90deg,#ff6a8a,#ff2d55)}
.pc-sig{font-size:15px;color:#ffd23f;margin-top:4px;line-height:1.2;
  text-shadow:0 0 8px rgba(255,210,63,.5)}

/* ── CARE SCREEN ── */
#care-picker{display:flex;gap:8px;overflow-x:auto;padding-bottom:8px;margin-bottom:10px}
#techlab-pet-picker{display:flex;gap:8px;overflow-x:auto;padding-bottom:8px;margin-bottom:10px}
.care-chip{flex:0 0 auto;width:64px;padding:6px 4px;background:var(--panel2);
  border:1px solid var(--line);text-align:center;transition:.12s;
  clip-path:polygon(0 0,calc(100% - 6px) 0,100% 6px,100% 100%,0 100%)}
.care-chip.on{border-color:var(--cyan);box-shadow:0 0 12px rgba(55,230,255,.3)}
.care-chip-sprite{width:44px;height:44px;display:block;margin:0 auto}
.care-chip span{font-size:15px;color:var(--muted);display:block;margin-top:2px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* Tamagotchi living-area stage: a background themed + randomly
   scattered to the pet's attribute (genCareBackground in JS), the
   pet centered in it, and the care activities scattered in a ring
   near the edges (layoutCareRing in JS sets each button's left/top%). */
.care-stage{position:relative;min-height:min(72vh,560px);
  border:1px solid var(--line2);overflow:hidden;margin-bottom:6px;
  clip-path:polygon(0 0,calc(100% - 14px) 0,100% 14px,100% 100%,14px 100%,0 calc(100% - 14px))}
.care-bg{position:absolute;inset:0;z-index:0;transition:background .3s}
.care-prop{position:absolute;transform:translate(-50%,-50%) rotate(var(--rot,0deg));
  font-size:var(--sz,20px);pointer-events:none;
  animation:carePropDrift 5s ease-in-out infinite}
@keyframes carePropDrift{
  0%,100%{transform:translate(-50%,-50%) rotate(var(--rot,0deg)) translateY(0)}
  50%{transform:translate(-50%,-50%) rotate(var(--rot,0deg)) translateY(-6px)}}

/* Info panel stays put near the top of the stage — it must never
   wander with the pet, or the loyalty bar would be unreadable. */
.care-info-fixed{position:absolute;left:50%;top:5%;transform:translateX(-50%);
  z-index:2;text-align:center;pointer-events:none;
  padding:8px 14px;background:rgba(10,7,20,.72);
  border:1px solid var(--line2);backdrop-filter:blur(3px);min-width:150px}
.care-name{font-size:17px;margin-bottom:2px}
.care-tier{font-size:15px;color:var(--muted);margin-bottom:5px}
.loy-bar{height:7px;background:rgba(0,0,0,.5);border:1px solid var(--line);overflow:hidden}
.loy-bar i{display:block;height:100%;
  background:linear-gradient(90deg,#ff6a8a,#ff2d55);transition:width .4s}

/* The pet itself — positioned via inline left/top% by the wander
   state machine (startCareWander/wanderStep in JS), so it's never
   fully static the way a plain float-in-place bob would be. */
.care-pet-wander{position:absolute;left:50%;top:52%;transform:translate(-50%,-50%);
  z-index:2;text-align:center;pointer-events:none;
  transition:left 1.4s ease-in-out,top 1.4s ease-in-out}
.care-pet-wander.face-l{transform:translate(-50%,-50%) scaleX(-1)}
.care-sprite{width:128px;height:128px;display:block;margin:0 auto;
  filter:drop-shadow(0 14px 18px rgba(0,0,0,.5))}
/* A little bob while walking so it doesn't glide like a ghost. */
.care-pet-wander.wandering .care-sprite{animation:careWalkBob .4s ease-in-out infinite}
@keyframes careWalkBob{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}
/* Bigger celebratory hop when a care action finishes. */
.care-pet-wander.happy .care-sprite{animation:careHappyHop .7s ease-out}
@keyframes careHappyHop{
  0%{transform:translateY(0) rotate(0deg)}
  25%{transform:translateY(-22px) rotate(-8deg)}
  50%{transform:translateY(0) rotate(6deg)}
  75%{transform:translateY(-10px) rotate(-4deg)}
  100%{transform:translateY(0) rotate(0deg)}}

.care-particle{position:absolute;bottom:10px;left:50%;font-size:23px;pointer-events:none;
  animation:careFloat 1.2s ease-out forwards}
@keyframes careFloat{
  0%{opacity:0;transform:translateY(0) scale(.5)}
  25%{opacity:1;transform:translateY(-18px) scale(1.15)}
  100%{opacity:0;transform:translateY(-70px) scale(.9)}}

/* Ambient mess prop — no stat, no failure state, just a visual nag
   that the existing Clean minigame sweeps away (see clearWaste()). */
.care-waste{position:absolute;transform:translate(-50%,-50%);font-size:25px;
  z-index:1;filter:saturate(1.4) hue-rotate(60deg);
  animation:wasteGlitch 1.8s ease-in-out infinite}
.care-waste.gone{opacity:0;transition:opacity .25s}
@keyframes wasteGlitch{0%,100%{opacity:.85}50%{opacity:.55}}

/* Ring of small circular activity buttons scattered around the
   stage's border, positioned via inline left/top% from JS. */
.care-ring{position:absolute;inset:0;z-index:3}
.care-ring-btn{position:absolute;transform:translate(-50%,-50%);
  width:52px;height:52px;border-radius:50%;padding:0;
  background:rgba(20,14,38,.88);border:2px solid var(--line2);
  display:flex;align-items:center;justify-content:center;
  transition:transform .15s,box-shadow .15s}
.care-ring-btn .crb-icon{font-size:25px;line-height:1}
.care-ring-btn.ready{border-color:var(--cyan);box-shadow:0 0 14px rgba(61,240,255,.35)}
.care-ring-btn.ready:active{transform:translate(-50%,-50%) scale(.9)}
.care-ring-btn.cooling{opacity:.5}
.care-ring-btn.need-buy{border-color:var(--gold)}
/* Pulses while the ambient mess prop is present — the "alert" from
   the spec, without a separate always-visible icon. */
.care-ring-btn.needs-clean{animation:needsCleanPulse 1s ease-in-out infinite}
@keyframes needsCleanPulse{
  0%,100%{box-shadow:0 0 8px rgba(120,255,180,.3)}
  50%{box-shadow:0 0 18px 4px rgba(120,255,180,.65)}}
.crb-badge{position:absolute;left:50%;bottom:-9px;transform:translateX(-50%);
  font-size:13px;padding:1px 6px;white-space:nowrap;
  background:rgba(10,7,20,.92);border:1px solid var(--line2)}
.crb-badge.loy{color:#ff6a8a}
.crb-badge.buy{color:var(--gold)}
.crb-badge.cd{color:var(--muted)}

/* ── PET DETAIL OVERLAY ──
   Below the generic modal(200) so the equip-item picker (which uses
   the generic modal) still layers correctly on top of this. Above
   everything else (drawer, header, care minigame at 220... actually
   keep this one BELOW care-game too, they never show at once). */
.pet-detail{position:fixed;inset:0;z-index:150;display:none;flex-direction:column;
  background:linear-gradient(180deg,var(--bg) 0%,var(--bg2) 55%,var(--bg) 100%);
  padding:calc(10px + env(safe-area-inset-top)) 14px calc(12px + env(safe-area-inset-bottom))}
.pet-detail.open{display:flex}
body.pet-detail-open{overflow:hidden}

.pd-head{display:flex;align-items:center;justify-content:space-between;
  gap:10px;padding-bottom:8px;border-bottom:1px solid var(--line)}
#pd-title{font-family:var(--pixel);font-size:21px;color:var(--txt)}

.pd-tabs{display:flex;gap:6px;margin:8px 0}
.pd-tab{flex:1;padding:7px 4px;font-size:16px;text-align:center;
  background:var(--panel2);border:1px solid var(--line2);color:var(--muted)}
.pd-tab.on{border-color:var(--cyan);color:var(--cyan);box-shadow:0 0 10px rgba(61,240,255,.3)}

.pd-viewport{flex:1;overflow:hidden}
.pd-track{display:flex;height:100%;transition:transform .25s ease}
.pd-page{flex:0 0 100%;overflow-y:auto;padding:4px 2px;-webkit-overflow-scrolling:touch}

.pd-team-btns{display:flex;gap:8px;margin-top:14px}
.pd-team-btn{flex:1;margin-top:0}
.pd-team-btn.danger{border-color:var(--red);color:var(--red)}
.pd-team-btn:disabled{opacity:.4}
#pd-dismiss-btn{margin-top:8px}
#pd-dismiss-btn:disabled{opacity:.35}

.pd-tree-title{font-family:var(--pixel);font-size:18px;margin-bottom:8px;text-align:center}
.pd-tree-title i{font-style:normal;font-size:15px;margin-left:4px}

/* ── CARE MINIGAME OVERLAY ──
   A fixed full-viewport layer opened over the care screen when an
   activity is used. Below toast(300)/click-ripple(9999) so feedback
   still reads on top, above everything else (drawer 49-50, header
   70, modal 200) so it plays as a modal task. */
.care-game{position:fixed;inset:0;z-index:220;display:none;flex-direction:column;
  background:linear-gradient(180deg,var(--bg) 0%,var(--bg2) 55%,var(--bg) 100%);
  padding:calc(10px + env(safe-area-inset-top)) 14px calc(12px + env(safe-area-inset-bottom))}
.care-game.open{display:flex}
body.care-game-open{overflow:hidden}

.cg-head{display:flex;align-items:center;justify-content:space-between;
  gap:10px;padding-bottom:8px;border-bottom:1px solid var(--line)}
.cg-title{font-family:var(--pixel);font-size:17px;color:var(--txt);line-height:1.3}
.cg-close{flex:0 0 auto;font-family:var(--pixel);font-size:17px;width:32px;height:32px;
  border:1px solid var(--line2);background:var(--panel2);color:var(--muted);
  clip-path:polygon(0 0,calc(100% - 6px) 0,100% 6px,100% 100%,0 100%)}
.cg-close:active{color:var(--red);border-color:var(--red)}

.care-game-stage{position:relative;flex:1;width:100%;overflow:hidden;
  margin-top:6px;touch-action:none;user-select:none;-webkit-user-select:none}

.cg-pet{position:absolute;left:50%;top:44%;transform:translate(-50%,-50%);
  z-index:2;pointer-events:none;text-align:center;transition:top .2s,left .65s ease-in-out}
.cg-pet-sprite{width:120px;height:120px;display:block;
  filter:drop-shadow(0 10px 16px rgba(0,0,0,.55))}
.cg-pet.chomp .cg-pet-sprite{animation:cgChomp .3s ease-out}
@keyframes cgChomp{0%{transform:scale(1)}40%{transform:scale(1.12) rotate(-3deg)}100%{transform:scale(1)}}

.cg-foot{padding-top:10px}
.cg-bar{height:9px;background:rgba(0,0,0,.5);border:1px solid var(--line);overflow:hidden}
.cg-bar i{display:block;height:100%;width:0;
  background:linear-gradient(90deg,#ff6a8a,#ff2d55);transition:width .25s}
.cg-hint{font-size:18px;color:var(--muted);text-align:center;margin-top:6px;min-height:20px}

/* — Bathing: dirt spots you brush away — */
.dirt-spot{position:absolute;transform:translate(-50%,-50%) scale(calc(.55 + var(--grime,1)*.55));
  width:34px;height:34px;pointer-events:none;z-index:3;
  opacity:calc(.35 + var(--grime,1)*.65);transition:opacity .15s,transform .15s}
.dirt-blob{display:block;width:100%;height:100%;
  border-radius:46% 54% 58% 42%/48% 42% 58% 52%;
  background:radial-gradient(circle at 30% 30%,rgba(120,90,40,.85),transparent 60%),
             radial-gradient(circle at 65% 60%,rgba(70,150,90,.55),transparent 55%),
             rgba(60,45,25,.55);
  box-shadow:inset 0 0 8px rgba(0,0,0,.4),0 0 6px rgba(120,255,180,.15);
  animation:dirtWobble 2.6s ease-in-out infinite}
.dirt-spot.scrub .dirt-blob{filter:brightness(1.8)}
.dirt-spot.gone{opacity:0 !important;transform:translate(-50%,-50%) scale(1.6) !important;
  transition:opacity .26s,transform .26s}
@keyframes dirtWobble{0%,100%{transform:rotate(-4deg)}50%{transform:rotate(4deg)}}
.scrub-spark{position:absolute;transform:translate(-50%,-50%);width:14px;height:14px;
  border-radius:50%;pointer-events:none;z-index:4;
  background:radial-gradient(circle,rgba(255,255,255,.9),rgba(120,255,220,.5) 60%,transparent 75%);
  animation:scrubSparkGo .34s ease-out forwards}
@keyframes scrubSparkGo{0%{opacity:1;transform:translate(-50%,-50%) scale(.4)}
  100%{opacity:0;transform:translate(-50%,-50%) scale(1.8)}}

/* — Feeding: tap the food away, bite by bite — */
.cg-food{position:absolute;left:50%;top:64%;transform:translate(-50%,-50%);z-index:3}
.cg-food-icon{display:block;font-size:52px;line-height:1;
  transform:scale(var(--bite,1));transition:transform .18s ease-out;
  filter:drop-shadow(0 6px 10px rgba(0,0,0,.5))}
.cg-food.bite .cg-food-icon{animation:foodBite .18s ease-out}
@keyframes foodBite{0%{filter:brightness(1)}50%{filter:brightness(1.7)}100%{filter:brightness(1)}}
.crumb-bit{position:absolute;left:50%;top:50%;width:5px;height:5px;border-radius:1px;
  background:#e8c26a;pointer-events:none;z-index:4;
  animation:crumbFly .5s ease-in forwards}
@keyframes crumbFly{0%{opacity:1;transform:translate(-50%,-50%) translate(0,0)}
  100%{opacity:0;transform:translate(-50%,-50%) translate(var(--dx),var(--dy))}}

/* — Toy: Packet Ball — */
.cg-ball{position:absolute;left:50%;top:44%;transform:translate(-50%,-50%);
  font-size:32px;z-index:4;transition-property:left,top;
  transition-timing-function:cubic-bezier(.3,.7,.4,1);
  filter:drop-shadow(0 4px 6px rgba(0,0,0,.5))}
.cg-ball.landed{animation:ballBounce .5s ease-in-out infinite}
.cg-ball.caught{animation:ballPop .3s ease-out}
@keyframes ballBounce{0%,100%{transform:translate(-50%,-50%) scale(1)}50%{transform:translate(-50%,-58%) scale(1.06)}}
@keyframes ballPop{0%{transform:translate(-50%,-50%) scale(1.5)}100%{transform:translate(-50%,-50%) scale(1)}}

/* — Toy: Laser Pointer — */
.cg-laser{position:absolute;left:15%;top:20%;width:16px;height:16px;border-radius:50%;
  transform:translate(-50%,-50%);z-index:4;
  background:radial-gradient(circle,#fff,#ff2d55 55%,transparent 75%);
  box-shadow:0 0 10px 4px rgba(255,45,85,.7);transition:left .12s linear,top .12s linear}
.cg-laser.pounced{animation:laserPounced .25s ease-out}
@keyframes laserPounced{0%{transform:translate(-50%,-50%) scale(1.8)}100%{transform:translate(-50%,-50%) scale(1)}}

/* — Toy: Logic Cube (Simon-says) — */
.cg-cube{position:absolute;left:50%;transform:translate(-50%,-50%) rotate(45deg);
  width:140px;height:140px;display:grid;grid-template-columns:1fr 1fr;
  grid-template-rows:1fr 1fr;gap:4px;z-index:4}
.cg-cube.locked{pointer-events:none}
.cg-face{border:2px solid var(--line2);transition:filter .15s,transform .15s}
.cg-face-a{background:linear-gradient(135deg,#3df0ff,#1a8ea8);border-radius:16px 4px 4px 4px}
.cg-face-b{background:linear-gradient(135deg,#ff5ce0,#a8228c);border-radius:4px 16px 4px 4px}
.cg-face-c{background:linear-gradient(135deg,#ffcf3f,#c98a10);border-radius:4px 4px 4px 16px}
.cg-face-d{background:linear-gradient(135deg,#4dff9e,#1c9c5c);border-radius:4px 4px 16px 4px}
.cg-face.flash{filter:brightness(2.2);transform:scale(.9)}

/* — Toy: Mini Arcade (whack-a-bug) — */
.cg-arcade{position:absolute;left:50%;transform:translate(-50%,-50%);
  display:grid;grid-template-columns:repeat(3,1fr);gap:8px;
  width:210px;height:210px;z-index:4}
.cg-cell{background:rgba(0,0,0,.35);border:1px solid var(--line);border-radius:50%;
  display:flex;align-items:center;justify-content:center;font-size:0;
  transition:font-size .1s}
.cg-cell.up{font-size:30px;animation:cellPop .15s ease-out}
.cg-cell.hit{filter:brightness(2)}
@keyframes cellPop{0%{font-size:0}100%{font-size:30px}}

/* ── WORLD MAP ── */
#world-stage{position:relative;width:100%;aspect-ratio:9/16;max-height:86vh;
  overflow:hidden;border:1px solid var(--line2);background:#05060f}
#world-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
#world-scrim{position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(ellipse at 50% 50%,transparent 45%,rgba(4,6,14,.5) 100%)}
.world-name-overlay{position:absolute;top:10px;left:50%;transform:translateX(-50%);
  z-index:4;text-align:center;padding:5px 16px;background:rgba(6,8,18,.62);
  backdrop-filter:blur(3px);border:1px solid rgba(140,190,255,.3);
  clip-path:polygon(0 0,calc(100% - 8px) 0,100% 8px,100% 100%,8px 100%,0 calc(100% - 8px));
  pointer-events:none;white-space:nowrap}
.world-name-overlay b{display:block;font-family:var(--pixel);font-size:20px;color:#eaf4ff;
  text-shadow:0 0 10px rgba(140,190,255,.6)}
.world-name-overlay span{font-size:14px;color:var(--muted)}
#world-pins{position:absolute;inset:0}
#world-train-layer{position:absolute;inset:0;z-index:5;pointer-events:none}

.zone-pin{position:absolute;transform:translate(-50%,-50%);
  display:flex;flex-direction:column;align-items:center;gap:3px;z-index:2}
.pin-dot{width:16px;height:16px;border-radius:50%;
  border:2px solid rgba(255,255,255,.9);animation:pinPulse 2.2s infinite}
.zone-pin.battle .pin-dot{background:#f0442c;
  box-shadow:0 0 0 4px rgba(240,68,44,.25),0 0 14px rgba(240,68,44,.8)}
.zone-pin.safe .pin-dot{background:#5cd65c;
  box-shadow:0 0 0 4px rgba(92,214,92,.25),0 0 14px rgba(92,214,92,.8)}
.zone-pin.hard .pin-dot{background:#8a1f1f;
  box-shadow:0 0 0 4px rgba(138,31,31,.3),0 0 12px rgba(138,31,31,.7)}
@keyframes pinPulse{
  0%,100%{transform:scale(1)}
  50%{transform:scale(1.22)}}
/* Default: a small, translucent single-line chip (name only) so the
   map/video reads through and neighboring nodes don't visually collide.
   Full detail (thai name + level/kind, in .pin-extra) unfolds on hover/
   active (desktop) or once "armed" — see wireArmedPinTap() in game.js:
   a first tap arms the node (just shows this box, no travel/fight yet)
   and auto-disarms after 5s; tapping the SAME node again within that
   window is the actual confirm. */
.pin-card{display:flex;flex-direction:column;align-items:center;
  max-width:72px;padding:2px 7px;background:rgba(6,10,20,.5);
  border:1px solid rgba(140,190,255,.4);backdrop-filter:blur(1.5px);
  white-space:nowrap;overflow:hidden;transition:.16s;
  clip-path:polygon(0 0,calc(100% - 4px) 0,100% 4px,100% 100%,0 100%)}
.pin-card b{display:block;max-width:100%;overflow:hidden;text-overflow:ellipsis;
  font-family:var(--pixel);font-size:12px;letter-spacing:.3px;color:#eaf4ff}
.pin-extra{display:flex;flex-direction:column;align-items:center;
  max-height:0;opacity:0;transition:.16s;overflow:hidden}
.pin-card i{font-size:14px;color:var(--muted);font-style:normal}
.pin-card em{font-size:14px;font-style:normal;color:var(--cyan)}
.zone-pin.safe .pin-card em{color:var(--green)}
.zone-pin.hard .pin-card em{color:#ff6a6a}
.zone-pin:hover,.zone-pin:active,.zone-pin.armed{z-index:20}
.zone-pin:hover .pin-card,.zone-pin:active .pin-card,.zone-pin.armed .pin-card{
  max-width:none;background:rgba(6,10,20,.78);
  border-color:var(--cyan);transform:translateY(-2px);
  box-shadow:0 0 16px rgba(55,230,255,.35)}
.zone-pin:hover .pin-extra,.zone-pin:active .pin-extra,.zone-pin.armed .pin-extra{max-height:60px;opacity:1}

/* region boss pin — dark red, bigger, a slowly-rotating radiant glow
   behind the dot so it reads as "moving" without literally animating
   position (the dot itself still pulses via the shared pinPulse). */
.boss-pin{z-index:3}
.boss-pin .boss-dot{width:22px;height:22px;background:#7a0d0d;
  border-color:rgba(255,180,150,.95);
  box-shadow:0 0 0 5px rgba(122,13,13,.3),0 0 22px rgba(255,40,20,.9);
  position:relative}
.boss-pin .boss-dot::before{content:'';position:absolute;inset:-14px;
  border-radius:50%;
  background:conic-gradient(from 0deg,rgba(180,10,10,0) 0%,rgba(255,40,20,.55) 18%,rgba(180,10,10,0) 40%);
  animation:bossRadiant 2.6s linear infinite;pointer-events:none}
@keyframes bossRadiant{to{transform:rotate(360deg)}}
.boss-card{border-color:rgba(255,90,60,.6) !important;background:rgba(20,4,4,.5) !important}
.boss-card b{color:#ff6a4a}
.boss-card em{color:#ff9a3d !important;font-weight:bold}
.boss-pin:hover .boss-card,.boss-pin:active .boss-card{
  background:rgba(20,4,4,.8) !important;
  border-color:#ff6a4a !important;box-shadow:0 0 18px rgba(255,60,30,.5) !important}

/* ── WARP GATE PINS ──
   warpIn (near the lowest-level zone, entry point on first visit) and
   warpOut (red, near the highest-level zone, leads to the next map) —
   same .zone-pin shape/pulse as a normal pin, distinct icon+colors. */
.zone-pin.warp-in .pin-dot{background:#3df0ff;
  box-shadow:0 0 0 4px rgba(61,240,255,.25),0 0 14px rgba(61,240,255,.8)}
.zone-pin.warp-out .pin-dot{background:#ff3d3d;width:20px;height:20px;
  box-shadow:0 0 0 5px rgba(255,61,61,.3),0 0 16px rgba(255,40,40,.9)}
.zone-pin.warp-in .pin-card{border-color:rgba(61,240,255,.6)}
.zone-pin.warp-out .pin-card{border-color:rgba(255,61,61,.6)}
.zone-pin.warp-out .pin-card em{color:#ff8a8a}

/* "you are here" marker — a small round crop of the player's first
   team pet's own sprite, non-clickable (the pin underneath still
   handles taps), sitting right on top of whichever node the player is
   currently standing at. */
.here-marker{position:absolute;transform:translate(-50%,-115%);z-index:3;
  width:34px;height:34px;pointer-events:none;
  filter:drop-shadow(0 2px 4px rgba(0,0,0,.7))}
.here-marker-ring{position:absolute;inset:0;border-radius:50%;
  border:2px solid #ffd23f;box-shadow:0 0 10px rgba(255,210,63,.7);
  background:rgba(10,14,28,.85);overflow:hidden;display:flex;align-items:center;justify-content:center}
.here-marker-ring img,.here-marker-ring svg{width:82%;height:82%}
.here-marker-tail{position:absolute;left:50%;bottom:-6px;transform:translateX(-50%);
  width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;
  border-top:7px solid #ffd23f}

/* ── TRAIN TRAVEL ── a "rail" div grows from the departure point
   toward the destination (border-top dashed = the broken trail line),
   with the train sprite riding at its leading edge. Rotated to match
   the travel angle, transform-origin pinned to the start point. */
.train-rail{position:absolute;top:0;left:0;height:0;
  border-top:3px dashed #ffd23f;transform-origin:0 0;
  filter:drop-shadow(0 0 4px rgba(255,210,63,.6))}
.train-sprite{position:absolute;right:-2px;top:-13px;width:30px;height:26px;
  filter:drop-shadow(0 2px 3px rgba(0,0,0,.6))}
.train-sprite svg{width:100%;height:100%;display:block}
.train-ambush-flash{position:absolute;inset:0;z-index:6;display:flex;
  align-items:center;justify-content:center;background:rgba(120,10,10,.35);
  animation:trainAmbushFlash .5s ease-out both}
.train-ambush-flash span{font-family:var(--pixel);font-size:30px;color:#fff;
  text-shadow:0 0 14px #ff3d3d,0 2px 3px #000;animation:trainAmbushShake .4s ease-in-out 2}
@keyframes trainAmbushFlash{from{opacity:0}to{opacity:1}}
@keyframes trainAmbushShake{0%,100%{transform:translateX(0)}50%{transform:translateX(-6px) rotate(-2deg)}}

/* ── AMBUSH REWARD CARDS ── shown after winning a train-ride mimic
   fight; pick exactly one. */
.reward-cards{display:flex;gap:10px;justify-content:center;padding:6px 0}
.reward-card{flex:1;max-width:120px;padding:12px 8px;text-align:center;
  background:var(--panel2);border:1px solid var(--line2);cursor:pointer;transition:.15s}
.reward-card:active{transform:scale(.95)}
.reward-card:hover{border-color:var(--gold);box-shadow:0 0 14px rgba(255,210,63,.3)}
.reward-card .rc-icon{font-size:34px;margin-bottom:6px}
.reward-card .rc-label{font-size:14px;color:var(--muted)}

/* zone briefing modal */
.zone-brief{font-size:15px}
.zb-desc{color:var(--muted);margin-bottom:10px;line-height:1.6}
.zb-meta{display:flex;gap:14px;flex-wrap:wrap;font-size:20px;color:var(--muted);
  padding:8px;border:1px solid var(--line);margin-bottom:10px}
.zb-meta b{color:var(--cyan);font-weight:400}
.zb-mons{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:10px}
.zb-mon{width:76px;text-align:center;padding:6px;
  background:rgba(6,10,22,.6);border:1px solid var(--line)}
.zb-sprite{width:56px;height:56px;display:block;margin:0 auto}
.zb-mon span{font-size:16px;color:var(--muted)}
.zb-warn{padding:8px;border:1px solid #8a1f1f;background:rgba(138,31,31,.15);
  color:#ff9a9a;font-size:20px;margin-bottom:8px}

/* ── SAFE SPOT ── */
.sc-owned{font-size:18px;color:var(--green);margin-bottom:6px}

/* ── THROWABLE POTIONS / POISONS ──
   #potion-bar now holds just two pouches — a potion satchel (left) and
   a poison pouch (right), touch-and-hold-and-drag each opens a radial
   wheel of its up-to-5 slotted items (see wireThrowPouches() in
   game.js). Dragging past the wheel's lock radius picks whichever
   wedge the finger is over, the wheel fades, and a carried icon tracks
   the pointer until released ("flicked") over a side of the
   battlefield — whichever unit is actually under it on release gets
   the effect, ally or enemy alike. */
#potion-bar{display:flex;justify-content:center;gap:120px;padding:8px 16px;
  background:var(--panel);border-top:1px solid var(--line);min-height:44px;
  align-items:center}
.throw-pouch{position:relative;width:46px;height:46px;border-radius:50%;
  border:2px solid var(--line2);background:var(--panel2);
  display:flex;align-items:center;justify-content:center;font-size:24px;
  touch-action:none;user-select:none;transition:.15s}
.throw-pouch.potion{border-color:rgba(92,214,92,.5)}
.throw-pouch.poison{border-color:rgba(180,90,220,.5)}
.throw-pouch:active{transform:scale(.92)}
.throw-pouch.on-cd{opacity:.45}

/* Radial wheel + carried icon live in a page-fixed overlay so they can
   be dragged anywhere on screen regardless of #potion-bar's own
   position near the bottom edge. */
#throw-layer{position:fixed;inset:0;z-index:60;pointer-events:none}
.throw-wedge{position:fixed;transform:translate(-50%,-50%);
  display:flex;flex-direction:column;align-items:center;gap:2px;
  padding:5px 7px;border-radius:8px;background:rgba(6,10,20,.92);
  border:1px solid rgba(140,190,255,.5);white-space:nowrap;transition:.1s}
.throw-wedge .tw-icon{font-size:22px}
.throw-wedge .tw-name{font-size:12px;color:var(--muted)}
.throw-wedge.hot{border-color:var(--cyan);box-shadow:0 0 12px rgba(55,230,255,.5);
  transform:translate(-50%,-50%) scale(1.12)}
.throw-wedge.cd{opacity:.4}
.throw-carry{position:fixed;transform:translate(-50%,-50%);
  font-size:30px;filter:drop-shadow(0 0 6px rgba(255,255,255,.6));
  transition:filter .12s}
.throw-carry.over-target{filter:drop-shadow(0 0 12px rgba(255,230,90,.95))}
/* 3x enlarge the instant the flick starts (see onThrowPointerMove's
   lock-in) so the item reads clearly instead of hiding under the
   finger that's now dragging it around. */
.throw-carry.throw-carry-big{font-size:90px}

/* Effect-text + remaining-stock box shown beside whichever wedge is
   "hot" while still deciding (before dragging past the lock radius) —
   see updateThrowExplain() in game.js. */
.throw-explain{position:fixed;transform:translate(-100%,-50%);
  opacity:0;pointer-events:none;transition:opacity .12s;
  display:flex;flex-direction:column;gap:2px;max-width:150px;
  padding:6px 9px;border-radius:8px;background:rgba(6,10,20,.94);
  border:1px solid rgba(140,190,255,.5)}
.throw-explain.on{opacity:1}
.throw-explain.on-right{transform:translate(0,-50%)}
.throw-explain b{font-family:var(--pixel);font-size:13px;color:#eaf4ff}
.throw-explain span{font-size:12px;color:var(--muted);line-height:1.3}
.throw-explain i{font-size:11px;font-style:normal;color:var(--gold)}

/* impact "glass breaking" burst — small tinted shard fragments flying
   outward, same rotate-then-translate-local-X idiom as dustPuff()'s
   particle ring (each shard gets its own --ga angle/--gd distance). */
.glass-break{position:fixed;z-index:61;transform:translate(-50%,-50%);
  width:0;height:0;pointer-events:none}
.glass-break i{position:absolute;left:0;top:0;width:6px;height:9px;
  background:var(--gb-color,rgba(140,220,255,.9));opacity:0;
  clip-path:polygon(50% 0,100% 30%,80% 100%,20% 100%,0 30%);
  animation:glassShardFly .48s ease-out forwards;
  transform:rotate(var(--ga,0deg)) translateX(0)}
@keyframes glassShardFly{
  0%{opacity:1;transform:rotate(var(--ga,0deg)) translateX(2px) scale(1)}
  100%{opacity:0;transform:rotate(var(--ga,0deg)) translateX(var(--gd,32px)) scale(.35)}}

/* Brief 1s color wash directly over the struck creature's own sprite —
   see bodyFlashEffect() in game.js, appended into that unit's own
   .bu-sprite-wrap so it moves/scales with the sprite for free. */
.throw-body-flash{position:absolute;inset:-6px;border-radius:50%;
  pointer-events:none;z-index:4;animation:throwBodyFlash 1s ease-out forwards}
.throw-body-flash.green{background:radial-gradient(rgba(92,214,92,.75),rgba(92,214,92,0) 70%)}
.throw-body-flash.red{background:radial-gradient(rgba(255,60,60,.75),rgba(255,60,60,0) 70%)}
@keyframes throwBodyFlash{
  0%{opacity:0}
  15%{opacity:1}
  100%{opacity:0}}
.potion-empty{font-size:18px;color:var(--muted)}
.heal-pop{position:absolute;transform:translate(-50%,-50%);z-index:9;
  font-family:var(--pixel);font-size:30px;color:#4ee88a;
  -webkit-text-stroke:4px #0d2416;paint-order:stroke fill;
  animation:healRise 1s ease-out forwards;pointer-events:none}
@keyframes healRise{
  0%{opacity:0;transform:translate(-50%,-40%) scale(.5)}
  20%{opacity:1;transform:translate(-50%,-55%) scale(1.15)}
  35%{transform:translate(-50%,-52%) scale(1)}
  100%{opacity:0;transform:translate(-50%,-90%) scale(.9)}}

/* Bench healer opening-buff pop — a small portrait box over the
   active ally's head naming which off-field pet just cast a team
   buff (see runOpeningBuffs/supportCasterPop in game.js), since that
   caster has no on-stage unit of its own to animate. */
.support-cast-pop{position:absolute;transform:translate(-50%,-100%);z-index:11;
  display:flex;flex-direction:column;align-items:center;gap:2px;
  width:max-content;max-width:150px;
  pointer-events:none;animation:supportPopInOut 2s ease-in-out forwards}
.support-cast-box{width:44px;height:44px;padding:3px;flex:0 0 auto;
  background:rgba(10,7,22,.85);border:2px solid var(--cyan);border-radius:6px;
  box-shadow:0 0 10px rgba(61,240,255,.5);
  display:flex;align-items:flex-end;justify-content:center;overflow:hidden}
.support-cast-box .support-cast-sprite{width:100%;height:100%;object-fit:contain;
  image-rendering:pixelated}
.support-cast-name{font-family:var(--pixel);font-size:12px;color:var(--cyan);
  text-shadow:0 2px 3px rgba(0,0,0,.8);white-space:nowrap}
.support-cast-skill{font-family:var(--pixel);font-size:12px;color:#ffd23f;
  text-shadow:0 2px 3px rgba(0,0,0,.8);white-space:nowrap}
.support-cast-desc{font-size:12px;line-height:1.25;color:#dfe6ee;text-align:center;
  text-shadow:0 1px 2px rgba(0,0,0,.9);
  background:rgba(10,7,22,.7);border-radius:4px;padding:1px 4px}
@keyframes supportPopInOut{
  0%{opacity:0;transform:translate(-50%,-80%) scale(.6)}
  12%{opacity:1;transform:translate(-50%,-100%) scale(1)}
  82%{opacity:1;transform:translate(-50%,-100%) scale(1)}
  100%{opacity:0;transform:translate(-50%,-115%) scale(.85)}}

/* ── BATTLE ── */
#battle-top{display:flex;align-items:center;gap:10px;padding:10px 12px;
  border-bottom:1px solid var(--line);background:var(--panel)}
#battle-title{font-family:var(--pixel);font-size:25px;color:var(--cyan)}
#battle-wave{font-size:20px;color:var(--muted)}
/* ── VR2-STYLE STAGE ──
   Scenic sky + hills + grass platform. The stage is the screen; all
   the HUD chrome that used to crowd it now sits below the fold. */
#battle-stage{
  position:relative;min-height:290px;overflow:hidden;
  --time-scale:1;
}
/* Camera wrapper — pans and zooms the scene. will-change keeps the
   transform on the GPU so panning stays smooth on phones. */
#stage-cam{
  position:absolute;inset:0;
  display:flex;justify-content:space-between;align-items:flex-end;
  /* left/right padding is load-bearing, not just spacing: a fighter's
     sprite scales from its OWN centre (transform-origin:50% 100% on
     .bu-sprite), and #battle-stage clips anything (overflow:hidden)
     that grows past its edge. 16px used to leave a large/oversized
     sprite's near-edge side with barely any (or negative) clearance —
     see the --cr-scale cap in renderBattleSide() for the other half of
     this fix, which caps how big a sprite can render in the first
     place so this padding is guaranteed to be enough. */
  padding:0 34px 54px;
  transform-origin:50% 50%;will-change:transform;
}
#sky{position:absolute;inset:0;
  background:linear-gradient(180deg,#1c2c6e 0%,#2f4a9e 42%,#5f86c9 78%,#8fb4dd 100%)}
#sky::before,#sky::after{content:'';position:absolute;border-radius:50%;
  background:rgba(255,255,255,.85);filter:blur(1px)}
#sky::before{width:120px;height:30px;left:12%;top:18%;
  box-shadow:38px 8px 0 -4px rgba(255,255,255,.75),80px -4px 0 -8px rgba(255,255,255,.6)}
#sky::after{width:90px;height:24px;right:14%;top:30%;
  box-shadow:-30px 6px 0 -4px rgba(255,255,255,.7)}
#hills{position:absolute;left:0;right:0;bottom:34px;height:120px;
  background:
    radial-gradient(ellipse 180px 96px at 18% 100%, #e8eef7 0 46%, transparent 47%),
    radial-gradient(ellipse 150px 80px at 46% 100%, #d3ddec 0 46%, transparent 47%),
    radial-gradient(ellipse 200px 108px at 82% 100%, #e8eef7 0 46%, transparent 47%);
  opacity:.95}
#ground{position:absolute;left:0;right:0;bottom:0;height:52px;
  background:linear-gradient(180deg,#4f9b34 0 34%,#3d7a28 34% 60%,#6b4a24 60% 100%)}
#ground::before{content:'';position:absolute;left:0;right:0;top:0;height:8px;
  background:repeating-linear-gradient(90deg,#5cb03c 0 5px,#4f9b34 5px 10px)}
/* stone platform the fighters stand on */
#ground::after{content:'';position:absolute;left:6%;right:6%;top:-16px;height:18px;
  background:linear-gradient(180deg,#b8bec7,#8d949e);border-radius:3px;
  box-shadow:inset 0 -3px 0 rgba(0,0,0,.18)}
.bside{position:relative;z-index:2;display:flex;align-items:flex-end;justify-content:center;width:120px}

/* ── NAME PLATES (VR2: name box + heart + number, no HP bar) ── */
.name-plate{position:absolute;top:12px;z-index:6;min-width:118px}
.name-plate.left{left:14px}
.name-plate.right{right:14px;text-align:right}
.np-name{display:inline-block;padding:6px 13px;font-family:var(--pixel);
  font-size:25px;line-height:1;letter-spacing:.5px;color:#2a1a0a;
  background:linear-gradient(180deg,#fff6dc,#e8cf9a);
  border:2px solid #8a6df;border-radius:4px;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.7),0 3px 0 rgba(0,0,0,.35),
             0 5px 12px rgba(0,0,0,.35)}
.np-lv{font-size:17px;color:#eaf2ff;margin-top:3px;text-shadow:0 2px 3px rgba(0,0,0,.8)}

/* Active stat-buff readout next to the level line — one colored chip
   per buffed stat (aggregated across every active ailment that
   touches it, since Data Bless/Sanctuary etc. can now stack — see
   statBuffChips() in game.js), with a turn countdown so it's obvious
   the buff is temporary and roughly how long is left. */
.np-buffs{display:flex;flex-wrap:wrap;gap:4px;margin-top:2px;max-width:150px}
.name-plate.right .np-buffs{justify-content:flex-end}
.stat-buff-chip{font-size:13px;font-weight:700;text-shadow:0 1px 2px rgba(0,0,0,.9);
  white-space:nowrap}
.stat-buff-chip i{font-style:normal;opacity:.75;font-size:10px;margin-left:1px}
.np-hp{display:flex;align-items:center;margin-top:5px}
.name-plate.right .np-hp{justify-content:flex-end}
/* An enlarged ally (x1.25 size tier) grows tall enough from the bottom
   of the stage to reach into the name plate and cover the MP gauge
   stacked below HP — see renderBattleSide()'s `enlarged` check. HP+MP
   sit side by side instead, so neither one gets covered. */
.np-vitals-inline{display:flex;align-items:center;gap:8px;margin-top:5px}
.np-vitals-inline .np-hp,.np-vitals-inline .np-mp{margin-top:0}

/* ── HP/MP "LIQUID" GAUGES ──
   Replaces the old bold-number heart glyph and the separate MP
   percentage bar (whose displayed % silently went stale after a
   skill spent MP — see refreshBattleUnits() in game.js, which now
   updates these too). Both HP and MP render the same way: a dim
   "empty" layer always fully visible, and a bright fill layer on top
   clipped from the TOP by (100 - pct)% via inline style — so as pct
   drops the visible fill shrinks from the top down, like liquid
   draining out of a cup, until nothing colored is left at 0. HP is
   heart-shaped (masked to an SVG heart path); MP reuses the exact
   same fill/clip mechanism but as a plain circle, tinted light blue. */
.vital{position:relative;width:38px;height:34px;flex:0 0 auto}
.vital-fill-bg,.vital-fill{position:absolute;inset:0}
.vital-heart{
  --heart-svg:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cpath d='M50 90C22 70 4 51 4 30C4 14 17 3 32 3C41 3 48 8 50 17C52 8 59 3 68 3C83 3 96 14 96 30C96 51 78 70 50 90Z' fill='white'/%3E%3C/svg%3E");
}
.vital-heart .vital-fill-bg,.vital-heart .vital-fill{
  -webkit-mask-image:var(--heart-svg);mask-image:var(--heart-svg);
  -webkit-mask-size:contain;mask-size:contain;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
}
.vital-heart .vital-fill-bg{background:#3a1420}
.vital-heart .vital-fill{background:linear-gradient(180deg,#ff6478,#c81c33);
  filter:drop-shadow(0 0 4px rgba(255,74,94,.6))}
.vital-circle{width:32px;height:32px}
.vital-circle .vital-fill-bg{border-radius:50%;background:#173347}
.vital-circle .vital-fill{border-radius:50%;background:linear-gradient(180deg,#a8ecff,#3daeea);
  filter:drop-shadow(0 0 4px rgba(79,195,247,.6))}
.vital-fill{transition:clip-path .35s ease}
.vital-num{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-family:var(--pixel);font-size:13px;color:#fff;line-height:1;
  text-shadow:0 1px 2px rgba(0,0,0,.9),0 0 3px rgba(0,0,0,.9);z-index:2;pointer-events:none}

/* ── BOSS BATTLE UI ── */
.np-boss-tag{font-family:var(--pixel);font-size:14px;letter-spacing:.5px;
  color:#ff8a5a;text-shadow:0 0 8px rgba(255,60,20,.6);margin-bottom:2px}
.boss-hpwrap{margin-top:4px;display:flex;flex-direction:column;gap:3px}
.boss-hpbar{width:140px;height:10px;border:2px solid rgba(255,255,255,.4);
  background:rgba(0,0,0,.5);border-radius:2px;overflow:hidden}
.boss-hpbar i{display:block;height:100%;transition:width .3s ease;
  background:linear-gradient(180deg,#ff8a5a,#c8321a)}
.boss-hpbar.phase2 i{background:linear-gradient(180deg,#ff3d3d,#7a0d0d);
  box-shadow:0 0 10px rgba(255,40,20,.7)}
.boss-phase-pips{display:flex;gap:4px}
.boss-phase-pips .pip{width:8px;height:8px;border-radius:50%;
  border:1px solid rgba(255,255,255,.5);background:rgba(0,0,0,.4)}
.boss-phase-pips .pip.on{background:#ff5a3d;box-shadow:0 0 6px rgba(255,90,60,.8)}
.rage-marks{position:absolute;top:-6px;left:50%;transform:translateX(-50%);
  display:flex;gap:4px;font-size:23px;z-index:5;animation:rageBounce .5s ease-in-out infinite alternate}
@keyframes rageBounce{from{transform:translateX(-50%) translateY(0)}to{transform:translateX(-50%) translateY(-6px)}}
.bu-sprite-wrap.is-boss{filter:drop-shadow(0 0 14px rgba(255,40,20,.5))}

/* ── TURN BANNERS ── */
#banner-layer{position:absolute;left:0;right:0;top:74px;z-index:7;
  display:flex;flex-direction:column;align-items:center;gap:6px;pointer-events:none}
.turn-banner{
  padding:7px 22px;color:#fff;font-family:var(--pixel);
  background:linear-gradient(180deg,rgba(96,84,120,.96),rgba(48,40,66,.96));
  border:2px solid rgba(255,255,255,.45);border-radius:3px;
  font-size:25px;line-height:1;letter-spacing:1.5px;white-space:nowrap;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 4px 14px rgba(0,0,0,.5);
  animation:bannerIn .22s cubic-bezier(.2,1.5,.4,1) both;
}
.turn-banner.out{animation:bannerOut .22s ease-in both}
.turn-banner.crit{background:rgba(180,40,40,.95);font-weight:bold;letter-spacing:2.5px}
.turn-banner.ko{background:rgba(30,34,42,.95)}
.turn-banner.sig{background:linear-gradient(90deg,rgba(160,90,20,.95),rgba(200,140,30,.95));
  border-color:#ffd23f;color:#fff8e0;letter-spacing:2px;
  box-shadow:0 0 18px rgba(255,210,63,.5)}
@keyframes bannerIn{from{opacity:0;transform:translateY(-12px) scale(.9)}to{opacity:1;transform:none}}
@keyframes bannerOut{to{opacity:0;transform:translateY(-8px) scale(.96)}}

/* ── IMPACT BURST ── */
.impact-burst{position:absolute;transform:translate(-50%,-50%);
  width:0;height:0;z-index:8;pointer-events:none}
.impact-burst .streak{position:absolute;left:0;top:0;display:block;
  width:var(--len,90px);height:2px;transform-origin:0 50%;
  background:linear-gradient(90deg,rgba(255,255,255,.95),rgba(255,255,255,0));
  animation:streakOut .34s ease-out forwards}
@keyframes streakOut{
  0%{opacity:0;width:0}
  25%{opacity:1}
  100%{opacity:0;width:var(--len,90px)}}
.impact-burst .slash{position:absolute;left:-46px;top:-46px;width:92px;height:92px;
  border:6px solid transparent;border-top-color:#ffd23f;border-right-color:#ffb02e;
  border-radius:50%;transform:rotate(-35deg);
  animation:slashArc .3s ease-out forwards}
@keyframes slashArc{
  0%{opacity:0;transform:rotate(-95deg) scale(.4)}
  40%{opacity:1}
  100%{opacity:0;transform:rotate(35deg) scale(1.3)}}
.impact-burst .spark{position:absolute;left:0;top:0;width:5px;height:5px;
  border-radius:50%;background:#ffe066;
  animation:sparkFly .42s ease-out forwards;
  transform:rotate(var(--a)) translateX(0)}
@keyframes sparkFly{
  0%{opacity:1;transform:rotate(var(--a)) translateX(0) scale(1)}
  100%{opacity:0;transform:rotate(var(--a)) translateX(var(--d)) scale(.3)}}
.impact-burst.crit .slash{border-top-color:#fff;border-right-color:#ffd23f;
  width:120px;height:120px;left:-60px;top:-60px}

/* ── CRIT ATTACK: attacker SWELLS TO 2x, TILTS, then CHARGES ──
   Replaces the old white-out self VFX entirely. The wind-up swells the
   attacker to 2x and tilts it back; the lunge hurls it forward across
   a big distance still oversized; the return settles it back to
   normal. --dir is +1 for the ally side, -1 for the foe side, so the
   charge always heads toward the opponent. */
.bunit.crit-attack{ z-index:9 !important; }
.bunit.crit-attack.wind-up{ animation-name: windUpCrit; }
@keyframes windUpCrit{
  0%   { transform: translateX(0) scale(1) rotate(0deg); }
  100% { transform: translateX(calc(var(--dir,1) * -30px)) scale(2) rotate(calc(var(--dir,1) * -14deg)); }
}
.bunit.crit-attack.lunge-out{ animation-name: lungeOutCrit; }
@keyframes lungeOutCrit{
  0%   { transform: translateX(calc(var(--dir,1) * -30px)) scale(2) rotate(calc(var(--dir,1) * -14deg)); }
  100% { transform: translateX(calc(var(--dir,1) * 90px)) scale(1.9) rotate(calc(var(--dir,1) * 8deg)); }
}
.bunit.crit-attack.lunge-back{ animation-name: lungeBackCrit; }
@keyframes lungeBackCrit{
  0%   { transform: translateX(calc(var(--dir,1) * 90px)) scale(1.9) rotate(calc(var(--dir,1) * 8deg)); }
  100% { transform: translateX(0) scale(1) rotate(0deg); }
}

/* ── GIANT DAMAGE NUMBERS ── */
.dmg-big{position:absolute;z-index:12;transform:translate(-50%,-50%);
  display:flex;flex-direction:column;align-items:center;pointer-events:none;
  animation:dmgPunch 1s cubic-bezier(.25,1.3,.4,1) forwards}
.dmg-num{font-family:var(--pixel);font-size:52px;line-height:1;
  color:#ffffff;
  -webkit-text-stroke:6px #14100c;paint-order:stroke fill;
  text-shadow:0 6px 0 rgba(0,0,0,.35);
  transform:rotate(var(--tilt,-5deg))}
.dmg-big.crit .dmg-num{font-size:104px;color:#ff1f1f;
  -webkit-text-stroke:9px #2b0505;
  text-shadow:0 0 26px rgba(255,40,40,.8),0 10px 0 rgba(0,0,0,.45)}
/* Punch in, then drift upward while fading out — the number should
   travel, not sit in place waiting to disappear. */
/* Appears at full opacity and immediately begins rising AND fading, so
   it's already on its way out the moment you register it. */
@keyframes dmgPunch{
  0%   {opacity:1; transform:translate(-50%,-45%) scale(.5)}
  12%  {opacity:1; transform:translate(-50%,-62%) scale(1.22)}
  24%  {opacity:.9; transform:translate(-50%,-72%) scale(1)}
  55%  {opacity:.5; transform:translate(-50%,-108%) scale(.97)}
  100% {opacity:0; transform:translate(-50%,-160%) scale(.88)}}

/* ── SCREEN SHAKE + HIT-STOP ── */
#battle-stage.shake{animation:stageShake .26s ease}
#battle-stage.shake-hard{animation:stageShakeHard .34s ease}
@keyframes stageShake{
  0%,100%{transform:translate(0,0)}
  25%{transform:translate(-4px,2px)}
  60%{transform:translate(3px,-2px)}}
@keyframes stageShakeHard{
  0%,100%{transform:translate(0,0)}
  15%{transform:translate(-8px,4px)}
  35%{transform:translate(7px,-4px)}
  55%{transform:translate(-5px,2px)}
  80%{transform:translate(4px,-2px)}}
/* Freeze all animation at the moment of contact — cheap, huge impact */
#battle-stage.hitstop *{animation-play-state:paused !important}
/* Slow-motion: multiply every stage animation duration. */
#battle-stage[style*="--time-scale"] .bunit,
#battle-stage[style*="--time-scale"] .vfx *{
  animation-duration:calc(var(--anim-ms,400ms) / var(--time-scale,1))}

/* ── BENCH (your remaining VIRUZ) ── */
#battle-bench{display:flex;gap:8px;justify-content:center;padding:10px 12px;
  background:var(--panel);border-top:1px solid var(--line)}
.bench-chip{position:relative;width:52px;height:52px;border:2px solid var(--line);
  background:var(--panel2);display:flex;align-items:center;justify-content:center;
  box-shadow:inset 0 1px 0 var(--rim),0 3px 8px rgba(0,0,0,.4)}
.bench-chip.active{border-color:var(--cyan);
  box-shadow:inset 0 1px 0 var(--rim),0 0 18px rgba(61,240,255,.5),0 3px 8px rgba(0,0,0,.4)}
.bench-chip.down{opacity:.3;filter:grayscale(1)}
.bench-sprite{width:38px;height:38px;display:block}
.bench-hp{position:absolute;bottom:-2px;right:2px;font-size:16px;color:#fff;
  text-shadow:0 1px 2px #000}

/* ── SWAP MENU ── */
#swap-menu{position:absolute;inset:0;z-index:20;display:none;
  flex-direction:column;align-items:center;justify-content:center;gap:12px;
  background:rgba(6,10,20,.86);backdrop-filter:blur(3px)}
#swap-menu.on{display:flex}
.swap-title{font-family:var(--pixel);font-size:28px;color:var(--cyan);letter-spacing:1px}
.swap-row{display:flex;gap:10px;flex-wrap:wrap;justify-content:center}
.swap-card{width:96px;padding:8px;background:var(--panel2);
  border:1px solid var(--line2);cursor:pointer;transition:.12s;
  clip-path:polygon(0 0,calc(100% - 8px) 0,100% 8px,100% 100%,8px 100%,0 calc(100% - 8px))}
.swap-card:hover{border-color:var(--cyan);transform:translateY(-3px);
  box-shadow:0 0 16px rgba(55,230,255,.3)}
.swap-sprite{width:62px;height:62px;display:block;margin:0 auto}
.swap-name{font-size:18px;text-align:center;margin-top:4px;color:var(--txt)}
.swap-hp{font-size:18px;text-align:center;color:#ff8a9a}
.bunit{width:92px;text-align:center;position:relative;z-index:1}
/* ── MOBILE SMOOTHNESS: force GPU compositing ──
   will-change promotes these frequently-animated elements to their own
   compositor layer so the phone's GPU handles their transform/opacity
   changes instead of the main thread repainting each frame — the main
   lever for the phone-vs-desktop stutter gap. backface-visibility
   prevents scale-animation flicker on some iOS Safari versions. NOTE:
   we deliberately do NOT set `transform` here — .bunit/.bu-sprite
   already drive transform via scale/flip/keyframes, and a static value
   would clobber them. will-change alone triggers layer promotion. */
.bunit, .bu-sprite, .vfx-clip, .impact-burst, .dmg-big,
.cg-pet-sprite, .cg-ball, .cg-laser, .dirt-spot, .scrub-spark,
.explode-shard{
  will-change: transform, opacity;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}
/* The hit-flash (hitWhite/hitWhiteRaster below) animates `filter`,
   which some mobile GPUs composite less reliably than transform/opacity
   — the reported symptom was the white-out simply not appearing some
   hits on phone while always showing on desktop. Hinting filter here
   too, on the exact elements those keyframes target, gives it the same
   own-layer promotion as the transform properties above. */
.bu-sprite .cr-body, img.bu-sprite{ will-change: filter; }
.bunit.dead{opacity:.25;filter:grayscale(1)}

/* Attack movement: the whole unit (bar, name, sprite) slides toward
   the target using the --tx/--ty custom properties set in JS, holds
   briefly at the target, then eases back to its original spot. Using
   a real translate (not just the sprite) keeps the HP bar/name glued
   to the sprite while it moves, which reads much clearer in combat. */
/* Wind-up: pull back slightly before striking. This anticipation is
   what makes an attack read as an attack rather than a teleport. */
.bunit.wind-up{
  animation-name: windUp;
  animation-duration: var(--wind-ms, 220ms);
  animation-timing-function: ease-out;
  animation-fill-mode: forwards;
  z-index:5;
}
@keyframes windUp{
  0%   { transform: translateX(0) scale(1); }
  100% { transform: translateX(calc(var(--dir,1) * -14px)) scale(1.04,.97); }
}
.bunit.lunge-out{
  animation-name: lungeOut;
  animation-duration: var(--lunge-ms, 320ms);
  animation-timing-function: cubic-bezier(.3,.05,.4,1);
  animation-fill-mode: forwards;
  z-index:5;
}
.bunit.lunge-back{
  animation-name: lungeBack;
  animation-duration: var(--return-ms, 260ms);
  animation-timing-function: ease-in;
  animation-fill-mode: forwards;
  z-index:5;
}
@keyframes lungeOut{
  0%   { transform: translateX(calc(var(--dir,1) * -18px)) scale(1.06,.95); }
  100% { transform: translateX(calc(var(--dir,1) * 72px)) scale(1.14,.9); }
}
@keyframes lungeBack{
  0%   { transform: translateX(calc(var(--dir,1) * 46px)) scale(1.1,.94); }
  100% { transform: translateX(0) scale(1); }
}

/* Miss: the TARGET (not the attacker) briefly blurs and flinches
   further away — same --dir sign as the attacker's own lunge, since
   for the target that direction IS "away". Fixed, short duration
   (well under 1s) regardless of combo speed-up. */
.bunit.evade-recoil{
  animation-name: evadeRecoil;
  animation-duration: 460ms;
  animation-timing-function: ease-out;
  z-index:6;
}
@keyframes evadeRecoil{
  0%   { transform: translateX(0); filter: blur(0); }
  25%  { transform: translateX(calc(var(--dir,1) * 22px)); filter: blur(3px); }
  60%  { transform: translateX(calc(var(--dir,1) * 14px)); filter: blur(1px); }
  100% { transform: translateX(0); filter: blur(0); }
}
.miss-pop{position:absolute;transform:translate(-50%,-50%);z-index:10;
  font-family:var(--pixel);font-size:30px;color:#dfe6ee;
  -webkit-text-stroke:5px #14100c;paint-order:stroke fill;
  animation:missPop .8s ease-out forwards;pointer-events:none}
@keyframes missPop{
  0%  {opacity:0;transform:translate(-50%,-40%) scale(.6) rotate(-6deg)}
  20% {opacity:1;transform:translate(-50%,-58%) scale(1.1) rotate(-6deg)}
  100%{opacity:0;transform:translate(-50%,-85%) scale(1) rotate(-6deg)}}

/* Impact: target flashes white and shakes. The flash is a CSS filter
   pulse rather than swapping images, so it works on every sprite with
   zero extra assets. */
.bunit.hit .bu-sprite-wrap{ animation-name: hitShake; animation-duration: var(--hit-ms, 480ms); animation-timing-function: ease; }
/* White-out on hit. Applied to the SVG's inner group so the drop
   shadow and the fill both flash, which reads much stronger than
   a filter on the wrapper alone. */
.bunit.hit .bu-sprite .cr-body{ animation-name: hitWhite; animation-duration: var(--hit-ms, 480ms); animation-timing-function: ease; }
/* Raster creatures can't target an inner group, so flash the img itself */
.bunit.hit img.bu-sprite{ animation-name: hitWhiteRaster; animation-duration: var(--hit-ms, 480ms); animation-timing-function: ease; }
@keyframes hitWhiteRaster{
  0%   { filter:brightness(1); }
  12%  { filter:brightness(0) invert(1) drop-shadow(0 0 14px #fff); }
  45%  { filter:brightness(0) invert(1) drop-shadow(0 0 8px #fff); }
  100% { filter:brightness(1); }
}
@keyframes hitWhite{
  0%   { filter:none; }
  12%  { filter:brightness(0) invert(1) drop-shadow(0 0 14px #fff); }
  45%  { filter:brightness(0) invert(1) drop-shadow(0 0 8px #fff); }
  100% { filter:none; }
}

/* Idle float — every creature bobs gently while standing still.
   Randomised per unit via --float-delay so they don't bob in sync. */
.float{ animation: floatBob 2.4s ease-in-out infinite; animation-delay: var(--float-delay, 0s); }
/* The bob keyframes must carry the creature scale too — setting bare
   `transform` here was silently wiping --cr-scale, so every enemy
   rendered at 1x regardless of its configured size. */
@keyframes floatBob{
  0%,100% { transform: scale(calc(var(--cr-scale,1) * var(--raster-mult,1))) translateY(0); }
  50%     { transform: scale(calc(var(--cr-scale,1) * var(--raster-mult,1))) translateY(-7px); }
}
/* Attack pose: lean forward and squash slightly. Replaces the old
   attack.gif swap now that creatures are SVG. */
.bu-sprite.attacking{ animation: attackPose var(--lunge-ms,520ms) ease-out forwards; }
@keyframes attackPose{
  0%   { transform: rotate(0deg) scale(1,1); }
  40%  { transform: rotate(-13deg) scale(1.1,.92); }
  100% { transform: rotate(-7deg) scale(1.06,.96); }
}
.bu-sprite.flip.attacking{ animation-name: attackPoseFlip; }
@keyframes attackPoseFlip{
  0%   { transform: scaleX(-1) rotate(0deg); }
  40%  { transform: scaleX(-1) rotate(-13deg) scale(1.1,.92); }
  100% { transform: scaleX(-1) rotate(-7deg) scale(1.06,.96); }
}
@keyframes hitShake{
  0%,100%{ transform:translateX(0) scale(1); }
  15%{ transform:translateX(-13px) scale(1.06,.95); }
  35%{ transform:translateX(11px) scale(.96,1.04); }
  55%{ transform:translateX(-8px); }
  75%{ transform:translateX(5px); }
}
@keyframes hitFlash{
  0%   { filter:brightness(1) drop-shadow(0 0 0 rgba(255,255,255,0)); }
  15%  { filter:brightness(2.6) saturate(0) drop-shadow(0 0 14px #fff); }
  40%  { filter:brightness(1.9) saturate(.2) drop-shadow(0 0 10px #fff); }
  100% { filter:brightness(1) drop-shadow(0 0 0 rgba(255,255,255,0)); }
}
.bu-sprite-wrap{ position:relative; display:inline-block; }
.bu-sprite{width:76px;height:76px;display:block;margin:0 auto;overflow:visible;
  --raster-mult:1;
  transform:scale(calc(var(--cr-scale,1) * var(--raster-mult,1)));transform-origin:50% 100%}
/* the wrapper keeps the scaled sprite anchored to the platform */
.bu-sprite-wrap{display:flex;align-items:flex-end;justify-content:center;
  min-height:76px}
/* Curse tint — a persistent (not one-shot) colored wash over the whole
   sprite for as long as a Freeze/Charm/Stone curse or the Corrupted
   debuff is active (see updateCurseTint()/curseTintColor() in game.js,
   AILMENTS[id].color in data.js is the source of truth per-curse — pink
   for Charm, icy blue for Freeze, etc). This is a flat colored box —
   what actually keeps it confined to the creature's own silhouette
   (instead of reading as a solid rectangle) is mask-image, set in JS to
   the sprite's own src (raster) or its live inline markup as a data URI
   (procedural SVG) — see spriteMaskUrl(). mix-blend-mode:color then
   recolors the creature toward that hue while preserving its own
   shading, rather than just flattening it to a block of color. The
   pulse breathes between ~30-70% so it reads as "about 50% intensity"
   on average, matching the ask. Removed the instant the ailment itself
   clears — see the same function. Sized/positioned to match .bu-sprite
   exactly: same box, same bottom-center anchor, same scale transform. */
.curse-tint{position:absolute;left:50%;bottom:0;width:76px;height:76px;
  margin-left:-38px;transform-origin:50% 100%;
  transform:scale(calc(var(--cr-scale,1) * var(--raster-mult,1)));
  background:var(--curse-color,#fff);
  mask-repeat:no-repeat;mask-position:center bottom;mask-size:contain;
  -webkit-mask-repeat:no-repeat;-webkit-mask-position:center bottom;-webkit-mask-size:contain;
  mix-blend-mode:color;pointer-events:none;z-index:2;
  animation:curseTintPulse 1.4s ease-in-out infinite}
@keyframes curseTintPulse{0%,100%{opacity:.3}50%{opacity:.7}}
/* GIF creatures are raster; keep pixel art crisp and match SVG sizing */
img.is-gif{image-rendering:pixelated;object-fit:contain}
/* v2 PNG species art (attribute/mutation-colored) — same story: real
   photos/pixel-art have their own aspect ratio, not the SVGs' square
   viewBox, so without object-fit:contain a wide creature gets
   squashed to fit a square box (this was the "too short horizontally"
   bug — the fix already existed for .is-gif, it just never got
   extended to this newer art set). */
img.is-art2{image-rendering:pixelated;object-fit:contain}
/* Outside battle, v2 art sits in boxes originally sized for the
   square SVG viewBox. Since object-fit:contain now preserves the
   real (usually wider-than-tall) proportions instead of stretching,
   those boxes read as mostly-empty vertical space around a small
   image — shortening just the height (keeping width, since layout
   grids depend on it) lets the art fill its box properly. */
.bench-sprite.is-art2{height:24px}
.br-sprite.is-art2{height:33px}
.care-chip-sprite.is-art2{height:28px}
.cr-sprite.is-art2{height:28px}
.pc-sprite.is-art2{height:54px}
.ps-sprite.is-art2{height:48px}
.starter-sprite.is-art2{height:60px}
.swap-sprite.is-art2{height:39px}
.zb-sprite.is-art2{height:35px}
.care-sprite.is-art2{height:80px}
/* PNG/JPEG/GIF (raster) characters render 2x larger than SVG ones. A
   --raster-mult custom property (2 for raster, 1 for SVG) is folded
   into EVERY sprite transform below — base, float, flip — so the
   doubling survives animation instead of being reset by a keyframe
   that only knew about --cr-scale. */
img.bu-sprite.is-gif{--raster-mult:2}
/* Same doubling for v2 species IN BATTLE ONLY — applies identically
   to both the ally and the enemy slot since it's keyed off the shared
   .bu-sprite class, not which side is rendering. */
img.bu-sprite.is-art2{--raster-mult:2}
/* object-fit:contain centers the art vertically inside the 76px box by
   default. Raster art is rarely square (most v2 PNGs are much wider
   than tall), so "contain" leaves empty space above AND below the
   creature — pushing its feet up off the platform instead of onto it.
   Anchoring to the bottom edge keeps feet flush with the ground line,
   matching how the procedural SVG creatures already sit (they use
   preserveAspectRatio="xMidYMax meet", bottom-anchored by default). */
.bu-sprite.is-gif, .bu-sprite.is-art2{ object-position:50% 100%; }
/* flip and scale must combine — a bare scaleX(-1) would wipe the scale */
.bu-sprite.flip{transform:scale(calc(var(--cr-scale,1) * var(--raster-mult,1) * -1),calc(var(--cr-scale,1) * var(--raster-mult,1)))}
.bu-sprite.flip.float{animation-name:floatBobFlip}
@keyframes floatBobFlip{
  0%,100%{transform:scale(calc(var(--cr-scale,1) * var(--raster-mult,1) * -1),calc(var(--cr-scale,1) * var(--raster-mult,1))) translateY(0)}
  50%    {transform:scale(calc(var(--cr-scale,1) * var(--raster-mult,1) * -1),calc(var(--cr-scale,1) * var(--raster-mult,1))) translateY(-7px)}}
#fx-layer{position:absolute;inset:0;pointer-events:none;z-index:20;overflow:hidden}
/* This rule was missing entirely — playVfxAsset() sets img.style.left/
   top in pixels via JS, but without position:absolute those properties
   do nothing and the <img> falls into normal document flow, rendering
   full-width at the top of the page instead of anchored near the unit.
   That's why effects appeared to float in a black box unrelated to the
   pet's position. */
.vfx-clip{position:absolute;pointer-events:none;
  image-rendering:auto;transform-origin:center center;z-index:21}
.vfx-clip.vfx-instant-fade{animation:vfxFadeOut .5s ease-out forwards}
@keyframes vfxFadeOut{from{opacity:1}to{opacity:0}}
/* A "terminal" always reads dark, in either app theme — like Mac
   Terminal.app keeping a black background in Light Appearance. Text
   colors here are fixed (not the var(--cyan)/var(--muted)/etc tokens
   that flip with the theme, which would go dark-on-near-black and
   become illegible in light mode) since this box's own background
   never flips along with them. */
#battle-log{margin:10px 12px;background:rgba(4,6,14,.85);border:1px solid var(--line);
  padding:8px;height:110px;overflow-y:auto;font-size:15px;line-height:1.55}
#battle-log::-webkit-scrollbar{width:4px}
#battle-log::-webkit-scrollbar-thumb{background:var(--line2)}
.blog-line{color:#dcd6f0}
.blog-line.ally{color:#3df0ff} .blog-line.foe{color:#ff8a8a}
.blog-line.buff{color:#4dff9e} .blog-line.win{color:#ffcf3f}
.blog-line.lose{color:#ff4d6a} .blog-line.sys{color:#9c8ac4}
#battle-ctrl{display:flex;align-items:center;gap:8px;padding:0 12px 14px}
/* Forced encounter — flee is blocked outright (see fleeBattle()) during
   a mimic ambush; dimmed rather than truly disabled so a tap still
   surfaces the explanatory toast instead of doing nothing silently. */
#flee-btn.flee-locked{opacity:.4;border-color:var(--line)}
.speed-btn{font-family:var(--mono);font-size:20px;padding:5px 10px;
  border:1px solid var(--line);background:var(--panel)}
.speed-btn.on{border-color:var(--green);color:var(--green)}
#battle-done{margin-left:auto}

/* ── RAID ── */
.raid-card{padding:11px;background:var(--panel);border:1px solid var(--line);
  border-left:3px solid var(--tier);margin-bottom:9px}
.rc-head{display:flex;justify-content:space-between;margin-bottom:4px}
.rc-name{font-family:var(--pixel);font-size:23px}
.rc-tier{font-size:18px;color:var(--tier)}
.rc-meta{display:flex;gap:12px;font-size:20px;color:var(--muted);margin-bottom:3px}
.rc-loot{font-size:20px;color:var(--gold)}
.raid-result{font-size:15px}
.rr-vs{display:flex;justify-content:space-between;align-items:center;
  padding:8px;border:1px solid var(--line);margin-bottom:8px}
.rr-x{color:var(--red);font-family:var(--pixel);font-size:23px}
.rr-log{max-height:200px;overflow-y:auto}
.rr-line{padding:3px 0;color:var(--muted)}
.rr-line.win{color:var(--gold)} .rr-line.lose{color:var(--red)}
.rr-line.info{color:var(--cyan)}

/* ── MODAL / TOAST ── */
#modal-back{position:fixed;inset:0;z-index:200;display:none;
  background:rgba(2,4,10,.8);backdrop-filter:blur(4px);
  align-items:center;justify-content:center;padding:16px}
#modal-back.on{display:flex}
/* ── NPC SAFE-SPOT SCENE ──
   Positioned as an overlay directly on top of the portrait, matching
   the reference screenshot: speech bubble anchored above-right of the
   merchant's head, menu buttons stacked on the right side over the
   market-stall area of the art — not separate full-width elements
   below the image. All percentages are relative to .npc-scene, which
   wraps the portrait 1:1, so this holds at any screen width. */
.npc-scene{position:relative;margin-bottom:14px;line-height:0}
.npc-portrait{width:100%;display:block;border-radius:6px}

/* Bubble tail points down at the merchant's actual head position
   (measured from the reference portrait: head centred ~29%/31%), so
   the bubble body sits just above-right of it. Menu overlays the
   market-stall area on the right side (~55-90% x, 68-88% y) where
   your circles pointed, not a generic corner. */
.npc-bubble{position:absolute;left:14%;top:1%;width:56%;
  padding:10px 14px;background:#fff;color:#1a1420;border-radius:16px;
  font-size:16px;line-height:1.4;text-align:center;
  box-shadow:0 6px 18px rgba(0,0,0,.5);z-index:3}
.npc-bubble::after{content:'';position:absolute;left:22%;bottom:-9px;
  border:9px solid transparent;border-top-color:#fff;border-bottom:0}

.npc-menu{position:absolute;right:3%;top:60%;width:42%;
  display:flex;flex-direction:column;gap:8px;z-index:3}
.npc-opt{width:100%;text-align:center;font-family:var(--pixel);font-size:16px;
  padding:10px 6px;background:rgba(255,250,230,.96);color:#2a1c0a;
  border:2px solid #c9a54e;border-radius:8px;
  box-shadow:0 3px 0 rgba(120,80,20,.4),0 5px 12px rgba(0,0,0,.4)}
.npc-opt:active{transform:translateY(2px);box-shadow:0 1px 0 rgba(120,80,20,.4)}
.npc-opt:disabled{opacity:.5}

/* ── PET CARD INFO BUTTON ── */
.pet-card{position:relative}
.pc-info{position:absolute;top:6px;right:6px;width:22px;height:22px;
  border-radius:50%;font-size:14px;line-height:1;padding:0;
  background:rgba(20,14,38,.85);border:1px solid var(--line2);color:var(--cyan);
  display:flex;align-items:center;justify-content:center;z-index:3}
.pc-info:hover{border-color:var(--cyan);box-shadow:0 0 10px rgba(61,240,255,.4)}
.pc-equip{position:absolute;top:6px;left:6px;width:22px;height:22px;
  border-radius:50%;font-size:14px;line-height:1;padding:0;
  background:rgba(20,14,38,.85);border:1px solid var(--line2);color:var(--magenta);
  display:flex;align-items:center;justify-content:center;z-index:3}
.pc-equip:hover{border-color:var(--magenta);box-shadow:0 0 10px rgba(255,79,216,.4)}

/* ── EQUIPMENT ──
   4 sockets positioned directly on top of the generated circuit-board
   art (assets/ui/equip_circuit_bg.jpg) at the exact pixel-measured spot
   of each empty socket baked into that image — see EQ_SOCKET_LAYOUT in
   game.js for the measurements. Tapping a socket (filled or empty)
   opens the same picker modal (openEquipPicker), which still uses the
   list-row classes below for its item list. */
.eq-board{position:relative;width:100%;max-width:360px;margin:0 auto 12px;
  aspect-ratio:896/1200;border-radius:10px;overflow:hidden}
.eq-board-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
.eq-socket{position:absolute;transform:translate(-50%,-50%);aspect-ratio:1;
  display:flex;align-items:center;justify-content:center;cursor:pointer;transition:transform .12s}
.eq-socket:active{transform:translate(-50%,-50%) scale(.92)}
.eq-socket-round{border-radius:50%}
.eq-socket.filled{filter:drop-shadow(0 0 6px rgba(120,220,255,.45))}
.eq-socket-label{position:absolute;top:-20px;left:50%;transform:translateX(-50%);
  font-size:12px;color:#bfe9ff;text-shadow:0 1px 3px #000,0 0 6px #000;white-space:nowrap;pointer-events:none}
.eq-socket-inner{width:78%;height:78%;display:flex;align-items:center;justify-content:center}
.eq-socket-inner .habit-icon-img{width:100% !important;height:100% !important;object-fit:contain}
.eq-socket-plus{font-size:26px;color:rgba(140,220,255,.5);font-family:var(--pixel)}
.eq-slot-row{display:flex;align-items:center;gap:10px;padding:9px;
  background:var(--panel);border:1px solid var(--grade,var(--line));
  clip-path:polygon(0 0,calc(100% - 9px) 0,100% 9px,100% 100%,9px 100%,0 calc(100% - 9px))}
.eq-slot-icon{flex:0 0 auto;width:34px;height:34px;font-size:21px;
  display:flex;align-items:center;justify-content:center;
  background:var(--panel2);border:1px solid var(--line)}
.eq-slot-icon.empty{opacity:.4}
.eq-icon-img{width:100%;height:100%;object-fit:contain;image-rendering:pixelated;
  filter:drop-shadow(0 0 3px var(--eq-glow,transparent)) drop-shadow(0 0 6px var(--eq-glow,transparent))}
.eq-icon-img.grade-radiant{animation:gradeRadiant 1.8s ease-in-out infinite}
@keyframes gradeRadiant{
  0%,100%{filter:drop-shadow(0 0 4px var(--eq-glow,transparent)) drop-shadow(0 0 8px var(--eq-glow,transparent))}
  50%{filter:drop-shadow(0 0 9px var(--eq-glow,transparent)) drop-shadow(0 0 18px var(--eq-glow,transparent))}}

/* Potion/poison sprite icons (see potionIconHtml() in game.js) — sized
   in em so the SAME rule scales correctly in every context an emoji
   icon used to (shop cards, wheel wedges, the loadout tab, the carried
   throw icon at both its normal and 3x-enlarged size), just by
   inheriting whatever font-size that context already sets. */
.pot-icon-img{width:1em;height:1em;object-fit:contain;
  image-rendering:pixelated;vertical-align:middle;display:inline-block}
.sc-icon .pot-icon-img{width:36px;height:36px;display:block;margin:0 auto}
.eq-slot-info{flex:1;min-width:0}
.eq-slot-name{font-family:var(--pixel);font-size:18px}
.eq-slot-name.muted{color:var(--muted)}
.eq-slot-sub{font-size:15px;color:var(--muted);margin-top:1px}
.eq-slot-stats{font-size:15px;color:var(--txt);margin-top:2px}
.eq-picker-list{display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow:auto}
.eq-card{border-color:var(--grade,var(--line)) !important}
.eq-card-btns{display:flex;gap:6px;justify-content:center;margin-top:4px}

/* ── PET STATUS WINDOW ── */
.ps-head{display:flex;gap:12px;align-items:center;margin-bottom:12px}
.ps-sprite{width:76px;height:76px;display:block;flex:0 0 auto}
.ps-headinfo{flex:1;min-width:0}
.ps-rar{font-size:17px;margin-bottom:2px}
.ps-habits{font-size:15px;color:var(--muted);margin-bottom:4px;display:flex;align-items:center;gap:4px;flex-wrap:wrap}
.ps-lv{font-size:16px;color:var(--muted);margin-bottom:4px}
.ps-loy{font-size:16px;display:flex;align-items:center;gap:6px}
.ps-stats{display:flex;flex-direction:column;gap:1px;
  padding:10px;background:var(--panel2);border:1px solid var(--line);margin-bottom:12px}
.ps-stat{font-size:23px;color:var(--txt);display:flex;align-items:center;gap:8px;
  line-height:1.25;padding:4px 2px}
.ps-stat i{font-style:normal;color:var(--muted);width:28px;flex:0 0 auto;text-align:center}
.ps-stat b{color:var(--cyan);font-weight:400}
.ps-delta{font-style:normal;font-size:.68em;display:flex;gap:2px}
.ps-delta .pd-x{color:#ffd23f}
.ps-delta .pd-y{color:#ff6a8a}
.ps-skills-title{font-family:var(--pixel);font-size:20px;color:var(--cyan);margin-bottom:8px}
.ps-passives{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}
.ps-passive-row{display:flex;align-items:flex-start;gap:10px;
  padding:8px 10px;background:var(--panel2);border:1px solid var(--line);font-size:15px}
.ps-passive-row i{font-style:normal;flex:0 0 auto;font-size:21px;line-height:1.3}
.ps-passive-row b{display:block;color:var(--gold);font-weight:400}
.ps-passive-row span{display:block;color:var(--muted);font-size:14px;line-height:1.35}
.ps-skills{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}
.ps-skill-row{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:8px 10px;background:var(--panel2);border:1px solid var(--line)}
.ps-skill-name{background:none;border:none;color:var(--gold);font-size:17px;
  text-align:left;padding:0;text-decoration:underline;text-underline-offset:2px}
.ps-toggle{display:flex;align-items:center;gap:6px;font-size:15px;color:var(--muted);cursor:pointer}
.ps-toggle input{accent-color:var(--cyan)}

/* ── SKILL EXPLAINER WINDOW ── */
.se-desc{font-size:15px;color:var(--txt);margin-bottom:10px;line-height:1.5}
.se-meta{display:flex;gap:14px;font-size:14px;color:var(--muted);
  padding:8px;border:1px solid var(--line);margin-bottom:10px}
.se-meta b{color:var(--cyan);font-weight:400}
.se-effects{margin:0 0 10px;padding-left:18px;font-size:14px;color:var(--txt);line-height:1.7}
.se-note{font-size:13px;color:var(--gold);padding:8px;
  border:1px solid rgba(255,207,63,.35);background:rgba(255,207,63,.08);margin-bottom:10px}

#modal{width:100%;max-width:560px;max-height:82vh;overflow:auto;
  background:var(--panel2);border:1px solid var(--line2);
  clip-path:polygon(0 0,calc(100% - 14px) 0,100% 14px,100% 100%,14px 100%,0 calc(100% - 14px))}
#modal-head{display:flex;align-items:center;justify-content:space-between;
  padding:12px;border-bottom:1px solid var(--line)}
#modal-title{font-family:var(--pixel);font-size:25px;color:var(--cyan)}
#modal-close{font-size:23px;line-height:1;color:var(--muted);padding:0 6px}
#modal-body{padding:12px}
.modal-grid{display:flex;gap:10px;flex-wrap:wrap;justify-content:center}
.reveal{text-align:center;padding:10px;border:1px solid var(--rar)}
.reveal-sprite{width:128px;height:128px;display:block;margin:0 auto;overflow:visible;
  filter:drop-shadow(0 0 22px var(--attr))}
.reveal-name{font-family:var(--pixel);font-size:28px;margin:8px 0 4px}
.reveal-rar{font-size:15px;color:var(--rar);margin-bottom:6px}
.reveal-attr{font-size:15px;color:var(--attr)}
.reveal-trait{font-size:20px;color:var(--green);margin-top:5px}
#toast{position:fixed;top:14px;left:50%;transform:translate(-50%,-160%);
  z-index:300;padding:11px 16px;background:var(--panel2);
  border:1px solid var(--cyan);font-size:15px;line-height:1.6;white-space:pre-line;
  box-shadow:0 0 26px rgba(55,230,255,.25);transition:transform .3s;text-align:center}
#toast.on{transform:translate(-50%,0)}

@media(max-width:600px){
  .pet-card{width:calc(50% - 5px)}
  .shop-card{width:calc(50% - 5px)}
  .bunit{width:76px}
  .bu-sprite{width:62px;height:62px}
  .node-hint{display:none}
}
