/* ============================================================
   Service-Overtchat — site public
   Vitrine du projet. Palette partagée avec le panel, mais mise
   en page aérée : on présente, on n'exploite pas.
   ============================================================ */

:root {
  --bg:        #0b0f14;
  --bg-soft:   #0f151d;
  --surface:   #131b24;
  --surface-2: #1a242f;
  --border:    #24303d;
  --border-hi: #33445a;

  --text:  #dce6f2;
  --muted: #93a2b6;
  --dim:   #64748b;

  --accent:    #35d0c0;
  --accent-dk: #1b7a72;
  --blue:      #6ea8fe;
  --purple:    #a984ff;
  --ok:        #3fd07a;
  --warn:      #e8b93f;
  --err:       #f2626b;

  --mono: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --wrap: 1120px;
}

* { box-sizing: border-box; }

/*
 * `hidden` doit cacher — toujours.
 *
 * La règle du navigateur, `[hidden] { display: none }`, vient de sa feuille
 * de style par défaut : n'importe quelle règle d'auteur portant un `display`
 * l'emporte sur elle. Toute classe qui pose `display: flex` ou `display: grid`
 * rend donc l'attribut inopérant, en silence, et l'élément reste à l'écran.
 *
 * Le piège s'est refermé deux fois ici : sur `.bandeau-alerte` (le « Service
 * perturbé » fantôme de la page d'accueil, corrigé au cas par cas plus bas)
 * puis sur `.chat-zone`, dont la grille à trois colonnes restait affichée
 * sous le formulaire d'entrée — colonnes vides et fil sans message, alors que
 * le balisage la déclarait `hidden`.
 *
 * Une fois suffit : la règle est posée ici pour tout le site, et le prochain
 * `display` sur un élément masquable n'aura pas à s'en souvenir.
 */
[hidden] { display: none !important; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.7;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 24px; }
.narrow { max-width: 860px; }

/* ── En-tête ───────────────────────────────────────────── */

.site-head {
  position: sticky; top: 0; z-index: 40;
  background: rgba(11, 15, 20, .88);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}
.site-head .wrap {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; height: 62px;
}
/* `flex: none` + `nowrap` : comprimé par la nav (space-between), le bloc
   rétrécissait jusqu'à sa largeur minimale et le nom se repliait à la césure
   du tiret — « Service- / Overtchat » sur deux lignes. Le logo ne cède pas ;
   c'est à la nav de s'adapter (ses liens se replient sous 860px). */
.logo { display: flex; align-items: center; gap: 11px; flex: none; }
.logo:hover { text-decoration: none; }
.logo-mark {
  width: 34px; height: 34px; border-radius: 9px;
  background: linear-gradient(135deg, var(--accent), var(--accent-dk));
  display: grid; place-items: center;
  color: #06231f; font-weight: 800; font-size: 16px;
  box-shadow: 0 0 20px rgba(53, 208, 192, .3);
}
/* Le `line-height: 1.7` du body ferait du nom + tagline un bloc de ~43px :
   la pastille de 34px, centrée dessus, paraît flotter sous le nom. Un
   interligne serré recale le tout — le footer, qui réutilise .logo-txt sans
   tagline, y gagne le même aplomb. */
.logo-txt { font-weight: 680; color: var(--text); letter-spacing: .2px; line-height: 1.2; white-space: nowrap; }

/* ── Le nom de l'en-tête, frappé à la machine ───────────
 *
 * Demandé par SerVuS le 17/08/2026 après avoir vu l'accueil : « fais pareil à
 * chaque page ». L'en-tête étant sur toutes les pages, la frappe s'y rejoue à
 * chaque navigation — c'est l'effet voulu, et il est court.
 *
 * ⚠ En chasse fixe, pour la même raison qu'à l'accueil : un cran de frappe
 * vaut `1ch`, la largeur d'un caractère. En écriture proportionnelle, les
 * crans se décalent des lettres et le nom finit coupé ou trop large.
 *
 * ⚠ Plus RAPIDE qu'à l'accueil, et sans retard : ici le nom n'attend aucun
 * dessin, et l'en-tête est ce qu'on lit en premier en arrivant sur une page.
 * Une entrée d'une seconde à chaque clic deviendrait une attente.
 *
 * ⚠⚠ LA PLACE EST RÉSERVÉE PAR LE PARENT, JAMAIS PAR LE NOM LUI-MÊME.
 *
 * Sans réservation, le nom qui s'allonge pousse la navigation à chaque frappe
 * et onze entrées de menu sautent à droite pendant toute l'animation. La
 * première version la posait donc en `min-width` sur `.logo-nom` — et
 * **annulait la frappe** : une largeur animée ne descend jamais sous son
 * propre `min-width`, si bien que le nom était entier dès la première image.
 *
 * Mesuré dans le navigateur, pas deviné : la largeur restait à 140,188 px de
 * bout en bout, sept échantillons durant. Le rendu paraissait juste — le nom
 * s'affichait, le menu ne bougeait pas — et il ne s'y passait rien.
 *
 * Les deux besoins ne tiennent pas sur le même élément : le PARENT garde la
 * place, l'ENFANT se dévoile.
 */
.site-head .logo-txt { min-width: calc(var(--n, 17) * 1ch); }

.logo-nom {
  display: inline-block;
  font-family: var(--mono);
  font-weight: 700; letter-spacing: 0;
  width: calc(var(--n, 17) * 1ch);
  overflow: hidden; white-space: nowrap;
  vertical-align: bottom;
  border-right: 2px solid var(--accent);
  animation: logo-taper .62s both, logo-curseur .75s 1.1s 4;
}
@keyframes logo-taper   { from { width: 0; } }
/* ⚠ Le clignotement s'arrête après quatre passes : un curseur qui bat sans fin
   dans un en-tête tire l'œil de tout le reste de la page. */
@keyframes logo-curseur { 50% { border-right-color: transparent; } }

@media (prefers-reduced-motion: reduce) {
  .site-head .logo-txt { min-width: 0; }
  .logo-nom { width: auto; border-right: 0; animation: none; }
}
.logo-txt small {
  display: block; margin-top: 2px;
  font-size: 10.5px; line-height: 1.3;
  color: var(--dim); font-family: var(--mono); font-weight: 400;
  white-space: nowrap;
}

.site-nav { display: flex; align-items: center; gap: 4px; }
/* Onze entrées : à 14px/13px de padding la barre excède le conteneur de
   1120px et chaque lien se replie sur deux lignes. Compact et insécable —
   c'est la nav qui s'adapte, jamais les libellés qui se cassent. */
.site-nav a {
  padding: 7px 9px; border-radius: 8px;
  color: var(--muted); font-size: 13px; font-weight: 520;
  white-space: nowrap;
}
.site-nav a:hover { background: var(--surface); color: var(--text); text-decoration: none; }
.site-nav a.active { color: var(--accent); background: rgba(53, 208, 192, .1); }
.site-nav a.cta {
  background: linear-gradient(180deg, var(--accent), var(--accent-dk));
  color: #05201d; font-weight: 660; margin-left: 8px;
}
.site-nav a.cta:hover { filter: brightness(1.1); text-decoration: none; }

/* ── Menu replié ───────────────────────────────────────────
   Masqué par défaut : il ne prend la relève que lorsque la barre horizontale
   se retire, sous 1080px. Construit sur <details>, il fonctionne sans script.
*/
.nav-repli { display: none; position: relative; }

.nav-repli > summary {
  list-style: none;                 /* retire le triangle, Firefox   */
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text); font-size: 13px; font-weight: 560;
  cursor: pointer; user-select: none;
  /* 44px de haut : la cible tactile recommandée. En deçà, le bouton se
     manque une fois sur trois au pouce. */
  min-height: 44px; box-sizing: border-box;
}
.nav-repli > summary::-webkit-details-marker { display: none; }
.nav-repli > summary:hover { border-color: var(--accent); }
/* Le focus doit rester visible : c'est le seul repère de qui navigue au
   clavier, et le menu est ici l'unique accès aux pages. */
.nav-repli > summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* Trois barres dessinées par les bordures, sans image ni glyphe : le
   rendu ne dépend alors d'aucune police et reste net à toute densité. */
.nav-repli-barres {
  width: 16px; height: 2px; background: currentColor;
  position: relative; flex: none;
}
.nav-repli-barres::before,
.nav-repli-barres::after {
  content: ""; position: absolute; left: 0;
  width: 16px; height: 2px; background: currentColor;
}
.nav-repli-barres::before { top: -5px; }
.nav-repli-barres::after  { top: 5px; }

/* Ouvert, le bouton prend la couleur d'accent : l'état est lisible sans
   avoir à repérer si le panneau est déployé. */
.nav-repli[open] > summary { border-color: var(--accent); color: var(--accent); }

.nav-repli-panneau {
  position: absolute; right: 0; top: calc(100% + 8px);
  display: flex; flex-direction: column; gap: 2px;
  min-width: 210px;
  /* Sur un écran étroit, un panneau de largeur fixe déborderait à droite et
     deviendrait inatteignable : il ne dépasse jamais la fenêtre. */
  max-width: calc(100vw - 24px);
  padding: 7px;
  background: var(--bg-2, #0f151c);
  border: 1px solid var(--border); border-radius: 11px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, .45);
  z-index: 60;
  /* Plus haut que l'écran — onze entrées sur un téléphone couché —, le
     panneau défile au lieu de sortir du cadre. */
  max-height: calc(100vh - 84px); overflow-y: auto;
}
.nav-repli-panneau a {
  padding: 10px 12px; border-radius: 8px;
  color: var(--muted); font-size: 14px; font-weight: 520;
  white-space: nowrap;
}
.nav-repli-panneau a:hover { background: var(--surface); color: var(--text); text-decoration: none; }
.nav-repli-panneau a.active { color: var(--accent); background: rgba(53, 208, 192, .1); }
.nav-repli-panneau a.cta {
  margin-top: 5px; text-align: center;
  background: linear-gradient(180deg, var(--accent), var(--accent-dk));
  color: #05201d; font-weight: 660;
}

