/* ==========================================================================
   LE DÉCOR — on est dans une map de Worms Armageddon
   ==========================================================================

   De haut en bas : ciel, nuages, feuilles emportées par le vent, terrain, et
   l'eau qui ondule en bas.

   🔑 Rien de l'eau n'est inventé, et plus rien n'y est même dessiné : les
   nappes SONT le sprite de surface du jeu, `layer\layer.spr`, extrait de
   `DATA/Water/Blue/Water.dir` par `outils/extraire-eau.js`. La rampe de 15
   teintes que ce fichier lisait déjà en clair juste après l'en-tête est
   exactement celle que ce sprite emploie — c'était la même eau.

   🚨 `pointer-events: none` partout : un décor qui intercepte un clic est bien
   pire que pas de décor, et le symptôme (« le bouton ne réagit pas ») ne
   désigne jamais le fond comme coupable.
   ⚠️ `position: fixed` : le décor ne défile pas avec la page, sinon la terre et
   l'eau remonteraient au milieu de l'écran dès le premier scroll. */

:root {
    /*  Le fond de l'eau, tel que le jeu le DESSINE : c'est l'entrée 8 de la
        palette de `Water.dir`, et la valeur qu'on relève au pixel sur une
        capture de map. ⚠️ `DATA/Water/Blue/colour.txt` annonce 47 55 123 —
        trois points plus sombre. On garde la couleur RENDUE, pas la couleur
        déclarée. Les 14 autres teintes de la rampe ne sont pas ici : elles
        vivent dans le sprite de vagues du jeu (outils/extraire-eau.js).
        🔑 Et cette couleur n'est plus seulement un repli : c'est elle qui peint
        toute la masse d'eau sous les bandes de vagues. Voir `.eau`. */
    --eau-fond: rgb(50, 59, 126);

    /* Terrain : masse brune, croûte claire, et le liseré jaune-olive qui
       souligne le bord — c'est lui qui rend la silhouette lisible dans le jeu. */
    --terre-masse:  #6b4a2f;
    --terre-clair:  #8f6540;
    --terre-liseré: #c9b83c;
}

#decor { position: fixed; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }

/*  LE CURSEUR DU JEU — l'œuf de Pâques du décor (voir decor.js).
 *
 *  🔑 C'EST LE POINTEUR DE SOURIS DE WORMS ARMAGEDDON, PAS UNE ÉVOCATION.
 *  `outils/extraire-curseur.js` le découpe dans `Graphics/Cursor.bmp` de
 *  l'installation du jeu — la planche des cinq curseurs d'interface — et le
 *  double au plus proche voisin, 16×16 → 32×32, sans un pixel inventé.
 *  ⚠️ Première version : une cocarde de visée tirée du rip communautaire. Un
 *  réticule dit « visez », pas « il y a quelque chose ici ». La vraie planche
 *  n'est pas dans les rips (ils rippent les sprites de jeu, pas l'interface),
 *  d'où l'erreur.
 *
 *  🚨 LA RÈGLE EST POSÉE SUR `body`, ET C'EST OBLIGÉ. Le décor n'est jamais
 *  l'élément survolé — `pointer-events: none` ET `z-index: -1` le mettent hors
 *  d'atteinte du test de survol (mesuré : `elementFromPoint` au centre d'un ver
 *  renvoie #app, jamais le ver). Il n'a donc AUCUN `:hover` à accrocher. On
 *  s'appuie à la place sur le fait que `cursor` s'HÉRITE : `decor.js` pose cette
 *  classe pendant que la souris est au-dessus d'un ver, et l'élément réellement
 *  survolé — un conteneur de mise en page, qui ne déclare aucun curseur —
 *  l'hérite du corps. Le dessin apparaît exactement là où il faut.
 *  ⚠️ Un bouton, un champ, un lien déclarent LEUR curseur : ils gagnent sur
 *  l'héritage, et de toute façon `decor.js` retire la classe dès que la souris
 *  passe sur quoi que ce soit d'interactif. L'interface ne voit jamais la flèche.
 *
 *  🚨 LE MOT-CLÉ DE REPLI EN FIN DE DÉCLARATION N'EST PAS DÉCORATIF. Sans lui,
 *  une image qui ne charge pas rend la règle ENTIÈRE invalide et on se retrouve
 *  sans curseur du tout. `pointer` plutôt que `crosshair` : le message est
 *  « il y a quelque chose à cliquer ici », pas « visez ».
 *
 *  🔑 `0 0` EST LA POINTE DE LA FLÈCHE, et ce n'est pas un réglage à l'œil :
 *  l'outil prend le premier pixel dessiné en balayant la case de haut en bas,
 *  puis VÉRIFIE que ce point est opaque dans l'image produite. Un curseur dont
 *  le point actif tombe dans le vide clique à côté de ce qu'on voit, et rien ne
 *  le signale — ni le navigateur, ni l'œil.
 *  ⚠️ 32×32 est le plafond : au-delà, plusieurs plateformes ignorent l'image
 *  EN SILENCE. L'outil refuse de produire plus grand. */
body.pointe-decor { cursor: url(/assets/curseur-worms.png) 0 0, pointer; }

/*  🔑 LE VRAI CIEL DU JEU. `gradient.png` est le dégradé du thème, tel quel :
 *  8 px de large, 916 de haut, du zénith à l’horizon. On l’étire sur toute la
 *  fenêtre — étirer un dégradé vertical ne se voit pas, et ça évite de
 *  recopier ses couleurs à la main.
 *  ⚠️ Avant, c’était un `linear-gradient` de six bornes relevées au pixel sur
 *  une capture. Bonne approximation, mais une approximation. */
.ciel {
    position: absolute; inset: 0;
    background: url(/assets/decor/ciel.png) no-repeat;
    background-size: 100% 100%;
}

/*  Les collines de fond du thème, avec leurs arbres. Elles se répètent
 *  horizontalement et se calent JUSTE AU-DESSUS du terrain : c’est ce plan
 *  intermédiaire qui donne la profondeur, entre le ciel et le sol. */
/*  🚨 CETTE IMAGE EST UN REPLI : `decor.js` la remplace dès le démarrage par
 *  celle du thème tiré au sort, `collines-<thème>.png`. Elle reste ici pour le
 *  cas sans JavaScript — un décor amputé de son plan intermédiaire perd toute
 *  sa profondeur.
 *  ⚠️ `background-size: auto 100%` : les `back.png` des thèmes vont de 139 à
 *  159 px de haut. Imposer la HAUTEUR et laisser la largeur suivre garde le
 *  rapport de chacun ; imposer les deux les écraserait. */
.collines {
    position: absolute; left: 0; right: 0; bottom: 252px; height: 118px;
    background: url(/assets/decor/collines.png) repeat-x bottom left;
    background-size: auto 100%;
    image-rendering: pixelated;
    opacity: .92;
}

