/* ═══════════════════════════════════════════════════════════════════════════
   NECObot — composants (panel, guide, pages d'autorisation)
   Servi sous /theme/components.css, après tokens.css et base.css.

   Règles communes :
   · l'état passe par les attributs ARIA (aria-current, aria-selected, aria-invalid,
     aria-expanded, aria-busy, role="switch") : le style suit l'accessibilité, pas l'inverse ;
   · jamais la couleur seule : icône, texte, forme (point plein ou creux) ou position ;
   · aucune ressource externe ni image data: (compatible avec une CSP stricte) ;
   · une seule couleur d'état : --color-danger (erreurs, actions dangereuses).
   Démonstration de chaque composant : apercu.html.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Mise en page ── */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}
.grid-auto {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: var(--space-4);
}

/* ── Marque : avatar + « NECObot »
   <a class="brand" href="/"><img class="brand-avatar" src="/theme/necotho-avatar.png" alt="">
   <span class="brand-mark"><b>NECO</b>bot</span></a> ── */
.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--color-text);
  text-decoration: none;
}
.brand-avatar {
  width: 2rem;
  height: 2rem;
  flex: none;
  border-radius: var(--radius-full);
  background: var(--color-avatar-bg); /* pas de liseré pendant le chargement */
  object-fit: cover;
}
.brand-mark {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-regular);
  letter-spacing: var(--tracking-tight);
  line-height: 1;
  white-space: nowrap;
}
.brand-mark b {
  font-weight: var(--fw-bold);
}

/* ── Boutons
   <button class="btn btn-primary">Enregistrer</button> ; variantes : btn-danger (contour),
   btn-danger-solid (confirmation), btn-ghost ; tailles btn-sm, btn-lg ; btn-icon, btn-block.
   Chargement : aria-busy="true" (le libellé reste lu par les lecteurs d'écran). ── */
.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-height);
  padding: 0 var(--space-4);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--color-text);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-wide);
  line-height: 1.1;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  user-select: none;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.btn:hover {
  background: var(--color-hover);
  border-color: var(--color-text-muted);
}
.btn:active {
  background: var(--color-active);
  transform: translateY(1px);
}
.btn-primary {
  background: var(--color-brand);
  border-color: var(--color-brand);
  color: var(--color-on-brand);
}
.btn-primary:hover {
  background: rgb(var(--color-brand-rgb) / 0.86);
  border-color: rgb(var(--color-brand-rgb) / 0.86);
}
.btn-primary:active {
  background: rgb(var(--color-brand-rgb) / 0.78);
}
.btn-ghost {
  border-color: transparent;
}
.btn-ghost:hover {
  border-color: transparent;
}
.btn-danger {
  border-color: var(--color-danger-line);
  color: var(--color-danger);
}
.btn-danger:hover {
  border-color: var(--color-danger);
  background: var(--color-danger-soft);
}
.btn-danger-solid {
  background: var(--color-danger);
  border-color: var(--color-danger);
  color: var(--color-on-danger);
}
.btn-danger-solid:hover {
  background: rgb(var(--color-danger-rgb) / 0.88);
  border-color: rgb(var(--color-danger-rgb) / 0.88);
}
.btn-sm {
  min-height: var(--control-height-sm);
  padding: 0 var(--space-3);
  font-size: var(--fs-xs);
}
.btn-lg {
  min-height: 3rem;
  padding: 0 var(--space-6);
  font-size: var(--fs-md);
}
.btn-block {
  display: flex;
  width: 100%;
}
.btn-icon {
  width: var(--control-height);
  padding: 0;
}
.btn-icon.btn-sm {
  width: var(--control-height-sm);
}
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}
.btn:disabled:hover,
.btn[aria-disabled="true"]:hover {
  background: transparent;
  border-color: var(--color-border-strong);
}
.btn-primary:disabled:hover,
.btn-primary[aria-disabled="true"]:hover {
  background: var(--color-brand);
  border-color: var(--color-brand);
}
.btn[aria-busy="true"] {
  color: transparent;
  pointer-events: none;
}
.btn[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset: 0;
  width: 1rem;
  height: 1rem;
  margin: auto;
  border: 2px solid var(--color-text);
  border-right-color: transparent;
  border-radius: 50%;
  animation: nb-spin 0.7s linear infinite;
}
.btn-primary[aria-busy="true"]::after {
  border-color: var(--color-on-brand);
  border-right-color: transparent;
}

/* ── Croix de fermeture dessinée en CSS (nette à toute taille, aucun glyphe) :
   <button class="btn btn-ghost btn-icon btn-sm" aria-label="Fermer"><span class="icon-x" aria-hidden="true"></span></button> ── */