/* ── Groupe déroulant de la barre ──────────────────────────
   Bâti sur <details>, comme le menu replié : il s'ouvre au clavier, sans une
   ligne de script, et reste utilisable si le JavaScript est refusé.

   Il sert dans les DEUX navigations. Dans la barre horizontale, son panneau
   flotte au-dessus du contenu ; dans le menu replié, où tout est déjà une
   colonne, il se déplie sur place — un panneau flottant dans un panneau
   flottant serait sorti de l'écran par la droite. */

.nav-groupe { position: relative; }

.nav-groupe > summary {
  list-style: none;                 /* retire le triangle, Firefox */
  cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 5px;
  padding: 7px 9px; border-radius: 8px;
  color: var(--muted); font-size: 13px; font-weight: 520;
  white-space: nowrap;
}
.nav-groupe > summary::-webkit-details-marker { display: none; }
.nav-groupe > summary::after {
  content: "▾"; font-size: 10px; opacity: .7;
  transition: transform .15s ease;
}
.nav-groupe[open] > summary::after { transform: rotate(180deg); }
.nav-groupe > summary:hover { background: var(--surface); color: var(--text); }
.nav-groupe.active > summary { color: var(--accent); background: rgba(53, 208, 192, .1); }

.nav-groupe-panneau {
  position: absolute; left: 0; top: calc(100% + 8px); z-index: 40;
  display: flex; flex-direction: column; gap: 2px;
  min-width: 210px; max-width: calc(100vw - 24px);
  padding: 7px;
  background: var(--bg-2, #0f151c);
  border: 1px solid var(--border); border-radius: 11px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, .45);
}
.nav-groupe-panneau a {
  padding: 9px 12px; border-radius: 8px;
  color: var(--muted); font-size: 13.5px; font-weight: 520;
  white-space: nowrap;
}
.nav-groupe-panneau a:hover { background: var(--surface); color: var(--text); text-decoration: none; }
.nav-groupe-panneau a.active { color: var(--accent); background: rgba(53, 208, 192, .1); }

/* Dans le menu replié : plus de flottement, on déplie en place. */
.nav-repli-panneau .nav-groupe > summary { font-size: 14px; padding: 10px 12px; }
.nav-repli-panneau .nav-groupe-panneau {
  position: static; box-shadow: none; background: none;
  border: 0; border-left: 1px solid var(--border);
  border-radius: 0; margin: 2px 0 2px 12px; padding: 2px 0 2px 6px;
  min-width: 0;
}

/* ── Chat : règles, avertissement, appel à un opérateur ────
   Trois cadres dans la colonne de gauche, sous les salons. Repliés par
   défaut : un visiteur vient discuter, pas lire un règlement — mais ils sont
   là, visibles, plutôt que dans une page qu'il faudrait penser à ouvrir.

   `margin-top:auto` pousse le bloc en bas de la colonne : la liste des salons
   garde sa place naturelle en haut, et les cadres ne la repoussent pas quand
   un salon s'ajoute. */

/* ⚠ L'appel vient AVANT les deux dépliants, et non en bas de colonne.
   Mesuré au navigateur en 1280×900 : poussé en pied par `margin-top:auto`,
   le bouton se retrouvait à y=1050 — hors écran. Il fallait faire défiler
   pour demander de l'aide. C'est la seule commande de cette page dont on a
   besoin dans l'urgence ; elle ne peut pas être celle qu'on cherche.
   Les règles, elles, se lisent à froid : leur place est en dessous. */
.chat-regles { margin-top: 12px; display: grid; gap: 6px; }
.chat-regles .chat-bloc-appel { order: -1; }

.chat-bloc {
  border: 1px solid var(--border); border-radius: 8px;
  background: var(--bg, #0b0f14);
}
.chat-bloc > summary {
  list-style: none; cursor: pointer; user-select: none;
  padding: 7px 9px; font-size: 11.5px; font-weight: 600;
  color: var(--muted); display: flex; align-items: center; gap: 5px;
}
.chat-bloc > summary::-webkit-details-marker { display: none; }
.chat-bloc > summary::after { content: "▾"; margin-left: auto; font-size: 9px; opacity: .65; }
.chat-bloc[open] > summary::after { content: "▴"; }
.chat-bloc > summary:hover { color: var(--text); }
.chat-bloc-warn > summary { color: var(--warn); }

/* Borné en hauteur et défilant : déplié, le règlement complet chasserait la
   liste des salons hors de l'écran. */
.chat-bloc-corps {
  max-height: 34vh; overflow-y: auto;
  padding: 2px 9px 9px; font-size: 11.5px; line-height: 1.5;
  color: var(--muted);
}
.chat-bloc-corps p { margin: 0 0 7px; }
.chat-bloc-corps ul { margin: 0 0 9px; padding-left: 14px; }
.chat-bloc-corps li { margin-bottom: 5px; }
.chat-bloc-intro { color: var(--dim); text-transform: uppercase;
                   font-size: 10px; letter-spacing: .05em; margin-bottom: 5px; }
.chat-bloc-sanction { display: block; font-size: 10px; opacity: .85; }
.chat-bloc-sanction.err  { color: var(--err); }
.chat-bloc-sanction.warn { color: var(--warn); }
.chat-bloc-note { color: var(--dim); font-size: 10.5px; margin: 6px 0 0; }
.chat-bloc-lien { margin: 0; font-size: 11px; }

.chat-bloc-appel { padding: 9px; display: grid; gap: 7px; }
.chat-bloc-appel .btn { width: 100%; justify-content: center; }
#chat-appel-motif {
  width: 100%; padding: 6px 8px; font-size: 11.5px;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
}
.chat-appel-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin-top: 6px; }
.chat-bloc-appel .chat-bloc-note:empty { display: none; }
.chat-bloc-note.ok  { color: var(--ok); }
.chat-bloc-note.err { color: var(--err); }

/* En dessous de 860 px la grille du chat s'empile : la colonne de gauche
   devient une bande horizontale, et ces cadres y prendraient toute la page
   avant même le premier message. On les garde, sans le `margin-top:auto`
   qui n'a plus de sens hors colonne, et on resserre. */
@media (max-width: 860px) {
  .chat-regles { margin-top: 10px; padding-top: 0; }
  .chat-bloc-corps { max-height: 40vh; }
}

/* ── Schémas ───────────────────────────────────────────────
   Un schéma qui rétrécit devient un schéma qu'on ne lit pas.

   Mesuré sur la page Guide en 375 px : le SVG s'affichait sur 273 px pour un
   viewBox de 720, soit une échelle de 0,38 — un texte déclaré à 12 arrivait à
   4,5 px à l'écran. La page ne débordait pas, aucune mesure de largeur ne
   signalait quoi que ce soit, et les trois schémas étaient illisibles.

   On garde donc une largeur MINIMALE et on laisse le cadre défiler, comme le
   fait déjà .tbl-wrap pour les tableaux. Défiler pour lire vaut mieux que
   voir sans lire. */

.schema { overflow-x: auto; }
.schema svg { display: block; width: 100%; min-width: 660px; height: auto; }

/* ── Le logo qui s'écrit, en tête de l'accueil ──────────
 *
 * ⚠⚠ IL N'Y A PAS D'ÉCRAN DE CHARGEMENT, ET C'EST LE POINT. Rien ici ne
 * masque la page, rien n'ajoute d'attente : le titre et les boutons sont
 * présents dès la première image, l'animation ne fait que les accompagner.
 * Un voile avec une barre de 0 à 100 % aurait fabriqué le délai qu'il
 * prétend couvrir — cette page est servie en quelques centaines de ms.
 *
 * L'ordre raconte quelque chose : la bulle s'écrit d'abord (la conversation),
 * puis les liens se tracent (le réseau se monte), puis les nœuds se posent
 * (les serveurs arrivent), puis le nom. Une seconde en tout.
 *
 * ⚠ `will-change` n'est PAS employé : sur trois petites formes il coûterait
 * une couche de composition par élément pour économiser des microsecondes.
 */

.logo-anim {
  display: flex; align-items: center; justify-content: center;
  gap: 20px; margin: 0 0 30px;
}
.logo-anim svg {
  width: 104px; height: 104px; flex: none;
  overflow: visible;   /* le halo du nœud déborde du viewBox */
}

