/* ============================================
   VETUU — Actor Styles
   
   TERMINOLOGY:
   - Character = Player (unique, single instance)
   - Actor = Everything else (NPCs, enemies, guards, etc.)
   - Both use .actor base class; character adds .character
   
   STACKING CONTEXT:
   Each .actor creates its own stacking context via isolation: isolate.
   Child z-indexes are LOCAL to that actor:
   
     0: .shadow (ground shadow)
     1: .sprite (character image)  
     2: .hp-bar (health bar) - unified for all actors
     3: .level-badge (level indicator)
   
   Actor-to-actor ordering: z-index 15 for actors, 100 for character.
   
   TINTING SYSTEM:
   All actors use the same skeleton sprite. Visual differentiation via
   data-tint attribute + hue-rotate filters:
   
     data-tint="guard"    → blue/cyan (180deg)
     data-tint="medic"    → pink (320deg)
     data-tint="civilian" → tan (35deg)
     data-tint="passive"  → green (60deg)
     data-tint="engaged"  → red (-30deg)
     data-tint="alpha"    → gold (40deg)
     data-tint="boss"     → purple (280deg)
   
   No data-tint = yellow base sprite (character, basic NPCs)
   ============================================ */

/* ---------- Base Actor ---------- */
.actor {
  position: absolute;
  width: var(--tile-size);
  height: calc(var(--tile-size) * 4 / 3); /* 32px for 24px tiles */
  z-index: 15;
  /* Create local stacking context - child z-indexes don't leak globally */
  isolation: isolate;
  /* Containment for performance - isolate layout/paint calculations */
  contain: layout style;
  /* GPU compositing for crisp rendering at all zoom levels */
  backface-visibility: hidden;
  transform-style: preserve-3d;
  
  /* Position via CSS custom properties - JS sets these, CSS handles transform
     This keeps animation on compositor thread for smooth 60fps */
  --actor-x: 0px;
  --actor-y: 0px;
  transform: translate3d(var(--actor-x), var(--actor-y), 0);
  
  /* Spawn fade-in transition - smooth entry for all actors */
  opacity: 1;
  transition: opacity var(--fade-quick) ease-out;
}

/* Spawning state - start invisible, fade in when class is removed */
.actor.spawning {
  opacity: 0;
}

/* Despawning state - fade out */
.actor.despawning {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--fade-quick) ease-out !important;
}

/* Worker despawning - explicit rule to override .npc transitions */
.worker.despawning {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--fade-quick) ease-out !important;
}

/* ============================================
   CARGO WORKER VARIANTS
   Styling for workers in cargo scenarios
   ============================================ */

/* Base cargo worker (no special styling, inherits from .worker) */
.worker.cargo-worker {
  /* Placeholder for future cargo-worker-specific styles */
}

/* Train cargo workers appear UNDER train cars (z-index 11)
   This creates the visual effect of workers working beside/under the train */
.worker.train-cargo-worker,
.worker.cargo-worker--under-vehicle {
  z-index: 10;
}

/* Sprite element - holds character image */
.actor > .sprite {
  position: absolute;
  inset: 0;
  z-index: 1; /* Above shadow (0) */
  /* Sprite rendering */
  background-image: var(--sprite-idle);
  background-size: 100% 100%;
  background-repeat: no-repeat;
  /* Ensure pixel art scales sharply */
  image-rendering: pixelated;
  image-rendering: -moz-crisp-edges;
  image-rendering: crisp-edges;
  /* Scale from feet so bob keeps feet planted */
  transform-origin: center bottom;
  /* Combined filter: outline + optional tint
     --actor-tint is empty by default, set via data-tint attribute */
  filter: 
    drop-shadow(1px 0 0 var(--sprite-outline))
    drop-shadow(-1px 0 0 var(--sprite-outline))
    drop-shadow(0 1px 0 var(--sprite-outline))
    drop-shadow(0 -1px 0 var(--sprite-outline))
    var(--actor-tint);
  /* GPU layer via 3D transform context */
  backface-visibility: hidden;
  transform: translate3d(0, 0, 0);
  /* Smooth return from leap/dash animations */
  transition: transform 0.1s ease-out;
  /* Allow clicks to pass through to parent .actor element */
  pointer-events: none;
  /* PERF: Containment to isolate paint/layout calculations */
  contain: strict;
}

/* ============================================
   TINTING SYSTEM
   Uses data-tint attribute to set --actor-tint custom property.
   Avoids repeating full filter chain for each tint variation.
   ============================================ */

/* Base: no tint (empty custom property) */
.actor { --actor-tint: ; }

/* Role tints - permanent, set at creation */
.actor[data-tint="guard"]    { --actor-tint: hue-rotate(180deg); }
.actor[data-tint="medic"]    { --actor-tint: hue-rotate(320deg); }
.actor[data-tint="civilian"] { --actor-tint: hue-rotate(35deg) saturate(0.9); }
.actor[data-tint="alpha"]    { --actor-tint: hue-rotate(40deg) saturate(1.3); }
.actor[data-tint="boss"]     { --actor-tint: hue-rotate(280deg) saturate(1.3); }

/* State tints - dynamic, change based on combat state */
.actor[data-tint="passive"]  { --actor-tint: hue-rotate(60deg); }
.actor[data-tint="engaged"]  { --actor-tint: hue-rotate(-30deg) saturate(1.5); }

/* Selection glow color - set per actor type */
.actor { --selection-glow: var(--danger); }
.npc   { --selection-glow: var(--success); }

/* Targeted state - adds selection glow to filter */
.actor.targeted > .sprite {
  filter: 
    drop-shadow(1px 0 0 var(--sprite-outline))
    drop-shadow(-1px 0 0 var(--sprite-outline))
    drop-shadow(0 1px 0 var(--sprite-outline))
    drop-shadow(0 -1px 0 var(--sprite-outline))
    drop-shadow(0 0 2px var(--selection-glow))
    var(--actor-tint);
}

/* ============================================
   UNIFIED HP BAR
   Same structure for character and all actors.
   Uses child elements (not pseudo-elements) for flexibility.
   ============================================ */
.actor > .hp-bar {
  position: absolute;
  top: -8px;
  left: 2px;
  right: 2px;
  height: 4px;
  background: var(--hp-bg, var(--alpha-black-60));
  overflow: hidden;
  z-index: 2;
  transition: opacity var(--fade-standard) ease;
}

.actor > .hp-bar > .hp-fill {
  display: block;
  height: 100%;
  width: 100%;
  background: var(--enemy-hp-color);
  transform: scale3d(calc(var(--hp-pct, 100) / 100), 1, 1);
  transform-origin: left center;
  transition: transform 0.2s ease;
}

/* Character (player) uses green HP bar */
.character > .hp-bar > .hp-fill {
  background: var(--player-hp-color);
}

/* NPCs use NPC HP color */
.npc > .hp-bar > .hp-fill {
  background: var(--npc-hp-color);
}

/* Hide HP bar when at full health */
.actor.full-health > .hp-bar {
  opacity: 0;
}

/* Dynamic silhouette shadow - projects the sprite shape as a shadow
   CSS custom properties are set by time.js based on sun position:
   --shadow-skew: skewX angle (0 for overhead sun, +/- for east/west)
   --shadow-scale-y: vertical flatten (shorter at noon, longer at dawn/dusk)
   --shadow-scale-x: horizontal stretch (wider at low sun angles)
   (opacity is fixed at 0.2 - not dynamic)
*/
.actor > .shadow {
  position: absolute;
  z-index: 0; /* Behind sprite (1) */
  /* Match sprite dimensions */
  width: 100%;
  height: 100%;
  bottom: 0;
  left: 0;
  /* Use same sprite as the character for silhouette shadow */
  background-image: var(--sprite-idle);
  background-size: 100% 100%;
  background-repeat: no-repeat;
  image-rendering: pixelated;
  /* Transform to create projected shadow effect - values from :root (set by time.js)
     Order MUST match shadow-bounce animation: scale3d → skewX → translate3d */
  transform-origin: center bottom;
  transform: 
    scale3d(var(--shadow-scale-x, 1.1), var(--shadow-scale-y, 0.4), 1)
    skewX(var(--shadow-skew, 0deg))
    translate3d(0, 0, 0);
  /* PERF: Removed blur filter - just brightness(0) for black silhouette
     The transform flatten + low opacity already creates soft appearance */
  filter: brightness(0);
  opacity: 0.2;
  pointer-events: none;
  /* Transition smooths: animation exit, sun position updates */
  transition: transform var(--fade-snappy) ease-out, opacity var(--fade-slow) ease-out;
  /* PERF: Containment to isolate paint/layout calculations */
  contain: strict;
}

