/* ==========================================================================
   ARTAWEB — Administration · admin-socle.css
   --------------------------------------------------------------------------
   Socle : variables, mise en page, boutons, champs, tables, cartes, alertes.

   CE FICHIER EST UNE PART D'UN TOUT. `admin.css` faisait 8 026 lignes et
   274 Ko : impossible à parcourir, impossible à situer. Il a été DÉCOUPÉ le
   2026-09-09 — découpé, pas réécrit : la concaténation des cinq parts, dans
   l'ordre où adminStylesheetLinks() les charge, redonne l'original CARACTÈRE
   POUR CARACTÈRE. L'audit le demandait dans ces termes : « le découper, oui ;
   le réécrire, non — le risque dépasse largement le gain ».

   L'ORDRE COMPTE. Ces feuilles se surchargent l'une l'autre ; les charger
   dans un autre ordre change le rendu. Elles sont donc listées en UN seul
   endroit : adminStylesheetLinks(), dans includes/admin-assets.php.
   ========================================================================== */

/* ============================================================
   ARTAWEB — Admin Styles
   Interface contemporaine, aérée, neutre + accent piloté par la MARQUE.
   L'accent (--ad-accent) et la couleur secondaire sont injectés depuis la
   Charte graphique par renderAdminThemeVars() ; les valeurs ci-dessous ne
   sont que des replis. Toutes les dérivées (survol, fond doux, focus, variante
   sombre) sont calculées en color-mix() → l'admin adopte l'identité de tout
   site réutilisant le CMS. Les couleurs d'alerte/danger restent figées.
   ============================================================ */

/* ----------------------------------------------------------
   Reset local (scope admin uniquement)
   ---------------------------------------------------------- */
