/* ============================================================
   POPOTE WAR — LES ÉCRANS
   ============================================================ */

/* Le mot du chien de garde. Au-dessus de tout, y compris de la porte :
   quand il parle, c'est que rien d'autre ne s'affiche. */
.secours-mot {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 200;
  margin: 0;
  padding: 1rem var(--pad) calc(1rem + var(--safe-b));
  background: var(--toile);
  border-top: 1px solid var(--laiton);
  color: var(--sable);
  font-size: 0.875rem;
  line-height: 1.4;
  text-align: center;
}
.secours-mot[hidden] { display: none; }

/* ============================================================
   LE POSTE DE GARDE
   ------------------------------------------------------------
   La première chose que voit un visiteur sans session. Il doit
   ressembler au jeu et non à un formulaire posé devant lui :
   même toile, même laiton, même fonte d'affiche.

   Il tient DANS L'ÉCRAN sans défiler jusqu'à 640 pixels de haut
   — c'est le format le plus court de la fiche Play Store, et le
   pire moment pour demander de faire défiler est celui où l'on
   ne sait pas encore ce qu'on regarde.

   Au-delà de ce que le formulaire occupe, on ne met rien : pas
   d'illustration, pas de promesse en trois colonnes. Le jeu est
   derrière la porte, pas dessus.
   ============================================================ */
.porte {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: max(var(--safe-t), 14px) var(--pad) max(var(--safe-b), 14px);
  /* La toile du jeu, et non un fond neutre : c'est le premier contact avec
     sa matière. Le dégradé sombre garde le texte lisible dessus. */
  background:
    linear-gradient(180deg, rgba(20, 21, 15, .82), rgba(20, 21, 15, .96)),
    var(--nuit) url("/img/matiere/toile-tente.webp") center / 320px repeat;
  overflow-y: auto;
}
.porte[hidden] { display: none; }
/* Le document ne défile plus tant que la porte est ouverte. Voir le
   commentaire de `ouvrirPorte` : le jeu, derrière, a sa propre hauteur. */
.porte-ouverte, .porte-ouverte body { overflow: hidden; }

.porte__voile {
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Une lueur de projecteur en haut : elle donne un haut et un bas à un
     écran qui n'a par ailleurs aucun repère. */
  background: radial-gradient(120% 55% at 50% 0%,
    rgba(200, 162, 76, .13), transparent 70%);
}

.porte__boite {
  position: relative;
  width: min(100%, 380px);
  display: grid;
  justify-items: stretch;
  gap: .5rem;
  text-align: center;
}

.porte__sur {
  font-family: var(--pochoir);
  font-size: 0.6875rem;
  letter-spacing: .3em;
  color: var(--laiton);
  opacity: .85;
  margin: 0;
}
/* Le titre en deux blocs : POPOTE plein, WAR détouré. C'est la seule
   fantaisie typographique de la page, et elle tient lieu de logo. */
.porte__nom {
  font-family: var(--display);
  font-size: clamp(2.4rem, 15vw, 3.4rem);
  line-height: .92;
  letter-spacing: .01em;
  color: var(--sable);
  margin: 0 0 .3rem;
  text-shadow: 0 3px 0 rgba(0, 0, 0, .55);
}
.porte__nom span {
  color: transparent;
  -webkit-text-stroke: 2px var(--laiton);
  margin-left: .12em;
}

/* Le nom entier du poste, sous le titre. Il n'est pas là pour être lu vite :
   il est là pour que « PAF » veuille dire quelque chose la fois d'après. */
.porte__paf {
  font-family: var(--pochoir);
  font-size: 0.625rem;
  letter-spacing: .16em;
  line-height: 1.3;
  color: var(--sable-b);
  margin: 0 0 .4rem;
}

.porte__corps { display: grid; gap: .5rem; }
/* La consigne, en clair. C'est la seule phrase que le visiteur DOIT lire :
   elle porte donc la couleur du texte principal, pas celle des notes. */
.porte__dit {
  font-size: 0.9375rem;
  line-height: 1.35;
  color: var(--sable);
  margin: 0;
}
.porte__note--haut { margin-bottom: .35rem; }
.porte__titre {
  font-family: var(--display);
  font-size: 1.1rem;
  letter-spacing: .06em;
  color: var(--laiton);
  margin: 0 0 .1rem;
}
/* Les deux chemins ont la même largeur et la même hauteur. Ce qui les
   distingue est la COULEUR — orange d'action pour « créer », or pour « se
   connecter » — et c'est voulu : la plupart de ceux qui arrivent ici n'ont
   pas de compte. Un joueur qui revient trouve le sien au même endroit, à la
   même taille, immédiatement sous le premier. */
.porte__btn { width: 100%; }
.porte__sans { margin-top: .35rem; }
.porte__note {
  font-size: 0.6875rem;
  line-height: 1.3;
  color: var(--sable-b);
  opacity: .75;
  margin: 0;
}
/* LES CHAMPS. `.field` n'était habillé que sous `.sheet` : hors d'une
   feuille, ces trois entrées seraient sorties nues, en blanc système sur
   fond kaki. On reprend le même habillage plutôt que d'en inventer un
   second — c'est le même objet, il doit se reconnaître.

   Alignés à gauche : un texte saisi centré se lit mal, et ce n'est pas
   ainsi qu'un champ se comporte partout ailleurs dans le jeu. */
.porte__corps .field {
  width: 100%;
  min-height: var(--tap);
  padding: 0 .9rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--kaki);
  background: var(--nuit);
  color: var(--sable);
  font: inherit;
  text-align: left;
  box-shadow: var(--relief-in);
}
.porte__corps .field:focus { border-color: var(--laiton); outline: none; }
.porte__corps .err, .porte__corps .ok {
  font-size: 0.75rem; line-height: 1.3; margin: 0;
  min-height: 1.2em;
}
.porte__corps .err { color: var(--alerte-c); }
.porte__corps .ok { color: var(--laiton); }

/* ============================================================
   ACCUEIL
   ------------------------------------------------------------
   Cinq bandes, de haut en bas : la plaque d'identité, l'écusson,
   les trois horloges, le coupon s'il existe, et le tiers bas
   tactile. Aucune n'est vide : c'est le défaut qu'on corrige.

   La grille est en `auto auto 1fr auto auto` — l'écusson prend
   ce qui reste, donc l'écran est plein sur un petit modèle comme
   sur un grand, sans jamais faire défiler.
   ============================================================ */
#screen-home {
  display: grid;
  grid-template-rows: auto 1fr auto auto auto;
  gap: var(--gap);
  padding: var(--safe-t) var(--pad) calc(var(--tabbar) + var(--safe-b));

  /* IL DÉFILE QUAND IL DÉBORDE, à toute taille.

     La règle d'origine était `overflow: hidden`, avec l'intention « l'écran
     est plein sur un petit modèle comme sur un grand, sans jamais faire
     défiler ». L'intention était juste ; la mise en œuvre coupait.

     Deux pannes l'ont montré, et aucune n'était un cas limite. À 360×640,
     AFFRONTER UN SOLDAT et le CARNET tombaient sous la ligne et devenaient
     INATTEIGNABLES — pas seulement invisibles. Sur un écran de bureau, la
     rangée du milieu était écrasée à zéro et son contenu débordait vers le
     haut, un écusson de grade par-dessus le nom du joueur.

     Un écran qui défile de deux centimètres est un désagrément. Un écran
     qui cache un bouton, ou qui superpose deux blocs, est une panne. */
  overflow-y: auto;
  overflow-x: hidden;
}

/* ------------------------------------------------------------
   LA PLAQUE D'IDENTITÉ
   Ce que le joueur EST, en une bande : grade, matricule, galons,
   solde. Le relief vient d'un liseré clair en haut et d'une ombre
   portée en bas — comme une plaque métallique estampée.
   ------------------------------------------------------------ */
.plaque {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: .5rem var(--gap);
  margin: var(--gap) calc(var(--pad) * -1) 0;
  padding: .7rem var(--pad) .6rem;
  background: linear-gradient(180deg, var(--toile-2), var(--toile));
  border-bottom: 1px solid var(--kaki);
  box-shadow: inset 0 1px 0 rgba(217,207,168,.09), 0 6px 18px rgba(0,0,0,.45);
  /* La plaque d'identité est une PLAQUE : elle est boulonnée. Avec le
     tableau d'affichage, c'est le seul objet du jeu qui porte des rivets —
     quand tout est riveté, plus rien ne l'est. */
  background-image: var(--rivets), linear-gradient(180deg, var(--toile-2), var(--toile));
}

.plaque__id {
  display: flex;
  align-items: center;
  gap: .6rem;
  text-align: left;
  min-height: var(--tap);
}
/* Le visage dans un médaillon rond, et l'écusson en cachet sur son coin.
   Rond et non carré : c'est le seul objet du jeu qui représente une
   PERSONNE, et il doit se distinguer des plaques et des panneaux au premier
   coup d'œil. */
.plaque__face {
  position: relative;
  flex: 0 0 44px;
  width: 44px; height: 44px;
}
.plaque__portrait {
  display: block;
  width: 44px; height: 44px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--nuit);
  border: 2px solid var(--laiton);
  box-shadow: 0 2px 6px rgba(0, 0, 0, .55);
}
.plaque__portrait .visuel { width: 100%; height: 100%; object-fit: cover; filter: none; }
/* La tenue teinte le médaillon. C'est le signe le plus discret des trois, et
   celui qu'on voit en premier : une couleur se lit avant une forme. */
.plaque__face--tenue .plaque__portrait {
  border-color: var(--teinte, var(--laiton));
  box-shadow: 0 2px 6px rgba(0, 0, 0, .55), 0 0 10px -3px var(--teinte, var(--laiton));
}
/* LA COURONNE déborde du médaillon : elle est donc plus grande que lui, et
   posée dessous. Un anneau à la taille exacte du portrait n'entourerait
   rien — il le recouvrirait. */
.plaque__couronne {
  position: absolute;
  inset: -9px;
  line-height: 0;
  pointer-events: none;
}
.plaque__couronne:empty { display: none; }
.plaque__couronne .visuel {
  width: 100%; height: 100%;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .7));
}

.plaque__corps {
  position: absolute;
  left: -9px; bottom: -5px;
  display: grid; place-items: center;
  width: 24px; height: 24px;
  border-radius: 50%;
  color: var(--teinte, var(--sable-b));
  line-height: 0;
  z-index: 2;
}
.plaque__corps:empty { display: none; }
/* L'écu rendu porte sa propre matière : il n'a besoin ni du disque de fond
   ni du liseré qui servaient au pictogramme tracé. */
.plaque__corps:not(:has(.visuel)) {
  background: var(--nuit);
  border: 1px solid var(--teinte, var(--kaki));
}
.plaque__corps .visuel { width: 100%; height: 100%; filter: drop-shadow(0 1px 2px rgba(0,0,0,.8)); }

.plaque__insigne {
  position: absolute;
  right: -6px; bottom: -4px;
  width: 24px;
  line-height: 0;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .8));
}
.plaque__insigne .ecusson,
.plaque__insigne .visuel { width: 100%; height: auto; display: block; }
.plaque__txt { display: grid; }
.plaque__grade {
  font-size: var(--fs-xs);
  letter-spacing: .2em;
  color: var(--laiton);
}
.plaque__nom {
  font-family: var(--display);
  font-size: var(--fs-lg);
  letter-spacing: .02em;
  color: var(--sable);
  line-height: 1.05;
}

.plaque__droite { display: flex; align-items: center; gap: .35rem; }
.plaque__reglages {
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  color: var(--sable-b);
}
.plaque__reglages:active { color: var(--sable); }

/* Le solde. La pastille est dessinée en CSS : un jeton de laiton,
   avec un liseré clair en haut pour attraper la lumière. */
.solde {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  font-family: var(--chiffre);
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--laiton-c);
}
/* LA PIÈCE.
   C'était un dégradé radial : un disque doré de dix-sept pixels, celui de
   n'importe quel jeu de navigateur. Or tout ce qui s'achète dans ce jeu se
   compte en pièces — c'est le seul objet que le joueur voit sur CHAQUE
   écran, et il n'avait pas de dessin.

   Le fichier fait deux kilo-octets à cette taille. Le dégradé reste
   dessous : hors connexion, au tout premier lancement, il vaut mieux un
   disque doré qu'un carré vide. */
.solde::before {
  content: "";
  width: 18px; height: 18px;
  border-radius: 50%;
  background:
    url("/img/monnaie/piece-mini.webp") center / contain no-repeat,
    radial-gradient(circle at 34% 28%, var(--laiton-c), var(--laiton) 62%, #7d6323 100%);
  box-shadow: 0 1px 3px rgba(0,0,0,.5);
  /* Un cadeau qui tombe se remarque. La classe est posée par le code qui
     crédite, et retirée à la fin de l'animation. */
  transform-origin: 50% 50%;
}
.solde--gagne::before { animation: piece-tourne .7s ease-out; }
@keyframes piece-tourne {
  0%   { transform: rotateY(0) scale(1); }
  45%  { transform: rotateY(180deg) scale(1.35); }
  100% { transform: rotateY(360deg) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .solde--gagne::before { animation: none; }
}

/* Les galons : la barre de progression vers le grade suivant. */
.galons { grid-column: 1 / -1; }
.galons__piste {
  height: 7px;
  border-radius: 4px;
  background: #0E0F0A;
  border: 1px solid rgba(0,0,0,.5);
  overflow: hidden;
}
.galons__fill {
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--kaki), var(--laiton) 78%, var(--laiton-c));
  box-shadow: 0 0 10px rgba(200,162,76,.35);
  transition: width var(--mid) var(--ease);
}
.galons__txt {
  margin-top: .3rem;
  font-size: var(--fs-xs);
  color: var(--sable-b);
}

/* ============================================================
   LA VITRINE — la signature de l'écran
   ------------------------------------------------------------
   L'écusson du grade actuel, et derrière lui, en pointillés, le
   grade suivant. On voit ce qu'on est ET ce qu'on vise au même
   endroit : c'est la promesse qui fait relancer un duel.
   ============================================================ */
.vitrine {
  position: relative;
  display: grid;
  place-items: center;
  align-content: center;
  gap: .15rem;

  /* ⚠ `min-height: 0` A ÉTÉ RETIRÉ D'ICI, et c'est une correction, pas un
     oubli.

     Il autorisait cette rangée — le `1fr` de l'écran — à être écrasée
     jusqu'à zéro quand le bas de l'accueil demandait plus de place qu'il
     n'y en avait. La rangée disparaissait, mais pas son CONTENU : l'écusson,
     le camp et le combattant débordaient alors vers le haut, par-dessus la
     plaque d'identité. Sur bureau, un écusson de grade recouvrait le nom du
     joueur.

     Sans lui, la rangée garde la hauteur de ce qu'elle porte, et c'est
     l'écran qui défile. Un contenu qu'on atteint en glissant vaut toujours
     mieux qu'un contenu qui se superpose. */

  /* LES TAILLES DE CETTE VITRINE SE MESURENT SUR ELLE, PAS SUR LA FENÊTRE.

     Tout était en `vw`. Sur un téléphone, la fenêtre EST la colonne du jeu,
     et l'illusion tenait. Sur un ordinateur, le jeu vit dans une colonne de
     cinq cent cinquante pixels au milieu d'un écran de mille neuf cents :
     `42vw` y vaut huit cents pixels, si bien que chaque taille partait
     buter contre le plafond de son `min()`. Les proportions n'avaient plus
     aucun rapport avec la place réellement disponible.

     `cqw` mesure la largeur de CETTE boîte. Les mêmes proportions valent
     alors sur les deux, sans une seule règle d'écran. */
  container-type: inline-size;
}

/* La lueur derrière l'écusson : c'est elle qui détache l'objet du
   fond et lui donne l'air d'être posé sous une lampe. */
.vitrine__halo {
  position: absolute;
  width: min(74cqw, 300px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(200,162,76,.16), transparent 66%);
  pointer-events: none;
}

/* ------------------------------------------------------------
   LE CAMP
   ------------------------------------------------------------
   Le héros de l'écran. L'écusson ne disparaît pas pour autant —
   il descend en médaillon sur le coin du camp, comme un cachet
   posé sur une photographie. Les deux disent la même chose à
   deux vitesses : le camp se voit de loin, l'écusson se lit de
   près.
   ------------------------------------------------------------ */
/* ------------------------------------------------------------
   LES TROIS COLONNES DE LA VITRINE
   ------------------------------------------------------------
   Le galon, le camp, le combattant. Le camp prend la place
   restante : c'est lui qu'on voit de loin, et les deux autres
   sont calés sur des largeurs fixes pour qu'il ne se fasse pas
   grignoter par un personnage un peu large.

   Tout est calé EN BAS. Les trois objets n'ont pas la même
   hauteur — un écusson est carré, un camp est large, un
   combattant est haut — et centrés verticalement, ils
   flotteraient à trois étages. Alignés par le bas, ils se
   tiennent sur le même sol.
   ------------------------------------------------------------ */
.vitrine__rang {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: end;
  justify-items: center;
  gap: .3rem;
  width: 100%;
  min-height: 0;
}

/* LE COMBATTANT, à droite. Sa hauteur suit celle du camp : deux hauteurs
   indépendantes feraient bouger la rangée à chaque changement de grade. */
.vitrine__perso {
  display: block;
  line-height: 0;
  padding: 0;
  border: 0;
  width: clamp(52px, 21cqw, 104px);
  /* La lueur sous les pieds tient lieu de sol, comme dans l'arène. */
  background: radial-gradient(ellipse 44% 6% at 50% 99%, rgba(0, 0, 0, .55), transparent 70%);
}
.vitrine__perso:empty { display: none; }
.vitrine__perso .visuel {
  width: 100%; height: auto;
  max-height: min(30vh, 260px);
  object-fit: contain;
  object-position: bottom;
  filter: drop-shadow(0 8px 12px rgba(0, 0, 0, .55));
}
/* Il est touchable : on le dit par un léger recul sous le doigt, et rien
   d'autre. Un cadre autour d'un personnage détouré le remettrait dans une
   boîte dont on vient de le sortir. */
.vitrine__perso:active .visuel { transform: translateY(1px) scale(.97); }

.vitrine__camp {
  position: relative;
  width: 100%;
  line-height: 0;
  /* LE PLAFOND EST DICTÉ PAR LE BOUTON, PAS PAR L'IMAGE.

     À 38 vh, le camp était superbe et poussait COMBATTRE sous la ligne de
     flottaison : il fallait faire défiler l'accueil pour trouver la seule
     action que l'écran demande. Un beau décor qui cache le bouton coûte plus
     qu'il ne rapporte.

     30 vh plafonnés à 260 px laissent le bouton à l'écran sur tous les
     formats visés — vérifié en production, pas estimé. */
  max-height: min(30vh, 260px);
}
.vitrine__camp .visuel {
  width: 100%;
  height: auto;
  max-height: min(30vh, 260px);
  object-fit: contain;
  /* Le diorama flotte : l'ombre le pose sur l'écran au lieu de le laisser
     en apesanteur. Elle est portée par la silhouette, pas par la boîte. */
  filter: drop-shadow(0 14px 22px rgba(0, 0, 0, .6));
}

/* Le fantôme du grade suivant. Il ne s'affiche que TANT QU'IL N'Y A PAS DE
   CAMP — c'est-à-dire à peu près jamais, aujourd'hui que les quatorze camps
   sont livrés. Sa largeur passe en `cqw` comme les autres : laissée en
   `58vw`, elle valait onze cents pixels sur un écran de bureau. */
.vitrine__cible {
  position: absolute;
  width: min(58cqw, 232px);
  opacity: .5;
  transform: translateY(-8px) scale(1.16);
  pointer-events: none;
}
.vitrine__cible .ecusson { width: 100%; height: auto; display: block; }

/* L'écusson tient sa colonne, à gauche. Il n'est plus posé en médaillon sur
   le coin du camp : les trois objets ont chacun leur place, et un galon
   collé sur un diorama se lisait comme une vignette, pas comme un grade.

   La largeur est mesurée sur la VITRINE (`cqw`), pas sur la fenêtre : voir
   le commentaire de `.vitrine`. */
.vitrine__actuel {
  position: relative;
  width: clamp(44px, 17cqw, 88px);
  filter: drop-shadow(0 10px 20px rgba(0, 0, 0, .55));
}
.vitrine__actuel .ecusson,
.vitrine__actuel .visuel { width: 100%; height: auto; display: block; }
/* Le fantôme du grade suivant s'efface dès qu'un camp est là : trois objets
   superposés au centre ne feraient plus qu'une tache. */
.vitrine:has(.vitrine__camp .visuel) .vitrine__cible { display: none; }

.vitrine__camp-nom {
  font-size: var(--fs-xs);
  letter-spacing: .12em;
  color: var(--sable-b);
  text-align: center;
}

.vitrine__grade {
  margin-top: .5rem;
  font-family: var(--display);
  /* En `cqw` comme le reste de la vitrine : le nom du grade doit grandir
     avec la colonne du jeu, pas avec la fenêtre du navigateur. */
  font-size: clamp(1.6rem, 7.5cqw, var(--fs-2xl));
  letter-spacing: .04em;
  line-height: 1;
  color: var(--sable);
  text-shadow: 0 2px 0 rgba(0,0,0,.55);
  text-align: center;
}
.vitrine__objectif {
  font-size: var(--fs-xs);
  letter-spacing: .1em;
  color: var(--laiton);
  text-align: center;
}

/* Une promotion disponible se signale : l'écusson visé cesse d'être
   un fantôme et respire. C'est la seule animation de l'écran. */
.vitrine--pret .vitrine__cible { opacity: .9; animation: souffle 2.4s var(--ease) infinite; }
@keyframes souffle {
  0%, 100% { transform: translateY(-8px) scale(1.16); }
  50%      { transform: translateY(-8px) scale(1.21); }
}
@media (prefers-reduced-motion: reduce) {
  .vitrine--pret .vitrine__cible { animation: none; opacity: .9; }
}

/* ============================================================
   LES ÉCRANS COURTS
   ------------------------------------------------------------
   Toutes les vérifications précédentes avaient été faites à
   412 × 900 — un téléphone récent, et celui qui m'arrangeait.
   La capture de boutique, prise au format de référence du Play
   Store (360 × 640), a montré autre chose : COMBATTRE tombait
   120 pixels SOUS la ligne de flottaison. Sur un iPhone SE, 102.

   C'est-à-dire que sur les téléphones les plus modestes — ceux
   du public que ce jeu vise — il fallait faire défiler l'accueil
   pour trouver la seule action qu'il demande.

   Ce qui cède, c'est la VITRINE : elle est le bloc décoratif.
   Le camp rapetisse, le nom du camp disparaît, le grade se
   resserre. On ne retire aucune information — le nom du camp est
   dans la collection — on retire de la place.
   ============================================================ */
@media (max-height: 730px) {
  /* La plaque d'identité perd un tiers de sa respiration verticale. Elle
     porte quatre informations sur deux lignes : c'est de l'air qu'on lui
     retire, pas du contenu. */
  .plaque { padding-top: .45rem; padding-bottom: .4rem; }
  .corps-barre { padding-bottom: .15rem; }

  /* Sur un écran court, la vitrine se resserre pour que COMBATTRE reste
     au-dessus de la ligne. Le défilement, lui, est désormais permanent —
     voir `#screen-home`. */
  .vitrine { gap: 0; }
  .vitrine__camp .visuel { max-height: 18vh; }
  .vitrine__perso .visuel { max-height: 18vh; }
  .vitrine__grade { margin-top: .2rem; font-size: clamp(1.2rem, 6cqw, 1.6rem); }
  .vitrine__camp-nom { display: none; }
  .vitrine__actuel { width: clamp(40px, 14cqw, 64px); }

  .horloge { padding: .35rem .3rem .4rem; }
  .horloge__val { font-size: var(--fs-lg); }
  .corps { min-width: 56px; padding: .3rem .25rem .28rem; }
  .corps-etat { margin: -.2rem 0 0; }
  .combattre { min-height: 58px; padding: .4rem 1rem; }
  .ordre { gap: .4rem; }
}

/* ============================================================
   LES TROIS HORLOGES
   ------------------------------------------------------------
   Série, guerre, saison. Elles ne se cachent JAMAIS : sans
   donnée, elles invitent à agir. Un écran d'accueil qui perd un
   bloc laisse un trou, et un trou se lit comme une panne.
   ============================================================ */
.horloges {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--gap);
}
.horloge {
  display: grid;
  gap: 1px;
  padding: .55rem .4rem .6rem;
  border-radius: var(--r);
  background: var(--toile);
  border: 1px solid var(--kaki);
  border-top-width: 3px;
  text-align: center;
  min-height: var(--tap);
}
.horloge:active { background: var(--toile-2); }
.horloge__lbl {
  font-size: 0.6875rem;
  letter-spacing: .16em;
  color: var(--sable-b);
}
.horloge__val {
  font-family: var(--display);
  font-size: var(--fs-lg);
  line-height: 1.1;
  color: var(--sable);
}
.horloge__sub {
  font-size: 0.6875rem;
  color: var(--sable-b);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Le liseré du haut porte l'information : chaque horloge a sa couleur,
   et une horloge « en attente » reste grise. */
.horloge--serie  { border-top-color: var(--alerte); }
.horloge--guerre { border-top-color: var(--vert-ok); }
.horloge--saison { border-top-color: var(--laiton); }
.horloge--vide   { border-top-color: var(--kaki); }
.horloge--vide .horloge__val { color: var(--sable-b); }

/* ============================================================
   LE COUPON
   ------------------------------------------------------------
   Un ticket : bord cranté à gauche, sceau de la marque, échéance
   toujours lisible. Il n'apparaît que s'il existe — c'est le seul
   bloc de l'écran qui a le droit de disparaître, parce que sa
   présence est justement l'événement.
   ============================================================ */
.coupon {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: .75rem;
  padding: .6rem .8rem;
  border-radius: var(--r);
  background:
    radial-gradient(circle at 0 50%, transparent 7px, var(--toile-2) 7px) left / 51% 100% no-repeat,
    radial-gradient(circle at 100% 50%, transparent 7px, var(--toile-2) 7px) right / 51% 100% no-repeat;
  border: 1px dashed var(--laiton);
  color: var(--sable);
  text-decoration: none;
}
.coupon__sceau {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: 0.625rem;
  letter-spacing: .22em;
  color: var(--laiton);
  opacity: .8;
}
.coupon__corps { display: grid; }
.coupon__val {
  font-family: var(--display);
  font-size: var(--fs-xl);
  line-height: 1;
  color: var(--laiton-c);
}
.coupon__exp { font-size: var(--fs-xs); color: var(--sable-b); }
.coupon__cta {
  padding: .5rem .7rem;
  border-radius: var(--r-sm);
  background: var(--laiton);
  color: var(--nuit);
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: .06em;
}

/* ============================================================
   LE TIERS BAS
   ============================================================ */
.ordre { display: grid; gap: .4rem; }

/* Le bouton. Il occupe toute la largeur, il est le seul en rouge de
   fusée, et rien d'autre sur l'écran ne lui ressemble. */
.combattre {
  display: grid;
  gap: 1px;
  width: 100%;
  min-height: 68px;
  padding: .55rem 1rem;
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, var(--alerte-c), var(--alerte) 58%, #8E3009);
  color: #fff;
  /* La lèvre de quatre pixels : c'est l'épaisseur du bouton, celle qui
     disparaît quand on l'enfonce. Un bouton qui ne fait que changer de
     couleur à l'appui n'a jamais été un objet. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, .35),
    inset 0 -3px 0 rgba(0, 0, 0, .28),
    0 4px 0 #6E2507,
    0 10px 22px rgba(193, 68, 14, .35);
  transition: transform var(--fast) var(--ease), box-shadow var(--fast);
  /* Le seul élément animé au repos de tout l'écran : c'est ce qui dit où
     appuyer, sans une flèche ni un mot. Très lent et très faible — une
     respiration, pas un clignotant. */
  animation: respire-bouton 2.6s ease-in-out infinite;
}
@keyframes respire-bouton {
  50% { transform: scale(1.018); }
}
@media (prefers-reduced-motion: reduce) { .combattre { animation: none; } }
.combattre__txt {
  font-family: var(--display);
  font-size: clamp(1.5rem, 7vw, 2rem);
  letter-spacing: .06em;
  line-height: 1;
  text-shadow: 0 2px 0 rgba(0,0,0,.3);
}
.combattre__sub {
  font-size: var(--fs-xs);
  letter-spacing: .08em;
  opacity: .82;
}

/* Affronter un humain : présent, mais clairement second. Le duel contre
   le serveur part tout de suite ; celui-ci demande d'attendre quelqu'un,
   et un lobby vide au lancement décourage plus qu'il n'attire. */
.duel-humain {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  min-height: var(--tap);
  padding: 0 .9rem;
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: var(--toile);
  color: var(--sable);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .1em;
}
.duel-humain:active { background: var(--toile-2); }
/* Les sabres croisés : l'emblème de l'onglet Combat, celui-là même et pas
   un tracé approchant. Il est déjà dans la coquille du service worker,
   donc il ne coûte pas une requête de plus. */
.duel-humain__ico {
  width: 26px; height: 26px;
  flex: 0 0 auto;
  background: url("/img/onglet/onglet-combat.webp") center / contain no-repeat;
}
.duel-humain__sub {
  margin-left: auto;
  font-weight: 400;
  letter-spacing: .04em;
  color: var(--sable-b);
}

/* ------------------------------------------------------------
   LE LOBBY — en feuille, pas en écran
   ------------------------------------------------------------ */
.champ-lbl {
  font-family: var(--pochoir);
  font-size: var(--fs-xs);
  letter-spacing: .18em;
  color: var(--laiton);
  margin: .6rem 0 .35rem;
}

.mises { display: flex; gap: .4rem; flex-wrap: wrap; }
.mise {
  flex: 1 1 auto;
  min-width: 62px;
  min-height: var(--tap);
  border-radius: var(--r-sm);
  border: 1px solid var(--kaki);
  background: var(--toile);
  color: var(--sable-b);
  font-family: var(--chiffre);
  font-size: var(--fs-sm);
}
.mise--on {
  border-color: var(--laiton);
  background: var(--kaki);
  color: var(--laiton-c);
}
.mise[disabled] { opacity: .3; }

.salons { display: grid; gap: .35rem; max-height: 34dvh; overflow-y: auto; }
.salon {
  display: grid;
  /* Quatre colonnes depuis que l'emblème du corps ouvre la ligne. Avec les
     trois d'origine, le quatrième enfant serait passé à la ligne suivante
     de la grille et aurait poussé la mise sous le nom. */
  grid-template-columns: auto 1fr auto auto;
  align-items: center;
  gap: .6rem;
  min-height: var(--tap);
  padding: 0 .8rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--kaki);
  background: var(--toile);
  text-align: left;
}
.salon:active { background: var(--toile-2); }
.salon__nom { font-weight: 700; color: var(--sable); }
.salon__grade { font-size: var(--fs-xs); color: var(--sable-b); }
.salon__mise { font-family: var(--chiffre); font-size: var(--fs-xs); color: var(--laiton); }

