/* ══════════════════════════════════════════════════════════════════════════════════════
   CONTOURS D'AVATAR                                                     (3.0.30.510)

   Demande d'Eric : « je voudrai que pour l'avatar on puisse choisir des contours animé,
   qui sont toujours animé mais ca prennent le moins de ressources possible dans la
   taverne ».

   ══ POURQUOI CE N'EST NI UN GIF NI UN PNG ═══════════════════════════════════════════
   Un contour anime livre en image, c'est un fichier de plusieurs centaines de ko que le
   navigateur DECODE en boucle, image par image, sur le processeur -- et une fois PAR
   AVATAR AFFICHE. Trente membres dans la liste, c'est trente boucles de decodage qui
   tournent en permanence pendant toute la soiree.

   Ici, tout est dessine par le moteur de rendu a partir de degrades : aucun fichier a
   telecharger, une seule regle CSS par style, et surtout la contrainte ci-dessous.

   ══ LA REGLE ABSOLUE DE CE FICHIER ══════════════════════════════════════════════════
   On n'anime QUE « transform » et « opacity ». Rien d'autre. Jamais.

   Ces deux proprietes-la sont les seules que la carte graphique sait appliquer toute
   seule : le dessin est peint UNE FOIS, mis de cote, puis simplement tourne ou attenue a
   chaque image. Le processeur ne refait aucun calcul.

   Sont donc INTERDITS ici, meme si le rendu serait plus joli :
       filter / drop-shadow / blur animes  -> repeint tout le disque a chaque image
       box-shadow anime                    -> pareil
       background-position ou -image animes-> pareil
       width / height / inset animes       -> recalcule la mise en page a chaque image
       backdrop-filter                     -> lit le fond de la page a chaque image
       un <canvas> par avatar              -> le pire de tous

   Le halo de chaque contour est donc un degrade FIXE (peint une fois, jamais retouche),
   et jamais une ombre floue animee.

   ══ will-change : AJOUTE APRES COUP (3.0.30.513) ════════════════════════════════════
   La premiere version s'en passait, au motif que Chromium promeut deja tout seul un
   element porteur d'une animation de transform. C'est vrai en general -- mais pas
   garanti quand l'element porte un MASQUE, ce qui est le cas de tous les anneaux ici.
   Il est donc pose explicitement, et uniquement sur les anneaux animes : les contours
   figes le relachent (voir plus bas), pour ne pas reserver de memoire video pour des
   dizaines d'elements immobiles.

   ══ DEUX ETATS : ANIME ET FIGE ══════════════════════════════════════════════════════
   .ct        -> le contour, anime
   .ct-fige   -> le MEME contour, immobile (aucune couche composee, cout nul)

   Les avatars des MESSAGES sont figes : il peut y en avoir une centaine dans un salon
   charge, et personne ne regarde le contour d'un avatar de 40 pixels a cote d'une ligne
   de texte. Ne sont animes que les endroits ou on regarde vraiment quelqu'un : la liste
   des membres, sa propre barre en bas, et la carte de profil.

   Trois interrupteurs coupent l'animation partout, sans rien changer d'autre :
       body.contours-figes            -> case « Figer les contours » du selecteur
       prefers-reduced-motion         -> reglage systeme de l'utilisateur
       .ct-fige                       -> au cas par cas, pose par la page

   ══ AJOUTER UN CONTOUR ══════════════════════════════════════════════════════════════
   1. Copier un bloc « .ct-xxx » ci-dessous, changer les couleurs et la vitesse.
   2. Ajouter { id:"xxx", nom:"..." } dans la liste CONTOURS de app.js, a la place voulue
      (l'ordre du tableau EST l'ordre de la fenetre). Ajouter « groupe:"..." » ouvre une
      nouvelle famille a partir de cette ligne ; sans ce champ, l'entree rejoint la
      famille en cours.
   L'identifiant ne doit contenir que des minuscules, des chiffres et des tirets, et tenir
   en 24 caracteres : le serveur refuse tout le reste (ChatHub.SetContour). Pas d'accent,
   donc « emeraude » et non « émeraude ».
   Rien d'autre. Ni le serveur ni la base ne connaissent la liste (voir ChatHub.SetContour).
   ══════════════════════════════════════════════════════════════════════════════════════ */


/* ── Geometrie commune ───────────────────────────────────────────────────────────────
   --ct-ep     epaisseur du trait de l'anneau
   --ct-ecart  distance entre le bord de l'avatar et l'anneau
   --ct-halo   distance entre le bord de l'avatar et le bord exterieur du halo
   --ct-vit    duree d'un tour complet

   Les valeurs par defaut conviennent a un avatar de 32 a 40 pixels ; les contextes plus
   grands les redefinissent plus bas.

   ══ ELARGI EN 3.0.30.511 ═══════════════════════════════════════════════════════════
   Eric, apres avoir vu la premiere version : « ils sont trop leger, en gros ca ne prend
   pas assez de place autour de l'avatar » -- puis, capture a l'appui : « dans la taverne
   ca ne fait pas la meme taille [que dans le selecteur] [...] la plupart des contours
   fonctionne dans l'apercu pour choisir mais dans la taverne ils ne s'affichent pas ».

   Deux causes distinctes, corrigees ensemble :

   1. LES APERCUS PROMETTAIENT PLUS QUE LA LISTE. Le selecteur montre des ronds de 46 px,
      la liste des membres en affichait 32. Meme epaisseur relative, mais deux fois moins
      de matiere a l'ecran : ce qui est franc dans la fenetre de choix devient un filet
      dans le panneau de droite. L'avatar de la liste passe donc a 36 px (style.css) et le
      trait a 5 px -- les deux tailles se ressemblent enfin.

   2. QUATRE CONTOURS SE FAISAIENT LEUR PROPRE EPAISSEUR. braise (2px), plasma (3px),
      engrenage et pierre (3,5px) redefinissaient --ct-ep dans leur bloc. Tant que la
      valeur commune valait 2,5 px, ca passait inapercu. Une fois elle elargie, ces
      quatre-la restaient fins -- et sur un fond sombre, un filet de 2 px sur un avatar
      sombre, ca ne se voit tout simplement pas. Aucun contour ne fixe plus son epaisseur :
      elle vient UNIQUEMENT du contexte (liste, message, carte, apercu). Un style ne
      choisit que ses couleurs et sa vitesse.

   Consequence a connaitre : l'anneau d'un avatar de liste fait 50 px de diametre. La
   hauteur de ligne passe donc de 44 a 52 px, et l'avatar de 32 a 36 px, dans style.css
   (« .member » et « .member-avatar »). C'est tout ce que les contours changent en dehors
   de ce fichier -- pour revenir en arriere, ces deux lignes-la et les valeurs ci-dessous.

   ══ LE DEUXIEME CERCLE (3.0.30.514) ═════════════════════════════════════════════════
   Eric : « il y a en gros en general 2 cercles autour de l'avatar, la il n'affiche que
   le premier, ca doit etre parce qu'il manque de place pour l'afficher ».

   Diagnostic juste, et calcul a l'appui. Le « deuxieme cercle », c'est le halo -- mais
   ce qu'on en voit, c'est seulement ce qui DEPASSE de l'anneau :

       bord exterieur de l'anneau = 18 (rayon avatar) + 2 (ecart) + 5 (trait) = 25 px
       bord exterieur du halo     = 18 + 10 (--ct-halo)                       = 28 px
       ce qui depasse             =                                              3 px

   Trois pixels, dont l'opacite tombe a zero sur la fin : autant dire rien. Dans le
   selecteur, ou l'avatar fait 46 px, il en depassait 5 -- assez pour qu'on distingue
   les deux cercles, pas assez pour que la liste tienne la promesse.

   Le halo passe donc a 15 px : 8 px de lueur AU-DELA de l'anneau, soit largement de quoi
   lire deux cercles distincts. Il deborde alors de la ligne de 7 px en haut et en bas,
   donc sur les voisins -- sans consequence : a cette distance-la le degrade est deja
   quasiment transparent, deux halos qui se chevauchent se melangent au lieu de se
   couper.

   ERREUR DE CE PARAGRAPHE, CORRIGEE EN .515 : j'avais ecrit ici « rien n'est rogne,
   aucun overflow:hidden sur le chemin ». C'etait FAUX -- je ne l'avais pas cherche, je
   l'avais suppose. .avatar et .member-avatar en portent un dans style.css, et c'est LUI
   qui coupait tout. Voir le bloc suivant. Elargir le halo etait donc juste, mais sans
   la correction du .515 ca ne pouvait rien donner dans la Taverne.

   ══ VALEURS ACTUELLES (3.0.30.517) — LES CHIFFRES CITES PLUS HAUT SONT L'HISTORIQUE ══
   Les 5px de trait et 15px de halo racontes ci-dessus n'existent plus : ils compensaient
   le rognage. Celui-ci corrige, tout a ete rendu (.516), puis Eric : « il faut reduire de
   1 pixel pour que ca ne deborde pas encore ». D'ou 2px de trait.

   L'arithmetique, pour la liste des membres :

       avatar 36px                    -> rayon 18
       + ecart 2px + trait 2px        -> anneau exterieur rayon 22  (diametre 44)
       + halo 9px                     -> lueur exterieure rayon 27  (diametre 54)
       demi-hauteur de ligne (48px)   =                          24

   ANNEAU : 22 <= 24. Il ne touche plus la ligne du dessus ni celle du dessous, 2px de
   marge. C'etait la demande.

   HALO : c'est LUI qui empietait. Eric, capture a l'appui : « ca touche la section d'un
   autre pseudo, ca empiete un peu ». Mesure faite sur sa capture : la lueur allait bien
   jusqu'au rayon 27, pour une demi-ligne de 24 -- 3px sur le voisin. J'avais ecrit que
   c'etait sans consequence ; ca se voit des que la ligne d'a cote change de fond (survol,
   ligne selectionnee), et c'est exactement la qu'il l'a vu.

   Le halo passe donc a 6px : rayon exterieur 24, soit PILE la demi-hauteur de ligne.
   Plus rien ne sort de sa ligne, ni anneau ni lueur.

   CE QUE CA COUTE, ET LES DEUX SEULES FACONS DE RECUPERER UNE VRAIE LUEUR :
   il ne reste que 2px entre le bord de l'anneau (22) et la limite (24). La lueur ne peut
   donc plus former un « deuxieme cercle » distinct, elle adoucit le bord de l'anneau.
   C'est arithmetique : avec un avatar de 36px dans une ligne de 48px, il n'y a que 6px
   de marge verticale a se partager entre l'ecart, le trait et la lueur.

       - lignes plus hautes : .member { height: 54px } dans style.css, et --ct-halo: 9px
         ici. La lueur revient, la liste montre ~2 membres de moins par ecran.
       - avatar plus petit : .member-avatar { 32px } dans style.css. La marge passe a 8px,
         donc 4px de lueur possible, sans toucher a la hauteur de ligne.

   Pour supprimer la lueur completement : --ct-halo: 0. */