/*  Les nuages : des images pixel qui dérivent, pas des taches floues. Le jeu
 *  n’a pas de nuage flou — tout y est dessiné au pixel, jusqu’au ciel. */
.nuage {
    position: absolute; left: 0;
    background-repeat: no-repeat;
    /*  `contain` et non `100% 100%` : ce sont les vrais nuages du jeu, et les
        étirer hors de leur rapport se voit immédiatement. Le script leur donne
        déjà une taille proportionnelle. */
    background-size: contain;
    image-rendering: pixelated;
    animation-name: deriver;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
    will-change: transform;
}

/* ==========================================================================
   LE TERRAIN
   ==========================================================================
   🔑 Ce n’est plus du CSS : c’est la VRAIE texture de terrain du jeu.
   `outils/generer-terrain.js` ouvre `DATA/Level/-Desert/SOIL.IMG` — un .IMG
   non compressé, donc lisible tel quel — et en tire la palette et le motif
   de 64x64 px, puis lui dessine une silhouette basse.

   ⚠️ Le plan était de découper une vraie carte. Les cartes disponibles
   sont toutes des cartes à thème (personnages découpés, châteaux, bateaux) :
   reconnaissables entre mille, donc inutilisables comme fond. On garde la
   matière du jeu, on lui redonne un profil neutre.

   🚨 Le précédent terrain était un empilement de dégradés radiaux. Ça ne
   ressemblait à rien : un dégradé ne fait pas de grain, et le grain est
   justement ce qui distingue un terrain de Worms d’un aplat marron.

   🔑 ET LA MATIÈRE EST TIRÉE AU SORT À CHAQUE CHARGEMENT. Six thèmes du jeu
   sont retenus sur vingt-neuf, après mesure du contraste du texte à travers le
   voile de lisibilité (voir `outils/generer-terrain.js`) ; `decor.js` en
   choisit un dès le démarrage et remplace cette image par `terrain-<thème>.png`,
   ainsi que les collines par celles du MÊME thème.

   🚨 SEULE LA MATIÈRE TOURNE. La FORME est commune aux six — un seul profil
   dans `terrain.json` —, donc changer de thème ne déplace aucun objet posé sur
   le sol. Et LE CIEL NE TOURNE PAS : le voile de lisibilité plus bas est réglé
   à trois points de --ciel-haut pour être invisible en haut de page, et un ciel
   clair le ferait apparaître en bandeau sombre sur l'en-tête.

   ⚠️ L'image ci-dessous est donc un REPLI, celle du thème par défaut : elle
   sert quand JavaScript ne tourne pas. */

.terre {
    position: absolute; left: 0; right: 0; bottom: 70px; height: 240px;
    background: url(/assets/decor/terrain.png) repeat-x bottom center;
    /*  `auto 100%` : la tuile s’étire en hauteur SANS se déformer, et se
        répète horizontalement. Son relief a un nombre entier de cycles par
        tuile, donc le raccord est invisible. */
    background-size: auto 100%;
    image-rendering: pixelated;
}

/* ==========================================================================
   L'EAU
   ==========================================================================
   Quatre nappes de vagues qui glissent a des vitesses differentes.

   🔑 CE SONT LES VRAIES IMAGES DU JEU, PLUS UN DESSIN. La surface d'eau de
   Worms Armageddon s'appelle `layer\layer.spr` et dort dans
   `DATA/Water/Blue/Water.dir` — le premier bloc de l'archive, 40 % de son
   poids. Douze images de 256 x 37 qui n'utilisent QUE les index 9 a 23 de la
   palette, c'est-a-dire exactement la rampe de 15 teintes qu'on lisait deja.
   `outils/extraire-eau.js` la decompresse. Avant, les nappes etaient des
   sinusoides peintes avec cette meme rampe : c'etait honnete, mais la crete
   du jeu n'est pas une sinusoide et surtout, elle BOUGE.

   🔑 LA HOULE EST LA VRAIE TROUVAILLE. Les douze images ne partagent pas leur
   boite : la crete de la colonne 0 vaut 3, 4, 5, 7, 8, 9, 10, 9, 8, 6, 5, 4
   sur le cycle. Toute la nappe se souleve de sept pixels puis redescend. Aucun
   degrade, aucune sinusoide generee ne savait faire ca — c'est ce qui separe
   une mer d'un carton ondule.

   🚨 CHAQUE NAPPE FAIT `100 % + SA TUILE` DE LARGE ET SE DEPLACE
   D'EXACTEMENT UNE TUILE. C'est la seule facon d'avoir une boucle sans saut.
   L'ancienne version faisait 200 % de large et se deplacait de -50 % : la
   translation ne tombait pas sur un multiple du motif, donc le raccord
   sautait a chaque tour. Et sur un ecran etroit, 200 % ne suffisait meme plus
   a couvrir le deplacement, laissant apparaitre le vide a droite.

   🔑 LA TUILE FAIT 256 px ET C'EST MESURE, PAS CHOISI : sur les douze images,
   la crete de la colonne 255 est EXACTEMENT celle de la colonne 0. Le jeu la
   repete telle quelle. ⚠️ Deux images DIFFERENTES ne se raccordent pas entre
   elles — leurs cretes de bord different de 7 px au plus — donc en coller deux
   pour fabriquer une tuile plus large, l'idee qui vient, poserait une marche
   visible a chaque raccord.

   ⚠️ Les nappes sont posees par le HAUT (`top`), pas par le bas : ce qui
   doit tomber juste, c'est la ligne de crete, et elle est en haut de chaque
   image. */

.eau {
    position: absolute; left: 0; right: 0; bottom: 0; height: 150px;
    overflow: hidden;
    /*  🚨 L'APLAT DE PROFONDEUR, ET SON DEPART N'EST PAS LIBRE. Il ne s'agit
        plus d'un repli : depuis que la premiere nappe ne fait que 37 px de
        haut, c'est CE degrade qui peint toute la masse d'eau sous les vagues.
        Sa couleur est l'index 9 de la palette, celui-la meme qui termine le
        bas de chaque bande — la jointure est donc invisible.
        Mesure : avec `.vague--1` posee a 12 px, la crete vit entre 12 et 25 et
        le bas de bande entre 34 et 48. Demarrer avant 25 ferait FLOTTER une
        ligne sombre au-dessus d'une crete claire ; demarrer apres 34 laisserait
        passer du ciel sous la bande. 28 est au milieu du seul intervalle
        possible. `outils/extraire-eau.js` recalcule ces bornes a chaque
        extraction et les affiche. */
    background: linear-gradient(to bottom, transparent 0 28px, var(--eau-fond) 28px);
}

