/* ============================================================
   combat-fx.css — Arène de combat et effets d'animation
   ============================================================
   Extrait de styles.css (sections « Zone des sprites » → « Image rémanente »,
   « Keyframes d'animation », « Classes d'animation appliquées par JS », et les
   deux règles de la sphère d'énergie). Tout ce dont combat-ui.js a besoin pour
   faire vivre l'arène : sprites, murs, mines, projectiles, effets par
   compétence, images-clés.

   POURQUOI UN FICHIER À PART : la maquette du nouvel accueil (index-1) doit
   pouvoir jouer ces animations SANS charger styles.css, qui porte toute
   l'ancienne direction artistique — et dont la règle `body` écraserait la
   sienne. Ce fichier ne contient aucune règle globale (pas de body, *, :root).

   ORDRE DE CHARGEMENT : sur les pages de jeu, ce fichier est lié AVANT
   styles.css. Les règles étaient jusque-là à l'intérieur de styles.css, avant
   ses surcharges mobiles de l'arène (`@media (max-width: 768px)` →
   .sprite-arena, .sprite-img, .sprite-placeholder) : lié en premier, il garde
   exactement cette position dans la cascade. Le lier APRÈS ferait perdre le
   rétrécissement des sprites sur mobile.

   DÉPENDANCES : les couleurs d'effet (--laser-color, --fire-color…) sont posées
   en ligne par combat-ui.js. Deux variables de thème sont utilisées avec un
   repli, lu uniquement là où styles.css est absent : --p (halo de respiration)
   et --r-md (coins de deux voiles).
   ============================================================ */

/* ── Zone des sprites ── */
.sprite-arena {
    flex: 1;
    width: 100%;
    display: flex;
    align-items: flex-end;
    justify-content: space-around;
    position: relative;
    overflow: visible;
    min-height: 80px;
}

.sprite-slot {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    flex: 1;
    position: relative;   /* ancre les éléments attachés (lame…) sur le slot, pas l'arène */
}

.sprite-placeholder {
    width: 90px;
    height: 90px;
    background: rgba(255,255,255,0.10);
    border: 2px dashed rgba(255,255,255,0.25);
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.2rem;
    font-weight: 900;
    color: rgba(255,255,255,0.45);
    letter-spacing: 0;
    transition: filter 0.1s, transform 0.15s;
    will-change: transform, filter;
}

.sprite-placeholder.sprite-dead {
    opacity: 0;
    pointer-events: none;
}

.sprite-img {
    width: 120px;
    height: 120px;
    object-fit: contain;
    image-rendering: pixelated;
    transition: filter 0.1s, transform 0.15s;
    will-change: transform, filter;
}

/* Le sprite de p1 (toi, gauche) fait face à droite, p2 (droite) face à gauche */
.sprite-slot-p1 .sprite-placeholder,
.sprite-slot-p1 .sprite-img { transform-origin: left center; }
.sprite-slot-p2 .sprite-placeholder,
.sprite-slot-p2 .sprite-img { transform-origin: right center; }

/* ── Respiration subtile sur TON héros actif (p1) — piste H ──
   Animation idle : remplacée par les anim-* d'action (définies plus bas, donc
   prioritaires à spécificité égale). Coupée si l'OS demande des animations réduites. */
@keyframes kf-breathe {
    0%, 100% { transform: scale(1);    filter: drop-shadow(0 0 2px transparent); }
    50%      { transform: scale(1.03); filter: drop-shadow(0 0 8px var(--p, rgba(255,255,255,.35))); }
}
@media (prefers-reduced-motion: no-preference) {
    .sprite-slot-p1 .sprite-img,
    .sprite-slot-p1 .sprite-placeholder {
        animation: kf-breathe 3s ease-in-out infinite;
    }
}
/* Sprite K.O. (opacity 0) : la respiration reste invisible, rien à faire. */