/* ══ VALEURS ACTUELLES (3.0.30.541) — CE QUI PRECEDE EST L'HISTORIQUE ═══════════════
   Eric : « reduises soit un peu l'avatar pour pouvoir avoir un plus gros contour, ou
   alors augmente l'espace entre les pseudos dans la liste [...] et aussi avoir davantage
   de fond disponible ».

   Les deux branches de l'alternative sont celles que ce fichier proposait deja, a la fin
   du bloc precedent. On les prend TOUTES LES DEUX, parce qu'elles ne se concurrencent
   pas : elles agrandissent la meme marge, chacune par un bout.

       avant                      apres
       avatar        36px         34px      (Eric : « reduire un peu l'avatar »)
       ligne         48px         54px      (Eric : « augmenter l'espace »)
       marge          6px         10px      <- c'est LA le sujet

   La marge, c'est (demi-hauteur de ligne) moins (rayon de l'avatar) : 24 - 18 = 6 avant,
   27 - 17 = 10 maintenant. Tout le reste s'y loge.

   ══ CE QUE --ct-ecart FAIT VRAIMENT (mesure au pixel, .543) ══════════════════════════
   Le paragraphe qui occupait cette place etait FAUX, et il l'etait depuis le .517. Il
   disait : « + ecart 2px -> l'anneau commence a 19 ; + trait 3px -> il finit a 22 ».

   C'est l'inverse. Le masque de ::after garde la bande EXTERIEURE de sa boite :

       radial-gradient(farthest-side, #0000 calc(100% - ep), #000 calc(100% - ep))
                                      ^ transparent jusqu'a (bord - ep), opaque apres

   et la boite de ::after a pour rayon (rayon avatar + ecart). Le trait mange donc vers
   l'INTERIEUR a partir de la, il ne s'ajoute pas vers l'exterieur. « ecart » ne nomme pas
   un intervalle entre l'avatar et l'anneau : il fixe le bord EXTERIEUR de l'anneau.

   Releve au pixel (anneau blanc uni sur fond noir, avatar de 34px, ecart 2, trait 3) :

       bande peinte, du centre :   16,00 -> 18,88     et non 19 -> 22
       lueur, jusqu'a :                     27
       demi-hauteur de ligne :              27

   Personne n'avait vu l'erreur parce qu'elle ne change rien a ce qu'on voit : l'anneau
   mord 1px sur le bord de l'avatar, ce qui est exactement l'allure voulue. Elle change en
   revanche tout calcul qu'on fait A PARTIR d'elle -- et c'est ce qui a fausse le reglage
   de la pastille de statut, plus bas dans ce fichier.

   Le partage reel, donc :

       avatar 34px                     -> rayon 17
       anneau (bord exterieur a r+ecart, 3px vers l'interieur)     16 -> 19
       lueur jusqu'a 10px du bord de l'avatar                           27
       demi-hauteur de ligne (54px)                                     27

   La lueur s'arrete PILE au bord de la ligne : rien n'empiete sur le voisin, ce qui etait
   la demande du .517 et qui reste vraie. Et elle depasse l'anneau de 8px -- le
   « deuxieme cercle » redevient largement lisible, ce qu'on avait du sacrifier a l'epoque.

   Le trait passe a 3px. Un rond plus petit avec un trait plus epais, c'est deux fois le
   meme gain : plus de matiere dessinee, et plus de contraste avec le fond.

   ATTENTION AUX TROIS AUTRES ENDROITS QUI EN DEPENDENT :
     - .contour-apercu (plus bas) suit l'avatar de la liste au pixel pres, par principe.
       Il passe donc a 34px lui aussi. Un apercu qui n'est pas le rendu n'est pas un
       apercu (voir son bloc).
     - .me-bar .member-avatar (style.css) : meme taille, et son padding vertical passe a
       10px, sinon la lueur deborde du bloc.
     - .avatar.ct (avatars des messages) garde sa propre lueur ; il herite en revanche du
       trait de 3px, ce qui lui va : 40px de rond, 1px de plus qu'avant sur le rayon
       exterieur, et l'interligne des messages est large.

   CE QUE CA COUTE : la liste montre environ un membre de moins par ecran (54 au lieu de
   48 par ligne). C'etait le prix annonce, et c'est celui qu'Eric a choisi de payer. */
.ct {
  position: relative;
  --ct-ep: 3px;
  --ct-ecart: 2px;
  --ct-halo: 10px;
  --ct-vit: 5s;
}

/* ::before = le HALO. Fixe, jamais anime : il est peint avec le reste de la page et ne
   coute donc rien par image. Un degrade radial, pas une ombre floue (voir l'en-tete). */
.ct::before {
  content: "";
  position: absolute;
  inset: calc(-1 * var(--ct-halo));
  border-radius: 50%;
  pointer-events: none;
  background: none;
  animation-delay: var(--ct-phase, 0s);   /* voir « LA SACCADE » plus bas */
}

/* ::after = L'ANNEAU. C'est lui qui tourne.

   Le masque radial evide le centre : il ne reste qu'un anneau de --ct-ep d'epaisseur.
   Sans lui, le degrade conique remplirait tout le disque et masquerait l'avatar.

   -webkit-mask est double par mask sans prefixe : le WebView2 embarque suit la version
   de Chromium installee sur le poste, qui peut etre anterieure a la prise en charge de
   la forme courte non prefixee. Les deux lignes coutent zero et couvrent les deux cas. */
.ct::after {
  content: "";
  position: absolute;
  inset: calc(-1 * var(--ct-ecart));
  border-radius: 50%;
  pointer-events: none;
  -webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - var(--ct-ep)), #000 calc(100% - var(--ct-ep)));
          mask: radial-gradient(farthest-side, #0000 calc(100% - var(--ct-ep)), #000 calc(100% - var(--ct-ep)));
  animation: ct-tourne var(--ct-vit) linear infinite;

  /* ══ LA SACCADE — POURQUOI CETTE LIGNE (3.0.30.513) ═══════════════════════════════
     Eric : « l'animation qui tourne saccade un peu par moment ».

     « Par moment », et pas en continu : ce n'etait donc pas un probleme de puissance,
     et c'est ce detail qui donne la reponse.

     A CHAQUE evenement « presence » -- c'est-a-dire des que N'IMPORTE QUI change de
     statut, lance un jeu, se connecte ou se deconnecte -- renderMembers reconstruit
     la liste entiere en reassignant innerHTML. Tous les avatars sont donc DETRUITS et
     RECREES. Or une animation CSS repart de sa premiere image sur un element neuf :
     tous les anneaux revenaient d'un coup a 0 degre. Ce n'est pas une image perdue,
     c'est un SAUT en arriere -- et il tombait pile aux moments ou la Taverne s'agite,
     donc jamais quand on l'observait exprès.

     La page pose « --ct-phase » a chaque rendu : le temps ecoule depuis l'ouverture,
     en negatif. Un delai negatif demarre l'animation DEJA avancee d'autant. Comme
     elle boucle a l'infini, l'anneau recree reprend exactement la ou en etait
     l'ancien, quelle que soit sa duree de tour. Voir contourPhase() dans app.js.

     Doit rester APRES le raccourci « animation », qui remet animation-delay a zero. */
  animation-delay: var(--ct-phase, 0s);

  /* Promotion explicite en couche graphique. J'avais ecrit plus haut que Chromium le
     faisait deja tout seul pour une animation de transform : c'est vrai en general,
     mais pas garanti quand l'element porte un masque -- exactement notre cas. Le
     doute coute 10 ko de memoire video par anneau (50x50 px) ; l'oublier coute une
     animation qui retombe sur le processeur et hoquette des que la page travaille.
     Uniquement sur les anneaux ANIMES : .ct-fige et body.contours-figes l'annulent. */
  will-change: transform;
}

/* Les animations du fichier. TOUTES ne touchent que « transform » ou « opacity » : c'est
   la regle en tete de fichier, et elle vaut pour les nouvelles comme pour les anciennes. */
@keyframes ct-tourne     { from { transform: rotate(0deg);   } to { transform: rotate(360deg);  } }
@keyframes ct-tourne-inv { from { transform: rotate(0deg);   } to { transform: rotate(-360deg); } }
@keyframes ct-battement  { 0%,100% { opacity: .55; } 50% { opacity: 1; } }

/* ══ TROIS MOUVEMENTS DE PLUS (3.0.30.543) ═══════════════════════════════════════════
   Eric : « un truc beaucoup plus compliqué ». La galerie n'utilisait qu'UN mouvement --
   le tour complet a vitesse constante -- alors que transform en autorise bien d'autres,
   tous au meme prix : zero. Ce qui coute, c'est de REPEINDRE, pas de bouger.

   ct-tourne-respire  rotation ET mise a l'echelle dans la meme image-cle. Deux
                      transformations dans UN seul transform, et non deux animations
                      concurrentes : deux regles « transform » sur le meme element se
                      remplacent l'une l'autre, la derniere gagne, et on ne verrait
                      qu'une des deux. C'est le piege classique.
   ct-balance         un balayage aller-retour au lieu d'un tour. Avec ease-in-out, il
                      ralentit aux extremites : c'est ce qui fait « pendule » et pas
                      « essuie-glace ».
   (les crans)        pas d'image-cle a ecrire : « animation-timing-function: steps(12) »
                      suffit a transformer ct-tourne en aiguille qui saute d'un cran.
   ══════════════════════════════════════════════════════════════════════════════════════ */
