/**
 * CAPTURE DE MONSTRES - feuille de style unique de la mécanique.
 * File: modules/capture/capture.css
 * Documentation: Documentations/Capture/capture_css.md
 *
 * POURQUOI CE FICHIER EXISTE
 * Tailwind est purgé statiquement (scripts/tailwind/tailwind.config.js) : toute
 * classe Tailwind inédite écrite depuis un fichier JS n'existe tout simplement
 * pas au runtime tant que "npm run build" n'a pas été relancé. Or les modules
 * modules/capture/*.js construisent leur DOM à la volée. Ils ne peuvent donc
 * utiliser QUE des sélecteurs maison, et ces sélecteurs vivent tous ici.
 *
 * CONTRAINTES DURES RESPECTÉES PAR CE FICHIER
 * 1. Uniquement des sélecteurs #id et des classes maison préfixées
 *    "capture-" ou "capsule-monstre-". Aucune classe
 *    Tailwind n'est créée, seules des classes Tailwind DÉJÀ présentes dans le
 *    build peuvent être réutilisées par le JS.
 * 2. La spécificité d'un sélecteur #id (1-0-0) bat les utilitaires Tailwind
 *    (0-1-0) chargés avant : c'est ce qui permet à #combat-companion-image de
 *    piloter sa taille sans !important.
 * 3. Le fichier est chargé en DERNIER (après modules/ui/onboarding.css), donc à
 *    spécificité égale il gagne la cascade.
 * 4. Aucune transition CSS sur le "transform" du marqueur de carte : Leaflet
 *    réécrit transform à chaque pan/zoom et le compagnon glisserait en diagonale
 *    à l'écran. Le lissage se fait côté JS par micro-pas de 250 ms.
 *
 * SYSTÈME DE TAILLES
 * On reprend la convention clamp() de modules/ui/ultrawide_layout.css
 * (--combat-player-avatar-h, --combat-monster-avatar-size) : une variable par
 * élément redimensionnable, déclarée sur :root, puis réajustée dans les media
 * queries mobile portrait et ultrawide. Rien n'est écrit en pixels durs dans
 * les règles elles-mêmes, tout passe par les variables.
 *
 * CONVENTION DE NOMMAGE (contrat avec les modules JS, verifie selecteur par
 * selecteur : toute regle de ce fichier est reellement posee par un module, et
 * tout selecteur maison ecrit par un module est declare ici)
 *   capture_anim.js      -> #capture-anim-layer, .capture-capsule-sprite,
 *                           .capture-aspiration, .capture-aspiration-cone,
 *                           .capture-shake, .capture-capsule-shake,
 *                           .capture-cage-fond, .capture-cage-monstre, .capture-cage-avant,
 *                           .capture-flash, .capture-anim-label(-echec)
 *   companion_combat.js  -> #combat-companion-image, .capture-visible,
 *                           .capture-companion-idle, .capture-companion-attaque
 *   companion_map.js     -> .capture-companion-marker, .capture-companion-sprite,
 *                           .capture-companion-aura, .capture-companion-label
 *   companion_remote.js  -> reutilise TELS QUELS les selecteurs ci-dessus : un
 *                           compagnon distant se distingue par son libelle,
 *                           jamais par un style different
 *   capture_inventory_ui -> .capsule-monstre-sprite, .capsule-monstre-actif,
 *                           .capsule-monstre-puissance
 *   capture_reward_panel -> .capture-reward-section et ses enfants
 *                           (section 5 bis)
 *   capture_stats_modal  -> #capture-stats-modal, skin .structure-* injecte
 *                           par le module lui-meme (voir section 6)
 *
 * PIÈGES DÉJÀ IDENTIFIÉS ET TRAITÉS ICI
 * - Leaflet applique .leaflet-div-icon (fond blanc + bordure) à tout divIcon :
 *   .capture-companion-marker doit explicitement remettre le fond à transparent,
 *   sinon un carré blanc apparaît sous le compagnon.
 * - Le preflight Tailwind pose img { max-width: 100% } : les sprites du
 *   compagnon et de la modale doivent redéclarer leur largeur, sinon ils sont
 *   plaqués sur la largeur du conteneur.
 * - La case d'inventaire d'une capsule pleine porte toujours quantity 1 : le
 *   badge .item-quantity est masqué, et .capsule-monstre-puissance prend sa
 *   place en haut à droite avec la puissance du monstre.
 * - .inventory-item img porte déjà pointer-events:none dans game_styles.css ;
 *   on le redit sur les overlays qui ne sont pas des <img> (le badge <span>).
 */

/* ------------------------------------------------------------------------- */
/* 1. VARIABLES GLOBALES                                                      */
/* ------------------------------------------------------------------------- */

:root {
    /* Sprite du compagnon dans l'écran de combat (2e enfant flex de
       #combat-self-avatar-container). Volontairement plus petit que
       --combat-player-avatar-h (clamp 220px..640px) pour rester un acolyte. */
    --combat-companion-size: clamp(70px, 13vh, 190px);

    /* Marqueur du compagnon sur la carte Leaflet. Aligné sur la taille des
       marqueurs monstres existants pour ne pas écraser le décor. */
    --capture-marker-size: clamp(34px, 7vw, 56px);

    /* Sprite du monstre incrusté dans la case d'inventaire de la capsule.
       Exprimé en pourcentage de la case, qui est elle-même en aspect-ratio 1/1. */
    --capture-slot-sprite: 78%;

    /* Durées d'animation, alignées sur CaptureConfig.DUREE_SECOUSSE_MS (700) et
       sur la fenêtre de 500 ms de jouerAspiration. Si ces constantes changent
       côté JS, elles doivent changer ici aussi. */
    --capture-duree-secousse: 700ms;
    --capture-duree-aspiration: 500ms;
    --capture-duree-flash: 620ms;

    /* Palette de la mécanique, dérivée du skin terminal
       (modules/ui/terminal_modal_skin.css) pour rester cohérent visuellement.
       Seules les teintes réellement utilisées par une règle sont déclarées :
       la modale Statistiques reprend, elle, le vert terminal pur (#00ff00, celui
       de l'ancienne modale de mise à jour, retirée le 2026-10-04) pour ne pas
       jurer avec le cadre qu'elle emprunte. */
    --capture-vert: #22c55e;
    --capture-vert-clair: #86efac;

    /* Largeur maximale de la modale Statistiques. Bornée pour ne pas s'étirer
       sur un écran ultrawide, comme --ui-max-w-6xl le fait ailleurs. */
    --capture-modal-max-w: 34rem;

    /* Empilement : au dessus de l'inventaire, du menu contextuel (z 1600) et de
       l'écran de combat, mais sous la lightbox #item-observe-modal (10000000). */
    --capture-modal-z: 9999990;
}

/* ------------------------------------------------------------------------- */
/* 2. COMPAGNON EN COMBAT (companion_combat.js)                               */
/* ------------------------------------------------------------------------- */

/**
 * Le sprite est inséré comme SECOND enfant flex de #combat-self-avatar-container,
 * conteneur absolu sans largeur ni "right" : sa largeur est shrink-to-fit et il
 * est ancré à gauche par ultrawide_layout.css:82. Ajouter un enfant l'élargit
 * donc vers la droite SANS déplacer l'avatar du joueur.
 * align-self:flex-end pose les pieds du compagnon au sol, le parent étant en
 * items-center (donc centré verticalement par défaut).
 */
/**
 * PLACEMENT DU COMPAGNON EN COMBAT - regle partagee.
 *
 * Deux elements la portent, et c'est tout l'interet :
 *   #combat-companion-image        le sprite reel, monte par companion_combat.js ;
 *   .capture-ancrage-compagnon     une sonde invisible que companion_swap_anim.js
 *                                  pose une fraction de seconde pour SAVOIR ou le
 *                                  sprite va apparaitre, et y faire atterrir la
 *                                  cage de sortie.
 *
 * La sonde existe parce qu'au moment ou la cage vole, le sprite n'est pas encore
 * monte : companion_swap.echanger() ne le monte qu'APRES l'animation, sinon on
 * verrait le compagnon apparaitre avant que la cage s'ouvre.
 *
 * Avant cette regle, l'animation calculait son point d'atterrissage a des
 * fractions de la boite de l'avatar, reglees a l'oeil. Deux formules pour un meme
 * point : elles ont diverge des que la position du sprite a change.
 */
