/*  Worms Finder — la presence dans le canal WormNET.
 *
 *  🔑 TOUS LES SELECTEURS SONT PORTES PAR `#vue-wormnet`, ET C'EST DELIBERE.
 *  Avec la specificite d'un identifiant, ces regles gagnent quel que soit
 *  l'ordre des feuilles dans le <head> : cette feuille ne depend donc d'aucune
 *  cascade, ce qui compte quand style.css et decor.css sont en cours de
 *  refonte par ailleurs. Corollaire : on n'ecrit RIEN ici qui deborde de
 *  l'onglet WormNET, et rien du tout dans style.css ni decor.css.
 *
 *  ⚠️ On reutilise les variables de style.css (--texte, --carte, --bord…) sans
 *  jamais les redefinir : une palette recopiee finirait par diverger, et c'est
 *  precisement ce que l'agent de la lisibilite est en train de reparer.
 */

/*  Le sujet du canal, tel que le serveur l'annonce (RPL_TOPIC). C'est du texte
 *  ecrit par les operateurs du canal, pas par nous : il est cite, pas affirme. */
#vue-wormnet .wn-sujet {
    margin: .6rem 0 0;
    padding: .5rem .75rem;
    border-left: 3px solid var(--bord);
    color: var(--texte-doux);
    font-size: .88rem;
    font-style: italic;
}

/*  La liste des occupants. Une grille qui se remplit toute seule : le canal
 *  passe de 3 a 40 personnes selon l'heure, et une colonne unique ferait une
 *  bande de trois ecrans de haut le soir. */
#vue-wormnet .wn-occupants {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: 0;
}

/*  🔑 UNE LIGNE DE LISTE, PAS UNE CARTE. Chez le snooper un joueur tient sur
 *  12,6 px : drapeau, pseudo, rien autour. La vignette qu'on avait ici (aplat,
 *  bordure, coins arrondis, .35rem de marge interne) faisait 24 px, et la
 *  moitie du canal tombait sous la ligne de flottaison.
 *  🚨 PLUS D'APLAT NI DE BORDURE SUR LA LIGNE : c'est `.wn-cadre` qui porte
 *  desormais l'aplat sombre et le filet, une fois pour toute la liste. Mesure
 *  du texte sur ce fond-la (carte posee sur le lavis d'un panneau, sur les six
 *  themes, pire cas `desert-classique`) : --texte 12,26:1 et --texte-doux
 *  8,47:1. On gagne donc en lisibilite en perdant la vignette. */
#vue-wormnet .wn-occ {
    display: flex;
    align-items: center;
    gap: .4rem;
    min-width: 0;
    padding: .07rem .35rem;
    background: transparent;
    border: 0;
    border-radius: 0;
}

/*  🚨 `image-rendering: pixelated` est indispensable : les drapeaux sont les
 *  VRAIS fichiers du jeu, 20x17 pixels, converts par outils/convertir-assets.js.
 *  Sans cette ligne le navigateur les lisse et ils deviennent une bouillie. */
#vue-wormnet .wn-occ img {
    width: 20px;
    height: 17px;
    flex: none;
    image-rendering: pixelated;
}

/*  ⚠️ La case du drapeau garde sa largeur meme quand il n'y a pas de drapeau :
 *  sinon les pseudos ne s'alignent plus d'une ligne a l'autre et la colonne
 *  part en escalier des qu'un joueur ne declare pas de pays. */
#vue-wormnet .wn-occ .wn-sans-drapeau {
    width: 20px;
    height: 17px;
    flex: none;
    border: 1px dashed var(--bord);
    border-radius: 0;
    opacity: .5;
}

#vue-wormnet .wn-pseudo {
    color: var(--texte);
    font-weight: 600;
    font-size: .92rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*  Le code pays quand Worms Armageddon n'a pas le drapeau correspondant — cas
 *  reel et frequent : l'Ukraine n'a aucun drapeau dans le jeu, et les joueurs
 *  ukrainiens portent donc le drapeau « inconnu ». Mieux vaut deux lettres
 *  exactes qu'un drapeau approchant. */
#vue-wormnet .wn-pays {
    flex: none;
    padding: .05rem .3rem;
    border: 1px solid var(--bord);
    border-radius: 0;
    color: var(--texte-doux);
    font-size: .68rem;
    letter-spacing: .04em;
}

/*  Le client declare (« Great Snooper v2.2 », « HostingBuddy », « 3.8.1 »).
 *  C'est ce qui distingue un joueur d'un observateur, et ca tient en un mot —
 *  donc en retrait, a droite, et jamais au detriment du pseudo. */
#vue-wormnet .wn-client {
    margin-left: auto;
    color: var(--texte-doux);
    font-size: .72rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 45%;
}

/*  Quelqu'un vient d'entrer : on connait son pseudo (le JOIN le porte) mais pas
 *  encore son drapeau (il faut attendre le prochain WHO, au plus une minute).
 *  On l'affiche quand meme, en attente — le retirer ferait clignoter la liste. */
#vue-wormnet .wn-occ--partiel .wn-pseudo { opacity: .75; }

/*  L'etat « la veille est eteinte ». Ni une panne (rien n'est casse) ni un
 *  canal vide (on ne regarde pas) : une troisieme couleur, la sienne. */
