/* ===== design tokens ===== */
:root {
  /* core palette */
  --bg: #1a1d2e;
  --bg2: #14172a;
  --card: #25293f;
  --card-soft: #2f3450;
  --text: #f3f3f3;
  --muted: #9498b8;
  --accent: #f9a826;
  --accent-soft: #ffd07a;
  --on-accent: #1a1d2e;
  --hp: #4caf50;
  --hp-bg: #1b1d2a;
  --bad: #e74c3c;
  --good: #2ecc71;
  --crit: #ff9800;
  --player: #5fa8ff;
  --enemy: #ff6b6b;
  --magic: #a83af0;
  --poison: #b86bff;

  /* accent alpha tiers (kept literal because rgba() can't reference vars) */
  --accent-alpha-12: rgba(249, 168, 38, 0.12);
  --accent-alpha-18: rgba(249, 168, 38, 0.18);
  --accent-alpha-22: rgba(249, 168, 38, 0.22);
  --accent-alpha-35: rgba(249, 168, 38, 0.35);
  --accent-alpha-45: rgba(249, 168, 38, 0.45);
  --accent-alpha-60: rgba(249, 168, 38, 0.6);

  /* fonts */
  --font-base:
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: "Consolas", "Monaco", monospace;
  --font-emoji:
    "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;

  /* radii */
  --r-xs: 3px;
  --r-sm: 6px;
  --r-md: 8px;
  --r-lg: 10px;
  --r-xl: 14px;
  --r-card: 12px; /* cards, podium, modal containers */
  --r-pill: 999px;

  /* durations */
  --t-fast: 120ms;
  --t-medium: 200ms;
  --t-base: 220ms;
  --t-slow: 420ms;
  --t-xslow: 600ms;

  /* spacing scale (4px base) */
  --s-0: 0;
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 20px;
  --s-6: 24px;

  /* font-size scale */
  --fs-xs: 0.7em;
  --fs-sm: 0.85em;
  --fs-base: 1em;
  --fs-md: 1.05em;
  --fs-lg: 1.2em;
  --fs-xl: 1.5em;
  --fs-2xl: 2.4em;

  /* z-index scale */
  --z-base: 0;
  --z-elevated: 1;
  --z-floating: 5;
  --z-popup: 100;
  --z-modal: 1000;

  /* letter-spacing scale */
  --ls-tight: -0.02em;
  --ls-normal: 0;
  --ls-wide: 0.04em;
  --ls-wider: 0.18em;

  /* line-height scale */
  --lh-tight: 1;
  --lh-snug: 1.2;
  --lh-normal: 1.4;
  --lh-relaxed: 1.7;
  --lh-loose: 1.9;

  /* alpha tokens (rgba literals previously scattered) */
  --alpha-black-35: var(--alpha-black-35);
  --alpha-black-45: var(--alpha-black-45);
  --alpha-black-55: var(--alpha-black-55);
  --alpha-black-60: rgba(0, 0, 0, 0.6);
  --alpha-white-04: var(--alpha-white-04);
  --alpha-white-05: var(--alpha-white-05);
  --alpha-white-06: var(--alpha-white-06);
  --alpha-white-08: var(--alpha-white-08);
  --bad-alpha-08: rgba(231, 76, 60, 0.08);
  --bad-alpha-40: rgba(231, 76, 60, 0.4);
  --good-alpha-08: var(--good-alpha-08);

  /* shadows */
  --shadow-card: 0 2px 8px var(--alpha-black-35);
  --shadow-elevated: 0 6px 18px var(--alpha-black-45);
  --shadow-modal: 0 20px 60px var(--alpha-black-60);
  --shadow-accent-glow: 0 0 12px var(--accent-alpha-45);

  /* faint borders / dividers */
  --border-faint: 1px solid var(--alpha-white-04);
  --border-soft: 1px solid var(--alpha-white-08);

  /* equipment slot colors (used by build cells, summary icons, etc.) */
  --slot-weapon: #d96d6d;
  --slot-shield: #6da7d9;
  --slot-helm: #d9c46d;
  --slot-armor: #6dd98a;
  --slot-gloves: #c4a96d;
  --slot-amulet: #d4d96d;
  --slot-boots: #6dd9d2;
  --slot-ring: #c46dd9;
  --slot-card: #d9a36d;
  --slot-rune: #b56dd9;
}

/* ===== reset / base ===== */

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  background: linear-gradient(180deg, var(--bg2), var(--bg));
  color: var(--text);
  font-family: var(--font-base);
  min-height: 100vh;
}

/* generic focus ring — applies to anything keyboard-focusable */
:where(button, [role="link"], [tabindex], input, summary):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* utility: hide via class. JS toggles classList.add/remove("hidden"). */
.hidden {
  display: none !important;
}

/* ===== layout shells ===== */

main {
  margin: 0 auto;
  padding: 16px;
  max-width: 1400px;
}

.screen {
  display: none;
  max-width: 760px;
  margin: 0 auto;
}
.screen.active {
  display: block;
}
.screen.wide {
  max-width: none;
}

/* ===== app-grid: 3-region layout used by the game screen.
 * left = build items + pet/zodiac cells (mascotas + signo + carta)
 * center = vertical arena (enemy HP+sprite top, player HP+sprite bottom)
 * right = stats totals + class card + passives + effects
 * HP banners live INSIDE the arena (.arena-banner-top / .arena-banner-bottom). */
.app-grid {
  display: grid;
  grid-template-areas: "left   center right";
  grid-template-columns: minmax(240px, 280px) 1fr minmax(240px, 280px);
  grid-template-rows: 1fr;
  gap: 12px;
  min-height: calc(100vh - 96px);
  align-items: stretch;
}
.region-left {
  grid-area: left;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
.region-right {
  grid-area: right;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}
.region-center {
  grid-area: center;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

/* Empty regions disappear so non-game screens (no left/right content yet) don't
 * leave gaping holes; the surrounding tracks stay at their min sizes anyway. */
.app-grid .region-left:empty,
.app-grid .region-right:empty {
  display: none;
}

/* Compact card variant for side panels. Looks like .card but tighter. */
.side-card {
  padding: 10px 12px;
}
.side-card h3 {
  margin: 0 0 8px;
  font-size: 1em;
}


/* HP banner: full-width strip for player (top) / enemy (bottom). Keeps the
 * exact same children as the old .hp-mini (so setHP/setEnergy/setPoisonStacks
 * keep working by ID), just stretches them across the screen with a horizontal
 * cap-rail of debuff + cd badges. */
.hp-banner {
  position: relative;
  background: var(--alpha-black-55);
  border-radius: var(--r-md);
  padding: 8px 14px;
  border: 1px solid var(--alpha-white-05);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Player sits at the bottom row and the enemy at the top, so the arena-facing
 * edge of each banner is the *top* edge for the player and the *bottom* edge
 * for the enemy. */
.hp-banner.side-player {
  border-top: 3px solid var(--player);
}
.hp-banner.side-enemy {
  border-bottom: 3px solid var(--enemy);
}
.hp-banner .name {
  font-weight: bold;
  font-size: 0.95em;
  flex: 0 0 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 280px;
}
.hp-banner .name-row {
  gap: 10px;
}
.hp-banner .hp-bar {
  height: 22px;
}
.hp-banner .hp-text {
  font-size: 0.85em;
}
.hp-banner .debuff-badges,
.hp-banner .cd-badges {
  flex-wrap: wrap;
}

/* Mobile: stack center (arena with embedded HP banners) → right → left. */
@media (max-width: 900px) {
  .app-grid {
    grid-template-areas:
      "center"
      "right"
      "left";
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto;
    min-height: auto;
  }
}

/* ===== modal overlay (generic, used by tournament card) ===== */
.modal-overlay:not([hidden]) {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  background: rgba(8, 10, 22, 0.78);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  animation: drops-modal-fade 180ms ease-out;
}
.modal-overlay .modal-panel {
  background: var(--card);
  border-radius: var(--r-xl);
  padding: 22px 26px;
  max-width: 760px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow:
    var(--shadow-modal),
    0 0 0 1px var(--card-soft);
  position: relative;
  animation: drops-modal-pop var(--t-base) cubic-bezier(0.2, 0.9, 0.3, 1.05);
}
.modal-overlay .modal-close {
  position: absolute;
  top: 8px;
  right: 10px;
  background: transparent;
  color: var(--muted);
  border: none;
  font-size: 1.6em;
  line-height: 1;
  padding: 4px 10px;
  cursor: pointer;
}
.modal-overlay .modal-close:hover {
  color: var(--text);
  background: transparent;
  transform: none;
}

.card h2 {
  margin-top: 0;
  color: var(--accent);
}
.card h3 {
  color: var(--accent-soft);
}

/* ===== forms / buttons ===== */

input[type="text"] {
  width: 100%;
  padding: 12px;
  margin: 12px 0;
  border: 2px solid var(--card-soft);
  border-radius: var(--r-md);
  background: var(--bg);
  color: var(--text);
  font-size: 1em;
}

button {
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  padding: 12px 20px;
  border-radius: var(--r-md);
  font-size: 1em;
  font-weight: bold;
  cursor: pointer;
  transition:
    transform var(--t-fast),
    background var(--t-base);
}
button:hover {
  background: var(--accent-soft);
  transform: translateY(-1px);
}
button:active {
  transform: translateY(0);
}

ul {
  padding-left: 20px;
}
li {
  padding: 4px 0;
}
li.me {
  color: var(--accent);
  font-weight: bold;
}

/* ===== match-formed cinematic intro ===== */

.match-preview {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  gap: var(--s-2);
  margin: 14px 0;
}
.match-preview .character {
  width: 130px;
  height: 195px;
}
.match-preview-pet {
  display: flex;
  align-items: flex-end;
}
.match-preview-pet .character,
.match-preview-pet .character-svg {
  width: 80px;
  height: 105px;
}

.start-card {
  text-align: center;
  padding: 22px 20px 18px;
  background:
    radial-gradient(ellipse at 50% 0%, var(--accent-alpha-12), transparent 65%),
    var(--card);
}
.start-banner {
  font-size: var(--fs-sm);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
}
.start-hero {
  position: relative;
  padding: 4px 0 12px;
}
.start-race-label,
.start-player-label {
  font-size: 0.72em;
  letter-spacing: 4px;
  color: var(--accent-soft);
  opacity: 0.7;
  margin: 6px 0 2px;
}
.start-race-name {
  font-size: 2.4em;
  font-weight: 800;
  letter-spacing: 3px;
  color: var(--accent);
  text-shadow: 0 0 18px var(--accent-alpha-45);
  line-height: 1.05;
  margin-bottom: 4px;
}
.start-sprite {
  margin: 6px 0 4px;
  filter: drop-shadow(0 6px 14px var(--alpha-black-55));
}
.start-sprite .character {
  width: 180px;
  height: 270px;
}
.start-player-name {
  font-size: var(--fs-xl);
  font-weight: 700;
  color: var(--text);
  letter-spacing: 0.5px;
  margin-bottom: 10px;
  word-break: break-word;
}
.start-persona {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
  margin: 4px 0 10px;
}
.start-lore {
  font-size: 0.92em;
  color: var(--muted);
  font-style: italic;
  max-width: 480px;
  margin: 0 auto 6px;
  min-height: 1.1em;
}
.start-roster {
  margin: 12px 0 6px;
  text-align: left;
  background: var(--card-soft);
  border-radius: var(--r-md);
  padding: 6px 12px;
}
.start-roster summary {
  cursor: pointer;
  color: var(--accent-soft);
  padding: 4px 0;
  font-size: 0.95em;
  user-select: none;
}
.start-roster ul {
  margin: 6px 0 4px;
  padding-left: 20px;
}

.start-card.intro .start-race-name {
  animation: start-race-in 520ms cubic-bezier(0.2, 0.9, 0.2, 1) both;
}
.start-card.intro .start-sprite {
  animation: start-sprite-in 520ms ease-out 120ms both;
}
.start-card.intro .start-player-name,
.start-card.intro .start-persona,
.start-card.intro .start-lore {
  animation: start-fade-up var(--t-slow) ease-out 260ms both;
}

@keyframes start-race-in {
  0% {
    transform: scale(1.6);
    opacity: 0;
    letter-spacing: 14px;
  }
  60% {
    transform: scale(0.96);
    opacity: 1;
    letter-spacing: 2px;
  }
  100% {
    transform: scale(1);
    opacity: 1;
    letter-spacing: 3px;
  }
}
@keyframes start-sprite-in {
  0% {
    transform: translateY(14px) scale(0.94);
    opacity: 0;
  }
  100% {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
}
@keyframes start-fade-up {
  0% {
    transform: translateY(8px);
    opacity: 0;
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}

/* ===== combat arena ===== */

.combat-card {
  padding: 14px;
}

.combat-title {
  text-align: center;
  font-size: 1.1em;
  margin-bottom: 12px;
  color: var(--accent-soft);
  font-weight: bold;
}
.combat-title-persona {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  margin-left: 4px;
  font-size: var(--fs-sm);
  font-weight: normal;
  vertical-align: middle;
}

.arena {
  position: relative;
  background: linear-gradient(180deg, #2c3157 0%, #1a1d34 60%, #0a0e1c 100%);
  border-radius: var(--r-lg);
  height: 280px;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: end;
  padding: 14px 18px 0 18px;
  overflow: hidden;
  box-shadow: inset 0 4px 12px rgba(0, 0, 0, 0.4);
  contain: layout paint;
}

/* Vertical arena (used by the new app-grid game screen): enemy HP+sprite on
 * the top-right corner, player HP+sprite on the bottom-left corner, VS mark
 * floating between them. The slot stacks ditch left/right semantics — corner
 * placement comes from per-slot justify-content (start/end). The enemy sprite
 * gets a scaleX(-1) so it keeps facing the player diagonally across the arena,
 * and --side flips sign so attack-lunge/hit-shake travel toward the opponent.
 * HP banners are embedded inside the arena via .arena-banner-top / -bottom. */
.arena.vertical {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: stretch;
  height: auto;
  min-height: 600px;
  padding: 12px 14px;
  overflow: visible;
  gap: 6px;
}
/* HP banners embedded inside the arena: lighter background so they blend with
 * the arena gradient, full width, and the team-color edge faces the sprite. */
.arena-banner {
  background: rgba(8, 10, 22, 0.62);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  width: 100%;
  flex: 0 0 auto;
  z-index: 3;
}
.arena-banner-top {
  border-bottom: 3px solid var(--enemy);
  border-top: 1px solid var(--alpha-white-05);
}
.arena-banner-bottom {
  border-top: 3px solid var(--player);
  border-bottom: 1px solid var(--alpha-white-05);
}
/* Sprite slots between the two banners get to share the leftover space. */
.arena.vertical .combatant-slot.top,
.arena.vertical .combatant-slot.bottom {
  flex: 1 1 0;
  min-height: 140px;
}
.arena.vertical .arena-floor {
  display: none;
}

/* ===== Wiki ability preview arena ===== */
/* Overrides the base .arena (grid / 280px / align-items:end) which this
   element also carries so spawnProjectile's closest('.arena') resolves here. */
.fx-preview-arena {
  position: relative;
  height: 240px;
  min-height: 0;
  margin-bottom: 16px;
  padding: 0;
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, #2c3157 0%, #1a1d34 60%, #0a0e1c 100%);
  box-shadow: inset 0 4px 12px rgba(0, 0, 0, 0.4);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: space-between;
  contain: layout paint;
}
.fx-preview-hint {
  position: absolute;
  top: 8px;
  left: 0;
  right: 0;
  text-align: center;
  font-size: 0.8em;
  color: rgba(255, 255, 255, 0.55);
  pointer-events: none;
  z-index: 2;
}
.fx-preview-arena .fx-slot {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  min-height: 90px;
}
.fx-preview-arena .fx-slot.top {
  justify-content: flex-start;
  align-items: flex-end;
  padding: 18px 0 0 24px;
}
.fx-preview-arena .fx-slot.bottom {
  justify-content: flex-end;
  align-items: flex-start;
  padding: 0 24px 12px 0;
}
/* Mirror the top combatant so the two face each other across the arena. */
.fx-preview-arena .fx-slot.top .character {
  transform: scaleX(-1);
}
.fx-preview-arena .fx-slot .character {
  width: 96px;
  height: 96px;
}
/* Totems planted next to the caster sit a bit smaller than the fighter. */
.fx-preview-arena .fx-slot .character.totem {
  width: 60px;
  height: 60px;
}

/* Checkbox column in the ability tables. */
.fx-ability-table .fx-check-col,
.fx-ability-table .fx-check-cell {
  width: 36px;
  text-align: center;
}
.fx-ability-table .fx-check {
  width: 16px;
  height: 16px;
  cursor: pointer;
  accent-color: var(--player, #6ee7ff);
}
.combatant-slot.top,
.combatant-slot.bottom {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: auto;
  --side: 1;
  padding: 0;
}
/* Diagonal layout inside the vertical arena: enemy pinned to top-right,
 * player pinned to bottom-left. The slot is flex-direction: column, so
 * justify-content controls the *vertical* axis (top / bottom of the slot)
 * and align-items controls the *horizontal* axis (left / right). --side
 * gives each combatant the right sign for attack/hit animations. */
.arena.vertical .combatant-slot.top {
  justify-content: flex-start;
  align-items: flex-end;
  padding-right: 10px;
  --side: -1;
}
.arena.vertical .combatant-slot.bottom {
  justify-content: flex-end;
  align-items: flex-start;
  padding-left: 10px;
  --side: 1;
}
/* Mirror the enemy sprite so it faces the player across the diagonal. */
.arena.vertical .combatant-slot.top .character-svg {
  transform: scaleX(-1);
}
/* Pet sits on the *inner* side of its owner (toward the opposite corner) so
 * it stays inside the arena instead of dangling off the edge. */
.arena.vertical .combatant-slot.bottom .pet-host {
  left: 140px;
  right: auto;
}
.arena.vertical .combatant-slot.top .pet-host {
  right: 140px;
  left: auto;
}
/* Default fallback when neither .top nor .bottom matched (defensive). */
.arena.vertical .pet-host {
  position: absolute;
  bottom: 4px;
}

/* Totem host: shaman totems live next to the player, stacked above the pet
 * on the player's right side (the player corner is bottom-left, so the
 * pet+totem column sits to the right of the sprite). Renders up to 3
 * totem sprites stacked vertically so multiple totems (Curación + Guardián
 * + Llamas) all fit without overlapping the HP banner. Each child .totem
 * is a small character sprite (see Sprites.createTotem). */
.arena.vertical .totem-host {
  position: absolute;
  bottom: 80px;
  left: 140px;
  right: auto;
  width: 64px;
  display: flex;
  flex-direction: column-reverse;
  align-items: center;
  gap: 2px;
  z-index: 1;
  pointer-events: none;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5));
}
.totem-host .character.totem {
  width: 56px;
  height: 60px;
  animation: idle-bob 2.4s ease-in-out infinite;
  transform-origin: 50% 95%;
}
.totem-host .character.totem .character-svg {
  width: 56px;
  height: 60px;
}
/* Per-kind aura tint behind each totem so the trio reads at a glance:
 * green = heal, blue = guard, orange = fire. */
.totem-host .totem-healing { filter: drop-shadow(0 0 6px rgba(140, 230, 130, 0.7)); }
.totem-host .totem-guardian { filter: drop-shadow(0 0 6px rgba(120, 180, 240, 0.7)); }
.totem-host .totem-fire { filter: drop-shadow(0 0 6px rgba(255, 150, 60, 0.75)); }

/* Pulse animation triggered when the matching totem ticks: a quick
 * stagger + glow flash so the player can see exactly which totem just
 * fired without staring at the cooldown badges. Removed automatically
 * by Sprites.pulseTotem after ~620ms. */
.totem-host .character.totem.pulse {
  animation: totem-pulse 0.6s ease-out;
}
@keyframes totem-pulse {
  0%   { transform: scale(1); filter: brightness(1); }
  35%  { transform: scale(1.18); filter: brightness(1.6) saturate(1.4); }
  100% { transform: scale(1); filter: brightness(1); }
}

.arena-floor {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 36px;
  background: linear-gradient(180deg, #1a1d34 0%, #06070d 100%);
  border-top: 1px solid var(--alpha-white-06);
}

/* --side gives every direction-aware @keyframe a sign multiplier so we can
 * ship one rule per animation instead of left/right pairs. */
.combatant-slot {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  z-index: 2;
  height: 100%;
  justify-content: flex-end;
}
.combatant-slot.left {
  --side: 1;
  align-items: flex-start;
  padding-left: 10px;
}
.combatant-slot.right {
  --side: -1;
  align-items: flex-end;
  padding-right: 10px;
}

/* Mirror only the SVG, not the .character wrapper. This lets every animation
 * use plain transforms on .character without --mirror keyframe variants. */
.combatant-slot.right .character-svg {
  transform: scaleX(-1);
}

.hp-mini {
  position: relative;
  width: 100%;
  max-width: 220px;
  background: var(--alpha-black-55);
  border-radius: var(--r-md);
  padding: 6px 10px;
  margin-bottom: 6px;
  border: 1px solid var(--alpha-white-05);
}
.combatant-slot.left .hp-mini {
  border-left: 3px solid var(--player);
}
.combatant-slot.right .hp-mini {
  border-right: 3px solid var(--enemy);
  text-align: right;
}
.hp-mini .name {
  font-weight: bold;
  font-size: 0.95em;
  margin-bottom: 4px;
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hp-bar {
  position: relative;
  background: var(--hp-bg);
  height: 16px;
  border-radius: var(--r-sm);
  overflow: hidden;
}
.hp-fill {
  background: linear-gradient(90deg, var(--hp), #88e088);
  height: 100%;
  width: 100%;
  transition: width 0.35s ease-out;
}
.hp-fill.low {
  background: linear-gradient(90deg, var(--bad), #ff8888);
}
.hp-fill.mid {
  background: linear-gradient(90deg, var(--accent), var(--accent-soft));
}
.hp-text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.75em;
  font-weight: bold;
  color: #fff;
  text-shadow:
    0 0 4px rgba(0, 0, 0, 0.85),
    0 1px 1px rgba(0, 0, 0, 0.6);
  pointer-events: none;
  letter-spacing: 0.04em;
}

/* Energy shield bar — sits right under the HP card. Cyan to set it apart from
 * HP green/red. Always reserves space so the hp-mini footprint stays fixed
 * whether the combatant has Energy or not — the JS toggles the [hidden]
 * attribute, which we override below to keep layout flow. */
.energy-bar {
  position: relative;
  margin-top: 4px;
  height: 8px;
  border-radius: var(--r-sm);
  background: rgba(0, 200, 255, 0.12);
  overflow: hidden;
  border: 1px solid rgba(0, 200, 255, 0.35);
  box-shadow: 0 0 6px rgba(0, 200, 255, 0.2);
}
/* Reserve the energy slot when the combatant has no Energy — the JS sets
 * `wrap.hidden = true`, so override the UA `display: none` to keep the row. */
.energy-bar[hidden] {
  display: block;
  visibility: hidden;
}
.energy-fill {
  background: linear-gradient(90deg, #4cd0ff, #b3ecff);
  height: 100%;
  width: 100%;
  transition: width 0.25s ease-out;
}
.energy-text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.7em;
  font-weight: bold;
  color: #d6f3ff;
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.7);
  pointer-events: none;
  letter-spacing: 0.04em;
}

/* status indicators — charge meters (dodge/block/crit, only shown for the
 * matching charge passive) sit inline next to the combatant's name; debuff
 * stacks (poison/freeze/bleed/ignite/slow) get their own row inside the
 * hp-mini card. Both reserve a fixed vertical slot so the sprite never
 * shifts when icons appear or disappear. */
.charge-badges,
.debuff-badges {
  display: flex;
  gap: 3px;
  min-height: 16px;
  align-items: center;
  z-index: 3;
}
.debuff-badges {
  margin-top: 3px;
}
.combatant-slot.right .debuff-badges {
  justify-content: flex-end;
}

/* Name row sits at the top of hp-mini and pairs the name with charge-badges.
 * Right slot mirrors the row so badges stay on the inboard side of the name. */
.name-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 1.4em;
}
.combatant-slot.right .name-row {
  flex-direction: row-reverse;
}

/* Status badges (poison/freeze/bleed/slow/ignite + charge) share layout &
   typography. Per-badge rules below override only color, background, border,
   and the pulsing keyframe. */
.poison-badge,
.freeze-badge,
.bleed-badge,
.slow-badge,
.ignite-badge,
.break-badge,
.haste-badge,
.adrenaline-badge,
.fury-badge,
.charge-badge {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 1px 4px;
  border-radius: var(--r-pill);
  font-size: 0.65em;
  font-weight: bold;
  letter-spacing: var(--ls-wide);
  border: 1px solid;
  line-height: 1;
}
/* class-level `display: inline-flex` above ties with the UA `[hidden]` rule on
 * specificity, so the badges leak through when the JS sets `el.hidden = true`.
 * Restore the hide with an attribute selector that wins by specificity. */
.poison-badge[hidden],
.freeze-badge[hidden],
.bleed-badge[hidden],
.slow-badge[hidden],
.ignite-badge[hidden],
.break-badge[hidden],
.haste-badge[hidden],
.adrenaline-badge[hidden],
.fury-badge[hidden],
.charge-badge[hidden] {
  display: none;
}
.poison-badge .poison-icon,
.freeze-badge .freeze-icon,
.bleed-badge .bleed-icon,
.slow-badge .slow-icon,
.ignite-badge .ignite-icon,
.break-badge .break-icon,
.haste-badge .haste-icon,
.adrenaline-badge .adrenaline-icon,
.fury-badge .fury-icon,
.charge-badge .charge-icon {
  font-size: 1em;
  line-height: 1;
}
.poison-badge .poison-count,
.freeze-badge .freeze-count,
.bleed-badge .bleed-count,
.slow-badge .slow-count,
.ignite-badge .ignite-count,
.break-badge .break-count,
.haste-badge .haste-count,
.adrenaline-badge .adrenaline-count,
.fury-badge .fury-count,
.charge-badge .charge-count {
  min-width: 1.2em;
  text-align: center;
}

.poison-badge {
  background: rgba(184, 107, 255, 0.18);
  border-color: rgba(184, 107, 255, 0.45);
  color: var(--poison);
  box-shadow: 0 0 6px rgba(184, 107, 255, 0.25);
  animation: poison-pulse 1.6s ease-in-out infinite;
}
@keyframes poison-pulse {
  0%,
  100% {
    box-shadow: 0 0 4px rgba(184, 107, 255, 0.2);
  }
  50% {
    box-shadow: 0 0 10px rgba(184, 107, 255, 0.55);
  }
}

.freeze-badge {
  background: rgba(120, 200, 255, 0.18);
  border-color: rgba(120, 200, 255, 0.55);
  color: #b6e6ff;
  box-shadow: 0 0 6px rgba(120, 200, 255, 0.3);
  animation: freeze-pulse 1.6s ease-in-out infinite;
}
@keyframes freeze-pulse {
  0%,
  100% {
    box-shadow: 0 0 4px rgba(120, 200, 255, 0.25);
  }
  50% {
    box-shadow: 0 0 12px rgba(120, 200, 255, 0.65);
  }
}

/* Frozen overlay applied to .character while the engine's freeze window is
 * open (FreezeDurationMs ≈ 1s). Stiff shiver replaces idle-bob so the sprite
 * reads as locked-in-ice; cyan filter + frosty fog + ice glints layer over
 * the SVG. Hit/dodge keep their own filters via :not() guards so reactive
 * flashes remain visible through the freeze. */
.character.frozen {
  animation: frozen-shiver 0.18s ease-in-out infinite;
}
.character.frozen:not(.anim-hit):not(.anim-dodge) .character-svg {
  filter: drop-shadow(0 0 8px rgba(140, 210, 255, 0.95))
    drop-shadow(0 0 14px rgba(80, 160, 255, 0.55)) brightness(0.9)
    saturate(0.85) hue-rotate(180deg);
}
.character.frozen::before,
.character.frozen::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
}
.character.frozen::before {
  background: radial-gradient(
    ellipse at 50% 55%,
    rgba(190, 235, 255, 0.32) 0%,
    rgba(140, 200, 255, 0.16) 50%,
    transparent 78%
  );
  mix-blend-mode: screen;
  animation: frozen-fog 1s ease-in-out infinite;
}
.character.frozen::after {
  background-image:
    radial-gradient(
      circle at 22% 28%,
      rgba(230, 245, 255, 0.95) 0 1.6px,
      transparent 2.2px
    ),
    radial-gradient(
      circle at 78% 22%,
      rgba(230, 245, 255, 0.85) 0 1.3px,
      transparent 2px
    ),
    radial-gradient(
      circle at 35% 65%,
      rgba(230, 245, 255, 0.85) 0 1.6px,
      transparent 2.2px
    ),
    radial-gradient(
      circle at 70% 75%,
      rgba(230, 245, 255, 0.85) 0 1.1px,
      transparent 1.8px
    ),
    radial-gradient(
      circle at 50% 18%,
      rgba(230, 245, 255, 0.85) 0 1.3px,
      transparent 2px
    ),
    radial-gradient(
      circle at 18% 82%,
      rgba(230, 245, 255, 0.75) 0 1.1px,
      transparent 1.8px
    );
  animation: frozen-sparkle 1.4s ease-in-out infinite;
}
@keyframes frozen-shiver {
  0%,
  100% {
    transform: translateX(0);
  }
  50% {
    transform: translateX(0.6px);
  }
}
@keyframes frozen-fog {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 1;
  }
}
@keyframes frozen-sparkle {
  0%,
  100% {
    opacity: 0.9;
    transform: translateY(0);
  }
  50% {
    opacity: 0.4;
    transform: translateY(-1.5px);
  }
}

.bleed-badge {
  background: rgba(220, 38, 78, 0.18);
  border-color: rgba(220, 38, 78, 0.55);
  color: #ff8aa1;
  box-shadow: 0 0 6px rgba(220, 38, 78, 0.3);
  animation: bleed-pulse 1.6s ease-in-out infinite;
}
.bleed-badge.saturated {
  /* When stacks reach the cap (10) the next physical hit will detonate the
     wound for 2× damage. Brighten the badge so the player can see it coming. */
  background: rgba(220, 38, 78, 0.32);
  border-color: rgba(255, 80, 110, 0.95);
  color: #fff;
  animation: bleed-saturated 0.7s ease-in-out infinite;
}
@keyframes bleed-pulse {
  0%,
  100% {
    box-shadow: 0 0 4px rgba(220, 38, 78, 0.25);
  }
  50% {
    box-shadow: 0 0 12px rgba(220, 38, 78, 0.65);
  }
}
@keyframes bleed-saturated {
  0%,
  100% {
    box-shadow: 0 0 6px rgba(255, 80, 110, 0.55);
  }
  50% {
    box-shadow: 0 0 16px rgba(255, 80, 110, 0.95);
  }
}

.slow-badge {
  background: rgba(180, 180, 195, 0.18);
  border-color: rgba(200, 200, 215, 0.55);
  color: #d8d8e8;
  box-shadow: 0 0 6px rgba(200, 200, 215, 0.3);
  animation: slow-pulse 2s ease-in-out infinite;
}
@keyframes slow-pulse {
  0%,
  100% {
    box-shadow: 0 0 4px rgba(200, 200, 215, 0.3);
  }
  50% {
    box-shadow: 0 0 12px rgba(200, 200, 215, 0.65);
  }
}

.break-badge {
  background: rgba(150, 120, 90, 0.2);
  border-color: rgba(190, 150, 110, 0.55);
  color: #e6c79a;
  box-shadow: 0 0 6px rgba(190, 150, 110, 0.3);
  animation: break-pulse 1.8s ease-in-out infinite;
}
@keyframes break-pulse {
  0%,
  100% {
    box-shadow: 0 0 4px rgba(190, 150, 110, 0.3);
  }
  50% {
    box-shadow: 0 0 12px rgba(220, 170, 120, 0.65);
  }
}

.ignite-badge {
  background: rgba(255, 110, 30, 0.18);
  border-color: rgba(255, 140, 50, 0.55);
  color: #ffb86b;
  box-shadow: 0 0 6px rgba(255, 140, 50, 0.35);
  animation: ignite-pulse 1.4s ease-in-out infinite;
}
@keyframes ignite-pulse {
  0%,
  100% {
    box-shadow: 0 0 4px rgba(255, 140, 50, 0.35);
  }
  50% {
    box-shadow: 0 0 14px rgba(255, 180, 70, 0.85);
  }
}

/* Haste ("prisa") is a self-buff — green-cyan palette so it reads as a buff
   instead of bleeding into the red/orange/purple debuff family. */
.haste-badge {
  background: rgba(120, 230, 180, 0.18);
  border-color: rgba(120, 230, 180, 0.55);
  color: #8af0c0;
  box-shadow: 0 0 6px rgba(120, 230, 180, 0.35);
  animation: haste-pulse 1.2s ease-in-out infinite;
}
@keyframes haste-pulse {
  0%,
  100% {
    box-shadow: 0 0 4px rgba(120, 230, 180, 0.35);
  }
  50% {
    box-shadow: 0 0 14px rgba(160, 250, 200, 0.85);
  }
}

/* Adrenalina is a self-buff (+atk on taking hits) — amber/gold so it reads as
   an aggressive comeback buff distinct from the green haste palette. */
.adrenaline-badge {
  background: rgba(255, 196, 60, 0.18);
  border-color: rgba(255, 196, 60, 0.55);
  color: #ffd87a;
  box-shadow: 0 0 6px rgba(255, 196, 60, 0.35);
  animation: adrenaline-pulse 1.3s ease-in-out infinite;
}
@keyframes adrenaline-pulse {
  0%,
  100% {
    box-shadow: 0 0 4px rgba(255, 196, 60, 0.35);
  }
  50% {
    box-shadow: 0 0 14px rgba(255, 216, 110, 0.85);
  }
}

/* Furia is a self-buff (+% damage on landing hits) — hot red so it reads as a
   rising-rage offensive buff. */
.fury-badge {
  background: rgba(255, 80, 60, 0.18);
  border-color: rgba(255, 80, 60, 0.55);
  color: #ff9b88;
  box-shadow: 0 0 6px rgba(255, 80, 60, 0.35);
  animation: fury-pulse 1.1s ease-in-out infinite;
}
@keyframes fury-pulse {
  0%,
  100% {
    box-shadow: 0 0 4px rgba(255, 80, 60, 0.35);
  }
  50% {
    box-shadow: 0 0 14px rgba(255, 120, 90, 0.9);
  }
}

/* Charge badges fill an inset gradient from left to right based on the
   --charge css variable (0..100). When the next relevant action will trigger
   (engine reports value+pct >= 100) the JS adds .primed and the badge glows
   to telegraph the upcoming dodge/block/crit. */
.charge-badge {
  position: relative;
  overflow: hidden;
}
.charge-badge::before {
  content: "";
  position: absolute;
  inset: 0;
  width: calc(var(--charge, 0) * 1%);
  pointer-events: none;
  z-index: 0;
  transition: width 120ms linear;
}
.charge-badge .charge-icon,
.charge-badge .charge-count {
  position: relative;
  z-index: 1;
}

.dodge-badge {
  background: rgba(120, 220, 200, 0.14);
  border-color: rgba(120, 220, 200, 0.45);
  color: #b8f0dd;
  box-shadow: 0 0 4px rgba(120, 220, 200, 0.22);
}
.dodge-badge::before {
  background: linear-gradient(
    90deg,
    rgba(120, 220, 200, 0.55),
    rgba(120, 220, 200, 0.18)
  );
}
.dodge-badge.primed {
  border-color: rgba(150, 255, 220, 1);
  color: #f1fff7;
  animation: dodge-primed 0.6s ease-in-out infinite;
}
.dodge-badge.primed::before {
  background: linear-gradient(
    90deg,
    rgba(150, 255, 220, 0.8),
    rgba(150, 255, 220, 0.45)
  );
}
@keyframes dodge-primed {
  0%,
  100% {
    box-shadow: 0 0 6px rgba(150, 255, 220, 0.55);
  }
  50% {
    box-shadow: 0 0 18px rgba(150, 255, 220, 0.95);
  }
}

.block-badge {
  background: rgba(245, 200, 90, 0.14);
  border-color: rgba(245, 200, 90, 0.45);
  color: #ffe6a8;
  box-shadow: 0 0 4px rgba(245, 200, 90, 0.22);
}
.block-badge::before {
  background: linear-gradient(
    90deg,
    rgba(245, 200, 90, 0.55),
    rgba(245, 200, 90, 0.18)
  );
}
.block-badge.primed {
  border-color: rgba(255, 220, 120, 1);
  color: #fffaea;
  animation: block-primed 0.6s ease-in-out infinite;
}
.block-badge.primed::before {
  background: linear-gradient(
    90deg,
    rgba(255, 220, 120, 0.8),
    rgba(255, 220, 120, 0.45)
  );
}
@keyframes block-primed {
  0%,
  100% {
    box-shadow: 0 0 6px rgba(255, 220, 120, 0.55);
  }
  50% {
    box-shadow: 0 0 18px rgba(255, 220, 120, 0.95);
  }
}

.crit-badge {
  background: rgba(255, 90, 100, 0.14);
  border-color: rgba(255, 90, 100, 0.45);
  color: #ffc1c8;
  box-shadow: 0 0 4px rgba(255, 90, 100, 0.22);
}
.crit-badge::before {
  background: linear-gradient(
    90deg,
    rgba(255, 90, 100, 0.55),
    rgba(255, 90, 100, 0.18)
  );
}
.crit-badge.primed {
  border-color: rgba(255, 130, 140, 1);
  color: #fff4f5;
  animation: crit-primed 0.6s ease-in-out infinite;
}
.crit-badge.primed::before {
  background: linear-gradient(
    90deg,
    rgba(255, 140, 150, 0.85),
    rgba(255, 200, 90, 0.5)
  );
}
@keyframes crit-primed {
  0%,
  100% {
    box-shadow: 0 0 6px rgba(255, 130, 140, 0.55);
  }
  50% {
    box-shadow: 0 0 18px rgba(255, 180, 100, 0.95);
  }
}

/* Cooldown badges — one per side, sit below the debuff row. Show the
 * upcoming basic attack, wand spell, and any periodic class passive that
 * fires on a fixed cadence (Divine Hammer, Hachazo, Poison Potion). The
 * ::before bar fills 0%→100% over the real-time cooldown (already divided
 * by REPLAY_SPEED) via a CSS transition on width; JS resets the transition
 * each time the engine fires the action. .fired pulses the whole badge. */
.cd-badges {
  display: flex;
  gap: 3px;
  min-height: 14px;
  align-items: center;
  margin-top: 3px;
  z-index: 3;
}
.combatant-slot.right .cd-badges {
  justify-content: flex-end;
}
.cd-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 1px 5px;
  border-radius: var(--r-pill);
  font-size: 0.62em;
  font-weight: bold;
  letter-spacing: var(--ls-wide);
  border: 1px solid;
  line-height: 1;
  overflow: hidden;
  min-width: 30px;
  justify-content: center;
}
.cd-badge[hidden] {
  display: none;
}
.cd-badge::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 0%;
  pointer-events: none;
  z-index: 0;
  transition: width var(--cd-duration, 0ms) linear;
}
.cd-badge.filling::before {
  width: 100%;
}
.cd-badge .cd-icon,
.cd-badge .cd-time {
  position: relative;
  z-index: 1;
}
.cd-badge .cd-time {
  min-width: 3.4em;
  text-align: center;
  opacity: 0.9;
  font-variant-numeric: tabular-nums;
}
.cd-badge.fired {
  animation: cd-fired 280ms ease-out;
}
@keyframes cd-fired {
  0% {
    box-shadow: 0 0 0 rgba(255, 235, 150, 0);
    transform: scale(1);
  }
  40% {
    box-shadow: 0 0 12px rgba(255, 235, 150, 0.95);
    transform: scale(1.18);
  }
  100% {
    box-shadow: 0 0 0 rgba(255, 235, 150, 0);
    transform: scale(1);
  }
}

.cd-attack {
  background: rgba(255, 200, 140, 0.14);
  border-color: rgba(255, 200, 140, 0.45);
  color: #ffd9b3;
}
.cd-attack::before {
  background: linear-gradient(
    90deg,
    rgba(255, 200, 140, 0.55),
    rgba(255, 200, 140, 0.18)
  );
}
.cd-spell {
  background: rgba(160, 200, 255, 0.14);
  border-color: rgba(160, 200, 255, 0.45);
  color: #c6d8ff;
}
.cd-spell::before {
  background: linear-gradient(
    90deg,
    rgba(160, 200, 255, 0.55),
    rgba(160, 200, 255, 0.18)
  );
}
.cd-hammer {
  background: rgba(255, 220, 90, 0.14);
  border-color: rgba(255, 220, 90, 0.55);
  color: #ffea9a;
}
.cd-hammer::before {
  background: linear-gradient(
    90deg,
    rgba(255, 220, 90, 0.55),
    rgba(255, 220, 90, 0.18)
  );
}
.cd-hachazo {
  background: rgba(220, 90, 90, 0.14);
  border-color: rgba(220, 90, 90, 0.5);
  color: #ffaab1;
}
.cd-hachazo::before {
  background: linear-gradient(
    90deg,
    rgba(220, 90, 90, 0.55),
    rgba(220, 90, 90, 0.18)
  );
}
.cd-seismicslam {
  background: rgba(255, 160, 60, 0.14);
  border-color: rgba(255, 160, 60, 0.5);
  color: #ffce8f;
}
.cd-seismicslam::before {
  background: linear-gradient(
    90deg,
    rgba(255, 160, 60, 0.55),
    rgba(255, 160, 60, 0.18)
  );
}
.cd-whirlwind {
  background: rgba(190, 205, 220, 0.14);
  border-color: rgba(190, 205, 220, 0.5);
  color: #d8e2ee;
}
.cd-whirlwind::before {
  background: linear-gradient(
    90deg,
    rgba(190, 205, 220, 0.55),
    rgba(190, 205, 220, 0.18)
  );
}
.cd-javelin {
  background: rgba(200, 180, 110, 0.14);
  border-color: rgba(200, 180, 110, 0.5);
  color: #e6d9a8;
}
.cd-javelin::before {
  background: linear-gradient(
    90deg,
    rgba(200, 180, 110, 0.55),
    rgba(200, 180, 110, 0.18)
  );
}
.cd-potion {
  background: rgba(160, 120, 220, 0.14);
  border-color: rgba(160, 120, 220, 0.5);
  color: #d4baf4;
}
.cd-potion::before {
  background: linear-gradient(
    90deg,
    rgba(160, 120, 220, 0.55),
    rgba(160, 120, 220, 0.18)
  );
}
.cd-marker {
  background: rgba(255, 130, 130, 0.14);
  border-color: rgba(255, 130, 130, 0.5);
  color: #ffb8b8;
}
.cd-marker::before {
  background: linear-gradient(
    90deg,
    rgba(255, 130, 130, 0.55),
    rgba(255, 130, 130, 0.18)
  );
}
.cd-hands {
  background: rgba(255, 240, 180, 0.14);
  border-color: rgba(255, 240, 180, 0.55);
  color: #ffeebd;
}
.cd-hands::before {
  background: linear-gradient(
    90deg,
    rgba(255, 240, 180, 0.55),
    rgba(255, 240, 180, 0.18)
  );
}
.cd-regen {
  background: rgba(140, 220, 150, 0.14);
  border-color: rgba(140, 220, 150, 0.5);
  color: #bdf2c3;
}
.cd-regen::before {
  background: linear-gradient(
    90deg,
    rgba(140, 220, 150, 0.55),
    rgba(140, 220, 150, 0.18)
  );
}
.cd-energy {
  background: rgba(120, 220, 255, 0.14);
  border-color: rgba(120, 220, 255, 0.5);
  color: #bce8ff;
}
.cd-energy::before {
  background: linear-gradient(
    90deg,
    rgba(120, 220, 255, 0.55),
    rgba(120, 220, 255, 0.18)
  );
}
.cd-laugh {
  background: rgba(255, 170, 90, 0.14);
  border-color: rgba(255, 170, 90, 0.55);
  color: #ffd0a8;
}
.cd-laugh::before {
  background: linear-gradient(
    90deg,
    rgba(255, 170, 90, 0.55),
    rgba(255, 170, 90, 0.18)
  );
}

.vs-mark {
  font-weight: bold;
  font-size: 1.3em;
  color: var(--accent);
  align-self: center;
  text-shadow: 0 0 10px var(--accent-alpha-60);
  z-index: 1;
  transition: opacity var(--t-xslow) ease-out;
}
.vs-mark.faded {
  opacity: 0.18;
}

.character-host {
  width: 130px;
  height: 180px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

/* Pet sprite sits beside its owner on the floor, on the inner side toward the
 * VS mark. Each pet keeps the .character idle-bob; we never route attack/cast
 * animations to it. The arena's existing right-side flip already mirrors it. */
.pet-host {
  position: absolute;
  bottom: 6px;
  width: 70px;
  height: 90px;
  z-index: 1;
  pointer-events: none;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.5));
}
.pet-host .character,
.pet-host .character-svg {
  width: 70px;
  height: 90px;
}
.combatant-slot.left .pet-host {
  left: 132px;
}
.combatant-slot.right .pet-host {
  right: 132px;
}

/* Animation target. Idle bob loops by default; transient classes override
 * the animation shorthand entirely (so idle pauses while attacking, etc). */
.character {
  width: 130px;
  height: 180px;
  position: relative;
  transform-origin: 50% 95%;
  animation: idle-bob 2.4s ease-in-out infinite;
  will-change: transform;
}
.character-svg {
  width: 100%;
  height: 100%;
  overflow: visible;
}

@keyframes idle-bob {
  0%,
  100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-4px);
  }
}