#combat-companion-image,
#combat-companion-fx,
.capture-ancrage-compagnon {
    /* POSITION : centre de l'avatar du joueur, puis décalage.
       Le sprite est sorti du flux flex (`absolute`), ce qui a un effet de bord
       voulu : le conteneur retrouve la largeur du seul avatar joueur, donc son
       centre EST le centre de l'avatar, et `left: 50%` / `top: 50%` y tombent
       juste. Tant que le compagnon était un enfant flex, il élargissait le
       conteneur et décalait ce repère. */
    --combat-companion-dx: 30vw;   /* vers la droite */

    /* Decalage vertical SIGNE depuis le centre de l'avatar, exprime EN HAUTEUR
       D'AVATAR JOUEUR et pas en vh. Negatif = vers le haut.

       +0,25 : la BASE du compagnon se pose au quart de la hauteur de l'avatar en
       partant de sa base. Depuis le centre, cela fait bien +1/4 de hauteur
       (base = centre + 1/2 - 1/4). Regle demandee explicitement.

       -6 % et non +50 % (les pieds) : le panneau de combat
       `#turn-based-combat-self-ui` est au plan SELF_UI contre SELF_AVATAR pour le
       conteneur de l'avatar (cf. modules/combat/combat_zindex.js), et il occupe
       les 46 % bas de l'ecran en mobile, 37 % en desktop. Toute la moitie basse
       de l'avatar est donc deja cachee derriere lui. Aux pieds, le compagnon
       etait invisible : verifie en combat reel, `elementFromPoint` sur trois
       points du sprite renvoyait les boutons d'action. Monter son z-index
       au-dessus du panneau le ferait recouvrir ces boutons, ce n'est pas une
       option : le calage vertical reste donc la seule solution.

       Le mobile est le cas contraignant. A -8,4 % le sprite affleure le haut du
       panneau en 452x734 ; a -6 % ses 12 derniers pixels passent derriere, soit
       14 % de sa hauteur, ce qui se lit comme un compagnon qui se tient DERRIERE
       le bandeau plutot que comme un sprite coupe. En desktop rien n est cache.
       Descendre plus le ferait disparaitre en mobile.

       C'est bien la hauteur RENDUE, facteur d'echelle compris : sans le
       `* scale` le decalage se calculerait sur une hauteur qui n'est pas celle
       affichee. L'avatar est borne par un clamp
       (`--combat-player-avatar-h`, 220px..640px) alors que vh ne l'est pas, donc
       un decalage en vh se desolidarisait de lui des qu'on changeait d'ecran.
       Les deux variables sont declarees sur :root dans
       modules/ui/ultrawide_layout.css ; les valeurs de repli servent uniquement
       si cette feuille n'est pas chargee. */
    --combat-companion-dy: calc(
        var(--combat-player-avatar-h, clamp(220px, 42vh, 640px))
        * var(--combat-player-avatar-scale, 1.7) * 0.25);
    position: absolute;
    left: calc(50% + var(--combat-companion-dx));
    top: calc(50% + var(--combat-companion-dy));

    /* Centrage par `translate` et NON par `transform` : transform est déjà pris
       par l'animation d'entrée puis par la respiration idle, une valeur posée là
       serait écrasée par les deux. `translate` est une propriété distincte,
       appliquée avant `transform` dans la chaîne : les deux cohabitent.

       -50 % horizontal : centrage classique sur le point d'ancrage.
       -100 % VERTICAL, et pas -50 % : c'est le BAS du sprite qui se pose sur le
       point d'ancrage, jamais son centre. Le compagnon a donc toute sa hauteur
       AU-DESSUS de ce point, ce qui est exactement ce dont dépend le calage sur
       le haut du panneau d'UI (voir `--combat-companion-dy`). Avec un centrage
       en -50 %, la moitié basse passait sous le point d'ancrage et il fallait
       compenser à la main. */
    translate: -50% -100%;
}

/**
 * LA ZONE DES NOMBRES FLOTTANTS DU COMPAGNON.
 *
 * Elle partage l'ancrage du sprite (selecteur groupe ci-dessus) : UNE SEULE source de
 * verite pour `--combat-companion-dx/dy`. Deux formules independantes pour un meme
 * point ont deja diverge une fois dans ce module (cf. companion_swap_anim) ; on ne
 * recommence pas.
 *
 * Rappel de l'ancrage : `translate: -50% -100%` pose le BAS de l'element sur le point
 * d'ancrage. La zone recouvre donc exactement la boite du sprite, meme translate,
 * largeur et hauteur egales a `--combat-companion-size`.
 *
 * Sans elle, un nombre flottant monte dans le conteneur de l'avatar joueur
 * s'afficherait SUR LE JOUEUR : le compagnon en est decale de 30vw vers la droite.
 */
#combat-companion-fx {
    width: var(--combat-companion-size);
    height: var(--combat-companion-size);
    pointer-events: none;
    /* Au-dessus du sprite (z local 1) mais dans la meme pile : les nombres flottants
       accompagnent le compagnon, ils ne doivent pas sortir de sa colonne. */
    z-index: 2;
}

/**
 * LA JAUGE DU COMPAGNON EST UNE LIGNE DU PANNEAU D'ACTION.
 *
 * Elle a d'abord ete posee au-dessus du sprite, en absolu, avec une exception de plan
 * d'affichage pour passer devant le panneau. Deux problemes : le sprite est place
 * ENTIEREMENT derriere ce panneau (mesure en combat reel : sprite en y 607-719, haut du
 * panneau y 578), donc la jauge flottait au-dessus d'un compagnon qu'on ne voyait qu'a
 * peine ; et elle ne se lisait pas avec les autres jauges, alors qu'elle dit la meme
 * chose qu'elles.
 *
 * Elle est desormais inseree JUSTE APRES la barre de vie du joueur, dans le flux
 * (`companion_vitalite.js`, ancre `#combat-self-hpbar`) : meme largeur, meme colonne,
 * plus fine. Plus d'exception de z-index, plus de translate, plus de dependance a
 * `--combat-companion-size`.
 */
#combat-companion-hpbar {
    width: 100%;
    pointer-events: none;
    /* Le JS pose `items-center` : ses enfants (la jauge) prenaient alors la largeur de
       leur contenu, soit ~0 (fond et texte sont en absolu). Resultat : un trait avec une
       lettre au bout. `stretch` rend la pleine largeur, comme la barre du joueur, dont
       le parent est un `flex-col` sans alignement. L'#id bat l'utilitaire Tailwind. */
    align-items: stretch;
}

/**
 * PLUS FINE QUE CELLES DU JOUEUR : le compagnon est un acolyte, sa ligne ne doit pas
 * peser autant que les siennes.
 *
 * `createHealthBar` pose `h-6` (1.5rem) EN DUR sur l'enveloppe de la barre ; le JS
 * echange cette classe contre celle-ci. Une classe NOMMEE et non un utilitaire
 * Tailwind fabrique a la volee : la purge est statique dans ce projet.
 *
 * Hauteur depuis le 2026-10-05 : `--combat-barre-pv-compagnon-h` (1rem, libelle et icones
 * de statut), definie avec celles des autres barres dans modules/combat/combat_barres.css.
 */
#combat-companion-hpbar .capture-compagnon-jauge {
    height: var(--combat-barre-pv-compagnon-h, 1rem);
}

/* Le libelle porte le nom du compagnon EN PLUS des points de vie : il est plus long
   que « 120/120 PV ». On le laisse retrecir plutot que deborder sur un nom a rallonge. */
#combat-companion-hpbar .health-text {
    font-size: 0.7rem;
    line-height: 1;
    padding: 0 0.25rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* STATUTS DU COMPAGNON DANS SA JAUGE DE PV (2026-10-05), cales a droite, au-dessus du
   libelle (`.health-text` est en z-10). Ils etaient en rangee sous la jauge. Le repli
   (enveloppe introuvable) garde une rangee centree dans le flux. */