/*  🚨 DEUX ANIMATIONS SUR LA MEME NAPPE, ET ELLES NE PEUVENT PAS FUSIONNER.
    Le defilement est CONTINU (`linear`) et le changement d'image est DISCRET
    (`steps(12)`) : une seule animation devrait choisir, et lisser les douze
    images en un fondu que le jeu ne fait pas. On garde donc `transform` pour
    glisser — c'est le compositeur qui travaille — et `background-position-y`
    pour choisir l'image dans la planche.

    ⚠️ LA PLANCHE EST VERTICALE. Les autres bandes du decor (`ver.png`,
    `bulle1.png`) sont horizontales, mais une nappe se REPETE en X pour couvrir
    l'ecran : empiler les images en X ferait defiler les douze cote a cote au
    lieu d'en montrer une. */
.vague {
    position: absolute; left: 0;
    --tuile: 256px;                /* la largeur mesuree de la tuile du jeu */
    width: calc(100% + var(--tuile));
    height: 37px;                  /* la hauteur d'une image de la planche */
    background-image: url(/assets/decor/eau.png);
    background-repeat: repeat-x;
    image-rendering: pixelated;
    animation-name: onduler, houle;
    animation-timing-function: linear, steps(12);
    animation-iteration-count: infinite;
    will-change: transform;
}
@keyframes onduler { to { transform: translateX(calc(-1 * var(--tuile))); } }
/*  -444 px = 12 images de 37. `steps(12)` s'arrete a -407 : la douzieme image
    est bien jouee, la treizieme n'existe pas. */
@keyframes houle { to { background-position-y: -444px; } }

/*  🔑 CHAQUE NAPPE EST LA MEME IMAGE, DECALEE DEUX FOIS. Le jeu n'a qu'une
 *  surface d'eau ; ce sont ses copies posees a des hauteurs differentes qui
 *  font la mer. On les separe donc par leur PHASE, pas par leur dessin :
 *   - le premier `animation-delay` decale la nappe HORIZONTALEMENT (une
 *     avance dans le defilement vaut un decalage de motif — ca evite une
 *     declaration `background-position-x` qui se battrait avec la houle) ;
 *   - le second decale l'IMAGE jouee, de trois crans a chaque fois. Sans lui,
 *     les quatre nappes se souleveraient en meme temps et la mer entiere
 *     respirerait d'un bloc au lieu de laisser passer des vagues.
 *
 *  Les vitesses sont celles d'avant, au pixel par seconde pres : 14,8 / 16,4 /
 *  20,8 / 34,5 px/s. Plus une nappe est basse, plus elle est proche, donc plus
 *  elle defile vite. ⚠️ La duree a change parce que la TUILE a change (256 au
 *  lieu de 768 a 896), pas la vitesse.
 *
 *  🚨 LA CADENCE DE LA HOULE VIENT D'UNE MESURE SUR LA PLANCHE, PAS DU GOÛT.
 *  Première version : 1,8 s pour les douze images, soit 6,7 images par seconde,
 *  et ça se voyait — la mer sautait. La raison est dans le sprite : d'une image
 *  à la suivante, la ligne de crête ne bouge en moyenne que d'UN PIXEL, et sa
 *  hauteur moyenne ne bouge pas du tout (6,4 à 6,5 px sur les douze). Ce n'est
 *  pas une houle qui monte et descend, c'est un FRÉMISSEMENT sur place.
 *  🔑 Or un frémissement d'un pixel joué sept fois par seconde, c'est sept
 *  petits sauts simultanés sur toute la largeur de l'écran : l'œil ne voit pas
 *  de l'eau, il voit un clignotement régulier. À quinze ou vingt images par
 *  seconde, le même pixel devient un scintillement continu. On ne peut PAS
 *  rendre ça fluide en lissant : `background-position-y` interpolé ferait
 *  GLISSER la planche et montrerait deux images à la fois, coupées net. La
 *  seule voie est d'aller plus vite.
 *  ⚠️ Le nombre d'images vient du jeu ; la cadence exacte du jeu, non — elle ne
 *  se relève pas sans le faire tourner. Ce qui est mesuré ici, c'est l'écart
 *  entre images, et c'est lui qui dit qu'il faut monter.
 *
 *  🚨 ET CHAQUE NAPPE A SA PROPRE PÉRIODE. Avec les quatre à 1,8 s, toute la
 *  mer frémissait sur le même battement — les phases décalées ne font que
 *  déplacer un battement commun, elles ne le cassent pas. Des périodes
 *  différentes, elles, ne se rattrapent jamais. Même logique que le défilement :
 *  plus une nappe est basse, plus elle est proche, plus elle est rapide.
 *
 *  🚨 QUATRE NAPPES, PAS CINQ, et 150 px d'eau : il FAUT qu'il reste une
 *  cinquantaine de pixels d'aplat sombre en bas. Avec cinq nappes, les vagues
 *  occupaient toute la hauteur et la mer perdait sa profondeur — le décor
 *  ressemblait à du carton ondulé. */
.vague--1 { top: 12px; animation-duration: 17.3s, 0.78s; animation-delay:     0s,     0s; }
.vague--2 { top: 31px; animation-duration: 15.6s, 0.68s; animation-delay:  -5.5s, -0.17s; }
.vague--3 { top: 50px; animation-duration: 12.3s, 0.60s; animation-delay:  -8.3s, -0.30s; }
.vague--4 { top: 69px; animation-duration:  7.4s, 0.52s; animation-delay:  -1.4s, -0.39s; }

/* ------------------------------------------------- LES BULLES QUI REMONTENT
   🔑 LE VRAI SPRITE DU JEU, seize images chacune. Elles ne viennent d'aucun rip :
   elles dorment dans `DATA/Water/Blue/Water.dir`, l'archive PAR COULEUR D'EAU,
   et `outils/extraire-bulles.js` la décompresse. Leurs palettes sont des
   tranches EXACTES de la rampe qui peint les vagues — la petite prend le fond
   sombre, la grosse une tranche plus claire. Le jeu éclaircit donc la bulle à
   mesure qu'elle monte, et c'est pour ça qu'aucune ne se détache de son eau.

   🚨 DEUX ANIMATIONS, DONC DEUX ÉLÉMENTS. La bulle monte (`transform`) pendant
   que son dessin défile (`background-position-x`). Tout mettre sur un seul
   élément marcherait, mais `.sprite` porte DÉJÀ son animation, sa durée et son
   `steps()` : il faudrait les réécrire toutes les trois en listes parallèles,
   et une seule oubliée casse l'autre en silence. Une boîte qui monte, un sprite
   qui tourne dedans.

   🔑 LE TREMBLEMENT LATÉRAL N'EST PAS ICI — IL EST DANS LES IMAGES. Le jeu
   décale la boîte de la bulle d'un pixel d'une image à l'autre, et l'extracteur
   compose la bande sur une origine COMMUNE pour le conserver. Aucune sinusoïde
   à ajouter : celle de Team17 est déjà dedans.

   ⚠️ LES BULLES SONT LES DERNIERS ENFANTS DE `.eau`, et l'ordre compte : posées
   avant les nappes, l'aplat de profondeur de `.vague--1` les masquerait
   entièrement. `decor.js` les ajoute donc en fin de conteneur.

   ⚠️ `--monte` est posé par `decor.js` : la hauteur d'eau dépend du CSS, et une
   bulle qui monterait d'une valeur figée dépasserait la crête ou s'arrêterait
   en route le jour où `.eau` change de hauteur. */
