/*
 * Loadout — Dungeon Crawler overlay. Full-canvas (1920×1080) browser
 * source. Three states stack on the same root, exclusive via
 * data-state on #root: "idle" (transparent, render nothing), "recruit"
 * (party assembly with countdown), "adventure" (scene log scroll +
 * party row), "loot" (per-survivor reveal cards), "duel" (1v1 vs
 * panel). The DLL drives transitions via dungeon.* / duel.* bus
 * events; main.js animates the rest.
 */
:root {
  --bg-card:  rgba(15,15,17, 0.90);
  --bg-card-2: rgba(24,24,27, 0.92);
  --border: rgba(58,134,255,.36);
  --accent: #3A86FF;
  --accent-2: #00F2EA;
  --gold: #F0B429;
  --pink: #FF5DA2;
  --win: #3FB950;
  --lose: #F85149;
  --text: #EFEFF1;
  --muted: #ADADB8;
  /* Rarity colours feed the loot reveal frames + the rarity dot on
     each loot card. Match the Discord embed colours so the same
     "epic glow" reads consistently across surfaces. */
  --r-common: #B0B0B0;
  --r-uncommon: #46D160;
  --r-rare: #3A86FF;
  --r-epic: #B452FF;
  --r-legendary: #F0B429;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
  width: 100vw; height: 100vh;
  background: transparent;
  color: var(--text);
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  overflow: hidden;
}

/* Streamer-supplied background image. main.js sets this via the ?bg=
   URL param. We center + cover-fit the image and lay a dim scrim
   over it so the recruit / adventure / loot panels still read at
   full contrast. The dim is a CSS variable so the streamer can
   tune transparency via ?bgOpacity= without re-deploying anything. */
body.has-custom-bg {
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
}
body.has-custom-bg::before {
  content: '';
  position: fixed;
  inset: 0;
  background: rgba(0,0,0, var(--custom-bg-dim, .5));
  z-index: 0;
  pointer-events: none;
}

#root {
  position: fixed;
  inset: 0;
  pointer-events: none;
}
#root[data-state="idle"] > * { display: none; }

/* ── Recruit panel ──────────────────────────────────────────────── */

.recruit-panel {
  display: none;
  position: absolute;
  left: 50%; top: 14%;
  transform: translateX(-50%);
  width: min(680px, 80vw);
  background: linear-gradient(180deg, var(--bg-card) 0%, var(--bg-card-2) 100%);
  border: 1px solid var(--border);
  border-left: 6px solid var(--accent);
  border-radius: 16px;
  padding: 22px 26px;
  box-shadow: 0 18px 48px rgba(0,0,0,.55), 0 0 32px rgba(58,134,255,.20);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  animation: panelIn .35s cubic-bezier(.2,.8,.2,1) both;
}
#root[data-state="recruit"] .recruit-panel { display: block; }

.recruit-title {
  display: flex; align-items: center; gap: 12px;
  font-size: 28px; font-weight: 800;
  margin-bottom: 6px;
}
.r-icon { font-size: 32px; filter: drop-shadow(0 0 8px var(--accent)); }
.recruit-sub { color: var(--muted); font-size: 16px; margin-bottom: 14px; }
.recruit-sub code {
  background: rgba(58,134,255,.18);
  color: var(--accent);
  padding: 2px 8px; border-radius: 6px;
  font-family: 'Consolas', 'Monaco', monospace;
  font-size: 15px; font-weight: 700;
}
.recruit-timer-bar {
  width: 100%; height: 8px;
  background: rgba(255,255,255,.08);
  border-radius: 4px; overflow: hidden;
}
.recruit-timer-bar .bar {
  height: 100%; width: 100%;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  /* JS sets --bar-pct via CSS var so the bar shrinks every animation
     frame without re-flowing layout. */
  transform-origin: left center;
  transform: scaleX(var(--bar-pct, 1));
  transition: transform .25s linear;
}
.recruit-timer-text {
  text-align: right; color: var(--muted);
  font-size: 14px; margin-top: 4px; margin-bottom: 16px;
  font-variant-numeric: tabular-nums;
}

/* Run condition. Hidden unless the raid rolled one — the element is
   always in the DOM so the banner can appear without a reflow of the
   panel above it. */