/* ── Projectile de combat ── */
.combat-projectile {
    position: absolute;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: var(--proj-color, #fff);
    box-shadow: 0 0 12px 4px var(--proj-color, #fff);
    top: 50%;
    transform: translateY(-50%);
    pointer-events: none;
    z-index: 10;
}

/* ── Lame attachée au sprite (ex : Lame Plasma) ──
   Posée DANS le slot du héros (#sprite-p1/p2) → elle voyage avec lui quand il fonce.
   Le JS gère son apparition/disparition ; ici on décrit son aspect et sa place. */
.combat-blade {
    position: absolute;
    height: 200px;            /* taille de la lame (ajuste si trop grande/petite) */
    width: auto;
    bottom: -50px;            /* hauteur : à peu près le buste (sprites collés en bas) */
    left: 46%;               /* place horizontale de départ, près du héros (p2 : miroir 54 % posé par le JS) */
    pointer-events: none;
    z-index: 5;              /* au-dessus du sprite, sous le projectile (z-index 10) */
    filter: drop-shadow(0 0 6px var(--blade-color, #a855f7));  /* la lueur, recolorable */
}

/* ── Boucliers de murs persistants (Barrière Magique / Champ de Force) ──
   screen.svg sert de POCHOIR (mask) : le moteur le remplit de --wall-color.
   Les opacités sont dans le SVG, donc on utilise une couleur unie. */
.combat-wall {
    position: absolute;
    bottom: -40px;           /* hauteur du bouclier (ajuste si trop haut/bas) */
    left: 55%;
    transform: translateX(-50%);
    width: 175px;            /* taille du bouclier (ajuste pour entourer le héros) */
    height: 175px;
    pointer-events: none;
    z-index: 4;              /* devant le sprite, derrière la lame */
    background: var(--wall-color, #fff);
    -webkit-mask: url(anim_assets/screen.svg) center / contain no-repeat;
            mask: url(anim_assets/screen.svg) center / contain no-repeat;
}
/* Champ de Force décalé de quelques pixels pour ne pas se superposer
   exactement à la Barrière Magique quand les deux murs sont actifs. */
.combat-wall-roche {
    transform: translateX(-50%) translateY(5%) translate(4px, -4px);
}
/* Miroir pour le joueur 2 (sprite de droite) : le bouclier est décalé du côté
   opposé (left 55% → 45%) et retourné horizontalement (scaleX(-1)) pour faire
   face à la gauche, comme le héros p2. */
.sprite-slot-p2 .combat-wall {
    left: 45%;
    transform: translateX(-50%) scaleX(-1);
}
.sprite-slot-p2 .combat-wall-roche {
    transform: translateX(-50%) translateY(5%) translate(-4px, -4px) scaleX(-1);
}
/* Fondu d'apparition du bouclier (joué une seule fois, à la pose du mur).
   On n'anime QUE l'opacité pour ne pas écraser le transform de centrage/décalage. */
.combat-wall-in {
    animation: kf-wall-in 1.5s ease-out;
}
@keyframes kf-wall-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
/* Réaction du héros protégé : léger balancement gauche-droite + éclat lumineux. */
.sprite-placeholder.anim-wall-react,
.sprite-img.anim-wall-react {
    animation: kf-wall-react var(--anim-dur, 1.5s) ease-in-out;
}
@keyframes kf-wall-react {
    0%, 100% { transform: translateX(0);    filter: brightness(1); }
    25%      { transform: translateX(-5px); filter: brightness(1.6) drop-shadow(0 0 8px #fff); }
    75%      { transform: translateX(5px);  filter: brightness(1.6) drop-shadow(0 0 8px #fff); }
}

/* ── Saut d'énergie : niveaux (plateformes) + atome ──
   Assets CARRÉS au dessin centré : la ligne du niveau est au centre vertical de
   sa boîte, le cercle de l'atome au centre de la sienne. Donc la hauteur visible
   = bottom + (moitié de la taille). Centrage horizontal par left+margin (PAS par
   transform), pour laisser le transform libre au tremblement et à la téléportation. */
.energy-level {
    position: absolute;
    left: 50%;
    margin-left: -50px;      /* moitié de width → centre la plateforme */
    width: 100px;            /* taille des niveaux (la ligne fait toute la largeur) */
    height: auto;            /* asset carré → hauteur = 100px, ligne au milieu (≈ +50) */
    pointer-events: none;
    z-index: 4;
    opacity: 0;              /* invisible avant le fade in (géré par le JS) */
}
.energy-level-bot { bottom: 4px; }    /* ligne du bas visible à ≈ 54px */
.energy-level-top { bottom: 64px; }   /* ligne du haut à ≈ 114px (60px plus haut → saut de l'atome) */
/* ── Rafale (Rafale de vent / Tempête de sable) : recouvre TOUTE l'UI ──
   rafale.svg sert de POCHOIR (mask), rempli de --rafale-color. Position fixed
   plein écran, débordant largement (160vw/160vh) pour qu'aucun bord ne soit
   visible pendant le balayage → vraie impression de tempête. */
/* Wrapper qui CLIPPE la rafale au rectangle (arrondi) du combat field. */
.combat-rafale-clip {
    position: absolute;
    inset: 0;
    overflow: hidden;
    border-radius: var(--r-md, 0);   /* épouse les coins arrondis de l'arène */
    pointer-events: none;
    z-index: 9;                   /* par-dessus sprites et cartes, dans le field */
}
.combat-rafale {
    position: absolute;
    left: -50%;
    top: -30%;
    width: 200%;             /* déborde le field → reste couvrant pendant le balayage */
    height: 160%;
    pointer-events: none;
    opacity: 0;              /* le JS pilote l'opacité (fondu entrant/sortant) */
    background: var(--rafale-color, #74c0fc);
    /* cover : remplit TOUTE la boîte en gardant les proportions de l'asset. */
    -webkit-mask: url(anim_assets/rafale.svg) no-repeat center / cover;
            mask: url(anim_assets/rafale.svg) no-repeat center / cover;
}

/* ── Champ de mines : mines posées sous le héros (persistant + lancer animé) ── */
.combat-mine {
    position: absolute;
    width: 26px;             /* taille des mines (ajuste si besoin) */
    height: auto;
    transform: translateX(-50%);   /* centré sur sa position left */
    pointer-events: none;
    z-index: 3;              /* au pied du héros (sous la lame/aura, sur le sol) */
    filter: drop-shadow(0 0 2px rgba(0,0,0,0.55));
}

/* ── Mine (compétence "Mine") : mines qui flottent un peu au-dessus du héros
   (posées en arc par le lancer, puis persistantes tant que le compteur > 0). ── */
.combat-mine-hazard {
    position: absolute;
    width: 48px;             /* taille des mines (ajuste si besoin) */
    height: auto;
    transform: translateX(-50%);   /* centré sur sa position left */
    pointer-events: none;
    z-index: 8;              /* au-dessus du héros (elles flottent) */
    filter: drop-shadow(0 2px 3px rgba(0,0,0,0.55));
}

/* ── Censure : croix qui apparaissent sur la cible (le JS les anime) ── */
.combat-cross {
    position: absolute;
    left: 50%;
    bottom: 40px;
    width: 120px;             /* taille des croix (ajuste si besoin) */
    height: auto;
    pointer-events: none;
    z-index: 8;
    opacity: 0;              /* le JS pilote l'apparition (pop) et l'estompage */
    transform: translateX(-50%);
    filter: drop-shadow(0 0 4px rgba(0,0,0,0.55));
}

/* ── Bénédiction : étoiles jaunes (teintées via masque) qui montent (le JS les anime) ── */
.combat-star {
    position: absolute;
    width: 26px;             /* taille des étoiles (ajuste si besoin) */
    height: 26px;
    pointer-events: none;
    z-index: 8;
    opacity: 0;              /* le JS pilote l'opacité, la montée et la brillance */
    background: var(--star-color, #ffe066);
    -webkit-mask: url(anim_assets/etoile.svg) no-repeat center / contain;
            mask: url(anim_assets/etoile.svg) no-repeat center / contain;
}

/* ── Teinte de sprite RÉUTILISABLE (chimique, cautérisation, futurs effets) ──
   Voile coloré qui ÉPOUSE le sprite : quand c'est une vraie image, le JS pose un
   masque = l'image → silhouette exacte ; pour le placeholder (sans image), repli
   rectangle arrondi via .combat-sprite-tint-box. Couleur + opacité posées par le JS.
   → Prêt pour les vrais sprites : aucune teinte n'est un carré en dur. */
.combat-sprite-tint {
    position: absolute;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    pointer-events: none;
    z-index: 7;                              /* au-dessus du sprite */
    opacity: 0;                              /* piloté par le JS */
    -webkit-mask-repeat: no-repeat;  mask-repeat: no-repeat;
    -webkit-mask-position: center;   mask-position: center;
    -webkit-mask-size: contain;      mask-size: contain;
}
.combat-sprite-tint-box { border-radius: 12px; }   /* repli placeholder (sans image) */

/* ── Souffle Glacé : particules givrées du souffle (le JS les émet et les anime) ──
   Petit disque à cœur blanc et pourtour bleu glacé, bords vaporeux (blur + halo). */
.combat-frost {
    position: absolute;
    border-radius: 50%;
    pointer-events: none;
    z-index: 9;                              /* par-dessus les sprites, dans l'arène */
    opacity: 0;                              /* piloté par le JS */
    background: radial-gradient(circle at 50% 40%,
                #ffffff 0%, var(--frost-color, #a5d8ff) 55%, transparent 74%);
    filter: blur(1px) drop-shadow(0 0 6px var(--frost-color, #a5d8ff));
}

/* ── Cautérisation : petit nuage de vapeur blanche (particules) au-dessus du lanceur ── */
.combat-cauter-puff {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: 0; height: 0;                     /* point d'ancrage au-dessus du sprite */
    z-index: 11;
    pointer-events: none;
}
.combat-cauter-particle {
    position: absolute;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(255,255,255,0.95), rgba(255,255,255,0) 70%);
    filter: blur(1.5px);
    transform: translate(-50%, -50%);
    opacity: 0;                              /* piloté par le JS */
    pointer-events: none;
}

/* ── Arme Chimique : colonnes de fumée (bruit fractal SVG) + teinte du sprite ──
   Colonne violette à bords vaporeux : fond dégradé (côtés doux) + masque vertical
   (dense en bas, s'effiloche vers le haut) ; le JS pose left/bottom/height/filter
   et l'anime (montée depuis les pieds via scaleY, origine bas). */
.combat-smoke {
    position: absolute;
    width: 120px;
    margin-left: -60px;              /* centré sur sa position left */
    transform-origin: bottom center; /* grandit depuis les pieds vers le haut */
    pointer-events: none;
    z-index: 12;                     /* au-dessus du sprite cible → le masque */
    opacity: 0;
    will-change: transform, opacity;
    background: linear-gradient(to right,
        transparent 0%, var(--cloud-color, #9b59b6) 22%,
        var(--cloud-color, #9b59b6) 78%, transparent 100%);
    -webkit-mask: linear-gradient(to top, #000 0%, #000 22%, rgba(0,0,0,.7) 55%, transparent 96%);
            mask: linear-gradient(to top, #000 0%, #000 22%, rgba(0,0,0,.7) 55%, transparent 96%);
}

/* ── Arme Chimique : nuages violets projetés sur la cible + teinte du sprite ── */
.combat-cloud {
    position: absolute;
    width: 140px;
    height: 46px;            /* ~ratio du nuage (le JS gère left/bottom/opacité) */
    pointer-events: none;
    z-index: 8;
    opacity: 0;
    transform: translateX(-50%);   /* centré sur sa position left (animée) */
    background: var(--cloud-color, #9b59b6);
    -webkit-mask: url(anim_assets/nuage.svg) no-repeat center / contain;
            mask: url(anim_assets/nuage.svg) no-repeat center / contain;
    filter: drop-shadow(0 0 4px var(--cloud-color, #9b59b6));
}
/* Gros nuage qui reste sur l'adversaire jusqu'à la fin (attaché à son slot). */
.combat-cloud-big {
    width: 180px;            /* plus gros que les nuages projetés (ajuste si besoin) */
    height: 115px;
    left: 50%;               /* centré sur le sprite cible */
    bottom: 0;               /* couvre le corps du sprite */
    z-index: 9;              /* au-dessus du voile et du sprite */
}
/* (La teinte violette de l'Arme Chimique utilise désormais .combat-sprite-tint,
   comme la cautérisation → voir plus haut. Plus de rectangle en dur.) */

/* ── Alignement magnétique : aimant qui pivote entre les deux héros (le JS l'anime) ── */
.combat-magnet {
    position: absolute;
    left: 50%;
    bottom: 38px;            /* à mi-hauteur des sprites, entre les deux héros */
    width: 90px;             /* taille de l'aimant (ajuste si besoin) */
    height: auto;
    pointer-events: none;
    z-index: 7;
    opacity: 0;              /* le JS pilote l'opacité + la rotation */
    transform: translateX(-50%);
    filter: drop-shadow(0 0 5px var(--magnet-color, #ffe066));  /* lueur recolorable */
}

/* ── Arc Électrique : éclairs qui s'étendent du lanceur jusqu'à la cible ──
   Attachés à l'arène. Position et longueur sont MESURÉES par le JS
   (playArcElectric) : la base de l'éclair tombe pile sur le centre du sprite
   du lanceur, quelle que soit la taille de l'arène ou du sprite. On ne fixe
   donc ici NI left, NI width, NI bottom — seulement l'aspect.
   Le JS pose aussi le transform (recentrage vertical + scaleX(-1) pour p2). */
.combat-eclair {
    position: absolute;
    height: auto;            /* ratio naturel des éclairs (~3.16:1) */
    pointer-events: none;
    z-index: 7;
    transform-origin: center;
    filter: drop-shadow(0 0 5px var(--eclair-color, #000000ff));  /* lueur électrique */
}

/* ── Champ électrique : petits éclairs collés au sprite + fond d'arène jauni ──
   Éclairs : eclair1.svg posé sur le corps du sprite (le JS met width/left/bottom/
   transform aléatoires) ; clignotent (créés/retirés par le JS). */
.combat-efield-spark {
    position: absolute;
    height: auto;
    pointer-events: none;
    z-index: 8;                          /* au-dessus du sprite */
    filter: drop-shadow(0 0 4px var(--eclair-color, #ffe066));   /* lueur électrique */
}
/* Voile jaune du FOND : posé en z-index négatif dans le field (qui est en
   isolation → contexte d'empilement) → il teinte l'arrière-plan mais passe
   DERRIÈRE sprites et hero cards, qui ne changent JAMAIS de couleur. */
.combat-efield-bg {
    position: absolute;
    inset: 0;
    z-index: -1;                         /* derrière tout le contenu du field */
    opacity: 0;                          /* couleur + opacité posées inline (anim & persistant) */
    pointer-events: none;
    border-radius: inherit;              /* épouse les coins arrondis du field */
}

/* Champ plasma : FLAMME de gaz rosée (silhouette SVG pointue déformée par le bruit
   fractal, comme les colonnes de l'Arme Chimique). Le JS pose taille/position et la
   fait grandir depuis la base. Le blur + le blend « screen » donnent le rendu gaz. */
.combat-plasma-flame {
    position: absolute;
    bottom: 0;                           /* base aux pieds du sprite */
    transform-origin: bottom center;     /* grandit vers le haut */
    pointer-events: none;
    z-index: 9;                          /* vapeur devant le sprite */
    opacity: 0;                          /* piloté par le JS */
    mix-blend-mode: screen;              /* les flammes se superposent en s'éclaircissant */
    filter: blur(1.5px);                 /* adoucit le rendu (gaz) */
    will-change: transform, opacity;
}
.combat-plasma-flame svg { width: 100%; height: 100%; display: block; }

/* ── Marée montante : nappe d'eau (eau.svg) sur toute la largeur de l'arène.
   Le conteneur (hauteur posée en px par le JS) est ancré en bas et translaté
   verticalement pour la montée/descente ; il clippe l'image lors du ballotement
   horizontal. z-index devant les sprites → « immersion » sans les masquer (eau
   semi-transparente). transform-only : pas de repaint pendant l'anim. */
.combat-tide {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    pointer-events: none;
    z-index: 9;
    overflow: hidden;                    /* clippe le débord horizontal de l'image */
    will-change: transform;
}
.combat-tide img {
    position: absolute;
    /* On ZOOME dans l'asset au lieu de l'ajuster au plateau : l'image est bien plus
       grande que la nappe (qui la clippe), si bien qu'on n'en voit qu'un fragment
       agrandi → grosses vagues. À l'écran : ~1/3 de l'asset en hauteur (height 300 %)
       et ~1/2 en largeur (width 200 %). top:0 aligne la surface ondulée (haut de
       l'asset) sur le haut de la nappe ; left:-50 % centre la moitié visible. */
    top: 0; left: -50%;
    width: 200%; height: 300%;
    display: block;
    object-fit: fill;
}

/* ── Tsunami : nappe d'eau qui GLISSE d'un bord à l'autre du plateau (le JS pose
   la translation horizontale et l'opacité). Le wrapper clippe au rectangle arrondi
   de l'arène (comme la Rafale) ; la nappe fait exactement la taille du field et
   couvre tout le plateau à mi-course. transform-only : pas de repaint pendant l'anim. */
.combat-tsunami-clip {
    position: absolute;
    inset: 0;
    overflow: hidden;
    border-radius: var(--r-md, 0);   /* épouse les coins arrondis de l'arène */
    pointer-events: none;
    z-index: 9;                   /* devant les sprites, immersion sans les masquer (eau semi-transparente) */
}
/* .combat-tsunami = le SLIDER (div) : 200 % de large, CENTRÉ (déborde de 50 % de
   chaque côté), glissé + fondu par le JS. Centré → translateX(0) couvre pile le
   field, et le décalage ±·dir (translateX en % = % de la LARGEUR DU SLIDER) reste
   symétrique entre p1 et p2. */
.combat-tsunami {
    position: absolute;
    top: 0; left: -50%;
    width: 200%; height: 100%;
    pointer-events: none;
    opacity: 0;                   /* le JS pilote l'opacité (fondu entrant/sortant) */
    will-change: transform, opacity;
}
/* .combat-tsunami-img = l'image, CARRÉE et TOURNÉE À 90° → vaguelettes verticales
   (perpendiculaires au glissement horizontal). Un carré reste identique après une
   rotation de 90° : il couvre donc le slider avant comme après la rotation. */
.combat-tsunami-img {
    position: absolute;
    top: 50%; left: 50%;
    width: 100%;                  /* = largeur du slider */
    aspect-ratio: 1 / 1;          /* carré (hauteur = largeur) → couvre le slider même tourné */
    transform: translate(-50%, -50%) rotate(90deg);
    object-fit: cover;            /* remplit le carré en gardant les proportions (grosses vagues) */
}

/* ── Charge enflammée : ruban de flammes orbital + explosion de flammes au contact.
   Embers = dégradés radiaux chauds (cœur clair → couleur → transparent), floutés,
   en blend « screen » (superposition lumineuse). Le JS pose taille/position/orbite. */
.combat-fire-ribbon {
    position: absolute;
    left: 50%;
    bottom: 0;
    border-radius: 50%;
    background: radial-gradient(circle, #fff6cc 0%, var(--fire-color, #ff6b6b) 55%, transparent 74%);
    filter: blur(1.5px);
    pointer-events: none;
    mix-blend-mode: screen;
}
.combat-fire-burst {                     /* flash central de l'explosion */
    position: absolute;
    left: 50%;
    margin-left: -65px;
    width: 130px; height: 130px;
    border-radius: 50%;
    background: radial-gradient(circle, #fff 0%, #ffd23b 22%, var(--fire-color, #ff6b6b) 52%, transparent 72%);
    filter: blur(2px);
    pointer-events: none;
    z-index: 11;
    opacity: 0;                          /* piloté par le JS */
    mix-blend-mode: screen;
}
.combat-fire-shard {                     /* éclats de flammes projetés */
    position: absolute;
    left: 50%;
    bottom: 0;
    border-radius: 50%;
    background: radial-gradient(circle, #fff6cc 0%, var(--fire-color, #ff6b6b) 55%, transparent 75%);
    filter: blur(1px);
    pointer-events: none;
    z-index: 11;
    mix-blend-mode: screen;
}

/* ── Corruption : drain d'énergie — particules NOIRES à AURA BLANCHE qui sortent de
   la cible et rentrent dans le lanceur (le JS pose taille/position et l'anime). */
.combat-drain-particle {
    position: absolute;
    border-radius: 50%;
    background: radial-gradient(circle, #000 0%, #111 55%, transparent 72%);
    box-shadow: 0 0 5px 1px rgba(255, 255, 255, 0.85);   /* aura blanche */
    pointer-events: none;
    z-index: 10;
}

/* ── Pinces Croco : sphères de lumière drainées de la cible vers le lanceur
   (le JS pose taille/position et les anime). */
.combat-light-orb {
    position: absolute;
    border-radius: 50%;
    background: radial-gradient(circle, #ffffff 0%, #ffe066 55%, rgba(255, 224, 102, 0) 75%);
    box-shadow: 0 0 6px 2px rgba(255, 224, 102, 0.8);
    pointer-events: none;
    z-index: 10;
    mix-blend-mode: screen;
}

/* ── Nécrose : taches noir-bleuté (#070738) qui éclosent sur la cible teintée en
   bleu livide (le JS pose taille/position et anime le pop). */
.combat-necrose-spot {
    position: absolute;
    border-radius: 50%;
    background: radial-gradient(circle, #070738 0%, #0a0a44 52%, rgba(7, 7, 56, 0) 75%);
    pointer-events: none;
    z-index: 8;                 /* au-dessus de la teinte bleue du sprite (z:7) */
    opacity: 0;                 /* piloté par le JS */
    will-change: transform, opacity;
}

/* ── Pourrissement : taches de pourriture qui éclosent sur la cible. Noires par
   défaut, violettes via .combat-rot-spot-purple. Le voile .combat-rot-shroud
   (moucheté violet/noir, masqué à la silhouette du sprite par makeSpriteTint)
   se ferme par-dessus pour recouvrir entièrement la cible. Couleur + opacité JS. */
.combat-rot-spot {
    position: absolute;
    border-radius: 50%;
    background: radial-gradient(circle, #0a000f 0%, #1a0322 50%, rgba(26, 3, 34, 0) 74%);
    pointer-events: none;
    z-index: 8;                 /* au-dessus de la teinte du sprite (z:7) */
    opacity: 0;                 /* piloté par le JS */
    will-change: transform, opacity;
}
.combat-rot-spot-purple {
    background: radial-gradient(circle, #9b59b6 0%, #6a1f7a 52%, rgba(106, 31, 122, 0) 76%);
}
.combat-rot-shroud {
    z-index: 9;                 /* au-dessus des taches (z:8) */
    will-change: opacity;
}

/* ── Morsure Empoisonnée : deux mâchoires (morsure.svg) qui se referment sur la
   cible — celle du bas est retournée par le JS (scaleY(-1)). Halo violet
   venimeux ; positions, écartement et fermeture pilotés par le JS. */
.combat-bite-jaw {
    position: absolute;
    left: 50%;
    pointer-events: none;
    z-index: 10;                /* au-dessus de la teinte violette du sprite */
    opacity: 0;                 /* piloté par le JS */
    filter: drop-shadow(0 0 6px rgba(155, 89, 182, 0.9));
    will-change: transform, opacity;
}

/* ── Étranglement : trois ronces (ronce2.svg) enroulées autour de la cible —
   celle du milieu est retournée par le JS (scaleY(-1)). Positions, éclosion
   et resserrement pilotés par le JS. */
.combat-ronce {
    position: absolute;
    left: 50%;
    pointer-events: none;
    z-index: 10;                /* au-dessus du sprite de la cible */
    opacity: 0;                 /* piloté par le JS */
    filter: drop-shadow(0 0 4px rgba(46, 80, 20, 0.7));
    will-change: transform, opacity;
}

/* ── Ronces : arches (ronce1.svg) qui éclosent l'une devant l'autre, du lanceur
   vers la cible. Elles SURGISSENT DU SOL : transform-origin en bas, le JS anime
   le scale. Positions et tailles pilotées par le JS. */
.combat-ronce-arche {
    position: absolute;
    pointer-events: none;
    z-index: 8;                 /* sous les ronces d'enroulement (z:10) */
    opacity: 0;                 /* piloté par le JS */
    transform-origin: center bottom;
    filter: drop-shadow(0 0 4px rgba(46, 80, 20, 0.7));
    will-change: transform, opacity;
}

/* ── Dopage : seringue assemblée en deux calques — seringue2 = PISTON (derrière),
   seringue1 = TUBE/cylindre (devant). Le JS anime le piston qui coulisse DANS le
   tube (injection), puis le sprite gonfle. Assets horizontaux, aiguille à gauche. */
.combat-syringe-rig {
    position: absolute;
    pointer-events: none;
    z-index: 10;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
}
.combat-syringe-rig img { position: absolute; height: auto; }
.combat-syringe-tube   { z-index: 2; left: 0; }    /* cylindre au premier plan */
.combat-syringe-piston { z-index: 1; }             /* piston DERRIÈRE le tube */

/* ── Floraison : fleurs (fleur1/2/3.svg) qui éclosent au hasard dans l'arène ── */
.combat-flower {
    position: absolute;
    height: auto;
    pointer-events: none;
    z-index: 6;
    opacity: 0;                          /* piloté par le JS */
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.35));
}

/* ── Gatling Gun : arme posée devant le lanceur (alternance gatling1/gatling2) +
   traits de lumière (traceurs) qui filent vers la cible. */
.combat-gatling {
    position: absolute;
    height: auto;
    pointer-events: none;
    z-index: 8;                          /* devant le sprite du lanceur */
    filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.5));
}

/* ── Perceuse : foret de minage posé devant le lanceur (alternance drill1/drill2
   → illusion de rotation). Attaché au slot, il voyage avec le lanceur qui charge. */
.combat-drill {
    position: absolute;
    height: auto;
    pointer-events: none;
    z-index: 8;                          /* devant le sprite du lanceur */
    opacity: 0;                          /* piloté par le JS */
    filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.5));
}

/* ── Pinces Croco : les deux mâchoires posées devant le lanceur (celle du bas est
   retournée par le JS). Attachées au slot, elles chargent avec lui. */
.combat-croco-jaw {
    position: absolute;
    height: auto;
    pointer-events: none;
    z-index: 8;                          /* devant le sprite du lanceur */
    opacity: 0;                          /* piloté par le JS */
    filter:  drop-shadow(0 0 1px rgba(255, 255, 255, 0.9));;
}

.combat-tracer {
    position: absolute;
    border-radius: 2px;
    background: linear-gradient(90deg, rgba(255, 236, 150, 0) 0%, #fff 70%, #fff 100%);
    box-shadow: 0 0 6px 1px rgba(255, 236, 150, 0.9);   /* halo lumineux */
    pointer-events: none;
    z-index: 9;
    mix-blend-mode: screen;
}

/* ── Frappe orbitale : fin trait de ciblage (monte) → gros rayon laser (descend) →
   flash blanc plein cadre. Le JS pose position/hauteur et anime le scaleY. */
.combat-orbital-line {
    position: absolute;
    background: linear-gradient(to top, var(--laser-color, #e599f7), rgba(229, 153, 247, 0.25));
    box-shadow: 0 0 6px 1px var(--laser-color, #e599f7);
    pointer-events: none;
    z-index: 9;
}
.combat-orbital-beam {
    position: absolute;
    border-radius: 0 0 10px 10px;
    background: linear-gradient(to bottom, rgba(255, 255, 255, 0.9), var(--laser-color, #e599f7) 45%);
    box-shadow: 0 0 22px 4px var(--laser-color, #e599f7);
    pointer-events: none;
    z-index: 9;
    mix-blend-mode: screen;
}
.combat-orbital-flash {
    position: absolute;
    inset: 0;
    background: #fff;
    pointer-events: none;
    z-index: 50;                         /* au-dessus de TOUT : flash plein cadre */
    opacity: 0;                          /* piloté par le JS */
}

/* ── Gaz toxique : nuages (nuage.svg en masque) teintés violet, qui déferlent des
   deux côtés pour recouvrir le champ (le JS pose taille/position et l'anime). */
.combat-toxic-cloud {
    position: absolute;
    background: #9b59b6;
    -webkit-mask: url(anim_assets/nuage.svg) no-repeat center / contain;
            mask: url(anim_assets/nuage.svg) no-repeat center / contain;
    pointer-events: none;
    z-index: 11;
    opacity: 0;                          /* piloté par le JS */
    filter: blur(1px) drop-shadow(0 0 4px rgba(155,89,182,0.6));
}

/* ── Intoxication : nuages (nuage.svg en masque) teintés violet #5c2d91 qui
   recouvrent la cible (le JS pose taille/position et l'anime). */
.combat-intox-cloud {
    position: absolute;
    background: #5c2d91;
    -webkit-mask: url(anim_assets/nuage.svg) no-repeat center / contain;
            mask: url(anim_assets/nuage.svg) no-repeat center / contain;
    pointer-events: none;
    z-index: 12;
    opacity: 0;                          /* piloté par le JS */
    filter: blur(1px) drop-shadow(0 0 4px rgba(92,45,145,0.7));
}

/* ── Radiation : vaguelettes concentriques (anneaux radioactifs). Cercle à
   bordure colorée, centre transparent ; le JS pose taille/position/couleur et
   pilote l'expansion (scale) + le fondu. */
.combat-radiation-ring {
    position: absolute;
    border-radius: 50%;
    border: 4px solid var(--radiation-color, #8ce563);
    box-shadow: 0 0 16px var(--radiation-color, #8ce563),
                inset 0 0 16px var(--radiation-color, #8ce563);
    pointer-events: none;
    z-index: 10;                          /* au-dessus du fond, sous les repères */
    opacity: 0;                           /* piloté par le JS */
    will-change: transform, opacity;
}

/* ── Espace réciproque : voile noir 50% (percé d'un trou circulaire qui se referme
   pendant l'animation) puis noir persistant tant que l'inversion est active. */
.combat-reciproc {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 25;                          /* au-dessus des sprites & hero cards */
}
.combat-reciproc svg { width: 100%; height: 100%; display: block; }
.combat-reciproc-bg {                    /* version persistante (rendue par renderField) */
    position: absolute;
    inset: 0;
    background: #000;
    opacity: 0.5;
    pointer-events: none;
    z-index: 25;
}

/* ── Délire : ?/! (qmark.svg / emark.svg) qui apparaissent autour de la cible ── */
.combat-delire-mark {
    position: absolute;
    height: auto;
    pointer-events: none;
    z-index: 11;
    opacity: 0;                          /* piloté par le JS */
    filter: drop-shadow(0 0px 10px rgba(255, 255, 255, 0.70));
}

/* ── Iai : flash en étoile (éclat de lame) + arcs tranchants sur la cible ── */
.combat-iai-star {
    position: absolute;
    pointer-events: none;
    z-index: 12;
    opacity: 0;                          /* piloté par le JS */
    background: radial-gradient(circle, #fff 0%, #fff 28%, rgba(255,255,255,0.6) 52%, transparent 74%);
    clip-path: polygon(50% 0%, 58% 42%, 100% 50%, 58% 58%, 50% 100%, 42% 58%, 0% 50%, 42% 42%);   /* étincelle 4 branches */
    filter: drop-shadow(0 0 8px #fff);
}
.combat-iai-slash {
    position: absolute;
    pointer-events: none;
    z-index: 12;
    filter: drop-shadow(0 0 4px #fff) drop-shadow(0 0 9px #a5d8ff);   /* tranchant lumineux */
}
.combat-iai-slash svg  { width: 100%; height: 100%; display: block; overflow: visible; }
.combat-iai-slash path { fill: none; stroke: #fff; stroke-width: 3.5; stroke-linecap: round; }

/* ── Tranchant Sombre : halo noir en impulsion (lanceur) + tranchant noir à halo blanc (cible) ── */
.combat-darkslash-halo {
    position: absolute;
    pointer-events: none;
    z-index: 11;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(6, 4, 12, 0.92) 0%, rgba(20, 8, 32, 0.6) 45%, rgba(20, 8, 32, 0) 72%);
    filter: blur(1px);
}
.combat-darkslash-slash {
    position: absolute;
    pointer-events: none;
    z-index: 12;
    filter: drop-shadow(0 0 4px #fff) drop-shadow(0 0 10px #fff);   /* tranchant noir à halo blanc */
}
.combat-darkslash-slash svg  { width: 100%; height: 100%; display: block; overflow: visible; }
.combat-darkslash-slash path { fill: none; stroke: #050308; stroke-width: 4; stroke-linecap: round; }

/* ── Incantations : runes magiques (rune1..5.svg) projetées vers la cible ── */
.combat-rune {
    position: absolute;
    height: auto;
    pointer-events: none;
    z-index: 9;
    opacity: 0;                          /* piloté par le JS */
    filter: drop-shadow(0 0 5px rgba(167, 139, 250, 0.9));   /* lueur magique */
}

/* ── Rituel : le sceau (rituel.svg) tourne dans le plan sous les pieds du lanceur ── */
.combat-rituel {
    position: absolute;
    left: 50%;
    height: auto;
    pointer-events: none;
    z-index: 4;                          /* sous le sprite du lanceur */
    opacity: 0;                          /* piloté par le JS */
    filter: drop-shadow(0 0 8px rgba(192, 22, 27, 0.85));   /* lueur du sceau */
}

/* ── Poing Dimensionnel : des poings (poing.svg) surgissent autour de la cible et
   foncent sur elle. Positions, rotation, ruée et fondu pilotés par le JS. */
.combat-poing {
    position: absolute;
    height: auto;
    pointer-events: none;
    z-index: 11;                         /* au-dessus du sprite de la cible */
    opacity: 0;                          /* piloté par le JS */
    filter: drop-shadow(0 0 7px rgba(167, 139, 250, 0.85));   /* lueur dimensionnelle */
    will-change: transform, opacity;
}

/* ── Inversion temporelle : sablier (sablier.svg) qui tourne sur lui-même ── */
.combat-sablier {
    position: absolute;
    height: auto;
    pointer-events: none;
    z-index: 11;
    opacity: 0;                          /* piloté par le JS */
    filter: drop-shadow(0 0 6px rgba(120, 200, 255, 0.75));   /* lueur temporelle */
}

/* ── Fonte : sprite secondaire (clone) qui s'aplatit en fondant + gouttes qui
   glissent le long du sprite principal. */
.combat-melt-ghost { z-index: 5; }      /* clone superposé, au-dessus du sprite */
.combat-melt-drop {
    position: absolute;
    background: var(--drop-color, #a5d8ff);
    opacity: 0;                          /* piloté par le JS */
    pointer-events: none;
    z-index: 7;                          /* gouttes au premier plan */
    border-radius: 46% 46% 54% 54% / 58% 58% 42% 42%;   /* forme de goutte */
    box-shadow: 0 0 3px var(--drop-color, #a5d8ff);
}

/* ── Pacte : fantôme sombre (clone du lanceur) qui enfle DERRIÈRE le lanceur, façon
   ombre démoniaque, pendant que des gouttes de sang perlent. Le vrai sprite est
   temporairement remonté au premier plan par le JS pour passer devant ce fantôme. */
.combat-pacte-ghost {
    z-index: 1;                          /* derrière le sprite (bump temporaire à z-index:2) */
    filter: brightness(0.22) drop-shadow(0 0 8px rgba(192, 22, 27, 0.7));   /* silhouette d'ombre + halo rouge sang */
}

/* ── Rassemblement des Villains : des CLONES du sprite du lanceur (comme Fonte /
   Pacte) surgissent à des positions aléatoires de son côté du terrain, teintés
   ENTIÈREMENT EN NOIR (brightness(0) → silhouette pleine). Le JS pose position et
   taille et pilote l'apparition/disparition (opacité) ; un flottement doux tourne
   en boucle en CSS (l'échelle du clone est conservée via --vg-scale). */
.combat-villain-ghost {
    pointer-events: none;
    z-index: 4;
    opacity: 0;                          /* le JS pilote apparition + disparition */
    filter: brightness(0) drop-shadow(0 0 6px rgba(0,0,0,0.85));   /* silhouette entièrement noire */
    animation: villain-ghost-float 2.2s ease-in-out infinite;
}
/* Flottement vertical léger ; l'échelle (< 1) du clone est reconduite à chaque image. */
@keyframes villain-ghost-float {
    0%, 100% { transform: translateY(0)    scale(var(--vg-scale, 1)); }
    50%      { transform: translateY(-9px) scale(var(--vg-scale, 1)); }
}

/* ── Fierté du Guerrier ── */
.combat-warrior-sphere {
    position: absolute;
    width: 60px;
    height: 60px;
    pointer-events: none;
    z-index: 10;
    transform-origin: center center;
    filter: drop-shadow(0 0 10px #ffe066) brightness(1.2); /* Éclat doré héroïque */
}

/* ── Boule de feu ──────────────────────────────────────────────────────────
   Ce n'est PAS une sphère : c'est une masse de flammes. Une queue de comète et
   cinq lobes sont fondus ensemble par le filtre SVG #doh-fire-goo (cf. combat-ui.js),
   qui bosselle et fait onduler leur contour commun. Un cœur incandescent est posé
   par-dessus, hors du filtre, pour garder un centre net et lumineux.
   Toutes les animations sont BORNÉES (la flamme ne vit qu'~1,2 s) : rien ne tourne
   en boucle perpétuelle sur la page. */
.combat-fireball {
    position: absolute;
    pointer-events: none;
    z-index: 12;
    transform-origin: center center;
    filter: drop-shadow(0 0 12px #ff6b2b) drop-shadow(0 0 30px rgba(224, 49, 49, 0.6));
}

/* Le groupe filtré : c'est lui qui prend la forme organique. */
.combat-fireball .fb-goo {
    position: absolute;
    inset: 0;
    filter: url(#doh-fire-goo);
}

/* Les lobes : jaune au cœur, orange, rouge sur le bord. Chacun respire et dérive
   avec un déphasage (animation-delay négatif posé par le JS) → contour vivant. */
.combat-fireball .fb-lobe {
    position: absolute;
    border-radius: 50%;
    background: radial-gradient(circle at 45% 40%,
        #ffe066 0%, #ffa502 38%, #ff6b2b 68%, #e03131 100%);
    animation: kf-fb-lobe 0.5s ease-in-out 12 alternate;
}
@keyframes kf-fb-lobe {
    from { transform: translate(4%, -6%) scale(1.1);  }
    to   { transform: translate(-5%, 5%) scale(0.9);  }
}

/* La queue de comète : les flammes que la boule laisse derrière elle. */
.combat-fireball .fb-tail {
    position: absolute;
    border-radius: 50%;
    background: linear-gradient(to right,
        rgba(224, 49, 49, 0) 0%, rgba(224, 49, 49, 0.75) 35%, #ff6b2b 75%, #ffa502 100%);
    animation: kf-fb-tail 0.34s ease-in-out 18 alternate;
}
@keyframes kf-fb-tail {
    from { transform: scaleX(calc(0.85 * var(--fb-flip, 1))) scaleY(0.8)  translateY(4%);  }
    to   { transform: scaleX(calc(1.1  * var(--fb-flip, 1))) scaleY(1.05) translateY(-4%); }
}

/* Le cœur : blanc-jaune, hors du filtre, légèrement flou → incandescence. */
.combat-fireball .fb-core {
    position: absolute;
    border-radius: 50%;
    background: radial-gradient(circle at 45% 42%,
        #fff 0%, #fffbe6 32%, #ffe066 62%, rgba(255, 165, 2, 0) 100%);
    filter: blur(2px);
    animation: kf-fb-core 0.16s ease-in-out 20 alternate;
}
@keyframes kf-fb-core {
    from { transform: scale(0.92); opacity: 0.85; }
    to   { transform: scale(1.08); opacity: 1;    }
}

/* Braises de la traînée ET étincelles de l'impact (même grain, couleur variable). */
.combat-fire-spark {
    position: absolute;
    pointer-events: none;
    z-index: 13;
    border-radius: 2px;
    transform-origin: center center;
    /* Petit TRAIT lumineux : blanc chaud à la tête, fondu vers la teinte à la queue. */
    background: linear-gradient(90deg,
        rgba(255,255,255,0) 0%,
        #fff 18%,
        var(--spark-color, #ffa502) 60%,
        rgba(255,165,2,0) 100%);
    box-shadow: 0 0 6px 1px var(--spark-color, #ffa502);
}

/* Flash blanc-doré au point d'impact. */
.combat-fire-flash {
    position: absolute;
    pointer-events: none;
    z-index: 11;
    border-radius: 50%;
    transform-origin: center center;
    background: radial-gradient(circle, #fff 0%, #ffe066 35%, rgba(255, 107, 43, 0) 72%);
}

/* ── Kiai : onde de choc circulaire qui jaillit autour du lanceur (le JS pose
   position/taille au centre du sprite et pilote l'expansion + le fondu). ── */
.combat-kiai-wave {
    position: absolute;
    pointer-events: none;
    z-index: 8;
    border-radius: 50%;
    border: 4px solid var(--kiai-color, #e17055);
    box-shadow: 0 0 14px var(--kiai-color, #e17055),
                inset 0 0 12px var(--kiai-color, #e17055);
    opacity: 0;                          /* piloté par le JS */
    transform: translate(-50%, -50%) scale(0.2);
}

/* ── Saut Fracassant : débris telluriques qui pleuvent du haut à chaque impact
   (petits éclats de roche irréguliers, teinte terre pilotée par le JS). ── */
.combat-quake-debris {
    position: absolute;
    pointer-events: none;
    z-index: 12;
    transform-origin: center center;
    /* Éclat irrégulier plutôt qu'un rond parfait. */
    border-radius: 45% 55% 40% 60% / 55% 45% 60% 40%;
    background: radial-gradient(circle at 38% 32%,
        color-mix(in srgb, var(--debris-color, #c49a6c) 55%, #fff) 0%,
        var(--debris-color, #c49a6c) 60%,
        color-mix(in srgb, var(--debris-color, #c49a6c) 60%, #000) 100%);
    box-shadow: 0 0 3px rgba(0, 0, 0, 0.35);
}

/* ── Crachat Venimeux : gouttes liquides brillantes (goutte allongée + reflet) ── */
.combat-venom-drop {
    position: absolute;
    pointer-events: none;
    z-index: 10;
    transform-origin: center center;
    border-radius: 50%;                  /* ovale (le JS l'allonge + l'oriente vers la cible) */
    background:
        radial-gradient(circle at 36% 26%, rgba(255,255,255,0.9) 0%, rgba(255,255,255,0) 42%),   /* reflet */
        radial-gradient(circle at 50% 62%, #c17ad6 0%, #9b59b6 48%, #6c3483 100%);                /* venin */
    box-shadow: 0 0 6px rgba(155,89,182,0.75), inset 0 -2px 3px rgba(108,52,131,0.6);
}

/* ── Sable dans l'œil : grains de sable projetés (JS pose left/top/taille/couleur,
   pilote la trajectoire en éventail). Petits points ronds marron, sans asset. ── */
.combat-sand-grain {
    position: absolute;
    pointer-events: none;
    z-index: 10;
    border-radius: 50%;                  /* grain rond */
    background: #a0522d;                  /* repli ; le JS repose la couleur exacte */
    box-shadow: 0 0 2px rgba(80,40,15,0.7), inset 0 -1px 1px rgba(60,30,10,0.55);
}

/* ── Intoxication : UNE grosse goutte de venin, en forme de larme (pointe en HAUT,
   qui mène le long de la trajectoire ; le JS l'oriente + la projette en arc). ── */
.combat-intox-drop {
    position: absolute;
    width: 44px;
    height: 60px;
    pointer-events: none;
    z-index: 10;
    transform-origin: center center;
    /* Larme : pointe en haut (22,3), bulbe arrondi en bas. */
    clip-path: path('M22 3 C 41 26, 41 47, 22 57 C 3 47, 3 26, 22 3 Z');
    background:
        radial-gradient(circle at 38% 62%, rgba(255,255,255,0.85) 0%, rgba(255,255,255,0) 40%),   /* reflet */
        radial-gradient(circle at 50% 60%, #c17ad6 0%, #9b59b6 46%, #6c3483 100%);                 /* venin */
    filter: drop-shadow(0 0 8px rgba(155,89,182,0.85));
}

/* ── Poisse : la goutte violette (même look qu'Intoxication) se COLLE à la cible.
   Flaque gluante irrégulière + filets qui coulent vers le bas (le JS les fait pousser). ── */
.combat-poisse-splat {
    position: absolute;
    pointer-events: none;
    z-index: 10;
    transform-origin: center center;
    /* blob irrégulier gluant */
    border-radius: 46% 54% 42% 58% / 62% 56% 44% 38%;
    background:
        radial-gradient(circle at 40% 34%, rgba(255,255,255,0.7) 0%, rgba(255,255,255,0) 45%),   /* reflet */
        radial-gradient(circle at 50% 45%, #c17ad6 0%, #9b59b6 50%, #6c3483 100%);                 /* venin */
    filter: drop-shadow(0 1px 4px rgba(108,52,131,0.7));
}
.combat-poisse-drip {
    position: absolute;
    pointer-events: none;
    z-index: 9;
    /* filet : arrondi en haut, s'effile en pointe arrondie vers le bas */
    border-radius: 40% 40% 50% 50% / 10% 10% 90% 90%;
    background: linear-gradient(to bottom, #9b59b6 0%, #8e44ad 55%, #6c3483 100%);
    filter: drop-shadow(0 0 3px rgba(155,89,182,0.65));
}

/* ── Inferno : flammèches (dégradé feu clair→orange→rouge→transparent) ── */
.combat-inferno-flame {
    position: absolute;
    pointer-events: none;
    z-index: 10;
    transform-origin: center center;
    /* Base claire et chaude en bas, s'effilant en transparence vers la pointe (haut).
       Le JS oriente chaque flammèche vers l'extérieur → langue de feu qui lèche. */
    background: linear-gradient(to top,
        #fff3c4 0%, #ffd24d 16%, #ff9a1f 42%, #ff3b1a 72%, rgba(255,59,26,0) 100%);
    border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%;
    mix-blend-mode: screen;              /* les flammes se cumulent en s'éclaircissant */
    filter: blur(0.5px);
}

/* ── Surtension : soleil de rayons-éclairs (eclair1/2.svg) autour du lanceur ── */
.combat-surtension-ray {
    position: absolute;
    pointer-events: none;
    z-index: 7;                          /* comme les éclairs d'arc électrique */
    transform-origin: left center;       /* la base du rayon reste ancrée au lanceur */
    filter: drop-shadow(0 0 5px var(--eclair-color, #ffe066));  /* lueur électrique */
}

/* ── Ange Gardien : ange qui monte au-dessus du lanceur (le JS l'anime) ── */
.combat-angel {
    position: absolute;
    left: 50%;
    bottom: 40px;            /* part vers le haut du sprite */
    width: 100px;             /* taille de l'ange (ajuste si besoin) */
    height: auto;
    pointer-events: none;
    z-index: 7;
    opacity: 0;              /* le JS pilote l'opacité (fondu) + la montée */
    transform: translateX(-50%);
    filter: drop-shadow(0 0 6px var(--angel-color, #ffe9a8));  /* halo doré */
}

/* ── Signal Super-Héroïque : le signal (signal.svg) qui se dévoile au-dessus
   du lanceur — le JS pilote le fondu d'apparition et le positionnement. */
.combat-signal {
    position: absolute;
    left: 50%;
    width: 90px;
    height: auto;
    pointer-events: none;
    z-index: 8;
    opacity: 0;              /* le JS pilote le fondu d'apparition */
    transform: translateX(-50%);
    filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.75));  /* halo lumineux */
}

/* ── Image rémanente : copies translucides du sprite (le JS les crée/retire) ── */
.afterimage {
    pointer-events: none;
    filter: drop-shadow(0 0 4px var(--ghost-color, #81ecec));
}

.energy-atom {
    position: absolute;
    left: 50%;
    margin-left: -22px;      /* moitié de width → centre l'atome horizontalement */
    bottom: 32px;            /* cercle centré à ≈ 54px → pile sur la ligne du bas */
    width: 44px;             /* boîte carrée ; le cercle occupe ~40% (≈ 18px visibles) */
    height: auto;
    pointer-events: none;
    z-index: 6;              /* au-dessus des niveaux */
    opacity: 0;
    filter: drop-shadow(0 0 5px var(--atom-color, #fd79a8));  /* lueur recolorable */
}

/* ── Keyframes d'animation ── */
@keyframes kf-attack {
    0%   { transform: translateX(0) scale(1); }
    35%  { transform: translateX(var(--atk-dir, 40px)) scale(1.08); }
    65%  { transform: translateX(var(--atk-dir, 40px)) scale(1.08); }
    100% { transform: translateX(0) scale(1); }
}
@keyframes kf-hit {
    0%         { filter: brightness(1); transform: translateX(0); }
    20%        { filter: brightness(5) saturate(0); transform: translateX(-6px); }
    45%        { filter: brightness(1); transform: translateX(5px); }
    70%        { filter: brightness(3) saturate(0); transform: translateX(-3px); }
    100%       { filter: brightness(1); transform: translateX(0); }
}
@keyframes kf-aura {
    0%, 100% { filter: brightness(1) drop-shadow(0 0 0px var(--aura-color, #fff)); }
    50%      { filter: brightness(1.3) drop-shadow(0 0 16px var(--aura-color, #fff)); }
}
@keyframes kf-faint {
    0%   { transform: translateY(0) rotate(0deg); opacity: 1; }
    100% { transform: translateY(80px) rotate(8deg); opacity: 0; }
}
/* Entrée/sortie de switch : glissement HORIZONTAL par le côté du héros.
   IMPORTANT : valeurs LITTÉRALES par direction (kf-…-left / kf-…-right). Un var()
   à l'intérieur d'un @keyframes empêche l'interpolation de `transform` (le navigateur
   SAUTE à la valeur finale au lieu de glisser) — seule l'opacité s'animait alors,
   ce qui donnait l'impression que l'entrée « ne se jouait pas ». */
@keyframes kf-enter-left  { 0% { transform: translateX(-180%); opacity: 0; } 100% { transform: translateX(0); opacity: 1; } }
@keyframes kf-enter-right { 0% { transform: translateX(180%);  opacity: 0; } 100% { transform: translateX(0); opacity: 1; } }
@keyframes kf-leave-left  { 0% { transform: translateX(0); opacity: 1; } 100% { transform: translateX(-180%); opacity: 0; } }
@keyframes kf-leave-right { 0% { transform: translateX(0); opacity: 1; } 100% { transform: translateX(180%);  opacity: 0; } }

/* ── Classes d'animation appliquées par JS ── */
.sprite-placeholder.anim-attack,
.sprite-img.anim-attack {
    animation: kf-attack var(--anim-dur, 0.7s) ease-in-out forwards;
}
.sprite-placeholder.anim-hit,
.sprite-img.anim-hit {
    animation: kf-hit var(--anim-dur, 0.4s) ease forwards;
}
.sprite-placeholder.anim-aura,
.sprite-img.anim-aura {
    animation: kf-aura var(--anim-dur, 0.7s) ease-in-out forwards;
}
.sprite-placeholder.anim-faint,
.sprite-img.anim-faint {
    animation: kf-faint var(--anim-dur, 0.6s) ease-in forwards;
}
/* p1 glisse par la GAUCHE, p2 par la DROITE : le nom d'animation est surchargé par
   côté (les longhands `animation-name` ci-dessous, placés APRÈS, priment sur le
   shorthand tout en conservant durée/easing/fill). */
.sprite-placeholder.anim-enter,
.sprite-img.anim-enter {
    animation: kf-enter-left var(--anim-dur, 0.5s) ease-out forwards;
}
.sprite-slot-p2 .anim-enter { animation-name: kf-enter-right; }
.sprite-placeholder.anim-leave,
.sprite-img.anim-leave {
    animation: kf-leave-left var(--anim-dur, 0.4s) ease-in forwards;
    z-index: 6;   /* le sortant passe au-dessus des murs/mines pendant sa sortie */
}
.sprite-slot-p2 .anim-leave { animation-name: kf-leave-right; }

.combat-energy-sphere {
    position: absolute;
    width: 90px; /* Ajustable selon la taille native du SVG */
    height: auto;
    pointer-events: none;
    z-index: 10;
}

/* « Matière Noire » : même sphère que l'explosion d'énergie, masquée en noir.
   brightness(0) écrase toutes les couleurs à zéro tout en conservant la
   transparence → silhouette noire de la sphère. */
.combat-dark-mask {
    filter: brightness(0);
}