/* Walk bob for all moving actors - sprite bobs, shadow bounces in sync
   IMPORTANT: Use translate3d NOT scale3d for the bob animation.
   Scaling causes drop-shadow filter to bleed over edges (outline appears thicker).
   Translation moves the sprite down, simulating the same head-bob effect without
   affecting the outline rendering. */
.actor.moving > .sprite {
  animation: walk-bob 0.2s ease-in-out infinite;
}

/* Shadow bounce synced with sprite bob - matches footfall rhythm.
   Transform transition disabled during animation to prevent conflict. */
.actor.moving > .shadow {
  animation: shadow-bounce 0.2s ease-in-out infinite;
  transition: opacity var(--fade-slow) ease-out;
}

@keyframes walk-bob {
  /* Translate-based bob (NOT scale) to avoid drop-shadow bleed on outline.
     Moving down 1px simulates the head dipping, feet stay planted via transform-origin: center bottom */
  0%, 100% { transform: translate3d(0, 0, 0); }
  50% { transform: translate3d(0, 1px, 0); }
}

/* Shadow bounce: subtle squash on footfall (50%), return to normal (0%/100%)
   Uses CSS variables so sun position is respected.
   Note: skewX has no 3D equivalent but is still GPU-composited in the transform chain */
@keyframes shadow-bounce {
  0%, 100% { 
    transform: 
      scale3d(var(--shadow-scale-x, 1.1), var(--shadow-scale-y, 0.4), 1) 
      skewX(var(--shadow-skew, 0deg)) 
      translate3d(0, 0, 0); 
  }
  50% { 
    transform: 
      scale3d(calc(var(--shadow-scale-x, 1.1) * 1.03), calc(var(--shadow-scale-y, 0.4) * 0.94), 1) 
      skewX(var(--shadow-skew, 0deg)) 
      translate3d(0, 0, 0);
  }
}

/* ---------- Character (Player) ---------- */
/* Character uses #player ID for JS performance + .character class for styling */
#player,
.character {
  z-index: 100;
  /* No CSS transition - movement animated via JS for Safari smoothness */
  will-change: transform;
}

/* Character states */
.character.ghost {
  opacity: 0.5;
  animation: ghost-pulse 2s ease-in-out infinite;
  filter: brightness(1.2) saturate(0.5);
}

.character.sprinting {
  filter: brightness(1.1);
}

/* Dashing visual effect for Leap and similar abilities
   Creates an airborne woosh: fast arc with squash/stretch
   PERF: No blur filter - it's expensive on animated elements */
.character.dashing {
  filter: brightness(1.3) saturate(1.1);
}

.character.dashing > .sprite {
  /* Leap arc: anticipation squash → stretch up → arc peak → land squash */
  animation: leap-arc 0.25s cubic-bezier(0.2, 0, 0.3, 1) forwards;
}

.character.dashing > .shadow {
  /* Shadow compresses during leap, shows we're airborne */
  animation: leap-shadow 0.25s cubic-bezier(0.2, 0, 0.3, 1) forwards;
}

@keyframes leap-arc {
  /* Anticipation squash */
  0% { 
    transform: scale3d(1.15, 0.85, 1) translate3d(0, 2px, 0);
  }
  /* Launch - stretch and rise */
  20% { 
    transform: scale3d(0.9, 1.2, 1) translate3d(0, -12px, 0);
  }
  /* Peak of arc */
  50% { 
    transform: scale3d(0.85, 1.25, 1) translate3d(0, -16px, 0);
  }
  /* Descending */
  80% { 
    transform: scale3d(0.9, 1.15, 1) translate3d(0, -8px, 0);
  }
  /* Land squash */
  100% { 
    transform: scale3d(1.1, 0.9, 1) translate3d(0, 1px, 0);
  }
}

@keyframes leap-shadow {
  0% { 
    opacity: 0.2;
    transform: scale3d(var(--shadow-scale-x, 1.1), var(--shadow-scale-y, 0.4), 1)
               skewX(calc(var(--shadow-skew, 0deg) + var(--shadow-facing-offset, 0deg)))
               translate3d(0, 0, 0);
  }
  /* Shadow shrinks when airborne (character higher = shadow smaller/lighter) */
  50% { 
    opacity: 0.08;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 0.6), calc(var(--shadow-scale-y, 0.4) * 0.6), 1)
               skewX(calc(var(--shadow-skew, 0deg) + var(--shadow-facing-offset, 0deg)))
               translate3d(0, 0, 0);
  }
  100% { 
    opacity: 0.2;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 1.15), calc(var(--shadow-scale-y, 0.4) * 1.1), 1)
               skewX(calc(var(--shadow-skew, 0deg) + var(--shadow-facing-offset, 0deg)))
               translate3d(0, 0, 0);
  }
}

/* ---------- Vehicle Knockback Animation ---------- */
/* Character gets hit by vehicle - flung into the air with smooth arc
   Scale INCREASES at peak to simulate height (character appears larger when "closer" to camera in air)
   Slower, more cinematic feel with proper arc trajectory */
.character.vehicle-knockback {
  /* Flash red on impact */
  filter: brightness(1.4) saturate(0.8);
}

.character.vehicle-knockback > .sprite {
  /* Flung into air - smooth parabolic arc
     No 'forwards' fill - animation ends at identity transform, JS cleans up */
  animation: vehicle-knockback-arc var(--knockback-duration, 600ms) cubic-bezier(0.25, 0.1, 0.25, 1);
}

.character.vehicle-knockback > .shadow {
  /* Shadow shrinks during flight (character is higher = smaller shadow) */
  animation: vehicle-knockback-shadow var(--knockback-duration, 600ms) cubic-bezier(0.25, 0.1, 0.25, 1);
}

/* Smooth parabolic arc - fewer keyframes for smoother interpolation
   Scale increases at peak (player appears larger when "higher" = closer to camera)
   IMPORTANT: End at scale3d(1,1,1) to avoid sub-pixel artifacts on recovery */
@keyframes vehicle-knockback-arc {
  /* Impact - squash on hit */
  0% { 
    transform: scale3d(1.3, 0.7, 1) translate3d(0, 4px, 0) rotate3d(0, 0, 1, 0deg);
  }
  /* Peak of arc - maximum scale, maximum height, slight rotation */
  45% { 
    transform: scale3d(1.5, 1.5, 1) translate3d(0, -52px, 0) rotate3d(0, 0, 1, -15deg);
  }
  /* Landing squash */
  85% { 
    transform: scale3d(1.2, 0.8, 1) translate3d(0, 2px, 0) rotate3d(0, 0, 1, 0deg);
  }
  /* Settle to clean 1:1 scale - no sub-pixel artifacts */
  100% { 
    transform: scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 0, 1, 0deg);
  }
}

@keyframes vehicle-knockback-shadow {
  /* Impact - shadow normal */
  0% { 
    opacity: var(--shadow-opacity, 0.2);
    transform: scale3d(var(--shadow-scale-x, 1.1), var(--shadow-scale-y, 0.4), 1);
  }
  /* Peak - shadow at minimum (player highest) */
  45% { 
    opacity: 0.04;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 0.25), calc(var(--shadow-scale-y, 0.4) * 0.25), 1);
  }
  /* Landing - shadow expands briefly */
  85% { 
    opacity: 0.25;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 1.15), calc(var(--shadow-scale-y, 0.4) * 1.1), 1);
  }
  /* Settle to normal - clean values */
  100% { 
    opacity: var(--shadow-opacity, 0.2);
    transform: scale3d(var(--shadow-scale-x, 1.1), var(--shadow-scale-y, 0.4), 1);
  }
}

/* Heavy hit variant - for high-speed collisions (bigger arc, more dramatic) */
.character.vehicle-knockback-heavy > .sprite {
  /* No 'forwards' fill - animation ends at identity transform, JS cleans up */
  animation: vehicle-knockback-heavy-arc var(--knockback-duration, 900ms) cubic-bezier(0.25, 0.1, 0.25, 1);
}

.character.vehicle-knockback-heavy > .shadow {
  animation: vehicle-knockback-heavy-shadow var(--knockback-duration, 900ms) cubic-bezier(0.25, 0.1, 0.25, 1);
}

