/* =============================================================================
   YASHIMABOT — la seule source de vérité du style.
   =============================================================================

   ⚠ Le style vivait en QUATRE exemplaires : 1 530 lignes de CSS en ligne
   réparties sur quatre pages, sans aucune feuille externe, les douze variables
   de palette redéfinies dans chacune. `login.html` n'en déclarait que onze —
   une couleur y avait déjà divergé.

   C'est ce qui expliquait les chiffres de l'audit #48 : 81 couleurs en dur pour
   douze variables, 11 tailles de police, 11 styles de bouton dont sept uniques.
   La palette n'était pas ignorée par négligence : la maintenir supposait de la
   corriger quatre fois.

   Tout ce qui suit est un JETON. Aucune valeur ne doit être écrite en dur
   ailleurs.
   ============================================================================= */

/* --- La police, embarquée -------------------------------------------------
   Les quatre pages la chargeaient depuis Google Fonts. Un bot auto-hébergé qui
   sert ses propres pages n'a pas à dépendre d'un tiers pour son apparence — ni
   à lui faire connaître l'adresse de chacun de ses visiteurs. */
@font-face {
    font-family: 'Crimson Text';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/polices/crimson-latin.woff2') format('woff2');
}

@font-face {
    font-family: 'Cinzel';
    font-style: normal;
    font-weight: 400 600;
    font-display: swap;
    src: url('/polices/cinzel-latin.woff2') format('woff2');
}

:root {
    /* --- Palette : cinq couleurs, un rôle chacune -------------------------
       Les contrastes sont CALCULÉS, pas estimés — rapport WCAG sur --abysse. */
    /* ⚠ PALETTE « ENCRE ET OR ». L'accent prend la couleur de l'iris : elle dit
       que l'accent VIENT de l'œil, et non l'inverse. Le violet ne disparaît
       pas — il descend aux bordures et aux fonds, où il tient l'ambiance sans
       se disputer l'attention avec ce qu'on doit lire.

       Contrastes recalculés sur le NOUVEAU fond (#0B0910), pas repris de
       l'ancien : changer le fond change tous les rapports. */
    --abysse:    #0B0910;   /* le fond, partout                              */
    --parchemin: #EDE6DA;   /* texte principal            — 13,5:1           */
    --brume:     #A79B8C;   /* texte secondaire, libellés —  6,3:1           */
    --lueur:     #E3B341;   /* accents, liens, focus      —  8,4:1           */
    /* ⚠ S'appelait `--oeil` : c'était la couleur de l'iris du logo, et le logo a
       changé. Le nom mentait déjà bien avant — sur ses douze usages, UN SEUL était
       l'œil ; les onze autres disent « suspendu », « danger », « quitter ». Une
       variable nommée par son emplacement d'origine et non par son intention. */
    --alerte:    #F0503C;   /* alertes, danger, suspendu  —  5,6:1           */

    /* ⚠ MÊME TEINTE que `--palier-haute`, et c'est voulu : la palette n'a qu'un
       vert, et deux verts proches se liraient comme une erreur d'impression.
       Mais PAS le même nom, et c'est voulu aussi : `--palier-haute` veut dire
       « 75-99 % d'avancement ». S'en servir pour l'état de connexion referait le
       défaut corrigé juste au-dessus — une variable nommée par l'endroit où elle
       est née plutôt que par ce qu'elle dit. */
    --vivant:    #4ADE80;   /* connexion vivante, suivi actif — 10,8:1       */

    /* ⚠ #7C7264 avait été essayé d'abord pour `--murmure` : mesuré à 3,5:1,
       donc SOUS le seuil de 4,5:1. C'est exactement la faute que le #49 avait
       relevée sur #3a2050 — une teinte choisie pour dire « discret » et qui
       dit « illisible ». Éclaircie jusqu'à passer, sans cesser d'être en
       retrait. Le motif d'origine tient toujours : un texte qu'on ne peut pas
       lire ne se distingue pas d'un texte absent, et c'est précisément celui
       qui explique une page vide. */
    --murmure:   #948A7B;   /* texte volontairement discret —  4,9:1         */

    /* Nuances de fond, dérivées de l'encre. */
    --abysse-clair: #14111A;
    --abysse-haut:  #1C1824;

    /* ⚠ Ces deux-là ne passent PAS le seuil de lisibilité. Elles restent —
       l'identité en dépend, et l'œil de YashimaBot les emploie — mais JAMAIS pour
       du texte. Bordures et aplats seulement. C'est une règle, pas une
       suppression. */
    --sortilege: #7A3FA0;   /* le violet de la marque, conservé tel quel     */
    --bordure:   #3A3040;

    /* --- Typographie : trois rôles ----------------------------------------
       Titres en Cinzel, l'arcane de YashimaBot ; texte en Crimson Text, la serif du
       projet. Les deux sont EMBARQUÉES — l'esthétique est conservée, et elle ne
       dépend plus de Google. La pile système ne sert que de repli.

       Chiffres en tabulaire, pour que les colonnes de ports, de checks et de
       pourcentages s'alignent au lieu de se décaler d'un caractère à l'autre. */
    /* ⚠ Le repli emoji est EXPLICITE. Les noms de guildes et de salons Discord en
       contiennent couramment — « 🌀-archipelago ». Les deux polices embarquées ne
       couvrent que le latin : sans famille emoji nommée, le rendu dépend de la
       chaîne de repli du navigateur, qui varie d'un système à l'autre. */
    --emoji:   'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
    --titre:   'Cinzel', Georgia, var(--emoji), serif;
    --texte:   'Crimson Text', Georgia, var(--emoji), serif;
    --texte-repli: system-ui, -apple-system, 'Segoe UI', sans-serif;
    --chiffre: ui-monospace, 'Cascadia Mono', 'Consolas', monospace;

    /* Échelle fixe, SIX pas — là où onze valeurs se disputaient la place. */
    --t-xs: 11px;
    --t-s:  13px;
    --t-m:  15px;
    --t-l:  18px;
    --t-xl: 24px;
    --t-xxl: 32px;

    /* --- Espacement : base 4, six pas ------------------------------------- */
    --e-1: 4px;
    --e-2: 8px;
    --e-3: 12px;
    --e-4: 16px;
    --e-5: 24px;
    --e-6: 32px;

    /* --- Les paliers de progression ---------------------------------------
       ⚠ Contrastes CALCULÉS sur la piste de jauge (--abysse-clair), pas choisis
       à l'œil. Le seuil WCAG pour un objet graphique porteur de sens est 3:1.

       ⚠ Le dégradé qu'ils remplacent partait de --sortilege, à 2,88:1 — SOUS le
       seuil. La moitié gauche de chaque jauge était déjà trop pâle, et personne
       ne l'avait vu : le #49 avait relevé cette couleur pour du texte, jamais
       pour une jauge.

       ⚠ Ces teintes ne portent JAMAIS l'information seules — rouge et vert sont
       indistinguables pour environ 8 % des hommes. Le pourcentage et le compte
       sont toujours écrits à côté. */
    --palier-faible:  #F0503C;   /*   0–24 %  —  5,29:1  (c'est --alerte)        */
    --palier-basse:   #F5A623;   /*  25–49 %  —  9,26:1                        */
    --palier-moyenne: #E8C547;   /*  50–74 %  — 11,18:1                        */
    --palier-haute:   #4ADE80;   /*  75–99 %  — 10,77:1                        */
    --palier-pleine:  #FFD166;   /*    100 %  — 13,02:1                        */

    /* --- UN seul rayon. Les cinq précédents disparaissent. ----------------- */
    --rayon: 3px;

    /* --- Compatibilité ----------------------------------------------------
       ⚠ Ces alias portent les anciens noms, utilisés par les 1 530 lignes déjà
       en place. Ils permettent de retirer les quatre blocs `:root` dupliqués
       SANS réécrire l'existant : les pages continuent de fonctionner, et il n'y
       a plus qu'un endroit où changer une couleur.

       À résorber écran par écran, pas d'un coup.

       ⚠ Les alias SUIVENT la nouvelle palette, pour que les écrans non encore
       repris basculent avec elle plutôt que de rester en arrière — c'était
       déjà le principe, il fallait juste ne pas l'oublier en changeant les
       cinq couleurs au-dessus.

       ⚠ `--magenta` et `--magenta-dark` étaient la cinquième et la sixième
       teinte violette du fichier ; elles pointent maintenant vers
       `--sortilege` et `--bordure`, donc les filets tricolores deviennent
       monochromes sans qu'il faille les réécrire un à un.

       ⚠ `--text-mid` passe de `--lueur` à `--brume`, et c'est un changement de
       SENS, pas de teinte : il désignait l'accent, il désigne désormais le
       texte secondaire — ce que son nom a toujours dit. Sous l'ancienne
       palette les deux étaient proches ; sous celle-ci, l'accent est de l'or,
       et tout le texte secondaire du dépôt serait devenu doré. */
    --purple:       var(--sortilege);
    --purple-light: #B98FD4;
    --purple-dark:  var(--bordure);
    --purple-glow:  var(--lueur);
    --dark:         var(--abysse);
    --dark-mid:     var(--abysse-clair);
    --dark-light:   var(--abysse-haut);
    --magenta:      var(--sortilege);
    --magenta-dark: var(--bordure);
    --text-light:   var(--parchemin);
    --text-mid:     var(--brume);
    --border:       var(--bordure);
}