.icon-x {
  position: relative;
  display: inline-block;
  width: 0.875rem;
  height: 0.875rem;
  flex: none;
}
.icon-x::before,
.icon-x::after {
  content: "";
  position: absolute;
  top: calc(50% - 1px);
  left: 0;
  width: 100%;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
}
.icon-x::before { transform: rotate(45deg); }
.icon-x::after { transform: rotate(-45deg); }

/* ── Indicateur de chargement : <span class="spinner" role="status"><span class="sr-only">Chargement…</span></span> ── */
.spinner {
  display: inline-block;
  width: 1.25em;
  height: 1.25em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  vertical-align: -0.2em;
  animation: nb-spin 0.7s linear infinite;
}
@keyframes nb-spin {
  to { transform: rotate(360deg); }
}

/* ── Cartes
   <section class="card"><header class="card-header"><div><h2 class="card-title">…</h2>
   <p class="card-subtitle">…</p></div>…</header>…<footer class="card-footer">…</footer></section> ── */
.card {
  padding: var(--space-6);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: var(--color-text);
}
.card-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.card-title {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-tight);
}
.card-subtitle {
  margin-top: var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--fs-sm);
}
.card-footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-3);
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: var(--border-width) solid var(--color-border);
}
.card-inset {
  background: var(--color-bg-inset);
}
a.card,
.card-interactive {
  display: block;
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
a.card:hover,
.card-interactive:hover {
  border-color: var(--color-border-strong);
  background: var(--color-surface-2);
}

/* Chiffre clé : <div class="stat"><p class="stat-label">Membres</p><p class="stat-value">1 284</p>
   <p class="stat-meta">+12 cette semaine</p></div> */
.stat-label {
  color: var(--color-text-muted);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.stat-value {
  margin-top: var(--space-2);
  font-family: var(--font-display);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--tracking-tight);
  line-height: 1;
}
.stat-meta {
  margin-top: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--fs-sm);
}

/* ── Champs
   <div class="field"><label class="field-label" for="x">Nom</label>
   <input class="input" id="x" aria-describedby="x-aide x-erreur" aria-invalid="true">
   <p class="field-hint" id="x-aide">…</p><p class="field-error" id="x-erreur">✕ …</p></div>
   Plusieurs champs : les espacer par le conteneur (.stack, grille avec gap), pas par des marges. ── */
.field {
  display: grid;
  align-content: start; /* dans une grille de formulaire, le libellé reste collé à son champ */
  gap: var(--space-2);
}
.field-label {
  color: var(--color-text);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}
.field-hint {
  color: var(--color-text-muted);
  font-size: var(--fs-xs);
}
.field-error {
  display: flex;
  gap: var(--space-2);
  color: var(--color-danger);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}
.field-error:empty {
  display: none;
}
.input,
.select,
.textarea {
  width: 100%;
  min-height: var(--control-height);
  padding: var(--space-2) var(--space-3);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-md);
  background-color: var(--color-bg-inset);
  color: var(--color-text);
  font-size: var(--fs-md);
  line-height: var(--lh-snug);
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.input:hover,
.select:hover,
.textarea:hover {
  border-color: var(--color-text-muted);
}
.input:focus,
.select:focus,
.textarea:focus {
  border-color: var(--color-focus);
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: 0;
}
.input[aria-invalid="true"],
.select[aria-invalid="true"],
.textarea[aria-invalid="true"] {
  border-color: var(--color-danger);
}
.input[aria-invalid="true"]:focus,
.select[aria-invalid="true"]:focus,
.textarea[aria-invalid="true"]:focus {
  outline-color: var(--color-danger);
}
.input:disabled,
.select:disabled,
.textarea:disabled {
  opacity: 0.55;
}
.input[readonly] {
  border-style: dashed;
}
.textarea {
  min-height: 6rem;
  resize: vertical;
  line-height: var(--lh-normal);
}
.input-sm {
  min-height: var(--control-height-sm);
  font-size: var(--fs-sm);
}
/* Liste déroulante : chevron dessiné par deux dégradés (pas d'image) */
.select {
  appearance: none;
  padding-right: var(--space-10);
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 1.25rem) 55%, calc(100% - 0.9rem) 55%;
  background-size: 0.35rem 0.35rem, 0.35rem 0.35rem;
  background-repeat: no-repeat;
}
/* Liste ouverte (demande de l'administrateur du 06/10/2026 : « rendre plus beau aussi […] les listes déroulantes »).
   Liste du système (Firefox, Safari, Chromium avant la version 135) : options aux couleurs du thème, là où le système les
   prend (Windows, Linux ; jamais macOS), l'option active reste celle du système. */