/* Direction-aware combat animations. var(--side) is +1 on the left slot and
 * -1 on the right slot; the SVG flip is a separate transform on .character-svg
 * so it doesn't fight with these. */
.character.anim-attack {
  animation: attack-lunge 0.36s ease-out;
}
@keyframes attack-lunge {
  0% {
    transform: translateX(0) translateY(0);
  }
  40% {
    transform: translateX(calc(40px * var(--side, 1))) translateY(-6px)
      rotate(calc(-3deg * var(--side, 1)));
  }
  100% {
    transform: translateX(0) translateY(0);
  }
}

/* Melee charge: attacker runs across the arena toward the target, swings on
 * impact, then runs back. --mx / --my are set in JS from the live bounding
 * rects so the travel reaches whatever corner the target sits in. */
.character.anim-melee-rush {
  animation: melee-rush 0.48s ease-in-out;
}
.character.anim-melee-rush .character-svg {
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.45));
}
@keyframes melee-rush {
  0% {
    transform: translate(0, 0);
  }
  35% {
    transform: translate(var(--mx, 0px), var(--my, 0px))
      rotate(calc(-5deg * var(--side, 1)));
  }
  50% {
    transform: translate(var(--mx, 0px), calc(var(--my, 0px) - 4px))
      rotate(calc(6deg * var(--side, 1))) scale(1.05);
  }
  65% {
    transform: translate(var(--mx, 0px), var(--my, 0px))
      rotate(calc(-2deg * var(--side, 1)));
  }
  100% {
    transform: translate(0, 0);
  }
}

.character.anim-cast {
  animation: cast-jump 0.42s ease-out;
}
.character.anim-cast .character-svg {
  filter: drop-shadow(0 0 8px var(--magic));
}
@keyframes cast-jump {
  0% {
    transform: translateY(0);
  }
  35% {
    transform: translateY(-12px) scale(1.04);
  }
  100% {
    transform: translateY(0);
  }
}

.character.anim-hit {
  animation: hit-shake 0.36s ease-out;
}
.character.anim-hit .character-svg {
  filter: drop-shadow(0 0 6px rgba(255, 40, 40, 0.8)) brightness(1.4)
    saturate(1.5);
}
@keyframes hit-shake {
  0% {
    transform: translateX(0);
  }
  20% {
    transform: translateX(calc(-8px * var(--side, 1))) rotate(-2deg);
  }
  40% {
    transform: translateX(calc(6px * var(--side, 1))) rotate(2deg);
  }
  60% {
    transform: translateX(calc(-4px * var(--side, 1))) rotate(-1deg);
  }
  80% {
    transform: translateX(calc(2px * var(--side, 1)));
  }
  100% {
    transform: translateX(0);
  }
}

.character.anim-dodge {
  animation: dodge-leap var(--t-slow) ease-out;
}
.character.anim-dodge .character-svg {
  filter: drop-shadow(0 0 6px rgba(80, 255, 200, 0.8));
  opacity: 0.7;
}
@keyframes dodge-leap {
  0% {
    transform: translate(0, 0);
  }
  50% {
    transform: translate(calc(-25px * var(--side, 1)), -12px);
    opacity: 0.6;
  }
  100% {
    transform: translate(0, 0);
  }
}

.character.anim-heal {
  animation: heal-pulse 0.6s ease-out;
}
.character.anim-heal .character-svg {
  filter: drop-shadow(0 0 10px rgba(80, 255, 80, 0.85));
}
@keyframes heal-pulse {
  0% {
    transform: translateY(0) scale(1);
  }
  50% {
    transform: translateY(-4px) scale(1.04);
  }
  100% {
    transform: translateY(0) scale(1);
  }
}

.character.anim-death {
  animation: death-fall 0.8s ease-in forwards;
}
@keyframes death-fall {
  0% {
    transform: translate(0, 0) rotate(0);
    opacity: 1;
  }
  100% {
    transform: translate(0, 40px) rotate(calc(-90deg * var(--side, 1)));
    opacity: 0.3;
  }
}

.character.anim-win {
  animation: win-cheer 0.9s ease-out 2;
}
@keyframes win-cheer {
  0% {
    transform: translateY(0);
  }
  30% {
    transform: translateY(-14px) scale(1.05);
  }
  60% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(0);
  }
}

.character.anim-lose {
  animation: lose-slump 0.6s ease-out forwards;
}
@keyframes lose-slump {
  to {
    transform: translateY(8px) rotate(calc(-6deg * var(--side, 1)));
    opacity: 0.6;
  }
}

.character.equip-pop .character-svg {
  animation: equip-flash 0.6s ease-out;
}
@keyframes equip-flash {
  0% {
    filter: drop-shadow(0 0 0 rgba(255, 215, 80, 0));
  }
  40% {
    filter: drop-shadow(0 0 16px rgba(255, 215, 80, 0.95));
  }
  100% {
    filter: drop-shadow(0 0 0 rgba(255, 215, 80, 0));
  }
}

.character.level-up-flash .character-svg,
.level-up-flash .character-svg {
  animation: level-up-pulse 0.8s ease-out;
}
@keyframes level-up-pulse {
  0% {
    filter: drop-shadow(0 0 0 rgba(120, 220, 255, 0));
    transform: scale(1);
  }
  35% {
    filter: drop-shadow(0 0 22px rgba(120, 220, 255, 0.95));
    transform: scale(1.06);
  }
  100% {
    filter: drop-shadow(0 0 0 rgba(120, 220, 255, 0));
    transform: scale(1);
  }
}

/* ===== limb animation =====
 * Pivots use absolute SVG user coordinates (viewBox is 0 0 80 120) so they
 * stay correct even when the right combatant slot flips the parent SVG with
 * transform: scaleX(-1) — fill-box gets quirky under that flip on some
 * browsers. Arms/legs pivot at shoulder/hip, head at the neck.
 * Idle keeps the sprite alive; on .anim-hit the limbs flail violently. */
.character .sv-arm-l,
.character .sv-arm-r,
.character .sv-leg-l,
.character .sv-leg-r,
.character .sv-head {
  will-change: transform;
}
.character .sv-arm-l {
  transform-origin: 61px 52px;
}
.character .sv-arm-r {
  transform-origin: 19px 52px;
}
.character .sv-leg-l {
  transform-origin: 46px 80px;
}
.character .sv-leg-r {
  transform-origin: 34px 80px;
}
.character .sv-head {
  transform-origin: 40px 48px;
}

.character .sv-arm-l {
  animation: idle-arm-sway-l 2.4s ease-in-out infinite;
}
.character .sv-arm-r {
  animation: idle-arm-sway-r 2.4s ease-in-out infinite;
}
.character .sv-leg-l {
  animation: idle-leg-sway-l 2.4s ease-in-out infinite;
}
.character .sv-leg-r {
  animation: idle-leg-sway-r 2.4s ease-in-out infinite;
}
.character .sv-head {
  animation: idle-head-tilt 2.4s ease-in-out infinite;
}

@keyframes idle-arm-sway-l {
  0%,
  100% {
    transform: rotate(-2.5deg);
  }
  50% {
    transform: rotate(3deg);
  }
}
@keyframes idle-arm-sway-r {
  0%,
  100% {
    transform: rotate(2.5deg);
  }
  50% {
    transform: rotate(-3deg);
  }
}
@keyframes idle-leg-sway-l {
  0%,
  100% {
    transform: rotate(1.2deg);
  }
  50% {
    transform: rotate(-1.2deg);
  }
}
@keyframes idle-leg-sway-r {
  0%,
  100% {
    transform: rotate(-1.2deg);
  }
  50% {
    transform: rotate(1.2deg);
  }
}
@keyframes idle-head-tilt {
  0%,
  100% {
    transform: rotate(-1.2deg);
  }
  50% {
    transform: rotate(1.2deg);
  }
}

/* Violent flail on hit — overrides idle for the duration of .anim-hit. */
.character.anim-hit .sv-arm-l {
  animation: hit-flail-arm-l 0.42s ease-out;
}
.character.anim-hit .sv-arm-r {
  animation: hit-flail-arm-r 0.42s ease-out;
}
.character.anim-hit .sv-leg-l {
  animation: hit-flail-leg-l 0.42s ease-out;
}
.character.anim-hit .sv-leg-r {
  animation: hit-flail-leg-r 0.42s ease-out;
}
.character.anim-hit .sv-head {
  animation: hit-whip-head 0.42s ease-out;
}

@keyframes hit-flail-arm-l {
  0% {
    transform: rotate(0);
  }
  18% {
    transform: rotate(55deg);
  }
  36% {
    transform: rotate(-40deg);
  }
  54% {
    transform: rotate(30deg);
  }
  72% {
    transform: rotate(-18deg);
  }
  88% {
    transform: rotate(8deg);
  }
  100% {
    transform: rotate(0);
  }
}
@keyframes hit-flail-arm-r {
  0% {
    transform: rotate(0);
  }
  18% {
    transform: rotate(-55deg);
  }
  36% {
    transform: rotate(40deg);
  }
  54% {
    transform: rotate(-30deg);
  }
  72% {
    transform: rotate(18deg);
  }
  88% {
    transform: rotate(-8deg);
  }
  100% {
    transform: rotate(0);
  }
}
@keyframes hit-flail-leg-l {
  0% {
    transform: rotate(0);
  }
  20% {
    transform: rotate(22deg);
  }
  45% {
    transform: rotate(-18deg);
  }
  70% {
    transform: rotate(12deg);
  }
  100% {
    transform: rotate(0);
  }
}
@keyframes hit-flail-leg-r {
  0% {
    transform: rotate(0);
  }
  20% {
    transform: rotate(-22deg);
  }
  45% {
    transform: rotate(18deg);
  }
  70% {
    transform: rotate(-12deg);
  }
  100% {
    transform: rotate(0);
  }
}
@keyframes hit-whip-head {
  0% {
    transform: rotate(0) translateY(0);
  }
  20% {
    transform: rotate(28deg) translateY(2px);
  }
  45% {
    transform: rotate(-22deg) translateY(0);
  }
  70% {
    transform: rotate(12deg) translateY(1px);
  }
  100% {
    transform: rotate(0) translateY(0);
  }
}

/* While dead/lost/frozen, freeze the limbs so the corpse doesn't wave around. */
.character.anim-death .sv-arm-l,
.character.anim-death .sv-arm-r,
.character.anim-death .sv-leg-l,
.character.anim-death .sv-leg-r,
.character.anim-death .sv-head,
.character.anim-lose .sv-arm-l,
.character.anim-lose .sv-arm-r,
.character.anim-lose .sv-leg-l,
.character.anim-lose .sv-leg-r,
.character.anim-lose .sv-head,
.character.frozen .sv-arm-l,
.character.frozen .sv-arm-r,
.character.frozen .sv-leg-l,
.character.frozen .sv-leg-r,
.character.frozen .sv-head {
  animation: none;
}

/* ===== projectiles ===== */