.rejoindre { display: grid; grid-template-columns: 1fr auto; gap: .4rem; }
.rejoindre .field { margin-bottom: 0; }

/* Le code du salon : gros, parce qu'on le lit à voix haute. */
.gros-code {
  font-family: var(--display);
  font-size: var(--fs-2xl);
  letter-spacing: .12em;
  text-align: center;
  color: var(--laiton-c);
  padding: .8rem;
  margin: .4rem 0;
  border-radius: var(--r);
  border: 1px dashed var(--laiton);
  background: var(--toile);
  cursor: pointer;
}

/* L'ordre du jour : replié, il ne coûte qu'une ligne. Le compteur
   « 2/3 » suffit à donner envie de terminer. */
/* L'ordre du jour est un panneau d'affichage : il porte donc du feutre,
   fondu en `overlay` pour que la fibre apparaisse sans changer la valeur du
   panneau. C'est la seule surface du jeu où l'on ÉPINGLE quelque chose. */
.jour {
  border-radius: var(--r);
  /* Feutre, rivets, fond : le tableau d'affichage est le second objet
     boulonné du jeu — et le seul sur lequel on épingle vraiment quelque
     chose. Les rivets passent AVANT le feutre dans l'empilement, sinon la
     fusion `overlay` du feutre les mangerait. */
  background:
    var(--rivets),
    url("/img/matiere/feutre-tableau.webp") 0 0 / 190px 190px repeat,
    var(--toile);
  background-blend-mode: normal, overlay, normal;
  border: 1px solid var(--kaki);
  box-shadow: var(--relief);
  overflow: hidden;
}
.jour__tete {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 42px;
  padding: 0 .8rem;
  font-size: var(--fs-xs);
  letter-spacing: .16em;
  color: var(--sable-b);
  cursor: pointer;
  list-style: none;
}
.jour__tete::-webkit-details-marker { display: none; }
.jour__compte { font-family: var(--chiffre); color: var(--laiton); }
.jour__liste { display: grid; gap: .3rem; padding: 0 .8rem .7rem; }
.jour__liste li {
  display: flex;
  justify-content: space-between;
  gap: var(--gap);
  font-size: var(--fs-xs);
  color: var(--sable-b);
}
.jour__liste li::before {
  content: "▢";
  margin-right: .4rem;
  color: var(--kaki);
}
.jour__liste li[data-done="1"] { color: var(--vert-ok); }
.jour__liste li[data-done="1"]::before { content: "▣"; color: var(--vert-ok); }

/* LA PRIME — dernière ligne de la liste, quand les trois sont faites.
   Pas de case à cocher devant : ce n'est pas une mission de plus, c'est
   la récompense des trois. Le trait au-dessus fait la séparation. */
.jour__liste li.jour__prime {
  margin-top: .45rem;
  padding-top: .55rem;
  border-top: 1px solid var(--kaki);
  color: var(--laiton);
}
/* La case à cocher est posée par `.jour__liste li::before`, qui est PLUS
   spécifique qu'un simple `.jour__prime::before` : sans reprendre la même
   longueur de sélecteur, la case restait devant le bouton et le décalait. */
.jour__liste li.jour__prime::before { content: none; }
.jour__prendre {
  width: 100%;
  font-size: var(--fs-xs);
  padding: .5rem;
}

/* ============================================================
   L'ÉCHELLE DE PRÉSENCE
   Sept cases dans la feuille de récompense. Ce qui compte n'est
   pas la case du jour mais la SUIVANTE : c'est elle qui fait
   revenir demain. Elle reste donc visible, en creux, jamais
   masquée.
   ============================================================ */
/* ⚠️ LA BANDE SE BORNE, et les cases DISENT CE QU'ELLES DONNENT.

   Sept colonnes en `1fr` avec `aspect-ratio: 1` : la case est aussi
   HAUTE que large, donc sa hauteur suit la largeur du parent. Dans une
   feuille qui s'étalait sur dix-neuf cents pixels, cela faisait sept
   carrés de deux cent quatre-vingt-quinze pixels de côté, avec un
   chiffre de onze pixels au milieu.

   La feuille est bornée depuis (voir `bureau.css`), mais on borne AUSSI
   ici : une contrainte de mise en page qui ne tient que par la largeur
   de son parent se casse au premier parent qui change.

   Et surtout, elles ne portaient QUE LEUR NUMÉRO. Le commentaire de
   cet écran affirme depuis toujours que « voir la marche suivante est
   ce qui fait revenir demain » — sept carrés numérotés de un à sept ne
   la montrent pas. Chaque case annonce maintenant sa prime. */
.presence {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: .3rem;
  margin: .9rem 0;
  max-width: 420px;
  margin-inline: auto;
}
.presence__jour {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 2px;
  /* Une hauteur FIXE plutôt qu'un carré : le contenu tient en deux
     lignes courtes, et un carré large deviendrait haut pour rien. */
  min-height: 58px;
  padding: .25rem .1rem;
  border: 1px solid var(--kaki);
  border-radius: var(--r-sm);
  font-family: var(--chiffre);
  font-size: var(--fs-xs);
  color: var(--kaki);
}
/* Le numéro du jour, discret : c'est le repère, pas l'information. */
.presence__n { font-size: 0.5rem; opacity: .75; }
/* La prime, elle, est ce qu'on vient lire. */
.presence__lot {
  font-family: var(--corps, inherit);
  font-size: 0.5rem;
  line-height: 1.1;
  text-align: center;
  overflow: hidden;
}
.presence__jour[data-etat="avenir"] .presence__lot { opacity: .8; }
.presence__jour[data-etat="passe"] {
  border-color: var(--vert-ok);
  color: var(--vert-ok);
}
.presence__jour[data-etat="aujourdhui"] {
  border-color: var(--laiton);
  background: var(--laiton);
  color: var(--nuit, #17150e);
  font-weight: 700;
  transform: scale(1.12);
}

/* ============================================================
   ÉLÉMENTS PARTAGÉS DES ÉCRANS SECONDAIRES
   ============================================================ */

.sous-onglets {
  display: flex;
  gap: .3rem;
  padding: .2rem;
  border-radius: var(--r);
  background: var(--toile);
  border: 1px solid var(--kaki);
}
/* ⚠️ CINQ ONGLETS NE TIENNENT PAS COMME TROIS.

   Cette barre a été réglée pour trois boutons. La Collection en porte
   cinq — GRADES, DÉCORATIONS, TAMPONS, BREVETS, CLASSEMENT — et à
   `letter-spacing: .1em`, leur texte remplit la case bord à bord. Rien
   ne déborde à la mesure, et pourtant ça ne respire pas : c'est
   exactement ce que Louka appelle « pas épuré ».

   L'interlettrage se resserre, le texte peut rétrécir — `flex: 1` sans
   `min-width: 0` refuse de passer sous la largeur de son contenu — et
   un mot trop long s'abrège plutôt que de pousser ses voisins. */
/* ⚠️ PAS D'ELLIPSE — première tentative, et elle était PIRE.

   Couper « DÉCORATIONS » en « DÉCORATIO… » ne fait pas épuré : ça se
   lit comme un défaut d'affichage. Un mot tronqué en silence est une
   information qu'on retire sans le dire.

   Ce qui manquait n'était pas de la place, c'était de la MESURE : cinq
   mots français de onze lettres à douze pixels avec un interlettrage
   de un dixième de cadratin ne tiennent pas dans quatre cent cinquante
   pixels. On réduit donc le corps et l'interlettrage jusqu'à ce qu'ils
   tiennent, et on autorise le retour à la ligne pour ce qui dépasserait
   encore — « BONNES RÉPONSES », sur l'autre barre, s'y met déjà.

   Se replier plutôt que se couper : on perd un peu de hauteur, jamais
   une lettre. */
.sous-onglet {
  /* `auto` ET NON `0` : chaque onglet part de la largeur de SON MOT, puis
     grandit pour remplir la barre. À largeur égale, c est le mot le plus
     long qui commande — « DÉCORATIONS » impose donc sa taille aux quatre
     autres, et sur un téléphone de trois cent quatre-vingt-dix pixels,
     cinq mots français n y tiennent plus. Proportionnés à leur contenu,
     ils tiennent, et la barre respire au lieu de se remplir bord à bord. */
  flex: 1 1 auto;
  min-width: 0;
  padding: .2rem .35rem;
  line-height: 1.15;
  /* `var(--tap)` — la taille de cible du jeu, et non trente-huit pixels
     décidés ici. Ces trois boutons sont la navigation de la collection et
     du classement : les seuls du jeu qui passaient sous la barre, et les
     deux boutiques exigent quarante-quatre pixels.

     La correction ne coûte rien : le bandeau gagne six pixels de haut, sur
     un écran qui défile déjà. */
  min-height: var(--tap);
  border-radius: var(--r-sm);
  font-size: 0.6875rem;
  letter-spacing: .02em;
  color: var(--sable-b);
}
.sous-onglet--on { background: var(--kaki); color: var(--laiton-c); }

.aide {
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--sable-b);
  padding: .6rem .8rem;
  border-radius: var(--r-sm);
  border-left: 2px solid var(--kaki);
}

/* --- L'échelle des grades --- */
.grade-ligne {
  display: grid;
  /* Trois colonnes, la première élargie pour accueillir le camp : la liste
     est devenue une galerie, et une galerie a besoin de place pour ses
     pièces. */
  grid-template-columns: 78px 1fr auto;
  align-items: center;
  gap: .7rem;
  min-height: 76px;
  padding: .4rem .7rem;
  border-radius: var(--r);
  border: 1px solid var(--ink-3, var(--kaki));
  background: var(--toile);
  text-align: left;
  /* Un grade non atteint reste LISIBLE — c'est lui qui donne envie. Il est
     éteint, pas caché : le camp passe en gris, comme une photographie qu'on
     n'a pas encore prise. */
  opacity: .62;
}
.grade-ligne .grade-ligne__camp { filter: grayscale(1) brightness(.72); }
.grade-ligne--ok { opacity: 1; border-color: var(--kaki); }
.grade-ligne--ok .grade-ligne__camp { filter: none; }
.grade-ligne--pret {
  opacity: 1;
  border-color: var(--laiton);
  background: linear-gradient(90deg, color-mix(in srgb, var(--laiton) 12%, var(--toile)), var(--toile));
}
/* À portée : le camp reprend ses couleurs. On achète ce qu'on voit. */
.grade-ligne--pret .grade-ligne__camp { filter: none; }

.grade-ligne__vign { position: relative; display: block; line-height: 0; }
.grade-ligne__camp { display: block; line-height: 0; }
.grade-ligne__camp .visuel {
  width: 78px; height: auto;
  filter: drop-shadow(0 3px 5px rgba(0, 0, 0, .55));
}
.grade-ligne__ins { line-height: 0; }
.grade-ligne__ins .ecusson,
.grade-ligne__ins .visuel { width: 34px; height: auto; }
/* L'écusson se pose sur le coin du camp, comme sur l'accueil. La même
   grammaire d'un écran à l'autre : le camp porte, l'écusson signe. */
.grade-ligne__ins--sur {
  position: absolute;
  left: -4px; bottom: -2px;
  width: 30px;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .7));
}
.grade-ligne__ins--sur .ecusson,
.grade-ligne__ins--sur .visuel { width: 30px; }

.grade-ligne__txt { display: grid; gap: 1px; }
.grade-ligne__nom { font-weight: 700; color: var(--sable); }
.grade-ligne__camp-nom {
  font-size: 0.625rem; letter-spacing: .1em; color: var(--laiton);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.grade-ligne__sous { font-size: var(--fs-xs); color: var(--sable-b); }
.grade-ligne__etat {
  font-family: var(--chiffre);
  font-size: var(--fs-xs);
  color: var(--laiton);
}

/* --- Les décorations --- */
.medailles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: .5rem;
}
.medaille {
  display: grid;
  gap: 2px;
  padding: .7rem .4rem;
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: var(--toile);
  text-align: center;
  /* Ce qui manque reste VISIBLE, simplement éteint : c'est ce qui
     manque qui fait jouer. Le cacher retire la raison de continuer. */
  opacity: .45;
  filter: grayscale(1);
}
.medaille--ok { opacity: 1; filter: none; border-color: var(--laiton); }
/* La décoration est un SVG, plus un caractère : il lui faut une boîte, pas
   une taille de police. `line-height: 0` évite le liseré de deux pixels que
   la ligne de base réserve sous toute image en ligne — invisible sur une
   carte, très visible sur une grille de vingt et une. */
.medaille__ico { display: flex; justify-content: center; line-height: 0; margin-bottom: 2px; }
.medaille__ico .deco { display: block; }
.medaille__nom { font-size: 0.6875rem; color: var(--sable); line-height: 1.2; }
.medaille__etat { font-family: var(--chiffre); font-size: 0.625rem; color: var(--laiton); }

/* La cérémonie de promotion : rien d'autre à l'écran, un seul bouton. */
.ceremonie__sur {
  font-size: 0.625rem; letter-spacing: .2em; color: var(--laiton); text-align: center;
}
.ceremonie__grade {
  text-align: center;
  /* Plus gros que les autres titres de feuille : c'est le seul moment du jeu
     où l'on annonce quelque chose plutôt que de le demander. */
  font-size: clamp(1.7rem, 8vw, var(--fs-2xl));
  margin: 0;
}
.ceremonie__ins {
  width: 84px; margin: .2rem auto .5rem;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, .55));
  animation: pose .5s var(--ease);
}
.ceremonie__ins .ecusson,
.ceremonie__ins .visuel { width: 100%; height: auto; display: block; }
@keyframes pose {
  0% { opacity: 0; transform: scale(.55) rotate(-12deg); }
  70% { transform: scale(1.06) rotate(2deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}
@media (prefers-reduced-motion: reduce) { .ceremonie__ins { animation: none; } }

/* ============================================================
   LE CHOIX DU PORTRAIT
   ------------------------------------------------------------
   Douze visages en grille, tous visibles d'un coup. Pas de
   seconde feuille : un choix qu'il faut aller chercher n'est pas
   fait.
   ============================================================ */
.portraits { display: grid; gap: .35rem; margin-bottom: .2rem; }
.portraits__grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(66px, 1fr));
  gap: .4rem;
}
.portraits__case {
  display: grid;
  justify-items: center;
  gap: 3px;
  padding: .3rem .2rem .35rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--kaki);
  background: var(--toile);
  color: var(--sable-b);
}
.portraits__img {
  display: block;
  width: 46px; height: 46px;
  border-radius: 50%;
  overflow: hidden;
  border: 2px solid var(--kaki);
  background: var(--nuit);
}
.portraits__img .visuel { width: 100%; height: 100%; object-fit: cover; filter: none; }
.portraits__nom {
  font-size: 0.5625rem;         /* 9 px — une étiquette, pas du texte */
  letter-spacing: .04em;
  line-height: 1.1;
  text-align: center;
}
.portraits__case--on {
  border-color: var(--laiton);
  background: var(--toile-2);
  color: var(--sable);
}
.portraits__case--on .portraits__img {
  border-color: var(--laiton);
  box-shadow: 0 0 10px -2px var(--laiton);
}

/* ============================================================
   LES DEUX BOUTIQUES DE L'INTENDANCE
   ------------------------------------------------------------
   L'arsenal se paie en COINS — gagnés en jouant. Les paquetages
   se paient en SOLDE — achetée avec de l'argent. Les deux
   grilles se ressemblent volontairement : c'est le même geste.
   Ce qui les distingue est la MONNAIE, et elle est écrite sur
   chaque carte.
   ============================================================ */
.arsenal-boutique {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: .5rem;
}
/* Les paquetages sont PLUS LARGES que les objets : ils portent un inventaire
   de cinq lignes, et une carte d'objet ne le tiendrait pas. Deux par rangée
   au plus — c'est un achat qu'on lit, pas une vignette qu'on parcourt. */
.paquetages {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: .5rem;
}

.objet, .paquetage {
  display: grid;
  justify-items: center;
  gap: 1px;
  padding: .6rem .4rem .5rem;
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: var(--toile);
  box-shadow: var(--relief);
  text-align: center;
}
.objet__ico { line-height: 0; margin-bottom: 2px; }
.objet__ico .visuel { width: 46px; height: 46px; }
.objet__nom { font-size: 0.6875rem; color: var(--sable); line-height: 1.15; }
.objet__prix {
  font-family: var(--chiffre); font-size: var(--fs-xs); color: var(--laiton);
}
.objet__avoir { font-size: 0.5625rem; color: var(--sable-b); }
/* Trop cher : la carte s'éteint SANS disparaître. Ce qu'on ne peut pas
   encore s'offrir est précisément ce qui fait jouer. */
.objet--cher { opacity: .5; }
.objet--cher .objet__prix { color: var(--alerte-c); }

/* La couronne encadre l'emblème sur la carte, exactement comme elle
   encadrera le portrait du joueur : on ne décrit pas ce qu'on vend, on le
   montre déjà en place. */
.paquetage__vitrine {
  position: relative;
  display: grid;
  place-items: center;
  width: 74px; height: 74px;
  margin-bottom: 3px;
}
.paquetage__couronne { position: absolute; inset: 0; line-height: 0; }
.paquetage__couronne .visuel {
  width: 100%; height: 100%;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .6));
}
/* L'ÉCU TIENT DANS LE TROU DE SA COURONNE, ET PAS AILLEURS.

   Servi sans contrainte, il sortait à sa taille naturelle : il débordait de
   l'anneau et recouvrait le libellé de la carte. Le pictogramme qu'il
   remplace faisait vingt-six pixels et ne posait pas la question.

   `--trou` est le diamètre intérieur MESURÉ de la couronne, en pourcentage
   de sa largeur — il va de 43 % pour le tuyau du pompier à 61 % pour la
   torsade du gendarme. Voir `TROUS` dans game/sets.js. Le facteur 0.86
   laisse respirer : un écu qui touche l'anneau se lit comme un débordement,
   même quand il n'en est pas un. */
.paquetage__ico {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  line-height: 0;
  color: var(--teinte, var(--sable-b));
}
.paquetage__ico .visuel {
  width: calc(var(--trou, 50) * 0.86%);
  height: auto;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, .7));
}

/* L'inventaire : cinq lignes, avant le prix. Un prix devant un nom demande
   de la confiance ; devant un contenu, il propose un marché. */
.paquetage__liste {
  list-style: none;
  margin: .3rem 0 .1rem;
  padding: 0;
  display: grid;
  gap: 1px;
  text-align: left;
  width: 100%;
}
.paquetage__liste li {
  position: relative;
  padding-left: .7rem;
  font-size: 0.5625rem;
  line-height: 1.25;
  color: var(--sable-b);
}
.paquetage__liste li::before {
  content: "·";
  position: absolute;
  left: .15rem;
  color: var(--teinte, var(--laiton));
}
.paquetage__nom {
  font-size: 0.5625rem; letter-spacing: .08em; color: var(--sable-b); line-height: 1.15;
}
.paquetage__titre { font-size: 0.6875rem; color: var(--sable); line-height: 1.2; }
.paquetage__etat {
  font-family: var(--chiffre); font-size: 0.5625rem; color: var(--laiton); margin-top: 2px;
}
.paquetage--acquis { border-color: var(--teinte, var(--laiton)); }
.paquetage--acquis .paquetage__titre { color: var(--teinte, var(--sable)); }
/* Celui qu'on porte : la teinte devient une lueur. Un seul à la fois, donc
   un seul point lumineux dans la grille. */
.paquetage--porte {
  background: var(--toile-2);
  box-shadow: var(--relief), 0 0 14px -6px var(--teinte, var(--laiton));
}
.paquetage--porte .paquetage__etat { color: var(--teinte, var(--laiton)); }

/* ------------------------------------------------------------
   LES TENUES
   ------------------------------------------------------------
   Deux colonnes seulement, et une vitrine haute. Ce qui se vend
   ici est une SILHOUETTE : la réduire à une vignette de quarante
   pixels reviendrait à cacher l'article.
   ------------------------------------------------------------ */
.tenues {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: .5rem;
}
.tenue {
  display: grid;
  justify-items: center;
  gap: 1px;
  padding: .5rem .4rem .5rem;
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: var(--toile);
  box-shadow: var(--relief);
  text-align: center;
}
/* Le personnage est posé sur le SOL de sa vitrine, pas centré dedans :
   c'est ainsi qu'il apparaîtra dans l'arène, et six cartes dont les pieds
   s'alignent se comparent, là où six flottants se ressemblent. */
.tenue__vitrine {
  display: grid;
  align-items: end;
  justify-items: center;
  height: 116px;
  line-height: 0;
  margin-bottom: 4px;
  /* Une lueur sous les pieds qui vaut ligne de sol. */
  background: radial-gradient(ellipse 46% 9% at 50% 98%, rgba(0, 0, 0, .5), transparent 70%);
}
.tenue__vitrine .visuel {
  height: 112px; width: auto;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, .5));
}
/* Tant que les douze rendus ne sont pas livrés, la vitrine ne réserve pas
   116 pixels de vide : elle disparaît, et la carte se referme dessus. */
.tenue__vitrine:empty { display: none; }
.tenue__nom { font-size: 0.6875rem; color: var(--sable); line-height: 1.2; }
.tenue__quoi {
  font-size: 0.5625rem; line-height: 1.25; color: var(--sable-b);
}
.tenue__etat {
  font-family: var(--chiffre); font-size: 0.5625rem; color: var(--laiton); margin-top: 3px;
}
.tenue--acquise { border-color: var(--laiton); }
.tenue--portee {
  background: var(--toile-2);
  box-shadow: var(--relief), 0 0 14px -6px var(--laiton);
}
.tenue--portee .tenue__etat { color: var(--sable); }

/* ------------------------------------------------------------
   LES AURAS
   ------------------------------------------------------------
   La vitrine EST l'aura, en marche — mêmes grains, mêmes
   animations que dans l'arène. Une pastille de couleur ne dirait
   rien : ce qui se vend ici est un mouvement, donc on le joue.
   ------------------------------------------------------------ */
.auras {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: .5rem;
}
.aura {
  display: grid;
  justify-items: center;
  gap: 1px;
  padding: .5rem .4rem;
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: var(--toile);
  box-shadow: var(--relief);
  text-align: center;
}
.aura__vitrine {
  position: relative;
  width: 100%; height: 66px;
  overflow: hidden;
  border-radius: calc(var(--r) - 2px);
  background: var(--nuit);
  margin-bottom: 4px;
}
/* Les grains reprennent EXACTEMENT les règles de l'arène : `.arene__aura i`
   et ses variantes. On les rattache ici plutôt que de les recopier — deux
   copies divergeraient, et la vitrine finirait par mentir sur l'article. */
/* ⚠ LA VITRINE MONTRAIT DES POINTS DE COULEUR, PAS L'ARTICLE.
   Le commentaire ci-dessus promettait qu'elle reprenait « exactement les
   règles de l'arène ». Elle les RECOPIAIT — et la copie s'est arrêtée à
   trois variantes. Tant qu'il y avait six auras, on ne voyait qu'une
   différence de teinte ; à quatorze, huit cartes sur quatorze seraient
   devenues le même point qui monte, sous huit noms différents.

   La vitrine sert donc le VRAI grain, celui de l'arène, posé par la même
   variable. Le point de couleur reste dessous : hors connexion, au
   premier lancement, une carte vide vaut moins qu'une pastille. */
.aura__vitrine i {
  position: absolute;
  bottom: 0;
  width: 15px; height: 15px;
  scale: var(--s, 1);
  border-radius: 50%;
  background:
    var(--grain, none) center / contain no-repeat,
    radial-gradient(circle, var(--aura, transparent) 40%, transparent 70%);
  opacity: 0;
  animation: monte-vitrine 3.4s linear infinite;
}
/* Les trajectoires de l'arène parcourent trois cents pixels ; la vitrine
   en fait soixante-six. On rejoue donc les mêmes GESTES à son échelle —
   monter, tomber, traverser, jaillir, tourner — plutôt que de clipper des
   animations qui sortent du cadre en deux images. */