/* --- Le focus clavier, visible ---------------------------------------------
   Il ne l'était nulle part : naviguer au clavier revenait à avancer à l'aveugle. */
:focus-visible {
    outline: 2px solid var(--lueur);
    outline-offset: 2px;
    border-radius: var(--rayon);
}

/* --- LES HUIT ÎLES, ET L'ÉTAT QU'ELLES PORTENT ------------------------------

   ⚠ CE DESSIN N'EST PAS DÉCORATIF. Il dit si le bot suit encore la room :
   `js/other/partage.js` lui écrit `data-etat`. Ce que quatre écrans peinent à
   dire, il le dit d'un coup d'œil — et c'est la seule chose que la page publique
   montre à ce sujet. Le retirer supprimerait un état, pas un ornement.

   ⚠ ⓘ CE COMMENTAIRE DISAIT L'INVERSE, ET C'ÉTAIT MA FAUTE. Il portait :
   « YashimaBot, dans ses formes finales, EST un œil géant qui observe. » La phrase
   était vraie de VAATI — sa forme finale, dans Zelda, est un œil géant — et le
   remplacement mécanique du nom l'a rendue absurde sans que rien ne le signale :
   Yashima, ce sont huit îles, et aucune n'est un œil. Un renommage se relit,
   il ne se substitue pas.

   Le fond reste vrai, lui : ce bot est un PUR OBSERVATEUR — constat du #27, ni
   LocationChecks, ni Say, ni Bounce. Il regarde une partie et rapporte, sans
   jamais y toucher. Un archipel qu'on observe depuis la mer dit cela aussi bien
   qu'un œil, sans emprunter la mythologie de quelqu'un d'autre.
   --------------------------------------------------------------------------- */
.iles-etat {
    display: inline-block;
    width: 26px;
    height: 26px;
    color: var(--vivant);
    filter: drop-shadow(0 0 6px color-mix(in srgb, var(--vivant) 45%, transparent));
    transition: color .4s ease, opacity .4s ease, filter .4s ease;
    vertical-align: middle;
}

.iles-etat circle { fill: currentColor; }

/* ⚠ L'île de tête suit la MÊME couleur que les autres ici, contrairement à la
   marque de la colonne. Là-bas, l'or lui donne un haut et empêche l'anneau de se
   lire comme un indicateur de chargement. Ici, la couleur DIT l'état : deux
   teintes à la fois rendraient le vert et le gris moins nets, et c'est
   précisément ce qu'on demande à ce dessin de dire d'un coup d'œil. */

/* Connexion perdue : tout passe au gris. ⚠ L'opacité SEULE ne suffirait pas —
   un vert vif à 40 % reste vert sur ce fond sombre, et l'état se devinerait au
   lieu de se lire. La teinte change donc aussi, et la lueur s'éteint. */
.iles-etat[data-etat="ferme"] {
    opacity: .5;
    color: var(--brume);
    filter: none;
}

/* ⓘ Aucune animation, et le fichier disait déjà pourquoi à propos de l'œil :
   « la pulsation ne dit rien qu'un état statique ne dise déjà ». S'y ajoute une
   raison propre à ce dessin : huit disques en anneau qui pulsent, c'est la forme
   exacte d'un indicateur de chargement. La page semblerait attendre. */

/* ⓘ `@keyframes veille` vivait ici, et pulsait la lueur de l'œil. Elle part avec
   son seul appelant. ⓘ `login.html` avait sa jumelle, `veille-connexion` — deux noms
   qui se ressemblaient sans avoir jamais été liés. Elle est partie avec l'œil de
   cette page : plus aucune marque de ce dépôt n'est animée. */

/* --- Réduction des animations, partout -------------------------------------
   Le réglage système est respecté sans exception : ce qui bouge s'arrête. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* --- Les chiffres s'alignent -----------------------------------------------
   Ports, checks, pourcentages, dates : en colonne, ils doivent se comparer
   d'un coup d'œil, pas se décaler d'un caractère à l'autre. */
.chiffre, td.chiffre {
    font-family: var(--chiffre);
    font-variant-numeric: tabular-nums;
}

/* --- Les formulaires de configuration ---------------------------------------
   ⚠ Il n'y avait ni message d'erreur par champ, ni aide, ni valeur par défaut
   affichée : trente-cinq champs, et pour tout retour un mot — « Erreur » — dans
   une notification qui s'efface au bout de trois secondes.
   --------------------------------------------------------------------------- */

/* Les liens de l'en-tête d'`index.html`. Leur style vivait dans une chaîne de
   caractères, répété trois fois avec ses huit déclarations en ligne — parce que
   la barre était composée en `innerHTML`. Elle ne l'est plus. */
.lien-entete {
    display: inline-flex;
    align-items: center;
    padding: var(--e-1) var(--e-3);
    font-family: var(--titre);
    font-size: var(--t-xs);
    color: var(--brume);
    text-decoration: none;
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
}

.lien-entete:hover { color: var(--lueur); border-color: var(--sortilege); }

/* --- La première visite ------------------------------------------------------
   ⚠ `login.html` ne disait nulle part ce que fait le bot : un nom, une devise, un
   bouton. Qui arrive là n'a rien pour décider s'il veut continuer.
   --------------------------------------------------------------------------- */
.promesse {
    margin: var(--e-4) 0;
    font-size: var(--t-m);
    line-height: 1.5;
    color: var(--parchemin);
    text-align: left;
}

/* La preuve est DESSINÉE, pas photographiée : une capture serait un binaire à
   refaire à chaque changement d'interface, et elle vieillirait en silence. */
.apercu {
    margin: 0 0 var(--e-4);
    padding: var(--e-3);
    text-align: left;
    background: #313338;                 /* le fond d'un salon Discord */
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
}

.apercu-ligne { display: flex; gap: var(--e-3); align-items: flex-start; }

.apercu-avatar {
    flex: none;
    width: 32px; height: 32px;
    display: grid; place-items: center;
    border-radius: 50%;
    background: var(--abysse-haut);
    color: var(--alerte);
}

.apercu-nom { font-family: var(--titre); font-size: var(--t-s); color: var(--lueur); }

/* ⚠ La pastille « BOT » de Discord est minuscule dans l'original — je l'avais
   reproduite à 10 px, et le banc l'a refusée dans la seconde. Elle suit donc
   l'échelle du projet : discrète sur grand écran, lisible sur téléphone. La
   ressemblance ne justifie pas de recopier un défaut. */
.apercu-marque {
    margin-left: var(--e-1);
    padding: 0 var(--e-1);
    font-size: var(--t-xs);
    color: #fff;
    background: #5865f2;
    border-radius: 2px;
    vertical-align: middle;
}

/* La barre de couleur à gauche : la signature visuelle d'un embed Discord. */
.apercu-embed {
    margin-top: var(--e-2);
    padding: var(--e-2) var(--e-3);
    font-size: var(--t-s);
    color: #dbdee1;
    background: #2b2d31;
    border-left: 3px solid var(--lueur);
    border-radius: 0 var(--rayon) var(--rayon) 0;
}

/* ⚠ #D37166 EN DUR, et ce n'est pas un oubli : c'est la couleur que le bot envoie
   vraiment pour un piège — `ConstructeurMessageAp.Piege`, 13857126 en décimal, le
   saumon d'Archipelago. Elle n'appartient pas à cette palette, pas plus que les
   #2b2d31 et #dbdee1 de l'encadré, qui sont ceux de Discord. Une maquette qui montre
   ce que fait un AUTRE système emprunte ses couleurs, sinon elle le représente faux.

   ⚠ Si `Piege` change de valeur là-bas, cette ligne doit suivre — rien ne les relie
   automatiquement, et l'écart ne se verrait qu'en comparant la page au vrai Discord. */