/* Heavy arc - more extreme scale and height, with tumble rotation
   IMPORTANT: End at scale3d(1,1,1) to avoid sub-pixel artifacts on recovery */
@keyframes vehicle-knockback-heavy-arc {
  /* Violent impact - extreme squash */
  0% { 
    transform: scale3d(1.5, 0.5, 1) translate3d(0, 6px, 0) rotate3d(0, 0, 1, 0deg);
  }
  /* Apex - maximum scale, max height, peak rotation */
  45% { 
    transform: scale3d(1.7, 1.7, 1) translate3d(0, -75px, 0) rotate3d(0, 0, 1, -30deg);
  }
  /* Crumple landing - squash on impact */
  82% { 
    transform: scale3d(1.25, 0.75, 1) translate3d(0, 4px, 0) rotate3d(0, 0, 1, 0deg);
  }
  /* Settle to clean 1:1 scale */
  100% { 
    transform: scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 0, 1, 0deg);
  }
}

/* Heavy shadow - follows the same arc */
@keyframes vehicle-knockback-heavy-shadow {
  /* Impact - shadow normal */
  0% { 
    opacity: var(--shadow-opacity, 0.2);
    transform: scale3d(var(--shadow-scale-x, 1.1), var(--shadow-scale-y, 0.4), 1);
  }
  /* Peak - shadow at minimum (player highest) */
  45% { 
    opacity: 0.03;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 0.18), calc(var(--shadow-scale-y, 0.4) * 0.18), 1);
  }
  /* Landing impact - shadow expands */
  82% { 
    opacity: 0.28;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 1.2), calc(var(--shadow-scale-y, 0.4) * 1.15), 1);
  }
  /* Settle to normal - clean values */
  100% { 
    opacity: var(--shadow-opacity, 0.2);
    transform: scale3d(var(--shadow-scale-x, 1.1), var(--shadow-scale-y, 0.4), 1);
  }
}

.character.healing {
  animation: heal-glow 0.5s ease-out forwards;
}

@keyframes heal-glow {
  0% { filter: brightness(1.5) saturate(1.3); }
  100% { filter: none; }
}

@keyframes ghost-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 0.6; }
}

.character.downed {
  opacity: 0.6;
  filter: grayscale(0.6) brightness(0.8);
  animation: downed-pulse 2s ease-in-out infinite;
}

@keyframes downed-pulse {
  0%, 100% { opacity: 0.4; filter: grayscale(0.6) brightness(0.7); }
  50% { opacity: 0.7; filter: grayscale(0.4) brightness(0.9); }
}

.character.downed > .shadow {
  opacity: 0;
}

/* ---------- Player Silhouette (Under Roof) ---------- */
/* Shows when player is beneath an obscuring element like the depot roof.
   Positioned above the roof so player remains visible. */
.player-silhouette {
  position: absolute;
  width: var(--tile-size);
  height: calc(var(--tile-size) * 4 / 3);
  pointer-events: none;
  /* Above roof (z-index 15) and its darkness overlay */
  z-index: 17;
  /* Hidden by default - instant hide, quick fade-in */
  opacity: 0;
  transition: opacity 0.1s ease-out;
}

/* Visible state - slightly slower fade-in for smoother appearance */
.player-silhouette.visible {
  opacity: 1;
  transition: opacity 0.15s ease-out;
}

.player-silhouette > .sprite {
  position: absolute;
  width: var(--tile-size);
  height: calc(var(--tile-size) * 4 / 3);
  background-image: var(--sprite-idle);
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center bottom;
  /* Black silhouette with subtle white stroke (4 directional shadows) */
  filter: 
    brightness(0)
    drop-shadow(1px 0 0 var(--alpha-white-50))
    drop-shadow(-1px 0 0 var(--alpha-white-50))
    drop-shadow(0 1px 0 var(--alpha-white-50))
    drop-shadow(0 -1px 0 var(--alpha-white-50));
  opacity: 0.7;
}

/* Walk bob animation mirrors player sprite movement */
.player-silhouette.moving > .sprite {
  animation: walk-bob 0.2s ease-in-out infinite;
}

/* ---------- Character Death Animation ---------- */
/* Initial death animation - plays while screen fades to black */
.character.dying-anim > .sprite {
  animation: player-death 3s ease-out forwards;
}

.character.dying-anim > .shadow {
  animation: player-death-shadow 3s ease-out forwards;
}

/* 
 * Death animations use --death-dir (1 or -1) set by JS to randomize fall direction.
 * This creates visual variety when multiple corpses are on screen.
 * Player death is 3s (slower/more cinematic) vs 1s for enemies.
 */
@keyframes player-death {
  /* Standing - moment of fatal hit */
  0% {
    transform: scale3d(1, 1, 1) translate3d(0, 0, 0) rotate3d(0, 0, 1, 0deg);
    filter: brightness(1);
  }
  /* Impact jolt - sharp recoil from fatal blow */
  5% {
    transform: scale3d(1.15, 0.85, 1) translate3d(calc(3px * var(--death-dir, -1)), 3px, 0) rotate3d(0, 0, 1, calc(-10deg * var(--death-dir, -1)));
    filter: brightness(2.2) saturate(0.3);
  }
  /* Counter-stagger - body tries to compensate */
  12% {
    transform: scale3d(0.92, 1.05, 1) translate3d(calc(-2px * var(--death-dir, -1)), -3px, 0) rotate3d(0, 0, 1, calc(6deg * var(--death-dir, -1)));
    filter: brightness(1.4) saturate(0.25);
  }
  /* Second stagger - losing balance */
  22% {
    transform: scale3d(1.05, 0.92, 1) translate3d(calc(2px * var(--death-dir, -1)), 2px, 0) rotate3d(0, 0, 1, calc(-4deg * var(--death-dir, -1)));
    filter: brightness(1.1) saturate(0.2);
  }
  /* Knees buckle - sudden vertical drop begins */
  35% {
    transform: scale3d(1.08, 0.8, 1) translate3d(calc(1px * var(--death-dir, -1)), 5px, 0) rotate3d(0, 0, 1, calc(-8deg * var(--death-dir, -1)));
    filter: brightness(0.9) saturate(0.15) grayscale(0.3);
    opacity: 0.95;
  }
  /* Crumpling - rapid vertical collapse */
  50% {
    transform: scale3d(1.2, 0.6, 1) translate3d(calc(4px * var(--death-dir, -1)), 9px, 0) rotate3d(0, 0, 1, calc(-14deg * var(--death-dir, -1)));
    filter: brightness(0.7) saturate(0.1) grayscale(0.5);
    opacity: 0.85;
  }
  /* Secondary impact - hits ground */
  68% {
    transform: scale3d(1.3, 0.45, 1) translate3d(calc(3px * var(--death-dir, -1)), 12px, 0) rotate3d(0, 0, 1, calc(-20deg * var(--death-dir, -1)));
    filter: brightness(0.55) saturate(0.05) grayscale(0.75);
    opacity: 0.7;
  }
  /* Settling - micro-shift as body settles */
  85% {
    transform: scale3d(1.38, 0.38, 1) translate3d(calc(5px * var(--death-dir, -1)), 14px, 0) rotate3d(0, 0, 1, calc(-25deg * var(--death-dir, -1)));
    filter: brightness(0.45) grayscale(0.9);
    opacity: 0.55;
  }
  /* Final rest - crumpled heap */
  100% {
    transform: scale3d(1.4, 0.35, 1) translate3d(calc(4px * var(--death-dir, -1)), 15px, 0) rotate3d(0, 0, 1, calc(-22deg * var(--death-dir, -1)));
    filter: brightness(0.4) grayscale(1);
    opacity: 0.5;
  }
}

@keyframes player-death-shadow {
  0% {
    opacity: 0.2;
    transform: scale3d(var(--shadow-scale-x, 1.1), var(--shadow-scale-y, 0.4), 1);
  }
  50% {
    opacity: 0.1;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 1.2), calc(var(--shadow-scale-y, 0.4) * 0.8), 1);
  }
  100% {
    opacity: 0;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 1.5), calc(var(--shadow-scale-y, 0.4) * 0.5), 1);
  }
}

/* ---------- Death Overlay (Cinematic Fade) ---------- */
#death-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: #000000;
  opacity: 0;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Slow fade to black - cinematic feel */
  transition: opacity 2s ease-in;
}

#death-overlay.active {
  opacity: 1;
  pointer-events: auto;
}