.condition { display: none; align-items: baseline; gap: 8px; margin-bottom: 14px;
  padding: 8px 12px; border-radius: 10px;
  background: rgba(255,196,92,.10); border: 1px solid rgba(255,196,92,.28);
  animation: capIn .4s cubic-bezier(.2,.8,.2,1) both; }
.condition.on { display: flex; }
.condition .cond-icon { font-size: 17px; color: #ffc45c; flex-shrink: 0;
  align-self: center; }
.condition .cond-name { font-weight: 800; font-size: 14px; color: #ffd98a;
  white-space: nowrap; }
.condition .cond-blurb { font-size: 13px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.condition.stage-cond { margin: 2px 0 0; padding: 5px 10px; width: max-content; max-width: 100%; }
.condition.stage-cond .cond-name { font-size: 13px; }

/* Party grid: avatar tiles wrap as more viewers !join. Up to 8 in
   the default config; CSS handles wrapping at 4 across without
   re-layouting the panel. */
.party-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
}
.party-tile {
  background: rgba(58,134,255,.08);
  border: 1px solid rgba(58,134,255,.24);
  border-radius: 10px;
  padding: 10px;
  text-align: center;
  animation: tileIn .35s cubic-bezier(.2,.8,.2,1) both;
}
.party-tile .pt-avatar {
  width: 48px; height: 48px;
  margin: 0 auto 6px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 800; color: #0B1525;
  /* Class tint paints the ring around the avatar. main.js sets --ring
     inline per-tile from the bus payload so each viewer's class colour
     reads instantly. Fallback is the default accent. */
  border: 2px solid var(--ring, var(--accent));
  overflow: hidden;
}
.party-tile .pt-avatar-img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  border-radius: inherit;
}

/* Class sprite mode — when the avatar slot renders the inline SVG
   pixel-art instead of an avatar URL, swap the round 1:1 container
   for a TALL rounded rectangle (3:4 aspect) that matches the sprite's
   16×24 viewBox. The class-tinted ring becomes a 3px border around
   the rectangle. Pixels stay crisp via image-rendering: pixelated.
   Backdrop is a slightly-darker fill so the figure reads against any
   underlying canvas. */
.pt-avatar.has-sprite,
.lc-avatar.has-sprite,
.d-avatar.has-sprite {
  background: rgba(15, 15, 20, 0.55);
  padding: 0;
  font-size: 0;
  line-height: 0;
  border-radius: 6px;          /* was 50% (round) */
}
.pt-avatar.has-sprite svg,
.lc-avatar.has-sprite svg,
.d-avatar.has-sprite svg {
  width: 100%; height: 100%;
  display: block;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}

/* ── Drawn icons (icons.js) ─────────────────────────────────────────
   Loot, rooms, monsters and UI marks are our own 16x16 pixel art, not
   emoji — emoji pick up a different font on every OS (OBS's bundled
   Chrome included), drag another vendor's art direction onto the
   stream, and read at the wrong weight next to the character sprites.

   Sizing rides on the container's font-size (the icon is 1em square),
   so every type-scale rule already in this file keeps working and a
   new mount point needs no new rule. */
.dg-icon {
  width: 1em; height: 1em;
  display: block;
  image-rendering: pixelated;
  image-rendering: crisp-edges;
}
.r-icon, .a-icon, .lt-icon, .scene-line .s-glyph, .loot-card .lc-glyph {
  display: flex; align-items: center; justify-content: center;
}
/* The recruit and spoils marks carry the same glow the headline type
   does, so the art sits in the panel rather than on top of it. */
.r-icon .dg-icon  { filter: drop-shadow(0 0 8px var(--accent)); }
.lt-icon .dg-icon { filter: drop-shadow(0 0 14px rgba(240,180,41,.5)); }
/* A duellist with no character to draw shows their side's mark at a
   size that suits the 80px ring rather than the 1em default. */
.duelist .d-avatar.has-icon .dg-icon { width: 46px; height: 46px; }

/* Per-context sprite container sizes — each tile gets a 3:4 portrait
   slot scaled appropriately. The original round avatar widths are
   preserved as the base width so the layout doesn't shift; only the
   height extends. */