@keyframes ct-tourne-respire {
  0%   { transform: rotate(0deg)   scale(1);    }
  50%  { transform: rotate(180deg) scale(1.07); }
  100% { transform: rotate(360deg) scale(1);    }
}
@keyframes ct-balance {
  0%, 100% { transform: rotate(-42deg); }
  50%      { transform: rotate(42deg);  }
}


/* ── Les interrupteurs qui figent tout ───────────────────────────────────────────────
   « animation: none » ne cache rien : le contour reste exactement la, dessine pareil.
   Il cesse simplement de tourner -- et l'element n'est alors plus une couche composee. */
.ct-fige::before,
.ct-fige::after,
body.contours-figes .ct::before,
body.contours-figes .ct::after {
  animation: none !important;
  /* On rend AUSSI la memoire video : un anneau immobile n'a aucune raison de garder
     une couche graphique reservee pour lui. Sans cette ligne, « figer les contours »
     arreterait le mouvement sans rien economiser d'autre. */
  will-change: auto !important;
}
@media (prefers-reduced-motion: reduce) {
  .ct::before, .ct::after { animation: none !important; will-change: auto !important; }
}


/* ── Tailles selon l'endroit ─────────────────────────────────────────────────────────
   L'epaisseur doit suivre la taille de l'avatar, sinon un anneau regle pour un rond de
   64 pixels etrangle un rond de 32. */

/* ══ LA VRAIE CAUSE DE « IL N'Y EN A QU'UN BOUT » (3.0.30.515) ═══════════════════════
   Eric, apres trois tentatives ratees de ma part : « il manque toujours le reste du
   contour y en a qu'un bout [...] tu as elargi le contour des avatar dans les choix mais
   pas dans l'affichage ensuite des avatar dans la taverne ». Il avait raison, et sur les
   trois points a la fois.

   style.css, section AVATARS, une ligne ecrite bien avant les contours :

       .avatar, .member-avatar { background-size:cover; background-position:center;
                                 overflow:hidden; }

   overflow:hidden + border-radius:50% = TOUT ce qui depasse du rond de l'avatar est
   coupe. Or l'anneau et le halo vivent justement DEHORS : l'anneau commence 2px au-dela
   du bord, le halo va jusqu'a 15px. Tout etait donc rase net, et il ne restait que le
   liseré qui mordait encore sur le rond.

   Ce qui explique les trois symptomes d'un coup :
     - « il n'y en a qu'un bout »            -> le reste etait coupe
     - « il n'affiche que le premier cercle » -> le halo, entierement dehors, disparaissait
     - « tu as elargi dans les choix mais pas dans la taverne » -> exactement ca : les
       apercus du selecteur portent la classe .contour-apercu, qui n'a PAS ce overflow.
       Chaque fois que j'elargissais, le selecteur s'ameliorait et la Taverne EMPIRAIT,
       puisque j'y poussais une part croissante de l'anneau hors du rond, donc hors du
       cadre. J'ai passe trois versions a mesurer des choses justes au mauvais endroit.

   On rend donc le debordement visible -- uniquement sur les avatars qui portent un
   contour. Sans risque : le fond de l'avatar est deja decoupe en rond par
   border-radius:50% (les fonds sont clipes par le rayon de bordure, overflow ne sert a
   rien pour ca), et le seul contenu de ces elements est le texte des initiales, centre.

   Deux classes dans le selecteur = specificite 0-2-0, ce qui l'emporte sur le 0-1-0 de
   style.css meme si style.css est chargee apres. Pas de !important. */
.avatar.ct,
.member-avatar.ct,
.up-avatar.ct,
.ri-avatar.ct,
.contour-apercu.ct {
  overflow: visible;
}

/* Liste des membres et barre du bas (34px depuis le .541) : valeurs par defaut. */

/* Avatar d'un message (40px) : l'interligne y est genereux, le halo peut respirer. */
.avatar.ct { --ct-halo: 8px; }

/* Carte de profil (64px, avec sa bordure sombre de 4px) : c'est LA qu'on regarde le
   contour, tout y est nettement plus large.
   Le halo s'arrete a 14px : au-dela il depasserait le bord gauche de la carte (14px de
   padding dans .up-body) et se ferait couper net par son overflow:hidden. */
.up-avatar.ct {
  --ct-ep: 3.5px;
  --ct-ecart: 3px;
  --ct-halo: 14px;
}

/* ══ LA PASTILLE DE STATUT NE DOIT PLUS MORDRE L'ANNEAU (3.0.30.512) ═════════════════
   Eric : « il manque encore un bout, tu vois il en manque un bout encore ».

   Ce n'etait pas le contour : c'etait la pastille verte/orange posee en bas a droite de
   l'avatar. Elle fait 12px PLUS une bordure de 3px de la couleur du panneau -- soit un
   disque opaque de 18px, centre a 15,6px du centre de l'avatar, qui s'etend donc jusqu'a
   24,6px. Or l'anneau occupe la bande 20 -> 25px. La pastille passait tout simplement
   PAR-DESSUS, et son cerne sombre y decoupait un cran bien net.

   Invisible avant les contours (il n'y avait rien a mordre), invisible dans le selecteur
   (les apercus n'ont pas de pastille) : d'ou un « bout manquant » qu'on ne voyait qu'a un
   seul endroit.

   La pastille rentre donc dans l'avatar des qu'il y a un contour : bordure de 2px et
   decalage de +2px au lieu de -2px, ce qui la ramene a 19,3px du centre -- juste sous le
   bord interieur de l'anneau, qui redevient un cercle complet.

   Le selecteur « .ct + .status-dot » vise la pastille qui SUIT immediatement un avatar
   porteur d'un contour : c'est exactement la structure produite par la page (le div de
   l'avatar puis le span de la pastille, dans .member-avatar-wrap comme dans .me-bar).
   Un membre sans contour ne bouge pas d'un pixel.

   ══ RESSERRE A 3px EN .541, PUIS A 4px EN .543 ══════════════════════════════════════
   Le .541 a resserre la pastille de 2 a 3px « pour lui donner 2,5px de marge ». Ce chiffre
   etait faux : il venait du calcul errone de --ct-ecart corrige plus haut, qui plaçait le
   bord INTERIEUR de l'anneau a 19px du centre alors qu'il est a 16.

   Releve sur la diagonale bas-droite, la ou la pastille approche l'anneau (anneau blanc
   uni, pastille coloriee pour la distinguer, lecture au huitieme de pixel) :

       pastille, du centre :   jusqu'a 17,13
       anneau, du centre :     16,00 -> 18,88  droit devant
                               17,25 -> 18,75  sur la diagonale

   L'anneau y perdait 1,25px sur 2,9, soit pres de la moitie de sa largeur, mange par la
   pastille. Ca ne se lisait pas comme un cran -- le cerne de la pastille a la couleur du
   panneau, l'anneau restait continu -- mais il s'amincissait la, et c'est la meme cause
   que le defaut du .512.

   Un pixel de plus. A 4px, la pastille s'arrete a 15,9px : juste sous les 16px du bord
   interieur. L'anneau garde sa largeur pleine sur tout le tour.

       ecart = racine(2) x (11 - decalage) + 6      (rayon de la pastille : 6px)
       decalage 3 -> 17,31      decalage 4 -> 15,90      bord interieur : 16,00

   ══ ET C'EST JUSTEMENT CE QUI LA RENDAIT TROP CENTREE (3.0.30.556) ══════════════════
   Eric : « le point vert, orange ou autre n'est pas un peu trop au centre du cercle ?
   il faudrait le mettre plus vers l'exterieur ».

   Il a raison, et le calcul ci-dessus explique exactement pourquoi : « ne pas toucher
   l'anneau » et « etre sur le bord » sont deux exigences qui s'excluent a cette taille.

       centre de la pastille, du centre de l'avatar :   9,90  (58 % du rayon)
       bord de l'avatar :                              17,00
       anneau :                                        16,00 -> 18,88

   Pour que la pastille n'effleure pas l'anneau il faut que son centre reste a 10px ou
   moins -- soit a peine plus de la moitie du rayon. Visuellement, elle flotte dans
   l'image au lieu d'etre epinglee au bord, et c'est ce qu'Eric voit.

   Il n'y a pas de position intermediaire : des qu'on la sort, elle recouvre l'anneau.
   Evider l'anneau sous la pastille n'est pas possible non plus -- l'anneau TOURNE
   (transform: rotate sur ::after), un masque pose dessus tournerait avec lui et le trou
   ferait le tour de l'avatar.

   On choisit donc franchement la convention de Discord et de Steam : la pastille est
   POSEE SUR le bord, par-dessus l'anneau, et son cerne a la couleur du panneau menage
   un vide net tout autour. L'anneau passe derriere, il n'est pas ronge : il est masque
   sur environ 55 degres, ce qui se lit comme un badge epingle et non comme un defaut --
   d'autant que la plupart des contours sont deja des arcs ajoures qui tournent.

   Reglage (attention : box-sizing border-box, la largeur INCLUT le cerne) :

       pastille 14px, cerne 3px   -> disque de couleur de 8px, rayon exterieur 7
       decalage -2px              -> centre a 16,97 du centre de l'avatar

   soit tout juste sur le bord de l'avatar (rayon 17), et la meme place que pour un
   membre SANS contour (18,38) : les deux listes s'alignent enfin a l'oeil.

   Le disque de couleur passe de 6 a 8px au passage : sur un anneau vif, un point de 6px
   se distinguait mal. */
/* La TAILLE (14px, cerne 3px) est desormais celle de tout le monde, posee une seule fois
   dans style.css -- la mettre ici ne l'appliquait qu'aux avatars a contour et creait deux
   tailles dans la meme liste (voir le commentaire de .status-dot la-bas). Il ne reste
   donc ici que ce qui est propre aux contours... et il ne reste RIEN : la position est la
   meme avec et sans contour, et c'est justement ce qui les aligne a l'oeil. La regle
   « .ct + .status-dot » a donc disparu ; seul ce commentaire reste, pour que personne ne
   la reecrive sans avoir relu l'arithmetique ci-dessus.

   Mesure de controle (banc_pastille.js, avec et sans contour, alternes) :
       pastille : 14x14 dans les six cas
       centre a 16,97 du centre de l'avatar (rayon 17) -- pile sur le bord */