#death-overlay.fade-out {
  opacity: 0;
  /* Slow reveal - like waking from a coma */
  transition: opacity 4s ease-out;
}

/* Death message - quirky text */
.death-message {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 4vw, 2.5rem);
  color: #888888;
  text-align: center;
  padding: 2rem;
  max-width: 80vw;
  opacity: 0;
  transform: translate3d(0, 20px, 0);
  /* Smooth fade in for message */
  transition: opacity 1.5s ease-out, transform 1.5s ease-out;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.8);
  letter-spacing: 0.05em;
}

.death-message.visible {
  opacity: 1;
  transform: translate3d(0, 0, 0);
}

.character.immune {
  animation: immune-flash 0.3s ease-in-out infinite;
  pointer-events: none;
}

@keyframes immune-flash {
  0%, 100% { opacity: 1; filter: brightness(1.5) saturate(1.2); }
  50% { opacity: 0.4; filter: brightness(1) saturate(1); }
}

/* ---------- Player Corpse (Visual + Marker) ---------- */
.player-corpse {
  position: absolute;
  width: var(--tile-size);
  height: calc(var(--tile-size) * 4 / 3); /* 32px for 24px tiles */
  z-index: 5; /* Below living actors, on ground level */
  pointer-events: auto;
  cursor: pointer;
}

/* Corpse sprite - fallen body matching death animation end state */
.player-corpse > .sprite {
  position: absolute;
  width: var(--tile-size);
  height: calc(var(--tile-size) * 4 / 3);
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center bottom;
  /* Fallen pose - matches player-death animation 100% keyframe (crumpled heap) */
  transform: scale3d(1.4, 0.35, 1) translate3d(calc(4px * var(--death-dir, -1)), 15px, 0) rotate3d(0, 0, 1, calc(-22deg * var(--death-dir, -1)));
  filter: brightness(0.4) grayscale(1);
  opacity: 0.6;
  /* Subtle outline to make visible against terrain */
  -webkit-filter: 
    drop-shadow(1px 0 0 var(--alpha-black-50))
    drop-shadow(-1px 0 0 var(--alpha-black-50))
    drop-shadow(0 1px 0 var(--alpha-black-50))
    drop-shadow(0 -1px 0 var(--alpha-black-50))
    brightness(0.4) grayscale(1);
  filter: 
    drop-shadow(1px 0 0 var(--alpha-black-50))
    drop-shadow(-1px 0 0 var(--alpha-black-50))
    drop-shadow(0 1px 0 var(--alpha-black-50))
    drop-shadow(0 -1px 0 var(--alpha-black-50))
    brightness(0.4) grayscale(1);
}


/* ---------- NPCs ---------- */
.npc {
  cursor: pointer;
  /* Movement duration set dynamically via --npc-move-speed CSS variable */
  --npc-move-speed: 280ms;
  transition: transform var(--npc-move-speed) ease-out, opacity var(--fade-quick) ease-out;
  will-change: transform;
}

.npc.idle {
  /* Idle movement is slower (2.5x base duration) with ease-out */
  transition: transform calc(var(--npc-move-speed) * 2.5) ease-out, opacity var(--fade-quick) ease-out;
}

/* Guard aiming - visual telegraph before pot shot */
.npc.aiming > .sprite {
  filter: 
    drop-shadow(1px 0 0 var(--sprite-outline))
    drop-shadow(-1px 0 0 var(--sprite-outline))
    drop-shadow(0 1px 0 var(--sprite-outline))
    drop-shadow(0 -1px 0 var(--sprite-outline))
    drop-shadow(0 0 6px oklch(0.7 0.15 200))
    drop-shadow(0 0 12px oklch(0.6 0.12 200 / 0.6));
}

/* Storm retreat/return - continuous motion */
.npc.retreating {
  transition: transform var(--npc-move-speed) ease-in-out, opacity var(--fade-quick) ease-out;
}

/* Snap position without animation (for sync/teleport) - keeps opacity transitions */
.npc.snap-position {
  transition: opacity var(--fade-quick) ease-out !important;
}

/* NPC targeted state and role tints now handled by unified system
   (see TINTING SYSTEM above) */

.npc[data-hidden="true"] { display: none; }

/* Quest markers now handled by .nameplate-quest in nameplate system */

/* NPC role tints now handled by data-tint attribute (see TINTING SYSTEM above) */

/* NPC downed state - PERF: removed filter transition, filter is static */
.npc.downed {
  opacity: 0.3;
  filter: grayscale(0.8) brightness(0.6);
  transition: opacity var(--fade-standard) ease;
}

/* ---------- Enemies ---------- */
.enemy {
  cursor: pointer;
  /* Movement duration set dynamically via --enemy-move-speed CSS variable */
  --enemy-move-speed: 400ms;
  transition: transform var(--enemy-move-speed) linear, opacity var(--fade-quick) ease-out;
  will-change: transform;
}

.enemy.idle {
  /* Idle movement is slower (2.5x base duration) with ease-out */
  transition: transform calc(var(--enemy-move-speed) * 2.5) ease-out, opacity var(--fade-quick) ease-out;
}

/* Leaping (gap closer) - uses exact --enemy-move-speed with ease-out for arc feel */
.enemy.leaping {
  transition: transform var(--enemy-move-speed) ease-out, opacity var(--fade-quick) ease-out;
}

/* Snap position without animation (for sync/teleport) - keeps opacity transitions */
.enemy.snap-position {
  transition: opacity var(--fade-quick) ease-out !important;
}

/* Flinching - brief freeze when hit by pot shot, before scattering */
.enemy.flinching > .sprite {
  animation: flinch-shake 100ms ease-out;
}

@keyframes flinch-shake {
  0%, 100% { transform: translate3d(0, 0, 0); }
  25% { transform: translate3d(-2px, 0, 0); }
  50% { transform: translate3d(2px, 0, 0); }
  75% { transform: translate3d(-1px, 0, 0); }
}

/* Enemy tints and selection outlines now handled by data-tint attribute 
   and unified .actor.targeted system (see TINTING SYSTEM above) */

/* ---------- Enemy Death Animation System ---------- */
/* Three disintegration styles based on kill type:
   - death-fire: Laser kills - burns up with embers (existing)
   - death-explode: Grenades/abilities - pixels burst outward
   - death-mince: Melee kills - pixels fall and scatter downward
   All end in debris pile that fades out. */

/* Shared death state base */
.enemy.dying-anim,
.enemy.death-fire,
.enemy.death-explode,
.enemy.death-mince {
  pointer-events: none;
  contain: layout style;
}

/* ---------- MINCE DEATH (Melee) ---------- */
/* Pixels fall down and scatter like being chopped up */
.enemy.death-mince > .sprite {
  will-change: transform, opacity;
  filter: brightness(1.5) saturate(0.7);
  animation: mince-disintegrate 0.5s ease-in forwards;
}

.enemy.death-mince > .shadow {
  will-change: transform, opacity;
  animation: death-shadow-fade 0.5s ease-out forwards;
}

@keyframes mince-disintegrate {
  0% {
    transform: scale3d(1, 1, 1) translate3d(0, 0, 0);
    opacity: 1;
    clip-path: inset(0 0 0 0);
  }
  15% {
    transform: scale3d(1.05, 0.95, 1) translate3d(0, 2px, 0);
    opacity: 1;
    clip-path: inset(0 0 0 0);
  }
  30% {
    transform: scale3d(1.1, 0.8, 1) translate3d(calc(2px * var(--death-dir, 1)), 6px, 0);
    opacity: 0.9;
    clip-path: inset(10% 5% 0 5%);
  }
  50% {
    transform: scale3d(1.3, 0.5, 1) translate3d(calc(4px * var(--death-dir, 1)), 12px, 0);
    opacity: 0.6;
    clip-path: inset(30% 10% 0 10%);
  }
  70% {
    transform: scale3d(1.5, 0.3, 1) translate3d(calc(3px * var(--death-dir, 1)), 16px, 0);
    opacity: 0.3;
    clip-path: inset(60% 15% 0 15%);
  }
  100% {
    transform: scale3d(1.8, 0.15, 1) translate3d(calc(2px * var(--death-dir, 1)), 18px, 0);
    opacity: 0;
    clip-path: inset(100% 20% 0 20%);
  }
}

/* ---------- EXPLODE DEATH (Grenades/Abilities) ---------- */
/* Pixels burst outward in all directions */
.enemy.death-explode > .sprite {
  will-change: transform, opacity;
  filter: brightness(2) saturate(0.5);
  animation: explode-disintegrate 0.4s ease-out forwards;
}