body.admin-body,
body.admin-login-body {
    /* Neutres : gris propres, brand-neutres (fonctionnent avec toute marque) */
    --ad-bg:         #ffffff;
    --ad-bg-alt:     #f7f8fa;
    --ad-bg-sunk:    #eef0f3;
    --ad-text:       #16181d;
    --ad-text-soft:  #454a53;
    /* Gris secondaire. Assombri de #878d97 à #676d76 le 2026-08-24 : l'ancienne
       valeur donnait un contraste de 3,34:1 sur blanc (WCAG AA en exige 4,5),
       et elle habille surtout des textes de 10 à 11,5 px — hints, méta,
       micro-libellés. La nouvelle passe AA sur les trois fonds de l'admin
       (#ffffff 5,22 · #f7f8fa 4,91 · #eef0f3 4,57). */
    --ad-text-mute:  #676d76;
    --ad-border:     #e7e9ee;
    --ad-border-str: #d3d7df;

    /* Accent de MARQUE (replis ; surchargés par renderAdminThemeVars()) */
    --ad-accent:     #4F7A17;
    /* Couleur du voile de chargement. VOLONTAIREMENT distincte de --ad-accent :
       l'accent suit la charte du SITE administré et peut être n'importe quoi,
       alors que ce voile dit « le CMS travaille ». C'est l'orange d'Artaweb
       (img/logo-artaweb.svg). Une installation qui veut la sienne n'a qu'à
       redéfinir ce jeton dans css/site-admin.css. */
    --ad-loader:     #ff8736;
    --ad-secondary:  #99551E;
    /* Dérivées calculées → aucune couleur d'accent en dur ailleurs */
    --ad-accent-h:   color-mix(in srgb, var(--ad-accent) 84%, #000);   /* survol (plus foncé) */
    --ad-accent-sf:  color-mix(in srgb, var(--ad-accent) 11%, #fff);   /* fond doux (badges, états légers) */
    --ad-accent-dk:  color-mix(in srgb, var(--ad-accent) 58%, #fff);   /* lisible sur fond sombre (sidebar) */
    --ad-focus:      color-mix(in srgb, var(--ad-accent) 30%, transparent);

    /* Alerte / états : FIGÉS (jamais dérivés de la marque) */
    --ad-danger:     #d23b30;
    --ad-danger-sf:  #fdecec;
    --ad-success:    #1e7a3c;
    --ad-success-sf: #e7f4ec;
    --ad-warn:       #8a6100;
    --ad-warn-sf:    #fdf3d7;

    /* Alias historiques : ces trois noms étaient UTILISÉS sans jamais être définis
       (var() sans repli = propriété invalide → fond transparent). Le cas le plus
       visible était le contrôle segmenté « Contenue / Pleine largeur », dont
       l'option cochée restait sans fond ; ce contrôle a été retiré le 2026-09-08
       avec sa feuille (.seg), mais les deux alias servent toujours ailleurs. */
    --ad-surface: var(--ad-bg);
    --ad-muted:   var(--ad-text-mute);
    --ad-radius:  var(--ad-r-sm);

    /* --- Couleurs de CATÉGORIE de composant --------------------------------
       Pastille pleine à glyphe BLANC (cf. .block-chip). Chaque base est figée et
       désaturée — six identités réellement distinctes en TEINTE, pas seulement en
       luminosité — puis teintée vers l'accent de marque pour rester parente de la
       charte. Une seule molette : --ad-cat-tint (0% = teintes pures).
       RÈGLE : le glyphe est toujours blanc, donc chaque base est vérifiée à
       >= 4,9:1 sur blanc, et reste >= 4:1 même après teintage par un accent très
       clair. C'est le SEUL blanc-sur-couleur de l'admin ; ne pas étendre ce motif
       à une couleur entièrement dérivée de la marque, qui n'offre aucune garantie. */
    --ad-cat-tint:      12%;   /* complément du 88% ci-dessous : garder les deux d'accord */
    --ad-cat-text:      color-mix(in srgb, #4E5A6B 88%, var(--ad-accent));
    --ad-cat-action:    color-mix(in srgb, #96573A 88%, var(--ad-accent));
    --ad-cat-media:     color-mix(in srgb, #2F7180 88%, var(--ad-accent));
    --ad-cat-structure: color-mix(in srgb, #5F5388 88%, var(--ad-accent));
    --ad-cat-blog:      color-mix(in srgb, #4E7A52 88%, var(--ad-accent));
    --ad-cat-events:    color-mix(in srgb, #3A5FA0 88%, var(--ad-accent));
    --ad-cat-advanced:  color-mix(in srgb, #7A4A63 88%, var(--ad-accent));

    /* --- Hiérarchie à 4 niveaux : Page > Section > Ligne > Composant --------
       UN SEUL curseur, le RAIL (liseré gauche) : de l'accent de marque (niveau 1)
       vers le neutre (niveau 4). Plus on descend, moins ça pèse. Tout dérive de
       --ad-accent, donc l'échelle suit la charte du site.
       Ces couleurs servent de LISERÉ, jamais de fond de texte (cf. règle ci-dessus). */
    --ad-lvl1: var(--ad-accent);
    --ad-lvl2: color-mix(in srgb, var(--ad-accent) 60%, var(--ad-border-str));
    --ad-lvl3: color-mix(in srgb, var(--ad-accent) 34%, var(--ad-border-str));
    --ad-lvl4: color-mix(in srgb, var(--ad-accent) 16%, var(--ad-border-str));

    /* États d'ouverture : volontairement IDENTIQUES aux 4 niveaux — « ouvert »
       est un seul et même concept, quel que soit l'élément qu'on déplie. */
    --ad-open-bg:   var(--ad-accent-sf);
    --ad-open-ring: 0 0 0 1px color-mix(in srgb, var(--ad-accent) 40%, transparent);

    --ad-r-xs:   6px;
    --ad-r-sm:   9px;
    --ad-r-md:   13px;
    --ad-r-lg:   18px;
    --ad-r-pill: 999px;

    --ad-trans: .16s;

    /* Ombres douces multi-couches, neutres et discrètes */
    --ad-shadow-sm: 0 1px 2px rgba(18, 22, 31, .05), 0 1px 1px rgba(18, 22, 31, .03);
    --ad-shadow-md: 0 1px 2px rgba(18, 22, 31, .04), 0 8px 24px rgba(18, 22, 31, .07);
    --ad-shadow-lg: 0 4px 12px rgba(18, 22, 31, .08), 0 24px 56px rgba(18, 22, 31, .18);

    /* Longueur de ligne maximale des textes de lecture suivie (« mesure »).
       En `ch` — la largeur du glyphe « 0 » — donc relative à la police de
       l'élément : le nombre de caractères par ligne reste constant quelle que
       soit la taille du texte. 68ch tient ~75-85 caractères, la plage lisible.
       Au-delà, l'œil perd sa ligne en revenant à la marge gauche. */
    --ad-measure: 68ch;

    /* Échelle remontée d'un cran le 2026-08-30 : la base était à 13,5 px et les
       notes sous les champs à 11,5 px — trop petit pour un back-office où l'on
       passe des heures. Tout étant tokenisé, un seul réglage déplace l'interface
       entière ; seules ~25 tailles en dur (repérées plus bas) ont dû suivre. */
    --ad-fs-xs:  13px;
    --ad-fs-sm:  14px;
    --ad-fs-md:  15px;
    --ad-fs-lg:  17.5px;
    --ad-fs-xl:  23px;
    --ad-fs-2xl: 29px;

    /* Rôles typographiques. L'échelle ci-dessus dit des TAILLES ; ceux-ci disent
       à quoi elles servent. Une micro-étiquette en majuscules s'écrivait avec
       six tailles différentes (9, 10, 11, 11.5, 12.5px et .72rem), quatre
       interlettrages et deux graisses selon l'écran. Un seul rôle, un seul token.
       12,5px est le PLANCHER : en dessous, le texte n'est plus lisible. Une
       micro-étiquette ne suit PLUS --ad-fs-xs : les notes sous les champs ont
       besoin de grandir, les étiquettes en capitales beaucoup moins. */
    --ad-fs-label: 12.5px;
    --ad-ls-label: .06em;
    --ad-fw-label: 600;
    /* Note sous un champ (« hint »). Ce nom était DÉJÀ utilisé par trois règles
       sans avoir jamais été défini (.admin-login__foot, .palette-gen__label,
       .palette-gen__msg) : elles tombaient sur leur repli en rem. Le définir
       ici les répare et donne au rôle un point de réglage unique. */
    --ad-fs-hint: var(--ad-fs-xs);
    /* Pastilles et compteurs : plus petits qu'une étiquette, mais un seul
       gabarit pour tous (il y en avait six). */
    /* PLANCHER DE LISIBILITÉ : 12,5 px (2026-09-08). En dessous, un texte reste
       lisible sur un écran d'ordinateur bien réglé, pas sur un téléphone tenu à
       bout de bras. Les badges étaient à 11,5 px et reviennent 57 fois sur le
       seul écran « Pages & menu ». Deux exceptions, et elles ne sont pas du
       texte : les pictogrammes « ! » des messages d'état (un glyphe dans une
       pastille de 14 px, voir .save-status__icon) et la maquette miniature de la
       Charte (.dp-mock__*), qui représente une page réduite — l'agrandir
       détruirait ce qu'elle montre. */
    --ad-fs-badge: 12.5px;

    /* --- Chasse fixe : la police du CODE ------------------------------------
       Même panne que --ad-fs-hint ci-dessus : `--ad-font-mono` était DÉJÀ
       consommée par deux règles (.link-picker__summary--url, .link-modal__preview)
       sans avoir jamais été déclarée — elles tombaient sur leur repli. La
       définir les répare et donne à la police de code un point de réglage unique,
       au lieu des cinq piles qui étaient recopiées à la main plus bas.

       LA TAILLE EST EN EM, DONC RELATIVE. Un monospace paraît plus large qu'Inter
       à taille égale : un nom de fichier nu dans un tableau à 15px se lisait
       nettement plus gros que le texte d'à côté. En em, un `code` garde sa juste
       proportion partout — dans un tableau comme dans une note à 13px — au lieu
       d'être figé à une valeur qui aurait raison ici et tort ailleurs.
       .92 est la valeur que l'aide contextuelle emploie déjà : on s'aligne
       dessus plutôt que d'introduire une seconde vérité. */
    --ad-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
    --ad-fs-mono: .92em;

    /* --- Taille des boutons : DEUX gabarits, pas un de plus -----------------
       Normal  : toute action dans un en-tête, une carte, un formulaire.
       Petit   : actions d'icônes dans une ligne de liste (page, section,
                 article, catégorie) — sinon chaque ligne doublerait de hauteur.
       L'emphase d'un bouton principal passe par la COULEUR et la GRAISSE,
       jamais par la taille : c'est ce qui créait des boutons dépareillés.
       Icône seule : même hauteur, largeur réduite (pas de libellé à loger). */
    --ad-btn-py:      10px;
    --ad-btn-px:      18px;
    --ad-btn-px-icon: 12px;
    --ad-btn-py-sm:   6px;
    --ad-btn-px-sm:   12px;
    /* Hauteur imposée : sans elle, un bouton à icône (16 px) est 2 px plus haut
       qu'un bouton à texte seul (ligne de 14 px) — deux tailles pour un même
       gabarit. Ces valeurs correspondent au contenu le plus haut de chaque tier. */
    --ad-btn-h:       40px;
    --ad-btn-h-sm:    32px;

    /* TROIS HAUTEURS DE CONTRÔLE, ET PAS UNE DE PLUS — déclarées ici pour qu'on
       puisse les compter (2026-09-08, révisé le 2026-09-16) :
         40px  --ad-btn-h      bouton normal
         38px  --ad-field-h    champ de saisie ET liste déroulante
         32px  --ad-btn-h-sm   bouton compact d'une ligne de liste
               --ad-select-h   alias de --ad-field-h ; voir sa déclaration
       La quatrième était en dur, et c'est ce qui la rendait invisible : on ne
       compte pas ce qui n'a pas de nom. Sa valeur et son écart avec le champ de
       saisie viennent d'une décision antérieure, expliquée à l'endroit où elle
       s'applique (« UNE SEULE HAUTEUR, ET C'EST LE POINT ») ; on lui donne un
       nom sans la rouvrir. */
    /* ALIGNÉE SUR LE CHAMP DE SAISIE le 2026-09-16. Elle valait 30px, et c'était
       argumenté : un <select> n'accueille pas de frappe, et les écrans qui en
       alignent beaucoup y gagnaient en densité. Mais l'argument se payait à
       chaque formulaire qui mêle les deux — une liste et un champ côte à côte
       ne s'alignaient pas, à huit pixels près. Constat de Patrick sur les
       réglages de section, où la grille met justement les deux en vis-à-vis.
       On garde le nom : le jour où les listes devraient redescendre, il y a un
       endroit où le dire. */
    --ad-select-h:    var(--ad-field-h);

    /* Hauteur d'ouverture des champs qui montrent du BALISAGE : l'éditeur de
       texte enrichi, sa vue source, et le champ du composant « HTML libre ».
       Une seule valeur pour les trois — sinon basculer de l'un à l'autre fait
       sauter la page. Déclarée ici plutôt que dans wysiwyg.css : admin.css est
       chargé EN PREMIER (adminHeader), donc la variable existe déjà quand
       wysiwyg.css la lit. Le repli `180px` posé sur chaque consommateur couvre
       le cas inverse. */
    --wys-h:          180px;

    /* Habillage du bouton ORDINAIRE (2026-08-30). Il était blanc sur fond blanc,
       bordé de --ad-border (#e7e9ee, 1,3:1) : rien ne le distinguait de la carte
       qui le portait. Il prend un fond gris franc et une bordure à 3:1 — le seuil
       WCAG 1.4.11 pour un contour porteur de sens. L'emphase du bouton PRINCIPAL
       reste ce qu'elle était : la couleur de marque, seule teinte pleine de l'écran. */
    --ad-btn-bg:      var(--ad-bg-sunk);   /* #eef0f3 */
    --ad-btn-bg-h:    #e2e5ea;
    /* MESURÉ, pas estimé. Le seuil de 3:1 doit tenir sur le fond le plus SOMBRE
       de l'admin (#eef0f3), pas seulement sur le blanc — c'est lui qui contraint.
       #9aa0aa, essayé d'abord, n'y faisait que 2,30:1. #828a96 est la valeur la
       plus claire qui passe partout : blanc 3,49 · #f7f8fa 3,28 · #eef0f3 3,05. */
    --ad-btn-bd:      #828a96;
    --ad-btn-bd-h:    #6b7280;

    /* --- Taille et contour des CHAMPS ---------------------------------------
       Symétriques des --ad-btn-* ci-dessus, qui n'avaient aucun pendant : la
       hauteur (36px) et le rembourrage (7px 11px) des champs étaient en dur.
       La bordure suit la même règle des 3:1 que celle des boutons — un champ
       blanc bordé de #e7e9ee dans une carte blanche ne se lisait pas comme un
       champ. */
    --ad-field-bd:    #828a96;   /* même mesure que --ad-btn-bd ci-dessus */
    --ad-field-bd-h:  #6b7280;
    /* ⚠️ UN CHAMP NE DOIT JAMAIS ÊTRE PLUS HAUT QUE LE BOUTON D'À CÔTÉ.
       Ces trois valeurs ont été promues depuis des valeurs en dur (36px,
       7px 11px) — et augmentées au passage à 42/9/13. Le champ dépassait donc
       --ad-btn-h (40px) de 2px, et --ad-btn-h-sm (32px) de DIX. Partout où un
       champ et un bouton se suivent sur une ligne (« Ajouter une section »,
       « Ajouter un onglet »…), le conteneur s'étirait à la hauteur du champ et
       le bouton flottait au milieu : c'est le décalage qu'on voyait, et il
       venait du rembourrage, pas de l'alignement.
       Ramenées à 38/8/12 : le champ repasse SOUS le bouton principal, et la
       cible tactile reste très au-dessus des 24px du critère WCAG 2.5.8. */
    --ad-field-h:     38px;
    --ad-field-py:    8px;
    --ad-field-px:    12px;

    /* Rembourrage du contenu, en UN seul endroit (2026-09-08). La barre d'ancres
       de la Charte se déborde en pleine largeur avec une marge négative : elle
       recopiait --ad-space-6 (36px) en dur, alors que le contenu retombe à
       --ad-space-3 (12px) sous 900px. La barre dépassait donc de 48px, mesurés
       à 407px dans 391 et à 800 dans 784. Une variable, redéfinie au même
       endroit que le rembourrage lui-même, supprime l'écart par construction. */
    --ad-content-px: 36px;   /* = --ad-space-6, déclaré plus bas */

    /* ET L'AXE VERTICAL, QUI MANQUAIT (2026-09-18). Le commentaire ci-dessus
       décrit le défaut et sa solution — mais seul le rembourrage HORIZONTAL est
       devenu une variable. Le vertical est resté écrit en dur : les barres
       collantes remontaient de --ad-space-6 (36px) quand le contenu, lui,
       retombe à 12px sous 768px. Mesuré le 2026-09-18 à 700px de large : la
       barre dépassait de 24px et finissait 13px SOUS le bas de l'en-tête, donc
       cachée derrière lui. Même bug, même remède, l'autre moitié. */
    --ad-content-pt: 36px;   /* = --ad-space-6 */

    --ad-space-1: 4px;
    --ad-space-2: 8px;
    --ad-space-3: 12px;
    --ad-space-4: 16px;
    --ad-space-5: 24px;
    --ad-space-6: 36px;
    /* LE RYTHME DES RÉGLAGES (2026-10-22, « aérer les écrans », maquette retenue par
       Patrick) : un seul écart entre deux champs (il allait de 12 à 16 px selon
       l'écran), et l'air laissé avant un groupe, au-dessus de son filet. */
    --ad-rythme-champ:  20px;
    --ad-rythme-groupe: 36px;

    --ad-sidebar-w: 236px;

    --ad-header-h: 68px;   /* hauteur de la barre d'en-tête sticky (offsets sticky) */

    font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
                 "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: var(--ad-fs-md);
    line-height: 1.5;
    color: var(--ad-text);
    background: var(--ad-bg-alt);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* WYSIWYG en admin : wysiwyg.css utilise les variables du thème PUBLIC (--color-*,
   --space-*…), non chargées ici → l'éditeur riche s'affichait sans bordure ni fond.
   On mappe ces variables sur les variables admin pour réparer TOUTES les zones de
   texte avancé (éditeur d'article, composants texte, etc.). */
body.admin-body,
body.admin-login-body {
    --color-border:  var(--ad-border);
    --color-bg:      var(--ad-bg);
    --color-surface: var(--ad-bg-alt);
    --color-text:    var(--ad-text);
    --color-muted:   var(--ad-text-mute);
    --color-accent:  var(--ad-accent);
    /* --color-secondary MANQUAIT, et cela se voyait ailleurs qu'ici : la palette
       de l'éditeur riche la lit pour peindre sa pastille « Secondaire ». Absente,
       la lecture revenait vide et le repli EN DUR de js/wysiwyg.js s'appliquait —
       un bleu ardoise qui n'était la couleur d'aucun site. Les deux couleurs de
       marque descendent de la Charte par renderAdminThemeVars() ; il n'y avait
       aucune raison d'en relayer une seule. */
    --color-secondary: var(--ad-secondary);
    --radius-sm:     var(--ad-r-sm);
    --space-md:      var(--ad-space-4);
    --font-body:     inherit;
    --font-heading:  inherit;
}

body.admin-body *,
body.admin-login-body * {
    box-sizing: border-box;
}

/* Pied de page admin (fin, centré, discret) */
.admin-footer {
    margin-top: var(--ad-space-6);
    padding: var(--ad-space-4) var(--ad-space-6);
    border-top: 1px solid var(--ad-border);
    text-align: center;
    font-size: var(--ad-fs-xs);
    color: var(--ad-text-mute);
}
.admin-footer a { color: var(--ad-text-soft); text-decoration: none; }
.admin-footer a:hover { color: var(--ad-accent); text-decoration: underline; }

/* Contrôle uniforme « Publié / Brouillon » : coche VERTE (publié) / GRISE (brouillon).
   L'œil est réservé à la visibilité responsive — ici c'est l'état de publication. */
.publish-check {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border: 1px solid var(--ad-border);
    background: var(--ad-bg);
    border-radius: var(--ad-r-pill);
    /* Rembourrage SYMÉTRIQUE depuis le retrait de la coche : le 7px de gauche
       compensait l'icône, il décentrait le mot une fois seul. */
    padding: 6px 12px;
    cursor: pointer;
    font-size: var(--ad-fs-xs);
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    transition: color var(--ad-trans) ease, border-color var(--ad-trans) ease, background var(--ad-trans) ease;
}
/* PUBLIÉ : contour vert, fond clair. C'est l'état NORMAL, il n'a pas à crier. */
.publish-check.is-published { color: var(--ad-success); border-color: color-mix(in srgb, var(--ad-success) 40%, var(--ad-border)); }
.publish-check.is-published:hover { background: var(--ad-success-sf); }
/* BROUILLON : pastille PLEINE et sombre. Le gris clair d'avant se confondait
   avec les libellés voisins — un contenu absent du site ressemblait à un
   contenu en ligne, et c'est le sens même de l'écran qui s'y perdait. Un aplat
   sombre se repère au premier coup d'œil dans une longue liste, sans couleur
   d'alerte : un brouillon n'est pas une erreur, c'est un état. */
.publish-check.is-draft {
    color: var(--ad-bg);
    background: var(--ad-text);
    border-color: var(--ad-text);
}
.publish-check.is-draft:hover {
    background: var(--ad-text-soft);
    border-color: var(--ad-text-soft);
}
.publish-check[disabled] { opacity: .5; cursor: default; }

/* Langues prêtes à l'emploi : une rangée par langue, le bouton au bout.
   Pas une grille de cartes — elles sont quatre, et une liste se lit plus vite
   qu'un damier de quatre éléments. */
.admin-lang-ready { display: flex; flex-direction: column; gap: 2px; margin-top: var(--ad-space-3); }
.admin-lang-ready__item {
    display: flex; align-items: center; gap: var(--ad-space-2);
    padding: 7px 0;
    border-bottom: 1px solid var(--ad-border);
}
.admin-lang-ready__item:last-child { border-bottom: 0; }
.admin-lang-ready__name { font-weight: 600; }
.admin-lang-ready__code {
    font-size: var(--ad-fs-xs); color: var(--ad-text-mute);
    font-variant-numeric: tabular-nums; letter-spacing: .04em;
}
/* Le bouton (ou la pastille d'état) est repoussé au bout de la ligne : l'œil
   descend une colonne au lieu de zigzaguer derrière des noms de longueurs
   différentes. */
.admin-lang-ready__item > :last-child { margin-left: auto; }
.admin-lang-ready__form { margin: 0; }

/* Visibilité responsive (#4) : œil + menu déroulant (Afficher partout / Masquer…). */
.vis-eye { position: relative; display: inline-flex; }
.vis-eye__btn {
    display: inline-flex; align-items: center; justify-content: center;
    /* Dessiné comme le bouton « dupliquer » (.btn--ghost .btn--sm) : même bordure, même hauteur (2026-10-20). */
    border: 1px solid var(--ad-btn-bd);
    background: var(--ad-bg);
    color: var(--ad-text);
    border-radius: var(--ad-r-sm);
    padding: 7px; cursor: pointer;
    transition: color var(--ad-trans) ease, border-color var(--ad-trans) ease, background var(--ad-trans) ease;
}
.vis-eye__btn svg { width: 16px; height: 16px; display: block; }
.vis-eye__btn:hover { background: var(--ad-btn-bg); border-color: var(--ad-btn-bd-h); color: var(--ad-text); }
/* RESTREINT : l'œil BARRÉ, un fond ambré plein, et un mot à côté. La couleur
   seule était le seul signal — elle passe inaperçue dans une liste, et elle
   échappe à qui distingue mal les teintes (un homme sur douze). Trois signaux
   valent mieux qu'un, et aucun des trois ne dépend des deux autres. */
.vis-eye.is-hidden .vis-eye__btn {
    color: var(--ad-warn, #b26a00);
    background: var(--ad-warn-sf);
    border-color: color-mix(in srgb, var(--ad-warn, #b26a00) 55%, var(--ad-border));
}
.vis-eye__tag {
    display: inline-flex; align-items: center;
    margin-left: 4px;
    padding: 2px 7px;
    border-radius: var(--ad-r-pill);
    background: var(--ad-warn-sf);
    color: var(--ad-warn, #b26a00);
    border: 1px solid color-mix(in srgb, var(--ad-warn, #b26a00) 35%, var(--ad-border));
    font-size: var(--ad-fs-xs); font-weight: 600; line-height: 1;
    white-space: nowrap;
}
/* Le mot disparaît sous 640 px : l'œil barré et l'ambre suffisent quand la
   place manque, et une ligne de liste ne doit pas se casser pour une étiquette. */
@media (max-width: 640px) {
    .vis-eye__tag { display: none; }
}
.vis-eye.is-open .vis-eye__btn { border-color: var(--ad-accent); color: var(--ad-accent); }
.vis-eye__menu {
    position: absolute; top: calc(100% + 4px); right: 0; z-index: 40;
    min-width: 230px;
    background: var(--ad-bg);
    border: 1px solid var(--ad-border);
    border-radius: var(--ad-r-md);
    box-shadow: var(--ad-shadow-md);
    padding: 4px;
    display: flex; flex-direction: column; gap: 2px;
}
.vis-eye__menu[hidden] { display: none; }
.vis-eye__opt {
    display: flex; align-items: center; gap: 8px;
    text-align: left; width: 100%;
    border: none; background: none; cursor: pointer;
    padding: 7px 10px; border-radius: var(--ad-r-sm);
    font: inherit; font-size: var(--ad-fs-sm); color: var(--ad-text);
}
.vis-eye__opt:hover { background: var(--ad-bg-sunk); }
.vis-eye__opt::before {
    content: ""; width: 14px; height: 14px; flex: 0 0 auto;
    border: 1.5px solid var(--ad-border-str); border-radius: 50%;
}
.vis-eye__opt.is-selected { color: var(--ad-accent); font-weight: 600; }
.vis-eye__opt.is-selected::before {
    border-color: var(--ad-accent);
    background: radial-gradient(var(--ad-accent) 0 3.5px, transparent 4.5px);
}
/* En-tête d'éditeur de section : titre + œil de visibilité alignés. */
.admin-section-editor__head {
    display: flex;
    align-items: center;
    gap: var(--ad-space-3);
    margin-bottom: var(--ad-space-5);
}
/* Le h2 suit la poignée : il n'est pas premier-né, et la règle générale lui poserait une marge haute qui le fait
   descendre sous les boutons. */
.admin-main .admin-section-editor .admin-section-editor__head h2 { margin: 0; }

/* Icônes SVG (style trait, héritent de la couleur courante) */
.lt-icon {
    display: inline-block;
    width: 18px;
    height: 18px;
    vertical-align: middle;
    stroke: currentColor;
    flex-shrink: 0;
}

body.admin-body h1,
body.admin-body h2,
body.admin-body h3,
body.admin-body h4,
body.admin-body h5,
body.admin-body h6,
body.admin-login-body h1,
body.admin-login-body h2 {
    font-family: inherit;
    font-weight: 600;
    letter-spacing: 0;
    color: var(--ad-text);
    margin: 0;
}

/* CODE : IL N'Y AVAIT AUCUNE RÈGLE DE BASE.
   Un `<code>` nu — un nom de fichier d'archive, une clé de champ, un chemin —
   tombait sur la feuille par défaut du navigateur : sa police monospace à lui,
   à la taille héritée du contexte. Dans un tableau à 15px, cela donnait un
   monospace à 15px, qui paraît plus gros que l'Inter d'à côté. D'où le réglage
   unique ci-dessous, en em pour rester proportionné partout.

   SÉLECTEUR NU, ET C'EST DÉLIBÉRÉ. Spécificité (0,0,1) : la plus basse possible,
   donc TOUTE règle de classe déjà en place garde la main — .webp-table code
   (13px), .admin-i18n__head code (14px), .admin-help__body code (.92em),
   .fb-field__namehint code (inherit) ne bougent pas d'un pixel. Le préfixer de
   `body.admin-body` l'aurait porté à (0,1,2) et lui aurait fait écraser
   exactement ce qu'il doit épargner. Aucun risque de fuite vers le site public :
   cette feuille n'est chargée que par l'administration. */
code, kbd, samp, pre {
    font-family: var(--ad-font-mono);
    font-size: var(--ad-fs-mono);
}

/* UN TITRE QUI SUIT DU CONTENU A BESOIN D'AIR.
   La remise à zéro ci-dessus pose `margin: 0` sur TOUS les titres de l'admin.
   Nécessaire pour celui qui OUVRE une carte — il vivrait sinon en double du
   rembourrage. Ruineux pour celui qui vient APRÈS un tableau ou un formulaire :
   « Actions » et « Journal » se collaient au tableau précédent, sans séparation.
   `:not(:first-child)` fait exactement ce partage, et épargne les premiers.
   Spécificité (0,2,1) contre (0,1,1) pour `body.admin-body h3` : elle passe. */
.admin-main :is(h2, h3, h4):not(:first-child) {
    margin-top: var(--ad-space-6);
    margin-bottom: var(--ad-space-3);
}

/* TROIS NIVEAUX DE TITRE (2026-10-22, « aérer les écrans ») — carte, groupe, étiquette.
   NIVEAU 2, L'INTERTITRE DE GROUPE, quel que soit son balisage : plus grand qu'une
   étiquette, dans la couleur du texte, en casse normale, marqué d'un trait d'accent.
   Il était en gris, en capitales, à la taille des étiquettes (14 px contre 14 px gras)
   — plus faible que les champs qu'il annonce. Une liste, un dessin : un intertitre
   neuf prend l'une de ces classes, il ne redéfinit rien.
   `body.admin-body` : passer devant la remise à zéro des titres (0,1,2) ; placé après
   `.admin-main :is(h2, h3, h4)` pour que sa marge du bas l'emporte. */
body.admin-body .admin-card > h3:not([class]),
body.admin-body .admin-subtitle,
body.admin-body .admin-secset__group-title,
body.admin-body .empl-pages__titre,
body.admin-body .admin-checkgroup > legend,
body.admin-body fieldset.admin-form__group > legend {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 16px;
    padding-left: 0;
    padding-right: 0;
    font-size: 16px;
    font-weight: 650;
    line-height: 1.3;
    letter-spacing: 0;
    text-transform: none;
    color: var(--ad-text);
}
body.admin-body .admin-card > h3:not([class])::before,
body.admin-body .admin-subtitle::before,
body.admin-body .admin-secset__group-title::before,
body.admin-body .empl-pages__titre::before,
body.admin-body .admin-checkgroup > legend::before,
body.admin-body fieldset.admin-form__group > legend::before {
    content: "";
    flex: none;
    width: 4px;
    height: 1.05em;
    border-radius: 2px;
    background: var(--ad-accent);
}
/* Un intertitre nu qui suit du contenu ouvre un groupe : un filet, et l'air au-dessus
   (la marge vient de la règle ci-dessus, `.admin-main :is(…):not(:first-child)`). */
body.admin-body .admin-card > h3:not([class]):not(:first-child) {
    padding-top: 28px;
    border-top: 1px solid var(--ad-border);
}
/* Un groupe de champs en <fieldset> — horaires, couleurs… — n'a plus le cadre gris
   du navigateur. `.admin-fieldset`, cadre VOULU (création d'une galerie), n'est pas visé. */
fieldset.admin-form__group {
    margin-inline: 0;
    padding: 0;
    border: 0;
    min-inline-size: 0;
}

/* Generic link color limited to main content (pas dans la sidebar/nav) */
.admin-main a,
.admin-login a { color: var(--ad-accent); }
.admin-main a:hover,
.admin-login a:hover { text-decoration: underline; }

/* Sidebar links: couleur claire imposée, priorité sur la règle générique */
.admin-sidebar a,
.admin-sidebar .admin-nav__link { color: rgba(255,255,255,.82); }
.admin-sidebar a:hover,
.admin-sidebar .admin-nav__link:hover { color: #fff; text-decoration: none; }
.admin-sidebar .admin-nav__link.is-active { color: #fff; }

/* ----------------------------------------------------------
   Login
   ---------------------------------------------------------- */
.admin-login-body {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    background: var(--ad-bg-alt);
}

.admin-login {
    width: 100%;
    max-width: 360px;
    padding: var(--ad-space-4);
}

.admin-login__card {
    background: var(--ad-bg);
    padding: var(--ad-space-6);
    border: 1px solid var(--ad-border);
    border-radius: var(--ad-r-md);
    box-shadow: 0 1px 2px rgba(0,0,0,.04), 0 8px 24px rgba(10,15,30,.06);
    text-align: center;
}

.admin-login__logo {
    margin: 0 auto var(--ad-space-5);
}

/* Repli quand le site n'a pas d'IMAGE de logo — cas d'un logo texte.
   Même geste que la pastille de la barre latérale, mais ses couleurs à elle ne
   conviennent pas ici : elles sont blanches sur fond translucide, pensées pour
   la barre SOMBRE. Sur la carte de connexion, qui est claire, elles seraient
   invisibles. D'où l'accent de l'administration, sur son fond doux. */
.admin-login__mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    margin: 0 auto var(--ad-space-3);
    border-radius: var(--ad-r-md);
    background: var(--ad-accent-sf);
    color: var(--ad-accent);
    font-size: 26px;
    font-weight: 700;
    line-height: 1;
}
/* Le monogramme du site (favicon 180 px de la Charte), quand il n'y a pas de
   logo dessiné. Il porte son propre fond et ses propres couleurs : on ne fait
   que le poser et l'arrondir. `alt=""` dans le balisage — le nom du site est
   écrit juste dessous, le répéter n'apprendrait rien. */
.admin-login__monogram {
    display: block;
    width: 64px;
    height: 64px;
    margin: 0 auto var(--ad-space-3);
    border-radius: var(--ad-r-md);
}
.admin-login__brand {
    margin: 0 0 var(--ad-space-5);
    color: var(--ad-text-soft);
    font-size: var(--ad-fs-sm);
    font-weight: 600;
}
.admin-login__foot {
    margin: var(--ad-space-4) 0 0;
    text-align: center;
    font-size: var(--ad-fs-hint, .85rem);
}
.admin-login__foot a {
    color: var(--ad-text-mute);
    text-decoration: none;
}
.admin-login__foot a:hover {
    color: var(--ad-text);
    text-decoration: underline;
}
/* « Se déconnecter » est un bouton (POST signé), dessiné comme les liens du pied. */
.admin-login__foot-btn {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: var(--ad-text-mute);
    cursor: pointer;
}
.admin-login__foot-btn:hover,
.admin-login__foot-btn:focus-visible {
    color: var(--ad-text);
    text-decoration: underline;
}
/* La phrase qui dit ce que demande la carte, sous la marque. */
.admin-login__intro {
    margin: 0 0 var(--ad-space-4);
    color: var(--ad-text-soft);
    font-size: var(--ad-fs-sm);
    line-height: 1.45;
}

/* ----------------------------------------------------------
   Layout : Sidebar + Main
   ---------------------------------------------------------- */
.admin-body {
    display: flex;
    min-height: 100vh;
}

.admin-sidebar {
    width: var(--ad-sidebar-w);
    background: #0f1218;
    color: #e6e8ee;
    display: flex;
    flex-direction: column;
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 50;
    border-right: 1px solid #000;
}

.admin-sidebar__logo {
    padding: var(--ad-space-4) var(--ad-space-4);
    border-bottom: 1px solid rgba(255,255,255,.08);
    display: flex;
    align-items: center;
    gap: var(--ad-space-2);
}

.admin-sidebar__mark {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    fill: #fff;
}

/* Repli sans logo SVG : pastille avec l'initiale de la marque. */
.admin-sidebar__mark--letter {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    background: rgba(255, 255, 255, .16);
    color: #fff;
    font-weight: 700;
    font-size: 18px;
    line-height: 1;
}

.admin-sidebar__brand {
    font-size: var(--ad-fs-sm);
    font-weight: 600;
    line-height: 1.25;
    letter-spacing: .02em;
    /* min-width: 0 — sans lui, un élément de flex refuse de descendre sous la
       largeur de son contenu, et un nom long POUSSE la barre latérale au lieu
       de se laisser tronquer. La règle qui coupe, en dessous, serait alors
       parfaitement inopérante. */
    min-width: 0;
}

/* Le NOM DU SITE administré. Il vient du client, donc sa longueur ne se devine
   pas : « Artaweb » tient, « Fondation romande pour le patrimoine bâti » non.
   Coupé par le CSS et non en PHP — la barre latérale se réduit (mode compact,
   petits écrans), et une troncature calculée côté serveur ignorerait la largeur
   réelle. Le `title` (posé dans adminHeader) rend le nom entier au survol. */
.admin-sidebar__brand-name {
    display: block;
    font-weight: 400;
    color: rgba(255,255,255,.6);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.admin-nav {
    flex: 1;
    padding: var(--ad-space-2) 0;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
}

.admin-nav__section {
    font-size: var(--ad-fs-label);
    font-weight: var(--ad-fw-label);
    text-transform: uppercase;
    letter-spacing: var(--ad-ls-label);
    color: rgba(255,255,255,.35);
    padding: var(--ad-space-4) var(--ad-space-4) var(--ad-space-2);
}

.admin-nav__link {
    display: flex;
    align-items: center;
    gap: var(--ad-space-3);
    padding: 7px var(--ad-space-4);
    color: rgba(255,255,255,.82);
    font-size: var(--ad-fs-md);
    font-weight: 500;
    text-decoration: none;
    border-left: 2px solid transparent;
    transition: background .12s ease, color .12s ease;
}

.admin-nav__link:hover {
    color: #fff;
    background: rgba(255,255,255,.05);
    text-decoration: none;
}

.admin-nav__link.is-active {
    color: #fff;
    background: color-mix(in srgb, var(--ad-accent) 22%, transparent);
    border-left-color: var(--ad-accent-dk);
}
.admin-nav__link.is-active .admin-nav__icon { color: var(--ad-accent-dk); opacity: 1; }

.admin-nav__icon {
    width: 18px;
    height: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    opacity: .8;
    flex-shrink: 0;
}
.admin-nav__icon .lt-icon { width: 18px; height: 18px; }

/* ---- Groupes en accordéon (admin-menu.php, 2026-09-27) ----
   Le titre d'un groupe est un BOUTON qui garde l'allure du titre de section :
   même taille, mêmes capitales, même gris. Un chevron dit qu'il se replie ; un
   point dit qu'un écran replié demande de l'attention (demandes non lues, base
   à mettre à jour, licence) — rien d'urgent ne se cache dans un pli.

   PLUS VISIBLES (2026-09-28, relevé par Patrick) : au gris des titres de
   section (35 %), un groupe FERMÉ se réduisait à une ligne pâle, et l'œil
   allait aux liens des groupes ouverts (82 %). Le titre devient une vraie
   ligne : texte clair et gras, filet entre les groupes, fond au survol, et —
   fermé — le nombre d'écrans qu'il contient. */
/* Le filet prend la couleur primaire du site — la version lisible sur fond
   sombre (--ad-accent-dk), adoucie de moitié. */
.admin-nav__groupe { border-top: 1px solid color-mix(in srgb, var(--ad-accent-dk) 55%, transparent); }
.admin-nav__groupe-titre {
    display: flex;
    align-items: center;
    gap: var(--ad-space-2);
    width: 100%;
    background: none;
    border: 0;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
}
/* Deux classes : passer .admin-nav__section, qui donne le gris et l'espacement. */
.admin-nav__section.admin-nav__groupe-titre {
    padding: 14px var(--ad-space-4);
    color: rgba(255,255,255,.72);
    font-weight: 700;
    transition: background .12s ease, color .12s ease;
}
.admin-nav__groupe:not(.is-ouvert) > .admin-nav__groupe-titre { color: rgba(255,255,255,.88); }
.admin-nav__section.admin-nav__groupe-titre:hover { color: #fff; background: rgba(255,255,255,.06); }
.admin-nav__groupe.is-ouvert > .admin-nav__groupe-titre { padding-bottom: 10px; }
.admin-nav__groupe-titre:focus-visible { outline: 2px solid var(--ad-accent); outline-offset: -2px; }
.admin-nav__groupe-nombre {
    margin-left: auto;
    min-width: 20px;
    padding: 0 6px;
    border-radius: var(--ad-r-pill);
    background: rgba(255,255,255,.12);
    color: rgba(255,255,255,.8);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0;
    text-align: center;
    line-height: 16px;   /* pas plus haut que le texte : la ligne garde sa hauteur */
}
.admin-nav__groupe.is-ouvert .admin-nav__groupe-nombre { display: none; }
.admin-nav__chevron {
    margin-left: auto;
    flex: none;
    width: 8px;
    height: 8px;
    border-right: 2px solid currentColor;
    border-bottom: 2px solid currentColor;
    transform: rotate(-45deg);
    transition: transform .15s ease;
}
/* Fermé, le nombre occupe déjà la marge automatique : le chevron le suit. */
.admin-nav__groupe:not(.is-ouvert) .admin-nav__chevron { margin-left: var(--ad-space-1); }
.admin-nav__groupe.is-ouvert .admin-nav__chevron { transform: rotate(45deg); }
.admin-nav__groupe-alerte {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--ad-accent);
}
.admin-nav__groupe.is-ouvert .admin-nav__groupe-alerte { display: none; }
@media (prefers-reduced-motion: reduce) { .admin-nav__chevron { transition: none; } }
/* L'aide, en bas du menu, hors groupe. */
.admin-nav__bas {
    margin-top: auto;
    padding-top: var(--ad-space-2);
    border-top: 1px solid rgba(255,255,255,.08);
}

/* La déconnexion est un BOUTON (formulaire POST signé, cf. admin/logout.php) et
   non plus un lien : on remet à zéro l'habillage natif du bouton pour qu'il reste
   visuellement identique aux autres entrées du menu. */
/* Démo publique (crochet 5) : une entrée fermée, montrée au lieu d'être tue. */
.admin-nav__link.is-ferme { opacity: .55; cursor: help; }
.admin-nav__link.is-ferme:hover { opacity: .8; }
.admin-nav__cadenas { margin-left: auto; display: inline-flex; }
.admin-nav__cadenas svg { width: 14px; height: 14px; }

button.admin-nav__link {
    background: none;
    border: 0;
    font-family: inherit;
    /* `line-height` AUSSI : la feuille de style du navigateur impose « normal » aux
       boutons au lieu de le laisser hériter. Sans cette ligne, le bouton mesurait
       22,5 px de haut là où le lien qu'il remplace en faisait 26,8 — mesuré dans le
       navigateur, pas deviné. */
    line-height: inherit;
    width: 100%;
    text-align: left;
    cursor: pointer;
}
/* DÉCONNEXION — dans le bloc utilisateur, en icône seule.
   Elle occupait une ligne entière sous ce bloc, avec le mot en toutes lettres :
   deux fois la hauteur pour une action qu'on fait une fois par jour. Le mot
   survit en INFOBULLE, et vit en base parmi les libellés du moteur — voir
   t('logout'), traduit en FR/DE/IT/EN/ES. */
.admin-sidebar__logout { margin: 0; display: flex; }

.admin-sidebar__logout-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    border-radius: var(--ad-r-sm);
    background: transparent;
    /* ⚠️ COULEUR SOMBRE, PAS BLANCHE. Le bouton vivait AVANT directement sur la
       barre latérale, qui est presque noire ; il est maintenant DANS le bloc
       utilisateur, dont le fond est clair (--ad-bg-alt, #f7f8fa). Reprendre
       telle quelle l'ancienne couleur donnait du blanc sur du blanc : une icône
       invisible. On prend celle du sous-titre du même bloc. */
    color: var(--ad-text-mute);
    cursor: pointer;
    transition: color .15s, background .15s;
}
.admin-sidebar__logout-btn svg { width: 17px; height: 17px; }
.admin-sidebar__logout-btn:hover,
.admin-sidebar__logout-btn:focus-visible {
    color: var(--ad-text);
    background: rgba(0,0,0,.06);
}

.admin-sidebar__footer {
    padding: var(--ad-space-3) var(--ad-space-4);
    border-top: 1px solid rgba(255,255,255,.08);
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: var(--ad-fs-sm);
}

/* (Le bloc utilisateur est stylé plus bas — section multi-utilisateurs.
   L'ancienne règle « uppercase 10px » a été retirée : elle déteignait sur le
   nom/rôle et écrasait le texte quand le menu se rétrécissait.) */

/* ----------------------------------------------------------
   Menu : repli (icônes) — desktop — + volet (drawer) — mobile
   ---------------------------------------------------------- */
.admin-sidebar { transition: width .2s ease, transform .25s ease; }

/* Bouton « replier » (desktop) */
.admin-sidebar__collapse {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px; height: 28px;
    flex-shrink: 0;
    background: transparent;
    border: 1px solid rgba(255,255,255,.14);
    border-radius: var(--ad-r-sm);
    color: rgba(255,255,255,.7);
    cursor: pointer;
    transition: background .12s, color .12s;
}
.admin-sidebar__collapse:hover { background: rgba(255,255,255,.08); color: #fff; }
.admin-sidebar__collapse svg { transition: transform .2s ease; }

/* Burger (visible seulement en mode volet) */
.admin-burger {
    display: none;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    width: 40px; height: 38px;
    padding: 0 9px;
    margin-right: var(--ad-space-1);
    background: transparent;
    border: 1px solid var(--ad-border);
    border-radius: var(--ad-r-sm);
    cursor: pointer;
    flex-shrink: 0;
}
.admin-burger span { display: block; height: 2px; width: 100%; background: var(--ad-text); border-radius: 2px; }
/* Garde le titre collé au burger (groupe gauche), actions à droite. */
.admin-header > h1 { margin-right: auto; }

/* Mobile : toutes les actions d'en-tête (Enregistrer, Ajouter, Voir le site) en
   ICÔNE SEULE → boutons compacts, tout tient sur la ligne du haut.

   640 → 860 LE 2026-09-15. Entre les deux, les libellés tenaient encore « à la
   règle » mais l'en-tête était déjà serré : le burger apparaît à 860 (la barre
   latérale devient un tiroir), le titre reprend toute la largeur laissée libre,
   et trois boutons légendés se retrouvaient à l'étroit contre lui.
   ON REPREND CETTE RUPTURE-LÀ PLUTÔT QUE D'EN INVENTER UNE. 768 aurait marché
   aussi — la feuille l'emploie déjà cinq fois — mais 860 est le point où
   l'en-tête CHANGE DÉJÀ de forme. Une seule rupture, un seul changement d'allure :
   c'est la discipline du reste du projet, et cela fait une valeur de moins à
   tenir accordée. */
@media (max-width: 860px) {
    .admin-header__actions .btn__label { display: none; }
    .admin-header__actions .btn { padding-left: var(--ad-btn-px-icon); padding-right: var(--ad-btn-px-icon); }
    .admin-header__actions { gap: var(--ad-space-1); }
    /* En-tête plus compact (spécificité (0,2,0) pour battre le « padding »
       shorthand de base défini plus loin dans le fichier). */
    .admin-body .admin-header { padding-left: var(--ad-space-3); padding-right: var(--ad-space-3); gap: var(--ad-space-2); }
    .admin-body .admin-header > h1 { font-size: var(--ad-fs-lg); }
}

/* Overlay sombre derrière le volet mobile */
.admin-nav-overlay {
    position: fixed; inset: 0;
    background: rgba(0,0,0,.45);
    z-index: 45;
    opacity: 0;
    transition: opacity .2s ease;
}
.admin-nav-overlay[hidden] { display: none; }
.admin-body--nav-open .admin-nav-overlay { opacity: 1; }

/* ---- État REPLIÉ (icônes seules) — desktop uniquement ---- */
@media (min-width: 861px) {
    /* Spécificité (0,2,0) pour battre « body.admin-body » (0,1,1) qui pose 236px. */
    .admin-body.admin-body--nav-collapsed { --ad-sidebar-w: 66px; }
    .admin-body--nav-collapsed .admin-sidebar__logo { flex-wrap: wrap; justify-content: center; padding-left: 0; padding-right: 0; }
    .admin-body--nav-collapsed .admin-sidebar__collapse { margin-left: 0; }
    .admin-body--nav-collapsed .admin-sidebar__collapse svg { transform: rotate(180deg); }
    .admin-body--nav-collapsed .admin-sidebar__brand,
    .admin-body--nav-collapsed .admin-nav__section,
    .admin-body--nav-collapsed .admin-sidebar__user-info,
    .admin-body--nav-collapsed .admin-sidebar__logout,
    .admin-body--nav-collapsed .admin-nav__badge { display: none; }
    /* Libellés des liens masqués sans wrapper : on annule la taille de police
       (le texte devient 0), l'icône garde sa taille fixe. */
    .admin-body--nav-collapsed .admin-nav__link {
        font-size: 0;
        justify-content: center;
        gap: 0;
        padding-left: 0; padding-right: 0;
    }
    .admin-body--nav-collapsed .admin-sidebar__user { justify-content: center; padding: 8px; }
    /* Icônes seules : les groupes s'effacent, toutes les icônes restent. Un fin
       filet sépare ce qui était des groupes. */
    /* `!important` OBLIGATOIRE : admin-editeurs.css normalise `[hidden] { display:
       none !important }` pour toute l'admin. Entre deux `!important`, la
       spécificité tranche, et celle-ci (0,3,0) l'emporte sur (0,1,0). */
    .admin-body--nav-collapsed .admin-nav__groupe-corps[hidden] { display: block !important; }
    .admin-body--nav-collapsed .admin-nav__groupe + .admin-nav__groupe { border-top: 1px solid rgba(255,255,255,.08); }
}

/* ---- Mode VOLET (drawer) — mobile / tablette ----
   Spécificité renforcée (.admin-body …) pour battre les règles de base
   .admin-main / .admin-sidebar définies plus loin dans le fichier. */
@media (max-width: 860px) {
    .admin-burger { display: flex; }
    .admin-sidebar__collapse { display: none; }
    /* FERMÉ, IL SORT AUSSI DU CLAVIER (audit clavier du 2026-10-23, TAB-1) : poussé hors de l'écran, ses dizaines
       de liens restaient dans le parcours, et le premier Tab de chaque écran partait dans un menu invisible.
       `visibility` bascule APRÈS la glissade de fermeture, AVANT celle d'ouverture — le motif du volet du site
       (components.css). */
    .admin-body .admin-sidebar {
        transform: translateX(-100%);
        width: 264px;
        box-shadow: 0 0 40px rgba(0,0,0,.35);
        visibility: hidden;
        transition: transform .25s ease, visibility 0s linear .25s;
    }
    .admin-body .admin-main { margin-left: 0; }
    .admin-body.admin-body--nav-open .admin-sidebar {
        transform: translateX(0) !important;
        visibility: visible;
        transition: transform .25s ease, visibility 0s;
    }
    /* Le repli (icônes) n'a pas de sens en volet : le menu s'ouvre toujours déployé. */
    .admin-body.admin-body--nav-collapsed .admin-sidebar { --ad-sidebar-w: 264px; width: 264px; }
    .admin-body.admin-body--nav-collapsed .admin-main { margin-left: 0; }
}

/* LE FOCUS, UNE RÈGLE POUR TOUTE L'ADMINISTRATION (audit clavier du 2026-10-23).
   Chaque composant posait le sien : un halo `--ad-focus` (l'accent à 30 %, ≈ 1,5:1 sur blanc, quand un indicateur
   de focus en demande 3:1) précédé d'un `outline: none` — invisible aussi en contraste élevé (Windows), qui efface
   les ombres. Ce qui n'avait pas de règle (liens simples, `summary`, cases) gardait le fin contour du navigateur :
   deux styles de focus sur le même écran. Désormais : un contour plein de l'accent, ici, en repli de poids nul
   (`:where`) ; une règle plus précise peut l'habiller, jamais l'effacer sans le remplacer (RULES.md). Le halo
   `--ad-focus` reste un FOND d'accompagnement, pas un indicateur. */
:where(body.admin-body, body.admin-login-body) :focus-visible {
    outline: 2px solid var(--ad-accent);
    outline-offset: 2px;
}

/* ALLER AU CONTENU — le lien d'évitement de l'administration (audit clavier du 2026-10-23). Hors de la vue
   tant qu'il n'a pas le focus ; au premier Tab, il apparaît en haut à gauche, par-dessus le menu. La cible
   (<main id="admin-main" tabindex="-1">) reçoit le focus sans contour : elle n'est pas une commande. */
.admin-skip {
    position: absolute;
    left: var(--ad-space-3, 12px);
    top: -100px;
    z-index: 1000;
    padding: 10px 16px;
    border-radius: var(--ad-radius, 8px);
    background: var(--ad-accent);
    color: #fff;
    font-weight: 600;
    text-decoration: none;
}
.admin-skip:focus-visible {
    top: var(--ad-space-3, 12px);
    outline: 2px solid var(--ad-text, #1d2125);
    outline-offset: 2px;
}
.admin-main:focus { outline: none; }
.admin-body--nu .admin-skip { display: none; }

/* Main content */
.admin-main {
    flex: 1;
    min-width: 0; /* autorise le rétrécissement : sans ça, un contenu non-sécable
                     (ex. libellé de champ en nowrap) élargit toute la page sur mobile */
    margin-left: var(--ad-sidebar-w);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

.admin-header {
    background: color-mix(in srgb, var(--ad-bg) 82%, transparent);
    -webkit-backdrop-filter: saturate(180%) blur(10px);
    backdrop-filter: saturate(180%) blur(10px);
    padding: var(--ad-space-4) var(--ad-space-6);
    border-bottom: 1px solid var(--ad-border);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ad-space-4);
    flex-wrap: wrap;
    position: sticky;
    top: 0;
    z-index: 10;
    min-height: var(--ad-header-h);
}

.admin-header h1 {
    font-size: var(--ad-fs-xl);
    font-weight: 650;
    letter-spacing: -0.015em;
    display: inline-flex;
    align-items: center;
    gap: var(--ad-space-3);
}
.admin-header h1 .lt-icon {
    width: 24px;
    height: 24px;
    color: var(--ad-accent);
    flex: 0 0 auto;
}

.admin-header__actions {
    display: flex;
    gap: var(--ad-space-2);
    align-items: center;
    flex-wrap: wrap;
}
/* LA RÈGLE DES BOUTONS D'EN-TÊTE (2026-10-21, RULES.md « Design Rules »).
   Un bouton qui ne vaut que pour certains onglets (adminActionOnglets()) se
   cache sur les autres : `.btn` impose son display, l'attribut seul ne suffit pas.
   « Enregistrer » signale les modifications en attente (js/admin.js). */
.admin-header__actions [hidden] { display: none; }
/* LES OUTILS DE L'EN-TÊTE (2026-10-22, Patrick : « gagner de la place sans sacrifier l'accès ») : Aide,
   Voir le site, Historique. Icônes seules, sans fond ni bordure, un tiers plus grandes que celles d'un
   bouton (22 px contre 16), regroupées à DROITE quel que soit l'ordre où l'écran les pose (`order`) —
   l'historique arrive avec les actions de l'écran. Le nom reste en infobulle et pour les lecteurs d'écran. */
body.admin-body .admin-header__actions .admin-outil {
    order: 10;
    min-height: 0;
    padding: 6px;
    border: 0;
    border-radius: 50%;
    background: transparent;
    box-shadow: none;
    color: var(--ad-text-soft);
}
body.admin-body .admin-header__actions .admin-outil .lt-icon { width: 22px; height: 22px; }
/* L'ORDRE DES BOUTONS D'EN-TÊTE (2026-10-22, Patrick : « les icônes colorées avant et les icônes sans fond après » ;
   « le Visuel toujours la première icône après + Nouveau… et Enregistrer »), quel que soit l'ordre où l'écran les
   pose : 1. les boutons pleins (« + Nouveau… », « Enregistrer ») ; 2. le Visuel (css/admin-bascule.css) ; 3. les
   autres boutons encadrés (retour, Aperçu) ; 4. les outils sans fond (ci-dessus, order 10) — y compris un outil
   enveloppé dans un petit formulaire, comme « Annuler » (revisionBoutonsVersions()), dont le formulaire gardait
   sinon l'ordre 0 et passait devant le Visuel. */
.admin-header__actions > * { order: 3; }
.admin-header__actions > .btn--primary,
.admin-header__actions > form:has(> .btn--primary) { order: 1; }
.admin-header__actions > form:has(> .admin-outil) { order: 10; }
body.admin-body .admin-header__actions .admin-outil:hover,
body.admin-body .admin-header__actions .admin-outil:focus-visible { background: var(--ad-bg-sunk); color: var(--ad-accent); }

.admin-header__actions .btn.is-dirty {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--ad-accent) 30%, transparent);
}

.admin-content {
    padding: var(--ad-content-pt) var(--ad-content-px) calc(var(--ad-space-6) * 2);
    flex: 1;
    max-width: 1440px;
    width: 100%;
}

/* UN ÉCRAN RENDU DANS UN AUTRE (`admin-body--nu`, chantier 87). Le composant
   déplié sur la surface de composition est l'éditeur de cet écran-ci, rendu
   seul dans un cadre (admin/blocks.php?solo=) : ni menu, ni barre de titre,
   ni pied, et AUCUNE hauteur minimale — la surface ajuste le cadre à la
   hauteur du contenu, et un `100vh` la ferait grandir sans fin. Le glisser et
   les gestes Dupliquer / Supprimer n'ont pas de sens hors de la liste : ils
   viendront dans le rendu (étape 3). */
.admin-body--nu { display: block; min-height: 0; }
.admin-body--nu .admin-sidebar,
.admin-body--nu .admin-nav-overlay,
.admin-body--nu .admin-header,
.admin-body--nu .admin-footer { display: none !important; }
.admin-body--nu .admin-main { margin-left: 0 !important; min-height: 0; display: block; }
.admin-body--nu .admin-content { padding: 0; max-width: none; }
.admin-solo .admin-block { margin: 0; }
.admin-solo .admin-block__drag,
.admin-solo .admin-block__chev,
.admin-solo .admin-block__actions .inline-form,
.admin-body--nu .editor-savebar { display: none !important; }
/* UNE LANGUE À LA FOIS (décision de Patrick) : la surface la choisit par les
   boutons de son en-tête et l'ouvre ici (?lang=, js/admin.js). Les onglets de
   langue de l'éditeur feraient une seconde façon d'en changer. */
.admin-solo .admin-lang-tabs,
.admin-solo .card-lang-tabs,
.admin-solo :has(> [data-rep-lang]),
.admin-solo :has(> [data-hours-lang]) { display: none !important; }
/* AJOUTER DEPUIS LA SURFACE (?ajout=) : le formulaire de l'écran, sans sa
   fenêtre — le choix du type de composant, ou le nom et le modèle d'une
   section. Le bouton qui l'ouvrait et la suppression d'un modèle restent à
   l'écran Contenus. */
.admin-solo--ajout { padding: var(--ad-space-4); background: var(--ad-surface, #fff); }
.admin-solo #toggle-add-section,
.admin-solo .modele-choix__suppr { display: none !important; }
.admin-solo .admin-section-add { display: block; margin: 0; }
/* Replier la carte n'a pas de sens ici : c'est la surface qui ferme. */
.admin-solo .admin-block__toggle { pointer-events: none; }

/* LE HAUT D'UN ÉCRAN — une seule façon de faire (2026-10-19, relevé par
   Patrick : « parfois un titre, parfois une carte sans titre, parfois une carte
   avec titre, parfois un message sur fond jaune collé en haut… brouillon »).
     1. le titre de l'écran (h1, adminHeader()) ;
     2. les onglets, si l'écran en a ;
     3. le CHAPÔ : une ou deux phrases qui disent à quoi sert l'écran — du texte
        simple, jamais une carte, jamais un fond coloré ;
     4. les cartes, chacune titrée dès qu'il y en a plusieurs.
   Une carte qui ne contient qu'une explication n'est pas une carte : c'est un
   chapô. Le jaune (admin-alert--warn) est réservé à ce qui demande un geste.
   Le chapô d'un onglet (blog, agenda, traductions…) prend la même classe. */
.admin-chapo {
    margin: 0 0 var(--ad-space-5);
    max-width: 75ch;
    color: var(--ad-text-mute);
    line-height: 1.55;
}
.admin-chapo strong { color: var(--ad-text); }

/* ----------------------------------------------------------
   Forms
   ---------------------------------------------------------- */
.admin-form__group { margin-bottom: var(--ad-rythme-champ); }
.admin-form__group--inline {
    display: flex;
    align-items: center;
    gap: var(--ad-space-3);
    margin-bottom: var(--ad-rythme-champ);
}

.admin-form__label {
    display: block;
    font-size: var(--ad-fs-sm);
    font-weight: 600;   /* NIVEAU 3 : un peu plus appuyée (500 jusqu'au 2026-10-22) */
    margin-bottom: 6px;
    color: var(--ad-text);
}

/* Note sous un champ. Trois corrections du 2026-08-30 :
   - `display:block` + marge explicite : la classe est portée par <p> (83 fois),
     <small> (74) et <span> (5), or admin.css ne remet à zéro aucune marge de <p>.
     Un hint en <p> gardait donc le margin-bottom du navigateur et un hint en
     <small>, inline, se collait à droite d'un champ étroit. Même rôle, trois
     espacements. Cela rend inutile la rustine .admin-grid-2 > small (plus bas).
   - couleur --ad-text-soft au lieu de --ad-text-mute : le gris de commentaire
     passait AA de justesse (4,57:1 sur le fond le plus sombre de l'admin) sur un
     texte que l'on doit pouvoir LIRE, pas seulement deviner. #454a53 est à 9:1.
   - taille pilotée par --ad-fs-hint, le token de rôle. */
.admin-form__hint {
    display: block;
    font-weight: 400;
    color: var(--ad-text-soft);
    font-size: var(--ad-fs-hint);
    line-height: 1.45;
    margin: 6px 0 0;
    max-width: 68ch;   /* jamais plus large qu'une ligne de lecture (2026-10-22) */
}

/* L'AIR AUTOUR D'UNE AIDE — une règle, pour tout l'admin (2026-09-24).
   La note n'avait que 3 px au-dessus et RIEN en dessous : juste sous son champ,
   c'était voulu ; partout ailleurs, elle se collait au bloc suivant. Mesuré sur
   27 écrans : 59 notes sur 137 à moins de 8 px de leur voisin, la plupart à 0 px
   d'une liste, d'un tableau, d'un bouton ou d'un formulaire.
   Deux rôles, deux espacements :
   - ACCROCHÉE À UN CHAMP (elle suit un libellé, un champ, une case) : 3 px au-dessus,
     elle appartient à ce qu'elle commente ;
   - PARAGRAPHE À PART (tête de carte, avant une liste, entre deux blocs) : un
     paragraphe, 12 px au-dessus et au-dessous — deux notes voisines partagent
     ces 12 px, les marges verticales se confondant.
   Dans un groupe de champs, 8 px suffisent avant ce qui suit : on reste dans le
   même réglage. Les surcharges locales (.admin-i18n__tools, .legal-etat…) sont plus
   précises et gardent la main. */
.admin-form__hint:not(:last-child) { margin-bottom: var(--ad-space-3); }
p.admin-form__hint { margin-top: var(--ad-space-3); }
p.admin-form__hint:first-child { margin-top: 0; }
:is(input, select, textarea, label, .admin-form__label, .admin-form__input, .admin-form__select,
    .admin-form__textarea, .admin-form__check, .admin-form__password, .link-picker) + .admin-form__hint { margin-top: 6px; }
.admin-form__group > .admin-form__hint:not(:last-child) { margin-bottom: var(--ad-space-2); }

.admin-form__input,
.admin-form__select,
.admin-form__textarea,
select.admin-form__input,
input[type="text"].admin-form__input,
input[type="number"].admin-form__input,
input[type="email"].admin-form__input,
input[type="password"].admin-form__input,
input[type="url"].admin-form__input {
    width: 100%;
    min-height: var(--ad-field-h);
    padding: var(--ad-field-py) var(--ad-field-px);
    border: 1px solid var(--ad-field-bd);
    border-radius: var(--ad-r-sm);
    background: var(--ad-bg);
    color: var(--ad-text);
    font: inherit;
    font-size: var(--ad-fs-md);
    line-height: 1.3;
    transition: border-color .12s ease, box-shadow .12s ease, background .12s ease;
}

/* ----------------------------------------------------------
   LISTES DÉROULANTES — LA MÊME HAUTEUR QUE LES CHAMPS DE SAISIE (2026-09-16)

   Elles étaient à 30px, et l'argument tenait : un <select> n'accueille pas de
   frappe, il n'a donc pas besoin de la surface de confort d'un champ texte, et
   les écrans qui en alignent beaucoup (Mise en forme d'un composant, réglages
   d'une ligne, Charte graphique) y gagnaient en densité.

   CE QUE CET ARGUMENT NE VOYAIT PAS : les formulaires qui MÊLENT les deux. Une
   liste et un champ côte à côte ne s'alignaient pas, à huit pixels près — assez
   pour se voir, trop peu pour qu'on sache le nommer. Relevé par Patrick sur les
   réglages de section, où la grille met justement les deux en vis-à-vis.

   On monte les listes plutôt que de descendre les champs, pour trois raisons :
   38px reste au-dessus du plancher des listes (au-dessous de 30, la flèche
   native se serre et le texte touche la bordure) ; c'est la hauteur qui garde
   la cible tactile bien au-delà des 24px du critère WCAG 2.5.8 ; et elle
   s'accorde aux 40px du bouton, donc un champ suivi d'un bouton s'aligne.

   Le padding suit la même règle que le champ de saisie : sans cela, le texte
   de la liste flotterait dans une boîte devenue plus haute que lui.

   AVANT, LE BACK-OFFICE EN AVAIT TROIS : 38px pour la famille `admin-form`,
   33px pour `design-field` (padding propre, sans `min-height`), 30px pour les
   listes. Il n'en reste qu'une. `--small` ne l'a jamais changée : cette
   variante-là restreint la LARGEUR.
   ---------------------------------------------------------- */
select.admin-form__input,
select.admin-form__select,
select.design-field__input,
.admin-form__select {
    min-height: var(--ad-select-h);
    padding: var(--ad-field-py) var(--ad-field-px);
    line-height: 1.25;
}

.admin-form__input:hover,
.admin-form__select:hover,
.admin-form__textarea:hover {
    border-color: var(--ad-field-bd-h);
}

.admin-form__input::placeholder,
.admin-form__textarea::placeholder { color: var(--ad-text-mute); }

.admin-form__input:focus,
.admin-form__select:focus,
.admin-form__textarea:focus,
.admin-form__input:focus-visible,
.admin-form__select:focus-visible,
.admin-form__textarea:focus-visible {
    outline: 2px solid transparent; /* la bordure le dit ; le contour reparaît en contraste élevé */
    border-color: var(--ad-accent);
    box-shadow: 0 0 0 3px var(--ad-focus);
}

/* Champ mot de passe avec bouton « afficher / masquer » */
.admin-form__password { position: relative; }
.admin-form__password .admin-form__input { padding-right: 42px; }

.admin-form__password-toggle {
    position: absolute;
    top: 50%;
    right: 4px;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 0;
    border-radius: var(--ad-r-sm);
    background: transparent;
    color: var(--ad-text-mute);
    cursor: pointer;
    transition: color .12s ease, background .12s ease;
}
.admin-form__password-toggle:hover { color: var(--ad-text); background: rgba(0,0,0,.04); }
.admin-form__password-toggle:focus-visible {
    outline: 2px solid var(--ad-accent); outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--ad-focus);
    color: var(--ad-text);
}

/* Par défaut (mot de passe masqué) : icône « œil » ; état affiché : « œil barré ». */
.admin-form__password-icon--hide { display: none; }
.admin-form__password-toggle[aria-pressed="true"] .admin-form__password-icon--show { display: none; }
.admin-form__password-toggle[aria-pressed="true"] .admin-form__password-icon--hide { display: block; }

.admin-form__input--small { max-width: 120px; }
/* Employée par admin/blocks.php et admin/forms.php — et longtemps déclarée nulle part :
   les menus déroulants qu'elle devait resserrer prenaient toute la largeur
   disponible, en travers de leur ligne. Même valeur que son voisin de
   dessus, dont elle est le pendant pour un `<select>`. */
.admin-form__select--small { max-width: 160px; }
.admin-form__input--xsmall { max-width: 80px; }

.admin-form__input--mono  {
    font-family: var(--ad-font-mono);
}

.admin-form__textarea {
    resize: vertical;
    min-height: 96px;
}

/* Champ de CODE (composant « HTML libre ») : même plancher que l'éditeur de
   texte enrichi et que sa vue source, pour que trois champs qui montrent la
   même chose — du balisage — ouvrent à la même taille. `white-space: pre` n'y
   est pas : ce champ-ci se saisit à la main, une ligne repliée s'y lit mieux
   qu'une ligne qui sort du cadre. */
textarea.admin-form__input--mono {
    min-height: var(--wys-h, 180px);
    tab-size: 4;
}

/* Rangée de boutons dans une carte (Sauvegarde manuelle, Copie de travail,
   écrans Galeries et Portfolio). Onze rangées la portaient sans qu'aucune
   règle n'existe : deux boutons passés à la ligne sur téléphone se touchaient.
   Alignée à gauche, contrairement à .admin-form__actions qui clôt un formulaire. */
.admin-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--ad-space-2);
    align-items: center;
}

.admin-form__actions {
    margin-top: var(--ad-space-4);
    display: flex;
    gap: var(--ad-space-2);
    align-items: center;
    justify-content: flex-end; /* boutons d'action alignés à droite */
}

.admin-form--inline {
    display: flex;
    align-items: center;
    gap: var(--ad-space-2);
    flex-wrap: wrap;
}

.admin-form__submit { width: 100%; }

.inline-form { display: inline; }

/* Numeric steppers : hide arrows for cleaner look */
input[type=number].admin-form__input::-webkit-outer-spin-button,
input[type=number].admin-form__input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* ----------------------------------------------------------
   Buttons (admin scope)
   ---------------------------------------------------------- */
body.admin-body .btn,
body.admin-login-body .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--ad-space-2);
    padding: var(--ad-btn-py) var(--ad-btn-px);
    min-height: var(--ad-btn-h);
    font: inherit;
    font-size: var(--ad-fs-md);
    font-weight: 500;
    line-height: 1.2;
    border-radius: var(--ad-r-sm);
    border: 1px solid var(--ad-btn-bd);
    background: var(--ad-btn-bg);
    color: var(--ad-text);
    cursor: pointer;
    text-decoration: none;
    box-shadow: var(--ad-shadow-sm);
    transition: background .12s ease, border-color .12s ease, color .12s ease,
                box-shadow .12s ease, transform .04s ease;
}

body.admin-body .btn:hover,
body.admin-login-body .btn:hover {
    background: var(--ad-btn-bg-h);
    border-color: var(--ad-btn-bd-h);
    text-decoration: none;
}
body.admin-body .btn:active,
body.admin-login-body .btn:active { transform: translateY(1px); }

body.admin-body .btn:focus-visible,
body.admin-login-body .btn:focus-visible {
    outline: 2px solid var(--ad-accent); outline-offset: 2px;
    box-shadow: 0 0 0 3px var(--ad-focus);
}

body.admin-body .btn .lt-icon,
body.admin-login-body .btn .lt-icon { width: 16px; height: 16px; }

body.admin-body .btn--primary,
body.admin-login-body .btn--primary {
    background: var(--ad-accent);
    color: #fff;
    border-color: var(--ad-accent);
    font-weight: 600;
    box-shadow: 0 1px 2px color-mix(in srgb, var(--ad-accent) 32%, transparent);
}
body.admin-body .btn--primary:hover,
body.admin-login-body .btn--primary:hover {
    background: var(--ad-accent-h);
    border-color: var(--ad-accent-h);
    color: #fff;
    box-shadow: 0 4px 14px color-mix(in srgb, var(--ad-accent) 34%, transparent);
}

body.admin-body .btn--outline {
    background: var(--ad-bg);
    border: 1px solid var(--ad-accent);
    color: var(--ad-accent);
    font-weight: 600;
}
body.admin-body .btn--outline:hover {
    background: var(--ad-accent-sf);
    border-color: var(--ad-accent);
    color: var(--ad-accent-h);
}

body.admin-body .btn--ghost {
    border-color: var(--ad-btn-bd);
    background: var(--ad-bg);
    color: var(--ad-text);
    box-shadow: none;
}
body.admin-body .btn--ghost:hover {
    background: var(--ad-btn-bg);
    border-color: var(--ad-btn-bd-h);
    color: var(--ad-text);
}

body.admin-body .btn--danger {
    color: var(--ad-danger);
    border-color: color-mix(in srgb, var(--ad-danger) 40%, var(--ad-btn-bd));
    background: var(--ad-bg);
}
body.admin-body .btn--danger:hover {
    background: var(--ad-danger-sf);
    border-color: var(--ad-danger);
    color: var(--ad-danger);
}

body.admin-body .btn--sm {
    padding: var(--ad-btn-py-sm) var(--ad-btn-px-sm);
    min-height: var(--ad-btn-h-sm);
    font-size: var(--ad-fs-sm);
}

/* UN BOUTON DÉSACTIVÉ DOIT EN AVOIR L'AIR.
   Il n'existait AUCUNE règle générale pour `.btn[disabled]` dans tout l'admin —
   seulement quatre règles locales (media-picker, link-modal, block-picker,
   btn--dark). Partout ailleurs, un bouton désactivé gardait l'apparence exacte
   d'un bouton actif : la corbeille d'une ligne unique, par exemple, invitait au
   clic et ne faisait rien. C'est le pire des deux mondes — ni action, ni
   explication. La règle vaut pour les DEUX corps, comme `.btn` lui-même.
   L'infobulle, elle, se pose au cas par cas : le style dit QUE c'est éteint, le
   `title` dit POURQUOI. Les deux sont nécessaires. */
body.admin-body .btn[disabled],
body.admin-body .btn.is-disabled,
body.admin-login-body .btn[disabled] {
    opacity: .4;
    cursor: not-allowed;
    box-shadow: none;
}
body.admin-body .btn[disabled]:hover,
body.admin-body .btn.is-disabled:hover,
body.admin-login-body .btn[disabled]:hover {
    background: var(--ad-bg);
    border-color: var(--ad-btn-bd);
    color: var(--ad-text);
    transform: none;
}
body.admin-body .btn--danger[disabled]:hover {
    color: var(--ad-danger);
    border-color: color-mix(in srgb, var(--ad-danger) 40%, var(--ad-btn-bd));
}

/* Bouton NOIR. Une seule action le porte aujourd'hui : « Traduire », dans la
   barre de langues. Le noir la sort de la hiérarchie de marque sans lui voler
   la place du bouton principal — deux pleins de la même famille de couleur, en
   revanche, brouilleraient la lecture. Scopé sur les DEUX corps comme .btn et
   .btn--primary : les autres modificateurs n'existent que sous .admin-body,
   ce qui est un oubli à ne pas reproduire. */
body.admin-body .btn--dark,
body.admin-login-body .btn--dark {
    background: var(--ad-text);
    border-color: var(--ad-text);
    color: #fff;
    font-weight: 600;
}
body.admin-body .btn--dark:hover,
body.admin-login-body .btn--dark:hover {
    background: #000;
    border-color: #000;
    color: #fff;
}
body.admin-body .btn--dark[disabled],
body.admin-body .btn--dark.is-disabled {
    background: var(--ad-text-mute);
    border-color: var(--ad-text-mute);
}

/* ----------------------------------------------------------
   Alerts
   ---------------------------------------------------------- */
/* ⚠️ PAS `display: flex` PAR DÉFAUT.
   Le flex avait été posé pour le motif « icône + texte », où il n'y a que deux
   enfants. Mais un item flex est créé pour CHAQUE enfant — y compris chaque
   nœud de texte anonyme entre deux balises. Une alerte au texte enrichi
   (« <strong>Ce que fait ce bouton.</strong> Il remet en place… ») se retrouvait
   donc découpée en quatre ou cinq COLONNES, illisible. Constaté le 2026-09-03
   sur l'écran « Mise à jour » et sur le bandeau de licence.
   Le texte coule normalement ; le flex ne revient que là où il sert vraiment,
   c'est-à-dire quand une icône ouvre l'alerte. Si `:has()` n'était pas compris,
   la dégradation est douce : l'icône passe simplement au-dessus du texte. */
.admin-alert {
    display: block;
    padding: var(--ad-space-3) var(--ad-space-4);
    border-radius: var(--ad-r-sm);
    margin-bottom: var(--ad-space-4);
    font-size: var(--ad-fs-md);
    line-height: 1.5;
    border: 1px solid transparent;
}
.admin-alert:has(> .lt-icon),
.admin-alert:has(> svg) {
    display: flex;
    align-items: flex-start;
    gap: var(--ad-space-2);
}
.admin-alert .lt-icon { flex: 0 0 auto; margin-top: 1px; }
/* Une alerte d'UNE ligne : le constat à gauche, le geste à droite (bandeau
   « module sans page », 2026-10-19). Sous 700 px, le bouton passe dessous. */
.admin-alert--ligne {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--ad-space-2) var(--ad-space-4);
}
.admin-alert--ligne .inline-form { margin: 0; }
/* Un message posé entre les blocs d'un écran prend le même écart qu'eux
   (24 px, comme carte → carte et chapô → carte). Deux messages qui se suivent
   restent groupés. Mesuré le 2026-10-19 : 16 px ici, 24 px partout ailleurs. */
.admin-content > .admin-alert,
.admin-content > form > .admin-alert { margin-bottom: var(--ad-space-5); }
.admin-content > .admin-alert:has(+ .admin-alert),
.admin-content > form > .admin-alert:has(+ .admin-alert) { margin-bottom: var(--ad-space-3); }

/* --- La croix de fermeture (posée par js/admin.js) ------------------------
   POSITIONNÉE, et non posée dans le flux : une alerte est tantôt `block`,
   tantôt `flex` (quand elle porte une icône), et un bouton en fin de flux
   se serait rangé différemment dans les deux cas — au bout de la phrase ici,
   sur sa propre ligne là. Le coin haut-droit est le même partout.
   La réserve de place est portée par le conteneur (padding-right), sinon un
   texte long passerait SOUS la croix. */
.admin-alert--dismissible {
    position: relative;
    padding-right: calc(var(--ad-space-4) + 22px);
}
.admin-alert__close {
    position: absolute;
    top: 6px;
    right: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    /* Un bouton n'hérite ni de la police ni de l'interligne : on le lui dit. */
    font: inherit;
    line-height: inherit;
    color: inherit;
    background: none;
    border: 0;
    border-radius: var(--ad-r-xs);
    /* Discrète au repos — elle ne doit pas concurrencer le message qu'elle
       ferme —, franche au survol pour rester trouvable. */
    opacity: .5;
    cursor: pointer;
    transition: opacity var(--ad-trans) ease, background var(--ad-trans) ease;
}
.admin-alert__close:hover { opacity: 1; background: rgba(0, 0, 0, .07); }
.admin-alert__close:focus-visible {
    outline: 2px solid var(--ad-accent); outline-offset: 2px;
    opacity: 1;
    box-shadow: 0 0 0 3px var(--ad-focus);
}
/* Un lien de fin d'alerte respire, au lieu de coller au dernier mot. */
.admin-alert > a { margin-left: .35em; }
/* Une alerte peut porter une ACTION plutôt qu'un lien de texte : après une mise
   à jour, « recharger » n'est pas une note de bas de page, c'est le geste
   suivant. Le bouton s'en détache par un espace, et ne prend pas la marge des
   liens en ligne ci-dessus. */
.admin-alert__action { margin: .75rem 0 0; }
.admin-alert__action > a { margin-left: 0; }
.admin-alert--success { background: var(--ad-success-sf); color: var(--ad-success); border-color: #bfe0cd; }
.admin-alert--error   { background: var(--ad-danger-sf);  color: var(--ad-danger);  border-color: #f2c6c2; }
.admin-alert--warn    { background: var(--ad-warn-sf);    color: var(--ad-warn);    border-color: #ecd79a; }
.admin-alert--info    {
    background: var(--ad-accent-sf);
    color: var(--ad-text-soft);
    border-color: color-mix(in srgb, var(--ad-accent) 30%, var(--ad-border));
}
.admin-alert--info .lt-icon { color: var(--ad-accent); }

/* ----------------------------------------------------------
   Cards / panels
   ---------------------------------------------------------- */
.admin-card {
    background: var(--ad-bg);
    border: 1px solid var(--ad-border);
    border-radius: var(--ad-r-lg);
    padding: var(--ad-space-6);
    margin-bottom: var(--ad-space-5);
    box-shadow: var(--ad-shadow-sm);
}

/* CARTE D'ACTION IRRÉVERSIBLE. Un écran de confirmation ne doit pas ressembler
   à l'écran ordinaire qu'il remplace : le rail rouge et le titre coloré disent,
   avant qu'on lise, qu'on est sorti du travail courant. Réservé à ce qui ne se
   défait pas — aujourd'hui la suppression d'une langue et de ses traductions. */
.admin-card--danger {
    border-color: color-mix(in srgb, var(--ad-danger) 35%, var(--ad-border));
    border-left: 4px solid var(--ad-danger);
}
body.admin-body .admin-card--danger > h2 { color: var(--ad-danger); }

.admin-card__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--ad-space-3);
    margin: 0 0 var(--ad-space-4);
    padding-bottom: var(--ad-space-3);
    border-bottom: 1px solid var(--ad-border);
}

/* Variante EMPILÉE : le titre sur sa ligne, le texte dessous. La tête ordinaire
   pose un titre et un BOUTON côte à côte ; un titre suivi d'une phrase, lui,
   s'écrasait dans une colonne étroite à gauche du texte (aperçu du menu). Le
   filet du h2 saute : la tête porte déjà le sien. */
.admin-card__head--empile {
    flex-direction: column;
    align-items: stretch;
    gap: var(--ad-space-2);
}
body.admin-body .admin-card__head--empile > h2 {
    margin: 0;
    padding-bottom: 0;
    border-bottom: 0;
}
.admin-card__head--empile > .admin-card__sub { margin: 0; }

.admin-card__title {
    font-size: var(--ad-fs-lg);
    font-weight: 600;
    margin: 0;
}

/* NIVEAU 1 — le titre de carte : plus grand, plus d'air dessous (2026-10-22 ;
   17,5 px et 12 px jusque-là). */
body.admin-body .admin-card h2 {
    font-size: 20px;
    font-weight: 650;
    letter-spacing: -.01em;
    margin: 0 0 24px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--ad-border);
}

.admin-card__sub {
    font-size: var(--ad-fs-xs);
    color: var(--ad-text-mute);
    margin: 0 0 var(--ad-space-3);
}

/* ----------------------------------------------------------
   Tables
   ---------------------------------------------------------- */
/* UNE SEULE RÈGLE POUR TOUS LES TABLEAUX DE L'ADMINISTRATION (2026-09-08).
   Douze écrans affichent un tableau, et ils traitaient l'étroitesse de quatre
   façons choisies au cas par cas : cette enveloppe partagée, trois enveloppes
   propres à un écran qui redisaient la même ligne, un masquage de colonnes, et
   trois écrans sans rien du tout. Le client apprenait une manipulation par
   écran ; chaque nouvel écran redécidait.

   DEUX STRATÉGIES, ET UN CRITÈRE POUR CHOISIR — pas quatre habitudes :

     1. Le tableau a des COLONNES qui ne peuvent pas rétrécir (une matrice de
        droits, une liste d'utilisateurs, un journal de mises à jour) : il est
        enveloppé dans `.admin-table-scroll` et défile horizontalement, seul,
        sans emporter la page.
     2. Le tableau est une suite d'ÉTIQUETTE / VALEUR dont la valeur peut se
        couper (une demande reçue) : `table-layout: fixed` et
        `overflow-wrap: anywhere` la font passer à la ligne. Rien à faire
        défiler, donc rien à envelopper. Voir `.admin-sub__table`.

   Le masquage de colonnes sous un seuil reste possible, mais seulement quand
   une colonne est vraiment secondaire, et il se documente là où il est écrit
   (voir `.admin-table--users`). Ce n'est pas une troisième stratégie par
   défaut : c'est une exception nommée.

   Les trois enveloppes propres ci-dessous gardent leur nom — il porte du sens
   dans le balisage de leur écran — mais plus leur copie de la règle. */
.admin-table-scroll,
.perm-table-wrap,
.admin-preview-wrap,
.admin-help__table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.admin-table-scroll .admin-table { min-width: 32rem; }

.admin-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--ad-fs-md);
    background: var(--ad-bg);
    border: 1px solid var(--ad-border);
    border-radius: var(--ad-r-sm);
    overflow: hidden;
}

.admin-table th {
    font-weight: var(--ad-fw-label);
    text-align: left;
    padding: 8px 12px;
    background: var(--ad-bg-alt);
    border-bottom: 1px solid var(--ad-border);
    font-size: var(--ad-fs-label);
    text-transform: uppercase;
    letter-spacing: var(--ad-ls-label);
    color: var(--ad-text-soft);
}

.admin-table td {
    padding: 8px 12px;
    border-bottom: 1px solid var(--ad-border);
    vertical-align: middle;
}
.admin-table tr:last-child td { border-bottom: none; }
.admin-table tr:hover td { background: var(--ad-bg-alt); }

/* LA COLONNE D'ACTIONS (2026-10-18) : les icônes Copier, Ouvrir, Modifier,
   Dupliquer, Supprimer, en bout de ligne. Dans une table qui défile
   (`.admin-table-scroll`), elle reste COLLÉE au bord droit : sur un téléphone,
   on fait glisser les colonnes, jamais les boutons hors de l'écran. Elle porte
   donc son propre fond — sans lui, le texte qui défile passerait dessous. */
/* (Espacement et retour à la ligne : admin-editeurs.css, où la classe est née.) */
.admin-table__actions .inline-form,
.admin-table__actions .admin-inline-form { display: inline-block; margin: 0; }
/* `overflow: hidden` arrondit la table, mais en fait le repère de `sticky` : la
   colonne restait collée… à la table elle-même, donc hors de l'écran. `clip`
   coupe pareil sans créer ce repère — c'est la zone qui défile qui compte. */
.admin-table-scroll .admin-table { overflow: clip; }
.admin-table-scroll .admin-table__actions {
    position: sticky;
    right: 0;
    background: var(--ad-bg);
    box-shadow: inset 1px 0 0 var(--ad-border);
}
.admin-table-scroll th.admin-table__actions { background: var(--ad-bg-alt); }
.admin-table-scroll tr:hover td.admin-table__actions { background: var(--ad-bg-alt); }

/* ----------------------------------------------------------
   Badges
   ---------------------------------------------------------- */
.admin-badge {
    display: inline-block;
    font-size: var(--ad-fs-badge);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .05em;
    padding: 2px 7px;
    border-radius: var(--ad-r-xs);
    background: var(--ad-bg-sunk);
    color: var(--ad-text-soft);
}
.admin-badge--success { background: var(--ad-success-sf); color: var(--ad-success); }
.admin-badge--muted   { background: var(--ad-bg-sunk);    color: var(--ad-text-mute); }
.admin-badge--warn    { background: var(--ad-warn-sf);    color: var(--ad-warn); }
.admin-badge--danger  { background: var(--ad-danger-sf);  color: color-mix(in srgb, var(--ad-danger) 82%, #000); } /* 4,2:1 → 6:1 sur son fond rose (mesuré 2026-10-02) */
a.admin-badge--warn,
a.admin-badge--danger { text-decoration: none; }

/* Section dépliable (résumé cliquable avec chevron) — Nouveautés / Historique */
.admin-collapse__summary { cursor: pointer; list-style: none; }
.admin-collapse__summary::-webkit-details-marker { display: none; }
.admin-collapse__summary h2 { display: inline-flex; align-items: center; gap: .5em; margin: 0; }
.admin-collapse__summary h2::after {
    content: "▸"; font-size: .7em; color: var(--ad-text-mute); transition: transform .15s ease;
}
details[open] > .admin-collapse__summary h2::after { transform: rotate(90deg); }

/* ----------------------------------------------------------
   Tabs
   ---------------------------------------------------------- */
/* UN SEUL ONGLET DANS TOUTE L'ADMINISTRATION (2026-10-19, relevé par Patrick :
   « une fois 15px et une fois 14px »). Deux familles coexistaient : celle-ci
   (15 px, graisse 500, onglet actif en encre) et `.admin-page-tab` d'admin-ecrans.css
   (14 px, 600, actif en accent), plus les ancres de la Charte (14 px, 500, actif en
   encre). Mesuré écran par écran, tous ont désormais les MÊMES valeurs :
   14 px (--ad-fs-sm), graisse 600, 7px 12px, gris au repos, accent quand actif.
   Qui touche à l'une de ces trois règles touche aux deux autres. */
.admin-tabs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ad-space-1);
    margin-bottom: var(--ad-space-4);
    border-bottom: 1px solid var(--ad-border);
}

.admin-tab {
    padding: 7px var(--ad-space-3);
    font-size: var(--ad-fs-sm);
    font-weight: 600;
    text-decoration: none;
    color: var(--ad-text-mute);
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    transition: color var(--ad-trans) ease, border-color var(--ad-trans) ease;
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: var(--ad-space-2);
}
/* `.admin-main a` colore tout lien en accent : sans cette reprise, les onglets
   AU REPOS d'En-tête & pied, du Portfolio, des Demandes et de l'éditeur de page
   étaient tous orange, comme l'onglet actif. */
.admin-main .admin-tab { color: var(--ad-text-mute); }
.admin-main .admin-tab:hover { color: var(--ad-text); text-decoration: none; }
.admin-main .admin-tab.is-active {
    color: var(--ad-accent);
    border-bottom-color: var(--ad-accent);
}
.admin-tab__count {
    font-size: var(--ad-fs-badge);
    background: var(--ad-bg-sunk);
    color: var(--ad-text-mute);
    padding: 1px 6px;
    border-radius: 10px;
}
.admin-tabs__page {
    margin: 0 0 var(--ad-space-2);
    font-size: var(--ad-fs-sm);
    color: var(--ad-text-mute);
}

/* Fil d'ariane de l'éditeur : Pages › [1] Page X › [2] Section Y. Reprend les
   étiquettes de niveau, donc le chemin se lit avec le même code couleur que les
   écrans qu'il relie. */
.admin-crumb {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 7px;
    margin: 0 0 var(--ad-space-3);
    font-size: var(--ad-fs-sm);
    color: var(--ad-text-soft);
}
/* Le lien du fil se VOIT comme un lien (2026-10-20) : il remplace le bouton
   « ← Retour aux pages » de l'en-tête. Accent et graisse, souligné au survol. */
.admin-main .admin-crumb > a { color: var(--ad-accent); font-weight: 600; text-decoration: none; }
.admin-main .admin-crumb > a:hover { color: var(--ad-accent-h); text-decoration: underline; text-underline-offset: 3px; }
.admin-crumb__sep { color: var(--ad-text-mute); }

/* Sélecteur de page : le nom de la page, devenu un menu (admin/includes/page-switcher.php). */
.admin-pageswitch { position: relative; }
.admin-pageswitch__btn {
    /* Un VRAI CHAMP : cadre, fond et flèche nette — on voit qu'on peut cliquer. */
    display: inline-flex; align-items: center; justify-content: space-between; gap: 10px;
    min-width: 11rem; padding: 6px 12px;
    border: 1px solid var(--ad-border); border-radius: var(--ad-r-md);
    background: var(--ad-bg); font-size: var(--ad-fs-base, 1rem);
    cursor: pointer; list-style: none;
}
.admin-pageswitch__btn::-webkit-details-marker { display: none; }
.admin-pageswitch__btn:hover,
.admin-pageswitch[open] > .admin-pageswitch__btn { background: var(--ad-bg-soft, rgba(0,0,0,.06)); border-color: var(--ad-accent); }
.admin-pageswitch__btn:focus-visible { outline: 2px solid var(--ad-accent); outline-offset: 1px; }
/* La flèche : un triangle plein, net à toute taille (le glyphe texte restait minuscule). */
.admin-pageswitch__chev {
    width: 0; height: 0; font-size: 0; flex: 0 0 auto;
    border-left: 5px solid transparent; border-right: 5px solid transparent;
    border-top: 7px solid var(--ad-accent);
    transition: transform var(--ad-trans) ease;
}
.admin-pageswitch[open] .admin-pageswitch__chev { transform: rotate(180deg); }
.admin-pageswitch__list {
    position: absolute; z-index: 40; top: calc(100% + 4px); left: -4px;
    min-width: 17rem; max-width: min(26rem, 88vw); max-height: 60vh; overflow: auto;
    margin: 0; padding: 4px; list-style: none;
    background: var(--ad-bg); border: 1px solid var(--ad-border); border-radius: var(--ad-r-md);
    box-shadow: 0 8px 24px rgba(0,0,0,.14);
}
.admin-pageswitch__item {
    display: block; padding: 6px 10px; border-radius: 6px;
    color: var(--ad-text); text-decoration: none; font-size: var(--ad-fs-sm);
}
a.admin-pageswitch__item:hover, a.admin-pageswitch__item:focus-visible { background: var(--ad-bg-soft, rgba(0,0,0,.06)); color: var(--ad-accent); text-decoration: none; }
.admin-pageswitch__item--d1 { padding-left: 24px; }
.admin-pageswitch__item--d2 { padding-left: 38px; }
.admin-pageswitch__item--d3 { padding-left: 52px; }
.admin-pageswitch__item.is-current { font-weight: 700; background: color-mix(in srgb, var(--ad-accent) 12%, transparent); }
.admin-pageswitch__item.is-off { color: var(--ad-text-mute); cursor: default; }
.admin-pageswitch__note { margin-left: 6px; color: var(--ad-text-mute); font-size: .85em; }
.admin-pageswitch__all { margin-top: 4px; padding-top: 4px; border-top: 1px solid var(--ad-border); }
.admin-pageswitch__all a { display: block; padding: 6px 10px; font-size: var(--ad-fs-sm); }

/* ----------------------------------------------------------
   BARRE DE LANGUES — le composant unique
   ----------------------------------------------------------
   Une seule rangée : les onglets à gauche, « Traduire » à droite.
   Elle remplace SIX manières concurrentes d'afficher les langues (onglets
   d'éditeur, trois variantes « carte », groupe de boutons du constructeur de
   formulaires, et deux grilles côte à côte). Le côte à côte tenait à 3 langues
   et cassait au-delà ; les onglets, eux, ne coûtent rien de plus au dixième.

   Le balisage est celui que `js/admin.js` et `AdminCore.wireTabs()` savent
   DÉJÀ piloter (.admin-lang-tabs / .admin-lang-tab[data-lang] /
   .admin-lang-content[data-lang]) : aucun moteur d'onglets n'a été écrit, et
   l'ARIA + le clavier viennent gratuitement, y compris là où ils manquaient.
   ---------------------------------------------------------- */
.admin-lang-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--ad-space-2);
    margin-bottom: var(--ad-space-3);
}
/* Dans la barre, les onglets et le bouton portent leur marge de la barre. */
.admin-lang-bar > .admin-lang-tabs,
.admin-lang-bar > .admin-translate { margin: 0; }

.admin-lang-tabs {
    display: flex;
    flex-wrap: wrap;   /* à dix langues, on passe à la ligne au lieu d'écraser */
    gap: 2px;
    margin-bottom: var(--ad-space-3);
    background: var(--ad-bg-sunk);
    padding: 2px;
    border-radius: var(--ad-r-sm);
    width: fit-content;
}

.admin-lang-tab {
    padding: 6px 14px;
    font-size: var(--ad-fs-label);
    font-weight: var(--ad-fw-label);
    text-transform: uppercase;
    letter-spacing: var(--ad-ls-label);
    border: none;
    border-radius: var(--ad-r-xs);
    background: transparent;
    color: var(--ad-text-soft);
    cursor: pointer;
    transition: background .12s ease, color .12s ease;
}

.admin-lang-tab:hover { color: var(--ad-text); background: rgba(0,0,0,.05); }
/* Onglet actif en NOIR : la langue en cours d'édition doit se lire d'un coup
   d'œil, sans dépendre de la couleur de marque du site (qui peut être claire). */
.admin-lang-tab.is-active,
.admin-lang-tab.is-active:hover {
    background: var(--ad-text);
    color: #fff;
}

.admin-lang-content { display: none; }
.admin-lang-content.is-active { display: block; }

/* ----------------------------------------------------------
   Écran Langues — sélecteur d'ajout
   La langue et sa région précèdent les champs qu'elles pré-remplissent ; en
   flex plutôt qu'en grille, pour que la région escamotée (langue sans variante)
   ne laisse pas une colonne vide derrière elle.
   ---------------------------------------------------------- */
.admin-lang-pick {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--ad-space-3);
}
.admin-lang-pick > .admin-form__group {
    flex: 1 1 240px;
    max-width: 380px;
}
.admin-lang-pick > .admin-form__group[hidden] { display: none; }

.admin-lang-notes {
    margin: 0 0 var(--ad-space-4);
    font-size: var(--ad-fs-hint);
    line-height: 1.7;
    color: var(--ad-text-soft);
}
.admin-lang-notes[hidden] { display: none; }
.admin-lang-notes .admin-badge { margin-right: 5px; }

/* Traduction automatique (DeepL) — bouton dans l'éditeur.
   ⚠ Tout bouton « Traduire » DOIT rester enveloppé dans un .admin-translate :
   c'est ce que vise l'interrupteur global (.admin-body--deepl-off, plus bas). */
.admin-translate {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0 0 12px;
}
/* Le bouton n'a pas lieu d'être sur la langue SOURCE : il n'y a rien à y
   traduire. Le JS pose l'attribut au changement d'onglet. */
.admin-translate[hidden] { display: none; }
.btn-translate.is-loading {
    opacity: 0.7;
    cursor: progress;
}
/* Bouton de lot désactivé : rien à traduire (clic neutralisé côté JS). */
.btn-bulk-translate.is-disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
/* Séparateur discret dans une carte admin. */
.admin-sep {
    border: 0;
    border-top: 1px solid var(--ad-border, #e5e7eb);
    margin: var(--ad-space-4, 16px) 0;
}

/* Interrupteur global : masque les boutons « Traduire avec DeepL » des éditeurs
   (les blocs .admin-translate qui contiennent un .btn-translate). La traduction en
   lot de la page Traductions (.btn-bulk-translate) reste, elle, toujours visible. */
.admin-body--deepl-off .admin-translate:has(.btn-translate) {
    display: none;
}
.btn-translate__msg {
    font-size: var(--ad-fs-hint);
    color: var(--ad-text-soft);
}

/* ----------------------------------------------------------
   Block editor
   ---------------------------------------------------------- */
.admin-section-editor h2 {
    font-size: var(--ad-fs-xl);
    font-weight: 650; /* aligné sur .admin-header h1 */
    margin-bottom: var(--ad-space-5);           /* air titre ↔ onglets */
}

.admin-blocks {
    display: flex;
    flex-direction: column;
    gap: var(--ad-space-2);
}

/* Mode colonnes : aperçu des colonnes (ratios via --cols, ou grid-template inline legacy) */
.admin-blocks--cols { display: block; }
.admin-cols {
    display: grid;
    grid-template-columns: var(--cols, 1fr);
    gap: var(--ad-space-3);
    align-items: start;
}
.admin-col {
    display: flex;
    flex-direction: column;
    gap: var(--ad-space-2);
    min-width: 0;
    border: 1px dashed var(--ad-border);
    border-radius: var(--ad-r-md);
    padding: var(--ad-space-2);
    min-height: 60px;
}
.admin-col.is-drop { border-color: var(--ad-accent); background: var(--ad-accent-sf); }
/* L'état « en cours d'édition » est défini plus bas, juste après .admin-block :
   ici il précédait la règle de base, qui l'écrasait dès qu'elles se disputaient
   une propriété (même spécificité → c'est l'ordre du fichier qui tranche). */

/* Zone d'édition pleine largeur (sous les colonnes) */
.admin-edit-zone:empty { display: none; }
.admin-edit-zone { margin-top: var(--ad-space-4); }
.admin-edit-zone .admin-block__body {
    display: block;
    padding: var(--ad-space-4);
    border: 1px solid var(--ad-border);
    border-left: 4px solid var(--ad-lvl1);   /* amarre le formulaire à la carte, restée plus haut */
    border-radius: var(--ad-r-md);
    background: var(--ad-bg);
    box-shadow: var(--ad-open-ring);
}
/* En multi-colonnes, admin.js DÉPLACE le corps de l'éditeur hors de sa carte, vers
   cette zone posée sous la ligne entière : sans rappel, on édite un formulaire sans
   savoir de quel composant il vient. data-editing est renseigné par admin.js. */
.admin-edit-zone[data-editing]::before {
    content: "Édition — " attr(data-editing);
    display: block;
    margin-bottom: var(--ad-space-2);
    font-size: var(--ad-fs-badge);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--ad-accent);
}

/* Barre « Enregistrer » flottante : visible tant qu'un éditeur de bloc est ouvert,
   pour ne jamais avoir à scroller jusqu'au bouton (cf. oubli après DeepL). */
.editor-savebar {
    position: fixed;
    left: 50%;
    bottom: 20px;
    transform: translateX(-50%);
    z-index: 60;
    display: flex;
    align-items: center;
    gap: var(--ad-space-2);
    padding: 7px 8px 7px 14px;
    background: var(--ad-bg);
    border: 1px solid var(--ad-border);
    border-radius: 999px;
    box-shadow: 0 8px 26px rgba(0, 0, 0, .18);
}
.editor-savebar[hidden] { display: none; }
.editor-savebar__warn,
.editor-savebar__msg { display: none; }
/* État « modifications non enregistrées » : ⚠ + libellé + bouton qui pulse. */
.editor-savebar.is-dirty { border-color: var(--ad-danger); }
.editor-savebar.is-dirty .editor-savebar__warn {
    display: inline;
    color: var(--ad-danger);
    font-size: 1.15em;
    line-height: 1;
}
.editor-savebar.is-dirty .editor-savebar__msg {
    display: inline;
    color: var(--ad-danger);
    font-size: var(--ad-fs-sm);
    font-weight: 600;
}
.editor-savebar.is-dirty .editor-savebar__btn { animation: savebarPulse 1.15s ease-in-out infinite; }
@keyframes savebarPulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--ad-danger) 55%, transparent); }
    50%      { box-shadow: 0 0 0 6px color-mix(in srgb, var(--ad-danger) 0%, transparent); }
}
/* Rappel ⚠ sur le bouton Enregistrer intégré à l'éditeur aussi. */
.admin-form__actions.is-dirty .btn--primary::before { content: "⚠ "; }
@media (prefers-reduced-motion: reduce) {
    .editor-savebar.is-dirty .editor-savebar__btn { animation: none; }
}
@media (max-width: 640px) {
    .editor-savebar {
        left: 12px; right: 12px; bottom: 12px;
        transform: none;
        justify-content: flex-end;
        border-radius: var(--ad-r-md);
    }
}
@media (max-width: 900px) {
    .admin-cols { grid-template-columns: 1fr !important; }
}

/* ----------------------------------------------------------
   Builder par LIGNES (admin)
   ---------------------------------------------------------- */
.admin-rows { display: block; }
.admin-row {
    border: 1px solid var(--ad-border);
    border-left: 3px solid var(--ad-lvl3);     /* rail du NIVEAU 3 (ligne) — était
                                                  l'accent pur, donc identique au
                                                  rail des sections : deux niveaux
                                                  différents, une seule couleur. */
    border-radius: var(--ad-r-md);
    background: var(--ad-bg-sunk);              /* niveau creusé (le plus foncé des 3) */
    padding: var(--ad-space-3);
    margin-bottom: var(--ad-space-5);           /* plus d'air entre les lignes */
}
.admin-row__header {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--ad-space-2);
    margin-bottom: var(--ad-space-3);
    padding-bottom: var(--ad-space-2);
    border-bottom: 1px solid var(--ad-border);
}
.admin-row__drag { cursor: grab; color: var(--ad-text-mute); user-select: none; }
.admin-row__drag:active { cursor: grabbing; }

.admin-row__layout { display: inline-flex; gap: var(--ad-space-1); }
/* Le groupe « Publié · visibilité · dupliquer · supprimer » est poussé à droite EN BLOC, comme dans l'en-tête
   d'un composant (2026-10-20, demande de Patrick) : seuls les onglets restent à gauche. C'est la pastille
   « Publié » — la première du groupe — qui porte la marge. */
.admin-row__header > .publish-check { margin-left: auto; }
.admin-row__dup { margin-left: 0; }
.admin-row__delete { margin-left: 0; }

/* Bouton « Ajouter une ligne » aligné à droite, comme les autres actions */
.admin-rows__add {
    display: flex;
    justify-content: flex-end;
    margin-top: var(--ad-space-3);
}

/* Bouton « + Composant » en pied de colonne */
.admin-col__add {
    margin-top: auto;
    padding: var(--ad-space-2);
    border: 1px dashed var(--ad-border-str);
    border-radius: var(--ad-r-sm);
    background: var(--ad-bg);
    color: var(--ad-text-soft);
    font: inherit;
    font-size: var(--ad-fs-sm);
    cursor: pointer;
    transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.admin-col__add:hover {
    border-color: var(--ad-accent);
    color: var(--ad-accent);
    background: var(--ad-accent-sf);
}

/* ----------------------------------------------------------
   Modale d'ajout de composant
   ---------------------------------------------------------- */
.block-modal[hidden] { display: none; }
.block-modal {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--ad-space-4);
}
.block-modal__overlay {
    position: absolute;
    inset: 0;
    background: rgba(15, 18, 24, .55);
}
.block-modal__panel {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 760px;
    max-height: 86vh;
    overflow-y: auto;
    background: var(--ad-bg);
    border: 1px solid var(--ad-border);
    border-radius: var(--ad-r-lg);
    box-shadow: var(--ad-shadow-lg);
    padding: var(--ad-space-5);
}
.block-modal__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--ad-space-4);
}
.block-modal__title { font-size: var(--ad-fs-xl);
    font-weight: 650; /* aligné sur .admin-header h1 */
}
.block-modal__x {
    border: none;
    background: none;
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    color: var(--ad-text-mute);
    padding: 4px 8px;
    border-radius: var(--ad-r-sm);
}
.block-modal__x:hover { background: var(--ad-bg-alt); color: var(--ad-text); }

/* ----------------------------------------------------------
   Sélecteur visuel d'icônes
   Réutilise la coquille .block-modal. Le panneau est large et se scinde en
   trois : en-tête, barre de recherche FIGÉE, corps défilant.
   PAS D'ACCORDÉON, à la demande : les groupes restent dépliés, séparés par
   leur titre, et c'est le corps qui défile. Replier obligerait à deviner dans
   quel groupe chercher ; défiler laisse l'œil balayer.
   ---------------------------------------------------------- */
.icon-lib__panel {
    max-width: 860px;
    display: flex;
    flex-direction: column;
    max-height: 88vh;
    overflow: hidden;                 /* c'est le corps qui défile */
    padding: var(--ad-space-4) var(--ad-space-5);
}
.icon-lib__barre {
    display: flex;
    align-items: center;
    gap: var(--ad-space-2);
    padding-bottom: var(--ad-space-3);
    border-bottom: 1px solid var(--ad-border);
    flex: 0 0 auto;
}
.icon-lib__barre input[type="search"] { flex: 1 1 auto; min-width: 0; }
.icon-lib__corps {
    flex: 1 1 auto;
    overflow-y: auto;
    padding-top: var(--ad-space-3);
    scrollbar-width: thin;
}
.icon-lib__groupe[hidden] { display: none; }
.icon-lib__titre {
    font-size: var(--ad-fs-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--ad-text-mute);
    margin: var(--ad-space-4) 0 var(--ad-space-2);
}
.icon-lib__groupe:first-child .icon-lib__titre { margin-top: 0; }
/* `auto-fill` et non `auto-fit` : une grille filtrée à deux icônes garde des
   cellules de taille normale au lieu de les étirer sur toute la largeur. */
.icon-lib__grille {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
    gap: var(--ad-space-2);
}
.icon-lib__item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: var(--ad-space-3) 6px;
    border: 1px solid var(--ad-border);
    border-radius: var(--ad-r-md);
    background: var(--ad-bg);
    color: var(--ad-text);
    cursor: pointer;
    font: inherit;
    text-align: center;
    transition: border-color var(--ad-trans) ease, background var(--ad-trans) ease;
}
.icon-lib__item[hidden] { display: none; }
.icon-lib__item:hover { border-color: var(--ad-accent); background: var(--ad-bg-alt); }
.icon-lib__item:focus-visible { outline: 2px solid var(--ad-accent); outline-offset: 2px; }
.icon-lib__item.is-selected {
    border-color: var(--ad-accent);
    box-shadow: inset 0 0 0 1px var(--ad-accent);
    color: var(--ad-accent);
}
/* La légende tient sur deux lignes et coupe les mots trop longs : sans cela,
   « Presse-papiers » élargissait sa cellule et cassait l'alignement. */
.icon-lib__legende {
    font-size: var(--ad-fs-xs);
    line-height: 1.25;
    color: var(--ad-text-mute);
    overflow-wrap: anywhere;
}
.icon-lib__item.is-selected .icon-lib__legende { color: inherit; }
.icon-lib__vide {
    padding: var(--ad-space-5) 0;
    text-align: center;
    color: var(--ad-text-mute);
}

/* --- Le CHAMP, dans l'éditeur --- */
.icon-picker { display: inline-flex; align-items: center; gap: 6px; }
.icon-picker__btn {
    display: inline-flex;
    align-items: center;
    gap: var(--ad-space-2);
    padding: 6px var(--ad-space-3);
    border: 1px solid var(--ad-border);
    border-radius: var(--ad-r-sm);
    background: var(--ad-bg);
    color: var(--ad-text);
    font: inherit;
    font-size: var(--ad-fs-sm);
    cursor: pointer;
}
.icon-picker__btn:hover { border-color: var(--ad-accent); }
.icon-picker__vignette {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px; height: 22px;
    flex: 0 0 auto;
    color: var(--ad-accent);
}
.icon-picker__vignette:empty::before {
    content: "—";
    color: var(--ad-text-mute);
}
.icon-picker__nom { color: var(--ad-text-mute); }
.icon-picker__clear {
    border: none; background: none; cursor: pointer;
    font-size: 18px; line-height: 1;
    color: var(--ad-text-mute);
    padding: 2px 6px; border-radius: var(--ad-r-sm);
}
.icon-picker__clear:hover { background: var(--ad-bg-alt); color: var(--ad-danger, #b42318); }

/* ----------------------------------------------------------
   Visionneuse d'un média (bouton « Voir » de la médiathèque)
   Réutilise la coquille .block-modal : rien à écrire pour le voile, le
   centrage, le panneau ni la croix de fermeture. Le panneau se règle sur
   L'IMAGE — largeur au plus juste, jamais plus grande que sa taille réelle,
   pour ne pas montrer une vignette agrandie et floue en promettant « voir ».
   ---------------------------------------------------------- */
.media-view__panel {
    max-width: min(96vw, 1400px);
    width: auto;
    padding: var(--ad-space-4);
}
.media-view__frame {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Damier discret : une image à fond transparent (PNG, WebP) se lit mal sur
       du blanc — on ne saurait pas si le blanc est le fond ou l'image. */
    background-color: var(--ad-bg-alt);
    background-image:
        linear-gradient(45deg, rgba(127, 127, 127, .16) 25%, transparent 25%, transparent 75%, rgba(127, 127, 127, .16) 75%),
        linear-gradient(45deg, rgba(127, 127, 127, .16) 25%, transparent 25%, transparent 75%, rgba(127, 127, 127, .16) 75%);
    background-size: 18px 18px;
    background-position: 0 0, 9px 9px;
    border: 1px solid var(--ad-border);
    border-radius: var(--ad-r-md);
    overflow: hidden;
}
.media-view__frame img,
.media-view__frame video {
    display: block;
    max-width: 100%;
    max-height: calc(86vh - 7rem);    /* place pour l'en-tête et la légende */
    width: auto;
    height: auto;
}
.media-view__meta {
    margin: var(--ad-space-3) 0 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--ad-space-1) var(--ad-space-3);
    font-size: var(--ad-fs-sm);
    color: var(--ad-text-mute);
}
.media-view__meta code {
    font-family: var(--ad-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: .95em;
}
.media-view__meta strong { color: var(--ad-text); font-weight: 600; }

/* ----------------------------------------------------------
   Sélecteur de médias (popup)
   Réutilise la coquille .block-modal ; le panneau est plus large et
   scinde en-tête / corps défilant / pied fixe.
   ---------------------------------------------------------- */
.media-picker__panel {
    max-width: 980px;
    display: flex;
    flex-direction: column;
    max-height: 88vh;
    overflow: hidden;                 /* c'est le corps qui défile */
    padding: var(--ad-space-4) var(--ad-space-5);
}
.media-picker__body {
    flex: 1 1 auto;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--ad-space-3);
    padding-right: 2px;
}

.media-picker__bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ad-space-2);
}
.media-picker__search { flex: 1 1 240px; min-width: 0; }

.media-picker__newfolder {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--ad-space-2);
    padding: var(--ad-space-2);
    background: var(--ad-bg-sunk);
    border-radius: var(--ad-r-sm);
}
.media-picker__newfolder[hidden] { display: none; }