#vue-wormnet .wn-eteint {
    border-color: rgba(255, 210, 63, .45);
}
/*  🚨 CE VOYANT RESTE UN POINT, IL NE DOIT JAMAIS DEVENIR LE CD-ROM.
 *  « La veille est eteinte » n'est pas un chargement : rien n'est en cours, on
 *  a simplement decide de ne pas regarder. Un CD qui tourne ici dirait qu'on
 *  essaie d'ouvrir le canal, ce qui est faux.
 *  Cet etat portait `bandeau--attente` en plus de `wn-eteint`, uniquement pour
 *  son liseré — et il neutralisait deja l'animation ici. radar.js ne le pose
 *  plus (rien d'autre ne venait de cette classe), MAIS on redit la geometrie du
 *  point ici : c'est la seule chose qui protege si quelqu'un remet la classe.
 *  ⚠️ La specificite d'un identifiant fait gagner ce bloc quel que soit l'ordre
 *  des feuilles — c'est tout l'interet de la regle en tete de fichier. */
#vue-wormnet .wn-eteint .pastille {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--jaune);
    box-shadow: none;
    animation: none;
}
#vue-wormnet .wn-commande {
    display: inline-block;
    margin-top: .3rem;
    padding: .1rem .4rem;
    background: rgba(0, 0, 0, .35);
    border: 1px solid var(--bord);
    border-radius: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .82rem;
    color: var(--texte);
}

/* ======================================================== LA BARRE DE CANAUX
 *
 *  🔑 LA FORME EST CELLE DU JEU, ET CELLE QUE LES SNOOPERS REPRENNENT DEJA :
 *  une rangee de « #Canal » dans un cadre, le courant en evidence par
 *  INVERSION (fond rouge sombre, texte jaune) plutot que par un soulignement.
 *  C'est la convention que la communaute lit depuis vingt ans ; en inventer une
 *  autre serait le seul moyen de perdre les gens.
 *
 *  ⚠️ Les couleurs viennent des variables mesurees de style.css (--wa-cadre,
 *  --wa-label, --wa-actif) : aucune palette recopiee ici, sinon elle divergera.
 */

#vue-wormnet .wn-canaux {
    display: flex;
    flex-wrap: wrap;
    gap: .3rem;
    padding: .35rem;
    background: rgba(0, 0, 0, .25);
    border: 1px solid var(--wa-cadre);
}

#vue-wormnet .wn-canal {
    display: inline-flex;
    align-items: baseline;
    gap: .4rem;
    padding: .3rem .6rem;
    background: transparent;
    color: var(--texte);
    border: 1px solid transparent;
    font: inherit;
    font-size: .9rem;
    font-weight: 700;
    cursor: pointer;
}

/*  ⚠️ Le liseré du survol est POSE SUR UNE BORDURE TRANSPARENTE deja presente,
 *  jamais ajoute : une bordure qui apparait au survol decale tous les voisins
 *  d'un pixel, et la rangee entiere tressaute au passage de la souris. */
#vue-wormnet .wn-canal:hover { border-color: var(--wa-cadre); }

#vue-wormnet .wn-canal--actif {
    background: var(--wa-actif);
    color: var(--wa-label);
    border-color: var(--wa-cadre);
}

/*  Le canal vers lequel on est en train de basculer : ni l'ancien, ni encore le
 *  nouveau. 🚨 Il ne prend PAS l'apparence du courant — tant que le JOIN n'est
 *  pas confirme, dire « vous etes ici » serait faux. */
#vue-wormnet .wn-canal--vise {
    border-color: var(--wa-label);
    border-style: dashed;
}

/*  🚨 Pendant une bascule, TOUS les onglets sont desactives. Un second clic
 *  partirait avant la reponse du premier, et le serveur le refuserait pour
 *  cadence (10 s minimum entre deux JOIN) : le visiteur verrait un refus
 *  qu'il n'a pas provoque. */
#vue-wormnet .wn-canal[disabled] { opacity: .55; cursor: default; }

/*  Le nombre de personnes annonce par le LIST. ⚠️ Il porte un « ~ » parce que
 *  c'est une MESURE DATEE, pas un direct : le module ne redemande la liste
 *  qu'une fois toutes les cinq minutes, et l'age est ecrit sous la barre. */
#vue-wormnet .wn-canal-nb {
    color: var(--texte-doux);
    font-weight: 400;
    font-size: .78rem;
    font-variant-numeric: tabular-nums;
}
#vue-wormnet .wn-canal--actif .wn-canal-nb { color: var(--wa-label); opacity: .8; }

/*  🚨 « Le sonar ne regarde pas ce canal » n'est PAS « ce canal est calme ».
 *  L'annuaire HTTP de WormNET rend une liste vide pour n'importe quel nom de
 *  canal, y compris invente : rien, jamais, ne pourra distinguer les deux a
 *  partir de la reponse seule. On le marque donc a la source, sur l'onglet. */
#vue-wormnet .wn-canal-nonsonde {
    padding: 0 .25rem;
    border: 1px dashed var(--wa-cadre);
    color: var(--texte-doux);
    font-weight: 400;
    font-size: .66rem;
    text-transform: uppercase;
    letter-spacing: .04em;
}

/*  ⚠️ La note d'age de la mesure n'est plus sous la barre de canaux : elle est
 *  passee en bas du panneau des parties avec les autres explications (voir plus
 *  bas). Sa marge est donc reglee la-bas, avec celles des autres notes — deux
 *  regles pour le meme element, dont une a deux identifiants, c'est exactement
 *  le genre de duel qu'on ne remarque qu'apres avoir change la mauvaise. */