#combat-companion-hpbar .capture-compagnon-statuts {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.125rem;
    font-size: 0.7rem;
    line-height: 1;
}
#combat-companion-hpbar .capture-compagnon-jauge > .capture-compagnon-statuts {
    position: absolute;
    top: 50%;
    right: 0.125rem;
    transform: translateY(-50%);
    z-index: 20;
}
/* Icones peintes a 18 px (peindreIconesStatut) : ramenees a 0.8rem dans la jauge de 1rem. */
#combat-companion-hpbar .capture-compagnon-statuts svg,
#combat-companion-hpbar .capture-compagnon-statuts img {
    width: 0.8rem;
    height: 0.8rem;
}
/* Le libelle (centre, tronque) laisse la place aux icones au lieu de passer dessous. */
#combat-companion-hpbar .capture-compagnon-jauge:has(> .capture-compagnon-statuts > :nth-child(1)) .health-text { padding-right: 1.1rem; }
#combat-companion-hpbar .capture-compagnon-jauge:has(> .capture-compagnon-statuts > :nth-child(2)) .health-text { padding-right: 2rem; }
#combat-companion-hpbar .capture-compagnon-jauge:has(> .capture-compagnon-statuts > :nth-child(3)) .health-text { padding-right: 2.9rem; }

/* Compagnon hors de combat : jauge grisee, l'etat se lit sans avoir a chercher le
   sprite, qui est derriere le panneau. */
#combat-companion-hpbar.capture-compagnon-hpbar-ko {
    filter: grayscale(1) brightness(0.7);
    opacity: 0.8;
}

/* La sonde ne sert qu'a etre mesuree : aucune peinture, aucune interception, et
   surtout aucune place prise dans le flux. */
.capture-ancrage-compagnon {
    width: 0;
    height: var(--combat-companion-size);
    opacity: 0;
    pointer-events: none;
    transform: none;
    transition: none;
    animation: none;
}

#combat-companion-image {
    height: var(--combat-companion-size);
    width: auto;

    /* 70vw et non 40vw : depuis que le compagnon est à sa taille réelle, sa
       hauteur peut atteindre 1,5 fois la hauteur de référence du joueur. À 40vw,
       la largeur devenait la contrainte dominante et un gros compagnon était
       rapetissé jusqu'à annuler tout le calcul d'échelle. Cette borne n'est plus
       qu'un garde-fou contre un sprite anormalement large. */
    max-width: 70vw;

    /* Devant l'adversaire, derrière le joueur.

       Ce 1 est LOCAL au conteneur de l'avatar joueur : celui-ci porte le plan
       SELF_AVATAR (cf. modules/combat/combat_zindex.js) et cree donc son propre
       contexte d'empilement. Tout ce qu'il contient passe devant l'adversaire,
       range plus bas dans le bareme ; il ne reste qu'a placer le compagnon sous
       l'avatar, a l'interieur.

       Le compagnon herite ainsi de SELF_AVATAR et non du plan COMPANIONS des
       coequipiers, dont il devrait relever. Ecart assume : tout son
       positionnement est ancre sur l'avatar (voir --combat-companion-dx / dy
       plus haut), le sortir du conteneur casserait ce calage. Les deux ne se
       superposent jamais a l'ecran, le compagnon etant colle a l'avatar. */
    z-index: 1;
    object-fit: contain;

    /* LES PIEDS RESTENT SUR LE POINT D'ANCRAGE, même quand la largeur borne.
       `translate: -100%` cale le BAS DE LA BOÎTE sur le sol. Si `max-width`
       rétrécit l'image, `object-fit: contain` la centre verticalement dans une
       boîte restée haute : le sprite se met à flotter au-dessus du sol. Ancré en
       bas, il reste planté, et la seule conséquence d'une largeur contrainte est
       qu'il paraît plus petit, ce qui est le comportement voulu. */
    object-position: bottom;

    pointer-events: none;
    opacity: 0;
    transform: translateY(14px) scale(0.92);

    /* MEME PROFONDEUR DE CHAMP QUE LE JOUEUR : l'avatar du joueur porte la classe
       Tailwind `blur-[2px]` des sa creation, et la perd en fondu au rack focus de
       fin de combat. Le compagnon est au meme plan, il doit suivre exactement.
       Le flou est declare ICI et non par une classe Tailwind parce que le sprite
       est cree par companion_combat.js, pas par le moteur de combat.

       Le flou et l'ombre portee tiennent dans UNE SEULE declaration `filter` :
       deux `filter` dans la meme regle ne se cumulent pas, le second gagne. */
    filter: blur(2px) drop-shadow(0 6px 10px rgba(0, 0, 0, 0.6));
    transition: opacity 0.6s ease-out, transform 0.6s ease-out, filter 1s ease-out;
    border-radius: 12px;
    image-rendering: auto;
}

/* Révélation déclenchée par CaptureCompanionCombat.revelerEntree(). */
#combat-companion-image.capture-visible {
    opacity: 1;
    transform: translateY(0) scale(1);
}

/**
 * Mise au point sur le compagnon, en meme temps que sur le joueur.
 *
 * Posee par le timer `victory:blur-fade` de turn_based_combat.js, qui bascule la
 * mise au point du decor vers le premier plan a la fin du combat. Une CLASSE et
 * pas un `style.filter` inline : le sprite a deja une transition sur `opacity` et
 * `transform` (entree en combat), et ecrire `element.style.transition` pour le
 * flou les effacerait toutes les deux.
 */
#combat-companion-image.capture-companion-net {
    /* L'ombre portee est reprise telle quelle : `filter` remplace la liste
       entiere, l'omettre ferait disparaitre l'ombre en meme temps que le flou. */
    filter: blur(0px) drop-shadow(0 6px 10px rgba(0, 0, 0, 0.6));
}

/**
 * Respiration du compagnon en combat. Classe SÉPARÉE de .capture-visible : une
 * animation gagne toujours sur une transition pour la même propriété, donc si
 * les deux étaient fusionnées l'animation d'entrée serait avalée. Le JS pose
 * .capture-companion-idle après l'entrée, avec un animation-delay négatif
 * aléatoire inline (même technique que renderTeammates) pour désynchroniser.
 */
.capture-companion-idle {
    animation: capture-companion-breathe 3.6s ease-in-out infinite;
}

@keyframes capture-companion-breathe {
    0% { transform: translateY(0) scale(1); }
    50% { transform: translateY(-3px) scale(1.012); }
    100% { transform: translateY(0) scale(1); }
}

/**
 * Bond d'attaque du compagnon (`CaptureCompanionCombat.jouerTour`).
 *
 * POURQUOI CETTE ANIMATION EXISTE : le moteur fait partir l'animation du lanceur
 * depuis `attackerType`, qui vaut `'self'` pour le compagnon (contrainte
 * F-CAPTURE-1). Le GIF du lanceur se joue donc sur l'avatar du JOUEUR, et le
 * sprite du compagnon ne bouge pas d'un pixel : le joueur voyait des dégâts
 * s'appliquer et une ligne d'historique apparaître, sans que rien à l'écran ne
 * désigne le compagnon comme l'auteur du coup. Ce bond est la réponse : c'est le
 * seul signal visuel qui rattache l'attaque à lui.
 *
 * Il n'est PAS confié au moteur : rediriger `attackerType` vers le sprite du
 * compagnon reviendrait à toucher tout le chemin d'animation du joueur pour un
 * cas particulier. L'animation vit ici, le module la déclenche, le moteur ignore
 * jusqu'à son existence.
 *
 * TRAJECTOIRE : l'adversaire est en haut à droite, le compagnon en bas à gauche
 * à côté du joueur. Le bond part donc vers le haut et la droite, marque l'impact,
 * puis revient. Le léger `scale` au sommet donne l'élan sans changer l'ancrage,
 * `transform-origin` restant au centre bas (les pieds au sol).
 *
 * PRIORITÉ SUR LA RESPIRATION : `.capture-companion-idle` anime déjà `transform`
 * en boucle. Deux animations sur la même propriété : la dernière déclarée gagne,
 * mais l'ordre du fichier n'est pas une garantie fiable quand le JS ajoute et
 * retire des classes. Le JS RETIRE donc `.capture-companion-idle` le temps du
 * bond et la remet après — voir `jouerBondAttaque`.
 */