.apercu-embed-2 { border-left-color: #D37166; }

.apercu figcaption {
    margin-top: var(--e-3);
    font-size: var(--t-s);
    font-style: italic;
    color: var(--brume);
    text-align: center;
}

/* --- La couleur d'une barre suit son palier ----------------------------------
   Une seule déclaration par palier, employée par la page publique ET par la page
   de suivi : les deux racontent la même partie, elles doivent la raconter de la
   même façon.
   --------------------------------------------------------------------------- */
[data-palier="faible"]  { background: var(--palier-faible)  !important; }
[data-palier="basse"]   { background: var(--palier-basse)   !important; }
[data-palier="moyenne"] { background: var(--palier-moyenne) !important; }
[data-palier="haute"]   { background: var(--palier-haute)   !important; }

/* Une partie finie mérite mieux qu'un aplat : le doré est la seule teinte de la
   gamme qui se remarque sans qu'on la cherche. */
[data-palier="pleine"] {
    background: linear-gradient(90deg, var(--palier-haute), var(--palier-pleine)) !important;
}

/* Le compte sous la barre — « 110 / 120 ». Un pourcentage seul ne dit pas si
   50 % vaut cinq lieux ou quatre cents. */
.compte-lieux {
    font-family: var(--chiffre);
    font-variant-numeric: tabular-nums;
    font-size: var(--t-s);
    color: var(--brume);
}

/* --- Les quatre états d'une zone ---------------------------------------------
   ⚠ Neuf zones échouaient en SILENCE : un `return` nu laissait le tableau sur
   ses données précédentes, ou sur « Chargement… », pour toujours. Rien ne
   distinguait « le serveur est tombé » de « ça arrive ».
   --------------------------------------------------------------------------- */

/* Le squelette occupe la MÊME hauteur qu'une ligne réelle : sans cela, la page
   se dépliait d'un coup à l'arrivée des données, et ce qu'on lisait en dessous
   sautait. */
.squelette {
    height: 1.25rem;
    margin: var(--e-2) 0;
    border-radius: var(--rayon);
    background: linear-gradient(90deg,
                var(--abysse-clair) 0%, var(--abysse-haut) 50%, var(--abysse-clair) 100%);
    background-size: 200% 100%;
}

@media (prefers-reduced-motion: no-preference) {
    .squelette { animation: attente 1.4s ease-in-out infinite; }
}

@keyframes attente {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

/* Le bouton de réessai vit DANS le message d'erreur : c'est là qu'on le
   cherche, pas dans un coin de la page. */
.etat-panne .btn {
    display: block;
    margin: var(--e-3) auto 0;
}

/* --- Le partage public d'une partie ------------------------------------------
   ⚠ Il vivait dans un bandeau a part, AU-DESSUS de l'en-tete. Le lien d'une
   partie est pourtant une de ses proprietes, au meme titre que son serveur et
   son login : le separer obligeait a le chercher ailleurs que la ou on lit tout
   le reste. Il est desormais une ligne du bloc d'informations.
   --------------------------------------------------------------------------- */
.ligne-partage {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--e-2);
    margin-top: var(--e-3);
    padding-top: var(--e-3);
    border-top: 1px solid var(--bordure);
}

.partage-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--e-2);
    flex: 1 1 20rem;
}

/* Le lien en clair, dans un champ. Un bouton seul ne permet ni de le relire, ni
   de le copier a la main quand le presse-papiers refuse — ce qui arrive des que
   la page n'est pas servie en HTTPS. */
.champ-lien {
    flex: 1 1 22rem;
    min-width: 0;
    padding: var(--e-1) var(--e-2);
    font-family: var(--chiffre);
    font-size: var(--t-s);
    color: var(--brume);
    background: var(--abysse);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
}

.champ-lien:focus { color: var(--parchemin); border-color: var(--lueur); }

/* Une mention volontairement en retrait, mais lisible : état vide, absence de
   donnée, précision secondaire. Remplaçait deux `style=` en ligne à 1,4:1. */
.mention-discrete {
    color: var(--murmure);
    font-style: italic;
    font-size: var(--t-s);
}

/* --- Le lien de retour ------------------------------------------------------
   ⚠ Il n'existait que dans le `<style>` de la page de suivi. Reutilise sur la
   page d'un serveur, il tombait au bleu par defaut du navigateur — mesure a
   2,11:1 pour un seuil de 4,5:1, et le banc l'a signale des la premiere mesure.

   Une classe definie dans UNE page n'est pas une classe : c'est un style local
   qui ressemble a une classe, et qui ment des qu'on l'emprunte. */
.back {
    display: inline-flex;
    align-items: center;
    color: var(--brume);
    text-decoration: none;
    font-family: var(--titre);
    font-size: var(--t-s);
    padding: var(--e-2) var(--e-3);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    transition: border-color .15s ease, color .15s ease;
    min-height: 44px;
}

.back:hover,
.back:focus-visible {
    border-color: var(--lueur);
    color: var(--lueur);
}

/* --- La courbe d'évolution ---------------------------------------------------
   SVG écrit à la main, aucune bibliothèque — le #49 a embarqué les polices pour
   ne plus dépendre d'un tiers, charger un moteur de graphiques depuis un CDN
   reviendrait exactement dessus. */
.graphe {
    display: block;
    width: 100%;
    height: auto;
    overflow: visible;
}

.graphe .grille {
    stroke: var(--bordure);
    stroke-width: 1;
}

/* ⚠ `--t-s`, pas une valeur en dur : ce jeton passe à 15 px sous 900 px, donc les
   graduations respectent le plancher de lisibilité comme le reste. Écrites à 11 px,
   elles étaient sous ce plancher — et le `viewBox` faisant exactement la largeur du
   conteneur, ces 11 px étaient bien 11 px à l'écran, pas une illusion d'échelle. */
.graphe .graduation {
    fill: var(--murmure);
    font-family: var(--chiffre);
    font-size: var(--t-s);
}

.graphe .courbe {
    fill: none;
    stroke-width: 1.75;
    stroke-linejoin: round;
    stroke-linecap: round;
}

/* Le total passe par-dessus, plus épais, dans la couleur des jauges pleines. */
.graphe .courbe-total {
    stroke: var(--parchemin);
    stroke-width: 3;
}

/* --- Un graphique par paquet de mondes ---------------------------------------
   ⚠ `figure` porte une marge par défaut dans tous les navigateurs — sans ce
   `margin`, chaque paquet en héritait 40 px à gauche et à droite, et les
   graphiques ne s'alignaient plus sur le reste de la carte. */
.graphe-paquet {
    margin: 0 0 var(--e-4);
}

.graphe-titre {
    font-family: var(--titre);
    font-size: var(--t-s);
    color: var(--murmure);
    margin-bottom: var(--e-2);
}

/* --- Le survol met une courbe en avant ---------------------------------------

   ⚠ Aucun redessin : `evolution.js` pose deux classes, tout se joue ici. Refaire
   le SVG à chaque pixel parcouru par la souris coûterait soixante polylignes par
   mouvement, sur une page qui en porte déjà plusieurs centaines.

   ⚠ L'opacité seule, et pas la couleur : la courbe visée garde SA teinte et SON
   motif. Les repeindre d'une couleur d'accent ferait perdre le lien avec la
   pastille de légende au moment précis où on la cherche. */
.graphe .courbe {
    transition: opacity .12s ease-out, stroke-width .12s ease-out;
}

.evolution-vise .courbe {
    opacity: .15;
    stroke-width: 1.25;
}

.evolution-vise .courbe-visee {
    opacity: 1;
    stroke-width: 3.25;
}

/* --- Lire le graphique à la souris --------------------------------------------

   ⚠ `crosshair` dit les DEUX gestes que le tracé accepte : suivre une courbe, et
   sélectionner une plage. Un curseur de flèche laissait croire à une image.

   ⚠ `user-select: none` : sans lui, le glissement du zoom surligne les graduations
   au passage, et on relâche sur un graphique barré de bleu. */
.graphe {
    cursor: crosshair;
    user-select: none;
}

/* ⚠ `position: fixed`, donc en coordonnées de fenêtre : la bulle n'a besoin d'aucun
   ancêtre positionné — le conteneur de la page de suivi n'en est pas un — et elle
   échappe au `overflow: hidden` du plein écran, qui l'aurait tranchée près des
   bords.

   ⚠ `pointer-events: none` : posée sous le pointeur, elle recevrait sinon le survol
   à la place du graphique, et se mettrait à clignoter en se fuyant elle-même. */
.graphe-bulle {
    position: fixed;
    z-index: 40;
    pointer-events: none;
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--e-2) var(--e-3);
    background: var(--abysse-clair);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    box-shadow: 0 4px 14px rgb(0 0 0 / .45);
    font-size: var(--t-s);
    color: var(--brume);
    white-space: nowrap;
}

/* ⚠ INDISPENSABLE, et pas une redondance. La règle `[hidden] { display: none }` de
   la feuille du navigateur ne porte qu'un sélecteur d'attribut ; le `display: flex`
   ci-dessus porte une classe, donc il l'emporte. Sans cette ligne, la bulle et le
   cadre de sélection restaient visibles en permanence — deux boîtes grises posées
   dans un coin du graphique, que rien n'expliquait. Vu à l'écran. */
.graphe-bulle[hidden],
.graphe-selection[hidden] { display: none; }

.graphe-bulle b { color: var(--parchemin); font-family: var(--titre); }

.graphe-bulle-pct {
    font-family: var(--chiffre);
    font-variant-numeric: tabular-nums;
    color: var(--lueur);
}

/* La plage que l'on est en train de choisir, pendant le glissement. */
.graphe-selection {
    position: fixed;
    z-index: 39;
    pointer-events: none;
    background: color-mix(in srgb, var(--lueur) 14%, transparent);
    border-left: 1px solid var(--lueur);
    border-right: 1px solid var(--lueur);
}

/* --- Le choix de la période ---------------------------------------------------
   ⚠ « Tout » est un bouton comme les autres, et c'est le chemin du retour. Le
   double-clic sur le tracé n'en est que le raccourci : un raccourci n'est jamais la
   seule porte, sinon la fonction n'existe que pour qui l'a devinée. */
.graphe-periode {
    display: flex;
    align-items: center;
    gap: var(--e-3) var(--e-4);
    margin-bottom: var(--e-3);
    flex-wrap: wrap;
}

.graphe-plages {
    display: flex;
    gap: var(--e-2);
    flex-wrap: wrap;
}

.graphe-plage {
    background: none;
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    padding: var(--e-1) var(--e-3);
    font: inherit;
    font-family: var(--chiffre);
    font-size: var(--t-s);
    color: var(--brume);
    cursor: pointer;
    min-height: 44px;
}