/* ================================================= LA DISPOSITION DU SNOOPER
 *
 *  🔑 DEUX COLONNES, ET TOUT L'ECRAN. C'est LA moitie manquante de la reprise :
 *  les couleurs etaient deja celles du jeu, mais les quatre panneaux restaient
 *  empiles dans la colonne de 900 px du reste du site. Or la silhouette du
 *  snooper est ce qu'on reconnait de loin, avant meme les couleurs.
 *
 *  MESURE FAITE SUR https://snoop.wormnet.net/snooper/, fenetre de 2560 px :
 *    · #databox       10,10   2540 x 1176  — toute la fenetre, marge de 10 px
 *    · #leftdatabox   10,10   2296 x 1176  — 90 % de la largeur
 *        · #gamelistbox  10,10   2296 x 470  — « Games »
 *        · #chatbox      10,480  2296 x 706  — « Messages » (qu'on ne reprend pas)
 *    · #playerlistbox 2316,20  224 x 1156  — 9 %, A DROITE, SUR TOUTE LA HAUTEUR
 *    · #channellist   1911,20  385 x 26    — la rangee de canaux est EN HAUT A
 *      DROITE DU PANNEAU DES PARTIES, sur la ligne du label, pas dans une boite
 *      a elle.
 *  🚨 Rien ne defile dans la page : la fenetre ne bouge pas, ce sont les listes
 *  qui defilent DANS leur panneau. C'est pour ca qu'il y a `.wn-defilant`.
 *
 *  CE QU'ON NE REPREND TOUJOURS PAS, et ce n'est pas un oubli :
 *    · le panneau « Messages » et la ligne « Your name / Message » du bas —
 *      lib/irc.js est muet par construction (liste blanche de commandes, aucune
 *      fonction pour parler). Dessiner un champ de saisie promettrait une chose
 *      qu'on ne fera jamais.
 *    · le bouton « Host » en haut a droite du panneau des joueurs — on n'heberge
 *      pas.
 *    · la bande de rang collee a chaque pseudo — le projet n'a aucune notion de
 *      classement, nulle part.
 *  A leur place, la colonne de gauche porte la grille horaire, qui est a nous.
 */

/*  🚨 CETTE REGLE VISE `main`, ET ELLE EST POURTANT PORTEE PAR `#vue-wormnet`.
 *
 *  Le site vit dans une colonne de 900 px et doit y rester : il couvre onze
 *  jeux, il n'est pas un observateur de salon. Seul cet onglet-ci a le droit de
 *  prendre l'ecran. Le `:has()` fait exactement ca : il ne s'applique que
 *  pendant que la vue WormNET est la vue montree — les autres portent
 *  `hidden`, pose par file.js a chaque bascule d'onglet. Des qu'on quitte
 *  l'onglet, `main` retrouve ses 900 px sans qu'aucune classe n'ait bouge.
 *
 *  ⚠️ Specificite : `:has()` prend celle de son argument le plus fort, donc
 *  (1,1,1) contre (0,0,1) pour le `main { max-width: 900px }` de style.css.
 *  Cette regle gagne quel que soit l'ordre des feuilles — la meme garantie que
 *  celle qu'on s'est donnee en tete de fichier.
 *
 *  ⚠️ Le `padding-bottom: 3rem` de `main` part avec : il ajoutait 48 px SOUS
 *  une grille deja calee sur la hauteur de la fenetre, et faisait donc
 *  apparaitre une barre de defilement de page — exactement ce que le snooper
 *  n'a pas. Il est rendu tel quel sur mobile, plus bas. */
/*  🚨 L'EN-TETE DU SITE SUIT, SINON ELLE DEVIENT UN ILOT. Deux juges exterieurs
 *  ont releve la meme chose sans se concerter : une fois le contenu passe pleine
 *  largeur, le bloc marque + onglets restait une bande de 900 px centree
 *  au-dessus d'un ecran de 2 300 px, avec sa marque calee a 30 % de la largeur
 *  et un vide de 500 px a sa droite. Trois axes differents sur le meme ecran.
 *  On les elargit ensemble ou pas du tout.
 *  ⚠️ Comme la regle ci-dessous, celle-ci vise des elements HORS de
 *  `#vue-wormnet` mais reste conditionnee a lui : `body:has()` ne matche que
 *  pendant que la vue WormNET est la vue montree. Sur les trois autres onglets,
 *  l'en-tete retrouve ses 900 px sans qu'aucune classe n'ait bouge.
 *
 *  ⚠️ On ne touche QUE `max-width`. Les deux barres ont deja leur marge interne
 *  de 1 rem, et la regle mobile de style.css la resserre a .8 rem en dessous de
 *  780 px : la redefinir ici la battrait par specificite et casserait ce
 *  reglage-la sans qu'aucun test ne le dise. Six pixels d'ecart avec le bord des
 *  panneaux ne valent pas ce risque. */