.projectile {
  position: absolute;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%);
  animation: proj-fly var(--dur, 480ms) ease-in-out forwards;
  z-index: 5;
}
.projectile-fireball {
  background: radial-gradient(circle, #ffd966 0%, #ff7a1a 50%, #c14b1f 100%);
  box-shadow:
    0 0 14px #ff8a30,
    0 0 28px #c14b1f;
}
.projectile-drain {
  background: radial-gradient(circle, #ff66aa 0%, #a01030 70%);
  box-shadow: 0 0 14px #ff5588;
}
.projectile-arcane {
  background: radial-gradient(circle, #cc88ff 0%, var(--magic) 70%);
  box-shadow: 0 0 14px var(--magic);
}
.projectile-reflect {
  background: radial-gradient(circle, #88ddff 0%, #3a8acc 70%);
  box-shadow: 0 0 12px var(--player);
  width: 12px;
  height: 12px;
}

/* Magic ball — basic-attack projectile for magic weapons (wands/staves).
 * Small luminous blue orb, smaller than spell projectiles to read as a basic. */
.projectile-magic_ball {
  background: radial-gradient(
    circle at 35% 35%,
    #e6f4ff 0%,
    #6ec6e8 45%,
    #1e5aa8 100%
  );
  box-shadow:
    0 0 10px #6ec6e8,
    0 0 20px #3a8acc;
  width: 12px;
  height: 12px;
}

/* Arrow — thin shaft that points along the flight vector. The clip-path makes
 * a stylised arrow (head + shaft + fletching) without any image asset. */
.projectile-arrow {
  width: 38px;
  height: 8px;
  border-radius: 0;
  background: linear-gradient(90deg, #3a2410 0%, #6b3f1a 60%, #d8b066 100%);
  box-shadow: 0 0 6px rgba(60, 30, 10, 0.6);
  clip-path: polygon(
    0 35%,
    18% 35%,
    18% 0%,
    28% 0%,
    28% 35%,
    78% 35%,
    78% 0%,
    100% 50%,
    78% 100%,
    78% 65%,
    28% 65%,
    28% 100%,
    18% 100%,
    18% 65%,
    0 65%
  );
  animation: arrow-fly var(--dur, 280ms) cubic-bezier(0.4, 0.05, 0.4, 1)
    forwards;
}
@keyframes arrow-fly {
  0% {
    transform: translate(-50%, -50%) rotate(var(--ang, 0rad)) scale(0.6);
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  100% {
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy)))
      rotate(var(--ang, 0rad)) scale(1);
    opacity: 1;
  }
}

/* Precision shot — Demon Hunter "Disparo Certero" passive proc. A heavy
 * crossbow bolt: chunky elongated slug with a white-hot core, crimson body,
 * and a violet/black demonic halo. Larger than any other projectile in the
 * arena so the read is "big shot". The ::before is a piercing white tracer
 * laid along the flight vector; the ::after is a sweeping shadow trail that
 * fades behind the bolt. The element uses arrow-fly so it rotates to match
 * the angle of attack. */
.projectile-precision_shot {
  width: 88px;
  height: 22px;
  border-radius: 12px;
  background: radial-gradient(
    ellipse at 80% 50%,
    #ffffff 0%,
    #ffe6ec 12%,
    #ff5070 30%,
    #c01030 55%,
    #5a0510 85%,
    #1a0006 100%
  );
  box-shadow:
    0 0 14px rgba(255, 80, 110, 0.95),
    0 0 28px rgba(170, 20, 40, 0.85),
    0 0 52px rgba(80, 0, 110, 0.7),
    inset 0 0 8px rgba(40, 0, 10, 0.55);
  filter: drop-shadow(0 0 6px rgba(255, 200, 200, 0.6))
    drop-shadow(0 0 22px rgba(120, 0, 60, 0.85));
  animation: arrow-fly var(--dur, 380ms) cubic-bezier(0.3, 0.04, 0.4, 1)
    forwards;
}
.projectile-precision_shot::before {
  /* Piercing white tracer — thin bright streak through the body of the slug. */
  content: "";
  position: absolute;
  left: 8%;
  right: 8%;
  top: 50%;
  height: 4px;
  margin-top: -2px;
  background: linear-gradient(
    90deg,
    rgba(255, 230, 230, 0) 0%,
    rgba(255, 255, 255, 0.95) 60%,
    rgba(255, 255, 255, 1) 100%
  );
  border-radius: 2px;
  box-shadow: 0 0 6px #ffffff;
  pointer-events: none;
}
.projectile-precision_shot::after {
  /* Demonic shadow trail behind the bolt — violet smoke fading backward. */
  content: "";
  position: absolute;
  right: 75%;
  top: 50%;
  width: 90px;
  height: 26px;
  margin-top: -13px;
  background: linear-gradient(
    90deg,
    rgba(60, 0, 90, 0) 0%,
    rgba(120, 10, 80, 0.45) 35%,
    rgba(200, 20, 60, 0.7) 75%,
    rgba(255, 100, 130, 0.85) 100%
  );
  border-radius: 50% 0 0 50%;
  filter: blur(5px);
  pointer-events: none;
}

/* Explosive arrow — Archer "Flecha Explosiva" passive proc. Same arrow
 * silhouette as .projectile-arrow but ~2× larger and clad in flame: the shaft
 * itself uses a hot orange→red gradient and the element drops two stacked
 * flame-colored shadows so it reads as a burning bolt. A ::after trail spawns
 * a fading ember plume behind the shaft. */
.projectile-explosive_arrow {
  width: 76px;
  height: 18px;
  border-radius: 0;
  background: linear-gradient(
    90deg,
    #4a0e02 0%,
    #b03208 30%,
    #ff7a1a 60%,
    #ffe066 90%,
    #ffffff 100%
  );
  box-shadow:
    0 0 10px rgba(255, 140, 40, 0.85),
    0 0 22px rgba(220, 60, 20, 0.75),
    0 0 40px rgba(255, 90, 20, 0.55);
  filter: drop-shadow(0 0 8px #ffb070) drop-shadow(0 0 18px #c14b1f);
  clip-path: polygon(
    0 35%,
    18% 35%,
    18% 0%,
    28% 0%,
    28% 35%,
    78% 35%,
    78% 0%,
    100% 50%,
    78% 100%,
    78% 65%,
    28% 65%,
    28% 100%,
    18% 100%,
    18% 65%,
    0 65%
  );
  animation: arrow-fly var(--dur, 420ms) cubic-bezier(0.35, 0.05, 0.45, 1)
    forwards;
}
.projectile-explosive_arrow::before {
  /* Ember halo wrapping the head — pulses while in flight. */
  content: "";
  position: absolute;
  right: -6px;
  top: 50%;
  width: 30px;
  height: 30px;
  margin-top: -15px;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(255, 240, 180, 0.85) 0%,
    rgba(255, 140, 40, 0.55) 45%,
    rgba(160, 30, 10, 0) 80%
  );
  animation: ember-pulse 0.18s ease-in-out infinite alternate;
  pointer-events: none;
}
.projectile-explosive_arrow::after {
  /* Fire trail behind the arrow — long horizontal plume in the shaft frame. */
  content: "";
  position: absolute;
  right: 80%;
  top: 50%;
  width: 80px;
  height: 22px;
  margin-top: -11px;
  background: linear-gradient(
    90deg,
    rgba(255, 60, 10, 0) 0%,
    rgba(255, 100, 20, 0.5) 35%,
    rgba(255, 200, 80, 0.8) 80%,
    rgba(255, 240, 200, 0.95) 100%
  );
  border-radius: 50% 0 0 50%;
  filter: blur(4px);
  pointer-events: none;
}
@keyframes ember-pulse {
  0% {
    transform: scale(0.85);
    opacity: 0.85;
  }
  100% {
    transform: scale(1.15);
    opacity: 1;
  }
}

/* Thrown axe — Warrior Hachazo as a flying projectile. Wooden handle (::before)
 * + bloody curved bit (::after); the whole element spins several full turns
 * while it travels from attacker to target via --dx/--dy. Same silhouette as
 * .axe-chop-fx but smaller so it reads as a hatchet in flight. */
.projectile-axe {
  width: 56px;
  height: 56px;
  background: transparent;
  border-radius: 0;
  box-shadow: none;
  animation: axe-throw-fly var(--dur, 420ms) cubic-bezier(0.3, 0.05, 0.4, 1)
    forwards;
  filter: drop-shadow(0 0 6px rgba(255, 80, 50, 0.55))
    drop-shadow(0 2px 10px rgba(120, 8, 4, 0.7));
}
.projectile-axe::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 8px;
  height: 46px;
  margin-left: -4px;
  margin-top: -8px;
  background:
    linear-gradient(180deg, rgba(80, 6, 4, 0.55) 0%, transparent 25%),
    linear-gradient(
      90deg,
      #2a1607 0%,
      #5e3510 25%,
      #b07a32 50%,
      #5e3510 75%,
      #2a1607 100%
    );
  border-radius: 2px;
  box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.7);
}
.projectile-axe::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 52px;
  height: 38px;
  margin-left: -26px;
  margin-top: -28px;
  background:
    linear-gradient(
      135deg,
      transparent 45%,
      rgba(170, 18, 10, 0.7) 70%,
      rgba(70, 4, 4, 0.9) 100%
    ),
    linear-gradient(
      180deg,
      transparent 38%,
      rgba(150, 16, 8, 0.55) 65%,
      rgba(80, 6, 4, 0.85) 100%
    ),
    linear-gradient(180deg, #f4f8ff 0%, #cdd5e3 28%, #8a93a5 68%, #4d525f 100%);
  clip-path: polygon(
    6% 32%,
    18% 14%,
    38% 4%,
    62% 0%,
    85% 14%,
    100% 38%,
    100% 62%,
    85% 86%,
    62% 100%,
    38% 96%,
    18% 86%,
    6% 68%
  );
  box-shadow:
    0 0 8px rgba(255, 90, 50, 0.55),
    0 0 16px rgba(140, 10, 5, 0.6),
    inset 0 0 6px rgba(40, 0, 0, 0.55),
    inset 0 -4px 6px rgba(120, 10, 5, 0.7);
}
@keyframes axe-throw-fly {
  0% {
    transform: translate(-50%, -50%) rotate(0deg) scale(0.7);
    opacity: 0;
  }
  12% {
    opacity: 1;
  }
  100% {
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy)))
      rotate(1080deg) scale(1);
    opacity: 1;
  }
}

/* Spear — Warrior "Lanza Arrojada" active. A rigid javelin that flies pointing
 * along its travel direction (no tumble), unlike the spinning axe. The shaft +
 * head live on ::before; the outer element only carries the trajectory. */
.projectile-spear {
  width: 50px;
  height: 50px;
  background: transparent;
  box-shadow: none;
  border-radius: 0;
  animation: spear-fly var(--dur, 380ms) cubic-bezier(0.25, 0.5, 0.4, 1)
    forwards;
  filter: drop-shadow(0 0 5px rgba(216, 200, 144, 0.5));
}
.projectile-spear::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46px;
  height: 6px;
  margin-left: -23px;
  margin-top: -3px;
  background: linear-gradient(
    90deg,
    #3a2c14 0%,
    #6e5a2a 30%,
    #b0a070 60%,
    #e8e0c0 82%,
    #ffffff 100%
  );
  border-radius: 2px;
  clip-path: polygon(0 35%, 68% 22%, 100% 50%, 68% 78%, 0 65%);
}
@keyframes spear-fly {
  0% {
    transform: translate(-50%, -50%) rotate(var(--ang, 0rad)) scale(0.7);
    opacity: 0;
  }
  12% {
    opacity: 1;
  }
  100% {
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy)))
      rotate(var(--ang, 0rad)) scale(1);
    opacity: 1;
  }
}

/* Golpe Sísmico — Warrior active. A shockwave ring expands from the target's
 * feet. Self-contained overlay (not mounted on the sprite) so it never touches
 * the sprite's resting transform / facing mirror. */
.seismic-fx {
  position: absolute;
  width: 22px;
  height: 11px;
  margin-left: -11px;
  margin-top: -5px;
  border: 3px solid rgba(255, 170, 70, 0.85);
  border-radius: 50%;
  pointer-events: none;
  z-index: 6;
  transform: translate(-50%, -50%) scale(0.3);
  animation: seismic-ring 500ms ease-out forwards;
  box-shadow: 0 0 16px rgba(255, 150, 50, 0.6);
}
@keyframes seismic-ring {
  0% {
    transform: translate(-50%, -50%) scale(0.3);
    opacity: 0.95;
  }
  100% {
    transform: translate(-50%, -50%) scale(3.8);
    opacity: 0;
  }
}

/* Torbellino — Warrior active. A ring of blades whirls around the target.
 * Conic gradient gives the blade streaks; self-contained overlay. */
.whirlwind-fx {
  position: absolute;
  width: 72px;
  height: 72px;
  margin-left: -36px;
  margin-top: -36px;
  pointer-events: none;
  z-index: 6;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  background: conic-gradient(
    from 0deg,
    transparent 0deg,
    rgba(208, 216, 224, 0.85) 60deg,
    transparent 110deg,
    transparent 180deg,
    rgba(208, 216, 224, 0.7) 240deg,
    transparent 300deg
  );
  -webkit-mask: radial-gradient(
    circle,
    transparent 40%,
    #000 46%,
    #000 92%,
    transparent 96%
  );
  mask: radial-gradient(
    circle,
    transparent 40%,
    #000 46%,
    #000 92%,
    transparent 96%
  );
  animation: whirl-spin 480ms linear forwards;
  filter: drop-shadow(0 0 6px rgba(200, 210, 220, 0.5));
}
@keyframes whirl-spin {
  0% {
    transform: translate(-50%, -50%) rotate(0deg) scale(0.6);
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -50%) rotate(540deg) scale(1.1);
    opacity: 0;
  }
}

/* Ice shard — pale blue diamond */
.projectile-ice_shard {
  background: radial-gradient(circle, #f0fbff 0%, #88c8ec 55%, #2a6db0 100%);
  box-shadow:
    0 0 14px #b0e8ff,
    0 0 26px #4a90d8;
  width: 16px;
  height: 16px;
  border-radius: 2px;
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}

/* Meteor — large fiery rock with hot core */
.projectile-meteor {
  background: radial-gradient(
    circle at 35% 35%,
    #fff7c0 0%,
    #ff8a20 35%,
    #8a1010 90%
  );
  box-shadow:
    0 0 18px #ff5510,
    0 0 36px #aa1010,
    0 0 60px #560000;
  width: 26px;
  height: 26px;
}
.projectile-meteor::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(
    circle at 65% 65%,
    transparent 0%,
    rgba(0, 0, 0, 0.45) 100%
  );
}

/* Arcane orb — luminous violet/cyan */
.projectile-arcane_orb {
  background: radial-gradient(
    circle at 35% 35%,
    #f4ecff 0%,
    #b08aff 50%,
    #4828a0 100%
  );
  box-shadow:
    0 0 16px #c4a0ff,
    0 0 30px #7848e8;
  width: 18px;
  height: 18px;
}
.projectile-arcane_orb::before {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 1px solid rgba(220, 200, 255, 0.55);
  pointer-events: none;
}

/* Life siphon — dark green/violet ethereal */
.projectile-life_siphon {
  background: radial-gradient(
    circle at 40% 40%,
    #c8ffd4 0%,
    #6bbf64 45%,
    #4a1264 100%
  );
  box-shadow:
    0 0 14px #80e090,
    0 0 26px #6020a0;
  width: 16px;
  height: 16px;
}
.projectile-life_siphon::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    transparent 55%,
    rgba(120, 30, 160, 0.35) 80%,
    transparent 100%
  );
}

/* Runic bolt — golden glyph */
.projectile-runic_bolt {
  background: radial-gradient(
    circle at 35% 35%,
    #fff7c8 0%,
    #f0c040 50%,
    #8b5e1e 100%
  );
  box-shadow:
    0 0 14px #ffd860,
    0 0 28px #c08010;
  width: 18px;
  height: 18px;
}
.projectile-runic_bolt::before {
  content: "✦";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #4a2810;
  font-size: 13px;
  font-weight: bold;
  line-height: 1;
  text-shadow: 0 0 3px #fff7c8;
}

/* Chain lightning — bright cyan electric */
.projectile-chain_lightning {
  background: radial-gradient(circle, #ffffff 0%, #88e8ff 40%, #2080d0 100%);
  box-shadow:
    0 0 14px #b0eeff,
    0 0 28px #4090e0,
    0 0 4px #ffffff;
  width: 14px;
  height: 14px;
  animation:
    proj-fly var(--dur, 480ms) ease-in-out forwards,
    lightning-flicker 0.16s ease-in-out infinite alternate;
}
.projectile-chain_lightning::before,
.projectile-chain_lightning::after {
  content: "";
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  border: 1px solid rgba(180, 240, 255, 0.7);
}
.projectile-chain_lightning::after {
  inset: -6px;
  border-color: rgba(120, 200, 255, 0.4);
}
@keyframes lightning-flicker {
  0% {
    box-shadow:
      0 0 14px #b0eeff,
      0 0 26px #4090e0,
      0 0 4px #ffffff;
  }
  100% {
    box-shadow:
      0 0 6px #ffffff,
      0 0 18px #80e8ff,
      0 0 32px #2080d0;
  }
}

/* Arcane nova — bright magenta with expanding rings */
.projectile-arcane_nova {
  background: radial-gradient(
    circle at 35% 35%,
    #fff0ff 0%,
    #ff80f0 50%,
    #a020c0 100%
  );
  box-shadow:
    0 0 16px #ff80f0,
    0 0 32px #c050d0,
    0 0 56px #6010a0;
  width: 22px;
  height: 22px;
}
.projectile-arcane_nova::before,
.projectile-arcane_nova::after {
  content: "";
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  border: 2px solid rgba(255, 200, 255, 0.55);
  animation: nova-ring 0.6s ease-out infinite;
}
.projectile-arcane_nova::after {
  inset: -14px;
  border-color: rgba(255, 160, 240, 0.4);
  animation-delay: 0.3s;
}
@keyframes nova-ring {
  0% {
    transform: scale(0.55);
    opacity: 1;
  }
  100% {
    transform: scale(1.55);
    opacity: 0;
  }
}

@keyframes proj-fly {
  0% {
    transform: translate(-50%, -50%) scale(0.5);
  }
  20% {
    transform: translate(
        calc(-50% + var(--dx) * 0.2),
        calc(-50% + var(--dy) * 0.2 - 12px)
      )
      scale(1.05);
  }
  60% {
    transform: translate(
        calc(-50% + var(--dx) * 0.6),
        calc(-50% + var(--dy) * 0.6 - 8px)
      )
      scale(1);
  }
  100% {
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy)))
      scale(0.6);
    opacity: 0.6;
  }
}

/* Golden hammer — Paladin "Martillo de los Dioses" passive proc. A glowing
 * yellow warhammer that tumbles end-over-end across the arena. The element
 * itself is invisible; the head + handle live on ::before / ::after so the
 * outer transform handles trajectory while the inner sprite reads the spin.
 * The aura behind it pulses with a screen-blend halo for the "muy luminoso"
 * brief — overlapping shadows give the impression of light bleeding past
 * the silhouette. */
.projectile-golden_hammer {
  width: 56px;
  height: 56px;
  background: transparent;
  box-shadow: none;
  border-radius: 0;
  animation: hammer-fly var(--dur, 480ms) cubic-bezier(0.25, 0.55, 0.4, 1)
    forwards;
  filter: drop-shadow(0 0 18px #ffe066)
    drop-shadow(0 0 36px rgba(255, 220, 80, 0.7));
}
.projectile-golden_hammer::before {
  /* Handle: vertical bar with metallic warm-gold gradient, slight bevel. */
  content: "";
  position: absolute;
  left: 50%;
  top: 18px;
  width: 8px;
  height: 38px;
  margin-left: -4px;
  background: linear-gradient(
    90deg,
    #6a3f0a 0%,
    #c98e1a 30%,
    #ffe066 50%,
    #c98e1a 70%,
    #6a3f0a 100%
  );
  border-radius: 2px;
  box-shadow:
    0 0 6px #ffd54a,
    inset 0 0 2px #fff7c0;
}
.projectile-golden_hammer::after {
  /* Head: chunky rectangle with a fiery-yellow gradient and stacked glows. */
  content: "";
  position: absolute;
  left: 50%;
  top: 0;
  width: 48px;
  height: 22px;
  margin-left: -24px;
  background: radial-gradient(
    ellipse at 30% 28%,
    #ffffff 0%,
    #fff7c0 18%,
    #ffe066 38%,
    #f0a818 70%,
    #8a4a08 100%
  );
  border-radius: var(--r-xs);
  box-shadow:
    0 0 14px #fff19a,
    0 0 28px #ffd54a,
    0 0 48px rgba(255, 200, 60, 0.85),
    inset 0 0 10px #fff8d8,
    inset 0 -3px 6px rgba(120, 60, 0, 0.45);
}
@keyframes hammer-fly {
  0% {
    transform: translate(-50%, -50%) rotate(-30deg) scale(0.55);
    opacity: 0;
  }
  10% {
    opacity: 1;
  }
  35% {
    transform: translate(
        calc(-50% + var(--dx) * 0.32),
        calc(-50% + var(--dy) * 0.32 - 38px)
      )
      rotate(360deg) scale(1.1);
  }
  70% {
    transform: translate(
        calc(-50% + var(--dx) * 0.7),
        calc(-50% + var(--dy) * 0.7 - 18px)
      )
      rotate(720deg) scale(1.05);
  }
  100% {
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy)))
      rotate(1080deg) scale(1);
    opacity: 1;
  }
}

/* Piercing arrow — Archer "Flecha Perforante" passive proc. A silvered bolt:
 * thinner than the precision shot and lit from within (white-hot core, cold
 * blue accents) so it reads as armor-piercing rather than brutalist. Same
 * arrow silhouette as .projectile-arrow but stretched horizontally with a
 * stark white tracer along the shaft; the ::after is a fading cyan halo. */
.projectile-piercing_arrow {
  width: 92px;
  height: 14px;
  border-radius: 0;
  background: linear-gradient(
    90deg,
    #1a2840 0%,
    #4a6a92 25%,
    #c8e4ff 60%,
    #ffffff 92%,
    #ffffff 100%
  );
  box-shadow:
    0 0 8px rgba(220, 240, 255, 0.95),
    0 0 18px rgba(120, 180, 255, 0.8),
    0 0 36px rgba(60, 120, 220, 0.55);
  filter: drop-shadow(0 0 6px #d8ecff) drop-shadow(0 0 14px #4a78c8);
  clip-path: polygon(
    0 35%,
    12% 35%,
    12% 0%,
    22% 0%,
    22% 35%,
    82% 35%,
    82% 0%,
    100% 50%,
    82% 100%,
    82% 65%,
    22% 65%,
    22% 100%,
    12% 100%,
    12% 65%,
    0 65%
  );
  animation: arrow-fly var(--dur, 360ms) cubic-bezier(0.25, 0.05, 0.4, 1)
    forwards;
}
.projectile-piercing_arrow::before {
  /* Bright white tracer along the shaft. */
  content: "";
  position: absolute;
  left: 5%;
  right: 5%;
  top: 50%;
  height: 3px;
  margin-top: -1.5px;
  background: linear-gradient(
    90deg,
    rgba(200, 220, 255, 0) 0%,
    rgba(255, 255, 255, 1) 70%,
    rgba(255, 255, 255, 1) 100%
  );
  border-radius: 1.5px;
  box-shadow: 0 0 6px #ffffff;
  pointer-events: none;
}
.projectile-piercing_arrow::after {
  /* Cyan vapor trail behind the bolt. */
  content: "";
  position: absolute;
  right: 80%;
  top: 50%;
  width: 88px;
  height: 18px;
  margin-top: -9px;
  background: linear-gradient(
    90deg,
    rgba(50, 90, 180, 0) 0%,
    rgba(120, 180, 255, 0.5) 35%,
    rgba(200, 230, 255, 0.85) 80%,
    rgba(255, 255, 255, 0.95) 100%
  );
  border-radius: 50% 0 0 50%;
  filter: blur(3px);
  pointer-events: none;
}

/* Blood Pact — Demon Hunter passive proc. A crimson swirl projectile dripping
 * sanguine vapor that crashes into the target. The body is a fat globule of
 * dark blood with a near-white hot center; the trailing ::after is a long
 * smear of darker arterial red that fades behind it as it flies. */
.projectile-blood_pact {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: radial-gradient(
    circle at 40% 35%,
    #ffe8e8 0%,
    #ff5060 20%,
    #c00820 45%,
    #5a0210 80%,
    #1a0004 100%
  );
  box-shadow:
    0 0 10px rgba(255, 80, 80, 0.9),
    0 0 22px rgba(200, 20, 40, 0.85),
    0 0 44px rgba(90, 0, 20, 0.7),
    inset 0 0 8px rgba(40, 0, 10, 0.6);
  filter: drop-shadow(0 0 8px rgba(255, 120, 140, 0.6))
    drop-shadow(0 0 18px rgba(120, 0, 30, 0.8));
  animation: proj-fly var(--dur, 360ms) cubic-bezier(0.3, 0.05, 0.4, 1) forwards;
}
.projectile-blood_pact::before {
  /* Spinning blood swirl inside the globule. */
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  background: conic-gradient(
    from 0deg,
    rgba(255, 80, 80, 0.85) 0%,
    rgba(120, 0, 20, 0.95) 35%,
    rgba(255, 80, 80, 0.85) 70%,
    rgba(120, 0, 20, 0.95) 100%
  );
  filter: blur(1.5px);
  animation: blood-swirl 0.4s linear infinite;
}
.projectile-blood_pact::after {
  /* Trailing smear. */
  content: "";
  position: absolute;
  right: 70%;
  top: 50%;
  width: 60px;
  height: 22px;
  margin-top: -11px;
  background: linear-gradient(
    90deg,
    rgba(50, 0, 10, 0) 0%,
    rgba(120, 10, 30, 0.55) 40%,
    rgba(200, 20, 40, 0.8) 80%,
    rgba(255, 90, 100, 0.9) 100%
  );
  border-radius: 50% 0 0 50%;
  filter: blur(4px);
  pointer-events: none;
}
@keyframes blood-swirl {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}

/* ===== Juicio Divino (Paladin Divine Judgement) ===== */

/* Column of warm white-gold light descending from above onto the target.
 * The beam itself is a tall narrow gradient; ::before is the impact crown
 * at the bottom, ::after is a fading bloom ring around it. The whole effect
 * fades out within ~600ms so the damage flash that follows can take the
 * stage cleanly. */
.holy-beam-fx {
  position: absolute;
  width: 68px;
  height: 320px;
  pointer-events: none;
  transform: translate(-50%, -100%);
  z-index: 7;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 200, 0) 0%,
    rgba(255, 245, 170, 0.55) 15%,
    rgba(255, 240, 150, 0.85) 35%,
    rgba(255, 230, 110, 0.95) 65%,
    rgba(255, 200, 60, 0.7) 92%,
    rgba(255, 180, 30, 0) 100%
  );
  filter: drop-shadow(0 0 18px #fff19a)
    drop-shadow(0 0 36px rgba(255, 215, 80, 0.85));
  mix-blend-mode: screen;
  animation: holy-beam-pulse 600ms ease-out forwards;
}
.holy-beam-fx::before {
  /* Impact crown — concentric arcs at the bottom of the column. */
  content: "";
  position: absolute;
  left: 50%;
  bottom: -28px;
  width: 140px;
  height: 56px;
  margin-left: -70px;
  background: radial-gradient(
    ellipse at center bottom,
    rgba(255, 250, 200, 0.9) 0%,
    rgba(255, 220, 100, 0.7) 35%,
    rgba(255, 180, 30, 0) 75%
  );
  border-radius: 50%;
  filter: blur(2px);
  animation: holy-beam-crown 600ms ease-out forwards;
}
.holy-beam-fx::after {
  /* Outer bloom — wider, softer halo. */
  content: "";
  position: absolute;
  left: 50%;
  bottom: -48px;
  width: 220px;
  height: 100px;
  margin-left: -110px;
  background: radial-gradient(
    ellipse at center,
    rgba(255, 240, 150, 0.45) 0%,
    rgba(255, 200, 60, 0.25) 50%,
    rgba(255, 180, 30, 0) 100%
  );
  border-radius: 50%;
  filter: blur(6px);
  animation: holy-beam-bloom 600ms ease-out forwards;
}
@keyframes holy-beam-pulse {
  0% {
    opacity: 0;
    transform: translate(-50%, -100%) scaleX(0.4);
  }
  20% {
    opacity: 1;
    transform: translate(-50%, -100%) scaleX(1.05);
  }
  85% {
    opacity: 0.9;
    transform: translate(-50%, -100%) scaleX(1);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -100%) scaleX(0.85);
  }
}
@keyframes holy-beam-crown {
  0% {
    opacity: 0;
    transform: scale(0.3);
  }
  30% {
    opacity: 1;
    transform: scale(1.1);
  }
  100% {
    opacity: 0;
    transform: scale(1.6);
  }
}
@keyframes holy-beam-bloom {
  0% {
    opacity: 0;
    transform: scale(0.4);
  }
  35% {
    opacity: 0.85;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(1.5);
  }
}
.holy-cast-halo {
  position: absolute;
  width: 100px;
  height: 100px;
  margin-left: -50px;
  margin-top: -50px;
  pointer-events: none;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(255, 240, 150, 0.55) 0%,
    rgba(255, 200, 60, 0.25) 45%,
    rgba(255, 180, 30, 0) 80%
  );
  filter: blur(3px);
  mix-blend-mode: screen;
  animation: holy-cast-halo 420ms ease-out forwards;
  z-index: 5;
}
@keyframes holy-cast-halo {
  0% {
    opacity: 0;
    transform: scale(0.6);
  }
  40% {
    opacity: 1;
    transform: scale(1.15);
  }
  100% {
    opacity: 0;
    transform: scale(1.4);
  }
}

/* ===== Embestida (Warrior Charge) ===== */

/* Horizontal motion-blurred shoulder bash anchored on the defender. The
 * --charge-dir CSS var (+1 attacker-on-left, -1 attacker-on-right) flips the
 * chevrons so the streak always reads as coming from the attacker. */