.select option {
  background-color: var(--color-surface-3);
  color: var(--color-text);
}
/* Chromium 135 et plus (Edge, Chrome) : liste personnalisable native (appearance: base-select), dessinée par la page, en
   amélioration progressive (sans elle, la liste du système ci-dessus, sans casse). Même select natif : valeurs, évènements
   input et change, formulaires et lecteurs d'écran inchangés. Liste : fond des fenêtres, contour des champs, ombre,
   16rem au plus avec ascenseur (moins s'il manque de place : elle s'ouvre alors du côté le plus grand) ; options au
   survol et au clavier sur un voile de la couleur du texte (posé par-dessus le fond : les options teintées de la console de
   modération gardent leur teinte), anneau de focus au clavier ; option choisie en gras, liseré de la marque à gauche et
   coche dessinée en CSS à droite ; chevron qui se retourne à l'ouverture (comme la liste avec recherche du panel).
   Clavier de Chromium : flèches, Entrée et Espace ouvrent la liste fermée, puis flèches, Début, Fin et lettres y déplacent
   le focus, Entrée choisit, Échap ferme sans rien changer ; une lettre sur la liste fermée choisit directement, comme
   avant. Seul écart avec la liste du système de Windows : une flèche sur la liste fermée l'ouvre au lieu de changer la
   valeur (et d'envoyer change) à chaque appui. Largeur : celle de l'option choisie (width: auto) ; une liste de largeur
   automatique reçoit un min-inline-size de la largeur de sa plus longue option, pour ne pas bouger (style.css). */
@supports (appearance: base-select) {
  .select,
  .select::picker(select) {
    appearance: base-select;
  }
  /* Texte de l'option choisie : sur plusieurs lignes au besoin. Coupé sur une ligne, il ne rétrécit pas (pas de « … »
     possible dans le bouton de cette liste) : il poussait le chevron hors de la liste, et la page débordait à 360 px
     (banc Edge, liste « Serveur » de la refonte) */
  .select {
    align-items: center;
    gap: var(--space-2);
    padding-right: var(--space-3);
    background-image: none;
    white-space: normal;
    text-align: start;
  }
  .select:open {
    border-color: var(--color-text-muted);
  }
  .select::picker-icon {
    content: "";
    content: "" / "";
    flex: none;
    width: 0.45rem;
    height: 0.45rem;
    margin-inline: auto 0.2rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    rotate: 45deg;
    translate: 0 -0.12rem;
    transition: rotate var(--dur-fast) var(--ease), translate var(--dur-fast) var(--ease);
  }
  .select:open::picker-icon {
    rotate: -135deg;
    translate: 0 0.12rem;
  }
  .select::picker(select) {
    box-sizing: border-box;
    max-inline-size: min(36rem, calc(100vw - 2 * var(--space-4)));
    max-block-size: min(16rem, 100%);
    margin-block: var(--space-1);
    padding: var(--space-1);
    overflow-y: auto;
    border: var(--border-width) solid var(--color-border-strong);
    border-radius: var(--radius-md);
    background: var(--color-surface-3);
    color: var(--color-text);
    box-shadow: var(--shadow-md);
    scrollbar-width: thin;
    scrollbar-color: var(--color-border-strong) transparent;
  }
  .select option {
    gap: var(--space-2);
    min-block-size: var(--control-height-sm);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    font-size: var(--fs-sm);
    font-weight: var(--fw-regular);
    line-height: var(--lh-snug);
    white-space: normal;
    overflow-wrap: anywhere;
    cursor: pointer;
  }
  .select option:hover,
  .select option:focus-visible {
    background-image: linear-gradient(var(--color-hover), var(--color-hover));
  }
  .select option:focus-visible {
    outline: var(--focus-width) solid var(--color-focus);
    outline-offset: calc(var(--focus-width) * -1);
  }
  .select option:checked {
    background-image: linear-gradient(var(--color-selected), var(--color-selected));
    box-shadow: inset 3px 0 0 var(--color-brand);
    font-weight: var(--fw-semibold);
  }
  .select option:checked:hover,
  .select option:checked:focus-visible {
    background-image: linear-gradient(var(--color-hover), var(--color-hover)), linear-gradient(var(--color-selected), var(--color-selected));
  }
  .select option::checkmark {
    content: "";
    content: "" / "";
    order: 1;
    flex: none;
    width: 0.35rem;
    height: 0.7rem;
    margin-inline: auto 0.15rem;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    rotate: 45deg;
    translate: 0 -0.1rem;
  }
  .select option:disabled {
    opacity: 0.55;
    cursor: not-allowed;
  }
  /* Téléphone : options à la hauteur d'une cible tactile */
  @media (pointer: coarse) {
    .select option {
      min-block-size: var(--control-height);
    }
  }
  /* Contraste élevé de Windows (dégradés et ombres retirés) : option survolée ou au clavier aux couleurs de sélection
     du système ; l'option choisie garde sa coche et son gras */
  @media (forced-colors: active) {
    .select option:hover,
    .select option:focus-visible {
      forced-color-adjust: none;
      background: Highlight;
      color: HighlightText;
    }
  }
}
/* Champ + bouton accolés : <div class="input-group"><input class="input">…<button class="btn">…</button></div> */
.input-group {
  display: flex;
}
.input-group > .input {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.input-group > .btn {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  margin-left: calc(var(--border-width) * -1);
}
/* Cases à cocher et boutons radio natifs aux couleurs de la marque */
.check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
}
.check input {
  width: 1.125rem;
  height: 1.125rem;
  margin: 0;
  accent-color: var(--color-brand);
  cursor: pointer;
}