.graphe-plage:hover,
.graphe-plage:focus-visible {
    border-color: var(--lueur);
    color: var(--parchemin);
}

/* ⚠ La période retenue se distingue par sa BORDURE et son texte, pas par la seule
   couleur : `aria-pressed` la dit aux outils d'assistance, et le contraste la dit à
   qui ne distingue pas l'ambre du gris. */
.graphe-plage-active {
    border-color: var(--lueur);
    color: var(--abysse);
    background: var(--lueur);
}

.graphe-entre {
    display: flex;
    align-items: center;
    gap: var(--e-2);
    flex-wrap: wrap;
    font-size: var(--t-s);
    color: var(--brume);
}

.graphe-entre label {
    display: flex;
    align-items: center;
    gap: var(--e-1);
}

/* ⚠ Les champs de date héritent de la police du système, pas de la page : sans ces
   deux lignes ils arrivent en 13,33 px et en couleur système, soit sous le plancher
   de lisibilité et hors de la palette. Même faute que les boutons de légende. */
.graphe-entre input {
    font: inherit;
    font-family: var(--chiffre);
    padding: var(--e-1) var(--e-2);
    min-height: 44px;
    background: var(--abysse-clair);
    color: var(--parchemin);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    color-scheme: dark;
}

.graphe-appliquer {
    background: none;
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    padding: var(--e-1) var(--e-3);
    font: inherit;
    font-size: var(--t-s);
    color: var(--brume);
    cursor: pointer;
    min-height: 44px;
}

.graphe-appliquer:hover,
.graphe-appliquer:focus-visible {
    border-color: var(--lueur);
    color: var(--parchemin);
}

.graphe-etendue {
    font-family: var(--chiffre);
    font-size: var(--t-s);
    color: var(--murmure);
}

/* ⚠ Le mouvement se coupe, le contraste reste : quelqu'un qui demande moins
   d'animation demande moins de MOUVEMENT, pas de perdre la mise en évidence. */
@media (prefers-reduced-motion: reduce) {
    .graphe .courbe { transition: none; }
}

.legende {
    list-style: none;
    display: flex;
    flex-wrap: wrap;
    gap: var(--e-2) var(--e-4);
    margin: var(--e-3) 0 0;
    padding: 0;
    font-size: var(--t-s);
    color: var(--brume);
}

.legende li {
    display: flex;
    align-items: center;
    gap: var(--e-2);
}

.legende b { color: var(--parchemin); font-variant-numeric: tabular-nums; }

.pastille {
    width: 12px;
    height: 12px;
    border-radius: 2px;
    flex: 0 0 auto;
}

/* --- La pastille est le TRAIT, pas un carré ----------------------------------
   ⚠ Depuis que les courbes se distinguent aussi par leur motif — plein, tireté,
   pointillé, une fois les douze teintes recyclées — un carré de couleur ne suffit
   plus à retrouver sa courbe : deux mondes de même teinte y sont identiques. Le
   segment reprend la teinte ET le motif, donc la légende montre littéralement ce
   qui est tracé. */
.pastille-trait {
    width: 22px;
    height: 12px;
    border-radius: 0;
    overflow: visible;
}

.pastille-trait .trait-total { stroke: var(--parchemin); }

/* --- La légende commande les courbes -----------------------------------------

   ⚠ Ces règles vivent ICI et non dans `coquille.css` : `partage.html` ne charge
   que cette feuille. Placées là-bas, le bouton y gardait le style par défaut du
   navigateur — 13,33 px et la couleur système — et le banc l'a pris deux fois,
   pour la taille et pour le contraste.

   ⚠ Un `<button>` hérite des styles de bouton du navigateur : on les défait, sinon
   chaque entrée deviendrait un bouton gris à bordure. */
.legende-entree {
    display: flex;
    align-items: center;
    gap: var(--e-2);
    background: none;
    border: none;
    padding: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
    text-align: left;
    /* ⚠ 44 px, le plancher tactile du #54, et non 24 : une légende de douze entrées
       y gagne deux rangées, mais une cible qu'on rate au doigt éteint la mauvaise
       courbe — et rien ne dit qu'on s'est trompé. */
    min-height: 44px;
}

.legende-entree:hover { color: var(--parchemin); }

/* ⚠ La pastille garde un contour quand elle s'éteint : sans lui, une couleur claire
   passée à 25 % d'opacité se confond avec le fond, et l'entrée perd son repère. */
.legende-masquee { color: var(--murmure); text-decoration: line-through; }
.legende-masquee .pastille {
    opacity: .25;
    box-shadow: inset 0 0 0 1px var(--bordure);
}

/* ⚠ Le mot, et non seulement le gris — le rouge comme le gris ne portent jamais
   l'information seuls. `text-decoration: none` parce que la barre du parent le
   traverserait, et « (masquée) » barré se lit mal. */
.legende-etat {
    font-family: var(--titre);
    font-size: var(--t-xs);
    color: var(--murmure);
    text-decoration: none;
}

/* --- Les mondes sans progression ----------------------------------------------

   ⚠ Ce bloc est la CONTREPARTIE du masquage automatique. Une partie à soixante
   mondes en compte le plus souvent quarante qui n'ont rien coché pendant la fenêtre
   journalisée, et les tracer rendait le graphique illisible — mais les retirer sans
   le dire ferait croire qu'ils ont quitté la partie. Il dit combien, et les rend.

   ⚠ Il est en bas et il est discret, pas caché : un `<details>` replié aurait fait
   disparaître le compte lui-même, qui est justement ce qu'il faut voir. */
.evolution-repos {
    margin-top: var(--e-3);
    padding-top: var(--e-3);
    border-top: 1px solid var(--bordure);
}

.evolution-tout {
    background: none;
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    padding: var(--e-2) var(--e-3);
    font: inherit;
    font-size: var(--t-s);
    color: var(--brume);
    cursor: pointer;
    /* Le plancher tactile du #54, comme pour les entrées de légende. */
    min-height: 44px;
}

.evolution-tout:hover,
.evolution-tout:focus-visible {
    border-color: var(--lueur);
    color: var(--parchemin);
}

/* ⚠ `.bloc-titre` n'avait AUCUNE règle : `partage.js` l'écrivait, rien ne le
   stylait, et il rendait donc en texte courant. On lui en donne juste assez pour
   qu'il porte un lien à sa droite — la taille et la couleur restent celles dont il
   hérite, pour ne rien changer à ce que la page montrait. */
.bloc-titre {
    display: flex;
    align-items: baseline;
    gap: var(--e-3);
}

/* ⚠ Le lien vers le plein écran est posé dans l'en-tête du bloc « Évolution », de
   la page de suivi comme de la page publique. `margin-left: auto` le pousse à
   droite sans que l'en-tête ait à devenir une grille : il est en flex des deux
   côtés. */
.lien-plein-ecran {
    margin-left: auto;
    font-family: var(--titre);
    font-size: var(--t-s);
    color: var(--lueur);
    text-decoration: none;
    white-space: nowrap;
    /* ⚠ 44 px, le plancher tactile du #54 — le banc visuel l'a refusé à 21 px, sur
       les deux pages à la fois. Un lien de deux mots n'y arrive pas tout seul : il
       lui faut `inline-flex` pour que la hauteur minimale s'applique, et `center`
       pour que le texte reste au milieu de la cible au lieu d'en coiffer le haut. */
    display: inline-flex;
    align-items: center;
    min-height: 44px;
}

.lien-plein-ecran:hover,
.lien-plein-ecran:focus-visible { text-decoration: underline; }

/* --- La courbe en plein écran -------------------------------------------------

   ⚠ `courbe.html` est servie à DEUX adresses et ne charge que cette feuille : ces
   règles ne peuvent pas vivre dans `coquille.css`, pour la raison écrite plus haut
   à propos de la légende.

   ⚠ `100dvh` et non `100vh` : sur un téléphone, `vh` compte la barre d'adresse
   comme si elle n'était pas là, et le bas du graphique passait dessous. La page
   n'est pas faite pour un téléphone, mais elle ne doit pas s'y casser.

   ⚠ La page est CALÉE sur 1920×1080, pas figée à cette taille : une grille à deux
   rangées remplit ce qu'on lui donne. Un cadre de 1920 px en dur aurait défilé
   horizontalement sur tout écran plus étroit. */
.plein-ecran {
    height: 100dvh;
    display: grid;
    grid-template-rows: auto 1fr;
    /* ⚠ Serré à dessein : chaque pixel pris ici est un pixel de moins pour le tracé,
       qui est la seule raison d'être de cette page. */
    gap: var(--e-2);
    padding: var(--e-2) var(--e-3);
    background: var(--abysse);
    color: var(--parchemin);
    font-family: var(--texte);
    overflow: hidden;
}

/* ⚠ `flex-start` et non `space-between` : `selecteur-langue.js` se pose dans le
   premier `header` qu'il trouve, donc ICI — et il devient un TROISIÈME enfant.
   Réparti, le lien de retour se retrouvait au milieu de la barre, loin du titre
   qu'il accompagne. Le sélecteur est renvoyé à droite tout seul. */
.plein-ecran-tete {
    display: flex;
    align-items: baseline;
    gap: var(--e-4);
    flex-wrap: wrap;
}

.plein-ecran-tete .boite-langue { margin-left: auto; }