#combat-companion-image.capture-companion-attaque {
    animation: capture-companion-lunge 620ms cubic-bezier(0.34, 1.3, 0.64, 1) both;
}

@keyframes capture-companion-lunge {
    0%   { transform: translate(0, 0) scale(1); }
    /* Recul d'élan : le mouvement se lit mieux s'il commence par l'inverse. */
    18%  { transform: translate(-6%, 2%) scale(0.97); }
    /* Détente vers l'adversaire, en haut à droite. */
    46%  { transform: translate(26%, -14%) scale(1.08); }
    /* Impact : arrêt net, très légèrement écrasé. */
    56%  { transform: translate(30%, -11%) scale(1.04); }
    100% { transform: translate(0, 0) scale(1); }
}

/**
 * LE COMPAGNON ENCAISSE.
 *
 * Miroir exact du bond : même mécanique, sens inverse. Il est poussé vers la gauche
 * et vers le bas, loin de l'adversaire, puis se redresse. La durée est tenue EN
 * DOUBLE avec `DUREE_RECUL_MS` de companion_combat.js (320 ms) : changer l'une sans
 * l'autre laisse la classe posée trop longtemps ou trop peu.
 *
 * Plus court que le bond : encaisser est un sursaut, pas une charge.
 *
 * Le `filter` est déclaré DANS la règle de l'animation et non à part : la règle de
 * base de `#combat-companion-image` porte déjà un `filter: drop-shadow(...) blur(...)`,
 * et un second `filter` ne se cumule pas, il remplace. On reprend donc l'ombre à
 * l'identique dans la keyframe, sinon elle disparaîtrait pendant le recul.
 */
#combat-companion-image.capture-companion-touche {
    animation: capture-companion-recul 320ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

@keyframes capture-companion-recul {
    0%   { transform: translate(0, 0) scale(1); }
    /* Choc : poussé en arrière et légèrement écrasé, teinte rouge vif. */
    22%  { transform: translate(-14%, 4%) scale(0.94); filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.45)) hue-rotate(-15deg) saturate(3.2) brightness(1.55); }
    /* Rebond court, la teinte retombe. */
    58%  { transform: translate(5%, -2%) scale(1.02); filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.45)) saturate(1.4) brightness(1.15); }
    100% { transform: translate(0, 0) scale(1); }
}

/**
 * COMPAGNON K.O.
 *
 * Il ne respire plus, il ne bondit plus, et il se lit comme hors jeu d'un coup d'œil :
 * désaturé et assombri. `animation: none !important` coupe la respiration même si la
 * classe `.capture-companion-idle` est restée posée, ce qui évite d'avoir à garantir
 * l'ordre de retrait des classes côté JS.
 *
 * Le sprite reste affiché : un compagnon qui disparaîtrait d'un coup se lirait comme
 * un bug d'affichage, pas comme une défaite.
 */
#combat-companion-image.capture-companion-ko {
    animation: none !important;
    transform: translate(0, 0) scale(1);
    filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.45)) grayscale(1) brightness(0.55);
    opacity: 0.75;
}

@media (prefers-reduced-motion: reduce) {
    #combat-companion-image.capture-companion-touche {
        animation: none;
    }
}

/**
 * Badge de cage a gauche du nom de l'adversaire (capture_combat_badge.js).
 *
 * Miniature posee en PREMIER enfant du wrapper du nom, un flex `items-center gap-2` :
 * l'alignement et l'espacement viennent donc du conteneur, il n'y a ni marge ni
 * positionnement a poser ici.
 *
 * DEUX ETATS, UNE SEULE IMAGE :
 *   .est-possede  en couleur, le joueur detient deja cette espece ;
 *   .est-absent   en noir et blanc, l'espece est capturable mais absente.
 * Le noir et blanc est un filtre `grayscale`, pas un second fichier : rien de plus
 * a dessiner, a charger ni a maintenir quand une nouvelle cage est ajoutee.
 *
 * Pas de transition ni d'animation propre : le wrapper demarre a opacity 0 et
 * n'apparait qu'avec l'animation d'entree du combat. Le badge en herite, ce qui le
 * fait apparaitre exactement en meme temps que le nom qu'il accompagne.
 */
.capture-badge-cage {
    width: 18px;
    height: 18px;
    flex: 0 0 auto;
    object-fit: contain;
    image-rendering: auto;
    pointer-events: none;
}

/* Deja en collection : couleurs d'origine et lueur verte, lisible sur decor sombre
   comme clair. */
.capture-badge-cage.est-possede {
    filter: drop-shadow(0 0 3px rgba(74, 222, 128, 0.85)) drop-shadow(0 1px 2px rgba(0, 0, 0, 0.8));
}

/**
 * Capturable mais pas encore possede : noir et blanc.
 *
 * `grayscale(1)` seul rendrait la cage terne et presque invisible sur un fond de
 * combat sombre. On remonte donc legerement le contraste et la luminosite, et on
 * garde une ombre portee neutre (jamais verte : le vert est reserve a l'etat
 * possede, c'est ce qui distingue les deux d'un coup d'oeil). L'opacite reduite
 * acheve de dire "pas encore a vous" sans le rendre illisible.
 */
.capture-badge-cage.est-absent {
    filter: grayscale(1) contrast(1.15) brightness(1.1)
            drop-shadow(0 1px 2px rgba(0, 0, 0, 0.9));
    opacity: 0.72;
}

/* ------------------------------------------------------------------------- */
/* 3. ANIMATIONS DE LA TENTATIVE DE CAPTURE (capture_anim.js)                 */
/* ------------------------------------------------------------------------- */

/**
 * Calque de travail créé par CaptureAnim dans #turn-based-combat-container.
 * Il est détruit gratuitement par endTurnBasedCombat qui supprime le conteneur
 * entier, donc aucun nettoyage DOM n'est nécessaire côté JS.
 * pointer-events:none est impératif : le calque couvre l'écran de combat et
 * bloquerait sinon les boutons d'action.
 *
 * LE PLAN COMPTE : la cage doit passer DERRIÈRE l'avatar du joueur. Elle est
 * lancée depuis lui vers le fond de la scène ; en avant-plan elle volait par-dessus
 * son dos, ce qui inversait la profondeur et donnait l'impression qu'elle partait
 * de derrière la caméra.
 *
 * 40, c'est THROWN_ITEM dans modules/combat/combat_zindex.js : le rang des objets
 * lancés par le joueur, entre son avatar (60) et l'adversaire visé (20). La cage
 * passe donc sous celui qui la lance et par-dessus celui qu'elle vise, ce qui est
 * exactement le sens de la trajectoire.
 *
 * Cette valeur était auparavant coincée à 29 : les deux conteneurs étaient TOUS
 * DEUX à z-30 et seul l'ordre du DOM les départageait, si bien qu'aucun entier ne
 * tombait entre eux et que passer sous l'avatar imposait de passer sous
 * l'adversaire aussi. Le barème les sépare, la contrainte est levée.
 *
 * Le calque porte un z-index, il crée donc un contexte d'empilement : aucun de
 * ses enfants ne peut en sortir. Les z-index 1 / 2 / 3 des trois couches de la
 * cage sont relatifs à lui, et restent intacts.
 */
#capture-anim-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
    overflow: hidden;
    z-index: 40; /* THROWN_ITEM, cf. modules/combat/combat_zindex.js */
}

/**
 * Cage dessinée par l'animation (sprite images/items/cage_de_capture.png).
 *
 * PAS de largeur ici : depuis la refonte de la séquence, la cage se dimensionne
 * sur le MONSTRE (20 % plus grande que lui) et non sur le viewport. Le calcul est
 * fait en JS dans calculerAncrage(), et la taille posée en inline. Une valeur CSS
 * serait soit écrasée, soit en conflit selon l'ordre d'application.
 *
 * transform-origin au centre : la cage grossit et pivote autour du monstre qu'elle
 * enferme, pas autour de sa propre base.
 */
.capture-capsule-sprite {
    position: absolute;
    height: auto;
    max-width: none;
    pointer-events: none;
    transform-origin: 50% 50%;
    filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.65));
}