.party-tile .pt-avatar.has-sprite {
  width: 48px;
  height: 72px;            /* 3:4 — was 48 round */
  margin: 0 auto 6px;
}
.loot-card .lc-avatar.has-sprite {
  width: 56px;
  height: 84px;            /* 3:4 — was 56 round */
  margin: 0 auto 8px;
}
.duelist .d-avatar.has-sprite {
  width: 72px;
  height: 108px;           /* 3:4 — was 80 round; 72 wide leaves room
                              for the bigger card content */
  margin: 0 auto 10px;
}
.party-tile .pt-name { font-size: 13px; font-weight: 700; }
.party-tile .pt-meta { font-size: 11px; color: var(--muted); margin-top: 2px; }

/* ── Adventure log ──────────────────────────────────────────────── */

.adventure {
  display: none;
  position: absolute;
  right: 24px; top: 24px;
  width: 440px; max-height: 78vh;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-left: 6px solid var(--accent);
  border-radius: 14px;
  padding: 18px 20px 18px 22px;
  box-shadow: 0 14px 38px rgba(0,0,0,.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  animation: panelIn .35s cubic-bezier(.2,.8,.2,1) both;
}
#root[data-state="adventure"] .adventure { display: block; }

.adventure-header {
  display: flex; align-items: center; gap: 10px;
  font-size: 20px; font-weight: 800;
  margin-bottom: 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid rgba(255,255,255,.08);
}
.a-icon { font-size: 24px; }

.scenes {
  display: flex; flex-direction: column; gap: 10px;
  max-height: 64vh; overflow: hidden;
}
.scene-line {
  display: flex; align-items: flex-start; gap: 10px;
  background: rgba(255,255,255,.04);
  border-radius: 8px;
  padding: 10px 12px;
  animation: sceneIn .4s cubic-bezier(.2,.8,.2,1) both;
}
.scene-line .s-glyph { font-size: 22px; line-height: 1; flex-shrink: 0; }
.scene-line .s-text  { font-size: 14px; line-height: 1.4; color: var(--text); }
.scene-line.encounter { border-left: 3px solid var(--lose); }
.scene-line.trap      { border-left: 3px solid var(--gold); }
.scene-line.treasure  { border-left: 3px solid var(--win); }
.scene-line.story     { border-left: 3px solid var(--muted); }
/* A raider going down — engine.js flags the scene, so a skull-icon
   monster in an ordinary encounter never picks this styling up. */
.scene-line.fatal     { border-left-color: var(--lose); background: rgba(248,81,73,.10); }
.scene-line.fatal .s-text { color: #ffc9c5; }
.scene-line.duel-strike, .scene-line.duel-start, .scene-line.duel-end {
  border-left: 3px solid var(--accent-2);
}

/* ── Loot reveal ─────────────────────────────────────────────────── */

.loot-reveal {
  display: none;
  position: absolute;
  inset: 0;
  align-items: center; justify-content: center;
  flex-direction: column;
  /* Match the adventure panel's centered alignment so the transition
     from "adventure" -> "loot" reads as a natural finale, not a flash
     of new content somewhere else on the canvas. */
}
#root[data-state="loot"] .loot-reveal { display: flex; }

.loot-title {
  display: flex; align-items: center; gap: 12px;
  font-size: 36px; font-weight: 900;
  text-shadow: 0 0 18px rgba(240,180,41,.45);
  margin-bottom: 24px;
  animation: tileIn .5s cubic-bezier(.2,.8,.2,1) both;
}
.loot-grid {
  display: grid;
  grid-auto-flow: column;
  gap: 18px;
  max-width: 90vw;
  overflow: hidden;
}
.loot-card {
  width: 220px;
  background: var(--bg-card);
  border: 2px solid var(--r-common);
  border-radius: 14px;
  padding: 14px;
  text-align: center;
  position: relative;
  animation: lootCardIn .55s cubic-bezier(.2,.8,.2,1) both;
}
.loot-card.rarity-common    { border-color: var(--r-common); }
.loot-card.rarity-uncommon  { border-color: var(--r-uncommon); box-shadow: 0 0 20px rgba(70,209,96,.32); }
.loot-card.rarity-rare      { border-color: var(--r-rare);     box-shadow: 0 0 22px rgba(58,134,255,.36); }
.loot-card.rarity-epic      { border-color: var(--r-epic);     box-shadow: 0 0 28px rgba(180,82,255,.45); }
.loot-card.rarity-legendary { border-color: var(--r-legendary); box-shadow: 0 0 36px rgba(240,180,41,.55); animation: lootCardIn .55s cubic-bezier(.2,.8,.2,1) both, legendaryShimmer 2.4s ease-in-out infinite; }
.loot-card.fallen { border-color: var(--lose); opacity: .7; }

/* Avatar header on the loot card — viewer's character above the loot
   image so the card reads as "this is YOUR loot" rather than a generic
   item. Class tint paints the ring; main.js sets --ring inline. */
.loot-card .lc-avatar {
  width: 56px; height: 56px;
  margin: 0 auto 8px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(58,134,255,.14);
  font-size: 26px; font-weight: 800;
  border: 2px solid var(--ring, var(--accent));
  overflow: hidden;
}
.loot-card .lc-avatar img {
  width: 100%; height: 100%; object-fit: cover; display: block; border-radius: inherit;
}

.loot-card .lc-user { font-size: 14px; font-weight: 700; margin-bottom: 6px; }
.loot-card .lc-glyph { font-size: 56px; line-height: 1; margin: 8px 0; }
.loot-card .lc-name  { font-size: 16px; font-weight: 800; margin-bottom: 4px; }
.loot-card .lc-rarity { font-size: 11px; text-transform: uppercase; letter-spacing: 1.5px; color: var(--muted); margin-bottom: 8px; }
.loot-card.rarity-uncommon  .lc-rarity { color: var(--r-uncommon); }
.loot-card.rarity-rare      .lc-rarity { color: var(--r-rare); }
.loot-card.rarity-epic      .lc-rarity { color: var(--r-epic); }
.loot-card.rarity-legendary .lc-rarity { color: var(--r-legendary); }
.loot-card .lc-stats { font-size: 12px; color: var(--accent-2); }
.loot-card .lc-reward { font-size: 13px; font-weight: 700; color: var(--gold); margin-top: 6px; }
.loot-card .lc-fallen { font-size: 12px; color: var(--lose); font-weight: 700; }

/* ── Duel panel ─────────────────────────────────────────────────── */

.duel {
  display: none;
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: min(820px, 88vw);
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 24px;
  box-shadow: 0 18px 48px rgba(0,0,0,.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  animation: panelIn .35s cubic-bezier(.2,.8,.2,1) both;
}
#root[data-state="duel"] .duel { display: block; }

.duel-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
}
.duelist { text-align: center; }
.duelist .d-avatar {
  width: 80px; height: 80px;
  margin: 0 auto 10px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-2));
  display: flex; align-items: center; justify-content: center;
  font-size: 40px;
  border: 3px solid var(--accent);
  overflow: hidden;
}
.duelist.right .d-avatar {
  background: linear-gradient(135deg, var(--pink), var(--gold));
  border-color: var(--pink);
}
/* Avatar <img> fill — main.js drops one of these into .d-avatar when
   the duelist set an avatar URL on their character. */