.plein-ecran-tete h1 {
    font-family: var(--titre);
    /* ⚠ `--t-m` et non `--t-l` : le titre n'est pas le sujet de la page, le
       graphique l'est. Deux lignes de moins pour lui, c'est deux lignes de plus
       pour ce qu'on est venu regarder. */
    font-size: var(--t-m);
    color: var(--lueur);
    letter-spacing: 1px;
}

/* ⚠ `min-height: 0` sur la rangée souple. Sans lui, une grille refuse de faire
   descendre son contenu sous sa taille naturelle — le SVG gardait sa hauteur
   calculée, la rangée débordait, et la légende sortait de la fenêtre : exactement
   ce que cette page existe pour éviter. */
/* ⚠ `flex-start` et non `center` : le tracé occupe désormais toute la place, et
   centrer un contenu qui remplit déjà ne fait que répartir en haut ET en bas le
   moindre écart de mesure. Aligné en tête, ce qui reste se voit d'un seul côté. */
.plein-ecran-corps {
    min-height: 0;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
}

/* --- Le volet de la légende, en bas et replié ---------------------------------

   ⚠ HORS FLUX, et ce n'est pas un détail de style. Le tracé occupe toute la hauteur
   restante ; si le volet était dans le flux, l'ouvrir aurait poussé son propre
   contenu sous le bord de la fenêtre — que `.plein-ecran` coupe. Superposé, il
   recouvre le bas du graphique le temps qu'on le consulte, et le rend ensuite.

   ⚠ `padding-bottom` sur le conteneur RÉSERVE la bande du volet replié. Sans elle,
   la barre « Légende — 15 mondes » se posait par-dessus les heures de l'axe. C'est
   aussi ce que `courbe.js` mesure pour déduire la hauteur du tracé : les deux
   chiffres ne sont donc jamais à recaler l'un sur l'autre. */
.plein-ecran-corps > #evolution {
    min-height: 0;
    position: relative;
    padding-bottom: 46px;
}

.legende-repli {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--abysse);
    border-top: 1px solid var(--bordure);
    /* Ouvert, il ne prend jamais plus que la moitié de l'écran, et défile au-delà. */
    max-height: 55dvh;
    overflow: auto;
}

.legende-repli > summary {
    padding: var(--e-2) var(--e-3);
    font-family: var(--titre);
    font-size: var(--t-s);
    color: var(--brume);
    cursor: pointer;
    /* Le plancher tactile du #54 : un volet qu'on rate au doigt ne s'ouvre pas. */
    min-height: 44px;
    display: flex;
    align-items: center;
}

.legende-repli > summary:hover,
.legende-repli > summary:focus-visible { color: var(--parchemin); }

.legende-repli-corps { padding: 0 var(--e-3) var(--e-3); }

/* Un seul graphique ici : il n'a pas de voisin dont il faudrait l'écarter. */
.plein-ecran .graphe-paquet { margin-bottom: 0; }

/* --- Les compteurs d'en-tête -------------------------------------------------
   ⚠ Ils ne vivaient que dans le `<style>` de la page d'un serveur. Réutilisés
   sur la page d'administration d'un serveur, ils y sont apparus SANS AUCUN
   style : trois libellés et trois nombres empilés en vrac. C'est exactement la
   faute de `.back` deux heures plus tôt — une classe définie dans UNE page n'est
   pas une classe, c'est un style local qui ment dès qu'on l'emprunte. */
.global-stats {
    display: flex;
    gap: var(--e-3);
    margin-bottom: var(--e-4);
    flex-wrap: wrap;
}

.global-stat {
    flex: 1;
    min-width: 160px;
    background: linear-gradient(160deg, var(--abysse-haut), var(--abysse-clair));
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    padding: var(--e-4) var(--e-5);
    text-align: center;
}

.global-stat .label {
    font-family: var(--titre);
    font-size: var(--t-s);
    color: var(--brume);
    margin-bottom: var(--e-1);
}

.global-stat .value {
    font-family: var(--titre);
    font-size: var(--t-xl);
    color: var(--lueur);
}

/* --- La grille des serveurs Discord -----------------------------------------
   L'accueil ne montre plus les portails de tous les serveurs mêlés : il montre
   les serveurs, et on entre dans celui qu'on veut.

   ⚠ La grille reste affichée même avec un seul serveur — décision du
   propriétaire. Le chemin est alors le même pour tout le monde, quel qu'en soit
   le nombre. */
.grille-serveurs {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: var(--e-4);
}

.tuile-serveur {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--e-3);
    padding: var(--e-5) var(--e-4);
    background: var(--abysse-clair);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    color: var(--parchemin);
    text-decoration: none;
    text-align: center;
    transition: border-color .2s ease, transform .2s ease;
    /* Une tuile est une CIBLE : elle doit se toucher au doigt sans viser. */
    min-height: 160px;
    justify-content: center;
}

.tuile-serveur:hover,
.tuile-serveur:focus-visible {
    border-color: var(--lueur);
    transform: translateY(-2px);
}

/* L'icône du serveur, ou son initiale quand il n'en a pas. */
.tuile-icone {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--abysse-haut);
    border: 1px solid var(--bordure);
}

.tuile-initiale {
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--titre);
    font-size: var(--t-xl);
    color: var(--lueur);
}

.tuile-nom {
    font-family: var(--titre);
    font-size: var(--t-m);
    letter-spacing: .5px;
    /* Un nom de serveur Discord peut faire cent caractères sans un espace. */
    overflow-wrap: anywhere;
}

/* ⚠ La suspension se voit DÈS la grille, et pas seulement par la couleur : le
   mot « Suspendu » est écrit. Rouge et vert sont indistinguables pour environ
   8 % des hommes — une bordure teintée seule ne dirait rien à une personne sur
   douze, exactement comme les jauges du #61. */
.tuile-suspendue {
    border-color: var(--alerte);
    background: color-mix(in srgb, var(--alerte) 8%, var(--abysse-clair));
}

.tuile-etat {
    font-family: var(--titre);
    font-size: var(--t-s);
    color: var(--alerte);
    letter-spacing: .5px;
}

/* --- L'indicateur de fraîcheur ----------------------------------------------

   ⚠ Il n'existe QUE lorsqu'il a quelque chose à apprendre — le module ne le crée
   pas quand tout va bien. Un bandeau « en direct » permanent occuperait l'écran
   pour dire ce que l'absence de problème dit déjà, et on cesserait de le voir :
   donc on ne le verrait pas non plus quand il change.

   ⚠ En BAS À GAUCHE, à l'opposé de la pile de notifications : les deux peuvent
   s'afficher en même temps — une panne produit l'un et l'autre — et se
   chevaucher aurait rendu les deux illisibles. */
.fraicheur {
    position: fixed;
    left: var(--e-4);
    bottom: var(--e-4);
    z-index: 400;
    display: flex;
    align-items: center;
    gap: var(--e-3);
    padding: var(--e-2) var(--e-3);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--abysse-clair);
    box-shadow: 0 4px 16px rgba(0, 0, 0, .5);
    font-family: var(--titre);
    font-size: var(--t-xs);
    color: var(--parchemin);
    max-width: min(90vw, 30rem);
}

/* La gravité se lit à la bordure, pas seulement au texte — mais le texte reste
   explicite : « Hors ligne », « Reconnexion… », jamais une pastille seule. */
.fraicheur-reconnexion { border-left: 3px solid var(--brume); }
.fraicheur-perime { border-left: 3px solid var(--alerte); }
.fraicheur-hors-ligne { border-left: 3px solid var(--alerte); }

/* ⚠ Le seul état dont on ne sort pas en attendant : il porte la bordure PLEINE,
   pas un simple filet à gauche. Les trois autres disent « ça va revenir » ; celui-ci
   dit « il faut faire quelque chose », et rien d'autre sur cet écran ne le dira. */
.fraicheur-session {
    border: 1px solid var(--alerte);
    border-left: 3px solid var(--alerte);
}

/* --- La pile de notifications -----------------------------------------------

   ⚠ Le conteneur de chaque page — `#toast`, ou `#notification` sur la page
   d'une partie — devient une PILE. Son ancienne apparence vivait dans `.toast`,
   recopié dans quatre feuilles, et dans `.notification`. Le module JavaScript
   remplace la classe par celle-ci : les anciennes règles ne s'appliquent donc
   plus, sans qu'on ait à les retirer partout, et la position devient la même
   sur toutes les pages.

   ⚠ Elle croît vers le HAUT, et les nouveaux messages s'ajoutent en bas. Insérer
   par le haut déplacerait sous le curseur les messages déjà lus. */
.pile-notes {
    position: fixed;
    right: var(--e-4);
    bottom: var(--e-4);
    z-index: 400;
    display: flex;
    flex-direction: column;
    gap: var(--e-2);
    /* Aucun fond ni bordure : c'est un conteneur, pas un message. */
    background: none;
    border: none;
    padding: 0;
    /* ⚠ La pile ne doit pas intercepter les clics de la page qu'elle survole ;
       chaque message, lui, les reprend. */
    pointer-events: none;
    max-width: min(90vw, 26rem);
}

.note {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: var(--e-3);
    padding: var(--e-3) var(--e-4);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    background: var(--abysse-clair);
    box-shadow: 0 4px 16px rgba(0, 0, 0, .5);
    font-size: var(--t-s);
    color: var(--parchemin);
    animation: note-entree .18s ease-out;
}