/* Rail des conversations privees (28px) : le plus petit, contour discret et fige. */
.ri-avatar.ct {
  --ct-ep: 2px;
  --ct-ecart: 1.5px;
  --ct-halo: 5px;
}


/* ══════════════════════════════════════════════════════════════════════════════════════
   LA GALERIE

   Chaque contour tient en deux declarations : le halo fixe (::before) et l'anneau qui
   tourne (::after). Les pourcentages d'un degrade conique sont des fractions de TOUR :
   « 0 55% » = du debut jusqu'aux 55% du cercle.

   ══ POURQUOI rgba(...,0) ET JAMAIS #0000 ═══════════════════════════════════════════
   Piege vu au rendu : une couleur qui s'eteint vers #0000 (= NOIR transparent) traverse
   tout un dégradé de gris sombres avant de disparaitre. A l'ecran, ca ne fait pas un
   halo qui s'estompe, ca fait un ANNEAU TERNE, sale, autour de l'avatar -- tres visible
   sur les contours clairs (cyan, glace).

   On eteint donc toujours vers la MEME couleur en transparence : rgba(34,211,238,0)
   plutot que #0000. Le degrade reste dans sa teinte du debut a la fin.

   Exception : les hard stops (repeating-conic-gradient avec deux positions identiques,
   « #0000 0 13deg, #fff 13deg 16deg ») ne font AUCUNE interpolation -- ils passent d'une
   couleur a l'autre d'un coup. #0000 y est sans danger, et plus lisible.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── Néon cyan ── un seul arc net, la comete la plus simple ── */
.ct-neon-cyan::before { background: radial-gradient(closest-side, rgba(34,211,238,0) 66%, rgba(34,211,238,0.43) 88%, rgba(34,211,238,0) 100%); }
.ct-neon-cyan::after  { --ct-vit: 4.5s;
  background: conic-gradient(rgba(34,211,238,.30) 0 12%,
                             rgba(34,211,238,.62) 34%, #22d3ee 44%, #cffafe 50%,
                             #22d3ee 56%, rgba(34,211,238,.62) 66%,
                             rgba(34,211,238,.30) 88%, rgba(34,211,238,.30) 100%); }

/* ── Néon violet ── */
.ct-neon-violet::before { background: radial-gradient(closest-side, rgba(168,85,247,0) 66%, rgba(168,85,247,0.46) 88%, rgba(168,85,247,0) 100%); }
.ct-neon-violet::after  { --ct-vit: 4.5s;
  background: conic-gradient(rgba(168,85,247,.32) 0 12%,
                             rgba(192,132,252,.62) 34%, #a855f7 44%, #f3e8ff 50%,
                             #a855f7 56%, rgba(192,132,252,.62) 66%,
                             rgba(168,85,247,.32) 88%, rgba(168,85,247,.32) 100%); }