.duelist .d-avatar-img {
  width: 100%; height: 100%; object-fit: cover; display: block; border-radius: inherit;
}
.duelist .d-name { font-size: 18px; font-weight: 800; }
.duelist .d-hp { width: 100%; height: 10px; background: rgba(255,255,255,.10); border-radius: 5px; margin-top: 8px; overflow: hidden; }
.duelist .d-hp-bar { height: 100%; background: linear-gradient(90deg, var(--win), var(--accent-2)); transform-origin: left center; transform: scaleX(1); transition: transform .35s ease; }
.duelist.right .d-hp-bar { background: linear-gradient(90deg, var(--pink), var(--gold)); }

.duel-vs {
  font-size: 36px; font-weight: 900; color: var(--accent);
  text-shadow: 0 0 14px rgba(58,134,255,.6);
}
.duel-log {
  margin-top: 16px;
  display: flex; flex-direction: column; gap: 6px;
  min-height: 90px;
}
.duel-log .scene-line { background: rgba(255,255,255,.05); }
.duel-result {
  margin-top: 14px;
  font-size: 24px; font-weight: 900;
  text-align: center;
  color: var(--gold);
  text-shadow: 0 0 12px rgba(240,180,41,.45);
  min-height: 32px;
}

/* ── Animations ─────────────────────────────────────────────────── */