.la-bulle {
  fill: none; stroke: url(#la-grad); stroke-width: 6;
  stroke-linecap: round; stroke-linejoin: round;
  /* ⚠ En FRACTIONS du tracé, grâce au `pathLength="1"` posé sur le path : 1
     est le tracé entier, quelle que soit sa géométrie. Aucune longueur à
     deviner, aucun JavaScript à charger pour la mesurer.
     Une valeur devinée « large » avait été essayée : 460 pour un tracé qui en
     mesure 306. Le trait finissait de s'écrire au tiers de l'animation. */
  stroke-dasharray: 1; stroke-dashoffset: 1;
  animation: la-ecrire .72s cubic-bezier(.6, .05, .3, 1) forwards;
}
.la-lien {
  fill: none; stroke: var(--accent); stroke-width: 4;
  stroke-linecap: round; stroke-linejoin: round; opacity: .55;
  stroke-dasharray: 1; stroke-dashoffset: 1;   /* idem — fractions du tracé */
  animation: la-ecrire .34s ease-out .60s forwards;
}
.la-noeud {
  fill: var(--accent);
  transform-box: fill-box; transform-origin: center;
  transform: scale(0); opacity: 0;
  animation: la-poser .38s cubic-bezier(.34, 1.56, .64, 1) forwards;
}
.la-noeud.n1 { animation-delay: .78s; }
.la-noeud.n2 { animation-delay: .90s; }
.la-noeud.n3 { animation-delay: 1.02s; }

.la-nom { text-align: left; opacity: 0; animation: la-venir .5s ease-out .46s forwards; }

/* ── Le nom, frappé à la machine ────────────────────────
 *
 * Demandé par SerVuS le 17/08/2026 après avoir vu la première version.
 *
 * ⚠⚠ EN CHASSE FIXE, ET CE N'EST PAS UN CHOIX D'ORNEMENT. La frappe se fait
 * en découvrant le texte d'un cran par caractère (`steps()`), et un cran vaut
 * `1ch` — la largeur d'un caractère. Dans une police proportionnelle, un « i »
 * et un « O » n'ont pas la même largeur : les crans se décalent du texte, et
 * la dernière lettre apparaît trop tôt ou reste coupée. La chasse fixe fait
 * coïncider exactement le cran et la lettre.
 *
 * Elle est en plus juste au fond : une machine à écrire EST à chasse fixe.
 *
 * ⚠ `--n` et le `steps()` viennent d'`accueil.php`, calculés sur `APP_NAME` :
 * une feuille de style ne sait pas compter les lettres d'un nom qui peut
 * changer.
 */
.la-nom strong {
  display: block;
  font-family: var(--mono);
  font-size: 30px; font-weight: 700; letter-spacing: 0;

  /* La largeur est la mesure du texte, pas celle du conteneur : animer vers
     `100%` s'étirerait jusqu'au bord de la page et la frappe finirait dans le
     vide. */
  width: calc(var(--n, 17) * 1ch);
  max-width: 100%;
  overflow: hidden; white-space: nowrap;

  /* Le curseur. Il vit sur la bordure droite, donc il avance AVEC le texte
     découvert — c'est ce qui donne la sensation de frappe plutôt que de
     dévoilement. */
  border-right: 3px solid var(--accent);

  background: linear-gradient(100deg, var(--accent), var(--blue) 70%);
  -webkit-background-clip: text; background-clip: text; color: transparent;

  /* Deux animations : la frappe, puis le clignotement du curseur. La première
     est retardée pour laisser le dessin s'écrire d'abord. */
  animation: la-taper 1.15s .95s both, la-curseur .75s 2.1s infinite;
}
.la-nom span {
  display: block; margin-top: 6px;
  font-family: var(--mono); font-size: 12px; color: var(--dim);
}

@keyframes la-ecrire { to { stroke-dashoffset: 0; } }
@keyframes la-poser  { to { transform: scale(1); opacity: 1; } }
@keyframes la-venir  { from { transform: translateY(7px); } to { opacity: 1; transform: none; } }
@keyframes la-taper  { from { width: 0; } }
/* ⚠ La bordure DISPARAÎT, elle ne change pas de couleur pour le fond : le fond
   du hero porte un dégradé, et une bordure peinte de sa couleur s'y verrait. */
@keyframes la-curseur { 50% { border-right-color: transparent; } }

/* ⚠ Qui a demandé moins d'animations voit le logo ACHEVÉ, pas rien : on
   retire le mouvement, jamais le contenu. */
@media (prefers-reduced-motion: reduce) {
  .la-bulle, .la-lien { stroke-dashoffset: 0; animation: none; }
  .la-noeud { transform: none; opacity: 1; animation: none; }
  .la-nom   { opacity: 1; animation: none; }
  /* ⚠ Le nom entier, et SANS curseur : un trait qui clignote indéfiniment est
     précisément ce que ce réglage demande d'éviter. */
  .la-nom strong { width: auto; border-right: 0; animation: none; }
}

/* Sur mobile le logo passe au-dessus du nom : côte à côte, les 74 px de
   dessin volent la largeur au nom du réseau, qui se coupe. */
@media (max-width: 560px) {
  .logo-anim { flex-direction: column; gap: 12px; }
  .la-nom { text-align: center; }
  /* ⚠ 30px en chasse fixe sur 17 caractères réclament ~510 px : le nom
     déborderait de l'écran. La frappe reste, la taille suit la largeur. */
  .la-nom strong { font-size: 21px; margin: 0 auto; }
  .logo-anim svg { width: 84px; height: 84px; }
}

/* ── Hero ──────────────────────────────────────────────── */

.hero {
  padding: 86px 0 70px;
  background:
    radial-gradient(900px 380px at 50% -140px, rgba(53, 208, 192, .13), transparent 70%),
    radial-gradient(700px 320px at 85% 40px, rgba(169, 132, 255, .08), transparent 70%);
  border-bottom: 1px solid var(--border);
  text-align: center;
}
.hero h1 {
  margin: 0 0 16px;
  font-size: clamp(30px, 5vw, 47px);
  line-height: 1.16; letter-spacing: -1px; font-weight: 700;
}
.hero h1 .grad {
  background: linear-gradient(100deg, var(--accent), var(--blue) 55%, var(--purple));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero p.lead {
  margin: 0 auto 30px; max-width: 660px;
  font-size: 17px; color: var(--muted);
}
.hero-actions { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }

.eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 13px; margin-bottom: 22px;
  border: 1px solid var(--border-hi); border-radius: 999px;
  background: var(--surface);
  font-size: 12px; font-family: var(--mono); color: var(--muted);
}
.eyebrow .dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ok); box-shadow: 0 0 9px var(--ok);
}

/* ── Boutons ───────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 11px 22px; border-radius: 9px;
  border: 1px solid var(--border-hi); background: var(--surface-2);
  color: var(--text); font-size: 14.5px; font-weight: 580;
  cursor: pointer; font-family: inherit;
  transition: background .14s, border-color .14s, transform .06s;
}
.btn:hover { background: #22303e; border-color: #435874; text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn.primary {
  background: linear-gradient(180deg, var(--accent), var(--accent-dk));
  border-color: var(--accent-dk); color: #05201d; font-weight: 670;
}
.btn.primary:hover { filter: brightness(1.09); }
.btn.sm { padding: 7px 14px; font-size: 13px; }
/* ⚠ Le webchat écrit « btn sm ghost » à trois endroits — les boutons Annuler
   et Réglages. La règle n'existait QUE dans app.css, la feuille du panel : le
   bouton se rendait, avec le fond plein d'un bouton ordinaire. Aucune erreur,
   nulle part. Mesuré le 10/08/2026. */
.btn.ghost { background: transparent; }

/* ── Sections ──────────────────────────────────────────── */

section { padding: 66px 0; }
section + section { border-top: 1px solid var(--border); }

.sec-head { text-align: center; margin-bottom: 42px; }
.sec-head.left { text-align: left; }
.sec-tag {
  font-size: 11.5px; font-weight: 660; letter-spacing: .12em;
  text-transform: uppercase; color: var(--accent); margin-bottom: 9px;
}
.sec-head h2 { margin: 0 0 10px; font-size: 28px; letter-spacing: -.5px; font-weight: 660; }
.sec-head p { margin: 0 auto; max-width: 640px; color: var(--muted); font-size: 15.5px; }
.sec-head.left p { margin-left: 0; }

h3 { font-size: 19px; margin: 34px 0 12px; font-weight: 640; letter-spacing: -.2px; }
h4 { font-size: 15.5px; margin: 24px 0 8px; font-weight: 620; color: var(--text); }

/* ── Grilles et cartes ─────────────────────────────────── */

.grid { display: grid; gap: 20px; }
.g2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.g3 { grid-template-columns: repeat(auto-fit, minmax(275px, 1fr)); }
.g4 { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 13px;
  padding: 24px;
  transition: border-color .15s, transform .15s;
}
.card.hover:hover { border-color: var(--border-hi); transform: translateY(-2px); }
/* ⚠ La carte d'avertissement de « Serveurs compatibles » — le cas d'ircu. Elle
   doit se distinguer des deux autres du même bloc ; sans cette règle, elle leur
   était identique et l'avertissement ne se voyait plus. La couleur vient de la
   variable du site, pas de la valeur en dur qu'app.css emploie. */
.card.bord-warn { border-color: var(--warn); }
.card h3 { margin: 0 0 8px; font-size: 17px; }
.card p { margin: 0; color: var(--muted); font-size: 14px; }
.card .ico {
  width: 40px; height: 40px; border-radius: 10px;
  background: var(--surface-2); border: 1px solid var(--border);
  display: grid; place-items: center; font-size: 18px;
  margin-bottom: 15px; color: var(--accent);
}

.stat-card { text-align: center; padding: 22px 16px; }
.stat-card .n {
  font-size: 30px; font-weight: 700; font-family: var(--mono);
  letter-spacing: -1px; color: var(--accent);
}
.stat-card .l { font-size: 12.5px; color: var(--dim); margin-top: 3px; }

/* ── Tableaux ──────────────────────────────────────────── */

.tbl-wrap {
  overflow-x: auto; border: 1px solid var(--border);
  border-radius: 11px; background: var(--surface);
}
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
thead th {
  text-align: left; padding: 11px 15px;
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--dim); font-weight: 650;
  background: var(--bg-soft); border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
tbody td { padding: 10px 15px; border-bottom: 1px solid rgba(36, 48, 61, .55); vertical-align: top; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: rgba(26, 36, 47, .5); }

/* Ligne de séparation à l'intérieur d'un tableau — annonce un sous-groupe
   sans rompre l'alignement des colonnes, contrairement à une suite de
   tableaux distincts. Le survol ne la teinte pas : ce n'est pas une donnée,
   c'est un intertitre. */
