/* ============================================================
   Service-Overtchat — panel d'exploitation
   Thème sombre, dense, orienté supervision.
   ============================================================ */

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

  --text:   #d8e2ee;
  --muted:  #7d8da0;
  --dim:    #55637a;

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

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

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.55;
}

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

/* ── Structure ─────────────────────────────────────────── */

.shell { display: flex; min-height: 100vh; }

.sidebar {
  width: 236px; flex: 0 0 236px;
  background: linear-gradient(180deg, var(--bg-soft), var(--bg));
  border-right: 1px solid var(--border);
  padding: 18px 0;
  position: sticky; top: 0; height: 100vh;
  overflow-y: auto;
}

.brand {
  display: flex; align-items: center; gap: 10px;
  padding: 0 18px 18px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 14px;
}
.brand-mark {
  width: 32px; height: 32px; flex: 0 0 32px;
  border-radius: 8px;
  background: linear-gradient(135deg, var(--accent), var(--accent-dk));
  display: grid; place-items: center;
  font-weight: 700; color: #06231f; font-size: 15px;
  box-shadow: 0 0 18px rgba(53, 208, 192, .25);
}
.brand-name { font-weight: 650; letter-spacing: .2px; }
.brand-sub { font-size: 11px; color: var(--dim); font-family: var(--mono); }

.nav-group { padding: 4px 10px 12px; }
.nav-title {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--dim); padding: 8px 10px 6px; font-weight: 600;
}
.nav a {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 11px; margin: 1px 0;
  border-radius: 8px; color: var(--muted);
  font-size: 13.5px; transition: background .12s, color .12s;
}
.nav a:hover { background: var(--surface); color: var(--text); text-decoration: none; }
.nav a.active {
  background: linear-gradient(90deg, rgba(53,208,192,.14), transparent);
  color: var(--accent);
  box-shadow: inset 2px 0 0 var(--accent);
}
.nav .ico { width: 16px; text-align: center; opacity: .85; font-size: 13px; }

/* Pastille d'état dans le menu — Online / Offline / Warline des services
   Anope. Poussée à droite pour que les trois s'alignent quelle que soit la
   longueur du nom, et rétrécie : c'est une indication qu'on balaie du regard,
   pas une information qu'on vient lire. Les couleurs restent celles des
   badges du reste du panel — un « warn » doit avoir la même teinte partout,
   sans quoi on apprend deux vocabulaires. */
.nav .nav-etat {
  margin-left: auto;
  font-size: 9.5px;
  padding: 1px 6px;
  letter-spacing: .02em;
  flex: none;
}

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 14px 24px;
  border-bottom: 1px solid var(--border);
  background: rgba(15, 21, 29, .85);
  backdrop-filter: blur(8px);
  position: sticky; top: 0; z-index: 20;
}
.topbar h1 { font-size: 17px; margin: 0; font-weight: 620; }
.topbar .sub { color: var(--dim); font-size: 12px; }

.statusbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

.content { padding: 22px 24px 60px; max-width: 1500px; width: 100%; }

/* ── Pastilles d'état ──────────────────────────────────── */

/*
 * `hidden` doit cacher — toujours.
 *
 * La règle `[hidden] { display: none }` vient de la feuille par défaut du
 * navigateur : n'importe quelle règle d'auteur portant un `display` l'emporte
 * sur elle. `.pill` ci-dessous pose `inline-flex`, si bien que la pastille
 * « N demandes » de l'en-tête restait affichée même à zéro, attribut `hidden`
 * ou pas — et le script qui la masque au retour à zéro n'avait aucun effet
 * visible. Posé pour tout le panel plutôt qu'au cas par cas : le prochain
 * élément masquable n'aura pas à s'en souvenir.
 */
[hidden] { display: none !important; }