@keyframes panelIn {
  0%   { opacity: 0; transform: translateX(-50%) translateY(-12px); }
  100% { opacity: 1; transform: translateX(-50%) translateY(0); }
}
.duel { animation-name: duelIn; }
@keyframes duelIn {
  0%   { opacity: 0; transform: translate(-50%, -45%) scale(.95); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
@keyframes tileIn {
  0%   { opacity: 0; transform: scale(.85) translateY(8px); }
  100% { opacity: 1; transform: scale(1) translateY(0); }
}
@keyframes sceneIn {
  0%   { opacity: 0; transform: translateY(8px); }
  100% { opacity: 1; transform: translateY(0); }
}
@keyframes lootCardIn {
  0%   { opacity: 0; transform: rotateY(90deg) scale(.85); }
  60%  { opacity: 1; transform: rotateY(-8deg) scale(1.04); }
  100% { opacity: 1; transform: rotateY(0)    scale(1); }
}
@keyframes legendaryShimmer {
  0%, 100% { box-shadow: 0 0 36px rgba(240,180,41,.55); }
  50%      { box-shadow: 0 0 56px rgba(240,180,41,.85); }
}

/* Connection indicator (debug only). */
.dev-status {
  position: fixed;
  bottom: 6px; right: 8px;
  font-size: 10px;
  color: var(--muted);
  background: rgba(0,0,0,.5);
  padding: 3px 6px;
  border-radius: 4px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ══ Battle stage ═══════════════════════════════════════════════════
   The raid as a side-view fight: party on the left facing right, the
   monster on the right facing back. Everything moves via CSS keyframes
   on transform/opacity — JS only adds a class and removes it on
   animationend, so an OBS browser source is not re-laying-out the page
   sixty times a second while a stream is running. */
#root[data-state="battle"] .battle { display: flex; }
.battle {
  /* Centred with auto margins, not translateX(-50%) — the tileIn
     entrance animates `transform`, which would wipe a centering
     translate out and slide the whole stage off the canvas. */
  display: none;
  position: absolute; left: 0; right: 0; bottom: 6vh; margin: 0 auto;
  flex-direction: column; gap: 12px;
  width: min(1180px, 82vw);
  background: rgba(12,12,18,.82);
  border: 1px solid rgba(255,255,255,.10);
  border-radius: 18px;
  padding: 16px 20px 18px;
  box-shadow: 0 24px 60px rgba(0,0,0,.55);
  animation: tileIn .45s cubic-bezier(.2,.8,.2,1) both;
}
.battle-head { display: flex; align-items: center; gap: 10px; font-size: 20px; font-weight: 800; }
.battle-head .b-icon { font-size: 22px; }
.battle-head .b-room { margin-left: auto; font-size: 13px; font-weight: 600; color: var(--muted); }

.stage {
  position: relative; height: 240px;
  display: flex; align-items: flex-end; justify-content: space-between;
  padding: 0 8px 14px;
  border-radius: 12px;
  background:
    radial-gradient(120% 80% at 50% 110%, rgba(255,255,255,.05), rgba(0,0,0,0) 70%),
    linear-gradient(180deg, rgba(255,255,255,.02), rgba(0,0,0,.25));
  overflow: hidden;
}
.stage.shake { animation: stageShake .32s ease-in-out; }
.stage-floor {
  position: absolute; left: 0; right: 0; bottom: 10px; height: 2px;
  background: linear-gradient(90deg, rgba(255,255,255,0), rgba(255,255,255,.22), rgba(255,255,255,0));
}

/* ── Party line ── */
.party-line { display: flex; align-items: flex-end; gap: 10px; }
.fighter {
  position: relative; width: 74px;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  transition: opacity .3s ease;
}
.fighter .f-sprite {
  width: 62px; height: 93px;
  image-rendering: pixelated; image-rendering: crisp-edges;
}
.fighter .f-sprite svg { width: 100%; height: 100%; display: block; }
.fighter .f-name {
  font-size: 11px; font-weight: 700; max-width: 74px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fighter .f-hp {
  width: 60px; height: 4px; border-radius: 999px; overflow: hidden;
  background: rgba(255,255,255,.14);
}
.fighter .f-hp i {
  display: block; height: 100%; width: 100%;
  background: linear-gradient(90deg, var(--win), #9BE8A4);
  transition: width .35s cubic-bezier(.2,.8,.2,1);
}
.fighter[data-hurt="low"] .f-hp i { background: linear-gradient(90deg, var(--gold), #FFD98A); }
.fighter[data-hurt="crit"] .f-hp i { background: linear-gradient(90deg, var(--lose), #FF9C97); }

/* Attack lunge — toward the monster, then back. */
.fighter.attack .f-sprite { animation: lungeRight .42s cubic-bezier(.3,1.4,.4,1); }
.fighter.hurt .f-sprite { animation: flinchLeft .34s ease-out; }
.fighter.hurt .f-sprite svg { animation: hitFlash .34s steps(1); }
.fighter.dodge .f-sprite { animation: sidestep .38s ease-out; }
.fighter.heal .f-sprite svg { animation: healPulse .6s ease-out; }
.fighter.dead { opacity: .38; }
.fighter.dead .f-sprite { animation: fallOver .7s cubic-bezier(.3,.9,.4,1) forwards; }
.fighter.dead .f-name { color: var(--lose); text-decoration: line-through; }

/* ── The monster ── */
.foe {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  margin-right: 12px; opacity: 0; pointer-events: none;
  transition: opacity .25s ease;
}
.foe.present { opacity: 1; }
.foe-sprite {
  width: 150px; height: 150px;
  image-rendering: pixelated; image-rendering: crisp-edges;
  filter: drop-shadow(0 6px 12px rgba(0,0,0,.5));
}
.foe-sprite svg { width: 100%; height: 100%; display: block; }
.foe-name { font-size: 15px; font-weight: 800; letter-spacing: -.01em; }
.foe-hp {
  width: 170px; height: 7px; border-radius: 999px; overflow: hidden;
  background: rgba(255,255,255,.14);
}
.foe-hp i {
  display: block; height: 100%; width: 100%;
  background: linear-gradient(90deg, var(--lose), #FF9C97);
  transition: width .4s cubic-bezier(.2,.8,.2,1);
}
.foe.enter .foe-sprite { animation: foeEnter .5s cubic-bezier(.2,.9,.3,1.1); }
.foe.attack .foe-sprite { animation: lungeLeft .44s cubic-bezier(.3,1.4,.4,1); }
.foe.hurt .foe-sprite { animation: foeFlinch .3s ease-out; }
.foe.hurt .foe-sprite svg { animation: hitFlash .3s steps(1); }
.foe.slain .foe-sprite { animation: foeDie .8s cubic-bezier(.3,.9,.4,1) forwards; }

/* ── Floating damage numbers ── */
.stage-fx { position: absolute; inset: 0; pointer-events: none; }
.fx-num {
  position: absolute; font-size: 20px; font-weight: 900; letter-spacing: -.02em;
  text-shadow: 0 2px 0 rgba(0,0,0,.6);
  animation: floatUp 1.05s cubic-bezier(.2,.8,.2,1) forwards;
}
.fx-num.dmg { color: #FF8A80; }
.fx-num.foe-dmg { color: #FFE9A0; }
.fx-num.miss { color: #9AA6C0; font-size: 15px; font-weight: 700; }
.fx-num.heal { color: #7BFFA8; }
.fx-num.crit { font-size: 27px; color: #FFD24A; }

/* ── Caption strip ── */
.caption {
  min-height: 26px; font-size: 15px; line-height: 1.35; color: var(--text);
  display: flex; align-items: center; gap: 9px;
}
.caption .cap-icon { font-size: 19px; flex-shrink: 0; }
.caption.step { animation: capIn .32s cubic-bezier(.2,.8,.2,1); }
.caption.fatal { color: #ffc9c5; }

/* ── Fork vote ── */
.fork { display: none; flex-direction: column; gap: 8px; }
.battle.voting .fork { display: flex; }
.battle.voting .caption { display: none; }
.fork-q { font-size: 16px; font-weight: 800; }
.fork-opts { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 10px; }
.fork-opt {
  background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.12);
  border-radius: 10px; padding: 9px 12px; position: relative; overflow: hidden;
}
.fork-opt .fo-key {
  display: inline-block; min-width: 20px; margin-right: 7px; padding: 1px 6px;
  border-radius: 5px; background: var(--accent); color: #08131b;
  font-size: 12px; font-weight: 900; text-transform: uppercase;
}
.fork-opt .fo-label { font-size: 14px; font-weight: 700; }
.fork-opt .fo-blurb { font-size: 12px; color: var(--muted); margin-top: 2px; }
.fork-opt .fo-count { position: absolute; right: 10px; top: 8px; font-size: 15px; font-weight: 900; }
.fork-opt .fo-fill {
  position: absolute; left: 0; bottom: 0; height: 3px; width: 0;
  background: var(--accent); transition: width .3s ease;
}
.fork-opt.leading { border-color: var(--accent); background: rgba(0,242,234,.08); }
.fork-opt.chosen { animation: chosenPulse .6s ease-out; border-color: var(--win); }
.fork-timer { height: 4px; border-radius: 999px; background: rgba(255,255,255,.12); overflow: hidden; }
.fork-timer i { display: block; height: 100%; width: 100%; background: var(--accent); }

/* ── Cheer ticker ── */
.cheers { display: flex; flex-wrap: wrap; gap: 6px; min-height: 0; }
.cheer {
  font-size: 12px; font-weight: 700; padding: 3px 9px; border-radius: 999px;
  background: rgba(240,180,41,.14); border: 1px solid rgba(240,180,41,.4); color: #FFD98A;
  animation: capIn .3s cubic-bezier(.2,.8,.2,1);
}

@keyframes lungeRight { 0%{transform:translateX(0)} 40%{transform:translateX(26px)} 100%{transform:translateX(0)} }
@keyframes lungeLeft  { 0%{transform:translateX(0)} 40%{transform:translateX(-34px)} 100%{transform:translateX(0)} }
@keyframes flinchLeft { 0%{transform:translateX(0)} 30%{transform:translateX(-9px)} 60%{transform:translateX(4px)} 100%{transform:translateX(0)} }
@keyframes foeFlinch  { 0%{transform:translateX(0)} 30%{transform:translateX(11px)} 100%{transform:translateX(0)} }
@keyframes sidestep   { 0%{transform:translateY(0)} 40%{transform:translateY(-13px)} 100%{transform:translateY(0)} }
@keyframes hitFlash   { 0%{filter:brightness(4) saturate(0)} 50%{filter:brightness(4) saturate(0)} 51%{filter:none} 100%{filter:none} }
@keyframes healPulse  { 0%{filter:none} 30%{filter:brightness(1.6) hue-rotate(60deg)} 100%{filter:none} }
@keyframes fallOver   { to { transform: translateY(16px) rotate(-78deg); filter: grayscale(1); } }
@keyframes foeEnter   { 0%{transform:translateX(90px) scale(.85); opacity:0} 100%{transform:translateX(0) scale(1); opacity:1} }
@keyframes foeDie     { to { transform: translateY(26px) rotate(22deg) scale(.9); opacity: 0; } }
@keyframes floatUp    { 0%{transform:translateY(0) scale(.7); opacity:0}
                        18%{transform:translateY(-10px) scale(1.12); opacity:1}
                        100%{transform:translateY(-58px) scale(1); opacity:0} }
@keyframes stageShake { 0%,100%{transform:translateX(0)} 20%{transform:translateX(-6px)}
                        45%{transform:translateX(5px)} 70%{transform:translateX(-3px)} }
@keyframes capIn      { from { opacity:0; transform: translateY(6px); } to { opacity:1; transform:none; } }
@keyframes chosenPulse{ 0%{box-shadow:0 0 0 0 rgba(91,255,149,.5)} 100%{box-shadow:0 0 0 14px rgba(91,255,149,0)} }