/* ── Champ fichier (demande de l'administrateur du 06/10/2026 : « rendre plus beau aussi l'import de fichiers »)
   <span class="fichier"><input class="input fichier-input" id="x" type="file" accept="…"></span>, libellé par for="x".
   Le champ natif reste le seul contrôle : transparent et posé sur toute la zone, il reçoit le clic, le clavier (Tab,
   Espace), les lecteurs d'écran (libellé, fichier choisi) et le fichier glissé-déposé sur la zone, avec les mêmes
   contrôles et le même évènement change qu'un choix par la fenêtre du système. Le bouton et le texte sont dessinés en
   CSS, cachés aux lecteurs d'écran (texte de remplacement vide : le champ natif dit déjà tout) : « Aucun fichier
   choisi », ou le nom et le poids du fichier, que la page pose dans data-nom ; data-survol : un fichier est glissé
   au-dessus de la zone (site/public/app.js). Sans cette feuille (gardée une heure par le navigateur), le champ natif
   s'affiche comme avant (classe input) et rien d'autre. ── */
.fichier {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  min-width: 0;
  min-height: var(--control-height);
  padding: var(--space-1) var(--space-3) var(--space-1) var(--space-1);
  border: var(--border-width) dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  background: var(--color-bg-inset);
  color: var(--color-text-subtle);
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  transition: border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
/* Bouton « Choisir un fichier » : forme d'un petit bouton du panel (police des titres, contour des champs) */
.fichier::before {
  content: "Choisir un fichier";
  content: "Choisir un fichier" / "";
  display: inline-flex;
  flex: none;
  align-items: center;
  min-height: var(--control-height-sm);
  padding: 0 var(--space-3);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  background: var(--color-surface-2);
  color: var(--color-text);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-wide);
  white-space: nowrap;
  transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
/* Fichier choisi : nom et poids (coupés par « … » s'ils sont trop longs ; l'info-bulle du champ natif donne le nom
   entier), sinon « Aucun fichier choisi » */
.fichier::after {
  content: "Aucun fichier choisi";
  content: "Aucun fichier choisi" / "";
  flex: 1 1 8rem;
  min-width: 0;
  padding: var(--space-1) 0 var(--space-1) var(--space-1);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fichier[data-nom]::after {
  content: attr(data-nom);
  content: attr(data-nom) / "";
  color: var(--color-text);
  font-weight: var(--fw-semibold);
}
.fichier:hover {
  border-color: var(--color-text-muted);
}
.fichier:hover::before {
  border-color: var(--color-text-muted);
  background: var(--color-surface-3);
}
.fichier:active::before {
  transform: translateY(1px);
}
/* Un fichier glissé au-dessus de la zone : trait plein, fond sélectionné, consigne */
.fichier[data-survol] {
  border-style: solid;
  border-color: var(--color-text);
  background: linear-gradient(var(--color-selected), var(--color-selected)), var(--color-bg-inset);
}
.fichier[data-survol]::after {
  content: "Dépose le fichier ici";
  content: "Dépose le fichier ici" / "";
  color: var(--color-text);
  font-weight: var(--fw-semibold);
}
/* Focus : anneau autour de la zone, au clavier seulement (navigateurs sans :has : à tout focus) */
.fichier:focus-within {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: var(--focus-offset);
}
.fichier:has(.fichier-input:focus:not(:focus-visible)) {
  outline: none;
}
.fichier:has(.fichier-input:disabled) {
  opacity: 0.55;
}
.fichier:has(.fichier-input:disabled):hover {
  border-color: var(--color-border-strong);
}
.fichier:has(.fichier-input:disabled):hover::before {
  border-color: var(--color-border-strong);
  background: var(--color-surface-2);
  transform: none;
}
/* Le champ natif, transparent, couvre toute la zone (même technique que .switch-input) */
.fichier .fichier-input {
  position: absolute;
  inset: 0;
  z-index: 1;
  width: 100%;
  height: 100%;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  opacity: 0;
  cursor: pointer;
}
.fichier .fichier-input::file-selector-button {
  cursor: pointer;
}
.fichier .fichier-input:disabled,
.fichier .fichier-input:disabled::file-selector-button {
  cursor: not-allowed;
}

/* ── Interrupteur (case à cocher native, donc utilisable au clavier et dans un formulaire)
   <label class="switch"><input class="switch-input" type="checkbox" role="switch" checked>
   <span class="switch-track" aria-hidden="true"></span><span class="switch-label">Annonces</span></label>
   Activé : piste pleine et pastille à droite (lisible sans couleur). Ne pas ajouter aria-checked :
   l'état natif de la case est celui qu'annoncent les lecteurs d'écran (règle ARIA in HTML). ── */
.switch {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--control-height-sm);
  cursor: pointer;
}
.switch-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.switch-track {
  position: relative;
  flex: none;
  width: 2.75rem;
  height: 1.5rem;
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-full);
  background: var(--color-bg-inset);
  transition: background-color var(--dur-base) var(--ease), border-color var(--dur-base) var(--ease);
}
.switch-track::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: calc(1.5rem - 8px);
  height: calc(1.5rem - 8px);
  border-radius: 50%;
  background: var(--color-text-muted);
  transition: transform var(--dur-base) var(--ease), background-color var(--dur-base) var(--ease);
}
.switch:hover .switch-track {
  border-color: var(--color-text-muted);
}
.switch-input:checked + .switch-track {
  border-color: var(--color-brand);
  background: var(--color-brand);
}
.switch-input:checked + .switch-track::after {
  background: var(--color-on-brand);
  transform: translateX(1.25rem);
}
.switch-input:focus-visible + .switch-track {
  outline: var(--focus-width) solid var(--color-focus);
  outline-offset: var(--focus-offset);
}
.switch-input:disabled + .switch-track,
.switch-input:disabled ~ .switch-label {
  opacity: 0.45;
}
.switch-label {
  font-size: var(--fs-sm);
}