.bulle {
    position: absolute; bottom: 0;
    animation-name: bulle-monte;
    animation-timing-function: linear;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}
/*  Elle apparaît juste au-dessus du fond et se dissout en atteignant la crête —
    le jeu la fait éclater, mais un éclatement de six pixels ne se lit pas à
    cette échelle, alors qu'une disparition franche sous la crête se remarque. */
@keyframes bulle-monte {
    from { transform: translateY(0);            opacity: 0; }
    12%  { opacity: 1; }
    78%  { opacity: 1; }
    to   { transform: translateY(var(--monte)); opacity: 0; }
}
.sprite--bulle1 { --bande: url(/assets/decor/bulle1.png); }
.sprite--bulle2 { --bande: url(/assets/decor/bulle2.png); }
.sprite--bulle3 { --bande: url(/assets/decor/bulle3.png); }

/* ==========================================================================
   LES FEUILLES DANS LE VENT
   ==========================================================================
   ⚠️ Dessins originaux (outils/generer-feuilles.js), pas des assets Team17.
   🔑 La raison a changé : le `.dir` compressé du jeu SE LIT maintenant — le
   format a été retrouvé pour aller chercher les bulles (voir
   `outils/extraire-bulles.js`). Les feuilles restent dessinées parce que
   personne n'est allé voir si le jeu en a, pas parce qu'on ne saurait pas. */

.particule-derive {
    position: absolute; left: 0;
    animation-name: deriver;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
    will-change: transform;
}
@keyframes deriver {
    from { transform: translateX(-8vw); }
    to   { transform: translateX(108vw); }
}

.particule-balance {
    animation-name: balancer;
    animation-timing-function: ease-in-out;
    animation-iteration-count: infinite;
    animation-direction: alternate;
}
@keyframes balancer {
    from { transform: translateY(-16px); }
    to   { transform: translateY(16px); }
}

/*  Une feuille tourne sur elle-même en tombant. Ici une vraie rotation CSS
 *  convient : contrairement aux objets du menu du jeu, on n'a pas de planche
 *  d'images dessinées à faire défiler. */
.particule {
    background-repeat: no-repeat;
    background-size: contain;
    image-rendering: pixelated;
    animation-name: tournoyer;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
}
@keyframes tournoyer {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
}

/* ==========================================================================
   LA JAUGE DE VENT
   ==========================================================================
   🔑 CE SONT LES VRAIES FLÈCHES DU JEU, ce n’est plus un dessin. `extraire-vent.js`
   découpe une tuile de 8 x 13 px dans `Misc/windr.png` et `Misc/windl.png` du
   rip ; la barre intérieure fait 192 px, soit EXACTEMENT les 2 x 96 px des
   deux planches — douze flèches par côté à plein vent. Cadre biseauté d’un
   pixel. Elle part DU MILIEU et pousse vers la gauche ou la droite — ce
   n’est pas une barre de progression, c’est une girouette.
   ⚠️ Ce qu’elle remplace disait 154 x 10 px et 7 px de pas, relevés sur une
   capture manifestement redimensionnée. Aucune des trois valeurs n’était juste.

   🚨 DEUX IMAGES, PARCE QUE LA COULEUR CHANGE AVEC LE SENS. Le jeu peint le
   vent d’est en ROUGE et le vent d’ouest en BLEU — mesuré sur les deux
   planches, qui portent pourtant la même palette de 256 entrées. La
   géométrie, elle, est un miroir EXACT : un seul fichier retourné par
   `scaleX(-1)` suffisait tant que la jauge était rouge des deux côtés, mais
   un retournement ne change pas une teinte.

   🔑 Et elle n’est pas décorative : `decor.js` oriente les feuilles selon
   la même valeur. Une jauge qui annonce un vent que rien ne subit se voit.

   En bas à droite, comme dans le jeu. */
.vent {
    position: absolute; right: 22px; bottom: 22px;
    padding: 1px; background: #000;
    border: 1px solid;
    border-color: #4f5158 #6f7178 #93959a #6f7178;
}
/*  🚨 192 ET 13 SONT LES COTES DU SPRITE, PAS UN GOÛT. La largeur doit rester
    un multiple de 16 : chaque moitié se remplit par crans d’une flèche de
    8 px, et 154 px n’en donnait que 9,6 par côté — à plein vent le dernier
    cran débordait et se faisait rogner par l’`overflow: hidden`, sans que
    rien ne le signale. 96 px sur mobile (style.css) tombe juste aussi. */
.vent-jauge {
    position: relative; width: 192px; height: 13px;
    background: #000; overflow: hidden;
}
/*  `left: 50%` pour les deux sens, et pour l’ouest un `translateX(-100%)` posé
 *  par `decor.js` : la boîte bascule ENTIÈREMENT à gauche du milieu, son bord
 *  droit reste collé au centre pendant que la largeur s’anime, et le motif
 *  n’est PAS retourné — c’est l’image qui change, pas la géométrie. */
.vent-remplissage {
    position: absolute; top: 0; bottom: 0; left: 50%; width: 0;
    background: url(/assets/decor/vent-est.png) repeat-x left center;
    image-rendering: pixelated;
    transition: width .45s ease-out;
    /*  🔑 La partie remplie NE BOUGE PAS : ce sont les flèches dedans qui
        défilent, dans le sens qu’elles indiquent. Une seule image-clé sert les
        deux sens : `decor.js` pose `animation-direction: reverse` pour
        l’ouest, ce qui fait glisser le motif vers la gauche.
        🚨 ET IL POSE LA DIRECTION, JAMAIS LE NOM. Un `animation-name` en ligne
        battrait le `animation: none` du bloc « mouvement réduit » plus bas, qui
        n’a aucun moyen de se défendre contre un style en ligne.
        🚨 Une flèche fait 8 px : déplacer le fond d’une autre valeur ferait
        sauter le motif à chaque boucle. */
    animation: vent-defile 1s linear infinite;
}
@keyframes vent-defile { to { background-position-x: 8px; } }