body:has(#vue-wormnet:not([hidden])) .entete--app,
body:has(#vue-wormnet:not([hidden])) .onglets {
    max-width: none;
}

main:has(> #vue-wormnet:not([hidden])) {
    max-width: none;
    /*  ⚠️ .6rem de chaque cote : c'est la gouttiere de 10 px que le snooper
     *  laisse autour de son `#databox`. Le 1 rem du reste du site fait respirer
     *  une colonne de 900 px ; sur toute la largeur il ne fait que rentrer les
     *  panneaux sans raison. */
    padding: 0 .6rem .6rem;
}

/*  🔑 LA GRILLE. Deux colonnes, deux rangees, et la colonne des joueurs qui
 *  traverse les deux rangees — c'est ce `grid-row: 1 / -1` qui fait « sur toute
 *  la hauteur », et rien d'autre.
 *
 *  ⚠️ `minmax(0, 1fr)` et pas `1fr` : une piste de grille vaut `auto` au
 *  minimum, donc une seule ligne de liste trop longue elargit la colonne de
 *  gauche et pousse celle des joueurs hors de l'ecran. Le `0` autorise la piste
 *  a etre plus etroite que son contenu, et les ellipses des pseudos et des
 *  titres de partie font le reste.
 *
 *  ⚠️ `100dvh` et non `100vh` : sur mobile la barre d'adresse mange la
 *  difference, et `100vh` ferait depasser la page d'une centaine de pixels.
 *  Le retrait de 10 rem est MESURE, pas estime : l'en-tete du site plus la
 *  barre d'onglets placent le haut de `#vue-wormnet` a 138 px du haut du
 *  document, et on garde une vingtaine de pixels sous la grille. Si l'en-tete
 *  change de hauteur un jour, c'est ce nombre-la qu'il faut re-mesurer — le
 *  symptome sera une barre de defilement de page qui reapparait. */
#vue-wormnet {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(13rem, 16rem);
    grid-template-rows: minmax(14rem, 1fr) auto;
    gap: .5rem;
    height: calc(100dvh - 10rem);
    min-height: 34rem;
}

/*  Un panneau devient une colonne interne : en-tete fixe en haut, zone
 *  defilante en dessous. ⚠️ `min-height: 0` est indispensable — sans lui un
 *  element de grille refuse de descendre sous la hauteur de son contenu, et le
 *  `overflow` de `.wn-defilant` ne s'active jamais. */
#vue-wormnet > .bloc {
    display: flex;
    flex-direction: column;
    min-height: 0;
    margin-bottom: 0;
}

#vue-wormnet #wn-bloc-parties { grid-column: 1; grid-row: 1; }
#vue-wormnet #wn-bloc-heures  { grid-column: 1; grid-row: 2; }
#vue-wormnet #wn-bloc-joueurs { grid-column: 2; grid-row: 1 / -1; }

/*  La zone qui defile sous l'en-tete du panneau. C'est elle qui remplace le
 *  defilement de la page. */
#vue-wormnet .wn-defilant {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    scrollbar-width: thin;
}

/*  L'EN-TETE DE PANNEAU : le label jaune a gauche, la rangee de canaux a droite.
 *
 *  🚨 PAS DE FILET SOUS LE LABEL, ET C'EST UN RELEVE, PAS UN GOUT. Chez le
 *  snooper le label « Games » est pose SUR LE FOND, au-dessus du cadre, et rien
 *  ne le souligne : le seul trait de la zone est celui qui entoure la liste.
 *  Un trait ici en ajoutait un second et faisait lire « bandeau interne de
 *  panneau » au lieu de « titre pose au-dessus d'un tableau ».
 *
 *  🔑 `min-height` : LES DEUX TABLEAUX DE L'ECRAN DOIVENT AVOIR LEUR BANDE DE
 *  COLONNES SUR LA MEME LIGNE. Chez le snooper « Game » et « Name » sont a
 *  3 px l'un de l'autre, et c'est ce qui fait lire deux colonnes d'un meme
 *  ecran. Chez nous la ligne de titre du panneau des parties fait 36 px (la
 *  rangee de canaux la remplit) et celle des joueurs 23 px : sans plancher, les
 *  deux bandes se decalaient de 13 px. Le plancher est la hauteur de la rangee
 *  de canaux, mesuree, pas choisie. */
#vue-wormnet .wn-entete {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: .4rem .8rem;
    min-height: 2.25rem;
    margin-bottom: .3rem;
}

/*  ⚠️ .88 rem et pas .95 : a .95 rem « Who's in the channel » fait 162 px et,
 *  avec l'age de la mesure a cote, ne tient pas dans la colonne de 16 rem — la
 *  ligne de titre passait a deux lignes et emportait l'alignement avec elle.
 *  La taille est la MEME dans les trois panneaux : un label jaune plus petit
 *  d'un cote que de l'autre se voit tout de suite. */
#vue-wormnet .wn-entete .titre-section {
    margin: 0;
    padding-bottom: 0;
    border-bottom: 0;
    font-size: .88rem;
}

/*  Dans l'en-tete, la rangee de canaux se resserre : elle partage la ligne avec
 *  le label au lieu d'avoir un panneau a elle. */
#vue-wormnet .wn-entete .wn-canaux { padding: .15rem; gap: .15rem; }
#vue-wormnet .wn-entete .wn-canal  { padding: .12rem .45rem; font-size: .84rem; }

/*  🚨 LA COLONNE DES JOUEURS EST ETROITE — 9 % chez le snooper, 11 % chez nous
 *  (16 rem mesures sur 2 304 px de contenu) parce que nos lignes portent en
 *  plus le client declare, la ou il met une bande de rang. La grille
 *  auto-remplie de `.wn-occupants` y tombe donc a une seule colonne, ce qui est
 *  exactement la forme du snooper. On le dit explicitement plutot que de le
 *  laisser au hasard d'un `minmax(15rem, 1fr)`. */