.pill {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 10px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--border);
  font-size: 12px; font-family: var(--mono); color: var(--muted);
  white-space: nowrap;
}
/* Quatre états distincts. « Arrêté » était rouge : un service qu'on a
   volontairement coupé ressemblait à une panne, et l'œil ne pouvait plus
   distinguer l'incident du repos. Le rouge est désormais réservé à l'erreur. */
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dim); flex: 0 0 7px; }
.dot.on      { background: var(--ok);   box-shadow: 0 0 8px rgba(63,208,122,.7); }
.dot.off     { background: var(--dim);  box-shadow: none; }
.dot.error   { background: var(--err);  box-shadow: 0 0 8px rgba(242,98,107,.75); }
.dot.pending { background: var(--warn); box-shadow: 0 0 8px rgba(232,185,63,.6);
               animation: pulse 1.4s ease-in-out infinite; }
.dot.warn    { background: var(--warn); box-shadow: 0 0 8px rgba(232,185,63,.6); }

@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .35 } }

/* Pastille complète, teintée selon l'état — lisible sans regarder le point. */
.pill.st-on      { border-color: rgba(63,208,122,.35);  color: #9de8bd; }
.pill.st-error   { border-color: rgba(242,98,107,.45);  color: #ffb3b8;
                   background: rgba(242,98,107,.08); }
.pill.st-pending { border-color: rgba(232,185,63,.35);  color: #f0d798; }
.pill.st-off     { color: var(--muted); }

/* ── Alerte : une demande attend une décision ───────────────
 *
 * Clignotante, et c'est voulu : une demande d'ouverture de salon attend une
 * personne, pas un traitement automatique. Elle est présente sur toutes les
 * pages du panel, faute de quoi elle ne serait vue que par qui ouvre déjà le
 * bon onglet — c'est-à-dire personne.
 *
 * Le clignotement porte sur la BORDURE et la teinte, jamais sur le texte : un
 * chiffre qui disparaît par intermittence ne se lit pas.
 */
.pill.alerte {
  border-color: rgba(242,98,107,.55);
  background: rgba(242,98,107,.10);
  color: #ffb3b8;
  text-decoration: none;
  animation: alerte-pulse 1.6s ease-in-out infinite;
}
.pill.alerte:hover { background: rgba(242,98,107,.18); }
.pill.alerte .dot  { animation: pulse 1.6s ease-in-out infinite; }

@keyframes alerte-pulse {
  0%, 100% { border-color: rgba(242,98,107,.55); background: rgba(242,98,107,.10); }
  50%      { border-color: rgba(242,98,107,.95); background: rgba(242,98,107,.24); }
}

/* Un clignotement permanent fatigue, et pour certaines personnes il fait bien
   pire — migraines, gêne vestibulaire, crises chez les épileptiques
   photosensibles. Quand le système déclare préférer moins d'animation, la
   pastille reste visible et colorée, simplement immobile. */
@media (prefers-reduced-motion: reduce) {
  .pill.alerte,
  .pill.alerte .dot { animation: none; }
  .pill.alerte {
    border-color: rgba(242,98,107,.95);
    background: rgba(242,98,107,.22);
  }
}

/* ── Cartes ────────────────────────────────────────────── */

.grid { display: grid; gap: 16px; }
.grid.c2 { grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); }
.grid.c3 { grid-template-columns: repeat(auto-fit, minmax(270px, 1fr)); }
.grid.c4 { grid-template-columns: repeat(auto-fit, minmax(195px, 1fr)); }

/* ── Contenu + colonne latérale ────────────────────────────
 *
 * Le tableau de bord porte à droite un cadre de vue générale. Deux colonnes,
 * la latérale à largeur fixe.
 *
 * ⚠⚠ `minmax(0, 1fr)` ET NON `1fr`. Une piste de grille en `1fr` a pour
 * taille minimale AUTO, c'est-à-dire la largeur du contenu le plus large
 * qu'elle porte : un tableau un peu long, et la colonne refuse de rétrécir,
 * poussant la page à déborder horizontalement. Le défaut est invisible sur un
 * grand écran et ne se voit qu'au doigt, sur téléphone. `minmax(0, …)` rend
 * la piste réellement compressible.
 *
 * ⚠ Le repli se fait à 1100px et non 900px comme le reste : ici deux colonnes
 * doivent tenir, dont une de 320px — le seuil ne se déduit pas des autres. */
.avec-cote { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 16px; align-items: start; }
.avec-cote > .cote { position: sticky; top: 16px; }

@media (max-width: 1100px) {
  .avec-cote { grid-template-columns: minmax(0, 1fr); }
  /* ⚠ Une fois la colonne passée dessous, la coller en haut la ferait
     flotter au milieu du contenu qui défile. */
  .avec-cote > .cote { position: static; }
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
  background: linear-gradient(180deg, var(--surface-2), var(--surface));
}
.card-head h2 { margin: 0; font-size: 13.5px; font-weight: 620; letter-spacing: .2px; }
.card-head .hint { font-size: 11.5px; color: var(--dim); font-weight: 400; }
.card-body { padding: 16px; }
.card-body.tight { padding: 0; }

/* Tuile de métrique */
.stat { padding: 15px 16px; }
.stat .k { font-size: 11px; color: var(--dim); text-transform: uppercase; letter-spacing: .07em; }
.stat .v { font-size: 26px; font-weight: 640; font-family: var(--mono); margin-top: 3px; letter-spacing: -.5px; }
.stat .v.accent { color: var(--accent); }
.stat .v.ok  { color: var(--ok); }
.stat .v.warn{ color: var(--warn); }
.stat .v.err { color: var(--err); }
.stat .d { font-size: 11.5px; color: var(--dim); margin-top: 2px; }

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

.tbl-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th {
  text-align: left; padding: 9px 14px;
  font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--dim); font-weight: 600;
  border-bottom: 1px solid var(--border); background: var(--bg-soft);
  white-space: nowrap;
}
tbody td { padding: 9px 14px; border-bottom: 1px solid rgba(36,48,61,.6); vertical-align: middle; }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: rgba(26,36,47,.55); }
td.mono, .mono { font-family: var(--mono); font-size: 12.5px; }
td.num { text-align: right; font-family: var(--mono); }
.muted { color: var(--muted); }
.dim { color: var(--dim); }

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