.tr-sep td {
  background: var(--bg-soft);
  border-top: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  padding: 7px 15px;
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  font-weight: 650; color: var(--accent);
}
.tr-sep td .dim { font-weight: 400; letter-spacing: 0; text-transform: none; }
.tr-sep td .dim::before { content: '· '; }
tbody tr.tr-sep:hover { background: none; }
tbody tr.tr-sep:first-child td { border-top: 0; }
td.mono, .mono { font-family: var(--mono); font-size: 12.5px; }
/* ⚠ Les colonnes de comptes de la page « Commandes ». La règle n'existait que
   dans app.css : les nombres s'alignaient à GAUCHE, sous un en-tête lui-même à
   gauche, et deux colonnes de chiffres ne se comparaient plus d'un coup d'œil.
   L'en-tête compte aussi — la page pose la classe sur `th` comme sur `td`. */
th.num, td.num { text-align: right; font-family: var(--mono); }
/* ⚠ Les HUIT séparateurs de la page privée d'un salon. La règle n'existait que
   dans app.css, et `site.css` ne stylait AUCUN `hr` : les huit traits
   s'affichaient au défaut du navigateur — un relief gris épais sur un fond
   sombre. C'est la page où un propriétaire règle son salon, donc la plus vue
   des sept trouvailles du 10/08/2026. La page pose sa propre marge en ligne ;
   ce qui manquait ici, c'est le filet. */
hr.sep { border: 0; border-top: 1px solid var(--border); margin: 16px 0; }
td.cmd { font-family: var(--mono); font-size: 12.5px; color: var(--accent); white-space: nowrap; }
.muted { color: var(--muted); }
.dim { color: var(--dim); }

/* ── Badges ────────────────────────────────────────────── */

.badge {
  display: inline-block; padding: 2px 9px; border-radius: 5px;
  font-size: 11px; font-weight: 620; font-family: var(--mono);
  border: 1px solid transparent; white-space: nowrap;
}
.badge.ok    { background: rgba(63,208,122,.12);  color: var(--ok);     border-color: rgba(63,208,122,.3); }
.badge.err   { background: rgba(242,98,107,.12);  color: var(--err);    border-color: rgba(242,98,107,.3); }
.badge.warn  { background: rgba(232,185,63,.12);  color: var(--warn);   border-color: rgba(232,185,63,.3); }
.badge.info  { background: rgba(110,168,254,.12); color: var(--blue);   border-color: rgba(110,168,254,.3); }
.badge.accent{ background: rgba(53,208,192,.12);  color: var(--accent); border-color: rgba(53,208,192,.3); }
.badge.pur   { background: rgba(169,132,255,.12); color: var(--purple); border-color: rgba(169,132,255,.3); }
.badge.off   { background: rgba(125,141,160,.1);  color: var(--muted);  border-color: rgba(125,141,160,.25); }

/* ── Code ──────────────────────────────────────────────── */

pre, .code {
  background: #070b10; border: 1px solid var(--border);
  border-radius: 10px; padding: 15px 18px;
  font-family: var(--mono); font-size: 12.8px; line-height: 1.66;
  overflow-x: auto; color: #a8bacd; margin: 14px 0;
}
pre .c { color: var(--dim); }          /* commentaire */
pre .k { color: var(--accent); }       /* commande    */
pre .s { color: var(--warn); }         /* chaîne      */
code {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 5px; padding: 1px 6px;
  font-family: var(--mono); font-size: 12.5px; color: var(--accent);
}
pre code { background: none; border: 0; padding: 0; color: inherit; }

/* ── Étapes numérotées ─────────────────────────────────── */

.steps { counter-reset: st; display: grid; gap: 16px; }
.step {
  position: relative; padding: 22px 24px 22px 66px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
}
.step::before {
  counter-increment: st; content: counter(st);
  position: absolute; left: 20px; top: 21px;
  width: 30px; height: 30px; border-radius: 50%;
  background: linear-gradient(135deg, var(--accent), var(--accent-dk));
  color: #05201d; font-weight: 700; font-size: 14px;
  display: grid; place-items: center; font-family: var(--mono);
}
.step h3 { margin: 0 0 6px; font-size: 16.5px; }
.step p { margin: 0 0 10px; color: var(--muted); font-size: 14px; }
.step p:last-child, .step pre:last-child { margin-bottom: 0; }
.step.locked { opacity: .72; }
.step.locked::before {
  background: var(--surface-2); color: var(--dim);
  border: 1px solid var(--border-hi);
}

/* ── Encadrés ──────────────────────────────────────────── */

.note {
  border-left: 3px solid var(--accent);
  background: rgba(53, 208, 192, .06);
  padding: 14px 18px; border-radius: 0 9px 9px 0; margin: 18px 0;
  font-size: 14px;
}
.note.warn { border-color: var(--warn); background: rgba(232, 185, 63, .06); }
.note.err  { border-color: var(--err);  background: rgba(242, 98, 107, .06); }
.note.info { border-color: var(--blue); background: rgba(110, 168, 254, .06); }
.note strong { color: var(--text); }
.note p { margin: 0; color: var(--muted); }
.note p + p { margin-top: 8px; }

/* ── Légende (états d'options) ─────────────────────────── */

.legend { display: grid; gap: 12px; }
.legend-row {
  display: flex; gap: 15px; align-items: flex-start;
  padding: 14px 17px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 10px;
}
.legend-sym {
  flex: 0 0 auto; font-family: var(--mono); font-size: 13px;
  padding: 3px 10px; border-radius: 6px; font-weight: 650;
  border: 1px solid var(--border-hi); background: var(--bg-soft);
  white-space: nowrap;
}
.legend-sym.on   { color: var(--ok);     border-color: rgba(63,208,122,.4); }
.legend-sym.todo { color: var(--warn);   border-color: rgba(232,185,63,.4); }
.legend-sym.off  { color: var(--dim); }
.legend-txt { font-size: 14px; }
.legend-txt strong { display: block; margin-bottom: 2px; }
.legend-txt span { color: var(--muted); }

/* ── Charte ────────────────────────────────────────────── */

.rule {
  display: flex; gap: 17px; padding: 20px 22px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 12px; margin-bottom: 15px;
}
.rule-n {
  flex: 0 0 34px; height: 34px; border-radius: 9px;
  background: var(--surface-2); border: 1px solid var(--border-hi);
  display: grid; place-items: center;
  font-family: var(--mono); font-weight: 700; font-size: 14px; color: var(--accent);
}
.rule-body h3 { margin: 0 0 6px; font-size: 16px; }
.rule-body p { margin: 0; color: var(--muted); font-size: 14px; }
.rule-body p + p { margin-top: 7px; }

/* ── Sommaire latéral ──────────────────────────────────── */

/*
 * `minmax(0, 1fr)` et non `1fr` — le même piège que sur `.chat-zone`.
 *
 * Une colonne `1fr` ne descend jamais sous la largeur MINIMALE de son
 * contenu : un tableau large la pousse, et c'est la page entière qui prend
 * une barre de défilement horizontale. Les `overflow-x: auto` posés sur les
 * enveloppes de tableau — .tbl-wrap, .cmp-wrap — n'y peuvent rien : on ne
 * fait défiler que ce qu'on a d'abord contraint.
 *
 * Constaté sur téléphone : à 375 px de large, la page des commandes en
 * réclamait 521, celle des serveurs compatibles 904. Le contenu ne débordait
 * pas de son cadre, c'est le cadre qui refusait de rétrécir.
 */
.with-toc { display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: 42px; align-items: start; }
.toc { position: sticky; top: 86px; }
.toc-title {
  font-size: 11px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--dim); font-weight: 660; margin-bottom: 10px;
}
.toc a {
  display: block; padding: 6px 11px; border-radius: 7px;
  color: var(--muted); font-size: 13.5px;
}
.toc a:hover { background: var(--surface); color: var(--text); text-decoration: none; }

/* ── Pied de page ──────────────────────────────────────── */

/* Pied de page — compact.
   Les rubriques restent, mais leurs liens se posent CÔTE À CÔTE au lieu de
   s'empiler en cinq colonnes : le bloc passe d'environ 340 px à 190 px, et
   n'oblige plus à faire défiler chaque page jusqu'en bas pour un lien. */
.site-foot {
  border-top: 1px solid var(--border);
  background: var(--bg-soft);
  padding: 24px 0 20px; margin-top: 20px;
}
.foot-haut {
  display: flex; align-items: center; gap: 10px 20px; flex-wrap: wrap;
}
.foot-haut .logo { margin: 0; }
/* flex-basis généreuse : sous cette largeur la phrase passe à la ligne
   entière plutôt que de se couper en colonne étroite à côté du logo. */
.foot-tag { margin: 0; color: var(--dim); font-size: 13px; flex: 1 1 300px; }

.foot-liens {
  display: flex; flex-wrap: wrap; gap: 9px 28px; margin-top: 16px;
}
/* Chaque rubrique est une ligne à elle seule tant qu'il y a la place, et se
   replie d'un bloc — jamais un lien orphelin sous le titre du voisin. */
.foot-groupe {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 12px;
}
.foot-groupe h4 {
  margin: 0; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .08em; color: var(--dim); font-weight: 660;
  flex: 0 0 auto;
}
.foot-groupe a { color: var(--muted); font-size: 13px; }
.foot-groupe a:hover { color: var(--accent); }

.foot-bottom {
  margin-top: 18px; padding-top: 13px; border-top: 1px solid var(--border);
  display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  color: var(--dim); font-size: 12.5px;
}

/* ── Divers ────────────────────────────────────────────── */

ul.clean { list-style: none; padding: 0; margin: 12px 0; }
ul.clean li {
  position: relative; padding-left: 24px; margin-bottom: 9px;
  color: var(--muted); font-size: 14.5px;
}
ul.clean li::before {
  content: "▸"; position: absolute; left: 4px; color: var(--accent);
}
ul.plain { padding-left: 22px; color: var(--muted); }
ul.plain li { margin-bottom: 7px; }

.anchor { scroll-margin-top: 84px; }