#vue-wormnet #wn-bloc-joueurs .wn-occupants {
    grid-template-columns: minmax(0, 1fr);
    gap: .2rem;
    margin-top: 0;
}

/*  🚨 LA GRILLE HORAIRE NE PEUT PAS RESTER CARREE A CETTE LARGEUR.
 *  `.hm-case` porte `aspect-ratio: 1` : la HAUTEUR d'une case suit sa largeur.
 *  Dans les 900 px du reste du site elles faisaient 34 px ; sur 2 000 px elles
 *  sont montees a 76 px, la grille a pris 664 px des 995 disponibles, et la
 *  liste des parties — ce qu'on vient voir — s'est retrouvee ecrasee a 321 px.
 *
 *  Premiere tentative : plafonner la LARGEUR de la grille a 62 rem. Ca reglait
 *  la hauteur mais laissait la moitie droite du panneau vide, ou le terrain du
 *  decor transparaissait — au point qu'un juge exterieur a lu la texture de la
 *  roche comme une suite de la grille. Un panneau a moitie vide n'est pas la
 *  forme du snooper, dont le second panneau de gauche est plein bord a bord.
 *
 *  🔑 D'OU LA VRAIE CORRECTION : on rend la case RECTANGULAIRE. La grille
 *  reprend toute la largeur, et c'est `grid-auto-rows` qui fixe la hauteur —
 *  donc elle ne depend plus du tout de la largeur de l'ecran. Une case de
 *  heatmap n'a aucune raison d'etre carree : ce qu'elle porte est une couleur,
 *  pas une forme.
 *  ⚠️ `aspect-ratio: auto` SEUL ne suffit pas : sans hauteur de rangee, les
 *  cases retomberaient a zero pixel — elles n'ont aucun contenu. */
#vue-wormnet #wn-bloc-heures .heatmap {
    max-width: none;
    grid-auto-rows: 1.45rem;
}
#vue-wormnet #wn-bloc-heures .hm-case { aspect-ratio: auto; }

/* ===================== LA DENSITE DU SNOOPER, ET SA STRUCTURE DE PANNEAU ====
 *
 *  🔑 CE QUI A ETE MESURE SUR SA PAGE, ET QU'ON REPREND ICI :
 *    · une partie tient sur UNE ligne d'environ 11 px, avec l'hote dans sa
 *      propre colonne ; un joueur sur 12,6 px. Nos cartes a deux lignes
 *      faisaient 53 px et 24 px : cinq fois et deux fois trop, et la moitie du
 *      canal passait sous la ligne de flottaison.
 *    · une bande grise de NOMS DE COLONNES (« Game | Host », « Name ») en tete
 *      de chaque liste.
 *    · LE CADRE ENTOURE LA LISTE, PAS LE PANNEAU : chez lui les labels
 *      « Games » / « Players » sont poses AU-DESSUS du filet, sur le fond.
 *
 *  🚨 CE QU'ON NE PEUT PAS REPRENDRE, ET POURQUOI CE N'EST PAS UN OUBLI :
 *  il montre le VRAI DRAPEAU du pays de chaque partie, la ou nous ecrivons
 *  « FLAG 5 ». Nos drapeaux de joueurs, eux, sont bien les images du jeu — la
 *  difference est que le serveur resout le fichier pour les occupants
 *  (`drapeauFichier`) et pas pour les parties, qui ne portent qu'un indice
 *  numerique. Le resoudre ici demanderait de RECOPIER la table indice→fichier
 *  dans le navigateur, ce que serveur.js interdit explicitement (« sans
 *  recopier cette table dans le code client, ou les deux finiraient par
 *  diverger en silence »). Cela se corrige cote serveur, pas ici.
 */

/*  Masque a l'oeil, garde pour les lecteurs d'ecran. La bande de colonnes est
 *  `aria-hidden` : sans ces mots-la dans chaque ligne, « HostingBuddy » et
 *  « 2min » seraient lus sans qu'on sache jamais ce qu'ils sont. */
#vue-wormnet .wn-vh {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/*  🚨 LE PANNEAU PERD SON FILET, PAS SON LAVIS — ET C'EST LA DISTINCTION QUI
 *  COMPTE. Le lavis est ce qui porte le texte secondaire au-dessus du plancher
 *  de lisibilite (4,14:1 sur le voile nu contre 5,09:1 avec, pire cas
 *  `desert-classique`) : le retirer casserait la page. Le filet, lui, n'est
 *  que du dessin ; il se redessine plus bas AUTOUR DE LA LISTE, ce qui est sa
 *  place chez le snooper. La regle `.bloc` de style.css continue donc de poser
 *  les deux ensemble, comme son cas de test l'exige — on ne fait que deplacer
 *  le trait dans cet onglet-ci. */
#vue-wormnet > .bloc {
    border: 0;
    padding: .3rem .5rem .45rem;
}

/*  Le cadre de la liste : l'aplat sombre du snooper et son filet de 1 px.
 *  Mesures sur cet empilement (carte posee sur le lavis du panneau, six themes,
 *  pire cas `desert-classique`) : --texte 12,26:1, --texte-doux 8,47:1,
 *  --wa-cadre 5,48:1. Tout est au-dessus du plancher, et mieux que sur le
 *  lavis nu — l'aplat de liste AIDE la lisibilite, il ne la coute pas. */