@keyframes monte-vitrine {
  0%   { transform: translate(0, 0); opacity: 0; }
  20%  { opacity: .95; }
  100% { transform: translate(calc(var(--dx, 0) * .3px), -70px); opacity: 0; }
}
@keyframes tombe-vitrine {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  20%  { opacity: .95; }
  100% { transform: translate(calc(var(--dx, 0) * .3px), 70px) rotate(200deg); opacity: 0; }
}
@keyframes traverse-vitrine {
  0%   { transform: translateX(0); opacity: 0; }
  15%  { opacity: .95; }
  85%  { opacity: .8; }
  100% { transform: translateX(150px); opacity: 0; }
}
@keyframes tourne-vitrine {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  10%  { opacity: 1; }
  25%  { transform: translate(20px, -12px) rotate(90deg); }
  50%  { transform: translate(0, -24px) rotate(180deg); }
  75%  { transform: translate(-20px, -12px) rotate(270deg); }
  100% { transform: translate(0, 0) rotate(360deg); opacity: 0; }
}
/* CE QUI TOMBE */
.aura__vitrine--cendres i,
.aura__vitrine--feuilles i {
  bottom: auto; top: 0;
  animation-name: tombe-vitrine;
  animation-duration: 5s;
}
.aura__vitrine--feuilles i { width: 19px; height: 19px; animation-duration: 6s; }
/* CE QUI TRAVERSE */
.aura__vitrine--sable i {
  bottom: auto;
  top: calc(8% + var(--y, 0) * .8%);
  left: -12% !important;
  animation-name: traverse-vitrine;
  animation-duration: 2.2s;
  animation-timing-function: linear;
}
/* CE QUI JAILLIT */
.aura__vitrine--etincelles i {
  width: 10px; height: 10px;
  animation-duration: 1.2s;
}
/* CE QUI S'ÉLARGIT */
.aura__vitrine--vapeur i {
  width: 26px; height: 26px;
  animation-duration: 4.6s;
}
/* CE QUI HÉSITE EN MONTANT */
.aura__vitrine--bulles i { animation-duration: 3.4s; animation-timing-function: cubic-bezier(.4, 0, .7, 1); }
/* CE QUI TOURNE */
.aura__vitrine--lucioles i,
.aura__vitrine--triomphe i {
  bottom: 34%;
  animation-name: tourne-vitrine;
  animation-timing-function: linear;
}
.aura__vitrine--lucioles i { animation-duration: 5.5s; }
.aura__vitrine--triomphe i { width: 20px; height: 20px; animation-duration: 7s; }
.aura__vitrine--givre i {
  bottom: auto; top: 0;
  animation-name: tombe;
  animation-duration: 5.2s;
}
.aura__vitrine--orage i {
  bottom: auto;
  top: calc(10% + var(--y, 0) * 1%);
  width: 2px; height: 14px;
  border-radius: 1px;
  animation: claque 2.4s steps(1) infinite;
}
.aura__vitrine--or i {
  bottom: 8%;
  left: 50% !important;
  width: 46%; height: 46%;
  margin-left: -23%;
  border-radius: 50%;
  background: none;
  border: 2px solid var(--aura, transparent);
  animation: onde 3s ease-out infinite;
}
.aura__nom { font-size: 0.6875rem; color: var(--sable); line-height: 1.2; }
.aura__quoi { font-size: 0.5625rem; line-height: 1.25; color: var(--sable-b); }
.aura__etat {
  font-family: var(--chiffre); font-size: 0.5625rem; color: var(--laiton); margin-top: 3px;
}
.aura--acquise { border-color: var(--aura, var(--laiton)); }
.aura--portee {
  background: var(--toile-2);
  box-shadow: var(--relief), 0 0 14px -6px var(--aura, var(--laiton));
}
.aura--portee .aura__etat { color: var(--aura, var(--laiton)); }

@media (prefers-reduced-motion: reduce) {
  .aura__vitrine i { animation: none; opacity: .45; }
}

/* ============================================================
   MA FICHE
   ------------------------------------------------------------
   Le seul écran du jeu où l'on se regarde sans être en train de
   jouer. Il ouvre donc sur le PERSONNAGE et non sur des chiffres :
   une fiche qui commence par un bilan est un relevé de compte,
   une fiche qui commence par une silhouette est une identité.
   ============================================================ */
.fiche {
  display: grid;
  justify-items: center;
  gap: .4rem;
  margin-bottom: .3rem;
}

/* La scène : couronne derrière, aura autour, combattant devant. Les trois
   pièces achetées se voient ENSEMBLE, ce qui n'arrive nulle part ailleurs —
   en combat, on regarde la question. */
.fiche__scene {
  position: relative;
  display: grid;
  place-items: end center;
  width: min(100%, 260px);
  height: 232px;
  container-type: inline-size;
  /* Le sol, sous les pieds. Sans lui le personnage flotte, exactement
     comme dans l'arène avant qu'on ne lui en donne un. */
  background: radial-gradient(ellipse 40% 6% at 50% 97%, rgba(0, 0, 0, .6), transparent 70%);
}
.fiche__perso { line-height: 0; }
.fiche__perso .visuel {
  height: 220px; width: auto;
  filter: drop-shadow(0 10px 14px rgba(0, 0, 0, .6));
}
.fiche__couronne {
  position: absolute;
  left: 50%; top: 4%;
  width: 62cqw;
  aspect-ratio: 1;
  transform: translateX(-50%);
  line-height: 0;
  opacity: .82;
  z-index: -1;
}
.fiche__couronne .visuel { width: 100%; height: 100%; }
/* L'aura reprend TELLES QUELLES les règles de l'arène — mêmes grains,
   mêmes animations, même fichier de particule. Une vitrine qui rejoue
   l'effet à sa façon finit par mentir sur ce qu'on a acheté.

   ELLE RESPIRE PLUS LARGE QU'EN COMBAT, et seulement ici. L'arène la
   resserre exprès — « une aura doit émaner de quelqu'un », et à côté d'une
   question à lire en treize secondes, des braises jusque dans le coin de
   l'écran deviennent du bruit. La fiche n'a pas cette contrainte : c'est
   la seule page où l'on se regarde sans jouer, et l'aura y est l'achat
   qu'on vient vérifier. */
.fiche__aura {
  inset: 4% 12% 0;
  opacity: 1;
}

.fiche__nom {
  display: flex;
  align-items: center;
  gap: .5rem;
}
.fiche__galon { width: 44px; line-height: 0; flex: 0 0 44px; }
.fiche__galon .visuel, .fiche__galon .ecusson { width: 100%; height: auto; display: block; }
.fiche__id { display: grid; }
.fiche__id b {
  font-family: var(--display);
  font-size: 1.35rem;
  line-height: 1;
  color: var(--sable);
}
.fiche__id span {
  font-size: 0.625rem;
  letter-spacing: .12em;
  color: var(--laiton);
}

/* ------------------------------------------------------------
   LES QUATRE FENTES
   ------------------------------------------------------------
   Ce qu'on porte, et le chemin pour en changer. Une fente vide ne
   se cache pas : elle annonce ce qu'on pourrait y mettre.
   ------------------------------------------------------------ */
.fentes {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: .4rem;
}
.fente {
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: .5rem .25rem;
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: var(--toile);
  box-shadow: var(--relief);
  text-align: center;
}
.fente__ico {
  display: grid; place-items: center;
  height: 42px;
  line-height: 0;
  color: var(--sable-b);
}
.fente__ico .visuel { max-height: 42px; width: auto; max-width: 100%; }
.fente__nom {
  font-size: 0.5625rem; letter-spacing: .1em; color: var(--laiton);
}
.fente__porte {
  font-size: 0.625rem; line-height: 1.2; color: var(--sable);
}
/* Rien dedans : la fente s'éteint sans disparaître, et son libellé dit ce
   qu'on n'a pas plutôt qu'un tiret muet. */
.fente--vide { opacity: .58; }
.fente--vide .fente__porte { color: var(--sable-b); }

/* ------------------------------------------------------------
   LES ÉTATS DE SERVICE
   ------------------------------------------------------------
   Huit nombres qui existaient dans le profil depuis le premier
   duel et n'étaient affichés nulle part.
   ------------------------------------------------------------ */
.service {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: .4rem;
  margin-bottom: .6rem;
}
.service__case {
  display: grid;
  justify-items: center;
  gap: 1px;
  padding: .5rem .2rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--kaki);
  background: var(--toile);
}
.service__val {
  font-family: var(--chiffre);
  font-size: 1.05rem;
  line-height: 1;
  color: var(--laiton-c);
}
.service__lbl {
  font-size: 0.5rem;
  letter-spacing: .04em;
  line-height: 1.2;
  color: var(--sable-b);
  text-align: center;
}

@media (max-width: 359px) {
  /* Sous 360 px, quatre colonnes donnent des cases de soixante pixels où
     « meilleure série » se coupe en trois lignes. On passe à deux. */
  .fentes, .service { grid-template-columns: repeat(2, 1fr); }
}

/* ============================================================
   L'ÉCRAN D'ARRIVÉE — le combattant
   ------------------------------------------------------------
   Cet écran était du texte sur du noir, après chaque duel. Il
   porte maintenant celui qu'on vient de mener au combat, avec sa
   couronne et son aura : ce qu'on a payé se voit là aussi.

   Sa hauteur suit l'ÉCRAN et non un nombre fixe : cet écran-là
   porte aussi les gains, les déblocages, un coupon éventuel et
   trois boutons. Le personnage est ce qui doit céder la place,
   jamais « REJOUER ».
   ============================================================ */
.result__perso {
  position: relative;
  display: grid;
  place-items: end center;
  height: min(30vh, 230px);
  margin-bottom: .2rem;
  container-type: inline-size;
  /* Le halo de la bannière, derrière lui : c'est la couleur du grade, et
     elle relie l'écran d'arrivée à la plaque d'identité. */
  background: radial-gradient(ellipse 45% 40% at 50% 78%,
    var(--ban-de, transparent), transparent 70%);
}
.result__perso:empty { display: none; }
.result__silhouette { line-height: 0; }
.result__silhouette .visuel {
  height: min(30vh, 230px); width: auto;
  filter: drop-shadow(0 10px 16px rgba(0, 0, 0, .65));
}
.result__couronne {
  position: absolute;
  left: 50%; top: 4%;
  width: 58cqw; aspect-ratio: 1;
  transform: translateX(-50%);
  line-height: 0;
  opacity: .8;
  z-index: -1;
}
.result__couronne .visuel { width: 100%; height: 100%; }
.result__aura { inset: 12% 28% 0; }
/* Après une défaite : plus sombre, légèrement affaissé. Un seul réglage,
   et l'écran cesse de féliciter quelqu'un qui vient de perdre. */
.result__perso--vaincu .result__silhouette .visuel {
  filter: grayscale(.55) brightness(.62) drop-shadow(0 6px 10px rgba(0, 0, 0, .7));
  transform: translateY(4px) scaleY(.97);
}

/* La décoration qui vient de tomber : MONTRÉE, pas nommée. C'est un objet
   dessiné, et c'est le seul moment où on le découvre en l'obtenant. */
.unlock__deco {
  display: flex;
  align-items: center;
  gap: .6rem;
  text-align: left;
}
.unlock__medaille { flex: 0 0 44px; line-height: 0; }
.unlock__medaille svg { width: 44px; height: auto; display: block; }

/* ============================================================
   LES TITRES ET LES BANNIÈRES
   ------------------------------------------------------------
   Le contenu de longue durée. Un titre non obtenu reste VISIBLE,
   avec sa condition et son avancement — c'est ce qui manque qui
   fait jouer, et le masquer retire la seule raison de continuer
   quand le grade est fini.
   ============================================================ */
.titres {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  gap: .4rem;
  margin-bottom: .6rem;
}
.titre {
  display: grid;
  gap: 2px;
  padding: .5rem .6rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--kaki);
  background: var(--toile);
  text-align: left;
}
.titre__nom {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--laiton-c);
  line-height: 1.2;
}
.titre__quoi { font-size: 0.625rem; line-height: 1.3; color: var(--sable-b); }
.titre__part {
  font-family: var(--chiffre);
  font-size: 0.625rem;
  color: var(--laiton);
}
/* Fermé : éteint, mais lisible. Le nom devient « ??? » côté code — ici on
   ne fait que baisser la lumière, jamais cacher la condition. */
/* LES HUIT TITRES DU CLASSÉ.
   Ils portent leur écusson à gauche : c'est ce qui les fait lire comme une
   SÉRIE plutôt que comme huit lignes parmi trente-neuf. La carte passe donc
   à deux colonnes, et son texte se range à droite. */
.titre--rang {
  grid-template-columns: 30px 1fr;
  align-items: center;
  column-gap: .5rem;
}
.titre__blason { grid-row: 1 / -1; line-height: 0; }
.titre__blason img, .titre__blason svg { width: 100%; height: auto; }
/* Un titre non gagné garde son écusson VISIBLE mais éteint : le nom reste
   caché — c'est la règle de tous les titres — la provenance non. C'est
   justement elle qui doit donner envie d'aller le chercher. */
.titre--ferme.titre--rang .titre__blason { opacity: .55; }

.titre--ferme { opacity: .5; }
.titre--ferme .titre__nom { color: var(--sable-b); letter-spacing: .1em; }
.titre--porte {
  border-color: var(--laiton);
  background: var(--toile-2);
  box-shadow: 0 0 14px -6px var(--laiton);
}

.bannieres {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: .4rem;
  margin-bottom: .6rem;
}
.banniere {
  display: grid;
  gap: 1px;
  min-height: var(--tap);
  padding: .5rem .4rem;
  border-radius: var(--r-sm);
  border: 1px solid var(--kaki);
  /* La bannière EST son propre aperçu : la carte porte le dégradé qu'elle
     posera sur la plaque. Décrire une couleur par un mot serait absurde
     quand on peut la montrer. */
  background: linear-gradient(160deg, var(--ban-de), var(--ban-vers));
  text-align: center;
}
.banniere__nom { font-size: 0.75rem; color: var(--sable); }
.banniere__quoi {
  font-size: 0.5625rem; letter-spacing: .08em; color: var(--sable-b);
}
.banniere--fermee { opacity: .42; }
.banniere--portee { border-color: var(--laiton); box-shadow: 0 0 14px -6px var(--laiton); }

/* Le titre sous le matricule, sur la fiche et sur la plaque d'accueil. */
.fiche__id em {
  font-style: normal;
  font-size: 0.75rem;
  color: var(--laiton-c);
  letter-spacing: .02em;
}
.plaque__titre {
  font-size: 0.625rem;
  letter-spacing: .06em;
  color: var(--laiton-c);
  line-height: 1.1;
}
.plaque__titre:empty { display: none; }

/* La bannière teinte le fond de la plaque et celui de la scène de la fiche.
   `--ban-de` et `--ban-vers` sont posés en JavaScript ; sans eux, les deux
   valeurs de repli redonnent exactement l'ancien fond. */
.plaque {
  background-image: linear-gradient(160deg,
    var(--ban-de, transparent), var(--ban-vers, transparent));
}
.fiche__scene {
  background-image:
    /* Le sol, sous les pieds. */
    radial-gradient(ellipse 40% 6% at 50% 97%, rgba(0, 0, 0, .6), transparent 70%),
    /* LA LUEUR DE LA BANNIÈRE — une ellipse, plus une bande.
       C'était `linear-gradient(180deg, …)` : le dégradé s'estompait vers le
       bas mais s'arrêtait NET sur les bords gauche et droit du bloc, qui
       fait 260 px de large et 232 de haut. Résultat : un rectangle franc
       derrière le combattant, qu'on lisait comme un défaut de découpe de
       l'image alors que l'image, elle, est bien détourée.

       Une ellipse n'a pas de bord : la couleur du grade se voit toujours,
       mais elle émane du personnage au lieu de l'encadrer. */
    radial-gradient(ellipse 72% 58% at 50% 38%,
                    var(--ban-de, transparent) 0%,
                    transparent 70%);
}

/* Le compte de décorations, au classement. Discret : c'est une mention,
   pas un score — le score a sa colonne, à droite. */
.rang__deco {
  font-family: var(--chiffre);
  font-size: 0.625rem;
  color: var(--laiton);
  opacity: .8;
  white-space: nowrap;
}

/* ------------------------------------------------------------
   LES REMISES ACHETÉES EN COINS
   ------------------------------------------------------------
   Le pourcentage est le héros de la carte : c'est la seule chose
   qu'on compare entre trois offres. Le prix vient après, dans la
   fonte des chiffres.
   ------------------------------------------------------------ */
.remises {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: .5rem;
}
.remise {
  display: grid;
  justify-items: center;
  gap: 1px;
  padding: .7rem .3rem .6rem;
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: var(--toile);
  box-shadow: var(--relief);
  text-align: center;
}
.remise { container-type: inline-size; }
.remise__val {
  font-family: var(--display);
  /* La taille suit la CARTE : « -20 % » tient en gros, « Patch offert »
     demande deux lignes. `cqw` mesure la carte elle-même, donc la valeur
     reste juste que la grille ait deux ou trois colonnes. On ne rogne
     jamais un libellé pour le faire tenir. */
  font-size: clamp(0.9rem, 13cqw, 1.5rem);
  line-height: 1.05;
  color: var(--laiton-c);
}
.remise__lbl { font-size: 0.5625rem; color: var(--sable-b); }
.remise__prix {
  font-family: var(--chiffre);
  font-size: var(--fs-xs);
  color: var(--laiton);
  margin-top: 3px;
}
/* Hors de portée : la carte s'éteint SANS disparaître. Ce qu'on ne peut pas
   encore s'offrir est précisément ce qui fait jouer un duel de plus. */
.remise--bloquee { opacity: .45; }
.remise--bloquee .remise__prix { color: var(--alerte-c); }

.visuel--fantome {
  filter: grayscale(1) brightness(.55) contrast(.8);
  opacity: .5;
}

/* La carte d'un camp, dans une feuille de détail ou de promotion. */
.camp-carte { display: grid; justify-items: center; gap: .15rem; margin-bottom: .5rem; }
.camp-carte:empty { display: none; }
.camp-carte__sur {
  font-size: 0.625rem; letter-spacing: .16em; color: var(--laiton);
}
.camp-carte__img { line-height: 0; }
.camp-carte__img .visuel {
  width: min(62vw, 230px); height: auto;
  filter: drop-shadow(0 10px 16px rgba(0, 0, 0, .6));
}
.camp-carte__nom {
  font-family: var(--display);
  font-size: var(--fs-lg);
  letter-spacing: .03em;
  color: var(--sable);
  text-align: center;
  line-height: 1.05;
}
.camp-carte__quoi {
  font-size: var(--fs-xs); color: var(--sable-b); text-align: center;
  max-width: 30ch;
}

.detail-ico { display: flex; justify-content: center; line-height: 0; margin-bottom: .4rem; }
/* Ternie tant qu'elle n'est pas obtenue — le MÊME dessin, éteint. Deux
   jeux de dessins auraient divergé au premier ajout. */
.detail-ico--creux { opacity: .5; filter: grayscale(1); }
.detail-ins { width: 96px; margin: 0 auto .5rem; }
.detail-ins .ecusson,
.detail-ins .visuel { width: 100%; height: auto; }
.detail-ok { color: var(--vert-ok); font-weight: 700; }
.detail-reste { color: var(--laiton); font-family: var(--chiffre); }

/* --- Classement --- */
.monrang {
  display: grid;
  gap: 2px;
  padding: var(--pad);
  border-radius: var(--r);
  background: linear-gradient(135deg, color-mix(in srgb, var(--laiton) 14%, var(--toile-2)), var(--toile));
  border: 1px solid var(--laiton);
  text-align: center;
}
.monrang__lbl { font-size: var(--fs-xs); letter-spacing: .18em; color: var(--sable-b); }
.monrang__val {
  font-family: var(--display);
  font-size: var(--fs-2xl);
  line-height: 1;
  color: var(--laiton-c);
}
.monrang__sous { font-size: var(--fs-xs); color: var(--sable-b); }
.monrang__note { font-size: var(--fs-xs); color: var(--laiton); margin-top: .3rem; }
/* La foule derrière soi. En laiton et non en sable : c'est la seule ligne
   de la carte qui MONTE quand on joue, et elle doit se distinguer du
   compteur de points juste au-dessus, qui lui est un simple état. */
.monrang__foule {
  font-size: var(--fs-xs);
  color: var(--laiton);
  margin-top: .35rem;
  border-top: 1px solid var(--kaki);
  padding-top: .35rem;
}

.rang {
  display: grid;
  /* CINQ COLONNES : place, GALON, nom et grade empilés, décorations, valeur.

     Le galon manquait, et la ligne n'était que du texte — vingt lignes
     identiques au pixel près, qu'on parcourt sans rien reconnaître. C'est
     pourtant le seul signe du jeu qui se lise SANS SAVOIR LIRE :
     « Adjudant-chef » ne dit rien à qui débute, deux barrettes dorées se
     comparent d'un coup d'œil — et un classement est fait pour comparer.

     Le nom et le grade s'EMPILENT désormais au lieu de partager une ligne :
     un matricule long poussait le grade hors du champ.

     La colonne des décorations reste en `auto` — vide, elle ne prend rien,
     et la ligne garde son allure pour un joueur qui n'en a aucune. */
  grid-template-columns: 2.4rem 30px 1fr auto auto;
  align-items: center;
  gap: .5rem;
  min-height: 48px;
  padding: 0 .7rem;
  border-radius: var(--r-sm);
  background: var(--toile);
  border: 1px solid transparent;
}
.rang--moi { border-color: var(--laiton); background: var(--toile-2); }
.rang__place { font-family: var(--chiffre); font-size: var(--fs-xs); color: var(--sable-b); }

/* LES TROIS PREMIERS SE VOIENT. Un classement où la première place se lit
   dans le même gris que la douzième ne récompense pas d'y être. Trois
   teintes, et rien de plus : un podium plus bavard volerait la vedette aux
   lignes qu'on vient lire. */