/* ==========================================================================
   LES OBJETS DU TERRAIN — vers, barils, mines
   ==========================================================================
   🔑 CE SONT LES VRAIS SPRITES ANIMÉS DU JEU, découpés en bandes horizontales
   par `outils/extraire-sprites.js`. Le ver respire et cligne (26 images), le
   baril vacille (20), la mine clignote (32) — et tout tient dans UNE règle
   d'animation, parce que seule `background-position` bouge.

   🚨 C'EST PRÉCISÉMENT CE QUI PERMET DE RETOURNER UN SPRITE. La version
   précédente animait `transform` pour faire respirer le ver, ce qui écrasait
   le `scaleX(-1)` posé par le script : les vers tournés à gauche se remettaient
   à l'endroit tout seuls. Ici l'animation ne touche pas `transform`, donc le
   retournement tient — et il est porté par la boîte, pas par le sprite, pour
   que l'étiquette d'équipe ne se retourne pas avec lui.

   ⚠️ `steps(n)` et non un défilement continu : un sprite doit sauter d'une
   image à l'autre, pas glisser entre les deux.

   🔑 Leur position vient du profil réel du terrain (terrain.json), pas d'un
   `bottom` deviné. */

.ver-boite {
    position: fixed;
    display: flex; flex-direction: column; align-items: center; gap: 2px;
    pointer-events: none;
}

.sprite {
    width: var(--l); height: var(--h);
    background-image: var(--bande);
    background-repeat: no-repeat;
    /*  La bande entière, mise à l'échelle : n images de --l de large. */
    background-size: calc(var(--l) * var(--images)) var(--h);
    image-rendering: pixelated;
    animation-name: defiler-sprite;
    animation-timing-function: steps(var(--images));
    animation-iteration-count: infinite;
}
@keyframes defiler-sprite {
    to { background-position-x: calc(var(--l) * var(--images) * -1); }
}

/*  Les quatre attentes. Même découpe, même ligne de pieds : elles sont
 *  interchangeables, et un ver peut prendre n'importe laquelle. */
.sprite--ver         { --bande: url(/assets/decor/ver.png); }
.sprite--ver-corde   { --bande: url(/assets/decor/ver-corde.png); }
.sprite--ver-regarde { --bande: url(/assets/decor/ver-regarde.png); }
.sprite--ver-souffle { --bande: url(/assets/decor/ver-souffle.png); }
.sprite--baril { --bande: url(/assets/decor/baril.png); }
/*  La mine est POSÉE, pas animée : ses images sont des angles de rotation. */
.sprite--mine  { --bande: url(/assets/decor/mine.png); animation: none; }

/* -------------------------------------------------- LES CAISSES QUI TOMBENT
   🔑 NI EN BOUCLE, NI FIGÉES : ELLES ARRIVENT. Les images des caisses sont deux
   saynètes, pas des cycles — la descente sous parachute, puis l'atterrissage.
   `decor.js` joue l'une puis l'autre de loin en loin, et entre deux la caisse
   reste parquée sur sa DERNIÈRE image, au repos.

   🚨 LES CHIFFRES QUI L'ONT DÉCIDÉ. Atterrissage : 68 à 567 pixels d'écart
   entre deux images voisines, 862 entre la dernière et la première — en boucle,
   la caisse claquerait une fois par tour. Descente : 390 à 1 440 contre 2 564 —
   c'est une DEMI-oscillation, lue ici en aller-retour, ce qui donne un vrai
   balancement de pendule.

   ⚠️ Deux séries, tirées au sort : la caisse d'armes (le cageot au « ? ») et la
   caisse utilitaire (la malle à la clé à molette). */

/*  Au repos : parquée sur la dernière image de sa bande.
    ⚠️ `animation-name: none` et non le raccourci `animation` : `decor.js` pose
    la durée en ligne, et le raccourci l'effacerait. */
.sprite--caisse,
.sprite--caisse-outils {
    animation-name: none;
    background-position-x: calc(var(--l) * var(--dernier) * -1);
}
.sprite--caisse        { --bande: url(/assets/decor/caisse.png); }
.sprite--caisse-outils { --bande: url(/assets/decor/caisse-outils.png); }

/*  L'atterrissage, joué UNE FOIS.
    🚨 `jump-none` EST OBLIGATOIRE ICI. Avec le `steps(n)` ordinaire de `.sprite`,
    la dernière valeur atteinte est -n×largeur : la bande est alors entièrement
    sortie du cadre et la caisse DISPARAÎT au lieu de rester posée. `jump-none`
    répartit les n images de 0 à -(n-1)×largeur inclus, donc l'animation finit
    exactement sur la dernière image — celle du repos, la même que ci-dessus. */
.sprite--pose {
    animation-name: caisse-pose;
    animation-timing-function: steps(var(--images), jump-none);
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}
@keyframes caisse-pose {
    from { background-position-x: 0; }
    to   { background-position-x: calc(var(--l) * var(--dernier) * -1); }
}

/*  La voilure. Elle vit DANS la boîte de sa caisse, calée en bas — c'est ce qui
    met les pieds des deux découpes au même endroit au moment du relais (voir
    `equiperVoilure` dans decor.js). Cachée tant qu'aucune caisse ne tombe. */
.caisse-vol { position: absolute; bottom: 0; left: 50%; display: none; }
.sprite--caisse-vol        { --bande: url(/assets/decor/caisse-vol.png); }
.sprite--caisse-outils-vol { --bande: url(/assets/decor/caisse-outils-vol.png); }
/*  Le balancement du parachute : demi-oscillation lue en aller-retour. */
.sprite--caisse-vol,
.sprite--caisse-outils-vol { animation-direction: alternate; }

/*  🔑 LA DESCENTE EST UN `transform`, ET C'EST GRATUIT. Le sprite de la voilure
    n'anime que `background-position`, comme tout le reste ici : `transform` est
    donc libre pour la translation. C'est exactement la propriété que la version
    « ver qui respire en transform » avait rendue inutilisable, et qu'on garde
    disponible depuis.
    ⚠️ La translation est portée par le CONTENEUR, pas par le sprite : deux
    animations sur la même propriété du même élément ne cohabitent pas.
    ⚠️ Elle part de 320 px plus haut, pas du haut de la page : le mouvement reste
    dans le tiers bas de l'écran plutôt que de balayer la colonne de lecture. */
.caisse-vol--tombe {
    animation-name: caisse-chute;
    animation-timing-function: linear;
    animation-iteration-count: 1;
    animation-fill-mode: both;
}
@keyframes caisse-chute {
    from { transform: translateY(var(--chute)); opacity: 0; }
    12%  { opacity: 1; }
    to   { transform: translateY(0); opacity: 1; }
}