/* ⚠ La nature se lit à la BORDURE et au symbole, jamais à la seule couleur :
   rouge et vert sont indistinguables pour environ 8 % des hommes. */
.note-reussite { border-left: 3px solid var(--lueur); }
.note-echec { border-left: 3px solid var(--alerte); }

.note-reussite .note-texte::before { content: '✔ '; color: var(--lueur); }
.note-echec .note-texte::before { content: '⚠ '; color: var(--alerte); }

.note-texte { flex: 1; overflow-wrap: anywhere; }

.note-fermer {
    flex: none;
    background: none;
    border: none;
    color: var(--murmure);
    font-size: var(--t-l);
    line-height: 1;
    cursor: pointer;
    padding: 0 var(--e-1);
}

.note-fermer:hover { color: var(--parchemin); }

.note-fermer:focus-visible {
    outline: 2px solid var(--lueur);
    outline-offset: 2px;
}

@keyframes note-entree {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}

/* ⚠ Un message qui glisse à chaque confirmation, sur une page où l'on en
   déclenche vingt d'affilée, donne la nausée à qui y est sensible. L'état reste
   parfaitement lisible sans animation. */
@media (prefers-reduced-motion: reduce) {
    .note { animation: none; }
}

/* --- Le réglage d'un fil ----------------------------------------------------- */

/* ⚠ Un avertissement qui n'est PAS une erreur : la liste d'objets n'est pas
   encore arrivée du serveur Archipelago, ce qui se résout tout seul. D'où
   l'ambre plutôt que le rouge — et le texte, qui dit quoi faire (attendre)
   plutôt que de constater. */
/* ⚠ Le TROISIÈME niveau du #66 : l'action requise. Il ne s'efface pas seul,
   parce qu'il décrit un état qui demande une intervention — une liste d'hôtes
   vide interdit toute connexion, une liste d'objets manquante empêche tout
   réglage. Un message temporaire aurait disparu avant qu'on ait compris.

   ⚠ Il unifie deux styles concurrents : cette classe, et un bloc rouge écrit en
   attributs `style=` dans `admin.html` et `admin-guilde.html`. Deux apparences
   pour la même intention, dont l'une échappait à toute feuille — donc au thème,
   au contraste mesuré du #61, et à toute correction ultérieure. */
.bandeau-avertissement {
    border: 1px solid var(--alerte);
    background: color-mix(in srgb, var(--alerte) 8%, var(--abysse-clair));
    border-radius: var(--rayon);
    padding: var(--e-3) var(--e-4);
    margin-bottom: var(--e-4);
    font-size: var(--t-s);
    font-family: var(--titre);
    color: var(--parchemin);
}

/* Le même bandeau, quand il s'agit d'un blocage et non d'une attente. */
.bandeau-avertissement.bandeau-grave {
    border-color: var(--alerte);
    background: color-mix(in srgb, var(--alerte) 14%, var(--abysse-clair));
}

/* ⚠ Deux tableaux côte à côte : un jeu déclare plusieurs centaines d'objets, et
   sur une seule colonne la page fait des mètres — le sélecteur de famille sort
   de l'écran dès qu'on descend.

   ⚠ Une seule colonne sous 900 px. À deux, chaque tableau aurait moins de la
   moitié d'un écran de téléphone pour quatre colonnes, dont un nom d'objet qui
   peut faire trente caractères : tout se serait replié en escalier. */
.deux-colonnes {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--e-4);
    align-items: start;
}

@media (max-width: 900px) {
    .deux-colonnes { grid-template-columns: 1fr; }
}

/* ⚠ Le titre d'une famille traverse le tableau. Il doit se distinguer d'une ligne
   d'objet au premier coup d'œil, sinon la liste redevient plate : d'où le fond,
   la casse et la couleur d'accent — trois signaux plutôt qu'un. */
.ligne-famille th {
    padding: 0;
    background: var(--abysse-haut);
    border-top: 1px solid var(--bordure);
}

/* ⚠ Le bouton occupe TOUTE la cellule. Une cible de la taille du texte seul
   obligerait à viser le titre au pixel près, sur une liste qui en compte
   plusieurs dizaines. */
.ligne-famille button {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    cursor: pointer;
    color: var(--lueur);
    font-family: var(--titre);
    font-size: var(--t-s);
    letter-spacing: .5px;
    padding: var(--e-3);
    /* Un nom de famille peut être long — « A Town in Chaos » — et la colonne est
       étroite quand deux tableaux se partagent l'écran. */
    overflow-wrap: anywhere;
}

.ligne-famille button:hover { background: color-mix(in srgb, var(--lueur) 10%, transparent); }

.ligne-famille button:focus-visible {
    outline: 2px solid var(--lueur);
    outline-offset: -2px;
}

/* ⚠ La flèche double l'information de `aria-expanded`, qui ne se voit pas. Ouvert
   et fermé doivent se distinguer à l'œil autant qu'au lecteur d'écran. */
.famille-fleche {
    display: inline-block;
    width: 1em;
    color: var(--brume);
}

/* Le nombre d'objets de la famille : présent, mais il ne doit pas concurrencer
   le nom. */
.famille-compte {
    color: var(--murmure);
    font-family: var(--chiffre);
    font-size: var(--t-xs);
}

/* ⚠ Les lignes repliées sont MASQUÉES, pas retirées du HTML : la recherche du
   navigateur ne trouve que ce qui existe, et Ctrl+F est un usage courant sur une
   page de plusieurs centaines d'objets. */
.famille:not(.ouverte) tr:not(.ligne-famille) { display: none; }

/* --- L'interrupteur ----------------------------------------------------------

   ⚠ C'est une VRAIE case à cocher, redessinée — pas un <div> qui lui ressemble.
   Elle garde donc le clavier, l'espace pour basculer, le focus visible et son
   `aria-label`, tout ce qu'un faux interrupteur oblige à reconstruire à la main
   et qu'on reconstruit toujours à moitié.

   ⚠ L'état se lit à la POSITION du bouton, pas seulement à la couleur. Rouge et
   vert sont indistinguables pour environ 8 % des hommes, et cette page en aligne
   plusieurs centaines : une teinte seule aurait rendu la liste illisible pour
   une personne sur douze — le constat du #61, déjà appliqué aux jauges. */
.bascule {
    appearance: none;
    -webkit-appearance: none;
    /* Assez grand pour se viser au doigt : une case native fait 13 px. */
    width: 44px;
    height: 24px;
    border-radius: 12px;
    background: var(--abysse-haut);
    border: 1px solid var(--bordure);
    position: relative;
    cursor: pointer;
    flex: none;
    transition: background-color .15s ease, border-color .15s ease;
}

.bascule::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 2px;
    transform: translateY(-50%);
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--murmure);
    transition: left .15s ease, background-color .15s ease;
}

.bascule:checked {
    background: color-mix(in srgb, var(--lueur) 30%, var(--abysse-haut));
    border-color: var(--lueur);
}

.bascule:checked::after {
    left: 22px;
    background: var(--lueur);
}

.bascule:focus-visible {
    outline: 2px solid var(--lueur);
    outline-offset: 2px;
}

/* ⚠ Le mouvement se coupe pour qui l'a demandé au système. Un glissement par
   ligne sur une liste de trois cents objets n'est pas anodin : c'est le genre
   d'animation qui donne la nausée à qui y est sensible. L'état, lui, reste
   parfaitement lisible sans transition. */
@media (prefers-reduced-motion: reduce) {
    .bascule,
    .bascule::after { transition: none; }
}

/* Un plafond tient en cinq chiffres : un champ pleine largeur par ligne aurait
   poussé la colonne « déjà annoncés » hors de l'écran sur un téléphone. */
.saisie-courte {
    width: 6.5em;
    min-width: 0;
}

/* --- Les fils qu'on possède -------------------------------------------------

   ⚠ UNE LISTE, et non plus une grille de tuiles. Chaque fil tenait dans un pavé
   de 240 px sur quatre lignes ; à sept fils cela faisait sept cadres à parcourir
   du regard, en zigzag, pour lire sept fois les mêmes rubriques. En rangées, les
   noms s'alignent les uns sous les autres et se comparent d'un coup d'œil.

   ⚠ L'ordre des rubriques a changé AVEC la mise en page, et c'est indissociable :
   dans une tuile on lisait de haut en bas, du contexte vers le sujet — serveur,
   salon, slot. Sur une rangée on lit de gauche à droite, et c'est le sujet qui
   doit venir en tête, sans quoi les sept lignes commencent toutes par le même
   nom de serveur et il faut aller chercher le reste au milieu. */
/* ⚠ Le lien est sur la CELLULE du slot, pas sur la rangée : `<tr>` ne peut pas
   être un lien, et l'envelopper d'un `<a>` produirait un tableau que les
   navigateurs recomposent en silence. Le nom du fil est le sujet de la ligne,
   donc c'est lui qu'on clique. */
.lien-slot {
    font-family: var(--titre);
    color: var(--lueur);
    text-decoration: none;
    letter-spacing: .5px;
    overflow-wrap: anywhere;
}

.lien-slot:hover,
.lien-slot:focus-visible { text-decoration: underline; }