.embestida-fx {
  position: absolute;
  width: 180px;
  height: 60px;
  pointer-events: none;
  transform: translate(-50%, -50%) scaleX(var(--charge-dir, 1));
  z-index: 6;
  animation: embestida-flash 480ms ease-out forwards;
}
.embestida-fx::before {
  /* Motion-blurred streak — a long horizontal gradient flying into the target. */
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  height: 18px;
  margin-top: -9px;
  background: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 240, 200, 0.6) 35%,
    rgba(255, 200, 100, 0.95) 70%,
    rgba(255, 100, 40, 0.9) 100%
  );
  filter: blur(3px) drop-shadow(0 0 8px rgba(255, 180, 80, 0.85));
  border-radius: 0 50% 50% 0;
  animation: embestida-streak 480ms ease-out forwards;
}
.embestida-fx::after {
  /* Impact chevrons — three slashes stacked at the target end. */
  content: "";
  position: absolute;
  right: 6px;
  top: 50%;
  width: 36px;
  height: 56px;
  margin-top: -28px;
  background:
    linear-gradient(
      120deg,
      transparent 30%,
      rgba(255, 250, 220, 0.95) 50%,
      transparent 70%
    ),
    linear-gradient(
      120deg,
      transparent 30%,
      rgba(255, 180, 80, 0.85) 50%,
      transparent 70%
    ),
    linear-gradient(
      120deg,
      transparent 30%,
      rgba(220, 60, 30, 0.85) 50%,
      transparent 70%
    );
  background-size: 100% 18px;
  background-position:
    0 0,
    0 20px,
    0 40px;
  background-repeat: no-repeat;
  filter: drop-shadow(0 0 6px rgba(255, 200, 100, 0.9));
}
@keyframes embestida-flash {
  0% {
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  90% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
@keyframes embestida-streak {
  0% {
    transform: translateX(-60%) scaleX(0.4);
    opacity: 0;
  }
  25% {
    transform: translateX(-20%) scaleX(0.9);
    opacity: 1;
  }
  100% {
    transform: translateX(8%) scaleX(1);
    opacity: 0.85;
  }
}

/* ===== Trampa de Osos (Pathfinder Bear Trap) ===== */

/* Two serrated jaws drop in, then snap together (rotate inward) and rattle
 * briefly before fading. The .trap-plate is the base plate of the trap. */
.bear-trap-fx {
  position: absolute;
  width: 90px;
  height: 60px;
  pointer-events: none;
  transform: translate(-50%, -50%);
  z-index: 6;
  animation: bear-trap-shake 700ms ease-out forwards;
}
.bear-trap-fx .trap-plate {
  position: absolute;
  left: 50%;
  top: 38px;
  width: 88px;
  height: 14px;
  margin-left: -44px;
  background: linear-gradient(180deg, #5e6878 0%, #2c333f 60%, #14181f 100%);
  border-radius: 4px;
  box-shadow:
    0 4px 6px rgba(0, 0, 0, 0.65),
    inset 0 1px 2px rgba(255, 255, 255, 0.25);
}
.bear-trap-fx .trap-jaw {
  position: absolute;
  width: 44px;
  height: 36px;
  top: 12px;
  background: linear-gradient(180deg, #c8cdd8 0%, #7e8694 60%, #2a313c 100%);
  clip-path: polygon(
    0 100%,
    8% 75%,
    18% 55%,
    30% 38%,
    44% 24%,
    58% 14%,
    72% 8%,
    86% 6%,
    100% 12%,
    100% 22%,
    86% 22%,
    74% 26%,
    60% 34%,
    48% 44%,
    38% 56%,
    28% 70%,
    18% 84%,
    8% 96%
  );
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.6));
}
.bear-trap-fx .trap-jaw-left {
  left: 4px;
  transform-origin: 8% 90%;
  animation: trap-jaw-left 700ms cubic-bezier(0.4, 0.04, 0.4, 1) forwards;
}
.bear-trap-fx .trap-jaw-right {
  right: 4px;
  transform: scaleX(-1);
  transform-origin: 92% 90%;
  animation: trap-jaw-right 700ms cubic-bezier(0.4, 0.04, 0.4, 1) forwards;
}
@keyframes trap-jaw-left {
  0% {
    transform: rotate(-95deg);
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
  45% {
    transform: rotate(-95deg);
  }
  60% {
    transform: rotate(-8deg);
  }
  72% {
    transform: rotate(-22deg);
  }
  100% {
    transform: rotate(-14deg);
    opacity: 0.95;
  }
}
@keyframes trap-jaw-right {
  0% {
    transform: scaleX(-1) rotate(-95deg);
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
  45% {
    transform: scaleX(-1) rotate(-95deg);
  }
  60% {
    transform: scaleX(-1) rotate(-8deg);
  }
  72% {
    transform: scaleX(-1) rotate(-22deg);
  }
  100% {
    transform: scaleX(-1) rotate(-14deg);
    opacity: 0.95;
  }
}
@keyframes bear-trap-shake {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) translateY(-30px);
  }
  20% {
    opacity: 1;
    transform: translate(-50%, -50%) translateY(0);
  }
  62% {
    transform: translate(-50%, -50%) translate(-3px, 0);
  }
  68% {
    transform: translate(-50%, -50%) translate(3px, 0);
  }
  74% {
    transform: translate(-50%, -50%) translate(-2px, 0);
  }
  80% {
    transform: translate(-50%, -50%) translate(2px, 0);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -50%);
  }
}

/* ===== Pacto de Sangre (Demon Hunter Blood Pact) ===== */

/* Crimson swirl bursts off the demon hunter when they pay the self-cost. The
 * .projectile-blood_pact above does the actual flight; this is the cast-side
 * "I just cut myself" tell. */
.blood-pact-fx {
  position: absolute;
  width: 80px;
  height: 80px;
  pointer-events: none;
  transform: translate(-50%, -50%);
  z-index: 5;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    rgba(255, 80, 80, 0.85) 0%,
    rgba(180, 10, 30, 0.7) 45%,
    rgba(60, 0, 10, 0) 80%
  );
  filter: blur(2px) drop-shadow(0 0 14px rgba(220, 30, 50, 0.8));
  animation: blood-pact-burst 420ms ease-out forwards;
}
.blood-pact-fx::before,
.blood-pact-fx::after {
  content: "";
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  border: 2px solid rgba(255, 100, 120, 0.7);
  animation: blood-pact-ring 420ms ease-out forwards;
}
.blood-pact-fx::after {
  inset: -14px;
  border-color: rgba(160, 20, 40, 0.5);
  animation-delay: 80ms;
}
@keyframes blood-pact-burst {
  0% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.4);
  }
  35% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1.15);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -50%) scale(0.85);
  }
}
@keyframes blood-pact-ring {
  0% {
    opacity: 0.95;
    transform: scale(0.5);
  }
  100% {
    opacity: 0;
    transform: scale(1.6);
  }
}

/* ===== melee slash ===== */

/* Diagonal gash flashed over the target on a melee physical hit. The cut
 * uses two crossing strokes (white-hot core + colored edge); --slash-dir
 * mirrors the angle so the cut always reads as coming from the attacker. */
.slash-fx {
  position: absolute;
  width: 80px;
  height: 80px;
  pointer-events: none;
  transform: translate(-50%, -50%) rotate(calc(-30deg * var(--slash-dir, 1)));
  z-index: 6;
  animation: slash-flash 360ms ease-out forwards;
}
.slash-fx::before,
.slash-fx::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 90px;
  height: 4px;
  transform: translate(-50%, -50%);
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 240, 220, 0) 8%,
    rgba(255, 240, 220, 0.95) 30%,
    #ffffff 50%,
    rgba(255, 240, 220, 0.95) 70%,
    transparent 92%
  );
  box-shadow:
    0 0 8px rgba(255, 200, 180, 0.95),
    0 0 18px rgba(255, 80, 60, 0.8),
    0 0 32px rgba(180, 20, 20, 0.55);
  border-radius: 2px;
}
.slash-fx::after {
  height: 2px;
  transform: translate(-50%, -50%) rotate(calc(20deg * var(--slash-dir, 1)));
  opacity: 0.8;
}
.slash-fx-crit {
  width: 110px;
  height: 110px;
  filter: drop-shadow(0 0 6px #ffd040);
}
.slash-fx-crit::before,
.slash-fx-crit::after {
  width: 130px;
  height: 6px;
  box-shadow:
    0 0 10px #fff4c0,
    0 0 24px #ff9020,
    0 0 44px #ff3010;
}
@keyframes slash-flash {
  0% {
    transform: translate(-50%, -50%) rotate(calc(-50deg * var(--slash-dir, 1)))
      scale(0.4);
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
  60% {
    transform: translate(-50%, -50%) rotate(calc(-15deg * var(--slash-dir, 1)))
      scale(1.05);
    opacity: 0.95;
  }
  100% {
    transform: translate(-50%, -50%) rotate(calc(0deg * var(--slash-dir, 1)))
      scale(1.2);
    opacity: 0;
  }
}

/* ===== heavy axe chop (Warrior Hachazo) =====
 * --chop-dir: attacker on the left → +1, right → -1. ::before is the wooden
 * handle, ::after is the bloody axe-head (curved bit silhouette with red
 * streaks layered over steel). Blood-red drop-shadows replace the warm glow
 * so the sprite reads as a freshly-used bloodied axe. */
.axe-chop-fx {
  position: absolute;
  width: 112px;
  height: 112px;
  pointer-events: none;
  transform: translate(-50%, -50%);
  z-index: 6;
  animation: axe-chop-swing 520ms cubic-bezier(0.32, 0.2, 0.45, 1) forwards;
  filter: drop-shadow(0 0 6px rgba(255, 80, 50, 0.55))
    drop-shadow(0 2px 14px rgba(120, 8, 4, 0.75));
}
.axe-chop-fx::before {
  /* Handle: wooden shaft, darker near the head where blood pooled. */
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 11px;
  height: 70px;
  margin-left: -5.5px;
  margin-top: -4px;
  background:
    linear-gradient(180deg, rgba(80, 6, 4, 0.55) 0%, transparent 30%),
    linear-gradient(
      90deg,
      #2a1607 0%,
      #5e3510 25%,
      #b07a32 50%,
      #5e3510 75%,
      #2a1607 100%
    );
  border-radius: 3px;
  box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.7);
  transform-origin: top center;
  transform: rotate(calc(-22deg * var(--chop-dir, 1)));
}
.axe-chop-fx::after {
  /* Head: curved axe-bit clip-path (poll on the left, cutting edge bulging
   * to the right). Background layers: a diagonal blood smear, a vertical
   * blood drip pooling along the edge, then the steel base. scaleX(--chop-dir)
   * flips the bit so it always faces away from the attacker. */
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 78px;
  height: 56px;
  margin-left: -39px;
  margin-top: -46px;
  background:
    linear-gradient(
      135deg,
      transparent 45%,
      rgba(170, 18, 10, 0.7) 70%,
      rgba(70, 4, 4, 0.9) 100%
    ),
    linear-gradient(
      180deg,
      transparent 38%,
      rgba(150, 16, 8, 0.55) 65%,
      rgba(80, 6, 4, 0.85) 100%
    ),
    linear-gradient(180deg, #f4f8ff 0%, #cdd5e3 28%, #8a93a5 68%, #4d525f 100%);
  clip-path: polygon(
    6% 32%,
    18% 14%,
    38% 4%,
    62% 0%,
    85% 14%,
    100% 38%,
    100% 62%,
    85% 86%,
    62% 100%,
    38% 96%,
    18% 86%,
    6% 68%
  );
  box-shadow:
    0 0 10px rgba(255, 90, 50, 0.55),
    0 0 22px rgba(140, 10, 5, 0.7),
    inset 0 0 8px rgba(40, 0, 0, 0.55),
    inset 0 -5px 8px rgba(120, 10, 5, 0.75);
  transform: scaleX(var(--chop-dir, 1));
}
@keyframes axe-chop-swing {
  0% {
    transform: translate(-50%, -90%) rotate(calc(-95deg * var(--chop-dir, 1)))
      scale(0.55);
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
  55% {
    transform: translate(-50%, -55%) rotate(calc(-25deg * var(--chop-dir, 1)))
      scale(1.08);
    opacity: 1;
  }
  75% {
    transform: translate(-50%, -50%) rotate(calc(5deg * var(--chop-dir, 1)))
      scale(1.15);
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -50%) rotate(calc(15deg * var(--chop-dir, 1)))
      scale(1.05);
    opacity: 0;
  }
}

/* Blood drips flung from the axe-head when the chop lands. Each drop is
 * positioned around the impact and animates a short fall + fade. --drip-dx
 * and --drip-dy control horizontal spray and gravity per drop. */
.axe-blood-drip {
  position: absolute;
  width: 7px;
  height: 12px;
  border-radius: 50% 50% 40% 40% / 35% 35% 65% 65%;
  background: radial-gradient(
    circle at 50% 28%,
    #d8180e 0%,
    #8a0a06 55%,
    #3a0202 100%
  );
  box-shadow: 0 0 5px rgba(140, 10, 5, 0.75);
  pointer-events: none;
  z-index: 6;
  transform: translate(-50%, -50%);
  animation: axe-blood-fall 480ms cubic-bezier(0.4, 0.05, 0.55, 1) forwards;
}
@keyframes axe-blood-fall {
  0% {
    transform: translate(-50%, -50%) scaleY(0.4);
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  100% {
    transform: translate(
        calc(-50% + var(--drip-dx, 0px)),
        calc(-50% + var(--drip-dy, 32px))
      )
      scaleY(1.5);
    opacity: 0;
  }
}

/* ===== cartoon hit burst (stars + POW bubble) =====
 * Spawned by Sprites.spawnHitStars; intensity (damage / maxHP) drives star
 * count, size, distance, and whether a POW bubble appears. The container
 * .hit-burst is positioned at the impact point; children are absolutely
 * positioned relative to it and animate outward via --dx/--dy. */
.hit-burst {
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 7;
}
.hit-star {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--sz, 16px);
  height: var(--sz, 16px);
  margin-left: calc(var(--sz, 16px) * -0.5);
  margin-top: calc(var(--sz, 16px) * -0.5);
  background: radial-gradient(
    circle at 35% 30%,
    #fff7c8 0%,
    #ffd84a 45%,
    #f59e0b 85%
  );
  clip-path: polygon(
    50% 0%,
    61% 35%,
    98% 35%,
    68% 57%,
    79% 91%,
    50% 70%,
    21% 91%,
    32% 57%,
    2% 35%,
    39% 35%
  );
  filter: drop-shadow(0 0 4px rgba(255, 200, 80, 0.85));
  animation: hit-star-fly var(--dur, 700ms) cubic-bezier(0.18, 0.7, 0.4, 1)
    forwards;
}
.hit-star-big {
  background: radial-gradient(
    circle at 35% 30%,
    #ffffff 0%,
    #fff19a 35%,
    #ffb840 80%,
    #d96a00 100%
  );
  filter: drop-shadow(0 0 6px rgba(255, 220, 120, 0.95));
}
/* Tweety-style swirling birds for huge hits — two arc lines + a dot. */
.hit-bird {
  background: none;
  clip-path: none;
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.5));
}
.hit-bird::before,
.hit-bird::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--sz, 16px) * 0.7);
  height: calc(var(--sz, 16px) * 0.45);
  border: 2px solid #1a1a1a;
  border-color: #1a1a1a transparent transparent transparent;
  border-radius: 50%;
  transform: translate(-100%, -50%);
}
.hit-bird::after {
  transform: translate(0%, -50%);
}

/* Color variants for hit-star bursts. Driven by spawnHitStars `variant` opt:
 * phys=red, mag=blue, crit=orange, dodge=white, brown=block. Each variant
 * overrides both the small and big star palettes plus the drop-shadow glow,
 * but leaves the star clip-path/animation alone. */
.hit-burst-phys .hit-star {
  background: radial-gradient(
    circle at 35% 30%,
    #ffe1e1 0%,
    #ff5757 45%,
    #b71212 85%
  );
  filter: drop-shadow(0 0 4px rgba(255, 90, 90, 0.9));
}
.hit-burst-phys .hit-star-big {
  background: radial-gradient(
    circle at 35% 30%,
    #ffffff 0%,
    #ffa0a0 35%,
    #ff3838 80%,
    #850000 100%
  );
  filter: drop-shadow(0 0 6px rgba(255, 110, 110, 0.95));
}
.hit-burst-mag .hit-star {
  background: radial-gradient(
    circle at 35% 30%,
    #e0f0ff 0%,
    #5aa7ff 45%,
    #1748b8 85%
  );
  filter: drop-shadow(0 0 4px rgba(110, 170, 255, 0.9));
}
.hit-burst-mag .hit-star-big {
  background: radial-gradient(
    circle at 35% 30%,
    #ffffff 0%,
    #a8d0ff 35%,
    #3a80ff 80%,
    #0a2b85 100%
  );
  filter: drop-shadow(0 0 6px rgba(130, 180, 255, 0.95));
}
.hit-burst-crit .hit-star {
  background: radial-gradient(
    circle at 35% 30%,
    #ffe8c0 0%,
    #ff9c2e 45%,
    #c44a00 85%
  );
  filter: drop-shadow(0 0 5px rgba(255, 150, 50, 0.95));
}
.hit-burst-crit .hit-star-big {
  background: radial-gradient(
    circle at 35% 30%,
    #ffffff 0%,
    #ffc878 35%,
    #ff7a14 80%,
    #8a2e00 100%
  );
  filter: drop-shadow(0 0 7px rgba(255, 160, 60, 1));
}
.hit-burst-dodge .hit-star {
  background: radial-gradient(
    circle at 35% 30%,
    #ffffff 0%,
    #f4f8ff 50%,
    #b8c4d8 90%
  );
  filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.95));
}
.hit-burst-dodge .hit-star-big {
  background: radial-gradient(
    circle at 35% 30%,
    #ffffff 0%,
    #e8eef8 40%,
    #a0adc4 85%,
    #6d7a90 100%
  );
  filter: drop-shadow(0 0 7px rgba(255, 255, 255, 1));
}
.hit-burst-block .hit-star {
  background: radial-gradient(
    circle at 35% 30%,
    #f0d8a8 0%,
    #b07020 45%,
    #5a3208 85%
  );
  filter: drop-shadow(0 0 4px rgba(180, 110, 40, 0.85));
}
.hit-burst-block .hit-star-big {
  background: radial-gradient(
    circle at 35% 30%,
    #ffe8b8 0%,
    #c88030 35%,
    #834612 80%,
    #3a1c02 100%
  );
  filter: drop-shadow(0 0 6px rgba(200, 130, 50, 0.95));
}

@keyframes hit-star-fly {
  0% {
    transform: translate(0, 0) rotate(0deg) scale(0.3);
    opacity: 0;
  }
  18% {
    opacity: 1;
    transform: translate(calc(var(--dx) * 0.25), calc(var(--dy) * 0.25 - 4px))
      rotate(calc(var(--rot) * 0.2)) scale(1.05);
  }
  70% {
    opacity: 1;
    transform: translate(calc(var(--dx) * 0.92), calc(var(--dy) * 0.92))
      rotate(calc(var(--rot) * 0.85)) scale(1);
  }
  100% {
    opacity: 0;
    transform: translate(var(--dx), calc(var(--dy) + 14px)) rotate(var(--rot))
      scale(0.6);
  }
}
.hit-ring {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--ring-sz, 80px);
  height: var(--ring-sz, 80px);
  margin-left: calc(var(--ring-sz, 80px) * -0.5);
  margin-top: calc(var(--ring-sz, 80px) * -0.5);
  border: 3px solid rgba(255, 235, 180, 0.85);
  border-radius: 50%;
  box-shadow:
    0 0 18px rgba(255, 200, 100, 0.7),
    inset 0 0 12px rgba(255, 220, 140, 0.5);
  animation: hit-ring-expand 380ms ease-out forwards;
}
@keyframes hit-ring-expand {
  0% {
    transform: scale(0.15);
    opacity: 0;
  }
  30% {
    opacity: 0.9;
  }
  100% {
    transform: scale(1);
    opacity: 0;
  }
}
.hit-pow {
  position: absolute;
  left: 0;
  top: 0;
  transform: translate(-50%, -120%) rotate(var(--pow-rot, 0deg))
    scale(var(--pow-scale, 1));
  font-family: "Bangers", "Impact", system-ui, sans-serif;
  font-weight: 900;
  font-size: 1.9em;
  letter-spacing: 0.04em;
  color: #fff3a8;
  background: radial-gradient(
    circle at 35% 30%,
    #ffeb6b 0%,
    #ffb834 60%,
    #c95a05 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  text-shadow:
    2px 2px 0 #1a0e00,
    -2px 2px 0 #1a0e00,
    2px -2px 0 #1a0e00,
    -2px -2px 0 #1a0e00,
    0 0 14px rgba(255, 180, 60, 0.7);
  paint-order: stroke fill;
  -webkit-text-stroke: 3px #1a0e00;
  filter: drop-shadow(0 3px 0 rgba(0, 0, 0, 0.5));
  animation: hit-pow-pop 720ms cubic-bezier(0.2, 1.8, 0.4, 1) forwards;
  white-space: nowrap;
}
@keyframes hit-pow-pop {
  0% {
    transform: translate(-50%, -120%) rotate(var(--pow-rot, 0deg)) scale(0.1);
    opacity: 0;
  }
  30% {
    transform: translate(-50%, -130%) rotate(var(--pow-rot, 0deg))
      scale(calc(var(--pow-scale, 1) * 1.25));
    opacity: 1;
  }
  60% {
    transform: translate(-50%, -130%) rotate(var(--pow-rot, 0deg))
      scale(var(--pow-scale, 1));
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -160%) rotate(var(--pow-rot, 0deg))
      scale(calc(var(--pow-scale, 1) * 0.9));
    opacity: 0;
  }
}

/* ===== defense impact / shield sparks =====
 * Spawned by Sprites.spawnDefenseSparks when the defender's Def shaved a
 * non-trivial slice off an incoming hit (or fully blocked it). The .def-burst
 * container is anchored to the impact point on the defender's near side;
 * .def-spark children fly out in a cone away from the attacker, .def-shield-
 * ring expands from the same point, and .def-shield-flash brightens the whole
 * defender sprite for one frame on tier 2+. Tiers (low/mid/high/block) are
 * driven by JS — CSS just receives knobs through CSS vars. */
.def-burst {
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 8;
}
.def-spark {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--sz, 4px);
  height: var(--sz, 4px);
  margin-left: calc(var(--sz, 4px) * -0.5);
  margin-top: calc(var(--sz, 4px) * -0.5);
  background: var(--core, #e8eef7);
  border-radius: 50%;
  box-shadow: 0 0 6px var(--glow, rgba(255, 235, 170, 0.85));
  animation: def-spark-fly var(--dur, 280ms) cubic-bezier(0.2, 0.6, 0.4, 1)
    forwards;
}
.def-spark-long {
  width: calc(var(--sz, 4px) * 4);
  height: calc(var(--sz, 4px) * 0.6);
  border-radius: 1px;
  transform: rotate(var(--ang, 0));
  margin-left: calc(var(--sz, 4px) * -2);
  margin-top: calc(var(--sz, 4px) * -0.3);
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--core, #e8eef7) 30%,
    var(--core, #e8eef7) 70%,
    transparent 100%
  );
}
.def-spark-accent {
  box-shadow:
    0 0 10px var(--core, #fff4b3),
    0 0 4px #fff;
}
@keyframes def-spark-fly {
  0% {
    transform: translate(0, 0) scale(0.4);
    opacity: 0;
  }
  15% {
    opacity: 1;
    transform: translate(calc(var(--dx) * 0.18), calc(var(--dy) * 0.18))
      scale(1.1);
  }
  100% {
    opacity: 0;
    transform: translate(var(--dx), calc(var(--dy) + 12px)) scale(0.5);
  }
}
.def-spark-long {
  /* Long sparks share the same flight curve but keep the rotation set by --ang. */
  animation-name: def-spark-fly-long;
}
@keyframes def-spark-fly-long {
  0% {
    transform: rotate(var(--ang, 0)) translate(0, 0);
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: rotate(var(--ang, 0)) translate(calc(var(--dx) * 0.7), 0);
  }
}
.def-shield-ring {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--ring-sz, 80px);
  height: var(--ring-sz, 80px);
  margin-left: calc(var(--ring-sz, 80px) * -0.5);
  margin-top: calc(var(--ring-sz, 80px) * -0.5);
  border: 2px solid var(--ring-color, rgba(255, 235, 180, 0.9));
  border-radius: 50%;
  box-shadow:
    0 0 10px var(--ring-color, rgba(255, 235, 180, 0.7)),
    inset 0 0 6px var(--ring-color, rgba(255, 235, 180, 0.4));
  animation: def-ring-expand 320ms ease-out forwards;
}
.def-shield-ring-2 {
  animation-delay: 80ms;
  animation-duration: 380ms;
  border-width: 1px;
  opacity: 0.7;
}
@keyframes def-ring-expand {
  0% {
    transform: scale(0.2);
    opacity: 0;
  }
  25% {
    opacity: 0.95;
  }
  100% {
    transform: scale(1);
    opacity: 0;
  }
}
.def-shield-flash {
  position: absolute;
  width: var(--flash-size, 100px);
  height: var(--flash-size, 100px);
  margin-left: calc(var(--flash-size, 100px) * -0.5);
  margin-top: calc(var(--flash-size, 100px) * -0.5);
  border-radius: 50%;
  pointer-events: none;
  z-index: 7;
  background: radial-gradient(
    circle,
    rgba(255, 255, 255, var(--flash-alpha, 0.5)) 0%,
    rgba(255, 255, 255, 0) 65%
  );
  mix-blend-mode: screen;
  animation: def-flash 220ms ease-out forwards;
}
.def-shield-flash-mag {
  background: radial-gradient(
    circle,
    rgba(200, 180, 255, var(--flash-alpha, 0.5)) 0%,
    rgba(120, 200, 255, calc(var(--flash-alpha, 0.5) * 0.6)) 30%,
    rgba(0, 0, 0, 0) 65%
  );
}
@keyframes def-flash {
  0% {
    opacity: 0;
    transform: scale(0.7);
  }
  20% {
    opacity: 1;
    transform: scale(1.05);
  }
  100% {
    opacity: 0;
    transform: scale(1.15);
  }
}
/* Screen shake driven by amplitude --shake-amp; toggled on/off via the
 * .def-shake class on #combat-stage (or the arena). */
.def-shake {
  animation: def-shake-kf 140ms steps(6, end);
}
@keyframes def-shake-kf {
  0% {
    transform: translate(0, 0);
  }
  16% {
    transform: translate(
      calc(var(--shake-amp, 3px) * -1),
      calc(var(--shake-amp, 3px) * 0.4)
    );
  }
  33% {
    transform: translate(
      calc(var(--shake-amp, 3px) * 0.7),
      calc(var(--shake-amp, 3px) * -0.6)
    );
  }
  50% {
    transform: translate(
      calc(var(--shake-amp, 3px) * -0.5),
      calc(var(--shake-amp, 3px) * 0.5)
    );
  }
  66% {
    transform: translate(
      calc(var(--shake-amp, 3px) * 0.4),
      calc(var(--shake-amp, 3px) * -0.3)
    );
  }
  83% {
    transform: translate(
      calc(var(--shake-amp, 3px) * -0.2),
      calc(var(--shake-amp, 3px) * 0.2)
    );
  }
  100% {
    transform: translate(0, 0);
  }
}

/* ===== poison bubble burst =====
 * Spawned by Sprites.spawnPoisonBubbles when a poison tick deals damage. The
 * container .poison-burst sits at the target's center; .poison-bubble children
 * drift upward in a small fan with translucent violet gradient + glow. */
.poison-burst {
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: none;
  z-index: 7;
}
.poison-bubble {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--sz, 12px);
  height: var(--sz, 12px);
  margin-left: calc(var(--sz, 12px) * -0.5);
  margin-top: calc(var(--sz, 12px) * -0.5);
  border-radius: 50%;
  background: radial-gradient(
    circle at 32% 28%,
    rgba(255, 240, 255, 0.9) 0%,
    rgba(220, 160, 255, 0.7) 28%,
    rgba(140, 60, 220, 0.55) 70%,
    rgba(80, 20, 140, 0.45) 100%
  );
  box-shadow:
    0 0 8px rgba(184, 107, 255, 0.7),
    inset 0 0 4px rgba(255, 220, 255, 0.6);
  filter: drop-shadow(0 0 3px rgba(184, 107, 255, 0.85));
  opacity: 0;
  animation: poison-bubble-rise var(--dur, 900ms)
    cubic-bezier(0.25, 0.8, 0.35, 1) var(--delay, 0ms) forwards;
}
@keyframes poison-bubble-rise {
  0% {
    transform: translate(0, 0) scale(0.2);
    opacity: 0;
  }
  18% {
    transform: translate(calc(var(--dx) * 0.2), calc(var(--dy) * 0.2)) scale(1);
    opacity: 0.95;
  }
  70% {
    transform: translate(calc(var(--dx) * 0.85), calc(var(--dy) * 0.85))
      scale(1.05);
    opacity: 0.85;
  }
  100% {
    transform: translate(var(--dx), calc(var(--dy) - 12px)) scale(0.4);
    opacity: 0;
  }
}