#vue-wormnet .wn-cadre {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: var(--carte);
    border: 1px solid var(--wa-cadre);
}

/*  La bande de noms de colonnes. Mesures : --texte-doux 6,83:1 et --wa-cadre
 *  4,42:1 sur cet empilement (lavis + carte + en-tete).
 *  ⚠️ Elle est HORS de `.wn-defilant` : un en-tete de colonnes qui defile avec
 *  sa liste ne sert plus a rien des la deuxieme ligne. */
#vue-wormnet .wn-colonnes {
    flex: none;
    display: grid;
    gap: .5rem;
    padding: .1rem .35rem;
    background: var(--wa-entete);
    border-bottom: 1px solid var(--wa-cadre);
    color: var(--texte-doux);
    font-size: .72rem;
}

/*  🔑 UNE SEULE DEFINITION DES COLONNES POUR LA BANDE ET POUR LES LIGNES.
 *  Deux `grid-template-columns` recopies finiraient par diverger d'un demi-rem
 *  et les libelles ne seraient plus au-dessus de leur colonne — le defaut
 *  classique d'un faux tableau. La variable est portee par le panneau. */
/*  ⚠️ La colonne « Host » est LARGE exprès. Chez le snooper elle commence a
 *  84 % de la largeur du panneau, et c'est cette longue respiration entre le nom
 *  de la partie et l'hote qui fait lire un tableau plutot qu'une suite de
 *  lignes. Une colonne juste assez large pour « HostingBuddy » collait les
 *  quatre colonnes de droite les unes aux autres. */
/*  🔑 LES ETIQUETTES COLLENT AU NOM DE LA PARTIE, ET LA PISTE DU NOM EST DONC
 *  DE LARGEUR FIXE. Chez le snooper le cadenas, l'icone de mode et le drapeau
 *  sont accroches au nom de la partie ; chez nous ils partaient a l'autre bout
 *  de la ligne, a mille pixels du titre qu'ils qualifient — deux juges
 *  exterieurs l'ont releve sans se concerter.
 *
 *  🚨 POURQUOI 20 rem EN DUR ET PAS `auto` OU `1fr`. CHAQUE LIGNE EST SA PROPRE
 *  GRILLE : un `<li>` ne partage pas ses pistes avec ses voisins. Une piste
 *  `auto` se dimensionne donc SUR SA SEULE LIGNE, et l'essai precedent — les
 *  etiquettes en premiere colonne — a mis les trois noms de parties a x=221,
 *  x=56 et x=32 selon le nombre d'etiquettes. Mesure faite, pas supposee. Avec
 *  une piste bornee a 20 rem, la resolution ne depend plus du contenu : elle
 *  vaut 20 rem sur toutes les lignes tant qu'il y a la place, donc les noms ET
 *  les etiquettes commencent au meme x partout. Un nom plus long est coupe par
 *  son ellipse, ce qui est deja son comportement.
 *  ⚠️ LE `1fr` EST SUR LA PISTE DES ETIQUETTES, PAS SUR CELLE DE L'HOTE, ET
 *  C'EST CE QUI PLACE LE FILET DE COLONNE AU BON ENDROIT. Chez le snooper la
 *  frontiere « Game | Host » tombe a 93 % de la largeur de la table : le nom
 *  respire sur presque toute la ligne et l'hote est un etroit ruban a droite.
 *  Avec le `1fr` sur l'hote, notre frontiere tombait a 17 % — mesure sur la
 *  capture — et le filet coupait la ligne juste apres le nom au lieu de juste
 *  avant l'hote. En donnant le jeu aux etiquettes (calees a gauche, donc elles
 *  ne bougent pas) et une largeur fixe a l'hote, la frontiere remonte a ~87 %.
 *  ⚠️ Les trois dernieres pistes etant fixes, le bord droit de chaque cellule
 *  tombe au meme pixel sur toutes les lignes ET sur la bande de libelles —
 *  c'est ce qui aligne le tableau sans `subgrid`. */
#vue-wormnet #wn-bloc-parties {
    --wn-cols: minmax(8rem, 20rem) minmax(1rem, 1fr) 9rem 4.4rem 4rem;
}
#vue-wormnet .wn-colonnes--parties { grid-template-columns: var(--wn-cols); }
#vue-wormnet .wn-colonnes--joueurs { grid-template-columns: minmax(0, 1fr); }
/*  🚨 LE LIBELLE S'ALIGNE COMME SA COLONNE, PAS COMME LE TEXTE. « Host » etait
 *  cale a gauche de sa cellule alors que les noms d'hote sont cales a droite :
 *  95 px d'ecart entre le titre et ce qu'il titre, et l'oeil ne rattachait plus
 *  les deux. Les trois colonnes de droite sont a droite, libelle compris. */
#vue-wormnet .wn-colonnes span:nth-child(3),
#vue-wormnet .wn-colonnes span:nth-child(4),
#vue-wormnet .wn-colonnes span:nth-child(5) { text-align: right; }

/*  Le filet vertical que le snooper pose dans sa bande d'en-tete, a la frontiere
 *  « Game | Host ». C'est le seul endroit ou il separe deux colonnes — les
 *  lignes de donnees, elles, n'ont aucun trait. Mesure de --wa-cadre sur cette
 *  bande : 4,42:1, au-dessus du seuil des elements non textuels. */