/**
 * Aspiration du monstre : posée sur #combat-opponent-image.
 * Implémentée en TRANSITION et non en animation, pour que jouerEchec puisse
 * restaurer le sprite en retirant simplement la classe (retour animé gratuit).
 * Si le sprite restait invisible, le monstre serait absent pour tout le reste
 * du combat : c'est le bug le plus grave possible de cette mécanique.
 */
.capture-aspiration {
    transform: scale(0.06) translateY(8%) !important;
    opacity: 0 !important;
    filter: brightness(2.2) saturate(0.2);
    transition:
        transform var(--capture-duree-aspiration) cubic-bezier(0.55, 0, 0.85, 0.35),
        opacity var(--capture-duree-aspiration) ease-in,
        filter var(--capture-duree-aspiration) ease-in;
}

/* Cône d'aspiration facultatif, dessiné dans #capture-anim-layer. */
.capture-aspiration-cone {
    position: absolute;
    pointer-events: none;
    background: radial-gradient(
        circle at 50% 100%,
        rgba(134, 239, 172, 0.55) 0%,
        rgba(34, 197, 94, 0.25) 45%,
        rgba(34, 197, 94, 0) 72%
    );
    animation: capture-aspiration var(--capture-duree-aspiration) ease-in forwards;
}

@keyframes capture-aspiration {
    0% { opacity: 0; transform: scale(0.4); }
    35% { opacity: 1; transform: scale(1.05); }
    100% { opacity: 0; transform: scale(0.15); }
}

/**
 * Secousses. Deux classes distinctes pour deux cibles :
 *  - .capture-shake         : secousse générique (conteneur, sprite adverse...)
 *  - .capture-capsule-shake : secousse de la capsule elle-même, avec rotation
 *                             plus marquée et pivot au sol.
 * Les deux partagent la même durée que CaptureConfig.DUREE_SECOUSSE_MS.
 */
.capture-shake {
    animation: capture-shake var(--capture-duree-secousse) ease-in-out;
}

.capture-capsule-shake {
    animation: capture-capsule-shake var(--capture-duree-secousse) ease-in-out;
    transform-origin: 50% 50%;
}

/**
 * Couches du groupe de cage (capture_anim.js).
 *
 * L'illusion "le monstre est dedans" tient a UN SEUL point : l'ordre d'empilement.
 *   z-index 1  .capture-cage-fond    la coque et les barreaux de derriere
 *   z-index 2  .capture-cage-monstre le monstre, centre et reduit
 *   z-index 3  .capture-cage-avant   les barreaux de devant, PAR DESSUS lui
 *
 * Les trois sont posees en position absolue dans le meme conteneur, et c'est le
 * CONTENEUR qu'on anime : elles bougent donc comme un seul objet. Les animer
 * separement les ferait glisser les unes par rapport aux autres et l'effet
 * s'effondrerait immediatement.
 *
 * Le positionnement exact (inset, taille, centrage du monstre) est pose en inline
 * par le JS, qui seul connait la taille reelle du sprite adverse. On ne declare ici
 * que ce qui ne depend pas de la mesure.
 */
.capture-cage-fond,
.capture-cage-avant,
.capture-cage-monstre {
    pointer-events: none;
    user-select: none;
}

@keyframes capture-shake {
    0% { transform: translate(0, 0) rotate(0deg); }
    12% { transform: translate(-4px, 0) rotate(-6deg); }
    28% { transform: translate(4px, 0) rotate(6deg); }
    44% { transform: translate(-3px, 0) rotate(-4deg); }
    60% { transform: translate(3px, 0) rotate(4deg); }
    76% { transform: translate(-2px, 0) rotate(-2deg); }
    100% { transform: translate(0, 0) rotate(0deg); }
}

@keyframes capture-capsule-shake {
    0% { transform: translate(0, 0) rotate(0deg) scale(1); }
    10% { transform: translate(-6px, -2px) rotate(-14deg) scale(1.03); }
    30% { transform: translate(6px, -2px) rotate(14deg) scale(1.03); }
    50% { transform: translate(-5px, 0) rotate(-10deg) scale(1.01); }
    70% { transform: translate(5px, 0) rotate(10deg) scale(1.01); }
    88% { transform: translate(-2px, 0) rotate(-3deg) scale(1); }
    100% { transform: translate(0, 0) rotate(0deg) scale(1); }
}

/**
 * Flash de résolution. Élément plein calque ajouté dans #capture-anim-layer par
 * jouerReussite(). L'échec n'a volontairement pas de voile : il est raconté par
 * l'éclat de la capsule, la secousse du bloc adverse et le "RATE !" flottant.
 */
.capture-flash {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(
        circle at 50% 55%,
        rgba(240, 253, 244, 0.95) 0%,
        rgba(134, 239, 172, 0.55) 35%,
        rgba(34, 197, 94, 0) 70%
    );
    opacity: 0;
    animation: capture-flash var(--capture-duree-flash) ease-out forwards;
}

@keyframes capture-flash {
    0% { opacity: 0; transform: scale(0.7); }
    25% { opacity: 1; transform: scale(1); }
    100% { opacity: 0; transform: scale(1.35); }
}

/* Bandeau de texte optionnel de fin d'animation ("CAPTURE !" / "RATE !"). */
.capture-anim-label {
    position: absolute;
    left: 50%;
    top: 38%;
    transform: translate(-50%, -50%);
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-weight: 700;
    font-size: clamp(1.1rem, 4.5vw, 2.1rem);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--capture-vert-clair);
    text-shadow: 0 0 10px rgba(34, 197, 94, 0.75), 0 2px 6px rgba(0, 0, 0, 0.9);
    pointer-events: none;
    white-space: nowrap;
    animation: capture-flash var(--capture-duree-flash) ease-out forwards;
}

.capture-anim-label.capture-anim-label-echec {
    color: #fca5a5;
    text-shadow: 0 0 10px rgba(239, 68, 68, 0.75), 0 2px 6px rgba(0, 0, 0, 0.9);
}

/* ------------------------------------------------------------------------- */
/* 4. CASE D'INVENTAIRE D'UNE CAPSULE PLEINE (capture_inventory_ui.js)        */
/* ------------------------------------------------------------------------- */

/**
 * decorerCase pose itemEl.dataset.capsuleMonstre = data.uid, ce qui donne
 * l'attribut data-capsule-monstre. La quantité vaut toujours 1 (chaque capsule
 * porte un uid unique, addItem ne les empile donc jamais) : le badge est masqué
 * pour laisser la place au sprite incrusté en bas à gauche.
 */
.inventory-item[data-capsule-monstre] {
    position: relative;
}

.inventory-item[data-capsule-monstre] .item-quantity {
    display: none;
}

/**
 * Sprite du monstre, AU PREMIER PLAN devant la cage.
 *
 * POURQUOI CETTE COMPOSITION PLUTOT QU'UNE ILLUSTRATION PAR MONSTRE : les cages
 * dessinées avec leur occupant sont superbes en grand, mais dans une case de 60 px
 * la coque métallique et les barreaux mangent toute la surface, l'occupant devient
 * une tache sombre au centre et deux espèces sont indiscernables. Ici la cage sert
 * de fond et le monstre passe DEVANT, en grand : l'espèce se lit au premier coup
 * d'oeil, ce qui est le seul rôle que doit remplir une case d'inventaire.
 *
 * Le sprite est centré et déborde volontairement du cadre de la cage vers le bas
 * (bottom négatif) : sans ce débordement il paraîtrait enfermé dans la sphère et
 * redeviendrait petit. L'ombre portée le décolle du fond.
 *
 * L'image passe obligatoirement par cheminVignette + onerror replierImageItem :
 * les sprites sources font 269 à 512 px pour un affichage de quelques dizaines.
 * max-width:none neutralise le preflight Tailwind (img { max-width: 100% }).
 */
.capsule-monstre-sprite {
    position: absolute;
    left: 50%;
    bottom: -4%;
    width: var(--capture-slot-sprite);
    height: auto;
    max-width: none;
    transform: translateX(-50%);
    pointer-events: none;
    image-rendering: auto;
    object-fit: contain;
    /* Contour sombre puis lueur : le monstre se détache de la cage quelle que
       soit sa couleur propre, y compris sur les sprites très clairs. */
    filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.95))
            drop-shadow(0 2px 3px rgba(0, 0, 0, 0.85));
    z-index: 2;
}