.rang__place--podium { font-weight: 700; }
.rang__place[data-podium="1"] { color: #E8C349; text-shadow: 0 0 8px rgba(232, 195, 73, .45); }
.rang__place[data-podium="2"] { color: #C6CBD1; }
.rang__place[data-podium="3"] { color: #C08A52; }

/* LE GALON. `insigne()` sert l'image quand elle est livrée et retombe sur
   le tracé sinon — un classement ne doit pas se vider hors connexion. */
.rang__galon { position: relative; display: block; width: 30px; line-height: 0; }

/* LE PRESTIGE, en pastille sur le coin du galon.
   Il était publié depuis toujours et affiché nulle part : un Colonel
   prestige 3 et un Colonel prestige 0 étaient indistinguables. C est
   pourtant le seul objectif qui reste une fois le grade fini.

   Une pastille et non une colonne : la ligne en a déjà cinq, et un
   prestige nul — le cas de presque tout le monde — ne doit rien coûter
   en largeur. */
.rang__prestige {
  position: absolute;
  right: -5px; bottom: -3px;
  min-width: 13px; height: 13px;
  padding: 0 2px;
  border-radius: 7px;
  background: var(--laiton);
  color: var(--nuit);
  font-family: var(--chiffre);
  font-size: 0.5rem;
  line-height: 13px;
  text-align: center;
  box-shadow: 0 0 0 1.5px var(--toile);
}

/* LE RANG CLASSÉ, dans la sous-ligne. Il se lit à sa COULEUR avant son
   nom — sur vingt lignes, on repère un vert d émeraude sans lire. */
.rang__classe { color: var(--rang-teinte, var(--sable-b)); font-weight: 600; }
.rang__galon img, .rang__galon svg { width: 100%; height: auto; display: block; }

.rang__qui { display: grid; gap: 1px; min-width: 0; }
.rang__nom { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rang__grade {
  font-size: var(--fs-xs); color: var(--sable-b);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rang__val { font-family: var(--chiffre); color: var(--laiton); }

/* L'EFFECTIF D'UN RÉGIMENT n'a pas de galon à montrer : ses lignes disent
   le grade en toutes lettres, sous le nom. On retire donc la colonne de
   30 px réservée à l'insigne — sans quoi il faudrait y poser une cellule
   vide, et une colonne vide sur trente lignes est trente fois rien qui
   prend de la place. */
.rang--effectif { grid-template-columns: 2.4rem 1fr auto auto; }

/* LE VOYANT DE PRÉSENCE, dans l'effectif d'un régiment.

   Il passe à la ligne suivante de la grille — `grid-column: 1 / -1` — et
   non à côté des galons : à trente lignes sur un téléphone, « inactif
   depuis 23 j » posé en bout de ligne pousse le pseudo hors de l'écran.

   Éteint, il ne porte QUE du texte. Une pastille grise à côté de « vu il y
   a 3 j » dirait deux fois la même chose, et un rond gris sur vingt-huit
   lignes finit par se lire comme une décoration. */
.rang__vu {
  grid-column: 1 / -1;
  font-size: var(--fs-xs);
  color: var(--sable-b);
  display: flex;
  align-items: center;
  gap: .35rem;
}
/* `--vert-ok` et pas un vert neuf : c'est le vert du jeu, celui de
   l'accompli, et il a déjà sa variante pour le thème clair. Ajouter un
   second vert ferait deux verts qui se ressemblent sans se valoir. */
.rang__vu--on { color: var(--vert-ok); }
.rang__vu--on::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: 50%;
  background: currentColor;
  /* Le halo dit « maintenant » sans clignoter. Une animation sur trente
     lignes est un écran qui grouille, et il faudrait de toute façon la
     couper pour qui a demandé moins de mouvement. */
  box-shadow: 0 0 0 2px rgba(122, 138, 53, .3);
  flex: none;
}

/* --- Régiment --- */
.clan-tete {
  display: grid;
  gap: 2px;
  padding: var(--pad);
  border-radius: var(--r);
  background: var(--toile-2);
  border-left: 4px solid var(--laiton);
}
.clan-tete__tag { font-family: var(--chiffre); font-size: var(--fs-xs); color: var(--laiton); }
.clan-tete__nom { font-family: var(--display); font-size: var(--fs-lg); color: var(--sable); }
.clan-tete__devise { font-size: var(--fs-xs); color: var(--sable-b); font-style: italic; }

.clan-semaine {
  display: grid;
  gap: .3rem;
  padding: var(--pad);
  border-radius: var(--r);
  background: var(--toile);
  border: 1px solid var(--kaki);
}
.clan-semaine__lbl { font-size: var(--fs-xs); letter-spacing: .16em; color: var(--sable-b); }
.clan-semaine__val { font-family: var(--display); font-size: var(--fs-xl); color: var(--laiton-c); line-height: 1; }
.clan-semaine__sous { font-size: var(--fs-xs); color: var(--sable-b); }

/* --- LA GUERRE DE LA SEMAINE ---
   Le seul bloc de l'écran qui paie en argent réel : il porte donc le
   liseré de laiton, et il est le seul à le porter. Le reste de l'écran
   reste sur toile nue — sans quoi rien ne ressort. */
.guerre {
  display: grid;
  gap: .35rem;
  padding: var(--pad);
  border-radius: var(--r);
  background:
    radial-gradient(120% 90% at 50% 0%, rgba(196, 150, 74, .16), transparent 70%),
    var(--toile-2);
  border: 1px solid var(--laiton-s, var(--laiton));
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
}
.guerre__titre {
  font-size: var(--fs-xs);
  letter-spacing: .18em;
  color: var(--laiton-c);
}
.guerre__val {
  font-family: var(--display);
  font-size: var(--fs-xxl, var(--fs-xl));
  line-height: 1;
  color: var(--sable);
}
.guerre__sous { font-size: var(--fs-xs); color: var(--sable-b); }
.guerre__moi { font-size: var(--fs-sm); color: var(--sable); margin-top: .25rem; }
.guerre__regle,
.guerre__prime { font-size: var(--fs-xs); color: var(--sable-b); line-height: 1.4; }
.guerre__prime { color: var(--laiton-c); }

/* --- LE BULLETIN DE LA GUERRE PASSÉE --- */
.guerre-bulletin { display: grid; gap: .25rem; }
.guerre-bulletin__nom {
  font-family: var(--display);
  font-size: var(--fs-lg);
  color: var(--laiton-c);
}
.guerre-bulletin__nom--nous { color: var(--sable); text-shadow: 0 0 12px rgba(196, 150, 74, .5); }

/* ============================================================
   COMBAT
   ------------------------------------------------------------
   La question et les quatre réponses tiennent sur un écran, sur
   le plus petit modèle visé. Toujours. Si une question est trop
   longue, on réduit le corps de la QUESTION, jamais celui des
   réponses.
   ============================================================ */
#screen-combat {
  display: grid;
  /* Sept rangées depuis que l'arène existe : haut, chrono, jauges, ARÈNE,
     question, réponses, arsenal.

     Le `1fr` est passé de la question à l'arène. Il était sur la question,
     qui n'en avait pas besoin — elle fait deux lignes — et le reste de la
     hauteur restait donc en vide noir au milieu de l'écran le plus important
     du jeu. C'est ce vide que l'arène occupe. */
  /* `minmax(0, 1fr)` ET NON `1fr` — la différence n'est pas cosmétique.

     Un `1fr` vaut `minmax(auto, 1fr)` : la rangée ne descend jamais sous la
     hauteur minimale de son contenu. L'arène porte un personnage de deux
     cent cinquante pixels ; elle refusait donc de rétrécir, et sur un écran
     de 640 pixels l'écran de combat réclamait 793 pixels. Les réponses
     tombaient sous la ligne — sur l'écran où il faut répondre en treize
     secondes.

     `minmax(0, 1fr)` autorise la rangée à descendre jusqu'à zéro. C'est
     elle, et elle seule, qui absorbe ce qui manque : le personnage
     rapetisse, la question et les réponses ne bougent pas. */
  grid-template-rows: auto auto auto minmax(0, 1fr) auto auto auto;
  gap: .5rem;
  padding-left: var(--pad);
  padding-right: var(--pad);
  overflow: hidden;
}

/* ------------------------------------------------------------
   LE DÉCOR DU DUEL
   ------------------------------------------------------------
   Un lieu derrière le combat, choisi par le corps. Il est posé en
   pseudo-élément et non en fond de l'écran, pour trois raisons
   qu'un simple `background` ne permettrait pas :

     · une OPACITÉ propre — un fond de couche n'en a pas, et il
       aurait fallu passer par un mode de fusion ;
     · un MASQUE qui l'éteint vers le bas, pour que les quatre
       réponses reposent sur du noir franc ;
     · un FLOU léger, qui interdit à un détail du décor de se
       disputer l'attention avec la question.

   `--decor` est posé par le JavaScript à chaque duel. Quand il
   est absent, la règle ne peint rien : aucune adresse morte,
   aucune requête 404.
   ------------------------------------------------------------ */
#screen-combat::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: var(--decor, none) center / cover no-repeat;
  opacity: .42;
  filter: blur(1.5px) saturate(.8);
  /* Plein en haut, éteint aux deux tiers : la question et les réponses
     retrouvent le fond sombre du jeu, où elles se lisent le mieux. */
  -webkit-mask-image: linear-gradient(180deg, #000 0 44%, transparent 74%);
  mask-image: linear-gradient(180deg, #000 0 44%, transparent 74%);
}

.combat__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: .5rem;
}
.combat__round {
  font-family: var(--chiffre);
  font-size: var(--fs-sm);
  color: var(--laiton);
  letter-spacing: .1em;
}
.combat__quit {
  width: var(--tap);
  height: var(--tap);
  font-size: var(--fs-md);
  color: var(--sable-b);
}

.timer { height: 7px; border-radius: 4px; background: #0E0F0A; overflow: hidden; }
.timer__fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--vert-ok), var(--laiton));
  transform-origin: left;
}
.timer__fill--low { background: linear-gradient(90deg, var(--alerte-c), var(--alerte)); }
/* Sous trois secondes, elle bat. Une barre qui rétrécit ne se voit pas quand
   on lit une question — le regard est sur le texte. Un clignotement, lui,
   s'attrape en vision périphérique. */
.timer__fill--urgent { animation: bat .52s ease-in-out infinite; }
@keyframes bat {
  50% { opacity: .38; box-shadow: 0 0 10px 1px var(--alerte-c); }
}

.hp { display: grid; grid-template-columns: 1fr auto 1fr; gap: .5rem; align-items: center; }
.hp__label {
  font-size: var(--fs-xs);
  letter-spacing: .12em;
  color: var(--laiton);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hp__side--foe { text-align: right; }

/* ------------------------------------------------------------
   LE VISAGE CONTRE LA JAUGE
   ------------------------------------------------------------
   Vingt-six pixels, posés sur la ligne du libellé. Chacun regarde
   vers l'extérieur de sa moitié : le joueur à gauche, l'adversaire
   à droite, comme les jauges elles-mêmes.

   Il est en `float` et non en grille : la ligne du libellé est un
   paragraphe, et l'enrouler autour du médaillon garde le nom sur
   la même ligne de base sans toucher à la structure des jauges —
   celle-ci porte le chronomètre d'un duel, ce n'est pas l'endroit
   où l'on remanie une mise en page.
   ------------------------------------------------------------ */
.hp__tete {
  float: left;
  width: 26px; height: 26px;
  margin-right: .4rem;
  border-radius: 50%;
  overflow: hidden;
  background: var(--nuit);
  border: 1.5px solid var(--laiton);
  line-height: 0;
}
.hp__side--foe .hp__tete { float: right; margin-right: 0; margin-left: .4rem; }
.hp__tete:empty { display: none; }
.hp__tete .visuel {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: none;
}
/* Celui qui encaisse le coup blanchit une fraction de seconde, comme son
   combattant : la jauge, le personnage et le visage disent la même chose
   au même instant. */
.hp__side--hit .hp__tete { border-color: var(--alerte-c); }
.hp__track {
  position: relative;
  height: 14px;
  border-radius: 7px;
  background: #0E0F0A;
  border: 1px solid rgba(0,0,0,.6);
  overflow: hidden;
  margin: 3px 0;
}

/* ------------------------------------------------------------
   LA BARRE FANTÔME
   ------------------------------------------------------------
   La barre claire, derrière la barre de couleur, reçoit la même
   largeur au même instant — et la rejoint deux dixièmes plus tard,
   lentement. L'écart entre les deux DESSINE la tranche perdue.

   C'est la signature de tous les jeux de combat, et c'est la
   différence entre « un nombre a changé » et « j'ai pris un coup ».
   Sans elle, un joueur qui lit la question ne voit rien passer.
   ------------------------------------------------------------ */
.hp__ghost {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 100%;
  background: rgba(217, 207, 168, .5);
  transition: width .5s var(--ease) .22s;
}
/* La jauge adverse se vide par la gauche : son fantôme doit donc être
   accroché à droite, sinon il découvre la tranche du mauvais côté. */
.hp__side--foe .hp__ghost { left: auto; right: 0; }

.hp__fill {
  position: relative;
  height: 100%;
  width: 100%;
  /* Rapide, presque sec : c'est le fantôme qui porte la lenteur. Les deux
     lents, on ne voit plus l'écart ; les deux rapides, il n'y a plus d'écart. */
  transition: width .13s linear;
}
.hp__fill--me { background: linear-gradient(180deg, #9BAE45, var(--vert-ok)); }
/* LA COULEUR D'UNITÉ — pièce de paquetage.
   Votre jauge prend la teinte du corps que vous portez. Celle de
   l'adversaire NE CHANGE PAS : deux jauges de la même famille de couleur ne
   se distingueraient plus d'un coup d'œil, et c'est précisément ce qu'on
   demande à ces deux barres en plein duel. */
.combat--unite .hp__fill--me {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--unite) 72%, #FFFFFF),
    var(--unite));
}
.combat--unite .arene__cote::before {
  background: radial-gradient(circle,
    color-mix(in srgb, var(--unite) 34%, transparent), transparent 68%);
}
/* LA JAUGE ADVERSE EST HACHURÉE, et la mienne ne l'est jamais.

   Deux raisons, et la seconde ne s'est révélée qu'en regardant une capture.

   La première : vert contre rouge est la pire paire possible pour un œil
   deutéranope, et c'est celle que ce jeu servait depuis le début à deux
   barres qu'il faut distinguer en une fraction de seconde.

   La seconde : la couleur d'unité teinte MA jauge de la couleur de mon
   corps — et celle des pompiers est un orange brûlé qui tombe juste à côté
   du rouge d'en face. Le commentaire d'à côté disait qu'on ne teintait pas
   la jauge adverse pour éviter deux couleurs voisines ; il n'avait pas vu
   que la teinte du joueur pouvait s'en approcher toute seule.

   Une hachure ne dépend d'aucune des deux couleurs. Elle tient donc pour
   les six corps, et pour ceux qui viendront. */
.hp__fill--foe {
  background:
    repeating-linear-gradient(115deg,
      rgba(255, 255, 255, .11) 0 3px, transparent 3px 7px),
    linear-gradient(180deg, #9E2A1A, var(--sang));
  margin-left: auto;
}
.hp__num { font-family: var(--chiffre); font-size: var(--fs-xs); color: var(--sable-b); }
.hp__vs {
  font-family: var(--display);
  font-size: var(--fs-sm);
  color: var(--kaki);
  letter-spacing: .1em;
}

/* Secousse au moment où l'on encaisse : le retour physique du coup. */
.hp__side--hit { animation: secousse .3s; }
@keyframes secousse {
  25% { transform: translateX(-5px); }
  75% { transform: translateX(5px); }
}

/* ============================================================
   L'ARÈNE
   ------------------------------------------------------------
   Les deux écussons de grade, face à face, dans le tiers d'écran
   qui était vide. On n'invente aucun dessin : c'est l'objet
   signature du jeu, et le montrer ici rappelle à chaque question
   ce qu'on défend.

   Ils sont inclinés l'un vers l'autre de sept degrés. Posés
   d'aplomb, ils seraient deux vignettes côte à côte ; inclinés,
   ils s'affrontent.
   ============================================================ */
.arene {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* CALÉS EN BAS, pas au milieu. Les deux combattants se tiennent sur une
     ligne de sol commune : centrés verticalement, un personnage court et un
     personnage haut sembleraient flotter à deux étages différents. */
  align-items: end;
  justify-items: center;
  min-height: 0;
  overflow: hidden;
}
.arene__cote {
  position: relative;
  display: grid;
  place-items: end center;
  height: 100%;
  width: 100%;
  /* Le rectangle du côté n'est PAS carré : c'est celui d'un corps debout. */
  min-height: 0;
}

/* ------------------------------------------------------------
   LE COMBATTANT EN PIED
   ------------------------------------------------------------
   Sa taille est donnée en HAUTEUR, et cette hauteur est celle de
   la rangée `1fr` de l'écran de combat — celle qui absorbe ce qui
   reste. Un personnage dimensionné ainsi ne peut RIEN repousser :
   sur un écran court il rapetisse, il ne déborde pas. C'est la
   seule façon sûre d'ajouter de la hauteur à cet écran-là.

   LA PROFONDEUR. L'adversaire est rendu plus petit et posé plus
   haut : il est plus loin. Deux silhouettes de même taille côte à
   côte donnent un écran de sélection de personnage, pas un duel.
   ------------------------------------------------------------ */
.arene__perso {
  display: block;
  line-height: 0;
  /* Le plafond suit la HAUTEUR DE L'ÉCRAN et non un nombre fixe : deux
     cent cinquante pixels sur un téléphone de 640 font plus du tiers de
     la page, avant même la question et les quatre réponses. */
  height: min(100%, 30vh);
  filter: drop-shadow(0 8px 10px rgba(0, 0, 0, .55));
  animation: campe 3.6s ease-in-out infinite;
  transform-origin: bottom center;
}
.arene__perso:empty { display: none; }
.arene__perso .visuel {
  display: block;
  height: 100%; width: auto;
  filter: none;
}
/* L'IMAGE SOURCE REGARDE À DROITE. Le joueur est à gauche et regarde donc
   déjà son adversaire ; l'adversaire est la MÊME image retournée. Un seul
   fichier sert les deux camps — c'est pour cela que rien, dans ces douze
   dessins, ne doit se lire de gauche à droite. */
.arene__cote--foe .arene__perso {
  transform: scaleX(-1);
  /* Plus petit ET plus haut : c'est ce qui le place en retrait. */
  height: min(82%, 25vh);
  align-self: start;
  animation-name: campe-foe;
  /* Décalé d'une demi-période : deux respirations à l'unisson ressemblent à
     une animation, pas à deux adversaires. */
  animation-delay: -1.8s;
}

/* ------------------------------------------------------------
   LA COURONNE DU PAQUETAGE
   ------------------------------------------------------------
   Six couronnes ont été rendues pour encadrer un portrait — et
   la mesure a tranché contre : leur diamètre intérieur va de
   43 % à 61 % de leur largeur, si bien qu'il aurait fallu les
   servir entre 76 et 108 pixels autour d'un médaillon de 44.
   Un cadre deux fois plus grand que ce qu'il encadre, sur la
   barre d'identité, et illisible à cette taille.

   Elles se posent donc DERRIÈRE le combattant, où elles ont
   170 pixels et cinq manches pour se voir. C'est le meilleur
   emplacement possible d'un achat : celui où l'adversaire le
   voit aussi.
   ------------------------------------------------------------ */
.arene__couronne {
  position: absolute;
  left: 50%;
  top: 6%;
  width: min(80%, 170px);
  aspect-ratio: 1;
  transform: translateX(-50%);
  line-height: 0;
  z-index: -1;          /* derrière le personnage, devant l'aura */
  opacity: .82;         /* elle accompagne le combattant, elle ne le dispute pas */
  pointer-events: none;
}
.arene__couronne:empty { display: none; }
.arene__couronne .visuel {
  width: 100%; height: 100%;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, .7));
}
/* Sans personnage — lot non chargé — la couronne n'a plus rien à entourer :
   elle disparaît plutôt que de flotter derrière un médaillon. */
.arene__cote:not(:has(.arene__perso .visuel)) .arene__couronne { display: none; }

/* L'OMBRE AU SOL. Sans elle, un personnage détouré est un autocollant posé
   sur l'écran ; avec elle, il se tient quelque part. */
.arene__cote:has(.arene__perso .visuel)::after {
  content: "";
  position: absolute;
  bottom: 2px;
  width: 46%; height: 9px;
  border-radius: 50%;
  background: radial-gradient(ellipse, rgba(0, 0, 0, .55), transparent 70%);
  pointer-events: none;
}
/* L'adversaire est posé en haut : son ombre suit SES pieds, qui sont à 82 %
   de la hauteur du cadre, pas au bas de celui-ci. */
.arene__cote--foe:has(.arene__perso .visuel)::after {
  bottom: auto;
  top: calc(82% - 5px);
  width: 38%; height: 7px;
}
/* Le visage occupe le médaillon, l'écusson se pose sur son coin bas. La
   même grammaire que la plaque d'identité et que l'échelle des grades :
   celui qui la voit trois fois n'a plus à l'apprendre. */
.arene__tete {
  display: block;
  width: 100%; aspect-ratio: 1;
  border-radius: 50%;
  overflow: hidden;
  background: var(--nuit);
  border: 3px solid var(--laiton);
  box-shadow: inset 0 0 18px rgba(0, 0, 0, .6);
}
.arene__tete .visuel { width: 100%; height: 100%; object-fit: cover; filter: none; }

.arene__ins {
  position: absolute;
  bottom: -6px;
  width: 42%;
  line-height: 0;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .8));
}
/* Chacun porte son galon du côté opposé à l'autre : les deux écussons au
   même endroit se retrouveraient au milieu, là où jaillissent les dégâts. */
.arene__cote .arene__ins { left: -8%; }
.arene__cote--foe .arene__ins { left: auto; right: -8%; }
.arene__ins .ecusson,
.arene__ins .visuel { display: block; width: 100%; height: auto; }

/* LE MÉDAILLON S'EFFACE DEVANT LE PERSONNAGE. Les deux sont dans le HTML —
   l'un est le repli de l'autre — mais les afficher ensemble donnerait une
   tête flottante devant un corps qui en a déjà une. */
.arene__cote:has(.arene__perso .visuel) .arene__tete { display: none; }

/* L'ÉCUSSON DE GRADE, une fois le personnage là, se pose à SES PIEDS et
   vers l'extérieur. Les 42 % de largeur d'avant valaient pour un médaillon
   de 150 pixels ; rapportés à une demi-colonne d'écran, ils donneraient un
   galon plus large que le combattant. */
.arene__cote:has(.arene__perso .visuel) .arene__ins {
  width: clamp(26px, 17%, 46px);
  bottom: 0; left: 6%;
}
.arene__cote--foe:has(.arene__perso .visuel) .arene__ins {
  bottom: auto; top: calc(82% - 16px);
  left: auto; right: 6%;
}

/* Quand le portrait manque — lot non livré, image en cours de chargement —
   l'écusson reprend sa place au centre. Aucun trou, jamais. */
.arene__cote:not(:has(.arene__tete .visuel)) .arene__tete { display: none; }
.arene__cote:not(:has(.arene__tete .visuel)):not(:has(.arene__perso .visuel)) .arene__ins {
  position: static; width: 100%;
}

/* LE REPLI SANS PERSONNAGE — tant que les tenues ne sont pas chargées, le
   médaillon reprend la mise en page qu'il avait : cadré sur sa largeur,
   incliné, respirant. C'est l'ancien écran, intact. */
.arene__cote:not(:has(.arene__perso .visuel)) {
  place-items: center;
  width: clamp(64px, min(32vw, 20vh), 150px);
  transform: rotate(-7deg);
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, .55));
  animation: respire 3.4s ease-in-out infinite;
}
.arene__cote--foe:not(:has(.arene__perso .visuel)) {
  animation-name: respire-foe;
  animation-delay: -1.7s;
}
@keyframes respire {
  50% { transform: rotate(-7deg) scale(1.045); }
}
@keyframes respire-foe {
  0%, 100% { transform: rotate(7deg) scale(1); }
  50% { transform: rotate(7deg) scale(1.045); }
}

/* ------------------------------------------------------------
   LA RESPIRATION DU COMBATTANT
   ------------------------------------------------------------
   Un personnage parfaitement immobile pendant treize secondes
   passe pour une image cassée. Ce n'est pas une animation : c'est
   le minimum qui distingue « en vie » de « figé ».

   Le point d'ancrage est en BAS : un corps qui grandit depuis son
   centre décolle du sol à chaque inspiration.
   ------------------------------------------------------------ */
@keyframes campe {
  0%, 100% { transform: translateY(0) scaleY(1); }
  50% { transform: translateY(-2px) scaleY(1.012); }
}
@keyframes campe-foe {
  0%, 100% { transform: scaleX(-1) translateY(0) scaleY(1); }
  50% { transform: scaleX(-1) translateY(-2px) scaleY(1.012); }
}

/* LE COUP ENCAISSÉ. Le combattant recule et blanchit une fraction de
   seconde. C'est le seul retour visuel qui dise QUI a pris le coup avant
   même que la jauge n'ait fini de descendre. */
.arene__cote--touche .arene__perso {
  animation: encaisse .34s var(--ease);
}
.arene__cote--foe.arene__cote--touche .arene__perso {
  animation-name: encaisse-foe;
}
@keyframes encaisse {
  0% { transform: translateX(0); filter: brightness(1); }
  22% { transform: translateX(-9px); filter: brightness(2.4) saturate(.2); }
  100% { transform: translateX(0); filter: brightness(1); }
}
@keyframes encaisse-foe {
  0% { transform: scaleX(-1) translateX(0); filter: brightness(1); }
  22% { transform: scaleX(-1) translateX(-9px); filter: brightness(2.4) saturate(.2); }
  100% { transform: scaleX(-1) translateX(0); filter: brightness(1); }
}

/* Une lueur derrière chaque combattant, aux couleurs de sa jauge : elle relie
   le combattant à sa barre de vie sans un mot d'explication. */
.arene__cote::before {
  content: "";
  position: absolute;
  bottom: -12%;
  width: 128%; aspect-ratio: 1;
  border-radius: 50%;
  /* Assez large pour déborder de la silhouette : une lueur contenue dans le
     personnage ne se voit pas, elle se confond avec lui. */
  background: radial-gradient(circle, rgba(122, 138, 53, .3), transparent 62%);
  z-index: -1;
  pointer-events: none;
}
.arene__cote--foe::before {
  bottom: auto; top: -6%;
  background: radial-gradient(circle, rgba(158, 42, 26, .32), transparent 62%);
}
.arene__cote:not(:has(.arene__perso .visuel))::before {
  bottom: auto; top: auto;
  width: 175%;
}

/* ------------------------------------------------------------
   L'AURA
   ------------------------------------------------------------
   Zéro octet d'image : quelques grains qui montent derrière le
   combattant, dans la teinte de l'aura achetée.

   Elle est DERRIÈRE le personnage (`z-index: -1`) et jamais
   devant : une particule qui passe sur un visage ne se lit plus
   comme une atmosphère, elle se lit comme une salissure.
   ------------------------------------------------------------ */
.arene__aura {
  position: absolute;
  /* RESSERRÉE SUR LE COMBATTANT, pas étalée sur la demi-colonne. Livrée à
     toute la largeur du côté, l'aura semait des braises jusque dans le coin
     de l'écran : on ne lisait plus « ce joueur brûle », on lisait « il y a
     des points orange qui traînent ». Une aura doit émaner de quelqu'un. */
  inset: 12% 24% 0;
  z-index: -1;
  overflow: visible;
  pointer-events: none;
}

/* ------------------------------------------------------------
   CE QUI MANQUAIT : LA LUMIÈRE
   ------------------------------------------------------------
   Une aura n'était QUE des particules — dix-huit grains de quatorze
   pixels dans une colonne de cinq cents. Photographiée, elle donnait
   trois taches orange près des bottes, et le reproche était juste :
   ça ne se lit pas comme une aura, ça se lit comme des pixels perdus.

   Ce que trois cents coins doivent acheter, c'est une DIFFÉRENCE
   VISIBLE À L'INSTANT. Elle tient en deux couches qui ne coûtent pas
   un octet de plus :

     · un halo derrière le combattant, dans la teinte, qui respire ;
     · une flaque de lumière au sol, sous ses bottes.

   La flaque fait double emploi, et c'est délibéré : elle ancre le
   personnage au sol. Sans elle, l'adversaire de l'arène semble
   flotter — ce qu'on voit très bien sur la photo d'avant.

   Les particules, elles, restent le DÉTAIL qu'on remarque en second.
   C'est le bon ordre : la lumière porte de loin, le grain récompense
   le regard qui s'attarde. */
.arene__aura::before,
.arene__aura::after {
  content: "";
  position: absolute;
  left: 50%;
  translate: -50% 0;
  pointer-events: none;
}
/* LE HALO — centré sur le TORSE, et plus haut que large.

   Premier réglage : un cercle posé à 8 % du bas. Mesuré puis photographié,
   il faisait bien 244 pixels — mais son centre tombait sur les bottes,
   c'est-à-dire derrière le seul endroit du personnage qui masque tout. Le
   halo était là et ne se voyait pas.

   Une silhouette debout est haute et étroite : son halo doit l'être aussi,
   sinon il éclaire le sol et le vide de chaque côté. */
.arene__aura::before {
  bottom: 14%;
  width: 240%;
  aspect-ratio: .72;
  background: radial-gradient(ellipse at center,
    color-mix(in srgb, var(--aura, transparent) 34%, transparent) 0%,
    color-mix(in srgb, var(--aura, transparent) 20%, transparent) 34%,
    color-mix(in srgb, var(--aura, transparent) 7%, transparent) 58%,
    transparent 76%);
  animation: aura-respire 4.6s ease-in-out infinite;
}
/* LA FLAQUE — une ellipse écrasée, posée sous les bottes. */
.arene__aura::after {
  bottom: -2%;
  width: 150%;
  height: 13%;
  border-radius: 50%;
  background: radial-gradient(ellipse at center,
    color-mix(in srgb, var(--aura, transparent) 62%, transparent) 0%,
    color-mix(in srgb, var(--aura, transparent) 22%, transparent) 45%,
    transparent 72%);
  filter: blur(2px);
  animation: aura-flaque 4.6s ease-in-out infinite;
}
@keyframes aura-respire {
  0%, 100% { transform: scale(.92); opacity: .78; }
  50%      { transform: scale(1.06); opacity: 1; }
}
@keyframes aura-flaque {
  0%, 100% { transform: scaleX(.9); opacity: .7; }
  50%      { transform: scaleX(1.08); opacity: 1; }
}
/* Sans aura, aucune lumière : `transparent` dans un `color-mix` donne bien
   du transparent, mais on retire les couches plutôt que de peindre du vide. */
.arene__aura:not([style*="--aura"])::before,
.arene__aura:not([style*="--aura"])::after { display: none; }
/* CHAQUE GRAIN EST UNE VRAIE PARTICULE, pas un point de couleur.
   Une braise a un cœur blanc et des bords qui s'effritent, un cristal de
   givre a six branches, un éclair a des ramifications. Un disque de 4 px
   coloré n'a rien de tout cela — il ressemblait à un bug d'affichage plus
   qu'à une atmosphère.

   Les cinq fichiers pèsent 18 Ko à eux tous : c'est le lot le plus léger du
   jeu, pour l'effet le plus visible. */
.arene__aura i {
  position: absolute;
  bottom: 0;
  /* VINGT-DEUX PIXELS, ET UNE ÉCHELLE PROPRE À CHAQUE GRAIN.
     À quatorze, tous identiques, on obtenait une pluie régulière de
     confettis : la régularité est exactement ce qui fait « effet
     d'interface » plutôt que « matière ». Une braise proche est grosse,
     une braise lointaine est petite ; c'est ce que `--s` fabrique. */
  width: 22px; height: 22px;
  scale: var(--s, 1);
  background: var(--grain, none) center / contain no-repeat;
  opacity: 0;
  animation: monte 3.8s linear infinite;
  will-change: transform, opacity;
}
/* Tant que l'image n'est pas là — premier lancement hors connexion — on
   retombe sur le point de couleur d'avant. Une aura payée ne disparaît
   jamais complètement. */
.arene__aura:not([style*="--grain"]) i {
  width: 4px; height: 4px;
  border-radius: 50%;
  background: var(--aura, transparent);
}
/* LA MONTÉE — plus haute, et elle DÉRIVE.
   Cent cinquante pixels dans une colonne de cinq cents, c'est un tiers :
   les braises s'éteignaient au niveau des genoux. Elles montent
   maintenant jusqu'aux épaules, et `--dx` les fait partir de côté comme
   le fait un air chaud. Une trajectoire strictement verticale se lit
   comme une animation ; une dérive se lit comme de la chaleur. */
@keyframes monte {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  14%  { opacity: .95; }
  70%  { opacity: .65; }
  100% { transform: translate(calc(var(--dx, 0) * 1px), -270px) rotate(20deg); opacity: 0; }
}

/* GIVRE — les cristaux TOMBENT. Une aura de gel qui monte serait de la
   vapeur, pas du froid. */
.arene__aura--givre i {
  bottom: auto; top: 0;
  animation-name: tombe;
  animation-duration: 5.2s;
}
@keyframes tombe {
  0%   { transform: translate(0, 0) rotate(0); opacity: 0; }
  16%  { opacity: .95; }
  100% { transform: translate(calc(var(--dx, 0) * .6px), 300px) rotate(240deg); opacity: 0; }
}
/* ORAGE — pas de trajet, des éclats brefs et secs, sur toute la hauteur.
   Deux battements par cycle au lieu d'un : un seul éclair toutes les deux
   secondes et demie se rate en clignant des yeux. */
.arene__aura--orage i {
  bottom: auto;
  top: calc(8% + var(--y, 0) * 1.1%);
  width: 20px; height: 36px;
  animation: claque 2.4s steps(1) infinite;
}
@keyframes claque {
  0%, 78%   { opacity: 0; }
  79%, 82%  { opacity: 1; }
  83%, 87%  { opacity: 0; }
  88%, 90%  { opacity: .9; }
  91%, 100% { opacity: 0; }
}
/* L'orage éclaire ce qu'il touche : le halo bat AVEC les éclairs au lieu
   de respirer, sinon la teinte reste sage pendant que le ciel claque. */
.arene__aura--orage::before { animation: aura-eclaire 2.4s steps(1) infinite; }
@keyframes aura-eclaire {
  0%, 78%   { opacity: .5; transform: scale(.95); }
  79%, 83%  { opacity: 1;  transform: scale(1.12); }
  84%, 100% { opacity: .5; transform: scale(.95); }
}
/* OR — un éclat qui grandit et s'efface au lieu de monter. C'est la plus
   chère : elle doit se distinguer des quatre autres de loin, et un
   scintillement qui pulse au centre se remarque plus qu'une poussière
   dorée de plus. */
.arene__aura--or i {
  bottom: 26%;
  width: 64px; height: 64px;
  animation: onde 3s ease-out infinite;
}
@keyframes onde {
  0%   { transform: scale(.2) rotate(0deg); opacity: 0; }
  12%  { opacity: .95; }
  100% { transform: scale(2.6) rotate(90deg); opacity: 0; }
}
/* L'or ne respire pas, il PULSE — c'est la plus chère des six, elle doit
   se distinguer des cinq autres avant même qu'on lise son nom. */
.arene__aura--or::before {
  animation-duration: 3s;
  animation-timing-function: cubic-bezier(.2, .8, .3, 1);
}
.arene__aura--or::after { animation-duration: 3s; }

/* Un joueur qui a demandé moins de mouvement en a fini avec les particules :
   l'aura reste, immobile et discrète, mais elle ne bouge plus. */
@media (prefers-reduced-motion: reduce) {
  .arene__perso { animation: none; }
  .arene__aura i { animation: none; opacity: .3; }
  .arene__cote--touche .arene__perso { animation: none; }
}

/* ------------------------------------------------------------
   LES NOMBRES QUI JAILLISSENT
   Le joueur n'a pas à lire une jauge pour savoir ce qu'il vient
   de prendre. Ils partent du côté qui encaisse.
   ------------------------------------------------------------ */