/* ===== floating combat text ===== */

.floating-text {
  position: absolute;
  transform: translate(-50%, -50%);
  font-weight: bold;
  font-size: var(--fs-lg);
  pointer-events: none;
  animation: float-up 1.05s ease-out forwards;
  z-index: 6;
  text-shadow:
    1px 1px 0 #000,
    -1px 1px 0 #000,
    1px -1px 0 #000,
    -1px -1px 0 #000;
}
.floating-text.damage {
  color: #ff8888;
}
.floating-text.crit {
  color: var(--crit);
  font-size: var(--fs-xl);
}
.floating-text.heal {
  color: var(--good);
}
.floating-text.dodge {
  color: #88e0ff;
  font-size: 1em;
}
.floating-text.proc {
  color: var(--accent-soft);
  font-size: 0.95em;
}
.floating-text.self {
  color: #ff5060;
  font-size: 0.95em;
  text-shadow: 0 0 4px rgba(180, 10, 30, 0.85);
}
.floating-text.poison {
  color: var(--poison);
  text-shadow:
    0 0 6px rgba(184, 107, 255, 0.55),
    1px 1px 0 #000,
    -1px 1px 0 #000,
    1px -1px 0 #000,
    -1px -1px 0 #000;
}
.floating-text.freeze {
  color: #b6e6ff;
  text-shadow:
    0 0 6px rgba(120, 200, 255, 0.65),
    1px 1px 0 #002a3a,
    -1px 1px 0 #002a3a,
    1px -1px 0 #002a3a,
    -1px -1px 0 #002a3a;
}
.floating-text.bleed {
  color: #ff8aa1;
  text-shadow:
    0 0 6px rgba(220, 38, 78, 0.65),
    1px 1px 0 #2a000a,
    -1px 1px 0 #2a000a,
    1px -1px 0 #2a000a,
    -1px -1px 0 #2a000a;
}
.floating-text.ignite {
  color: #ffb86b;
  text-shadow:
    0 0 6px rgba(255, 140, 50, 0.7),
    1px 1px 0 #2a1500,
    -1px 1px 0 #2a1500,
    1px -1px 0 #2a1500,
    -1px -1px 0 #2a1500;
}
.floating-text.shield {
  color: #b3ecff;
  text-shadow:
    0 0 6px rgba(76, 208, 255, 0.65),
    1px 1px 0 #001f33,
    -1px 1px 0 #001f33,
    1px -1px 0 #001f33,
    -1px -1px 0 #001f33;
}
.floating-text.haste {
  color: #8af0c0;
  text-shadow:
    0 0 6px rgba(120, 230, 180, 0.65),
    1px 1px 0 #002a18,
    -1px 1px 0 #002a18,
    1px -1px 0 #002a18,
    -1px -1px 0 #002a18;
}

@keyframes float-up {
  0% {
    transform: translate(-50%, -50%) scale(0.6);
    opacity: 0;
  }
  20% {
    transform: translate(-50%, -70%) scale(1.1);
    opacity: 1;
  }
  100% {
    transform: translate(-50%, -180%) scale(1);
    opacity: 0;
  }
}

/* ===== event log ===== */

.event-log {
  background: var(--bg);
  border-radius: var(--r-md);
  padding: 10px;
  height: 180px;
  overflow-y: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  line-height: 1.5;
  margin-top: 14px;
}
.event-log .ev {
  padding: 2px 0;
}
.event-log .ev-time {
  color: var(--muted);
  opacity: 0.75;
  margin-right: 4px;
  font-size: var(--fs-sm);
}

/* Combat log stays inline by default — pinned to the left edge would now
 * collide with the new items panel in the app-grid game screen. Use the
 * header toggle (L) to expand/collapse instead. */
.event-log .ev.crit {
  color: var(--crit);
  font-weight: bold;
}
.event-log .ev.dodge {
  color: var(--good);
}
.event-log .ev.dmg {
  color: var(--text);
}
.event-log .ev.poison {
  color: var(--poison);
}
.event-log .ev.bleed {
  color: #ff8aa1;
}
.event-log .ev.ignite {
  color: #ffb86b;
}
.event-log .ev.heal {
  color: var(--good);
}
.event-log .ev.proc {
  color: var(--accent);
}
.event-log .ev.death {
  color: var(--bad);
  font-weight: bold;
}
.event-log .ev.end {
  color: var(--accent);
  font-weight: bold;
  margin-top: 8px;
  border-top: 1px dashed var(--card-soft);
  padding-top: 6px;
}

/* ===== modal overlay (drops + persona share this skin) ===== */

/* :not([hidden]) keeps the UA `[hidden] { display: none }` rule winning over
 * the modal's `display: flex` — no need for !important. */
#drops-card:not([hidden]),
#persona-card:not([hidden]) {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(8, 10, 22, 0.78);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  margin: 0;
  border-radius: 0;
  box-shadow: none;
  animation: drops-modal-fade 180ms ease-out;
}
#drops-container,
#persona-container {
  background: var(--card);
  border-radius: var(--r-xl);
  padding: 22px 26px;
  max-width: 760px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  box-shadow:
    0 20px 60px var(--alpha-black-60),
    0 0 0 1px var(--card-soft);
  animation: drops-modal-pop var(--t-base) cubic-bezier(0.2, 0.9, 0.3, 1.05);
  contain: layout;
}
#persona-container {
  max-width: 860px;
}

.drops-toggle-btn {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 101;
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--card-soft);
  border-radius: var(--r-md);
  padding: 6px 12px;
  font: inherit;
  font-size: 0.85em;
  cursor: pointer;
  box-shadow: 0 4px 12px var(--alpha-black-60);
  transition:
    background var(--t-fast),
    border-color var(--t-fast);
}
.drops-toggle-btn:hover {
  border-color: var(--text);
}
#drops-card.drops-collapsed {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  pointer-events: none;
}
#drops-card.drops-collapsed .drops-toggle-btn {
  pointer-events: auto;
}
#drops-card.drops-collapsed #drops-container {
  display: none;
}

@keyframes drops-modal-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
@keyframes drops-modal-pop {
  from {
    transform: translateY(8px) scale(0.97);
    opacity: 0;
  }
  to {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
}

.drops-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 10px;
}

/* shared "card option" skin for drop cards and persona cards */
.persona-option-card,
.drop-card {
  background: var(--card-soft);
  border: 2px solid var(--card-soft);
  border-radius: var(--r-lg);
  padding: 12px;
  cursor: pointer;
  color: var(--text);
  font: inherit;
  transition:
    transform var(--t-fast),
    border-color var(--t-base),
    box-shadow var(--t-base);
  position: relative;
}
.persona-option-card:hover,
.drop-card:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: 0 4px 14px var(--accent-alpha-18);
}
.persona-option-card:focus-visible,
.drop-card:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-color: var(--accent);
}
.persona-option-card.active,
.drop-card.active {
  border-color: var(--good);
  box-shadow: 0 0 14px rgba(46, 204, 113, 0.45);
}

/* persona card layout */
.persona-option-card {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  text-align: left;
}
.persona-option-card .persona-glyph {
  flex: 0 0 56px;
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 36px;
  background: linear-gradient(
    140deg,
    var(--alpha-white-06),
    rgba(255, 255, 255, 0.02)
  );
  border-radius: var(--r-xl);
  border: var(--border-soft);
}
.persona-option-card .meta {
  flex: 1;
  min-width: 0;
}
.persona-option-card .meta h4 {
  margin: 0 0 4px 0;
  color: var(--accent-soft);
}
.persona-option-card .meta .role {
  color: var(--muted);
  font-size: 0.82em;
  margin-bottom: 4px;
}
.persona-option-card .meta .stats {
  font-size: var(--fs-sm);
}

/* drop card layout */
.drop-card {
  text-align: left;
  width: 100%;
}
.drop-card h4 {
  margin: 0 0 6px 0;
  color: var(--accent-soft);
}
.drop-card .slot {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin-bottom: 6px;
}
.drop-card .stats {
  font-size: var(--fs-sm);
  margin: 6px 0;
}
.drop-card .effect {
  font-size: var(--fs-sm);
  color: var(--accent);
  margin-top: 4px;
}
.drop-card .replaces {
  font-size: 0.8em;
  color: var(--bad);
  margin-top: 4px;
}
.drop-card .drop-key {
  position: absolute;
  top: 6px;
  right: 8px;
  background: var(--bg);
  border: 1px solid var(--card-soft);
  color: var(--accent);
  font-size: 0.7em;
  font-weight: bold;
  padding: 1px 5px;
  border-radius: var(--r-xs);
  font-family: var(--font-mono);
  letter-spacing: 0.5px;
}

.drop-sprite {
  width: 100%;
  height: 130px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(
    180deg,
    rgba(20, 30, 60, 0.55),
    var(--alpha-black-55)
  );
  border-radius: var(--r-md);
  padding: 6px;
  margin-bottom: 8px;
}
.drop-sprite .character,
.drop-sprite .character-svg {
  width: 100px;
  height: 130px;
}
.drop-sprite .fallback-item-icon {
  font-size: 64px;
}
.drop-sprite.is-card {
  background: transparent;
  padding: 0;
  height: auto;
}

/* hover state shifts inline content into a contrasting strip */
.drop-card:hover .sc,
.drop-card:focus-visible .sc {
  background: var(--bg2);
  color: #ffffff;
}
.drop-card:hover .sc-label,
.drop-card:focus-visible .sc-label {
  color: var(--accent);
}
.drop-card:hover .effect,
.drop-card:focus-visible .effect {
  background: var(--bg2);
  color: var(--accent-soft);
  padding: 4px 6px;
  border-radius: var(--r-xs);
}
.drop-card:hover .range-tag,
.drop-card:focus-visible .range-tag {
  background: var(--bg2);
}
.drop-card:hover .drop-key,
.drop-card:focus-visible .drop-key {
  background: var(--bg2);
  border-color: var(--accent);
}
.drop-card:hover h4,
.drop-card:focus-visible h4,
.drop-card:hover .slot,
.drop-card:focus-visible .slot {
  background: var(--bg2);
  padding: 2px 6px;
  border-radius: var(--r-xs);
  display: inline-block;
}

.effect-line {
  display: inline;
}
.effect-icon {
  display: inline-block;
  font-family: var(--font-emoji);
  font-size: var(--fs-md);
  margin-right: 2px;
  vertical-align: -1px;
}

.drop-skip {
  margin-top: 10px;
  background: var(--card-soft);
  color: var(--muted);
  width: 100%;
}
.drop-skip:hover {
  background: var(--bad);
  color: var(--text);
}

.drops-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: 8px;
}
.drops-header h3 {
  margin: 0;
}

.drop-timer {
  position: relative;
  width: 46px;
  height: 46px;
  flex-shrink: 0;
}
.drop-timer-ring {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}
.drop-timer-ring .ring-track {
  fill: none;
  stroke: var(--card-soft);
  stroke-width: 3;
}
.drop-timer-ring .ring-fill {
  fill: none;
  stroke: var(--good);
  stroke-width: 3;
  stroke-linecap: round;
  transition: stroke var(--t-medium);
}
.drop-timer-ring .ring-fill.warn {
  stroke: var(--accent);
}
.drop-timer-ring .ring-fill.danger {
  stroke: var(--bad);
}
.drop-timer-num {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
  font-size: 0.95em;
  color: var(--good);
  transition: color var(--t-medium);
}
.drop-timer.warn .drop-timer-num {
  color: var(--accent);
}
.drop-timer.danger .drop-timer-num {
  color: var(--bad);
  animation: timer-shake 0.5s ease-in-out infinite;
}
@keyframes timer-shake {
  0%,
  100% {
    transform: translateX(0);
  }
  50% {
    transform: translateX(2px);
  }
}

/* ===== bracket sidebar / tree ===== */

#bracket-results {
  margin-top: 14px;
}
#bracket-results:not(:empty) {
  border-top: 1px solid var(--card-soft);
  padding-top: 10px;
}
#bracket-results .match {
  padding: 6px 10px;
  margin: 4px 0;
  background: var(--card-soft);
  border-radius: var(--r-sm);
  font-size: 0.9em;
}
#bracket-results .match.me {
  border-left: 3px solid var(--accent);
}
.round-header {
  margin-top: 10px;
  font-weight: bold;
  color: var(--accent-soft);
  font-size: 0.95em;
}
#bracket-results .match-tag {
  display: inline-block;
  background: var(--card-soft);
  color: var(--accent-soft);
  padding: 1px 5px;
  border-radius: var(--r-xs);
  font-size: 0.75em;
  margin-right: 4px;
  letter-spacing: 0.4px;
}

/* ===== round-robin standings table ===== */
.standings {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 12px;
}
.standings-head,
.standings-row {
  display: grid;
  grid-template-columns: 32px 1fr 40px 40px;
  gap: var(--s-2);
  align-items: center;
  padding: 4px 8px;
  border-radius: var(--r-sm);
  font-size: 0.9em;
}
.standings-head {
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: var(--accent-soft);
  border-bottom: 1px solid var(--card-soft);
  padding-bottom: 6px;
  margin-bottom: 4px;
}
.standings-row {
  background: var(--card-soft);
}
.standings-row.me {
  border-left: 3px solid var(--accent);
  padding-left: 5px;
  color: var(--accent);
  font-weight: 600;
}
.standings-row .st-rank {
  color: var(--accent-soft);
  font-weight: 600;
  text-align: right;
}
.standings-row .st-wins {
  text-align: right;
  font-weight: 600;
  color: var(--good);
}
.standings-row .st-played {
  text-align: right;
  color: var(--muted);
}
.standings-head .st-rank,
.standings-head .st-wins,
.standings-head .st-played {
  text-align: right;
}

/* ===== fallback emoji icons for items without dedicated SVGs ===== */

.fallback-item-icon {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-emoji), "Noto Sans Runic";
  font-size: 32px;
  line-height: 1;
}
.fallback-item-icon.slot-rune {
  color: var(--accent);
  font-weight: bold;
  font-size: 36px;
  text-shadow: 0 0 8px var(--accent-alpha-45);
}
.fallback-item-icon.placeholder {
  opacity: 0.35;
  filter: grayscale(0.6);
  font-size: 28px;
}

/* Weapon range badge — single class used inline in tooltips, gallery slot
 * row, and as a stand-alone block inside drop cards. .block is the only
 * modifier; the 3 colors come from the [data-range] attribute. */
.range-tag {
  display: inline-block;
  font-size: 0.7em;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  border-radius: var(--r-xs);
  padding: 1px 5px;
  margin-left: 4px;
}
.range-tag.block {
  display: block;
  margin-left: 0;
  margin-top: 4px;
}
.range-tag.melee {
  background: rgba(220, 120, 70, 0.18);
  color: #e89970;
}
.range-tag.ranged {
  background: rgba(80, 180, 220, 0.18);
  color: #6ec6e8;
}
.range-tag.magic {
  background: rgba(170, 110, 230, 0.2);
  color: #c79bff;
}

.tag-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
  margin-top: 4px;
}
.tag-badge {
  display: inline-block;
  font-size: 0.68em;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  border-radius: var(--r-xs);
  padding: 1px 6px;
  border: 1px solid currentColor;
  line-height: 1.3;
}
.tag-strength {
  color: #f08a4b;
  background: rgba(240, 138, 75, 0.12);
}
.tag-dexterity {
  color: #6cd07a;
  background: rgba(108, 208, 122, 0.12);
}
.tag-intelligence {
  color: #6aa9ff;
  background: rgba(106, 169, 255, 0.14);
}

/* ===== shared item tooltip ===== */

.item-tooltip {
  position: fixed;
  z-index: 1000;
  pointer-events: none;
  max-width: 280px;
  background: var(--card);
  border: 1px solid var(--card-soft);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-md);
  padding: 10px 12px;
  box-shadow: 0 8px 24px var(--alpha-black-45);
  font-size: var(--fs-sm);
  opacity: 0;
  transform: translateY(-2px);
  transition:
    opacity var(--t-fast) ease-out,
    transform var(--t-fast) ease-out;
  display: none;
}
.item-tooltip.visible {
  opacity: 1;
  transform: translateY(0);
  display: block;
}
.item-tooltip .tt-name {
  font-weight: 700;
  color: var(--accent);
  font-size: var(--fs-md);
  display: flex;
  align-items: center;
  gap: 6px;
}
.item-power {
  margin-left: auto;
  display: inline-block;
  padding: 1px 7px;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 999px;
  font-size: 0.78em;
  font-weight: 700;
  color: var(--accent-soft);
  letter-spacing: 0.4px;
  white-space: nowrap;
}
.item-power::before {
  content: "★ ";
  opacity: 0.85;
}
.item-tooltip .tt-slot {
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  margin-bottom: 6px;
}
.item-tooltip .tt-lore {
  font-size: var(--fs-sm);
  color: var(--muted);
  font-style: italic;
  margin: 4px 0 6px 0;
  line-height: 1.35;
}
.item-tooltip .tt-stats {
  line-height: 1.9;
  margin: 4px 0;
}
.item-tooltip .tt-effect {
  color: var(--accent-soft);
  margin-top: 6px;
  line-height: 1.4;
}
.item-tooltip .tt-implicit-label {
  font-size: 0.68em;
  text-transform: uppercase;
  letter-spacing: 0.8px;
  color: #9ec5ff;
  margin: 8px 0 2px;
  font-weight: 600;
}
.item-tooltip .tt-effect-implicit {
  color: #b8d8ff;
  margin-top: 2px;
}
/* Inline (non-tooltip) implicit block used in drop / wiki / detail cards.
   Single-row: label + chips on the same line so it adds almost no vertical
   space over the regular .stats line. */
.item-implicit {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px;
  margin: 2px 0;
  padding: 2px 6px;
  background: rgba(120, 180, 255, 0.08);
  border-left: 2px solid rgba(120, 180, 255, 0.55);
  border-radius: var(--r-xs, 4px);
}
.item-implicit-label {
  font-size: 0.65em;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: rgba(180, 210, 255, 0.95);
  font-weight: 600;
}
.item-implicit .stats {
  color: rgba(200, 220, 255, 0.95);
}
.item-tooltip .tt-warn {
  margin-top: 6px;
  color: var(--bad);
  font-size: var(--fs-sm);
}
.item-tooltip .tt-section-label {
  font-size: 0.7em;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: var(--muted);
  margin: 8px 0 3px;
}
.item-tooltip .tt-replaces {
  background: rgba(0, 0, 0, 0.25);
  border-radius: var(--r-xs);
  padding: 5px 7px;
}
.item-tooltip .tt-replaces-name {
  color: var(--bad);
  font-weight: 600;
  margin-bottom: 3px;
  font-size: 0.95em;
}
.item-tooltip .tt-deltas .sc.tt-delta.up {
  background: rgba(46, 204, 113, 0.18);
  color: var(--good);
}
.item-tooltip .tt-deltas .sc.tt-delta.down {
  background: rgba(231, 76, 60, 0.18);
  color: var(--bad);
}
.item-tooltip .tt-deltas .sc.tt-delta.zero {
  opacity: 0.45;
}

/* ===== build sidebar ===== */

.build-section-label {
  font-size: 0.72em;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  margin: 10px 0 4px;
}
.build-effects {
  margin: 6px 0 12px;
  background: rgba(0, 0, 0, 0.18);
  border-radius: var(--r-sm);
  padding: 6px 10px;
}
/* Passive-proc chips tinted by the kind of value they show, so a quick
   scan of the Efectos line tells the player what each passive does:
   green = curación, azul = daño mágico, rojo = daño físico, cian =
   escudo. The base .sc styling sets the chip shape; these only override
   the colour of the number and the chip border. */
.sc-passive-heal {
  color: #6fdc8c;
  border-color: rgba(111, 220, 140, 0.45);
}
.sc-passive-mag {
  color: #6fb8ff;
  border-color: rgba(111, 184, 255, 0.45);
}
.sc-passive-phys {
  color: #ff7a7a;
  border-color: rgba(255, 122, 122, 0.45);
}
.sc-passive-shield {
  color: #7ee0ff;
  border-color: rgba(126, 224, 255, 0.45);
}
/* Mini green badge tacked onto a blue magic-damage chip when the spell also
   heals (Drenaje, Sifón Vital). Sits inline after the damage number. */
.sc-heal-suffix {
  color: #6fdc8c;
  margin-left: 3px;
}
.build-effect-row {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-size: var(--fs-sm);
  padding: 3px 0;
  flex-wrap: wrap;
}
.build-effect-row .effect-icon {
  font-size: var(--fs-md);
}
.build-effect-row .be-desc {
  color: var(--accent-soft);
  flex: 1;
  min-width: 120px;
}
.build-effect-row .be-from {
  color: var(--muted);
  font-size: var(--fs-sm);
  font-style: italic;
}

/* both build cells (sidebar + summary) share a slot-color skin. The grid
   container is the build-items-host element itself (the sidebar's
   #build-items-host gets the class via index.html; the end-screen creates a
   plain <div class="build-items-host">). */
.build-items-host {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(76px, 1fr));
  gap: 6px;
}
.build-item-cell {
  background: var(--card-soft);
  border-radius: var(--r-sm);
  padding: 6px 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  border-top: 2px solid var(--card);
  text-align: center;
  transition:
    transform var(--t-fast) ease-out,
    background var(--t-fast) ease-out;
}
.build-item-cell.empty {
  opacity: 0.55;
}
.build-item-cell:not(.empty) {
  cursor: help;
}
.build-item-cell:not(.empty):hover,
.build-item-cell:not(.empty):focus {
  background: var(--card);
  outline: none;
  transform: translateY(-1px);
}

/* slot colors come from --slot-* tokens */
.build-item-cell.slot-weapon {
  border-top-color: var(--slot-weapon);
}
.build-item-cell.slot-shield {
  border-top-color: var(--slot-shield);
}
.build-item-cell.slot-helm {
  border-top-color: var(--slot-helm);
}
.build-item-cell.slot-armor {
  border-top-color: var(--slot-armor);
}
.build-item-cell.slot-ring {
  border-top-color: var(--slot-ring);
}
.build-item-cell.slot-card {
  border-top-color: var(--slot-card);
}
.build-item-cell.slot-gloves {
  border-top-color: var(--slot-gloves);
}
.build-item-cell.slot-amulet {
  border-top-color: var(--slot-amulet);
}
.build-item-cell.slot-boots {
  border-top-color: var(--slot-boots);
}
.build-item-cell.slot-rune {
  border-top-color: var(--slot-rune);
}

.build-item-sprite {
  width: 52px;
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.build-item-sprite .character,
.build-item-sprite .character-svg {
  width: 52px;
  height: 52px;
}

/* tarot cards have a 2:3 aspect — give them a taller box so art doesn't squish */
.build-item-cell.slot-card .build-item-sprite {
  width: 60px;
  height: 90px;
}
.build-item-sprite .tarot-card {
  width: 60px;
  height: 90px;
}

.build-item-slot {
  font-size: 0.65em;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--accent-soft);
}
.build-item-name {
  font-size: 0.72em;
  color: var(--text);
  line-height: 1.2;
  word-break: break-word;
  text-align: center;
}
.build-item-cell.empty .build-item-name {
  color: var(--muted);
  font-style: italic;
}

/* ===== tarot cards ===== */

.tarot-card {
  --tarot-a: #f9d976;
  --tarot-b: #a87a30;
  --tarot-accent: #fff7c8;
  width: 110px;
  height: 165px;
  border-radius: var(--r-lg);
  background: linear-gradient(160deg, var(--tarot-a) 0%, var(--tarot-b) 100%);
  border: 2px solid var(--tarot-accent);
  position: relative;
  overflow: hidden;
  box-shadow:
    0 3px 10px rgba(0, 0, 0, 0.5),
    inset 0 0 18px rgba(255, 255, 255, 0.18);
  flex-shrink: 0;
  font-family: "Georgia", serif;
  color: var(--tarot-accent);
  transition:
    transform var(--t-medium),
    box-shadow var(--t-medium);
}
.tarot-card::before {
  content: "";
  position: absolute;
  inset: 4px;
  border-radius: var(--r-sm);
  border: 1px solid rgba(255, 255, 255, 0.25);
  pointer-events: none;
}
.tarot-card.compact {
  width: 60px;
  height: 90px;
}

.tarot-card .tarot-roman,
.tarot-card .tarot-roman-br {
  position: absolute;
  font-weight: bold;
  font-size: var(--fs-sm);
  letter-spacing: 1px;
  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7);
  z-index: 2;
}
.tarot-card .tarot-roman {
  top: 6px;
  left: 8px;
}
.tarot-card .tarot-roman-br {
  bottom: 6px;
  right: 8px;
  transform: rotate(180deg);
}

.tarot-card.compact .tarot-roman,
.tarot-card.compact .tarot-roman-br {
  font-size: 0.65em;
  top: 3px;
  left: 5px;
}
.tarot-card.compact .tarot-roman-br {
  top: auto;
  bottom: 3px;
  right: 5px;
  left: auto;
}

.tarot-card .tarot-icon-wrap {
  position: absolute;
  inset: 24px 12px 24px 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}
.tarot-card.compact .tarot-icon-wrap {
  inset: 14px 6px 14px 6px;
}
.tarot-card .tarot-icon {
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 1px 2px var(--alpha-black-60));
}
.tarot-card .tarot-name {
  position: absolute;
  bottom: 6px;
  left: 0;
  right: 0;
  text-align: center;
  font-size: 0.7em;
  letter-spacing: 0.5px;
  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
  z-index: 2;
}

.drop-card.slot-card {
  padding: 10px;
}
.drop-card.slot-card .drop-tarot-art {
  flex-shrink: 0;
}
.drop-card:hover .tarot-card {
  transform: rotate(-2deg) translateY(-2px);
  box-shadow:
    0 6px 16px var(--alpha-black-60),
    inset 0 0 18px rgba(255, 255, 255, 0.25);
}

/* ===== gallery ===== */

main:has(#screen-gallery.active) {
  max-width: 1100px;
}
main:has(#screen-mockups.active) {
  max-width: 1100px;
}

/* ===== mockups screen ===== */

.mockup-section {
  margin-top: 20px;
  border-top: 1px dashed var(--card-soft);
  padding-top: 14px;
}
.mockup-section:first-of-type {
  border-top: 0;
  padding-top: 0;
}
.mockup-section-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 10px;
}
.mockup-section-head h3 {
  margin: 0;
  color: var(--accent);
  font-size: var(--fs-md);
  text-transform: uppercase;
  letter-spacing: 0.6px;
}
.mockup-section-body {
  /* The mocked .card inside lives in its own column so it doesn't grow to the
     full preview-page width. Keeps the snapshot proportional to what the
     player actually sees in-game. */
  max-width: 720px;
}
.mockup-section-body .card {
  background: var(--card);
}
.mockup-drops-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 10px;
  margin-top: 8px;
}

.gtab {
  background: transparent;
  color: var(--muted);
  padding: 8px 14px;
  border: none;
  border-bottom: 3px solid transparent;
  border-radius: 0;
  font-weight: 600;
  font-size: 0.95em;
  cursor: pointer;
  transition:
    color var(--t-fast),
    border-color var(--t-fast),
    background var(--t-fast);
}
.gtab:hover {
  color: var(--accent-soft);
  background: var(--alpha-white-04);
  transform: none;
}
.gtab.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* Stats screen tabs share the same look as the wiki tabs (.gtab) — kept
   as a parallel class so the JS handlers stay separate. */
.stab {
  background: transparent;
  color: var(--muted);
  padding: 8px 14px;
  border: none;
  border-bottom: 3px solid transparent;
  border-radius: 0;
  font-weight: 600;
  font-size: 0.95em;
  cursor: pointer;
  transition:
    color var(--t-fast),
    border-color var(--t-fast),
    background var(--t-fast);
}
.stab:hover {
  color: var(--accent-soft);
  background: var(--alpha-white-04);
  transform: none;
}
.stab.active {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* Champion list — one card per past tournament winner.
 * Visually mirrors the rank-1 podium card from the end-of-tournament screen
 * (gold gradient + accent glow + sparkles), since every entry here is a
 * tournament winner. Only the most-recent entry runs sparkles to keep the
 * 30-card list cheap. */
.champions-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 6px;
}
.champions-title {
  margin: 4px 0 8px 0;
  color: var(--accent);
  font-size: 1.05em;
  letter-spacing: 0.4px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.champions-title .trophy {
  filter: drop-shadow(0 0 6px var(--accent-alpha-60));
}
.champion-card {
  position: relative;
  background: linear-gradient(
    135deg,
    var(--accent-alpha-18),
    var(--card-soft) 60%
  );
  border: 1px solid var(--accent-alpha-35);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-lg);
  padding: 10px 12px;
  box-shadow:
    0 0 0 1px var(--accent-alpha-35),
    0 4px 16px rgba(249, 168, 38, 0.13);
  overflow: hidden;
  contain: layout paint;
  opacity: 0;
  transform: translateY(8px);
  animation: champion-in 480ms cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
  animation-delay: var(--champ-delay, 0ms);
}
@keyframes champion-in {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: none;
  }
}
.champion-card .sparkle-host {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
}