/**
 * L'icône de la cage est reculée : légèrement réduite, remontée et assombrie, pour
 * jouer le rôle de fond derrière le monstre. Sans cela les barreaux passeraient
 * visuellement devant lui malgré l'ordre d'empilement.
 */
.inventory-item[data-capsule-monstre] > img:first-of-type {
    transform: translateY(-8%) scale(0.86);
    filter: brightness(0.72) saturate(0.85);
}

/**
 * Puissance du monstre, en haut à droite de la case.
 *
 * POURQUOI LÀ : c'est l'emplacement qu'occupe .item-quantity sur toutes les
 * autres cases du sac, badge masqué ici puisqu'une capsule est toujours en
 * quantité 1. L'oeil du joueur va déjà chercher un chiffre à cet endroit : on
 * lui donne la seule valeur qui distingue deux captures de la même espèce.
 *
 * Le fond opaque et le contour sont indispensables : le sprite du monstre passe
 * juste dessous, un chiffre posé à nu dessus serait illisible sur les espèces
 * claires. z-index 3, au-dessus du sprite (2). font-size en clamp : les cases
 * descendent à 48 px sur les petits écrans, un chiffre à quatre positions doit
 * y tenir sans déborder. pointer-events à none, comme la pastille : un <span>
 * n'hérite pas du pointer-events:none posé sur .inventory-item img.
 */
.capsule-monstre-puissance {
    position: absolute;
    top: 2px;
    right: 2px;
    padding: 0 3px;
    min-width: 14px;
    border-radius: 3px;
    background: rgba(0, 0, 0, 0.82);
    border: 1px solid rgba(34, 197, 94, 0.75);
    color: #86efac;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: clamp(7px, 1.9vw, 10px);
    font-weight: 700;
    line-height: 1.4;
    text-align: center;
    letter-spacing: -0.02em;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
    pointer-events: none;
    z-index: 3;
}

/**
 * Pastille "compagnon actif". Un <span> et non un <img> : .inventory-item img
 * porte déjà pointer-events:none dans game_styles.css, pas les <span>, d'où le
 * rappel explicite ici.
 */
.capsule-monstre-actif {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #00ff00;
    box-shadow: 0 0 6px #00ff00;
    pointer-events: none;
    z-index: 3;
    animation: capture-pastille-pulse 1.8s ease-in-out infinite;
}

@keyframes capture-pastille-pulse {
    0% { box-shadow: 0 0 4px #00ff00; opacity: 0.85; }
    50% { box-shadow: 0 0 9px #00ff00; opacity: 1; }
    100% { box-shadow: 0 0 4px #00ff00; opacity: 0.85; }
}

/**
 * Halo de la case du compagnon actif. :has() est ignoré en bloc par les
 * navigateurs qui ne le supportent pas : la règle disparaît sans casser le
 * reste de la feuille, la pastille suffit alors à identifier le compagnon.
 */
.inventory-item[data-capsule-monstre]:has(.capsule-monstre-actif) {
    border-color: var(--capture-vert);
    box-shadow: 0 0 8px rgba(34, 197, 94, 0.55) inset;
}

/* ------------------------------------------------------------------------- */
/* 5. MARQUEUR DU COMPAGNON SUR LA CARTE (companion_map.js)                   */
/* ------------------------------------------------------------------------- */

/**
 * Classe posée sur le L.divIcon. Leaflet ajoute automatiquement
 * .leaflet-div-icon (fond blanc + bordure grise) : sans ce reset, un carré
 * blanc apparaît sous le compagnon. Les !important sont donc nécessaires,
 * .leaflet-div-icon étant déclaré dans la feuille Leaflet chargée avant.
 *
 * AUCUNE transition sur transform : Leaflet réécrit transform à chaque pan et à
 * chaque zoom, une transition ferait glisser le compagnon en diagonale à
 * l'écran. Le lissage vient des micro-pas de 250 ms côté JS.
 */
.capture-companion-marker {
    background: transparent !important;
    background-color: transparent !important;
    border: 0 !important;
    box-shadow: none !important;
    pointer-events: none !important;
    transition: none !important;
    will-change: transform;
}

/**
 * Sprite du compagnon. On borne en max-width / max-height (et non en
 * width / height) : le JS pose width:auto;height:auto en inline pour preserver
 * le rapport d'aspect, et une largeur ferme serait de toute facon ecrasee par
 * ce inline. Les bornes reprennent --capture-marker-size, que le JS relit lui
 * aussi en repli inline : la responsivite reste pilotee d'ici.
 */
.capture-companion-marker .capture-companion-sprite,
.capture-companion-marker img {
    display: block;
    max-width: var(--capture-marker-size);
    max-height: var(--capture-marker-size);
    width: auto;
    height: auto;
    object-fit: contain;
    pointer-events: none;
    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.65));
}

/* Miroir horizontal quand le compagnon marche vers la gauche. La classe
   .flipped est deja en prod (systems/game_styles.css:1073) ; on la redit ici
   avec une specificite superieure pour qu'elle survive au contexte Leaflet. */
.capture-companion-marker .capture-companion-sprite.flipped {
    transform: scaleX(-1);
}

/* Le divIcon réutilise .monster-icon-container (respiration déjà en prod,
   game_styles.css:1011) : on ne redéfinit surtout pas l'animation ici. Le
   padding-top de la classe d'origine réserve la place d'une barre de PV que le
   compagnon n'a pas ; construireIcone() le neutralise en inline, ne pas le
   redéclarer ici (le inline gagnerait de toute facon). */
.capture-companion-marker .monster-icon-container {
    display: block;
}

/**
 * Ombre portee au sol, posee par construireIcone(). Teintee de vert pour
 * distinguer d'un coup d'oeil le compagnon des monstres hostiles, qui n'ont
 * pas d'ombre coloree.
 */
.capture-companion-marker .capture-companion-aura {
    position: absolute;
    left: 50%;
    bottom: 0;
    width: calc(var(--capture-marker-size) * 0.72);
    height: calc(var(--capture-marker-size) * 0.24);
    transform: translateX(-50%);
    border-radius: 50%;
    background: radial-gradient(
        ellipse at center,
        rgba(34, 197, 94, 0.45) 0%,
        rgba(0, 0, 0, 0.35) 45%,
        rgba(34, 197, 94, 0) 75%
    );
    filter: blur(1px);
    pointer-events: none;
}

/**
 * Libellé "MONSTRE de JOUEUR", posé au-dessus du sprite.
 *
 * POURQUOI IL EST NÉCESSAIRE : depuis que les compagnons sont visibles par tous
 * (companion_remote.js), une carte peuplée affiche plusieurs monstres qui ne
 * sont ni des ennemis ni les vôtres. Sans nom, impossible de savoir à qui ils
 * appartiennent, ni de reconnaître le sien.
 *
 * POSITIONNEMENT : `bottom: 100%` le place au-dessus du sprite sans jamais
 * entrer dans le flux, donc sans décaler le monstre d'un pixel ; `left: 50%`
 * plus la translation le centre quelle que soit la longueur du texte.
 * `white-space: nowrap` interdit le retour à la ligne : un nom coupé en deux
 * ferait grandir le marqueur vers le haut et sauter tous les autres.
 *
 * LISIBILITÉ : fond noir semi-opaque plus contour vert. Les fonds de carte vont
 * du blanc (jour) au gris très sombre (nuit) : un texte sans caisson disparaît
 * sur l'un ou sur l'autre. Le `text-shadow` achève de détacher les lettres sur
 * les tuiles chargées en détails.
 *
 * `pointer-events: none` : le marqueur entier est déjà non interactif, mais le
 * libellé déborde de la boîte de l'icône et intercepterait sinon les clics
 * destinés à la carte (déplacement, marqueurs situés dessous).
 *
 * Le libellé ne suit PAS le miroir du sprite : .flipped est posé sur l'image
 * seule, jamais sur le conteneur, sinon le texte s'afficherait à l'envers dès
 * que le compagnon marche vers la gauche.
 */
.capture-companion-marker .capture-companion-label {
    position: absolute;
    left: 50%;
    bottom: 100%;
    margin-bottom: 2px;
    transform: translateX(-50%);
    max-width: 15rem;
    padding: 1px 5px;
    border-radius: 4px;
    background: rgba(0, 0, 0, 0.7);
    border: 1px solid rgba(34, 197, 94, 0.55);
    color: #bbf7d0;
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
    font-size: clamp(8px, 2.2vw, 11px);
    font-weight: 600;
    line-height: 1.35;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.95);
    pointer-events: none;
}