.enemy.death-explode > .shadow {
  will-change: transform, opacity;
  animation: death-shadow-fade 0.3s ease-out forwards;
}

@keyframes explode-disintegrate {
  0% {
    transform: scale3d(1, 1, 1);
    opacity: 1;
    filter: brightness(1);
  }
  15% {
    transform: scale3d(1.3, 1.3, 1);
    opacity: 1;
    filter: brightness(3) saturate(0.3);
  }
  35% {
    transform: scale3d(1.8, 1.8, 1);
    opacity: 0.7;
    filter: brightness(2) saturate(0.2);
  }
  60% {
    transform: scale3d(2.5, 2.5, 1);
    opacity: 0.3;
    filter: brightness(1.5) saturate(0.1);
  }
  100% {
    transform: scale3d(3.5, 3.5, 1);
    opacity: 0;
    filter: brightness(1) saturate(0);
  }
}

/* ---------- FIRE DEATH (Laser) ---------- */
/* Burns up with bright flash - uses existing laser-disintegrate */
.enemy.death-fire > .sprite {
  will-change: transform, opacity;
  filter: brightness(2) saturate(0.5);
  animation: laser-disintegrate 0.6s ease-out forwards;
}

.enemy.death-fire > .shadow {
  will-change: transform, opacity;
  animation: laser-disintegrate-shadow 0.6s ease-out forwards;
}

/* Legacy support: dying-anim now uses mince as default */
.enemy.dying-anim > .sprite {
  will-change: transform, opacity;
  filter: brightness(1.5) saturate(0.7);
  animation: mince-disintegrate 0.5s ease-out forwards;
}

.enemy.dying-anim > .shadow {
  will-change: transform, opacity;
  animation: death-shadow-fade 0.5s ease-out forwards;
}

/* Shared shadow fade animation */
@keyframes death-shadow-fade {
  0% {
    opacity: 0.2;
    transform: scale3d(var(--shadow-scale-x, 1.1), var(--shadow-scale-y, 0.4), 1);
  }
  100% {
    opacity: 0;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 0.3), calc(var(--shadow-scale-y, 0.4) * 0.3), 1);
  }
}

/* Dead/debris state - all death types end in debris pile */
.enemy.dead {
  pointer-events: none;
  z-index: 4; /* Below living actors */
}

.enemy.dead > .sprite {
  /* Dark debris smudge on ground */
  background-image: none !important;
  background: radial-gradient(ellipse 100% 100% at 50% 50%,
    oklch(0.2 0.02 50 / 0.7) 0%,
    oklch(0.25 0.03 45 / 0.5) 40%,
    oklch(0.3 0.02 40 / 0.25) 70%,
    transparent 100%
  );
  transform: scale3d(1.2, 0.35, 1) translate3d(0, 18px, 0);
  transform-origin: center bottom;
  filter: none;
  border-radius: 50%;
  opacity: 0.7;
  animation: debris-fade 6s ease-out forwards;
}

.enemy.dead > .shadow {
  opacity: 0;
}

@keyframes debris-fade {
  0% { opacity: 0.7; }
  100% { opacity: 0.5; }
}

/* Nameplate hidden for dead enemies (handled by .actor-nameplate section) */

/* Despawning - debris fades out */
.enemy.despawning {
  pointer-events: none;
}

.enemy.despawning > .sprite,
.enemy.despawning > .shadow {
  opacity: 0;
  transition: opacity var(--fade-dramatic) ease-out;
}

/* ---------- Laser Disintegration Death ---------- */
/* Alternate death for laser weapon kills - enemy vaporizes into ash pile
   PERF: Only animate transform + opacity (GPU composited).
   Filter is static to avoid expensive per-frame filter recalculation. */
.enemy.disintegrating {
  pointer-events: none;
  /* PERF: Isolate animation from affecting layout of other elements */
  contain: layout style;
}

.enemy.disintegrating > .sprite {
  /* GPU compositing hints */
  will-change: transform, opacity;
  /* Static bright filter - no animation, just set once */
  filter: brightness(2) saturate(0.5);
  animation: laser-disintegrate 0.6s ease-out forwards;
}

.enemy.disintegrating > .shadow {
  will-change: transform, opacity;
  animation: laser-disintegrate-shadow 0.6s ease-out forwards;
}

/* Nameplate fade during disintegration (handled by .actor-nameplate section) */

/* PERF: Only animate transform + opacity (GPU-accelerated)
   Filter changes are expensive - use static filter on .sprite instead */
@keyframes laser-disintegrate {
  /* Hit by laser - bright flash, slight expansion */
  0% {
    transform: scale3d(1, 1, 1) translate3d(0, 0, 0);
    opacity: 1;
  }
  /* Overload - white-hot expansion */
  15% {
    transform: scale3d(1.15, 1.15, 1) translate3d(0, -2px, 0);
    opacity: 1;
  }
  /* Burning up - starting to collapse */
  35% {
    transform: scale3d(1.0, 0.85, 1) translate3d(0, 3px, 0);
    opacity: 0.8;
  }
  /* Collapsing into ash - rapid shrink */
  55% {
    transform: scale3d(0.7, 0.5, 1) translate3d(0, 8px, 0);
    opacity: 0.5;
  }
  /* Final collapse - tiny smudge */
  80% {
    transform: scale3d(0.35, 0.2, 1) translate3d(0, 13px, 0);
    opacity: 0.25;
  }
  /* Dissipated */
  100% {
    transform: scale3d(0.15, 0.08, 1) translate3d(0, 15px, 0);
    opacity: 0;
  }
}

@keyframes laser-disintegrate-shadow {
  0% {
    opacity: 0.2;
    transform: scale3d(var(--shadow-scale-x, 1.1), var(--shadow-scale-y, 0.4), 1);
  }
  /* Flash washes out shadow */
  15% {
    opacity: 0.05;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 1.1), calc(var(--shadow-scale-y, 0.4) * 1.1), 1);
  }
  /* Shadow shrinks with body */
  55% {
    opacity: 0.08;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 0.5), calc(var(--shadow-scale-y, 0.4) * 0.5), 1);
  }
  100% {
    opacity: 0;
    transform: scale3d(calc(var(--shadow-scale-x, 1.1) * 0.2), calc(var(--shadow-scale-y, 0.4) * 0.2), 1);
  }
}

/* Ash pile - smoldering remains after laser disintegration
   PERF: Use transform for positioning, not width/height/margin (avoids layout).
   Glow effect via pseudo-element with opacity animation (GPU-friendly). */
.enemy.ash-pile {
  pointer-events: none;
  z-index: 4; /* Below regular corpses */
  /* PERF: Isolate from layout recalculations */
  contain: layout style;
}

.enemy.ash-pile > .sprite {
  /* Dark scorched smudge on ground - static radial gradient */
  background-image: none !important;
  background: radial-gradient(ellipse 100% 100% at 50% 50%,
    oklch(0.18 0.02 50 / 0.85) 0%,
    oklch(0.22 0.03 45 / 0.6) 40%,
    oklch(0.25 0.02 40 / 0.3) 70%,
    transparent 100%
  );
  /* PERF: Use transform for size/position instead of width/height/margin */
  transform: scale3d(1.2, 0.35, 1) translate3d(0, 18px, 0);
  transform-origin: center bottom;
  filter: none;
  border-radius: 50%;
  opacity: 0.85;
  /* Ash pile fades slightly over time */
  animation: ash-fade 4s ease-out forwards;
}

/* Note: Smolder effect now uses JS particle system (ember-particle) instead of pseudo-element */

.enemy.ash-pile > .shadow {
  opacity: 0;
}

/* Nameplate hidden for ash piles (handled by .actor.dead selector) */

/* Ash pile base - slight fade */
@keyframes ash-fade {
  0% { opacity: 0.9; }
  100% { opacity: 0.7; }
}

/* Ash pile despawning - fades out like regular corpse */
.enemy.ash-pile.despawning > .sprite {
  opacity: 0;
  transition: opacity var(--fade-dramatic) ease-out;
}

/* ---------- Ember Smoke Particles ---------- */
/* Rising smoke/ember pixels from laser disintegration ash piles.
   Similar to vehicle dust but with red/orange palette and upward drift.
   PERF: CSS-only animation, GPU composited (transform + opacity only). */

#ember-smoke-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 12; /* Above dust, below actors */
}