/*  🔑 LES TOMBES RESPIRENT, ET C'EST `alternate` QUI LE PERMET. Leurs 20 images
    étirent puis écrasent la pierre de quelques pixels — mais elles ne font que
    l'ALLER : jouées en boucle simple, elles remettraient la tombe droite d'un
    coup à chaque tour (pour `tombe-visage` : 147 pixels d'écart au maximum entre
    deux images voisines, contre 403 entre la dernière et la première).
    Lue en aller-retour, la demi-respiration du jeu redevient une respiration
    complète, et la bande ne coûte pas une image de plus.

    ⚠️ C'est bien `animation-direction` et non `animation` : le raccourci
    écraserait la durée et le délai que `decor.js` pose en ligne, et toutes les
    tombes respireraient en cadence. */
.sprite--tombe-visage    { --bande: url(/assets/decor/tombe-visage.png); }
.sprite--tombe-gravee    { --bande: url(/assets/decor/tombe-gravee.png); }
.sprite--tombe-monolithe { --bande: url(/assets/decor/tombe-monolithe.png); }
.sprite--tombe-visage,
.sprite--tombe-gravee,
.sprite--tombe-monolithe { animation-direction: alternate; }

/*  L'étiquette d'équipe — deux cadres empilés, relevés au pixel sur une capture
    du jeu (« Bellingham » au-dessus de « 100 ») lue avec `lirePngRgba`.

    🔑 CADRE CLAIR SUR FOND BLEU NUIT, ET NON L'INVERSE. C'est l'erreur que
    corrige cette version : elle dessinait un gris moyen (#7c8084, luminance
    128) autour d'un aplat noir. Sur la capture, la bordure est au contraire le
    trait le PLUS clair de l'étiquette — luminance relevée 141 à 158 sur une
    image rééchantillonnée, donc ~175 en réalité — et le fond n'est pas noir
    mais rgb(6,6,33), la MÊME valeur mesurée à l'intérieur des deux cadres.

    🚨 UN PIXEL DE JEU VAUT DEUX PIXELS CSS, ici comme pour les sprites. La
    capture est au 1:1 du jeu — le ver y mesure 22x26, l'encre exacte d'une
    image de `ver.png` — et tout le décor est tracé à ECHELLE 2. Les cotes
    relevées sont donc doublées : cadre du nom 75x16 → 150x32, bordure 1 → 2 px,
    chasse d'un caractère 6,7 → 13,4 px. C'est ce dernier chiffre qui fixe la
    police : Consolas gras avance de 0,55 em, soit 24 px de corps. L'ancien
    10 px faisait une étiquette deux fois trop petite pour ses vers.

    ⚠️ LES DEUX CADRES NE FUSIONNENT PAS LEUR BORDURE, ILS SE TOUCHENT. Au
    raccord, la bande claire fait deux fois l'épaisseur d'une bordure isolée sur
    la capture (~3 px contre ~1,5) : ce sont bien deux traits collés, pas un
    seul. Le `margin-top: -2px` de la version précédente les confondait. */
.ver-plaque {
    display: flex; flex-direction: column; align-items: center;
    font-family: 'Consolas', 'DejaVu Sans Mono', monospace;
    font-weight: 700; line-height: 1;
    white-space: nowrap;
}
.ver-nom, .ver-vie {
    font-size: 24px;
    /*  2 px de marge = 1 px de jeu, et 24 + 2×2 + 2×2 = 32 px, soit les 16 px
        de hauteur relevés sur la capture. */
    padding: 2px 4px;
    background: #060621;
    border: 2px solid #aab0ba;
}
/*  🔑 LE NOM ET LA VIE PORTENT LA MÊME TEINTE D'ÉQUIPE. Le jeu colore le bloc
    entier : c'est ce qui permet, d'un coup d'œil sur une carte, de dire quels
    vers appartiennent à qui. Les séparer — nom en couleur, chiffre en blanc —
    cassait précisément ce repère.
    ⚠️ La couleur est donc posée sur la PLAQUE et héritée par les deux cadres ;
    ne pas la redéclarer ici, sinon on retombe sur deux couleurs. */
.ver-vie {
    /*  🔑 4 px de marge horizontale en plus, et c'est un rattrapage de police,
        pas une cote du jeu : le jeu donne la même marge aux deux cadres, mais
        sa police dessine les chiffres plus larges que les lettres (7,7 px
        contre 6,7). La nôtre est à chasse fixe, donc sans ce rattrapage le
        cadre de la vie sortirait trop étroit face à celui du nom. */
    padding: 2px 8px;
}

/*  🚨 `.sprite--pose` ET `.caisse-vol--tombe` DOIVENT FIGURER ICI NOMMÉMENT.
    Ils sont plus spécifiques que `.sprite` (deux classes contre une pour le
    premier, un sélecteur étranger pour le second) : sans eux, les caisses
    continueraient de tomber du ciel sur un écran réglé en mouvement réduit,
    alors que c'est le mouvement le plus voyant de tout le décor.
    ⚠️ `decor.js` ne programme déjà aucun largage en mode sobre ; cette règle est
    la seconde barrière, celle qui tient même si l'autre saute. */
@media (prefers-reduced-motion: reduce) {
    .nuage, .vague, .sprite, .vent-remplissage,
    .sprite--pose, .caisse-vol--tombe { animation: none; }
    .caisse-vol { display: none !important; }
}

/* ==========================================================================
   ACCUEIL — pseudo et drapeau, rien d'autre
   ========================================================================== */

#accueil {
    min-height: 100vh;
    display: flex; align-items: center; justify-content: center;
    padding: 2rem 1rem 17rem;      /* de la place pour le terrain et l'eau */
}
.accueil-boite { width: 100%; max-width: 520px; text-align: center; }
.accueil-boite .marque { margin-bottom: .5rem; }
.accueil-boite .baseline { margin-bottom: 1.6rem; }
.accueil-boite .carte { text-align: left; }

.champ--gros { font-size: 1.15rem; padding: .75rem .8rem; }
.bouton--gros { width: 100%; padding: .85rem 1.2rem; font-size: 1rem; margin-top: 1rem; }

.drapeau-entete {
    display: flex; align-items: center; justify-content: space-between;
    gap: .5rem; margin: 1.1rem 0 .4rem;
}
.drapeau-choisi { display: flex; align-items: center; gap: .45rem; font-size: .85rem; color: var(--texte); }
.drapeau-choisi img { width: 24px; height: 16px; image-rendering: pixelated; }

/*  Grille défilante : 92 drapeaux ne tiennent pas à l'écran, et les étaler
 *  ferait descendre le bouton hors de vue sur un portable. */
.drapeaux {
    display: grid; gap: 4px; margin-top: .5rem;
    grid-template-columns: repeat(auto-fill, minmax(38px, 1fr));
    max-height: 190px; overflow-y: auto;
    padding: .5rem; background: rgba(0, 0, 0, .3);
    border: 1px solid var(--bord); border-radius: 8px;
}
.drapeau {
    padding: .25rem; line-height: 0; cursor: pointer;
    background: transparent; border: 2px solid transparent; border-radius: 5px;
}
.drapeau:hover { background: rgba(255, 255, 255, .1); }
.drapeau--choisi { border-color: var(--jaune); background: rgba(255, 210, 63, .18); }
.drapeau img { width: 24px; height: 16px; image-rendering: pixelated; }