.badge {
  display: inline-block; padding: 2px 8px; border-radius: 5px;
  font-size: 11px; font-weight: 600; 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); }

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

.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 13px; border-radius: 7px;
  border: 1px solid var(--border-hi); background: var(--surface-2);
  color: var(--text); font-size: 12.5px; font-weight: 550;
  cursor: pointer; font-family: inherit;
  transition: background .12s, border-color .12s, transform .06s;
}
.btn:hover { background: #22303e; border-color: #435874; text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn.sm { padding: 4px 9px; font-size: 11.5px; }
.btn.primary {
  background: linear-gradient(180deg, var(--accent), var(--accent-dk));
  border-color: var(--accent-dk); color: #05201d; font-weight: 650;
}
.btn.primary:hover { filter: brightness(1.08); }
.btn.danger { border-color: rgba(242,98,107,.4); color: var(--err); }
.btn.danger:hover { background: rgba(242,98,107,.12); border-color: var(--err); }
.btn.ghost { background: transparent; }
.btn[disabled] { opacity: .45; cursor: not-allowed; }

.btn-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* ── Formulaires ───────────────────────────────────────── */

.form-grid { display: grid; gap: 13px; }
.form-grid.c2 { grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
/* `.c3` était ÉCRITE sans être déclarée — botnet.php demandait trois colonnes
   et n'en obtenait aucune, la grille retombant sur une seule. Trouvée le
   17/08/2026 par le contrôle des classes composées (point 31) : `.form-grid`
   et `.c3` existaient chacune, jamais ensemble. Le seuil est plus bas que
   celui de `.c2` : à largeur égale, trois colonnes tiennent moins large. */
.form-grid.c3 { grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); }
.field { display: flex; flex-direction: column; gap: 5px; }
label { font-size: 11.5px; color: var(--muted); font-weight: 550; }
input[type=text], input[type=password], input[type=number], select, textarea {
  background: var(--bg-soft); border: 1px solid var(--border);
  border-radius: 7px; padding: 8px 11px;
  color: var(--text); font-size: 13px; font-family: inherit; width: 100%;
  transition: border-color .12s, box-shadow .12s;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(53,208,192,.12);
}
textarea { font-family: var(--mono); font-size: 12.5px; resize: vertical; min-height: 80px; }
.help { font-size: 11px; color: var(--dim); }
.inline-form { display: inline-flex; gap: 6px; align-items: center; }

/* ── Édition dans une cellule de tableau ───────────────── */
/*
 * Sujet et modes des salons : la valeur affichée EST le déclencheur. Un
 * <details> plutôt qu'un panneau en JavaScript — le repli fonctionne sans
 * script, et la valeur reste lisible tant qu'on ne l'ouvre pas.
 */

.cell-edit > summary {
  cursor: pointer; list-style: none;
  border-bottom: 1px dashed rgba(53,208,192,.4);
  display: inline-block; max-width: 100%;
}
.cell-edit > summary::-webkit-details-marker { display: none; }
.cell-edit > summary:hover { color: var(--accent); border-bottom-color: var(--accent); }
.cell-edit[open] > summary { color: var(--accent); margin-bottom: 10px; }

.cell-form {
  min-width: 300px; max-width: 420px;
  padding: 12px; border-radius: 8px;
  background: var(--bg-soft); border: 1px solid var(--border);
  white-space: normal;
}

.mode-grid {
  display: grid; grid-template-columns: repeat(2, minmax(140px, 1fr));
  gap: 4px 12px; margin: 8px 0 12px;
}
.mode-case {
  display: flex; align-items: baseline; gap: 6px;
  cursor: pointer; font-size: 11.5px; line-height: 1.5;
}
.mode-case input[type=checkbox] { width: auto; margin: 0; flex: 0 0 auto; cursor: pointer; }
.mode-case .mono { color: var(--text); font-weight: 600; }
.mode-case .dim  { font-weight: 400; }

.mode-param {
  display: flex; align-items: center; gap: 10px; margin-top: 8px;
}
.mode-param .mode-case { flex: 0 0 auto; }
.mode-param input[type=text], .mode-param input[type=number] { width: 110px; padding: 5px 8px; font-size: 12px; }

/* ── Alertes ───────────────────────────────────────────── */

/*
 * Bloc, et non flex.
 *
 * En `display:flex`, CHAQUE enfant devient une colonne : une alerte écrite
 * « <strong>titre</strong> texte <div>précision</div> » se rendait sur trois
 * colonnes serrées, et le texte y arrivait haché. C'est du contenu de lecture,
 * pas une disposition — il coule en bloc, le <strong> reste en ligne et un
 * <div> passe à la ligne suivante, ce qu'on attend.
 *
 * Les deux alertes qui ont VRAIMENT besoin d'une rangée (salons.php et
 * ulines.php, texte à gauche et bouton à droite) posent déjà `display:flex`
 * en style en ligne : elles ne changent pas de rendu.
 */
.alert {
  padding: 11px 15px; border-radius: 8px; margin-bottom: 18px;
  border: 1px solid; font-size: 13px; line-height: 1.6;
}
.alert.ok   { background: rgba(63,208,122,.09); border-color: rgba(63,208,122,.35); color: #9de8bd; }
.alert.err  { background: rgba(242,98,107,.09); border-color: rgba(242,98,107,.35); color: #ffb3b8; }
.alert.warn { background: rgba(232,185,63,.09); border-color: rgba(232,185,63,.35); color: #f0d798; }
.alert.info { background: rgba(110,168,254,.08); border-color: rgba(110,168,254,.3); color: #b9d4ff; }

/* ── Carte d'avertissement, et texte en deux colonnes ──── */
/*
 * Pour une information qu'il faut LIRE, pas seulement remarquer : une alerte
 * la comprime sur une ligne, une carte lui donne la largeur de la page. Le
 * liseré coloré dit la gravité sans crier.
 */

.card.bord-warn { border-color: rgba(232,185,63,.45); }
.card.bord-warn .card-head { background: rgba(232,185,63,.07); }
.card.bord-warn .card-head h2 { color: #f0d798; }
.card.bord-err  { border-color: rgba(242,98,107,.45); }
.card.bord-err  .card-head { background: rgba(242,98,107,.07); }
.card.bord-err  .card-head h2 { color: #ffb3b8; }

.duo { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 8px 34px; }
.duo p { margin: 0 0 9px; line-height: 1.65; }
.duo p:last-child { margin-bottom: 0; }
.duo-titre {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--dim); font-weight: 650; margin-bottom: 9px;
}
.etapes { margin: 0 0 10px; padding-left: 20px; }
.etapes li { margin-bottom: 6px; line-height: 1.6; }
.etapes li:last-child { margin-bottom: 0; }

/* ── Journal / sortie console ──────────────────────────── */

.log {
  background: #070b10; border: 1px solid var(--border); border-radius: 8px;
  padding: 12px 14px; font-family: var(--mono); font-size: 12px;
  line-height: 1.65; max-height: 480px; overflow: auto;
  white-space: pre-wrap; word-break: break-word; color: #9fb0c4;
}
.log .ln { display: block; }
.log .ln:hover { background: rgba(53,208,192,.05); }
.log .err  { color: var(--err); }
.log .warn { color: var(--warn); }
.log .ok   { color: var(--ok); }
.log .ts   { color: var(--dim); }

/* ── Barre de progression ──────────────────────────────── */

.bar { height: 6px; background: var(--bg-soft); border-radius: 999px; overflow: hidden; margin-top: 8px; }
.bar > i { display: block; height: 100%; background: linear-gradient(90deg, var(--accent), var(--blue)); }
.bar.warn > i { background: linear-gradient(90deg, var(--warn), var(--err)); }

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

.section-title {
  font-size: 12px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--dim); font-weight: 650; margin: 26px 0 12px;
}
.section-title:first-child { margin-top: 0; }
.empty { padding: 26px; text-align: center; color: var(--dim); font-size: 13px; }
.kv { display: grid; grid-template-columns: minmax(120px, 190px) 1fr; gap: 7px 16px; font-size: 13px; }
.kv dt { color: var(--muted); }
.kv dd { margin: 0; font-family: var(--mono); font-size: 12.5px; word-break: break-all; }
hr.sep { border: 0; border-top: 1px solid var(--border); margin: 16px 0; }

/* ── Connexion ─────────────────────────────────────────── */

.login-wrap { display: grid; place-items: center; min-height: 100vh; padding: 20px; }
.login-card { width: 100%; max-width: 380px; }
.login-card .card-body { padding: 24px; }
.login-head { text-align: center; margin-bottom: 22px; }
.login-head .brand-mark { margin: 0 auto 12px; width: 46px; height: 46px; font-size: 20px; }
.login-head h1 { margin: 0; font-size: 19px; }
.login-head p { margin: 4px 0 0; color: var(--dim); font-size: 12.5px; }

/* ── Le titre de l'écran de connexion ──────────────────────
   Posé au-dessus du logo, demandé par SerVuS le 17/08/2026 : c'est ce qu'on
   doit lire en arrivant, avant de savoir de quel réseau il s'agit.

   ⚠⚠ L'ANIMATION EST UN VOLET, PAS UNE FRAPPE — et le choix est technique
   autant qu'esthétique. La frappe de l'en-tête du site (`site.css`, `.logo-nom`)
   impose trois contraintes qui ne passent pas ici :

     1. une CHASSE FIXE obligatoire, un cran valant `1ch` — en police
        proportionnelle les crans se décalent des lettres ;
     2. une largeur exprimée en `ch`, donc « Réservé aux administrateurs »
        réclamerait ~27ch, soit bien plus que les 380 px de `.login-card` sans
        descendre la police à une taille illisible ;
     3. un nombre de lettres passé en `--n` et en `steps(n, end)`, calculé côté
        PHP — un mécanisme qui n'a de sens que pour une constante comme
        `APP_NAME`, pas pour une phrase écrite en dur.

   `clip-path` n'a aucune de ces contraintes : il découvre le texte tel qu'il
   est peint, dans n'importe quelle police, sur autant de lignes qu'il en prend.

   ⚠ Et il évite le piège du `min-width` qui a coûté cher sur l'en-tête du site :
   ici rien n'anime la LARGEUR, donc rien ne peut être bloqué par une largeur
   minimale. Le texte occupe sa place dès la première image ; seul son
   dévoilement est différé. Aucun élément ne saute autour. */
.admin-titre { text-align: center; margin-bottom: 18px; }

.admin-titre .at-1,
.admin-titre .at-2 {
  display: block;
  /* Le volet part fermé à droite et s'ouvre vers la droite. */
  clip-path: inset(0 100% 0 0);
  animation: admin-volet .8s cubic-bezier(.2, .7, .3, 1) both;
}

/* ⚠ La taille suit la largeur de l'écran EN CONTINU, par `clamp()`, au lieu d'un
   palier unique. Un palier laisse forcément un trou : celui à 400px laissait les
   écrans de 401 à 430px (iPhone Pro Max) sur la taille de bureau. Le `clamp` n'a
   pas de trou par construction, et il n'y a donc plus rien à re-régler quand un
   nouveau format d'écran apparaît.
   Mesuré au navigateur à 320, 375, 390, 430 et 1280px : le titre tient sur UNE
   ligne partout et occupe 66 à 70% de la carte. La borne basse (28px) est dictée
   par 320px, la haute (38px) par la carte qui plafonne à 380px. */
.admin-titre .at-1 {
  font-size: clamp(28px, 8.4vw, 38px);
  font-weight: 700;
  letter-spacing: -.2px;
  line-height: 1.15;
  color: var(--text);
}

/* ⚠ Le sous-titre est ce qui serre en premier — majuscules et `letter-spacing`
   l'allongent bien au-delà de sa taille de police. Son interlettrage descend donc
   de .14em à .12em, ce qui lui rend la place que le grossissement lui prend. */
.admin-titre .at-2 {
  margin-top: 6px;
  font-size: clamp(11.5px, 3.2vw, 14px);
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent);
  /* Le second suit le premier, il ne l'accompagne pas : l'oeil lit « où suis-je »
     avant « qui a le droit ». */
  animation-delay: .28s;
}

@keyframes admin-volet { to { clip-path: inset(0 0 0 0); } }

/* ⚠ Le palier `max-width: 400px` a été RETIRÉ, il n'est pas devenu inutile : posé
   après les règles ci-dessus, il les écrasait et annulait le `clamp` sous 400px.
   Ne pas le remettre « au cas où » — c'est le trou 401-430px qu'on vient de
   fermer, et la borne basse du `clamp` couvre déjà les écrans étroits. */

/* ⚠ On retire le MOUVEMENT, jamais le CONTENU — même doctrine que `site.css`.
   Un volet resté fermé masquerait entièrement le titre : `clip-path: none` est
   donc indispensable, `animation: none` seul ne suffirait pas. */
@media (prefers-reduced-motion: reduce) {
  .admin-titre .at-1,
  .admin-titre .at-2 { clip-path: none; animation: none; }
}

/* ── Section repliable de détail ───────────────────────────
   Le « menu déroulant » demandé le 05/09/2026 pour les liens ULine et pour
   Anope : un en-tête cliquable qui porte le nom et l'état, et dessous le
   détail. Sur <details>, comme .cell-edit et .nav-repli — le repli fonctionne
   sans script, chaque section garde son état à l'ouverture, et une recherche
   Ctrl+F du navigateur y déplie ce qu'elle trouve.

   ⚠ Le titre ne se met pas en gras À L'OUVERTURE seulement : replié, il est la
   seule chose visible d'une section, et c'est replié qu'on lit la liste. */
.repli {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-soft);
  margin-bottom: 10px;
}
.repli > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 11px 14px; border-radius: var(--radius);
}
.repli > summary::-webkit-details-marker { display: none; }
.repli > summary:hover { background: var(--surface); }
.repli > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.repli[open] > summary {
  border-bottom: 1px solid var(--border);
  border-radius: var(--radius) var(--radius) 0 0;
}

/* Le chevron, tracé plutôt que dessiné : pas de glyphe, donc pas de police à
   avoir, et il tourne à l'ouverture pour dire dans quel sens ça va. */
.repli > summary::before {
  content: ""; flex: none;
  width: 6px; height: 6px;
  border-right: 2px solid var(--accent); border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg); transition: transform .12s ease;
}
.repli[open] > summary::before { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) {
  .repli > summary::before { transition: none; }
}

.repli-titre { font-weight: 600; color: var(--text); }
.repli-note  { font-size: 11px; color: var(--muted); margin-left: auto; }
.repli-corps { padding: 14px; border-top: 0; }

/* ── Menu replié du panel ──────────────────────────────────
   Masqué tant que la barre latérale est visible : il ne prend la relève que
   sous 900px, où celle-ci sort de l'écran. Construit sur <details>, il
   fonctionne sans script. */
.nav-repli { display: none; position: relative; }

.nav-repli > summary {
  list-style: none;
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px;      /* cible tactile recommandée */
  border-radius: 9px;
  border: 1px solid var(--border, #223);
  background: var(--surface, #131a22);
  color: var(--text, #e6edf3);
  cursor: pointer; user-select: none; flex: none;
}
.nav-repli > summary::-webkit-details-marker { display: none; }
.nav-repli > summary:hover { border-color: var(--accent, #35d0c0); }
.nav-repli > summary:focus-visible {
  outline: 2px solid var(--accent, #35d0c0); outline-offset: 2px;
}
.nav-repli[open] > summary { border-color: var(--accent, #35d0c0); color: var(--accent, #35d0c0); }

/* Trois barres tracées par les bordures : ni image, ni glyphe, donc un rendu
   net à toute densité et indépendant des polices disponibles. */
.nav-repli-barres {
  width: 17px; height: 2px; background: currentColor; position: relative;
}
.nav-repli-barres::before,
.nav-repli-barres::after {
  content: ""; position: absolute; left: 0;
  width: 17px; height: 2px; background: currentColor;
}
.nav-repli-barres::before { top: -5px; }
.nav-repli-barres::after  { top: 5px; }

.nav-repli-panneau {
  position: absolute; left: 0; top: calc(100% + 9px);
  min-width: 232px; max-width: calc(100vw - 24px);
  padding: 8px;
  background: var(--bg-2, #0f151c);
  border: 1px solid var(--border, #223); border-radius: 12px;
  box-shadow: 0 18px 44px rgba(0, 0, 0, .5);
  z-index: 60;
  /* Une vingtaine d'entrées réparties en six groupes : le panneau défile
     plutôt que de sortir de l'écran. */
  max-height: calc(100vh - 92px); overflow-y: auto;
}
.nav-repli-panneau .nav-group { margin-bottom: 9px; }
.nav-repli-panneau .nav-group:last-child { margin-bottom: 0; }
.nav-repli-panneau .nav-title {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .7px;
  color: var(--dim, #7d8b99); padding: 4px 8px; font-weight: 600;
}
.nav-repli-panneau a {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 10px; border-radius: 8px;
  color: var(--muted, #a9b6c3); font-size: 13.5px; text-decoration: none;
}
.nav-repli-panneau a:hover { background: var(--surface, #131a22); color: var(--text, #e6edf3); }
.nav-repli-panneau a.active { color: var(--accent, #35d0c0); background: rgba(53, 208, 192, .1); }
.nav-repli-panneau .ico { width: 17px; text-align: center; flex: none; }

@media (max-width: 900px) {
  /* La barre latérale reste hors champ : c'est le menu replié qui la remplace.
     La règle « .sidebar.open » attendait une classe que rien n'ajoutait, et la
     navigation du panel était donc inatteignable sur un téléphone. */
  .sidebar { position: fixed; left: -240px; z-index: 50; transition: left .2s; }
  .sidebar.open { left: 0; }
  .content { padding: 16px; }

  .nav-repli { display: block; }
  /* Le bouton précède le titre : la barre passe en ligne, alignée en haut,
     pour que le sous-titre ne décale pas le menu vers le bas. */
  .topbar { align-items: flex-start; gap: 12px; padding: 14px 16px; }
  .topbar-titre { min-width: 0; }
  /* La barre est collante : un titre long qui se replie sur trois lignes lui
     ferait manger un sixième de la hauteur d'écran, à demeure. Titre plus
     court et césure autorisée la ramènent à deux lignes au pire. */
  .topbar-titre h1 { font-size: 15px; overflow-wrap: anywhere; }
  .topbar .sub { font-size: 11.5px; }
}

/* ── Le panel au doigt ─────────────────────────────────────
   Même correctif que sur le site, appliqué le 10/08/2026 après l'avoir mesuré
   ici aussi — et le panel était PIRE.

   Relevé sur sa page de connexion, dans un viewport de 375 px :

     champs   13 px, 35 px de haut
     bouton   12,5 px, 32 px de haut   ← le tout premier geste du panel

   ⚠ SOUS 16 px, iOS ZOOME DE LUI-MÊME : on touche le champ, la page saute, il
   faut dézoomer pour lire la suite. Sur la page de CONNEXION, c'est le premier
   contact avec l'administration du réseau — et il se fait de travers.

   ⚠ Le bouton à 32 px était le plus gênant des trois : sur le site il était
   déjà à 44, ici non. On vise l'écran, on rate, on recommence.

   ⚠⚠ CE COMMENTAIRE A DIT FAUX JUSQU'AU 02/09/2026. Il affirmait : « PAS DE
   `!important` ICI, contrairement au site : ces champs tiennent leurs
   dimensions de cette feuille, pas d'un attribut `style` ». La deuxième
   moitié est vraie, la conclusion ne l'est pas — le danger n'était pas un
   attribut `style`, mais une règle PLUS SPÉCIFIQUE de cette même feuille :

     .mode-param input[type=text]   (0,2,1)   12px   ← ligne 386
     input[type=text]               (0,1,1)   16px   ← ci-dessous

   Les champs de `?p=salons` restaient donc à 12 px, et iOS zoomait sur chacun
   d'eux. Le raisonnement était juste sur les attributs `style` et aveugle sur
   la cascade, ce qui est bien plus courant.

   ⚠ Le `!important` ne porte que sur `font-size`, la seule propriété qui
   déclenche le zoom. L'étendre au reste serait la dette que le commentaire
   d'origine redoutait, et là il avait raison. */
@media (pointer: coarse), (max-width: 700px) {
  /* ⚠⚠ CETTE GARDE A ÉTÉ POSÉE, PUIS N'A JAMAIS ÉTÉ ATTEINTE — 04/09/2026.
     Elle listait les types un par un. Or 17 champs du panel s'écrivent
     `<input name="nick" value="…">`, SANS attribut `type` : le type vaut
     « text » pour le navigateur et pour JavaScript, mais le sélecteur
     `input[type=text]` ne les voit pas — l'attribut n'existe pas. Ils sont
     donc restés à 13,3 px, et sur iOS un champ sous 16 px déclenche un zoom
     dont on ne ressort pas. Mesuré sur les 30 pages du panel à 375 px :
     51 champs concernés, sur 6 pages.
     Le sélecteur dit maintenant ce qu'il veut dire — « tout champ de saisie
     SAUF ceux qui n'en sont pas » — et il couvrira les types à venir. */
  input:not([type=checkbox]):not([type=radio]):not([type=hidden]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]):not([type=color]):not([type=range]),
  select, textarea {
    font-size: 16px !important;
    min-height: 44px;
  }
  textarea { min-height: 96px; }

  /* ⚠ Le bouton compte autant que le champ : c'est lui qu'on vise en dernier,
     et souvent d'un pouce. */
  .btn, button[type=submit] { min-height: 44px; font-size: 14px; }

  /* Une case de 13 px se rate une fois sur trois. */
  input[type=checkbox], input[type=radio] { width: 22px; height: 22px; flex: none; }

  /* Deux colonnes sur 375 px n'en laissent la place à aucune des deux. */
  .grid.c2, .grid.c3, .grid.c4 { grid-template-columns: 1fr; }
}