.ember-particle {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 1px;
  /* Subtle pixel outline like vehicle dust */
  box-shadow: inset 0 0 0 0.25px oklch(0.2 0.02 20 / 0.3);
  /* GPU compositing */
  will-change: transform, opacity;
  /* Hidden until activated */
  opacity: 0;
  pointer-events: none;
}

/* Color variants - ember/smoke palette */
.ember-particle[data-variant="0"] { background: var(--particle-ember-1); } /* Bright orange */
.ember-particle[data-variant="1"] { background: var(--particle-ember-2); } /* Orange-red */
.ember-particle[data-variant="2"] { background: var(--particle-ember-3); } /* Red */
.ember-particle[data-variant="3"] { background: var(--particle-ember-4); } /* Dark red */
.ember-particle[data-variant="4"] { background: var(--particle-ember-5); } /* Charcoal */
.ember-particle[data-variant="5"] { background: var(--particle-ember-6); } /* Dark smoke */

/* Ember rise animations - particles drift upward with slight sway */
@keyframes ember-rise-0 {
  0% { transform: translate3d(0, 0, 0) scale3d(0.5, 0.5, 1); opacity: 0; }
  10% { transform: translate3d(2px, -4px, 0) scale3d(1, 1, 1); opacity: 0.9; }
  50% { transform: translate3d(-1px, -18px, 0) scale3d(0.9, 0.9, 1); opacity: 0.7; }
  100% { transform: translate3d(3px, -35px, 0) scale3d(0.4, 0.4, 1); opacity: 0; }
}

@keyframes ember-rise-1 {
  0% { transform: translate3d(0, 0, 0) scale3d(0.6, 0.6, 1); opacity: 0; }
  10% { transform: translate3d(-3px, -3px, 0) scale3d(1.1, 1.1, 1); opacity: 0.85; }
  50% { transform: translate3d(2px, -20px, 0) scale3d(0.85, 0.85, 1); opacity: 0.6; }
  100% { transform: translate3d(-2px, -40px, 0) scale3d(0.3, 0.3, 1); opacity: 0; }
}

@keyframes ember-rise-2 {
  0% { transform: translate3d(0, 0, 0) scale3d(0.4, 0.4, 1); opacity: 0; }
  15% { transform: translate3d(1px, -5px, 0) scale3d(0.9, 0.9, 1); opacity: 0.95; }
  60% { transform: translate3d(-2px, -22px, 0) scale3d(0.7, 0.7, 1); opacity: 0.5; }
  100% { transform: translate3d(1px, -38px, 0) scale3d(0.25, 0.25, 1); opacity: 0; }
}

@keyframes ember-rise-3 {
  0% { transform: translate3d(0, 0, 0) scale3d(0.7, 0.7, 1); opacity: 0; }
  12% { transform: translate3d(-2px, -4px, 0) scale3d(1.05, 1.05, 1); opacity: 0.8; }
  55% { transform: translate3d(3px, -16px, 0) scale3d(0.8, 0.8, 1); opacity: 0.55; }
  100% { transform: translate3d(-1px, -32px, 0) scale3d(0.35, 0.35, 1); opacity: 0; }
}

@keyframes ember-rise-4 {
  0% { transform: translate3d(0, 0, 0) scale3d(0.5, 0.5, 1); opacity: 0; }
  8% { transform: translate3d(2px, -2px, 0) scale3d(0.95, 0.95, 1); opacity: 0.9; }
  45% { transform: translate3d(-3px, -14px, 0) scale3d(0.75, 0.75, 1); opacity: 0.65; }
  100% { transform: translate3d(2px, -28px, 0) scale3d(0.3, 0.3, 1); opacity: 0; }
}

@keyframes ember-rise-5 {
  0% { transform: translate3d(0, 0, 0) scale3d(0.55, 0.55, 1); opacity: 0; }
  10% { transform: translate3d(-1px, -5px, 0) scale3d(1, 1, 1); opacity: 0.85; }
  50% { transform: translate3d(2px, -24px, 0) scale3d(0.8, 0.8, 1); opacity: 0.5; }
  100% { transform: translate3d(-2px, -45px, 0) scale3d(0.2, 0.2, 1); opacity: 0; }
}

/* ---------- NPC/Guard Death Animation ---------- */
/* Guards use same death animation as enemies */
.npc.dying-anim {
  pointer-events: none;
}

.npc.dying-anim > .sprite {
  animation: enemy-death 1s ease-out forwards;
}

.npc.dying-anim > .shadow {
  animation: enemy-death-shadow 1s ease-out forwards;
}

/* Dead state - guard corpse on ground */
.npc.dead {
  pointer-events: none;
  z-index: 5; /* Below living actors */
}

.npc.dead > .sprite {
  transform: scale3d(1.4, 0.35, 1) translate3d(calc(3px * var(--death-dir, -1)), 14px, 0) rotate3d(0, 0, 1, calc(-20deg * var(--death-dir, -1)));
  filter: 
    drop-shadow(1px 0 0 var(--sprite-outline))
    drop-shadow(-1px 0 0 var(--sprite-outline))
    drop-shadow(0 1px 0 var(--sprite-outline))
    drop-shadow(0 -1px 0 var(--sprite-outline))
    brightness(0.35) grayscale(1);
  opacity: 0.5;
}

.npc.dead > .shadow {
  opacity: 0;
}

/* Enemy states */
.enemy.is-teleporting {
  transition: none !important;
}

/* Retreating enemies - still targetable by player */
.enemy.retreating {
  /* Keep pointer-events enabled so player can still attack fleeing enemies */
}

/* ---------- Enemy/NPC Vehicle Knockback ---------- */
/* Enemies and guards hit by vehicles - simpler tumble than player */
.enemy.vehicle-knockback,
.npc.vehicle-knockback {
  pointer-events: none;
}

.enemy.vehicle-knockback > .sprite,
.npc.vehicle-knockback > .sprite {
  animation: npc-vehicle-knockback var(--knockback-duration, 300ms) cubic-bezier(0.2, 0.8, 0.3, 1) forwards;
}

.enemy.vehicle-knockback > .shadow,
.npc.vehicle-knockback > .shadow {
  animation: npc-vehicle-knockback-shadow var(--knockback-duration, 300ms) ease-out forwards;
}

/* PERF: Removed filter animation - only animate transform (GPU-accelerated) */
@keyframes npc-vehicle-knockback {
  0% { 
    transform: scale3d(1.2, 0.7, 1) translate3d(0, 2px, 0);
    opacity: 0.7;
  }
  30% { 
    transform: scale3d(0.8, 1.2, 1) translate3d(0, -12px, 0) rotate3d(0, 0, 1, 15deg);
    opacity: 0.85;
  }
  70% {
    transform: scale3d(0.9, 1.0, 1) translate3d(0, -6px, 0) rotate3d(0, 0, 1, 5deg);
    opacity: 0.95;
  }
  100% { 
    transform: scale3d(1.1, 0.9, 1) translate3d(0, 1px, 0) rotate3d(0, 0, 1, 0deg);
    opacity: 1;
  }
}

@keyframes npc-vehicle-knockback-shadow {
  0% { opacity: 0.15; transform: scale3d(0.6, 0.3, 1); }
  30% { opacity: 0.05; transform: scale3d(0.4, 0.2, 1); }
  100% { opacity: var(--shadow-opacity, 0.2); transform: scale3d(var(--shadow-scale-x, 1), var(--shadow-scale-y, 0.4), 1); }
}

/* ---------- Legacy Enemy UI Elements (DEPRECATED) ---------- */
/* Old enemy-level-badge and enemy-hp-bar styles removed.
   Now using unified .actor-nameplate system below. */

@keyframes skull-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.6; }
}

/* Boss and parked-vehicle-npc tints handled by data-tint attribute
   (see TINTING SYSTEM above) */

/* ============================================
   ACTOR NAMEPLATE SYSTEM
   Unified nameplate for all actors (player, NPCs, enemies)
   
   Structure:
   - Row 1: Name
   - Row 2: Subtitle (type/affiliation)
   - Row 3: Bars (HP + Stamina stacked) + Level box
   ============================================ */

/* PERF: Nameplates use contain to isolate layout/paint.
   Only opacity transitions (GPU-accelerated).
   No max-height transitions - use display:none for hiding. */
.actor-nameplate {
  position: absolute;
  bottom: 100%;
  left: 50%;
  transform: translate3d(-50%, 0, 0);
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-bottom: 10px;
  z-index: 4;
  pointer-events: none;
  white-space: nowrap;
  contain: layout style;
}