.arene__chocs { position: absolute; inset: 0; pointer-events: none; }
.choc {
  position: absolute;
  top: 46%;
  font-family: var(--display);
  font-size: 1.65rem;
  letter-spacing: .02em;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .7);
  animation: jaillit .9s var(--ease) forwards;
  will-change: transform, opacity;
}
.choc--moi { left: 16%; color: var(--alerte-c); }
.choc--foe { right: 16%; color: var(--laiton-c); }
.choc--fort { font-size: 2.35rem; }
@keyframes jaillit {
  0%   { opacity: 0; transform: translateY(8px) scale(.65); }
  16%  { opacity: 1; transform: translateY(-5px) scale(1.14); }
  28%  { transform: translateY(-9px) scale(1); }
  100% { opacity: 0; transform: translateY(-50px) scale(.96); }
}

/* ------------------------------------------------------------
   LA SECOUSSE D'ÉCRAN
   Trois intensités, pas un continuum : au-delà de trois, l'œil ne
   fait plus la différence, et le code devient un réglage sans fin.
   L'amplitude passe par une variable — un seul jeu d'images clés
   pour les trois, donc une seule courbe à corriger.
   ------------------------------------------------------------ */
@keyframes choc-ecran {
  0%   { transform: translate3d(0, 0, 0); }
  14%  { transform: translate3d(calc(var(--amp) * -1), calc(var(--amp) * .38), 0); }
  31%  { transform: translate3d(var(--amp), calc(var(--amp) * -.28), 0); }
  52%  { transform: translate3d(calc(var(--amp) * -.52), 0, 0); }
  74%  { transform: translate3d(calc(var(--amp) * .3), 0, 0); }
  100% { transform: translate3d(0, 0, 0); }
}
.secousse--faible { --amp: 3px;  animation: choc-ecran .26s var(--ease); }
.secousse--moyen  { --amp: 6px;  animation: choc-ecran .32s var(--ease); }
.secousse--fort   { --amp: 11px; animation: choc-ecran .42s var(--ease); }

.question { display: grid; align-content: center; gap: .4rem; min-height: 0; }
.question__tag { font-size: var(--fs-xs); letter-spacing: .16em; color: var(--laiton); }
.question__text {
  font-family: var(--display);
  font-size: clamp(1.25rem, 5.6vw, 1.85rem);
  font-weight: 400;
  line-height: 1.18;
  letter-spacing: .01em;
  color: var(--sable);
}

.answers { display: grid; gap: .5rem; }

/* ============================================================
   LE POURQUOI, ENTRE DEUX QUESTIONS
   ------------------------------------------------------------
   Les sept mille questions de la banque portent leur explication,
   et aucune n'était montrée nulle part. On apprenait quelle était
   la bonne réponse, jamais pourquoi — ce qui fait d'un quiz de
   culture un simple tirage.

   Elle s'ajoute SOUS les réponses et les estompe sans les cacher :
   leur couleur est ce qui dit qui avait raison, et l'explication
   ne vaut que lue à côté d'elle.

   L'arsenal reste jouable et à l'écran pendant ce temps-là —
   mesuré sur 360×640, le plus petit téléphone courant : l'arène
   se resserre d'autant, rien ne déborde.
   ============================================================ */
.answers--efface { opacity: .35; pointer-events: none; }
.pourquoi {
  margin-top: .6rem;
  padding: .7rem .85rem;
  border-radius: var(--r);
  border-left: 3px solid var(--laiton);
  background: var(--toile-2);
  color: var(--sable);
  font-size: var(--fs-sm);
  line-height: 1.45;
  animation: pourquoi-monte .22s var(--ease);
}
@keyframes pourquoi-monte {
  from { opacity: 0; transform: translateY(6px); }
}
@media (prefers-reduced-motion: reduce) {
  .pourquoi { animation: none; }
}
.answer {
  display: flex;
  align-items: center;
  gap: .75rem;
  width: 100%;
  min-height: var(--tap);
  padding: .6rem .9rem;
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: linear-gradient(180deg, var(--toile-2), var(--toile));
  color: var(--sable);
  font-size: var(--fs-sm);
  text-align: left;
  box-shadow: inset 0 1px 0 rgba(217,207,168,.06);
  /* Le retour visuel part AVANT la réponse du serveur : un jeu qui
     semble lent est un jeu qu'on quitte. */
  transition: background var(--fast), border-color var(--fast), transform var(--fast);
}
.answer:active { transform: scale(.985); background: var(--toile-2); }
.answer__key {
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  flex: 0 0 28px;
  border-radius: var(--r-sm);
  background: var(--nuit);
  border: 1px solid var(--kaki);
  font-family: var(--display);
  font-size: var(--fs-xs);
  color: var(--laiton);
}
.answer--picked { border-color: var(--laiton); }
.answer--good {
  border-color: var(--vert-ok);
  background: linear-gradient(180deg, #3A431C, #2B3115);
  /* Une bonne réponse GONFLE brièvement. Un simple changement de couleur se
     confond avec l'état « appuyé » : il faut un mouvement pour que la
     récompense se distingue du simple accusé de réception. */
  animation: bonne .4s var(--ease);
}
@keyframes bonne {
  38% { transform: scale(1.035); }
}
.answer--bad {
  border-color: var(--sang);
  background: linear-gradient(180deg, #43211A, #2E1712);
  /* Trois allers-retours de six pixels : le geste universel du refus. Il
     dure moins d'un tiers de seconde — assez pour être vu, trop court pour
     retarder la manche suivante. */
  animation: mauvaise .34s;
}
@keyframes mauvaise {
  0%, 100% { transform: translateX(0); }
  16% { transform: translateX(-6px); }
  38% { transform: translateX(6px); }
  60% { transform: translateX(-4px); }
  82% { transform: translateX(3px); }
}
.answer--out { opacity: .28; pointer-events: none; }

/* ============================================================
   ACCESSIBILITÉ DU MOUVEMENT
   ------------------------------------------------------------
   Tout ce qui précède est du mouvement ajouté pour le plaisir.
   Un joueur sujet au mal des transports ne doit pas avoir à
   choisir entre jouer et se sentir mal : on garde les COULEURS,
   qui portent l'information, et on retire les DÉPLACEMENTS, qui
   ne portent que le plaisir.

   `--fast` et `--mid` sont déjà mis à zéro dans tokens.css : les
   transitions sont donc couvertes. Restent les animations, qui
   n'écoutent pas ces variables.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .secousse--faible, .secousse--moyen, .secousse--fort,
  .answer--good, .answer--bad, .hp__side--hit,
  .arene__cote, .arene__cote--foe, .timer__fill--urgent { animation: none; }
  /* Le nombre de dégâts reste, immobile : il porte une information que la
     jauge met une demi-seconde à donner. */
  .choc { animation: paraitre .9s linear forwards; }
  @keyframes paraitre { 0%, 72% { opacity: 1; } 100% { opacity: 0; } }
  .hp__ghost { transition: none; }
}

.arsenal {
  display: flex;
  gap: .4rem;
  overflow-x: auto;
  padding-bottom: max(.25rem, var(--safe-b));
  scrollbar-width: none;
}
.arsenal::-webkit-scrollbar { display: none; }
.arm {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  min-width: var(--tap);
  min-height: var(--tap);
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: var(--toile);
  font-size: 1.15rem;
  position: relative;
}
.arm[disabled] { opacity: .28; }
/* ============================================================
   LE CHÂSSIS — un seul relief pour tout le jeu
   ------------------------------------------------------------
   Chaque écran avait son dessin de panneau : une bordure ici, une
   ombre là, un dégradé ailleurs. Rien de faux — mais rien de
   commun, et un jeu se reconnaît à ce qui se répète.

   Ces neuf familles couvrent toutes les surfaces sur lesquelles
   on appuie ou qui portent une information. Elles partagent
   désormais la même tôle : arête claire en haut, arête sombre en
   bas, et une lèvre qui les POSE sur le fond au lieu de les y
   coller.

   Ce qui est ENFONCÉ prend le relief inverse : les pistes de
   jauges et les champs de saisie sont des creux, pas des reliefs,
   et les traiter pareil aplatit tout.
   ============================================================ */
.horloge,
.corps,
.medaille,
.grade-ligne,
.salon,
.paquet,
.arm,
.tampons__case,
.sous-onglets { box-shadow: var(--relief); }

.hp__track,
.timer,
.galons__piste,
.field { box-shadow: var(--relief-in); }

/* ============================================================
   LE CHOIX DU CORPS
   ------------------------------------------------------------
   Une barre qui défile plutôt qu'une liste déroulante : le choix
   reste VISIBLE, et changer d'avis coûte un geste au lieu de
   trois. Sept entrées ne tiennent pas dans 412 pixels — c'est
   assumé, le débordement horizontal fait partie du dessin et dit
   qu'il y en a d'autres.
   ============================================================ */
.corps-barre {
  display: flex;
  gap: .35rem;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* La barre déborde volontairement de la gouttière de l'écran, pour que le
     premier et le dernier bouton ne semblent pas coupés par le bord. */
  margin: 0 calc(var(--pad) * -1);
  padding: .15rem var(--pad) .3rem;
  scrollbar-width: none;
}
.corps-barre::-webkit-scrollbar { display: none; }

.corps {
  flex: 0 0 auto;
  scroll-snap-align: center;
  display: grid;
  justify-items: center;
  gap: 3px;
  min-width: 62px;
  padding: .45rem .3rem .35rem;
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: var(--toile);
  color: var(--sable-b);
  transition: border-color var(--fast) var(--ease), background var(--fast) var(--ease);
}
.corps__ico { display: grid; place-items: center; line-height: 0; }
/* L'emblème rendu, quand il existe. Les non-choisis sont désaturés et
   assombris : sept écussons émaillés allumés à la fois feraient une
   guirlande, et on ne verrait plus lequel est retenu. */
.corps__ico .visuel {
  width: 30px; height: 30px;
  filter: saturate(.45) brightness(.72);
  transition: filter .16s var(--ease);
}
.corps--on .corps__ico .visuel { filter: none; }
.corps__lbl {
  font-size: .5625rem;      /* 9 px — une étiquette, pas du texte courant */
  letter-spacing: .06em;
  white-space: nowrap;
}
/* Seul le corps retenu porte sa teinte. Sept emblèmes de sept couleurs
   allumés en même temps feraient une guirlande, et plus rien ne dirait
   lequel est choisi. */
.corps--on {
  border-color: var(--teinte, var(--laiton));
  background: var(--toile-2);
  color: var(--sable);
  /* Le relief du châssis EST REPRIS ici : `box-shadow` remplace, il ne
     complète pas. Sans le recopier, le corps choisi serait le seul bouton
     plat de la barre — exactement l'inverse de ce qu'on veut dire. */
  box-shadow:
    var(--relief),
    inset 0 0 0 1px rgba(0, 0, 0, .5),
    0 0 12px -6px var(--teinte, var(--laiton));
}
.corps--on .corps__ico { color: var(--teinte, var(--laiton)); }

.corps-etat {
  font-size: var(--fs-xs);
  color: var(--sable-b);
  text-align: center;
  margin: -.1rem 0 .1rem;
}

/* L'emblème du corps dans la liste des salons : il tient la première
   colonne, avant même le nom de l'hôte. */
.salon__corps { display: grid; place-items: center; line-height: 0; color: var(--teinte, var(--sable-b)); }
.salon__corps .visuel { width: 22px; height: 22px; }

.arm__ico { display: grid; place-items: center; line-height: 0; color: var(--sable); }
/* Une image rendue vient avec sa propre lumière : lui ajouter une ombre
   portée la ferait flotter au-dessus du bouton au lieu d'y être posée. On
   se contente d'un très léger contact au sol. */
.visuel { display: block; object-fit: contain;
  filter: drop-shadow(0 1px 1px rgba(0,0,0,.55)); }
.arm__n {
  position: absolute;
  right: 3px; bottom: 1px;
  font-family: var(--chiffre);
  font-size: .7rem;
  color: var(--laiton);
}

.veil {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  background: rgba(10, 11, 6, .93);
}
.veil--smoke { background: rgba(61, 67, 39, .97); }
.veil__count {
  font-family: var(--display);
  font-size: 6rem;
  color: var(--laiton);
  text-shadow: 0 4px 0 rgba(0,0,0,.5);
}
.veil__txt {
  font-family: var(--display);
  font-size: var(--fs-xl);
  letter-spacing: .3em;
  color: var(--sable);
}

/* ============================================================
   VICTOIRE
   ------------------------------------------------------------
   L'écran le plus rentable du jeu : le joueur vient de gagner,
   c'est le seul moment où il accepte volontiers une proposition.
   Le coupon est ici, et nulle part ailleurs — jamais après une
   défaite, jamais devant le bouton « Rejouer ».
   ============================================================ */
.result {
  flex: 1;
  display: grid;
  align-content: center;
  gap: var(--gap);
  padding: var(--pad);
  text-align: center;
}
.result__title {
  font-family: var(--display);
  font-size: var(--fs-3xl);
  letter-spacing: .05em;
  line-height: 1;
  text-shadow: 0 3px 0 rgba(0,0,0,.5);
}
.result--win .result__title { color: var(--laiton-c); }
.result--lose .result__title { color: var(--sable-b); }
.result__hp { font-family: var(--chiffre); font-size: var(--fs-lg); color: var(--sable-b); }

.result__gains { display: flex; justify-content: center; gap: 1.5rem; }
.gain { display: grid; gap: 2px; }
.gain__n {
  font-family: var(--chiffre);
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--laiton-c);
}
.gain__l { font-size: var(--fs-xs); letter-spacing: .1em; color: var(--sable-b); }

.result__unlock {
  padding: var(--pad);
  border-radius: var(--r);
  background: var(--toile);
  border: 1px solid var(--laiton);
}
.result__unlock b { color: var(--laiton-c); }
.result__unlock > div { display: flex; align-items: center; gap: .45rem; }
/* Le tracé ne doit pas se laisser écraser quand la ligne est longue :
   sans `flex-shrink: 0`, un « LIEUTENANT-COLONEL est à ta portée » réduit
   l'étoile à un trait. */
.unlock__ico { display: flex; flex: 0 0 auto; line-height: 0; color: var(--laiton); }

.result__coupon {
  display: grid;
  gap: .5rem;
  padding: var(--pad);
  border-radius: var(--r);
  background: var(--toile-2);
  border: 1px dashed var(--laiton);
}
.result__coupon .code {
  font-family: var(--display);
  font-size: var(--fs-xl);
  letter-spacing: .08em;
  color: var(--laiton-c);
}

/* Le rappel de matricule : une ligne, dans le flux, jamais par-dessus. */
.result__rappel {
  padding: .7rem .9rem;
  border-radius: var(--r);
  border: 1px dashed var(--kaki);
  background: var(--toile);
  color: var(--sable-b);
  font-size: var(--fs-xs);
  line-height: 1.4;
  text-align: left;
}
.result__rappel:active { background: var(--toile-2); }

.result__acts { display: grid; gap: .5rem; margin-top: .5rem; }
.result__acts .combattre { min-height: 60px; }

/* Le vide de l'écran de combat : la question descend vers les réponses
   plutôt que de flotter au milieu. Le regard va des jauges à la question
   puis aux réponses, groupées dans le tiers bas — au lieu de traverser
   deux zones vides. */
#screen-combat .question { align-content: end; padding-bottom: .4rem; }

/* ============================================================
   HORS CONNEXION ET ÉCRANS À VENIR
   ============================================================ */
.offline, .soon {
  flex: 1;
  display: grid;
  align-content: center;
  gap: var(--gap);
  padding: 2rem var(--pad);
  text-align: center;
}
.offline__ico, .soon__ico {
  display: flex; justify-content: center; line-height: 0;
  color: var(--laiton); margin-bottom: .3rem;
}
.offline h1, .soon h1 {
  font-family: var(--display);
  font-size: var(--fs-xl);
  letter-spacing: .06em;
  color: var(--sable);
}
.offline p, .soon p { color: var(--sable-b); font-size: var(--fs-sm); }

/* ============================================================
   ÉCRANS SECONDAIRES — en-tête et corps défilant
   ============================================================ */
.ecran-tete {
  padding: calc(var(--gap) + var(--safe-t)) var(--pad) var(--gap);
  border-bottom: 1px solid var(--kaki);
}
.ecran-tete h1 {
  font-family: var(--display);
  font-size: var(--fs-xl);
  letter-spacing: .05em;
  color: var(--sable);
}
.ecran-tete p { font-size: var(--fs-xs); color: var(--sable-b); margin-top: .15rem; }

.ecran-corps {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--gap) var(--pad) var(--pad);
  display: grid;
  gap: var(--gap);
  align-content: start;
  /* ⚠️ CHAQUE RANGÉE FAIT LA TAILLE DE SON CONTENU, ET RIEN D AUTRE.

     Sans cette ligne, une rangée en taille automatique peut être ÉCRASÉE
     sous la hauteur de son contenu. Il suffit que le bloc qu elle porte
     ait un overflow autre que visible : sa hauteur minimale automatique
     tombe alors à zéro, et la grille se sert de cette permission dès que
     le contenu total dépasse la hauteur de l écran.

     Mesuré sur l Intendance : le tiroir de la roulette demandait 477 px,
     la rangée lui en accordait 181, et son overflow caché coupait le
     reste. La roue apparaissait tranchée au tiers, l arsenal au milieu
     de sa deuxième ligne. Aucune erreur, aucune trace — le navigateur
     applique exactement ce que la feuille demande.

     Ce défaut dormait depuis toujours et ne pouvait pas se voir : il
     exige que le contenu tienne PRESQUE dans l écran. Les tiroirs, en
     repliant cinq mille deux cents pixels en neuf cents, ont créé cette
     condition pour la première fois. La fiche de profil, elle, l avait
     déjà — son bloc CLASSÉ était réduit à une bande. */
  grid-auto-rows: min-content;
}
.ecran-corps .sect {
  font-size: var(--fs-xs);
  letter-spacing: .18em;
  color: var(--laiton);
  margin-top: .5rem;
}
.ecran-corps .vide {
  font-size: var(--fs-sm);
  color: var(--sable-b);
  line-height: 1.5;
}

/* ------------------------------------------------------------
   LA REMISE EN COURS
   Un seul bouton, et il mène au paiement. Le code reste lisible
   en dessous : certains joueurs préfèrent le lire, et le voir
   rassure sur le fait que la remise existe vraiment.
   ------------------------------------------------------------ */
.cpnx {
  display: grid;
  gap: .45rem;
  padding: var(--pad);
  border-radius: var(--r-lg);
  background:
    radial-gradient(circle at 0 46%, transparent 9px, var(--toile-2) 9px) left / 51% 100% no-repeat,
    radial-gradient(circle at 100% 46%, transparent 9px, var(--toile-2) 9px) right / 51% 100% no-repeat;
  border: 1px dashed var(--laiton);
  text-align: center;
}
.cpnx__raison { font-size: var(--fs-xs); color: var(--sable-b); }
.cpnx__val {
  font-family: var(--display);
  font-size: var(--fs-2xl);
  line-height: 1;
  color: var(--laiton-c);
  text-shadow: 0 2px 0 rgba(0,0,0,.4);
}
.cpnx__exp { font-size: var(--fs-xs); color: var(--laiton); letter-spacing: .06em; }
.cpnx__code {
  font-family: var(--chiffre);
  font-size: var(--fs-sm);
  letter-spacing: .14em;
  color: var(--sable-b);
  padding: .3rem;
  cursor: pointer;
}

/* ------------------------------------------------------------
   LA SOLDE
   ------------------------------------------------------------ */
.solde-etat {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: .5rem;
  padding: .7rem;
  border-radius: var(--r);
  background: var(--toile);
  border: 1px solid var(--kaki);
}
.solde-etat__val {
  font-family: var(--display);
  font-size: var(--fs-xl);
  color: var(--laiton-c);
  line-height: 1;
}
.solde-etat__lbl { font-size: var(--fs-xs); color: var(--sable-b); letter-spacing: .1em; }

.paquets { display: grid; grid-template-columns: repeat(3, 1fr); gap: .4rem; }
.paquet {
  position: relative;
  display: grid;
  gap: 1px;
  padding: .8rem .3rem .6rem;
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: var(--toile);
  text-align: center;
  min-height: 92px;
}
/* Le paquet du milieu est celui qui se vend : on le désigne, ce qui
   épargne au joueur la comparaison de trois lignes. */
.paquet--phare {
  border-color: var(--laiton);
  background: linear-gradient(180deg, color-mix(in srgb, var(--laiton) 12%, var(--toile)), var(--toile));
}
.paquet__ruban {
  position: absolute;
  top: -8px; left: 50%;
  transform: translateX(-50%);
  padding: 1px 6px;
  border-radius: 4px;
  background: var(--laiton);
  color: var(--nuit);
  font-size: 0.5625rem;
  font-weight: 800;
  letter-spacing: .06em;
  white-space: nowrap;
}
.paquet__solde {
  font-family: var(--display);
  font-size: var(--fs-lg);
  color: var(--laiton-c);
  line-height: 1;
}
.paquet__lbl { font-size: 0.625rem; color: var(--sable-b); }
.paquet__prix {
  margin-top: .3rem;
  font-family: var(--chiffre);
  font-size: var(--fs-xs);
  color: var(--sable);
}

.coupons { display: grid; gap: .5rem; margin-top: var(--gap); text-align: left; }
.cpn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
  padding: .75rem var(--pad);
  border-radius: var(--r);
  background: var(--toile);
  border-left: 4px solid var(--laiton);
}
.cpn--dead { border-left-color: var(--kaki); opacity: .5; }
.cpn__v { font-weight: 700; color: var(--laiton-c); }
.cpn__e { font-size: var(--fs-xs); color: var(--sable-b); }

/* ============================================================
   ÉCRAN D'INSTALLATION (configuration manquante)
   Volontairement bavard : il ne doit jamais atteindre un joueur,
   seulement la personne qui déploie.
   ============================================================ */
.setup {
  padding: 2rem 1.25rem;
  display: grid;
  gap: 1rem;
  max-width: 40rem;
  margin: 0 auto;
}
.setup h1 { font-family: var(--display); font-size: var(--fs-xl); color: var(--alerte-c); }
.setup code {
  display: block;
  padding: .75rem;
  border-radius: var(--r-sm);
  background: var(--toile);
  font-family: var(--chiffre);
  font-size: var(--fs-xs);
  color: var(--laiton);
  overflow-x: auto;
}
.setup ol { display: grid; gap: .5rem; padding-left: 1.25rem; list-style: decimal; color: var(--sable-b); }

/* Le gain de guerre sur l'écran de résultat. Il arrive APRÈS les autres —
   le règlement est un aller-retour serveur — donc il apparaît, et une
   apparition sans transition se lit comme un défaut d'affichage. */
.gain--guerre { animation: gain-entre .28s ease-out both; }
.gain--guerre .gain__n { color: var(--laiton-c); }
@keyframes gain-entre {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .gain--guerre { animation: none; }
}

/* La pièce dans l'intendance : c'est l'écran où l'on échange de l'argent
   réel contre de la monnaie du jeu. Elle tourne lentement, sans effort —
   assez pour attirer l'œil, pas assez pour tourner autour de la tête. */