/* ── Tableaux
   <div class="table-wrap"><table class="table">…</table></div> ; .num aligne les chiffres ;
   .table-dense pour les tableaux d'édition ; ligne choisie : aria-selected="true". ── */
.table-wrap {
  overflow-x: auto;
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
}
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}
.table caption {
  padding: var(--space-3) var(--space-4);
  color: var(--color-text-muted);
  font-size: var(--fs-xs);
  text-align: left;
  caption-side: bottom;
}
.table th,
.table td {
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-width) solid var(--color-border);
  text-align: left;
  vertical-align: middle;
}
.table thead th {
  position: sticky;
  top: 0;
  background: var(--color-surface-2);
  color: var(--color-text-muted);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  white-space: nowrap;
}
.table tbody tr {
  transition: background-color var(--dur-fast) var(--ease);
}
.table tbody tr:hover {
  background: var(--color-hover);
}
.table tbody tr:last-child > * {
  border-bottom: 0;
}
.table tr[aria-selected="true"] {
  background: var(--color-selected);
  box-shadow: inset 3px 0 0 var(--color-brand);
}
.table .num,
.table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.table-dense th,
.table-dense td {
  padding: var(--space-2) var(--space-3);
}

/* ── Jauge : barre de progression native (demande de l'administrateur du 06/10/2026 : « rendre plus beau aussi […] les
   barres des votes des subs »)
   <progress class="jauge" max="5" value="3" aria-label="Hades II : 3 vote(s)"></progress> ; .jauge-douce : remplissage
   plus doux, pour les valeurs qui ne sont pas en tête. Piste discrète en creux, remplissage de la couleur du texte (plein
   comme doux, au moins 3:1 contre la piste : vérificateur de la marque), coins arrondis, 12 px de haut ; même aspect que
   les barres .bar-track du panel (sondages, NECOThink). La valeur reste écrite à côté : la jauge ne la remplace pas.
   Pseudo-éléments de Chromium et Safari (::-webkit-progress-*) et de Firefox (::-moz-progress-bar), en règles séparées
   (un pseudo-élément inconnu annulerait toute la règle). ── */
.jauge {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  inline-size: 100%;
  min-inline-size: 4rem;
  block-size: 0.75rem;
  overflow: hidden;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--color-bg-inset);
  box-shadow: inset 0 0 0 var(--border-width) var(--color-border);
  color: var(--color-text);
  vertical-align: middle;
}
.jauge::-webkit-progress-bar {
  border-radius: inherit;
  background: transparent;
}
.jauge::-webkit-progress-value {
  border-radius: inherit;
  background: var(--color-text);
}
.jauge::-moz-progress-bar {
  border-radius: inherit;
  background: var(--color-text);
}
.jauge-douce::-webkit-progress-value {
  background: rgb(var(--color-text-rgb) / 0.55);
}
.jauge-douce::-moz-progress-bar {
  background: rgb(var(--color-text-rgb) / 0.55);
}