/* Two-column grid: portrait (96×130) on the left, condensed info column on
   the right. The items strip sits below the grid so it can use the full
   card width as a single dense row. */
.champion-grid {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: 12px;
  align-items: start;
}
@media (max-width: 560px) {
  .champion-grid {
    grid-template-columns: 76px 1fr;
    gap: 10px;
  }
}
.champion-sprite {
  width: 96px;
  height: 130px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(
    circle at 50% 35%,
    rgba(255, 255, 255, 0.06),
    rgba(255, 255, 255, 0.02) 70%
  );
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 6px;
  overflow: hidden;
}
@media (max-width: 560px) {
  .champion-sprite {
    width: 76px;
    height: 104px;
  }
}
.champion-sprite .character-svg,
.champion-sprite > svg,
.champion-sprite > div {
  width: 100%;
  height: 100%;
}
.champion-info {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.champion-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}
.champion-head .champion-rank {
  font-size: 1.05em;
  font-weight: 800;
  color: var(--accent);
  text-shadow: 0 0 10px var(--accent-alpha-60);
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
}
.champion-head .champion-rank .trophy {
  font-size: 0.95em;
}
.champion-head .champion-rank .champion-pos {
  font-size: 0.78em;
  font-weight: 700;
  color: var(--accent-soft);
  text-shadow: none;
}
.champion-head .champion-name {
  font-weight: 700;
  font-size: 1em;
  color: var(--text);
  flex: 1;
  min-width: 0;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}
.champion-head .champion-date {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.78em;
}

.champion-persona {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

/* Counter chips: just an icon and a number — the full label lives in the
   title attribute. Drops the "Estadísticas del torneo · N combates"
   sub-label since combats lives on the chip tooltip via title now. */
.champion-counter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.champ-counter {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  padding: 2px 7px;
  font-size: 0.82em;
  color: var(--text);
  line-height: 1.3;
}
.champ-counter .cc-icon {
  font-size: 0.95em;
  filter: saturate(1.1);
}
.champ-counter .cc-value {
  font-weight: 700;
  color: var(--accent);
}

.champion-passive-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}
.champ-chip {
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 4px;
  padding: 3px 7px;
  font-size: 0.82em;
  color: var(--text);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  line-height: 1.2;
  transition:
    background var(--t-fast),
    border-color var(--t-fast);
}
.champ-chip:hover {
  background: var(--accent-alpha-12, rgba(255, 200, 50, 0.12));
  border-color: var(--accent);
}

/* Items strip — equipment sprites only, dense row across the full card.
   Click opens the inline detail panel where the full stats line lives. */
.champion-items-strip {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px dashed rgba(255, 255, 255, 0.08);
}
.champ-item-tile {
  width: 48px;
  height: 48px;
  padding: 0;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 5px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  transition:
    border-color var(--t-fast),
    transform var(--t-fast),
    background var(--t-fast);
}
.champ-item-tile:hover {
  border-color: var(--accent);
  background: var(--accent-alpha-12, rgba(255, 200, 50, 0.12));
  transform: translateY(-1px);
}
.champ-item-tile.rarity-rare {
  border-color: rgba(80, 140, 255, 0.55);
}
.champ-item-tile.rarity-epic {
  border-color: rgba(180, 90, 255, 0.55);
}
.champ-item-tile.rarity-legendary {
  border-color: rgba(255, 170, 60, 0.7);
  box-shadow: 0 0 6px rgba(255, 170, 60, 0.25);
}
.champ-item-sprite {
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.champ-item-sprite > * {
  width: 100%;
  height: 100%;
}

/* Stats winrate tables */
.stats-table-wrap {
  margin-top: 8px;
  overflow-x: auto;
}
.stats-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92em;
}
.stats-table th,
.stats-table td {
  padding: 6px 10px;
  text-align: left;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.stats-table th {
  color: var(--muted);
  font-size: 0.78em;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
  user-select: none;
}
.stats-table th.sorted-asc::after {
  content: " ▲";
  color: var(--accent);
}
.stats-table th.sorted-desc::after {
  content: " ▼";
  color: var(--accent);
}
.stats-table td.num,
.stats-table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.stats-table tr:hover td {
  background: rgba(255, 255, 255, 0.03);
}
.stats-table .row-clickable {
  cursor: pointer;
}
.stats-table .row-clickable:hover td {
  background: var(--accent-alpha-12, rgba(255, 200, 50, 0.07));
}
.stats-wr {
  font-weight: 600;
}
.stats-wr.wr-high {
  color: #6fd07a;
}
.stats-wr.wr-mid {
  color: #d4c460;
}
.stats-wr.wr-low {
  color: #b07070;
}

/* Inline detail panel that opens above the table when you click a row /
   passive chip / item chip. Keeps the user on-screen instead of switching
   tabs to the wiki. */
.stats-detail {
  background: var(--bg-card-soft, rgba(255, 255, 255, 0.03));
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 6px;
  padding: 12px 14px;
  margin-top: 10px;
}
.stats-detail h3 {
  margin: 0 0 4px 0;
}
.stats-detail .detail-close {
  float: right;
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 1.1em;
  cursor: pointer;
}
.stats-detail .detail-close:hover {
  color: var(--accent);
}
.stats-detail .detail-meta {
  color: var(--muted);
  font-size: 0.88em;
  margin-bottom: 8px;
}

/* "Cómo jugar" tab — long-form help, distinct from the data grids below. */
.howto-guide {
  display: flex;
  flex-direction: column;
  gap: 22px;
  line-height: 1.55;
}
.howto-section h3 {
  margin: 0 0 8px 0;
  color: var(--accent);
  font-size: 1.1em;
  border-bottom: 1px solid var(--card-soft);
  padding-bottom: 4px;
}
.howto-section p {
  margin: 0 0 10px 0;
  color: var(--muted);
}
.howto-list {
  margin: 4px 0 0 0;
  padding-left: 22px;
  color: var(--muted);
}
.howto-list li {
  margin-bottom: 6px;
}
.howto-list li b {
  color: var(--accent-soft);
  font-weight: 600;
}
.howto-stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 10px;
  margin-top: 8px;
}
.howto-stat {
  background: var(--card-soft);
  border: 1px solid var(--alpha-white-05);
  border-radius: 8px;
  padding: 10px 12px;
}
.howto-stat-head {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin-bottom: 4px;
}
.howto-stat-icon {
  font-size: var(--fs-lg);
}
.howto-stat-name {
  font-weight: 600;
  color: var(--accent-soft);
}
.howto-stat-short {
  margin-left: auto;
  font-size: 0.8em;
  color: var(--muted);
  background: var(--alpha-white-04);
  padding: 2px 6px;
  border-radius: var(--r-xs);
}
.howto-stat-desc {
  color: var(--muted);
  font-size: 0.92em;
}

.gallery-grid {
  display: grid;
  gap: 14px;
}
.gallery-grid.classes {
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.gallery-grid.items {
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.gallery-grid.cards {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  justify-items: center;
}
.gallery-grid.enemies {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.gallery-section-title {
  grid-column: 1 / -1;
  font-size: 1em;
  color: var(--accent-soft);
  border-bottom: 1px solid var(--card-soft);
  padding-bottom: 4px;
  margin: 14px 0 4px 0;
  text-transform: uppercase;
  letter-spacing: 1px;
  font-weight: 600;
}

/* Sub-section header inside a class block, splitting Activas from Pasivas. */
.gallery-subsection-title {
  grid-column: 1 / -1;
  font-size: 0.78em;
  color: var(--text-muted, #9aa3b2);
  margin: 8px 0 2px 0;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  font-weight: 600;
}

/* Passive/active kind chip — shown in the wiki card title and the pick screen
 * slot label so the player can tell auto-cast actives from always-on passives. */
.kind-chip {
  display: inline-block;
  font-size: 0.6em;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 1px 6px;
  border-radius: 999px;
  vertical-align: middle;
  margin-left: 4px;
}
.kind-active {
  background: rgba(255, 150, 60, 0.18);
  border: 1px solid rgba(255, 150, 60, 0.5);
  color: #ffc58a;
}
.kind-passive {
  background: rgba(120, 160, 220, 0.16);
  border: 1px solid rgba(120, 160, 220, 0.45);
  color: #bcd2ff;
}

/* winrate badge — chips appended when /api/stats has data */
.winrate-badge {
  margin-top: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
  align-items: center;
}
.winrate-badge .wr-chip {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--alpha-white-05);
  border: 1px solid var(--alpha-white-06);
  font-size: 0.75em;
  color: var(--muted);
  line-height: 1.5;
  white-space: nowrap;
}
.winrate-badge .wr-chip-pct {
  font-weight: 700;
  font-size: 0.82em;
  color: var(--text);
  background: rgba(255, 255, 255, 0.07);
  gap: 5px;
}
.winrate-badge .wr-chip-label {
  font-weight: 600;
  font-size: var(--fs-sm);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  opacity: 0.75;
}
.winrate-badge.wr-high .wr-chip-pct {
  color: #4ade80;
  background: rgba(74, 222, 128, 0.12);
  border-color: rgba(74, 222, 128, 0.35);
}
.winrate-badge.wr-mid .wr-chip-pct {
  color: #facc15;
  background: rgba(250, 204, 21, 0.12);
  border-color: rgba(250, 204, 21, 0.35);
}
.winrate-badge.wr-low .wr-chip-pct {
  color: #f87171;
  background: rgba(248, 113, 113, 0.12);
  border-color: rgba(248, 113, 113, 0.35);
}

.gentry {
  background: var(--card-soft);
  border-radius: var(--r-lg);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  border: var(--border-faint);
  transition:
    transform 0.15s,
    box-shadow 0.15s,
    border-color 0.15s;
}
.gentry:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
  box-shadow: 0 4px 14px var(--accent-alpha-18);
}

.gentry.class-entry {
  flex-direction: row;
  align-items: center;
  gap: 14px;
}
.gentry.class-entry .sprite-host {
  width: 120px;
  height: 180px;
  flex-shrink: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.gentry.class-entry .sprite-host .character {
  width: 120px;
  height: 180px;
}
.gentry.class-entry .meta {
  flex: 1;
}
.gentry.class-entry h3 {
  margin: 0 0 6px 0;
  color: var(--accent);
  font-size: var(--fs-lg);
}
.gentry.class-entry .role {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin-bottom: 8px;
  font-style: italic;
}
.gentry.class-entry .stats {
  line-height: 1.9;
  font-size: 0.9em;
}

.gentry.item-entry {
  display: grid;
  grid-template-columns: 84px 1fr;
  gap: 8px 10px;
  padding: 10px;
}
.gentry.item-entry .sprite-host {
  grid-row: 1;
  grid-column: 1;
  width: 84px;
  height: 110px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: linear-gradient(
    180deg,
    rgba(20, 30, 60, 0.4),
    rgba(0, 0, 0, 0.5)
  );
  border-radius: var(--r-md);
  padding: 4px 0;
}
.gentry.item-entry .meta {
  grid-row: 1;
  grid-column: 2;
  min-width: 0;
}
.gentry.item-entry > .winrate-badge {
  grid-column: 1 / -1;
  margin-top: 0;
}
.gentry.item-entry .sprite-host:has(.fallback-item-icon) {
  align-items: center;
  padding: 0;
}
.gentry.item-entry .sprite-host .fallback-item-icon {
  font-size: 48px;
}
.gentry.item-entry .sprite-host .character {
  width: 84px;
  height: 110px;
}
.gentry.item-entry h4 {
  margin: 0 0 4px 0;
  color: var(--accent-soft);
  font-size: 0.95em;
  display: flex;
  align-items: center;
  gap: 6px;
}
.gentry.item-entry .slot-tag {
  display: inline-block;
  background: var(--alpha-black-35);
  color: var(--muted);
  padding: 1px 6px;
  border-radius: var(--r-xs);
  font-size: 0.75em;
  margin-bottom: 6px;
}
.gentry.item-entry .stats {
  font-size: 0.8em;
  line-height: 1.7;
}
.gentry.item-entry .effect {
  font-size: 0.8em;
  color: var(--accent);
  margin-top: 4px;
}
.gentry.item-entry .spell-name {
  font-size: 0.8em;
  color: var(--magic);
  margin-top: 4px;
}

.gentry.card-entry {
  align-items: center;
  background: transparent;
  padding: 0;
  border: none;
}
.gentry.card-entry:hover {
  background: transparent;
  box-shadow: none;
  transform: none;
  border-color: transparent;
}
.gentry.card-entry .tarot-card {
  width: 150px;
  height: 225px;
}
.gentry.card-entry .card-meta {
  text-align: center;
  font-size: var(--fs-sm);
  margin-top: 8px;
  color: var(--text);
}
.gentry.card-entry .card-meta .name {
  color: var(--accent-soft);
  font-weight: bold;
  display: block;
  margin-bottom: 4px;
}
.gentry.card-entry .card-meta .effect {
  color: var(--accent);
  display: block;
  margin-top: 4px;
  font-size: var(--fs-sm);
}
.gentry.card-entry .card-meta .stats {
  font-size: 0.8em;
  line-height: 1.7;
}

.gentry.enemy-entry {
  flex-direction: row;
  align-items: center;
  gap: var(--s-3);
}
.gentry.enemy-entry .sprite-host {
  width: 100px;
  height: 140px;
  flex-shrink: 0;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: linear-gradient(180deg, #2c3157 0%, #0a0e1c 100%);
  border-radius: var(--r-md);
}
.gentry.enemy-entry .sprite-host .character {
  width: 100px;
  height: 140px;
}
.gentry.enemy-entry .meta {
  flex: 1;
}
.gentry.enemy-entry h4 {
  margin: 0;
  color: var(--accent-soft);
  font-size: 1em;
}
.gentry.enemy-entry .role {
  font-size: 0.8em;
  color: var(--muted);
  margin-bottom: 6px;
}
.gentry.enemy-entry .stats {
  font-size: var(--fs-sm);
  line-height: 1.7;
}
.gentry.enemy-entry .behavior {
  font-size: 0.8em;
  color: var(--magic);
  margin-top: 4px;
}

/* gallery: archetypes / zodiacs / pets — glyph + meta */
.gallery-grid.persona-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 14px;
}
.gentry.persona-entry {
  flex-direction: row;
  align-items: center;
  gap: 14px;
}
.gentry.persona-entry > .character,
.gentry.persona-entry > .character > .character-svg {
  flex: 0 0 120px;
  width: 120px;
  height: 180px;
}
.gentry.persona-entry .meta {
  flex: 1;
  min-width: 0;
}
.gentry.persona-entry h3 {
  margin: 0 0 6px 0;
  color: var(--accent);
  font-size: var(--fs-lg);
}
.gentry.persona-entry .role {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin-bottom: 8px;
  font-style: italic;
}
.gentry.persona-entry .stats {
  line-height: 1.9;
  font-size: 0.9em;
}
.gentry.persona-entry .per-level {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px dashed var(--border, rgba(255, 255, 255, 0.08));
  font-size: var(--fs-sm);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
}
.gentry.persona-entry .per-level-label {
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-size: var(--fs-sm);
}
.zodiac-glyph {
  color: #d8b4ff;
}
.pet-glyph {
  color: #a8e8c0;
}

/* ===== audio toggle ===== */

.audio-btn {
  background: var(--card);
  color: var(--accent-soft);
  border: none;
  padding: 4px 8px;
  border-radius: var(--r-card);
  font-size: 0.95em;
  line-height: 1;
  cursor: pointer;
  transition:
    background 0.15s,
    transform var(--t-fast);
}
.audio-btn:hover {
  background: var(--card-soft);
  transform: none;
}
.audio-btn[aria-pressed="true"] {
  color: var(--muted);
}

/* ===== queue ===== */

.dots-anim span {
  display: inline-block;
  animation: dots-blink 1.4s infinite;
  opacity: 0.2;
}
.dots-anim span:nth-child(2) {
  animation-delay: 0.2s;
}
.dots-anim span:nth-child(3) {
  animation-delay: 0.4s;
}
@keyframes dots-blink {
  0%,
  80%,
  100% {
    opacity: 0.2;
  }
  40% {
    opacity: 1;
  }
}

.queue-slots {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 6px;
  width: 100%;
  max-width: 360px;
}
.queue-slots:has(.queue-slot:only-child) {
  grid-template-columns: 1fr;
  max-width: 60px;
}
.queue-slot {
  aspect-ratio: 1 / 1;
  background: var(--card-soft);
  border-radius: var(--r-md);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: var(--fs-lg);
  border: var(--border-faint);
  transition:
    background var(--t-medium),
    color var(--t-medium),
    transform var(--t-medium);
}
.queue-slot.filled {
  background: linear-gradient(135deg, var(--accent), var(--accent-soft));
  color: var(--on-accent);
  transform: scale(1.05);
  box-shadow: 0 2px 8px rgba(249, 168, 38, 0.4);
}

/* ===== lobby room ===== */
/* Hosts the player roster + chat + action buttons rendered while the
   normal queue waits for a session to start. Two-column grid on wide
   viewports, stacks on narrow ones. */
.lobby-room {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.lobby-grid {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(260px, 1.4fr);
  gap: 14px;
  align-items: stretch;
}
@media (max-width: 720px) {
  .lobby-grid {
    grid-template-columns: 1fr;
  }
}
.lobby-section-title {
  margin: 0 0 6px;
  font-size: 0.95em;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.lobby-players,
.lobby-chat {
  background: var(--card-soft);
  border: var(--border-faint);
  border-radius: var(--r-md);
  padding: 10px 12px;
}
.lobby-player-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.lobby-slot {
  display: grid;
  grid-template-columns: 20px 1fr auto;
  gap: 8px;
  align-items: center;
  padding: 6px 8px;
  border-radius: var(--r-sm, 4px);
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid rgba(255, 255, 255, 0.04);
  font-size: 0.92em;
}
.lobby-slot.empty {
  opacity: 0.5;
}
.lobby-slot.filled {
  background: rgba(249, 168, 38, 0.08);
  border-color: rgba(249, 168, 38, 0.25);
}
.lobby-slot.bot {
  background: rgba(120, 200, 255, 0.06);
  border-color: rgba(120, 200, 255, 0.18);
}
.lobby-slot-icon {
  text-align: center;
}
.lobby-slot-name {
  color: var(--text);
  font-weight: 600;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}
.lobby-slot-name em {
  font-style: normal;
  color: var(--accent);
  font-weight: 700;
  font-size: 0.85em;
  margin-left: 4px;
}
.lobby-slot-empty {
  color: var(--muted);
  font-weight: 400;
  font-style: italic;
}
.lobby-slot-tag {
  color: var(--muted);
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  border: 1px solid rgba(255, 255, 255, 0.1);
  padding: 1px 6px;
  border-radius: 999px;
}

.lobby-chat {
  display: flex;
  flex-direction: column;
  min-height: 220px;
}
.lobby-chat-log {
  flex: 1;
  min-height: 160px;
  max-height: 240px;
  overflow-y: auto;
  background: rgba(0, 0, 0, 0.2);
  border-radius: var(--r-sm, 4px);
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.lobby-chat-line {
  font-size: 0.92em;
  line-height: 1.35;
  color: var(--text);
}
.lobby-chat-from {
  color: var(--muted);
  font-weight: 600;
  margin-right: 6px;
}
.lobby-chat-from.me {
  color: var(--accent);
}
.lobby-chat-from.bot {
  color: #78c8ff;
}
.lobby-chat-from::after {
  content: ":";
}
.lobby-chat-text {
  color: var(--text);
  word-break: break-word;
}
.lobby-chat-form {
  display: flex;
  gap: 6px;
  margin-top: 8px;
}
.lobby-chat-form input {
  flex: 1;
  background: rgba(0, 0, 0, 0.25);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: var(--r-sm, 4px);
  color: var(--text);
  padding: 6px 10px;
  font: inherit;
}
.lobby-chat-form input:focus {
  outline: none;
  border-color: var(--accent);
}
.lobby-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}
.lobby-actions button[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}

/* ===== match countdown ===== */

.match-countdown {
  margin-top: 14px;
  font-size: var(--fs-md);
  color: var(--muted);
  letter-spacing: 1px;
}
.match-countdown #match-countdown-num {
  display: inline-block;
  color: var(--accent);
  font-weight: bold;
  font-size: 2.2em;
  min-width: 1.4em;
  text-align: center;
  text-shadow: 0 0 14px var(--accent-alpha-45);
  vertical-align: -0.1em;
}
.match-countdown #match-countdown-num.pulse {
  animation: countdown-pulse 0.7s ease-out;
}
@keyframes countdown-pulse {
  0% {
    transform: scale(0.4);
    opacity: 0;
  }
  40% {
    transform: scale(1.3);
    opacity: 1;
  }
  100% {
    transform: scale(1);
    opacity: 0.9;
  }
}

/* ===== combat title flip-in ===== */

.combat-title.flip-in {
  animation: title-flip 0.45s ease-out;
}
@keyframes title-flip {
  0% {
    transform: rotateX(80deg) translateY(-6px);
    opacity: 0;
  }
  60% {
    transform: rotateX(-8deg);
    opacity: 1;
  }
  100% {
    transform: rotateX(0);
    opacity: 1;
  }
}

/* ===== PvE progress dots ===== */

.pve-progress {
  display: flex;
  gap: 6px;
  justify-content: center;
  margin-bottom: 10px;
  min-height: 12px;
}
.pve-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--card-soft);
  border: 1px solid var(--alpha-white-06);
  transition:
    background var(--t-medium),
    transform var(--t-medium),
    box-shadow var(--t-medium);
}
.pve-dot.done {
  background: var(--good);
}
.pve-dot.current {
  background: var(--accent);
  transform: scale(1.4);
  box-shadow: 0 0 8px var(--accent-alpha-60);
}

/* ===== pre-battle intro scene ===== */

.intro-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 6;
  overflow: hidden;
  border-radius: inherit;
  background: radial-gradient(
    ellipse at center,
    rgba(20, 6, 30, 0) 0%,
    rgba(20, 6, 30, 0.3) 55%,
    var(--alpha-black-65, rgba(0, 0, 0, 0.65)) 100%
  );
  animation: intro-vignette-in 0.18s ease-out;
}
@keyframes intro-vignette-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.intro-flash {
  position: absolute;
  inset: 0;
  background: rgba(255, 245, 220, 0);
  animation: intro-flash 1.5s ease-out;
}
@keyframes intro-flash {
  0% {
    background: rgba(255, 245, 220, 0);
  }
  6% {
    background: rgba(255, 250, 230, 0.7);
  }
  14% {
    background: rgba(255, 245, 220, 0);
  }
  26% {
    background: rgba(220, 235, 255, 0.55);
  }
  36% {
    background: rgba(255, 245, 220, 0);
  }
  52% {
    background: rgba(235, 245, 255, 0.5);
  }
  62% {
    background: rgba(255, 245, 220, 0);
  }
  78% {
    background: rgba(255, 240, 210, 0.4);
  }
  100% {
    background: rgba(255, 245, 220, 0);
  }
}

.intro-bolts {
  position: absolute;
  inset: 0;
}
.intro-bolt {
  position: absolute;
  top: -4%;
  width: 60px;
  height: 104%;
  transform: translateX(-50%);
  opacity: 0;
  filter: drop-shadow(0 0 6px #e6f5ff) drop-shadow(0 0 14px #b6d8ff)
    drop-shadow(0 0 22px var(--player));
  animation: intro-bolt-flash 0.38s ease-out forwards;
}
.intro-bolt-big {
  width: 90px;
  filter: drop-shadow(0 0 10px #ffffff) drop-shadow(0 0 22px #c4e0ff)
    drop-shadow(0 0 34px var(--player));
}
.intro-bolt-path {
  fill: none;
  stroke: #f5fbff;
  stroke-width: 3;
  stroke-linejoin: miter;
  stroke-linecap: round;
}
.intro-bolt-big .intro-bolt-path {
  stroke-width: 4.5;
}
.intro-bolt-fork {
  stroke-width: 1.8;
  opacity: 0.9;
}
.intro-bolt-big .intro-bolt-fork {
  stroke-width: 2.4;
}
@keyframes intro-bolt-flash {
  0% {
    opacity: 0;
    transform: translateX(-50%) scaleY(0.15) scaleX(var(--bolt-scale-x, 1));
  }
  20% {
    opacity: 1;
    transform: translateX(-50%) scaleY(1) scaleX(var(--bolt-scale-x, 1));
  }
  45% {
    opacity: 0.55;
  }
  65% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: translateX(-50%) scaleY(1) scaleX(var(--bolt-scale-x, 1));
  }
}

.intro-fire {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 70%;
  pointer-events: none;
}
.intro-ember {
  position: absolute;
  bottom: 6px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff0a0 0%, #ff8a30 45%, #c14b1f 100%);
  box-shadow:
    0 0 10px #ff7a1a,
    0 0 18px rgba(255, 120, 40, 0.6);
  opacity: 0;
  animation: intro-ember-rise linear forwards;
}
@keyframes intro-ember-rise {
  0% {
    transform: translate(0, 0) scale(0.4);
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  100% {
    transform: translate(var(--drift, 0), -180px) scale(0.2);
    opacity: 0;
  }
}

.intro-vs {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-weight: 900;
  font-size: clamp(64px, 14vw, 128px);
  letter-spacing: 0.05em;
  line-height: 1;
  color: #fff5cc;
  text-shadow:
    0 0 14px rgba(255, 200, 90, 0.9),
    0 0 28px rgba(255, 140, 40, 0.7),
    0 4px 0 var(--alpha-black-45);
  animation:
    intro-vs-slam 0.5s cubic-bezier(0.2, 1.4, 0.4, 1) 0.6s both,
    intro-vs-shake 0.3s ease-in-out 1.1s 1;
}
.intro-vs-shadow,
.intro-vs-main {
  display: block;
}
.intro-vs-shadow {
  position: absolute;
  inset: 0;
  color: rgba(180, 40, 40, 0.55);
  filter: blur(6px);
  transform: translate(3px, 4px);
}
@keyframes intro-vs-slam {
  0% {
    transform: translate(-50%, -50%) scale(3.4) rotate(-12deg);
    opacity: 0;
  }
  60% {
    transform: translate(-50%, -50%) scale(0.85) rotate(2deg);
    opacity: 1;
  }
  80% {
    transform: translate(-50%, -50%) scale(1.08) rotate(-1deg);
  }
  100% {
    transform: translate(-50%, -50%) scale(1) rotate(0);
    opacity: 1;
  }
}
@keyframes intro-vs-shake {
  0%,
  100% {
    margin-left: 0;
  }
  25% {
    margin-left: -6px;
  }
  75% {
    margin-left: 6px;
  }
}

/* intro-ready uses --side so the slide-in matches the slot side */
.character.intro-ready {
  animation: intro-ready-pose 1.5s ease-out forwards;
}
.character.intro-ready .character-svg {
  filter: drop-shadow(0 0 10px rgba(255, 200, 90, 0.7))
    drop-shadow(0 0 18px rgba(255, 80, 40, 0.45));
}
@keyframes intro-ready-pose {
  0% {
    transform: translateX(calc(-32px * var(--side, 1))) scale(0.92);
  }
  35% {
    transform: translateX(0) scale(1.06);
  }
  70% {
    transform: translateX(0) scale(1.02);
  }
  100% {
    transform: translateX(0) scale(1);
  }
}

/* ===== victory fanfare overlay =====
 * Sits on top of the arena after combat_end. Trumpets in the winner's top
 * corner (mirrored via [data-win=...]), tomatoes pelting the loser's bottom
 * corner. Pointer-events:none keeps clicks landing on whatever is underneath.
 */
.victory-fx {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 7;
  overflow: hidden;
  border-radius: inherit;
}

.victory-winner,
.victory-loser {
  position: absolute;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 6px 14px;
  border-radius: var(--r-md);
  font-weight: 900;
  letter-spacing: 0.04em;
  white-space: nowrap;
  animation: victory-badge-pop 0.55s cubic-bezier(0.2, 1.5, 0.4, 1) both;
  /* Both badges hover above their combatant-slot's sprite (slot is
   * position: relative). Auto-centered horizontally via margin: 0 auto on a
   * full-width absolute so the keyframes can use transform freely. */
  top: -6px;
  left: 0;
  right: 0;
  margin: 0 auto;
  width: fit-content;
  z-index: 8;
}
/* In the diagonal vertical arena the slot is full-width, so the centered
 * badge would float over the empty diagonal. Pin each badge over its own
 * combatant's corner instead. */
.arena.vertical .combatant-slot.bottom .victory-winner,
.arena.vertical .combatant-slot.bottom .victory-loser {
  left: 10px;
  right: auto;
  margin: 0;
}
.arena.vertical .combatant-slot.top .victory-winner,
.arena.vertical .combatant-slot.top .victory-loser {
  left: auto;
  right: 10px;
  margin: 0;
}
.victory-winner {
  justify-content: center;
  gap: 10px;
  background: linear-gradient(180deg, #ffd560 0%, #f08a1c 100%);
  color: #2a1a04;
  box-shadow:
    0 6px 16px rgba(255, 170, 50, 0.5),
    0 0 20px rgba(255, 220, 120, 0.55) inset;
  border: 2px solid #ffefa0;
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.45);
  font-size: 1em;
}
.victory-winner .victory-tag { font-size: 1em; }
.victory-winner .victory-name { font-size: 0.9em; }
.victory-winner .victory-trumpet { font-size: 1.4em; }
.victory-loser {
  background: linear-gradient(180deg, #4a1014 0%, #2a0608 100%);
  color: #ffd0d0;
  box-shadow: 0 6px 18px rgba(120, 20, 30, 0.55);
  border: 2px solid #7a1a22;
  font-size: var(--fs-sm);
  animation:
    victory-badge-pop 0.5s cubic-bezier(0.2, 1.4, 0.4, 1) 0.45s both,
    victory-badge-shake 0.35s ease-in-out 0.95s 3;
}

.victory-tag {
  font-size: var(--fs-md);
}
.victory-tag.bad {
  color: #ffb0b0;
}
.victory-name {
  font-weight: 700;
  opacity: 0.9;
  font-size: 0.95em;
}
.victory-label {
  display: flex;
  flex-direction: column;
  line-height: 1.05;
}
/* --mirror flips the trumpet for the right slot without needing a second
 * @keyframes — the scaleX is folded into the animation transform. */
.victory-trumpet {
  --mirror: 1;
  font-size: 1.6em;
  display: inline-block;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.4));
  animation: victory-trumpet-blow 0.5s ease-in-out infinite alternate;
}
.victory-trumpet.right {
  --mirror: -1;
  animation-delay: 0.18s;
}

@keyframes victory-badge-pop {
  0% {
    transform: scale(0.4) translateY(-6px);
    opacity: 0;
  }
  60% {
    transform: scale(1.08) translateY(0);
    opacity: 1;
  }
  100% {
    transform: scale(1) translateY(0);
    opacity: 1;
  }
}
@keyframes victory-badge-shake {
  0%,
  100% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-4px) rotate(-1.5deg);
  }
  75% {
    transform: translateX(4px) rotate(1.5deg);
  }
}
@keyframes victory-trumpet-blow {
  0% {
    transform: scaleX(var(--mirror, 1)) translateY(0) rotate(-8deg);
  }
  100% {
    transform: scaleX(var(--mirror, 1)) translateY(-4px) rotate(6deg);
  }
}