.drapeaux-titre {
    grid-column: 1 / -1; font-size: .68rem; text-transform: uppercase;
    letter-spacing: .1em; color: var(--texte-doux); font-weight: 700;
    margin: .4rem 0 .1rem;
}
.drapeaux-vide { grid-column: 1 / -1; color: var(--texte-doux); font-size: .85rem; padding: .5rem; }

/* ------------------------------------------------------- entête de l'app */

.entete--app {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap;
    max-width: 900px; margin: 0 auto; padding: 1.5rem 1rem .5rem;
    text-align: left;
}
.marque--petite { font-size: clamp(1.2rem, 4vw, 1.8rem); }
/*  🚨 PAS DE CADRE AUTOUR DE BOUTONS QUI EN ONT DÉJÀ UN.
 *
 *  📏 Signalé à l'écran, et c'est le même défaut que le panneau « Find a game » :
 *  la barre d'identité dessinait SON filet, puis « ♪ » et « Change » dessinaient
 *  les leurs à trois pixels de là. Trois traits parallèles pour deux boutons et
 *  un pseudo.
 *
 *  🔑 La barre n'est pas un contenant, c'est un ALIGNEMENT. Ce qu'elle apporte,
 *  c'est le `flex` et l'espacement ; le cadre et l'aplat servaient à la détacher
 *  du décor animé, et ce travail est déjà fait — par le voile de lisibilité en
 *  dessous, et par les boutons eux-mêmes, qui sont opaques et bordés.
 *
 *  ⚠️ Le pseudo, LUI, garde son aplat : c'est le seul élément de la barre qui
 *  soit du texte nu posé sur un décor qui bouge. Sans lui il deviendrait
 *  illisible dès qu'un nuage passe derrière — le contraire du but.
 */
.moi-carte {
    display: flex; align-items: center; gap: .5rem;
    padding: 0;
    background: none; border: 0;
    font-weight: 700; font-size: .9rem;
}
/*  L'aplat suit le NOM, pas la barre : la lisibilité est un besoin du texte.
 *  ⚠️ ET LE NOM ET « CHANGE » ONT EXACTEMENT LA MÊME BOÎTE. Ils se touchent :
 *  deux hauteurs différentes côte à côte se lisent comme un défaut d'alignement,
 *  pas comme une hiérarchie. Même padding, même bordure, même ligne de base —
 *  d'où le groupe de sélecteurs plutôt que deux règles qui dériveraient. */
.moi-carte > .moi-nom,
.moi-carte > #changer-profil {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .35rem .7rem;
    min-height: 2rem;
    background: var(--carte); border: 1px solid var(--bord);
    font-size: .9rem; line-height: 1.2;
}
/*  🚨 LE SON N'EST PAS ENCADRÉ, ET C'EST CE QUI LE RANGE À SA PLACE.
 *  Le nom et « Change » forment une paire : deux boîtes identiques, deux choses
 *  qui parlent de QUI vous êtes. Encadrer le haut-parleur en faisait un
 *  troisième membre de cette famille alors qu'il n'y appartient pas — c'est un
 *  réglage de la page, comme le volume d'une vidéo.
 *  🔑 Sans cadre, il faut qu'il pèse par sa TAILLE : une petite icône nue à côté
 *  de deux boîtes de 2 rem se lit comme une miette oubliée. L'icône monte donc à
 *  22 px et la zone cliquable garde les 2 rem des voisines — on aligne la ligne
 *  de base sans redessiner un cadre.
 *  ⚠️ La zone de clic ne rétrécit PAS avec le cadre : c'est la cible tactile, et
 *  elle doit rester de la taille du doigt, pas de celle du dessin.
 */
.moi-carte > #sons {
    min-height: 2rem; min-width: 2rem; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 0;
}
.moi-carte > #sons svg { width: 22px; height: 22px; }
/*  Le seul retour visuel qui reste : l'icône s'éclaire. */
.moi-carte > #sons:hover { color: var(--jaune); }
.moi-carte img { width: 24px; height: 16px; image-rendering: pixelated; }

/*  Vignette de drapeau dans les listes : rapport 3:2, comme dans le jeu. */
.vignette {
    flex: none; width: 27px; height: 18px;
    image-rendering: pixelated; object-fit: contain;
}
.vignette--vide {
    display: flex; align-items: center; justify-content: center;
    width: 27px; height: 18px;
    background: rgba(255, 255, 255, .1); border-radius: 4px;
    /*  700 : Tahoma s'arrête au gras, il n'y a pas de 800 (voir .marque dans
        style.css). Au-delà, le navigateur épaissit le dessin lui-même. */
    font-weight: 700; font-size: .75rem; color: var(--texte-doux);
}

/* ==========================================================================
   LE VOILE DE LISIBILITÉ
   ==========================================================================
   🔑 LA PIÈCE MAÎTRESSE, et elle ne retire rien au décor.

   Le problème mesuré : --texte (#eaf2f8) sur l'herbe du terrain, c'est 1,8:1.
   Sur le ciel de nuit d'avant, c'était 15:1 — d'où une interface qui « marchait
   bien » jusqu'au jour où on lui a mis une vraie map derrière.

   Trois façons de régler ça, et pourquoi celle-ci :
     — assombrir le décor en entier : c'est renoncer au décor, hors sujet ;
     — tout mettre dans des cartes opaques : ça règle le corps de texte mais
       pas les titres de section, le pied de page ni les onglets, qui n'ont
       aucun bloc sous eux — et ça transforme la page en pile de boîtes ;
     — un voile qui suit la COLONNE de contenu, et s'éteint sur les côtés.
       C'est celle-ci. Le décor reste à pleine intensité là où on le regarde
       (les bords, où vivent les vers, la jauge de vent et le chrono) et il
       s'estompe seulement là où on lit.

   Le dégradé est plein sur 60 rem — soit 960 px, la colonne de 900 px plus une
   marge — puis s'éteint jusqu'au bord de la fenêtre. Sur un écran de 1920, ça
   laisse 480 px de fondu de chaque côté : assez long pour qu'on ne voie aucune
   arête, seulement une pénombre.

   🔑 LA TEINTE EST CELLE DU CIEL DU JEU, pas du gris ni du noir. rgb(6,18,38)
   est à trois points de --ciel-haut (#0a1830) : par-dessus le haut de la page
   le voile est donc INVISIBLE — on ne peut pas assombrir du bleu nuit avec du
   bleu nuit — et il ne se met à mordre qu'en descendant vers le terrain, là où
   il y a vraiment quelque chose à calmer. C'est ce qui évite le « bandeau noir
   opaque et triste » en haut de l'écran.

   .62 est le minimum qui tient : mesuré sur le liseré jaune-olive du terrain
   (le pixel le plus clair du décor), il donne 5,0:1 pour --texte-doux et 7,2:1
   pour --texte. En dessous, les titres de section repassaient sous 4,5:1.

   🚨 `z-index: -1` COMME #decor, ET APRÈS LUI DANS LE HTML : c'est l'ordre du
   document qui les départage. Lui donner 0 le ferait passer DEVANT le contenu.
   ⚠️ `position: fixed` pour la même raison que le décor : le voile ne doit pas
   défiler, sinon il découvrirait le terrain dès le premier scroll. */