/* ── Badges et pastilles d'état
   <span class="badge badge-success"><span class="dot" aria-hidden="true"></span>Connecté</span>
   <span class="badge badge-danger"><span aria-hidden="true">✕</span>Hors ligne</span>
   .dot plein = actif ; .dot.dot-off creux = inactif (la forme porte l'information). ── */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 1.5rem;
  padding: 0 var(--space-2);
  border: var(--border-width) solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  color: var(--color-text-muted);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
}
.badge-solid {
  border-color: var(--color-brand);
  background: var(--color-brand);
  color: var(--color-on-brand);
}
.badge-success {
  border-color: var(--color-success);
  color: var(--color-success);
}
.badge-danger {
  border-color: var(--color-danger-line);
  background: var(--color-danger-soft);
  color: var(--color-danger);
}
.dot {
  width: 0.5rem;
  height: 0.5rem;
  flex: none;
  border-radius: 50%;
  background: currentColor;
}
.dot-off {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px currentColor;
}
.dot-live {
  animation: nb-pulse 1.6s var(--ease) infinite;
}
@keyframes nb-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* ── Encadré dans la page
   <div class="callout" role="status"><span class="callout-icon" aria-hidden="true">i</span><div>…</div></div>
   Erreur : .callout-danger avec role="alert" et « ! ». Pas de symbole ℹ : Windows l'affiche en emoji bleu. ── */
.callout {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: var(--border-width) solid var(--color-border);
  border-left: 3px solid var(--color-text);
  border-radius: var(--radius-md);
  background: var(--color-surface-2);
  font-size: var(--fs-sm);
}
.callout-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 1.25rem;
  height: 1.25rem;
  margin-top: 0.05rem;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-bold);
  line-height: 1;
}
.callout-danger {
  border-color: var(--color-danger-line);
  border-left-color: var(--color-danger);
  background: var(--color-danger-soft);
}
.callout-danger .callout-icon {
  color: var(--color-danger);
}

/* ── Notifications (toasts)
   <div class="toast-region" aria-live="polite">
   <div class="toast toast-success" role="status"><span class="toast-icon" aria-hidden="true">✓</span>
   <div><p class="toast-title">Enregistré</p><p class="toast-body">…</p></div>
   <button class="btn btn-ghost btn-icon btn-sm toast-close" aria-label="Fermer"><span class="icon-x" aria-hidden="true"></span></button></div></div>
   Erreur : .toast-danger avec role="alert" (icône ✕). ── */
.toast-region {
  position: fixed;
  right: var(--space-4);
  bottom: var(--space-4);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: min(24rem, calc(100vw - 2 * var(--space-4)));
  pointer-events: none;
}
.toast {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  border: var(--border-width) solid var(--color-border);
  border-left: 3px solid var(--color-text);
  border-radius: var(--radius-md);
  background: var(--color-surface-3);
  box-shadow: var(--shadow-lg);
  color: var(--color-text);
  pointer-events: auto;
  animation: nb-toast-in var(--dur-slow) var(--ease-out);
}
.toast-icon {
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: var(--color-text);
  color: var(--color-bg);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  line-height: 1;
}
.toast-title {
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
}
.toast-body {
  margin-top: var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--fs-sm);
}
.toast-close {
  margin: calc(var(--space-1) * -1) 0;
}
.toast-danger {
  border-color: var(--color-danger-line);
  border-left-color: var(--color-danger);
  background: linear-gradient(var(--color-danger-soft), var(--color-danger-soft)), var(--color-surface-3);
}
.toast-danger .toast-icon {
  background: var(--color-danger);
  color: var(--color-on-danger);
}
@keyframes nb-toast-in {
  from { opacity: 0; transform: translateY(0.5rem); }
}

/* ── Fenêtres : <dialog class="modal" aria-labelledby="t"> ouvert par dialog.showModal()
   <header class="modal-header"><h2 class="modal-title" id="t">…</h2>
   <button class="btn btn-ghost btn-icon btn-sm" aria-label="Fermer"><span class="icon-x" aria-hidden="true"></span></button></header>
   <div class="modal-body">…</div><footer class="modal-footer">…</footer>
   Confirmation dangereuse : .modal-danger (liseré rouge) + bouton .btn-danger-solid. ── */
.modal {
  width: min(32rem, calc(100vw - 2 * var(--space-4)));
  max-height: calc(100dvh - 2 * var(--space-4));
  margin: auto;
  padding: 0;
  overflow: auto;
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface-3);
  box-shadow: var(--shadow-lg);
  color: var(--color-text);
}
.modal::backdrop {
  /* Repli si le navigateur ne propage pas les jetons à ::backdrop (Chromium < 122) */
  background: var(--color-backdrop, rgb(0 0 0 / 0.6));
}
.modal[open] {
  animation: nb-modal-in var(--dur-base) var(--ease-out);
}
.modal-danger {
  border-top: 3px solid var(--color-danger);
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-6) 0;
}
.modal-title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
}
.modal-body {
  padding: var(--space-4) var(--space-6);
}
.modal-footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-6) var(--space-6);
}
@keyframes nb-modal-in {
  from { opacity: 0; transform: translateY(0.5rem) scale(0.98); }
}