#vue-wormnet .wn-colonnes--parties span:nth-child(3) {
    border-left: 1px solid var(--wa-cadre);
    padding-left: .45rem;
}

/*  La ligne d'une partie : un tableau, plus une carte.
 *  ⚠️ `border: 0` efface AUSSI le liseré jaune de gauche (`.ligne--partie` de
 *  style.css) : il marquait une carte, il n'a plus de sens sur une ligne de
 *  tableau, et le snooper n'a rien de tel. */
#vue-wormnet .ligne--partie {
    display: grid;
    grid-template-columns: var(--wn-cols);
    align-items: center;
    gap: .5rem;
    padding: .04rem .35rem;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    font-size: .8rem;
    line-height: 1.35;
    /*  🚨 PAS DE FONDU A L'APPARITION SUR UNE LIGNE DE TABLEAU.
     *  `.ligne` porte `animation: apparition .25s` (style.css) — pensee pour des
     *  cartes qui arrivent une par une. Ici radar.js REECRIT toute la liste a
     *  chaque etat recu, soit toutes les cinq secondes : les six lignes
     *  repartaient donc de l'opacite zero six fois par minute, et une capture
     *  prise au mauvais moment les montre a moitie effacees. Ce n'est pas une
     *  question de gout : c'est un clignotement sur la seule information que la
     *  page existe pour donner. */
    animation: none;
}

#vue-wormnet #liste { gap: 0; }

#vue-wormnet .wn-p-nom {
    font-weight: 700;
    color: var(--texte);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
#vue-wormnet .wn-p-hote {
    color: var(--texte-doux);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: right;
}
/*  ⚠️ `tabular-nums` : sans ca « 1min » et « 11min » n'ont pas la meme largeur
 *  et la colonne tremble a chaque rafraichissement, toutes les cinq secondes. */
#vue-wormnet .wn-p-age {
    color: var(--texte-doux);
    font-variant-numeric: tabular-nums;
    text-align: right;
    white-space: nowrap;
}

#vue-wormnet .ligne--partie .etiquettes { gap: .25rem; flex-wrap: nowrap; }
#vue-wormnet .ligne--partie .etiquette {
    font-size: .6rem;
    padding: .02rem .28rem;
    border-radius: 0;
    letter-spacing: .04em;
}
/*  Le bouton garde son jaune et son relief — c'est la seule chose qu'on fait
 *  que le snooper ne fait pas, et il doit rester le point le plus visible de la
 *  ligne. On ne lui reduit que la taille. */
#vue-wormnet .ligne--partie .bouton--principal {
    padding: .1rem .45rem;
    font-size: .7rem;
    box-shadow: none;
    justify-self: end;
}

/*  ------------------------------------------------- le haut de chaque panneau
 *  🚨 28 % DE LA HAUTEUR AVANT LA PREMIERE DONNEE, CONTRE 7 % CHEZ LUI. Mesure
 *  faite sur les captures : nos deux panneaux empilaient un titre, deux a
 *  quatre lignes de prose, une note dateee, un bandeau d'etat et un sujet de
 *  canal avant la premiere ligne utile. On garde TOUT — chacune de ces lignes
 *  dit quelque chose qu'on ne peut pas deviner autrement — mais au format d'une
 *  legende, pas d'un paragraphe. */
#vue-wormnet .wn-entete { margin-bottom: .3rem; padding-bottom: .2rem; }

#vue-wormnet .bloc > .aide,
#vue-wormnet #wn-canaux-note {
    margin: .3rem 0 0;
    font-size: .74rem;
    line-height: 1.3;
    /*  ⚠️ Une note de bas de panneau ne prend pas de place a la liste : elle
     *  garde sa hauteur naturelle et c'est `.wn-cadre` qui absorbe le reste
     *  (`flex: 1 1 auto`). Sans `flex: none`, la note serait etiree. */
    flex: none;
}

#vue-wormnet .bandeau {
    padding: .22rem .5rem;
    margin-bottom: .3rem;
    gap: .45rem;
    font-size: .78rem;
    box-shadow: none;
}
#vue-wormnet .bandeau .pastille { width: 8px; height: 8px; }

/*  ⚠️ Le sujet du canal tient sur UNE ligne, avec des points de suspension et le
 *  texte complet dans l'infobulle (radar.js pose deja le `title`). Sur deux ou
 *  trois lignes il repoussait la liste des joueurs d'autant, et c'est une
 *  citation, pas une consigne : on la donne, on ne la met pas en avant. */
#vue-wormnet .wn-sujet {
    margin: 0 0 .25rem;
    padding: .05rem .45rem;
    font-size: .74rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/*  Le vide n'est pas une carte non plus : il vit DANS le cadre de la liste, qui
 *  porte deja l'aplat. */
#vue-wormnet .vide {
    padding: 1.1rem .8rem;
    border-radius: 0;
    box-shadow: none;
}
/*  🚨 `:not(.vide--alerte)` N'EST PAS UNE COQUETTERIE DE SELECTEUR.
 *  `.vide--alerte` est l'etat « la collecte est morte » : rouge sombre, liseré
 *  rouge — le seul endroit de l'ecran qui doit crier, parce qu'une liste vide y
 *  passerait sinon pour « personne ne joue » alors qu'on ne sait rien. Avec un
 *  identifiant en tete, la regle ci-dessous bat `.vide--alerte` a plate couture
 *  et lui aurait retire fond ET bordure EN SILENCE. Le `:not()` fait que la
 *  regle ne le VOIT plus, au lieu de le battre. */