.media-picker__folders { margin-bottom: 0; }
.media-picker__folders .media-folder { cursor: pointer; font-family: inherit; }

/* Zone « Parcourir ou glisser » */
.media-picker__drop {
    border: 1px dashed var(--ad-border);
    border-radius: var(--ad-r-md);
    padding: var(--ad-space-3);
    text-align: center;
    background: var(--ad-bg-sunk);
}
.media-picker__drop[hidden] { display: none; }
.media-picker__drop.is-over {
    border-color: var(--ad-accent, var(--ad-text));
    background: var(--ad-accent-sf, var(--ad-bg-alt));
}
.media-picker__drop-text {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 6px;
    margin-bottom: 4px;
}
.media-picker__progress {
    height: 6px;
    margin-top: var(--ad-space-2);
    background: var(--ad-border);
    border-radius: 999px;
    overflow: hidden;
}
.media-picker__progress[hidden] { display: none; }
.media-picker__progress-bar {
    height: 100%;
    width: 0;
    background: var(--ad-accent, var(--ad-text));
    transition: width .15s linear;
}
.media-picker__errors {
    margin-top: var(--ad-space-2);
    color: var(--ad-danger, #c0392b);
    font-size: var(--ad-fs-sm);
}
.media-picker__errors[hidden] { display: none; }

/* Vignettes sélectionnables */
.media-picker__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
    gap: var(--ad-space-2);
}
/* « Afficher plus » : les médias arrivent par lots (js/admin-media-picker.js). */
.media-picker__more {
    display: block;
    margin: var(--ad-space-3) auto 0;
}
.media-picker__more[hidden] { display: none; }
.media-picker__item {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px;
    background: var(--ad-bg);
    border: 2px solid var(--ad-border);
    border-radius: var(--ad-r-sm);
    cursor: pointer;
    text-align: left;
    font: inherit;
    color: var(--ad-text);
}
.media-picker__item:hover { border-color: var(--ad-text-mute); }
.media-picker__item.is-selected {
    border-color: var(--ad-accent, var(--ad-text));
    background: var(--ad-accent-sf, var(--ad-bg-alt));
}
.media-picker__item img {
    width: 100%;
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: var(--ad-r-xs);
    background: var(--ad-bg-sunk);
}
.media-picker__item-name {
    font-size: var(--ad-fs-xs);
    line-height: 1.3;
    overflow-wrap: anywhere;
}
.media-picker__item-warn {
    align-self: flex-start;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--ad-bg-sunk);
    color: var(--ad-text-mute);
    font-size: var(--ad-fs-xs);
}