/* ── ArcaneTech ── deux arcs opposes, cyan et violet (le contour de ta photo) ── */
.ct-arcane::before { background: radial-gradient(closest-side, rgba(124,58,237,0) 64%, rgba(139,92,246,0.41) 86%, rgba(139,92,246,0) 100%); }
.ct-arcane::after  { --ct-vit: 6s;
  background: conic-gradient(rgba(99,102,241,.28) 0 2%,
                             #22d3ee 15%, #a5f3fc 22%, #22d3ee 29%, rgba(99,102,241,.28) 40%,
                             rgba(99,102,241,.28) 52%,
                             #8b5cf6 65%, #ddd6fe 72%, #8b5cf6 79%, rgba(99,102,241,.28) 90%); }

/* ── Vortex plasma ── le tour complet, tres colore, rapide ── */
.ct-plasma::before { background: radial-gradient(closest-side, rgba(217,70,239,0) 62%, rgba(217,70,239,0.43) 86%, rgba(217,70,239,0) 100%); }
.ct-plasma::after  { --ct-vit: 2.6s;
  background: conic-gradient(#22d3ee, #f472b6 25%, #d946ef 50%, #f472b6 75%, #22d3ee 100%); }

/* ── Anneau de feu ── trois langues de flamme, avec un battement de lumiere ── */
.ct-feu::before { background: radial-gradient(closest-side, rgba(255,120,0,0) 62%, rgba(255,120,0,0.51) 86%, rgba(255,120,0,0) 100%);
                  animation: ct-battement 1.9s ease-in-out infinite; }
.ct-feu::after  { --ct-vit: 3.2s;
  background: conic-gradient(#ff3b00, #ffb020 12%, #fff2a8 20%, #ff3b00 33%,
                             #ffb020 45%, #fff2a8 53%, #ff3b00 66%,
                             #ffb020 78%, #fff2a8 86%, #ff3b00 100%); }

/* ── Lave ── croute sombre fixe (::before en anneau, pas en halo) + coulees lentes ── */
.ct-lave::before {
  inset: calc(-1 * var(--ct-ecart));
  background: #3f1204;
  -webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - var(--ct-ep)), #000 calc(100% - var(--ct-ep)));
          mask: radial-gradient(farthest-side, #0000 calc(100% - var(--ct-ep)), #000 calc(100% - var(--ct-ep)));
}
.ct-lave::after { --ct-vit: 7s;
  background: conic-gradient(rgba(255,90,31,.25) 0 8%, #ff5a1f 18%, #ffd166 24%, rgba(255,90,31,.25) 32%,
                             rgba(255,90,31,.25) 46%, #ff5a1f 56%, #ffd166 62%, rgba(255,90,31,.25) 70%,
                             rgba(255,90,31,.25) 82%, #ff5a1f 92%, rgba(255,90,31,.25) 100%); }

/* ── Braise ── discret : presque rien, sauf une lueur qui respire ── */
.ct-braise::before { background: radial-gradient(closest-side, rgba(239,68,68,0) 66%, rgba(239,68,68,0.46) 88%, rgba(239,68,68,0) 100%);
                     animation: ct-battement 2.6s ease-in-out infinite; }
.ct-braise::after  { --ct-vit: 8s;
  background: conic-gradient(#7f1d1d, #ef4444 30%, #fca5a5 50%, #ef4444 70%, #7f1d1d 100%); }

/* ── Cristal de glace ── pale, lent, presque immobile a l'oeil ── */
.ct-glace::before { background: radial-gradient(closest-side, rgba(186,230,253,0) 66%, rgba(186,230,253,0.41) 88%, rgba(186,230,253,0) 100%); }
.ct-glace::after  { --ct-vit: 9s;
  background: conic-gradient(rgba(125,211,252,.3) 0 8.7%,
                             #7dd3fc 13%, #e0f2fe 16.7%, #7dd3fc 20.4%, rgba(125,211,252,.3) 24.7%,
                             rgba(125,211,252,.3) 42%,
                             #7dd3fc 46.3%, #e0f2fe 50%, #7dd3fc 53.7%, rgba(125,211,252,.3) 58%,
                             rgba(125,211,252,.3) 75.3%,
                             #7dd3fc 79.6%, #e0f2fe 83.3%, #7dd3fc 87%, rgba(125,211,252,.3) 91.3%,
                             rgba(125,211,252,.3) 100%); }

/* ── Engrenage ── dents regulieres en bronze, rotation lente et mecanique ── */
.ct-engrenage::before { background: radial-gradient(closest-side, rgba(209,154,74,0) 70%, rgba(209,154,74,0.3) 90%, rgba(209,154,74,0) 100%); }
.ct-engrenage::after  { --ct-vit: 14s;
  background: repeating-conic-gradient(#d19a4a 0 7deg, #7c5320 7deg 15deg); }

/* ── Runes célestes ── anneau indigo fixe + eclats qui defilent lentement ── */
.ct-runes::before {
  inset: calc(-1 * var(--ct-ecart));
  background: rgba(99,102,241,0.55);
  -webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - var(--ct-ep)), #000 calc(100% - var(--ct-ep)));
          mask: radial-gradient(farthest-side, #0000 calc(100% - var(--ct-ep)), #000 calc(100% - var(--ct-ep)));
}
.ct-runes::after { --ct-vit: 16s;
  background: repeating-conic-gradient(#0000 0 12deg, #e0e7ff 12deg 15deg); }

/* ── Cyber-matrix ── tirets verts, sens inverse, allure de scanner ── */
.ct-matrix::before { background: radial-gradient(closest-side, rgba(34,197,94,0) 68%, rgba(34,197,94,0.35) 88%, rgba(34,197,94,0) 100%); }
.ct-matrix::after  { --ct-vit: 5.5s;
  animation-name: ct-tourne-inv;
  background: repeating-conic-gradient(#0000 0 7.5deg, #22c55e 7.5deg 10deg, #86efac 10deg 12deg); }

/* ── Pierre ancienne ── mousse et granit, un tour toutes les 25 secondes ── */
.ct-pierre::before { background: radial-gradient(closest-side, rgba(125,139,106,0) 72%, rgba(125,139,106,0.32) 90%, rgba(125,139,106,0) 100%); }
.ct-pierre::after  { --ct-vit: 25s;
  background: conic-gradient(#4b5340, #93a37c 15%, #3f4636 28%, #a8b88f 46%,
                             #454c3a 62%, #93a37c 80%, #4b5340 100%); }

/* ── Abyssal ── halo profond fixe + arc violet a contre-sens, tres lent ── */
.ct-abysse::before {
  inset: calc(-1 * var(--ct-halo));
  background: radial-gradient(closest-side, rgba(46,16,101,0) 58%, rgba(46,16,101,0.55) 78%, rgba(124,58,237,0.41) 92%, rgba(124,58,237,0) 100%);
}
.ct-abysse::after { --ct-vit: 11s;
  animation-name: ct-tourne-inv;
  background: conic-gradient(rgba(124,58,237,.45) 0 14%, #8b5cf6 36%, #ddd6fe 50%, #8b5cf6 64%, rgba(124,58,237,.45) 86%); }

/* ── Or ── le classique, deux reflets qui font le tour ── */
.ct-or::before { background: radial-gradient(closest-side, rgba(212,175,55,0) 68%, rgba(212,175,55,0.38) 88%, rgba(212,175,55,0) 100%); }
.ct-or::after  { --ct-vit: 6.5s;
  background: conic-gradient(#8a6a1f, #d4af37 14%, #fff4c2 22%, #d4af37 34%, #8a6a1f 48%,
                             #d4af37 62%, #fff4c2 72%, #d4af37 84%, #8a6a1f 100%); }


/* ══════════════════════════════════════════════════════════════════════════════════════
   TREIZE DE PLUS                                                        (3.0.30.542)

   Eric : « j'aimerais en avoir plus ». La galerie passe de 14 a 27.

   ══ LA SEULE REGLE QUI COMPTE POUR LES NOUVEAUX ═════════════════════════════════════
   Aucun d'eux n'introduit de propriete animee : ce sont tous des degrades FIXES sur un
   anneau qui tourne, ou un halo dont l'opacite bat. Ajouter treize contours ne coute donc
   rien de plus par avatar affiche -- le cout d'un contour, c'est UNE couche composee qui
   tourne, et il est le meme quelle que soit la complexite du dessin.

   ══ CE QUI LES REND DIFFERENTS LES UNS DES AUTRES ═══════════════════════════════════
   Trois leviers seulement, et il faut en changer au moins deux pour qu'un contour ne soit
   pas « le meme en une autre couleur » :

     la FORME     arc unique / deux arcs opposes / tour complet / tirets / blocs nets
     la VITESSE   2s (nerveux) a 30s (quasi immobile), et le sens
     la MATIERE   degrade doux (metal, lueur) ou hard stops (pixels, circuit, radar)

   Les hard stops (« #0000 0 7deg, #fff 7deg 9deg ») ne font aucune interpolation : ils
   donnent une arete franche, ce qu'aucun degrade doux ne sait faire. C'est ce qui separe
   Pixel et Circuit du reste de la galerie.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* ── Rose néon ── la comete, en magenta. Meme forme que les deux autres neons : c'est
      volontaire, ils forment une famille et on les choisit a la couleur. ── */
.ct-rose-neon::before { background: radial-gradient(closest-side, rgba(244,114,182,0) 66%, rgba(244,114,182,0.46) 88%, rgba(244,114,182,0) 100%); }
.ct-rose-neon::after  { --ct-vit: 4.5s;
  background: conic-gradient(rgba(244,114,182,.32) 0 12%,
                             rgba(249,168,212,.62) 34%, #f472b6 44%, #fce7f3 50%,
                             #f472b6 56%, rgba(249,168,212,.62) 66%,
                             rgba(244,114,182,.32) 88%, rgba(244,114,182,.32) 100%); }

/* ── Prisme ── le spectre entier, sans arc ni creux : c'est le seul contour de la galerie
      qui n'a ni debut ni fin visible. Lent, sinon il donne le tournis. ── */
.ct-prisme::before { background: radial-gradient(closest-side, rgba(255,255,255,0) 68%, rgba(255,255,255,0.24) 88%, rgba(255,255,255,0) 100%); }
.ct-prisme::after  { --ct-vit: 8s;
  background: conic-gradient(#ff4d4d, #ffb020 14%, #ffe66d 28%, #4ade80 42%,
                             #22d3ee 56%, #6366f1 70%, #c084fc 84%, #ff4d4d 100%); }

/* ── Toxique ── vert acide, tirets larges, et un halo qui respire vite : c'est le
      battement qui fait « radioactif », pas la couleur seule. ── */
.ct-toxique::before { background: radial-gradient(closest-side, rgba(163,230,53,0) 64%, rgba(163,230,53,0.48) 88%, rgba(163,230,53,0) 100%);
                      animation: ct-battement 1.4s ease-in-out infinite; }
.ct-toxique::after  { --ct-vit: 6s;
  background: repeating-conic-gradient(#65a30d 0 14deg, #a3e635 14deg 22deg, #ecfccb 22deg 26deg); }

/* ── Orage ── bleu nuit presque noir, traverse d'eclairs blancs nets. Rapide et a
      contre-sens : c'est ce qui l'empeche de ressembler a « Cristal de glace ». ── */
.ct-orage::before { background: radial-gradient(closest-side, rgba(129,140,248,0) 66%, rgba(129,140,248,0.40) 88%, rgba(129,140,248,0) 100%); }
.ct-orage::after  { --ct-vit: 3.4s;
  animation-name: ct-tourne-inv;
  /* ELARGI APRES RENDU : les eclairs faisaient 2% du tour, soit moins de 3 px d'arc sur
     un anneau de 44 px de diametre -- a l'ecran, une poussiere blanche. Ils font 5%, et
     le fond est passe de #1e1b4b a #312e81 : un bleu nuit trop sombre sur le fond sombre
     de la Taverne, c'est un anneau qui n'existe qu'a ses eclairs. */
  background: conic-gradient(#312e81 0 16%, #ffffff 19%, #c7d2fe 22%, #312e81 25%,
                             #312e81 44%, #e0e7ff 48%, #ffffff 50%, #312e81 54%,
                             #312e81 68%, #a5b4fc 71%, #ffffff 74%, #312e81 78%,
                             #312e81 100%); }

/* ── Aurore boréale ── vert, cyan, violet, et 22 secondes par tour : assez lent pour
      qu'on ne voie pas la rotation, juste la couleur qui derive. ── */
.ct-aurore::before { background: radial-gradient(closest-side, rgba(45,212,191,0) 62%, rgba(45,212,191,0.30) 80%, rgba(167,139,250,0.34) 93%, rgba(167,139,250,0) 100%); }
.ct-aurore::after  { --ct-vit: 22s;
  background: conic-gradient(rgba(16,185,129,.55) 0 6%, #34d399 18%, #67e8f9 32%,
                             #a78bfa 48%, #67e8f9 64%, #34d399 78%,
                             rgba(16,185,129,.55) 94%, rgba(16,185,129,.55) 100%); }

/* ── Émeraude ── pierre taillee : fond vert sombre, deux facettes qui accrochent la
      lumiere. Rien a voir avec Cyber-matrix, qui est un scanner en tirets. ── */
.ct-emeraude::before { background: radial-gradient(closest-side, rgba(5,150,105,0) 68%, rgba(5,150,105,0.40) 88%, rgba(5,150,105,0) 100%); }
.ct-emeraude::after  { --ct-vit: 7.5s;
  background: conic-gradient(#064e3b, #10b981 16%, #d1fae5 24%, #10b981 32%, #064e3b 48%,
                             #10b981 64%, #ecfdf5 74%, #10b981 82%, #064e3b 100%); }

/* ── Sakura ── petales : des taches roses douces sur fond pale, tres lent. Le seul
      contour clair de la galerie qui ne soit pas froid. ── */
.ct-sakura::before { background: radial-gradient(closest-side, rgba(251,207,232,0) 66%, rgba(251,207,232,0.44) 88%, rgba(251,207,232,0) 100%); }
.ct-sakura::after  { --ct-vit: 18s;
  background: conic-gradient(rgba(251,207,232,.45) 0 5%, #f9a8d4 12%, #fff1f2 18%, rgba(251,207,232,.45) 25%,
                             rgba(251,207,232,.45) 38%, #fbcfe8 45%, #fff1f2 50%, rgba(251,207,232,.45) 57%,
                             rgba(251,207,232,.45) 70%, #f9a8d4 77%, #fff1f2 83%, rgba(251,207,232,.45) 90%,
                             rgba(251,207,232,.45) 100%); }

/* ── Argent ── le pendant d'Or, meme forme exactement : deux reflets qui font le tour.
      Sur un avatar sombre, c'est le contour le plus lisible de tous. ── */
.ct-argent::before { background: radial-gradient(closest-side, rgba(226,232,240,0) 68%, rgba(226,232,240,0.36) 88%, rgba(226,232,240,0) 100%); }
.ct-argent::after  { --ct-vit: 6.5s;
  background: conic-gradient(#475569, #cbd5e1 14%, #ffffff 22%, #cbd5e1 34%, #475569 48%,
                             #cbd5e1 62%, #ffffff 72%, #cbd5e1 84%, #475569 100%); }

/* ── Circuit imprimé ── vert carte mere + pistes dorees en aretes franches. Lent et
      mecanique comme Engrenage, mais en pistes fines au lieu de dents. ── */
/* Halo dore et non vert : APRES RENDU, avec une lueur verte, Circuit se confondait avec
   Cyber-matrix et Toxique dans la grille -- trois pastilles vertes a tirets. C'est l'or
   qui doit dominer, le vert n'est que le support. */
.ct-circuit::before { background: radial-gradient(closest-side, rgba(234,179,8,0) 72%, rgba(234,179,8,0.30) 90%, rgba(234,179,8,0) 100%); }
.ct-circuit::after  { --ct-vit: 20s;
  background: repeating-conic-gradient(#0b3a26 0 9deg, #eab308 9deg 14deg,
                                       #0b3a26 14deg 22deg, #fde68a 22deg 24deg); }

/* ── Pixel 8-bit ── huit blocs pleins, aucune transition : c'est le seul contour qui
      assume de ne pas etre lisse. Le clin d'oeil retro de la galerie. ── */
.ct-pixel::before { background: radial-gradient(closest-side, rgba(250,204,21,0) 70%, rgba(250,204,21,0.28) 90%, rgba(250,204,21,0) 100%); }
.ct-pixel::after  { --ct-vit: 12s;
  background: conic-gradient(#ef4444 0 45deg, #f97316 45deg 90deg, #facc15 90deg 135deg,
                             #4ade80 135deg 180deg, #22d3ee 180deg 225deg,
                             #3b82f6 225deg 270deg, #a855f7 270deg 315deg,
                             #ec4899 315deg 360deg); }

/* ── Radar ── un balayage unique avec sa traine, et rien d'autre sur le reste du tour.
      Deux secondes : de loin le plus rapide de la galerie, et il le faut -- un radar
      lent ne ressemble a rien. ── */
.ct-radar::before { background: radial-gradient(closest-side, rgba(34,197,94,0) 66%, rgba(34,197,94,0.30) 88%, rgba(34,197,94,0) 100%); }
.ct-radar::after  { --ct-vit: 2s;
  background: conic-gradient(rgba(34,197,94,0) 0 55%, rgba(34,197,94,.18) 72%,
                             rgba(34,197,94,.55) 88%, #4ade80 97%, #ecfdf5 100%); }

/* ── Ombre ── presque rien : un anneau graphite avec un seul reflet pale. Pour ceux qui
      veulent un contour sans que ca se voie. ── */
.ct-ombre::before { background: radial-gradient(closest-side, rgba(148,163,184,0) 72%, rgba(148,163,184,0.22) 90%, rgba(148,163,184,0) 100%); }
.ct-ombre::after  { --ct-vit: 15s;
  background: conic-gradient(#1f2937 0 30%, #64748b 46%, #cbd5e1 50%, #64748b 54%,
                             #1f2937 70%, #1f2937 100%); }

/* ── Fumée ── volutes grises qui derivent a contre-sens, 28 secondes par tour. Le plus
      discret de tous : a l'oeil, il ne bouge pas. ── */
.ct-fumee::before { background: radial-gradient(closest-side, rgba(203,213,225,0) 64%, rgba(203,213,225,0.26) 86%, rgba(203,213,225,0) 100%); }
.ct-fumee::after  { --ct-vit: 28s;
  animation-name: ct-tourne-inv;
  background: conic-gradient(rgba(148,163,184,.30) 0 10%, rgba(226,232,240,.75) 26%,
                             rgba(148,163,184,.30) 42%, rgba(203,213,225,.60) 58%,
                             rgba(100,116,139,.35) 74%, rgba(226,232,240,.65) 88%,
                             rgba(148,163,184,.30) 100%); }


/* ══════════════════════════════════════════════════════════════════════════════════════
   LES COMPLEXES                                                         (3.0.30.543)

   Eric : « un truc beaucoup plus compliqué ».

   Les 27 precedents partagent tous la MEME mecanique : un dessin fixe, un tour complet a
   vitesse constante. Ils ne different que par leurs couleurs. Ceux-ci changent la
   mecanique elle-meme, et c'est ca qui les rend reellement differents.

   ══ DEUX CALQUES QUI BOUGENT SEPAREMENT ═════════════════════════════════════════════
   Un contour dispose de deux calques : ::before et ::after. Jusqu'ici ::before ne servait
   qu'a poser un halo fixe. Rien n'oblige a ca -- « Lave » et « Runes celestes » en font
   deja un ANNEAU au lieu d'un halo. On va plus loin : ici ::before est un anneau qui a sa
   propre animation, sa propre vitesse et son propre sens.

   LE PRIX, ET IL EST REEL : un contour a deux anneaux n'a plus de halo. Le calque qui le
   dessinait sert maintenant au deuxieme cercle. Ce n'est pas un oubli, c'est le compromis
   -- et il n'y a pas de troisieme calque a disposition sans toucher au HTML de CINQ
   fonctions de rendu differentes, ce qui n'en vaut pas le risque.

   ══ CE QUE CA COUTE ═════════════════════════════════════════════════════════════════
   Deux couches composees par avatar au lieu d'une. A 54x54 px, une couche pese environ
   11 ko de memoire video : trente membres a l'ecran passent de ~350 ko a ~700 ko. Le
   processeur, lui, ne fait toujours RIEN par image -- ces animations ne repeignent
   jamais, elles deplacent une texture deja prete.

   C'est la difference de nature avec une image animee, et c'est pour ca que celle-ci
   reste exclue : un GIF de 30 avatars, c'est 30 zones a REPEINDRE a chaque image de
   l'animation, plus le transfert de la nouvelle image vers la carte graphique.

   ══ animation-name ET NON animation ═════════════════════════════════════════════════
   Sur ::before, on ecrit les proprietes une par une (-name, -duration, -timing-function,
   -iteration-count) au lieu du raccourci « animation ». Le raccourci remettrait
   animation-delay a zero, et c'est lui qui porte --ct-phase : l'anneau recree apres un
   evenement « presence » repartirait de zero degre. C'est exactement la saccade corrigee
   en .513, et le raccourci la ferait revenir sur ces six contours-la seulement.

   « will-change » est repose explicitement sur ces ::before : la regle par defaut ne le
   met que sur ::after, puisque c'etait le seul calque anime jusqu'ici.
   ══════════════════════════════════════════════════════════════════════════════════════ */

/* Reglages communs aux anneaux exterieurs.

   ══ POURQUOI PAS « inset: -(--ct-halo) », MA PREMIERE IDEE ═══════════════════════════
   Elle etait seduisante : --ct-halo est deja la limite a ne pas franchir dans chaque
   contexte, donc coller le deuxieme anneau juste en dedans semblait le placer au mieux
   partout. La pellicule de controle a montre le contraire.

   Sur la carte de profil, --ct-halo vaut 14 px pour un avatar de 64 : le deuxieme anneau
   se retrouvait a 13 px du premier, seul au milieu du vide. Ce n'etait plus un contour a
   deux cercles, c'etait un cercle avec un cerceau autour. La valeur qui convient a une
   LUEUR (qui s'estompe, et peut donc aller loin) ne convient pas a un TRAIT net.

   Le deuxieme anneau se place donc par rapport au PREMIER, pas par rapport a la limite,
   avec un intervalle et une epaisseur proportionnels au trait commun. Il reste ainsi
   lisible comme un couple a toutes les tailles, de 28 px a 64 px.

       inset     = ecart + 2,2 x trait   -> liste : 2 + 6,6  = 8,6 px  (rayon 25,6)
       epaisseur = 0,6 x trait           -> liste : 1,8 px    (bande 23,8 -> 25,6)

   Ce qui donne, dans la liste (mesure, pas estimation) :

       anneau interieur   16,0 -> 19,0
       vide                     19,0 -> 23,8    <- c'est lui qui fait « deux anneaux »
       anneau exterieur              23,8 -> 25,6
       limite de la ligne                       27,0

   Le vide de 4,8 px entre les deux est l'effet, pas un reste : colles l'un a l'autre, ils
   se liraient comme une seule bande epaisse. Le deuxieme est aussi plus FIN que le
   premier, pour la meme raison -- deux traits de meme poids ne se distinguent pas. */
.ct-reacteur::before,
.ct-noyau::before,
.ct-turbine::before {
  content: "";
  inset: calc(-1 * (var(--ct-ecart) + var(--ct-ep) * 2.2));
  border-radius: 50%;
  pointer-events: none;
  -webkit-mask: radial-gradient(farthest-side, #0000 calc(100% - var(--ct-ep) * 0.6), #000 calc(100% - var(--ct-ep) * 0.6));
          mask: radial-gradient(farthest-side, #0000 calc(100% - var(--ct-ep) * 0.6), #000 calc(100% - var(--ct-ep) * 0.6));
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  will-change: transform;
}

/* ── Réacteur ── deux anneaux, deux sens, deux vitesses. Le plus spectaculaire de la
      galerie : c'est le decalage entre les deux rotations qui donne la profondeur, un
      seul anneau ne peut pas la produire, quelle que soit sa couleur. ── */
.ct-reacteur::before {
  animation-name: ct-tourne-inv;
  animation-duration: 9s;
  background: conic-gradient(rgba(34,211,238,.20) 0 8%, #22d3ee 20%, #ecfeff 27%,
                             #22d3ee 34%, rgba(34,211,238,.20) 46%,
                             rgba(34,211,238,.20) 58%, #22d3ee 70%, #ecfeff 77%,
                             #22d3ee 84%, rgba(34,211,238,.20) 96%,
                             rgba(34,211,238,.20) 100%);
}
.ct-reacteur::after { --ct-vit: 3.6s;
  background: conic-gradient(rgba(251,146,60,.25) 0 10%, #fb923c 30%, #fff7ed 42%,
                             #fb923c 54%, rgba(251,146,60,.25) 78%,
                             rgba(251,146,60,.25) 100%); }

/* ── Noyau ── une structure IMMOBILE et une lumiere qui passe dessus. L'anneau exterieur
      ne tourne pas du tout : ce sont douze crans fixes. L'arc interieur les balaye. A
      l'oeil, ce sont les crans qui s'allument au passage -- alors que rien n'a change de
      couleur, c'est seulement la lumiere qui est passee derriere. ── */
.ct-noyau::before {
  animation-name: none;          /* la structure ne bouge pas : c'est tout l'effet */
  will-change: auto;             /* et donc aucune couche a reserver pour elle */
  /* Crans eclaircis apres la pellicule : en #164e63 sur fond sombre, la structure se
     devinait plus qu'elle ne se voyait, et l'effet « ca s'allume au passage » tombait a
     plat faute de quelque chose a allumer. */
  background: repeating-conic-gradient(#22d3ee 0 17deg, #155e75 17deg 30deg);
}
.ct-noyau::after { --ct-vit: 2.4s;
  background: conic-gradient(rgba(103,232,249,0) 0 58%, rgba(103,232,249,.22) 76%,
                             rgba(103,232,249,.70) 90%, #a5f3fc 97%, #ffffff 100%); }

/* ── Turbine ── deux mouvements de NATURE differente : l'exterieur saute de cran en cran
      (steps), l'interieur glisse a contre-sens. Ils ne se synchronisent jamais, l'oeil ne
      trouve pas de motif -- c'est ce qui le distingue de Reacteur, qui a deux rotations
      lisses. ── */
.ct-turbine::before {
  animation-name: ct-tourne;
  animation-duration: 8s;
  animation-timing-function: steps(16);
  background: repeating-conic-gradient(#334155 0 14deg, #94a3b8 14deg 18deg,
                                       #e2e8f0 18deg 22.5deg);
}
.ct-turbine::after { --ct-vit: 5s;
  animation-name: ct-tourne-inv;
  background: conic-gradient(rgba(56,189,248,.18) 0 12%, #38bdf8 34%, #e0f2fe 50%,
                             #38bdf8 66%, rgba(56,189,248,.18) 88%,
                             rgba(56,189,248,.18) 100%); }

/* ── Horloge ── douze crans par tour, douze secondes : une seconde par cran, exactement
      comme une trotteuse. C'est le SEUL contour qui n'avance pas continument, et ca se
      remarque tout de suite au milieu des autres. Il garde son halo (un seul calque
      anime). ── */
.ct-horloge::before { background: radial-gradient(closest-side, rgba(226,232,240,0) 70%, rgba(226,232,240,0.26) 90%, rgba(226,232,240,0) 100%); }
.ct-horloge::after  { --ct-vit: 12s;
  animation-timing-function: steps(12);
  background: conic-gradient(#1e293b 0 4%, #fbbf24 4% 9%, #fef3c7 9% 11%,
                             #334155 11% 25%, #475569 25% 27%,
                             #1e293b 27% 50%, #475569 50% 52%,
                             #1e293b 52% 75%, #475569 75% 77%,
                             #1e293b 77% 100%); }

/* ── Pulsar ── rotation ET respiration dans la meme image-cle : l'anneau enfle en passant
      a mi-tour puis se resserre. Le halo, lui, reste fixe -- deux choses qui pulsent en
      meme temps, ca ne fait plus un rythme, ca fait du bruit. ── */
.ct-pulsar::before { background: radial-gradient(closest-side, rgba(147,197,253,0) 62%, rgba(147,197,253,0.44) 86%, rgba(147,197,253,0) 100%); }
.ct-pulsar::after  { --ct-vit: 2.8s;
  animation-name: ct-tourne-respire;
  animation-timing-function: ease-in-out;
  background: conic-gradient(rgba(59,130,246,.22) 0 14%, #3b82f6 32%, #dbeafe 44%,
                             #ffffff 50%, #dbeafe 56%, #3b82f6 68%,
                             rgba(59,130,246,.22) 86%, rgba(59,130,246,.22) 100%); }

/* ── Balancier ── il ne fait JAMAIS le tour : il balaye 84 degres dans un sens, puis dans
      l'autre, en ralentissant aux extremites. Le seul contour de la galerie qui revienne
      sur ses pas, et le seul dont on puisse suivre un point des yeux. ── */
.ct-balancier::before { background: radial-gradient(closest-side, rgba(217,119,6,0) 68%, rgba(217,119,6,0.34) 88%, rgba(217,119,6,0) 100%); }
.ct-balancier::after  { --ct-vit: 3.4s;
  animation-name: ct-balance;
  animation-timing-function: ease-in-out;
  /* RESSERRE APRES LA PELLICULE : la zone claire couvrait 40% du tour pour un balayage
     de +/-42 degres, soit +/-11,7% -- la tache bougeait moins que sa propre largeur, et
     on ne voyait rien bouger du tout. Elle fait 16% : le va-et-vient se lit. */
  background: conic-gradient(rgba(120,53,15,.55) 0 42%, #d97706 46%, #fde68a 50%,
                             #d97706 54%, rgba(120,53,15,.55) 58%,
                             rgba(120,53,15,.55) 100%); }


/* ══════════════════════════════════════════════════════════════════════════════════════
   LE SELECTEUR

   Ouvert depuis la carte de profil, le clic droit sur son propre pseudo, ou le menu de
   statut. Les apercus sont animes -- ils sont 28 depuis le .542 et n'existent que pendant
   que la fenetre est ouverte, c'est sans commune mesure avec la liste des membres.

   28 anneaux qui tournent en meme temps, ca reste 28 couches composees par la carte
   graphique et zero calcul processeur : c'est toute la raison d'etre de la regle en tete
   de fichier. Le jour ou la galerie doublerait encore, la question a se poser serait de
   n'animer que la famille visible a l'ecran, pas de reduire le nombre de contours.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.contours-voile {
  position: fixed;
  inset: 0;
  z-index: 95;
  background: rgba(0,0,0,0.55);
  display: flex;
  align-items: center;
  justify-content: center;
}
.contours-voile.hidden { display: none; }

.contours-boite {
  background: var(--bg-float, #0F172A);
  border: 1px solid #000;
  border-radius: 12px;
  box-shadow: 0 10px 40px rgba(0,0,0,0.55);
  width: min(560px, 92vw);
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.contours-titre {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 14px 16px 10px;
  font-size: 16px;
  font-weight: 700;
  color: var(--text-strong, #fff);
}
.contours-titre .ct-spacer { flex: 1; }
.contours-fermer {
  background: rgba(255,255,255,0.08);
  border: none;
  color: var(--text-muted, #949ba4);
  font-size: 20px;
  line-height: 1;
  width: 30px; height: 30px;
  border-radius: 50%;
  cursor: pointer;
}
.contours-fermer:hover { background: rgba(255,255,255,0.19); color: #fff; }

.contours-sous-titre {
  padding: 0 16px 10px;
  font-size: 12.5px;
  color: var(--text-faint, #6b7280);
  line-height: 1.45;
}

/* La grille defile toute seule si la galerie s'allonge : rien a retoucher en ajoutant
   un contour. */
.contours-grille {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: 10px;
  padding: 4px 16px 14px;
  overflow-y: auto;
}

/* Titre de famille (3.0.30.542). « 1 / -1 » = de la premiere a la derniere colonne, quel
   que soit leur nombre : la grille est en auto-fill, elle en compte 4, 5 ou 3 selon la
   largeur de la fenetre, et un chiffre ecrit en dur ici casserait a la premiere
   redimension. Le premier titre ne prend pas de marge haute : il touche deja le bord. */
.contours-groupe {
  grid-column: 1 / -1;
  margin-top: 12px;
  padding: 0 2px 2px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-faint, #6b7280);
}
.contours-groupe:first-child { margin-top: 0; }

.contour-choix {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 12px 4px 8px;
  border-radius: 10px;
  border: 2px solid transparent;
  background: rgba(255,255,255,0.04);
  cursor: pointer;
  user-select: none;
}
.contour-choix:hover { background: rgba(255,255,255,0.09); }
.contour-choix.actif {
  border-color: var(--accent, #5865f2);
  background: rgba(88,101,242,0.19);
}

/* ══ L'APERCU EST EXACTEMENT L'AVATAR DE LA LISTE (3.0.30.516) ═══════════════════════
   Meme taille (36px) et AUCUNE variable a lui : il herite de .ct comme la liste. Ce que
   la fenetre de choix montre est donc, au pixel pres, ce qui sera affiche.

   C'est volontairement plus strict qu'avant. Le rond d'apercu faisait 46px avec ses
   propres epaisseurs, « parce qu'on le regarde de pres pour choisir » : resultat, il a
   promis pendant quatre versions un anneau que la liste ne rendait pas, et j'ai passe
   ce temps a chercher pourquoi les deux differaient. Un apercu qui n'est pas le rendu
   n'est pas un apercu.

   Si un jour l'envie revient de le grossir : grossir l'avatar de la LISTE (style.css),
   pas celui-ci.

   34px depuis le .541, comme la liste. Si ces deux chiffres divergent un jour, c'est un
   bug, pas un reglage. */
.contour-apercu {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 16px;
  color: #fff;
  background-size: cover;
  background-position: center;
}
/* Pas de « .contour-apercu.ct { --ct-... } » ici, et c'est le point : toute valeur
   posee a cet endroit recreerait l'ecart entre ce qu'on choisit et ce qu'on obtient. */

.contour-nom {
  font-size: 11.5px;
  color: var(--text-muted, #949ba4);
  text-align: center;
  line-height: 1.25;
}
.contour-choix.actif .contour-nom { color: var(--text-strong, #fff); }

.contours-pied {
  border-top: 1px solid rgba(255,255,255,0.11);
  padding: 11px 16px 13px;
  display: flex;
  /* flex-start et pas center : le libelle fait deux lignes, une case a cocher centree
     dessus se retrouve a hauteur de la ligne d'explication au lieu du titre. */
  align-items: flex-start;
  gap: 9px;
  font-size: 13px;
  color: var(--text-muted, #949ba4);
  cursor: pointer;
}
.contours-pied input { cursor: pointer; margin-top: 2px; flex-shrink: 0; }
.contours-pied .cp-aide {
  display: block;
  font-size: 11.5px;
  color: var(--text-faint, #6b7280);
  margin-top: 2px;
}


/* ══════════════════════════════════════════════════════════════════════════════════════
   OK / ANNULER                                                          (3.0.30.541)

   Eric : « avoir un bouton OK et annuler a la fenetre pour retrouver son ancien contour
   ou accepter le nouveau ».

   Avant, cliquer un contour l'enregistrait SUR LE SERVEUR dans la seconde, et le seul
   moyen de revenir en arriere etait de se rappeler lequel on avait. On essayait donc en
   diffusant chaque essai a toute la Taverne.

   Desormais le clic ne fait que PREVISUALISER : le contour change sous les yeux, partout
   ou il apparait chez soi, et rien ne part sur le reseau tant qu'on n'a pas dit OK.
   Annuler (comme la croix, le voile et Echap) remet celui d'avant. Voir ouvrirContours
   dans app.js.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.contours-actions {
  border-top: 1px solid rgba(255,255,255,0.11);
  padding: 11px 16px 13px;
  display: flex;
  align-items: center;
  gap: 10px;
}

/* La mention d'aperçu occupe la place libre a gauche : elle explique POURQUOI il y a des
   boutons, juste a cote d'eux, et pousse ceux-ci a droite sans marge calculee. */
.contours-astuce {
  flex: 1;
  min-width: 0;
  font-size: 11.5px;
  line-height: 1.35;
  color: var(--text-faint, #6b7280);
}

/* Memes mesures que .lfg-actions dans style.css : c'est deja la paire « Annuler / valider »
   de cette application, autant que les deux fenetres se ressemblent. */
.contours-actions button {
  border: none;
  border-radius: 6px;
  padding: 8px 16px;
  font-size: 14px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  flex-shrink: 0;
}
.ct-annuler { background: transparent; color: var(--text-muted, #949ba4); }
.ct-annuler:hover { color: var(--text-strong, #fff); background: rgba(255,255,255,0.08); }
.ct-ok { background: var(--accent, #5865f2); color: #fff; }
.ct-ok:hover { background: var(--accent-hover, #4752c4); }

/* Le pied de la case a cocher perd sa bordure : c'est la barre des boutons qui ferme
   desormais la fenetre, deux traits colles l'un sous l'autre feraient doublon. */
.contours-pied { border-top: none; padding-bottom: 4px; }


/* ══════════════════════════════════════════════════════════════════════════════════════
   LES PLANCHES                                                          (3.0.30.544)

   Eric : « on ne peut pas avoir des trucs plus classe comme sur Steam ? »

   ══ CE QUE FAIT STEAM, VERIFIE ET NON SUPPOSE ═══════════════════════════════════════
   Leurs cadres ne sont pas vectoriels : ce sont des images matricielles animees,
   dessinees image par image (leurs avatars animes sont des GIF de 184x184, 2 Mo maxi).
   L'ecart de rendu ne vient donc pas d'une limite du CSS, il vient de ce qu'il manquait
   ici : des IMAGES. Un degrade ne fait pas une flamme qui vacille ni des etincelles qui
   partent chacune dans sa direction.

   ══ POURQUOI PAS UN GIF, PUISQUE C'EST CE QUE FAIT STEAM ════════════════════════════
   Parce qu'une image animee se REPEINT. Trente membres dans la liste, c'est trente zones
   a repeindre a chaque image de la boucle, plus le transfert vers la carte graphique --
   et le reglage « Figer les contours » cesserait de fonctionner, puisqu'on ne met pas un
   GIF en pause. Steam a d'ailleurs fini par ajouter exactement le meme interrupteur, ses
   joueurs se plaignant des cadres animes dans la liste d'amis.

   ══ LA PLANCHE : LES IMAGES, SANS LE REPEINT ════════════════════════════════════════
   Les 30 images sont collees bout a bout dans UN seul PNG. On les fait defiler en
   DEPLACANT cette bande derriere une fenetre decoupee :

       .ct-film      la fenetre, en overflow:hidden
       .ct-film > i  la bande, 30 fois plus large, translatee par pas de 1/30

   On n'anime donc que « transform », comme partout ailleurs dans ce fichier : la bande
   est une texture unique, deja prete, que la carte graphique fait glisser. Aucun
   repeint, jamais. Et « animation: none » fige la bande sur son image courante, donc la
   case « Figer les contours » marche exactement comme pour les anneaux.

   Un bonus qui n'en est pas un : la texture est partagee. Trente membres portant le meme
   contour, c'est UN telechargement, UN decodage, UNE texture -- pas trente.

   ══ LE CALCUL DU DEFILEMENT ═════════════════════════════════════════════════════════
   La bande fait N fois la largeur de la fenetre. translateX(-100%) la deplace donc de N
   fenetres. Avec steps(N), l'animation s'arrete aux positions k/N pour k = 0..N-1, soit
   un decalage de -k fenetre : l'image k est pile dans la fenetre. Rien a calculer par
   contour, le meme keyframe sert pour toutes les planches.

   ══ GEOMETRIE : 142%, ET CE N'EST PAS UN CHIFFRE ROND PAR HASARD ════════════════════
   La fenetre doit valoir le meme multiple du diametre de l'avatar PARTOUT, sinon une
   planche unique ne pourrait pas servir a la fois la liste (34px), la carte de profil
   (64px) et les apercus. Le multiple est donc borne par le contexte le plus serre :

       carte de profil : .up-body a 14px de marge et un overflow:hidden. Pour un avatar
                         de 64px, l'effet ne peut deborder que de 14px, soit 21,9% du
                         diametre  ->  fenetre <= 143,8%
       liste           : demi-ligne 27 - rayon 17 = 10px de marge, soit 29%

   142% passe partout, avec 0,6px de reserve sur la carte. C'est le contexte le plus
   contraint qui a fixe la valeur, pas une preference.

   ══ CE QUE CA COUTE VRAIMENT ════════════════════════════════════════════════════════
   Une planche de 30 images en 128x128 pese 110 a 480 Ko selon l'effet. Elle n'est
   telechargee que si QUELQU'UN porte ce contour : le CSS ne charge pas une image dont
   aucun element ne se sert. Une personne avec « Nebuleuse » dans le salon, c'est 480 Ko
   une fois, puis le cache.

   C'est la raison pour laquelle cette famille reste PETITE et le restera. Cinq planches,
   c'est 1,3 Mo dans le pire des cas. Trente, ce serait indefendable -- et ca ne servirait
   a rien, puisque les 33 contours vectoriels couvrent deja tout ce qui se compose avec
   des degrades. Une planche ne se justifie que pour ce qu'un degrade ne sait PAS faire.

   ══ LE REPLI ════════════════════════════════════════════════════════════════════════
   Chaque contour a planche garde un ::before et un ::after classiques, aux memes
   couleurs, mais IMMOBILES. Ils servent de socle sous la planche, et de repli si le PNG
   n'est pas deploye : dans ce cas il reste un anneau correct au lieu d'un avatar nu.
   C'est aussi ce qui s'affiche dans les contextes figes (avatars des messages, rail des
   MP), ou l'on n'injecte pas de planche du tout -- voir garnirContours dans app.js.
   ══════════════════════════════════════════════════════════════════════════════════════ */

.ct-film {
  position: absolute;
  inset: -21%;              /* fenetre = 142% du diametre de l'avatar */
  overflow: hidden;
  border-radius: 50%;
  pointer-events: none;
  z-index: 1;               /* au-dessus du socle ::before / ::after */
}

.ct-film > i {
  position: absolute;
  left: 0;
  top: 0;
  height: 100%;
  width: calc(100% * var(--ct-img, 30));
  background-image: var(--ct-src, none);
  background-size: 100% 100%;
  background-repeat: no-repeat;
  animation: ct-defile var(--ct-film-vit, 2s) steps(var(--ct-img, 30)) infinite;
  /* Meme phase que tout le reste : une planche recreee par un evenement « presence »
     reprend a l'image ou en etait la precedente, au lieu de resauter a la premiere.
     Sans ca, toutes les flammes de la liste repartiraient de zero des que quelqu'un
     change de statut -- la saccade du .513, en pire parce qu'elle serait visible. */
  animation-delay: var(--ct-phase, 0s);
  will-change: transform;
}

@keyframes ct-defile { from { transform: translateX(0); } to { transform: translateX(-100%); } }

/* Les memes trois interrupteurs que pour les anneaux. « animation: none » laisse la
   planche sur son image courante : le contour reste affiche, il cesse de defiler. */
.ct-fige .ct-film > i,
body.contours-figes .ct-film > i {
  animation: none !important;
  will-change: auto !important;
}
@media (prefers-reduced-motion: reduce) {
  .ct-film > i { animation: none !important; will-change: auto !important; }
}

/* ── Étincelles ── escarbilles de forge : chacune part avec sa direction, sa vitesse et
      sa duree de vie. Aucun degrade ne produit ca. ── */
.ct-etincelles { --ct-src: url("contours/ct-etincelles.png?v=1"); --ct-img: 30; --ct-film-vit: 2.1s; }
.ct-etincelles::before { background: radial-gradient(closest-side, rgba(255,120,0,0) 64%, rgba(255,120,0,0.34) 88%, rgba(255,120,0,0) 100%); }
.ct-etincelles::after  { animation: none; background: conic-gradient(#c2410c, #f97316 50%, #c2410c 100%); }

/* ── Brasier ── une vraie flamme : la hauteur du feu est une somme d'ondes qui tournent
      a des vitesses sans rapport entre elles, le motif ne se repete jamais a l'oeil. ── */
.ct-brasier { --ct-src: url("contours/ct-flamme.png?v=1"); --ct-img: 30; --ct-film-vit: 1.7s; }
.ct-brasier::before { background: radial-gradient(closest-side, rgba(255,88,0,0) 62%, rgba(255,88,0,0.40) 88%, rgba(255,88,0,0) 100%); }
.ct-brasier::after  { animation: none; background: conic-gradient(#7c2d12, #ea580c 50%, #7c2d12 100%); }

/* ── Foudre ── des eclairs qui CLAQUENT : deux ou trois images, pas un fondu. C'est
      l'irregularite qui fait l'effet, et elle n'a pas d'equivalent en CSS. ── */
.ct-foudre { --ct-src: url("contours/ct-foudre.png?v=1"); --ct-img: 30; --ct-film-vit: 2.4s; }
.ct-foudre::before { background: radial-gradient(closest-side, rgba(99,102,241,0) 64%, rgba(99,102,241,0.38) 88%, rgba(99,102,241,0) 100%); }
.ct-foudre::after  { animation: none; background: conic-gradient(#312e81, #6366f1 50%, #312e81 100%); }

/* ── Nébuleuse ── trois nappes de couleur qui tournent en sens contraires, plus des
      etoiles qui scintillent. Ni debut ni fin : un degrade conique a toujours une
      couture quelque part, pas celle-ci. ── */
.ct-nebuleuse { --ct-src: url("contours/ct-nebuleuse.png?v=1"); --ct-img: 30; --ct-film-vit: 3.6s; }
.ct-nebuleuse::before { background: radial-gradient(closest-side, rgba(168,85,247,0) 62%, rgba(168,85,247,0.36) 88%, rgba(168,85,247,0) 100%); }
.ct-nebuleuse::after  { animation: none; background: conic-gradient(#4c1d95, #a855f7 33%, #22d3ee 66%, #4c1d95 100%); }

/* ── Météores ── trois corps en orbite qui GROSSISSENT en passant devant et s'effacent
      derriere. C'est cette profondeur que la rotation plate ne peut pas donner. ── */
.ct-meteores { --ct-src: url("contours/ct-meteores.png?v=1"); --ct-img: 30; --ct-film-vit: 2.8s; }
.ct-meteores::before { background: radial-gradient(closest-side, rgba(56,189,248,0) 66%, rgba(56,189,248,0.32) 88%, rgba(56,189,248,0) 100%); }
.ct-meteores::after  { animation: none; background: conic-gradient(#1e3a8a, #38bdf8 40%, #fbbf24 70%, #1e3a8a 100%); }

/* Note de famille : une seule famille en a une pour l'instant (« Spectaculaires »), parce
   qu'elle se comporte differemment des autres -- ses contours ne s'affichent pas sur les
   avatars des messages. Une regle qui surprend doit etre ecrite la ou on choisit, pas
   dans un fichier que personne n'ouvrira. */
.contours-groupe .cg-note {
  display: block;
  margin-top: 3px;
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.4;
  color: var(--text-faint, #6b7280);
}