/* ------------------------------------------------------------------------- */
/* 5 bis. GAINS DU COMPAGNON EN FIN DE COMBAT (capture_reward_panel.js)       */
/* ------------------------------------------------------------------------- */

/**
 * Section ajoutée en tête de #reward-items-container, dans la modale de butin.
 *
 * Le fond de cette modale est un dégradé sombre bordé de vert : la section
 * reprend ce vocabulaire (cadre vert translucide, monospace, vert clair) pour se
 * lire comme une partie du même écran, et non comme un encart rapporté. Elle
 * reste néanmoins légèrement plus sombre que le reste : c'est un gain annexe,
 * il ne doit pas voler la vedette au butin.
 */
.capture-reward-section {
    margin-bottom: 1.5rem;
    padding: 1rem 1.15rem;
    border: 1px solid rgba(34, 197, 94, 0.35);
    border-radius: 0.6rem;
    background: rgba(0, 0, 0, 0.35);
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}

.capture-reward-titre {
    margin-bottom: 0.7rem;
    font-size: 0.72rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: #4ade80;
    opacity: 0.9;
}

/* Sprite à gauche, détail à droite. `align-items: center` et non `flex-start` :
   la liste de gains fait une à six lignes, un alignement en haut laisserait le
   sprite flotter seul au-dessus d'un grand vide sur les gains courts. */
.capture-reward-corps {
    display: flex;
    align-items: center;
    gap: 1rem;
}

/* max-width en dur : le preflight Tailwind pose img { max-width: 100% }, qui
   étirerait le sprite sur toute la largeur du conteneur flex. */
.capture-reward-sprite {
    flex-shrink: 0;
    width: clamp(48px, 9vw, 76px);
    max-width: none;
    height: auto;
    object-fit: contain;
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.7));
}

.capture-reward-detail {
    flex: 1;
    min-width: 0;
}

/* Plusieurs compagnons : un separateur entre les blocs, jamais avant le premier. */
.capture-reward-corps + .capture-reward-corps {
    margin-top: 0.9rem;
    padding-top: 0.9rem;
    border-top: 1px solid rgba(34, 197, 94, 0.22);
}

/* Le compagnon qui a porte le coup fatal. Ambre et non vert : c'est la seule
   couleur du panneau qui ne soit pas celle du reste, et il faut que l'oeil y aille
   en premier. Sans le bandeau, le joueur verrait un compagnon gagner plusieurs fois
   plus que son allie sans savoir pourquoi, et l'attribuerait au hasard. */
.capture-reward-corps.est-acheveur {
    margin-left: -0.5rem;
    padding-left: 0.5rem;
    border-left: 3px solid rgba(251, 191, 36, 0.75);
}

.capture-reward-acheveur {
    margin-bottom: 0.4rem;
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #fbbf24;
}

/* Part de tours, affichee seulement quand plusieurs compagnons se partagent le
   combat : c'est elle qui explique pourquoi l'un gagne plus que l'autre. */
.capture-reward-part {
    margin-bottom: 0.4rem;
    font-size: 0.72rem;
    color: #86efac;
    opacity: 0.75;
}

.capture-reward-nom {
    margin-bottom: 0.45rem;
    font-weight: 700;
    font-size: 0.95rem;
    color: #86efac;
}

/* Grille implicite à deux colonnes : le libellé pousse, le gain reste collé à
   droite. Les gains s'alignent donc verticalement, ce qui les rend comparables
   d'un coup d'œil, ce qu'une simple liste ne permet pas. */
.capture-reward-ligne {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 0.75rem;
    padding: 0.15rem 0;
    font-size: 0.85rem;
    line-height: 1.35;
}

.capture-reward-stat {
    color: #d1fae5;
    opacity: 0.85;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.capture-reward-gain {
    flex-shrink: 0;
    font-weight: 700;
    color: #4ade80;
}

/* Apport sans palier franchi : meme colonne que le gain, attenue. C'est de la
   progression en barre, pas encore une stat gagnee. */
.capture-reward-apport {
    flex-shrink: 0;
    font-size: 0.78rem;
    color: #86efac;
    opacity: 0.7;
}

/* Barre vers le prochain point de la stat, sous sa ligne. */
.capture-reward-barre {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.1rem 0 0.3rem;
}

.capture-reward-barre-fond {
    flex: 1;
    height: 4px;
    border-radius: 2px;
    background: rgba(34, 197, 94, 0.15);
    overflow: hidden;
}

.capture-reward-barre-plein {
    height: 100%;
    border-radius: 2px;
    background: #4ade80;
}

.capture-reward-barre-texte {
    flex-shrink: 0;
    font-size: 0.68rem;
    color: #86efac;
    opacity: 0.65;
}

.capture-reward-points {
    margin-top: 0.5rem;
    padding-top: 0.5rem;
    border-top: 1px solid rgba(34, 197, 94, 0.22);
    font-size: 0.78rem;
    color: #86efac;
    opacity: 0.85;
}

@media (max-width: 520px) {
    .capture-reward-section { padding: 0.8rem 0.85rem; }
    .capture-reward-corps { gap: 0.7rem; }
}

/* ------------------------------------------------------------------------- */
/* 6. MODALE STATISTIQUES DU MONSTRE CAPTURE (capture_stats_modal.js)         */
/* ------------------------------------------------------------------------- */

/**
 * Rien ici, volontairement.
 *
 * La fiche du monstre capture suit le design system "terminal neon" impose par
 * Documentations/UI_UX/MODAL_DESIGN_SYSTEM_GUIDELINES.md, et son markup est
 * calque sur modules/mirador/info_modal.js : boite .structure-modal-box, kicker
 * // , hero encadre, lignes .structure-row produites par window.StructureInfoRows,
 * boutons .structure-btn.
 *
 * Comme la modale mirador, le module capture_stats_modal.js porte son propre skin
 * et l'injecte au premier ouvrir() (modele "no see = no load"). C'est deliberé :
 * la fiche s'ouvre depuis l'ecran de COMBAT, ou une feuille de style manquante se
 * verrait immediatement. Elle doit donc rester correcte meme si ce fichier n'est
 * pas charge.
 *
 * Ne pas reintroduire de regles .csm-* ici : elles seraient soit mortes, soit en
 * conflit de cascade avec le <style> injecte par le module.
 */

/* ------------------------------------------------------------------------- */
/* 7. COMPAGNON DANS LE HUD (companion_hud.js)                                */
/* ------------------------------------------------------------------------- */

/**
 * Vignette posee a GAUCHE de l'avatar du joueur, dans la premiere ligne du HUD.
 *
 * Meme boite que #character-image (2,5 rem) : la ligne du HUD est un flex
 * `items-center`, deux hauteurs differentes y decaleraient le nom du joueur et le
 * bouton calendrier d'un demi pixel a chaque apparition du compagnon.
 *
 * `flex: 0 0 auto` est indispensable : sans lui, un nom de joueur long ecrase la
 * vignette au lieu d'etre tronque, l'avatar voisin etant protege, lui, par ses
 * width et height en ligne.
 *
 * Le module la masque par l'ATTRIBUT hidden, jamais par display : la regle
 * Tailwind `space-x-2` s'appuie sur `:not([hidden])`, et un masquage par display
 * ferait heriter a l'avatar une marge gauche qu'il n'a pas aujourd'hui.
 */
#hud-companion-image {
    width: 2.5rem;
    height: 2.5rem;
    flex: 0 0 auto;
    object-fit: contain;
    cursor: pointer;

    /* Le compagnon se colle a l'avatar du joueur : les deux se lisent comme UNE
       vignette a deux personnages, pas comme deux icones de HUD independantes.

       La marge negative est le seul levier disponible ici. L'ecart vient de
       `space-x-2`, pose sur la LIGNE du HUD dans index.php, qui donne
       `margin-left: 0.5rem` a l'avatar en tant que second enfant non masque.
       Le retirer decollerait aussi le nom du joueur et le bouton calendrier ; le
       surcharger depuis cette feuille voudrait dire ecrire une regle sur
       #character-image, qui n'appartient pas a la mecanique de capture. On
       compense donc depuis notre propre element, ce qui laisse 2 px d'air. */
    margin-right: -0.375rem;

    transition: transform 150ms ease;
    -webkit-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
}