/* Bandeau d'interruption de service — accueil.
   Teinté d'avertissement et non d'erreur : le service est perturbé, pas mort,
   et la page reste consultable. Le liseré épais à gauche le distingue d'une
   carte ordinaire sans recourir à un aplat qui écraserait le reste. */
.bandeau-alerte {
  display: flex; align-items: flex-start; gap: 11px;
  max-width: 720px; margin: 0 auto 26px; padding: 12px 16px;
  border: 1px solid var(--warn); border-left-width: 3px;
  border-radius: 8px;
  background: rgba(232, 185, 63, .08);
  color: var(--text); font-size: 14.5px; line-height: 1.55;
  /* `.hero` centre son texte : sans cette remise à gauche, le message
     s'afficherait centré sous une pastille alignée à gauche, et chaque ligne
     serait décalée par rapport à la précédente. Une alerte se lit mal en
     drapeau centré. */
  text-align: left;
}
/* Le `display: flex` ci-dessus l'emporte sur la règle navigateur
   `[hidden] { display: none }` — un sélecteur de classe pèse plus lourd que
   la feuille UA. Sans la règle qui suit, le bandeau restait donc visible en
   permanence, attribut `hidden` ou pas : c'était le « Service perturbé »
   fantôme, que ni le cache ni le bfcache n'expliquaient. */
.bandeau-alerte[hidden] { display: none; }
.bandeau-alerte strong { color: var(--warn); }
.bandeau-alerte .pastille {
  flex: none; width: 9px; height: 9px; margin-top: 7px;
  border-radius: 50%; background: var(--warn);
}
.bandeau-alerte ul {
  margin: 6px 0 0; padding-left: 18px;
  font-size: 13.5px; color: var(--muted);
}
.bandeau-alerte li { margin-top: 2px; }

/* Réseau injoignable : ce n'est plus une gêne, c'est une porte fermée. */
.bandeau-alerte.err {
  border-color: var(--err);
  background: rgba(242, 98, 107, .08);
}
.bandeau-alerte.err strong    { color: var(--err); }
.bandeau-alerte.err .pastille { background: var(--err); }

/* ── Chat (bêta) ────────────────────────────────────────── */

.chat-etat { font-size: 13px; color: var(--muted); min-height: 18px; }
.chat-etat.ok  { color: var(--ok); }
.chat-etat.err { color: var(--err); }

/* ── Écran de chargement du chat ────────────────────────
 *
 * S'intercale entre le formulaire et le chat, le temps que la connexion, une
 * éventuelle identification et l'entrée dans le salon aboutissent. Un seul
 * balisage pour quatre traitements : `data-logo` décide de ce qui s'anime, ce
 * qui permet d'en changer sans recharger la page.
 */

.chat-chargement {
  display: flex; flex-direction: column; align-items: center; gap: 18px;
  max-width: 560px; margin: 0 auto; padding: 44px 24px 34px;
  border: 1px solid var(--border); border-radius: 14px;
  background: radial-gradient(120% 90% at 50% 0%, rgba(53,208,192,.07), transparent 60%),
              var(--bg-soft);
}

/* La scène est un carré fixe : les ondes et l'anneau s'y superposent au
   signe, centrés, sans déplacer le texte qui suit quand ils grandissent. */
.cc-scene {
  position: relative; width: 108px; height: 108px;
  display: flex; align-items: center; justify-content: center;
}
.cc-marque { width: 64px; height: 64px; position: relative; z-index: 2; }
.cc-fond   { fill: var(--accent); }
.cc-lettre {
  fill: #06231f; font-family: var(--mono); font-size: 34px; font-weight: 700;
}
.cc-anneau, .cc-onde { position: absolute; border-radius: 50%; opacity: 0; }
.cc-barres { position: absolute; display: none; gap: 5px; align-items: flex-end; height: 42px; }
.cc-barres i {
  display: block; width: 6px; border-radius: 3px;
  background: linear-gradient(180deg, var(--accent), var(--accent-dk));
}

.cc-nom {
  font-family: var(--mono); font-size: 16px; letter-spacing: .4px;
  color: var(--text); display: flex; align-items: center;
}
.cc-curseur { display: none; width: 8px; height: 17px; background: var(--accent); margin-left: 2px; }

.cc-etat { font-size: 13.5px; color: var(--muted); text-align: center; min-height: 19px; }
.cc-etat.ok  { color: var(--ok); }
.cc-etat.err { color: var(--err); }

/* Barre de progression indéterminée : elle dit « ça travaille », jamais
   « on en est à tant » — nous n'avons aucun moyen de le savoir, et une jauge
   qui progresserait au hasard serait un mensonge poli. */
.cc-piste {
  width: 190px; height: 3px; border-radius: 2px;
  background: var(--surface-2); overflow: hidden;
}
.cc-piste > i {
  display: block; width: 40%; height: 100%; border-radius: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  animation: cc-piste 1.5s ease-in-out infinite;
}
@keyframes cc-piste {
  0%   { transform: translateX(-110%); }
  100% { transform: translateX(360%); }
}

/* — Variante « orbite » : un point tourne autour du signe — */
.chat-chargement[data-logo="orbite"] .cc-anneau {
  opacity: 1; width: 100px; height: 100px;
  border: 1px dashed rgba(53,208,192,.28);
  animation: cc-tourne 9s linear infinite;
}
.chat-chargement[data-logo="orbite"] .cc-anneau::after {
  content: ''; position: absolute; top: -5px; left: 50%; margin-left: -5px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 14px rgba(53,208,192,.9);
}
@keyframes cc-tourne { to { transform: rotate(360deg); } }

/* — Variante « pulsation » : ondes concentriques, comme un ping — */
.chat-chargement[data-logo="pulse"] .cc-onde {
  width: 66px; height: 66px; border: 2px solid var(--accent);
  animation: cc-pulse 2.4s cubic-bezier(.2,.6,.35,1) infinite;
}
.chat-chargement[data-logo="pulse"] .cc-onde:nth-child(2) { animation-delay: .8s; }
.chat-chargement[data-logo="pulse"] .cc-onde:nth-child(3) { animation-delay: 1.6s; }
.chat-chargement[data-logo="pulse"] .cc-marque { animation: cc-respire 2.4s ease-in-out infinite; }
@keyframes cc-pulse {
  0%   { opacity: .75; transform: scale(1); }
  100% { opacity: 0;   transform: scale(2.3); }
}
@keyframes cc-respire {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.06); }
}

/* — Variante « onde » : des barres qui respirent, comme une conversation — */
.chat-chargement[data-logo="onde"] .cc-marque { width: 44px; height: 44px; margin-bottom: 46px; }
.chat-chargement[data-logo="onde"] .cc-barres { display: flex; bottom: 8px; }
.chat-chargement[data-logo="onde"] .cc-barres i { animation: cc-barre 1.15s ease-in-out infinite; }
.chat-chargement[data-logo="onde"] .cc-barres i:nth-child(1) { animation-delay: 0s;    }
.chat-chargement[data-logo="onde"] .cc-barres i:nth-child(2) { animation-delay: .13s;  }
.chat-chargement[data-logo="onde"] .cc-barres i:nth-child(3) { animation-delay: .26s;  }
.chat-chargement[data-logo="onde"] .cc-barres i:nth-child(4) { animation-delay: .39s;  }
.chat-chargement[data-logo="onde"] .cc-barres i:nth-child(5) { animation-delay: .52s;  }
@keyframes cc-barre {
  0%, 100% { height: 9px;  opacity: .45; }
  50%      { height: 40px; opacity: 1; }
}

/* — Variante « terminal » : le nom s'écrit, curseur clignotant — */
.chat-chargement[data-logo="terminal"] .cc-scene { width: 78px; height: 78px; }
.chat-chargement[data-logo="terminal"] .cc-marque { width: 52px; height: 52px; }
.chat-chargement[data-logo="terminal"] .cc-nom {
  border: 1px solid var(--border); border-radius: 8px;
  background: #070b10; padding: 9px 13px;
}
.chat-chargement[data-logo="terminal"] .cc-nom::before {
  content: '> '; color: var(--accent);
}
/* La largeur animée révèle le texte lettre à lettre. `ch` se cale sur la
   chasse de la fonte à espacement fixe : 17 caractères, 17 pas. */
.chat-chargement[data-logo="terminal"] .cc-nom-txt {
  display: inline-block; overflow: hidden; white-space: nowrap;
  vertical-align: bottom; width: 0;
  animation: cc-frappe 2.2s steps(17, end) infinite;
}
.chat-chargement[data-logo="terminal"] .cc-curseur {
  display: inline-block; animation: cc-clignote 1s step-end infinite;
}
@keyframes cc-frappe {
  0%       { width: 0; }
  60%, 100% { width: 17ch; }
}
@keyframes cc-clignote { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; } }

/* Boutons de choix des variantes, sous le cadre. */
.cc-choix { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/*
 * Mouvement réduit : on garde le signe et le message, on retire ce qui
 * bouge. Une animation en boucle peut déclencher un malaise chez qui a
 * demandé à son système de les limiter — et l'écran reste lisible sans.
 */
@media (prefers-reduced-motion: reduce) {
  .cc-anneau, .cc-onde, .cc-marque, .cc-barres i,
  .cc-piste > i, .cc-nom-txt, .cc-curseur { animation: none !important; }
  .cc-anneau { opacity: 1; }
  .chat-chargement[data-logo="terminal"] .cc-nom-txt { width: auto; }
  .chat-chargement[data-logo="onde"] .cc-barres i { height: 26px; }
}

/* Trois colonnes, comme un client IRC : salons | fil | présents. Le cadre
   occupe toute la largeur du .wrap — les colonnes latérales sont fixes, le
   fil prend le reste. Le minmax(0, 1fr) n'est pas décoratif : sans lui, une
   ligne insécable (URL, pseudo à rallonge) élargirait la colonne centrale au
   lieu de se couper. */
.chat-zone {
  display: grid; grid-template-columns: 180px minmax(0, 1fr) 210px;
  gap: 14px; align-items: stretch;
}

.chat-principal {
  display: flex; flex-direction: column; min-width: 0;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-soft); overflow: hidden;
}