/* Le serveur est le contexte, pas le sujet : il s'efface devant le slot. */
.slot-guilde { color: var(--murmure); overflow-wrap: anywhere; }
.slot-jeu { color: var(--murmure); font-style: italic; overflow-wrap: anywhere; }

/* ⚠ Une partie fermée reste AFFICHÉE, et le dit par un MOT — le badge « Partie
   terminée » de la colonne « État » — autant que par la teinte. Même raison
   qu'un serveur suspendu : une nuance seule ne dirait rien à une personne sur
   douze. Elle reste consultable, d'où l'atténuation plutôt que la disparition. */
tr.fermee { color: var(--murmure); }
tr.fermee .lien-slot { color: var(--brume); }

.slot-ferme {
    font-family: var(--titre);
    font-size: var(--t-s);
    color: var(--murmure);
    letter-spacing: .5px;
}

/* --- Le bandeau d'un serveur suspendu ---------------------------------------
   ⚠ Un serveur bloqué par le propriétaire du bot ne l'apprenait NULLE PART. Le
   blocage ne s'applique que dans `DiscordService`, qui fait quitter le serveur,
   et dans le filtre d'autorisation, qui ne vise que les personnes. Son
   administrateur voyait donc un tableau de bord normal, des portails
   configurés, et un bot muet. */
.bandeau-suspendu {
    margin-bottom: var(--e-4);
    padding: var(--e-4);
    background: color-mix(in srgb, var(--alerte) 12%, var(--abysse-clair));
    border: 1px solid var(--alerte);
    border-radius: var(--rayon);
}

.bandeau-suspendu .titre {
    font-family: var(--titre);
    font-size: var(--t-l);
    color: var(--alerte);
    margin-bottom: var(--e-2);
}

.bandeau-suspendu .motif {
    color: var(--parchemin);
    margin-bottom: var(--e-2);
}

.bandeau-suspendu .depuis {
    font-size: var(--t-s);
    color: var(--murmure);
}

/* --- La notification ---------------------------------------------------------
   `server.html` n'en avait aucune : les actions de partage ont besoin de dire
   qu'elles ont abouti, ou pourquoi elles n'ont pas abouti. */
.notification {
    position: fixed;
    left: 50%;
    bottom: var(--e-5);
    transform: translate(-50%, 200%);
    padding: var(--e-3) var(--e-5);
    max-width: min(90vw, 34rem);
    color: var(--parchemin);
    background: var(--abysse-haut);
    border: 1px solid var(--lueur);
    border-radius: var(--rayon);
    transition: transform .2s ease;
    z-index: 400;
}

.notification.visible { transform: translate(-50%, 0); }
.notification.erreur { border-color: var(--alerte); }

@media (prefers-reduced-motion: reduce) {
    .notification { transition: none; }
}

/* L'avertissement d'un aperçu réservé au propriétaire : la page est là, mais
   personne d'autre ne la voit. */
.avertissement {
    color: var(--alerte);
    border-color: var(--alerte) !important;
}

/* --- Les états guidés --------------------------------------------------------
   ⚠ Un écran vide n'est pas une information. Les huit états vides du tableau de
   bord disaient tous « aucun X » et s'arrêtaient là — au moment précis où
   l'utilisateur a besoin qu'on lui dise quoi faire.
   --------------------------------------------------------------------------- */
.guide {
    max-width: 640px;
    margin: var(--e-6) auto;
    padding: var(--e-5);
    text-align: center;
    background: var(--abysse-clair);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
}

.guide-titre {
    font-family: var(--titre);
    font-size: var(--t-l);
    color: var(--lueur);
    letter-spacing: 1px;
}

.guide p { margin: var(--e-3) 0; color: var(--parchemin); line-height: 1.5; }

.guide-action { margin-top: var(--e-4); }

/* Les permissions demandées, une ligne chacune, avec sa raison. */
.permissions {
    margin: var(--e-4) 0 0;
    padding: 0;
    list-style: none;
    text-align: left;
    font-size: var(--t-s);
    color: var(--brume);
}

.permissions li {
    display: grid;
    grid-template-columns: 13rem 1fr;
    gap: var(--e-3);
    padding: var(--e-1) 0;
}

.permissions b { color: var(--parchemin); font-weight: 600; }

@media (max-width: 900px) {
    .permissions li { grid-template-columns: 1fr; gap: 0; }
}

/* Les fenêtres de configuration d'`admin.html` sont écrites en chaînes de
   caractères par le script, chaque champ portant son style EN LIGNE, répété.
   Deux classes suffisent, et elles vivent ici comme le reste. */
.etiquette {
    display: block;
    margin-bottom: var(--e-1);
    font-family: var(--titre);
    font-size: var(--t-xs);
    color: var(--lueur);
}

.saisie {
    width: 100%;
    padding: var(--e-2) var(--e-3);
    color: var(--parchemin);
    background: var(--abysse);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
}

/* L'aide sous un libellé. Elle dit ce que le réglage FAIT, pas ce qu'il est. */
.aide-champ {
    display: block;
    margin-top: var(--e-1);
    font-size: var(--t-s);
    font-style: italic;
    color: var(--brume);
}

/* La valeur par défaut, quand ne rien saisir a un sens. */
.defaut-champ {
    font-style: normal;
    color: var(--lueur);
}

/* ⚠ Le message reste SOUS le champ tant qu'on corrige. Une notification
   passagère disparaît pendant qu'on relit sa saisie — donc au moment précis où
   elle servirait. */
.erreur-champ {
    margin-top: var(--e-1);
    font-size: var(--t-s);
    color: var(--alerte);
}

.champ-fautif {
    border-color: var(--alerte) !important;
}

/* Un bouton d'enregistrement en cours : il dit ce qu'il fait, et refuse le
   second clic. Sans cela, tout formulaire pouvait être soumis deux fois. */
button[data-en-cours="1"], button:disabled {
    opacity: .6;
    cursor: progress;
}

/* ============================================================================
   JUSQU'AU TÉLÉPHONE
   ============================================================================

   Mesuré à 390 px sur les quatre écrans : 67 déclarations de police sous 14 px
   — dont huit à 10 px —, dix cibles tactiles entre 24 et 35 px là où il en faut
   44, et six tableaux totalisant trente-sept colonnes.

   ⚠ Le seuil est à 900 px, pas à 700. Mesuré à 768 px — un iPad en portrait,
   l'une des trois largeurs que le prompt nomme — les tableaux débordaient
   ENCORE de 143 px sur `index.html` et de 69 px sur `admin.html`. Un seuil posé
   au jugé aurait laissé toute une famille d'écrans dans l'état d'avant.

   ⚠ Ce bloc emploie `!important` et des sélecteurs d'attribut. Ce n'est pas de
   la paresse : les tailles fautives sont, pour la plupart, écrites en STYLE
   EN LIGNE dans le HTML généré, et un style en ligne bat toute règle de feuille.
   Les deux seules autres voies étaient de reprendre les soixante-sept
   déclarations une à une — donc de changer aussi l'apparence sur écran large,
   qui convient — ou de renoncer. Tout est confiné à cette requête média.
   ============================================================================ */