/* Aucun cadre ni fond : le sprite se pose a nu sur le bandeau, comme l'avatar du
   joueur juste a cote. Le seul retour visuel est l'agrandissement, qui sert aussi
   d'etat de focus a la navigation manette et clavier. */
#hud-companion-image:hover,
#hud-companion-image:focus-visible {
    transform: scale(1.08);
}

#hud-companion-image:active {
    transform: scale(0.96);
}

/* ------------------------------------------------------------------------- */
/* 8. COMPAGNON SUR LA FICHE DE SELECTION (companion_char_select.js)          */
/* ------------------------------------------------------------------------- */

/**
 * Le compagnon actif du personnage, en retrait DERRIERE son avatar et decale vers
 * la droite, sur chaque fiche du carrousel de selection.
 *
 * Le conteneur n'existe QUE s'il y a un compagnon : sans monstre, la fiche garde le
 * balisage qu'elle avait avant, et aucun personnage sans capsule ne peut se decaler.
 *
 * `inline-flex` et non `block` : le conteneur epouse la taille de l'avatar, qui reste
 * le seul element en flux. C'est lui qui donne au bloc sa hauteur et sa largeur, donc
 * les pourcentages ci-dessous se lisent tous en fraction de l'avatar, quelle que soit
 * la taille rendue (w-20 en mobile, sm:w-24 au-dela).
 */
.capture-charselect-duo {
    position: relative;
    display: inline-flex;

    /* LA MARGE BASSE PASSE SUR LE CONTENEUR, et c'est le calcul d'echelle qui
       l'exige. L'avatar porte `mb-2` : en flex, sa marge compte dans la hauteur du
       conteneur, qui mesurait donc 8 px de plus que lui. Or le compagnon est
       dimensionne en POURCENTAGE de ce conteneur : sa taille aurait ete faussee de
       8 % sur un avatar de 96 px. Le conteneur reprend la marge, l'avatar la perd,
       l'espacement de la fiche ne bouge pas. */
    margin-bottom: 0.5rem;
}

.capture-charselect-duo .capture-charselect-avatar {
    margin-bottom: 0;
}

/* L'avatar passe devant. `position: relative` est indispensable : sans elle, le
   z-index ne s'applique pas et le compagnon, plus loin dans le DOM, passerait
   au-dessus. La marge basse reste sur l'avatar, elle sert d'espacement a la fiche. */
.capture-charselect-avatar {
    position: relative;
    z-index: 1;
}

/**
 * Le compagnon. `max-width: none` est obligatoire : le preflight Tailwind impose
 * `img { max-width: 100% }`, qui ici se lirait par rapport au conteneur et ecraserait
 * la largeur voulue.
 *
 * Il deborde volontairement a droite de l'avatar (`right` negatif) : c'est ce
 * debordement qui le fait lire comme un second personnage a cote, et non comme une
 * vignette collee. La fiche est bien plus large que l'avatar et n'a pas
 * d'overflow:hidden, rien n'est donc coupe.
 *
 * Legerement estompe et desature : il doit rester un arriere-plan et ne jamais
 * disputer la lecture a l'avatar, qui est le sujet de la fiche.
 */
.capture-charselect-compagnon {
    position: absolute;
    /* ANCRE PAR LES PIEDS, comme en combat. Le sprite est centre sur un point fixe
       (`left` + `translate: -50%`) et grandit VERS LE HAUT : sa taille change avec
       l'espece sans que son point d'appui bouge. Une taille posee par `right` aurait
       fait deriver les petits monstres vers la droite, loin de l'avatar.

       L'ancre est calee un peu au-dela du bord droit de l'avatar : le compagnon
       deborde donc toujours, quelle que soit sa taille, ce qui le fait lire comme un
       second personnage et non comme une tache derriere l'avatar. */
    /* Ancre par le BORD GAUCHE du sprite, et non par son centre : centre sur 86 %,
       un compagnon de 30 cm (21 px de large) se retrouvait ENTIEREMENT derriere
       l avatar, invisible. Le bord gauche fixe garantit un chevauchement constant et
       un debordement qui grandit avec l espece, ce qui est justement ce qu on veut
       montrer. Mesure : 3 px dehors pour le rat, 28 px pour le colosse. */
    left: 82%;
    bottom: 6%;

    /* HAUTEUR PAR DEFAUT, remplacee en ligne par companion_char_select quand
       l'espece a une taille au catalogue. Meme semantique qu'en combat : taille
       inconnue, on ne touche a rien. */
    height: 45%;
    width: auto;
    max-width: none;
    object-fit: contain;
    z-index: 0;
    opacity: 0.85;
    filter: saturate(0.9) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8));
    pointer-events: none;
    -webkit-user-select: none;
    user-select: none;
}

/* ------------------------------------------------------------------------- */
/* 9. RESPONSIVE                                                              */
/* ------------------------------------------------------------------------- */

/* --- Mobile portrait étroit : le compagnon ne doit jamais manger l'écran --- */
@media (max-width: 480px) {
    :root {
        --combat-companion-size: clamp(52px, 10.5vh, 96px);
        --capture-marker-size: clamp(30px, 9vw, 44px);
        --capture-slot-sprite: 82%;
        --capture-modal-max-w: 100%;
    }

    #combat-companion-image {
        /* 55vw : même raison qu'au-dessus, transposée à l'écran étroit. À 30vw un
           compagnon d'1,9 m tombait à la taille d'un rat. C'est le seul écran où
           cette borne mord vraiment, et `object-position: bottom` fait que le
           sprite rapetissé garde les pieds au sol. */
        max-width: 55vw;
        margin-left: 0.25rem;
        border-radius: 8px;
    }

    /* La fiche du monstre porte son propre responsive dans le <style> injecte
       par capture_stats_modal.js (media query 420 px). Rien a faire ici. */
}

/* --- Paysage très bas de plafond (téléphone couché) : on rabote la hauteur --- */
@media (max-height: 460px) and (orientation: landscape) {
    :root {
        --combat-companion-size: clamp(48px, 22vh, 110px);
    }

    /* La boite .structure-modal-box est deja plafonnee a calc(100dvh - 40px)
       par le skin du module : elle tient donc en paysage sans regle de plus. */
}

/* --- Ultrawide : on suit la convention de ultrawide_layout.css. La modale est
       déjà bornée par --capture-modal-max-w, on ne fait qu'assouplir la borne
       et empêcher le compagnon de dépasser la bande centrale de 72rem. --- */
@media (min-width: 1600px) {
    :root {
        --combat-companion-size: clamp(120px, 15vh, 230px);
        --capture-modal-max-w: 38rem;
    }

    #combat-companion-image {
        /* La bande de combat est plafonnée à --ui-max-w-6xl (72rem) par
           ultrawide_layout.css ; le repli 72rem couvre le cas où la variable
           n'est pas encore définie au moment du calcul. */
        max-width: calc(var(--ui-max-w-6xl, 72rem) * 0.3);
    }
}

/* --- Accessibilité : respect de prefers-reduced-motion. Les animations
       purement décoratives sont coupées, mais l'aspiration et le flash gardent
       une transition courte pour que l'état final reste correct (le sprite
       adverse DOIT redevenir visible après un échec). --- */
@media (prefers-reduced-motion: reduce) {
    .capture-companion-idle,
    #combat-companion-image.capture-companion-attaque,
    .capsule-monstre-actif,
    .capture-shake,
    .capture-capsule-shake,
    .capture-aspiration-cone {
        animation: none !important;
    }

    #combat-companion-image,
    #hud-companion-image,
    .capture-aspiration {
        transition-duration: 0.01ms !important;
    }

    .capture-flash,
    .capture-anim-label {
        animation-duration: 0.2s !important;
    }
}

/* EOF: modules/capture/capture.css - INTEGRITY_CHECK_OK */