/* Le sujet du salon actif, sur une ligne : tronqué à l'ellipse, le texte
   complet vit dans l'infobulle (title) posée par chat.js. */
.chat-sujet {
  min-height: 36px; padding: 8px 14px;
  border-bottom: 1px solid var(--border);
  font-family: var(--mono); font-size: 13px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Hauteur bornée, et non `flex:1` : le fil doit défiler DANS son cadre. Sans
   cela, c'est la page entière qui s'allonge à chaque message, et la zone de
   saisie s'échappe hors de l'écran au bout d'une dizaine de lignes. Le clamp
   suit l'écran (65vh) sans jamais descendre sous un format lisible ni
   s'étirer au ridicule sur un moniteur haut. */
.chat-flux { height: clamp(380px, 65vh, 720px); }

/* Un pane par salon, empilés dans le flux ; un seul est visible. C'est LUI
   qui défile — chaque salon garde ainsi sa position de lecture quand on
   passe de l'un à l'autre. */
.chat-pane {
  height: 100%; overflow-y: auto;
  padding: 12px 14px; font-size: 14px; line-height: 1.55;
}

.chat-ligne { margin-bottom: 3px; word-wrap: break-word; }
.chat-ligne .chat-h {
  color: var(--muted); font-family: var(--mono); font-size: 11.5px;
  margin-right: 7px; opacity: .7;
}
.chat-ligne .chat-auteur {
  font-weight: 600; margin-right: 6px; font-family: var(--mono); font-size: 13px;
}
.chat-ligne.msg    .chat-auteur { color: var(--accent); }
.chat-ligne.moi    .chat-auteur { color: var(--warn); }
.chat-ligne.prive  .chat-auteur { color: var(--err); }
/* Message dont la cible n'a pas d'onglet ici : lisible, mais visiblement pas
   du fil que l'on suit. */
.chat-ligne.autre  .chat-auteur { color: var(--muted); }
.chat-ligne.autre  .chat-txt    { color: var(--muted); }
.chat-ligne.notice .chat-auteur { color: var(--warn); }
.chat-ligne.notice .chat-txt    { color: var(--warn); }
.chat-ligne.info   .chat-txt    { color: var(--muted); font-style: italic; }
/* Refus du serveur : une commande de modération qui n'aboutit pas. Distinct
   de `info`, sans quoi le refus se perdrait dans les allées et venues. */
.chat-ligne.err    .chat-txt    { color: var(--err); }
/* Historique : ce qui s'est dit AVANT d'arriver. Volontairement en retrait —
   répondre à quelqu'un qui a parlé il y a trois jours et n'est plus là est
   l'erreur que ces lignes doivent éviter. L'opacité les distingue au premier
   coup d'œil, sans les rendre illisibles. */
.chat-ligne.hist                { opacity: .62; }
.chat-ligne.hist   .chat-auteur { color: var(--muted); font-weight: 500; }
.chat-ligne.hist   .chat-txt    { color: var(--muted); }
/* « On vous parle » — la seule ligne du fil qui porte un FOND.
   Les huit autres types ne jouent que sur la couleur du texte, et les cinq
   variables de couleur y sont déjà toutes prises : `--warn` deux fois,
   `--err` deux fois. Distinguer une mention par une sixième teinte aurait
   donc demandé d'en inventer une, ou d'en réemployer une troisième fois.
   Le fond et le trait de marge la rendent repérable en descendant le fil,
   sans toucher au vocabulaire de couleurs. */
.chat-ligne.cite {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-left: 2px solid var(--accent);
  border-radius: 0 4px 4px 0;
  margin-left: -6px; padding: 1px 4px 1px 6px;
}
.chat-ligne.cite   .chat-auteur { color: var(--accent); }

.chat-saisie-ligne {
  display: flex; gap: 8px; padding: 10px;
  border-top: 1px solid var(--border);
}
.chat-saisie-ligne input {
  flex: 1; min-width: 0;
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  padding: 9px 11px; color: var(--text); font-family: inherit; font-size: 14px;
}
.chat-saisie-ligne input:disabled { opacity: .55; }

.chat-cote {
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg-soft); padding: 12px;
  display: flex; flex-direction: column; min-height: 0;
}
.chat-cote h4 {
  font-size: 12px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); margin-bottom: 8px;
}

/* ── Colonne de gauche : les salons ── */

.chat-salons {
  list-style: none; margin: 0; padding: 0;
  flex: 1; min-height: 0; overflow-y: auto;
  font-family: var(--mono); font-size: 13px;
}
.chat-salons li {
  display: flex; align-items: center; gap: 7px;
  padding: 6px 8px; border-radius: 7px; cursor: pointer;
  color: var(--muted); user-select: none;
}
.chat-salons li:hover { background: var(--surface); color: var(--text); }
.chat-salons li.actif { background: var(--surface-2); color: var(--text); }
/* La pastille dit l'état : pleine = rejoint, accent = affiché, creuse = pas
   (ou plus) rejoint — un clic y entre. */