/* ── Navigation principale
   <header class="topnav" data-open="false"><a class="brand" href="/">…</a>
   <button class="btn btn-ghost btn-icon topnav-toggle" aria-expanded="false" aria-controls="menu">
   <span class="burger" aria-hidden="true"></span><span class="sr-only">Menu</span></button>
   <nav class="topnav-links" id="menu" aria-label="Sections"><a class="topnav-link" aria-current="page" href="…">…</a></nav>
   <div class="topnav-actions">…</div></header>
   Sous 768 px, les liens se replient derrière le bouton : le script passe data-open à "true" et
   aria-expanded à "true" (l'un ou l'autre suffit à ouvrir le menu). ── */
.topnav {
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-height: var(--nav-height);
  padding: 0 var(--space-4);
  border-bottom: var(--border-width) solid var(--color-border);
  background: rgb(var(--color-bg-rgb) / 0.92);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
.topnav-links {
  display: flex;
  flex: 1;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}
.topnav-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.25rem;
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.topnav-link:hover {
  background: var(--color-hover);
  color: var(--color-text);
}
.topnav-link[aria-current="page"] {
  background: var(--color-selected);
  box-shadow: inset 0 -2px 0 var(--color-brand);
  color: var(--color-text);
  font-weight: var(--fw-semibold);
}
.topnav-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-left: auto;
}
.topnav-toggle {
  display: none;
}
.burger,
.burger::before,
.burger::after {
  display: block;
  width: 1.125rem;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
}
.burger {
  position: relative;
}
.burger::before,
.burger::after {
  content: "";
  position: absolute;
  left: 0;
}
.burger::before { top: -6px; }
.burger::after { top: 6px; }
@media (max-width: 47.99rem) {
  .topnav {
    flex-wrap: wrap;
    padding: var(--space-2) var(--space-4);
  }
  .topnav-toggle {
    display: inline-flex;
    order: 2;
  }
  .topnav-actions {
    order: 1;
  }
  .topnav-links {
    display: none;
    order: 3;
    flex-basis: 100%;
    flex-direction: column;
    align-items: stretch;
    padding: var(--space-2) 0;
  }
  .topnav[data-open="true"] .topnav-links,
  .topnav:has(.topnav-toggle[aria-expanded="true"]) .topnav-links {
    display: flex;
  }
  .topnav-link {
    min-height: var(--control-height);
  }
  .topnav-link[aria-current="page"] {
    box-shadow: inset 3px 0 0 var(--color-brand);
  }
}

/* ── Navigation latérale (sections Discord / Twitch du panel)
   <nav class="sidenav" aria-label="Discord"><p class="sidenav-title">Discord</p>
   <a class="sidenav-link" aria-current="page" href="…"><span class="sidenav-icon" aria-hidden="true">#</span>Salons vocaux</a></nav>
   Sous 768 px : bandeau horizontal défilant au-dessus du contenu. ── */
.sidenav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: var(--sidebar-width);
  padding: var(--space-4) var(--space-3);
  border-right: var(--border-width) solid var(--color-border);
  background: var(--color-bg);
}
.sidenav-title {
  padding: var(--space-3) var(--space-3) var(--space-2);
  color: var(--color-text-subtle);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.sidenav-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 2.25rem;
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
  font-size: var(--fs-sm);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
.sidenav-link:hover {
  background: var(--color-hover);
  color: var(--color-text);
}
.sidenav-link[aria-current="page"] {
  background: var(--color-selected);
  box-shadow: inset 3px 0 0 var(--color-brand);
  color: var(--color-text);
  font-weight: var(--fw-semibold);
}
.sidenav-icon {
  display: inline-grid;
  place-items: center;
  width: 1.25rem;
  flex: none;
  color: var(--color-text-subtle);
  font-family: var(--font-mono);
}
.sidenav-link[aria-current="page"] .sidenav-icon {
  color: var(--color-text);
}
@media (max-width: 47.99rem) {
  .sidenav {
    width: auto;
    flex-direction: row;
    overflow-x: auto;
    padding: var(--space-2);
    border-right: 0;
    border-bottom: var(--border-width) solid var(--color-border);
    scrollbar-width: thin;
  }
  .sidenav-title {
    display: none;
  }
  .sidenav-link {
    flex: none;
  }
  .sidenav-link[aria-current="page"] {
    box-shadow: inset 0 -2px 0 var(--color-brand);
  }
}

/* ── Onglets : <div class="tabs" role="tablist"><button class="tab" role="tab" aria-selected="true">…</button></div> ── */
.tabs {
  display: flex;
  gap: var(--space-1);
  overflow-x: auto;
  border-bottom: var(--border-width) solid var(--color-border);
  scrollbar-width: thin;
}
.tab {
  position: relative;
  flex: none;
  padding: var(--space-3) var(--space-4);
  border: 0;
  background: none;
  color: var(--color-text-muted);
  font-family: var(--font-display);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease);
}
.tab:hover {
  color: var(--color-text);
}
.tab[aria-selected="true"] {
  color: var(--color-text);
  font-weight: var(--fw-semibold);
}
.tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  right: var(--space-3);
  bottom: -1px;
  left: var(--space-3);
  height: 2px;
  background: var(--color-brand);
}