/* Row 1: Name */
.nameplate-name {
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 600;
  color: var(--text-primary);
  text-shadow: 0 1px 2px var(--alpha-black-60);
  line-height: 1;
}

/* Row 2: Subtitle */
.nameplate-subtitle {
  font-family: var(--font-display);
  font-size: 8px;
  font-weight: 400;
  color: var(--text-primary);
  text-shadow: 0 1px 2px var(--alpha-black-60);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  line-height: 1;
  margin-top: 5px;
}

/* Row 3: Stats container (bars + level) */
.nameplate-stats {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-top: 8px;
}

/* Bars container (HP + Stamina stacked) */
.nameplate-bars {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 28px;
}

/* Individual bar */
.nameplate-bar {
  height: 4px;
  background: var(--alpha-black-60);
  overflow: hidden;
  width: 100%;
}

/* HP bar is taller */
.nameplate-bar.hp {
  height: 5px;
}

/* PERF: Only transform transitions on bar fills (GPU-accelerated) */
.nameplate-bar-fill {
  display: block;
  height: 100%;
  width: 100%;
  transform: scale3d(calc(var(--bar-pct, 100) / 100), 1, 1);
  transform-origin: left center;
  transition: transform 0.2s ease;
}

/* HP bar - role-based colors */
.nameplate-bar.hp .nameplate-bar-fill {
  background: var(--enemy-hp-color);
}

.character .nameplate-bar.hp .nameplate-bar-fill {
  background: var(--player-hp-color);
}

.npc .nameplate-bar.hp .nameplate-bar-fill {
  background: var(--npc-hp-color);
}

/* Stamina bar - role-based colors */
.nameplate-bar.stamina .nameplate-bar-fill {
  background: var(--enemy-stamina-color);
}

.character .nameplate-bar.stamina .nameplate-bar-fill {
  background: var(--player-stamina-color);
}

.npc .nameplate-bar.stamina .nameplate-bar-fill {
  background: var(--npc-stamina-color);
}

/* Hide bars if actor doesn't have that resource */
.nameplate-bar.stamina.hidden,
.nameplate-bar.sense.hidden {
  display: none;
}