/* Floating musical notes rising over the winner's badge. */
.victory-notes {
  position: absolute;
  top: 0;
  width: 45%;
  height: 70%;
  pointer-events: none;
}
.victory-fx[data-win="player"] .victory-notes {
  left: 0;
}
.victory-fx[data-win="enemy"] .victory-notes {
  right: 0;
}

.victory-note {
  position: absolute;
  bottom: 0;
  font-size: 1.6em;
  color: #ffe080;
  text-shadow: 0 0 8px rgba(255, 200, 80, 0.6);
  opacity: 0;
  animation: victory-note-rise 1.6s ease-out forwards;
}
@keyframes victory-note-rise {
  0% {
    transform: translate(0, 20px) scale(0.6);
    opacity: 0;
  }
  20% {
    opacity: 1;
  }
  100% {
    transform: translate(var(--drift, 0), -110px) scale(1.1);
    opacity: 0;
  }
}

/* Tomatoes arc in toward the loser's lower corner and splat. */
.victory-tomatoes {
  position: absolute;
  bottom: 0;
  width: 55%;
  height: 80%;
  pointer-events: none;
}
.victory-fx[data-win="player"] .victory-tomatoes {
  right: 0;
}
.victory-fx[data-win="enemy"] .victory-tomatoes {
  left: 0;
}

.victory-tomato {
  position: absolute;
  bottom: 30px;
  font-size: 1.4em;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.5));
  opacity: 0;
}
/* Player won → tomatoes fly from the right edge inward to splat near the
 * inner-right corner. Enemy won → mirror the trajectory. --throw-sign
 * controls the horizontal direction so a single keyframe covers both cases. */
.victory-tomato {
  animation: victory-tomato-throw 0.7s cubic-bezier(0.4, 0, 0.7, 1) forwards;
}
.victory-fx[data-win="player"] .victory-tomato {
  right: -10%;
  --throw-sign: -1;
}
.victory-fx[data-win="enemy"] .victory-tomato {
  left: -10%;
  --throw-sign: 1;
}
@keyframes victory-tomato-throw {
  0% {
    transform: translate(0, 60px) rotate(0);
    opacity: 0;
  }
  10% {
    opacity: 1;
  }
  100% {
    transform: translate(
        calc(65% * var(--throw-sign, 1) + var(--tx, 0px)),
        50px
      )
      rotate(var(--rot, 360deg));
    opacity: 1;
  }
}

.victory-splat {
  position: absolute;
  bottom: 16px;
  font-size: 1.3em;
  opacity: 0;
  color: #ff5050;
  text-shadow: 0 0 6px rgba(255, 80, 80, 0.65);
  animation: victory-splat-pop 0.45s ease-out forwards;
}
.victory-fx[data-win="player"] .victory-splat {
  right: 18%;
  transform: translate(var(--sx, 0), var(--sy, 0));
}
.victory-fx[data-win="enemy"] .victory-splat {
  left: 18%;
  transform: translate(var(--sx, 0), var(--sy, 0));
}
@keyframes victory-splat-pop {
  0% {
    transform: translate(var(--sx, 0), var(--sy, 0)) scale(0.3);
    opacity: 0;
  }
  35% {
    transform: translate(var(--sx, 0), var(--sy, 0)) scale(1.4);
    opacity: 1;
  }
  100% {
    transform: translate(var(--sx, 0), var(--sy, 0)) scale(1);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .victory-winner,
  .victory-loser {
    animation: none;
  }
  .victory-trumpet {
    animation: none;
  }
}

/* ===== end screen summary ===== */

.summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--s-2);
  margin-bottom: 14px;
}
.summary-cell {
  background: var(--card-soft);
  border-radius: var(--r-md);
  padding: 10px 12px;
  text-align: center;
  border-top: 2px solid var(--accent);
}
.summary-label {
  color: var(--muted);
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.summary-value {
  font-size: 1.4em;
  font-weight: bold;
  color: var(--accent-soft);
  margin-top: 2px;
}
.summary-persona {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}
.summary-persona .character,
.summary-persona .character-svg {
  width: 32px;
  height: 42px;
  animation: none;
}

.summary-build {
  background: var(--card-soft);
  border-radius: var(--r-md);
  padding: 12px 16px;
}
.summary-build h4 {
  margin: 0 0 8px 0;
  color: var(--accent);
}
.summary-build ul {
  padding-left: 18px;
}

.build-totals {
  margin: 6px 0 12px;
  padding: 8px 10px;
  background: rgba(0, 0, 0, 0.2);
  border-radius: var(--r-sm);
}
.build-totals-label {
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  margin-bottom: 6px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-2);
}
.build-totals-stats {
  line-height: 1.9;
}
.build-level {
  color: rgba(120, 220, 255, 0.95);
  font-weight: 700;
  letter-spacing: 0.4px;
}

/* Passives list inside the post-tournament summary build. The list reuses the
   PASSIVE_GLYPHS catalog and tints each icon with the class' --passive-color. */