/* En mode « choisir un dossier », la grille n'est qu'un aperçu du contenu. */
.media-picker.is-folder-mode .media-picker__item { cursor: default; }
.media-picker.is-folder-mode .media-picker__item:hover { border-color: var(--ad-border); }

/* Textes alternatifs (même donnée que l'écran Médias) */
.media-picker__alt {
    padding: var(--ad-space-3);
    background: var(--ad-bg-sunk);
    border-radius: var(--ad-r-sm);
}
.media-picker__alt[hidden] { display: none; }
.media-picker__alt-title { font-size: var(--ad-fs-md); margin-bottom: 2px; }
.media-picker__alt-fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--ad-space-2);
    margin: var(--ad-space-2) 0;
}
.media-picker__alt-field { display: flex; align-items: center; gap: 6px; }
.media-picker__alt-field span {
    font-size: var(--ad-fs-xs);
    font-weight: 700;
    color: var(--ad-text-mute);
}
.media-picker__alt-field input { flex: 1 1 auto; min-width: 0; }
.media-picker__alt-msg {
    margin-left: var(--ad-space-2);
    font-size: var(--ad-fs-sm);
    color: var(--ad-text-mute);
}

.media-picker__foot {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--ad-space-2);
    margin-top: var(--ad-space-3);
    padding-top: var(--ad-space-3);
    border-top: 1px solid var(--ad-border);
}
.media-picker__count { font-size: var(--ad-fs-sm); color: var(--ad-text-mute); }
.media-picker__actions { display: flex; gap: var(--ad-space-2); }
.media-picker__actions .btn[disabled] {
    opacity: .45;
    cursor: not-allowed;
    pointer-events: none;
}