#voile-lisibilite {
    position: fixed; inset: 0; z-index: -1; pointer-events: none;
    background: linear-gradient(to right,
        rgba(6, 18, 38, 0),
        rgba(6, 18, 38, .62) calc(50% - 30rem),
        rgba(6, 18, 38, .62) calc(50% + 30rem),
        rgba(6, 18, 38, 0));
}

/*  🚨 SUR UN TÉLÉPHONE, LE CALCUL EN REM S'EFFONDRE — silencieusement. Sur
 *  400 px de large, `calc(50% - 30rem)` vaut -280 px : le navigateur remonte
 *  cette borne à 0, et le voile devient un aplat plein bord à bord, sans le
 *  moindre fondu. On lirait très bien, mais le décor serait éteint sur toute la
 *  largeur — et sur mobile c'est justement le bas de l'écran, avec le terrain
 *  et l'eau, qui fait tout l'effet. On repasse donc en pourcentages : le fondu
 *  est court (7 %), mais il suffit à garder les bords francs. */
@media (max-width: 640px) {
    #voile-lisibilite {
        background: linear-gradient(to right,
            rgba(6, 18, 38, 0),
            rgba(6, 18, 38, .62) 7%,
            rgba(6, 18, 38, .62) 93%,
            rgba(6, 18, 38, 0));
    }
}

/*  🔑 LE FLOU EST UN BONUS, PAS LE MÉCANISME. Il était à 2 px : dérisoire face
 *  au grain d'une texture de terrain, qui reste parfaitement identifiable à
 *  travers. À 9 px le décor devient une nappe de couleur derrière la carte —
 *  on voit toujours qu'il bouge, on ne lit plus ses détails.
 *
 *  🚨 MAIS LA LISIBILITÉ NE DOIT RIEN LUI DEVOIR. Un navigateur sans
 *  `backdrop-filter` doit lire aussi bien : c'est pour ça que --carte est passé
 *  à un aplat sombre à 74 % plutôt que de compter sur le flou. Vérifié : sans
 *  flou du tout, on est encore à 12:1 pour --texte sur une carte.
 *
 *  `saturate(.6)` compte autant que le flou : ce qui gêne dans ce décor, ce
 *  n'est pas tant sa clarté que sa SATURATION — le vert de l'herbe et l'orange
 *  de la terre. Désaturé, ce qui passe à travers la carte devient un fond
 *  neutre au lieu d'une couleur qui se bat avec le texte. */
.carte, .ligne, .bandeau, .vide, .modale, .moi-carte {
    /*  ⚠️ Safari n'a levé le préfixe qu'en 18 ; sans cette ligne, tout un parc
        d'iPhone récents perd le flou. Il ne perdrait pas la lecture — voir
        ci-dessus — mais c'est une ligne. */
    -webkit-backdrop-filter: blur(9px) saturate(.6);
    backdrop-filter: blur(9px) saturate(.6);
}

/*  De la place pour le terrain (150 px) et l'eau (150 px). */
.pied { padding-bottom: 16rem; }

/* ==========================================================================
   LE BANDEAU DE TEMPS — en bas à gauche, comme dans le jeu
   ==========================================================================
   🔑 Deux compteurs empilés, relevés sur une capture : en haut, une petite
   plaque claire qui décompte vers la mort subite ; en dessous, un gros cube
   noir avec le temps du tour. C'est purement graphique — rien ici ne mesure
   quoi que ce soit du site.

   ⚠️ Chiffres en `tabular-nums` : sans ça, la largeur du bloc saute à chaque
   seconde parce qu'un « 1 » est plus étroit qu'un « 8 ». */

.chrono {
    position: absolute; left: 22px; bottom: 22px;
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    font-variant-numeric: tabular-nums;
    font-family: 'Consolas', 'DejaVu Sans Mono', monospace;
}
/*  🚨 LES DEUX BLOCS SONT NOIRS À CADRE GRIS. Deux essais ratés avant celui-ci :
 *  d'abord un biseau clair/sombre qui donnait un bouton en relief, puis une
 *  plaque à FOND GRIS et chiffres noirs — l'inverse de la réalité.
 *
 *  Mesures au pixel sur la capture (94x82) :
 *    - plaque : x 17..59, y 10..27  →  43x18, cadre gris de 2 px,
 *      intérieur presque noir (9,16,33 / 52,52,54), chiffres GRIS CLAIR
 *      (125..148), plus une ligne claire juste sous le bord haut ;
 *    - cube   : x 11..66, y 32..71  →  56x40, cadre gris de 3 px,
 *      intérieur noir (4,0,2), chiffres BLANCS (252,255,254).
 *
 *  🔑 Le cube est PLUS LARGE que la plaque, et les chiffres de la plaque sont
 *  volontairement plus ternes que ceux du cube — c'est ce contraste qui dit
 *  lequel des deux compte. */
/*  🚨 LE CUBE DOIT TOUJOURS ÊTRE PLUS LARGE QUE LA PLAQUE. Mesuré : 43 px de
    plaque pour 56 px de cube. Avec une police de 13 px, « 99:13 » débordait et
    la plaque dépassait le cube — l'inverse du jeu. D'où une police plus
    petite en haut, et un  en bas calé sur la largeur réelle. */
.chrono-mort {
    padding: 0 4px;
    font-size: 11px; font-weight: 700; line-height: 14px; letter-spacing: 0;
    color: #9aa0a4; background: #000;
    border: 2px solid #7c8084;
    /*  La ligne claire relevée juste sous le bord haut. */
    box-shadow: inset 0 1px 0 #93959e;
}
.chrono-tour {
    min-width: 56px; padding: 0 6px 2px;
    /*  700 et pas 800 : ce bloc hérite du monospace de `.chrono` (Consolas /
        DejaVu Sans Mono), et ces deux-là s'arrêtent au gras exactement comme
        Tahoma. À 30 px, un 800 synthétisé bouchait le creux des chiffres. */
    font-size: 30px; font-weight: 700; line-height: 34px; text-align: center;
    color: #fff; background: #000;
    border: 3px solid #7c8084;
}