.solde-etat__piece {
  width: 46px; height: 46px;
  align-self: center;
  animation: piece-repos 9s ease-in-out infinite;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .55));
}
.solde-etat__piece .visuel { width: 100%; height: 100%; object-fit: contain; }
@keyframes piece-repos {
  0%, 72%, 100% { transform: rotateY(0deg); }
  86%           { transform: rotateY(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .solde-etat__piece { animation: none; }
}

/* ------------------------------------------------------------
   LA LUEUR SUR LE COMBATTANT LUI-MÊME
   ------------------------------------------------------------
   Le dernier cran, et le plus décisif. Halo et grains vivent À CÔTÉ du
   personnage ; tant que le sprite reste peint exactement comme celui de
   l'adversaire, l'œil lit « il y a des braises qui volent », pas « ce
   soldat brûle ».

   Deux ombres portées dans la teinte de l'aura suivent le contour
   découpé du sprite — c'est ce que fait `drop-shadow` sur un PNG à fond
   transparent, et c'est précisément l'effet qu'aucune boîte ne peut
   donner. Une serrée pour le liseré, une large pour la diffusion.

   Réservé au joueur : l'aura de l'adversaire ne voyage pas, et deux
   contours colorés face à face rendraient l'arène illisible.
   ------------------------------------------------------------ */
.arene__cote--aura .arene__perso .visuel {
  filter:
    drop-shadow(0 0 3px color-mix(in srgb, var(--aura, transparent) 85%, transparent))
    drop-shadow(0 0 14px color-mix(in srgb, var(--aura, transparent) 55%, transparent));
  animation: aura-liseré 4.6s ease-in-out infinite;
}
@keyframes aura-liseré {
  0%, 100% { opacity: .93; }
  50%      { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .arene__cote--aura .arene__perso .visuel { animation: none; }
}

/* ============================================================
   L'OBJET DÉGAINÉ
   ------------------------------------------------------------
   Trois des sept ne montraient RIEN : casque, ration et grenade
   ne produisaient qu'un message d'une seconde. Un objet dépensé
   sans rien voir se lit comme un bouton qui n'a pas marché — et
   le joueur, la fois suivante, le garde plutôt que de le
   gaspiller. C'est l'exact inverse de ce qu'on attend d'un
   consommable.

   Une seule animation pour les sept, et c'est la DIRECTION qui
   porte le sens : ce qui part vers l'adversaire fait mal, ce qui
   reste sur soi protège. La grammaire s'apprend en deux duels
   sans qu'on l'explique.
   ============================================================ */
.volee {
  position: absolute;
  left: 50%;
  bottom: 8%;
  width: 54px; height: 54px;
  z-index: 5;
  pointer-events: none;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, .6));
}
.volee .visuel { width: 100%; height: 100%; object-fit: contain; }
.volee--foe { animation: volee-foe .62s cubic-bezier(.3, .1, .2, 1) forwards; }
.volee--moi { animation: volee-moi .55s cubic-bezier(.2, .7, .3, 1) forwards; }

/* Vers l'adversaire : haut et à droite, en tournant, et ça finit petit —
   l'objet s'éloigne. */
@keyframes volee-foe {
  0%   { transform: translate(-50%, 0) scale(.4) rotate(0deg); opacity: 0; }
  18%  { transform: translate(-50%, -10px) scale(1.15) rotate(-8deg); opacity: 1; }
  100% { transform: translate(88px, -190px) scale(.45) rotate(38deg); opacity: 0; }
}
/* Sur soi : l'objet monte à peine, grossit et se fond dans le combattant. */
@keyframes volee-moi {
  0%   { transform: translate(-50%, 20px) scale(.4); opacity: 0; }
  30%  { transform: translate(-50%, -22px) scale(1.3); opacity: 1; }
  100% { transform: translate(-50%, -46px) scale(.9); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .volee { display: none; }
}

/* --- LES EFFETS ENCORE ACTIFS, sous sa propre jauge ---
   Casque et ration attendent le coup suivant. Sans marque, deux manches
   plus tard le joueur ne sait plus s'il a encore son casque — et un
   objet qu'on ne voit pas est un objet qu'on n'utilise plus. */
.hp__effets {
  display: flex;
  gap: 3px;
  min-height: 20px;
  margin-top: 2px;
}
.hp__effet {
  width: 20px; height: 20px;
  border-radius: 4px;
  padding: 1px;
  background: color-mix(in srgb, var(--laiton) 22%, transparent);
  border: 1px solid var(--laiton);
  animation: effet-pose .3s ease-out both, effet-veille 2.2s ease-in-out .3s infinite;
}
.hp__effet .visuel { width: 100%; height: 100%; object-fit: contain; }
@keyframes effet-pose {
  from { transform: scale(.3); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
@keyframes effet-veille {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--laiton) 55%, transparent); }
  50%      { box-shadow: 0 0 6px 1px color-mix(in srgb, var(--laiton) 55%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .hp__effet { animation: none; }
}

/* Le thème révélé par la carte d'état-major : le renseignement doit se
   distinguer de l'étiquette ordinaire, sinon il passe pour elle. */
.qtag--carte { color: var(--laiton-c); animation: qtag-revele .4s ease-out both; }
@keyframes qtag-revele {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* ============================================================
   LES HUIT TRAJECTOIRES DU SECOND LOT
   ------------------------------------------------------------
   Deux auras ne se distinguent JAMAIS par leur seule couleur. Une
   braise orange et une étincelle orange qui montent de la même
   façon sont deux fois le même achat — et la seconde se remarque
   comme un doublon, pas comme une nouveauté.

   Chacune a donc son mouvement. C'est ce qu'on reconnaît de loin,
   avant la teinte et bien avant le nom.
   ============================================================ */

/* CENDRES — elles tombent en tournoyant, sans hâte. Ce qui reste
   quand le feu est passé ne tombe pas droit : il flotte. */
.arene__aura--cendres i {
  bottom: auto; top: 0;
  animation-name: cendre-tombe;
  animation-duration: 6.5s;
}
@keyframes cendre-tombe {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  12%  { opacity: .9; }
  50%  { transform: translate(calc(var(--dx, 0) * 1px), 150px) rotate(160deg); }
  100% { transform: translate(calc(var(--dx, 0) * -.8px), 320px) rotate(320deg); opacity: 0; }
}

/* SABLE — il TRAVERSE. Le seul mouvement horizontal du jeu, et c'est
   ce qui le rend reconnaissable en une demi-seconde. */
.arene__aura--sable i {
  bottom: auto;
  top: calc(10% + var(--y, 0) * 1.1%);
  left: -20%;
  animation-name: sable-traverse;
  animation-duration: 2.6s;
  animation-timing-function: linear;
}
@keyframes sable-traverse {
  0%   { transform: translateX(0); opacity: 0; }
  15%  { opacity: .9; }
  85%  { opacity: .7; }
  100% { transform: translateX(260px); opacity: 0; }
}

/* FEUILLES — elles descendent en pivotant sur elles-mêmes, plus lourdes
   que la cendre et plus lentes que la pluie. */
.arene__aura--feuilles i {
  bottom: auto; top: -8%;
  width: 26px; height: 26px;
  animation-name: feuille-descend;
  animation-duration: 7.5s;
}
@keyframes feuille-descend {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  10%  { opacity: 1; }
  35%  { transform: translate(calc(var(--dx, 0) * 1.2px), 110px) rotate(140deg); }
  70%  { transform: translate(calc(var(--dx, 0) * -1px), 240px) rotate(300deg); }
  100% { transform: translate(0, 350px) rotate(420deg); opacity: 0; }
}

/* ÉTINCELLES — elles JAILLISSENT et meurent. Courtes, sèches, nombreuses :
   l'inverse exact de la braise, qui monte lentement et s'attarde. */
.arene__aura--etincelles i {
  width: 14px; height: 14px;
  animation-name: etincelle-jaillit;
  animation-duration: 1.3s;
}
@keyframes etincelle-jaillit {
  0%   { transform: translate(0, 0) scale(1.3); opacity: 0; }
  8%   { opacity: 1; }
  100% { transform: translate(calc(var(--dx, 0) * 2px), -130px) scale(.3); opacity: 0; }
}

/* VAPEUR — elle s'élève en S'ÉLARGISSANT et perd ses contours. Peu de
   grains, gros et lents : une chaudière, pas un feu de camp. */
.arene__aura--vapeur i {
  width: 40px; height: 40px;
  animation-name: vapeur-monte;
  animation-duration: 5.5s;
}
@keyframes vapeur-monte {
  0%   { transform: translate(0, 0) scale(.4); opacity: 0; }
  20%  { opacity: .55; }
  100% { transform: translate(calc(var(--dx, 0) * 1.4px), -230px) scale(2.2); opacity: 0; }
}

/* BULLES — elles montent en HÉSITANT, de gauche à droite, et accélèrent
   vers la fin comme le fait une bulle qui approche de la surface. */
.arene__aura--bulles i {
  animation-name: bulle-remonte;
  animation-duration: 4.4s;
  animation-timing-function: cubic-bezier(.4, 0, .7, 1);
}
@keyframes bulle-remonte {
  0%   { transform: translate(0, 0) scale(.5); opacity: 0; }
  10%  { opacity: .9; }
  30%  { transform: translate(9px, -70px); }
  55%  { transform: translate(-9px, -150px); }
  80%  { transform: translate(7px, -230px); }
  100% { transform: translate(0, -300px) scale(1.1); opacity: 0; }
}

/* LUCIOLES — elles TOURNENT autour du combattant et ne se posent jamais.
   Le seul mouvement fermé du jeu : rien d'autre ne revient à son point
   de départ, et c'est ce qui la fait remarquer. */
.arene__aura--lucioles i {
  bottom: 30%;
  width: 18px; height: 18px;
  animation-name: luciole-tourne;
  animation-duration: 7s;
  animation-timing-function: linear;
}
@keyframes luciole-tourne {
  0%   { transform: translate(0, 0); opacity: 0; }
  10%  { opacity: 1; }
  25%  { transform: translate(46px, -34px); opacity: .55; }
  50%  { transform: translate(0, -68px); opacity: 1; }
  75%  { transform: translate(-46px, -34px); opacity: .55; }
  90%  { opacity: 1; }
  100% { transform: translate(0, 0); opacity: 0; }
}

/* TRIOMPHE — la plus chère, et la seule qui ORBITE lentement en tournant
   sur elle-même. Une couronne de laurier qui ne retombe pas. */
.arene__aura--triomphe i {
  bottom: 34%;
  width: 30px; height: 30px;
  animation-name: laurier-orbite;
  animation-duration: 9s;
  animation-timing-function: linear;
}
@keyframes laurier-orbite {
  0%   { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  8%   { opacity: 1; }
  25%  { transform: translate(58px, -26px) rotate(90deg); }
  50%  { transform: translate(0, -52px) rotate(180deg); }
  75%  { transform: translate(-58px, -26px) rotate(270deg); }
  92%  { opacity: 1; }
  100% { transform: translate(0, 0) rotate(360deg); opacity: 0; }
}

/* ============================================================
   LES EFFETS D'ARÈNE
   ------------------------------------------------------------
   L'objet dégainé traversait l'écran, et c'était tout : on voyait
   PARTIR le coup, jamais ARRIVER l'effet. Un fusil qui vole puis
   ne fait rien de visible se lit comme une décoration, pas comme
   une action — et le joueur cesse de s'en servir.

   Règle tenue partout ici : rien ne dure plus d'une seconde. Un
   duel se joue en treize secondes par manche ; un effet qui
   s'attarde vole du temps de lecture à la question.
   ============================================================ */

/* --- LE MOT QUI JAILLIT ---
   Il reprend la trajectoire des nombres de dégâts et part du même
   endroit : le joueur a déjà appris à regarder là. */
.marque {
  font-family: var(--pochoir, var(--display));
  font-size: 1.05rem;
  letter-spacing: .08em;
  text-shadow: 0 2px 4px rgba(0, 0, 0, .8);
}
.marque--tir    { color: var(--alerte-c, #F0A090); }
.marque--garde  { color: #A8C8E0; }
.marque--rage   { color: var(--flamme, #E2732B); font-size: 1.5rem; }
.marque--temps  { color: var(--vert-ok, #9FCB6A); }
.marque--vue    { color: var(--sable); font-size: .9rem; }
.marque--fumee  { color: #B9BCC0; }
.marque--vol    { color: var(--alerte-c, #F0A090); }

/* --- LE VOILE BREF SUR UN CÔTÉ --- */
.eclair {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  border-radius: var(--r);
}
.eclair--tir {
  /* Le départ du coup : une lueur chaude au niveau des mains. */
  background: radial-gradient(circle at 50% 62%, rgba(255, 214, 150, .85), transparent 42%);
  animation: eclair-bref .22s ease-out forwards;
}
.eclair--impact {
  background: radial-gradient(circle at 50% 52%, rgba(255, 158, 110, .9), transparent 55%);
  animation: eclair-bref .34s ease-out forwards;
}
.eclair--pare {
  /* Le casque qui encaisse : froid, net, métallique. */
  background: radial-gradient(circle at 50% 48%, rgba(190, 226, 255, .9), transparent 58%);
  animation: eclair-bref .4s ease-out forwards;
}
.eclair--rage {
  background: radial-gradient(circle at 50% 55%, rgba(226, 115, 43, .7), transparent 62%);
  animation: eclair-bref .55s ease-out forwards;
}
@keyframes eclair-bref {
  0%   { opacity: 0; transform: scale(.86); }
  22%  { opacity: 1; transform: scale(1.04); }
  100% { opacity: 0; transform: scale(1.1); }
}

/* --- LE TRACEUR ---
   Le trajet du tir. C'est lui qui manquait le plus : sans trajet,
   la cible encaissait un coup venu de nulle part. */
.tracer {
  position: absolute;
  left: 26%; top: 46%;
  width: 0; height: 3px;
  border-radius: 2px;
  background: linear-gradient(90deg, transparent, rgba(255, 226, 170, .95), rgba(255, 170, 90, .6));
  box-shadow: 0 0 10px rgba(255, 200, 120, .8);
  transform-origin: left center;
  animation: tracer-file .3s cubic-bezier(.2, .6, .3, 1) forwards;
  pointer-events: none;
  z-index: 4;
}
@keyframes tracer-file {
  0%   { width: 0; opacity: 0; }
  15%  { opacity: 1; }
  70%  { width: 48%; opacity: 1; }
  100% { width: 52%; opacity: 0; }
}

/* --- LE DÔME DU CASQUE ---
   Une calotte de lumière qui se referme sur le combattant. */
.dome {
  position: absolute;
  left: 24%; bottom: 6%;
  width: 26%; height: 46%;
  border-radius: 50% 50% 12% 12%;
  border: 2px solid rgba(180, 220, 255, .9);
  background: radial-gradient(ellipse at 50% 80%, rgba(150, 200, 250, .22), transparent 70%);
  box-shadow: 0 0 18px rgba(150, 200, 250, .5) inset;
  animation: dome-ferme .7s ease-out forwards;
  pointer-events: none;
  z-index: 2;
}
@keyframes dome-ferme {
  0%   { opacity: 0; transform: scale(1.35) translateY(-8px); }
  30%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.02); }
}

/* --- LE LEURRE ---
   Il RESTE planté, contrairement à tout le reste : c'est un objet
   posé qui attend le tir, pas un éclat. Il ne part qu'en servant. */
.leurre {
  position: absolute;
  left: 8%; bottom: 4%;
  width: 42px; height: 42px;
  opacity: .85;
  filter: drop-shadow(0 3px 5px rgba(0, 0, 0, .6));
  pointer-events: none;
  z-index: 2;
}
.leurre .visuel { width: 100%; height: 100%; object-fit: contain; }
.leurre--plante { animation: leurre-plante .4s ease-out both; }
@keyframes leurre-plante {
  0%   { opacity: 0; transform: translateY(-22px) rotate(-16deg); }
  70%  { opacity: .9; transform: translateY(2px) rotate(3deg); }
  100% { opacity: .85; transform: none; }
}
/* Il a pris le tir : il part en tournant. */
.leurre--souffle { animation: leurre-souffle .5s ease-in forwards; }
@keyframes leurre-souffle {
  0%   { opacity: .85; transform: none; }
  100% { opacity: 0; transform: translate(-30px, -26px) rotate(-140deg) scale(.7); }
}

/* --- LE CHRONOMÈTRE QUI GAGNE OU QUI PERD ---
   La montre ajoute cinq secondes, la grenade en vole la moitié.
   Sans marque, la barre bouge et personne ne sait pourquoi. */
.timer__fill--gagne { animation: chrono-gagne .9s ease-out; }
.timer__fill--vole  { animation: chrono-vole .9s ease-out; }
@keyframes chrono-gagne {
  0%, 100% { filter: none; }
  30%      { filter: brightness(1.9) saturate(1.4); }
}
@keyframes chrono-vole {
  0%, 100% { filter: none; }
  15%      { filter: brightness(2.2) hue-rotate(-30deg); }
  45%      { filter: brightness(.6); }
}

@media (prefers-reduced-motion: reduce) {
  .eclair, .tracer, .dome { display: none; }
  .leurre { animation: none; }
  .timer__fill--gagne, .timer__fill--vole { animation: none; }
}

/* Le tir de l'ADVERSAIRE : même trajet, sens inverse. Il part de la
   droite, où il se tient, et arrive sur nous. Sans lui, on perdait deux
   cent cinquante points de vie sans rien voir venir. */
.tracer--foe {
  left: auto; right: 26%;
  transform-origin: right center;
  background: linear-gradient(270deg, transparent, rgba(255, 190, 150, .95), rgba(226, 90, 60, .6));
  animation-name: tracer-file-foe;
}
@keyframes tracer-file-foe {
  0%   { width: 0; opacity: 0; }
  15%  { opacity: 1; }
  70%  { width: 48%; opacity: 1; }
  100% { width: 52%; opacity: 0; }
}

/* ------------------------------------------------------------
   LE DÉPLIANT
   ------------------------------------------------------------
   Pleine largeur sous sa rangée. `grid-column: 1 / -1` est ce qui
   le fait occuper toute la grille quel qu'en soit le nombre de
   colonnes — c'est la moitié CSS de finDeRangee().
   ------------------------------------------------------------ */
.depliant {
  grid-column: 1 / -1;
  background: var(--toile-2);
  border: 1px solid var(--laiton);
  border-radius: 9px;
  padding: .6rem;
  animation: depliant-ouvre .18s ease-out;
  overflow: hidden;
}
@keyframes depliant-ouvre {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/* La flèche de la tuile ouverte pivote : c'est le seul signe qui dit
   d'où sort le panneau quand la rangée en compte quatre. */
.fente__fleche, .objet__fleche { transition: transform .18s ease-out; }
[aria-expanded="true"] .fente__fleche,
[aria-expanded="true"].objet__fleche,
[aria-expanded="true"] .objet__fleche { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
  .depliant { animation: none; }
  .fente__fleche, .objet__fleche { transition: none; }
}

/* ------------------------------------------------------------
   LES VITRINES — la chose elle-même, pas son symbole
   ------------------------------------------------------------ */
.apercu {
  display: block;
  position: relative;
  height: 34px;
  overflow: hidden;
}
.apercu .visuel { height: 100%; width: auto; margin: 0 auto; }
/* Une fente vide s'efface, elle ne disparaît pas : ce qui manque est
   ce qui fait jouer. */
.apercu--nulle { opacity: .34; }
/* L'AURA EST RÉDUITE, PAS ÉCRASÉE.
   Les grains de l'Intendance montent de soixante-dix pixels : c'est calibré
   sur ses vitrines de 66 px. Forcer leur boîte à 34 px les fait sortir du
   cadre avant d'être visibles — la tuile paraît alors vide alors que tout
   est là et tourne.

   On garde donc la boîte à sa taille naturelle et on la met à l'échelle.
   Les trajets restent ceux du rayon, et continueront de l'être le jour où
   quelqu'un les retouche. */
.apercu--aura {
  display: grid;
  place-items: start center;
}
.apercu--aura .aura__vitrine {
  width: 66px;
  height: 66px;
  margin: 0;
  background: transparent;
  transform: scale(.52);          /* 66 × .52 ≈ 34, la hauteur d'une tuile */
  transform-origin: top center;
}

/* ------------------------------------------------------------
   LES CARTES DES PANNEAUX
   ------------------------------------------------------------
   Une carte de panneau montre une IMAGE — un rendu de tenue, un
   écu de corps — là où la vitrine d'aura montre des grains
   animés. Réutiliser `.aura__vitrine` pour les deux ne marchait
   pas : elle cadre bien une boîte, mais ne dit rien de la taille
   de ce qu'on y met. Le rendu de tenue gardait donc ses 384
   pixels de haut, et l'on n'en voyait que le coin supérieur
   gauche — transparent. Vingt cartes vides sous leur nom, sans
   une ligne dans la console.

   `max-height` plutôt que `height` : un écu est carré, une tenue
   est haute. Les deux doivent tenir SANS se déformer, et rester
   centrés — ce qui manquait aussi aux écus.
   ------------------------------------------------------------ */
.carte__vitrine {
  display: grid;
  place-items: center;
  height: 66px;
  line-height: 0;
  margin-bottom: 4px;
  color: var(--sable-b);
  /* Une lueur sous les pieds qui vaut ligne de sol, comme au rayon. */
  background: radial-gradient(ellipse 46% 9% at 50% 96%, rgba(0, 0, 0, .5), transparent 70%);
}
.carte__vitrine .visuel {
  max-height: 62px;
  max-width: 100%;
  width: auto;
  height: auto;
  filter: drop-shadow(0 3px 5px rgba(0, 0, 0, .5));
}
.carte__vitrine svg { opacity: .45; }
/* LA BANNIÈRE EST UN ÉCHANTILLON, PAS UN TROU.
   « Treillis », celle du grade zéro, va de #1E2016 à #272A1C : deux verts
   presque identiques, et volontairement ternes. Le dégradé s'affichait donc
   correctement tout en se lisant comme une case vide.

   Un liseré clair et un reflet en haut suffisent à dire « ceci est une
   couleur qu'on a choisie » plutôt que « ceci manque ». Les bannières vives
   des grades élevés n'y perdent rien. */
.apercu--banniere {
  height: 26px;
  width: 36px;
  margin: 4px auto;
  border-radius: 3px;
  border: 1px solid var(--sable-b);
  background:
    linear-gradient(180deg, rgba(217, 207, 168, .22) 0%, transparent 45%),
    linear-gradient(135deg, var(--ban-de), var(--ban-vers));
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .35);
}
.apercu--titre {
  font-family: var(--pochoir);
  font-size: .62rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--laiton);
  display: grid;
  place-items: center;
  text-align: center;
  line-height: 1.15;
}

/* ------------------------------------------------------------
   LES PANNEAUX DES FENTES
   ------------------------------------------------------------
   Le panneau est LARGE et BAS : il tient sous une rangée de
   tuiles, pas à la place de l'écran. Les cartes défilent donc en
   rangée plutôt que de s'empiler en grille — une grille de
   treize auras rendrait la fiche deux fois plus haute à chaque
   ouverture.
   ------------------------------------------------------------ */
.panneau__titre {
  font-family: var(--display);
  font-size: .74rem;
  letter-spacing: .07em;
  color: var(--laiton);
  margin: 0 0 .5rem;
}
.panneau__mot {
  font-size: var(--fs-sm);
  color: var(--sable-b);
  margin: 0 0 .5rem;
}
.auras--rangee {
  display: flex;
  gap: .4rem;
  overflow-x: auto;
  padding-bottom: .25rem;
  scroll-snap-type: x proximity;
}
.auras--rangee > * {
  flex: 0 0 5.2rem;
  scroll-snap-align: start;
}
/* Verrouillée : en pointillés, et son grain s'éteint. On voit ce qu'on
   n'a pas — c'est ce qui fait jouer — sans le confondre avec ce qu'on a. */
.aura--fermee { border-style: dashed; }
.aura--fermee .aura__vitrine { opacity: .4; filter: grayscale(.5); }
.aura--fermee .aura__etat { color: var(--laiton); }

.fente { position: relative; }
.fente__fleche {
  position: absolute;
  top: 3px;
  right: 3px;
  line-height: 0;
  color: var(--sable-b);
}

/* ------------------------------------------------------------
   LA CARTE D'OBJET, EN DEUX GESTES
   ------------------------------------------------------------
   La carte contient désormais deux boutons : la surface d'achat,
   qui occupe toute sa place, et la flèche qui décrit sans
   acheter. Un bouton dans un bouton est invalide — d'où le
   conteneur.

   `.objet` garde son cadre et son fond ; c'est `.objet__achat`
   qui reprend la grille intérieure, pour que rien ne bouge à
   l'écran.
   ------------------------------------------------------------ */
.objet { position: relative; padding: 0; }
.objet__achat {
  display: grid;
  justify-items: center;
  gap: 1px;
  width: 100%;
  padding: .6rem .4rem .5rem;
  background: none;
  border: 0;
  border-radius: var(--r);
  color: inherit;
  font: inherit;
  text-align: center;
}
.objet__fleche {
  position: absolute;
  top: 2px;
  right: 2px;
  padding: 5px;
  background: none;
  border: 0;
  line-height: 0;
  color: var(--sable-b);
}
.objet__aide {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--sable);
}

/* ------------------------------------------------------------
   LES BREVETS DE CORPS
   ------------------------------------------------------------
   Les six sont montrés MÊME À ZÉRO : ce qui manque est ce qui
   fait jouer, et un brevet caché tant qu'on ne l'a pas ne donne
   envie de rien puisqu'on ignore qu'il existe.

   La teinte du corps colore la jauge et rien d'autre — six
   cartes de six couleurs différentes feraient un nuancier, pas
   un état de service.
   ------------------------------------------------------------ */
.brevets__sup {
  margin: 0 0 .5rem;
  font-size: var(--fs-sm);
  color: var(--sable-b);
}
.brevets__sup b { color: var(--laiton); font-family: var(--chiffre); }
.brevets__sup em { color: var(--vert-ok); font-style: normal; }

.brevets {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: .4rem;
}
.brevet {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "ico nom" "ico etat" "part part";
  align-items: center;
  gap: 1px .45rem;
  padding: .45rem .5rem .55rem;
  border: 1px solid var(--kaki);
  border-radius: var(--r);
  background: var(--toile);
  overflow: hidden;
}
.brevet__ico { grid-area: ico; line-height: 0; color: var(--sable-b); }
.brevet__nom {
  grid-area: nom; font-size: 0.625rem; letter-spacing: .04em;
  color: var(--sable); text-transform: uppercase; line-height: 1.1;
}
.brevet__etat { grid-area: etat; font-size: 0.5625rem; color: var(--sable-b); }
.brevet__part {
  grid-area: part; font-family: var(--chiffre); font-size: 0.5625rem;
  color: var(--laiton); margin-top: 2px;
}
/* La jauge est une ligne au pied de la carte : elle se lit d'un coup d'œil
   sans prendre la place d'une barre. */
.brevet__jauge {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: rgba(140, 135, 111, .18);
}
.brevet__jauge::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: var(--part, 0%);
  background: var(--corps, var(--laiton));
  transition: width .5s var(--ease, ease-out);
}
.brevet--entame { border-color: rgba(200, 162, 76, .45); }
.brevet--entame .brevet__ico { color: var(--corps, var(--laiton)); }
.brevet--plein {
  border-color: var(--laiton);
  background: var(--toile-2);
}
.brevet--plein .brevet__ico { color: var(--laiton); }
.brevet--plein .brevet__etat { color: var(--vert-ok); }

@media (prefers-reduced-motion: reduce) {
  .brevet__jauge::after { transition: none; }
}

/* ============================================================
   LE RETOUR AU GESTE
   ------------------------------------------------------------
   Le jeu comptait déjà cinquante-cinq animations : il n'était pas
   figé, il était SOURD. Rien ne répondait au doigt, les cartes
   arrivaient d'un bloc, et les jauges s'affichaient déjà pleines.

   Trois ajouts, et pas un de plus. Une interface qui bouge
   partout ne paraît pas vive, elle paraît instable — et sur un
   écran de 360 px où l'on enchaîne trois duels de trois minutes,
   c'est fatigant avant d'être joli.

   Tout est éteint sous `prefers-reduced-motion` : un joueur qui
   demande moins de mouvement le demande ICI aussi.
   ============================================================ */

/* 1. LE DOIGT REÇOIT UNE RÉPONSE.
   Sans cela, on ne sait pas si le contact a été pris — et sur un dépliant
   qui met 180 ms à s'ouvrir, on touche deux fois. */
.fente:active,
.objet__achat:active,
.brevet:active,
.butin__piece:active {
  transform: scale(.97);
}
.fente, .objet__achat, .brevet, .butin__piece {
  transition: transform .09s ease-out;
}

/* 2. LES CARTES D'UN PANNEAU ENTRENT EN CASCADE.
   Vingt tenues qui apparaissent ensemble font une nappe ; les mêmes
   décalées de trente millisecondes font une main qui étale des cartes.
   Le décalage est plafonné à huit rangs : au-delà, on attendrait. */
.depliant .aura,
.brevets .brevet {
  animation: entree-carte .22s ease-out backwards;
}
.depliant .aura:nth-child(1),  .brevets .brevet:nth-child(1)  { animation-delay: .00s; }
.depliant .aura:nth-child(2),  .brevets .brevet:nth-child(2)  { animation-delay: .03s; }
.depliant .aura:nth-child(3),  .brevets .brevet:nth-child(3)  { animation-delay: .06s; }
.depliant .aura:nth-child(4),  .brevets .brevet:nth-child(4)  { animation-delay: .09s; }
.depliant .aura:nth-child(5),  .brevets .brevet:nth-child(5)  { animation-delay: .12s; }
.depliant .aura:nth-child(6),  .brevets .brevet:nth-child(6)  { animation-delay: .15s; }
.depliant .aura:nth-child(7)  { animation-delay: .18s; }
.depliant .aura:nth-child(n+8) { animation-delay: .21s; }

@keyframes entree-carte {
  from { opacity: 0; transform: translateY(7px) scale(.96); }
  to   { opacity: 1; transform: none; }
}

/* 3. LA JAUGE D'UN BREVET MONTE.
   Elle part de zéro (voir `profil.js`) : une barre déjà faite est un
   constat, une barre qui monte est un progrès. */
.brevet__jauge::after { transition: width .9s cubic-bezier(.22, .61, .36, 1); }

@media (prefers-reduced-motion: reduce) {
  .fente, .objet__achat, .brevet, .butin__piece { transition: none; }
  .fente:active, .objet__achat:active, .brevet:active, .butin__piece:active { transform: none; }
  .depliant .aura, .brevets .brevet { animation: none; }
  .brevet__jauge::after { transition: none; }
}

/* ------------------------------------------------------------
   UNE CARTE ARMÉE
   ------------------------------------------------------------
   Elle annonce que le prochain contact débite. Le prix laisse la
   place au mot « CONFIRMER » : un simple liseré n'aurait pas
   suffi, un joueur qui ne regarde pas la carte qu'il vient de
   toucher n'aurait vu qu'un achat qui ne part pas.
   ------------------------------------------------------------ */
.objet--arme {
  border-color: var(--alerte);
  background: var(--toile-2);
}
.objet--arme .objet__prix {
  color: var(--alerte-c);
  font-family: var(--display);
  letter-spacing: .05em;
  animation: arme-bat 1s ease-in-out infinite;
}
@keyframes arme-bat { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }

@media (prefers-reduced-motion: reduce) {
  .objet--arme .objet__prix { animation: none; }
}

/* ------------------------------------------------------------
   LE CONTOUR DE RANG, AUTOUR DU PORTRAIT
   ------------------------------------------------------------
   Une bordure, pas un dessin : ce portrait apparaît en quatre
   tailles différentes, et une image devrait être redessinée pour
   chacune. Les deux couleurs viennent de `peindreRang`.

   Un joueur NON CLASSÉ n'a pas cette classe du tout. On ne lui
   pose pas un gris terne : ce serait un rang de plus, alors
   qu'il n'en a pas encore.
   ------------------------------------------------------------ */
/* ⚠️ HUIT CONTOURS, ET NON UN SEUL REPEINT.

   La première version teintait un unique anneau de huit couleurs. Le
   défaut ne se voyait qu'en usage : ce contour sert à être vu SEUL — sur
   la plaque d'accueil, dans la liste des salons, en face de soi pendant un
   duel — et seul, le bronze d'un Bronze et l'or d'un Or sont deux bruns
   chauds. Personne n'a les deux sous les yeux pour comparer.

   Chaque rang porte donc une STRUCTURE, qui escalade. La couleur reste le
   premier signal ; la forme est ce qui la rend lisible isolément. */
.contour-rang {
  position: relative;
  border-radius: 50%;
  box-shadow:
    0 0 0 2px var(--nuit),
    0 0 0 4px var(--rang-teinte, var(--kaki)),
    0 0 10px -2px var(--rang-eclat, transparent);
}

/* ⚠️ TOUT CE QUI SUIT EST MASQUÉ EN COURONNE, ET C'EST LA MÉCANIQUE
   ENTIÈRE DU BLOC.

   Un pseudo-élément rond avec un fond est un DISQUE, pas un anneau. Deux
   conséquences, vues seulement en regardant l'image :

     · les rivets posés en `radial-gradient at 50% 0` tombaient sur le bord
       exact du cercle, donc à moitié hors du rayon : rognés à moins d'un
       pixel, invisibles. Platine, Diamant et Rubis sortaient avec un
       anneau nu, identique à celui d'Argent.
     · l'arc tournant, lui, se voyait trop : un dégradé conique sur un
       disque BARRE LE VISAGE d'un coin de couleur qui tourne. C'était déjà
       le cas avant ce travail, sur tous les rangs.

   `mask` découpe une couronne dans le disque : le fond ne subsiste
   qu'entre deux rayons. Les rivets tiennent alors entièrement dedans, et
   l'arc suit l'anneau au lieu de traverser le portrait.

   `-webkit-mask` est doublé : Safari en dessous de 15.4 ignore `mask` seul,
   et le contour y redeviendrait un disque plein sur le visage — le défaut
   qu'on vient de corriger, servi aux seuls appareils qui ne peuvent rien
   y faire. */
.contour-rang::before {
  content: "";
  position: absolute;
  inset: -9px;
  border-radius: 50%;
  pointer-events: none;
  -webkit-mask: radial-gradient(closest-side, transparent 0 80%, #000 82%);
          mask: radial-gradient(closest-side, transparent 0 80%, #000 82%);
}

/* LE SECOND ANNEAU — à partir d'Argent. */
.contour-rang--argent::before,
.contour-rang--platine::before,
.contour-rang--emeraude::before,
.contour-rang--diamant::before,
.contour-rang--maitre::before {
  background: var(--rang-teinte);
  opacity: .85;
}

/* OR ET RUBIS : le second anneau est DOUBLÉ — deux traits séparés d'un vide.

   ⚠️ MESURÉ, PAS SUPPOSÉ. La première version doublait par un `box-shadow`
   de trois pixels, et l'écart avec Argent était invisible : le halo se
   confondait avec la bordure qu'il doublait. Argent et Or sortaient avec la
   même silhouette — les deux rangs les plus proches en couleur étaient
   aussi les deux seuls qu'aucune forme ne séparait. */
.contour-rang--or::before,
.contour-rang--rubis::before {
  background: var(--rang-teinte);
  opacity: .85;
  -webkit-mask: radial-gradient(closest-side, transparent 0 72%,
    #000 74% 79%, transparent 81% 87%, #000 89%);
          mask: radial-gradient(closest-side, transparent 0 72%,
    #000 74% 79%, transparent 81% 87%, #000 89%);
}

/* LES RIVETS — quatre à Platine et Émeraude, huit à partir de Diamant.
   Un dégradé conique répété : des repères régulièrement espacés, dont le
   nombre se lit d'un coup d'œil. Ils remplacent la teinte pleine du second
   anneau, ils ne s'y ajoutent pas — un anneau plein PLUS des rivets ne
   donne qu'un anneau plein. */
.contour-rang--platine::before,
.contour-rang--emeraude::before {
  background: repeating-conic-gradient(
    var(--rang-eclat) 0 10deg, transparent 10deg 90deg);
  opacity: 1;
}
.contour-rang--diamant::before,
.contour-rang--maitre::before {
  background: repeating-conic-gradient(
    var(--rang-eclat) 0 8deg, transparent 8deg 45deg);
  opacity: 1;
}
/* Rubis garde son anneau doublé ET reçoit ses huit rivets : il est le
   septième rang, il porte les deux marques des précédents. */
.contour-rang--rubis::before {
  background: repeating-conic-gradient(
    var(--rang-teinte) 0 8deg, transparent 8deg 45deg);
  opacity: 1;
}

/* L'ARC TOURNANT — Émeraude, Rubis, Grand Maître.
   Il ne tourne PAS sur tous les rangs, et c'est le point de la règle : un
   éclat qui tourne partout n'est plus une distinction, c'est un fond
   d'écran. Assez lent pour ne pas tirer l'œil pendant un duel — ce
   portrait est aussi sur l'écran de combat. */
.contour-rang::after {
  content: "";
  position: absolute;
  inset: -4px;
  border-radius: 50%;
  pointer-events: none;
  background: conic-gradient(from 0deg,
    transparent 0deg, var(--rang-eclat, transparent) 40deg, transparent 90deg);
  opacity: 0;
  -webkit-mask: radial-gradient(closest-side, transparent 0 84%, #000 86%);
          mask: radial-gradient(closest-side, transparent 0 84%, #000 86%);
}
.contour-rang--emeraude::after,
.contour-rang--rubis::after,
.contour-rang--maitre::after {
  opacity: .9;
  animation: rang-tourne 6s linear infinite;
}
@keyframes rang-tourne { to { transform: rotate(1turn); } }

/* LA COURONNE DE LAURIER — le Grand Maître seul.
   Un halo qui respire, posé DERRIÈRE le médaillon. C'est le seul rang qui
   déborde de son anneau, et il n'y en a qu'un par saison en haut du
   tableau : il peut se permettre d'être vu de loin. */
.contour-rang--maitre {
  box-shadow:
    0 0 0 2px var(--nuit),
    0 0 0 4px var(--rang-teinte),
    0 0 0 6px var(--rang-eclat),
    0 0 18px -1px var(--rang-eclat);
  animation: rang-maitre 3.5s ease-in-out infinite;
}
@keyframes rang-maitre {
  0%, 100% { box-shadow: 0 0 0 2px var(--nuit), 0 0 0 4px var(--rang-teinte),
                         0 0 0 6px var(--rang-eclat), 0 0 14px -2px var(--rang-eclat); }
  50%      { box-shadow: 0 0 0 2px var(--nuit), 0 0 0 4px var(--rang-teinte),
                         0 0 0 6px var(--rang-eclat), 0 0 24px 1px var(--rang-eclat); }
}

/* ------------------------------------------------------------
   LE CADRE PEINT — quand le lot B est livré
   ------------------------------------------------------------
   Tout ce qui précède est le TRACÉ DE SECOURS. Il reste là et il
   reprend la main dès qu'un fichier manque : premier lancement
   hors connexion, lot pas encore engendré.

   Quand l'image existe, elle remplace l'anneau ET ses ornements —
   elle ne s'y ajoute pas. Un cadre peint par-dessus un anneau
   tracé donne deux cercles concentriques dont l'un n'était pas
   voulu, et c'est le genre de défaut qu'on met dix minutes à
   nommer parce qu'il ressemble à un choix.

   Le cadre est posé en PSEUDO-ÉLÉMENT, jamais en balise : ces
   nœuds contiennent déjà un portrait ou un galon, et y ajouter un
   enfant changerait leur mise en page.
   ------------------------------------------------------------ */
.contour-rang--peint {
  /* Plus de halo tracé : le cadre porte sa propre matière. On garde une
     ombre douce à la teinte du rang — c'est elle qui le décolle du fond
     sombre, et le cadre seul y disparaîtrait un peu. */
  box-shadow: 0 0 10px -3px var(--rang-eclat);
  animation: none;

  /* ⚠️ LE ROGNAGE DOIT SAUTER, ET C'EST LE POINT LE PLUS DÉLICAT DU BLOC.

     Ce contour se pose sur des médaillons qui ROGNENT LEUR CONTENU :
     `.plaque__portrait` porte `overflow: hidden` pour arrondir le portrait
     en cercle. Un cadre qui déborde de trente pour cent y était coupé net,
     et il ne restait à l'écran que la bordure laiton du médaillon — un
     anneau doré identique pour les huit rangs.

     Rien ne le signalait. La variable était posée, le pseudo-élément
     mesurait ses soixante-quatre pixels, son opacité valait un, et le
     fichier répondait 200. Tout était juste, et l'image était invisible.

     On rend donc le débordement visible ; l'arrondi du portrait est assuré
     autrement, par le `border-radius` de l'image elle-même. */
  overflow: visible;
  /* La bordure du médaillon ferait un second anneau sous le cadre. */
  border-color: transparent;
}
.contour-rang--peint > img,
.contour-rang--peint > .visuel { border-radius: 50%; }
.contour-rang--peint::before { content: none; }
.contour-rang--peint::after {
  content: "";
  position: absolute;
  /* Le cadre DÉBORDE LARGEMENT du médaillon, et il le faut.

     Son trou central occupe un peu plus de la MOITIÉ du fichier. Posé à
     ras du médaillon, ce trou est donc plus petit que le portrait, et
     l anneau vient mordre le haut des visages — les bérets étaient coupés.
     À -30 %, le cadre fait 160 % du médaillon, son trou en couvre près de
     neuf dixièmes, et le portrait respire dedans.

     Mesuré à l image, pas déduit : c est la proportion du trou dans le
     fichier qui commande, et elle vient du modèle. */
  inset: -30%;
  border-radius: 0;
  background: var(--rang-cadre) center / contain no-repeat;
  opacity: 1;
  animation: none;
  pointer-events: none;
  -webkit-mask: none;
          mask: none;
}
/* Le Grand Maître garde sa respiration : c'est le seul rang qui en a une,
   et elle porte sur l'ombre, pas sur le cadre. */
.contour-rang--peint.contour-rang--maitre { animation: rang-maitre 3.5s ease-in-out infinite; }

/* SOUS QUARANTE PIXELS, ON GARDE L'ANNEAU ET LA COULEUR, RIEN D'AUTRE.
   Des rivets de 1,6 px sur un médaillon de 34 px se fondent en un halo
   sale — ils ne distinguent plus rien et salissent la couleur, qui elle
   porte encore. Une distinction qu'on ne peut pas voir doit s'effacer,
   pas s'entasser. */
.contour-rang--mini::before { background: none; border: 0; box-shadow: none; }
.contour-rang--mini::after { animation: none; opacity: 0; }
/* ⚠️ SAUF QUAND LE CADRE EST PEINT. La règle du dessus éteint le pseudo-
   élément qui porte les ornements TRACÉS ; celui du cadre peint est le
   même nœud, et il se serait éteint avec eux. Un contour de rang aurait
   disparu de la plaque d accueil — c est-à-dire de l endroit où on le
   regarde le plus. */
.contour-rang--peint.contour-rang--mini::after { opacity: 1; }

.blason { display: block; width: 100%; height: auto; }
.blason--fantome { opacity: .5; }

@media (prefers-reduced-motion: reduce) {
  .contour-rang::after { animation: none; }
  .contour-rang--maitre { animation: none; }
}

/* ------------------------------------------------------------
   LE CLASSÉ, SUR LA FICHE
   ------------------------------------------------------------
   Le blason à gauche, ce qu'il vaut à droite, et la jauge au
   pied. Le rang teinte la carte — c'est le seul endroit du jeu
   où une couleur autre que le laiton a le droit de dominer,
   parce que c'est précisément le signal qu'on vient y lire.
   ------------------------------------------------------------ */
.classe {
  position: relative;
  display: grid;
  grid-template-columns: 58px 1fr;
  align-items: center;
  gap: .7rem;
  padding: .6rem .7rem .75rem;
  border: 1px solid var(--rang-teinte, var(--kaki));
  border-radius: var(--r);
  background:
    radial-gradient(ellipse 70% 120% at 12% 50%,
      color-mix(in srgb, var(--rang-teinte, transparent) 22%, transparent), transparent 70%),
    var(--toile);
  overflow: hidden;
}
.classe__blason { width: 58px; line-height: 0; }
.classe__dit { display: grid; gap: 2px; }
.classe__dit b {
  font-family: var(--display);
  font-size: 1.15rem;
  line-height: 1;
  color: var(--rang-eclat, var(--sable));
  letter-spacing: .02em;
}
.classe__dit span { font-size: var(--fs-sm); color: var(--sable-b); line-height: 1.3; }
.classe__jauge {
  position: absolute; left: 0; right: 0; bottom: 0; height: 3px;
  background: rgba(140, 135, 111, .18);
}
.classe__jauge::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: var(--part, 0%);
  background: linear-gradient(90deg,
    var(--rang-teinte, var(--kaki)), var(--rang-eclat, var(--laiton)));
  transition: width .9s cubic-bezier(.22, .61, .36, 1);
}
@media (prefers-reduced-motion: reduce) {
  .classe__jauge::after { transition: none; }
}

/* Le blason de l'hôte, dans la liste des salons. Petit : il dit un rang,
   il ne vend pas un adversaire. Absent quand l'hôte est encore en
   placements — lui inventer un rang tromperait celui qui choisit. */
.salon__rang { width: 22px; line-height: 0; flex: 0 0 22px; }
/* La cote, sur l'écran de victoire. C'est le seul gain qui peut être
   négatif : il change de couleur au lieu de disparaître. */
.gain--cote .gain__n { color: var(--laiton-c); }
.gain--perte .gain__n { color: var(--alerte-c); }

/* ------------------------------------------------------------
   LE MODE CLASSÉ, SUR L'ACCUEIL ET DANS SA FEUILLE
   ------------------------------------------------------------ */
.duel-humain--classe { border-color: var(--laiton); }

/* S'ENTRAÎNER — volontairement plus discret que les deux autres.
   C'est un repli, pas une offre : il ne doit pas concurrencer les modes
   qui font jouer avec quelqu'un. */
.entrainer {
  display: grid;
  justify-items: center;
  gap: 1px;
  width: 100%;
  padding: .5rem;
  margin-top: .4rem;
  background: none;
  border: 1px dashed var(--kaki);
  border-radius: var(--r);
  color: var(--sable-b);
  font-family: var(--display);
  font-size: .82rem;
  letter-spacing: .06em;
}
.entrainer__sub {
  font-family: var(--texte);
  font-size: 0.5625rem;
  letter-spacing: 0;
  opacity: .8;
}

/* L'ÉCHELLE DES HUIT RANGS — la préface : ce qu'on gagnera, avant de
   composer son sac. Un joueur qui fait ses courses sans avoir vu les
   rangs ne sait pas encore pourquoi il les fait. */
/* L'ÉCHELLE DES HUIT RANGS.

   ELLE DÉFILAIT SUR LE CÔTÉ, et se coupait à Émeraude. Les trois derniers
   rangs — Diamant, Rubis, Grand Maître — n'étaient donc jamais vus, et ce
   sont précisément ceux qui donnent envie de monter. Un défilement
   horizontal sans bord visible ne s'annonce pas : le joueur voit cinq
   cartes et croit qu'il y en a cinq.

   Une GRILLE de quatre colonnes, deux rangées, tout à l'écran d'un coup.
   Sur 360 px cela laisse 80 px par écusson, ce qui suffit largement — la
   liste des salons les montre à vingt-deux. */
.echelle {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: .3rem;
}
.echelon {
  display: grid;
  justify-items: center;
  align-content: start;
  gap: 1px;
  padding: .4rem .15rem .45rem;
  border: 1px solid var(--kaki);
  border-radius: var(--r);
  background: var(--toile);
  text-align: center;
}
.echelon--atteint {
  border-color: var(--rang-teinte);
  background: var(--toile-2);
}

/* L'ÉCUSSON, EN COULEUR, MÊME QUAND ON NE L'A PAS ENCORE.

   Trois versions ont été nécessaires, et les deux premières avaient le
   même défaut sous des formes différentes.

   La bande dessinait d'abord les huit EN CREUX tant qu'on n'avait pas fait
   ses placements : huit silhouettes grises identiques, à l'endroit même où
   le mode doit donner envie. Remplacer le creux par l'écusson désaturé
   n'a rien réglé — on passait de huit fantômes à huit taches sombres, et
   on ne distinguait toujours pas le Diamant du Rubis.

   Or LA COULEUR EST LE PREMIER SIGNAL d'un rang : c'est la règle posée en
   tête de `blason.js`, à quarante pixels personne ne compte des étoiles.
   La désaturer détruit précisément ce qui identifie. Et cette bande n'est
   pas un inventaire de ce qu'on possède : c'est L'ÉTALAGE de ce qu'on peut
   gagner. Un étalage se montre en couleur.

   « Acquis » se lit déjà par quatre autres signes — la bordure à la teinte
   du rang, le fond plus clair, le titre en couleur, et la ligne du bas qui
   dit « acquis » au lieu d'un seuil. Le cinquième était de trop, et c'était
   le seul qui coûtait quelque chose. */
.echelon__blason { width: 40px; line-height: 0; }
.echelon__blason img, .echelon__blason svg {
  width: 100%; height: auto;
  opacity: .88;
  transition: filter .25s, opacity .25s;
}
.echelon--atteint .echelon__blason img,
.echelon--atteint .echelon__blason svg {
  opacity: 1;
  /* Un halo à la teinte du rang, EN LIEU ET PLACE du gris — et non en plus :
     `filter` ne s'additionne pas entre deux déclarations, la dernière
     remplace la précédente. Les écrire toutes les deux laissait la première
     morte, ce qui marche par accident ici et ne marcherait pas ailleurs. */
  filter: drop-shadow(0 0 5px var(--rang-teinte));
}

/* Une tuile qui S'OUVRE. C'est un bouton, donc il faut lui retirer
   l'apparence que le navigateur donne aux boutons et lui rendre celle des
   autres tuiles du jeu. */
.echelon {
  font: inherit;
  color: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.echelon[aria-expanded="true"] {
  border-color: var(--rang-eclat);
  background: var(--toile-2);
}

.echelon__nom {
  font-size: 0.5625rem; color: var(--sable); line-height: 1.15;
  letter-spacing: .02em;
}

/* ------------------------------------------------------------
   LES TROIS LOTS D'UN RANG, DANS LE DÉPLIANT
   ------------------------------------------------------------
   Écusson, contour de profil, titre. Le contour n'était montré
   NULLE PART, alors que c'est celui des trois qu'on porte partout
   — plaque d'accueil, liste des salons, en face de soi pendant
   tout un duel. On le gagnait sans avoir su qu'il existait.

   Trois colonnes d'égale largeur : aucun des trois lots n'est
   secondaire, et une mise en page qui en avantagerait un le dirait
   plus fort que n'importe quel texte.
   ------------------------------------------------------------ */
.lots {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: .5rem;
  align-items: start;
  text-align: center;
}
.lot { display: grid; justify-items: center; gap: 3px; }

/* Une hauteur COMMUNE pour les trois vignettes. Sans elle, l'écusson —
   qui est le plus haut — décale les deux légendes voisines, et les trois
   lots cessent de se lire comme une série.

   ⚠️ ELLE TIENT COMPTE DU HALO, qui ne tient AUCUNE place dans la mise en
   page : un `box-shadow` déborde sans pousser personne. Le contour du
   Grand Maître rayonne à vingt-quatre pixels, et à 62 px de hauteur il
   venait laver la légende « CONTOUR DE PROFIL » d'un voile violet — le mot
   restait là, il n'était simplement plus lisible. C'est le genre de défaut
   qu'aucune mesure de géométrie ne signale : la boîte était au bon endroit,
   à la bonne taille, et contenue dans son panneau. Il a fallu regarder. */
.lot__vue {
  display: grid;
  place-items: center;
  width: 100%;
  height: 78px;
  line-height: 0;
}
/* Les légendes passent AU-DESSUS du halo, et sur le fond du panneau : le
   dégagement seul ne suffit pas sur les rangs qui rayonnent le plus. */
.lot__quoi, .lot__nom { position: relative; z-index: 1; }
.lot__vue--blason img, .lot__vue--blason svg { width: 58px; height: auto; }

/* Le médaillon : un vrai visage, celui du joueur. Un contour de rang
   autour d'un rond gris n'est qu'un cercle de couleur ; autour de SON
   portrait, c'est ce qu'il aura.

   ⚠️ IL EST DANS LA BOÎTE, IL N'EST PAS LA BOÎTE. La première version
   posait sa taille sur `.lot__vue` lui-même : la règle venait après celle
   de la hauteur commune, elle l'écrasait, et la légende « CONTOUR DE
   PROFIL » remontait de trente pixels au-dessus de ses deux voisines. */
.lot__face {
  width: 48px; height: 48px;
  border-radius: 50%;
  display: block;
}
.lot__face img {
  width: 100%; height: 100%;
  border-radius: 50%;
  object-fit: cover;
  display: block;
}

.lot__vue--titre {
  font-family: var(--titre, inherit);
  font-size: 0.6875rem;
  font-weight: 700;
  line-height: 1.15;
  color: var(--rang-eclat);
  padding: 0 .2rem;
  /* `place-items: center` centre déjà ; le texte, lui, doit pouvoir tenir
     sur deux lignes — « Grand Maître d'armes » n'entre pas sur une. */
  white-space: normal;
}

.lot__quoi {
  font-size: 0.4375rem;
  letter-spacing: .09em;
  color: var(--sable-b);
  text-transform: uppercase;
}
.lot__nom { font-size: 0.5rem; color: var(--sable); line-height: 1.2; }

.lots__mot {
  grid-column: 1 / -1;
  margin: .5rem 0 0;
  font-size: 0.625rem;
  line-height: 1.35;
  color: var(--sable-b);
  text-align: left;
  border-top: 1px solid var(--kaki);
  padding-top: .45rem;
}
/* LE LOT, ET NON LE PRIX. La ligne annonçait « 400 pts » — le coût
   d'entrée — sous une classe qui s'appelle « lot ». Elle porte maintenant
   le titre qu'on emporte, et c'est elle qu'on met en avant. */
.echelon__lot {
  font-size: 0.5rem; line-height: 1.1;
  color: var(--sable-b);
  margin-top: 1px;
}
.echelon--atteint .echelon__lot { color: var(--rang-eclat); }
/* Le seuil descend d'un cran : c'est une condition, pas une récompense. */
.echelon__seuil {
  font-family: var(--chiffre); font-size: 0.4375rem;
  color: var(--sable-b);
  opacity: .7;
}
.echelon--atteint .echelon__seuil { opacity: 1; }

/* LE RAYON DU PAQUETAGE — dix objets, une prise chacun. Toucher ajoute,
   retoucher retire : à dix cartes sur 360 px, un second bouton par carte
   ne tiendrait pas. */
.rayon {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: .4rem;
}
.pioche {
  position: relative;
  border: 1px solid var(--kaki);
  border-radius: var(--r);
  background: var(--toile);
}
.pioche__prendre {
  display: grid;
  justify-items: center;
  gap: 1px;
  width: 100%;
  padding: .45rem .3rem .4rem;
  background: none;
  border: 0;
  border-radius: var(--r);
  color: inherit;
  font: inherit;
}
/* Le « moins » n apparaît QUE sur une carte déjà prise : dix boutons de
   retrait sur un rayon vide seraient dix invitations à rien. */
.pioche__moins {
  position: absolute;
  left: 2px; bottom: 2px;
  width: 22px; height: 22px;
  padding: 0;
  background: var(--nuit);
  border: 1px solid var(--kaki);
  border-radius: 50%;
  color: var(--sable-b);
  font-size: 1rem;
  line-height: 1;
}
.pioche__ico { line-height: 0; }
.pioche__ico .visuel { width: 34px; height: 34px; }
.pioche__nom { font-size: 0.5625rem; color: var(--sable); line-height: 1.15; text-align: center; }
.pioche__n {
  position: absolute; top: 2px; right: 4px;
  font-family: var(--chiffre); font-size: 0.625rem; color: var(--laiton-c);
}
.pioche--prise { border-color: var(--laiton); background: var(--toile-2); }
/* Plein : les objets non pris s'éteignent sans disparaître — on doit voir
   ce qu'on aurait pu prendre, c'est ce qui fait recomposer son sac. */
.pioche--pleine { opacity: .42; }
.rayon__compte { font-size: var(--fs-sm); color: var(--sable-b); margin: .4rem 0 .2rem; }

/* ------------------------------------------------------------
   LE CLASSEMENT DES RANGS
   ------------------------------------------------------------
   Une ligne par joueur : sa place, son blason, son nom, ses
   points. La sienne est marquée — dans une liste de cinquante,
   on cherche d'abord la sienne.
   ------------------------------------------------------------ */
.classement { display: grid; gap: 3px; }
.classement__moi {
  margin: 0 0 .5rem;
  font-size: var(--fs-sm);
  color: var(--sable-b);
}
.classement__moi b { font-family: var(--chiffre); color: var(--laiton); font-size: 1.1rem; }
.classement__l {
  display: grid;
  grid-template-columns: 2.4rem 22px 1fr auto auto;
  align-items: center;
  gap: .5rem;
  padding: .35rem .5rem;
  border-radius: var(--r);
  background: var(--toile);
  border-left: 3px solid var(--rang-teinte, var(--kaki));
}
.classement__l--moi { background: var(--toile-2); outline: 1px solid var(--laiton); }
.classement__place { font-family: var(--chiffre); font-size: 0.6875rem; color: var(--sable-b); }
/* ⚠️ L'IMAGE N'AVAIT AUCUNE TAILLE, ET LA LIGNE FAISAIT 523 PIXELS DE
   HAUT.

   Cette ligne montrait un blason SVG, que `.blason { width: 100% }`
   dimensionnait. En passant à l'écusson PEINT, elle a reçu une `<img>`
   portant `width="512" height="512"` en attributs. La colonne de
   grille borne la LARGEUR à vingt-deux pixels ; la hauteur, elle,
   restait à cinq cent douze.

   Une seule ligne de tableau occupait donc plus d'un demi-écran. C'est
   moi qui l'ai introduit en remplaçant le tracé par l'image, et aucune
   mesure ne le disait — je n'avais regardé que la largeur. */
.classement__blason { line-height: 0; width: 22px; }
.classement__blason img, .classement__blason svg {
  width: 100%; height: auto; display: block;
}
/* Le nom prend la place qui reste, mais il ne la RÉCLAME pas : sans
   `min-width: 0`, une colonne `1fr` refuse de descendre sous la largeur
   de son texte et pousse le score au bord de l'écran. */
.classement__nom {
  font-size: var(--fs-sm); color: var(--sable);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* LE RANG, ENTRE LE NOM ET LES POINTS.

   La colonne du nom est en `1fr` : sur une ligne de quatre cent
   cinquante pixels, elle en occupait trois cent trente-neuf pour un
   matricule de quinze lettres, et poussait le score au bord. C'est ce
   que Louka appelle « beaucoup trop large » — la largeur n'était pas
   fausse, elle ne servait à rien.

   On la remplit plutôt que de la réduire : le rang, à sa couleur, se
   lit avant le nombre de points et dit la même chose en un mot. C'est
   aussi ce que fait la ligne de l'autre classement. */
.classement__rang {
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--rang-teinte, var(--sable-b));
  white-space: nowrap;
}
.classement__rp { font-family: var(--chiffre); font-size: 0.75rem; color: var(--laiton); }

/* La case des places gagnées, à côté de celle des points. Elle porte la
   couleur du gain ou de la perte comme la cote — c'est le seul autre
   chiffre de cet écran qui peut descendre. */
.gain--place .gain__n { color: var(--vert-ok); }
.gain--place.gain--perte .gain__n { color: var(--alerte-c); }


/* ============================================================
   LES TIROIRS DE L INTENDANCE
   ------------------------------------------------------------
   L'Intendance déroulait cinquante-deux articles à la file.
   Chaque rayon est maintenant un `<details>` : la balise donne
   gratuitement le clavier, l'état tenu par le navigateur, et
   l'ouverture automatique quand on cherche un mot dans la page.

   ⚠️ IL FAUT TUER LE TRIANGLE. Un `<summary>` affiche par défaut
   la flèche du système — grise, ronde, et de la forme exacte que
   ce jeu passe son temps à éviter. `list-style: none` la retire
   partout sauf sur Safari, qui a besoin du sélecteur `::-webkit`
   en plus. Les deux sont nécessaires ; l'un sans l'autre laisse
   un triangle sur un téléphone et pas sur l'autre.

   À la place : un CHEVRON dessiné en bordures, qui pivote. Il
   est du même laiton que le titre, et il tourne d'un quart de
   tour à l'ouverture — le seul mouvement de l'écran.
   ============================================================ */
.tiroir {
  border: 1px solid var(--kaki);
  border-radius: var(--r);
  background: linear-gradient(180deg, var(--toile-2), var(--toile));
  box-shadow: var(--relief);
  overflow: hidden;
}

.tiroir__tete {
  display: flex;
  align-items: center;
  gap: .6rem;
  /* Un rayon se touche au pouce : la barre entière fait la cible, et elle
     ne descend jamais sous la hauteur tactile minimale. */
  min-height: var(--tap);
  padding: 0 var(--pad);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.tiroir__tete::-webkit-details-marker { display: none; }

/* LE CHEVRON. Deux bordures d'un carré vide, tourné de 45° : il pointe
   vers le bas replié, vers le haut ouvert. Aucun fichier, aucune fonte. */
.tiroir__tete::before {
  content: "";
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--laiton);
  border-bottom: 2px solid var(--laiton);
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--fast) var(--ease);
  flex: 0 0 auto;
}
.tiroir[open] > .tiroir__tete::before {
  transform: rotate(225deg) translate(-2px, -2px);
}

.tiroir__nom {
  flex: 1 1 auto;
  font-size: var(--fs-xs);
  letter-spacing: .18em;
  color: var(--laiton);
}

/* LE COMPTE, REPLIÉ. « 3 / 18 » est ce qu'on veut savoir sans ouvrir :
   ce qu'on a, sur ce qui existe. Chiffres à chasse fixe — sinon la
   colonne saute d'un rayon à l'autre. */
.tiroir__compte {
  font-family: var(--chiffre);
  font-size: var(--fs-xs);
  color: var(--sable-b);
  letter-spacing: .04em;
  flex: 0 0 auto;
}

.tiroir__corps {
  display: grid;
  /* ⚠️ LA COLONNE DOIT ÊTRE DÉFINIE. Sans cette ligne, la colonne
     implicite est dimensionnée par son CONTENU — et une grille intérieure
     en repeat(auto-fill, minmax(104px, 1fr)) se résout alors à UNE colonne
     de 104 px. Mesuré : l arsenal passait de pleine largeur à 104 px, et
     ses sept objets se rangeaient en file indienne au milieu de l écran.
     minmax(0, 1fr) rend la largeur définie ET autorise le rétrécissement.
     Un simple 1fr n'aurait pas suffi : sa taille minimale reste auto,
     et un tableau large sortirait du cadre au lieu de se resserrer. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--gap);
  padding: 0 var(--pad) var(--pad);
}
/* Les titres internes n'ont plus de rayon à annoncer : ils sont DANS le
   rayon, et leur marge haute doublerait celle du corps. */
.tiroir__corps .sect:first-child { margin-top: 0; }

/* ============================================================
   LA ROULETTE DE L'INTENDANT
   ------------------------------------------------------------
   Huit parts égales, dessinées par un DÉGRADÉ CONIQUE. Aucune
   image, aucun canevas, aucune bibliothèque : une roue de trois
   cents pixels pèse zéro octet et se redessine à n'importe
   quelle taille.

   Les parts alternent deux teintes de treillis plutôt que huit
   couleurs différentes : une roue arc-en-ciel ferait machine à
   sous de fête foraine, et ce jeu passe son temps à éviter ça.
   Ce qui distingue les tranches, ce sont leurs ÉTIQUETTES.

   ⚠️ L'AIGUILLE EST FIXE, C'EST LE DISQUE QUI TOURNE. L'inverse
   demanderait de faire tourner une aiguille autour d'un centre
   qu'aucun élément ne matérialise, et de replacer les huit
   étiquettes à chaque image. Ici, une seule transformation.

   ⚠️ ET C'EST LE SERVEUR QUI DIT OÙ S'ARRÊTER. L'angle vient de
   `anglePourSecteur`, à partir de l'index rendu par la fonction.
   La roue ne choisit rien.
   ============================================================ */
.roue {
  position: relative;
  width: min(280px, 78vw);
  aspect-ratio: 1;
  margin: .4rem auto .2rem;
}

.roue__disque {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 3px solid var(--laiton);
  box-shadow: var(--relief), inset 0 0 0 6px var(--nuit);
  background: conic-gradient(
    var(--kaki)     0deg  45deg,
    var(--toile-2) 45deg  90deg,
    var(--kaki)    90deg 135deg,
    var(--toile-2)135deg 180deg,
    var(--kaki)   180deg 225deg,
    var(--toile-2)225deg 270deg,
    var(--kaki)   270deg 315deg,
    var(--toile-2)315deg 360deg);
  /* La transition est posée en JavaScript au moment du lancer : la mettre
     ici ferait glisser la roue à chaque redessin de l'écran. */
  transform: rotate(0deg);
}

/* UNE ÉTIQUETTE PAR PART. Elle est ancrée au CENTRE puis poussée vers le
   bord : `transform-origin` au centre du disque, rotation de sa part, puis
   translation. Trois opérations dans une seule règle, et aucune
   position calculée en JavaScript. */
.roue__part {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46%;
  font-family: var(--chiffre);
  font-size: var(--fs-xs);
  color: var(--sable);
  text-align: right;
  white-space: nowrap;
  transform-origin: 0 50%;
  transform: rotate(calc(var(--part) * 45deg + 22.5deg)) translateX(4%);
  pointer-events: none;
}

/* L'AIGUILLE, EN HAUT ET IMMOBILE. Un simple triangle de bordures : elle
   pointe vers le bas, dans la roue, et c'est ce qu'elle désigne à l'arrêt
   qui fait foi. */
.roue__aiguille {
  position: absolute;
  top: -6px;
  left: 50%;
  width: 0;
  height: 0;
  transform: translateX(-50%);
  border-left: 11px solid transparent;
  border-right: 11px solid transparent;
  border-top: 18px solid var(--alerte);
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .5));
  z-index: 1;
}

/* Une roue qui tourne pendant trois secondes est une animation : on la
   coupe pour qui a demandé qu'on lui en épargne. Le résultat, lui, arrive
   exactement pareil — c'est le serveur qui l'a décidé. */
@media (prefers-reduced-motion: reduce) {
  .roue__disque { transition: none !important; }
}

/* LES ÉTIQUETTES DE LA MOITIÉ GAUCHE, REMISES À L'ENDROIT.
   ------------------------------------------------------------
   Une étiquette posée radialement se lit tant que sa direction reste dans
   le demi-cercle DROIT. Passé quatre-vingt-dix degrés, elle est renversée :
   quatre des huit tranches affichaient leur lot la tête en bas.

   Le remède tient en trois lignes, et il n'a rien d'évident : on ancre
   l'étiquette par son bord DROIT au centre de la roue, puis on la tourne
   d'un demi-tour de plus. Elle repart alors dans la bonne tranche — un
   demi-tour d'aller, un demi-tour de retour — mais son texte, lui, se lit
   de gauche à droite. */
.roue__part--retourne {
  left: auto;
  right: 50%;
  text-align: left;
  transform-origin: 100% 50%;
  transform: rotate(calc(var(--part) * 45deg + 22.5deg + 180deg)) translateX(-4%);
}

/* CE QUE LE RANG RAPPORTE, sur la fiche.
   ------------------------------------------------------------
   Le bloc CLASSÉ disait le rang et les points, et s'arrêtait là. Un joueur
   voyait « Diamant · 3 200 points » sans savoir qu'il venait de gagner un
   titre qu'il gardera après la remise à zéro de la saison, ni que son
   portrait avait pris un contour. Une récompense qu'on ne voit pas ne
   récompense personne.

   Plus petite et plus sourde que la ligne au-dessus : c'est une précision,
   pas une annonce. Elle passe à la ligne d'elle-même sur un téléphone
   étroit plutôt que de pousser le bloc hors du cadre. */
.classe__dit .classe__lot,
.classe__lot {
  display: block;
  margin-top: .3rem;
  font-size: var(--fs-xs);
  color: var(--laiton);
  line-height: 1.4;
}

/* ============================================================
   LE POSTE TENU DANS LE RÉGIMENT
   ------------------------------------------------------------
   Officier, sous-officier. Le grade d'ARMÉE — sergent, colonel —
   et le titre vivent déjà sur la même ligne ; le poste y était
   noyé en troisième position, « Sergent · Mémoire de la popote ·
   chef », où il se lisait comme un détail de plus.

   Or c'est le seul des trois qui donne des DROITS : radier,
   composer la liste de guerre. Il lui faut donc sa propre marque.

   ⚠️ IL S'APPELLE `poste` PARCE QUE `.rang__galon` ÉTAIT DÉJÀ
   PRIS — l'écusson de trente pixels du classement. Deuxième
   collision de noms de ce chantier, après `.rayon`. Rien ne
   prévient : la classe s'applique, elle prend simplement la mise
   en forme de quelqu'un d'autre.

   LE SOLDAT N'EN PORTE PAS. C'est le cas par défaut, et poser une
   étiquette sur vingt-huit lignes pour en distinguer deux revient
   à n'en distinguer aucune.
   ============================================================ */
.rang__poste {
  justify-self: end;
  padding: .1rem .4rem;
  border: 1px solid var(--laiton);
  border-radius: var(--r-sm);
  font-size: 10px;
  letter-spacing: .1em;
  color: var(--laiton);
  white-space: nowrap;
}
/* L'officier est PLEIN, le sous-officier CREUX : la hiérarchie se lit
   avant le mot, ce qui compte sur une ligne de trente. */
.rang__poste--officier {
  background: var(--laiton);
  color: var(--nuit);
}

/* La ligne que le chef peut toucher pour nommer. Aucun bouton par ligne :
   à trente membres, ce seraient trente boutons qui ne servent qu'à lui. */
.rang--nommable { cursor: pointer; }
.rang--nommable:active { transform: scale(.995); }
.rang--nommable:focus-visible {
  outline: 2px solid var(--laiton);
  outline-offset: 2px;
}

/* ============================================================
   EN GUERRE — LE TABLEAU DES DEUX CAMPS
   ------------------------------------------------------------
   La guerre était une course : chacun marquait dans son coin et
   regardait un classement. On ne jouait contre personne.

   Ici, chaque engagé a un vis-à-vis NOMMÉ, à sa position, et
   cette position se gagne séparément. La différence entre « mon
   régiment est troisième » et « je mène 40 à 25 contre
   Marsouin_06 » est toute la raison d'être de cet écran.

   LE SCORE EST EN POSITIONS, pas en points. Deux nombres, en
   grand. Un total de points ne dit pas qui gagne — et c'est
   précisément ce qu'on reprochait à l'ancienne course.
   ============================================================ */
.enguerre {
  display: grid;
  gap: .35rem;
  padding: var(--pad);
  border: 1px solid var(--laiton);
  border-radius: var(--r);
  background: linear-gradient(180deg, var(--toile-2), var(--toile));
  box-shadow: var(--relief);
}
.enguerre__titre {
  font-size: var(--fs-xs);
  letter-spacing: .18em;
  color: var(--alerte-c);
}
.enguerre__contre {
  font-family: var(--display);
  font-size: var(--fs-md);
  color: var(--sable);
}
.enguerre__score {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: .6rem;
  font-family: var(--chiffre);
  font-size: var(--fs-2xl);
  line-height: 1;
}
.enguerre__moi { color: var(--vert-ok); }
.enguerre__lui { color: var(--sang); }
.enguerre__sep { color: var(--sable-b); font-size: var(--fs-lg); }
.enguerre__sous {
  text-align: center;
  font-size: var(--fs-xs);
  color: var(--sable-b);
}

/* ------------------------------------------------------------
   UNE POSITION, ET SON VIS-À-VIS
   ------------------------------------------------------------
   Six colonnes : la position, mon nom, mes points, « vs », ses
   points, son nom. Les deux noms se rétrécissent, les chiffres
   jamais — c'est eux qu'on compare.

   La ligne du joueur est marquée : à seize positions, retrouver
   la sienne ne doit pas demander de lire seize noms.
   ------------------------------------------------------------ */
.vsa {
  display: grid;
  grid-template-columns: 1.6rem minmax(0, 1fr) auto auto auto minmax(0, 1fr);
  align-items: center;
  gap: .4rem;
  padding: .3rem .2rem;
  border-top: 1px solid var(--kaki);
  font-size: var(--fs-xs);
}
.vsa__pos { color: var(--sable-b); font-family: var(--chiffre); }
.vsa__nom {
  color: var(--sable);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.vsa__nom--eux { text-align: right; }
.vsa__pts { font-family: var(--chiffre); color: var(--sable); }
.vsa__vs { color: var(--sable-b); font-size: 10px; }

/* La position gagnée se voit d'un côté, la perdue de l'autre. Une bordure
   latérale plutôt qu'un fond : à seize lignes, seize fonds colorés font un
   damier qu'on ne lit plus. */
.vsa--gagnee { border-left: 3px solid var(--vert-ok); }
.vsa--perdue { border-right: 3px solid var(--sang); }
/* MA LIGNE. À sept pour cent, la teinte ne se voyait pas — mesuré à
   l écran : la ligne du joueur était indiscernable des quinze autres,
   c est-à-dire que la marque ne servait à rien. Un liseré de laiton en
   plus du fond : on la retrouve sans lire un nom. */
.vsa--moi {
  background: rgba(200, 162, 76, .16);
  box-shadow: inset 0 0 0 1px var(--laiton);
  border-radius: var(--r-sm);
}
.vsa--moi .vsa__nom { color: var(--laiton-c); }

/* ============================================================
   LE BILAN DE NOTRE GUERRE, LE LUNDI MATIN
   ------------------------------------------------------------
   Le compte rendu montrait le classement général et le régiment
   champion. Un régiment APPARIÉ n'y figure pas : il se règle
   contre son adversaire, pas contre le tableau. On lui annonçait
   donc « aucun régiment n'a atteint le seuil » le matin même où
   on venait de le payer.

   Trois lignes, et l'issue en premier. Un joueur qui ouvre son
   régiment le lundi veut savoir s'il a gagné, pas lire un
   classement pour le déduire.
   ============================================================ */
.bilan {
  display: grid;
  gap: .15rem;
  justify-items: center;
  padding: var(--pad);
  border-radius: var(--r);
  border: 1px solid var(--kaki);
  background: linear-gradient(180deg, var(--toile-2), var(--toile));
  box-shadow: var(--relief);
}
/* La bordure porte l'issue : un fond coloré se lit comme une alerte, et
   une défaite n'en est pas une. */
.bilan--gagne { border-color: var(--vert-ok); }
.bilan--perdu { border-color: var(--sang); }

.bilan__issue {
  font-family: var(--display);
  font-size: var(--fs-lg);
  letter-spacing: .06em;
  color: var(--sable-b);
}
.bilan--gagne .bilan__issue { color: var(--vert-ok); }
.bilan--perdu .bilan__issue { color: var(--sang); }

.bilan__score {
  font-family: var(--chiffre);
  font-size: var(--fs-2xl);
  line-height: 1;
  color: var(--sable);
}
.bilan__contre { font-size: var(--fs-sm); color: var(--sable-b); }
.bilan__dit {
  margin-top: .3rem;
  font-size: var(--fs-xs);
  color: var(--laiton);
  text-align: center;
}

/* ============================================================
   LES DEUX INVITATIONS
   ------------------------------------------------------------
   Poser l'icône sur l'écran d'accueil, autoriser les notifications.
   Deux feuilles, la même forme — parce qu'elles demandent la même
   chose : une permission, en échange d'une raison.
   ============================================================ */

/* Les boutons d'une feuille, empilés et pleine largeur. Côte à côte,
   « PLUS TARD » et « INSTALLER » se touchent sous le pouce, et le
   refus se donne par erreur. */
.sheet-actions { display: grid; gap: .5rem; margin-top: 1rem; }
.sheet-actions .btn { width: 100%; }

/* LE GESTE DE L'IPHONE, NUMÉROTÉ.

   ⚠️ CETTE LISTE N'AVAIT AUCUN STYLE, et c'était le seul chemin
   d'installation sur iOS : un `<ol>` nu dans une feuille sombre, sans
   retrait, dont les numéros tombaient hors du cadre. La moitié des
   joueurs voyait donc trois lignes désalignées et sans numéros là où
   se joue l'installation — la seule chose qu'Apple ne fait pas à notre
   place, et la seule qu'on avait laissée sans soin. */
.ios-pas {
  list-style: none;
  counter-reset: pas;
  display: grid;
  gap: .55rem;
  margin: .9rem 0;
  padding: 0;
}
/* ⚠️ SURTOUT PAS `display: grid` SUR CE `li`, ET C'EST LE DÉFAUT QUE LA
   PREMIÈRE VERSION AVAIT.

   Une grille fait de CHACUN de ses enfants un élément de grille — y compris
   les `<b>` et les morceaux de texte entre eux. « Appuie sur <b>Partager</b>,
   en bas de Safari » devenait donc trois cases : le mot en gras partait sur
   sa propre ligne et chevauchait la suite, ce qui se lisait « Partagern bas
   de Safari ».

   On garde donc le `li` en flux normal — le texte reste du texte, le gras
   reste dedans — et on pose le numéro dans la gouttière, en absolu. */
.ios-pas li {
  counter-increment: pas;
  position: relative;
  padding-left: 2.3rem;
  min-height: 1.6rem;
  color: var(--sable);
  line-height: 1.45;
}
.ios-pas li::before {
  content: counter(pas);
  position: absolute;
  left: 0;
  top: 0;
  font-family: var(--chiffre);
  font-size: var(--fs-xs);
  color: var(--noir);
  background: var(--laiton);
  border-radius: 50%;
  width: 1.6rem; height: 1.6rem;
  display: grid; place-items: center;
}
.ios-pas b { color: var(--laiton); }

/* CE QU'ON ENVERRA, ET RIEN D'AUTRE. La liste est courte exprès : une
   permission se décide sur ce qu'elle apporte, pas sur un paragraphe. */
.invite-liste {
  list-style: none;
  margin: .7rem 0;
  padding: 0;
  display: grid;
  gap: .4rem;
}
/* Même précaution qu'au-dessus, et pour la même raison : ces lignes n'ont
   pas de gras aujourd'hui, elles en auront un jour. Une mise en page qui
   se casse à l'ajout d'un `<b>` est une mise en page qui se cassera. */
.invite-liste li {
  position: relative;
  padding-left: 1.1rem;
  color: var(--sable);
  font-size: var(--fs-sm);
}
.invite-liste li::before {
  content: "—";
  position: absolute;
  left: 0;
  color: var(--laiton);
}

/* ============================================================
   LA VISITE DU CFIM
   ------------------------------------------------------------
   Une carte basse, posée AU-DESSUS de la barre d'onglets et non
   par-dessus l'écran : la visite doit laisser voir ce dont elle
   parle. C'est la différence entre apprendre le nom des choses et
   apprendre leur place — et la place est la seule chose qu'un
   joueur vient chercher.
   ============================================================ */

.cfim {
  position: fixed;
  left: 0; right: 0;
  /* Juste au-dessus de la barre d'onglets, encoche comprise. La barre fait
     56 px et porte déjà le retrait de sécurité de l'iPhone. */
  bottom: calc(56px + env(safe-area-inset-bottom, 0px));
  z-index: 900;   /* sous les feuilles (999), au-dessus de tout le reste */
  margin: 0 .6rem .6rem;
  padding: .8rem .9rem 1rem;
  border: 1px solid var(--laiton);
  border-radius: var(--r);
  background: var(--toile);
  box-shadow: 0 -10px 30px rgba(0, 0, 0, .55);
  animation: cfim-monte .28s ease-out;
}
@keyframes cfim-monte {
  from { transform: translateY(14px); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .cfim { animation: none; }
}

.cfim__tete {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: .6rem;
  align-items: center;
  margin-bottom: .5rem;
}
.cfim__portrait {
  width: 44px; height: 44px;
  border-radius: 50%;
  overflow: hidden;
  border: 1px solid var(--laiton);
  display: block;
  line-height: 0;
  flex: none;
}
.cfim__portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cfim__qui { display: grid; gap: 1px; min-width: 0; }
.cfim__qui b { color: var(--sable); letter-spacing: .06em; }
.cfim__qui span { font-size: var(--fs-xs); color: var(--sable-b); }

.cfim__texte { color: var(--sable); line-height: 1.45; margin: 0 0 .8rem; }

.cfim__actions { display: grid; gap: .45rem; }
.cfim__actions .btn { width: 100%; }
/* « PASSER » est plus discret que « SUIVANT », et volontairement second :
   il doit rester atteignable sans être le geste par défaut. */
.cfim__passer { font-size: var(--fs-xs); }

/* ------------------------------------------------------------
   LA CIBLE ALLUMÉE
   ------------------------------------------------------------
   Pas de trou découpé dans un voile, pas de calcul de position :
   une classe sur l'élément, et c'est la feuille de style qui fait
   le halo. Un onglet déplacé demain ne casse rien.
   ------------------------------------------------------------ */
.cfim-cible {
  position: relative;
  z-index: 901;
  border-radius: 10px;
  box-shadow: 0 0 0 2px var(--laiton), 0 0 18px 4px rgba(200, 162, 76, .45);
  animation: cfim-bat 1.6s ease-in-out infinite;
}
@keyframes cfim-bat {
  0%, 100% { box-shadow: 0 0 0 2px var(--laiton), 0 0 14px 2px rgba(200, 162, 76, .35); }
  50%      { box-shadow: 0 0 0 2px var(--laiton), 0 0 22px 7px rgba(200, 162, 76, .6); }
}
@media (prefers-reduced-motion: reduce) {
  .cfim-cible { animation: none; }
}

/* PENDANT LA VISITE, ON NE JOUE PAS. Le corps de l'écran reste visible —
   c'est le but — mais il ne se touche plus : un joueur qui lance un duel
   au milieu de l'étape trois se retrouve en combat avec une carte de
   didacticiel par-dessus. La barre d'onglets, elle, reste vivante :
   c'est d'elle qu'on parle. */
body[data-cfim] .ecran-corps { pointer-events: none; }
body[data-cfim] .cfim, body[data-cfim] .tabs { pointer-events: auto; }

/* ============================================================
   LE COFFRE DU RÉGIMENT
   ------------------------------------------------------------
   Trois grilles de boutons : à qui, combien, quoi. Des boutons
   plutôt qu'un champ de saisie — un clavier masque la moitié de
   la feuille sur un téléphone, et personne ne donne « 437 ».
   ============================================================ */

.coffre__qui {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(0, 1fr));
  gap: .35rem;
  /* Trente camarades : la liste défile plutôt que de pousser les boutons
     de don hors de l'écran. C'est le seul endroit de la feuille qui peut
     grandir sans limite. */
  max-height: 34vh;
  overflow-y: auto;
  margin-bottom: .6rem;
}
.coffre__qui .btn { width: 100%; font-size: var(--fs-sm); }

.coffre__paliers,
.coffre__objets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(88px, 1fr));
  gap: .35rem;
  margin-bottom: .6rem;
}
.coffre__paliers .btn,
.coffre__objets .btn { width: 100%; font-size: var(--fs-sm); }

/* ============================================================
   LES DEUX TEMPS DE LA GUERRE
   ------------------------------------------------------------
   Un bandeau en tête du régiment : où l'on en est, et combien de
   temps il reste. Les deux phases se distinguent par la teinte du
   filet, pas par un aplat — un bandeau coloré en haut de chaque
   écran finit par ne plus se lire.
   ============================================================ */
.phase {
  display: grid;
  gap: .15rem;
  padding: .7rem .8rem;
  margin-bottom: .8rem;
  border: 1px solid var(--kaki);
  border-left: 3px solid var(--kaki);
  border-radius: var(--r);
  background: var(--toile);
}
.phase--combat { border-left-color: var(--sang); }
.phase--preparation { border-left-color: var(--laiton); }

.phase__titre {
  font-size: var(--fs-xs);
  letter-spacing: .18em;
  color: var(--sable-b);
  margin: 0;
}
.phase--combat .phase__titre { color: #E8B4A6; }
.phase--preparation .phase__titre { color: var(--laiton); }

/* Le temps restant est le chiffre qu'on vient chercher : il est le plus
   gros de la carte, et en chiffres tabulaires pour ne pas sautiller de
   seconde en seconde. */
.phase__reste {
  font-family: var(--chiffre);
  font-size: 1.05rem;
  color: var(--sable);
  margin: 0;
}
.phase__quoi { font-size: var(--fs-xs); color: var(--sable-b); margin: 0; line-height: 1.4; }

/* ============================================================
   LE CHAT
   ------------------------------------------------------------
   Un flux qui défile dans une hauteur BORNÉE, et une ligne de
   saisie qui ne bouge pas. Sur un téléphone, le clavier mange la
   moitié de l'écran : si le flux prenait toute la place, le champ
   passerait dessous et on écrirait à l'aveugle.
   ============================================================ */

.tchat__flux {
  display: grid;
  gap: .5rem;
  max-height: 46vh;
  overflow-y: auto;
  padding: .3rem .1rem;
  margin-bottom: .6rem;
  /* Le défilement s'arrête au flux, il ne remonte pas la feuille entière. */
  overscroll-behavior: contain;
}

.tchat__msg {
  display: grid;
  gap: 1px;
  padding: .45rem .6rem;
  border-radius: var(--r);
  background: var(--kaki-fonce, rgba(255, 255, 255, .04));
  border-left: 2px solid var(--kaki);
}
/* Les siens, distingués par la teinte du filet et non par un alignement
   à droite : trente messages en zigzag se lisent moins bien qu'une
   colonne, et la moitié de la largeur serait perdue. */
.tchat__msg--moi { border-left-color: var(--laiton); }

/* Le nom est un BOUTON : c'est par lui qu'on signale et qu'on écrit en
   privé. Il doit donc se voir comme tel — souligné en pointillé, ce qui
   dit « on peut appuyer » sans en faire un bouton de plus à l'écran. */
.tchat__qui {
  font-size: var(--fs-xs);
  color: var(--laiton);
  text-align: left;
  background: none;
  border: 0;
  padding: 0;
  text-decoration: underline dotted;
  text-underline-offset: 2px;
  justify-self: start;
}
.tchat__texte { color: var(--sable); margin: 0; line-height: 1.4; word-break: break-word; }
.tchat__heure { font-size: .625rem; color: var(--sable-b); justify-self: end; }

.tchat__ecrire {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .4rem;
  align-items: center;
}
.tchat__ecrire .champ { width: 100%; }

/* La poche du chat, à côté de celle du carnet. */
#btnTchat { margin-right: .4rem; }

/* ============================================================
   LE VOLET DE DISCUSSION
   ------------------------------------------------------------
   Il glisse depuis la GAUCHE et laisse le jeu visible à côté —
   c'est ce que Louka a demandé, et c'est le bon choix pour une
   autre raison : une feuille couvre l'écran, donc on la ferme pour
   jouer, donc on ne parle jamais pendant qu'on joue. Un chat qu'il
   faut fermer pour continuer n'est pas un chat, c'est une boîte
   aux lettres.
   ============================================================ */

.volet {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  z-index: 940;   /* sous les feuilles (999), au-dessus du jeu */
  width: min(86vw, 380px);
  display: flex;
  flex-direction: column;
  gap: .5rem;
  padding: .8rem .7rem calc(.8rem + env(safe-area-inset-bottom, 0px));
  background: var(--toile);
  border-right: 1px solid var(--laiton);
  box-shadow: 8px 0 28px rgba(0, 0, 0, .55);
  overflow-y: auto;
  overscroll-behavior: contain;
  animation: volet-entre .22s ease-out;
}
@keyframes volet-entre {
  from { transform: translateX(-100%); }
  to   { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .volet { animation: none; }
}

/* LA POIGNÉE, collée au bord droit du volet — la forme que Louka a
   montrée. Elle est POSÉE HORS du volet en largeur : c'est ce qui la fait
   lire comme une languette qu'on rabat, et non comme un bouton de plus
   dans la liste. */
.volet__poignee {
  position: absolute;
  top: 50%;
  right: -22px;
  transform: translateY(-50%);
  width: 22px;
  height: 64px;
  border: 1px solid var(--laiton);
  border-left: 0;
  border-radius: 0 10px 10px 0;
  background: var(--toile);
  color: var(--laiton);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  /* Au moins quarante-quatre pixels sous le pouce : la languette est
     étroite à l'œil, sa cible ne l'est pas. */
  padding: 0;
}
.volet__poignee::after {
  content: "";
  position: absolute;
  inset: -12px -14px;
}

/* Dans le volet, le flux prend toute la hauteur restante : c'est lui qui
   défile, pas le volet entier — sinon la ligne de saisie s'en va vers le
   haut dès qu'on remonte le fil. */
.volet .tchat__flux {
  flex: 1 1 auto;
  max-height: none;
  min-height: 0;
}
.volet .sous-onglets { flex: none; }
.volet .tchat__ecrire, .volet .aide, .volet .vide { flex: none; }

/* Le jeu reste visible et touchable à côté : le volet n'est pas modal.
   On assombrit seulement, pour que l'œil sache où il est. */
body[data-tchat] #app { filter: brightness(.72); }
@media (min-width: 720px) {
  /* Sur large écran, le volet ne cache rien : on ne assombrit pas. */
  body[data-tchat] #app { filter: none; }
}