@media (max-width: 900px) {

    /* --- Les tableaux deviennent des cartes empilées ------------------------
       Le défilement horizontal était la solution en place, et le prompt a
       raison de la refuser : on ne compare pas deux lignes qu'on ne peut pas
       voir en même temps, et rien n'indique qu'il reste des colonnes à droite.

       ⚠ Choix assumé : TOUS les champs restent, aucun n'est replié derrière un
       dépliement. Ces tableaux comptent peu de lignes — une poignée de portails,
       une poignée de guildes. Replier aurait échangé un défaut de largeur contre
       un geste de plus sur des données déjà courtes, et le prompt demande de ne
       pas dégrader la fonctionnalité. */
    table, tbody, tr, td { display: block; width: 100%; }

    /* L'en-tête ne peut plus coiffer de colonnes : son texte part dans
       `data-colonne`, cellule par cellule (voir le script de chaque page). */
    thead {
        position: absolute;
        width: 1px; height: 1px;
        overflow: hidden;
        clip-path: inset(50%);
    }

    tbody tr {
        background: var(--abysse-clair);
        border: 1px solid var(--bordure);
        border-radius: var(--rayon);
        padding: var(--e-2) var(--e-3);
        margin-bottom: var(--e-3);
    }

    tbody td {
        display: grid;
        grid-template-columns: 8rem 1fr;
        align-items: baseline;
        /* Sans cela, un badge « Oui » de trois lettres s'étire sur toute la
           largeur de la carte : en grille, un élément occupe sa case entière. */
        justify-items: start;
        gap: var(--e-3);
        border: none;
        padding: var(--e-2) 0;
        /* Une adresse `wss://hote:port` ne doit pas pousser la carte hors écran. */
        overflow-wrap: anywhere;
    }

    tbody td::before {
        content: attr(data-colonne);
        font-family: var(--titre);
        font-size: var(--t-xs);
        letter-spacing: .5px;
        color: var(--brume);
        /* ⚠ L'étiquette hérite du style de SA cellule : sous une cellule en
           italique — la colonne « Jeu » de l'accueil — elle s'affichait en
           italique, seule de la carte. Elle nomme une colonne, elle ne fait pas
           partie de la valeur. */
        font-style: normal;
    }

    /* La ligne « aucun résultat » couvre toute la largeur : pas d'étiquette. */
    tbody td[colspan] { display: block; text-align: center; }
    tbody td[colspan]::before { content: none; }

    /* --- Les en-têtes se replient ------------------------------------------
       Mesuré : 161 px de débordement sur `admin.html`, 213 px sur
       `server.html`. Dans les deux cas une rangée souple qui ne se replie pas —
       titre, bouton et nom d'utilisateur poussés hors de l'écran, sans que rien
       n'indique qu'il y avait quelque chose à droite. */
    header, .header-inner, .header-left, .header-right, .nav, .actions {
        flex-wrap: wrap;
        gap: var(--e-2);
    }

    .header-inner { justify-content: flex-start; }

    /* Le titre de `server.html` porte l'adresse complète du serveur : elle doit
       se couper, pas pousser la page. */
    h1, #page-title { overflow-wrap: anywhere; }

    /* --- Le plancher de 14 px ----------------------------------------------
       ⚠ Le premier essai ne mordait pas, et la mesure l'a montré : les tailles
       fautives vivent dans le `<style>` de chaque page, chargé APRÈS
       `yashimabot.css`. À spécificité égale, la dernière règle l'emporte — d'où le
       `!important`, ici et pour les styles en ligne.

       Un plancher, pas une taille imposée : ce qui dépasse déjà 14 px ne bouge
       pas. */
    :root {
        --t-xs: 14px;
        --t-s:  15px;
    }

    [style*="font-size:10px"], [style*="font-size: 10px"],
    [style*="font-size:11px"], [style*="font-size: 11px"],
    [style*="font-size:12px"], [style*="font-size: 12px"],
    [style*="font-size:13px"], [style*="font-size: 13px"] { font-size: 14px !important; }

    .subtitle, .label, .badge, .badge-admin, .pill, .btn, .btn-sm, .back,
    .refresh-btn, .nav a, .header-right a, code, th, td, label, select, input,
    textarea, .hint-list, .hint-item, .conn-entry, .conn-time, .server-info,
    .avancement-detail, #username-display, .player-name, .stat-box .label,
    .file-label, .graphe-note,
    /* ⚠ `.player-jeu` remplace un `style="font-size:11px"` en ligne, que la règle
       ci-dessus rattrapait. Nommer la classe l'a sortie du filet : le banc l'a vu
       tout de suite, mais seulement une fois ses données factices corrigées — il
       mesurait jusque-là une liste de joueurs VIDE. */
    .player-jeu {
        font-size: 14px !important;
    }

    /* --- Les cibles tactiles ------------------------------------------------
       44 px de haut, et de quoi ne pas toucher la voisine par erreur. */
    button, .btn, .btn-sm, .back, .refresh-btn, select, input, textarea, a.btn {
        min-height: 44px;
    }

    /* Un lien de navigation reste un lien, mais il doit se toucher.
       ⚠ `display: inline-flex` est indispensable : `min-height` ne fait RIEN sur
       un élément en ligne. Les liens d'`index.html` sont écrits par le script
       avec un `padding` en ligne, et mesuraient 32 px — la règle de hauteur
       seule les laissait tels quels, sans erreur ni avertissement. */
    .nav a, .header-right a, #nav-links a, header .nav-links a {
        display: inline-flex;
        align-items: center;
        min-height: 44px;
        padding: 0 var(--e-3);
    }

    .btn, .btn-sm { padding: var(--e-2) var(--e-3); }

    .actions, td .actions {
        display: flex;
        flex-wrap: wrap;
        gap: var(--e-2);
    }

    .actions .btn { flex: 1 1 auto; }

    /* Un lien seul dans une cellule est une cible aussi : on lui donne sa
       hauteur sans le transformer en bouton. */
    tbody td > a {
        display: inline-block;
        min-height: 44px;
        line-height: 44px;
    }

    /* --- Les formulaires et les fenêtres ------------------------------------
       Une grille à deux colonnes sur 390 px donne deux champs de 170 px : on
       repasse sur une colonne. Les grilles écrites EN LIGNE comprises. */
    .form-row, .form-grid { grid-template-columns: 1fr !important; }

    [style*="grid-template-columns:1fr 1fr"],
    [style*="grid-template-columns: 1fr 1fr"] { grid-template-columns: 1fr !important; }

    /* ⚠ `dvh` et non `vh` : sur téléphone, `100vh` ignore la barre d'adresse du
       navigateur, donc le bas d'une fenêtre modale se retrouve sous elle. */
    .modal, [id$="-overlay"] > div {
        width: 100% !important;
        max-width: 100% !important;
        max-height: 88dvh;
        overflow-y: auto;
        padding: var(--e-4) !important;
    }

    /* La fenêtre s'ancre en haut : centrée, le clavier virtuel la pousse et son
       titre sort de l'écran. */
    [id$="-overlay"] { align-items: flex-start !important; padding: var(--e-3); }

    /* Un champ que le clavier virtuel recouvrirait doit remonter avec de la
       marge, sinon il affleure tout juste le bord. */
    input, select, textarea { scroll-margin-block: var(--e-6); }

    /* --- Ce qui déborde ------------------------------------------------------ */
    .container, .header-inner { padding-left: var(--e-3); padding-right: var(--e-3); }

    .card, .modal, img, svg, pre, code { max-width: 100%; }

    /* `login.html` posait une carte de 360 px sans garde-fou : à 390 px elle
       tenait de justesse, et pas du tout sur un écran plus étroit. */
    .card { width: auto; }

    /* La zone sûre : sur iPhone, le bas de l'écran est mangé par la barre
       d'accueil. */
    body { padding-bottom: env(safe-area-inset-bottom); }
}

/* --- La page tracker --------------------------------------------------------
   ⚠ Tout y etait au meme niveau visuel : huit compteurs cote a cote, dont aucun
   ne repondait a « ou en est cette partie ? ». La progression globale passe
   devant, le detail suit. */
.avancement {
    max-width: 1400px;
    /* ⚠ La marge basse etait a 0 : descendu sous les compteurs, le bloc collait
       la carte suivante, bord contre bord, et les deux se lisaient comme un
       seul cadre. En haut il n'en faut pas plus, la grille de compteurs pose
       deja ses 16 px et les marges adjacentes fusionnent. */
    margin: var(--e-4) auto var(--e-5);
    padding: var(--e-4);
    background: var(--abysse-clair);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
}

.avancement-tete {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--e-3);
    margin-bottom: var(--e-3);
}

.avancement-titre {
    font-family: var(--titre);
    font-size: var(--t-l);
    color: var(--lueur);
    letter-spacing: 2px;
}

#avancement-chiffre {
    font-size: var(--t-m);
    color: var(--parchemin);
    font-family: var(--chiffre);
    font-variant-numeric: tabular-nums;
}

.jauge {
    height: 10px;
    background: var(--abysse);
    border: 1px solid var(--bordure);
    border-radius: var(--rayon);
    overflow: hidden;
}

.jauge-barre {
    height: 100%;
    width: 0;
    background: linear-gradient(90deg, var(--sortilege), var(--lueur));
    transition: width .6s ease;
}

.avancement-detail {
    margin-top: var(--e-2);
    font-size: var(--t-s);
    color: var(--brume);
}

/* --- Les etats de la page ---------------------------------------------------
   ⚠ Un seul message couvrait « erreur de chargement » et « acces refuse ». Ce
   sont deux situations sans rapport : l'une se repare en attendant, l'autre en
   demandant un droit. */
.etat {
    padding: var(--e-4);
    border-radius: var(--rayon);
    font-size: var(--t-m);
    line-height: 1.5;
}

.etat a { color: var(--lueur); }

.etat-refus { background: var(--abysse-clair); border: 1px solid var(--bordure); color: var(--brume); }
.etat-vide  { background: var(--abysse-clair); border: 1px dashed var(--bordure); color: var(--brume); }
.etat-panne { background: color-mix(in srgb, var(--alerte) 10%, transparent);
              border: 1px solid color-mix(in srgb, var(--alerte) 45%, transparent);
              color: var(--parchemin); }

/* --- Le sélecteur de langue ------------------------------------------------
   ⚠ ICI et non dans le `<style>` de chaque page : il est posé par un module
   commun sur les neuf pages, donc son apparence doit venir d'un seul endroit.
   Recopiée neuf fois, elle aurait divergé — c'est déjà arrivé à `btn-edit-cfg`,
   employé sans jamais avoir été déclaré, qui s'affichait en blanc partout. */
.boite-langue {
    display: flex;
    align-items: center;
    gap: 6px;
    /* Il ne pousse rien : c'est un réglage, pas une destination. */
    flex: 0 0 auto;
}

.etiquette-langue {
    font-size: 12px;
    color: var(--brume);
    letter-spacing: .5px;
    /* ⚠ Insécable : « Language » sur deux lignes ferait grandir l'en-tête. */
    white-space: nowrap;
}

#choix-langue {
    /* ⚠ Les formulaires mettent tout `select` en `width:100%`, ce qui convient à
       des champs empilés. Dans une rangée d'en-tête, cette largeur chasserait les
       liens de navigation hors de l'écran — mesuré : 518 px de contenu dans 295. */
    width: auto;
    min-width: 0;
    padding: 4px 8px;
    font-size: 13px;
    background: var(--abysse-clair);
    border: 1px solid var(--bordure);
    border-radius: 2px;
    color: var(--parchemin);
}
