/* ============================================================
   Console d'administration HiveVibe.
   Sobre à dessein : on y prend des décisions qui touchent des
   personnes, pas un tableau de bord à contempler.
   ============================================================ */
:root {
  --bg: #0a0810;
  --card: #12101d;
  --panel: #1a1628;
  --line: #2b2242;
  --text: #f0edf8;
  --soft: #cfc9e0;
  --muted: #928ca8;
  --violet: #a855f7;
  --deep: #7c3aed;
  --ok: #34d399;
  --warn: #fbbf24;
  --danger: #f87171;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* ============================================================
   LA FONTE DE L'INTERFACE DU SYSTÈME, et pas Poppins.

   Poppins était déclarée ici sans être chargée : chaque machine servait
   ce qu'elle avait. Mesuré le 07/08/2026 sur le poste de l'administrateur :
   une SEULE graisse de Poppins y est installée — la Black — et les quatre
   poids demandés (300, 400, 600, 900) rendaient tous exactement 312,7 px.
   Autrement dit, toute la console s'écrivait en Black, du plus petit
   paragraphe d'aide aux titres. D'où la fatigue à la lecture, et d'où la
   hiérarchie devenue plate : un titre ne se distinguait plus d'un
   paragraphe, faute de contraste de graisse.

   Pire, cela variait d'un poste à l'autre : les modérateurs sans Poppins
   voyaient une console normale. Une interface d'équipe qui ne se
   ressemble pas d'une machine à l'autre est difficile à décrire au
   téléphone.

   Poppins est par ailleurs une géométrique d'AFFICHAGE — cercles quasi
   parfaits, 20 % plus large que Segoe UI à taille égale. Elle brille en
   titre et fatigue en texte courant. Ici on lit pour travailler.

   La pile système résout les trois : rendu identique partout, vraies
   graisses, zéro octet à télécharger.
   ============================================================ */
body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
               'Helvetica Neue', Arial, sans-serif;
  font-size: 15px;
  /* 1.6 plutôt que 1.5 : on parcourt ici des paragraphes entiers de
     notice et des extraits de conversation, pas des étiquettes. */
  line-height: 1.6;
}

/* ---------- connexion ---------- */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; }
.login-card {
  width: 100%; max-width: 380px; background: var(--card);
  border: 1px solid var(--line); border-radius: 18px; padding: 28px;
  display: flex; flex-direction: column; gap: 14px;
}
.login-card h1 { font-size: 22px; letter-spacing: -.4px; }
.login-card .sub { color: var(--muted); font-size: 13px; margin-top: -8px; }

label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; }
input, textarea {
  font-family: inherit; font-size: 14px; color: var(--text);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; padding: 10px 12px; width: 100%;
}
input:focus, textarea:focus { outline: none; border-color: var(--violet); }
textarea { resize: vertical; }
/* Sans cette règle, le texte d'invite prend le gris par défaut du navigateur,
   pensé pour un fond clair : illisible sur les fonds sombres de la console. */
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 1; }