/* Level box */
.nameplate-level {
  padding: 3px var(--gui-padding-sm);
  background: var(--bg-panel-alpha);
  font-family: var(--font-numbers);
  font-size: 9px;
  font-weight: bold;
  color: var(--text-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

/* Quest icon in nameplate - matches level badge styling */
.nameplate-quest {
  padding: 2px var(--gui-padding-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-numbers);
  font-size: 11px;
  font-weight: bold;
  margin-bottom: 10px;
  animation: nameplate-quest-bounce 1s ease-in-out infinite;
}

.nameplate-quest.available {
  background: var(--warning);
  color: var(--bg-dark);
}

.nameplate-quest.available::before {
  content: '!';
}

.nameplate-quest.return {
  background: var(--success);
  color: var(--bg-dark);
}

.nameplate-quest.return::before {
  content: '?';
}

@keyframes nameplate-quest-bounce {
  0%, 100% { transform: translate3d(0, 0, 0); }
  50% { transform: translate3d(0, -3px, 0); }
}

/* Hide nameplate during spawn and all death states */
/* PERF: Uses display:none - no transitions needed */
.actor.spawning .actor-nameplate,
.actor.dying-anim .actor-nameplate,
.actor.disintegrating .actor-nameplate,
.actor.dead .actor-nameplate,
.actor.ash-pile .actor-nameplate,
.actor.despawning .actor-nameplate {
  display: none;
}

/* ============================================
   NAMEPLATE VISIBILITY SETTINGS
   PERF: Uses display:none for hiding (no transitions)
   
   Player nameplate has its own toggle.
   Actor settings only affect .enemy and .npc (not .character)
   ============================================ */

/* Player Nameplate: Off - hide entire player nameplate */
.player-nameplate-off .character .actor-nameplate {
  display: none;
}

/* ---------- Nameplate Layer Settings (enemies, NPCs, bosses) ---------- */

/* Player nameplate: Off */
.player-nameplate-off .nameplate-player {
  display: none;
}

/* Names: Off - hide names for non-player nameplates */
.nameplate-names-off .nameplate-enemy .nameplate-name,
.nameplate-names-off .nameplate-enemy .nameplate-subtitle,
.nameplate-names-off .nameplate-npc .nameplate-name,
.nameplate-names-off .nameplate-npc .nameplate-subtitle,
.nameplate-names-off .nameplate-boss .nameplate-name,
.nameplate-names-off .nameplate-boss .nameplate-subtitle {
  display: none;
}

/* Names: Conditional - only show for key NPCs (has quest or important flag) */
.nameplate-names-conditional .nameplate-enemy .nameplate-name,
.nameplate-names-conditional .nameplate-enemy .nameplate-subtitle,
.nameplate-names-conditional .nameplate-npc:not([data-key-npc]) .nameplate-name,
.nameplate-names-conditional .nameplate-npc:not([data-key-npc]) .nameplate-subtitle {
  display: none;
}

/* Subtitles: Off - hide subtitles for all non-player nameplates */
.nameplate-subtitle-off .nameplate-enemy .nameplate-subtitle,
.nameplate-subtitle-off .nameplate-npc .nameplate-subtitle,
.nameplate-subtitle-off .nameplate-boss .nameplate-subtitle {
  display: none;
}

/* Health Bars: Off - hide bars for non-player nameplates */
.nameplate-hp-off .nameplate-enemy .nameplate-bar,
.nameplate-hp-off .nameplate-npc .nameplate-bar,
.nameplate-hp-off .nameplate-boss .nameplate-bar {
  display: none;
}

/* Hide bars container when bars are off */
.nameplate-hp-off .nameplate-enemy .nameplate-bars,
.nameplate-hp-off .nameplate-npc .nameplate-bars,
.nameplate-hp-off .nameplate-boss .nameplate-bars {
  display: none;
}

/* Health Bars: Conditional - only show bars when below 100% */
.nameplate-hp-conditional .nameplate-enemy.full-health .nameplate-bar,
.nameplate-hp-conditional .nameplate-npc.full-health .nameplate-bar,
.nameplate-hp-conditional .nameplate-boss.full-health .nameplate-bar {
  display: none;
}

/* Hide bars container when bars are conditionally hidden */
.nameplate-hp-conditional .nameplate-enemy.full-health .nameplate-bars,
.nameplate-hp-conditional .nameplate-npc.full-health .nameplate-bars,
.nameplate-hp-conditional .nameplate-boss.full-health .nameplate-bars {
  display: none;
}

/* Levels: Off - hide level badges for non-player nameplates */
.nameplate-level-off .nameplate-enemy .nameplate-level,
.nameplate-level-off .nameplate-npc .nameplate-level,
.nameplate-level-off .nameplate-boss .nameplate-level {
  display: none;
}

/* Levels: Conditional - only show when below 100% */
.nameplate-level-conditional .nameplate-enemy.full-health .nameplate-level,
.nameplate-level-conditional .nameplate-npc.full-health .nameplate-level,
.nameplate-level-conditional .nameplate-boss.full-health .nameplate-level {
  display: none;
}

/* Smooth position updates and fade transitions */
.parked-vehicle-npc {
  transition: transform var(--fade-standard) ease-out, opacity var(--fade-quick) ease-out;
  opacity: 1;
}

/* Fade in on spawn */
.parked-vehicle-npc.spawning {
  opacity: 0;
}

/* Fade out on despawn */
.parked-vehicle-npc.despawning {
  opacity: 0;
  pointer-events: none;
}

/* ============================================
   DOCK WORKER TINT
   Industrial tan/brown for cargo workers
   ============================================ */
[data-tint="worker"] > .sprite {
  --actor-tint: hue-rotate(25deg) saturate(0.8);
}

/* ============================================
   CARGO CRATES
   Pushable cargo objects for delivery scenario
   ============================================ */

/* Crate layer - contains all crate elements */
.crate-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  /* Below vehicles (12) and actors (10), so crates slide "into" vehicles */
  z-index: 9;
}

/* Individual crate */
.crate {
  position: absolute;
  width: var(--tile-size);
  height: var(--tile-size);
  z-index: 1;
  background: var(--crate-color, #B0733A);
  
  /* GPU compositing */
  backface-visibility: hidden;
  transform-style: preserve-3d;
  contain: layout style;
  
  /* Spawn fade */
  opacity: 1;
  transition: opacity var(--fade-quick) ease-out;
}

/* Crate spawning - fade in */
.crate.spawning {
  opacity: 0;
}

/* Crate despawning - fade out */
.crate.despawning {
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--fade-standard) ease-out !important;
}

/* Crate moving - raise z-index above other crates */
.crate.moving {
  z-index: 2;
}

/* ============================================
   NETWORK PLAYERS (Multiplayer)
   Other players rendered from server state.
   Uses direct JS transforms - no CSS transitions.
   ============================================ */
.network-player {
  /* Movement duration set dynamically via --player-move-speed CSS variable */
  --player-move-speed: 280ms;
  transition: transform var(--player-move-speed) linear, opacity var(--fade-quick) ease-out;
  will-change: transform;
}

/* Snap position without animation (for sync/teleport) - keeps opacity transitions */
.network-player.snap-position {
  transition: opacity var(--fade-quick) ease-out !important;
}

/* Network player hit flash */
.network-player.hit .sprite {
  filter: brightness(2) saturate(0.5);
}

/* Network player death animation - same as enemy/NPC death */
.network-player.dying-anim {
  pointer-events: none;
}

.network-player.dying-anim > .sprite {
  animation: enemy-death 1s ease-out forwards;
}

.network-player.dying-anim > .shadow {
  animation: enemy-death-shadow 1s ease-out forwards;
}

/* Network player dead state - corpse on ground */
.network-player.dead {
  pointer-events: none;
  z-index: 5; /* Below living actors */
}

.network-player.dead > .sprite {
  transform: scale3d(1.4, 0.35, 1) translate3d(calc(3px * var(--death-dir, -1)), 14px, 0) rotate3d(0, 0, 1, calc(-20deg * var(--death-dir, -1)));
  filter: 
    drop-shadow(1px 0 0 var(--sprite-outline))
    drop-shadow(-1px 0 0 var(--sprite-outline))
    drop-shadow(0 1px 0 var(--sprite-outline))
    drop-shadow(0 -1px 0 var(--sprite-outline))
    brightness(0.35) grayscale(1);
  opacity: 0.5;
}

.network-player.dead > .shadow {
  opacity: 0;
}

/* Network player ghost mode (corpse run) */
.network-player.ghost {
  opacity: 0.5;
  animation: ghost-pulse 2s ease-in-out infinite;
  filter: brightness(1.2) saturate(0.5);
}

/* ============================================
   NAMEPLATE LAYER SYSTEM
   Dedicated layer for all actor nameplates.
   Ensures nameplates are never obscured by actors.
   ============================================ */

/* Individual nameplate in the layer - positioned via transform */
.nameplate {
  position: absolute;
  left: 0;
  top: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  pointer-events: none;
  white-space: nowrap;
  will-change: transform;
  contain: layout style;
  /* Gap between nameplate and sprite head */
  padding-bottom: 10px;
  /* 
   * JS sets --np-x, --np-y (position in px).
   * Position transition is set directly on element by JS for reliable animation control.
   * 100% GPU-accelerated via translate3d.
   */
  transform: translate3d(
    calc(var(--np-x, 0px) - 50%), 
    calc(var(--np-y, 0px) - 100%), 
    0
  );
  /* Default: no transform transition. JS sets it directly when animating position. */
}

/* Stack offset wrapper - separate transform layer for independent transitions */
.nameplate-content {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Use gap instead of margin-top on children - gap auto-adjusts when items are hidden */
  gap: 5px;
  /* Stack offset via transform - GPU accelerated, independent of position */
  transform: translate3d(0, calc(var(--np-stack, 0px) * -1), 0);
  transition: transform 200ms ease-out;
  will-change: transform;
}

/* Reuse existing nameplate child styles */
.nameplate .nameplate-name {
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 600;
  color: var(--text-primary);
  text-shadow: 0 1px 2px var(--alpha-black-60);
  line-height: 1;
}

.nameplate .nameplate-subtitle {
  font-family: var(--font-display);
  font-size: 8px;
  font-weight: 400;
  color: var(--text-primary);
  text-shadow: 0 1px 2px var(--alpha-black-60);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  line-height: 1;
  /* Gap handled by parent flex container */
}

/* Row 3: Stats container (bars + level) - matches .actor-nameplate */
.nameplate .nameplate-stats {
  display: flex;
  align-items: center;
  gap: 2px;
  /* Gap handled by parent flex container */
}

/* Bars container (HP + Stamina stacked) */
.nameplate .nameplate-bars {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 28px;
}

/* Individual bar */
.nameplate .nameplate-bar {
  height: 4px;
  background: var(--alpha-black-60);
  overflow: hidden;
  width: 100%;
}

/* HP bar is taller */
.nameplate .nameplate-bar.hp {
  height: 5px;
}

/* PERF: Only transform transitions on bar fills (GPU-accelerated) */
.nameplate .nameplate-bar-fill {
  display: block;
  height: 100%;
  width: 100%;
  transform: scale3d(calc(var(--bar-pct, 100) / 100), 1, 1);
  transform-origin: left center;
  transition: transform 0.2s ease;
}

/* HP bar - role-based colors */
.nameplate .nameplate-bar.hp .nameplate-bar-fill {
  background: var(--enemy-hp-color);
}

.nameplate-player .nameplate-bar.hp .nameplate-bar-fill,
.nameplate-network-player .nameplate-bar.hp .nameplate-bar-fill {
  background: var(--player-hp-color);
}

.nameplate-npc .nameplate-bar.hp .nameplate-bar-fill {
  background: var(--npc-hp-color);
}

/* Stamina bar - role-based colors */
.nameplate .nameplate-bar.stamina .nameplate-bar-fill {
  background: var(--enemy-stamina-color);
}

.nameplate-player .nameplate-bar.stamina .nameplate-bar-fill,
.nameplate-network-player .nameplate-bar.stamina .nameplate-bar-fill {
  background: var(--player-stamina-color);
}

.nameplate-npc .nameplate-bar.stamina .nameplate-bar-fill {
  background: var(--npc-stamina-color);
}

/* Level box - matches .actor-nameplate */
.nameplate .nameplate-level {
  /* Asymmetric padding: less on top to visually center the number */
  padding: 2px var(--gui-padding-sm) 3px;
  background: var(--bg-panel-alpha);
  font-family: var(--font-numbers);
  font-size: 9px;
  font-weight: bold;
  color: var(--text-primary);
  text-shadow: 0 1px 2px var(--alpha-black-60);
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Quest icon in nameplate */
.nameplate .nameplate-quest {
  padding: 2px var(--gui-padding-sm);
  /* Gap handled by parent flex container */
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-numbers);
  font-size: 11px;
  font-weight: bold;
  animation: nameplate-quest-bounce 1s ease-in-out infinite;
}

.nameplate .nameplate-quest.available {
  background: var(--warning);
  color: var(--bg-dark);
}

.nameplate .nameplate-quest.return {
  background: var(--success);
  color: var(--bg-dark);
}

.nameplate .nameplate-quest.available::before {
  content: '!';
}

.nameplate .nameplate-quest.return::before {
  content: '?';
}

/* Targeted nameplate - highlighted */
.nameplate.targeted .nameplate-name {
  color: var(--target-highlight, #fff);
  text-shadow: 0 0 4px var(--target-glow, rgba(255,255,255,0.5));
}

/* Hovered nameplate - subtle highlight */
.nameplate.hovered .nameplate-name {
  color: var(--text-primary);
}

/* Type-specific colors */
.nameplate-enemy .nameplate-name {
  color: var(--enemy-name, #ff6b6b);
}

.nameplate-boss .nameplate-name {
  color: var(--boss-name, #ff4444);
  font-size: 12px;
}

.nameplate-npc .nameplate-name {
  color: var(--npc-name, #8bc34a);
}

.nameplate-network-player .nameplate-name {
  color: var(--player-name, #64b5f6);
}

/* Fade out during death states */
.nameplate.dying,
.nameplate.dead {
  opacity: 0;
  transition: opacity var(--fade-quick) ease-out;
}

/* Spawning state - fade in */
.nameplate.spawning {
  opacity: 0;
}

/* Hidden by fog of war - smooth fade */
.nameplate.fog-hidden {
  opacity: 0;
  transition: opacity var(--fade-quick) ease-out;
}

/* Revealed from fog - smooth fade in */
.nameplate.fog-revealed {
  transition: opacity var(--fade-quick) ease-out;
}