.build-passives {
  margin: 12px 0 8px;
  padding: 10px 12px;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.2);
  border-left: 3px solid var(--passive-color, #f1c40f);
}
.build-passives-label {
  font-size: 0.75em;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  margin-bottom: 8px;
}
.build-passives-list {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.build-passive-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.build-passive-icon {
  width: 36px;
  height: 36px;
  flex: 0 0 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.4);
  color: var(--passive-color, #f1c40f);
  filter: drop-shadow(0 0 4px var(--passive-color, #f1c40f));
}
.build-passive-icon svg {
  width: 70%;
  height: 70%;
  display: block;
}
.build-passive-meta {
  flex: 1;
  min-width: 0;
}
.build-passive-name {
  font-size: 0.92em;
  font-weight: 600;
  color: var(--passive-color, #f1c40f);
}
.build-passive-desc {
  font-size: 0.82em;
  color: #d8dde6;
  line-height: 1.3;
}

/* ===== end screen podium ===== */

.end-podium {
  margin: 4px 0 18px 0;
}
.end-podium-title {
  margin: 0 0 10px 0;
  color: var(--accent);
  font-size: 1em;
  letter-spacing: 0.4px;
}
.podium-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}
.podium-card {
  position: relative;
  background: var(--card-soft);
  border-radius: var(--r-lg);
  padding: 10px 12px;
  border-left: 3px solid var(--card);
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  opacity: 0;
  transform: translateY(8px);
  animation: podium-in 480ms cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
  overflow: hidden;
  contain: layout paint;
}
.podium-card.rank-1 {
  border-left-color: var(--accent);
  background: linear-gradient(
    135deg,
    var(--accent-alpha-18),
    var(--card-soft) 60%
  );
  box-shadow:
    0 0 0 1px var(--accent-alpha-35),
    0 6px 22px rgba(249, 168, 38, 0.15);
}
.podium-card.rank-2 {
  border-left-color: #c8d3e6;
  background: linear-gradient(
    135deg,
    rgba(200, 211, 230, 0.12),
    var(--card-soft) 60%
  );
}
.podium-card.rank-3 {
  border-left-color: #c08a5a;
  background: linear-gradient(
    135deg,
    rgba(192, 138, 90, 0.1),
    var(--card-soft) 60%
  );
}
.podium-card.rank-4 {
  filter: brightness(0.95);
}
.podium-card.rank-5,
.podium-card.rank-6,
.podium-card.rank-7,
.podium-card.rank-8 {
  filter: brightness(0.85);
}
.podium-card.me {
  outline: 2px dashed var(--accent-soft);
  outline-offset: -4px;
}

.podium-head {
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  flex-wrap: wrap;
}
.podium-rank {
  font-weight: 800;
  font-size: 1.1em;
  color: var(--accent-soft);
  min-width: 28px;
}
.podium-card.rank-1 .podium-rank {
  color: var(--accent);
  font-size: 1.35em;
  text-shadow: 0 0 12px var(--accent-alpha-60);
}
.podium-name {
  font-weight: 700;
  flex: 1;
  min-width: 0;
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}
.podium-race {
  color: var(--muted);
  font-size: var(--fs-sm);
}
.podium-persona {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
  width: 100%;
  margin-top: 2px;
}

/* persona chips inline */
.persona-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: 0.78em;
  background: var(--alpha-white-05);
  border: var(--border-soft);
  color: var(--text);
  white-space: nowrap;
}
.persona-chip .persona-icon {
  font-size: 0.95em;
  line-height: 1;
}
.persona-chip .persona-icon-sprite {
  width: 22px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  margin: -4px 2px -4px 0;
  flex: 0 0 auto;
}
.persona-chip .persona-icon-sprite .character,
.persona-chip .persona-icon-sprite .character-svg {
  width: 100%;
  height: 100%;
  animation: none;
}
.persona-chip.persona-race {
  background: rgba(80, 130, 220, 0.18);
  border-color: rgba(120, 170, 250, 0.35);
}
.persona-chip.persona-class {
  background: rgba(220, 170, 60, 0.18);
  border-color: rgba(250, 200, 100, 0.35);
}
.persona-chip.persona-zodiac {
  background: rgba(160, 120, 220, 0.2);
  border-color: rgba(200, 160, 250, 0.35);
}
.persona-chip.persona-pet {
  background: rgba(80, 200, 130, 0.18);
  border-color: rgba(130, 230, 170, 0.35);
}

#roster-list li {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1);
  align-items: center;
}
.roster-num {
  color: var(--muted);
  margin-right: 2px;
}
.roster-name {
  font-weight: 600;
}

.podium-body {
  display: flex;
  align-items: stretch;
  gap: var(--s-3);
}
.podium-sprite {
  flex: 0 0 96px;
  width: 96px;
  height: 132px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.podium-sprite .character {
  width: 96px;
  height: 132px;
}
.podium-card.rank-1 .podium-sprite {
  flex: 0 0 110px;
  width: 110px;
  height: 150px;
}
.podium-card.rank-1 .podium-sprite .character {
  width: 110px;
  height: 150px;
}
.podium-items {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3px 10px;
  font-size: 0.82em;
  align-content: center;
  flex: 1;
  min-width: 0;
}
.podium-items li {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.podium-items li.empty {
  color: var(--muted);
  font-style: italic;
}
.podium-items .ps {
  color: var(--muted);
  text-transform: uppercase;
  font-size: 0.75em;
  margin-right: 4px;
}

@keyframes podium-in {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* sparkles host overlays the whole card; particles rise from the bottom */
.sparkle-host {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: hidden;
  z-index: 0;
}
.podium-head,
.podium-body {
  position: relative;
  z-index: 1;
}

.sparkle {
  position: absolute;
  display: block;
  background: currentColor;
  border-radius: 50%;
  filter: blur(0.4px) drop-shadow(0 0 6px currentColor);
  opacity: 0;
  transform: translate(0, 0) scale(0.4);
  animation-name: sparkle-rise;
  animation-timing-function: cubic-bezier(0.2, 0.7, 0.3, 1);
  animation-fill-mode: forwards;
}
.sparkle.sparkle-big {
  background: currentColor;
  border-radius: 0;
  clip-path: polygon(
    50% 0%,
    58% 42%,
    100% 50%,
    58% 58%,
    50% 100%,
    42% 58%,
    0% 50%,
    42% 42%
  );
  filter: drop-shadow(0 0 8px currentColor);
}
@keyframes sparkle-rise {
  0% {
    opacity: 0;
    transform: translate(0, 0) scale(0.3) rotate(0deg);
  }
  18% {
    opacity: 1;
    transform: translate(calc(var(--dx) * 0.25), calc(var(--dy) * 0.25))
      scale(1) rotate(40deg);
  }
  100% {
    opacity: 0;
    transform: translate(var(--dx), var(--dy)) scale(0.55) rotate(220deg);
  }
}

/* ===== final rank reveal overlay ===== */

.rank-reveal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  animation: rr-fade-in 0.35s ease-out forwards;
  contain: strict;
}
.rank-reveal.rr-leaving {
  animation: rr-fade-out 0.3s ease-in forwards;
}
@keyframes rr-fade-in {
  to {
    opacity: 1;
  }
}
@keyframes rr-fade-out {
  to {
    opacity: 0;
  }
}

.rr-backdrop {
  position: absolute;
  inset: 0;
  background: radial-gradient(
    ellipse at center,
    rgba(20, 12, 40, 0.78) 0%,
    rgba(6, 4, 14, 0.94) 60%,
    rgba(0, 0, 0, 0.98) 100%
  );
}
.rank-reveal.tier-gold .rr-backdrop {
  background: radial-gradient(
    ellipse at center,
    rgba(80, 50, 10, 0.7) 0%,
    rgba(20, 12, 4, 0.95) 60%,
    rgba(0, 0, 0, 0.98) 100%
  );
}
.rank-reveal.tier-silver .rr-backdrop {
  background: radial-gradient(
    ellipse at center,
    rgba(70, 80, 100, 0.7) 0%,
    rgba(12, 16, 24, 0.95) 60%,
    rgba(0, 0, 0, 0.98) 100%
  );
}
.rank-reveal.tier-bronze .rr-backdrop {
  background: radial-gradient(
    ellipse at center,
    rgba(80, 50, 30, 0.65) 0%,
    rgba(18, 10, 6, 0.95) 60%,
    rgba(0, 0, 0, 0.98) 100%
  );
}

/* god-rays — heavy paint, give it composite hints */
.rr-rays {
  position: absolute;
  width: 220vmax;
  height: 220vmax;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(0deg);
  background: repeating-conic-gradient(
    from 0deg,
    rgba(255, 215, 90, 0.18) 0deg 6deg,
    rgba(255, 215, 90, 0) 6deg 18deg
  );
  mix-blend-mode: screen;
  opacity: 0;
  animation:
    rr-rays-spin 8s linear infinite,
    rr-rays-fade 0.5s ease-out 0.15s forwards;
  will-change: transform, opacity;
}
.rank-reveal.tier-silver .rr-rays {
  background: repeating-conic-gradient(
    from 0deg,
    rgba(220, 230, 245, 0.14) 0deg 7deg,
    rgba(220, 230, 245, 0) 7deg 22deg
  );
  animation:
    rr-rays-spin 12s linear infinite,
    rr-rays-fade 0.5s ease-out 0.2s forwards;
}
.rank-reveal.tier-bronze .rr-rays {
  background: repeating-conic-gradient(
    from 0deg,
    rgba(220, 150, 90, 0.12) 0deg 8deg,
    rgba(220, 150, 90, 0) 8deg 28deg
  );
  animation:
    rr-rays-spin 14s linear infinite,
    rr-rays-fade 0.6s ease-out 0.25s forwards;
}
@keyframes rr-rays-spin {
  to {
    transform: translate(-50%, -50%) rotate(360deg);
  }
}
@keyframes rr-rays-fade {
  to {
    opacity: 1;
  }
}

.rr-confetti {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.rr-confetto {
  position: absolute;
  top: -20px;
  width: 8px;
  height: 14px;
  border-radius: 2px;
  opacity: 0;
  animation: rr-confetto-fall linear forwards;
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.35);
  will-change: transform;
}
@keyframes rr-confetto-fall {
  0% {
    transform: translate(0, -20px) rotate(0);
    opacity: 0;
  }
  10% {
    opacity: 1;
  }
  100% {
    transform: translate(var(--sway, 0), 110vh) rotate(var(--rot, 360deg));
    opacity: 0.9;
  }
}

.rr-fireworks {
  position: absolute;
  inset: 0;
}
.rr-fw {
  position: absolute;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: currentColor;
  box-shadow:
    0 0 12px currentColor,
    0 0 28px currentColor;
  opacity: 0;
  transform: scale(0.2);
  animation: rr-fw-burst 1.6s ease-out forwards;
}
@keyframes rr-fw-burst {
  0% {
    opacity: 0;
    transform: scale(0.2);
    box-shadow: 0 0 0 currentColor;
  }
  20% {
    opacity: 1;
    transform: scale(1.2);
    box-shadow:
      0 0 22px currentColor,
      0 0 60px currentColor;
  }
  60% {
    opacity: 0.6;
    transform: scale(2.4);
    box-shadow:
      0 0 0 transparent,
      0 0 80px transparent;
  }
  100% {
    opacity: 0;
    transform: scale(3.6);
  }
}

.rr-stage {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 20px 28px;
  z-index: 2;
  transform: translateY(12px);
  animation: rr-stage-in 0.55s cubic-bezier(0.2, 1.2, 0.4, 1) 0.05s forwards;
  opacity: 0;
}
@keyframes rr-stage-in {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.rr-tagline {
  font-size: clamp(18px, 2.4vw, 28px);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(240, 240, 250, 0.75);
  text-shadow: 0 2px 4px var(--alpha-black-60);
  opacity: 0;
  animation: rr-fade-up 0.45s ease-out 0.2s forwards;
}
.rank-reveal.tier-gold .rr-tagline {
  color: #ffe07a;
  text-shadow:
    0 0 16px rgba(255, 200, 90, 0.85),
    0 2px 4px var(--alpha-black-60);
  font-size: clamp(22px, 3vw, 36px);
}
.rank-reveal.tier-silver .rr-tagline {
  color: #e8eef8;
  text-shadow:
    0 0 14px rgba(220, 230, 245, 0.7),
    0 2px 4px var(--alpha-black-60);
}
.rank-reveal.tier-bronze .rr-tagline {
  color: #f1b87a;
  text-shadow:
    0 0 12px rgba(230, 150, 80, 0.7),
    0 2px 4px var(--alpha-black-60);
}
.rank-reveal.tier-low .rr-tagline {
  color: #b76060;
}

@keyframes rr-fade-up {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.rr-crown {
  font-size: clamp(48px, 7vw, 88px);
  filter: drop-shadow(0 0 18px rgba(255, 200, 90, 0.9))
    drop-shadow(0 0 32px rgba(255, 140, 40, 0.6));
  opacity: 0;
  animation:
    rr-fade-up 0.5s ease-out 0.35s forwards,
    rr-crown-bob 1.6s ease-in-out 0.85s infinite;
}
@keyframes rr-crown-bob {
  0%,
  100% {
    transform: translateY(0) rotate(-3deg);
  }
  50% {
    transform: translateY(-6px) rotate(3deg);
  }
}

.rr-sprite {
  width: clamp(160px, 18vw, 220px);
  height: clamp(220px, 26vw, 300px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  position: relative;
  opacity: 0;
  transform: scale(0.7);
  animation: rr-sprite-pop 0.55s cubic-bezier(0.2, 1.4, 0.4, 1) 0.45s forwards;
}
.rr-sprite .character {
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 6px 18px var(--alpha-black-60));
}
.rank-reveal.tier-gold .rr-sprite .character .character-svg {
  filter: drop-shadow(0 0 18px rgba(255, 210, 90, 0.95))
    drop-shadow(0 0 38px rgba(255, 140, 40, 0.55));
}
.rank-reveal.tier-silver .rr-sprite .character .character-svg {
  filter: drop-shadow(0 0 14px rgba(220, 232, 250, 0.9));
}
.rank-reveal.tier-bronze .rr-sprite .character .character-svg {
  filter: drop-shadow(0 0 12px rgba(230, 150, 80, 0.85));
}
.rank-reveal.tier-low .rr-sprite .character .character-svg {
  filter: grayscale(0.55) brightness(0.7);
}
@keyframes rr-sprite-pop {
  to {
    opacity: 1;
    transform: scale(1);
  }
}

.rr-rank {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin-top: 6px;
}
.rr-rank-label {
  font-size: clamp(16px, 2vw, 22px);
  font-weight: 600;
  letter-spacing: 0.2em;
  color: rgba(240, 240, 250, 0.65);
  text-transform: uppercase;
}
.rr-rank-num {
  font-size: clamp(64px, 10vw, 128px);
  font-weight: 900;
  line-height: 1;
  color: #fff;
  text-shadow:
    0 0 18px rgba(255, 255, 255, 0.45),
    0 4px 0 var(--alpha-black-55);
  opacity: 0;
  transform: scale(2.6) rotate(-8deg);
  animation: rr-rank-slam 0.55s cubic-bezier(0.2, 1.5, 0.4, 1) 0.7s forwards;
}
.rank-reveal.tier-gold .rr-rank-num {
  color: #ffe27a;
  text-shadow:
    0 0 22px rgba(255, 210, 90, 0.95),
    0 0 42px rgba(255, 140, 40, 0.65),
    0 4px 0 var(--alpha-black-55);
}
.rank-reveal.tier-silver .rr-rank-num {
  color: #f1f4fa;
  text-shadow:
    0 0 18px rgba(220, 232, 250, 0.9),
    0 4px 0 var(--alpha-black-55);
}
.rank-reveal.tier-bronze .rr-rank-num {
  color: #f0bd80;
  text-shadow:
    0 0 16px rgba(230, 150, 80, 0.85),
    0 4px 0 var(--alpha-black-55);
}
.rank-reveal.tier-low .rr-rank-num {
  color: #d99090;
  text-shadow:
    0 0 12px rgba(180, 80, 80, 0.6),
    0 4px 0 var(--alpha-black-55);
}
@keyframes rr-rank-slam {
  0% {
    opacity: 0;
    transform: scale(2.6) rotate(-8deg);
  }
  60% {
    opacity: 1;
    transform: scale(0.92) rotate(2deg);
  }
  80% {
    transform: scale(1.06) rotate(-1deg);
  }
  100% {
    opacity: 1;
    transform: scale(1) rotate(0);
  }
}

.rr-sub {
  font-size: clamp(13px, 1.4vw, 18px);
  color: rgba(220, 225, 240, 0.6);
  letter-spacing: 0.05em;
  margin-top: 6px;
  opacity: 0;
  animation: rr-fade-up 0.4s ease-out 1.2s forwards;
}

/* ===== responsive ===== */

@media (max-width: 800px) {
  .gallery-grid.classes,
  .gallery-grid.persona-grid,
  .gallery-grid.items,
  .gallery-grid.cards,
  .gallery-grid.enemies {
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  }
}

@media (max-width: 600px) {
  main {
    padding: 10px;
  }
  .card {
    padding: 14px !important;
  }

  /* arena: tighter padding, smaller sprites, smaller hp-mini font so both
   * combatants comfortably fit a 360px viewport even with long enemy names. */
  .arena {
    height: 240px;
    padding: 10px 6px 0 6px;
  }
  /* Vertical arena (game screen): banners still on top + bottom, just tighter. */
  .arena.vertical {
    height: auto;
    min-height: 500px;
    padding: 10px 8px;
  }
  .combatant-slot.left {
    padding-left: 4px;
  }
  .combatant-slot.right {
    padding-right: 4px;
  }
  .character-host {
    width: 100px;
    height: 140px;
  }
  .character {
    width: 100px;
    height: 140px;
  }
  .vs-mark {
    font-size: 1em;
  }
  .hp-mini {
    padding: 5px 7px;
    max-width: 100%;
  }
  .hp-mini .name {
    font-size: 0.85em;
  }
  .hp-bar {
    height: 14px;
  }
  .hp-text {
    font-size: 0.7em;
  }
  .energy-bar {
    height: 7px;
  }
  .energy-text {
    font-size: 0.65em;
  }
  .charge-badges,
  .debuff-badges {
    gap: 2px;
    min-height: 14px;
  }
  /* pet floats just inside the inner edge of each slot, scaled to the
   * smaller character so it doesn't overlap the VS mark on phones. */
  .pet-host {
    width: 52px;
    height: 70px;
    bottom: 4px;
  }
  .pet-host .character,
  .pet-host .character-svg {
    width: 52px;
    height: 70px;
  }
  .combatant-slot.left .pet-host {
    left: 102px;
  }
  .combatant-slot.right .pet-host {
    right: 102px;
  }
  /* Vertical-arena pet/totem positions scaled to the 100px character. */
  .arena.vertical .combatant-slot.bottom .pet-host {
    left: 108px;
  }
  .arena.vertical .combatant-slot.top .pet-host {
    right: 108px;
  }
  .arena.vertical .totem-host {
    left: 108px;
    bottom: 70px;
  }

  /* match-formed cinematic: shrink the dominant sprite + RAZA label so the
   * whole hero block fits one phone screen without a scroll for the buttons. */
  .start-card {
    padding: 14px 12px 12px;
  }
  .start-race-name {
    font-size: 1.6em;
    letter-spacing: 2px;
  }
  .start-sprite .character {
    width: 130px;
    height: 195px;
  }
  .match-preview .character {
    width: 100px;
    height: 150px;
  }
  .match-preview-pet .character,
  .match-preview-pet .character-svg {
    width: 60px;
    height: 80px;
  }

  .gentry.class-entry {
    flex-direction: column;
    align-items: stretch;
  }
  .gentry.class-entry .sprite-host {
    align-self: center;
  }
  .gentry.item-entry .sprite-host {
    width: 64px;
    height: 88px;
  }
  .gentry.item-entry .sprite-host .character {
    width: 64px;
    height: 88px;
  }
  #drops-card {
    padding: 12px;
  }
  #drops-container {
    padding: 16px;
    max-height: 92vh;
  }
  /* drops grid: 2 cols at phone width is plenty since cards are ~150px wide
   * after we kill the 200px minmax floor; 1 col only kicks in at <360px. */
  .drops-grid,
  .mockup-drops-grid {
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 8px;
  }
  .drop-card {
    padding: 10px;
  }
  .drop-card .stats {
    font-size: 0.78em;
  }
  .event-log {
    height: 140px;
    font-size: 0.78em;
  }
  .summary-grid {
    grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
  }
  .summary-value {
    font-size: 1.15em;
  }
  .queue-slots {
    max-width: 280px;
  }
  .rr-sprite {
    width: 140px;
    height: 200px;
  }
  .rr-rank-num {
    font-size: 64px;
  }
  .rr-tagline {
    font-size: 16px;
    letter-spacing: 0.14em;
  }

  /* header tightens: the buttons row collides with the title at ≤375px when
   * gap-2 (8px) keeps full-size pills + emoji buttons next to a 1.1em title. */
  header {
    padding: 8px 10px !important;
    gap: 6px;
  }
  header > .flex {
    gap: 4px !important;
  }
  header .audio-btn {
    padding: 6px 8px;
    font-size: 0.9em;
  }
  header .tag {
    padding: 3px 8px;
    font-size: 0.78em;
  }
}

/* ============================================================
   Class-passive pick cards (1-of-3 after PvE fights 5/10/15).
   Reuse the .drop-card / .drops-grid layout via .slot-passive +
   .is-passive sprite host. --passive-color is set per class.
   ============================================================ */
.passive-class-archer {
  --passive-color: #6dd47a;
}
.passive-class-warrior {
  --passive-color: #e74c3c;
}
.passive-class-mage {
  --passive-color: #8e7dff;
}
.passive-class-demon_hunter {
  --passive-color: #ff7733;
}
.passive-class-paladin {
  --passive-color: #f1c40f;
}
.passive-class-pathfinder {
  --passive-color: #2bc4b4;
}

.drop-sprite.is-passive {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(160deg, rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.15));
  color: var(--passive-color, #f1c40f);
  filter: drop-shadow(0 0 10px var(--passive-color, #f1c40f));
}
.drop-sprite.is-passive svg {
  width: 64%;
  height: 64%;
  display: block;
}
.drop-card.slot-passive {
  border-color: var(--passive-color, #f1c40f);
  box-shadow:
    0 0 12px rgba(0, 0, 0, 0.4),
    0 0 0 1px var(--passive-color, #f1c40f) inset;
}
.drop-card.slot-passive:hover {
  box-shadow: 0 0 20px var(--passive-color, #f1c40f);
  transform: translateY(-2px);
}
.drop-card.slot-passive .slot {
  color: var(--passive-color, #f1c40f);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-size: 0.72em;
}

/* Gallery: passives section. Each class becomes its own grid block; the
   sprite host is a circle tinted by --passive-color (set on the wrapper). */
.gallery-grid.passives-grid {
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.gentry.passive-entry {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  padding: 10px;
}
.gentry.passive-entry .sprite-host.passive-sprite {
  width: 64px;
  height: 64px;
  flex: 0 0 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-card);
  background: linear-gradient(160deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.15));
  color: var(--passive-color, #f1c40f);
  filter: drop-shadow(0 0 6px var(--passive-color, #f1c40f));
}
.gentry.passive-entry .sprite-host.passive-sprite svg {
  width: 60%;
  height: 60%;
  display: block;
}
.gentry.passive-entry .meta {
  flex: 1;
  min-width: 0;
}
.gentry.passive-entry .meta h3 {
  font-size: 0.98em;
  margin: 0 0 4px;
  color: var(--passive-color, #f1c40f);
}
.gentry.passive-entry .meta .role {
  font-size: 0.84em;
  color: #d8dde6;
  line-height: 1.35;
}
.gentry.passive-entry .meta .stats {
  margin-top: 4px;
  font-size: 0.78em;
  opacity: 0.85;
}

@media (prefers-reduced-motion: reduce) {
  /* Cancel anything animating into view; instantly show the final state. */
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .character {
    animation: none !important;
  }
  .intro-flash,
  .intro-bolt,
  .intro-ember,
  .rr-confetti,
  .rr-fireworks,
  .rr-rays {
    display: none;
  }
}

/* ===== custom combat screen ===== */

main:has(#screen-custom.active) {
  max-width: 1100px;
}

.custom-pane {
  background: var(--card-soft);
  border-radius: var(--r-md);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.custom-pane-title {
  margin: 0 0 4px;
  color: var(--accent-soft);
  font-size: var(--fs-md);
}
.custom-form {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}
.custom-section {
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
}

.custom-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-size: 0.9em;
}
.custom-field-label {
  color: var(--muted);
  font-size: 0.78em;
  letter-spacing: 0.4px;
  text-transform: uppercase;
}
.custom-field input[type="text"],
.custom-field input[type="number"],
.custom-field select {
  width: 100%;
  padding: 7px 10px;
  margin: 0;
  background: var(--bg);
  color: var(--text);
  border: 2px solid var(--card-soft);
  border-radius: var(--r-sm);
  font-size: 0.95em;
  font-family: var(--font-base);
}
.custom-field select {
  cursor: pointer;
  appearance: none;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--accent-soft) 50%),
    linear-gradient(135deg, var(--accent-soft) 50%, transparent 50%);
  background-position:
    calc(100% - 16px) 50%,
    calc(100% - 11px) 50%;
  background-size:
    5px 5px,
    5px 5px;
  background-repeat: no-repeat;
  padding-right: 28px;
}
.custom-field input:focus,
.custom-field select:focus {
  border-color: var(--accent);
  outline: none;
}
.custom-field.has-error input,
.custom-field.has-error select {
  border-color: var(--bad);
}
.custom-field-error {
  min-height: 1em;
  color: var(--bad);
  font-size: 0.78em;
  display: none;
}
.custom-field-error.active {
  display: block;
}

.custom-personas {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-2);
}
.custom-stats-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-2);
  margin-top: 8px;
}
/* Below 480px both 2-column custom-* grids collapse to a single column. */
@media (max-width: 480px) {
  .custom-personas,
  .custom-stats-grid {
    grid-template-columns: 1fr;
  }
}

.custom-collapsible {
  background: var(--bg);
  border-radius: var(--r-sm);
  padding: 8px 12px;
  border: var(--border-soft);
}
.custom-collapsible > summary {
  cursor: pointer;
  color: var(--accent-soft);
  font-weight: 600;
  font-size: 0.9em;
  padding: 4px 0;
  user-select: none;
}
.custom-collapsible[open] > summary {
  margin-bottom: 6px;
}

.custom-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}
.custom-check input {
  accent-color: var(--accent);
}

/* Passive picker inside the custom-combat player form. The grid uses the
   same --passive-color hook as build-passives so each class tints its own
   cards. Cards toggle .active when the underlying checkbox is checked. */
.custom-passives-hint {
  color: var(--muted);
  font-size: 0.85em;
  padding: 4px 2px;
}
.custom-passives-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-2);
  margin-top: 6px;
}
@media (max-width: 480px) {
  .custom-passives-grid {
    grid-template-columns: 1fr;
  }
}
.custom-passive-card {
  display: grid;
  grid-template-columns: 18px 32px 1fr;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  border-radius: var(--r-sm);
  border: 1px solid rgba(255, 255, 255, 0.08);
  background: rgba(0, 0, 0, 0.18);
  cursor: pointer;
  transition:
    border-color 0.12s,
    background 0.12s;
}
.custom-passive-card:hover {
  border-color: var(--passive-color, var(--accent-soft));
}
.custom-passive-card.active {
  border-color: var(--passive-color, var(--accent));
  background: rgba(255, 255, 255, 0.04);
  box-shadow: inset 0 0 0 1px var(--passive-color, var(--accent));
}
.custom-passive-card input {
  accent-color: var(--passive-color, var(--accent));
}
.custom-passive-icon {
  width: 32px;
  height: 32px;
  color: var(--passive-color, #f1c40f);
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.custom-passive-icon svg {
  width: 100%;
  height: 100%;
}
.custom-passive-meta {
  min-width: 0;
}
.custom-passive-name {
  font-weight: 600;
  font-size: 0.9em;
  color: var(--text);
}
.custom-passive-desc {
  font-size: 0.78em;
  color: var(--muted);
  margin-top: 2px;
}

button.primary {
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--fs-md);
  padding: 12px 22px;
}
button.primary:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
}

/* sprite-aware dropdown used in screen-custom for race/class/zodiac/pet/items
   /enemies/spells. Native <select>/<option> can't render inline SVG, so we
   roll a button + listbox that mirrors the visuals of .custom-field select. */
.sprite-select {
  position: relative;
  width: 100%;
  outline: none;
}
.sprite-select-btn {
  width: 100%;
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 6px 10px;
  background: var(--bg);
  color: var(--text);
  border: 2px solid var(--card-soft);
  border-radius: var(--r-sm);
  font-size: 0.95em;
  font-weight: normal;
  text-align: left;
  cursor: pointer;
  min-height: 38px;
  transition: border-color var(--t-fast);
}
.sprite-select-btn:hover {
  transform: none;
  background: var(--bg);
  border-color: var(--accent-soft);
}
.sprite-select:focus-within .sprite-select-btn {
  border-color: var(--accent);
  outline: none;
}
.custom-field.has-error .sprite-select-btn {
  border-color: var(--bad);
}

.sprite-select-icon {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  font-family: var(--font-emoji);
  font-size: var(--fs-md);
  line-height: 1;
}
.sprite-select-icon .character {
  width: 26px;
  height: 26px;
  position: relative;
  transform: scale(0.42);
  transform-origin: center;
}
/* Item-only sprites are already scaled to fill their viewBox by
   buildItemSpriteOnly, so they shouldn't be shrunk further like full
   characters. They also need to bypass the transform stacking that lets
   the character body render at race-natural size. */
.sprite-select-icon .character:has(.character-svg.item-only) {
  transform: none;
}
.sprite-select-icon .character > svg,
.sprite-select-icon .character-svg {
  width: 100%;
  height: 100%;
}
.sprite-select-icon .fallback-item-icon {
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-emoji);
  font-size: 1.1em;
}
.sprite-select-label {
  flex: 1 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sprite-select-caret {
  flex: 0 0 auto;
  color: var(--accent-soft);
  font-size: 0.8em;
}

.sprite-select-list {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--card);
  border: 2px solid var(--accent);
  border-radius: var(--r-sm);
  box-shadow: 0 8px 22px var(--alpha-black-45);
  max-height: 280px;
  overflow-y: auto;
}
.sprite-select-list[hidden] {
  display: none;
}
.sprite-select-opt {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 5px 8px;
  border-radius: var(--r-xs);
  cursor: pointer;
  font-size: 0.92em;
  user-select: none;
}
.sprite-select-opt:hover,
.sprite-select-opt.highlighted {
  background: var(--accent-alpha-12);
}
.sprite-select-opt.active {
  color: var(--accent-soft);
}
.sprite-select-opt.active::after {
  content: "✓";
  margin-left: auto;
  color: var(--accent);
}

/* Revive proc (angel descending) — anchor is centered on the target, the
 * angel SVG hangs above and beams + sparkles flow downward onto the body. */
.revive-fx {
  position: absolute;
  pointer-events: none;
  z-index: 60;
  width: 0;
  height: 0;
}
.revive-angel {
  position: absolute;
  left: -80px;
  top: -120px;
  width: 160px;
  height: 140px;
  filter: drop-shadow(0 0 14px rgba(255, 230, 130, 0.9))
    drop-shadow(0 0 4px rgba(255, 255, 220, 0.8));
  animation: revive-angel-descend 1700ms ease-out forwards;
}
.revive-angel svg {
  width: 100%;
  height: 100%;
  display: block;
}
@keyframes revive-angel-descend {
  0% {
    transform: translateY(-44px) scale(0.9);
    opacity: 0;
  }
  18% {
    opacity: 1;
  }
  40% {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
  78% {
    transform: translateY(-3px) scale(1.02);
    opacity: 1;
  }
  100% {
    transform: translateY(-30px) scale(0.96);
    opacity: 0;
  }
}
.revive-beam {
  position: absolute;
  left: -3px;
  top: -16px;
  width: 6px;
  height: 0;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 225, 0.95) 0%,
    rgba(255, 220, 110, 0.7) 50%,
    rgba(255, 220, 110, 0) 100%
  );
  transform-origin: 50% 0;
  transform: rotate(var(--ang, 0deg));
  opacity: 0;
  filter: blur(0.5px);
  mix-blend-mode: screen;
  animation: revive-beam-grow 1100ms ease-out forwards;
  animation-delay: var(--delay, 0ms);
}
@keyframes revive-beam-grow {
  0% {
    height: 0;
    opacity: 0;
  }
  25% {
    height: 110px;
    opacity: 0.95;
  }
  70% {
    height: 130px;
    opacity: 0.75;
  }
  100% {
    height: 140px;
    opacity: 0;
  }
}
.revive-flash {
  position: absolute;
  left: -90px;
  width: 180px;
  height: 64px;
  border-radius: 50%;
  background: radial-gradient(
    ellipse at center,
    rgba(255, 255, 200, 0.9) 0%,
    rgba(255, 210, 110, 0.5) 40%,
    rgba(255, 210, 110, 0) 75%
  );
  transform: scale(0.2);
  opacity: 0;
  mix-blend-mode: screen;
  animation: revive-flash-pop 900ms ease-out 360ms forwards;
}
@keyframes revive-flash-pop {
  0% {
    transform: scale(0.2);
    opacity: 0;
  }
  35% {
    transform: scale(1.05);
    opacity: 0.95;
  }
  100% {
    transform: scale(1.4);
    opacity: 0;
  }
}
.revive-particle {
  position: absolute;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #fff4b0;
  box-shadow: 0 0 6px 1px rgba(255, 220, 120, 0.95);
  opacity: 0;
  mix-blend-mode: screen;
  animation: revive-particle-rise var(--dur, 1000ms) ease-out var(--delay, 0ms)
    forwards;
}
@keyframes revive-particle-rise {
  0% {
    transform: translate(0, 0) scale(0.6);
    opacity: 0;
  }
  15% {
    opacity: 1;
  }
  100% {
    transform: translate(0, var(--dy, -80px)) scale(0.3);
    opacity: 0;
  }
}

/* ===== item rarity (affix system) =====
 * Normal items keep the neutral chrome already inherited from slot-*.
 * Unique items get the gold treatment, modeled after the rank-1 podium
 * card (web/style.css ~3907-3911). The accent-alpha glow and gold name
 * color make uniques instantly readable in an offer.
 * `.rarity-rare` is preserved as an alias for backwards compatibility with
 * any cached/old replay logs that still ship the legacy rarity string. */
.drop-card.rarity-unique,
.drop-card.rarity-rare,
.gentry.item-entry.rarity-unique,
.gentry.item-entry.rarity-rare {
  background: linear-gradient(
    135deg,
    var(--accent-alpha-18),
    var(--card-soft) 60%
  );
  border-color: var(--accent-alpha-35);
  box-shadow:
    0 0 0 1px var(--accent-alpha-35),
    0 6px 22px rgba(249, 168, 38, 0.15);
}
.drop-card.rarity-unique h4,
.drop-card.rarity-rare h4,
.gentry.item-entry.rarity-unique h4,
.gentry.item-entry.rarity-rare h4 {
  color: var(--accent);
}
.drop-card.rarity-unique .item-power,
.drop-card.rarity-rare .item-power,
.gentry.item-entry.rarity-unique .item-power,
.gentry.item-entry.rarity-rare .item-power {
  color: var(--accent);
}

/* Template-only blurb explaining the affix rolls (shown in the wiki/catalog
 * cards which have no rarity stamped). Muted so it doesn't compete with
 * stats but still reads. */
.affix-blurb {
  margin-top: 6px;
  font-size: 0.78em;
  color: var(--muted);
  font-style: italic;
}
/* Affix catalog grid (Wiki → Afijos tab). One card per rollable affix,
   grouped by category (vanilla prefix, effect suffix). */
.affix-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 8px;
  margin-top: 8px;
}
.affix-row {
  padding: 8px 10px;
  background: rgba(255, 255, 255, 0.03);
  border-left: 3px solid rgba(180, 180, 180, 0.4);
  border-radius: var(--r-xs, 4px);
}
.affix-head {
  display: flex;
  align-items: baseline;
  gap: 6px;
}
.affix-icon {
  font-family: var(--font-emoji);
  font-size: 1em;
}
.affix-name {
  font-weight: 600;
  font-size: 0.9em;
  flex: 1;
}
.affix-range {
  font-size: 0.85em;
  color: var(--accent-soft, #b8d8ff);
  font-weight: 600;
}
.affix-meta-line {
  font-size: 0.72em;
  color: var(--muted);
  margin-top: 2px;
}
.affix-desc {
  font-size: 0.78em;
  color: rgba(220, 220, 220, 0.85);
  margin-top: 4px;
  line-height: 1.35;
  font-style: italic;
}
/* Live "cada 5s ≈ 87 daño físico" / "cura ≈ 32" line shown below the
   passive description in the wiki, the in-run pick screen, and the
   passive detail panel. Subtle accent so it reads as a derived value
   rather than another description string. */
.passive-proc {
  margin-top: 4px;
  font-size: 0.85em;
  color: #b8d8ff;
  font-style: italic;
}

/* =============================================================
 * Shaman + Druid proc visuals (spawned by Sprites.* helpers).
 * Pattern: positioned <div> at the target/caster center, removed
 * from the DOM after a fixed timeout by the spawn helper.
 * ============================================================= */

/* Bear Swipe (Druid Forma de Oso) — three red claw rakes drag across
 * the target. .bear-claw is a thin angled bar drawn with a linear
 * gradient that fades in then sweeps out, leaving a quick red afterglow. */
.bear-swipe-fx {
  position: absolute;
  width: 90px;
  height: 70px;
  pointer-events: none;
  transform: translate(-50%, -50%);
  z-index: 6;
  --swipe-dir: 1;
}
.bear-swipe-fx .bear-claw {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 76px;
  height: 6px;
  margin-left: -38px;
  margin-top: -3px;
  background: linear-gradient(
    90deg,
    rgba(255, 60, 50, 0) 0%,
    rgba(255, 90, 60, 0.95) 22%,
    rgba(255, 220, 200, 1) 50%,
    rgba(255, 90, 60, 0.95) 78%,
    rgba(255, 60, 50, 0) 100%
  );
  border-radius: 6px;
  box-shadow: 0 0 10px rgba(255, 60, 50, 0.65);
  opacity: 0;
  animation: bear-claw-sweep 480ms ease-out forwards;
  filter: drop-shadow(0 0 4px rgba(255, 80, 60, 0.8));
}
.bear-swipe-fx .bear-claw-1 {
  transform: rotate(calc(-18deg * var(--swipe-dir))) translateY(-18px) scaleX(var(--swipe-dir));
  animation-delay: 0ms;
}
.bear-swipe-fx .bear-claw-2 {
  transform: rotate(calc(0deg)) scaleX(var(--swipe-dir));
  animation-delay: 60ms;
}
.bear-swipe-fx .bear-claw-3 {
  transform: rotate(calc(18deg * var(--swipe-dir))) translateY(18px) scaleX(var(--swipe-dir));
  animation-delay: 120ms;
}
@keyframes bear-claw-sweep {
  0%   { opacity: 0; transform-origin: 0% 50%; }
  10%  { opacity: 1; }
  100% { opacity: 0; }
}

/* Nature Sparkle (Druid heals + Shaman healing totem tick). A cluster
 * of glowing green motes spirals upward from the caster, joined by a
 * faint expanding ring. Motes fly out via custom-prop --mx/--my, then
 * fade. */
.nature-sparkle-fx {
  position: absolute;
  width: 0;
  height: 0;
  pointer-events: none;
  transform: translate(-50%, -50%);
  z-index: 6;
}
.nature-sparkle-fx .nature-mote {
  position: absolute;
  left: 0;
  top: 0;
  width: 10px;
  height: 12px;
  margin-left: -5px;
  margin-top: -6px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 35% 35%, #f0ffd0 0%, #8ce070 45%, #2a8830 75%, transparent 100%);
  box-shadow: 0 0 10px rgba(120, 230, 120, 0.85);
  opacity: 0;
  animation: nature-mote-rise var(--dur, 800ms) ease-out forwards;
}
/* Holy variant — gold/white motes for cleric heals & buffs. Reuses the
 * nature-sparkle skeleton so the heal feedback stays consistent across
 * druid/cleric, just with the palette flipped to divine light. */
.nature-sparkle-fx-holy .nature-mote {
  background:
    radial-gradient(circle at 35% 35%, #ffffff 0%, #fff5b0 45%, #cba34a 78%, transparent 100%);
  box-shadow: 0 0 12px rgba(255, 230, 140, 0.95);
}
.nature-sparkle-fx-holy .nature-ring {
  border-color: rgba(255, 230, 140, 0.85);
  box-shadow: 0 0 14px rgba(255, 220, 120, 0.6);
}
.nature-sparkle-fx-big .nature-mote {
  width: 14px;
  height: 16px;
  margin-left: -7px;
  margin-top: -8px;
}
@keyframes nature-mote-rise {
  0%   { opacity: 0; transform: translate(0, 0) scale(0.6); }
  18%  { opacity: 1; }
  100% { opacity: 0; transform: translate(var(--mx, 0), var(--my, -40px)) scale(1.15); }
}
.nature-sparkle-fx .nature-ring {
  position: absolute;
  left: 0;
  top: 0;
  width: 60px;
  height: 24px;
  margin-left: -30px;
  margin-top: -12px;
  border-radius: 50%;
  border: 2px solid rgba(140, 230, 120, 0.7);
  box-shadow: 0 0 12px rgba(140, 230, 120, 0.5);
  opacity: 0;
  animation: nature-ring-expand 700ms ease-out forwards;
}
.nature-sparkle-fx-big .nature-ring {
  width: 90px;
  height: 36px;
  margin-left: -45px;
  margin-top: -18px;
}
@keyframes nature-ring-expand {
  0%   { opacity: 0; transform: scale(0.4); }
  20%  { opacity: 0.9; }
  100% { opacity: 0; transform: scale(1.7); }
}

/* Energy Pulse (Shaman Tótem Guardián tick) — blue shield bubble
 * expands around the caster, fades out. */
.energy-pulse-fx {
  position: absolute;
  width: 120px;
  height: 120px;
  margin-left: -60px;
  margin-top: -60px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 5;
  background:
    radial-gradient(circle, rgba(150, 210, 255, 0.45) 0%, rgba(120, 180, 240, 0.18) 55%, transparent 75%);
  border: 2px solid rgba(180, 220, 255, 0.85);
  box-shadow:
    0 0 24px rgba(140, 200, 255, 0.7),
    inset 0 0 18px rgba(200, 230, 255, 0.55);
  opacity: 0;
  animation: energy-pulse 600ms ease-out forwards;
}
/* Divine variant — bigger golden shield bubble for Cleric Escudo Divino.
 * Slightly larger pool + warmer halo so the player can tell the heavier
 * shield grant from the regular Escudo de Fe / Tótem Guardián tick. */
.energy-pulse-fx-divine {
  width: 160px;
  height: 160px;
  margin-left: -80px;
  margin-top: -80px;
  background:
    radial-gradient(circle, rgba(255, 230, 140, 0.55) 0%, rgba(255, 200, 80, 0.22) 55%, transparent 75%);
  border-color: rgba(255, 230, 140, 0.95);
  box-shadow:
    0 0 32px rgba(255, 215, 110, 0.85),
    inset 0 0 22px rgba(255, 240, 180, 0.65);
  animation: energy-pulse 760ms ease-out forwards;
}
@keyframes energy-pulse {
  0%   { opacity: 0; transform: scale(0.4); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1.4); }
}

/* Lightning Strike (Shaman Tormenta Eléctrica + a small variant for
 * Toque Eléctrico / Sobrecarga / Cadena de Rayos). A central bolt
 * column slams down with two side forks; brief glow halo and screen
 * flash. */
.lightning-strike-fx {
  position: absolute;
  width: 120px;
  height: 220px;
  pointer-events: none;
  transform: translate(-50%, -90%);
  z-index: 6;
}
.lightning-strike-fx-small {
  width: 60px;
  height: 110px;
}
.lightning-strike-fx .lstrike-bolt {
  position: absolute;
  left: 50%;
  top: 0;
  width: 14px;
  height: 100%;
  margin-left: -7px;
  background:
    linear-gradient(180deg,
      rgba(200, 230, 255, 0) 0%,
      rgba(180, 220, 255, 0.9) 15%,
      rgba(255, 255, 255, 1) 50%,
      rgba(180, 220, 255, 0.9) 85%,
      rgba(200, 230, 255, 0) 100%
    );
  clip-path: polygon(50% 0%, 70% 30%, 55% 30%, 80% 60%, 60% 60%, 80% 100%, 30% 60%, 50% 60%, 25% 30%, 45% 30%);
  filter: drop-shadow(0 0 12px rgba(160, 210, 255, 0.95));
  opacity: 0;
  animation: lstrike-flash 480ms ease-out forwards;
}
.lightning-strike-fx-small .lstrike-bolt {
  width: 8px;
  margin-left: -4px;
  filter: drop-shadow(0 0 6px rgba(160, 210, 255, 0.9));
}
.lightning-strike-fx .lstrike-glow {
  position: absolute;
  left: 50%;
  top: 75%;
  width: 100px;
  height: 60px;
  margin-left: -50px;
  margin-top: -30px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(220, 240, 255, 0.85) 0%, rgba(120, 180, 255, 0.3) 50%, transparent 75%);
  opacity: 0;
  animation: lstrike-glow 480ms ease-out forwards;
}
.lightning-strike-fx-small .lstrike-glow {
  width: 50px;
  height: 30px;
  margin-left: -25px;
  margin-top: -15px;
}
.lightning-strike-fx .lstrike-fork {
  position: absolute;
  top: 70%;
  width: 36px;
  height: 4px;
  background: linear-gradient(90deg, rgba(255, 255, 255, 0.95), rgba(180, 220, 255, 0));
  filter: drop-shadow(0 0 4px rgba(200, 230, 255, 0.9));
  opacity: 0;
  animation: lstrike-flash 420ms ease-out forwards;
  animation-delay: 80ms;
}
.lightning-strike-fx .lstrike-fork-l {
  right: 50%;
  margin-right: 2px;
  transform: rotate(20deg);
  transform-origin: 100% 50%;
}
.lightning-strike-fx .lstrike-fork-r {
  left: 50%;
  margin-left: 2px;
  transform: rotate(-20deg);
  transform-origin: 0% 50%;
}
@keyframes lstrike-flash {
  0%   { opacity: 0; }
  20%  { opacity: 1; }
  60%  { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes lstrike-glow {
  0%   { opacity: 0; transform: scale(0.5); }
  35%  { opacity: 1; transform: scale(1.2); }
  100% { opacity: 0; transform: scale(1.8); }
}

/* Moonfire projectile (Druid Fuego Lunar) — silver crescent moon that
 * flies from caster to target. Re-uses the .projectile flight controls
 * from the generic projectile system: --dx/--dy/--dur are set by
 * Sprites.spawnProjectile. */
.projectile-moonfire {
  position: absolute;
  width: 32px;
  height: 32px;
  margin-left: -16px;
  margin-top: -16px;
  pointer-events: none;
  z-index: 5;
  animation: projectile-fly var(--dur, 480ms) ease-in forwards;
  filter: drop-shadow(0 0 10px rgba(220, 230, 255, 0.85));
}
.projectile-moonfire::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at 65% 50%, #f0f4ff 0%, #cad4f0 40%, #6878a8 100%);
  border-radius: 50%;
  mask:
    radial-gradient(circle at 70% 50%, transparent 38%, #000 40%);
  -webkit-mask:
    radial-gradient(circle at 70% 50%, transparent 38%, #000 40%);
}
.projectile-moonfire::after {
  content: '';
  position: absolute;
  inset: -8px;
  background: radial-gradient(circle, rgba(220, 230, 255, 0.6) 0%, transparent 65%);
  border-radius: 50%;
  animation: projectile-trail var(--dur, 480ms) ease-out infinite;
}
.moonfire-impact-fx {
  position: absolute;
  width: 80px;
  height: 80px;
  margin-left: -40px;
  margin-top: -40px;
  pointer-events: none;
  z-index: 6;
  border-radius: 50%;
  border: 2px solid rgba(220, 230, 255, 0.85);
  background: radial-gradient(circle, rgba(240, 244, 255, 0.55) 0%, rgba(160, 180, 220, 0.2) 50%, transparent 75%);
  box-shadow: 0 0 28px rgba(200, 220, 255, 0.85);
  opacity: 0;
  animation: moonfire-impact 440ms ease-out forwards;
}
@keyframes moonfire-impact {
  0%   { opacity: 0; transform: scale(0.4); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: scale(1.6); }
}

/* Hurricane (Druid Huracán) — green leaves swirl around the target.
 * Each leaf rotates around the center, starts inward, expands out. */
.hurricane-fx {
  position: absolute;
  width: 130px;
  height: 130px;
  margin-left: -65px;
  margin-top: -65px;
  pointer-events: none;
  z-index: 6;
  animation: hurricane-rotate 700ms ease-out forwards;
}
.hurricane-fx::before {
  content: '';
  position: absolute;
  inset: 25%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(120, 220, 130, 0.45) 0%, transparent 65%);
  filter: blur(4px);
  animation: hurricane-core 700ms ease-out forwards;
}
.hurricane-fx .hurricane-leaf {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 16px;
  height: 10px;
  margin-left: -8px;
  margin-top: -5px;
  background: linear-gradient(90deg, #2a7a3a 0%, #6ad07a 60%, #b8e8c0 100%);
  border-radius: 50% 50% 50% 50% / 80% 40% 80% 40%;
  transform: rotate(var(--ang, 0deg)) translateX(20px);
  opacity: 0;
  animation: hurricane-leaf-fly 700ms ease-out forwards;
  animation-delay: var(--delay, 0ms);
  box-shadow: 0 0 4px rgba(100, 200, 120, 0.75);
}
@keyframes hurricane-rotate {
  0%   { transform: scale(0.4) rotate(0deg); opacity: 0; }
  20%  { opacity: 1; }
  100% { transform: scale(1.2) rotate(220deg); opacity: 0; }
}
@keyframes hurricane-core {
  0%   { opacity: 0; transform: scale(0.6); }
  40%  { opacity: 0.95; }
  100% { opacity: 0; transform: scale(1.4); }
}
@keyframes hurricane-leaf-fly {
  0%   { opacity: 0; transform: rotate(var(--ang)) translateX(10px); }
  25%  { opacity: 1; }
  100% { opacity: 0; transform: rotate(calc(var(--ang) + 180deg)) translateX(60px); }
}