/* ---------- boutons ---------- */
.btn {
  font-family: inherit; font-size: 14px; font-weight: 600; cursor: pointer;
  border: 1px solid var(--line); border-radius: 999px; padding: 10px 18px;
  background: var(--panel); color: var(--text); transition: .18s;
}
.btn:hover:not(:disabled) { border-color: var(--violet); }
.btn:disabled { opacity: .5; cursor: default; }
.btn.primary { background: linear-gradient(135deg, var(--violet), var(--deep)); border: 0; color: #fff; }
.btn.ghost { background: transparent; color: var(--soft); }

/* Recherche de membres (onglet Membres) */
.membre-search { display: flex; gap: 10px; margin: 0 0 18px; max-width: 460px; }
.membre-search input { flex: 1; padding: 10px 14px; border-radius: 10px;
  border: 1px solid var(--line, rgba(168,85,247,.3)); background: transparent; color: inherit;
  font-family: inherit; font-size: 14px; }
.membre-search input:focus { outline: none; border-color: var(--violet); }

/* Approuver et refuser ne doivent JAMAIS se confondre : couleur,
   position et libellé les séparent. */
.btn.oui { background: rgba(52,211,153,.14); border-color: rgba(52,211,153,.4); color: var(--ok); }
.btn.oui:hover { background: rgba(52,211,153,.24); border-color: var(--ok); }
.btn.non { background: rgba(248,113,113,.12); border-color: rgba(248,113,113,.4); color: var(--danger); }
.btn.non:hover { background: rgba(248,113,113,.22); border-color: var(--danger); }
.btn.danger { background: rgba(248,113,113,.16); border-color: rgba(248,113,113,.5); color: var(--danger); }
.btn.danger:hover { background: var(--danger); border-color: var(--danger); color: #fff; }
/* Signalement aux autorites deja fait : on remplace le bouton par la trace,
   pour qu'on ne le refasse pas et qu'on voie qu'il a eu lieu. */
.note-pharos { margin-left: 10px; font-size: 12px; font-weight: 600; color: var(--danger); opacity: .85; }
/* Bouton signalé « attention » : compte de 3 ans ou plus, certification à renouveler. */
.btn.attention { border-color: var(--warn); color: var(--warn); font-weight: 700;
  background: rgba(251,191,36,.1); }
.btn.attention:hover { background: rgba(251,191,36,.2); }
.pic-attn { margin-right: 2px; }

/* Bouton « voir plus » sous la liste de membres */
.membres-plus { text-align: center; margin-top: 16px; }

/* ---------- barre du haut ---------- */
.top {
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  padding: 14px 22px; background: var(--card); border-bottom: 1px solid var(--line);
  /* Au-dessus du tiroir et de son fond : sans cela le menu recouvrait le
     bouton qui l'a ouvert, et il n'y avait plus de quoi le refermer. */
  position: sticky; top: 0; z-index: 45;
}
.brand { font-weight: 800; letter-spacing: -.3px; }
.brand em { font-style: normal; color: var(--violet); font-weight: 600; }
.tabs { display: flex; gap: 6px; margin-left: auto; }
.tab {
  font-family: inherit; font-size: 13.5px; font-weight: 600; cursor: pointer;
  background: transparent; color: var(--muted); border: 0;
  border-radius: 999px; padding: 8px 14px; display: flex; align-items: center; gap: 8px;
}
.tab:hover { color: var(--text); }
.tab.is-on { background: rgba(168,85,247,.16); color: var(--violet); }
.pill {
  background: var(--panel); border-radius: 999px; padding: 1px 8px;
  font-size: 11.5px; font-weight: 700; min-width: 22px; text-align: center;
}
.tab.is-on .pill { background: var(--violet); color: #fff; }
.pill.chaud { background: var(--danger); color: #fff; }
/* Rien à traiter : la pastille s'efface au lieu d'attirer l'œil sur un zéro. */
.pill.is-zero { opacity: .45; }
/* badge « nouveaux inscrits » sur l'onglet Membres */
.tab-badge { background: var(--violet); color: #fff; border-radius: 999px; padding: 1px 7px;
  font-size: 11px; font-weight: 800; min-width: 18px; text-align: center; line-height: 1.5; }
.tab-badge[hidden] { display: none; }
/* Une commande payée attend un colis : le violet dirait « il y a du
   nouveau », le rouge dit « quelqu'un attend ». Même code couleur que les
   signalements critiques, pour que l'urgence se lise pareil partout. */
.tab-badge.chaud { background: var(--danger); }

/* ---- ce qu'un modérateur ne voit pas ----
   Le serveur refusait déjà ces routes ; la console, elle, montrait tout.
   Un modérateur voyait donc l'onglet Annonce, l'onglet Boutique et le
   bouton d'export, et récoltait un refus au moment d'agir. On ne masque
   rien de ce qu'il peut faire — seulement ce qui ne lui était offert que
   pour lui être repris. Le rôle est posé sur <body> par admin.js, et
   revérifié à chaque requête côté serveur : ceci est du confort, jamais
   une protection. */
body[data-role="moderator"] .admin-only { display: none !important; }

/* Veille sur l'équipe, en tête du journal. Détachée par son trait : ce
   n'est pas une décision prise, c'est un avis reçu. */
.staff-veille { margin-bottom: 26px; padding-bottom: 20px; border-bottom: 1px solid var(--line); }
.staff-veille h3, .vue[data-vue="journal"] > h3 { margin: 0 0 6px; font-size: 15px; }
.staff-veille .liste { margin-top: 12px; }
/* Ces fiches n'ont qu'un en-tête : sans cela, la marge du corps absent
   laisse un vide qui donne à lire un chargement qui aurait échoué. */
.staff-veille .fiche { padding: 13px 16px; }
.staff-veille .fiche-tete { margin-bottom: 0; }
/* L'angle mort d'un modérateur : orange, pas rouge. Ce n'est pas une
   faute, c'est une lacune de couverture — et elle se répare d'un mot. */
.staff-veille .fiche:has(.staff-aveugle) .fiche-tete { margin-bottom: 8px; }
.staff-aveugle { margin: 0; font-size: 13px; color: #fbbf24; }

/* ---- journal ---- */
/* `wrap` et `min-width: 0` : sur un téléphone, le menu déroulant des
   actions et le bouton ne tiennent pas côte à côte, et la barre poussait
   la page entière en défilement latéral. Un menu qui ne peut pas rétrécir
   déborde toujours de quelque part. */
.journal-barre { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; margin-bottom: 14px; }
.journal-barre label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted); min-width: 0; }
.journal-barre select { max-width: 100%; }
.jrn { display: grid; grid-template-columns: 150px 1fr; gap: 4px 14px; padding: 11px 14px;
  border-bottom: 1px solid var(--line); align-items: baseline; }
.jrn:last-child { border-bottom: 0; }
.jrn-quand { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.jrn-quoi { font-size: 14px; }
.jrn-action { font-weight: 700; }
.jrn-qui { color: var(--muted); }
.jrn-motif { grid-column: 2; font-size: 13px; color: var(--muted); font-style: italic; }
@media (max-width: 700px) {
  .jrn { grid-template-columns: 1fr; }
  .jrn-motif { grid-column: 1; }
}
.me { display: flex; align-items: center; gap: 12px; font-size: 13px; color: var(--muted);
  margin-left: auto; }

/* ---- menu ----
   Onze onglets ne tenaient plus sur une ligne : ils passaient à la
   seconde, poussaient le contenu vers le bas et se lisaient de moins en
   moins. Repliés dans un tiroir, ils redeviennent une liste qu'on lit.

   Le prix du repli, c'est le coup d'œil : on ne voit plus les compteurs
   sans ouvrir. D'où le badge sur le bouton lui-même, et le nom de l'écran
   courant à côté — sans lui, on ne sait plus où l'on est. */
.burger {
  display: flex; align-items: center; gap: 8px; position: relative;
  background: transparent; border: 1px solid var(--line); border-radius: 11px;
  padding: 9px 11px; cursor: pointer; color: var(--text);
}
.burger:hover { border-color: var(--violet); }
.burger-traits { display: grid; gap: 4px; }
.burger-traits i { display: block; width: 18px; height: 2px; border-radius: 2px; background: currentColor; }
.burger.est-ouvert { border-color: var(--violet); color: var(--violet); }
.burger-badge {
  background: var(--violet); color: #fff; border-radius: 999px; padding: 1px 7px;
  font-size: 11px; font-weight: 800; min-width: 18px; text-align: center; line-height: 1.5;
}
.burger-badge.chaud { background: var(--danger); }
.burger-badge[hidden] { display: none; }

.vue-courante { font-weight: 700; font-size: 14px; color: var(--muted); }

.menu-fond { position: fixed; inset: 0; background: rgba(0,0,0,.55); z-index: 40; }
.menu-fond[hidden] { display: none; }

/* Le tiroir, et non un panneau flottant : à onze entrées il doit pouvoir
   défiler sans emporter la page avec lui. */
/* Le tiroir glisse SOUS l'en-tête (voir le z-index de .top) : le bouton
   qui l'a ouvert doit rester visible et cliquable pour le refermer. D'où
   la réserve en haut, qui dégage la hauteur de l'en-tête. */
.tabs {
  position: fixed; top: 0; left: 0; bottom: 0; width: min(290px, 86vw);
  display: flex; flex-direction: column; gap: 3px; margin: 0;
  background: var(--card); border-right: 1px solid var(--line);
  padding: 80px 14px 18px; overflow-y: auto; z-index: 41;
}
.tabs[hidden] { display: none; }
.menu-titre {
  font-size: 11px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase;
  color: var(--muted); opacity: .7; margin: 0 0 10px 10px;
}
.tabs .tab { width: 100%; justify-content: flex-start; padding: 11px 12px; border-radius: 10px; font-size: 14px; }
.tabs .tab:hover { background: var(--panel); }
.menu-note { font-size: 11.5px; font-weight: 600; color: var(--muted); opacity: .75; }
/* Les compteurs se rangent à droite de leur entrée, alignés entre eux. */
.tabs .tab .pill, .tabs .tab .tab-badge { margin-left: auto; }

/* Le retour aux tuiles. Discret au repos — il ne doit pas concurrencer le
   titre de l'écran qu'on est venu voir — mais franc au survol.

   IL SUIT LE DÉFILEMENT — 16/09/2026, demande du propriétaire : sur un
   écran long (les membres, les publications), revenir aux tuiles
   demandait de remonter toute la page. `position: sticky` le garde sous
   la barre du haut ; il ramène en haut d'un clic. Le fond n'est plus
   transparent : collé, il passe PAR-DESSUS le contenu qui défile, et un
   fond transparent l'aurait rendu illisible.

   `--haut-top` est la hauteur RÉELLE de la barre, mesurée par le script :
   elle passe sur deux lignes quand la fenêtre est étroite, et une valeur
   écrite en dur l'aurait fait glisser dessous. */
.retour-hub {
  display: inline-flex; align-items: center; gap: 7px;
  position: sticky; top: calc(var(--haut-top, 60px) + 10px); z-index: 44;
  margin-bottom: 14px; padding: 7px 13px 7px 11px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--card); color: var(--muted);
  box-shadow: 0 4px 16px rgba(0, 0, 0, .25);
  font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer; transition: color .15s, border-color .15s, background .15s;
}
.retour-hub:hover { color: var(--violet); border-color: var(--violet); background: var(--panel); }
.retour-hub:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.retour-hub[hidden] { display: none; }   /* flex écrase le style de `hidden` */

/* ---- Accueil : la console en tuiles ----
   `auto-fill` plutôt qu'un nombre de colonnes fixe : la grille se règle
   sur la largeur disponible, du téléphone à l'écran large, sans point de
   rupture à entretenir. */
.hub {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
/* Hauteur minimale identique pour toutes : des cases inégales cesseraient
   d'être un repère, et c'est le repère qui fait tout l'intérêt d'une
   grille sur une liste. */
.hub-tile {
  position: relative;
  min-height: 104px; padding: 18px 12px 15px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; text-align: center; line-height: 1.3;
  border: 1px solid var(--line); border-radius: 16px;
  background: var(--card); color: var(--soft);
  font: inherit; font-size: 13.5px; font-weight: 600;
  cursor: pointer; transition: border-color .15s, background .15s, color .15s, transform .15s;
}
.hub-tile:hover { background: var(--panel); color: var(--text); border-color: var(--violet); transform: translateY(-1px); }
.hub-tile:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.hub-ic { width: 26px; height: 26px; flex: none; stroke: currentColor; fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
.hub-tile:hover .hub-ic { color: var(--violet); }
/* Le compteur passe en coin. Sous le libellé, il aurait poussé le texte
   sur deux lignes chez les uns et pas chez les autres, et cassé
   l'alignement qui fait la grille. */
.hub-num {
  position: absolute; top: 8px; right: 9px;
  min-width: 20px; height: 20px; padding: 0 6px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px; background: var(--violet); color: #fff;
  font-size: 11px; font-weight: 800;
}
.hub-num[hidden] { display: none; }   /* flex écrase le style de `hidden` */
.hub-num.chaud { background: var(--danger); }

/* ---- News ---- */
.news-bloc { border-top: 1px solid var(--line); padding-top: 16px; margin-top: 18px; }
.news-bloc h3 { font-size: 15px; margin: 0 0 7px; }
.news-bloc p { font-size: 14px; color: var(--soft); margin: 0 0 9px; line-height: 1.65; }
/* Ce qu'il y a À FAIRE se distingue de ce que l'écran EST : sans ce
   liseré, la notice se lit comme une visite guidée et rien n'en reste. */
.news-faire {
  border-left: 2px solid var(--violet); padding-left: 12px;
  color: var(--text) !important;
}
.news-faire em { font-style: normal; font-weight: 700; color: var(--violet); }
.news-tag {
  font-size: 10.5px; font-weight: 800; text-transform: uppercase; letter-spacing: .6px;
  color: var(--muted); border: 1px solid var(--line); border-radius: 999px;
  padding: 2px 8px; margin-left: 6px; vertical-align: middle;
}
.news-place { border-top: 0; padding-top: 0; margin-top: 0; }
/* Portées à TOUS les blocs de la notice, aux mêmes valeurs qu'avant :
   « Votre place » n'est plus le seul à énumérer. Rien ne bouge à l'écran,
   la règle couvre simplement les blocs qui en avaient besoin. */
.news-bloc ul { margin: 0 0 8px; padding-left: 18px; }
.news-bloc li { font-size: 14px; color: var(--soft); line-height: 1.65; margin-bottom: 9px; }
.news-place strong { color: var(--text); }

main { max-width: 900px; margin: 0 auto; padding: 24px 22px 80px; }
h2 { font-size: 20px; letter-spacing: -.3px; margin-bottom: 6px; }
/* Les textes qu'on LIT vraiment passent de 13 à 14 px : notices,
   aides, extraits. Les étiquettes et compteurs, eux, ne bougent pas —
   agrandir ce qu'on ne fait que repérer ne gagne rien et disperse. */
.aide { color: var(--soft); font-size: 14px; margin-bottom: 20px; line-height: 1.65; }
.vide { color: var(--muted); text-align: center; padding: 40px 0; }
/* Le vide de la liste des membres porte désormais une explication ET un
   bouton de sortie : il faut de la place entre les deux, et le texte doit
   rester lisible sur une seule colonne étroite. */
#videMembres { display: flex; flex-direction: column; align-items: center; gap: 14px; }
#videMembres span { max-width: 46ch; line-height: 1.5; }
#videMembres[hidden] { display: none; }   /* flex écrase le style de `hidden` */

.alerte {
  background: rgba(248,113,113,.12); border: 1px solid rgba(248,113,113,.4);
  border-radius: 12px; padding: 12px 16px; margin-bottom: 20px; font-size: 14px;
}
.alerte strong { color: var(--danger); }

/* Bandeau « la file a bougé ». Discret par construction : il annonce du
   travail, il n'en impose pas. L'actualisation reste un geste — voir la
   veille dans admin.js. `[hidden]` explicite : `display:flex` l'emporterait
   sinon sur l'attribut. */
.maj {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: rgba(139,92,246,.10); border: 1px solid rgba(139,92,246,.35);
  border-radius: 12px; padding: 10px 14px; margin-bottom: 16px; font-size: 13.5px;
}
.maj[hidden] { display: none; }

/* ---------- import d'un catalogue fournisseur ---------- */
.shop-map { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; padding: 0 14px; }
.shop-apercu, .shop-rapport, #shopImportResume, #shopImportLancer { margin-left: 14px; margin-right: 14px; }
/* `flex-direction` explicite : la règle générale des `label` les met en
   colonne, et une classe qui ne redit pas le sens la laisse gagner. */
.shop-map-ligne {
  display: flex; flex-direction: row; align-items: center;
  justify-content: space-between; gap: 12px;
  font-size: 13.5px; font-weight: 400;
}
.shop-map-ligne select { min-width: 220px; max-width: 55%; }
.shop-photos {
  display: flex; flex-direction: row; align-items: baseline; flex-wrap: wrap; gap: 8px;
  margin: 14px 0; font-size: 13.5px; font-weight: 400;
}
.shop-photos input { width: auto; }
.shop-apercu { margin: 12px 0; }
.shop-apercu-ligne {
  display: flex; flex-direction: column; gap: 2px;
  border-left: 3px solid var(--line); padding: 6px 0 6px 12px; margin-bottom: 8px;
}
.shop-apercu-ligne span { color: var(--muted); font-size: 12.5px; }
.shop-rapport { margin-top: 14px; font-size: 13.5px; }
.shop-problemes {
  margin: 8px 0 0 18px; color: var(--muted); font-size: 12.5px;
  max-height: 220px; overflow-y: auto;
}
/* Un produit qu'on ne peut pas mettre en vente doit le dire lui-même. */
.shop-sansprix {
  background: rgba(251,191,36,.15); border: 1px solid rgba(251,191,36,.45);
  color: #fbbf24; border-radius: 999px; padding: 1px 9px;
  font-size: 11.5px; font-weight: 700; white-space: nowrap;
}

/* ---------- fiches ---------- */
.liste { display: flex; flex-direction: column; gap: 14px; }
.fiche {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 18px;
}
.fiche.critique { border-color: rgba(248,113,113,.5); }
.fiche-tete { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.fiche-tete .nom { font-weight: 700; }
/* ============================================================
   « EN LIGNE EN CE MOMENT » dans la file de certification.

   Demandée le 19/08/2026. Un appel vidéo se provoque à deux : savoir qui
   est à l'écran décide de l'ordre dans lequel on prend les dossiers.

   ELLE RESPIRE, ET C'EST VOULU. Une pastille fixe se lit comme une
   décoration ; le battement dit « maintenant » — ce qu'aucune couleur ne
   dit toute seule. Trois secondes : assez lent pour ne pas agacer
   quelqu'un qui travaille dans cette page une heure durant.

   ET ELLE S'ARRÊTE pour qui a demandé moins d'animation. Une file de
   modération n'est pas un endroit où l'on impose du mouvement.
   ============================================================ */
.certif-enligne {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  margin-right: 7px; vertical-align: baseline;
  background: var(--ok, #34d399);
  box-shadow: 0 0 0 3px rgba(52, 211, 153, .18);
  animation: certif-souffle 3s ease-in-out infinite;
}
@keyframes certif-souffle {
  0%, 100% { box-shadow: 0 0 0 3px rgba(52, 211, 153, .18); }
  50%      { box-shadow: 0 0 0 6px rgba(52, 211, 153, .05); }
}
@media (prefers-reduced-motion: reduce) {
  .certif-enligne { animation: none; }
}
.fiche-tete .quand { color: var(--muted); font-size: 12.5px; margin-left: auto; }

.tag {
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .6px;
  padding: 3px 9px; border-radius: 999px;
}
.tag.rouge { background: rgba(248,113,113,.16); color: var(--danger); }
.tag.orange { background: rgba(251,191,36,.14); color: var(--warn); }
.tag.gris { background: var(--panel); color: var(--muted); }
.tag.vert { background: rgba(52,211,153,.14); color: var(--ok); }
.tag.violet { background: rgba(192,38,211,.16); color: #e879f9; }

.champ { display: flex; gap: 10px; font-size: 14px; margin-bottom: 4px; }
.champ dt { color: var(--muted); min-width: 120px; }
.champ dd { color: var(--text); }
.detail {
  background: var(--panel); border-radius: 10px; padding: 12px 14px;
  margin: 10px 0; font-size: 14px; color: var(--soft); white-space: pre-wrap;
}
.pharos {
  background: rgba(248,113,113,.1); border-left: 3px solid var(--danger);
  border-radius: 8px; padding: 10px 14px; margin: 10px 0; font-size: 13px;
}
/* Escalade d'un modérateur : ambre et non rouge — c'est une demande
   d'arbitrage, pas une alerte. Le rouge est réservé à ce qui impose une
   démarche hors application, il perdrait son sens à servir aussi ici. */
.escalade {
  background: rgba(251,191,36,.12); border-left: 3px solid #fbbf24;
  border-radius: 8px; padding: 10px 14px; margin: 10px 0; font-size: 13px;
  font-weight: 600;
}

.photo-zone { margin: 12px 0; }
.photo-zone img {
  max-width: 100%; max-height: 460px; border-radius: 12px;
  border: 1px solid var(--line); display: block;
}
.actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 14px; }
.actions .espace { margin-left: auto; }

/* ---------- fiche membre (recherche) ---------- */
.membre-row { display: flex; gap: 14px; align-items: flex-start; }
.membre-av {
  width: 56px; height: 56px; flex: none; border-radius: 12px;
  background: var(--panel) center/cover no-repeat; border: 1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 700; font-size: 18px; overflow: hidden;
}
.membre-av--init { background: linear-gradient(135deg, var(--violet), var(--deep)); }
.membre-main { flex: 1; min-width: 0; }
.membre-nom { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 16px; font-weight: 700; }
/* Le pseudo mène à la fiche du site. Il garde EXACTEMENT l'allure qu'il
   avait — même taille, même graisse, même couleur : la console est un
   outil de travail, pas une page web, et un lien bleu souligné au milieu
   des badges attirerait l'œil sur ce qui n'est pas la décision.
   Le soulignement n'apparaît qu'au survol, avec le curseur : c'est là
   qu'on cherche à savoir si c'est cliquable. */
.membre-lien { color: inherit; text-decoration: none; }
/* `a.` et non `.membre-lien:hover` : la même classe habille aussi les noms
   qu'on ne PEUT pas ouvrir — sans identifiant, ou compte supprimé. Sans
   cette précision ils se soulignaient au survol et promettaient un clic
   qui ne venait jamais. */
a.membre-lien:hover { text-decoration: underline; text-underline-offset: 3px; }
a.membre-lien { cursor: pointer; }
/* Le compte parti reste lisible — il faut bien savoir de qui l'entrée
   parle — mais s'annonce éteint, et le survol dit pourquoi. */
.membre-lien.est-parti { color: var(--muted); text-decoration: line-through;
  text-decoration-thickness: 1px; cursor: help; }
.membre-infos { font-size: 13px; color: var(--soft); margin-top: 4px; }
.membre-infos--sub { color: var(--muted); }

/* ---- La ligne d'état : les dates, sous le nom ----
   `tabular-nums` parce qu'on la lit en balayant une colonne de fiches :
   des chiffres de largeurs différentes font danser les dates d'une ligne
   à l'autre, et l'œil s'arrête sur le désalignement plutôt que sur la
   date. Discrète (couleur `muted`) : elle sert à décider, pas à alerter —
   l'alerte, c'est le badge au-dessus. */
.membre-etat { font-size: 12.5px; color: var(--muted); margin-top: 5px; line-height: 1.5;
  font-variant-numeric: tabular-nums; }

/* ---- « À faire » : la seule ligne qui dit quoi faire ----
   Violet et non rouge : ce n'est pas une alarme de plus — il y en avait
   déjà trois de trop sur cette fiche — c'est une consigne. Le liseré à
   gauche la détache du bloc d'informations sans lui donner le poids d'un
   encadré. Elle ne s'affiche que dans cinq cas ; hors d'eux, rien. */
.membre-afaire { margin-top: 8px; padding: 8px 11px; border-radius: 9px; font-size: 13px;
  line-height: 1.5; color: var(--text);
  background: color-mix(in srgb, var(--violet) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--violet) 30%, transparent);
  border-left: 3px solid var(--violet); }
.membre-afaire b { display: block; font-size: 11px; letter-spacing: .12em; font-weight: 800;
  text-transform: uppercase; color: var(--violet); margin-bottom: 2px; }
@media (max-width: 620px) {
  /* Sur téléphone la fiche est déjà haute : la consigne reste, l'étiquette
     se met en ligne pour ne pas coûter une ligne de plus. */
  .membre-afaire { padding: 7px 10px; font-size: 12.5px; }
}
/* Ville manquante : un avertissement, pas une information de plus. Teinté
   ambre plutôt que rouge — il n'y a pas de faute, seulement quelque chose
   à aller chercher auprès du membre. */
.membre-manque {
  font-size: 12.5px; margin-top: 5px; color: #f0b429;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 9px; border-radius: 999px;
  background: rgba(240, 180, 41, .12);
  border: 1px solid rgba(240, 180, 41, .3);
}
/* Suppression confirmée : rouge, et non ambre. L'ambre dit « il manque
   quelque chose à aller chercher » ; ici il n'y a plus rien à chercher,
   la personne est partie. La distinction doit se lire sans lire. */
.membre-parti {
  font-size: 12.5px; margin-top: 5px; color: #f87171;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 9px; border-radius: 999px;
  background: rgba(248, 113, 113, .12);
  border: 1px solid rgba(248, 113, 113, .35);
}

/* Compte de soirées d'un ORGANISATEUR VÉRIFIÉ : orange à zéro, vert dès la
   première. Les membres ordinaires gardent le gris de la ligne — ils sont à
   zéro par définition, et les colorer en orange peindrait toute la liste
   d'une alerte qui ne veut rien dire.

   Les couleurs viennent des variables de la console (--warn, --ok) :
   inventer un orange de plus le ferait diverger du reste à la première
   retouche. Pastille cerclée et non pleine : c'est une information à lire,
   pas un bouton à cliquer. */
.soirees-cpt { font-weight: 600; }
.soirees-cpt--zero,
.soirees-cpt--ok {
  padding: 1px 8px; border-radius: 999px; font-size: 12px;
  border: 1px solid currentColor;
}
.soirees-cpt--zero { color: var(--warn); background: rgba(251, 191, 36, .12); }
.soirees-cpt--ok   { color: var(--ok);   background: rgba(52, 211, 153, .12); }
.membre-mail { color: var(--muted); word-break: break-all; }

select {
  font-family: inherit; font-size: 14px; color: var(--text);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; padding: 9px 12px;
}

/* ---------- modale ---------- */
.modal-fond {
  position: fixed; inset: 0; z-index: 50; padding: 20px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(6,4,12,.75); backdrop-filter: blur(4px);
}
.modal {
  width: 100%; max-width: 460px; background: var(--card);
  border: 1px solid var(--line); border-radius: 18px; padding: 24px;
  display: flex; flex-direction: column; gap: 14px;
}
.modal h3 { font-size: 18px; letter-spacing: -.3px; }
.modal p { font-size: 14px; color: var(--soft); }
/* Le choix de la durée, dans la modale de sanction. Les trois options
   côte à côte et de même poids : présenter « définitif » comme le cas
   normal et les suspensions comme des variantes ferait pencher la main
   du mauvais côté.

   `[hidden]` explicite — la boîte est en flex, et le piège maison veut
   qu'un flex l'emporte sinon sur l'attribut. */
.modal-duree {
  display: flex; gap: 16px; flex-wrap: wrap; align-items: center;
  border: 1px solid var(--line); border-radius: 12px; padding: 10px 14px;
}
.modal-duree[hidden] { display: none; }
.modal-duree legend { padding: 0 6px; font-size: 12px; color: var(--soft);
  text-transform: uppercase; letter-spacing: .6px; }
.modal-duree label { display: flex; align-items: center; gap: 7px;
  font-size: 14px; cursor: none; }
.modal-duree input { accent-color: var(--violet); width: 16px; height: 16px; }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
.oblig { color: var(--danger); font-weight: 400; font-size: 11.5px; }

/* Précisions sous un titre de modale : qui l'on modifie, ce que le
   changement entraîne. Le paragraphe des effets se colore quand il
   annonce une conséquence, pour qu'on ne le lise pas en diagonale. */
.modal-sous { font-size: 13px; color: var(--muted); line-height: 1.5; }
.modal-sous.attention { color: var(--violet); font-weight: 600; }
/* `input, textarea { width: 100% }` s'applique aussi aux cases à cocher :
   la case gardait sa taille mais sa boîte occupait toute la ligne, et le
   libellé se retrouvait plaqué à droite, à cent pixels de ce qu'il
   nomme. */
.ligne-case { flex-direction: row; align-items: center; justify-content: flex-start;
  gap: 8px; font-weight: 500; }
.ligne-case input[type="checkbox"] { width: auto; flex: none; margin: 0; }
/* L'icône du calendrier est noire par défaut : invisible sur fond sombre. */
.modal input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1) opacity(.55); cursor: pointer;
}
.modal input[type="date"]:hover::-webkit-calendar-picker-indicator { filter: invert(1) opacity(1); }

/* ---------- La salle de certification, ouverte depuis la console ----------
   Plein écran : on est en train de regarder quelqu'un, pas de consulter
   une liste. Le bouton de sortie est en HAUT À GAUCHE, après deux essais
   ratés : en bas il couvrait la barre de la visio — on coupait son micro
   en croyant raccrocher — et à droite il se posait sur la vignette de sa
   propre caméra, que Jitsi place précisément là. Le coin haut gauche est
   vide depuis que le logo a été retiré. */
.appel-salle { position: fixed; inset: 0; z-index: 300; background: #000; }
.appel-cadre { width: 100%; height: 100%; border: 0; display: block; }
.appel-raccrocher {
  position: fixed; top: 16px; left: 16px; z-index: 301;
  padding: 11px 24px; border: none; border-radius: 999px; cursor: pointer;
  font-family: inherit; font-size: 14px; font-weight: 700; color: #fff;
  background: linear-gradient(135deg, #ef4444, #b91c1c);
  box-shadow: 0 10px 30px rgba(0,0,0,.45);
}
.appel-raccrocher:hover { filter: brightness(1.08); }

/* La décision, EN BAS À GAUCHE : c'est le dernier coin libre — sortie en
   haut à gauche, sa propre caméra en haut à droite, barre de la visio en
   bas au centre. SAUF SUR UN TÉLÉPHONE, où ce coin n'existe plus : voir
   la règle mobile juste après. */
.appel-decision {
  position: fixed; left: 16px; bottom: 16px; z-index: 301;
  display: flex; flex-direction: column; gap: 8px; align-items: flex-start;
  padding: 14px 16px; border-radius: 16px; max-width: 300px;
  background: rgba(6,4,12,.86); border: 1px solid var(--line);
  backdrop-filter: blur(6px);
}
.appel-decision .btn { width: 100%; justify-content: center; }
.appel-memo { margin: 0 0 2px; font-size: 12.5px; line-height: 1.45; color: var(--soft); }
.appel-memo span { color: var(--muted); font-size: 11.5px; }

/* SUR UN TÉLÉPHONE, LE BAS N'EST PLUS UN COIN LIBRE — 04/09/2026.

   Signalé depuis un mobile : « les boutons me cachent le bouton du son ».
   Le raisonnement ci-dessus tient sur un écran large, où le panneau
   occupe 300 px sur 1400 et laisse le centre à la barre de la visio. Sur
   un téléphone, ces 300 px FONT la largeur de l'écran : le panneau couvre
   toute la bande du bas, et avec elle les trois boutons de Jitsi —
   micro, RÉGLAGES, raccrocher.

   Celui du milieu est le bouton du son, celui qui répare la panne la
   plus banale d'une visio — mauvaise sortie audio, casque débranché
   (voir social/salle-visio.ts, 02/09/2026). On l'avait remis exprès, et
   la console le recouvrait.

   ON NE DEVINE PAS UNE MARGE. Remonter le panneau « juste au-dessus » de
   la barre de Jitsi demanderait d'en connaître la hauteur : elle vit
   dans un cadre d'un autre domaine, que rien ne nous laisse mesurer, et
   elle changera sans nous prévenir. On quitte donc la bande du bas au
   lieu de la frôler.

   Le panneau passe SOUS le bouton de sortie, en haut à gauche. Il mord
   sur le bas de sa propre vignette de caméra, que Jitsi place en haut à
   droite : c'est ce qu'on regarde le moins pendant qu'on vérifie le
   visage de quelqu'un d'autre. */
@media (max-width: 700px) {
  .appel-decision {
    top: 64px; bottom: auto; left: 12px; right: 12px;
    max-width: 300px; padding: 12px 14px; gap: 6px;
  }
}

/* La fenêtre de motif doit passer AU-DESSUS de l'appel : sans cette
   ligne, cliquer « Refuser » ouvrait un dialogue invisible derrière la
   vidéo, et le bouton paraissait mort. */
body.appel-en-cours .modal-fond { z-index: 320; }

.err { color: var(--danger); font-size: 13px; font-weight: 600; }
.err[hidden], [hidden] { display: none !important; }

.toast {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 999px; padding: 11px 22px; font-size: 14px; z-index: 60;
}

@media (max-width: 720px) {
  .top { gap: 12px; }
  .tabs { order: 3; width: 100%; margin-left: 0; overflow-x: auto; }
  .me { margin-left: auto; }
}

/* ---------- Membres : stats, outils, filtres, tri ---------- */
.membres-stats { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 14px; }
.stat-tile { display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  padding: 10px 16px; cursor: pointer; transition: all .15s; }
.stat-tile:hover { border-color: var(--violet); }
.stat-tile.is-on { border-color: transparent; background: linear-gradient(135deg, var(--violet), var(--deep)); }
.stat-val { font-size: 20px; font-weight: 700; color: var(--text); }
.stat-lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .4px; color: var(--muted); }
.stat-tile.is-on .stat-val, .stat-tile.is-on .stat-lbl { color: #fff; }
.stat-matched { font-size: 12.5px; color: var(--muted); margin-left: 4px; }
.membres-outils { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.membres-resume { font-size: 13px; color: var(--muted); margin-bottom: 12px; }
/* codes couleur des lignes membres */
.membre-fiche--banni { border-color: rgba(248,113,113,.5); background: rgba(248,113,113,.06); }
.membre-fiche--invisible { opacity: .55; }
.membre-fiche--noncert { border-style: dashed; }
.membres-filtres { display: flex; gap: 8px; flex-wrap: wrap; margin: 12px 0; }
.membres-tri { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 18px; }
.membres-tri label { flex-direction: row; align-items: center; gap: 8px; font-size: 13px; color: var(--soft); }
.membres-tri select, .membres-tri input { width: auto; min-width: 130px; }

/* ---------- Multimédias envoyés ---------- */
.media-filtres { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 18px; }
.chip {
  font-family: inherit; font-size: 13px; font-weight: 600; color: var(--soft);
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 999px; padding: 7px 15px; cursor: pointer; transition: all .15s;
}
.chip:hover { border-color: var(--violet); }
.chip.is-on { background: linear-gradient(135deg, var(--violet), var(--deep)); border-color: transparent; color: #fff; }
/* Publication déjà couverte par quelqu'un du staff. Verte et discrète :
   elle sert à PASSER, pas à attirer l'œil — c'est ce qui n'a pas de
   pastille qui demande l'attention. D'où le fond très pâle et l'absence
   de gras, à l'inverse de .chip-danger. */
.chip-vue {
  color: #4ade80; border-color: rgba(74,222,128,.35);
  background: rgba(74,222,128,.08); font-weight: 500; cursor: default;
}
.chip-vue:hover { border-color: rgba(74,222,128,.35); }

.chip-danger { color: var(--danger); border-color: rgba(248,113,113,.4); }
.chip-danger.is-on { background: var(--danger); border-color: transparent; color: #fff; }

/* Dossier des comptes mis à l'écart. Détaché des filtres courants par sa
   marge et son trait, et volontairement discret : on ne veut pas que
   l'œil y retombe sans l'avoir cherché. */
.chip-dossier {
  margin-left: auto; color: var(--soft); opacity: .75;
  border-style: dashed; border-color: var(--line);
}
.chip-dossier:hover { opacity: 1; }
.chip-dossier.is-on {
  opacity: 1; background: var(--panel); color: var(--text);
  border-style: solid; border-color: var(--violet);
}

.media-grille {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 16px;
}
.media-carte {
  background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  overflow: hidden; display: flex; flex-direction: column;
}
.media-vue {
  display: block; aspect-ratio: 1 / 1; background: #000; overflow: hidden;
}
.media-vue img, .media-vue video { width: 100%; height: 100%; object-fit: cover; display: block; }
.media-meta { padding: 10px 12px 4px; display: flex; flex-direction: column; gap: 4px; }
.media-badge {
  align-self: flex-start; font-size: 10.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .4px; color: var(--violet); background: rgba(168,85,247,.14);
  padding: 2px 8px; border-radius: 999px;
}
.media-badge.vid { color: var(--warn); background: rgba(251,191,36,.14); }
.media-who { font-size: 12.5px; color: var(--muted); }
.media-del { margin: 8px 12px 12px; }

@media (max-width: 720px) {
  .media-grille { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
}

/* ---- Multimédias : dossiers par membre ---- */
.media-recherche { margin-bottom: 16px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.media-recherche input {
  flex: 1; min-width: 200px; max-width: 340px; padding: 10px 14px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--card); color: var(--text); font-size: 14px;
}
.media-recherche input:focus, .media-recherche select:focus { outline: none; border-color: var(--violet); }
.media-recherche select {
  padding: 10px 12px; border-radius: 10px; border: 1px solid var(--line);
  background: var(--card); color: var(--text); font-size: 14px;
}
.dossier-grille {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 14px;
}
.dossier-carte {
  display: flex; flex-direction: column; align-items: center; gap: 8px; text-align: center;
  padding: 18px 12px; background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; cursor: pointer; color: var(--text);
  transition: border-color .15s, transform .15s;
}
.dossier-carte:hover { border-color: var(--violet); transform: translateY(-2px); }
.dossier-av {
  width: 64px; height: 64px; border-radius: 50%; flex: none; display: flex;
  align-items: center; justify-content: center; background: rgba(168,85,247,.14);
  background-size: cover; background-position: center;
}
.dossier-av svg { width: 30px; height: 30px; stroke: var(--violet); fill: none; stroke-width: 1.6; }
.dossier-av.has-photo { border: 1px solid var(--line); }
.dossier-nom { font-size: 14px; font-weight: 700; }
.dossier-compte { font-size: 12px; color: var(--muted); }

/* ---- Multimédias : contenu d'un membre ---- */
.media-fil { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; }
.media-membre-titre { font-size: 17px; font-weight: 700; }
.media-sous {
  font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px;
  color: var(--violet); margin: 20px 0 12px;
}

/* Annonce (diffusion en notification à tous les membres)

   Le fond était à #fff avec `color: inherit` : dans une console sombre, la
   couleur héritée est --text, un blanc cassé. On écrivait donc en blanc sur
   blanc. La règle générique des textarea, juste au-dessus, faisait déjà
   correctement le travail — on s'aligne dessus au lieu de la contredire. */
.annonce-textarea {
  width: 100%; box-sizing: border-box; padding: 12px 14px; border-radius: 10px;
  border: 1px solid rgba(168, 85, 247, .3);
  background: var(--panel); color: var(--text);
  font: inherit; font-size: 14px; line-height: 1.5; resize: vertical; min-height: 96px;
}
.annonce-textarea:focus { outline: none; border-color: var(--violet); }
.annonce-barre {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 12px; flex-wrap: wrap;
}
.annonce-titre {
  font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .5px;
  color: var(--violet); margin: 26px 0 12px;
}
.annonce-corps { font-size: 14px; line-height: 1.5; white-space: pre-wrap; margin-bottom: 8px; }
.annonce-cibles { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; margin-bottom: 10px; }
.annonce-cibles label { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; cursor: pointer; }

/* ============ Boutique ============ */
.shop-bar { display: flex; align-items: flex-end; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.shop-ship { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--soft); }
.shop-ship input { width: 120px; }
.shop-new { margin-bottom: 20px; border: 1px solid var(--line); border-radius: 12px; background: var(--card); }
.shop-new summary { cursor: pointer; padding: 12px 14px; font-weight: 600; }
.shop-form { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 0 14px 14px; }
.shop-form label, .shop-champ { display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--soft); }
.shop-form label:nth-child(4), .shop-champ:nth-child(4) { grid-column: 1 / -1; }
.shop-form > .btn { grid-column: 1 / -1; justify-self: start; }
.shop-fiche { border: 1px solid var(--line); }
.shop-fiche--off { opacity: .62; }
.shop-tete { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
.shop-nom { font-size: 16px; }
.shop-actif { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--soft); cursor: pointer; }
.shop-actif input { width: auto; }
.shop-sup { margin-left: auto; }
.shop-photos { display: flex; flex-wrap: wrap; gap: 10px; margin: 14px 0; }
.shop-photo { position: relative; width: 84px; height: 84px; border-radius: 10px; overflow: hidden; border: 1px solid var(--line); }
.shop-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shop-photo-x {
  position: absolute; top: 3px; right: 3px; width: 22px; height: 22px; padding: 0;
  border-radius: 50%; border: none; background: rgba(0,0,0,.62); color: #fff; font-size: 15px; line-height: 22px; cursor: pointer;
}
.shop-photo-add {
  width: 84px; height: 84px; border-radius: 10px; border: 1px dashed var(--line);
  display: flex; align-items: center; justify-content: center; font-size: 12px; color: var(--muted); cursor: pointer; text-align: center;
}
.shop-photo-add.is-busy { opacity: .5; }
.shop-vars { border-top: 1px solid var(--line); padding-top: 12px; }
.shop-vars-titre { font-size: 12.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px; color: var(--violet); margin-bottom: 8px; }
.shop-var { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; font-size: 13px; color: var(--muted); }
.shop-var-lbl { width: 160px; }
.shop-var-stk { width: 84px; }
.shop-var .btn { padding: 6px 10px; }
.shop-var-x { width: 34px; padding: 6px 0; }
.shop-var--add { margin-top: 4px; }

/* ---- Ouverture de la vente ---- */
.shop-vente-etat { font-size: 12px; font-weight: 700; color: var(--danger); }
.shop-vente-etat.is-open { color: var(--ok, #34d399); }

/* ---- Commandes ---- */
.shop-cmd-filtres { display: flex; gap: 8px; flex-wrap: wrap; margin: 12px 0; }
.shop-cmd-fiche { border-left: 3px solid var(--line); }
.shop-cmd--pending   { border-left-color: #fbbf24; }
.shop-cmd--paid      { border-left-color: #34d399; }
.shop-cmd--shipped   { border-left-color: var(--violet); }
.shop-cmd--cancelled { border-left-color: var(--danger); opacity: .6; }
.shop-cmd-tete {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px;
  font-variant-numeric: tabular-nums;
}
.shop-cmd-montant { margin-left: auto; font-weight: 800; }
.shop-cmd-qui { font-size: 13px; color: var(--muted); margin-bottom: 8px; }
.shop-cmd-ligne { font-size: 13px; color: var(--soft); padding: 1px 0; }
/* Adresse recopiée telle quelle sur l'étiquette : les retours à la ligne
   doivent être respectés, et le texte sélectionnable d'un bloc. */
.shop-cmd-adresse {
  white-space: pre-line; font-size: 13px; line-height: 1.5; margin: 10px 0;
  padding: 10px 12px; border-radius: 10px; background: var(--panel); color: var(--text);
  user-select: all;
}
.shop-cmd-actions { display: flex; gap: 8px; flex-wrap: wrap; }
@media (max-width: 560px) {
  .shop-form { grid-template-columns: 1fr; }
  .shop-var { flex-wrap: wrap; }
  .shop-var-lbl { width: 100%; }
}

/* ============================================================
   « À traiter » — l'écran d'arrivée.

   Une ligne = une file de travail = un clic pour y aller. Le nombre est
   la première chose lue, d'où sa taille : on ouvre cette console pour
   savoir COMBIEN il reste, pas pour lire des intitulés.
   ============================================================ */
.atraiter { display: flex; flex-direction: column; gap: 10px; }
.atraiter-ligne {
  display: flex; align-items: center; gap: 16px; width: 100%;
  padding: 16px 18px; border-radius: 14px; cursor: pointer;
  background: var(--card); border: 1px solid var(--line);
  color: inherit; font: inherit; text-align: left;
  transition: border-color .15s, transform .15s;
}
.atraiter-ligne:hover { border-color: var(--violet); transform: translateX(2px); }
/* Un signalement critique masque déjà la publication : il ne se confond
   pas avec le reste de la file. */
.atraiter-ligne--alerte { border-color: #f43f5e; }
.atraiter-nb {
  flex: none; min-width: 46px; text-align: center;
  font-size: 26px; font-weight: 800; color: var(--violet);
}
.atraiter-ligne--alerte .atraiter-nb { color: #f43f5e; }
.atraiter-txt { flex: 1; display: flex; flex-direction: column; gap: 2px; }
.atraiter-titre { font-size: 15.5px; font-weight: 600; }
.atraiter-note { font-size: 12.5px; color: var(--muted); }
.atraiter-fleche { flex: none; font-size: 18px; color: var(--muted); }
.atraiter-ligne:hover .atraiter-fleche { color: var(--violet); }

/* ============================================================
   Replis (<details>) — outils d'envoi, filtres secondaires, tri.

   Un <details> plutôt qu'un menu en JavaScript : le navigateur gère
   l'ouverture, le clavier et l'accessibilité tout seul, et rien ne peut
   rester coincé ouvert par une erreur de script.
   ============================================================ */
.menu-repli { position: relative; display: inline-block; }
.menu-repli > summary { list-style: none; cursor: pointer; user-select: none; }
.menu-repli > summary::-webkit-details-marker { display: none; }
.menu-repli-corps {
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0;
  display: flex; flex-direction: column; gap: 8px; padding: 12px;
  min-width: 260px; border-radius: 14px;
  background: var(--card); border: 1px solid var(--line);
  box-shadow: 0 16px 40px rgba(0,0,0,.5);
}

/* ============================================================
   LE MENU « ⋯ » D'UNE FICHE, EN COLONNES SUR GRAND ÉCRAN —
   26/08/2026, demandé par le propriétaire.

   Onze entrées empilées font une colonne plus haute que l'écran : on
   descend pour lire, on remonte pour choisir, et les dernières se
   perdent sous le bord. En téléphone c'est la seule disposition
   possible et elle reste ; à partir de 720 px il y a la place pour
   deux colonnes, et trois au-delà de 1100.

   SA POSITION EXACTE EST CALCULÉE PAR LE SCRIPT. Large de près de 600 px,
   il ne tient pas dans la place disponible d'un côté ou de l'autre dès
   qu'on n'est pas au milieu de l'écran : choisir un bord ne fait que
   déplacer le débordement — mesuré, 186 px de l'autre côté. admin.js le
   glisse donc juste assez pour qu'il rentre, et pas plus, pour qu'il
   reste près de son bouton. Le défaut avait déjà été payé sur les
   panneaux de filtres, quelques lignes plus bas.
   ============================================================ */
@media (min-width: 720px) {
  .fiche-actions-sec.menu-repli-corps {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px; left: 0; right: auto;
    width: max-content; max-width: min(600px, calc(100vw - 32px));
  }
  /* La position exacte est posée par admin.js à l'ouverture : le panneau
     est plus large que la place d'un côté OU DE L'AUTRE, et choisir un
     bord ne fait que déplacer le débordement. */
  /* Les boutons ne se comprimant plus à la largeur du texte, ils
     s'alignent : une grille de pastilles de tailles différentes se lit
     comme un désordre, pas comme un menu. */
  .fiche-actions-sec.menu-repli-corps > .btn { width: 100%; text-align: center; }
}
/* DEUX COLONNES, ET PAS TROIS — demandé le 26/08/2026 après essai à
   l'écran. J'avais ajouté un troisième palier au-delà de 1100 px : le
   panneau y faisait 863 px de large, plus large que la fiche qu'il
   recouvrait, et l'on ne savait plus de quel membre il parlait. Deux
   colonnes suffisent à ramener les dix entrées de 565 à 263 px de haut,
   ce qui était le but. */
/* Les panneaux LARGES (filtres, tri) ne flottent pas : ancrés au bouton qui
   les ouvre, ils sortaient de l'écran par la droite — constaté à l'écran, les
   deux dernières colonnes étaient coupées. Ils se déplient donc sous la
   ligne, sur toute la largeur. Le panneau d'envoi, lui, est étroit et reste
   un menu flottant classique. */
/* Panneaux larges : ancrés sur la RANGÉE de filtres, pas sur le bouton qui
   les ouvre. Ancrés au bouton, ils débordaient — par la droite quand le
   point d'ancrage était à gauche, par la gauche quand il passait à droite.
   Les deux essais se voyaient à l'écran. Calés sur toute la largeur de la
   rangée, ils ne peuvent plus sortir, et la hauteur de l'en-tête ne bouge
   pas puisqu'ils se superposent à la liste. */
.membres-filtres { position: relative; }
#menuFiltres, .membres-tri-repli { position: static; }
#menuFiltres > .menu-repli-corps,
.membres-tri-repli > .membres-tri {
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0; right: 0;
  width: auto; min-width: 0;
  padding: 16px; border-radius: 14px;
  background: var(--card); border: 1px solid var(--line);
  box-shadow: 0 16px 40px rgba(0,0,0,.5);
}
.membres-tri-repli > .membres-tri { display: flex; flex-wrap: wrap; gap: 12px; }
.filtres-groupes {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
}
/* Le conteneur des filtres devient la référence de mise en page : les
   panneaux dépliés s'insèrent APRÈS la rangée de puces, pas dedans. */
.membres-filtres { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
#menuFiltres > .menu-repli-corps, .membres-tri-repli > .membres-tri { order: 99; }
.filtres-groupe { display: flex; flex-wrap: wrap; gap: 6px; align-content: start; }
.filtres-titre {
  width: 100%; font-size: 11.5px; text-transform: uppercase;
  letter-spacing: .5px; color: var(--muted); margin-bottom: 2px;
}
/* Recherche et outils sur une ligne. */
.membres-barre { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.membres-barre .membre-search { flex: 1; min-width: 240px; margin: 0; }
.membres-tri-repli { margin: 10px 0 4px; }
.membres-tri-repli > summary { display: inline-block; }

/* ---- Actions d'une fiche membre : trois poids, pas un seul ----
   « Certifier » et « Blacklister » avaient exactement la même apparence.
   Les gestes courants restent visibles, les rares passent dans un menu,
   et ce qui engage durablement un compte est séparé par un trait. */
.fiche-actions-sec { min-width: 230px; }
.fiche-actions-danger {
  display: flex; gap: 8px; flex-wrap: wrap;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line);
}

/* ---- Messages privés signalés automatiquement (migration 058) ----
   L'extrait est cité, et se voit comme une citation : barre à gauche,
   italique, fond légèrement décalé. Le staff doit lire d'un coup d'œil que
   ces mots sont ceux d'un membre, pas une phrase de la console — se
   tromper là-dessus, sur de la correspondance privée, serait grave. */
.msgflag-regles { margin: 8px 0 10px; font-size: 12.5px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .5px; font-weight: 700; }
.msgflag-extrait {
  margin: 0 0 10px; padding: 10px 14px; border-left: 3px solid var(--violet);
  background: rgba(255,255,255,.03); border-radius: 0 10px 10px 0;
  font-size: 14.5px; line-height: 1.6; font-style: italic;
  overflow-wrap: anywhere;
}
.msgflag-recidive { margin: 0 0 10px; font-size: 13px; color: var(--danger); font-weight: 600; }
/* « Masquée automatiquement » se lit AVANT le texte, et doit se voir sans
   être cherchée : on ne juge pas de la même façon un propos en ligne et un
   propos que le site a déjà retiré sans que personne l'ait décidé. */
.filflag-masque {
  margin: 8px 0 0; padding: 7px 12px; border-radius: 9px;
  background: rgba(239,68,68,.12); border: 1px solid rgba(239,68,68,.35);
  color: var(--danger); font-size: 13px; font-weight: 700;
}
.fiche-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
/* `[hidden]` ne suffit pas sur un élément en flex : la classe l'emporte
   sur l'attribut. Trois bandeaux fantômes sur ce site sont nés de cet
   oubli — la règle est écrite d'avance, même si rien ne masque celui-ci
   aujourd'hui. */
.fiche-actions[hidden] { display: none; }

/* ---- Suggestions des membres (migration 059) ---- */
.sugg-autres { margin: 0 0 10px; font-size: 13px; color: var(--muted); }
.sugg-reponse {
  width: 100%; margin: 4px 0 0; padding: 10px 14px; border-radius: 12px;
  background: rgba(255,255,255,.03); border: 1px solid var(--line);
  color: inherit; font: inherit; font-size: 14.5px; line-height: 1.6;
  resize: vertical; min-height: 74px;
}
.sugg-reponse:focus { outline: none; border-color: var(--violet); }

/* La fenêtre de lecture d'une conversation. Volontairement sobre : on ne
   décore pas l'endroit où l'on lit la correspondance de deux personnes. */
.conv-fond {
  position: fixed; inset: 0; z-index: 100; display: flex;
  align-items: center; justify-content: center; padding: 24px;
  background: rgba(4, 2, 10, .78); backdrop-filter: blur(3px);
}
.conv-fond[hidden] { display: none; }
.conv-boite {
  display: flex; flex-direction: column; width: 100%; max-width: 720px;
  max-height: 84vh; border-radius: 18px; overflow: hidden;
  background: var(--card); border: 1px solid var(--line);
}
.conv-boite[hidden] { display: none; }
.conv-tete {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  padding: 16px 20px; border-bottom: 1px solid var(--line);
}
.conv-tete h3 { font-size: 16px; margin: 0; }
.conv-corps { flex: 1; overflow-y: auto; padding: 16px 20px; }
.conv-msg { padding: 10px 0; border-bottom: 1px solid rgba(255,255,255,.05); }
.conv-msg:last-child { border-bottom: 0; }
/* Le message qui a déclenché l'alerte, repérable sans le chercher : dans
   une conversation de deux cents lignes, c'est la seule qu'on vient lire. */
.conv-msg.est-signale {
  border-left: 3px solid var(--violet); padding-left: 12px; margin-left: -15px;
  background: rgba(168, 85, 247, .07);
}
.conv-msg-tete { margin: 0 0 4px; font-size: 12px; color: var(--muted); }
.conv-msg-corps { margin: 0; font-size: 14.5px; line-height: 1.6; white-space: pre-wrap;
  overflow-wrap: anywhere; }
.conv-pied {
  margin: 0; padding: 12px 20px; border-top: 1px solid var(--line);
  font-size: 12.5px; color: var(--muted);
}

/* ---- Inscriptions du jour : une NOUVELLE, pas une file ----
   En tête de l'écran d'arrivée, mais dans une tonalité à part : ni le
   chiffre géant des files, ni la bordure rouge des alertes. On raconte qui
   vient d'arriver, on ne réclame rien. */
/* De l'air sous le « À traiter » : collé, le titre du bloc passait pour
   un sous-titre de la page plutôt que pour une section à lui. */
.atraiter-jour { margin: 18px 0 26px; padding-bottom: 22px; border-bottom: 1px solid var(--line); }
/* `[hidden]` seul ne suffirait pas si ce bloc passait un jour en flex :
   une classe d'affichage l'emporte sur l'attribut. Le piège a déjà coûté
   trois bandeaux fantômes sur ce site — la règle est écrite d'avance. */
.atraiter-jour[hidden] { display: none; }
.atraiter-jour h3 { font-size: 16px; margin: 0 0 12px; }
.jour-liste { display: flex; flex-direction: column; gap: 8px; }
.jour-liste[hidden] { display: none; }
.jour-ligne {
  display: flex; align-items: center; gap: 13px; width: 100%;
  padding: 10px 14px; border-radius: 12px; cursor: pointer;
  background: var(--card); border: 1px solid var(--line);
  color: inherit; font: inherit; text-align: left;
  transition: border-color .15s, transform .15s;
}
.jour-ligne:hover { border-color: var(--violet); transform: translateX(2px); }
/* La ligne n'est plus un <button> depuis le 22/08/2026 (elle contient un
   lien, ce qu'un bouton n'a pas le droit de faire) : le focus clavier
   qu'il donnait gratuitement se redessine ici, sans quoi on tabulerait
   dans la liste sans jamais voir où l'on est. */
.jour-ligne:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.jour-avatar {
  flex: none; width: 38px; height: 38px; border-radius: 50%; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.jour-avatar img { width: 100%; height: 100%; object-fit: cover; }
.jour-avatar.sans-photo {
  background: linear-gradient(135deg, #7c3aed, #4c1d95);
  font-weight: 800; font-size: 16px; color: #fff;
}
.jour-txt { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.jour-pseudo { font-size: 15px; font-weight: 600; }
.jour-meta { font-size: 12.5px; color: var(--muted); }
.jour-etat {
  flex: none; padding: 3px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; color: var(--muted);
  border: 1px solid var(--line);
}
/* Orange et non rouge : rien n'est en faute, le membre n'a simplement pas
   encore cliqué son lien. Le rouge est réservé à ce qui a mal tourné. */
.jour-etat--attente { color: #fbbf24; border-color: rgba(251, 191, 36, .4); }
.jour-etat--ok { color: #34d399; border-color: rgba(52, 211, 153, .4); }
/* Sur un écran étroit, la pastille passe sous le pseudo plutôt que de
   comprimer le nom à deux lettres. */
@media (max-width: 560px) {
  .jour-ligne { flex-wrap: wrap; }
  .jour-etat { margin-left: 51px; }
}
@media (prefers-reduced-motion: reduce) {
  .jour-ligne { transition: none; }
  .jour-ligne:hover { transform: none; }
}

/* ---- Profils à compléter : du SUIVI, pas une décision ----
   Volontairement moins appuyé que les files au-dessus. Ces membres
   n'attendent rien de l'administration ; leur donner le même poids visuel
   qu'un signalement critique reviendrait à crier pour tout. */
.atraiter-suivi { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line); }
.atraiter-suivi h3 { font-size: 16px; margin: 0 0 6px; }
.atraiter-suivi .aide { margin: 0 0 14px; max-width: 62ch; }
.atraiter-ligne--suivi { padding: 12px 16px; }
.atraiter-ligne--suivi .atraiter-nb { font-size: 21px; color: var(--muted); }
.atraiter-ligne--suivi:hover .atraiter-nb { color: var(--violet); }

/* --- Publications : relecture de veille ---
   Le texte est rendu tel qu'il a été écrit, retours à la ligne compris :
   relire un texte reformaté, c'est relire autre chose que ce qu'ont lu les
   membres. `word-break` protège des collages sans espace, qui feraient
   déborder la fiche. */
.pub-texte { white-space: pre-wrap; word-break: break-word; line-height: 1.55;
  margin-bottom: 12px; }
/* Une publication déjà masquée se distingue au premier coup d'œil : sans
   cela, on la relit comme si elle était encore au fil. */
.fiche.est-masquee { opacity: .6; border-color: rgba(248,113,113,.35); }
#pubRecherche { width: 100%; max-width: 420px; margin-bottom: 14px; }

/* ---- commentaires, sous leur publication ----
   En retrait et sur un fond plus sombre : ils appartiennent a la carte,
   ils ne sont pas des fiches de meme rang. Le trait vertical fait le
   lien visuel avec ce a quoi ils repondent. */
.pub-coms { margin: 14px 0 0 0; padding-left: 14px; border-left: 2px solid var(--line); }
.pub-com { background: rgba(255,255,255,.02); border-radius: 10px;
  padding: 10px 12px; margin-bottom: 8px; }
.pub-com:last-child { margin-bottom: 0; }
.pub-com.est-masquee { opacity: .65; border-left: 2px solid var(--warn); }
.pub-com-tete { display: flex; align-items: baseline; gap: 10px; font-size: 13.5px; }
.pub-com-tete .quand { color: var(--muted); font-size: 12px; }
.pub-com-masque { color: var(--warn); font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .5px; }
.pub-com-texte { margin: 4px 0 8px; font-size: 14px; color: var(--soft); }
/* Le commentaire signalé, en toutes lettres dans la file : on ne décide
   pas d'un retrait sur la foi d'un motif, on lit ce qui a été écrit. */
.com-signale { border-left: 3px solid var(--warn); padding-left: 12px; font-style: italic; }

/* Bouton « J'ai tout vu jusqu'ici » : sous la liste, à sa place — on le
   trouve quand on a fini de parcourir, pas avant. */
.pub-vues-barre { margin-top: 18px; text-align: center; }
.pub-vues-barre[hidden] { display: none; }

/* ---- Parrains : le classement de ceux qui font venir du monde ----
   Un rang lisible, le pseudonyme, puis de quoi VÉRIFIER le chiffre —
   « 7 filleuls » sans les noms ne se contrôle pas et ne se raconte pas. */
.parrains-resume {
  font-size: 13.5px; color: var(--soft); margin: 0 0 16px;
  padding: 10px 14px; border-radius: 12px;
  background: rgba(168, 85, 247, .08);
  border: 1px solid rgba(168, 85, 247, .2);
}
.parrains-resume[hidden] { display: none; }
/* ---- Les ambassadeurs ----

   MÊME FORME QUE LES PARRAINS, sans le rang : ce n'est pas un
   classement. Les zéros y figurent, et une ligne qui porterait un
   numéro « 14 » se lirait comme un mauvais score alors qu'elle dit
   seulement « celui-ci n'a encore amené personne ».

   Les classes sont déclarées ICI plutôt que laissées au hasard : la
   console a déjà livré un écran où le pseudonyme et son rôle se
   touchaient, faute de style pour la classe que le script posait. */
.amb-ligne {
  padding: 13px 15px; border-radius: 13px; margin-bottom: 9px;
  background: var(--surface); border: 1px solid var(--border);
}
.amb-nom {
  display: flex; align-items: center; gap: 8px;
  font-size: 15px; font-weight: 700; margin-bottom: 5px;
}
.amb-codes { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.amb-chiffres { font-size: 13px; color: var(--soft); }
.amb-chiffres strong { font-size: 15px; color: var(--text); }

.parrain {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 13px 15px; border-radius: 13px; margin-bottom: 9px;
  background: var(--surface); border: 1px solid var(--border);
}
/* La première ligne se distingue : c'est celle qu'on vient chercher. */
.parrain--tete {
  border-color: rgba(240, 180, 41, .45);
  background: linear-gradient(135deg, rgba(240, 180, 41, .10), transparent);
}
.parrain-rang {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 800; color: var(--soft);
  background: rgba(168, 85, 247, .14);
}
.parrain--tete .parrain-rang { color: #1a1626; background: #f0b429; }
.parrain-bloc { min-width: 0; }
.parrain-nom {
  display: flex; align-items: center; gap: 8px;
  font-size: 15px; font-weight: 700; margin-bottom: 3px;
}
.parrain-detail { font-size: 13px; color: var(--soft); }
/* Les filleuls sur une ligne discrète : ils servent à vérifier, pas à
   être lus en premier. */
.parrain-filleuls { font-size: 12.5px; color: var(--muted); margin-top: 5px; line-height: 1.5; }

/* ---- Concours : création, podium, annonce ----
   Sur la même page que les parrains, parce qu'on désigne un gagnant en
   regardant le classement — pas de mémoire. */
.cc-sep { border: 0; border-top: 1px solid var(--line); margin: 34px 0 22px; }
.cc-form {
  display: flex; flex-direction: column; gap: 12px; max-width: 560px;
  padding: 18px 20px; border-radius: 14px;
  background: var(--card); border: 1px solid var(--line); margin-bottom: 22px;
}
.cc-form h3 { font-size: 16px; }
/* ---- la période, sortie de la file des champs ----
   Elle décide à elle seule de ce que le classement compte : une date
   fausse ne se voit pas, elle se découvre un mois plus tard sur un
   classement qui ne veut rien dire. */
.cc-periode {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 12px 14px 13px; margin: 0;
  background: rgba(168, 85, 247, .06);
}
.cc-periode legend {
  padding: 0 6px; font-size: 11px; font-weight: 800;
  letter-spacing: .8px; text-transform: uppercase; color: var(--violet);
}
.cc-periode-champs { display: flex; align-items: flex-end; gap: 10px; }
.cc-periode-champs label { flex: 1; min-width: 0; }
.cc-periode-fleche { padding-bottom: 11px; color: var(--muted); font-size: 15px; }
.cc-periode input[type="date"] {
  /* Chiffres à chasse fixe : deux dates l'une à côté de l'autre
     s'alignent, et l'œil compare les colonnes au lieu de relire. */
  font-variant-numeric: tabular-nums;
  font-size: 15px; font-weight: 600; letter-spacing: .3px;
  padding: 11px 12px; background: var(--panel);
}
/* L'icône du calendrier est noire par défaut : sur un fond sombre, on la
   devinait plus qu'on ne la voyait. `invert` la rend claire, et elle
   passe au violet au survol pour dire qu'elle s'ouvre. */
.cc-periode input[type="date"]::-webkit-calendar-picker-indicator {
  filter: invert(1) opacity(.55); cursor: pointer;
}
.cc-periode input[type="date"]:hover::-webkit-calendar-picker-indicator { filter: invert(1) opacity(1); }
.cc-periode-lu {
  margin: 10px 0 0; font-size: 13px; color: var(--soft); line-height: 1.5;
}
.cc-periode-lu[hidden] { display: none; }
.cc-periode-lu strong { color: var(--text); }
.cc-periode-lu .cc-duree { color: var(--muted); }
/* Une fin avant le début : le serveur refuse déjà, mais l'apprendre au
   moment d'envoyer est trop tard — on l'a saisi et relu pour rien. */
.cc-periode-lu.cc-faux { color: var(--danger); }
.cc-periode-lu.cc-faux strong { color: var(--danger); }

@media (max-width: 520px) {
  .cc-periode-champs { flex-direction: column; align-items: stretch; gap: 8px; }
  .cc-periode-fleche { display: none; }
}
.cc-check { flex-direction: row; align-items: center; gap: 9px; font-weight: 600; }
.cc-check input { width: auto; }
.aide-inline { font-weight: 400; color: var(--muted); font-size: 12px; }

.cc-item {
  padding: 16px 18px; border-radius: 14px; margin-bottom: 12px;
  background: var(--surface, var(--card)); border: 1px solid var(--line);
}
.cc-item-tete { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; margin-bottom: 4px; }
/* Les illustrations, en vignettes. Chacune porte son bouton de retrait :
   corriger une image sans lui aurait voulu dire les remplacer toutes. */
.cc-photos { display: flex; flex-wrap: wrap; gap: 8px; margin: 10px 0; }
.cc-photos:empty { display: none; }
.cc-photo-cell { position: relative; width: 120px; height: 90px; }
.cc-photo-cell img {
  width: 100%; height: 100%; object-fit: cover;
  border-radius: 10px; border: 1px solid var(--line);
}
.cc-photo-del {
  position: absolute; top: -6px; right: -6px;
  width: 24px; height: 24px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--card); color: var(--soft);
  font-size: 12px; line-height: 1; cursor: pointer;
}
.cc-photo-del:hover { background: var(--danger); color: #fff; border-color: var(--danger); }
.cc-photo-del:disabled { opacity: .5; cursor: default; }
.cc-photo-ajout {
  display: flex; flex-direction: column; gap: 5px;
  font-size: 12.5px; color: var(--muted); margin: 4px 0 10px;
}
/* Les trois rangs sur une ligne : on les remplit dans l'ordre, en
   recopiant le classement affiché juste au-dessus. */
.cc-podium { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; margin: 12px 0 8px; }
.cc-rang-champ { flex: 1 1 130px; font-size: 12.5px; }
.cc-annonce { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; margin-top: 8px; }
.cc-annonce .aide { flex: 1 1 100%; margin: 4px 0 0; }

/* ---- modifier un concours ----
   Les champs s'ouvrent SUR PLACE et pré-remplis : un formulaire vide
   aurait obligé à tout ressaisir pour changer un mot. */
.cc-modif-ouvre { display: inline-block; margin: 4px 0 2px; }
.cc-modif {
  display: flex; flex-direction: column; gap: 11px;
  margin: 4px 0 12px; padding: 14px 16px;
  border: 1px dashed var(--line); border-radius: 12px;
  background: var(--panel);
}
.cc-modif[hidden] { display: none; }   /* flex écrase le style de `hidden` */

/* ---- remonter un concours en haut du fil ----
   Le repère de la dernière remontée est à côté du bouton, pas ailleurs :
   sans lui on ne sait plus si l'on vient de le faire, et l'on recommence. */
.cc-remonter-barre { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin: 8px 0 2px; }
.cc-remonter-quand { font-size: 12.5px; color: var(--muted); }

/* ---- pastille de complétude d'une personne amenée ----
   Verte quand elle compte pour le concours, orange sinon. La MÊME règle
   qu'au classement (social/parrainage.ts) : une pastille verte à côté
   d'un score qui ne bouge pas serait pire que pas de pastille. */
.filleul-pastille {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 5px; vertical-align: 1px; flex: none;
}
.filleul-pastille.ok { background: var(--ok, #22c55e); }
.filleul-pastille.attente { background: #f0b429; }
/* La couleur ne porte jamais seule : « profil incomplet » est écrit, et
   le titre l'explique. Un daltonien lit la même chose que les autres. */
.filleul-nom--attente { color: var(--muted); }

/* ---- blocages : les membres que la communauté écarte ----
   UN BLOCAGE N'EST PAS UNE FAUTE. La ligne reste neutre par défaut ;
   seule la conjonction « trois blocages ET un motif de comportement »
   se distingue. Un membre bloqué dix fois pour incompatibilité ne doit
   pas ressembler à un dossier. */
.bloc-item {
  padding: 15px 17px; border-radius: 14px; margin-bottom: 11px;
  background: var(--surface, var(--card)); border: 1px solid var(--line);
}
.bloc-item--vif { border-color: rgba(240, 180, 41, .45); }
/* LA RAFALE SE VOIT AVANT D'ÊTRE LUE. Deux blocages en vingt-quatre
   heures veulent dire qu'il s'est passé quelque chose, et qu'on peut
   encore l'apprendre en demandant — c'est le seul cas de cet écran qui
   PÉRIME. Rouge, et un liseré à gauche : on doit le repérer en faisant
   défiler, sans lire une ligne. */
.bloc-item--rafale {
  border-color: rgba(239, 68, 68, .5);
  border-left: 3px solid var(--danger);
  background: rgba(239, 68, 68, .05);
}
.bloc-tag-rafale { font-weight: 700; }
/* Les sans-motif restent LISIBLES mais reculent d'un plan : on bloque
   pour mille raisons dont aucune n'est répréhensible, et les compter
   comme le reste ferait crier un écran qui n'a rien à dire. */
.bloc-tag-faible { opacity: .55; font-weight: 400; }
.bloc-motifs { display: flex; flex-wrap: wrap; gap: 7px; margin: 9px 0 11px; }
.bloc-motif {
  font-size: 12.5px; padding: 3px 10px; border-radius: 999px;
  border: 1px solid var(--line); color: var(--muted);
}
/* Les deux seuls motifs qui décrivent un COMPORTEMENT. Les autres — le
   malaise, l'incompatibilité — restent gris : ils ne se jugent pas. */
.bloc-motif--vif {
  border-color: rgba(240, 180, 41, .5); color: #f0b429;
  background: rgba(240, 180, 41, .08); font-weight: 600;
}

/* ============================================================
   Relancer par message ceux qui ne sont pas certifiés.

   Replié par défaut (<details>) : c'est un envoi en masse, il ne doit
   pas s'ouvrir sous la main de quelqu'un venu juger une photo.
   ============================================================ */
.relance-certif {
  margin-top: 26px; padding: 0 16px 16px;
  border: 1px solid var(--border); border-radius: 12px; background: rgba(255, 255, 255, .02);
}
.relance-certif > summary {
  cursor: pointer; padding: 14px 0; font-weight: 600; list-style: none;
  display: flex; align-items: center; gap: 10px;
}
.relance-certif > summary::-webkit-details-marker { display: none; }
.relance-certif > summary::before { content: '▸'; opacity: .5; }
.relance-certif[open] > summary::before { content: '▾'; }
.relance-certif .note { margin: 0 0 14px; font-size: 13px; line-height: 1.55; color: var(--muted); }

.relance-liste { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
.relance-item {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  padding: 7px 10px; border-radius: 8px; background: rgba(255, 255, 255, .03); font-size: 13px;
}
/* Déjà relancé : pâli, mais TOUJOURS LISTÉ. Le retirer ferait croire que
   la personne s'est certifiée, alors qu'elle n'a simplement pas répondu. */
.relance-item.est-fait { opacity: .5; }
.relance-nom { font-weight: 600; }
.relance-pourquoi { color: var(--muted); font-size: 12px; }
.relance-deja { margin-left: auto; font-size: 11.5px; color: var(--muted); font-style: italic; }

.relance-lbl { display: block; font-size: 13px; font-weight: 600; margin-bottom: 6px; }
.relance-lbl small { display: block; font-weight: 400; color: var(--muted); font-size: 12px; margin-top: 3px; }
.relance-certif textarea {
  width: 100%; padding: 11px 13px; border-radius: 10px; font: inherit; font-size: 13px;
  line-height: 1.55; color: inherit; background: rgba(0, 0, 0, .25); border: 1px solid var(--border);
  resize: vertical;
}
.relance-actions { display: flex; align-items: center; gap: 12px; margin-top: 12px; }
.relance-etat { font-size: 12.5px; color: var(--muted); }

/* ============================================================
   Alerte sonore des appels vidéo, sur l'accueil de la console.
   ============================================================ */
.son-alerte {
  margin: 0 0 22px; padding: 14px 16px; border-radius: 12px;
  border: 1px solid var(--border); background: rgba(255, 255, 255, .02);
}
/* `flex-direction: row` explicite : la règle globale `label` de cette
   console empile ses enfants en colonne, ce qui renvoyait la case à
   cocher au-dessus du texte au lieu de la mettre devant. */
.son-ligne { display: flex; flex-direction: row; gap: 10px; align-items: flex-start;
  cursor: pointer; font-weight: 400; }
/* `width: auto` obligatoire : la règle globale `input` de cette console
   impose `width: 100%` — pensée pour des champs de saisie, elle étirait
   la case à cocher sur 824 px et rejetait le texte à l'extrême droite,
   réduit à un caractère par ligne. Mesuré, pas deviné. */
.son-ligne input { margin-top: 2px; flex: none; width: auto; padding: 0; }
.son-ligne > span { flex: 1; }
.son-ligne small { display: block; margin-top: 3px; color: var(--muted); font-size: 12px; line-height: 1.5; }
.son-plage {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 12px 0 0 26px; font-size: 13px; color: var(--muted);
}
.son-plage[hidden] { display: none; }
/* LES HEURES PORTENT LA COULEUR DE LA CONSOLE, pas celle du texte
   d'accompagnement. `color: inherit` les faisait hériter du gris de
   `.son-plage`, prévu pour les mots « Silence de… à » : la seule
   information qu'on vient lire ici s'affichait dans la teinte la plus
   effacée de l'écran, à côté de mots qui, eux, ne changent jamais. */
.son-plage select {
  padding: 5px 8px; border-radius: 8px; font: inherit; font-size: 13px;
  color: var(--violet); background: rgba(0, 0, 0, .25); border: 1px solid var(--line);
  font-weight: 600;
}
/* `--border` n'existe pas dans la palette (elle s'appelle `--line`) : la
   déclaration était invalide et la bordure retombait sur currentColor,
   ce qui passait inaperçu tant que le texte était gris. Colorer les
   heures l'aurait rendue violette elle aussi, par accident. Quatre
   autres endroits de cette feuille ont la même faute — ils restent
   gris par le même hasard, à corriger un jour pour de bon. */
.son-note { margin: 10px 0 0 26px; font-size: 12px; color: var(--muted); }
.son-note[hidden] { display: none; }

/* La liste des filleuls est COMPLÈTE depuis le 16/08/2026 : elle peut
   compter trente noms. Un intitulé qui annonce le nombre, et des noms
   qui passent a la ligne proprement plutot qu'une phrase qui deborde. */
.parrain-filleuls-titre { font-size: 12px; color: var(--muted); margin-top: 7px;
  font-weight: 600; letter-spacing: .2px; }
.parrain-filleuls { display: flex; flex-wrap: wrap; gap: 3px 10px; align-items: baseline; }
/* Un écart entre le compteur et la liste ne doit pas passer inaperçu. */
.parrain-ecart { color: #f43f5e; font-weight: 600; }
/* Un filleul : sa pastille et son nom vont ensemble, et ne se séparent
   jamais en fin de ligne. */
.filleul { display: inline-flex; align-items: baseline; gap: 5px; white-space: nowrap; }

/* QUI A BLOQUÉ — réservé aux administrateurs, et journalisé. La liste ne
   s'ouvre que sur un clic : on ne tombe pas dessus en faisant defiler.
   En creux et non en couleur d'alerte : ce n'est pas une accusation,
   c'est une information lourde. */
.bloc-auteurs { margin-top: 10px; padding: 10px 12px; border-radius: 10px;
  background: rgba(168,85,247,.08); border: 1px solid rgba(168,85,247,.22); }
.bloc-auteurs-titre { font-size: 12px; font-weight: 600; color: var(--muted);
  margin-bottom: 6px; }
.bloc-auteur { font-size: 13px; padding: 2px 0; }

/* ---- Les deux sections de la file de certification ----
   « Dossiers reçus » et « À certifier — rien reçu » sont deux métiers :
   juger ce qui est arrivé, et aller chercher ceux qui n'ont rien envoyé.
   Le titre les sépare pour qu'on ne cherche pas une photo là où il n'y
   en a pas. */
.certif-titre { font-size: 15px; margin: 26px 0 4px; display: flex; align-items: center;
  gap: 9px; color: var(--soft); letter-spacing: .01em; }
.certif-titre:first-of-type { margin-top: 8px; }
/* La règle de tri, écrite une fois sous le titre. Un ordre qu'on subit
   sans le comprendre se lit comme un dérangement — et la première
   réaction devant un dossier de 20 h posé au-dessus de celui de 9 h est
   de croire à un bogue. */
.certif-ordre { font-size: 12.5px; color: var(--muted); margin: 0 0 12px;
  line-height: 1.5; max-width: 78ch; }
.certif-ordre b { color: var(--soft); font-weight: 700; }

/* « Je certifie maintenant » — la fenêtre d'astreinte.
   Discrète au repos, franche une fois armée : c'est un état qu'on doit
   pouvoir constater d'un coup d'oeil sans le chercher. */
.astreinte{display:flex;flex-wrap:wrap;align-items:center;gap:10px;
  margin:10px 0 16px;padding:10px 14px;border-radius:10px;
  background:rgba(148,140,168,.10);border:1px solid rgba(148,140,168,.22);}
.astreinte-etat{flex:1 1 240px;font-size:13.5px;opacity:.8;}
.astreinte-etat.est-active{opacity:1;font-weight:600;color:#16a34a;}
.astreinte-boutons{display:flex;gap:6px;flex-wrap:wrap;}

.btn.attention{border-color:rgba(245,158,11,.55);color:#b45309;}

/* ============================================================
   L'AIDE DU STAFF — un parcours, pas une notice.

   Refondue le 19/08/2026 : « ne mets que pour la vidéo, et qu'elle soit
   interactive ». La notice de huit sections a été remplacée par des
   étapes qu'on avance une à une.

   POURQUOI UNE SEULE ÉTAPE À L'ÉCRAN. Le texte précédent était juste et
   personne ne l'aurait lu en entier : neuf écrans de lecture avant le
   week-end. Une étape à la fois tient dans un regard, et la jauge dit
   combien il en reste — on sait ce qu'on s'engage à lire.

   LA HAUTEUR EST FIXÉE (min-height sur la carte). Sans elle, les étapes
   courtes font remonter les boutons sous le curseur : on clique
   « Suivant » deux fois de suite au même endroit et la seconde tombe
   dans le vide, ou pire, sur autre chose.
   ============================================================ */
.aide-staff{max-width:72ch;}
.aide-tete{margin:0 0 18px;}
.aide-tete h2{font-size:22px;font-weight:800;margin:0 0 4px;}
.aide-intro{color:var(--muted);font-size:14px;max-width:60ch;}

/* La jauge : autant de pastilles que d'étapes, cliquables — on relit
   volontiers l'étape 4 sans repasser par les trois premières. */
.aide-jauge{display:flex;align-items:center;gap:7px;flex-wrap:wrap;margin:0 0 14px;}
.aide-pas{width:11px;height:11px;border-radius:50%;padding:0;flex:0 0 auto;
  border:1px solid var(--line);background:transparent;cursor:pointer;
  transition:background .15s,border-color .15s,transform .15s;}
.aide-pas:hover{border-color:var(--violet);transform:scale(1.25);}
.aide-pas.est-fait{background:rgba(168,85,247,.45);border-color:transparent;}
.aide-pas.est-ici{background:var(--violet);border-color:transparent;
  box-shadow:0 0 0 3px rgba(168,85,247,.22);transform:scale(1.15);}
.aide-compte{margin-left:auto;font-size:12.5px;color:var(--muted);
  font-variant-numeric:tabular-nums;}

.aide-carte{background:var(--card);border:1px solid var(--line);
  border-radius:14px;padding:20px 22px;min-height:340px;line-height:1.65;}
.aide-carte h3{font-size:19px;font-weight:800;margin:0 0 10px;}
.aide-quoi{color:var(--soft);font-size:14.5px;margin:0 0 8px;}

/* Deux volets, et c'est la forme qui porte le propos : ce que VOUS
   faites, et ce que le MEMBRE voit. Le second est celui qu'on oublie,
   et c'est lui qui évite de renvoyer un lien que la personne a déjà. */
.aide-bloc{margin:14px 0 0;padding:11px 14px;border-radius:10px;
  border-left:3px solid transparent;background:rgba(255,255,255,.03);}
.aide-bloc h4{font-size:11px;font-weight:800;letter-spacing:.09em;
  text-transform:uppercase;margin:0 0 5px;}
.aide-bloc p{font-size:14px;margin:0 0 6px;}
.aide-bloc p:last-child{margin-bottom:0;}
.aide-bloc.est-vous{border-left-color:var(--violet);}
.aide-bloc.est-vous h4{color:var(--violet);}
.aide-bloc.est-membre{border-left-color:#38bdf8;}
.aide-bloc.est-membre h4{color:#38bdf8;}

/* Ce qui ne se discute pas : une promesse faite au membre, ou une règle. */
.aide-regle{margin:16px 0 0;padding:11px 14px;border-radius:10px;
  background:rgba(251,191,36,.08);border-left:3px solid rgba(251,191,36,.55);
  color:var(--soft);}
.aide-regle h4{font-size:11px;font-weight:800;letter-spacing:.09em;
  text-transform:uppercase;margin:0 0 5px;color:var(--warn);}
.aide-regle p{font-size:13.5px;margin:0 0 6px;}
.aide-regle p:last-child{margin-bottom:0;}

.aide-pieds{display:flex;gap:10px;align-items:center;margin:16px 0 0;}
.aide-pieds .btn{min-width:130px;justify-content:center;}
.aide-pieds .aide-fin{margin-left:auto;font-size:13px;color:var(--ok);font-weight:600;}

@media(max-width:640px){
  .aide-carte{padding:16px;min-height:0;}
  .aide-pieds .btn{flex:1 1 0;min-width:0;}
}

/* ============================================================
   LES ALBUMS D'UN MEMBRE, dans sa fiche.

   Une ligne par album : l'état d'abord — c'est ce qu'on vient vérifier —
   puis le titre, le détail, et le bouton à droite.

   Aucune image ici, et il ne faut pas qu'il y en ait un jour : la route
   n'en envoie pas, et c'est ce qui distingue cette liste de celle qui a
   été refusée en juillet.
   ============================================================ */
.alb-zone { margin-top: 10px; }
.alb-liste { display: flex; flex-direction: column; gap: 8px; }
.alb-ligne {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 12px;
  background: rgba(255,255,255,.02);
}
.alb-ligne b { font-size: 14px; }
.alb-detail { font-size: 12.5px; color: var(--soft); flex: 1; min-width: 120px; }
.alb-ligne .btn { flex: none; }

/* La file des départs : l'état d'abord, le temps restant en dernier —
   c'est ce qu'on lit et ce qu'on quitte des yeux. */
/* ============================================================
   ABONNEMENTS — 02/09/2026.

   ÉCRIT APRÈS COUP, ET C’EST LA LEÇON. Ces classes existaient dans le
   JavaScript sans une seule règle ici : la fiche s’affichait
   « Zeus - AdminSemestriel », pseudonyme collé à la formule, et les
   boutons collés au texte. Rien ne cassait, rien ne signalait — c’est
   la capture du propriétaire qui l’a montré.

   Le `gap` de la fiche fait tout l’espacement : ce sont des enfants de
   flex, pas des mots dans une phrase. Sans lui, ils se touchent.
   ============================================================ */
.abo-fiche { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* La déclaration se distingue du reste : elle attend une décision, la
   fiche d'à côté ne demande rien. */
.abo-decl { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  border-color: var(--warn, #fbbf24); }
.abo-titre { margin: 18px 0 8px; font-size: 14px; color: var(--soft); }
.abo-detail { font-size: 12.5px; color: var(--soft); flex: 1; min-width: 200px; }

/* L’historique, sous la fiche qu’on vient d’ouvrir. */
.abo-histo { display: flex; flex-direction: column; gap: 8px; }
.abo-ligne { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--soft); }
/* Un paiement annulé reste lisible mais s’efface du regard : il ne
   compte plus, et l’œil doit pouvoir le sauter. */
.abo-annule { color: var(--muted); text-decoration: line-through; }

/* Le formulaire suit la grille de la boutique — deux colonnes, une
   seule sur téléphone. Le bouton prend toute la largeur. */
.abo-form { display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
  margin: 0 0 18px; }
.abo-form label { display: flex; flex-direction: column; gap: 4px;
  font-size: 13px; color: var(--soft); }
.abo-form > .btn { grid-column: 1 / -1; justify-self: start; }
@media (max-width: 720px) { .abo-form { grid-template-columns: 1fr; } }

.depart-fiche { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.depart-detail { font-size: 12.5px; color: var(--soft); flex: 1; min-width: 160px; }

/* ============================================================
   LES MOTIFS TOUT PRÊTS D'UN REFUS — 22/08/2026.

   Six étiquettes au-dessus du champ, une par cas réel. Elles ne sont ni
   des boutons d'action ni des cases à cocher : cliquer ÉCRIT un texte
   qu'on relit et modifie avant d'envoyer. D'où l'allure discrète — le
   geste qui engage reste « Refuser », en bas et en rouge.

   L'ÉTIQUETTE CHOISIE SE MARQUE : sans elle, on ne sait plus lequel des
   six on vient de poser, et l'on reclique par doute — ce qui écrase le
   texte que l'on venait justement de retoucher.

   `title` PORTE LE TEXTE COMPLET : on peut lire ce qu'on va envoyer
   avant de le poser, sans écraser ce qui est déjà écrit.
   ============================================================ */
.modal-prets { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 10px; }
.modal-prets[hidden] { display: none; }
.modal-pret {
  border: 1px solid var(--line); background: var(--card); color: var(--soft);
  font: inherit; font-size: 12px; line-height: 1.2;
  padding: 6px 11px; border-radius: 999px; cursor: pointer;
  transition: border-color .15s, color .15s, background .15s;
}
.modal-pret:hover { border-color: var(--violet); color: var(--text); }
.modal-pret:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.modal-pret.est-choisi {
  border-color: var(--violet); color: var(--text);
  background: rgba(168, 85, 247, .16); font-weight: 600;
}

/* Un code de parrainage se recopie à la main depuis un message : chasse
   fixe, et de l'air autour des caractères. C'est aussi ce qui permet de
   le comparer d'un coup d'œil à celui qu'un membre a recopié de travers. */
.parrain-code { margin-top: 6px; }
.parrain-code code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: 13px; letter-spacing: .06em;
  background: var(--card); border: 1px solid var(--line);
  padding: 2px 8px; border-radius: 6px; color: var(--text);
}

/* ============================================================
   LA SECONDE BANDE DES MEMBRES — 24/08/2026.

   Composition, activité, entonnoir, portée. Elle informe là où les
   tuiles du haut appellent une action, et son dessin dit la différence :
   pas de fond de carte, pas de bordure qui attire, des chiffres plus
   petits. Quinze tuiles identiques à plat ne se lisent pas — on les
   parcourt sans les voir.

   CE QUI SE CLIQUE LE MONTRE, ET RIEN D'AUTRE NE LE PROMET. Seuls les
   `.est-cliquable` et les `.ent-reste` prennent un curseur et un survol.
   Un chiffre qui ne mène nulle part n'a ni l'un ni l'autre.
   ============================================================ */
.membres-bilan { margin: -4px 0 14px; }
.membres-bilan > summary { display: inline-flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--muted); cursor: pointer; padding: 4px 2px;
  list-style: none; user-select: none; }
.membres-bilan > summary::-webkit-details-marker { display: none; }
.membres-bilan > summary::before { content: '▸'; color: var(--violet); }
.membres-bilan[open] > summary::before { content: '▾'; }
.membres-bilan > summary:hover { color: var(--violet); }

.bilan-corps { display: flex; flex-wrap: wrap; gap: 18px 26px;
  padding: 12px 14px 14px; margin-top: 6px;
  border: 1px solid var(--line); border-radius: 12px; }
.bilan-groupe { flex: 1 1 260px; min-width: 240px; }
.bilan-titre { font-size: 11px; text-transform: uppercase; letter-spacing: .4px;
  color: var(--muted); font-weight: 700; margin: 0 0 8px; }
.bilan-ligne { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.bilan-chiffre { display: flex; flex-direction: column; gap: 1px; align-items: flex-start;
  background: none; border: 0; padding: 0; text-align: left; font: inherit; color: inherit; }
.bilan-val { font-size: 15px; font-weight: 700; color: var(--text); }
.bilan-lbl { font-size: 10.5px; text-transform: uppercase; letter-spacing: .3px; color: var(--muted); }
.bilan-chiffre.est-cliquable { cursor: pointer; }
.bilan-chiffre.est-cliquable:hover .bilan-val,
.bilan-chiffre.est-cliquable:focus-visible .bilan-val { color: var(--violet); }
.bilan-note { font-size: 11px; color: var(--muted); margin: 8px 0 0; line-height: 1.45; }

/* L'entonnoir : une barre par marche, du plus large au plus étroit.
   La barre porte le message que quatre nombres alignés ne portaient
   pas — sur quelle marche on trébuche. */
.ent-etape { margin-bottom: 8px; }
.ent-txt { font-size: 12px; color: var(--text); margin-bottom: 3px; }
.ent-barre { height: 6px; border-radius: 4px; background: var(--line); overflow: hidden; }
.ent-barre > span { display: block; height: 100%; border-radius: 4px;
  background: linear-gradient(90deg, var(--violet), var(--deep)); }
.ent-reste { margin-top: 4px; background: none; border: 0; padding: 0; font: inherit;
  font-size: 11px; color: #f87171; cursor: pointer; text-decoration: underline;
  text-underline-offset: 2px; }
.ent-reste:hover { color: #fca5a5; }

.bilan-courbe { width: 100%; height: 54px; display: block; }
.bilan-courbe polyline { fill: none; stroke: var(--violet); stroke-width: 1.6;
  stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }

/* Un bouton posé DANS une ligne de liste — « Envoyer le code », par
   exemple. Même bouton, en plus petit : à la taille normale il pèse plus
   que la ligne qui le porte, et c'est la ligne qu'on est venu lire. */
.btn.mini { font-size: 12px; padding: 4px 12px; }

/* ============================================================
   LA FICHE DE SURVEILLANCE — 24/08/2026.

   Elle se pose PAR-DESSUS la liste, sans la remplacer : on y vient
   depuis une ligne de membre et l'on y retourne. Un écran à part
   ferait perdre où l'on était.

   SON CALQUE EST AU-DESSUS DE TOUT CE QUI PEUT L'OUVRIR (la liste des
   membres, l'en-tête collant), la lecture d'une conversation au-dessus
   d'elle, ET LA MODALE DE CONFIRMATION AU-DESSUS DES DEUX.

   Ce dernier point a été appris à l'écran : posée d'abord à 180, la
   fiche recouvrait la modale (50) — celle-là même qui réclame le motif
   obligatoire. La demande s'ouvrait donc DERRIÈRE la fiche, présente,
   invisible, inatteignable : on ne pouvait plus ouvrir aucune
   surveillance. C'est le défaut du 20/08/2026, où un recadreur ouvert
   derrière un encart a coûté une photo de profil qu'on ne pouvait pas
   déposer. Une sonde de navigateur s'y est cognée avant qu'un
   modérateur n'ait à le faire, et `calques-css.test.ts` l'exige
   désormais.
   ============================================================ */
.surv-fond { position: fixed; inset: 0; z-index: 46; display: flex;
  align-items: center; justify-content: center; padding: 20px;
  background: rgba(6, 4, 12, .72); backdrop-filter: blur(4px); }
.surv-fond--lecture { z-index: 47; }
.surv { position: relative; width: min(860px, 100%); max-height: 88vh; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--line); border-radius: 16px;
  padding: 22px 24px 24px; }
.surv--conv { width: min(720px, 100%); }
.surv-close { position: absolute; top: 14px; right: 14px; width: 32px; height: 32px;
  border-radius: 9px; border: 1px solid var(--line); background: none;
  color: var(--violet); cursor: pointer; display: flex; align-items: center;
  justify-content: center; }
.surv-close:hover { background: rgba(168, 85, 247, .12); }
.surv-close svg { width: 15px; height: 15px; stroke: currentColor; fill: none;
  stroke-width: 2; stroke-linecap: round; }
.surv-titre { margin: 0 0 14px; font-size: 17px; }

/* L'état de la surveillance : discret quand il n'y en a pas, net quand
   il y en a une. C'est la première chose à savoir en ouvrant la fiche. */
.surv-etat { border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px;
  margin-bottom: 18px; font-size: 13px; color: var(--muted); }
.surv-etat.est-active { border-color: rgba(251, 191, 36, .5);
  background: rgba(251, 191, 36, .07); color: var(--text); }
.surv-etat p { margin: 0 0 8px; }
.surv-motif { margin: 0 0 10px; padding-left: 10px; border-left: 2px solid var(--violet);
  font-style: italic; color: var(--text); }

.surv-groupe { margin-bottom: 20px; }
.surv-groupe h4 { font-size: 11px; text-transform: uppercase; letter-spacing: .4px;
  color: var(--muted); margin: 0 0 10px; }
.surv-vide { font-size: 12.5px; color: var(--muted); margin: 0 0 10px; line-height: 1.5; }

.surv-chiffres { display: flex; flex-wrap: wrap; gap: 10px 22px; }
.surv-chiffre { display: flex; flex-direction: column; gap: 1px; }
.surv-val { font-size: 16px; font-weight: 700; }
.surv-lbl { font-size: 10.5px; text-transform: uppercase; letter-spacing: .3px; color: var(--muted); }

/* Une alerte du filtre : la règle, l'extrait, le destinataire. L'extrait
   est en citation parce que c'en est une — 160 caractères pris dans un
   message, jamais le message. */
.surv-alerte { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
.surv-alerte q { color: var(--text); }
.surv-meta { font-size: 11.5px; color: var(--muted); }

.surv-conv { display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
.surv-conv-qui { font-weight: 600; min-width: 140px; }
.surv-conv-n { color: var(--muted); }
/* Le déséquilibre saute aux yeux : quatorze messages, aucune réponse. */
.surv-conv-n.est-muet { color: #fbbf24; }

.surv-fil { display: flex; flex-direction: column; gap: 10px; }
.surv-msg { border: 1px solid var(--line); border-radius: 10px; padding: 8px 11px;
  font-size: 13.5px; }
.surv-msg.est-lui { border-color: rgba(168, 85, 247, .4); background: rgba(168, 85, 247, .06); }
.surv-msg-qui { display: block; font-size: 11px; color: var(--muted); margin-bottom: 3px; }
.surv-msg p { margin: 0; white-space: pre-wrap; word-break: break-word; }

/* ============================================================
   LES TUILES D'ARRIVÉE, EN DEUX GROUPES — 24/08/2026.

   Demandé par le propriétaire : « les tuiles importantes de sécurité en
   premier ». Vingt et une tuiles à la file se parcourent sans se lire —
   c'est déjà ce qui était arrivé aux filtres de la liste des membres,
   rangés depuis derrière un repli.

   UN TITRE PLUTÔT QU'UN SIMPLE RÉORDONNANCEMENT. Déplacer les tuiles en
   silence aurait marché aujourd'hui et se serait défait à la première
   tuile ajoutée : personne n'aurait su où la poser. Le titre dit la
   frontière, et la prochaine trouve sa place toute seule.

   `grid-column: 1 / -1` : le titre traverse la grille au lieu d'occuper
   une case. Sans cette ligne il deviendrait une vingt-deuxième tuile,
   plus petite et sans bordure — exactement ce qu'un titre ne doit pas
   être.
   ============================================================ */
.hub-titre { grid-column: 1 / -1; margin: 4px 0 0; font-size: 11px;
  text-transform: uppercase; letter-spacing: .5px; color: var(--muted);
  font-weight: 700; }
/* Le second groupe respire un peu plus : c'est là que le regard doit
   sentir qu'il change de registre. */
.hub-titre + * { margin-top: 0; }
.hub .hub-titre ~ .hub-titre { margin-top: 14px; }

/* ============================================================
   L'ÉQUIPE EN HAUT DE LA CONSOLE — 24/08/2026.

   Deux lignes : qui est là, qui est d'astreinte. Discret par
   construction — c'est un repère, pas une alerte : il se lit en
   arrivant et ne réclame rien.

   UNE SEULE COULEUR VIVE, et elle est réservée à la ligne vide de
   l'astreinte. C'est la seule information qui appelle un geste : plage
   sans personne, un membre qui appelle sera renvoyé vers la photo.
   Mettre du violet partout aurait noyé celle-là.
   ============================================================ */
.equipe { display: flex; flex-direction: column; gap: 6px;
  border: 1px solid var(--line); border-radius: 12px;
  padding: 10px 14px; margin-bottom: 16px; font-size: 13px; }
.equipe-ligne { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 12px; }
.equipe-titre { font-size: 10.5px; text-transform: uppercase; letter-spacing: .4px;
  color: var(--muted); font-weight: 700; min-width: 78px; }
.equipe-membre { display: inline-flex; align-items: center; gap: 5px; }
/* Le point vert : rond plein, comme partout ailleurs sur le site. */
.equipe-point { width: 7px; height: 7px; border-radius: 50%; background: #4ade80;
  box-shadow: 0 0 0 2px rgba(74, 222, 128, .18); }
.equipe-jusqua { color: var(--muted); font-size: 12px; }
.equipe-alerte { color: #fbbf24; font-size: 12px; }
/* La plage vide. Ambre et non rouge : ce n'est pas une faute, c'est un
   trou qu'on peut combler en deux clics juste à côté. */
.equipe-vide { color: #fbbf24; }

/* La même information dans la vue Certifications, à côté de sa propre
   fenêtre : on arme la sienne en sachant ce que font les autres. */
.astreinte-autres { color: var(--muted); font-size: 12.5px; }

/* ============================================================
   LE COFFRE DU CRÉATEUR — 27/08/2026.

   LE POINT NE DOIT PAS SE VOIR quand on ne le cherche pas : même
   taille qu un caractère de ponctuation, collé au titre, sans couleur
   criarde. Il ne dit qu une chose — quelque chose attend — et pour qui
   ne sait pas, ce n est qu un point.
   ============================================================ */
.menu-titre{cursor:default;user-select:none;}
.menu-pastille{display:inline-block;width:6px;height:6px;border-radius:50%;
  background:var(--violet,#a855f7);margin-left:6px;vertical-align:2px;opacity:.85;}

/* Deux colonnes sur écran large : la liste à gauche, le fil à droite —
   c est la forme d une messagerie, et l œil la connaît déjà. */
.coffre-plan{display:grid;grid-template-columns:minmax(220px,320px) 1fr;gap:16px;
  align-items:start;}
@media (max-width:820px){ .coffre-plan{grid-template-columns:1fr;} }
.coffre-item{cursor:pointer;}
.coffre-item.a-lire{border-color:var(--violet,#a855f7);}
.coffre-item-nom{font-weight:600;display:flex;align-items:center;gap:8px;}
.coffre-item-dernier{font-size:12.5px;opacity:.6;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap;margin-top:2px;}
.coffre-fil-tete{display:flex;align-items:center;justify-content:space-between;
  gap:12px;margin-bottom:10px;}
.coffre-messages{max-height:52vh;overflow-y:auto;display:flex;flex-direction:column;
  gap:8px;padding:4px;}
.coffre-msg{max-width:80%;padding:8px 12px;border-radius:12px;
  background:rgba(255,255,255,.06);white-space:pre-wrap;word-break:break-word;}
/* Les miens à droite, comme dans toute messagerie : la position dit qui
   parle, et l on n a pas à lire un nom pour le savoir. */
.coffre-msg.est-moi{align-self:flex-end;background:rgba(168,85,247,.22);}
.coffre-repondre{display:flex;gap:8px;align-items:flex-end;margin-top:10px;}
.coffre-repondre textarea{flex:1;min-width:0;}
.coffre-ajout{display:flex;gap:8px;margin:0 0 14px;max-width:520px;}
.coffre-ajout input{flex:1;min-width:0;}
/* Une photo reçue dans le coffre : bornée, et cliquable pour la voir
   en grand — la vignette suffit à savoir de quoi il s agit. */
.coffre-photo{display:block;max-width:260px;max-height:260px;border-radius:10px;
  margin-top:6px;cursor:pointer;}

/* ============================================================
   La liste des communes, dans la fenêtre « Commune du membre ».

   Hauteur BORNÉE et défilement : /api/cities peut rendre dix lignes,
   et une fenêtre qui grandit avec sa liste pousse ses propres boutons
   hors de l'écran — on cherche alors « Enregistrer » en faisant défiler
   la page derrière la fenêtre.
   ============================================================ */
.ville-liste {
  list-style: none;
  margin: .4rem 0 .2rem;
  padding: 0;
  max-height: 11rem;
  overflow-y: auto;
  border: 1px solid var(--bord, #d8d8de);
  border-radius: .4rem;
}
.ville-liste:empty { display: none; border: 0; }
.ville-item {
  padding: .45rem .6rem;
  cursor: pointer;
  border-bottom: 1px solid var(--bord-faible, #ececf1);
}
.ville-item:last-child { border-bottom: 0; }
.ville-item:hover,
.ville-item:focus { background: var(--survol, #f2f2f7); outline: none; }
/* La ligne retenue reste lisible SANS la couleur seule : elle porte
   aussi une coche. Un choix signalé par la seule teinte disparaît pour
   qui ne la distingue pas. */
.ville-item.is-choisi { background: var(--accent-faible, #e8f0fe); font-weight: 600; }
.ville-item.is-choisi::after { content: " ✓"; }
.ville-vide { padding: .45rem .6rem; opacity: .7; }

/* Un bouton qui APPELLE, pour un manque qui coûte cher.
   Réservé au cas « aucune commune » : le membre n'apparaît alors dans
   aucune recherche par distance, et c'est le seul défaut de cette fiche
   qu'un clic suffit à réparer. Une bordure, pas un aplat de couleur —
   la rangée en compte déjà six et un septième bouton coloré ne se
   distinguerait plus de rien. */
.btn.attire {
  border-color: var(--accent, #3b6ef2);
  color: var(--accent, #3b6ef2);
  font-weight: 600;
}

/* Une commune posée mais invisible aux autres membres.
   Ton PLUS DOUX que « membre-manque » : ce n'est pas un défaut à
   réparer, c'est un choix du membre qu'on explique. Les confondre ferait
   corriger ce qui n'a rien à corriger. */
.membre-masque {
  font-size: .82rem;
  opacity: .85;
  margin-top: .15rem;
}

/* ---- Inviter un membre (15/09/2026) ----
   Deux champs et un bouton sur une ligne, qui se replient en colonne
   sur un écran étroit : la console se consulte aussi depuis un
   téléphone, et c'est même le cas le plus probable ici — on saisit ces
   deux lignes pendant la soirée, pas le lendemain au bureau. */
.invit-form { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.invit-form input {
  flex: 1 1 220px; min-width: 0; padding: 11px 13px; border-radius: 10px;
  border: 1px solid rgba(168, 85, 247, .3);
  background: var(--panel); color: var(--text); font: inherit; font-size: 14px;
}
.invit-form input:focus { outline: none; border-color: var(--violet); }

/* La case « organisateur » sous le formulaire d'invitation. Le texte
   aligné en haut : il tient sur trois lignes, et centrer la case au
   milieu du paragraphe la rendrait difficile à viser. */
.invit-case {
  /* flex-direction EXPLICITE. La feuille declare plus haut
     « label { display:flex; flex-direction:column } » pour tous les
     labels du site. Poser display:flex sans redire la direction laisse
     donc la colonne en place : la case se retrouve SEULE sur sa ligne,
     et son texte dessous. Vu a l ecran, jamais en lisant la regle. */
  display: flex; flex-direction: row; align-items: flex-start; gap: 9px;
  margin-top: 12px; max-width: 760px;
  font-size: 13px; line-height: 1.5; cursor: pointer;
}
.invit-case input {
  flex: 0 0 auto; width: 17px; height: 17px; margin-top: 1px;
  accent-color: var(--violet);
}

/* Le lien rendu par la console, à coller dans son propre message. */
.invit-lien {
  margin-top: 14px; padding: 14px 16px; max-width: 760px;
  border: 1px solid rgba(168, 85, 247, .35); border-radius: 12px;
  background: rgba(168, 85, 247, .07);
}
.invit-lien[hidden] { display: none; }
.invit-lien-titre {
  margin: 0 0 8px; font-size: 13px; font-weight: 700; color: var(--violet);
}
/* Sélectionnable et lisible en entier : c'est fait pour être copié, pas lu. */
.invit-lien input {
  width: 100%; padding: 10px 12px; border-radius: 9px;
  border: 1px solid rgba(168, 85, 247, .3);
  background: var(--panel); color: var(--text);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
}
.invit-lien-actions { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.invit-lien-garde {
  margin: 10px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--soft);
}