.chat-salons li::before {
  content: ''; flex: none; width: 7px; height: 7px; border-radius: 50%;
  background: var(--ok);
}
.chat-salons li.actif::before { background: var(--accent); }
.chat-salons li.non-rejoint::before {
  background: transparent; border: 1px solid var(--dim);
  width: 5px; height: 5px;
}
.chat-salons .chat-nom {
  flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-salons .chat-verrou { flex: none; font-size: 10.5px; color: var(--dim); }
.chat-nonlus {
  flex: none; margin-left: auto;
  background: var(--accent); color: #06231f;
  font-size: 11px; font-weight: 700; line-height: 16px;
  border-radius: 9px; padding: 0 6px;
}
/* Un salon qu'on ne regarde pas, et où l'on vous cite : le nom bat doucement.
   Un clignotement franc serait insupportable au bout d'une minute — celui-ci
   respire, et s'arrête net dès qu'on ouvre le salon (`activer` remet
   `mentions` à zéro, et `majSalons` reconstruit la colonne). */
.chat-salons li.cite .chat-nom { color: var(--warn); font-weight: 600; }
.chat-nonlus.cite { background: var(--warn); color: #2a1a00; }
.chat-salons li.cite, .chat-nonlus.cite { animation: chat-bat 1.6s ease-in-out infinite; }
@keyframes chat-bat { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }
/* ⚠ Une animation qui ne s'arrête jamais est un obstacle pour qui souffre de
   troubles vestibulaires ou d'épilepsie photosensible, et le système
   d'exploitation sait le dire. On respecte ce réglage : la couleur, elle,
   suffit à distinguer une mention. */
@media (prefers-reduced-motion: reduce) {
  .chat-salons li.cite, .chat-nonlus.cite { animation: none; }
}

/* ── Colonne de droite : présents, puis pseudos réservés ── */

.chat-presents {
  list-style: none; margin: 0 0 12px; padding: 0;
  flex: 1.4; min-height: 60px; overflow-y: auto;
  font-family: var(--mono); font-size: 13px;
}
/* Le <li> passe en colonne : la ligne du pseudo d'abord, le menu de
   modération dessous quand il est déployé. */
.chat-presents li { display: flex; flex-direction: column; padding: 2px 0; color: var(--text); }
.chat-presents li.moi { color: var(--accent); font-weight: 600; }
.chat-present { display: flex; align-items: center; min-width: 0; }
.chat-presents li.cliquable .chat-present { cursor: pointer; }
.chat-presents li.cliquable .chat-present:hover .chat-rnick { color: var(--accent); }
.chat-presents li.ouvert { background: var(--bg-soft); border-radius: 6px; padding: 4px; }

/* ── Onglet « Activité serveur » ─────────────────────────
   En tête de la colonne des salons, et visuellement distinct : ce n'est pas
   un lieu de conversation mais un journal. Séparé par un filet du reste de
   la liste, pour qu'on ne le confonde pas avec un salon rejoint. */
.chat-salons .chat-serveur {
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px; padding-bottom: 8px;
  color: var(--muted); font-style: italic;
}
.chat-salons .chat-serveur.actif { color: var(--accent); font-style: normal; }

/* ── Conversations privées ───────────────────────────────
   Sous les salons, précédées de leur intertitre. Un salon se rejoint, une
   conversation s'ouvre : les mêler dans une seule liste laisserait croire
   qu'on peut « entrer » dans l'une comme dans l'autre. */
.chat-groupe {
  margin: 10px 0 2px; padding: 0 4px;
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--dim); font-weight: 650;
  border-top: 1px solid var(--border); padding-top: 8px;
  cursor: default;
}
/* L'intertitre est un <li> comme les autres : il hérite donc de la pastille
   d'état et du curseur de clic, qui n'ont ici aucun sens. */
.chat-salons .chat-groupe::before { display: none; }
.chat-salons .chat-groupe:hover { background: none; color: var(--dim); }

.chat-salons .chat-conv::before { background: var(--purple); }
.chat-salons .chat-conv.actif::before { box-shadow: 0 0 7px var(--purple); }

/* La croix n'apparaît qu'au survol de sa ligne — et reste visible en
   permanence sur écran tactile, où le survol n'existe pas. */
.chat-fermer {
  margin-left: auto; flex: 0 0 auto;
  background: none; border: 0; padding: 0 2px;
  color: var(--dim); font-size: 15px; line-height: 1;
  cursor: pointer; opacity: 0; transition: opacity .12s, color .12s;
}
.chat-salons li:hover .chat-fermer,
.chat-salons li.actif .chat-fermer { opacity: 1; }
.chat-fermer:hover { color: var(--err); }
@media (hover: none) { .chat-fermer { opacity: 1; } }

/* ── Menu de modération d'un présent ─────────────────────
   Déployé sous le pseudo, dans une colonne étroite : les boutons se replient
   d'eux-mêmes plutôt que de déborder du panneau. */
.chat-menu { margin: 6px 0 4px; }
.chat-menu-actions { display: flex; flex-wrap: wrap; gap: 4px; }
.chat-act {
  font-family: inherit; font-size: 11.5px; line-height: 1.1;
  padding: 5px 8px; border-radius: 6px; cursor: pointer;
  background: var(--bg); color: var(--muted);
  border: 1px solid var(--border);
}
.chat-act:hover { background: #22303e; color: var(--text); border-color: var(--border-hi); }
/* Rouge au survol seulement : une rangée de boutons rouges se lit mal, et
   ce qui compte est de marquer le geste au moment de le faire. */
.chat-act.danger:hover { border-color: var(--err); color: var(--err); }
.chat-motif {
  width: 100%; margin-bottom: 5px; padding: 5px 7px;
  font-family: inherit; font-size: 11.5px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
}
.chat-menu-note { margin-top: 5px; font-size: 11px; color: var(--dim); font-style: italic; }

/* ── Votre identité, en tête de la colonne de droite ─────
   Ce que le SERVEUR sait de vous : le pseudo porté, et s'il est identifié.
   Le bloc reste visible en permanence — c'est la question qu'on se pose le
   plus souvent, et elle n'avait aucune réponse à l'écran. */
.chat-moi {
  margin-bottom: 12px; padding: 8px 9px;
  background: var(--bg-soft); border: 1px solid var(--border); border-radius: 8px;
  font-family: var(--mono); font-size: 13px;
}
.chat-moi:empty { display: none; }
.chat-moi-tete { display: flex; align-items: center; gap: 6px; min-width: 0; }
.chat-moi-etat { margin-top: 3px; font-size: 11px; color: var(--dim); font-family: inherit; }
.chat-moi-actions { margin-top: 7px; display: flex; flex-wrap: wrap; gap: 4px; }
.chat-moi-forme { width: 100%; }

/* ── Réglages du salon ───────────────────────────────────
   Déployé entre le sujet et le fil, sur toute la largeur de la colonne
   centrale, et refermé dès qu'on change de salon. */
.chat-sujet-ligne { display: flex; align-items: center; gap: 10px; }
.chat-sujet-ligne .chat-sujet { flex: 1; min-width: 0; }
.chat-panneau {
  padding: 12px; border-bottom: 1px solid var(--border);
  background: var(--bg-soft); max-height: 46vh; overflow-y: auto;
}
.chat-pan-bloc { margin-bottom: 14px; }
.chat-pan-bloc:last-child { margin-bottom: 0; }
.chat-pan-bloc h5 {
  margin: 0 0 7px; font-size: 10.5px; text-transform: uppercase;
  letter-spacing: .08em; color: var(--dim); font-weight: 660;
}
.chat-pan-modes { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.chat-pan-modes label {
  display: flex; align-items: center; gap: 5px;
  font-size: 12.5px; color: var(--muted); cursor: pointer;
}
.chat-pan-ligne { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
/* Le champ prend la place restante, les boutons gardent la leur. */
.chat-pan-ligne .chat-motif { flex: 1 1 160px; width: auto; margin-bottom: 0; }
.chat-pan-bans { list-style: none; margin: 8px 0 0; padding: 0; }
.chat-pan-bans li {
  display: flex; align-items: center; gap: 8px; padding: 3px 0;
  font-family: var(--mono); font-size: 12.5px;
}

/* Le badge de grade devant chaque présent. Toujours rendu, même vide, pour
   que les pseudos sans grade restent alignés sur les gradés. */
.chat-prefixe {
  flex: none; width: 16px; height: 16px; margin-right: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 4px; font-size: 11px; font-weight: 700;
}
.chat-rnick {
  flex: 1; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* Les couleurs de grade — partagées entre les préfixes de salon et les
   badges de grade global, pour que ~ (owner de salon) et « owner » (grade
   réseau) se lisent d'une même couleur. Palette héritée du panel :
   owner violet, admin accent, op bleu, voice vert, oper rouge. Le halfop,
   sans équivalent au panel, prend l'ambre. */
.g-owner  { color: var(--purple); background: rgba(169, 132, 255, .14); }
.g-admin  { color: var(--accent); background: rgba(53, 208, 192, .12); }
.g-op     { color: var(--blue);   background: rgba(110, 168, 254, .14); }
.g-halfop { color: var(--warn);   background: rgba(232, 185, 63, .14); }
.g-voice  { color: var(--ok);     background: rgba(63, 208, 122, .14); }
.g-oper   { color: var(--err);    background: rgba(242, 98, 107, .12); }

/* Le dépliant des pseudos réservés — 21/08/2026.

   ⚠ Il n'apparaît qu'au-delà de dix comptes. En deçà, la liste s'affiche
   entière : un dépliant qui cache trois lignes coûte un clic pour rien. */
.chat-pli { list-style: none; margin-top: 4px; }
.chat-pli summary {
  cursor: pointer;
  font-size: 11.5px;
  color: var(--muted);
  padding: 3px 2px;
  border-radius: 4px;
}
.chat-pli summary:hover { color: var(--text); background: rgba(255, 255, 255, .04); }
.chat-reserves-plies { list-style: none; margin: 2px 0 0; padding: 0; }

/* Le grade devant le pseudo DANS LE FIL — ajouté le 21/08/2026.

   ⚠ Ce n'est pas `.chat-prefixe` : celui-là est un badge de 16 px avec un
   fond, taillé pour la colonne des présents où il faut aligner les pseudos.
   Répété devant chaque ligne de conversation, il écraserait le texte. Ici on
   ne garde que la COULEUR de la palette, et l'on annule le fond.

   ⚠ Cette règle doit rester APRÈS le bloc `.g-*`, sinon son `background: none`
   perd contre celui des `.g-*` : même spécificité, c'est la dernière écrite
   qui gagne. */
.chat-fpre { background: none; font-weight: 700; margin-right: 1px; }

/* Les pseudos enregistrés du réseau (?p=nicks) : point d'identification,
   pseudo, badge du grade global. */
.chat-reserves {
  list-style: none; margin: 0 0 12px; padding: 0;
  flex: 1; min-height: 40px; overflow-y: auto;
  font-family: var(--mono); font-size: 12.5px;
}
.chat-reserves li {
  display: flex; align-items: center; gap: 6px;
  padding: 3px 0; color: var(--text);
}
/* ══════════════════════════════════════════════════════════════════════
   ⚠⚠ DEUX FAMILLES DE GRADES, DEUX FORMES — 21/08/2026

   Le projet affiche deux choses qui n'ont RIEN à voir, et qui se côtoyaient
   dans la même couleur :

     « owner » (ce badge)        → un rôle sur LE RÉSEAU, tiré de la base
     « ~ » (.chat-prefixe)       → le propriétaire DU SALON, tiré du serveur

   On peut être « owner » en base sans aucun pouvoir sur un salon donné, et
   inversement. Le risque de confusion était déjà noté dans le code ; l'ajout
   des préfixes colorés dans le fil, le même jour, a multiplié les endroits où
   les deux se croisent.

   ⚠ On ne touche PAS aux symboles de salon : « ~ @ + » sont la convention IRC
   universelle, reconnue par quiconque a déjà utilisé un client. Les changer
   pour lever NOTRE ambiguïté ferait payer à l'usage commun une confusion qui
   nous appartient. C'est donc le badge du réseau qui change de forme — son
   libellé est de nous, il est à nous de le rendre distinct.

   La règle qui en sort, et qui tient sans être apprise :

     PLEIN et carré  = ici, dans ce salon, maintenant
     CREUX et arrondi = ailleurs, sur le réseau

   ⚠ Deux signaux plutôt qu'un : la forme ET le remplissage. Un seul se perd
   pour qui distingue mal les nuances, ou sur un écran mal réglé.
   ══════════════════════════════════════════════════════════════════════ */
.chat-grade {
  flex: none;
  /* La pilule : franchement arrondie, là où les badges de salon sont carrés. */
  border-radius: 999px;
  padding: 0 6px;
  font-size: 10px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  /* ⚠ Creux : le fond vient des règles .g-* posées AVANT celle-ci. On l'annule
     ici et l'on garde la couleur, qui devient le trait. Cette règle doit donc
     rester APRÈS le bloc .g-* — à spécificité égale, c'est la dernière écrite
     qui gagne. */
  background: none;
  box-shadow: inset 0 0 0 1px currentColor;
  line-height: 15px;
}
.chat-pt { flex: none; width: 7px; height: 7px; border-radius: 50%; background: var(--dim); }
.chat-pt.on { background: var(--ok); }

/* Même compacte, la barre complète demande environ 1060px. En deçà, elle cède
   la place au menu replié.

   Les trois entrées de second rang s'effaçaient auparavant à ce palier, et
   toutes les autres à 860px : sur un téléphone, l'en-tête ne proposait plus
   que le bouton « Administration » et le reste du site devenait inaccessible.
   Rien n'est plus masqué sans recours — ce qui quitte la barre se retrouve
   dans le menu. */
@media (max-width: 1080px) {
  .site-nav a:not(.cta) { display: none; }
  /* Le groupe déroulant n'est pas un <a> : sans cette ligne il survivait seul
     dans une barre vidée de tout le reste, à côté du bouton « Menu » qui
     contient déjà ses entrées. */
  .site-nav > .nav-groupe { display: none; }
  .nav-repli { display: block; }
  /* La marge séparait le bouton des liens qui le précédaient ; ceux-ci ayant
     disparu, elle ne ferait plus que décoller le bouton du menu. */
  .site-nav a.cta { margin-left: 0; }
}

/* Très étroit : le mot « Menu » cède la place aux seules barres, et le bouton
   « Administration » reste seul en toutes lettres. Les deux côte à côte
   pousseraient le logo à la césure. */
@media (max-width: 460px) {
  .nav-repli-mot { display: none; }
  .nav-repli > summary { padding: 8px 11px; }
  .site-head .wrap { gap: 10px; }
}

@media (max-width: 860px) {
  .with-toc { grid-template-columns: minmax(0, 1fr); }
  .toc { position: static; }
  .hero { padding: 56px 0 46px; }
  section { padding: 46px 0; }

  /* Le client se replie en une colonne : les salons deviennent une rangée
     horizontale déroulante au-dessus du fil, et les présents passent
     dessous — à 180 px de côté, le fil serait illisible sur un téléphone. */
  .chat-zone { grid-template-columns: 1fr; }
  .chat-cote-salons { padding: 8px 10px; }
  .chat-cote-salons h4 { display: none; }
  .chat-salons {
    display: flex; flex-direction: row; gap: 4px;
    overflow-x: auto; overflow-y: hidden; flex: none;
  }
  .chat-salons li { flex: none; }
  .chat-flux { height: min(52vh, 420px); }
  .chat-presents, .chat-reserves { flex: none; max-height: 140px; }
}

/* ── Les formulaires au doigt ──────────────────────────────
   Signalé par SerVuS le 10/08/2026 : « je pensais pouvoir cocher les cases
   depuis mon téléphone, mais c'est pas pratique ».

   Deux défauts, mesurés dans un vrai viewport de 375 px :

   ⚠ 1. TOUS LES CHAMPS ÉTAIENT À 14 px, deux à 13,3. **iOS zoome de lui-même
   dès qu'un champ fait moins de 16 px** : on touche le champ, la page saute,
   et il faut dézoomer pour lire la suite. C'est la cause principale de
   l'inconfort, et elle ne se voit sur aucune capture d'écran.

   ⚠ 2. LES HAUTEURS TOMBAIENT À 39-41 px, sous le minimum tactile de 44 px.
   Les boutons, eux, y étaient déjà — d'où une page où l'on vise juste les
   boutons et de travers les champs.

   ⚠ `!important` EST NÉCESSAIRE ICI, et c'est le seul endroit de cette
   feuille où il l'est : ces champs portent leurs dimensions en attribut
   `style`, qui l'emporte sur toute règle externe. Les retirer un par un des
   six pages concernées serait le geste propre ; il touche beaucoup de code
   pour un correctif que ces trois règles rendent entièrement.

   `pointer: coarse` vise le doigt plutôt qu'une largeur : une tablette au
   stylet n'a pas le même besoin qu'un téléphone tenu à une main. */
@media (pointer: coarse), (max-width: 700px) {
  input[type="text"], input[type="email"], input[type="password"],
  input[type="number"], input[type="search"], input[type="tel"],
  select, textarea {
    font-size: 16px !important;      /* en dessous, iOS zoome */
    min-height: 44px !important;     /* la cible tactile recommandée */
  }
  textarea { min-height: 88px !important; }

  /* ⚠ Une case de 13 px se rate une fois sur trois. Le label qui l'entoure
     est déjà cliquable — c'est la case elle-même qu'on visait mal. */
  input[type="checkbox"], input[type="radio"] {
    width: 22px; height: 22px; flex: none;
  }

  /* ⚠ SUR UN DOIGT, IL N'Y A PAS DE PETIT BOUTON. `.btn.sm` tombait à 38 px —
     les boutons pleins étaient déjà à 44, les petits non. Mesuré sur trois
     pages le 10/08/2026 : « Accueil », « Installation » et « Commandes ». */
  .btn, button { min-height: 44px; }
  /* Le libellé d'une case reste aligné sur elle malgré sa nouvelle taille. */
  label > input[type="checkbox"], label > input[type="radio"] { margin-top: 1px; }

  /* Deux champs côte à côte sur 375 px ne laissent la place ni à l'un ni à
     l'autre : sous cette largeur, ils s'empilent.

     ⚠ `minmax(0, 1fr)` ET NON `1fr`. Les deux se lisent pareil et ne font pas
     la même chose : `1fr` vaut `minmax(auto, 1fr)`, et `auto` ne descend
     JAMAIS sous la largeur minimale du contenu. Une commande dans un `<pre>`
     ou la plus longue option d'un menu déroulant imposaient donc leur largeur
     à toute la colonne — 482 px dans un conteneur de 323.

     Mesuré le 10/08/2026 : les pages Installation et Contact débordaient de
     106 px et 15 px, et la page était à faire défiler latéralement. */
  .grid.g2, .grid.g3, .grid.g4 { grid-template-columns: minmax(0, 1fr); }

  /* ⚠ UN MENU DÉROULANT PREND LA LARGEUR DE SA PLUS LONGUE OPTION. Celui de
     la page Commandes réclamait 514 px dans un formulaire de 273 : il
     dépassait de l'écran, avec son libellé. */
  select, input, textarea { max-width: 100%; }

  /* ⚠ Un chemin ou une commande sans espace ne se coupe nulle part. Le journal
     des versions en cite beaucoup — 17 px de débordement pour un seul chemin
     absolu. `anywhere` plutôt que `break-word` : lui seul agit aussi sur le
     calcul de la largeur minimale, qui est justement ce qui débordait. */
  code, kbd, samp { overflow-wrap: anywhere; }
}

/* ══ Le rendu du MARKDOWN — partage par toutes les pages qui en affichent ══
   ⚠ Ce bloc vivait dans le <style> de `versions.php`, donc il ne s'appliquait
   qu'a elle. Le jour ou une SECONDE page a rendu du markdown — celle
   d'OverScript, le 26/08/2026 — son journal s'est retrouve SANS mise en forme,
   et c'est `test-vues-site.php` qui l'a dit : « classe orpheline ».
   Une seule definition, partagee : recopier ce bloc aurait donne deux regles
   pour une seule chose, qui auraient diverge un jour. */
  .md h2 { margin:34px 0 12px; font-size:clamp(19px,2.6vw,24px); padding-bottom:8px;
           border-bottom:1px solid var(--border); scroll-margin-top:80px }
  .md h3 { margin:22px 0 9px; font-size:15.5px; color:var(--accent); scroll-margin-top:80px }
  .md h4 { margin:16px 0 7px; font-size:14px }
  /* ⚠ Le moteur s'arrêtait au niveau 4 ; il va jusqu'à 6 depuis le 25/08/2026
     (point 49). Aucun document n'en emploie aujourd'hui — c'est justement pour
     cela que la règle est posée maintenant : le jour où quelqu'un en écrit un,
     il ne s'affichera pas au défaut du navigateur, plus gros que le h4 qui le
     précède. */
  .md h5 { margin:14px 0 6px; font-size:13px; color:var(--muted) }
  .md h6 { margin:12px 0 6px; font-size:12.5px; color:var(--dim) }
  .md p  { margin:9px 0; line-height:1.72 }
  .md ul { margin:9px 0 9px 20px }
  /* ⚠ Le moteur ne rendait pas les listes numérotées : sans branche `<ol>`,
     leurs items étaient fondus dans un paragraphe (point 49, 25/08/2026).
     La balise existe depuis — il lui faut sa règle, sinon elle s'affiche au
     défaut du navigateur, c'est-à-dire sans retrait. Un cran de plus que les
     puces : le numéro est plus large qu'un point. */
  .md ol { margin:9px 0 9px 22px }
  .md li { margin:6px 0; line-height:1.68 }
  .md hr { border:0; border-top:1px solid var(--border); margin:26px 0 }
  .md code { font-family:var(--mono); font-size:12.5px; background:var(--bg-soft);
             border:1px solid var(--border); border-radius:4px; padding:1px 5px }
  .md em { font-style:italic }
  .md pre { margin:12px 0; padding:13px 15px; background:var(--bg-soft);
            border:1px solid var(--border); border-radius:6px; overflow-x:auto }
  .md pre code { background:none; border:0; padding:0; font-size:12.5px; line-height:1.62 }
  .md blockquote { margin:14px 0; padding:2px 0 2px 16px; border-left:3px solid var(--border);
                   color:var(--muted) }
  .md blockquote p:first-child { margin-top:0 }
  .md blockquote p:last-child  { margin-bottom:0 }
  .md del { color:var(--muted); text-decoration-thickness:1px }
  .md .tbl-wrap { overflow-x:auto; margin:12px 0 }
  .md table { border-collapse:collapse; width:100%; font-size:13.5px }
  .md th, .md td { border:1px solid var(--border); padding:8px 11px; text-align:left; vertical-align:top }
  .md th { background:var(--bg-soft); font-weight:600 }

/* ══ Le selecteur des deux journaux ═══════════════════════════════════════
   ⚠ Il vit ICI et non dans une page : il est employe par LES DEUX — celle de
   Service-Overtchat et celle d'OverScript. Le laisser dans l'une des deux
   aurait rendu les boutons de l'autre sans style, et c'est exactement ce que
   `test-vues-site.php` a signale le 26/08/2026 en les disant orphelins. */
.choix-journal { display:flex; gap:8px; flex-wrap:wrap; margin-top:20px }
.choix-actif, .choix-autre {
  padding:8px 15px; border-radius:999px; font-size:13.5px;
  border:1px solid var(--border); text-decoration:none;
}
.choix-actif { background:var(--surface-2); color:var(--text); border-color:var(--border-hi) }
.choix-autre { color:var(--muted) }
.choix-autre:hover { color:var(--text); border-color:var(--border-hi) }