/* ── Écran de connexion (panel, pages d'autorisation)
   <main class="login"><div class="login-card"><img class="login-avatar" src="/theme/necotho-avatar.png" alt="">
   <p class="login-brand brand-mark"><b>NECO</b>bot</p><h1 class="login-title">Connexion des modérateurs</h1>
   <p class="login-subtitle">…</p><form class="login-form">…champs…
   <p class="login-error" role="alert" id="erreur"></p><button class="btn btn-primary btn-lg btn-block">Se connecter</button>
   </form><p class="login-footer">NECObot</p></div></main>
   Le message d'erreur reste vide tant qu'il n'y a rien à dire (il est alors masqué). ── */
.login {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: var(--space-8) var(--space-4);
  background: var(--color-bg);
  isolation: isolate;
}
/* Damier « pixel » en vignette : présent sur les bords, effacé au centre */
.login::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--texture-pixel);
  -webkit-mask-image: radial-gradient(ellipse at center, transparent 35%, black 100%);
  mask-image: radial-gradient(ellipse at center, transparent 35%, black 100%);
  pointer-events: none;
}
.login-card {
  width: min(100%, 26rem);
  padding: var(--space-8) var(--space-6) var(--space-6);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-xl);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg);
  text-align: center;
}
.login-avatar {
  width: 5.5rem;
  height: 5.5rem;
  margin: 0 auto var(--space-4);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-full);
  background: var(--color-avatar-bg);
  object-fit: cover;
}
.login-brand {
  font-size: var(--fs-xl);
}
.login-title {
  margin-top: var(--space-3);
  font-size: var(--fs-2xl);
}
.login-subtitle {
  margin-top: var(--space-2);
  color: var(--color-text-muted);
  font-size: var(--fs-sm);
}
.login-form {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-6);
  text-align: left;
}
.login-error {
  display: flex;
  gap: var(--space-2);
  padding: var(--space-3);
  border: var(--border-width) solid var(--color-danger-line);
  border-radius: var(--radius-md);
  background: var(--color-danger-soft);
  color: var(--color-danger);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
}
.login-error:empty {
  display: none;
}
.login-footer {
  margin-top: var(--space-6);
  color: var(--color-text-subtle);
  font-family: var(--font-display);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

/* ── Séparateur « pixel » : <hr class="rule-pixel"> ── */
.rule-pixel {
  height: var(--pixel);
  border: 0;
  background: repeating-linear-gradient(90deg, var(--color-border-strong) 0 var(--pixel), transparent var(--pixel) calc(var(--pixel) * 2));
}

/* ── Contraste élevé de Windows : les ombres disparaissent, les indicateurs passent en contours ── */
@media (forced-colors: active) {
  .topnav-link[aria-current="page"],
  .sidenav-link[aria-current="page"],
  .table tr[aria-selected="true"] {
    outline: 2px solid CanvasText;
    outline-offset: -2px;
  }
  .tab[aria-selected="true"]::after,
  .switch-input:checked + .switch-track {
    background: Highlight;
  }
  .switch-track {
    border-color: CanvasText;
  }
  .switch-track::after {
    background: CanvasText;
  }
  .toast,
  .callout,
  .modal {
    border: 1px solid CanvasText;
  }
  /* Champ fichier (demande du 06/10/2026) : zone survolée par un fichier glissé, en couleur de sélection du système */
  .fichier[data-survol] {
    border-color: Highlight;
  }
  /* Jauge (demande du 06/10/2026) : les fonds y sont remplacés par celui de la page, la barre disparaîtrait ; contour de
     la couleur du texte, remplissage en couleur de sélection du système (plein comme doux) */
  .jauge {
    forced-color-adjust: none;
    border: 1px solid CanvasText;
    background: Canvas;
    box-shadow: none;
  }
  .jauge::-webkit-progress-value {
    background: Highlight;
  }
  .jauge::-moz-progress-bar {
    background: Highlight;
  }
}