#vue-wormnet .vide:not(.vide--alerte) {
    background: none;
    border: 0;
}
#vue-wormnet .vide-titre { font-size: .95rem; }

/*  ------------------------------------------------------- la colonne de droite
 *  9 % chez le snooper, 13 % chez nous a 19 rem. On resserre a 16 rem — environ
 *  11 % sur un ecran de 2 300 px — et les pseudos gardent leur place parce que
 *  le client declare passe en plus petit. */
#vue-wormnet #wn-bloc-joueurs .wn-occ { padding: .01rem .35rem; line-height: 1.35; }
#vue-wormnet #wn-bloc-joueurs .wn-pseudo { font-size: .8rem; font-weight: 700; }
/*  ⚠️ LARGEUR FIXE, PAS UN POURCENTAGE. En `max-width: 46%` la colonne du
 *  client commençait a un endroit different a chaque ligne — le milieu de la
 *  liste devenait dentele, alors que chez le snooper les trois colonnes d'une
 *  ligne de joueur sont a x fixe. A largeur fixe et calee a droite, tous les
 *  clients partent du meme trait. */
#vue-wormnet #wn-bloc-joueurs .wn-client {
    flex: none;
    width: 6.4rem;
    max-width: none;
    font-size: .62rem;
    text-align: right;
}
#vue-wormnet #wn-bloc-joueurs .wn-pays { font-size: .6rem; padding: 0 .2rem; }

/*  L'age de la mesure, remonte dans la ligne de titre du panneau (voir le
 *  commentaire dans index.html). ⚠️ `margin-left: auto` vient de
 *  `.bandeau-detail` et ne sert plus a rien ici — `.wn-entete` est deja en
 *  `space-between` — mais il ne gene pas non plus, et le laisser evite une
 *  regle de plus. On ne regle que la taille, pour qu'il reste secondaire face
 *  au label jaune a cote duquel il vit maintenant. */
#vue-wormnet #wn-bloc-joueurs .wn-entete { column-gap: .35rem; }
#vue-wormnet #wn-bloc-joueurs .wn-entete .bandeau-detail { font-size: .66rem; }

/*  ⚠️ Les cases de la grille horaire gardaient 2 px d'arrondi (style.css) : a
 *  cette echelle ca ne se voit pas une par une, mais 168 cases arrondies dans un
 *  panneau font une texture molle au milieu d'un ecran ou W:A n'arrondit rien. */
#vue-wormnet .hm-case { border-radius: 0; }

/*  La grille horaire prend le cadre elle aussi : chez le snooper, le second
 *  panneau de la colonne de gauche est encadre comme le premier. */
#vue-wormnet #wn-bloc-heures .wn-cadre { padding: .35rem; }
#vue-wormnet #wn-bloc-heures .grille-defilante { min-height: 0; }

/*  ⚠️ LE RESPONSIVE RETOMBE EN UNE COLONNE, ET RIEN D'AUTRE NE CHANGE.
 *  En dessous de 980 px, deux colonnes donneraient une colonne de joueurs de
 *  6 rem et des pseudos coupes a la troisieme lettre. On rend aussi le
 *  defilement a la page : sur un telephone, trois zones qui defilent
 *  separement dans un ecran de 600 px sont inutilisables. */
@media (max-width: 980px) {
    main:has(> #vue-wormnet:not([hidden])) { padding: 0 1rem 3rem; }

    #vue-wormnet {
        grid-template-columns: minmax(0, 1fr);
        grid-template-rows: auto;
        height: auto;
        min-height: 0;
    }
    #vue-wormnet #wn-bloc-parties,
    #vue-wormnet #wn-bloc-heures,
    #vue-wormnet #wn-bloc-joueurs { grid-column: 1; grid-row: auto; }

    #vue-wormnet > .bloc { margin-bottom: 1.4rem; }
    #vue-wormnet .wn-defilant { overflow-y: visible; }
    #vue-wormnet #wn-bloc-joueurs .wn-occupants {
        grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    }

    /*  🚨 LE TABLEAU DES PARTIES NE RENTRE PAS DANS UN TELEPHONE, ET ON NE LUI
     *  RETIRE PAS DE COLONNE POUR AUTANT. Mesure sur un ecran de 395 px : les
     *  cinq pistes valent 326 px de minimum plus 32 px de gouttieres, soit
     *  38 px de trop — et le document entier debordait de 92 px, ce qui donne
     *  une page qui glisse lateralement sous le doigt.
     *  On resserre les pistes ET on confine le debordement restant DANS le
     *  cadre, qui defile alors horizontalement. C'est exactement ce que fait
     *  deja `.grille-defilante` pour la grille horaire : meme probleme, meme
     *  reponse, et le tableau garde ses cinq colonnes. */
    #vue-wormnet #wn-bloc-parties {
        --wn-cols: minmax(5rem, 10rem) minmax(1rem, 1fr) 5.5rem 3.2rem 3.2rem;
    }
    #vue-wormnet #wn-bloc-parties .wn-cadre { overflow-x: auto; }
    #vue-wormnet #wn-bloc-parties .wn-colonnes,
    #vue-wormnet #wn-bloc-parties .wn-defilant { min-width: 21rem; }
    #vue-wormnet .ligne--partie { gap: .35rem; }
}