/* ===========================================================================
   VOILE DE CHARGEMENT — la pastille qui se remplit
   (balisage : admin/includes/admin-functions.php ; conduite : js/admin-loader.js)

   POURQUOI CETTE ANIMATION-LÀ. Un disque qui tourne, tout le monde en fait un ;
   il ne dit rien de plus que « attendez ». Celui-ci se REMPLIT : le orange monte
   dans la pastille, du bas vers le haut, et recommence. On lit « quelque chose
   avance » plutôt que « quelque chose tourne », et la pastille reste la marque
   Artaweb — même disque, même « a » blanc que dans la barre latérale.

   Coût : un SVG de six éléments, deux animations, aucune image, aucune
   dépendance. Le remplissage est un simple rectangle qui monte, découpé par un
   `clipPath` circulaire — pas de masque, pas de filtre.
   ========================================================================= */

.admin-loader {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, .82);
    backdrop-filter: blur(2px);
    animation: admin-loader-in .18s ease-out both;
}

@keyframes admin-loader-in { from { opacity: 0; } to { opacity: 1; } }

.admin-loader__mark { width: 64px; height: 64px; display: block; }
.admin-loader__disc { fill: var(--ad-loader, #ff8736); }
/* La vague : un voile CLAIR qui monte DANS le disque plein.
   Premier essai : disque pâle qu'un remplissage orange venait noircir. Le « a »
   blanc y devenait invisible tant que le niveau était bas — un logo illisible la
   moitié du temps. Ici le disque reste orange en permanence, la lettre reste
   lisible en permanence, et c'est la vague qui bouge. */
.admin-loader__wave { fill: #fff; opacity: .22; }

/* Le niveau qui monte. 1,6 s pour traverser le disque, puis on repart du bas :
   pas d'aller-retour, qui se lirait comme un recul. */
.admin-loader__fill {
    animation: admin-loader-fill 1.6s cubic-bezier(.45, 0, .55, 1) infinite;
    transform-origin: 50% 50%;
}
@keyframes admin-loader-fill {
    0%   { transform: translateY(34px); }
    82%  { transform: translateY(-34px); }
    100% { transform: translateY(-34px); }
}

/* Le « a » respire, très légèrement, au rythme du remplissage. C'est ce qui
   donne l'impression que la pastille est vivante et non figée derrière une
   animation qui la traverse. */
.admin-loader__letter {
    animation: admin-loader-breathe 1.6s ease-in-out infinite;
    /* `fill-box` : l'origine est le centre de la LETTRE, pas celui du SVG.
       Sans lui, elle grandirait en s'échappant vers le bas à droite. */
    transform-box: fill-box;
    transform-origin: center;
}
@keyframes admin-loader-breathe {
    0%, 100% { transform: scale(1); }
    50%      { transform: scale(1.08); }
}

/* Qui demande moins d'animation garde un signal : la pastille pleine, qui
   pulse doucement. Le voile reste utile — il dit que la page travaille. */
@media (prefers-reduced-motion: reduce) {
    .admin-loader { animation: none; }
    .admin-loader__fill { animation: none; transform: translateY(-34px); }
    .admin-loader__letter { animation: admin-loader-breathe 2.4s ease-in-out infinite; }
}

/* Un bouton qui attend sa confirmation DANS la page — quand le navigateur a
   cessé d'afficher les dialogues (voir confirmerAvecRepli, js/admin.js). Il doit
   se distinguer sans crier : c'est une question, pas une alerte. */
.btn.is-confirming {
    background: var(--ad-danger-bg, #fdecec);
    border-color: var(--ad-danger, #c0392b);
    color: var(--ad-danger, #c0392b);
    font-weight: 600;
}

/* BOUTON RÉDUIT À SON ICÔNE — la barre d'actions d'un média en aligne quatre.
   En toutes lettres (« Voir · Renommer · Remplacer · Supprimer »), elles
   pesaient plus que la vignette qu'elles accompagnent et repoussaient les
   fiches suivantes. Carré, le glyphe centré, la surface de clic conservée :
   40 px, au-dessus des 24 px que demandent les règles d'accessibilité pour une
   cible tactile — on gagne en lisibilité, pas en précision de visée.
   ⚠️ Chaque bouton garde `title` ET `aria-label` : une icône seule ne dit rien
   à un lecteur d'écran, et une infobulle ne lui parle pas. */
.btn--icon {
    width: 2.5rem;
    min-width: 2.5rem;
    height: 2.5rem;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.btn--icon svg { width: 1.05rem; height: 1.05rem; }
/* « Copié » sur un bouton à icône : il garde son glyphe et passe au vert. */
.btn.btn--icon.is-copied, .btn.btn--icon.is-copied:hover { color: var(--ad-success); border-color: var(--ad-success); }

/* ============================================================
   FENÊTRE DE CONFIRMATION (js/admin.js → Confirmation)
   ============================================================
   Elle remplace `confirm()`, que Chrome et Safari laissent l'utilisateur
   supprimer d'un coche — après quoi la fonction renvoyait `false` sans rien
   montrer, et plus aucun bouton Supprimer ne marchait. Une fenêtre dans la page
   ne peut pas être supprimée, et elle porte les mots du site.

   `<dialog>` fournit le piège de focus, la touche Échap et le voile : il n'y a
   ici que l'habillage. La règle `[hidden]` du fichier des éditeurs ne la
   concerne pas — un dialogue fermé l'est par son propre attribut `open`. */
.admin-confirm {
    max-width: min(30rem, calc(100vw - 2 * var(--ad-space-4)));
    padding: var(--ad-space-5);
    border: 1px solid var(--ad-border);
    border-radius: var(--ad-r-lg, var(--ad-r-md));
    background: var(--ad-bg);
    color: var(--ad-text);
    box-shadow: 0 18px 50px rgba(17, 24, 39, .18);
}
.admin-confirm::backdrop {
    background: rgba(17, 24, 39, .45);
}
.admin-confirm__texte {
    margin: 0 0 var(--ad-space-4);
    font-size: var(--ad-fs-md);
    line-height: 1.5;
}
/* Les actions à DROITE, et « Annuler » en premier dans le document : c'est lui
   qui reçoit le focus, pour qu'un appui sur Entrée ne valide pas une
   suppression. L'ordre visuel garde « Confirmer » à la place qu'on lui cherche,
   au bout de la ligne. */
.admin-confirm__actions {
    display: flex;
    gap: var(--ad-space-2);
    justify-content: flex-end;
    flex-wrap: wrap;
}


/* =============================================================================
   GLISSER-DÉPOSER AU POINTEUR — js/admin-core.js (moteur commun, étape C, 2026-10-22)
   =============================================================================
   Pendant le geste, l'élément tiré et ses frères se REPLIENT sur leur bande de tête ([data-dnd-tete], la partie qui
   porte la poignée) : toutes les cibles tiennent à l'écran. Une étiquette (.dnd-fantome) suit le pointeur. */
body.dnd-en-cours { user-select: none; -webkit-user-select: none; cursor: grabbing; }
.dnd-replie > :not([data-dnd-tete]) { display: none !important; }
.dnd-replie { min-height: 0 !important; }
.dnd-replie.is-dragging { opacity: .55; outline: 2px dashed var(--ad-accent); outline-offset: 2px; }
.dnd-fantome {
    position: fixed; z-index: 2000; pointer-events: none;
    max-width: 420px; overflow: hidden;
    background: var(--ad-bg); border: 1px solid var(--ad-accent); border-radius: var(--ad-r-md);
    box-shadow: 0 10px 30px rgba(0, 0, 0, .2); opacity: .95;
}
/* La poignée garde le doigt : sans `touch-action: none`, le navigateur prendrait le geste pour un défilement. */
[data-dnd-pointeur] { touch-action: none; cursor: grab; }

/* LE TÉMOIN D'ENREGISTREMENT d'un composant (moteur commun, étape D) — js/admin-enregistrement.js. Plus de bouton
   « Enregistrer » : le formulaire s'enregistre au fil de la saisie, et le témoin dit où il en est. */
.admin-form__etat { flex: 1 1 auto; font-size: var(--ad-fs-sm); color: var(--ad-text-mute); }
.admin-form__etat.is-erreur { color: var(--ad-danger); font-weight: 600; }
