/* ============================================================
   GAP Berstett — Feuille de styles principale
   Complément à Bootstrap 5 — aucun remplacement de classes BS
   ============================================================ */

/* ── Variables ─────────────────────────────────────────────── */
:root {
  /* ── Cœur de marque (charte 2026 : « Rot un Wiss » + vigne + or) ── */
  --gap-red:     #CE2B1E;   /* rouge profond, contraste AA sur blanc */
  --gap-green:   #2A7A3C;   /* vert vigne */
  --gap-gold:    #D4A017;   /* or paille */
  --gap-dark:    #1A1A1A;   /* encre */
  --gap-light:   #F8F6F2;   /* fond de section clair */
  --gap-cream:   #FBF9F5;   /* crème (fond alterné) */
  --gap-paper:   #F3EEE4;   /* papier (survol/relief sur fond crème) */
  --gap-border:  #E2DDD6;

  --gap-red-hover:   #B5271B;
  --gap-green-hover: #1F6030;

  /* ── Extension alsacienne ── */
  --gap-blue:    #274B6D;   /* bleu costume / faïence */
  --gap-clay:    #9A5B3B;   /* terre / colombage */
  --gap-straw:   #E8C662;   /* paille claire (accent doux) */

  /* ── Neutres de texte ── */
  --gap-muted:   #6B6459;   /* texte secondaire lisible (AA sur clair) */

  /* ── Couleurs sémantiques ── */
  --gap-success: #2A7A3C;
  --gap-warning: #D4A017;
  --gap-danger:  #B5271B;
  --gap-info:    #274B6D;

  /* ── Jetons de RÔLE — la seule API que les composants consomment ──────────
     Introduits le 05/08/2026. Les composants ci-dessous ne lisent QUE ces
     noms. Chaque jeton se replie sur l'ancien nom de marque (--gap-*) : un
     thème peut donc redéclarer L'UN OU L'AUTRE, et les valeurs par défaut
     restent celles du GAP — le rendu sans thème est inchangé au pixel.
     Pourquoi : --gap-red qui contiendrait du bleu chez un client est un piège
     de relecture. Les nouveaux thèmes redéclarent --color-primary, dont le
     nom reste juste quelle que soit la couleur. Ne PAS réintroduire var(--gap-*)
     dans les composants : hors de ce bloc, les noms de marque sont dépréciés. */
  --color-primary:         var(--gap-red);
  --color-primary-hover:   var(--gap-red-hover);
  --color-secondary:       var(--gap-green);
  --color-secondary-hover: var(--gap-green-hover);
  --color-accent:          var(--gap-gold);
  --color-ink:             var(--gap-dark);
  --color-surface:         var(--gap-light);
  --color-surface-alt:     var(--gap-cream);
  --color-surface-raised:  var(--gap-paper);
  --color-border:          var(--gap-border);
  --color-muted:           var(--gap-muted);
  --color-flourish-1:      var(--gap-blue);
  --color-flourish-2:      var(--gap-clay);
  --color-flourish-3:      var(--gap-straw);
  --color-success:         var(--gap-success);
  --color-warning:         var(--gap-warning);
  --color-danger:          var(--gap-danger);
  --color-info:            var(--gap-info);

  /* ── Échelle d'espacement ── */
  --space-1: .25rem; --space-2: .5rem;  --space-3: .75rem; --space-4: 1rem;
  --space-5: 1.5rem; --space-6: 2rem;   --space-7: 3rem;   --space-8: 4rem;

  --nav-height:  78px;
  --adminbar-h:  48px;

  --font-body:   'Inter', system-ui, sans-serif;
  --font-display:'Fraunces', 'Playfair Display', Georgia, serif;

  --transition:  .2s ease;
  --radius:      .5rem;
  --shadow-sm:   0 2px 8px rgba(26,26,26,.08);
  --shadow-md:   0 6px 24px rgba(26,26,26,.12);
  --shadow-lg:   0 16px 48px rgba(26,26,26,.16);

  /* ── Jetons de mouvement et d'empilement (F1, 24/08/2026) ─────────────────
     Introduits par ÉTRANGLEMENT : chaque valeur reprend un palier déjà
     dominant dans ce fichier (mesuré : .12s ×19, .15s ×24, .2s via
     --transition, .25-.35s, .5s ×19) — rendu inchangé au pixel. Les NOUVELLES
     écritures les consomment ; les anciennes se convertissent au fil des
     chantiers, jamais en lot (scripts/verif-jetons.php surveille les diffs).
     Un thème les surcharge par le même chemin que les couleurs.

     Durées : 1 interface dense (menus, outils) · 2 états courants ·
     3 standard (= --transition) · 4 images et panneaux · 5 apparitions.
     Les durées de feedback longues (halos .6-2.5s, marquee 42s) sont des cas
     nommés, hors échelle — ne pas les y forcer. */
  --dur-1: .12s;
  --dur-2: .15s;
  --dur-3: .2s;
  --dur-4: .3s;
  --dur-5: .5s;

  /* Courbes : « standard » est LA courbe du produit ; « entrée » et
     « ressort » reprennent les deux courbes expressives déjà en liste
     blanche côté PHP (SectionRenderer::animEase — smooth et spring). */
  --ease-standard: ease;
  --ease-entree:   cubic-bezier(.16, 1, .3, 1);
  --ease-ressort:  cubic-bezier(.34, 1.56, .64, 1);

  /* LE geste d'élévation au survol : une écriture de socle ; un thème qui
     veut une autre amplitude surcharge le jeton (ou sa règle), il ne
     réécrit plus le geste. */
  --lift: -4px;
  --lift-doux: -1px;   /* micro-élévation des boutons et liens (M3) */

  /* Empilement : les valeurs ACTUELLES des couches du socle — pas une cible
     idéale. Toute NOUVELLE surface flottante prend son jeton ici au lieu
     d'inventer un nombre entre 1030 et 20010. (Les collisions connues avec
     la bande Bootstrap — 4 exactes + 2 intercalées — se résolvent dans un
     lot dédié, pas au détour d'un jeton.) */
  --z-nav:      1030;
  --z-adminbar: 1040;
  --z-panneau:  1045;   /* panneau latéral FO (= offcanvas Bootstrap) */
  --z-bandeau:  1060;   /* bandeau RGPD, menus de la barre admin */
  --z-lightbox: 9000;
  --z-surcouche: 10000; /* overlays plein écran (aperçu appareil, sélecteur de lien) */
  --z-guide:    19000; /* guide d'accueil — au-dessus des surcouches, sous les toasts */
  --z-toast:    20000;
  --z-confirm:  20010;
}

/* ── Points de rupture du socle (documentation, F1) ─────────────────────────
   Trois seuils officiels, alignés sur Bootstrap : 575.98px (téléphone),
   767.98px (tablette portrait), 991.98px (tablette paysage / burger).
   Mesuré le 24/08 : 13 seuils distincts cohabitent dans ce fichier (480, 575,
   576, 720, 767, 768, 820, 900, 991, 992…) — dont des zones mortes d'un pixel
   (767 / 767.98 / 768). NE PAS introduire de nouveau seuil sans avoir vérifié
   qu'un des trois officiels ne convient pas ; la convergence de l'existant se
   fait au fil des chantiers (lot O4). Les media queries ne peuvent pas lire
   de variables CSS : ces seuils restent des littéraux, ce commentaire est
   leur source. */

/* ── Reset minimal ─────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  font-family: var(--font-body);
  color: var(--color-ink);
  background: #fff;
  line-height: 1.65;
}
img { max-width: 100%; height: auto; display: block; }
a { color: var(--color-primary); transition: color var(--transition); }
a:hover { color: var(--color-primary-hover); }

/* Lien d'évitement (V11) : hors écran tant qu'il n'a pas le focus clavier,
   première tabulation = « Aller au contenu ». Couleurs en jetons de rôle,
   au-dessus de tout (--z-confirm est le sommet de l'échelle). */
.gap-skip-link {
  position: absolute;
  left: -9999px;
  z-index: var(--z-confirm);
  background: var(--color-ink);
  color: #fff;
  padding: .6rem 1.1rem;
  border-radius: var(--radius);
  text-decoration: none;
}
.gap-skip-link:focus-visible {
  left: .75rem;
  top: .75rem;
}
/* La cible du lien ne doit pas hériter d'un anneau de focus permanent. */
#main-content:focus { outline: none; }

/* Accessibilité clavier (WCAG 2.4.7) : anneau de focus visible et cohérent sur
   tous les éléments interactifs, uniquement au clavier (:focus-visible). */
a:focus-visible,
button:focus-visible,
[tabindex]:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 2px;
  border-radius: 2px;
}
/* Les anciens navigateurs sans :focus-visible gardent un focus visible par défaut ;
   on ne neutralise jamais outline sans alternative. */

/* ── Typographie ───────────────────────────────────────────── */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1.2;
  color: var(--color-ink);
}
/* Échelle typographique (~1.25) — Fraunces en titres. */
h1 { font-size: clamp(2.2rem, 4.5vw, 2.75rem); line-height: 1.15; }
h2 { font-size: clamp(1.7rem, 3.5vw, 2.15rem); }
h3 { font-size: clamp(1.35rem, 2.5vw, 1.6rem); }
h4 { font-size: 1.25rem; }
h5 { font-size: 1.05rem; }
h6 { font-size: .9rem; letter-spacing: .02em; }
/* V1 (25/08/2026) — hiérarchie retrouvée. Le socle rendait TOUS les titres de
   section au même grand format : un nivellement que silliere et tacadence
   corrigeaient chacun dans leur coin (silliere theme.css:122-137, tacadence
   :276-298 — leurs règles gagnent toujours la cascade et restent intactes).
   Désormais le RANG porte l'échelle, sans aucun réglage nouveau : le titre
   d'ouverture (h1, promu ou injecté — un par page) garde le grand format,
   les sections courantes (h2) descendent d'un cran. */
.display-title {
  font-family: var(--font-display);
  font-size: clamp(1.9rem, 3.8vw, 2.6rem);
  font-weight: 900;
  line-height: 1.1;
  letter-spacing: -.01em;
}
h1.display-title {
  font-size: clamp(2.4rem, 6vw, 3.6rem);
  line-height: 1.06;
}
.section-eyebrow {
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--color-secondary);
}
/* Étiquette lisible sur fond sombre/coloré/image : paille claire (charte). */
.bg-dark-custom .section-eyebrow,
.bg-primary-custom .section-eyebrow,
.bg-secondary-custom .section-eyebrow,
.has-bg-image .section-eyebrow,
.has-dark-surface .section-eyebrow { color: var(--color-flourish-3); }

/* Tagline thématique (sous-titre d'un item de liste/zigzag, style « Nos sections »
   d'inspiration Odoo) : sérif italique chaleureuse, en terre colombage. */
.gap-tagline {
  font-family: var(--font-display);
  font-style: italic;
  font-size: 1.2rem;
  line-height: 1.35;
  color: var(--color-flourish-2);
  margin-top: -.15rem;
}
.bg-dark-custom .gap-tagline,
.bg-primary-custom .gap-tagline,
.bg-secondary-custom .gap-tagline,
.has-bg-image .gap-tagline,
.has-dark-surface .gap-tagline { color: var(--color-flourish-3); }

/* ── Couleurs utilitaires ──────────────────────────────────── */
.text-gap-red   { color: var(--color-primary)   !important; }
.text-gap-green { color: var(--color-secondary) !important; }
.text-gap-gold  { color: var(--color-accent)  !important; }
.bg-gap-red     { background: var(--color-primary)   !important; color: #fff; }
.bg-gap-green   { background: var(--color-secondary) !important; color: #fff; }
.bg-gap-dark    { background: var(--color-ink)  !important; color: #fff; }
.bg-gap-light   { background: var(--color-surface) !important; }
.bg-gap-gold    { background: var(--color-accent)  !important; color: var(--color-ink); } /* blanc sur or : 2,4:1 mesuré — encre : ~6:1 (V11) */

/* ── Boutons (charte : 4 variantes, 100 % tokens) ──────────────
   primary  = rouge plein · action principale
   secondary= vert plein  · action de soutien
   outline  = contour vert · sur fonds clairs
   ghost    = clair translucide · sur fonds sombres / images
   Base commune + états hover/active/focus/désactivé. */
.btn-gap-primary, .btn-gap-secondary, .btn-gap-outline, .btn-gap-ghost {
  display: inline-flex; align-items: center; justify-content: center; gap: .5rem;
  border-radius: var(--radius);
  padding: .6rem 1.4rem;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: .95rem;
  line-height: 1.2;
  border: 2px solid transparent;
  cursor: pointer;
  transition: background var(--transition), color var(--transition),
              border-color var(--transition), transform var(--transition), box-shadow var(--transition);
}
.btn-gap-primary:disabled, .btn-gap-secondary:disabled,
.btn-gap-outline:disabled, .btn-gap-ghost:disabled,
.btn-gap-primary.disabled, .btn-gap-secondary.disabled { opacity: .55; cursor: not-allowed; transform: none; box-shadow: none; }

.btn-gap-primary   { background: var(--color-primary);   color: #fff; }
.btn-gap-primary:hover  { background: var(--color-primary-hover); color: #fff; transform: translateY(var(--lift-doux)); box-shadow: var(--shadow-sm); }
.btn-gap-primary:active { transform: translateY(0); box-shadow: none; }

.btn-gap-secondary { background: var(--color-secondary); color: #fff; }
.btn-gap-secondary:hover  { background: var(--color-secondary-hover); color: #fff; transform: translateY(var(--lift-doux)); box-shadow: var(--shadow-sm); }
.btn-gap-secondary:active { transform: translateY(0); box-shadow: none; }

.btn-gap-outline   { background: transparent; color: var(--color-secondary); border-color: var(--color-secondary); }
.btn-gap-outline:hover { background: var(--color-secondary); color: #fff; }

.btn-gap-ghost     { background: transparent; color: #fff; border-color: rgba(255,255,255,.6); }
.btn-gap-ghost:hover   { background: rgba(255,255,255,.15); border-color: #fff; color: #fff; }

/* ── RÔLES : ce qu'un bouton FAIT, à côté de ce à quoi il ressemble ────────
   Trois rôles, posés par les gabarits EN PLUS de leur variante :

     .btn-gap-action    l'action de la composition — s'inscrire, envoyer,
                        choisir cette formule. Un par bloc.
     .btn-gap-alt       l'alternative, le second choix, le « en savoir plus ».
     .btn-gap-discret   le renvoi d'appoint. Un seul endroit l'emploie
                        aujourd'hui : le troisième bouton d'un cta-block.

   ── À quoi ils servent ──────────────────────────────────────────────────
   Un thème qui veut redessiner un bouton devait jusqu'ici RECOPIER le gabarit
   entier (themes/gap-berstett/sections/featured.php en est l'exemple : un
   correctif de socle ne l'atteint plus, il a fallu y retoucher à la main le
   12/08/2026). Il peut désormais viser le rôle.

   Les rôles n'ont VOLONTAIREMENT aucune apparence ici : ce sont des
   marqueurs. L'habillage continue de venir de la variante posée à côté, donc
   ajouter un rôle ne change rien à ce qui s'affiche — c'est ce qui a permis
   de les poser sur les quinze boutons du socle sans rien vérifier à l'œil.

   ── Comment un thème s'en sert ──────────────────────────────────────────
   Le CSS du thème est chargé APRÈS celui-ci (layouts/public.php) : à
   spécificité égale, il gagne.

     .btn-gap-alt { background: #fff; color: var(--color-ink); border: 0; }

   Pour ne viser qu'un gabarit, la section porte déjà `data-template` :

     [data-template="cta-block"] .btn-gap-alt { … }

   ⚠️ Les règles de contraste sur fond sombre (plus haut) sont plus
   spécifiques que `.btn-gap-alt` seul : elles gagneront. C'est voulu — la
   lisibilité prime sur la charte. Un thème qui veut aussi décider LÀ doit
   viser le même contexte, par exemple
   `.has-dark-surface .btn-gap-alt`. */

/* ── Le bouton secondaire suit le FOND, comme le texte ────────────────────────
   Les gabarits codaient `btn-gap-ghost` en dur — blanc sur transparent, dessiné
   pour une photo. Sur un fond clair, il disparaît : mesuré le 11/08 dans le
   comparatif, blanc #fff sur #F8F6F2 donne 1,03:1. Il n'était pas « invisible
   dès que le fond est clair » : il l'était TOUJOURS, puisque la carte se peint
   elle-même en surface claire quel que soit le fond de la section.

   Le gabarit n'a pas à choisir. Il demande le bouton secondaire
   (`btn-gap-outline`) ; c'est le CONTEXTE qui décide de son habillage, avec
   exactement les mêmes conditions que celles qui font passer le texte en blanc
   (cf. `.page-section.bg-dark-custom`, `.page-section.has-bg-image`). Un
   bouton ne peut pas être plus malin que le texte à côté duquel il se trouve.

   `btn-gap-ghost` reste disponible pour qui veut le blanc DÉLIBÉRÉMENT, par
   exemple par-dessus une image posée dans une carte.

   `:not(.gap-formule *)` EXCLUT les cartes qui se peignent elles-mêmes en
   clair : une carte de comparatif reste crème au milieu d'une section sombre,
   et la blanchir y réintroduirait le défaut d'origine. L'exclusion ne REMET
   aucune teinte — elle laisse simplement le thème décider, ce qui compte : la
   vitrine veut son bleu (#1E4C8C), le GAP son rouge. Un socle qui imposerait
   ici `--color-secondary` repeindrait la page qui vend. */
.page-section.bg-dark-custom .btn-gap-outline:not(.gap-formule *),
.page-section.bg-primary-custom .btn-gap-outline:not(.gap-formule *),
.page-section.bg-secondary-custom .btn-gap-outline:not(.gap-formule *),
.page-section.has-bg-image .btn-gap-outline:not(.gap-formule *),
.has-dark-surface .btn-gap-outline:not(.gap-formule *) {
  color: #fff; border-color: rgba(255,255,255,.6);
}
.page-section.bg-dark-custom .btn-gap-outline:not(.gap-formule *):hover,
.page-section.bg-primary-custom .btn-gap-outline:not(.gap-formule *):hover,
.page-section.bg-secondary-custom .btn-gap-outline:not(.gap-formule *):hover,
.page-section.has-bg-image .btn-gap-outline:not(.gap-formule *):hover,
.has-dark-surface .btn-gap-outline:not(.gap-formule *):hover {
  background: rgba(255,255,255,.15); border-color: #fff; color: #fff;
}

/* Sous-titres/textes atténués : contraste garanti (fini le gris illisible).
   Sur fonds sombre/coloré/image → clair translucide. */
.text-muted { color: var(--color-muted) !important; }
.bg-dark-custom .text-muted, .bg-primary-custom .text-muted,
.bg-secondary-custom .text-muted, .has-bg-image .text-muted,
.has-dark-surface .text-muted { color: rgba(255,255,255,.82) !important; }

/* ── Écrans de retour de la newsletter (confirmation, désinscription) ──────
   Deux émojis y tenaient lieu de pictogramme — ✅ et ❌ — jusqu'au 27/08/2026.
   Un tracé Feather en `currentColor` prend la couleur qu'on lui donne : celle
   du thème du client, et non celle qu'a choisie le fabricant du téléphone.
   Le vert et l'ambre du socle sont ceux des états (--color-success,
   --color-warning) : un thème qui les redéfinit emporte ces icônes avec lui. */
.gap-nl-icone { line-height: 1; }
.gap-nl-icone--ok        { color: var(--color-success); }
.gap-nl-icone--attention { color: var(--color-warning); }
/* Les tracés sont dessinés en 24×24 : à 56 px, un trait de 2 devient épais.
   1.5 garde le trait fin, comme dans le reste de l'interface. */
.gap-nl-icone svg { stroke-width: 1.5; }

/* ── Formulaires (charte) : champs de marque, fin du focus bleu Bootstrap ──
   S'applique aux formulaires publics (contact, newsletter…). */
.form-control, .form-select {
  border-color: var(--color-border);
  border-radius: var(--radius);
  transition: border-color var(--transition), box-shadow var(--transition);
}
.form-control:focus, .form-select:focus {
  border-color: var(--color-secondary);
  box-shadow: 0 0 0 .2rem rgba(42,122,60,.18);
}
.form-control::placeholder { color: var(--color-muted); opacity: .8; }
/* Cases à cocher / interrupteurs : coche à la couleur de marque (vert). */
.form-check-input:checked {
  background-color: var(--color-secondary);
  border-color: var(--color-secondary);
}
.form-check-input:focus {
  border-color: var(--color-secondary);
  box-shadow: 0 0 0 .2rem rgba(42,122,60,.18);
}

/* ── Navigation ────────────────────────────────────────────── */
.gap-navbar {
  height: var(--nav-height);
  background: #fff;
  border-bottom: 1px solid var(--color-border);
  position: sticky;
  top: 0;
  z-index: var(--z-nav);
  transition: box-shadow var(--transition);
  /* Contenu centré verticalement : sans ça le logo/les liens se collent au bord
     supérieur (marges haut/bas déséquilibrées). Vaut pour toutes les pages. */
  display: flex;
  align-items: center;
}
.gap-navbar > .container { width: 100%; }
.gap-navbar.scrolled { box-shadow: var(--shadow-md); }
.gap-navbar.admin-offset { top: var(--adminbar-h); }

.gap-navbar .nav-logo img { height: 44px; width: auto; }

/* ── Menu MOBILE (< 992px) ─────────────────────────────────────────────────
   Le burger Bootstrap n'avait pas d'icône (background-image:none) → invisible.
   On redéfinit l'icône + on transforme le collapse en panneau déroulant lisible.*/
@media (max-width: 991.98px) {
  .gap-navbar .navbar-toggler {
    border: 0; padding: .25rem; background: transparent; box-shadow: none; line-height: 0;
  }
  .gap-navbar .navbar-toggler-icon {
    width: 28px; height: 28px; display: inline-block;
    background: no-repeat center / 100% 100%;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='%231A1A1A' stroke-width='2.5' stroke-linecap='round' d='M4 8h22M4 15h22M4 22h22'/%3E%3C/svg%3E");
  }
  /* Panneau déroulant : sous la barre, fond plein, ombré. */
  .gap-navbar .navbar-collapse {
    position: absolute; top: 100%; left: 0; right: 0;
    background: #fff; border-top: 1px solid var(--color-border);
    box-shadow: 0 12px 28px rgba(0,0,0,.16);
    padding: .5rem 1.25rem 1.25rem;
  }
  .gap-navbar .navbar-nav {
    flex-direction: column; align-items: stretch; gap: 0; margin: 0 !important;
  }
  .gap-navbar .navbar-collapse .nav-link {
    color: var(--color-ink); padding: .8rem .25rem; border-bottom: 1px solid var(--color-border);
  }
  .gap-navbar .navbar-collapse .nav-link.active { color: var(--color-primary); }
  .gap-navbar .navbar-collapse .nav-link::after { display: none; }
  /* Bloc CTA + langues : pleine largeur sous les liens. */
  .gap-navbar .navbar-collapse > div {
    margin: 1rem 0 0 !important; justify-content: space-between; width: 100%;
  }
  .gap-navbar .navbar-collapse .gap-nav-cta { flex: 1; justify-content: center; }
  /* Replié en burger, les entrées deviennent une LISTE verticale à filets : une
     pastille y flotterait au milieu d'une ligne, sans rien signifier. Le niveau
     « bouton » y reprend donc la largeur de la ligne et s'y centre — même
     traitement que le CTA juste au-dessus, pour la même raison. */
  .gap-navbar .navbar-collapse .nav-link.gap-nav-highlight--bouton {
    display: flex; justify-content: center;
    margin: .5rem 0; border-bottom: 0;
  }
}

/* ── Entrée de menu mise en avant ────────────────────────────────────────────
   Deux NIVEAUX posés par nav.php, jamais deux styles : « combien j'insiste »
   est une décision d'éditeur, « à quoi ça ressemble » une décision de thème.
   Ce qui suit est donc un repli SOBRE, pour qu'un thème qui ne les peint pas
   n'affiche pas une entrée annoncée comme mise en avant et rigoureusement
   identique aux autres. Chaque theme.css passe devant.

   Les couleurs viennent des variables du thème actif : le repli s'accorde au
   site sans rien lui imposer.

   ⚠️ TOUS ces sélecteurs portent `.nav-link`, et ce n'est pas une coquetterie :
   `.gap-navbar .nav-link.active` est déclaré PLUS BAS dans ce fichier, à
   spécificité égale, et l'emportait — une entrée « bouton » sur sa propre page
   se retrouvait en rouge sur rouge, donc illisible (constaté au banc le
   05/09/2026). Ajouter `.nav-link` dit la vérité — ce sont des liens de la
   barre — et suffit à trancher. */
.gap-navbar .nav-link.gap-nav-highlight--accent {
  color: var(--color-primary);
  font-weight: 600;
}
.gap-navbar .nav-link.gap-nav-highlight--bouton {
  display: inline-flex; align-items: center; white-space: nowrap;
  background: var(--color-primary); color: #fff;
  padding: .4rem 1rem; border-radius: 999px;
  font-weight: 600;
}
.gap-navbar .nav-link.gap-nav-highlight--bouton:hover,
.gap-navbar .nav-link.gap-nav-highlight--bouton.active {
  background: var(--color-primary); color: #fff;
}
/* Le soulignement actif de la barre n'a pas de sens sous une pastille. */
.gap-navbar .nav-link.gap-nav-highlight--bouton::after { display: none !important; }

.gap-navbar .nav-link {
  font-size: .875rem;
  font-weight: 500;
  color: var(--color-ink);
  padding: .25rem .75rem;
  border-radius: var(--radius);
  transition: color var(--transition), background var(--transition);
  position: relative;
}

/* Le déroulant doit FLOTTER au-dessus de la page, pas s'insérer dans la barre.
   Bootstrap pose `position: static` sur `.navbar-nav .dropdown-menu` — c'est
   son comportement pour une navbar REPLIÉE, où le sous-menu s'affiche en ligne
   — et ne le repasse en `absolute` que via `.navbar-expand-*`. Or la navbar du
   socle n'utilise pas ce système (elle gère son repli avec `d-lg-flex`), si
   bien que la règle mobile s'appliquait à toutes les tailles : le menu ouvert
   poussait la barre et RECOUVRAIT le chevron qui venait de l'ouvrir.
   On rétablit donc ce que `.navbar-expand-lg` aurait fait, au même point de
   rupture. En dessous, `static` reste le bon comportement. */
@media (min-width: 992px) {
  .gap-navbar .navbar-nav .dropdown-menu {
    /* top/left explicites car le toggle porte data-bs-display="static" :
       Popper est désactivé, et sans lui Bootstrap ne pose aucune coordonnée.
       Ancré sur le <li>, donc sous le LIBELLÉ — Popper l'ancrait sur le
       chevron, à droite du texte, et le menu semblait appartenir à l'entrée
       suivante. */
    position: absolute; top: 100%; left: 0; margin-top: .25rem;
  }
}

/* Entrée de menu ayant des sous-pages : le lien et le chevron s'alignent ici,
   sur le <li> lui-même. Un conteneur intermédiaire séparerait le bouton de son
   .dropdown-menu, que Bootstrap cherche comme FRÈRE SUIVANT — le déroulant ne
   s'ouvrirait alors jamais, sans la moindre erreur visible. */
.gap-navbar .navbar-nav .gap-nav-parent {
  display: flex; align-items: center;
}

/* Chevron d'ouverture d'un sous-menu.
   Il est SÉPARÉ du lien parent, volontairement : le lien mène à la page
   parente, qui a son propre contenu, et le chevron ouvre le déroulant. Les
   fusionner priverait la page parente de tout accès au doigt.
   Mesuré à 8×8 px sans ces règles — sous le minimum WCAG 2.2 (24×24, critère
   2.5.8) et inutilisable au pouce. La cible est portée à 32×32 sans que le
   chevron lui-même grossisse : c'est la zone de clic qu'on agrandit. */
.gap-navbar .navbar-nav .dropdown-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; min-height: 32px;
  padding: 0; border: 0; box-shadow: none;
  color: var(--color-ink); text-decoration: none;
}
/* ⚠ Histoire à connaître (10/08/2026) : ce bouton porte AUSSI .nav-link, et
   le soulignement animé `.nav-link::after` avait écrasé le caret Bootstrap —
   chevron invisible, six pages filles inatteignables à la souris. Le remède
   d'époque redessinait un triangle en bordures dans ::after. Depuis V3
   (25/08/2026), l'affordance est un VRAI chevron Feather posé DANS le bouton
   (nav.php) : un enfant SVG qu'aucun ::after ne peut écraser — le ::after est
   simplement éteint, et il ne peut plus rien casser. */
.gap-navbar .navbar-nav .dropdown-toggle::after { content: none; }
.gap-navbar .navbar-nav .dropdown-toggle svg {
  transition: transform var(--dur-2) var(--ease-standard);
}
.gap-navbar .navbar-nav .dropdown-toggle[aria-expanded="true"] svg {
  transform: rotate(180deg);
}
.gap-navbar .navbar-nav .dropdown-toggle:hover,
.gap-navbar .navbar-nav .dropdown-toggle:focus-visible {
  color: var(--color-primary);
}
/* Le focus clavier doit rester visible : le bouton n'a aucun libellé, un
   contour est le seul repère de position dans la barre. */
.gap-navbar .navbar-nav .dropdown-toggle:focus-visible {
  outline: 2px solid var(--color-primary); outline-offset: 2px; border-radius: 4px;
}

/* ── Sous-sous-menu : le TROISIÈME niveau et au-delà ──────────────────────
   Ouvert le 17/08/2026, quand la hiérarchie des pages a cessé de s'arrêter à
   un cran. Bootstrap 5 n'imbrique pas ses déroulants : `data-bs-toggle` posé
   dans un `.dropdown-menu` referme celui qui le contient. Ces listes-ci
   n'utilisent donc PAS Bootstrap pour s'ouvrir — elles portent la classe
   `.dropdown-menu` (pour l'habillage, et pour son `.show` qui suffit à les
   afficher), et c'est GapSousNiveau qui pose ce `.show`.

   Rien ici ne s'applique à un site qui n'a que deux niveaux : sans
   `.gap-nav-sous` dans le document, ce bloc entier est inerte. */
.gap-navbar .gap-nav-sous { position: relative; }

/* Le chevron latéral se pose PAR-DESSUS le lien plutôt qu'à côté : un
   .dropdown-item occupe toute la largeur, et l'en amputer casserait
   l'alignement des libellés du niveau au-dessus. Le lien réserve la place à
   droite, le bouton s'y installe — 32×32 comme le chevron de premier niveau
   (WCAG 2.2, critère 2.5.8). */
.gap-navbar .gap-nav-sous > .dropdown-item { padding-right: 2.25rem; }
.gap-navbar .gap-nav-sous-toggle {
  position: absolute; top: 50%; right: .25rem; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; min-height: 32px;
  padding: 0; border: 0; background: none; box-shadow: none;
  color: var(--color-ink);
}
/* V3 : chevron Feather dans le bouton (nav.php), plus de triangle ::after. */
.gap-navbar .gap-nav-sous-toggle svg {
  transition: transform var(--dur-2) var(--ease-standard);
}
.gap-navbar .gap-nav-sous-toggle[aria-expanded="true"] svg {
  transform: rotate(90deg);
}
.gap-navbar .gap-nav-sous-toggle:hover,
.gap-navbar .gap-nav-sous-toggle:focus-visible { color: var(--color-primary); }
.gap-navbar .gap-nav-sous-toggle:focus-visible {
  outline: 2px solid var(--color-primary); outline-offset: 2px; border-radius: 4px;
}

@media (min-width: 992px) {
  /* La liste sort SUR LE CÔTÉ du parent, pas en dessous : superposée, elle
     masquerait les entrées voisines de la liste qui la contient. */
  .gap-navbar .gap-nav-sous-menu {
    position: absolute; top: -.5rem; left: 100%; margin: 0 0 0 .125rem;
  }
  /* Trop près du bord droit : la liste part de l'autre côté. La classe est
     posée par GapSousNiveau à l'ouverture, quand la mesure le demande — un
     media query ne peut pas savoir où se trouve UNE entrée. */
  .gap-navbar .gap-nav-sous.gap-vers-gauche > .gap-nav-sous-menu {
    left: auto; right: 100%; margin: 0 .125rem 0 0;
  }
}
/* Survol : même réglage de super-administrateur que le premier niveau, mêmes
   gardes — jamais sur un écran tactile, jamais dans la barre repliée. */
@media (min-width: 992px) and (hover: hover) and (pointer: fine) {
  [data-sousmenu="hover"] .gap-nav-sous:hover > .gap-nav-sous-menu { display: block; }
}

/* Barre REPLIÉE (téléphone, tablette) : le menu est déjà une colonne dépliée.
   Y empiler des listes qui sortent sur le côté n'aurait aucun sens — et une
   liste en position absolue déborderait de l'écran, défaut qu'on vient de
   payer deux fois (v1.8.1 et v1.8.2). Le sous-niveau s'INDENTE donc, toujours
   visible : deux ou trois pages de plus dans une colonne coûtent moins qu'un
   repli supplémentaire à ouvrir. */
@media (max-width: 991.98px) {
  .gap-navbar .gap-nav-sous-menu {
    display: block; position: static; float: none;
    width: auto; min-width: 0; max-width: 100%;
    border: 0; box-shadow: none; background: none;
    margin: 0; padding: 0 0 0 1rem;
  }
  .gap-navbar .gap-nav-sous-toggle { display: none; }  /* rien à ouvrir */
  .gap-navbar .gap-nav-sous > .dropdown-item { padding-right: 1rem; }
}
.gap-navbar .nav-link::after {
  content: '';
  display: block;
  height: 2px;
  background: var(--color-primary);
  position: absolute;
  bottom: -2px;
  left: .75rem;
  right: .75rem;
  transform: scaleX(0);
  transform-origin: left center; /* V3 : le trait pousse depuis le début du mot */
  transition: transform var(--dur-3) var(--ease-entree);
  border-radius: 1px;
}
.gap-navbar .nav-link:hover,
.gap-navbar .nav-link.active {
  color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 5%, transparent); /* V3 : suivait le rouge GAP en dur */
}
.gap-navbar .nav-link.active::after { transform: scaleX(1); }

/* Switcher de langue */
.lang-switcher { display: flex; gap: .25rem; align-items: center; }
.lang-switcher .lang-btn {
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  padding: .2rem .5rem;
  border-radius: .25rem;
  border: 1px solid var(--color-border);
  color: var(--color-ink);
  background: transparent;
  text-decoration: none;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}
.lang-switcher .lang-btn:hover,
.lang-switcher .lang-btn.active {
  background: var(--color-primary);
  color: #fff;
  border-color: var(--color-primary);
}
/* Langue en préparation : n'apparaît que chez un compte connecté. Bordure
   pointillée = « pas encore ouverte », sans rien retirer à sa lisibilité. */
.lang-switcher .lang-btn--preparation { border-style: dashed; }

/* ── Barre admin ───────────────────────────────────────────── */
.gap-adminbar {
  height: var(--adminbar-h);
  background: var(--color-ink);
  color: #fff;
  display: flex;
  align-items: center;
  padding: 0 1rem;
  gap: .75rem;
  font-size: .8rem;
  position: sticky;
  top: 0;
  z-index: var(--z-adminbar);
  flex-wrap: nowrap;
  overflow-x: auto;
}

/* ── La barre dit qu'elle continue ────────────────────────────────────────
   MESURÉ le 12/08/2026 à 375 px, en mode édition : le contenu de la barre
   fait 1098 px, il défile correctement, et tout est atteignable au doigt —
   mais AU REPOS on ne voit que deux contrôles sur dix, et rien n'annonce les
   huit autres. Ce n'est donc pas l'accessibilité qui manquait, c'est le
   SIGNAL : on ne cherche pas ce dont on ignore l'existence.

   Technique du « fondu au défilement », sans une ligne de JavaScript : deux
   voiles clairs restent collés aux bords (`scroll`), deux aplats de la couleur
   de la barre défilent AVEC le contenu (`local`) et les recouvrent tant qu'on
   est en butée. Résultat : le voile n'apparaît que du côté où il reste
   vraiment quelque chose à voir, et disparaît de lui-même quand on y arrive.
   Rien à calculer, rien à écouter, rien qui puisse se désynchroniser.

   Voile CLAIR et non ombre noire : la barre est déjà sombre
   (`var(--color-ink)`), une ombre s'y verrait à peine. La couleur de
   recouvrement lit la même variable — un thème qui change l'encre emporte le
   fondu avec lui, sans rien à retoucher ici.

   Réservé aux écrans étroits : au bureau, la barre tient sans défiler et un
   voile n'y signalerait rien. */
@media (max-width: 991.98px) {
  .gap-adminbar {
    background-image:
      linear-gradient(to right, var(--color-ink), rgba(0, 0, 0, 0) 2.2rem),
      linear-gradient(to left,  var(--color-ink), rgba(0, 0, 0, 0) 2.2rem),
      linear-gradient(to right, rgba(255, 255, 255, .17), rgba(255, 255, 255, 0) 1.1rem),
      linear-gradient(to left,  rgba(255, 255, 255, .17), rgba(255, 255, 255, 0) 1.1rem);
    background-repeat: no-repeat;
    background-size: 2.2rem 100%, 2.2rem 100%, 1.1rem 100%, 1.1rem 100%;
    background-position: left center, right center, left center, right center;
    background-attachment: local, local, scroll, scroll;
  }
}
.gap-adminbar .adminbar-btn {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .3rem .7rem;
  border-radius: .25rem;
  font-size: .78rem;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  border: none;
  transition: background var(--transition);
}
.gap-adminbar .adminbar-btn-edit {
  background: var(--color-secondary);
  color: #fff;
}
.gap-adminbar .adminbar-btn-edit:hover { background: var(--color-secondary-hover); color: #fff; }
/* Indicateur d'enregistrement automatique (remplace l'ancien bouton « Enregistrer »). */
.gap-adminbar .adminbar-autosave {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .3rem .7rem;
  border-radius: .25rem;
  font-size: .78rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: default;
  color: rgba(255,255,255,.7);
  background: rgba(255,255,255,.08);
  transition: background var(--transition), color var(--transition);
}
.gap-adminbar .adminbar-autosave-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: rgba(255,255,255,.45);
  flex-shrink: 0;
  transition: background var(--transition), transform var(--transition);
}
.gap-adminbar .adminbar-autosave.is-saving { color: #fff; background: rgba(255,255,255,.14); }
.gap-adminbar .adminbar-autosave.is-saving .adminbar-autosave-dot {
  background: var(--color-accent);
  animation: gap-autosave-pulse .8s ease-in-out infinite;
}
.gap-adminbar .adminbar-autosave.is-saved { color: #fff; background: rgba(40,167,69,.25); }
.gap-adminbar .adminbar-autosave.is-saved .adminbar-autosave-dot { background: var(--color-secondary); }
.gap-adminbar .adminbar-autosave.is-error { color: #fff; background: rgba(220,53,69,.3); }
.gap-adminbar .adminbar-autosave.is-error .adminbar-autosave-dot { background: var(--color-primary); }
@keyframes gap-autosave-pulse {
  0%, 100% { transform: scale(1);   opacity: 1;   }
  50%      { transform: scale(1.4); opacity: .5;  }
}
.gap-adminbar .adminbar-btn-ghost {
  background: rgba(255,255,255,.1);
  color: #fff;
}
.gap-adminbar .adminbar-btn-ghost:hover { background: rgba(255,255,255,.2); color: #fff; }
/* ══ Journal des modifications ═══════════════════════════════════
   Déplacé d'admin.css le 27/08/2026 : le journal se lit maintenant
   des DEUX côtés — écran du back-office, et modale du bandeau
   d'édition sur le site public. gap.css est la seule feuille que les
   deux layouts chargent. */
/* ── Journal du socle (/admin/systeme/changelog) ─────────────────
   Un document long, lu en continu : il lui faut une COLONNE, pas une
   pleine largeur. La contrainte porte sur la CARTE — posée sur le corps,
   elle laissait la carte s'étirer et le texte s'arrêter au tiers, d'où une
   large bande blanche à droite.
   L'interligne, lui, n'était pas un défaut de style : chaque ligne source
   devenait un paragraphe (corrigé dans App\Core\Markdown). */
.gap-changelog-card { max-width: 56rem; }

.gap-changelog { line-height: 1.65; color: #2C3038; }
.gap-changelog > :first-child { margin-top: 0; }
.gap-changelog > :last-child  { margin-bottom: 0; }
.gap-changelog p { margin: 0 0 1rem; }

/* Hiérarchie : la VERSION domine, le chantier vient dessous. Sans cela,
   les deux niveaux se ressemblaient et le document se lisait à plat.
   ⚠ Décalage d'un cran : Markdown::render rend « # » en h2 (le h1 est le
   titre de l'écran). Donc h2 = titre du document, h3 = VERSION,
   h4 = chantier. Se caler sur les dièses du fichier donnerait faux. */
.gap-changelog h2 {
  margin: 0 0 1.25rem; padding-bottom: .5rem;
  border-bottom: 1px solid #E6E4E0;
  font-size: 1.25rem; font-weight: 700; color: #191C21;
}
.gap-changelog h3 {
  margin: 2.75rem 0 1rem; padding-bottom: .4rem;
  border-bottom: 2px solid #E6E4E0;
  font-size: 1.35rem; font-weight: 700; color: #191C21;
}
.gap-changelog h4 {
  margin: 2rem 0 .6rem;
  font-size: 1.02rem; font-weight: 700; color: #B3261E;
}
.gap-changelog h5 {
  margin: 1.5rem 0 .5rem;
  font-size: .95rem; font-weight: 700; color: #2C3038;
}

.gap-changelog ul { margin: 0 0 1rem; padding-left: 1.35rem; }
.gap-changelog li { margin-bottom: .45rem; }

.gap-changelog blockquote {
  margin: 0 0 1rem; padding: .1rem 0 .1rem 1rem;
  border-left: 3px solid #E6E4E0; color: #6B7280; font-style: italic;
}

/* Le nom d'un fichier ou d'une migration se lit en un bloc : pas de coupure
   au milieu, et un fond qui le détache du texte courant. */
.gap-changelog code {
  background: #F5F3F0; border-radius: .25rem;
  padding: .05em .22em; font-size: .875em;
  color: #A03A56; white-space: nowrap;
}

/* Les tableaux de version débordent sur mobile : ils défilent seuls,
   la page ne défile jamais latéralement. */
.gap-changelog .table { display: block; overflow-x: auto; margin-bottom: 1.25rem; }
.gap-changelog .table td { white-space: nowrap; vertical-align: top; }

.gap-changelog hr { margin: 2.5rem 0; border-top: 1px solid #E6E4E0; opacity: 1; }

/* ── Sommaire des versions ───────────────────────────────────────
   Le document compte 36 versions pour plus de 200 chantiers : sans repère,
   retrouver ce qu'apportait une version donnée demandait de faire défiler à
   l'aveugle.

   Deux contextes : l'écran large du back-office, où le sommaire se colle en
   RAIL à gauche du texte, et le panneau étroit du bandeau d'édition, où il
   reste en tête, à plat.

   ⚠️ Le contexte est déclaré par une CLASSE (--compact), et non déduit d'une
   requête @container comme je l'avais d'abord écrit. La raison est une règle
   du langage, pas une préférence : une requête de conteneur style les
   DESCENDANTS du conteneur, jamais le conteneur lui-même. Poser
   `container-type` sur .gap-changelog-wrap puis lui donner `display: grid`
   depuis un `@container` ne pouvait donc rien faire — mesuré au banc, le
   sommaire restait à plat sur 1280 px. Le contexte est de toute façon connu
   à l'écriture : c'est l'appelant qui sait s'il rend un écran ou un panneau.

   L'écran, lui, garde une requête de MEDIA : sa largeur est bien celle de la
   fenêtre, et un rail de 13 rem n'a pas sa place sur un téléphone. */

.gap-changelog-toc {
  margin: 0 0 1.5rem;
  padding: .85rem 1rem;
  background: #F5F3F0;
  border: 1px solid #E6E4E0;
  border-radius: .4rem;
}
.gap-changelog-toc-titre {
  margin: 0 0 .5rem;
  font-size: .7rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .06em; color: #6B7280;
}
.gap-changelog-toc-liste {
  list-style: none;
  margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: .3rem;
}
.gap-changelog-toc-lien {
  display: inline-flex; align-items: baseline; gap: .4rem;
  padding: .22rem .55rem;
  border-radius: .25rem;
  text-decoration: none;
  color: #2C3038;
  font-size: .82rem;
  font-variant-numeric: tabular-nums;
  transition: background var(--transition);
}
.gap-changelog-toc-lien:hover,
.gap-changelog-toc-lien:focus-visible { background: #E6E4E0; color: #191C21; }
.gap-changelog-toc-num  { font-weight: 700; }
/* La date accompagne, elle ne se lit pas d'abord — et elle disparaît quand la
   place manque plutôt que de faire passer la liste à deux lignes par entrée. */
.gap-changelog-toc-date { color: #6B7280; font-size: .74rem; }

/* Écran large : le sommaire devient un rail qui SUIT la lecture. Jamais dans
   le panneau, dont la colonne est étroite quelle que soit la fenêtre. */
@media (min-width: 62rem) {
  .gap-changelog-wrap:not(.gap-changelog-wrap--compact) {
    display: grid;
    grid-template-columns: 13rem minmax(0, 1fr);
    gap: 2rem;
    align-items: start;
  }
  .gap-changelog-wrap:not(.gap-changelog-wrap--compact) .gap-changelog-toc {
    position: sticky;
    top: 1rem;
    margin: 0;
    /* Un sommaire plus haut que l'écran défile SEUL : sans cela, il poussait
       la page et le rail cessait d'être un rail. */
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
  }
  .gap-changelog-wrap:not(.gap-changelog-wrap--compact) .gap-changelog-toc-liste {
    flex-direction: column;
    gap: .1rem;
  }
  .gap-changelog-wrap:not(.gap-changelog-wrap--compact) .gap-changelog-toc-lien {
    justify-content: space-between;
  }
}

/* Panneau : la liste reste en tête, sur une ou deux lignes de pastilles. La
   date s'efface — dans 380 px elle ferait passer chaque entrée à deux lignes,
   et c'est le NUMÉRO qu'on cherche. */
.gap-changelog-wrap--compact .gap-changelog-toc-date { display: none; }
.gap-changelog-wrap--compact .gap-changelog-toc { max-height: 9rem; overflow-y: auto; }

/* Le lien du sommaire amène le titre SOUS le bord haut, pas collé contre —
   et sous la barre d'administration quand elle est là. */
.gap-changelog h3[id] { scroll-margin-top: 1.5rem; }

/* ── La modale du journal, dans l'échelle du socle ───────────────────
   Bootstrap place ses modales à 1055 et leur voile à 1050. Or l'échelle
   d'empilement du socle monte plus haut : `--z-bandeau` vaut 1060, et il
   porte le bandeau de consentement ET les menus de la barre admin — qui
   passaient donc PAR-DESSUS le journal ouvert.

   Une modale plein écran appartient au plan des surcouches, comme le
   sélecteur de liens et l'aperçu par appareil : elle consomme donc
   `--z-surcouche`, plutôt que de garder la valeur d'une bibliothèque qui
   ignore cette échelle.

   Le voile est un élément SÉPARÉ, que Bootstrap ajoute au <body> sans
   classe qui le rattache à sa modale. On ne le remonte donc que lorsque
   c'est CELLE-CI qui est ouverte (:has), sans quoi toutes les modales du
   back-office verraient leur voile passer au-dessus du reste. */
.gap-changelog-modal { z-index: var(--z-surcouche); }
body:has(.gap-changelog-modal.show) > .modal-backdrop {
  z-index: calc(var(--z-surcouche) - 1);
}

/* ── Signaler un défaut, proposer une idée (28/08/2026) ──────────────────
   MÊME plan d'empilement, et pour la même raison : les menus de la barre
   admin vivent à `--z-bandeau` (1060) et passeraient sinon par-dessus le
   formulaire — or c'est un menu de cette barre qui l'ouvre. */
.gap-feedback-modal { z-index: var(--z-surcouche); }
body:has(.gap-feedback-modal.show) > .modal-backdrop {
  z-index: calc(var(--z-surcouche) - 1);
}

/* ── Suivre les retours (14/09/2026) ─────────────────────────────────────
   Même plan d'empilement, pour la même raison : un menu de la barre l'ouvre. */
.gap-suivi-modal { z-index: var(--z-surcouche); }
body:has(.gap-suivi-modal.show) > .modal-backdrop {
  z-index: calc(var(--z-surcouche) - 1);
}

.gap-feedback-aide {
  font-size: .875rem;
  color: var(--gap-muted, #6c757d);
  margin-bottom: .6rem;
}

/* Ce qui part avec le message. Discret, mais JAMAIS masqué : on ne fait pas
   partir en silence des informations que la personne n'a pas vues. */
.gap-feedback-ctx {
  font-size: .78rem;
  color: var(--gap-muted, #6c757d);
  margin: .55rem 0 0;
  padding-left: .6rem;
  border-left: 2px solid rgba(127, 127, 127, .28);
  overflow-wrap: anywhere;
}

.gap-feedback-retour {
  font-size: .82rem;
  margin: .7rem 0 0;
  min-height: 1.2em;   /* place réservée : pas de saut de mise en page à l'envoi */
}

/* ── Désigner l'endroit qui ne va pas ────────────────────────────────────
   « La troisième carte » ne veut rien dire dans un signalement relu trois
   jours plus tard ; un clic sur l'élément, si. */
.gap-feedback-zone {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  font-size: .8rem;
  margin: .55rem 0 0;
  padding: .4rem .55rem;
  border-radius: .3rem;
  background: rgba(206, 43, 30, .08);
  color: inherit;
}
.gap-feedback-zone-quoi { flex: 1; overflow-wrap: anywhere; }
.gap-feedback-zone-x {
  border: 0; background: none; padding: 0; cursor: pointer;
  font-size: .74rem; text-decoration: underline; color: inherit; opacity: .75;
}
.gap-feedback-zone-x:hover { opacity: 1; }

/* Le pied porte « Montrer où » à gauche, les deux autres à droite. */
.gap-feedback-pied { justify-content: flex-end; }

/* Pendant le pointage : la page entière devient une cible. `!important` sur
   le curseur parce que les éléments interactifs imposent le leur — et un
   curseur qui change à chaque survol dirait le contraire de ce qu'on demande. */
body.gap-feedback-pointage,
body.gap-feedback-pointage * { cursor: crosshair !important; }

/* La surbrillance passe par un outline, JAMAIS par une bordure : une bordure
   occupe de la place et pousserait la mise en page à chaque survol — on
   désignerait alors une page qui n'est plus celle qu'on regarde. */
.gap-feedback-cible {
  outline: 2px solid var(--color-primary, #CE2B1E) !important;
  outline-offset: 1px;
  background: rgba(206, 43, 30, .07) !important;
}

.gap-feedback-consigne {
  position: fixed; left: 50%; bottom: 1.5rem; transform: translateX(-50%);
  /* Au-dessus du guide d'accueil, sinon celui-ci masquerait la consigne —
     exactement le défaut corrigé plus bas pour les dialogues. */
  z-index: calc(var(--z-toast) - 1);
  background: #161B22; color: #fff;
  padding: .6rem 1.1rem; border-radius: 2rem;
  font-size: .85rem; font-weight: 600;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .35);
  pointer-events: none;   /* on ne doit jamais désigner la consigne elle-même */
}
.gap-feedback-retour.is-ok    { color: #1a7f47; }
.gap-feedback-retour.is-error { color: #b02a37; }

/* ── « Votre signalement a abouti » ──────────────────────────────────────
   En tête du journal, pour l'auteur du signalement et lui seul. C'est la
   moitié qui manquait à la boucle : il écrit, puis il constate. */
.gap-retours-livres {
  border: 1px solid rgba(26, 127, 71, .35);
  border-left-width: 3px;
  border-radius: .4rem;
  padding: .7rem .9rem;
  margin-bottom: 1rem;
  background: rgba(26, 127, 71, .06);
}
.gap-retours-livres-titre {
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .02em;
  margin: 0 0 .4rem;
  color: #1a7f47;
}
.gap-retours-livres-liste {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .3rem;
  font-size: .85rem;
}
.gap-retours-livres-liste li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .4rem;
}
.gap-retours-livres-msg { overflow-wrap: anywhere; }
.gap-retours-livres-ver {
  font-size: .74rem;
  font-weight: 600;
  white-space: nowrap;
  color: #1a7f47;
}

/* ── Le suivi des retours : la liste (14/09/2026) ────────────────────────
   Partagée par la fenêtre du badge et l'écran plein du back-office — d'où
   sa place ici et non dans admin.css : le back-office charge les deux
   feuilles, le site seulement celle-ci. */
.gap-suivi-onglets { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .6rem; }
.gap-suivi-onglet {
  font-size: .82rem; font-weight: 600; text-decoration: none;
  padding: .25rem .75rem; border-radius: 999px;
  border: 1px solid rgba(127, 127, 127, .35); color: inherit;
}
.gap-suivi-onglet:hover { border-color: currentColor; color: inherit; }
.gap-suivi-onglet.is-active { background: #161B22; border-color: #161B22; color: #fff; }
.gap-suivi-legende { font-size: .76rem; color: var(--gap-muted, #6c757d); margin: 0 0 .9rem; }
.gap-suivi-vide { font-size: .88rem; color: var(--gap-muted, #6c757d); margin: 0; }
.gap-suivi-liste {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: .7rem;
}
.gap-suivi-item {
  padding: .65rem .8rem; border-radius: .45rem;
  border: 1px solid rgba(127, 127, 127, .22);
}
/* Les siens se repèrent d'un coup d'œil sans être mis à part : un défaut
   déjà signalé par quelqu'un d'autre doit se voir dans la MÊME liste. */
.gap-suivi-item.is-mien { border-left: 3px solid var(--color-primary, #CE2B1E); }
.gap-suivi-tete {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: .3rem .6rem; margin-bottom: .3rem;
}
.gap-suivi-etat {
  font-size: .72rem; font-weight: 700; letter-spacing: .02em;
  padding: .08rem .5rem; border-radius: 999px; white-space: nowrap;
}
/* « Reçu » en gris et non en rouge : pour qui a écrit, « pas encore lu »
   n'est pas une alerte. Le rouge reste à l'écran de traitement. */
.gap-suivi-etat--recu  { background: rgba(127, 127, 127, .16); color: #4a4a4a; }
.gap-suivi-etat--vu    { background: rgba(214, 150, 0, .18);   color: #7a5500; }
.gap-suivi-etat--livre { background: rgba(26, 127, 71, .14);   color: #1a7f47; }
.gap-suivi-meta { font-size: .76rem; color: var(--gap-muted, #6c757d); }
.gap-suivi-msg {
  margin: 0; font-size: .9rem; line-height: 1.45; overflow-wrap: anywhere;
  max-height: 12em; overflow-y: auto;
}
.gap-suivi-qui {
  margin: .35rem 0 0; font-size: .76rem; color: var(--gap-muted, #6c757d);
  overflow-wrap: anywhere;
}
.gap-suivi-qui code { font-size: .74rem; }
.gap-suivi-page { color: inherit; }
.gap-suivi-reponse {
  margin-top: .5rem; padding: .45rem .65rem; border-radius: .35rem;
  background: rgba(26, 127, 71, .06); border-left: 3px solid rgba(26, 127, 71, .45);
}
.gap-suivi-reponse-tete {
  display: flex; align-items: center; gap: .3rem;
  margin: 0 0 .15rem; font-size: .74rem; font-weight: 600; color: #1a7f47;
}
.gap-suivi-reponse-texte { margin: 0; font-size: .86rem; line-height: 1.45; overflow-wrap: anywhere; }

/* Dans la modale, le sommaire ne peut pas être « sticky » par rapport à la
   page : c'est le corps de la modale qui défile. Il reste donc en tête. */
.gap-changelog-modal .gap-changelog-toc { position: static; }

/* ── Versions repliables ─────────────────────────────────────────────
   Repliées, les 36 versions tiennent à l'écran ; on ouvre ce qu'on lit.
   Le repli est natif (<details>) : il fonctionne sans JavaScript, le
   navigateur l'annonce aux lecteurs d'écran, et Ctrl+F ouvre de lui-même la
   section qui contient le texte cherché. */
.gap-changelog-versions {
  display: flex;
  flex-direction: column;
  gap: .4rem;
}

.gap-changelog-version {
  background: #fff;
  border: 1px solid #E6E4E0;
  border-radius: .4rem;
  /* Sans quoi le contenu d'une version ouverte déborderait des coins. */
  overflow: hidden;
}
.gap-changelog-version[open] {
  border-color: #D9D5CF;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .04);
}

.gap-changelog-version-tete {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  padding: .7rem .9rem;
  cursor: pointer;
  list-style: none;          /* Safari garde son triangle sans ceci */
  user-select: none;
  transition: background var(--transition);
}
.gap-changelog-version-tete::-webkit-details-marker { display: none; }
.gap-changelog-version-tete:hover { background: #F5F3F0; }
/* Le focus clavier doit se voir : c'est la seule commande de cet écran. */
.gap-changelog-version-tete:focus-visible {
  outline: 2px solid var(--color-primary, #B3261E);
  outline-offset: -2px;
}

/* Un chevron dessiné en CSS plutôt qu'un caractère : il tourne à
   l'ouverture, et ne dépend d'aucune police. */
.gap-changelog-version-tete::before {
  content: '';
  flex: none;
  width: .42rem; height: .42rem;
  margin-right: .1rem;
  border-right: 2px solid #9A958D;
  border-bottom: 2px solid #9A958D;
  transform: rotate(-45deg);
  transition: transform var(--transition);
}
.gap-changelog-version[open] > .gap-changelog-version-tete::before {
  transform: rotate(45deg);
}

.gap-changelog-version-num {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: #191C21;
}
.gap-changelog-version-date { color: #6B7280; font-size: .82rem; }
/* Le nombre de chantiers, poussé à droite : il dit ce qu'on trouvera en
   ouvrant, au lieu d'obliger à ouvrir pour le savoir. */
.gap-changelog-version-n {
  margin-left: auto;
  padding: .05rem .45rem;
  background: #F5F3F0;
  border-radius: 1rem;
  font-size: .72rem;
  font-variant-numeric: tabular-nums;
  color: #6B7280;
}

.gap-changelog-version > .gap-changelog {
  padding: .2rem 1.1rem 1.1rem;
  border-top: 1px solid #EFEDE9;
}
/* Le titre de version vit maintenant dans le résumé : ce qui reste dans le
   corps commence aux chantiers, sans la grande marge d'un premier titre. */
.gap-changelog-version > .gap-changelog > :first-child { margin-top: .9rem; }

.gap-changelog-toc-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
}
.gap-changelog-toggle {
  background: none;
  border: none;
  padding: 0;
  font-size: .72rem;
  font-weight: 600;
  color: var(--color-primary, #B3261E);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.gap-changelog-toggle:hover { opacity: .75; }


/* Badge de version : un REPÈRE, pas une action — il ne doit pas peser autant
   que « Photos » ou « Site » à côté de lui. D'où le fond retiré, le poids
   normal et les chiffres tabulaires (« v1.21.1 » ne doit pas danser quand la
   version change). Il redevient un bouton franc au survol, puisqu'il en est
   un : son clic ouvre le journal. */
.gap-adminbar .adminbar-version {
  background: none;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
  opacity: .75;
  padding-inline: .5rem;
}
.gap-adminbar .adminbar-version:hover,
.gap-adminbar .adminbar-version:focus-visible {
  background: rgba(255,255,255,.15);
  opacity: 1;
}
/* Aperçu responsive (mode édition) : boutons barre admin + overlay iframe. */
.gap-adminbar .adminbar-device { display: flex; align-items: center; gap: .25rem; }
.gap-device-overlay {
  position: fixed; inset: 0; z-index: var(--z-surcouche);
  background: rgba(20, 20, 20, .82);
  display: flex; flex-direction: column; align-items: center;
  padding: .75rem 1rem 1.25rem; gap: .75rem;
}
.gap-device-toolbar {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  color: #fff;
}
.gap-device-toolbar button {
  border: 0; border-radius: .4rem; cursor: pointer;
  background: rgba(255,255,255,.12); color: #fff;
  font-size: .8rem; font-weight: 600; padding: .4rem .8rem;
}
.gap-device-toolbar button:hover { background: rgba(255,255,255,.25); }
.gap-device-toolbar button.is-active {
  background: var(--color-accent);
  box-shadow: 0 0 0 2px rgba(212,160,23,.4);
}
.gap-device-toolbar .gap-device-close { background: rgba(220,53,69,.55); }
.gap-device-toolbar .gap-device-close:hover { background: rgba(220,53,69,.85); }
.gap-device-note { font-size: .75rem; color: rgba(255,255,255,.65); }
.gap-device-frame {
  width: 375px; max-width: 96vw; flex: 1; min-height: 0;
  background: #fff; border-radius: 1rem; overflow: hidden;
  box-shadow: 0 12px 48px rgba(0,0,0,.5), 0 0 0 6px #222, 0 0 0 8px #444;
  transition: width .25s ease;
}
.gap-device-frame iframe { width: 100%; height: 100%; border: 0; display: block; }

/* Indicateur de langue éditée (mode édition). */
.gap-adminbar .adminbar-lang-label {
  font-size: .72rem;
  font-weight: 600;
  color: rgba(255,255,255,.6);
  white-space: nowrap;
}
.gap-adminbar .adminbar-lang-btn.active {
  background: var(--color-accent);
  color: #fff;
  box-shadow: 0 0 0 2px rgba(212,160,23,.4);
}
.gap-adminbar .adminbar-lang-btn.active:hover { background: var(--color-accent); }
.gap-adminbar .adminbar-lang-btn--preparation { outline: 1px dashed rgba(255,255,255,.55); outline-offset: -3px; }
.gap-adminbar .adminbar-lang-prep {
  font-size: .72rem;
  font-weight: 600;
  color: #fff;
  border: 1px dashed rgba(255,255,255,.6);
  border-radius: .25rem;
  padding: .1rem .4rem;
  white-space: nowrap;
}
.gap-adminbar .adminbar-btn-danger {
  background: var(--color-primary);
  color: #fff;
}
.gap-adminbar .adminbar-btn-danger:hover { background: var(--color-primary-hover); color: #fff; }

/* ── Barre admin réorganisée : cluster d'état + menus déroulants ────────── */
/* Cluster « ce qui vient de se passer » : autosave + annuler + corbeille. */
.gap-adminbar .adminbar-cluster {
  display: inline-flex; align-items: center; gap: .15rem;
  background: rgba(0,0,0,.28); border-radius: .35rem; padding: .12rem;
}
.gap-adminbar .adminbar-cluster .adminbar-autosave { background: transparent; }
.gap-adminbar .adminbar-cluster-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.9rem; height: 1.9rem; border: 0; border-radius: .3rem;
  background: transparent; color: rgba(255,255,255,.85); cursor: pointer;
  transition: background var(--transition), color var(--transition), opacity var(--transition);
}
.gap-adminbar .adminbar-cluster-btn:hover:not(:disabled) { background: rgba(255,255,255,.18); color: #fff; }
.gap-adminbar .adminbar-cluster-btn:disabled { opacity: .35; cursor: default; }

/* Menus déroulants de la barre (Aperçu, Site, compte). Position FIXED calculée
   en JS : la barre est en overflow-x:auto, un absolu serait rogné. */
.gap-adminbar .adminbar-menu-wrap { position: relative; }
.adminbar-menu {
  display: none; position: fixed; z-index: var(--z-bandeau);
  background: #1e2329; border: 1px solid rgba(255,255,255,.1); border-radius: .4rem;
  box-shadow: 0 4px 16px rgba(0,0,0,.4); min-width: 200px; padding: .3rem;
  flex-direction: column; gap: .1rem;
}
.adminbar-menu.open { display: flex; }
/* [hidden] doit gagner sur le display:flex ci-dessous : les formats d'aperçu
   plus larges que la fenêtre sont masqués par l'attribut (gap.js,
   GapDevicePreview). Même règle pour la barre de l'aperçu ouvert. */
.adminbar-menu-item[hidden],
.gap-device-toolbar [hidden] { display: none; }
.adminbar-menu-item {
  display: flex; align-items: center; gap: .5rem;
  border: 0; background: transparent; color: rgba(255,255,255,.85); cursor: pointer;
  font-size: .82rem; line-height: 1.2; padding: .5rem .6rem; border-radius: .3rem;
  text-align: left; text-decoration: none; width: 100%; white-space: nowrap;
  transition: background .12s, color .12s;
}
.adminbar-menu-item:hover { background: rgba(255,255,255,.1); color: #fff; }
.adminbar-menu-item--danger:hover { background: rgba(192,57,43,.25); color: #f8b4b4; }
.adminbar-menu-item svg { flex-shrink: 0; }
.adminbar-menu-info { cursor: default; color: rgba(255,255,255,.6); }
.adminbar-menu-info:hover { background: transparent; color: rgba(255,255,255,.6); }
.adminbar-menu form { width: 100%; }
/* Levée programmée de la maintenance (27/09/2026) : un champ date et heure
   dans le menu « Site », sous le fond sombre du menu. */
.adminbar-levee { display: flex; flex-direction: column; gap: .25rem; padding: .35rem .6rem .2rem; }
.adminbar-levee .adminbar-menu-item { margin: 0 -.6rem; width: calc(100% + 1.2rem); }
.adminbar-levee-label { font-size: .72rem; color: rgba(255,255,255,.6); }
.adminbar-levee-champ {
  font: inherit; font-size: .8rem; color: #fff; color-scheme: dark;
  background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.2);
  border-radius: .3rem; padding: .3rem .4rem;
}
.adminbar-levee-champ:focus { outline: 2px solid var(--color-accent); outline-offset: 1px; }

/* Pastille de santé du menu « Site » : vert = rien à signaler. */
.adminbar-health-dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--color-secondary); display: inline-block;
}
.adminbar-health-dot.is-danger { background: var(--color-primary); }

.adminbar-sep {
  width: 1px;
  height: 20px;
  background: rgba(255,255,255,.2);
  flex-shrink: 0;
}
.adminbar-spacer { flex: 1; }

/* Santé des services externes (menu « Site » de la barre) — une ligne par
   service configuré, pastille = dernier verdict CONSIGNÉ (SanteServices) :
   neutre tant que rien n'a été vérifié, jamais vert par défaut. */
.adminbar-svc {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  font-size: .75rem;
}
.adminbar-svc-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.adminbar-svc-dot.neutre  { background: transparent; box-shadow: inset 0 0 0 1.5px currentColor; opacity: .45; }
.adminbar-svc-dot.ok      { background: #4ade80; }
.adminbar-svc-dot.warning { background: var(--color-accent); }
.adminbar-svc-dot.danger  { background: var(--color-primary); }

/* Bannière maintenance */
.maintenance-banner {
  background: var(--color-primary);
  color: #fff;
  font-size: .8rem;
  font-weight: 600;
  text-align: center;
  padding: .35rem 1rem;
  letter-spacing: .03em;
}

/* ── Footer ────────────────────────────────────────────────── */
.gap-footer {
  background: var(--color-ink);
  color: rgba(255,255,255,.8);
  padding: 3.5rem 0 1.5rem;
  margin-top: auto;
}
.gap-footer h5 {
  font-family: var(--font-body);
  font-size: .7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: rgba(255,255,255,.55); /* .4 = 3,81:1 sur l'encre, sous AA pour du .7rem ; .55 ≈ 6:1 — la valeur déjà employée ailleurs dans ce footer (V11) */
  margin-bottom: 1rem;
}
.gap-footer a {
  color: rgba(255,255,255,.7);
  text-decoration: none;
  transition: color var(--transition);
}
.gap-footer a:hover { color: #fff; }
.gap-footer .footer-logo img { height: 52px; width: auto; filter: brightness(0) invert(1); opacity: .9; }
.gap-footer .footer-divider {
  border-color: rgba(255,255,255,.1);
  margin: 2rem 0 1rem;
}
.gap-footer .footer-bottom {
  font-size: .78rem;
  color: rgba(255,255,255,.4);
}
.gap-footer .footer-admin-link {
  opacity: .3;
  font-size: .72rem;
  transition: opacity .2s;
}
.gap-footer .footer-admin-link:hover { opacity: .7; }
.gap-footer .social-links { display: flex; gap: .75rem; flex-wrap: wrap; }
.gap-footer .social-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px; height: 36px;
  border-radius: 50%;
  background: rgba(255,255,255,.1);
  color: rgba(255,255,255,.7);
  font-size: .9rem;
  transition: background var(--transition), color var(--transition);
  text-decoration: none;
}
.gap-footer .social-link:hover {
  background: var(--color-primary);
  color: #fff;
}

/* ── Avertissement du pied de page ───────────────────────────────
   Rendu à l'un des trois emplacements du pied (haut / milieu / bas), réglés
   par footer_disclaimer_position. Le bloc n'existe PAS quand le réglage est
   vide hors édition : aucune marge fantôme sur les sites qui n'en veulent pas.
   Habillage volontairement discret — c'est une mention légère, pas un encadré
   d'alerte ; un thème qui veut autre chose surcharge ces deux règles. */
.gap-footer .footer-disclaimer-zone { position: relative; }
.gap-footer .footer-disclaimer {
  max-width: 60rem;
  margin: 0 auto 1.6rem;
  padding-top: 1.6rem;
  border-top: 1px dashed rgba(255,255,255,.16);
  font-size: .78rem;
  line-height: 1.6;
  color: rgba(255,255,255,.5);
  text-align: center;
}
/* Emplacement « tout en bas » : il suit déjà le filet du menu légal, un second
   trait ferait doublon. */
.gap-footer .footer-legal-menu + .footer-disclaimer-zone .footer-disclaimer {
  border-top: 0;
  padding-top: 0;
  margin-bottom: .9rem;
}

/* Flèches de placement (mode édition) — même patron que le sélecteur
   d'emplacement de média de la frise (.gap-tl-pos). */
.gap-footer-disclaimer-pos {
  position: absolute; top: .2rem; left: 0; z-index: 6;
  display: inline-flex; gap: 2px;
  background: #fff; border: 1px solid var(--color-border, #e7e1d6); border-radius: 7px; padding: 2px;
  box-shadow: 0 4px 12px rgba(20,18,16,.12);
}
.gap-fd-pos-btn {
  border: 0; background: transparent; cursor: pointer; width: 1.5rem; height: 1.5rem;
  border-radius: 5px; line-height: 1; font-size: .95rem; color: #6c645a;
  display: inline-flex; align-items: center; justify-content: center;
}
.gap-fd-pos-btn:hover { background: var(--color-surface-alt, #f6f1e7); color: var(--t-ink, #1a1a1a); }
.gap-fd-pos-btn.is-active { background: var(--color-primary, #c8102e); color: #fff; }

/* ── Disposition en flux (plusieurs blocs sur une ligne) ─────── */
/* Les sections sont des items flex : une section « pleine largeur » occupe
   toute la ligne, les sections partielles (½/⅓/⅔) consécutives se placent
   côte à côte puis passent à la ligne. Par défaut tout est en 'full' → le
   rendu est identique à un empilement de blocs classiques (non-régression). */
.gap-sections-flow {
  display: flex; flex-wrap: wrap; align-items: stretch;
  /* Gouttière ENTRE colonnes. `column-gap` et non `gap` : l'espacement
     vertical entre deux rangées appartient au rythme des sections
     (leur padding), pas à cette mécanique. */
  column-gap: var(--gap-gouttiere);
}
/* ── Couloir des rangées de colonnes ──────────────────────────────────────
   Par défaut 0 : les colonnes s'étalent bord à bord (comportement
   historique, celui du GAP). Un thème peut définir --gap-couloir pour caler
   les rangées dans sa largeur de lecture, p. ex. :
     :root{ --gap-couloir: max(1.2rem, calc((100% - 71.25rem) / 2)); }
   Chaque colonne est réduite de sa part du couloir, et la PREMIÈRE de chaque
   rangée (gap-row-first, posée par SectionRenderer — le CSS ne sait pas qui
   ouvre une rangée) porte la marge gauche. La rangée s'arrête ainsi
   d'elle-même à la même distance du bord droit. */
/* ── Gouttière entre colonnes ──────────────────────────────────────────────
   Deux sections mises côte à côte se touchaient : mesuré sur une page de
   1280 px, trois tiers rendaient 0→427, 427→853, 853→1280 — bord à bord.
   L'espacement n'existait qu'aux BORDS de la page (le couloir), jamais entre
   les colonnes. Relevé par Pierre-Louis le 10/08/2026.

   Une gouttière ne peut pas s'ajouter seule : les largeurs étant des
   fractions de la ligne, `column-gap` la ferait déborder et la dernière
   colonne basculerait en dessous. Chaque colonne prend donc sa part de ce qui
   RESTE — `--gap-espaces-rangee` porte ce qui est retiré, et le serveur pose
   `gap-cols-N` parce que lui seul sait combien de colonnes se partagent la
   rangée (cf. SectionRenderer::grouperRangees).

   1.5 rem : la gouttière déjà en vigueur À L'INTÉRIEUR des sections
   (`--bs-gutter-x` de Bootstrap). Un thème qui veut des bandeaux jointifs la
   remet à 0 — comme il le fait déjà pour le couloir. */
:root {
  --gap-couloir: 0px;
  --gap-gouttiere: 1.5rem;
  /* Rangée d'UNE colonne : aucune gouttière à retirer. C'est le cas d'une
     page de sections empilées — de très loin le plus fréquent — et il rend
     exactement comme avant l'introduction de la gouttière. */
  --gap-espaces-rangee: calc(2 * var(--gap-couloir));
}
.gap-cols-2 { --gap-espaces-rangee: calc(2 * var(--gap-couloir) + 1 * var(--gap-gouttiere)); }
.gap-cols-3 { --gap-espaces-rangee: calc(2 * var(--gap-couloir) + 2 * var(--gap-gouttiere)); }

.gap-w-full        { flex: 0 0 100%;      max-width: 100%; }
.gap-w-two-thirds  { flex: 0 0 calc((100% - var(--gap-espaces-rangee)) * .66666); max-width: calc((100% - var(--gap-espaces-rangee)) * .66666); }
.gap-w-half        { flex: 0 0 calc((100% - var(--gap-espaces-rangee)) * .5);     max-width: calc((100% - var(--gap-espaces-rangee)) * .5); }
.gap-w-third       { flex: 0 0 calc((100% - var(--gap-espaces-rangee)) * .33333); max-width: calc((100% - var(--gap-espaces-rangee)) * .33333); }
@media (min-width: 768px) {
  .gap-row-first { margin-left: var(--gap-couloir); }
}
/* Les enfants directs qui ne sont pas des sections restent pleine largeur. */
.gap-sections-flow > .gap-page-title,
.gap-sections-flow > .gap-add-section { flex: 0 0 100%; max-width: 100%; width: 100%; }
/* Section en colonne : le contenu occupe toute la colonne (pas de .container). */
.gap-col-container { width: 100%; padding-left: 1.5rem; padding-right: 1.5rem; }
/* Colonnes empilées sur mobile. */
@media (max-width: 767.98px) {
  .gap-w-two-thirds, .gap-w-half, .gap-w-third { flex: 0 0 100%; max-width: 100%; }
}

/* ── Sections de page ──────────────────────────────────────── */
/* Une bande n'élargit JAMAIS la page. Sans ce clip, tout ce qui dépasse
   latéralement dans une section fait défiler la page ENTIÈRE — invisible
   au bureau, où les gouttières absorbent le débordement, flagrant sur
   mobile. Les animations « Glissé droite » et « Incliné » suffisent à le
   provoquer : leur état AVANT révélation décale l'élément de 20 à 30px,
   et une rangée déjà large de tout l'écran finit hors cadre (12/08 :
   /fr/presentation, document de 405 pour un écran de 375).
   clip et NON hidden : hidden ferait de la section une zone de défilement
   et casserait position:sticky à l'intérieur. Avec clip, overflow-y reste
   « visible » (mesuré) — les menus d'édition débordent toujours vers le
   bas, et ce qui doit vraiment défiler de côté garde son propre conteneur
   (.table-responsive, bandes de vignettes). Safari < 16 ignore clip : le
   défaut y subsiste, rien n'y casse. Détail dans le CHANGELOG. */
.page-section { padding: 4rem 0; overflow-x: clip; }
.page-section.section-sm { padding: 2rem 0; }
.page-section.section-lg { padding: 6rem 0; }

/* Fonds de section */
.page-section.bg-light-custom  { background: var(--color-surface); }
.page-section.bg-dark-custom   { background: var(--color-ink); color: #fff; }

/* ── « Cette surface est sombre » — déclaré, jamais peint ──────────────────
   `has-dark-surface` ne pose AUCUN fond. Elle dit seulement que ce qu'elle
   contient repose sur du sombre, et déclenche toutes les règles de contraste
   du socle : titres, étiquette, tagline, texte atténué, bouton secondaire.

   ── Pourquoi elle existe ────────────────────────────────────────────────
   Le socle ne savait adapter ses contrastes que pour les fonds QU'IL peint —
   ceux réglés dans le CMS. Un thème qui peint lui-même une surface n'avait
   rien à quoi le socle puisse réagir : le 12/08/2026, le héros de la vitrine,
   peint en nuit par le thème, affichait un bouton secondaire à **1,86:1**
   (#1E4C8C sur #0F2438). Le défaut ne venait pas du thème mais du socle, qui
   n'offrait aucun moyen de DÉCLARER sans PEINDRE : la seule classe disponible,
   `bg-dark-custom`, impose sa couleur — un thème qui l'employait pour déclarer
   se retrouvait avec un fond qu'il n'avait pas choisi.

   ── Comment un thème s'en sert ──────────────────────────────────────────
   Sur la section entière (via « Classes personnalisées » de la section, ou en
   la posant depuis son propre CSS), ou sur n'importe quel conteneur interne
   qu'il peint lui-même — une carte, un héros, un encart. Elle agit par
   descendance, comme les classes de fond du CMS.

   ⚠️ Un thème qui peint une surface sombre SANS la déclarer garde la
   responsabilité entière de ses contrastes, `.btn-gap-outline` compris. */
.page-section.bg-dark-custom h1,
.page-section.bg-dark-custom h2,
.page-section.bg-dark-custom h3,
.has-dark-surface h1,
.has-dark-surface h2,
.has-dark-surface h3 { color: #fff; }
.has-dark-surface { color: #fff; }
.page-section.bg-primary-custom { background: var(--color-primary); color: #fff; }
.page-section.bg-secondary-custom { background: var(--color-secondary); color: #fff; }

/* Section avec image de fond */
.page-section.has-bg-image {
  background-size: cover;
  background-repeat: no-repeat;
  position: relative;
}
.page-section.has-bg-image > .bg-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0,0,0,var(--overlay-opacity, 0));
  pointer-events: none;
}
.page-section.has-bg-image > .container,
.page-section.has-bg-image > .container-fluid {
  position: relative;
  z-index: 1;
}
/* Fond fixe (effet « le contenu défile par-dessus », façon Odoo) : repli en
   défilement normal sur mobile — background-attachment:fixed est saccadé, voire
   cassé, sur iOS Safari. */
@media (max-width: 768px) {
  .page-section[style*="background-attachment:fixed"],
  .page-section[style*="background-attachment: fixed"] {
    background-attachment: scroll !important;
  }
}

/* Carrousel d'images EN FOND de section (derrière le contenu, fondu enchaîné).
   Le voile (.bg-overlay) et le contenu (.container, z-index:1) passent au-dessus. */
.has-bg-carousel { position: relative; }
.gap-bg-carousel { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.gap-bg-slide {
  position: absolute; inset: 0;
  background-size: cover; background-position: center;
  opacity: 0; transition: opacity 1.2s ease; will-change: opacity;
}
.gap-bg-slide.is-active { opacity: 1; }
/* Effet « glissement » du diaporama de fond (P1) : les slides se déplacent au
   lieu de se fondre. Positions pilotées par initBgCarousel (gap.js). */
.gap-bg-carousel--slide .gap-bg-slide {
  opacity: 1; transform: translateX(100%);
  transition-property: transform;
}
.gap-bg-carousel--slide .gap-bg-slide.is-active  { transform: translateX(0); }
.gap-bg-carousel--slide .gap-bg-slide.is-leaving { transform: translateX(-100%); }
.gap-bg-carousel--slide .gap-bg-slide.is-next    { transition: none; transform: translateX(100%); }
@media (prefers-reduced-motion: reduce) {
  .gap-bg-carousel--slide .gap-bg-slide { transform: none; opacity: 0; }
  .gap-bg-carousel--slide .gap-bg-slide.is-active { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .gap-bg-slide { transition: none; }
}
/* Carrousel en fond « fixe » : slides figés au viewport (mêmes conditions que le
   fond fixe d'une image ; l'anim est supprimée sur ces sections). Repli mobile. */
[data-bg-fixed] .gap-bg-slide { background-attachment: fixed; }
@media (max-width: 768px) {
  [data-bg-fixed] .gap-bg-slide { background-attachment: scroll; }
}
/* Parallaxe de fond : le background-position-y est piloté en JS (gap.js). */
[data-bg-parallax] { will-change: background-position; }

/* ═══ Média par item (template liste, mode média) ══════════════════════════════
   Réutilisable par toute page/thème. Carrousel INLINE (Splide + lightbox), BANDE
   pleine largeur (diaporama gap-bg-carousel + parallax JS), barre d'édition FRONT.
   Couleurs via variables de charte (avec repli) → un thème peut surcharger. */
/* Carrousel inline : vignettes défilantes à côté du texte. */
.gap-item-carousel .splide__slide { line-height: 0; }
.gap-item-carousel .splide__slide img {
  height: clamp(170px, 24vw, 240px); width: auto; border-radius: 8px; object-fit: cover;
  box-shadow: 0 12px 30px rgba(20,18,16,.16); cursor: zoom-in;
}
/* Le carrousel d'élément GARDE le cadre du thème, comme la photo simple d'à
   côté. Ces trois règles l'annulaient : une rangée à galerie ne ressemblait
   plus à ses voisines, alors que seul le contenu diffère. Le défaut visible
   sur « Nos sections » — quatre photos encadrées et inclinées, la cinquième
   nue — venait d'ici.
   Seule l'ombre du wrap est retirée : le carrousel porte déjà la sienne sur
   chaque vignette, et les deux se cumulaient en un halo sale. */
.gap-item-inline { box-shadow: none; }
/* Flèches / pagination Splide aux couleurs de la charte. */
.gap-item-carousel .splide__arrow {
  background: rgba(255,255,255,.95); box-shadow: 0 6px 18px rgba(20,18,16,.2);
  opacity: 1; width: 2.4rem; height: 2.4rem;
}
.gap-item-carousel .splide__arrow svg { fill: var(--color-primary, #ce2b1e); width: 1.1rem; height: 1.1rem; }
.gap-item-carousel .splide__arrow:hover:not(:disabled) { background: var(--color-primary, #ce2b1e); }
.gap-item-carousel .splide__arrow:hover:not(:disabled) svg { fill: #fff; }
.gap-item-carousel .splide__pagination__page.is-active { background: var(--color-primary, #ce2b1e); }

/* ═══ Style « Bandeau défilant » (galerie intégrée) ════════════════════════════
   Défilé horizontal continu des photos d'un album, pause au survol, bords
   estompés. Le balisage vient du template du socle (gallery-embed), qui rend
   les photos deux fois : l'animation translate de -50% et retombe donc sur la
   copie, sans couture.

   Promu du thème gap-berstett au socle — le mécanisme ne doit rien à un thème.
   Ce qui reste propre à un thème (largeur de page de l'en-tête, allure du
   bouton) vit dans son theme.css, qui surcharge ces règles.

   La classe `gap-marquee` porte le RUBAN ; la <section> qui l'entoure porte,
   elle, `t-marquee` (dérivée du slug de variante par SectionRenderer). Deux
   classes distinctes et non plus une seule : c'est ce qui permet d'estomper
   les bords du ruban sans rogner l'en-tête, là où il fallait auparavant un
   sélecteur `:has()` pour les départager. */
.gap-marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 4%, #000 96%, transparent);
}
/* La durée n'est plus une constante mais le résultat d'une VITESSE réglée sur
   la section : le gabarit pose --gap-marq-duree, calculée depuis la longueur
   réelle du ruban (SectionRenderer::marqueeDuration). Les 42 s restent en
   repli — une base dont la colonne n'a pas encore été migrée rend exactement
   comme avant. ⚠️ Toucher la hauteur ou l'espacement ci-dessous fausse ce
   calcul : les deux valeurs sont recopiées en constantes côté PHP. */
.gap-marquee-track {
  display: flex; gap: 1.2rem; width: max-content;
  animation: gap-marquee var(--gap-marq-duree, 42s) linear infinite; will-change: transform;
}
.gap-marquee:hover .gap-marquee-track { animation-play-state: paused; }
.gap-marquee-item { flex: 0 0 auto; display: block; margin: 0; }
.gap-marquee-item img {
  height: clamp(220px, 30vw, 360px); width: auto; display: block;
  object-fit: cover; border-radius: 8px;
}
/* -50% = un jeu de photos exactement ; le .6rem compense la moitié du gap,
   sans quoi le raccord saute d'un demi-espace à chaque tour. */
@keyframes gap-marquee { to { transform: translateX(calc(-50% - .6rem)); } }
/* Mouvement continu et involontaire : on le coupe, et le ruban redevient une
   bande que l'on fait défiler soi-même. Ne jamais laisser l'animation seule
   comme moyen d'accéder aux photos de droite. */
@media (prefers-reduced-motion: reduce) {
  .gap-marquee-track { animation: none; }
  .gap-marquee { overflow-x: auto; }
}
/* Le bouton de section passe en haut à droite pour ce style (cf. _section.php,
   $ctaInHeading) : sans mise en page correspondante, il retomberait sous le
   titre. Repli neutre du socle — un thème peut le reprendre à son compte. */
.t-marquee .section-heading {
  display: grid; grid-template-columns: 1fr auto; align-items: end; gap: .4rem 1.5rem;
}
.t-marquee .section-heading > :not(.section-heading-cta) { grid-column: 1; }
.t-marquee .section-heading .display-title { margin: 0; }
.t-marquee .section-heading-cta { grid-column: 2; grid-row: 1 / -1; margin: 0; align-self: end; }

/* Bande PLEINE LARGEUR = diaporama d'album en fond (façon s_carousel_wrapper Odoo) :
   réutilise .gap-bg-carousel / .gap-bg-slide. Doit être positionnée + avoir une
   hauteur pour que les slides (inset:0) s'affichent. Parallax : cf. gap.js. */
.gap-item-band {
  /* AUCUNE MARGE au-dessus. La bannière prolonge le contenu qu'elle illustre :
     une bande de fond de page entre les deux les sépare visuellement alors
     qu'ils forment un seul bloc. Le correctif n'avait été appliqué qu'à la
     variante « galerie » ; la bannière ordinaire gardait ses ~38 px. */
  width: 100vw; margin-left: calc(50% - 50vw); margin-top: 0;
  position: relative; overflow: hidden; background: var(--t-ink, #1a1a1a);
  height: clamp(320px, 42vw, 540px);
}
@media (max-width: 820px) {
  .gap-item-inline { margin-top: 1rem; }
  .gap-item-band { height: clamp(220px, 55vw, 320px); }
}

/* Bannière rendue en GALERIE (un affichage a été choisi) : pleine largeur,
   mais le contenu revient dans la largeur de lecture — une mosaïque étalée d'un
   bord à l'autre de l'écran ne se lit pas.

   AUCUNE MARGE au-dessus : la bannière doit toucher le contenu qu'elle
   prolonge. Une bande de fond de page apparaissait entre les deux et coupait
   visuellement deux blocs sombres qui devaient se suivre.

   La hauteur réglée est un MINIMUM, pas une hauteur fixe : une galerie s'étend
   selon son contenu, on ne la tronque pas — contrairement au diaporama de fond,
   dont l'image se recadre. Double classe : les règles du thème portent sur
   `.gap-item-band`, il faut donc être plus spécifique pour les reprendre. */
.gap-item-band.gap-item-band--galerie {
  margin-top: 0;
  height: auto;
  padding: clamp(1.2rem, 3vw, 2.2rem) 0;
  background: var(--t-ink, #1a1a1a);
  display: flex; align-items: center;
}
.gap-item-band.gap-item-band--galerie > .container { width: 100%; }

/* ── « Photos » : réglage retiré de l'écran ────────────────────────────────
   Le nombre de vignettes visibles ne dit plus rien à l'éditeur : le diaporama,
   seul affichage d'une vignette de carte, montre TOUTES les photos en les
   faisant défiler — la valeur n'y avait aucun effet visible, ce qui est pire
   qu'un réglage absent.
   MASQUÉ, pas supprimé : les valeurs déjà en base continuent de borner les
   affichages qui composent (mosaïque, grille, mur, « à la une »), et le jour
   où un affichage justifiera de nouveau ce choix, ces trois lignes suffisent
   à le rendre. Une seule règle pour les DEUX barres — celle du socle et celle
   des rangées texte-image — qui portent chacune leur copie du champ. */
.gap-media-edit-grp:has(.gap-media-edit-num) { display: none; }

/* Champ nombre de la barre d'édition (photos affichées) : même gabarit que les
   boutons voisins, pour que la barre reste une ligne et non un formulaire. */
.gap-media-edit-num {
  width: 4.2rem; padding: .18rem .35rem;
  border: 1px solid rgba(255,255,255,.25); border-radius: .3rem;
  background: rgba(255,255,255,.08); color: inherit;
  font-size: .82rem;
}

/* Barre d'édition FRONT (mode édition) : type de média, source, emplacement, parallax. */
.gap-media-edit {
  display: flex; flex-wrap: wrap; align-items: center; gap: .45rem 1.4rem;
  margin: 0 0 1.1rem; padding: .5rem .75rem;
  background: var(--color-surface-alt, #fbf9f5); border: 1px dashed var(--color-border, #e2ddd6); border-radius: 10px;
}
.gap-media-edit-grp { display: inline-flex; align-items: center; gap: .4rem; }
.gap-media-edit-lab {
  font-family: var(--font-body, sans-serif); font-weight: 700; font-size: .64rem;
  letter-spacing: .05em; text-transform: uppercase; color: var(--t-ink, #1a1a1a); opacity: .55;
}
/* Bouton survolé mais pas encore appliqué : l'aperçu est en cours, rien n'est
   enregistré. Un contour suffit à le distinguer de l'état actif (plein) — même
   convention que la barre de modes de la section. */
.gap-media-edit-btn.is-preview {
  border-color: var(--color-primary, #ce2b1e);
  color: var(--color-primary, #ce2b1e);
  box-shadow: inset 0 0 0 1px var(--color-primary, #ce2b1e);
}
.gap-media-edit-btn {
  border: 1px solid var(--color-border, #e2ddd6); background: #fff; color: var(--t-ink, #1a1a1a);
  border-radius: 999px; padding: .22rem .7rem; font-size: .78rem; line-height: 1.2; cursor: pointer;
  transition: background .15s, border-color .15s, color .15s;
}
.gap-media-edit-btn:hover { border-color: var(--color-primary, #ce2b1e); color: var(--color-primary, #ce2b1e); }
.gap-media-edit-btn.is-active { background: var(--color-primary, #ce2b1e); border-color: var(--color-primary, #ce2b1e); color: #fff; cursor: default; }
.gap-media-edit-album { max-width: 15rem; }
.gap-media-edit-state { font-size: .78rem; color: var(--t-ink, #1a1a1a); opacity: .7; font-style: italic; }
/* L'avertissement « album partagé » passe SOUS le sélecteur de média, sur sa
   propre ligne : en ligne, il poussait les réglages voisins et faisait tenir la
   barre sur trois hauteurs pour une information de second plan.
   `flex-basis:100%` provoque le retour à la ligne DANS le groupe, sans le
   sortir de son contexte — l'avertissement doit rester rattaché au média dont
   il parle. */
.gap-media-edit-warn {
  flex-basis: 100%;
  display: flex; align-items: center; gap: .35rem; font-size: .74rem;
  margin-top: .3rem;
  color: var(--color-primary, #ce2b1e); background: rgba(206,43,30,.08);
  border: 1px solid rgba(206,43,30,.25); border-radius: .4rem; padding: .25rem .55rem;
}
/* Le groupe « Média » doit pouvoir passer à la ligne pour l'accueillir. */
.gap-media-edit-grp:has(.gap-media-edit-warn) { flex-wrap: wrap; }
.gap-media-edit-detach {
  border: 0; background: none; padding: 0; cursor: pointer;
  color: var(--color-primary, #ce2b1e); font-size: .74rem; font-weight: 700; text-decoration: underline;
}
.gap-media-edit-photo,
.gap-media-edit-browse,
.gap-media-edit-choose {
  border: 1px solid var(--color-border, #e2ddd6); background: #fff; color: var(--t-ink, #1a1a1a);
  border-radius: 999px; padding: .22rem .7rem; font-size: .78rem; cursor: pointer;
}
.gap-media-edit-photo:hover,
.gap-media-edit-browse:hover,
.gap-media-edit-choose:hover { border-color: var(--color-primary, #ce2b1e); color: var(--color-primary, #ce2b1e); }
/* Vignette entière cliquable + état sélectionné bien lisible.
   Vaut pour les DEUX surfaces (page « Photos » et panneau en modale) : la
   sélection multiple existe des deux côtés, elle doit se voir des deux côtés. */
.gap-media-panel .gmm-item,
.gmp-layout .gmm-item { cursor: pointer; }
.gap-media-panel .gmm-item.gmm-selected .gmm-card,
.gmp-layout .gmm-item.gmm-selected .gmm-card { outline: 3px solid var(--color-primary, #ce2b1e); outline-offset: 1px; }
.gap-media-edit-speed { accent-color: var(--color-primary, #ce2b1e); width: 9rem; max-width: 40vw; }
.gap-media-edit-val { min-width: 2.5ch; font-variant-numeric: tabular-nums; font-size: .78rem; color: var(--t-ink, #1a1a1a); }

/* ═══ Gestionnaire de MÉDIAS — colonne de sources (albums = dossiers) + grille ══
   Rendu par `admin/media/_rail.php` + `_grid.php`, dans DEUX contextes :
   la page « Photos » du back-office et le panneau en modale (back ET front).
   Les règles portent donc sur `.gmp-layout`, que les deux surfaces rendent —
   surtout pas sur `.gap-media-panel`, qui n'existe que dans la modale. */
.gmp-layout { gap: 1rem; align-items: flex-start; min-height: 22rem; }
.gmp-layout .gmp-rail {
  width: 13.5rem; border-right: 1px solid var(--color-border, #e2ddd6); padding-right: .75rem;
  max-height: 60vh; overflow-y: auto;
}
.gmp-layout .gmp-rail-title {
  font-size: .66rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  /* #8a8378 sur blanc ne donnait que 3,75:1 — sous le seuil AA de 4,5:1 pour un
     texte de cette taille (mesuré le 12/08). Le gris de la charte, lui, tient
     à 5,85:1, et c'est le même que celui du pied de la modale. */
  color: #6b6459; margin: .9rem 0 .3rem;
}
.gmp-layout .gmp-source {
  display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  padding: .38rem .55rem; border-radius: 7px; text-decoration: none;
  color: var(--t-ink, #1a1a1a); font-size: .86rem; line-height: 1.25;
}
.gmp-layout .gmp-source:hover { background: var(--color-surface-raised, #f3eee4); }
.gmp-layout .gmp-source.is-active { background: var(--color-primary, #ce2b1e); color: #fff; }
.gmp-layout .gmp-source-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gmp-layout .gmp-source-count { font-size: .72rem; opacity: .65; font-variant-numeric: tabular-nums; }
/* Album absent du site : sans marqueur, on perdrait sa trace en le cherchant. */
.gmp-layout .gmp-source-hidden {
  margin-left: .35rem; padding: 0 .3rem; border-radius: 4px;
  background: var(--color-border, #e2ddd6); color: #6f6a62;
  font-size: .62rem; text-transform: uppercase; letter-spacing: .04em; vertical-align: middle;
}
.gmp-layout .gmp-source.is-active .gmp-source-hidden { background: rgba(255,255,255,.28); color: #fff; }
/* Ligne d'album : le lien FILTRE la grille, le bouton frère bascule la
   galerie. Deux gestes distincts, donc deux cibles distinctes — un bouton
   imbriqué dans le lien ne serait ni valide ni atteignable au clavier. */
.gmp-layout .gmp-source-row { display: flex; align-items: center; gap: .1rem; }
.gmp-layout .gmp-source-row > .gmp-source { flex: 1 1 auto; min-width: 0; }
.gmp-layout .gmp-source-vis {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; padding: 0; border: 0; border-radius: 6px;
  background: transparent; cursor: pointer;
  /* Pas d'opacité de repos : elle abaisserait le contraste de l'icône sous le
     seuil de 3:1 des composants d'interface. Le gris de la charte tient
     5,85:1, le vert 5,32:1 — l'état se lit à la forme ET à la couleur. */
  color: #6b6459;
}
.gmp-layout .gmp-source-vis.is-on { color: var(--gap-success, #2a7a3c); }
.gmp-layout .gmp-source-vis:hover { background: var(--color-surface-raised, #f3eee4); }
.gmp-layout .gmp-source-vis:focus-visible { outline: 2px solid var(--color-primary, #ce2b1e); outline-offset: 1px; }
.gmp-layout .gmp-source-vis[disabled] { cursor: progress; }

/* ── Page Galerie en mode édition : basculer un album sans la quitter ──────
   Le bouton est posé PAR-DESSUS la carte, hors de son lien. La carte d'un
   album non proposé s'estompe et porte son étiquette : l'éditeur voit d'un
   coup d'œil ce que le visiteur verra, et ce qu'il ne verra pas. */
.gap-album-edit { position: relative; }
.gap-album-vis {
  position: absolute; top: .5rem; right: .5rem; z-index: 3;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; border: 0; border-radius: 50%;
  background: rgba(255,255,255,.92); color: #6b6459; cursor: pointer;
  box-shadow: 0 1px 4px rgba(0,0,0,.22);
}
.gap-album-vis.is-on { color: var(--gap-success, #2a7a3c); }
.gap-album-vis:hover { background: #fff; }
.gap-album-vis:focus-visible { outline: 2px solid var(--color-primary, #ce2b1e); outline-offset: 2px; }
.gap-album-vis[disabled] { cursor: progress; }
.gap-album-hors {
  position: absolute; top: .5rem; left: .5rem; z-index: 3;
  padding: .12rem .45rem; border-radius: 4px;
  background: #1a1a1a; color: #fff;
  font-size: .68rem; text-transform: uppercase; letter-spacing: .04em;
}
.gap-album-edit:has(.gap-album-hors:not([hidden])) .gap-card { opacity: .62; }
.gmp-layout .gmp-new-album {
  margin-top: .6rem; width: 100%; border: 1px dashed var(--color-border, #e2ddd6); background: transparent;
  border-radius: 7px; padding: .4rem .5rem; font-size: .82rem; cursor: pointer; color: var(--t-ink, #1a1a1a);
}
.gmp-layout .gmp-new-album:hover { border-color: var(--color-primary, #ce2b1e); color: var(--color-primary, #ce2b1e); }
/* Porte vers la liste complète des albums (année, disposition, suppression) :
   le rail ne montre que le titre et le nombre de photos. */
.gmp-layout .gmp-rail-link {
  display: block; margin-top: .5rem; padding: .3rem .55rem;
  font-size: .78rem; color: #6f6a62; text-decoration: none;
}
.gmp-layout .gmp-rail-link:hover { color: var(--color-primary, #ce2b1e); }
.gmp-layout .gmp-main { min-width: 0; }

/* Bac de sélection : miniatures ordonnées, glissables (Sortable) pour choisir
   l'ordre d'une sélection propre. Reste visible en haut pendant qu'on parcourt. */
.gap-media-panel .gmp-tray {
  position: sticky; top: 0; z-index: 6;
  background: var(--color-surface-raised, #f3eee4); border: 1px solid var(--color-border, #e2ddd6);
  border-radius: 9px; padding: .5rem .6rem; margin-bottom: .6rem;
}
.gap-media-panel .gmp-tray-head {
  font-size: .7rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: #8a8378; margin-bottom: .4rem;
}
.gap-media-panel .gmp-tray-list { display: flex; flex-wrap: wrap; gap: .4rem; }
.gap-media-panel .gmp-tray-item {
  position: relative; width: 58px; height: 44px; border-radius: 6px; overflow: hidden;
  cursor: grab; box-shadow: 0 1px 4px rgba(0,0,0,.18); background: #e9e4db;
}
.gap-media-panel .gmp-tray-item:active { cursor: grabbing; }
.gap-media-panel .gmp-tray-item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gap-media-panel .gmp-tray-item.sortable-ghost { opacity: .35; }
.gap-media-panel .gmp-tray-rm {
  position: absolute; top: 1px; right: 1px; width: 16px; height: 16px; line-height: 14px;
  padding: 0; border: 0; border-radius: 50%; background: rgba(0,0,0,.6); color: #fff;
  font-size: 12px; cursor: pointer; text-align: center;
}
.gap-media-panel .gmp-tray-rm:hover { background: var(--color-primary, #ce2b1e); }

/* ── Zone d'action sur la sélection ───────────────────────────────────────────
   UNE barre, deux logements, une seule implémentation (_grid.php) :
     · page pleine largeur → collée au bas de la fenêtre ;
     · modale → déménagée dans le pied par gap-media-panel.js.
   Dans les deux cas elle est HORS DU FLUX : elle ne pousse plus la grille sous
   le curseur au moment où l'on coche (défaut relevé le 11/08).
   Pas de `display` ici : la barre porte `.d-none` (!important) tant que rien
   n'est sélectionné, et `flex` prend le relais quand le JS la retire. */
.gmm-bulk-bar {
  position: sticky; bottom: 0; z-index: 7;
  display: flex; flex-wrap: wrap;
  margin-top: .75rem; padding: .5rem .65rem;
  background: var(--color-surface-raised, #f3eee4);
  border: 1px solid var(--color-border, #e2ddd6); border-radius: 9px;
  box-shadow: 0 -4px 14px rgba(0, 0, 0, .07);
}
.gmm-bulk-bar .dropdown-menu { font-size: .86rem; }
/* Un client peut avoir quarante albums : la liste défile dans le menu. */
.gmm-album-list { min-width: 16rem; max-height: 42vh; overflow-y: auto; }
/* Le nombre de photos se range à droite du nom — mais SEULEMENT pour les
   albums : « Nouvel album avec ces photos » est une phrase, pas une ligne de
   liste, et son icône doit rester collée à son texte. */
.gmm-album-list .gmm-album-pick {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
}
.gmm-album-nb { font-size: .72rem; opacity: .6; font-variant-numeric: tabular-nums; }
.gmm-album-new { display: flex; align-items: center; gap: .4rem; }
.gmm-tag-panel { min-width: 20rem; }

/* Barre déménagée dans le pied : elle y perd son cadre et son collant, que le
   pied de modale porte déjà. Le compte, lui, s'efface — le pied a le sien, qui
   porte en plus les avertissements (« cet emplacement n'en reçoit qu'une »). */
.gmp-foot .gmm-bulk-bar {
  position: static; margin: 0; padding: 0;
  background: none; border: 0; box-shadow: none;
}
.gmp-foot .gmm-selection-count { display: none; }
/* Un trait, pour que le pied se lise en deux temps : à gauche ce qui AGIT sur
   la sélection, à droite ce qui CONCLUT. C'est le mélange des deux natures qui
   rendait l'ancien pied confus. */
.gmp-foot .gmm-bulk-bar:not(.d-none) + .gmp-foot-sortie {
  padding-left: .75rem; border-left: 1px solid var(--color-border, #e2ddd6);
}
/* ── Les guides d'utilisation se lisent comme des guides ─────────────────────
   `_grid.php` est rendu dans DEUX surfaces, mais `admin.css` n'est chargé que
   dans l'une. En front, `.adm-form-label` et `.adm-form-hint` n'avaient donc
   AUCUN style : ces phrases sortaient à la taille du texte courant du thème,
   à égalité avec ce qu'elles sont censées expliquer.
   Les valeurs reprennent celles d'admin.css (:390-396), pour que les deux
   surfaces racontent la même chose. `rem` et non `em` : la taille ne doit pas
   dépendre de la fonte de base du thème client. */
.gap-media-manager .adm-form-label {
  font-size: .8rem; font-weight: 600; line-height: 1.3; margin-bottom: .3rem;
}
.gap-media-manager .adm-form-hint {
  font-size: .75rem; line-height: 1.35; color: #6b7280; margin-top: .25rem;
}
/* Même famille, mêmes égards : le mode d'emploi du cadrage et le relevé
   d'usage (« Utilisée dans un album… »). Ils portaient `.small`, qui est en
   `em` — donc gros sous un thème à grande fonte. */
.gap-media-manager .gmm-edit-guide {
  font-size: .75rem; line-height: 1.4;
}
.gap-media-manager .gmm-edit-guide .btn { font-size: inherit; }

/* Le pied garde SA HAUTEUR quand la sélection apparaît. Sans cela, les boutons
   qui arrivent renvoyaient la phrase d'état à la ligne : le pied grandissait,
   la grille rétrécissait — le défaut même qu'on venait de chasser du haut de
   l'écran. C'est l'état qui se laisse rogner, jamais les actions. */
.gmp-foot { flex-wrap: nowrap; }
.gmp-foot .gmm-bulk-bar { flex-wrap: nowrap; }
/* Tous les contrôles du pied à la MÊME hauteur. « Utiliser ces photos » porte
   `btn-sm` comme ses voisins, mais `.btn-gap-primary` lui redonne le rembourrage
   des grands boutons (gap.css:177) : 41 px contre 31. Conséquence mesurée le
   12/08 — le pied passait de 56 à 66 px selon que ce bouton était là ou non,
   c'est-à-dire à chaque fois qu'une sélection apparaissait. Il reste le bouton
   principal par sa COULEUR ; sa taille n'a pas à faire bouger la boîte. */
.gmp-foot .btn-gap-primary { padding: .25rem .75rem; font-size: .875rem; }
.gmp-foot-etat {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gmp-foot-actions { flex-shrink: 0; }
/* Sous cette largeur, plus rien ne tient sur une ligne : on laisse aller.
   ⚠️ Le repli ne suffisait PAS, et le défaut était sévère : sur téléphone, le
   pied débordait à droite et « Supprimer », « Tout décocher », « Utiliser ces
   photos » et « Fermer » devenaient INATTEIGNABLES — on ne pouvait plus ni
   valider ni fermer autrement que par la croix. Constaté par Pierre-Louis le
   12/08, capture à l'appui.
   La cause : `.gmp-foot-actions` est un conteneur flex à moi, en `nowrap` par
   défaut (Bootstrap) et que j'avais mis en `flex-shrink: 0` pour protéger les
   boutons sur grand écran. Il ne rétrécissait donc pas, ne passait pas à la
   ligne, et poussait son contenu hors du cadre. Les deux règles du dessus
   n'atteignaient pas ce niveau-là.
   Sur téléphone, la hauteur du pied n'est plus le sujet : il vaut mieux trois
   lignes lisibles qu'une ligne dont la moitié est hors de l'écran. */
@media (max-width: 720px) {
  .gmp-foot,
  .gmp-foot .gmm-bulk-bar,
  .gmp-foot-actions,
  .gmp-foot-sortie { flex-wrap: wrap; }
  .gmp-foot-actions { flex-shrink: 1; min-width: 0; }
  /* L'état prend sa propre ligne : le rogner sur trois mots ne rend service
     à personne quand il y a la place en dessous. */
  .gmp-foot-etat { white-space: normal; flex: 1 0 100%; }
  /* Le trait qui sépare « agir » de « conclure » ne veut plus rien dire quand
     les deux groupes sont l'un SOUS l'autre. */
  .gmp-foot .gmm-bulk-bar:not(.d-none) + .gmp-foot-sortie {
    padding-left: 0; border-left: 0;
  }
}
/* Les menus s'ouvrent vers le HAUT depuis le pied. `.modal-dialog-scrollable`
   pose `overflow: hidden` sur `.modal-content` (pour arrondir le corps qui
   défile) — il rognerait le menu. Le corps garde son propre `overflow-y`,
   donc il continue de défiler. */
.gap-media-panel .modal-content { overflow: visible; }

@media (max-width: 720px) {
  .gmp-layout { flex-direction: column; }
  .gmp-layout .gmp-rail {
    width: 100%; border-right: 0; border-bottom: 1px solid var(--color-border, #e2ddd6);
    padding-right: 0; padding-bottom: .5rem; max-height: 11rem;
  }
}

/* ═══ Gestionnaire de DOCUMENTS (PDF) — même ossature que les médias ══════════
   Rail partagé (classes gmp-*) + liste tabulaire à la place de la grille : un
   PDF n'a pas de vignette. Rendu par admin/documents/_rail.php + _list.php,
   sur la page pleine largeur ET dans la modale (gap-doc-panel.js). */
.gmp-layout .gmp-source--child { padding-left: 1.6rem; }
/* Document tout juste déposé : on le retrouve d'un coup d'œil pour le choisir. */
.gdp-row--new > td { background: var(--color-surface-raised, #f6efe2); }
/* La liste défile dans son propre cadre — jamais la modale entière en travers. */
.gdp-table-wrap { overflow-x: auto; }
.gdp-table { min-width: 560px; }
.gdp-folder-head { flex-wrap: wrap; }

/* ═══ Carte OSM + formulaire de contact (sections socle) ═══════════════════════
   Structure/layout ici (socle) ; couleurs et charte dans le thème. */
.gap-map-embed {
  position: relative; width: 100%; aspect-ratio: 4 / 3;
  border-radius: 12px; overflow: hidden; background: #e9e4db;
}
.gap-map-iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; }

/* ── Plan rendu en mosaïque de tuiles (App\Core\TuilesCarte) ──────────────
 *
 * Remplace l'iframe OpenStreetMap depuis le 06/09/2026. Pour un visiteur ce
 * ne sont que des <img> : aucun JavaScript, et le plan s'affiche même sans.
 *
 * La mosaïque est rendue à 900×675 et CENTRÉE dans un cadre fluide qui la
 * recadre. gap-carte.js tient compte de ce décalage pour convertir un clic —
 * changer les dimensions ici sans les changer dans _plan.php décalerait le
 * repère de la moitié de la différence. */
.gap-plan { position: relative; overflow: hidden; background: #e9e4db; touch-action: none; }
.gap-plan-tuiles {
  position: absolute; left: 50%; top: 50%;
  width: 900px; height: 675px; margin: -337.5px 0 0 -450px;
}
.gap-plan-tuiles img { position: absolute; display: block; max-width: none; }

/* Le repère marque le CENTRE de la mosaïque, donc le point demandé. Sa pointe
   est en bas de l'icône : on le remonte d'une hauteur entière. */
.gap-plan-repere {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -100%);
  color: var(--color-primary, #ce2b1e);
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .35));
  pointer-events: none; line-height: 0;
}

.gap-plan-credit {
  position: absolute; right: 0; bottom: 0; margin: 0;
  padding: .15rem .5rem; font-size: .68rem; color: #4a463f;
  background: rgba(255, 255, 255, .78); border-top-left-radius: 6px;
}

/* ── Teinte du thème ──────────────────────────────────────────────────────
 *
 * `mix-blend-mode: color` prend la TEINTE et la SATURATION du voile et garde
 * la LUMINANCE de ce qu'il recouvre : la carte change de couleur sans perdre
 * un seul détail. C'est ce qui permet d'employer le fond le plus détaillé
 * sans qu'il soit bariolé — et c'est la raison pour laquelle le fond par
 * défaut est passé de la toile de fond grise au style détaillé (TuilesCarte).
 *
 * 28 % : mesuré sur tuiles réelles avant d'être écrit. Au-delà, la teinte
 * cesse d'accompagner la carte pour la remplacer.
 *
 * Le voile ne couvre QUE les tuiles : il est sous le repère et sous les
 * outils, qui doivent garder leurs couleurs propres. */
.gap-plan-teinte {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: var(--color-primary, #ce2b1e);
  mix-blend-mode: color;
  opacity: .28;
}
/* Repère, outils et mentions passent AU-DESSUS du voile. */
.gap-plan-repere, .gap-plan-outils, .gap-plan-etat, .gap-plan-credit { z-index: 2; }

/* ── Outils d'édition du plan ─────────────────────────────────────────── */
.gap-plan-outils {
  position: absolute; top: .6rem; left: .6rem; right: .6rem;
  display: flex; align-items: flex-start; justify-content: space-between; gap: .5rem;
}
/* Le bouton de MODE : c'est lui qui arme le clic. Il doit se voir — un mode
   invisible est un mode qu'on déclenche sans le savoir. */
.gap-plan-poser {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .45rem .8rem; border-radius: 8px; cursor: pointer;
  border: 1px solid rgba(0, 0, 0, .15); background: #fff; color: #1a1a1a;
  font-size: .84rem; font-weight: 600; box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
}
.gap-plan-poser:hover { border-color: var(--color-primary, #ce2b1e); }
.gap-plan-poser[aria-pressed="true"] {
  background: var(--color-primary, #ce2b1e); color: #fff; border-color: transparent;
}
.gap-plan-boutons { display: flex; flex-direction: column; gap: .3rem; }
.gap-plan-btn {
  width: 2.2rem; height: 2.2rem; border: 1px solid rgba(0, 0, 0, .15); border-radius: 8px;
  background: #fff; color: #1a1a1a; font-size: 1.15rem; line-height: 1; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .18);
}
.gap-plan-btn:hover { background: var(--color-primary, #ce2b1e); color: #fff; border-color: transparent; }

/* Bandeau d'état : position courante et retour d'enregistrement, en
   permanence — pas un message qui s'efface. */
.gap-plan-etat {
  position: absolute; left: 0; right: 0; bottom: 0; margin: 0;
  padding: .35rem .7rem; font-size: .76rem;
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  color: #fff; background: rgba(26, 26, 26, .74);
}
.gap-plan-etat-pos { font-variant-numeric: tabular-nums; opacity: .8; white-space: nowrap; }
.gap-plan-etat.is-ok   { background: rgba(31, 96, 48, .88); }
.gap-plan-etat.is-fail { background: rgba(181, 39, 27, .9); }

/* Le curseur DIT le mode : croix quand le clic pose, main quand il déplace. */
.gap-plan[data-item-id] .gap-plan-tuiles { cursor: grab; }
.gap-plan.is-pose .gap-plan-tuiles { cursor: crosshair; }
.gap-plan.is-glisse .gap-plan-tuiles { cursor: grabbing; }
.gap-plan.is-pose { outline: 2px solid var(--color-primary, #ce2b1e); outline-offset: -2px; }
.gap-plan.is-busy { opacity: .75; }
/* Le crédit se décale au-dessus du bandeau, pour ne pas le recouvrir. */
.gap-plan-etat ~ .gap-plan-credit { bottom: 1.7rem; }
.gap-map-info { display: flex; flex-direction: column; gap: 1.25rem; }
/* Emplacement de l'adresse (comme media_position de la timeline) : la colonne
   infos passe avant le plan quand data-map-pos="left". .row est un flex. */
.gap-map[data-map-pos="left"] .gap-map-info { order: -1; }
.gap-map-pos { display: flex; align-items: center; flex-wrap: wrap; gap: .35rem; margin-top: .2rem; }
.gap-map-pos-lab {
  font-size: .68rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: #8a8378;
}
.gap-map-pos-btn {
  border: 1px solid var(--color-border, #d9d3c8); background: #fff; color: #6b655c;
  border-radius: 7px; padding: .2rem .55rem; font-size: .78rem; cursor: pointer; line-height: 1.4;
}
.gap-map-pos-btn:hover { border-color: var(--color-primary, #ce2b1e); color: var(--color-primary, #ce2b1e); }
.gap-map-pos-btn.is-active {
  border-color: var(--color-primary, #ce2b1e); background: var(--color-primary, #ce2b1e); color: #fff;
}
.gap-map-edit {
  border: 1px dashed var(--color-border, #d9d3c8); border-radius: 10px; padding: .75rem .9rem;
  display: flex; flex-direction: column; gap: .4rem;
}
.gap-map-edit-lab {
  font-size: .68rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: #8a8378;
}
.gap-map-address { margin: 0; font-size: 1rem; min-height: 1.4em; }
.gap-map-address[data-empty]:empty::before {
  content: attr(data-placeholder); color: #a8a196; font-style: italic;
}
.gap-map-recenter,
.gap-lieu-poser {
  align-self: flex-start; border: 1px solid var(--color-primary, #ce2b1e); background: transparent;
  color: var(--color-primary, #ce2b1e); border-radius: 8px; padding: .35rem .7rem; font-size: .85rem; cursor: pointer;
  /* inline-flex : le bouton porte une icône SVG depuis qu'il ne porte plus
     d'émoji — sans ça le picto et le texte ne s'alignent pas. */
  display: inline-flex; align-items: center; gap: .4rem;
}
.gap-map-recenter:hover:not(:disabled),
.gap-lieu-poser:hover { background: var(--color-primary, #ce2b1e); color: #fff; }
.gap-map-recenter:disabled { opacity: .6; cursor: default; }

/* ── Section « Lieu » (gabarit place.php) ─────────────────────────────────
   Elle emprunte tout l'habillage de la carte : seul le lien d'itinéraire lui
   est propre. Voir la migration 20260905-2345 pour ce qui la distingue du
   type « Plan / carte ». */
.gap-place-itineraire {
  display: inline-flex; align-items: center; gap: .4rem;
  margin-top: .9rem; font-size: .88rem; font-weight: 600;
  color: var(--color-secondary, #2a7a3c); text-decoration: none;
}
.gap-place-itineraire:hover,
.gap-place-itineraire:focus-visible { text-decoration: underline; }
/* L'adresse du lieu est saisie sur plusieurs lignes (textarea) et rendue
   avec ses retours : sans cela, line-height:2 de la liste les écarterait
   comme s'il s'agissait de coordonnées distinctes. */
.gap-place .gap-map-coord--addr { line-height: 1.5; margin-bottom: .5rem; }
.gap-map-hint { font-size: .78rem; color: #8a8378; }
.gap-map-coords-title { font-size: 1.05rem; font-weight: 700; margin: 0 0 .6rem; }
.gap-map-coords-list { font-style: normal; line-height: 2; margin: 0; }
.gap-map-coord { display: block; }
.gap-map-coord[data-empty]:empty::before {
  content: attr(data-placeholder); color: #a8a196; font-style: italic;
}

/* ── Plan à plusieurs lieux (15/09/2026) ─────────────────────────────────
   Un élément de section par lieu (map.php). Les blocs gardent les classes de
   toujours (.gap-map-coords, .gap-map-coords-title…), que les thèmes habillent
   déjà : un thème n'a rien à apprendre pour qu'un deuxième lieu lui ressemble. */
.gap-lieux { display: flex; flex-direction: column; gap: 1.25rem; }
.gap-lieu { position: relative; }

/* Le NUMÉRO relie un repère à son bloc — au doigt, à l'impression, au lecteur
   d'écran (« Repère 2 »). Même pastille des deux côtés, pour qu'on les apparie
   sans légende. */
.gap-lieu-num,
.gap-plan-repere--numero {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.7em; height: 1.7em; padding: 0 .35em; border-radius: 999px;
  background: var(--color-primary, #ce2b1e); color: #fff;
  font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums;
}
/* Marge courte : une espace suit déjà la pastille dans le texte (map.php). */
.gap-lieu-num { margin-right: .25rem; font-size: .8em; vertical-align: .1em; }

/* Sur le plan : pastille cerclée de blanc, POINTE en bas sur le lieu. Sa
   position (left/top) est posée par _plan.php — « 50 % + décalage ». */
.gap-plan-repere--numero {
  font-size: .9rem; border: 2px solid #fff; filter: none;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .35);
  transform: translate(-50%, calc(-100% - 8px));
}
.gap-plan-repere--numero::after {
  content: ""; position: absolute; left: 50%; top: 100%; transform: translateX(-50%);
  margin-top: 2px; border: 6px solid transparent; border-top-color: var(--color-primary, #ce2b1e);
}

/* Repère seul : son nom en LÉGENDE, sans cadre de bloc. Visée par sa propre
   classe, pas par le modificateur d'usage : `.gap-lieu--repere` porte aussi
   le BLOC D'ÉDITION de ce lieu, qu'une mise en flex rangeait en une rangée
   centrée — outils, menu d'usage et titre côte à côte (signalé le 15/09/2026). */
.gap-lieu-legende { display: flex; align-items: center; margin: 0; }
.gap-lieu-note { margin: .5rem 0 0; font-size: .9rem; color: #6b655c; }
.gap-lieu-note[data-empty]:empty::before,
.gap-lieu-titre[data-empty]:empty::before {
  content: attr(data-placeholder); color: #a8a196; font-style: italic; font-weight: 400;
}

/* Édition : l'usage du lieu, l'adresse de son repère, ses boutons. */
.gap-lieu-usage {
  display: flex; align-items: center; gap: .5rem; margin: 0 0 .6rem;
  font-size: .72rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: #8a8378;
}
.gap-lieu-usage select {
  font-size: .82rem; font-weight: 400; letter-spacing: 0; text-transform: none; color: #1a1a1a;
  padding: .15rem .4rem; border: 1px solid var(--color-border, #d9d3c8); border-radius: 6px; background: #fff;
}
.gap-lieu-outils {
  margin-top: .75rem; padding-top: .6rem; border-top: 1px dashed var(--color-border, #d9d3c8);
  display: flex; flex-direction: column; gap: .4rem;
}
.gap-lieu-boutons { display: flex; flex-wrap: wrap; gap: .4rem; }
.gap-lieu-option {
  display: flex; align-items: center; gap: .4rem; margin: 0 0 .6rem;
  font-size: .82rem; color: #4a463f; cursor: pointer;
}

/* L'adresse PROPRE d'un lieu peut tenir sur plusieurs lignes (héritage du type
   « Lieu », fusionné le 15/09/2026) : sans cela, le line-height:2 de la liste
   les écarterait comme des coordonnées distinctes. Les blocs « coordonnées du
   site » n'en ont pas besoin, et gardent leur espacement d'avant. */
.gap-lieu--texte_repere .gap-map-coord--addr,
.gap-lieu--texte .gap-map-coord--addr { line-height: 1.5; margin-bottom: .5rem; }
/* Icône (emoji) en tête de carte — type « cartes-texte » (socle). */
.gap-card-icon { font-size: 2.1rem; line-height: 1; margin-bottom: .5rem; }

.gap-contact-form { max-width: 720px; margin-inline: auto; }
.gap-contact-form .form-label { margin-bottom: .3rem; }

/* ═══ Messages flash — affichage public (socle) ════════════════════════════════
   Retour visiteur après un POST redirigé (contact…). Charte peinte par le thème. */
.gap-flash-wrap { margin: 1.5rem 0 0; }
.gap-flash { border-radius: 12px; padding: .85rem 1.1rem; margin-bottom: .75rem; }
.gap-flash:last-child { margin-bottom: 0; }
.gap-flash-icon { font-size: 1.1rem; line-height: 1; }
.gap-flash-msg { font-size: .98rem; }

/* Fonds photo (image OU carrousel) : texte clair par défaut pour la lisibilité
   par-dessus l'image + voile (comme les fonds sombre/coloré). L'étiquette, la
   tagline et .text-muted ont déjà leur variante claire plus haut. */
.page-section.has-bg-image { color: #fff; }
.page-section.has-bg-image h1,
.page-section.has-bg-image h2,
.page-section.has-bg-image h3,
.page-section.has-bg-image h4,
.page-section.has-bg-image h5,
.page-section.has-bg-image h6 { color: #fff; }

/* ── Bandeau RGPD ───────────────────────────────────────────── */
.gap-cookie-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: var(--z-bandeau);
  background: #1A1A1A;
  color: rgba(255,255,255,.85);
  padding: .9rem 0;
  box-shadow: 0 -2px 12px rgba(0,0,0,.3);
  border-top: 2px solid var(--color-primary);
}
.gap-cookie-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.gap-cookie-text { font-size: .85rem; }
.gap-cookie-link { color: var(--color-accent); margin-left: .5rem; }
.gap-cookie-actions { display: flex; gap: .5rem; flex-shrink: 0; }

/* ── Carte contact ───────────────────────────────────────────── */
.contact-map {
  display: block;
  width: 100%;
  min-height: 300px;
  border: 0;
}
.map-consent-wrap {
  position: relative;
  /* --gap-bg-alt n'a JAMAIS été déclarée : ce fond était transparent depuis
     l'origine, silencieusement. On pose la surface du socle, avec l'ancien nom
     en entrée pour qui voudrait le définir un jour. */
  background: var(--gap-bg-alt, var(--color-surface));
  min-height: 300px;
}
.map-consent-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(26,26,26,.08);
  backdrop-filter: blur(2px);
}
.contact-info { font-size: .9rem; color: #374151; }
.contact-info a { color: #374151; text-decoration: none; }
.contact-info a:hover { color: var(--color-primary); }

/* ── Cards ─────────────────────────────────────────────────── */
/* Primitives de surface du SOCLE : tout nouveau composant « carte » doit
   composer avec .gap-surface (+ .gap-surface--lift pour le survol) plutôt que
   redéclarer fond/bordure/rayon/transition. Déclarées AVANT .gap-card pour que
   les surcharges de thème (t-*) continuent de gagner la cascade.
   .gap-card COMPOSE désormais avec elles (F2, 24/08/2026) : ses cinq
   déclarations et son survol étaient la copie mot pour mot de la primitive —
   le socle contredisait sa propre convention depuis le 09/08. L'amplitude du
   survol vit dans --lift : un thème surcharge le jeton, plus le geste. */
.gap-surface,
.gap-card {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  overflow: hidden;
  background: #fff;
  transition: transform var(--transition), box-shadow var(--transition);
}
.gap-surface--lift:hover,
.gap-card:hover {
  transform: translateY(var(--lift));
  box-shadow: var(--shadow-md);
}
.gap-card {
  height: 100%;
  /* Ancre de `.stretched-link` : sans `position`, la zone de clic étendue
     s'accrocherait au premier ancêtre positionné — la grille, voire la
     section — et la carte voisine deviendrait cliquable elle aussi. */
  position: relative;
}
/* Toute la carte mène quelque part : les AUTRES liens repassent au-dessus de
   la zone étendue, sinon `.stretched-link` les recouvrirait et ils
   deviendraient inertes — un lien dans une description, l'aperçu d'un album.
   `z-index: 2` et non 1 : la zone étendue de Bootstrap est elle-même à 1
   (vérifié dans bootstrap.min.css), et le bouton qui la porte est le DERNIER
   élément de la carte — à égalité, l'ordre du document lui donnerait raison
   contre un lien situé plus haut. */
.gap-card a:not(.stretched-link),
.gap-card button { position: relative; z-index: 2; }
.gap-card .gap-card-img {
  width: 100%;
  aspect-ratio: 4/3;
  object-fit: cover;
}
.gap-card .gap-card-body { padding: 1.25rem; }
/* ── Élément MIS EN AVANT (15/09/2026) ────────────────────────────────────
   Réglage de l'élément (« Mettre la carte en avant », « Mettre la ligne en
   avant »), porté par l'attribut `data-item-avant` sur la cellule d'une carte
   ou la rangée d'une liste. Rendu par défaut : celui de la formule mise en
   avant d'un comparatif (.gap-formule--avant) — bord à la couleur principale,
   ombre légère. Un thème le redessine en visant le même attribut.
   `.col` dans le sélecteur des cartes (0,3,0) : un thème qui retire le bord de
   ses cartes (`.t-reperes .gap-card`, 0,2,0) ne doit pas effacer du même coup
   la mise en avant qu'un éditeur vient de régler. */
.col[data-item-avant] > .gap-card {
  border: 2px solid var(--color-primary);
  box-shadow: 0 10px 28px rgba(15, 23, 42, .08);
}
.list-sections > .row[data-item-avant] {
  padding-block: 1.5rem;
  border: 2px solid var(--color-primary);
  border-radius: var(--radius);
  box-shadow: 0 10px 28px rgba(15, 23, 42, .08);
}
/* Image du template Featured (éditable en FO via $editablePhoto) */
.gap-featured-img {
  width: 100%;
  aspect-ratio: 4/3;
  object-fit: cover;
  border-radius: .5rem;
  display: block;
}
/* Image du template Liste (éditable en FO via $editablePhoto) */
.gap-list-img {
  width: 100%;
  aspect-ratio: 4/3;
  object-fit: cover;
  display: block;
}
.gap-card .gap-card-title {
  font-family: var(--font-display);
  font-size: 1.1rem;
  font-weight: 700;
  margin-bottom: .5rem;
}
.gap-card .gap-card-text {
  font-size: .875rem;
  color: var(--color-muted);
  line-height: 1.6;
}

/* ── Cadres photo ──────────────────────────────────────────── */
.frame-rounded    { border-radius: var(--radius); overflow: hidden; }
.frame-rounded-lg { border-radius: 24px; overflow: hidden; }
.frame-circle     { border-radius: 50%; overflow: hidden; }
.frame-shadow     { box-shadow: var(--shadow-md); }
.frame-shadow-soft { box-shadow: 0 10px 30px rgba(20, 18, 16, .12); }
.frame-shadow-colored { box-shadow: 0 12px 32px rgba(206,43,30,.25); }
.frame-polaroid {
  background: #fff; padding: .55rem .55rem 2.2rem;
  box-shadow: var(--shadow-md); border-radius: 2px;
}
.frame-border-primary   { border: 4px solid var(--color-primary); }
.frame-border-secondary { border: 4px solid var(--color-secondary); }
.frame-border-thin      { border: 1px solid var(--color-border); border-radius: var(--radius); overflow: hidden; }
.frame-clip-corner {
  clip-path: polygon(0 0, calc(100% - 32px) 0, 100% 32px, 100% 100%, 0 100%);
}
.frame-tilt-left  { transform: rotate(-2deg); }
.frame-tilt-right { transform: rotate(2deg); }
/* Inclinaisons « aléatoires » : 5 rotations cyclées par position de carte
   (cf SectionRenderer::frameClasses). Léger lift au survol pour la vie. */
.frame-tilt-rand-1 { transform: rotate(-3deg); }
.frame-tilt-rand-2 { transform: rotate(2.5deg); }
.frame-tilt-rand-3 { transform: rotate(-1.5deg); }
.frame-tilt-rand-4 { transform: rotate(3deg); }
.frame-tilt-rand-5 { transform: rotate(-2.2deg); }
[class*="frame-tilt-"] { transition: transform .25s ease; }
[class*="frame-tilt-"]:hover { transform: rotate(0deg) scale(1.01); }
/* Aperçu dans les formulaires pour les modes multi-cartes (rendu représentatif) */
.frame-preview.frame-tilt-alternate { transform: rotate(-2deg); }
.frame-preview.frame-tilt-random    { transform: rotate(2.5deg); }
.frame-clip-diagonal {
  clip-path: polygon(0 0, 100% 0, 100% 85%, 0 100%);
}

/* Cadres organiques (border-radius asymétrique + clip-path).
   arch/diamond/circle sont RETIRÉS du sélecteur mais leur CSS reste : les
   sections qui les portent encore gardent leur rendu (option « (ancien) »). */
.frame-organic { position: relative; }
.frame-organic img { width: 100%; height: 100%; object-fit: cover; }
.frame-organic-blob   { overflow: hidden; border-radius: 24% 76% 67% 33% / 30% 26% 74% 70%; }
.frame-organic-wave   { overflow: hidden; border-radius: 78% 22% 32% 68% / 70% 26% 74% 30%; }
.frame-organic-splat  { overflow: hidden; border-radius: 84% 16% 56% 44% / 52% 78% 22% 48%; }
.frame-organic-pebble { overflow: hidden; border-radius: 58% 42% 55% 45% / 48% 58% 42% 52%; }
.frame-organic-leaf   { overflow: hidden; border-radius: 8px 72% 8px 72%; }
.frame-organic-petal  { overflow: hidden; border-radius: 52% 52% 52% 10px / 55% 55% 48% 10px; }
.frame-organic-flow   { overflow: hidden; border-radius: 32% 68% 46% 54% / 58% 32% 68% 42%; }
/* Héritage (plus proposés au choix) */
.frame-organic-arch   { overflow: hidden; border-radius: 50% 50% 8px 8px; }
.frame-organic-diamond { clip-path: polygon(50% 0, 100% 50%, 50% 100%, 0 50%); }
.frame-organic-circle { overflow: hidden; border-radius: 47% 53% 51% 49% / 52% 48% 52% 48%; }
/* Aperçu du mode « organic aléatoire » (modale d'options) : forme blob représentative.
   Au rendu réel, frameClasses() attribue une forme organique différente par carte. */
.frame-organic-random { overflow: hidden; border-radius: 24% 76% 67% 33% / 30% 26% 74% 70%; }

/* ── Réglages fins du cadre (surcouche au préréglage) ─────────
   Les variables --frame-* sont posées sur la <section> (sectionStyles) et
   héritées ; ces marqueurs les consomment. Déclarés APRÈS les préréglages
   frame-* : à spécificité égale, le réglage fin gagne la cascade. */
.frame-x-radius { border-radius: var(--frame-radius); overflow: hidden; }
.frame-x-shadow { box-shadow: var(--frame-shadow); }
.frame-x-border { border: var(--frame-border); }
.frame-x-tilt   { transform: rotate(var(--frame-tilt)); transition: transform .25s ease; }
.frame-x-tilt:hover { transform: rotate(0deg) scale(1.01); }
.frame-x-pad    { padding: var(--frame-pad); background: #fff; }
/* Effets au survol (3 comportements distincts → classes dédiées) */
.frame-hover-lift { transition: transform .25s ease, box-shadow .25s ease; }
.frame-hover-lift:hover { transform: translateY(var(--lift)); box-shadow: var(--shadow-md); }
.frame-hover-zoom { overflow: hidden; }
.frame-hover-zoom img { transition: transform .35s ease; }
.frame-hover-zoom:hover img { transform: scale(1.06); }
.frame-hover-grayscale img { filter: grayscale(1); transition: filter .3s ease; }
.frame-hover-grayscale:hover img { filter: none; }
/* La zone de survol s'élargit à la CARTE qui porte le cadre : la photo se
   colorise (ou zoome) quand le curseur entre dans la carte, pas seulement sur
   l'image. Dans une grille serrée — ou avec un cadre organique, dont le
   clip-path rétrécit encore la cible — survoler l'image seule est un geste de
   précision inutile : c'est la carte entière qui est l'objet.
   Volontairement PAS appliqué à frame-hover-lift : ce cadre-là se détache
   géométriquement, et le voir sauter depuis le texte de la carte casserait la
   lecture (la carte, elle, a déjà son propre relief au survol). */
.gap-card:hover    .frame-hover-grayscale img,
.gap-person:hover  .frame-hover-grayscale img,
.gap-quote:hover   .frame-hover-grayscale img,
.gap-formule:hover .frame-hover-grayscale img { filter: none; }
.gap-card:hover    .frame-hover-zoom img,
.gap-person:hover  .frame-hover-zoom img,
.gap-quote:hover   .frame-hover-zoom img,
.gap-formule:hover .frame-hover-zoom img { transform: scale(1.06); }
/* Contrôles « Réglages fins » du panneau d'options (pastilles + swatches) */
.gap-frame-pills { display: flex; flex-wrap: wrap; gap: .3rem; }
.gap-frame-pills > button {
  padding: .22rem .55rem; border: 1px solid var(--color-border, #e2e2e2);
  border-radius: 999px; background: #fff; cursor: pointer;
  font-size: .75rem; font-weight: 600; color: #4b5563;
}
.gap-frame-pills > button:hover { border-color: var(--color-primary, #CE2B1E); }
.gap-frame-pills > button.is-active {
  border-color: var(--color-primary, #CE2B1E); color: var(--color-primary, #CE2B1E);
  background: rgba(206,43,30,.06);
}
.gap-frame-swatches { display: flex; gap: .35rem; margin-top: .4rem; }
.gap-frame-swatches > button {
  width: 24px; height: 24px; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--color-border, #d5d5d5); padding: 0;
}
.gap-frame-swatches > button.is-active {
  outline: 2px solid var(--color-primary, #CE2B1E); outline-offset: 1px;
}

/* ── Sommaire de page ──────────────────────────────────────────────────────
   COLLANT sous la navigation : il suit le défilement au lieu de rester en haut
   de page, où il ne servait qu'une fois. `sticky` et non `fixed` : l'élément
   reste dans le flux, donc il ne recouvre jamais le contenu — c'était le défaut
   des anciennes pastilles.

   COMPACT : une seule ligne, pas de numéros sur deux hauteurs. Le sommaire
   occupe forcément une place dans le flux (c'est le prix d'un élément qui ne
   recouvre rien) ; cette place doit donc être la plus courte possible. La
   version à tuiles décalait le contenu de près de cent pixels, celle-ci d'une
   quarantaine. */
.gap-sommaire {
  /* Enfant du flux flexible des sections : pleine largeur obligatoire, sinon il
     se rangerait en colonne à côté d'une section partielle. */
  flex: 0 0 100%; max-width: 100%; width: 100%;
  position: sticky;
  /* Sous la barre de navigation, elle-même collante. */
  top: var(--nav-height, 72px);
  z-index: 900;
  background: rgba(255, 255, 255, .96);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--color-border, #e2ded8);
  margin-bottom: clamp(1rem, 2.5vw, 1.75rem);
}
/* En mode édition, la barre admin décale toute la page : le sommaire suit,
   sinon il se colle sous une navigation qui n'est plus au même endroit. */
.admin-edit-mode .gap-sommaire,
body:has(.gap-navbar.admin-offset) .gap-sommaire {
  top: calc(var(--nav-height, 72px) + var(--adminbar-h, 0px));
}
.gap-sommaire-liste {
  list-style: none; margin: 0 auto; padding: 0;
  max-width: 1140px;
  display: flex; flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;              /* la barre de défilement gâcherait un filet de 40 px */
}
.gap-sommaire-liste::-webkit-scrollbar { display: none; }
.gap-sommaire-item { flex: 0 0 auto; }
.gap-sommaire-item > a {
  display: flex; align-items: baseline; gap: .4rem;
  padding: .55rem .85rem;
  text-decoration: none; color: inherit;
  font-size: .85rem; line-height: 1.2;
  white-space: nowrap;
  border-bottom: 2px solid transparent;
  transition: color .18s ease, border-color .18s ease;
}
.gap-sommaire-item > a:hover,
.gap-sommaire-item > a:focus-visible { color: var(--color-primary, #CE2B1E); }
/* Section en cours de lecture : un filet sous l'entrée — il tient dans la
   hauteur existante, là où un fond plein alourdirait la barre. */
.gap-sommaire-item > a.is-active {
  color: var(--color-primary, #CE2B1E);
  border-bottom-color: var(--color-primary, #CE2B1E);
}
.gap-sommaire-no {
  font-weight: 700; font-size: .72rem;
  color: var(--color-primary, #CE2B1E); opacity: .55;
  font-variant-numeric: tabular-nums;
}
.gap-sommaire-item > a.is-active .gap-sommaire-no { opacity: 1; }
.gap-sommaire-nom { font-weight: 600; }

/* Les ancres visées doivent s'arrêter SOUS la navigation et le sommaire, sinon
   le clic amène la section derrière deux barres collantes. */
.page-section { scroll-margin-top: calc(var(--nav-height, 72px) + 3.2rem); }

@media (prefers-reduced-motion: reduce) {
  .gap-sommaire-item > a { transition: none; }
}

/* ── Sommaire des ÉLÉMENTS d'une section (15/09/2026, retour n° 11 du GAP) ──
   En tête d'une liste ou d'une grille de cartes : un lien par élément, son
   numéro, son titre et son étiquette (SectionRenderer::sommaireElements).
   Rien à voir avec le sommaire de PAGE ci-dessus (.gap-sommaire, collant,
   piloté par gap.js) : autres classes. Un thème le redessine à sa façon ; le
   nombre de liens lui est donné par --gap-index-n. */
.gap-index {
  display: flex; flex-wrap: wrap; gap: .5rem .6rem;
  margin: 0 0 clamp(1.5rem, 4vw, 2.5rem);
}
.gap-index-lien {
  display: inline-flex; align-items: baseline; gap: .45rem;
  padding: .45rem .85rem; border: 1px solid var(--color-border, #e3ded5); border-radius: 999px;
  color: inherit; text-decoration: none; line-height: 1.2;
  transition: border-color .2s, background-color .2s;
}
.gap-index-lien:hover,
.gap-index-lien:focus-visible { border-color: var(--color-primary, #CE2B1E); }
.gap-index-no { font-weight: 800; font-size: .85em; color: var(--color-primary, #CE2B1E); font-variant-numeric: tabular-nums; }
.gap-index-titre { font-weight: 600; }
.gap-index-etiquette { font-size: .78em; opacity: .7; }
/* L'élément visé s'arrête sous la navigation, comme une section. :where() : un
   thème qui règle son propre décalage l'emporte sans surenchère. */
.page-section :where([id^="sec"][id*="-i"]) { scroll-margin-top: calc(var(--nav-height, 72px) + 3.2rem); }

@media (prefers-reduced-motion: reduce) {
  .gap-index-lien { transition: none; }
}

/* ── Éditeur de texte enrichi : barre flottante unifiée ──────
   Chrome GRAPHITE, le même langage que la barre de marques de titre
   (.gap-marques-barre) : les deux font le même métier — du formatage
   flottant en édition FO — et parlaient deux langues (blanc bordé contre
   graphite ; 9999 en dur contre jeton). Harmonisées le 25/08/2026 à la
   demande de Pierre-Louis (réserve du 25/08). Le COMPORTEMENT ne bouge
   pas : apparition au focus de la zone, position au-dessus de la zone —
   les commandes de bloc (titre, listes) s'appliquent sans sélection, une
   barre à la sélection seule les rendrait inaccessibles. */
.gap-editor-toolbar {
  display: none;
  position: fixed;
  z-index: var(--z-toast);
  background: #1e2329;
  border-radius: 7px;
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
  padding: 3px;
  gap: 2px;
  align-items: center;
}
.gap-editor-toolbar.visible { display: inline-flex; }
.gap-editor-toolbar button {
  background: transparent;
  border: none;
  color: #d5d9de;
  min-width: 30px; height: 30px;
  padding: 0 .4rem;
  border-radius: 5px;
  font-size: .9rem;
  line-height: 1;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background var(--dur-1) var(--ease-standard), color var(--dur-1) var(--ease-standard);
}
.gap-editor-toolbar button:hover { background: rgba(255,255,255,.12); color: #fff; }
.gap-editor-sep { width: 1px; height: 18px; background: rgba(255,255,255,.18); margin: 0 .15rem; }

/* Zones éditables (mode admin) — grammaire « voile discret » (chantier B,
   août 2026, remplace l'anneau permanent qui donnait un air de formulaire).
   Trois états, lisibles sur les 4 fonds (clair, vert, sombre, photo) car
   dérivés de currentColor — le thème bascule déjà la couleur du texte par
   fond, la grammaire suit toute seule :
     · REMPLIE : aucun cadre au repos — un ✎ discret et PERMANENT marque
       l'éditable (le survol renforce mais n'est jamais le seul signal).
     · VIDE (invite) : voile teinté + flou, placeholder dans la typographie
       finale. Ni panneau blanc, ni bordure.
     · EN ÉDITION : l'anneau bicolore (ex-signal permanent) devient le signal
       de focus — il remplace l'outline vert, invisible sur fond vert. */
[data-editable] {
  cursor: text; border-radius: .25rem;
  transition: box-shadow var(--transition), background var(--transition);
}
/* Zone remplie : ✎ permanent, discret. Cédé pendant l'édition (badge de
   langue), le flash « ✓ Enregistré » (.saved) et sur les champs marqués
   vides côté serveur (data-empty). */
[data-editable]:not(:empty):not(:focus):not(.saved):not([data-empty="1"])::after {
  content: "✎"; font-size: .55em; opacity: .35; margin-left: .35em;
  vertical-align: super; font-style: normal;
}
/* Libellé d'un bouton (CTA) : la pastille d'édition dédiée suffit, pas de ✎
   DANS le bouton. */
.gap-cta-edit [data-editable]::after { content: none; }
[data-editable]:hover {
  box-shadow: 0 0 0 1px rgba(0,0,0,.28), 0 0 0 2.5px rgba(255,255,255,.55);
}
[data-editable]:focus {
  outline: none;
  box-shadow: 0 0 0 1px rgba(0,0,0,.45), 0 0 0 3px rgba(255,255,255,.75);
  background: color-mix(in srgb, currentColor 13%, transparent);
}
/* Zone « à traduire » (19/09/2026) : dans une page d'une autre langue, elle
   montre l'ORIGINAL français, qui n'est pas encore traduit. Estompé et
   étiqueté, pour qu'on ne le prenne pas pour une traduction ; net dès qu'on
   y entre pour le traduire. */
[data-editable][data-a-traduire]:not(:focus) {
  opacity: .6;
  font-style: italic;
  box-shadow: inset 0 -1px 0 color-mix(in srgb, currentColor 45%, transparent);
}
/* Même chaîne que la règle du ✎ ci-dessus, plus [data-a-traduire] : sans
   elle, la règle du crayon (quatre :not) était plus spécifique et gagnait —
   constaté sur capture le 19/09/2026, l'étiquette ne s'affichait pas. */
[data-editable][data-a-traduire]:not(:empty):not(:focus):not(.saved):not([data-empty="1"])::after {
  /* Taille FIXE (rem), comme le badge de langue : en `em`, l'étiquette
     grossissait avec la zone — énorme sur un chiffre clé, jusqu'à renvoyer
     la valeur à la ligne (capture du 19/09/2026). */
  content: "FR · à traduire"; font: 700 .6rem/1 var(--font-body); opacity: 1;
  margin-left: .5em; vertical-align: super; font-style: normal;
  letter-spacing: .03em; white-space: nowrap;
}
[data-editable].saving  { opacity: .6; pointer-events: none; }
[data-editable].saved   { outline: 2px solid var(--color-secondary); animation: saved-pulse .6s ease; }
/* Badge « langue éditée » sur le champ actif : rappelle dans quelle langue la
   saisie est enregistrée (data-lang). Seuls les champs i18n portent data-lang. */
[data-editable][data-lang]:focus { position: relative; }
[data-editable][data-lang]:focus::after {
  content: attr(data-lang);
  position: absolute;
  top: -1.5em;
  right: 0;
  z-index: 5;
  text-transform: uppercase;
  font: 700 .6rem/1 var(--font-body);
  letter-spacing: .04em;
  color: #fff;
  background: var(--color-accent);
  padding: .2em .5em;
  border-radius: .25rem;
  pointer-events: none;
}
/* Invite d'un champ éditable vide (mode édition) : voile teinté dérivé de
   currentColor + flou d'arrière-plan — la surface se voit sur les 4 fonds,
   y compris une photo chargée, sans panneau blanc ni bordure. Le placeholder
   garde la typographie finale de la zone. */
[data-editable]:empty {
  background: color-mix(in srgb, currentColor 12%, transparent);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  border-radius: .35rem;
  min-height: 1.6em;
  padding-inline: .3em;
}
/* Le placeholder NEUTRALISE toute décoration ::before posée par un thème sur
   le même élément (ex. le trait d'eyebrow de silliere-v2026 : inline-block
   26×2px + fond). Sans ces remises à zéro, la cascade fusionne propriété par
   propriété : le texte du placeholder héritait de la boîte de 26px du trait
   et s'empilait lettre à lettre dans un pavé coloré — constaté le 08/08/2026
   sur le squelette d'une section neuve. */
[data-editable]:empty::before {
  content: attr(data-placeholder);
  color: currentColor; opacity: .55; font-style: italic;
  display: inline; width: auto; height: auto;
  background: none; margin: 0; vertical-align: baseline;
}
/* Champ item vide en mode édition : même voile (fini le panneau + tirets). */
[data-editable][data-empty="1"] {
  display: block; min-height: 2rem; min-width: 6rem;
  background: color-mix(in srgb, currentColor 12%, transparent);
  backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
  border-radius: .35rem;
}
[data-editable][data-empty="1"]::before {
  content: attr(data-placeholder);
  color: currentColor; opacity: .55; font-style: italic;
  padding: .25rem; display: block;
  width: auto; height: auto; background: none; margin: 0;
}

@keyframes saved-pulse {
  0%   { background: rgba(42,122,60,.2); }
  100% { background: transparent; }
}

/* ── Déclinaison « fragment en flux » (extension chantier B, infos légales,
   août 2026) — MÊMES tokens que « voile discret » (currentColor, alphas,
   rayon .35rem), un seul réglage retiré : le flou.
   Les zones éditables des documents légaux (mentions-legales.php,
   confidentialite.php, accessibilite.php) sont de courts fragments enchâssés
   dans une phrase juridique, jamais des blocs de section — et ces pages sont
   toujours sur fond clair uni, jamais photo. Le flou perd donc sa seule
   justification, et un défaut de Chromium le rend nuisible ici : un
   fragment vide peut envelopper sur 2 lignes (invite longue à largeur
   mobile) et le moteur calcule alors le flou sur la boîte TOTALE de
   l'élément fragmenté plutôt que ligne par ligne — ce qui floute aussi le
   texte qui PRÉCÈDE le fragment sur sa première ligne. Constaté sur
   pil-local à 380px : « Directeur de la publication : » devenait illisible.
   Le voile teinté seul (sans flou) n'a pas ce défaut et suffit sur fond
   clair — d'où cette exception scoped au conteneur existant. */
.gap-legal-content [data-editable]:empty,
.gap-legal-content [data-editable][data-empty="1"] {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* ── Filet de sécurité CONTRASTE (mode édition uniquement) ──────
   Certains éléments ont une couleur de marque FIXE qui peut coïncider avec le
   fond de la section → texte invisible pendant l'édition (ex. étiquette verte
   sur section verte, sous-titre gris sur fond sombre, bouton blanc/vert sur fond
   clair/vert). On force la lisibilité SANS toucher au rendu public (portée
   .gap-sections-edit). Le rendu final définitif sera traité par la charte. */

/* (A) Textes éditables sur fond sombre/coloré/image : forcés en blanc lisible,
   ce qui neutralise les couleurs fixes (étiquette verte, .text-muted, etc.). */
.gap-sections-edit .bg-dark-custom [data-editable],
.gap-sections-edit .bg-primary-custom [data-editable],
.gap-sections-edit .bg-secondary-custom [data-editable],
.gap-sections-edit .has-bg-image [data-editable],
.gap-sections-edit .has-dark-surface [data-editable] {
  color: #fff !important;
  -webkit-text-fill-color: #fff !important;
}
.gap-sections-edit .has-bg-image [data-editable] {
  text-shadow: 0 1px 3px rgba(0,0,0,.55);
}
/* Les zones vides portent le voile currentColor (grammaire « voile discret ») :
   le ::before hérite de la couleur du texte de la section, rien à surcharger. */

/* (B) Libellé éditable d'un BOUTON : lisible sur TOUT fond, quelle que soit la
   couleur de marque du bouton (blanc « ghost », vert « secondary », etc.).
   Puce claire à texte sombre le temps de l'édition. */
.gap-sections-edit .gap-cta-edit [data-editable] {
  background: rgba(255,255,255,.94) !important;
  color: #2b2b2b !important;
  -webkit-text-fill-color: #2b2b2b !important;
  text-shadow: none !important;
  border-radius: .35rem !important;
}

/* ── Lot 1 « édition intuitive » : états vides & badges ─────── */
/* État vide d'une collection (mode édition) : zone visible qui invite à agir. */
.gap-empty-state {
  padding: 1.5rem 1rem;
  border: 2px dashed #b9b3a8;
  border-radius: .5rem;
  background: rgba(255,255,255,.75);
  color: #5a544a;
  font-size: .95rem;
  text-align: center;
}
/* Alerte « lien manquant » d'un bouton CTA (mode édition). Opaque → lisible
   sur tout fond de section. */
.gap-cta-missing {
  display: inline-flex; align-items: center;
  margin-left: .35rem; padding: .2rem .5rem;
  background: #8a5a00; color: #ffe9c0;
  font-size: .68rem; font-weight: 700; letter-spacing: .02em;
  border-radius: 999px; white-space: nowrap;
  vertical-align: middle; cursor: help;
}
/* Badge-MENU de statut d'un item (Publié / Brouillon / Programmé) dans la pill
   d'outils : cliquable, règle le statut sans passer par le BO (MT3). */
.gap-item-status-wrap { position: relative; display: inline-flex; }
/* Masqué à l'œil, lu par les lecteurs d'écran. Une pastille de couleur ne dit
   rien à qui n'y voit pas : le libellé reste, hors du flux visuel. */
.gap-sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* PASTILLE de statut. « PUBLIÉ » en toutes lettres était la partie la plus
   large de la barre — 197 px au total, quand une tuile de logo en fait 124.
   La couleur porte l'état, l'infobulle et le menu portent les mots. */
.gap-item-status {
  display: inline-flex; align-items: center; justify-content: center; border: 0;
  width: 1.65rem; height: 1.65rem; padding: 0; margin-right: .1rem;
  background: #8a5a00;
  border-radius: 50%; cursor: pointer;
  box-shadow: 0 1px 4px rgba(0,0,0,.2);
  transition: filter .12s, box-shadow .12s;
}
.gap-item-status:hover { box-shadow: 0 0 0 2px rgba(255,255,255,.35); }
/* Le sous-groupe « dupliquer / supprimer » : ces deux-là touchent à
   l'EXISTENCE de l'élément, les autres à sa place. Un filet les sépare, pour
   qu'on ne supprime pas en visant « descendre ». */
.gap-item-grp {
  display: inline-flex; gap: .15rem;
  margin-left: .3rem; padding-left: .3rem;
  border-left: 1px solid rgba(255,255,255,.22);
}
.gap-item-status:hover { filter: brightness(1.2); }
.gap-item-status.is-published { background: rgba(34,102,68,.92); color: #d9f2e4; }
.gap-item-status.is-draft     { background: #8a5a00; color: #ffe9c0; }
.gap-item-status.is-scheduled { background: #1f4e79; color: #d5e8f7; }
.gap-item-status-menu {
  display: none; position: absolute; top: calc(100% + .35rem); left: 0; z-index: 100;
  background: #1e2329; border: 1px solid rgba(255,255,255,.1); border-radius: .4rem;
  box-shadow: 0 4px 16px rgba(0,0,0,.4); min-width: 210px; padding: .3rem;
  flex-direction: column; gap: .1rem; text-transform: none; letter-spacing: 0;
}
.gap-item-status-menu.open { display: flex; }
.gap-item-status-opt {
  display: flex; align-items: baseline; gap: .4rem;
  border: 0; background: transparent; color: rgba(255,255,255,.85); cursor: pointer;
  font-size: .82rem; line-height: 1.2; padding: .45rem .55rem; border-radius: .3rem;
  text-align: left; width: 100%; white-space: nowrap;
  transition: background .12s, color .12s;
}
.gap-item-status-opt:hover { background: rgba(255,255,255,.1); color: #fff; }
.gap-item-status-opt small { color: rgba(255,255,255,.45); font-size: .7rem; }
.gap-item-status-sched {
  display: flex; align-items: center; gap: .35rem;
  padding: .4rem .55rem; margin-top: .15rem;
  border-top: 1px solid rgba(255,255,255,.08);
}
.gap-item-status-sched label { color: rgba(255,255,255,.55); font-size: .72rem; margin: 0; }
.gap-item-status-date {
  background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.15);
  color: #fff; border-radius: .3rem; font-size: .72rem; padding: .15rem .3rem;
  color-scheme: dark; min-width: 0; flex: 1;
}
.gap-item-status-sched .gap-item-status-opt { width: auto; padding: .3rem .5rem; border: 1px solid rgba(255,255,255,.25); }

/* ── Animations viewport (Intersection Observer) ───────────── */
/* L'état masqué initial n'est appliqué QUE si JS est actif (.js sur <html>) :
   sans JS, l'IntersectionObserver n'ajoute jamais .anim-visible → on ne doit pas
   masquer le contenu. */
@media (prefers-reduced-motion: no-preference) {
  /* Durée (.5s) et courbe (ease) = valeurs par défaut ; gap.js peut les surcharger
     par élément (transition-duration / -timing-function inline) selon les réglages
     anim_duration / anim_easing de la section. */
  /* M1 (25/08/2026) : les 12 effets consomment les jetons — mêmes valeurs
     (--dur-5 = .5s, --ease-standard = ease), « rise » garde ses .6s (cas
     nommé). Un thème règle désormais la respiration de TOUTES les
     apparitions en surchargeant deux jetons, sans réécrire douze règles. */
  .js [data-anim]                { opacity: 0; will-change: opacity, transform, filter; }
  .js [data-anim="fade"]         { transition: opacity var(--dur-5) var(--ease-standard); }
  .js [data-anim="slide-up"]     { transform: translateY(30px);  transition: opacity var(--dur-5) var(--ease-standard), transform var(--dur-5) var(--ease-standard); }
  .js [data-anim="slide-down"]   { transform: translateY(-30px); transition: opacity var(--dur-5) var(--ease-standard), transform var(--dur-5) var(--ease-standard); }
  .js [data-anim="slide-left"]   { transform: translateX(-30px); transition: opacity var(--dur-5) var(--ease-standard), transform var(--dur-5) var(--ease-standard); }
  .js [data-anim="slide-right"]  { transform: translateX(30px);  transition: opacity var(--dur-5) var(--ease-standard), transform var(--dur-5) var(--ease-standard); }
  .js [data-anim="zoom"]         { transform: scale(.95);  transition: opacity var(--dur-5) var(--ease-standard), transform var(--dur-5) var(--ease-standard); }
  .js [data-anim="zoom-out"]     { transform: scale(1.06); transition: opacity var(--dur-5) var(--ease-standard), transform var(--dur-5) var(--ease-standard); }
  .js [data-anim="rise"]         { transform: translateY(60px); transition: opacity .6s var(--ease-standard), transform .6s var(--ease-standard); }
  .js [data-anim="blur"]         { filter: blur(12px); transition: opacity var(--dur-5) var(--ease-standard), filter var(--dur-5) var(--ease-standard); }
  .js [data-anim="flip"]         { transform: perspective(600px) rotateX(10deg); transition: opacity var(--dur-5) var(--ease-standard), transform var(--dur-5) var(--ease-standard); }
  .js [data-anim="tilt"]         { transform: perspective(800px) rotateY(-8deg) translateX(20px); transition: opacity var(--dur-5) var(--ease-standard), transform var(--dur-5) var(--ease-standard); }
  .js [data-anim].anim-visible   { opacity: 1; transform: none; filter: none; }
}

/* P0-2 (24/08/2026) — aperçu d'animation en mode édition. Le chrome d'édition
   (poignées, menus ⋮, outils d'item, barres média) est un DESCENDANT des
   éléments [data-anim] : l'opacité d'un parent plafonne ses enfants, il
   clignoterait avec eux pendant GapAnim.replay(). La classe .is-anim-preview,
   posée par gap.js le temps de l'effet, l'escamote proprement à la place.
   Hors aperçu, le mode édition ne masque jamais rien : gap.js y révèle tous
   les [data-anim] dès le chargement, sans animer. */
.admin-edit-mode .is-anim-preview .gap-insert-before,
.admin-edit-mode .is-anim-preview .gap-section-tools,
.admin-edit-mode .is-anim-preview .gap-item-tools,
.admin-edit-mode .is-anim-preview .gap-media-edit,
.admin-edit-mode .is-anim-preview .gap-add-item-wrap { visibility: hidden; }

/* ── Effet d'ouverture des sous-menus (réglage menu_submenu_effect) ────────
   Porté par data-sousmenu-effet sur la liste de navigation ; « aucun »
   (défaut) ne matche rien — le déroulé s'ouvre comme toujours. Animations
   UNIQUEMENT sous no-preference : un visiteur qui refuse le mouvement ne
   les voit jamais, quel que soit le réglage (réserve du 10/08). Elles
   couvrent aussi les sous-niveaux (.gap-nav-sous-menu est un .dropdown-menu). */
@media (prefers-reduced-motion: no-preference) {
  [data-sousmenu-effet="fondu"] .dropdown-menu.show {
    animation: gapSmFondu var(--dur-2) var(--ease-standard);
  }
  [data-sousmenu-effet="glisse"] .dropdown-menu.show {
    animation: gapSmGlisse var(--dur-3) var(--ease-standard);
  }
  @keyframes gapSmFondu  { from { opacity: 0; } }
  @keyframes gapSmGlisse { from { opacity: 0; transform: translateY(-6px); } }
}

/* Transition de navigation entre pages */
@media (prefers-reduced-motion: no-preference) {
  .page-transition-out { opacity: 0; transition: opacity .15s ease; }
  .page-transition-in  { animation: fadeIn .2s ease; }
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
}

/* Agenda public (V2) : l'année et le quantième portaient text-danger — le
   rouge Bootstrap brut, identique chez tous les clients. Le jeton suit le
   thème (bleu plan sur la vitrine, vert sauge chez Ta Cadence…). */
.gap-agenda-annee,
.gap-agenda-jour { color: var(--color-primary); }


/* ── Frise chronologique (V6, 25/08/2026 — extrait du gabarit timeline.php :
      76 lignes de <style> imprimées par le gabarit étaient invisibles pour
      les thèmes. Valeurs conservées à l'identique, trois adaptations : la
      ligne et les points passent en classes (ils étaient en style inline),
      le seuil mobile rejoint l'échelle officielle (767.98), et le rail
      mobile gagne ses repères de rangée). ── */
.gap-timeline .timeline-line{ position:absolute; left:50%; top:0; bottom:0; width:2px;
  background:var(--color-border); transform:translateX(-50%); }
.gap-tl-point{ width:14px; height:14px; border-radius:50%; margin:0 auto;
  background:var(--color-primary); border:3px solid #fff;
  box-shadow:0 0 0 2px var(--color-primary); }
.gap-tl-point--secondaire{ background:var(--color-secondary);
  box-shadow:0 0 0 2px var(--color-secondary); }

/* Sélecteur d'emplacement du média (édition) : petite barre en haut de l'entrée,
   hors du flux flex (position absolue) pour ne pas perturber média/texte. */
.gap-tl-entry{ position:relative; }
.gap-tl-pos{ position:absolute; top:-.6rem; left:0; z-index:2; display:inline-flex; gap:2px;
  background:#fff; border:1px solid var(--color-border); border-radius:7px; padding:2px;
  box-shadow:0 4px 12px rgba(20,18,16,.12); }
.gap-tl-entry[data-align="end"] .gap-tl-pos{ left:auto; right:0; }
.gap-tl-pos-btn{ border:0; background:transparent; cursor:pointer; width:1.5rem; height:1.5rem;
  border-radius:5px; line-height:1; font-size:.95rem; color:#6c645a; display:inline-flex;
  align-items:center; justify-content:center; }
.gap-tl-pos-btn:hover{ background:var(--color-surface-alt); color:var(--color-ink); }
.gap-tl-pos-btn.is-active{ background:var(--color-primary); color:#fff; }

/* Média d'entrée : disposition selon media_position. */
.gap-tl-entry{ display:flex; gap:.9rem; }
.gap-tl-entry[data-align="end"]{ align-items:flex-end; }
.gap-tl-entry[data-align="start"]{ align-items:flex-start; }
.gap-tl-entry[data-media-pos="top"]{ flex-direction:column; }
.gap-tl-entry[data-media-pos="bottom"]{ flex-direction:column-reverse; }
.gap-tl-entry[data-media-pos="left"]{ flex-direction:row; }
.gap-tl-entry[data-media-pos="right"]{ flex-direction:row-reverse; }
.gap-tl-entry[data-media-pos="left"] .gap-tl-media,
.gap-tl-entry[data-media-pos="right"] .gap-tl-media{ flex:0 0 auto; }
/* Boîte média à ratio FIXE — c'est le cadre, pas l'image, qui porte la taille
   (sans lui, object-fit:cover n'a rien à recadrer et le point focal reste sans
   effet — mesuré). 4/3 = le ratio des images de cartes : la frise s'aligne. */
.gap-tl-media-wrap{ margin:0; }
.gap-tl-media-wrap:not(.gap-photo-empty){ width:100%; max-width:170px; aspect-ratio:4/3; }
.gap-tl-img{ display:block; width:100%; height:100%; border-radius:8px; object-fit:cover;
  box-shadow:0 10px 26px rgba(20,18,16,.12); }
.gap-tl-icon{ display:inline-flex; align-items:center; justify-content:center;
  width:56px; height:56px; border-radius:50%; font-size:1.7rem; line-height:1;
  background:var(--color-surface-alt); box-shadow:0 6px 18px rgba(20,18,16,.10); }
.gap-tl-entry[data-media-pos="left"] .gap-tl-media-wrap:not(.gap-photo-empty),
.gap-tl-entry[data-media-pos="right"] .gap-tl-media-wrap:not(.gap-photo-empty){ max-width:96px; }

/* Affordance compacte en ÉDITION, tant qu'aucun média n'est choisi. */
.gap-tl-media--edit:not(.is-photo):not(.is-icon){ display:inline-flex; align-items:center; gap:.4rem; }
.gap-tl-icon--empty{ width:44px; height:44px; font-size:1.15rem;
  background:transparent; border:1px dashed var(--color-border); box-shadow:none;
  color:#9a9184; cursor:pointer; }
.gap-tl-icon .gap-icon-btn{ width:100%; height:100%; border-radius:inherit; outline-offset:-3px; }
.gap-tl-media--edit .gap-photo-empty{ min-height:0; width:auto;
  border-radius:8px; padding:0; background:transparent; border:0; }
.gap-tl-media--edit .gap-photo-add{ font-size:.78rem; padding:.35rem .6rem; white-space:nowrap; }

/* Icône OU photo : dès qu'un choix est posé, l'affordance de l'autre s'efface
   (jamais supprimée du DOM — bascule .is-icon / .is-photo par gap.js). */
.gap-tl-media--edit.is-photo .gap-tl-icon,
.gap-tl-media--edit.is-icon .gap-tl-media-wrap{ display:none; }

/* Mobile : le rail remplace la ligne centrale — et chaque rangée garde son
   repère, posé SUR le rail (avant V6, les points disparaissaient simplement
   et la frise devenait une liste bordée, sans plus rien d'une chronologie). */
@media (max-width: 767.98px) {
  .gap-timeline .timeline-line { display: none; }
  /* Marges à zéro : la gouttière NÉGATIVE de Bootstrap (−12px) annulait le
     padding du .container et posait le rail à x=0, collé au bord de l'écran
     (constaté le 12/08 sur la frise du GAP, mesuré de nouveau le 25/08 :
     18 rangées, rail au ras du bord). Sans gouttière, le rail s'aligne sur
     le bord intérieur du conteneur, comme tout le reste de la page. */
  .gap-timeline .row { position: relative; padding-left: 1.5rem;
    margin-left: 0; margin-right: 0;
    border-left: 2px solid var(--color-border); }
  .gap-timeline .row::before { content: ""; position: absolute;
    left: -6px; top: .5rem; width: 10px; height: 10px; border-radius: 50%;
    background: var(--color-primary); border: 2px solid #fff;
    box-shadow: 0 0 0 1px var(--color-primary); }
  .gap-tl-entry{ align-items:flex-start !important; }
  .gap-tl-entry[data-media-pos="left"],
  .gap-tl-entry[data-media-pos="right"]{ flex-direction:column; }
}

/* Pause du mouvement continu (M2, 25/08/2026 — WCAG 2.2.2) : carrousel
   (bouton natif Splide .splide__toggle, présent dès que l'autoplay est
   actif) et ruban défilant (bouton maison, .is-paused fige l'animation).
   Toujours visibles : au doigt, le survol n'existe pas. */
.splide__toggle,
.gap-marquee-toggle {
  position: absolute; top: .6rem; right: .6rem; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(20,18,16,.55); color: #fff;
  transition: background var(--dur-2) var(--ease-standard);
}
.splide { position: relative; }
.gap-marquee { position: relative; }
.splide__toggle:hover, .gap-marquee-toggle:hover { background: rgba(20,18,16,.8); }
/* Splide gère lui-même l'alternance de ses deux pictos (is-active). Le ruban :
   au repos on montre « pause », en pause on montre « lecture ». */
.gap-marquee-toggle .gap-marquee-toggle-play { display: none; }
.gap-marquee-toggle[aria-pressed="true"] .gap-marquee-toggle-pause { display: none; }
.gap-marquee-toggle[aria-pressed="true"] .gap-marquee-toggle-play { display: inline-flex; }
.gap-marquee.is-paused .gap-marquee-track { animation-play-state: paused; }

/* Barre flottante des marques de titre (E6, mode édition) : apparaît
   au-dessus de la sélection dans un titre éditable — un bouton par marque
   déclarée par le thème. Chrome graphite du mode édition. */
.gap-marques-barre {
  position: fixed; z-index: var(--z-toast);
  display: none; gap: 2px; padding: 3px;
  background: #1e2329; border-radius: 7px;
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
.gap-marques-barre.is-visible { display: inline-flex; }
.gap-marques-btn {
  border: 0; background: transparent; color: #d5d9de;
  font-size: .74rem; padding: .3rem .55rem; border-radius: 5px;
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-1) var(--ease-standard);
}
.gap-marques-btn:hover { background: rgba(255,255,255,.12); color: #fff; }

/* ── Utilitaires ───────────────────────────────────────────── */

/* Responsive léger : masquage par taille d'écran (interrupteurs de l'onglet
   Avancé d'une section, via css_class). Seuil aligné sur l'empilement mobile. */
@media (max-width: 767.98px) {
  .gap-hide-mobile { display: none !important; }
}
@media (min-width: 768px) {
  .gap-hide-desktop { display: none !important; }
}
/* Mode édition : ne JAMAIS masquer (sinon la section devient introuvable) ;
   indicateur visuel discret à la place. */
.gap-sections-edit .gap-hide-mobile,
.gap-sections-edit .gap-hide-desktop {
  display: revert !important;
  outline: 2px dashed #b58900;
  outline-offset: -2px;
}

.divider-red   { border: none; border-top: 3px solid var(--color-primary);   width: 48px; }
.divider-green { border: none; border-top: 3px solid var(--color-secondary); width: 48px; }
.divider-gold  { border: none; border-top: 3px solid var(--color-accent);  width: 48px; }

.pill {
  display: inline-block;
  /* .7 → .8rem (14/09/2026) : à 11,2 px, l'étiquette d'une carte se lisait
     mal — et sur une frise, c'est l'ANNÉE, l'information qu'on y cherche.
     Seuls les thèmes qui ne fixent pas leur propre taille en héritent. */
  font-size: .8rem;
  font-weight: 700;
  padding: .15rem .55rem;
  border-radius: 999px;
}
.pill-red   { background: rgba(206,43,30,.12);  color: var(--color-primary); }
.pill-green { background: rgba(42,122,60,.12);  color: var(--color-secondary); }
.pill-gold  { background: rgba(212,160,23,.15); color: #6E5507; } /* 3,98:1 → ≥5:1 pour un petit texte (V11, fixé à .7rem ; .8rem depuis le 14/09/2026) */
/* Modificateur « Mise en avant » (champ tag_style des cartes, 17/08/2026) :
   posé APRÈS pill-green, il l'emporte. Le thème par défaut prend l'or de
   la marque ; un thème client redéfinit les deux familles à sa palette. */
.pill-accent{ background: rgba(212,160,23,.18); color: #6E5507; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* ── Couverture d'article : cadre et pastilles de position ─────────────────
   object-fit en CSS et non en style inline : le remplacement d'image en
   place reconstruit le <img> à partir de data-img-class, les styles en ligne
   n'y survivraient pas. Les pastilles reprennent le langage des flèches de
   l'avertissement du pied (chrome d'édition, coin du cadre). */
.t-news-detail .ratio > img { object-fit: cover; }
.gap-cover-pos {
  position: absolute; top: .6rem; right: .6rem; z-index: 2;
  /* Bootstrap étire TOUT enfant direct d'un .ratio (.ratio > * :
     absolute + left:0 + 100 %/100 %) — sans ces quatre reprises, le
     chrome des pastilles couvrait le cadre entier d'un bloc graphite,
     image invisible (constaté le 26/08 sur la fiche article en mode
     édition). gap.css passe après bootstrap : à égalité de spécificité,
     ces déclarations gagnent. */
  left: auto; bottom: auto; width: auto; height: auto;
  display: inline-flex; gap: 2px; padding: 3px;
  background: #1e2329; border-radius: 7px;
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
.gap-cover-pos-btn {
  border: 0; background: transparent; color: #d5d9de;
  min-width: 26px; height: 26px; border-radius: 5px;
  font-size: .8rem; line-height: 1; cursor: pointer;
  transition: background var(--dur-1) var(--ease-standard);
}
.gap-cover-pos-btn:hover { background: rgba(255,255,255,.12); color: #fff; }
.gap-cover-pos-btn.is-active { background: rgba(255,255,255,.22); color: #fff; }

/* ── Galerie d'article : l'échelle de la page, pas celle de l'encart ───────
   L'article délègue au moteur des éléments (_item-gallery), dont le CSS
   scopé .gap-item-gallery est calibré pour une demi-colonne (~400 px).
   Dans les 820 px de la fiche, ces réglages font des timbres-poste : on
   les rehausse ici, sélecteurs plus profonds (le conteneur .gap-news-album
   s'ajoute), sans toucher aux encarts. La grille garde le 4/3 du rendu
   historique des articles — pas les carrés de la galerie intégrée. */
.gap-news-album .gap-item-gallery .gap-embed-grid {
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: .5rem;
}
.gap-news-album .gap-embed-grid .gap-embed-item { aspect-ratio: 4 / 3; }
.gap-news-album .gap-item-gallery .gap-embed-masonry-v2 { column-width: 200px; }
.gap-news-album .gap-item-gallery .gap-collage-t0,
.gap-news-album .gap-item-gallery .gap-collage-t1,
.gap-news-album .gap-item-gallery .gap-collage-t2 { width: clamp(140px, 30%, 240px); }
.gap-news-album .gap-item-gallery .gap-embed-mur {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.gap-news-album .gap-item-gallery .gap-embed-mur figcaption { font-size: .8rem; }

/* ── Fiche article : navigation entre articles ─────────────────────────────
   Trois zones (précédent | retour | suivant), le patron de l'annuaire
   (t-costume-nav), stylé au SOCLE : tous les clients l'ont sans retouche de
   thème. Masquée dans la modale d'aperçu, où « Voir l'article complet »
   fait le travail. */
.gap-news-nav {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  border-top: 1px solid var(--color-border, rgba(0,0,0,.1));
  padding-top: 1.25rem;
}
.gap-news-nav-side { flex: 1 1 0; min-width: 0; }
.gap-news-nav-side--right { text-align: right; }
.gap-news-nav-link { text-decoration: none; display: inline-block; max-width: 100%; }
.gap-news-nav-dir { display: block; font-size: .8rem; font-weight: 600; opacity: .65; }
.gap-news-nav-name {
  display: block; font-weight: 600; margin-top: .15rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.gap-news-nav-back { text-decoration: none; font-weight: 600; white-space: nowrap; }
.gap-news-nav-link:hover .gap-news-nav-name, .gap-news-nav-back:hover { text-decoration: underline; }
@media (max-width: 575.98px) {
  /* En colonne : le retour passe en dernier, les voisins gardent leur bord. */
  .gap-news-nav { flex-direction: column; align-items: stretch; gap: .75rem; }
  .gap-news-nav-back { order: 3; text-align: center; }
}
.gap-detail-body .gap-news-nav { display: none; }

/* ── Heures d'ouverture : le bouton de réglage du mode édition ─────────────
   Chrome d'édition, jamais rendu au visiteur (le gabarit ne l'émet qu'en
   mode édition, et seulement pour qui peut régler les paramètres). */
.gap-horaires-edit {
  display: inline-flex; align-items: center; gap: .35rem;
  margin-top: .9rem;
}

/* ── Lightbox ─────────────────────────────────────────────── */
.gap-lightbox {
  display: none;
  position: fixed; inset: 0; z-index: var(--z-lightbox);
  background: rgba(0,0,0,.92);
  align-items: center; justify-content: center;
  flex-direction: column;
}
.gap-lightbox.is-open { display: flex; }
.gap-lb-figure {
  display: flex; flex-direction: column; align-items: center;
  max-width: 90vw; max-height: 90vh; margin: 0;
}
.gap-lb-figure img {
  max-width: 90vw; max-height: 80vh;
  object-fit: contain; border-radius: var(--radius);
  outline: none;
}
.gap-lb-figure figcaption {
  color: rgba(255,255,255,.7);
  font-size: .8rem; margin-top: .5rem; text-align: center;
}
.gap-lb-close, .gap-lb-prev, .gap-lb-next {
  position: fixed;
  background: rgba(255,255,255,.12);
  border: none; color: #fff;
  border-radius: 50%; cursor: pointer;
  transition: background var(--transition);
  line-height: 1;
  display: inline-flex; align-items: center; justify-content: center; /* V4 : centre les SVG */
}
.gap-lb-close:hover, .gap-lb-prev:hover, .gap-lb-next:hover {
  background: rgba(255,255,255,.25);
}
.gap-lb-close { top: 1rem; right: 1rem; font-size: 1.2rem; width: 2.2rem; height: 2.2rem; }
.gap-lb-prev  { left: 1rem;  top: 50%; transform: translateY(-50%); font-size: 2rem; width: 3rem; height: 3rem; }
.gap-lb-next  { right: 1rem; top: 50%; transform: translateY(-50%); font-size: 2rem; width: 3rem; height: 3rem; }
@media (max-width: 767.98px) {
  .gap-lb-prev, .gap-lb-next { width: 2.2rem; height: 2.2rem; font-size: 1.4rem; }
}

/* V4 (25/08/2026) : la visionneuse entre en scène au lieu d'apparaître d'un
   coup — voile qui fond, image qui se pose ; à la navigation, l'image qui
   arrive fond (classe .gap-lb-fondu rejouée par lightbox.js, inerte en
   mouvement réduit). Compteur « n / N » : le repère que la visionneuse n'a
   jamais eu. */
.gap-lb-count {
  position: fixed; left: 50%; bottom: 1rem; transform: translateX(-50%);
  color: rgba(255,255,255,.7);
  font-size: .8rem; letter-spacing: .08em;
  font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: no-preference) {
  .gap-lightbox.is-open { animation: gap-lb-entre var(--dur-4) var(--ease-entree); }
  .gap-lightbox.is-open .gap-lb-figure { animation: gap-lb-pose var(--dur-4) var(--ease-entree); }
  .gap-lb-img.gap-lb-fondu { animation: gap-lb-image var(--dur-2) var(--ease-standard); }
  @keyframes gap-lb-entre { from { opacity: 0; } }
  @keyframes gap-lb-pose  { from { opacity: 0; transform: scale(.97); } }
  @keyframes gap-lb-image { from { opacity: 0; } }
}

/* ── Galerie mosaïque (CSS columns) ──────────────────────── */
.gap-masonry {
  columns: 2;
  column-gap: .5rem;
}
@media (min-width: 576px) { .gap-masonry { columns: 3; } }
@media (min-width: 992px) { .gap-masonry { columns: 4; } }
.gap-masonry > * {
  break-inside: avoid;
  margin-bottom: .5rem;
}
.gap-masonry img { width: 100%; display: block; border-radius: var(--radius); }

/* ── Carrousel d'accueil (Splide) ──────────────────────────── */
.gap-carousel-section { margin-bottom: 2.5rem; }
/* Carrousel : hauteur fixe — --gap-car-h (ex. 420px ou 60vh) est posée en style
   inline par carousel.php quand la section définit une hauteur ; sans réglage,
   fallback = hauteur historique (aucun changement pour l'existant). */
.gap-carousel-section .splide__slide img {
  width: 100%;
  height: var(--gap-car-h, clamp(280px, 58vh, 620px));
  object-fit: cover;
  display: block;
}

/* ── Fil d'Ariane ───────────────────────────────────────────── */
.gap-breadcrumb .breadcrumb-item + .breadcrumb-item::before {
  color: var(--color-border);
}
.gap-breadcrumb .breadcrumb-item a {
  color: var(--color-ink);
  text-decoration: none;
}
.gap-breadcrumb .breadcrumb-item a:hover {
  color: var(--color-primary);
  text-decoration: underline;
}
.gap-breadcrumb .breadcrumb-item.active {
  color: var(--color-muted); /* gris Bootstrap en dur : 4,26:1 sur le papier silliere — le jeton suit le thème (V11) */
}

/* ── Aperçu d'album (champ de section "gallery") ───────────── */
.gap-album-preview {
  display: flex;
  align-items: center;
  gap: .9rem;
  text-decoration: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: .6rem;
  transition: box-shadow var(--transition), border-color var(--transition);
}
.gap-album-preview:hover { box-shadow: var(--shadow-sm); border-color: var(--color-primary); }
.gap-album-preview-img {
  width: 96px; height: 64px;
  flex-shrink: 0;
  border-radius: .4rem;
  background-size: cover;
  background-position: center;
}
.gap-album-preview-body { display: flex; flex-direction: column; }
.gap-album-preview-title { font-weight: 600; color: var(--color-ink); }
.gap-album-preview-count { font-size: .8rem; color: var(--color-primary); }

/* Champ d'édition enrichie (la barre d'outils est gérée par .gap-editor-toolbar) */
.gap-richtext { min-height:90px; }
.gap-richtext:focus { outline:2px solid var(--color-secondary); outline-offset:1px; }

/* ── Édition de l'image des cartes (front, mode édition) ───── */
.gap-photo-wrap { position: relative; }
/* AUCUNE commande posée sur le média.
   Deux boutons ronds vivaient ici — modifier, retirer. Ils faisaient triple
   emploi avec la barre média de l'élément, entre eux, et avec le clic sur
   l'image qui ouvre déjà la médiathèque. Surtout, ils coûtaient la fidélité de
   l'aperçu : sur un portrait rond de 48 px, deux pastilles de 34 masquaient le
   visage entier.
   Règle du socle, celle que la frise applique depuis toujours : dès qu'un
   média est posé, le bloc redevient un conteneur simple, IDENTIQUE à ce que
   voit le visiteur. Les commandes vivent dans la barre média (.gap-media-edit),
   à l'échelle de l'élément, sous ses outils.

   Ce qui reste à porter : dire que la zone est cliquable. Un liseré pointillé
   permanent — le même langage que les autres zones éditables non textuelles
   (cf. .gap-icon-btn) — et non un signal de survol, qu'un novice ne découvre
   jamais. */
.admin-edit-mode .gap-photo-wrap {
  outline: 1px dashed rgba(90,100,115,.45);
  outline-offset: 3px;
  cursor: pointer;
}
.admin-edit-mode .gap-photo-wrap:hover { outline-color: var(--color-primary, #ce2b1e); }
.gap-photo-empty {
  display: flex; align-items: center; justify-content: center;
  min-height: 160px; border: 2px dashed var(--color-border);
  border-radius: var(--radius); background: var(--color-surface);
}
.gap-photo-add {
  border: 1px solid var(--color-border); background: #fff; border-radius: var(--radius);
  padding: .5rem 1rem; cursor: pointer; color: var(--color-ink); font-weight: 600;
}
.gap-photo-add:hover { border-color: var(--color-primary); color: var(--color-primary); }

/* ── Sélecteur d'icônes (front, mode édition) ─────────────────
   La zone icône est un BOUTON (jamais un contenteditable : le SVG rendu n'y
   survivrait pas), signalé par un liseré pointillé comme les autres zones. */
.gap-icon-btn {
  background: none; border: 0; padding: 0; margin: 0;
  color: inherit; font: inherit; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: .35rem;
  outline: 1px dashed rgba(90,100,115,.55); outline-offset: 3px;
  min-width: 1.25em; min-height: 1.25em;
}
.gap-icon-btn:hover,
.gap-icon-btn:focus-visible { outline: 2px solid var(--color-secondary); outline-offset: 3px; }
/* Zone vide : une invite discrète (même esprit que « ＋ Ajouter une image »). */
.gap-icon-btn--empty::before { content: '＋'; font-size: .85em; line-height: 1; opacity: .65; }

.gap-icon-picker {
  position: absolute; z-index: 1085;      /* au-dessus des outils de section */
  width: 296px; max-width: calc(100vw - 16px);
  background: #fff; border: 1px solid var(--color-border);
  border-radius: var(--radius); padding: .5rem;
  box-shadow: 0 10px 30px rgba(15,23,42,.18);
}
.gap-ip-head {
  font-size: .72rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: .04em; color: var(--color-ink-soft, #6b7280);
  padding: .1rem .25rem .35rem;
}
.gap-ip-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px; }
.gap-ip-item {
  border: 0; background: none; border-radius: .35rem;
  width: 100%; aspect-ratio: 1; min-height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--color-ink); cursor: pointer;
}
.gap-ip-item:hover { background: var(--color-surface); }
.gap-ip-item.is-current { color: var(--color-primary); box-shadow: inset 0 0 0 1px var(--color-primary); }
.gap-ip-item:focus-visible,
.gap-ip-clear:focus-visible { outline: 2px solid var(--color-secondary); outline-offset: 1px; }
.gap-ip-clear {
  display: block; width: 100%; margin-top: .4rem; padding: .35rem;
  border: 1px solid var(--color-border); background: none; border-radius: .35rem;
  font-size: .8rem; color: var(--color-ink); cursor: pointer;
}
.gap-ip-clear:hover { border-color: var(--color-primary); color: var(--color-primary); }

/* ── Palier 1 : édition de structure en front-office ─────────── */
/* Visible uniquement en mode édition (body.admin-edit-mode). */

.admin-edit-mode .page-section { position: relative; }
.admin-edit-mode .gap-card,
.admin-edit-mode [data-item-id] { position: relative; }

/* Bouton d'insertion ⊕ : ajoute un bloc juste avant cette section.
   Positionné en absolu sur le bord haut (ne casse pas les rangées flex).
   Discret par défaut, se révèle au survol de la section. */
.gap-insert-before {
  position: absolute; top: -14px; left: 50%; transform: translateX(-50%);
  z-index: 25; width: 28px; height: 28px; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer;
  background: var(--color-primary, #c62828); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.1rem; line-height: 1; font-weight: 700;
  box-shadow: 0 2px 8px rgba(0,0,0,.3);
  /* Visible en permanence (discret), plein au survol — découvrable sans deviner. */
  opacity: .55; transition: opacity .15s, transform .15s;
}
.admin-edit-mode .page-section:hover > .gap-insert-before,
.gap-insert-before:focus-visible { opacity: 1; }
.gap-insert-before:hover { transform: translateX(-50%) scale(1.12); }

/* Barre d'outils d'une section (en haut à droite) */
/* Menu ouvert : le bandeau d'outils passe DEVANT la barre de modes (z-index 30).
   Son `z-index: 20` crée un contexte d'empilement, dans lequel le `z-index: 100`
   du menu déroulant se retrouve enfermé — le menu ne pouvait donc jamais
   dépasser un élément frère plus haut placé, et la barre de modes le recouvrait.
   L'élévation est CONDITIONNELLE pour préserver l'intention d'origine : au
   repos les outils restent sous la navigation, où ils doivent être. */
/* TOUS les menus du bandeau, pas seulement celui des trois points : « + Zone »
   vit dans le même bandeau et retombait donc sous la barre de modes, alors que
   son voisin immédiat passait dessus. Un correctif qui ne couvre qu'un des deux
   menus d'une même barre est un correctif à moitié fait. */
.gap-section-tools:has(.gap-section-menu.open),
.gap-section-tools:has(.gap-zone-add-menu.open) { z-index: 40; }

.gap-section-tools {
  position: absolute; top: .5rem; right: .5rem; z-index: 20;
  display: flex; align-items: center; gap: .2rem;
  /* Ancré à droite : tout ce qui dépasse sort par la GAUCHE, hors écran et
     sans barre de défilement pour le rattraper. Cette borne garantit qu'aucune
     combinaison de badges ne puisse rendre les boutons inatteignables. */
  max-width: calc(100vw - 1rem);
  background: rgba(22, 27, 34, .88); border-radius: .5rem;
  padding: .2rem .3rem; backdrop-filter: blur(4px);
  box-shadow: 0 2px 8px rgba(0,0,0,.25);
}
/* Hero : la section remonte SOUS la navbar transparente (margin-top négatif du
   thème). Ses outils, ancrés en haut, tombaient derrière la navbar (z-index
   navbar 1030 > outils) et devenaient inaccessibles. On les redescend juste
   sous la navbar plutôt que de forcer un z-index qui les ferait flotter par-
   dessus la barre au défilement des autres sections. */
.admin-edit-mode .t-hero > .gap-section-tools,
.admin-edit-mode .t-hero > .gap-insert-before,
.admin-edit-mode .t-page-hero > .gap-section-tools,
.admin-edit-mode .t-page-hero > .gap-insert-before { top: calc(var(--nav-height) + .6rem); }
.gap-section-handle {
  cursor: grab; user-select: none; color: rgba(255,255,255,.7);
  display: inline-flex; align-items: center; padding: .2rem .25rem;
  border-radius: 3px; transition: color .15s;
}
.gap-section-handle:hover { color: #fff; }
.gap-section-handle:active { cursor: grabbing; }
/* Boutons ↑ ↓ : tri au clic, accessible clavier + mobile (alternative au drag). */
.gap-section-move {
  border: 0; background: transparent; color: rgba(255,255,255,.7); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: .8rem; line-height: 1; padding: .2rem .25rem;
  border-radius: 3px; transition: background .15s, color .15s;
}
.gap-section-move:hover { background: rgba(255,255,255,.15); color: #fff; }
.gap-section-hidden-badge {
  color: #ffd; font-size: .65rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; padding: .1rem .3rem;
  background: rgba(255,200,0,.15); border-radius: 3px;
}
/* Type de la section (édition) : rappelle sur quel composant on travaille. */
.gap-section-type-badge {
  color: rgba(255,255,255,.92); font-size: .65rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  padding: .12rem .4rem; margin-right: .1rem;
  background: rgba(255,255,255,.14); border-radius: 3px; white-space: nowrap;
}

/* Traçabilité — « qui a édité cette section en dernier ».
   Au repos : une pastille de 8px, rien d'autre. Le bandeau porte déjà six
   éléments, tous cliquables ; celui-ci est une information passive et n'a pas
   à peser autant qu'une action.
   Au survol OU au focus clavier : le texte se déplie. :focus-within et non
   :hover seul — sinon l'information est inatteignable au clavier et au doigt. */
.gap-section-edited {
  display: inline-flex; align-items: center; gap: .3rem;
  padding: .12rem .3rem; border-radius: 3px; cursor: default;
  color: rgba(255,255,255,.62);
  outline-offset: 2px;
}
.gap-section-edited-dot {
  width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px;
  border: 1px solid rgba(255,255,255,.45);   /* ancienne édition : contour seul */
}
/* Moins de 7 jours : pastille pleine. Repérer d'un balayage ce qui vient de
   bouger est tout l'intérêt d'un indice permanent. */
.gap-section-edited.is-recent .gap-section-edited-dot {
  background: #4ade80; border-color: #4ade80;
}
/* Jamais éditée depuis la migration : ni pleine, ni verte — et le texte le dit. */
.gap-section-edited.is-empty .gap-section-edited-dot {
  border-style: dashed; border-color: rgba(255,255,255,.3);
}
.gap-section-edited-text {
  font-size: .82rem;            /* du texte à LIRE : pas le .65rem des badges */
  white-space: nowrap; max-width: 0; overflow: hidden; text-overflow: ellipsis;
  opacity: 0; transition: max-width .18s ease, opacity .18s ease;
}
/* min(22rem, 45vw) et non 22rem : mesuré en 375px de large, un libellé long
   déplié poussait le bandeau 94px HORS de l'écran à gauche — le texte devenait
   illisible précisément là où on venait de le déplier. Au-delà de la borne, il
   se tronque proprement, et le title= porte la version entière. */
.gap-section-edited:hover .gap-section-edited-text,
.gap-section-edited:focus-visible .gap-section-edited-text,
.gap-section-edited:focus-within .gap-section-edited-text {
  max-width: min(22rem, 45vw); opacity: 1;
}
.gap-section-edited:hover, .gap-section-edited:focus-within {
  background: rgba(255,255,255,.1);
}
@media (prefers-reduced-motion: reduce) {
  .gap-section-edited-text { transition: none; }
}

/* Bouton ⋮ et menu déroulant */
.gap-section-menu-wrap { position: relative; }
.gap-section-dots {
  border: 0; background: transparent; color: rgba(255,255,255,.8); cursor: pointer;
  display: inline-flex; align-items: center; padding: .2rem .25rem;
  border-radius: 3px; transition: background .15s, color .15s;
}
.gap-section-dots:hover { background: rgba(255,255,255,.15); color: #fff; }
.gap-section-menu {
  display: none; position: absolute; top: calc(100% + .35rem); right: 0; z-index: 100;
  background: #1e2329; border: 1px solid rgba(255,255,255,.1); border-radius: .4rem;
  box-shadow: 0 4px 16px rgba(0,0,0,.4); min-width: 160px; padding: .3rem;
  flex-direction: column; gap: .1rem;
}
.gap-section-menu.open { display: flex; }
.gap-section-menu-item {
  display: flex; align-items: center; gap: .45rem;
  border: 0; background: transparent; color: rgba(255,255,255,.85); cursor: pointer;
  font-size: .82rem; line-height: 1; padding: .45rem .55rem; border-radius: .3rem;
  text-align: left; text-decoration: none; width: 100%; white-space: nowrap;
  transition: background .12s, color .12s;
}
.gap-section-menu-item:hover { background: rgba(255,255,255,.1); color: #fff; }
/* E1 (25/08/2026) : le geste destructeur se distingue AU REPOS, plus
   seulement au survol — dans un menu où tout se ressemblait, Supprimer
   n'était repérable qu'en le frôlant. */
.gap-section-menu-item--danger { color: #f0a9a3; }
.gap-section-menu-item--danger:hover { background: rgba(192,57,43,.2); color: #f87171; }
.gap-section-menu-item svg { flex-shrink: 0; }

/* ── Zones d'en-tête révélables (mode édition) ─────────────────────────────
   Une zone vide est « repliée » (masquée) jusqu'à ce qu'on la révèle via le
   menu « + Zone ». Ne s'applique qu'en édition (le rendu visiteur n'a jamais
   de zone repliée : les zones vides ne sont tout simplement pas rendues). */
.admin-edit-mode .gap-zone--collapsed { display: none !important; }

/* Même règle à l'échelle d'un ÉLÉMENT (11/09/2026) : un champ repliable vide
   — l'étiquette d'une carte — ne se montre pas non plus en édition. Le bouton
   « Ajouter une étiquette » de la barre d'outils le révèle, et il se replie
   de lui-même s'il est laissé vide (gap-structure.js). `!important` : la
   pastille porte `d-inline-block`, lui-même en !important. */
.admin-edit-mode .gap-champ--replie { display: none !important; }

/* Le même geste hors d'une carte (14/09/2026) : la fiche de détail d'un élément
   n'a pas de barre d'outils ; le bouton se pose là où l'étiquette apparaîtra. */
.gap-champ-ajout {
  display: inline-flex; align-items: center; gap: .35rem; margin-bottom: 1rem;
  padding: .3rem .75rem; border: 1.5px dashed currentColor; border-radius: .35rem;
  background: transparent; color: inherit; font-size: .85rem; opacity: .65; cursor: pointer;
}
.gap-champ-ajout:hover,
.gap-champ-ajout:focus-visible { opacity: 1; }

/* « Trier par année », posé à côté de « ＋ Ajouter un élément » (frise).
   La même allure que son voisin, mais PAS sa classe : le gestionnaire d'ajout
   écoute `.gap-add-item`, et un bouton qui la portait ajoutait un élément vide
   à chaque tri (constaté au banc le 14/09/2026). */
.gap-trier-annees {
  border: 1.5px dashed var(--color-border, #ccc); background: #fff; cursor: pointer;
  padding: .5rem 1.25rem; border-radius: var(--radius, .5rem); font-weight: 600;
  color: var(--color-ink, #333);
}
.gap-trier-annees:hover { border-color: var(--color-secondary, #2a8); color: var(--color-secondary, #2a8); }
.gap-trier-annees:disabled { opacity: .6; cursor: progress; }
.gap-trier-annees svg { vertical-align: -2px; margin-right: .3rem; }
.gap-add-item-wrap > * + * { margin-left: .5rem; }

/* Doublon de titres sur un gabarit mono-bloc (héros, bloc CTA) : l'en-tête
   hérité ET le titre de l'item coexistent. L'en-tête est signalé d'un liseré
   — l'infobulle (title) explique quoi faire. Jamais rien n'est supprimé. */
.admin-edit-mode .gap-zone--double {
  outline: 2px dashed rgba(179, 38, 30, .45);
  outline-offset: 4px;
}

/* Bouton + menu « + Zone » dans le bandeau de la section. */
.gap-zone-add-wrap { position: relative; display: inline-flex; }
.gap-zone-add-wrap.is-empty { display: none; }
.gap-zone-add-btn {
  border: 0; background: transparent; color: rgba(255,255,255,.85); cursor: pointer;
  display: inline-flex; align-items: center; gap: .15rem;
  font-size: .72rem; font-weight: 600; line-height: 1;
  padding: .25rem .4rem; border-radius: 3px; white-space: nowrap;
  transition: background .15s, color .15s;
}
.gap-zone-add-btn:hover { background: rgba(255,255,255,.15); color: #fff; }
.gap-zone-add-menu {
  display: none; position: absolute; top: calc(100% + .35rem); right: 0; z-index: 100;
  background: #1e2329; border: 1px solid rgba(255,255,255,.1); border-radius: .4rem;
  box-shadow: 0 4px 16px rgba(0,0,0,.4); min-width: 150px; padding: .3rem;
  flex-direction: column; gap: .1rem;
}
.gap-zone-add-menu.open { display: flex; }
.gap-zone-add-item {
  display: flex; align-items: center; gap: .4rem;
  border: 0; background: transparent; color: rgba(255,255,255,.85); cursor: pointer;
  font-size: .82rem; line-height: 1; padding: .45rem .55rem; border-radius: .3rem;
  text-align: left; width: 100%; white-space: nowrap;
  transition: background .12s, color .12s;
}
.gap-zone-add-item::before { content: "＋"; opacity: .6; }
.gap-zone-add-item:hover { background: rgba(255,255,255,.1); color: #fff; }
.gap-zone-add-item.is-hidden { display: none; }

/* Sélecteur de largeur (compose les rangées) dans le menu ⋮ */
.gap-section-width-row {
  display: flex; align-items: center; gap: .15rem;
  padding: .35rem .55rem; margin-top: .15rem;
  border-top: 1px solid rgba(255,255,255,.08);
}
.gap-section-width-label {
  color: rgba(255,255,255,.55); font-size: .72rem; margin-right: auto;
}
.gap-section-width {
  border: 1px solid rgba(255,255,255,.15); background: transparent;
  color: rgba(255,255,255,.8); cursor: pointer;
  min-width: 26px; height: 24px; padding: 0 .25rem; border-radius: .3rem;
  font-size: .72rem; line-height: 1; transition: background .12s, border-color .12s, color .12s;
}
.gap-section-width:hover { background: rgba(255,255,255,.12); color: #fff; }
.gap-section-width.is-active {
  background: var(--color-primary, #c62828); border-color: var(--color-primary, #c62828); color: #fff;
}

/* Outils d'un item (poignée + duplication + suppression) — pill SVG, VISIBLE
   en permanence en mode édition (opacité douce, pleine au survol). */
.gap-item-tools {
  position: absolute; top: .35rem; right: .35rem; z-index: 15;
  display: flex; gap: .15rem; opacity: .72; transition: opacity .15s;
  /* Le cluster d'outils survole le contenu (ex. zone média de la timeline) :
     sa boîte ne doit PAS bloquer les clics, seuls ses boutons les captent. */
  pointer-events: none;
}
.gap-item-tools > * { pointer-events: auto; }
[data-item-id]:hover .gap-item-tools,
.gap-card:hover .gap-item-tools { opacity: 1; }

/* ── Grille de cartes : les outils de l'élément SORTENT de la carte ──────────
   Les caler à gauche (règle ci-dessus) ne suffisait pas. Mesuré au banc, à
   1440 px, grille à 5 par rangée : la grappe d'outils fait 199 px, les boutons
   de l'image en occupent 81 de plus — 280 px à loger dans une carte de 240 px.
   Chevauchement constaté : 47 px. Aucune disposition à l'intérieur de la carte
   ne peut absorber ça.
   Les outils COMMANDENT la carte, ils ne lui appartiennent pas : ils prennent
   une bande au-dessus, dans la gouttière de la rangée. Les boutons de l'image
   gardent le coin haut-droit de la photo — chaque grappe a sa zone.
   ⚠ La barre doit être émise DANS la cellule [data-item-id], hors du bloc
   carte : celle-ci a overflow:hidden et la rognerait. cf. $itemTools($item, true). */
.gap-item-tools--dessus {
  top: 0; left: 0; right: auto; bottom: auto;
  /* Mesuré : la barre fait 197 px et une cellule tombe à 183 px sur téléphone
     (grille à 6, repliée en 2 colonnes) — elle doit alors passer à la ligne
     plutôt que déborder sur la voisine. */
  flex-wrap: wrap; max-width: 100%;
}

/* Réserve la bande, SANS toucher au padding de la cellule.
   ⚠ La première version calculait `padding-top: calc(var(--bs-gutter-y) +
   1.9rem)` pour ne pas écraser la gouttière que Bootstrap y pose. Piège :
   `--bs-gutter-y` peut valoir « 0 » SANS UNITÉ (c'est ce que pose `.g-0`, et
   il s'hérite). Additionner un nombre sans unité à une longueur rend le calc
   INVALIDE : la déclaration entière est ignorée, la bande n'est plus réservée,
   et la barre retombe sur le contenu. Le repli `0rem` de `var()` ne pouvait
   pas jouer — la variable existe, elle est seulement inutilisable.
   Constaté sur les tuiles de logos, dont le conteneur hérite d'un `0` nu.
   On réserve donc par la MARGE du premier élément en flux, ce qui n'entre en
   conflit avec rien. La cellule passe en colonne flexible : les marges n'y
   fusionnent pas avec celle du parent, ce qui arriverait sur un bloc sans
   padding — et c'est précisément le cas d'une tuile de logo. */
.admin-edit-mode [data-item-id]:has(> .gap-item-tools--dessus) {
  display: flex; flex-direction: column;
}
.admin-edit-mode [data-item-id] > .gap-item-tools--dessus + * { margin-top: 1.75rem; }
.gap-item-handle,
.gap-item-champ,
.gap-item-avant,
.gap-item-dup,
.gap-item-move,
.gap-item-del {
  border: 0; background: rgba(22, 27, 34, .82); color: rgba(255,255,255,.8); cursor: pointer;
  width: 1.65rem; height: 1.65rem; border-radius: .3rem;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .12s, color .12s;
  box-shadow: 0 1px 4px rgba(0,0,0,.2);
}
.gap-item-handle { cursor: grab; }
.gap-item-handle:active { cursor: grabbing; }
.gap-item-handle:hover,
.gap-item-champ:hover,
.gap-item-avant:hover,
.gap-item-dup:hover,
.gap-item-move:hover,
.gap-item-del:hover { background: rgba(22, 27, 34, .96); color: #fff; }
.gap-item-del:hover { background: rgba(192,57,43,.85); }
/* « Mettre en avant » (15/09/2026) : l'étoile se REMPLIT quand l'élément
   l'est — l'état ne repose pas sur la seule couleur. */
.gap-item-avant.is-active,
.gap-item-avant.is-active:hover { color: #ffd166; }
.gap-item-avant.is-active svg { fill: currentColor; }

/* États focus clavier visibles sur tous les outils d'édition (accessibilité) */
.gap-section-dots:focus-visible,
.gap-section-menu-item:focus-visible,
.gap-section-handle:focus-visible,
.gap-section-move:focus-visible,
.gap-item-handle:focus-visible,
.gap-item-champ:focus-visible,
.gap-item-avant:focus-visible,
.gap-item-dup:focus-visible,
.gap-item-move:focus-visible,
.gap-item-del:focus-visible,
.gap-cta-link:focus-visible {
  outline: 2px solid var(--color-secondary, #2a8);
  outline-offset: 1px;
  color: #fff;
}
/* Au survol d'un item, ses outils restent visibles si l'un d'eux a le focus */
.gap-item-tools:focus-within { opacity: 1; }

/* Bouton « ajouter un élément » */
.gap-add-item-wrap { text-align: center; margin-top: 1.25rem; }
.gap-add-item {
  border: 1.5px dashed var(--color-border, #ccc); background: #fff; cursor: pointer;
  padding: .5rem 1.25rem; border-radius: var(--radius, .5rem); font-weight: 600;
  color: var(--color-ink, #333);
}
.gap-add-item:hover { border-color: var(--color-secondary, #2a8); color: var(--color-secondary, #2a8); }

/* Bloc « ajouter une section » (fin de page) */
.gap-add-section {
  display: flex; justify-content: center; align-items: center; gap: .5rem;
  flex-wrap: wrap; margin: 2.5rem auto; padding: 1.5rem;
  border: 2px dashed var(--color-border, #ccc); border-radius: var(--radius, .5rem);
  max-width: 600px;
}
.gap-add-section-type {
  padding: .45rem .75rem; border: 1px solid var(--color-border, #ccc);
  border-radius: var(--radius, .5rem); font-weight: 600; min-width: 220px;
}
.gap-add-section-btn {
  border: 0; background: var(--color-secondary, #2a8); color: #fff; cursor: pointer;
  padding: .5rem 1.25rem; border-radius: var(--radius, .5rem); font-weight: 700;
}
.gap-add-section-btn:hover { filter: brightness(1.08); }

/* CTA éditable (mode édition) : libellé inline + bouton lien 🔗
   `flex-wrap: wrap` : sur téléphone, le bouton, sa pastille de lien et le
   badge « lien manquant » ne tiennent pas côte à côte. Ils débordaient — et
   pas seulement de leur cellule : la PAGE ENTIÈRE gagnait 10 px de largeur,
   d'où la bande blanche à droite et le défilement horizontal du document.
   Constaté par Pierre-Louis le 12/08 sur dev.gapberstett.fr, reproduit à
   375 px : document 385 px pour un écran de 375. Le défaut n'existe qu'en
   mode ÉDITION — un visiteur ne voit ni la pastille ni le badge. */
.gap-cta-edit { display: inline-flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.gap-cta-edit [data-editable] { min-width: 4rem; }
/* La cellule qui l'accueille doit pouvoir RÉTRÉCIR. `.section-heading` est une
   grille, et un élément de grille vaut `min-width: auto` par défaut : il refuse
   de passer sous la largeur de son contenu, quel que soit l'espace réel. Sans
   cette ligne, autoriser le retour à la ligne ne servirait à rien. */
.section-heading-cta { min-width: 0; }
.gap-cta-link {
  border: 0; background: rgba(22,27,34,.85); color: #fff; cursor: pointer;
  width: 1.7rem; height: 1.7rem; border-radius: 50%; font-size: .85rem; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background .12s, transform .12s; flex-shrink: 0;
}
.gap-cta-link:hover { background: var(--color-secondary, #2a8); transform: scale(1.08); }

/* Sélecteur de liens internes (GapLinkPicker) */
/* ── Sélecteur de liens ────────────────────────────────────────────────────
   Refonte du 26/08/2026 : la recherche et la liste passent devant, l'adresse
   libre descend en pied. Une entrée par LIGNE (libellé à gauche, adresse en
   gris à droite) — l'ancienne mise en deux lignes ne laissait tenir que huit
   pages avant de devoir défiler. */
.gap-linkpicker-overlay {
  display: none; position: fixed; inset: 0; z-index: var(--z-surcouche);
  background: rgba(0,0,0,.45); align-items: center; justify-content: center; padding: 1rem;
}
.gap-linkpicker-overlay.open { display: flex; }
.gap-linkpicker {
  background: #fff; border-radius: .6rem; box-shadow: 0 10px 40px rgba(0,0,0,.3);
  width: 100%; max-width: 560px; max-height: 80vh; display: flex; flex-direction: column;
  overflow: hidden;
}
.gap-linkpicker-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: .75rem 1rem; border-bottom: 1px solid var(--color-border, #e5e5e5);
}
.gap-linkpicker-close {
  border: 0; background: transparent; font-size: 1.5rem; line-height: 1; cursor: pointer;
  color: var(--color-ink, #333); padding: 0 .25rem;
}
.gap-linkpicker-search {
  margin: .75rem 1rem; padding: .5rem .7rem; border: 1px solid var(--color-border, #ccc);
  border-radius: .35rem; font-size: .9rem;
}
.gap-linkpicker-list {
  overflow-y: auto; padding: 0 1rem 1rem;
  /* La liste porte le défilement, pas la modale : l'adresse libre du pied
     reste visible quelle que soit la longueur de la liste. */
  flex: 1 1 auto; min-height: 6rem;
}
.gap-linkpicker-group + .gap-linkpicker-group { margin-top: .6rem; }
.gap-linkpicker-group-label {
  font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  color: var(--color-secondary, #2a8); margin: 0 0 .2rem; padding: .35rem .15rem .15rem;
  position: sticky; top: 0; background: #fff;
}
/* Une entrée = une ligne. L'adresse ne prend la place qu'il lui reste. */
.gap-linkpicker-item {
  display: flex; align-items: baseline; gap: .6rem;
  width: 100%; text-align: left; border: 0; background: transparent; cursor: pointer;
  padding: .3rem .5rem; border-radius: .35rem; transition: background .12s;
}
.gap-linkpicker-item:hover,
.gap-linkpicker-item:focus-visible { background: var(--color-surface, #f3f3f3); outline: none; }
.gap-linkpicker-item:focus-visible { box-shadow: inset 0 0 0 2px var(--color-secondary, #2a8); }
.gap-linkpicker-item-label {
  font-size: .9rem; font-weight: 600; color: var(--color-ink, #222);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 1 auto;
}
.gap-linkpicker-item-url {
  font-size: .75rem; color: #595959; margin-left: auto;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right;
  flex: 0 1 auto; min-width: 0;
  /* PAS de `direction: rtl` pour tronquer par la gauche : sur une adresse,
     il déplace visuellement la barre oblique de tête à la fin — « /fr/a-propos »
     s'affichait « fr/a-propos/ ». Une adresse fausse à l'écran vaut moins
     qu'une adresse tronquée (constaté à la capture du 26/08). */
}
/* La cible ACTUELLE du lien qu'on modifie : on doit voir ce qu'on remplace. */
.gap-linkpicker-item.is-current { background: var(--color-surface, #f3f3f3); }
.gap-linkpicker-item.is-current .gap-linkpicker-item-label::before {
  content: "✓ "; color: var(--color-secondary, #2a8); font-weight: 700;
}
/* Adresse libre : second recours, donc en pied et en retrait. */
.gap-linkpicker-ext {
  display: flex; flex-wrap: wrap; gap: .5rem; align-items: center;
  padding: .6rem 1rem; border-top: 1px solid var(--color-border, #e5e5e5);
  background: var(--color-surface, #f7f7f7);
}
.gap-linkpicker-ext-label { font-size: .78rem; color: #595959; white-space: nowrap; }
.gap-linkpicker-url {
  flex: 1 1 200px; padding: .4rem .6rem; border: 1px solid var(--color-border, #ccc);
  border-radius: .35rem; font-size: .85rem; background: #fff;
}
.gap-linkpicker-apply {
  border: 0; background: var(--color-secondary, #2a8); color: #fff; cursor: pointer;
  padding: .4rem .9rem; border-radius: .35rem; font-weight: 700; font-size: .82rem;
}
.gap-linkpicker-apply:hover { filter: brightness(1.08); }
.gap-linkpicker-empty { color: #595959; font-style: italic; padding: 1rem .25rem; margin: 0; }

/* Mise en évidence d'un élément fraîchement créé (après ajout) */
.gap-just-added {
  animation: gapJustAdded 2.5s ease-out;
  border-radius: var(--radius, .5rem);
}
@keyframes gapJustAdded {
  0%   { box-shadow: 0 0 0 3px var(--color-secondary, #2a8), 0 0 0 8px rgba(34,170,136,.35); }
  15%  { box-shadow: 0 0 0 3px var(--color-secondary, #2a8), 0 0 0 14px rgba(34,170,136,.25); }
  100% { box-shadow: 0 0 0 0 rgba(34,170,136,0), 0 0 0 0 rgba(34,170,136,0); }
}

/* Indices visuels Sortable */
.sortable-ghost { opacity: .4; }
.sortable-chosen { outline: 2px dashed var(--color-secondary, #2a8); outline-offset: 2px; }

/* ── Modale d'options de section (FO) : widgets repris du back-office ──────
   admin.css n'étant pas chargé en front, on redéfinit ici les styles des
   widgets utilisés dans la modale : éditeur de position du fond + aperçu
   du cadre photo. Les formes .frame-* viennent déjà de ce fichier. */
.bg-position-editor {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background-color: #E5E7EB;
  background-size: cover;
  background-repeat: no-repeat;
  border: 1px solid #D1D5DB;
  border-radius: 8px;
  cursor: crosshair;
  overflow: hidden;
}
.bg-position-editor .position-crosshair {
  position: absolute;
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  border: 2px solid #fff;
  border-radius: 50%;
  background: rgba(206,43,30,.85);
  box-shadow: 0 0 0 2px rgba(0,0,0,.25);
  cursor: grab;
}
.bg-position-editor .position-crosshair:active { cursor: grabbing; }
.frame-preview {
  width: 96px;
  height: 96px;
  background: linear-gradient(135deg, var(--color-secondary, #2A7A3C), var(--color-primary, #CE2B1E));
}

/* Calque d'aperçu de l'overlay (couleur + opacité) sur l'éditeur de position */
.bg-position-editor .bg-overlay-preview {
  position: absolute;
  inset: 0;
  background: transparent;
  pointer-events: none;
}

/* ── Sélecteur du type de section — inserteur 2 niveaux (BO + FO) ──────── */
.gap-type-search { position: sticky; top: 0; z-index: 2; margin: -.25rem 0 .9rem; }
.gap-type-search-input {
  width: 100%; padding: .55rem .8rem;
  border: 1px solid var(--color-border, #e2e2e2); border-radius: 8px;
  font-size: .9rem; background: #fff;
}
.gap-type-search-input:focus {
  outline: none; border-color: var(--color-primary, #CE2B1E);
  box-shadow: 0 0 0 2px rgba(206,43,30,.18);
}
.gap-type-group { margin: 0 0 1.1rem; }
.gap-type-group:last-child { margin-bottom: 0; }
.gap-type-group-title {
  margin: 0 0 .5rem; font-size: .72rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; color: #9aa0a6;
}
/* Fix scroll des modales scrollables dont le corps est enveloppé dans un <form>
   (ex. modale d'ajout de section en BO). Sans ça, le <form> — enfant flex direct
   de .modal-content — ne se contraint pas en hauteur et le .modal-body ne peut
   pas défiler (contenu rogné). On rend le form transparent au flux flex. */
.modal-dialog-scrollable .modal-content > form {
  display: flex;
  flex-direction: column;
  min-height: 0;
  flex: 1 1 auto;
  overflow: hidden;
}
.gap-type-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: .6rem;
}
@media (max-width: 575.98px) { .gap-type-grid { grid-template-columns: 1fr; } }
.gap-type-empty { margin: 1rem 0 0; font-size: .85rem; color: #6b7280; text-align: center; }
.gap-type-card { display: block; margin: 0; cursor: pointer; }
.gap-type-radio { position: absolute; opacity: 0; pointer-events: none; }
.gap-type-card-inner {
  display: flex; gap: .75rem; align-items: flex-start;
  height: 100%; padding: .75rem;
  border: 1px solid var(--color-border, #e2e2e2); border-radius: 10px;
  background: #fff; transition: border-color .15s, box-shadow .15s, transform .1s;
}
.gap-type-card:hover .gap-type-card-inner {
  border-color: var(--color-primary, #CE2B1E);
  box-shadow: 0 4px 14px rgba(0,0,0,.08);
}
.gap-type-radio:checked + .gap-type-card-inner {
  border-color: var(--color-primary, #CE2B1E);
  box-shadow: 0 0 0 2px rgba(206,43,30,.25);
}
.gap-type-radio:focus-visible + .gap-type-card-inner {
  outline: 2px solid var(--color-primary, #CE2B1E); outline-offset: 2px;
}
.gap-type-card-icon {
  flex: 0 0 auto; width: 48px; height: 32px;
  color: var(--color-secondary, #2a7a3c); margin-top: 2px;
}
.gap-type-icon { width: 48px; height: 32px; display: block; }
.gap-type-card-text { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.gap-type-card-label { font-weight: 700; font-size: .9rem; color: var(--color-ink, #222); }
.gap-type-card-desc { font-size: .76rem; line-height: 1.3; color: #6b7280; }
.gap-type-card.is-adding { opacity: .55; pointer-events: none; }
/* Écran 1 de l'inserteur : tuiles d'intention éditoriale. */
.gap-type-intents {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: .6rem;
}
@media (max-width: 575.98px) { .gap-type-intents { grid-template-columns: 1fr; } }
.gap-type-intent {
  display: flex; gap: .75rem; align-items: flex-start; text-align: left;
  width: 100%; padding: .9rem .8rem; margin: 0;
  border: 1px solid var(--color-border, #e2e2e2); border-radius: 10px;
  background: #fff; cursor: pointer; font: inherit;
  transition: border-color .15s, box-shadow .15s;
}
.gap-type-intent:hover, .gap-type-intent:focus-visible {
  border-color: var(--color-primary, #CE2B1E);
  box-shadow: 0 4px 14px rgba(0,0,0,.08);
}
.gap-type-intent:focus-visible { outline: 2px solid var(--color-primary, #CE2B1E); outline-offset: 2px; }
/* Écran 2 : bouton retour vers les intentions. */
.gap-type-back {
  display: inline-flex; align-items: center; gap: .35rem;
  margin: 0 0 .8rem; padding: .3rem .6rem;
  border: 1px solid var(--color-border, #e2e2e2); border-radius: 999px;
  background: #fff; cursor: pointer;
  font-size: .8rem; font-weight: 600; color: #4b5563;
}
.gap-type-back:hover { border-color: var(--color-primary, #CE2B1E); color: var(--color-ink, #222); }
.gap-type-kind { margin: 0 0 .8rem; }
.gap-type-kind:last-child { margin-bottom: 0; }
.gap-type-kind-title {
  margin: 0 0 .4rem; font-size: .72rem; font-weight: 600;
  letter-spacing: .04em; text-transform: uppercase; color: #b0b6bc;
}
/* Rangée de styles (variantes) révélée sous le modèle sélectionné.
   ⚠️ [hidden] doit gagner : display:block écraserait l'attribut sinon. */
.gap-type-styles {
  display: block; margin-top: .5rem; padding: .6rem .75rem;
  border: 1px solid var(--color-border, #e2e2e2); border-top-style: dashed;
  border-radius: 0 0 10px 10px; background: #fafaf8;
}
.gap-type-styles[hidden] { display: none; }
/* Les cartes ne s'étirent pas à la hauteur de la rangée révélée ailleurs. */
.gap-type-grid { align-items: start; }
.gap-type-styles-title {
  display: block; margin-bottom: .35rem;
  font-size: .68rem; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; color: #9aa0a6;
}
.gap-type-styles-row { display: flex; flex-wrap: wrap; gap: .35rem; }
.gap-type-style {
  padding: .25rem .6rem; border: 1px solid var(--color-border, #e2e2e2);
  border-radius: 999px; background: #fff; cursor: pointer;
  font-size: .76rem; font-weight: 600; color: #4b5563;
}
.gap-type-style:hover { border-color: var(--color-primary, #CE2B1E); }
.gap-type-style.is-active {
  border-color: var(--color-primary, #CE2B1E); color: var(--color-primary, #CE2B1E);
  background: rgba(206,43,30,.06);
}
.gap-type-style-add {
  display: block; width: 100%; margin-top: .55rem; padding: .4rem .8rem;
  border: 0; border-radius: 8px; background: var(--color-primary, #CE2B1E);
  color: #fff; font-size: .8rem; font-weight: 700; cursor: pointer;
}
.gap-type-style-add:hover { filter: brightness(1.08); }
/* BO : le formulaire a déjà son bouton « Ajouter » — pas de doublon. */
#addSectionModal .gap-type-style-add { display: none; }
.gap-add-section-open {
  border: 1px dashed var(--color-border, #ccc); background: #fff;
  border-radius: var(--radius, 8px); padding: .6rem 1.3rem;
  cursor: pointer; font-weight: 600; color: var(--color-ink, #222);
}
.gap-add-section-open:hover { border-color: var(--color-primary, #CE2B1E); color: var(--color-primary, #CE2B1E); }

/* ── Galerie intégrée (section gallery-embed) ─────────────────────────── */
.gap-embed-item { display: block; overflow: hidden; border-radius: var(--radius, .5rem); cursor: zoom-in; position: relative; }
.gap-embed-item img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .3s ease; }
.gap-embed-item:hover img { transform: scale(1.04); }

/* Mode grille : carrés réguliers */
.gap-embed-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: .5rem;
}
.gap-embed-grid .gap-embed-item { aspect-ratio: 1 / 1; }

/* Mode mosaïque : colonnes (masonry natif) */
.gap-embed-masonry { column-count: 4; column-gap: .5rem; }
@media (max-width: 991.98px) { .gap-embed-masonry { column-count: 3; } }
@media (max-width: 575.98px) { .gap-embed-masonry { column-count: 2; } }
.gap-embed-masonry .gap-embed-item { margin: 0 0 .5rem; break-inside: avoid; }

/* Mode carrousel : bande horizontale scroll-snap */
.gap-embed-carousel {
  display: flex; gap: .5rem; overflow-x: auto; scroll-snap-type: x mandatory;
  padding-bottom: .5rem; scrollbar-width: thin;
}
.gap-embed-carousel .gap-embed-item {
  flex: 0 0 auto; width: clamp(200px, 40%, 320px); aspect-ratio: 4 / 3; scroll-snap-align: start;
  border-radius: 10px; overflow: hidden;
}
/* Une portion de la photo suivante doit TOUJOURS dépasser : c'est le signal
   qui dit « ça défile », en permanence et sans rien expliquer. Sans cette
   marge, une largeur de conteneur bien tombée alignait pile les vignettes et
   la bande paraissait complète — plus rien n'invitait à faire défiler. */
.gap-embed-carousel {
  scroll-padding-left: .5rem;
  padding-right: 12%;
  /* Le fondu à droite : la photo qui dépasse s'efface au lieu d'être coupée
     net. Un bord franc se lit comme une fin, un dégradé comme une suite.
     Le masque est calé sur la MÊME valeur que le padding (12 %) : en fin de
     course, la zone estompée ne recouvre plus que ce vide, jamais la dernière
     photo — sinon elle paraîtrait délavée une fois le défilement terminé. */
  -webkit-mask-image: linear-gradient(to right, #000 88%, transparent 99%);
  mask-image: linear-gradient(to right, #000 88%, transparent 99%);
}
/* Idem sur la bande de miniatures du mode « Photo à la une » : même geste,
   même question posée au visiteur, donc même réponse visuelle. */
.gap-hero-thumbs {
  -webkit-mask-image: linear-gradient(to right, #000 90%, transparent 100%);
  mask-image: linear-gradient(to right, #000 90%, transparent 100%);
}

/* ── Mode magazine : rythme éditorial ──────────────────────────────────────
   Grille de 6 colonnes. Chaque photo reçoit du serveur sa place exacte
   (--gap-mag-c : colonne / --gap-mag-r : rangée). La composition est calculée
   en PHP (cf. gallery-embed.php) — un empilement de `nth-child(10n+7)` serait
   illisible et se dérèglerait à la moindre photo ajoutée au milieu. */
.gap-embed-magazine {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-auto-rows: clamp(110px, 13vw, 190px);
  gap: .5rem;
}
.gap-embed-magazine .gap-embed-item {
  /* Placement ENTIÈREMENT explicite (colonne ET rangée), fourni par le
     serveur. Laisser la grille placer d'elle-même creusait un trou : son
     curseur ne recule jamais, et une photo réclamant une colonne déjà
     dépassée était renvoyée à la rangée suivante. */
  grid-column: var(--gap-mag-c, auto / span 3);
  grid-row: var(--gap-mag-r, auto);
  border-radius: 10px;
  overflow: hidden;
}
.gap-embed-magazine .gap-embed-item img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* Le grand visuel et son alternance gauche/droite sont portés par les
   variables ci-dessus : rien à ajouter ici, la classe .gap-mag-lead ne sert
   plus qu'au repérage (et à la mise en page mobile). */

@media (max-width: 767.98px) {
  /* Deux colonnes : à cette largeur, un sixième d'écran ne montre plus rien
     d'une photo. La composition à six colonnes est donc entièrement reprise
     — d'où le retour au placement automatique, seul capable de recomposer
     sans laisser de vide. La hiérarchie survit : le grand visuel reste
     pleine largeur, sur deux rangées. */
  .gap-embed-magazine .gap-embed-item {
    grid-column: auto / span 3;
    grid-row: auto;
  }
  .gap-embed-magazine .gap-mag-lead {
    grid-column: 1 / span 6;
    grid-row: span 2;
  }
}

/* ── Galerie d'un ÉLÉMENT de liste ─────────────────────────────────────────
   Les affichages du socle, ramenés à l'échelle d'une demi-colonne. Le CADRE du
   thème est porté par ce conteneur : une rangée à galerie doit ressembler aux
   rangées à photo simple qui l'entourent. */
.gap-item-gallery { position: relative; }

/* Débordement borné et ESTOMPÉ. La colonne média est positionnée en absolu et
   centrée verticalement : rien ne borne sa hauteur, si bien qu'une mosaïque
   d'album entier débordait de sa rangée et recouvrait le contenu au-dessus.
   Le plafond de photos réduit le cas ; il ne le supprime pas (dix portraits en
   colonne restent hauts). On borne donc la hauteur et on efface le bas en
   fondu, plutôt que de couper net — un bord franc se lit comme une erreur de
   mise en page, un dégradé comme « la suite est ailleurs ». Le lien
   « + N autres » juste dessous dit où.
   `mask` seulement quand ça déborde vraiment : sinon le bas de la dernière
   photo serait délavé sans raison. */
.gap-item-gallery-bloc { position: relative; }
.gap-item-gallery {
  max-height: min(70vh, 520px);
  overflow: hidden;
}
/* Classe posée par le navigateur (GapGalerieFondu) quand le contenu dépasse
   réellement la hauteur bornée — le serveur ne peut pas le savoir. */
.gap-item-gallery--deborde {
  -webkit-mask-image: linear-gradient(to bottom, #000 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 78%, transparent 100%);
}
/* SUR UNE PAGE, NI BORNE NI FONDU (14/09/2026). La fiche d'un élément
   (.t-costume-detail, que sa fenêtre d'aperçu reprend telle quelle) et
   l'article (.gap-news-album) délèguent au même moteur que les encarts, mais
   leur galerie est dans le flux : aucune colonne en absolu à contenir, et
   toutes les photos y sont montrées (plafond = l'album). La borne n'y
   protégeait rien, elle COUPAIT. Mesuré : la bande de miniatures d'« À la
   une » cachée en entier sous une photo en portrait (92 px coupés sur la
   fiche, 30 dans sa fenêtre), et deux photos d'un collage de huit tronquées
   puis estompées — sans lien « + N autres », puisqu'il n'y en avait pas d'autres.
   Le masque est levé avec la borne : la classe de débordement est posée par
   le navigateur, et ce que la page montre ne doit pas dépendre du moment où
   il l'a vérifiée. */
.t-costume-detail .gap-item-gallery,
.gap-news-album .gap-item-gallery {
  max-height: none;
  overflow: visible;
  -webkit-mask-image: none;
  mask-image: none;
}
/* Le lien de débordement ne doit PAS être estompé : il est la sortie de
   secours. Il vit donc hors du bloc masqué. */
.gap-item-gallery-plus { position: relative; z-index: 2; }

/* Mosaïque : deux colonnes plutôt que les quatre du plein écran — à cette
   largeur, une colonne de 250 px n'en laisserait qu'une seule. */
.gap-item-gallery .gap-embed-masonry-v2 { column-width: 130px; column-gap: .5rem; }
.gap-item-gallery .gap-embed-masonry-v2 .gap-embed-item { margin-bottom: .5rem; }

/* Grille : trois vignettes par ligne, l'encart n'a pas la place d'en aligner six. */
.gap-item-gallery .gap-embed-grid {
  grid-template-columns: repeat(3, 1fr);
  gap: .4rem;
}

/* Collage et mur : mêmes gestes, échelle réduite. */
.gap-item-gallery .gap-embed-collage { gap: .6rem; }
.gap-item-gallery .gap-collage-t0,
.gap-item-gallery .gap-collage-t1,
.gap-item-gallery .gap-collage-t2 { width: clamp(90px, 30%, 150px); }
.gap-item-gallery .gap-embed-mur {
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: .7rem;
}
.gap-item-gallery .gap-embed-mur figcaption { font-size: .74rem; padding: .35rem .1rem .4rem; }

/* « À la une » : la bande de miniatures est plus basse, sinon elle mange la
   place du grand visuel dans un encart. */
.gap-item-gallery .gap-hero-thumbs { gap: .3rem; }
.gap-item-gallery .gap-hero-thumb { width: 52px; height: 38px; }

/* Repère des photos non montrées : discret, mais présent — sans lui, la limite
   se lirait comme un album incomplet. */
.gap-item-gallery-plus { margin: .5rem 0 0; text-align: center; }
.gap-item-gallery-plus a {
  font-size: .82rem; color: #6a6259; text-decoration: none;
  border-bottom: 1px dotted currentColor; cursor: zoom-in;
}
.gap-item-gallery-plus a:hover { color: var(--color-primary, #CE2B1E); }

/* ═══ Diaporama — affichage « hero-solo » ═══════════════════════════════════
   Le cadre N'EST PAS défini ici, et c'est tout le propos : il vient du thème
   (classes de cadre) et de la classe d'image du gabarit — `gap-card-img`
   recadre en 4/3, `gap-person-img` en carré, `gap-featured-img` arrondit. Le
   serveur les transmet au diaporama, qui devient indiscernable de la photo
   simple qu'il remplace.

   L'ancien « À la une seule » faisait l'inverse : hauteur libre, `contain`,
   coins arrondis en propre, ligne de comptage dessous. Choisir une galerie
   changeait la MISE EN PAGE au lieu de changer le contenu. */
.gap-item-diapo { position: relative; }
.gap-diapo .splide__slide { line-height: 0; }
.gap-diapo-vue { display: block; line-height: 0; cursor: zoom-in; }
/* Carte ENTIÈREMENT cliquable (14/09/2026) : la photo mène où mène la carte.
   La vue n'y est plus un lien (_gallery-diapo.php) ; la piste du diaporama
   repasse au-dessus de la zone cliquable pour rester glissable au doigt, et
   gap.js renvoie le toucher au bouton. Seulement une fois Splide monté : sans
   JavaScript, la zone cliquable couvre la photo et le lien fait le travail. */
.gap-card--cliquable .gap-diapo-vue { cursor: pointer; }
.gap-card--cliquable .gap-diapo.is-initialized .splide__track { position: relative; z-index: 2; }
.gap-diapo-vue img { width: 100%; display: block; }

/* ── La hauteur du cadre traverse le carrousel ─────────────────────────────
   Mesuré au banc, sur les cartes « Costumes » : le cadre porte un ratio 3/4
   (réglage de section), et la photo simple le remplit — 164 × 219. Le
   diaporama, lui, tombait à 164 × 123 : les quatre niveaux que Splide
   intercale (piste, liste, diapositive, lien) n'ont pas de hauteur, la chaîne
   des `height: 100%` s'y rompait, et l'image retombait sur son propre ratio.
   Deux cadres différents pour la même carte, exactement le défaut à corriger.

   Cadre SANS hauteur définie (le cas courant) : `height: 100%` se résout en
   `auto` et l'image garde le ratio de sa classe de gabarit. Rien ne change. */
.gap-item-diapo > .gap-diapo,
.gap-diapo .splide__track,
.gap-diapo .splide__list,
.gap-diapo .splide__slide,
.gap-diapo .splide__slide > .gap-diapo-vue,
.gap-diapo .splide__slide > .gap-diapo-vue > img { height: 100%; }

/* Aucun gabarit de référence (bannière pleine largeur, galerie de section) :
   un cadre neutre, mais FIXE. Sans ratio imposé, chaque photo donnerait au
   bloc une hauteur différente et la page sauterait à chaque défilement. */
.gap-diapo-img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; }

/* ── Repli si le montage n'a pas lieu ──────────────────────────────────────
   Splide pose `visibility: hidden` sur son conteneur tant qu'il n'est pas
   monté. Sur une carte, un JS en échec ne coûterait donc pas le défilement :
   il ferait DISPARAÎTRE l'image. Non monté, le diaporama montre sa première
   photo comme une photo simple — les suivantes sont clipées par la piste.
   Spécificité doublée à dessein : splide.min.css est chargé APRÈS gap.css
   (au fil des sections), une règle à un seul sélecteur perdrait. */
.gap-diapo.splide { visibility: visible; }
.gap-diapo:not(.is-initialized) .splide__slide { width: 100%; }

/* Pastilles POSÉES SUR l'image — Splide les place déjà en absolu : elles
   n'ajoutent pas un pixel de hauteur, et le cadre reste au millimètre celui
   de la photo simple. Assombries d'une ombre portée : sur une photo claire,
   des pastilles blanches sont invisibles. */
.gap-diapo .splide__pagination { padding: 0 .5rem .15rem; }
.gap-diapo .splide__pagination__page {
  background: rgba(255, 255, 255, .8);
  box-shadow: 0 1px 3px rgba(20, 18, 16, .5);
  width: 6px; height: 6px; margin: 2px; opacity: 1;
}
.gap-diapo .splide__pagination__page.is-active {
  background: #fff; transform: scale(1.3);
}

/* ── Filtres par étiquette (mode « Filtrable ») ────────────────────────────
   Des filtres, pas des onglets : ils agissent sur la grille en place. */
.gap-filtres {
  display: flex; flex-wrap: wrap; gap: .4rem;
  margin-bottom: 1rem;
}
.gap-filtre {
  border: 1px solid var(--color-border, #ddd);
  background: #fff; color: #333;
  border-radius: 999px;
  padding: .3rem .8rem;
  font-size: .85rem; line-height: 1.3;
  cursor: pointer;
  transition: background .18s ease, color .18s ease, border-color .18s ease;
}
.gap-filtre:hover { border-color: var(--color-primary, #CE2B1E); color: var(--color-primary, #CE2B1E); }
.gap-filtre.is-active {
  background: var(--color-primary, #CE2B1E); border-color: var(--color-primary, #CE2B1E); color: #fff;
}
/* L'effectif reste lisible sur le fond plein de l'état actif. */
.gap-filtre-n { opacity: .65; font-variant-numeric: tabular-nums; margin-left: .15rem; }
.gap-filtre.is-active .gap-filtre-n { opacity: .8; }
/* Photo écartée par le filtre : retirée du flux, pas seulement transparente —
   sinon la grille garderait des trous là où les photos masquées se tenaient. */
.gap-embed-grid .gap-embed-item[hidden] { display: none; }

/* ── Chronologie ───────────────────────────────────────────────────────────
   Un fil vertical, une année par groupe. Le fil n'est PAS décoratif : il relie
   visuellement des groupes que rien d'autre ne rattacherait. */
.gap-embed-timeline { position: relative; padding-left: 1.6rem; }
.gap-embed-timeline::before {
  content: ""; position: absolute; left: .35rem; top: .6rem; bottom: .6rem;
  width: 2px; background: var(--color-border, #e2ded8);
}
.gap-tl-groupe { position: relative; margin-bottom: 2rem; }
.gap-tl-groupe:last-child { margin-bottom: 0; }
.gap-tl-groupe::before {
  content: ""; position: absolute; left: -1.6rem; top: .55rem;
  width: .75rem; height: .75rem; border-radius: 50%;
  background: var(--color-primary, #CE2B1E);
  box-shadow: 0 0 0 3px #fff;
}
.gap-tl-annee {
  font-size: 1.35rem; margin: 0 0 .6rem;
  display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap;
}
.gap-tl-n { font-size: .8rem; font-weight: 400; color: #7a7a7a; }
.gap-tl-lot { display: flex; flex-wrap: wrap; gap: .5rem; }
.gap-tl-item {
  flex: 0 0 auto; width: clamp(90px, 14vw, 150px); aspect-ratio: 4 / 3;
  border-radius: 8px; overflow: hidden; display: block; cursor: zoom-in;
}
.gap-tl-item img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ── Collage ───────────────────────────────────────────────────────────────
   Composition libre : trois tailles, des angles stables (calculés côté serveur
   à partir de l'identifiant de la photo). */
.gap-embed-collage {
  display: flex; flex-wrap: wrap; align-items: flex-start;
  justify-content: center; gap: .9rem;
  padding: .8rem 0;
}
.gap-embed-collage .gap-embed-item {
  transform: rotate(var(--gap-col-a, 0deg));
  transition: transform .25s ease, box-shadow .25s ease, z-index 0s;
  box-shadow: 0 8px 22px rgba(20, 18, 16, .16);
  border-radius: 6px;
  background: #fff; padding: .4rem .4rem 1rem;   /* la marge blanche d'un tirage */
  position: relative; z-index: 1;
}
/* Au survol, la photo se redresse et passe devant : c'est le geste qu'on
   attend d'un tirage posé sur une table. */
.gap-embed-collage .gap-embed-item:hover {
  transform: rotate(0deg) scale(1.03);
  box-shadow: 0 16px 36px rgba(20, 18, 16, .26);
  z-index: 2;
}
.gap-embed-collage .gap-embed-item img {
  display: block; border-radius: 3px;
  width: 100%; height: auto;
}
.gap-embed-collage .gap-collage-t0 { flex: 0 0 auto; width: clamp(140px, 20vw, 230px); }
.gap-embed-collage .gap-collage-t1 { flex: 0 0 auto; width: clamp(170px, 26vw, 300px); }
.gap-embed-collage .gap-collage-t2 { flex: 0 0 auto; width: clamp(120px, 16vw, 190px); }

/* ── Mur de souvenirs ──────────────────────────────────────────────────────
   Des tirages légendés, légèrement penchés. La légende fait partie du rendu :
   c'est ce qui distingue ce mode d'une mosaïque. */
.gap-embed-mur {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1.2rem;
  padding: .5rem 0;
}
.gap-mur-item {
  margin: 0;
  background: #fff;
  padding: .5rem .5rem .2rem;
  border-radius: 4px;
  box-shadow: 0 6px 18px rgba(20, 18, 16, .15);
  transform: rotate(var(--gap-mur-a, 0deg));
  transition: transform .25s ease, box-shadow .25s ease;
}
.gap-mur-item:hover {
  transform: rotate(0deg) translateY(-3px);
  box-shadow: 0 14px 30px rgba(20, 18, 16, .24);
}
.gap-mur-item > a { display: block; cursor: zoom-in; }
.gap-mur-item img {
  width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block;
  border-radius: 2px;
}
.gap-mur-item figcaption {
  font-size: .82rem; line-height: 1.35; color: #4a453e;
  padding: .5rem .15rem .55rem; text-align: center;
  /* Manuscrite si le thème en fournit une, sinon la police du site. */
  font-family: var(--gap-font-hand, inherit);
}

@media (prefers-reduced-motion: reduce) {
  .gap-embed-collage .gap-embed-item,
  .gap-mur-item { transition: none; }
  .gap-embed-collage .gap-embed-item:hover { transform: rotate(var(--gap-col-a, 0deg)); }
  .gap-mur-item:hover { transform: rotate(var(--gap-mur-a, 0deg)); }
}

/* ── Mode albums : une carte par album ─────────────────────────────────────
   Seul mode qui ne montre pas des photos mais des entrées vers des albums. */
.gap-embed-albums {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 1rem;
}
.gap-album-card {
  display: flex; flex-direction: column;
  text-decoration: none; color: inherit;
  border-radius: 12px; overflow: hidden;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.10);
  transition: transform .2s ease, box-shadow .2s ease;
}
.gap-album-card:hover, .gap-album-card:focus-visible {
  transform: translateY(-3px);
  box-shadow: 0 8px 22px rgba(0,0,0,.16);
}
.gap-album-cover {
  display: block; position: relative;
  aspect-ratio: 4 / 3; background: #ececec; overflow: hidden;
}
.gap-album-cover img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .35s ease;
}
.gap-album-card:hover .gap-album-cover img { transform: scale(1.04); }
.gap-album-cover-vide {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  color: #9a9a9a; font-size: .85rem;
}
.gap-album-infos { display: block; padding: .7rem .85rem .85rem; }
.gap-album-titre {
  display: block; font-weight: 600; line-height: 1.3; color: #222;
}
.gap-album-meta {
  display: block; margin-top: .2rem;
  font-size: .8rem; color: #7a7a7a;
}
@media (prefers-reduced-motion: reduce) {
  .gap-album-card, .gap-album-cover img { transition: none; }
  .gap-album-card:hover { transform: none; }
  .gap-album-card:hover .gap-album-cover img { transform: none; }
}

/* Mode lightbox : couverture unique + badge « +N photos » */
.gap-embed-cover .gap-embed-item { display: none; }
.gap-embed-cover .gap-embed-item:first-child {
  display: block; width: 100%; max-height: 460px; aspect-ratio: 16 / 9;
}
.gap-embed-cover-badge {
  position: absolute; right: .75rem; bottom: .75rem;
  background: rgba(0,0,0,.65); color: #fff; font-weight: 600; font-size: .85rem;
  padding: .35rem .75rem; border-radius: 999px; pointer-events: none;
}

/* ── Section « Dernières actualités » (news-latest) ───────────────────── */
.gap-news-link { display: block; color: inherit; text-decoration: none; height: 100%; }
.gap-news-link:hover { color: inherit; }
.gap-news-link:hover .gap-card-title { color: var(--color-primary, #CE2B1E); }
.gap-news-more { display: inline-block; margin-top: .5rem; font-weight: 600; font-size: .85rem; color: var(--color-primary, #CE2B1E); }
.gap-news-src { font-weight: 700; color: var(--color-flourish-1); }
.gap-news-manage { display: inline-block; margin-left: 1rem; font-size: .85rem; color: var(--color-muted); text-decoration: underline; }
.gap-news-manage:hover { color: var(--color-primary); }
/* « Voir l'article complet → » : rendu par news/show.php pour la MODALE de
   détail (gap-detail-lightbox extrait [data-lightbox-content]), où il donne
   l'URL réelle, partageable. Sur la page dédiée, ce lien pointait la page
   où l'on se trouve : le masquage promis par le commentaire du gabarit
   n'avait jamais été écrit (constaté par Pierre-Louis le 25/08). */
.t-news-readfull { display: none; }
.gap-detail-body .t-news-readfull { display: block; }
/* Filtres de la page Actualités (rubrique + année) */
.gap-news-filters { display: flex; flex-direction: column; gap: .55rem; }
.gap-news-filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.gap-news-filter-label { font-size: .8rem; font-weight: 700; color: var(--color-muted); margin-right: .25rem; }
.gap-news-chip {
  display: inline-block; padding: .25rem .7rem; border-radius: 999px;
  font-size: .82rem; border: 1px solid var(--color-border); color: var(--color-ink);
  text-decoration: none; transition: background var(--transition), border-color var(--transition), color var(--transition);
}
.gap-news-chip:hover { border-color: var(--color-secondary); color: var(--color-secondary); }
.gap-news-chip.is-active { background: var(--color-secondary); border-color: var(--color-secondary); color: #fff; }
/* Badge de rubrique sur une carte d'actualité */
.gap-news-badge {
  display: inline-block; padding: .1rem .5rem; border-radius: 999px;
  font-size: .7rem; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  background: rgba(39,75,109,.1); color: var(--color-flourish-1);
}

/* ── Nudge SEO H1 (mode édition uniquement) ───────────────────────────── */
.gap-h1-notice {
  position: fixed; left: 50%; bottom: 18px; transform: translateX(-50%);
  z-index: 1055; max-width: min(680px, 92vw);
  display: flex; align-items: flex-start; gap: .75rem;
  background: #FEF3C7; color: #7c5e10; border: 1px solid #F1C40F;
  border-radius: 10px; padding: .7rem 1rem; box-shadow: 0 8px 28px rgba(0,0,0,.18);
  font-size: .85rem; line-height: 1.4;
}
.gap-h1-notice strong { color: #6b4f00; }
.gap-h1-notice-close {
  flex: 0 0 auto; border: none; background: transparent; color: #7c5e10;
  font-size: 1rem; line-height: 1; cursor: pointer; padding: 0 .15rem;
}
.gap-h1-notice-close:hover { color: var(--color-primary, #CE2B1E); }

/* Images de contenu agrandissables (lightbox au clic, public) */
.gap-zoomable { cursor: zoom-in; }

/* ── Notifications & confirmations (GapUI — remplace alert/confirm) ───────── */
.gap-toast-host {
  position: fixed;
  bottom: 1.25rem;
  right: 1.25rem;
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: .5rem;
  max-width: min(90vw, 380px);
  pointer-events: none;
}
.gap-toast {
  pointer-events: auto;
  cursor: pointer;
  padding: .7rem 1rem;
  border-radius: .5rem;
  font-size: .9rem;
  font-weight: 500;
  color: #fff;
  background: var(--color-ink);
  box-shadow: var(--shadow-md);
  border-left: 4px solid rgba(255,255,255,.5);
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .25s ease, transform .25s ease;
}
.gap-toast.is-in { opacity: 1; transform: none; }
/* Bouton d'action dans un toast (ex. « Annuler » après une suppression) */
.gap-toast-action {
  margin-left: .75rem; padding: .2rem .6rem;
  border: 1px solid rgba(255,255,255,.6); border-radius: .3rem;
  background: rgba(255,255,255,.12); color: #fff; cursor: pointer;
  font-size: .8rem; font-weight: 700; white-space: nowrap;
  transition: background .12s;
}
.gap-toast-action:hover { background: rgba(255,255,255,.28); }
/* Panneau « Corbeille » du mode édition FO — onglets Sections / Éléments,
   chacun avec son COMPTE (14/09/2026) : on voit ce que contient la corbeille
   avant d'ouvrir un onglet. Un compte nul s'efface sans disparaître. */
.gap-trash-count {
  display: inline-block; min-width: 1.5em; margin-left: .3rem; padding: 0 .4em;
  border-radius: 999px; background: var(--color-ink, #1a1a1a); color: #fff;
  font-size: .72em; font-weight: 700; line-height: 1.6; text-align: center; vertical-align: .1em;
}
.gap-trash-count.is-vide { background: rgba(0,0,0,.12); color: #555; }
.gap-trash-row {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .5rem 0; border-bottom: 1px solid var(--color-border, #eee);
}
.gap-trash-label { min-width: 0; font-size: .88rem; }
.gap-toast--success { background: var(--color-secondary); border-left-color: #fff; }
.gap-toast--error   { background: var(--color-primary);   border-left-color: #fff; }
.gap-toast--warning { background: var(--color-accent);  border-left-color: #fff; color: #1a1a1a; }
.gap-toast--info    { background: var(--color-ink);  border-left-color: var(--color-accent); }

.gap-confirm-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-confirm);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(26,26,26,.55);
  opacity: 0;
  transition: opacity .2s ease;
}
.gap-confirm-overlay.is-in { opacity: 1; }
.gap-confirm {
  background: #fff;
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  max-width: 420px;
  width: 100%;
  padding: 1.5rem;
  transform: scale(.96);
  transition: transform .2s ease;
}
.gap-confirm-overlay.is-in .gap-confirm { transform: none; }
.gap-confirm-msg {
  margin: 0 0 1.25rem;
  font-size: 1rem;
  line-height: 1.5;
  color: var(--color-ink);
}
/* Champ de saisie de GapUI.prompt() (même gabarit que la boîte de confirmation) */
.gap-confirm-input {
  width: 100%; margin-bottom: .9rem;
  font-size: .92rem; padding: .45rem .6rem;
  border: 1px solid var(--color-border, #ccc); border-radius: .35rem;
}
.gap-confirm-input:focus {
  outline: none; border-color: var(--color-secondary, #2a8);
  box-shadow: 0 0 0 2px rgba(34,170,136,.2);
}
.gap-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: .6rem;
}
.gap-confirm-actions button {
  padding: .5rem 1.1rem;
  border-radius: .35rem;
  border: none;
  font-weight: 600;
  font-size: .88rem;
  cursor: pointer;
  transition: background var(--transition), color var(--transition);
}
.gap-confirm-cancel { background: var(--color-surface); color: var(--color-ink); }
.gap-confirm-cancel:hover { background: var(--color-border); }
.gap-confirm-ok { background: var(--color-secondary); color: #fff; }
.gap-confirm-ok:hover { background: var(--color-secondary-hover); }
.gap-confirm-ok.is-danger { background: var(--color-primary); }
.gap-confirm-ok.is-danger:hover { background: var(--color-primary-hover); }

/* En-tête H1 injecté (titre de page) quand aucune section ne porte de titre. */
.gap-page-title {
  padding: 2.5rem 0 1rem;
}
.gap-page-title h1 {
  margin: 0;
}

/* ── Pages d'authentification (layout auth.php) ──────────────────────────── */
.gap-auth-body {
  background: #13161A;
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}
.gap-auth-card {
  width: 100%;
  max-width: 400px;
  background: #fff;
  border-radius: .75rem;
  padding: 2.5rem 2rem;
  box-shadow: 0 24px 64px rgba(0,0,0,.4);
}
.gap-auth-logo { height: 52px; width: auto; margin: 0 auto 1.5rem; display: block; }
.gap-auth-title { font-size: 1.15rem; font-weight: 700; text-align: center; margin: 0 0 .25rem; color: #111; }
.gap-auth-subtitle { font-size: .82rem; color: #595959; text-align: center; margin: 0 0 1.25rem; }
.gap-auth-card form { margin-top: 1.25rem; }
.gap-auth-link { display: block; text-align: center; margin-top: 1rem; font-size: .82rem; color: #595959; }

/* Ancre de section personnalisée (html_id) : décale le saut sous la nav fixe. */
.gap-section-anchor { display: block; height: 0; scroll-margin-top: calc(var(--nav-height) + 12px); }

/* ── Bloc FAQ / Accordéon ────────────────────────────────────────────────── */
/* ── Documents à télécharger (type « documents ») ───────────────────────────*/
.gap-doclist { list-style: none; margin: 0 auto; padding: 0; max-width: 720px; display: flex; flex-direction: column; gap: .5rem; }
.gap-doc {
  display: flex; align-items: center; gap: .85rem;
  padding: .8rem 1rem;
  border: 1px solid var(--color-border); border-radius: var(--radius);
  background: var(--color-surface-alt);
}
.gap-doc-icon { flex: 0 0 auto; color: var(--color-primary); display: inline-flex; }
.gap-doc-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.gap-doc-title { font-weight: 600; color: var(--color-primary); text-decoration: none; }
.gap-doc-title:hover { text-decoration: underline; }
a.gap-doc-title::after { content: " ↓"; opacity: .6; }
.gap-doc-desc { font-size: .9rem; color: var(--color-muted); }
.gap-doc-desc:empty { display: none; }
.gap-doc-meta {
  flex: 0 0 auto; font-size: .8rem; color: var(--color-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.gap-doc-meta:empty { display: none; }
@media (max-width: 480px) {
  .gap-doc { flex-wrap: wrap; }
  .gap-doc-meta { flex-basis: 100%; padding-left: calc(24px + .85rem); }
}

/* ── Tableau (type « tableau ») ─────────────────────────────────────────────
   Vraie <table> accessible. Le conteneur défile horizontalement sur mobile
   pour que la page, elle, ne défile jamais de côté. */
.gap-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0 auto; max-width: 100%; }
.gap-table {
  width: 100%; border-collapse: collapse;
  font-size: .95rem;
}
.gap-table th, .gap-table td {
  padding: .6rem .85rem;
  text-align: left;
  border-bottom: 1px solid var(--color-border);
  vertical-align: top;
}
.gap-table thead th {
  font-weight: 700;
  color: var(--color-ink);
  border-bottom: 2px solid var(--color-primary);
  white-space: nowrap;
}
/* Lignes alternées : repère de lecture, sans peser. */
.gap-table tbody tr:nth-child(even) { background: var(--color-surface); }
/* Colonnes numériques fréquentes (prix, horaires) : chiffres alignés. */
.gap-table td { font-variant-numeric: tabular-nums; }
/* Colonne d'outils d'édition : discrète, largeur minimale. */
.gap-table .gap-td-tools { width: 1%; white-space: nowrap; padding-right: .3rem; }
.gap-td-edit { display: inline-block; min-width: 2ch; min-height: 1.2em; }

/* ── Emplacement de l'image (featured : texte libre, hero) ──────────────────
   ⚙ Disposition → « Emplacement de l'image » : haut/bas empilent les deux
   colonnes ; la largeur du ratio n'a alors plus de sens, chaque bloc prend
   toute la largeur. Gauche/droite passent par l'ordre flex (comme le zigzag). */
.row.gap-media-vertical { flex-direction: column; }
.row.gap-media-vertical-reverse { flex-direction: column-reverse; }
.row.gap-media-vertical > [class*="col-md-"] { width: 100%; max-width: 100%; }

/* ── Logos partenaires (type « logos ») ─────────────────────────────────────
   Bandeau fluide, logos à hauteur bornée (largeur libre : un logo carré et un
   logo tout en longueur cohabitent). Le nom vit dans l'alt — il ne s'affiche
   qu'en mode édition, où il devient saisissable. */
.gap-logos {
  display: flex; flex-wrap: wrap;
  gap: 2rem 3rem;
  align-items: center; justify-content: center;
}
.gap-logo-item { display: flex; flex-direction: column; align-items: center; gap: .5rem; }
.gap-logo-lien { display: inline-flex; }
.gap-logo-img {
  height: 64px; width: auto; max-width: 200px;
  object-fit: contain;
}
/* L'affordance d'ajout du socle (cadre 160px) est trop grande pour un logo. */
.gap-logos .gap-photo-empty { min-height: 72px; min-width: 150px; }
.gap-logos .gap-photo-add { padding: .3rem .6rem; font-size: .8rem; }

/* ── Témoignages (type « temoignages », template quotes) ────────────────────
   Cartes de citation : le texte d'abord, l'auteur en pied (portrait rond
   facultatif). Le filet à gauche marque la citation sans guillemets décoratifs
   — le contenu reste du texte du rédacteur, pas un ornement. */
.gap-quote {
  height: 100%; margin: 0;
  padding: 1.5rem;
  background: var(--color-surface);
  border-radius: var(--radius);
  display: flex; flex-direction: column; gap: 1rem;
}
.gap-quote-text {
  margin: 0;
  padding-left: 1rem;
  border-left: 3px solid var(--color-primary);
  font-style: italic;
}
.gap-quote-auteur { display: flex; align-items: center; gap: .75rem; margin-top: auto; }
.gap-quote-imgwrap { flex: 0 0 auto; }
.gap-quote-img { width: 48px; height: 48px; border-radius: 50%; object-fit: cover; display: block; }
.gap-quote-qui { display: flex; flex-direction: column; }
.gap-quote-nom { font-weight: 600; }
/* Portrait absent : pas de grand cadre d'ajout dans le pied de citation. */
.gap-quote .gap-photo-empty { min-height: 48px; min-width: 48px; border-radius: 50%; }
.gap-quote .gap-photo-add { padding: .2rem .45rem; font-size: .72rem; }

/* ── Texte long replié (« Lire la suite ») ──────────────────────────────────
   Mécanisme GÉNÉRIQUE — rien ici ne connaît les témoignages. Le seul gabarit
   qui le pose aujourd'hui est `quotes`, mais card-grid ou list peuvent s'en
   servir sans une ligne de plus : il suffit du marqueur `data-gap-repli`.

   ⚠️ .gap-repli EST AJOUTÉE PAR LE JS (gap.js, module GapRepli), JAMAIS PAR LE
   RENDU SERVEUR. Le serveur envoie le texte ENTIER, déplié. Replier depuis la
   feuille de style amputerait la parole de quelqu'un chez un visiteur sans JS,
   sans lui laisser le moyen de lire la suite — c'est le contraire du but.
   Sans JS : texte intégral, cartes hautes, et c'est très bien.

   Le nombre de lignes se règle par --gap-repli-lignes : un thème peut le
   changer (texte plus gros, colonne plus étroite) sans toucher au socle. */
.gap-repli {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--gap-repli-lignes, 12);
  line-clamp: var(--gap-repli-lignes, 12);
  overflow: hidden;
}
/* Posé APRÈS la citation, donc frère du <blockquote> : dans .gap-quote, qui
   est une colonne flex, sans align-self il s'étirerait sur toute la largeur. */
.gap-repli-btn {
  align-self: flex-start;
  margin: 0; padding: 0; border: 0; background: none;
  font: inherit;
  /* Le bouton est NOTRE interface, pas les mots de la personne : il sort de
     l'italique de la citation même si un thème italise toute la figure. */
  font-style: normal;
  color: var(--color-primary);
  text-decoration: underline;
  text-underline-offset: .2em;
  cursor: pointer;
}
.gap-repli-btn:hover { text-decoration-thickness: 2px; }
.gap-repli-btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
/* Posée sur la RANGÉE par GapRepli tant qu'une de ses cartes est dépliée.
   Sans elle, `h-100` aligne toute la rangée sur la carte ouverte : mesuré le
   02/09, déplier un témoignage de 61 lignes laissait 1 562 px de vide entre le
   texte des cartes voisines et le nom de leur auteur. */
.gap-repli-libre { align-items: flex-start; }

/* ── Contenu intégré (type « externe », template embed) ─────────────────────
   Le cadre prend la largeur de la section ; la hauteur vient de l'élément
   (embed_height, bornée par le gabarit). La façade de consentement réutilise
   .map-consent-wrap/.map-consent-overlay (mêmes règles que la carte). */
.gap-embed { position: relative; border-radius: var(--radius); overflow: hidden; background: var(--color-surface); }
.gap-embed-frame { display: block; width: 100%; border: 0; }
.gap-embed-note { margin: .5rem 0 0; }
/* Bouton 🔗 posé sur le cadre (mode édition) : changer l'adresse sur place. */
.gap-embed .gap-embed-btn { position: absolute; top: 8px; right: 8px; z-index: 2; }
.gap-embed-vide .gap-cta-edit { display: inline-flex; align-items: center; gap: .4rem; margin-left: .75rem; }

/* ── Bandeau d'annonce (type « annonce », template announcement) ────────────
   Bande horizontale sobre : icône info, message, bouton facultatif. La
   couleur vient des options de fond de la section (bg_style) — le socle ne
   pose qu'une teinte neutre par défaut. */
.gap-annonce {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: .6rem 1rem;
  padding: .85rem 1.25rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  text-align: center;
}
.gap-annonce-icone { display: inline-flex; color: var(--color-primary); flex: 0 0 auto; }
.gap-annonce-texte { font-weight: 500; }
.gap-annonce-cta { font-weight: 600; text-decoration: underline; color: var(--color-primary); }
/* Hors période (mode édition seulement) : assourdi + étiquette d'état. */
.gap-annonce--inactive { opacity: .6; border-style: dashed; }
.gap-annonce-etat {
  flex: 1 1 100%; order: -1;
  font-size: .72rem; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;
  color: #B3261E;
}

/* ── Trombinoscope (type « trombinoscope », template people) ────────────────
   Portrait carré par défaut — le cadre « Cercle » (photo_frame) donne le
   trombinoscope classique. */
.gap-person { display: flex; flex-direction: column; align-items: center; text-align: center; gap: .15rem; }
.gap-person-imgwrap { width: min(160px, 70%); margin-bottom: .6rem; }
.gap-person-img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: var(--radius); display: block; }
.gap-person-nom { font-weight: 700; display: block; }
.gap-person-role { display: block; }
.gap-person-mots { margin: .35rem 0 0; }
.gap-person .gap-photo-empty { min-height: 120px; }

/* ── Comparatif de formules (type « comparatif », template pricing) ─────────
   Cartes côte à côte ; le badge (« Recommandé ») met la carte en avant. */
.gap-formule {
  position: relative;
  display: flex; flex-direction: column; gap: .35rem;
  padding: 1.75rem 1.5rem 1.5rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
}
.gap-formule--avant { border: 2px solid var(--color-primary); box-shadow: 0 10px 28px rgba(15, 23, 42, .08); }
.gap-formule-badge {
  position: absolute; top: 0; left: 50%; transform: translate(-50%, -50%);
  background: var(--color-primary); color: #fff;
  font-size: .72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
  padding: .25rem .8rem; border-radius: 999px; white-space: nowrap;
}
/* Badge vide (placeholder d'édition) : discret, pas une pilule pleine. */
.admin-edit-mode .gap-formule-badge:empty { background: none; color: var(--color-ink); outline: 1px dashed var(--color-border); min-width: 6ch; min-height: 1.4em; }
.gap-formule-nom { font-weight: 800; font-size: 1.05rem; }
.gap-formule-prix { font-size: 1.9rem; font-weight: 800; line-height: 1.1; color: var(--color-primary); }
.gap-formule-accroche { display: block; }
.gap-formule-liste { list-style: none; margin: .75rem 0 0; padding: 0; display: flex; flex-direction: column; gap: .45rem; }
.gap-formule-liste li { display: flex; align-items: flex-start; gap: .5rem; font-size: .92rem; }
.gap-formule-coche { display: inline-flex; margin-top: .15em; color: #1E7A46; flex: 0 0 auto; }
/* En édition, la liste est une zone éditable (gap-lignes.js, 15/09/2026) : une
   ligne vide garde sa hauteur à côté de sa coche, et une liste vide sa largeur
   pour montrer son invite.
   La ligne y est une GRILLE (coche | texte), au même dessin que le flex : le
   texte, boîte anonyme, remplit alors sa colonne. En flex, il ne couvrait que
   sa propre largeur, et un clic à droite d'une caractéristique posait le
   curseur en DÉBUT de ligne (mesuré dans Chrome le 15/09/2026). */
.gap-formule-liste[data-editable] { min-width: 10rem; }
.gap-formule-liste[data-editable] li { min-height: 1.4em; display: grid; grid-template-columns: auto 1fr; column-gap: .5rem; align-items: start; }
.gap-formule-pied { margin-top: auto; padding-top: 1rem; }

.gap-accordion { max-width: 820px; margin: 0 auto; }
.gap-accordion-item {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  margin-bottom: .6rem;
  background: #fff;
  overflow: hidden;
}
.gap-accordion-q {
  display: flex;
  align-items: center;
  gap: .6rem;
  justify-content: space-between;
  padding: .9rem 1.1rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--color-ink);
  cursor: pointer;
  list-style: none;
}
.gap-accordion-q::-webkit-details-marker { display: none; }
.gap-accordion-q-text { flex: 1; }
.gap-accordion-chevron {
  width: 10px; height: 10px; flex-shrink: 0;
  border-right: 2px solid var(--color-primary);
  border-bottom: 2px solid var(--color-primary);
  transform: rotate(45deg);
  transition: transform var(--transition);
}
details[open] > .gap-accordion-q .gap-accordion-chevron { transform: rotate(-135deg); }
.gap-accordion-a { padding: 0 1.1rem 1.1rem; }
/* V5 (25/08/2026) : l'ouverture n'est plus sèche — la réponse fond et glisse
   sur les jetons du socle. CSS pur : le keyframe part quand [open] rend le
   bloc. La fermeture reste native (instantanée) : animer la hauteur d'un
   <details> exigerait du JS, pour un gain qui ne le paie pas. Chevron déjà
   animé ; reduced-motion : ouverture sèche, comme avant. */
@media (prefers-reduced-motion: no-preference) {
  details[open] > .gap-accordion-a {
    animation: gap-acc-ouvre var(--dur-4) var(--ease-entree);
  }
  @keyframes gap-acc-ouvre {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
  }
}
.gap-accordion-a-body { color: #333; line-height: 1.7; }
.gap-accordion-a-body > :last-child { margin-bottom: 0; }
/* Mode édition : panneau déplié, question et réponse éditables */
.gap-accordion-item--edit { border-style: dashed; }
.gap-accordion-item--edit .gap-accordion-q { cursor: text; }

/* ── Formulaire personnalisable (custom-form) ─────────────────────────────
   Public : hérite de .gap-contact-form (largeur de lecture, libellés).
   Édition : chaque champ est une carte pointillée — libellé et aide éditables
   en place, contrôle neutralisé, barre de réglages (type, obligatoire, choix)
   sur le modèle de la barre média des listes. */
.gap-custom-form--edit { max-width: 720px; margin-inline: auto; }
.gap-form-field--edit {
  position: relative;
  border: 1px dashed rgba(0, 0, 0, .25);
  border-radius: .5rem;
  padding: 1rem 1rem .75rem;
  margin-bottom: 1rem;
  background: rgba(255, 255, 255, .6);
}
.gap-form-field--edit .gap-item-tools:not(.gap-item-tools--dessus) { position: absolute; top: .35rem; right: .5rem; }
.gap-form-preview { opacity: .75; }
.gap-form-preview-foot { max-width: 720px; margin-inline: auto; }
.gap-form-help:empty { display: none; }
.gap-custom-form--edit .gap-form-help { display: block; min-height: 1.2em; }
.gap-form-field-bar { margin-top: .65rem; flex-wrap: wrap; }
.gap-form-field-bar .gap-form-bar-label {
  display: block;
  font-size: .72rem;
  color: #666;
  margin-bottom: .15rem;
}
.gap-form-field-bar .gap-form-kind { width: auto; min-width: 11rem; }
.gap-form-choices-grp { flex: 1 1 100%; }
.gap-form-choices-grp .gap-form-choices { width: 100%; font-size: .82rem; }
.gap-form-empty {
  border: 1px dashed rgba(0, 0, 0, .25);
  border-radius: .5rem;
  padding: 1.25rem;
  text-align: center;
}
/* Pièce jointe : la ligne de limites est une CONTRAINTE du système (formats,
   poids, nombre), qui cohabite sous le champ avec l'aide rédigée par
   l'éditeur. Deux gris identiques se lisaient comme une seule phrase coupée :
   la contrainte passe en retrait pour que l'aide reste la voix du site. */
.gap-form-file-limits {
  font-size: .78rem;
  opacity: .75;
  margin-bottom: .1rem;
}
/* Retour d'envoi affiché AU NIVEAU du formulaire (et non en haut de page) :
   c'est là que la redirection ramène le visiteur. Un liseré et un léger
   décalage le distinguent des champs sans en faire une alerte de plus. */
.gap-form-retour {
  margin-bottom: 1.25rem;
  border-left: 4px solid currentColor;
}
.gap-form-retour:focus { outline: none; }
/* Variante d'erreur (V10, contact aligné sur le form-builder) : liste des
   champs fautifs, focalisée par gap.js au retour de redirection. */
.gap-form-retour--erreur {
  color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 8%, transparent);
  padding: .8rem 1rem;
  border-radius: var(--radius);
}
.gap-form-retour--erreur ul { padding-left: 1.1rem; }
.gap-form-retour .gap-flash-icon { flex: 0 0 auto; display: inline-flex; }

/* ── Bloc Chiffres clés (stats) ──────────────────────────────────────────── */
.gap-stats { margin: 0 auto; }
.gap-stat { padding: .5rem; }
.gap-stat-num {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.2rem, 5vw, 3.2rem);
  line-height: 1;
  color: var(--color-primary);
}
.gap-stat-label {
  margin-top: .35rem;
  font-size: .95rem;
  font-weight: 600;
  color: var(--color-ink);
}

/* ── Bloc Vidéo (façade RGPD click-to-load) ──────────────────────────────── */
.gap-videos { margin: 0 auto; }
.gap-video { margin: 0; }
.gap-video-facade,
.gap-video-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  width: 100%;
  border-radius: var(--radius);
  overflow: hidden;
}
.gap-video-facade {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .75rem;
  cursor: pointer;
  background: linear-gradient(135deg, #232323, #0c0c0c);
  color: #fff;
  border: none;
  transition: filter var(--transition);
}
.gap-video-facade:hover { filter: brightness(1.15); }
.gap-video-play {
  width: 64px; height: 64px; border-radius: 50%;
  background: var(--color-primary);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 6px 20px rgba(0,0,0,.4);
  transition: transform var(--transition);
}
.gap-video-facade:hover .gap-video-play { transform: scale(1.08); }
.gap-video-play::after {
  content: ''; margin-left: 4px;
  border-style: solid; border-width: 12px 0 12px 20px;
  border-color: transparent transparent transparent #fff;
}
.gap-video-note { font-size: .72rem; opacity: .7; padding: 0 1rem; text-align: center; }
.gap-video-facade--empty {
  background: repeating-linear-gradient(135deg, #f2f2f2 0 8px, #fafafa 8px 16px);
  color: #888; cursor: default; border: 1.5px dashed var(--color-border);
}
.gap-video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.gap-video-cap { margin-top: .5rem; }
.gap-video-title { font-weight: 600; color: var(--color-ink); }
.gap-video-tools { display: flex; align-items: center; gap: .5rem; margin-bottom: .25rem; }
.gap-video-link {
  border: 1px solid var(--color-border); background: #fff; border-radius: .3rem;
  padding: .15rem .5rem; font-size: .8rem; cursor: pointer;
}
.gap-video-link:hover { border-color: var(--color-primary); color: var(--color-primary); }

/* ── 2e menu footer (informations légales) ──────────────────────────────── */
.footer-legal-menu {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem 1.1rem;
  justify-content: center;
  margin-bottom: .9rem;
}
.footer-legal-menu a {
  font-size: .8rem;
  color: rgba(255,255,255,.6);
  text-decoration: none;
}
.footer-legal-menu a:hover { color: #fff; text-decoration: underline; }
/* « Gérer les cookies » : une ACTION au milieu de liens, donc un <button>.
   Il en prend l'apparence exacte — un bouton qui ressemblerait à un bouton
   dans cette rangée attirerait l'œil plus que les mentions légales. Reste
   focusable au clavier, avec un contour visible. */
.footer-legal-menu [data-gap-cookie-reset] {
  font: inherit;
  font-size: .8rem;
  color: rgba(255,255,255,.6);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.footer-legal-menu [data-gap-cookie-reset]:hover { color: #fff; text-decoration: underline; }
.footer-legal-menu [data-gap-cookie-reset]:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
  border-radius: 3px;
}
/* Pages légales : lisibilité du contenu */
.gap-legal-content { line-height: 1.75; color: #333; }
.gap-legal-content h2 { font-family: var(--font-display); }
.gap-legal-content ul { padding-left: 1.2rem; }
.gap-legal-content li { margin-bottom: .35rem; }

/* Lecteur vidéo natif (fichier local/direct dans le bloc Vidéo) */
.gap-video-el {
  width: 100%;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius);
  background: #000;
  display: block;
}

/* ── Panneau latéral « Options de la section » (mode édition FO) ──────────
   Remplace la modale : la page reste visible pendant le réglage.

   EMPILEMENT — il était à 20000, et la médiathèque s'ouvrait DERRIÈRE lui
   (constaté le 11/08) : le panneau restait cliquable alors que la modale est
   censée capter la main, et le voile n'assombrissait pas la zone qui la garde.

   Le remède n'est pas de remonter la modale. Au-dessus de 20000 elle passerait
   devant les notifications (.gap-toast-host, 20000) et les confirmations
   (.gap-confirm-overlay, 20010) qu'elle émet ELLE-MÊME — la fenêtre libre fait
   neuf entiers. C'est le panneau qui était mal placé : ce n'est pas une alerte,
   c'est une surface de travail. Il prend donc le rang que Bootstrap réserve à
   ce qu'il est — un tiroir latéral, `.offcanvas`, 1045.

   L'échelle du haut, pour qui viendra après (et pour ne pas réinventer un
   nombre) :
       1030  navbar               1055  modale Bootstrap · nudge SEO H1
       1040  bandeau admin        1060  menus du bandeau · bandeau RGPD
       1045  CE PANNEAU           1085  nuancier d'icônes
       1050  voile de modale      9000  lightbox · 9999 barre d'éditeur
                                 10000  aperçu device · sélecteur de liens
                                 19000  guide d'accueil
                                 20000  notifications · 20010 confirmations
   Tout ce qui doit surgir PAR-DESSUS le panneau (barre d'éditeur enrichi,
   sélecteur de liens, notifications, confirmations) est au-dessus de 1045 ;
   tout ce qu'il doit couvrir (page, sections, bandeau) est en dessous.
   Le désordre d'ensemble reste entier — 39 z-index, zéro variable `--z-*` —
   et vit dans la réserve : ce n'est pas un chantier de renommage, c'est un
   arbitrage de couches, et il n'a pas à être rendu ici. */
.gap-fo-panel {
  position: fixed; top: 0; right: 0; bottom: 0;
  width: 420px; max-width: 96vw;
  background: #fff;
  box-shadow: -8px 0 32px rgba(0,0,0,.18);
  display: flex; flex-direction: column;
  z-index: var(--z-panneau);
  transform: translateX(105%);
  transition: transform .25s ease;
}
.gap-fo-panel.open { transform: translateX(0); }
.gap-fo-panel-head {
  display: flex; align-items: center; gap: .6rem;
  padding: .8rem 1rem;
  background: #161B22; color: #fff;
  flex-shrink: 0;
}
.gap-fo-panel-head h5 {
  margin: 0; font-size: .95rem; font-weight: 600; flex: 1;
  color: #fff; font-family: var(--font-body, inherit); /* les h5 du thème sont sombres */
}
.gap-fo-panel-close {
  border: 0; background: rgba(255,255,255,.12); color: #fff;
  width: 30px; height: 30px; border-radius: 6px; cursor: pointer;
  font-size: .9rem; line-height: 1;
}
.gap-fo-panel-close:hover { background: rgba(255,255,255,.25); }
.gap-fo-panel-body { flex: 1; overflow-y: auto; padding: 1rem; }

/* Dans le panneau (étroit), les colonnes responsives passent en pleine largeur
   (les breakpoints Bootstrap regardent l'écran, pas le panneau) ; seules les
   moitiés explicites .col-6 restent côte à côte. */
.gap-fo-panel .row > [class*="col-"] { flex: 0 0 100%; max-width: 100%; }
.gap-fo-panel .row > .col-6 { flex: 0 0 50%; max-width: 50%; }
/* Onglets Contenu/Apparence/Avancé : segments égaux dans la largeur du panneau. */
.gap-fo-panel .nav-tabs { display: flex; flex-wrap: nowrap; }
.gap-fo-panel .nav-tabs .nav-item { flex: 1; text-align: center; }
.gap-fo-panel .nav-tabs .nav-link { width: 100%; font-size: .82rem; padding: .45rem .25rem; }
/* Pied du formulaire (Enregistrer) collé en bas pendant le défilement. */
.gap-fo-panel form > .d-flex.border-top {
  position: sticky; bottom: -1rem; margin-bottom: -1rem;
  background: #fff; padding-bottom: 1rem; z-index: 5;
}

/* Groupes de réglages repliables (accordéons du panneau). */
/* ── Groupes d'options PLATS (P3) : fini les cartes bordées empilées.
   Des lignes séparées par des filets ; le groupe ouvert est signalé par une
   barre d'accent verte à gauche. */
.gap-opt-group {
  border: 0; border-bottom: 1px solid #eceae3; border-radius: 0;
  margin: 0; background: transparent;
  border-left: 2px solid transparent; padding-left: .15rem;
  transition: border-color .15s, background .15s;
}
/* Groupe OUVERT : barre verte + fond légèrement teinté + respiration — deux
   groupes ouverts à la suite ne se confondent plus. */
.gap-opt-group[open] {
  border-left-color: var(--color-secondary, #2a8);
  background: #f5faf7;
  border-radius: 0 10px 10px 0;
  margin: .4rem 0;
  border-bottom-color: transparent;
}
.gap-opt-group > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: .55rem;
  padding: .7rem .35rem; font-weight: 600; font-size: .86rem;
  background: transparent; color: #44443f;
}
.gap-opt-group > summary:hover { color: #111; }
.gap-opt-group > summary svg { color: #8a877f; flex-shrink: 0; }
.gap-opt-group[open] > summary { color: var(--color-secondary-hover, #1d7a5f); }
.gap-opt-group[open] > summary svg { color: currentColor; }
.gap-opt-group > summary::-webkit-details-marker { display: none; }
.gap-opt-group > summary::after {
  content: "▾"; margin-left: auto; color: #b4b2a9; transition: transform .15s;
}
.gap-opt-group[open] > summary::after { transform: rotate(180deg); }
.gap-opt-group .gap-opt-body { padding: .25rem .35rem 1rem; }

/* Sous-titre interne d'un groupe (Lecture / Taille / Navigation…). */
.gap-opt-sub {
  font-size: .72rem; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: #8a877f;
  margin: .85rem 0 .5rem; padding-top: .35rem;
}
.gap-opt-sub:first-child { margin-top: .15rem; padding-top: 0; border-top: 0; }
.gap-opt-body > .gap-opt-sub:not(:first-child) { border-top: 1px dashed #e3e0d8; }

/* Réglages IMBRIQUÉS sous le choix qui les déclenche (ex. diaporama de fond) :
   le filet vertical dit « ceci dépend du choix au-dessus ». */
.gap-opt-nested {
  border-left: 2px solid #e3e0d8;
  margin-left: .35rem; padding-left: .75rem;
}

/* Pied d'action collant : Enregistrer/Annuler restent visibles au scroll. */
.gap-fo-panel .gap-fo-actions {
  position: sticky; bottom: -1rem; z-index: 5;
  background: #fff; margin: 0 -1rem -1rem; padding: .75rem 1rem calc(.75rem + 1rem);
  box-shadow: 0 -6px 12px -8px rgba(0,0,0,.15);
}

/* Onglets segmentés (remplacent les nav-tabs Bootstrap dans le panneau). */
.gap-fo-panel .nav-tabs {
  background: #f1efe8; border: 0; border-radius: .5rem; padding: 3px; gap: 2px;
}
.gap-fo-panel .nav-tabs .nav-link {
  border: 0; border-radius: .4rem; color: #5f5e5a; font-weight: 600;
}
.gap-fo-panel .nav-tabs .nav-link.active {
  background: #fff; color: #111; box-shadow: 0 0 0 1px #d8d5cd;
}

/* Panneau plein écran sur mobile / tablette étroite. */
@media (max-width: 575.98px) {
  .gap-fo-panel { width: 100%; max-width: none; }
}

/* ── Lot 4 « édition intuitive » : feedback local + guide d'accueil ────── */
/* Badge « ✓ Enregistré » collé à l'élément qui vient d'être sauvegardé
   (en plus de l'indicateur de la barre admin) — opaque, lisible partout. */
[data-editable].saved { position: relative; }
[data-editable].saved::after {
  content: "✓ Enregistré";
  position: absolute; top: -1.6em; left: 0; z-index: 6;
  background: var(--color-secondary, #2A7A3C); color: #fff;
  font: 700 .62rem/1 var(--font-body, sans-serif);
  letter-spacing: .03em; text-transform: uppercase;
  padding: .25em .55em; border-radius: .25rem;
  pointer-events: none; white-space: nowrap;
}

/* Guide d'accueil du mode édition (première visite). */
.gap-edit-intro {
  position: fixed; left: 50%; bottom: 1.25rem; transform: translateX(-50%);
  z-index: var(--z-guide); width: min(560px, calc(100vw - 2rem));
  background: #161B22; color: #fff;
  border-radius: .75rem; box-shadow: 0 10px 40px rgba(0,0,0,.35);
  padding: 1rem 1.25rem;
  font-size: .9rem; line-height: 1.45;
}
/* ⚠️ Le guide s'efface tant qu'un DIALOGUE est ouvert (28/08/2026).
   MESURÉ sur dev-silliere en posant le formulaire de signalement : le guide
   vit à `--z-guide` (19000), délibérément au-dessus des surcouches (10000) où
   vivent les modales du socle. Il recouvrait donc le pied de la modale, et
   `document.elementFromPoint` rendait bien `.gap-edit-intro` à l'emplacement
   du bouton « Envoyer » — le bouton n'était pas seulement masqué, il était
   INCLIQUABLE.
   Le défaut n'est pas dans l'échelle, qui est juste pour ce qu'elle décrit
   (un guide doit passer devant un aperçu d'appareil) : il est qu'un guide
   d'accueil n'a rien à faire devant un dialogue, lequel est par définition ce
   qui capte l'attention. On le retire donc pendant l'ouverture, plutôt que de
   déplacer un palier dont dépend le reste.
   Portée VOLONTAIREMENT large (`.modal.show`, pas la seule modale de
   signalement) : le journal ouvert depuis le badge avait le même défaut
   depuis le 27/08, et tout dialogue à venir l'aurait hérité. */
body:has(.modal.show) .gap-edit-intro { display: none; }

.gap-edit-intro strong { display: block; margin-bottom: .5rem; font-size: 1rem; }
.gap-edit-intro ul { margin: 0 0 .75rem; padding-left: 1.1rem; }
.gap-edit-intro li { margin-bottom: .25rem; }
.gap-edit-intro li b { color: #ffd166; }
.gap-edit-intro-ok {
  border: 0; border-radius: .4rem; cursor: pointer;
  background: var(--color-primary, #CE2B1E); color: #fff;
  font-weight: 700; font-size: .85rem; padding: .5rem 1.1rem;
}
.gap-edit-intro-ok:hover { background: var(--color-primary-hover, #B5271B); }

/* ── Section rafraîchie en place ──────────────────────────────
   Après enregistrement, la section est remplacée sans recharger la page. Sans
   repère visuel, un changement discret (une largeur, un fond) passe inaperçu
   et on doute que l'enregistrement ait eu lieu. Un halo bref le confirme. */
.gap-section-refreshed {
  animation: gap-section-refreshed 900ms ease-out;
}
@keyframes gap-section-refreshed {
  0%   { box-shadow: inset 0 0 0 2px rgba(206, 43, 30, .55); }
  100% { box-shadow: inset 0 0 0 2px rgba(206, 43, 30, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .gap-section-refreshed { animation: none; }
}

/* ══ Galerie — modes Masonry et Justified ═══════════════════════
   Les deux préservent le ratio des photos et se rendent en CSS pur : aucune
   mesure JavaScript, donc aucun sautillement pendant le chargement. Les
   proportions viennent du serveur (photos.width / photos.height), injectées
   en --gap-span et --gap-ar sur chaque vignette. */

/* ── Masonry (type Pinterest) ─────────────────────────────────
   Colonnes CSS : la hauteur de chaque photo est libre, le navigateur remplit
   les colonnes tout seul, à n'importe quelle largeur.

   J'avais d'abord tenté une grille à lignes fines, chaque photo occupant un
   nombre de lignes proportionnel à son ratio. Ça marchait — mais uniquement
   autour de 250 px de colonne : le nombre de lignes dépend de la largeur réelle,
   qu'une feuille de style ne connaît pas. À d'autres largeurs les photos
   débordaient de la place réservée et se chevauchaient. Une mise en page qui
   casse selon la taille de l'écran n'est pas une mise en page.

   Contrepartie assumée : la lecture visuelle descend colonne par colonne au
   lieu d'aller de gauche à droite. L'ordre du DOM, lui, reste chronologique —
   c'est celui que suivent les lecteurs d'écran et la navigation clavier. */
.gap-embed-masonry-v2 {
  column-width: 250px;
  column-gap: .75rem;
}
.gap-embed-masonry-v2 .gap-embed-item {
  display: block;
  overflow: hidden;
  border-radius: 10px;
  margin-bottom: .75rem;
  /* Empêche une photo d'être coupée entre deux colonnes. */
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
}
.gap-embed-masonry-v2 .gap-embed-item img {
  width: 100%;
  height: auto;          /* le ratio d'origine, sans recadrage */
  display: block;
  border-radius: 10px;
}

/* ── Justified (lignes de hauteur égale) ──────────────────────
   Chaque photo a une largeur de base proportionnelle à son ratio, et grandit
   pour combler la ligne. Résultat : des lignes parfaitement alignées, des
   photos jamais déformées. */
.gap-embed-justified {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
}
.gap-embed-justified .gap-embed-item {
  /* --gap-ar = largeur / hauteur. Une photo large occupe donc plus de place
     qu'une photo haute, à hauteur de ligne identique. */
  flex: var(--gap-ar, 1.33) 1 calc(var(--gap-ar, 1.33) * 14rem);
  min-width: 8rem;
  overflow: hidden;
  border-radius: 10px;
}
.gap-embed-justified .gap-embed-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;    /* l'étirement de la ligne écarte à peine du ratio */
  display: block;
  aspect-ratio: var(--gap-ar, 1.33);
}
/* Absorbe l'espace de la dernière ligne : sans lui, une photo isolée s'étire
   sur toute la largeur. */
.gap-justified-filler {
  flex: 999 1 0;
  min-width: 0;
  height: 0;
}

/* Survol commun aux deux modes — sobre, et désactivé si l'utilisateur a
   demandé moins d'animations. */
.gap-embed-masonry-v2 .gap-embed-item img,
.gap-embed-justified .gap-embed-item img {
  transition: transform .35s ease, filter .35s ease;
}
@media (hover: hover) {
  .gap-embed-masonry-v2 .gap-embed-item:hover img,
  .gap-embed-justified .gap-embed-item:hover img {
    transform: scale(1.03);
    filter: brightness(1.04);
  }
}
@media (prefers-reduced-motion: reduce) {
  .gap-embed-masonry-v2 .gap-embed-item img,
  .gap-embed-justified .gap-embed-item img { transition: none; }
  .gap-embed-masonry-v2 .gap-embed-item:hover img,
  .gap-embed-justified .gap-embed-item:hover img { transform: none; }
}

@media (max-width: 575.98px) {
  .gap-embed-masonry-v2 { column-width: 150px; }
  .gap-embed-justified .gap-embed-item {
    flex-basis: calc(var(--gap-ar, 1.33) * 9rem);
    min-width: 6rem;
  }
}

/* ══ Galerie — « Photo à la une » ═══════════════════════════════
   Remplace l'ancien mode « couverture ». Deux variantes : avec ou sans la
   bande de miniatures. */
.gap-embed-hero { margin: 0; }

.gap-hero-main {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  /* Fond crème de la charte : la zone vide pendant le chargement, et les
     bandes d'une photo au ratio différent, restent dans l'esprit du site
     plutôt que d'afficher un gris d'interface. */
  background: var(--color-surface-alt, #FBF9F5);
  border-radius: 12px;
  overflow: hidden;
  cursor: zoom-in;
  position: relative;
  line-height: 0;
}
.gap-hero-main img {
  width: 100%;
  height: auto;
  display: block;
  max-height: 68vh;
  border-radius: 12px;        /* une photo très haute ne doit pas occuper l'écran entier */
  object-fit: contain;
  transition: opacity .2s ease;
}
.gap-hero-main.is-loading img { opacity: .45; }

/* Loupe : indique que le clic agrandit. Discrète, elle se renforce au survol. */
.gap-hero-zoom {
  position: absolute;
  right: .75rem;
  bottom: .75rem;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(0, 0, 0, .45);
  color: #fff;
  opacity: .75;
  transition: opacity .25s ease, transform .25s ease;
}
@media (hover: hover) {
  .gap-hero-main:hover .gap-hero-zoom { opacity: 1; transform: scale(1.08); }
}
.gap-hero-main:focus-visible { outline: 3px solid var(--color-primary, #CE2B1E); outline-offset: 3px; }

.gap-hero-legend {
  /* Serif de la charte : la légende est du texte éditorial, pas de l'interface. */
  font-family: var(--font-display, Georgia, serif);
  font-style: italic;
  font-size: .95rem;
  color: #5a5a5a;
  margin: .6rem 0 0;
  line-height: 1.5;
}

/* Bande de miniatures : défilement horizontal, pour ne jamais pousser le
   contenu qui suit quel que soit le nombre de photos. */
.gap-hero-thumbs {
  display: flex;
  gap: .5rem;
  margin-top: .75rem;
  overflow-x: auto;
  padding-bottom: .3rem;
  scroll-snap-type: x proximity;
  scrollbar-width: thin;
}
.gap-hero-thumb {
  flex: 0 0 auto;
  width: 84px;
  height: 60px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  background: none;
  scroll-snap-align: start;
  opacity: .62;
  transition: opacity .2s ease, border-color .2s ease;
}
.gap-hero-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gap-hero-thumb:hover { opacity: .9; }
.gap-hero-thumb.is-active { opacity: 1; border-color: var(--color-primary, #CE2B1E); cursor: default; }
.gap-hero-thumb:focus-visible { outline: 3px solid var(--color-primary, #CE2B1E); outline-offset: 2px; opacity: 1; }

/* `.gap-hero-count` — « 5 photos — cliquez pour parcourir l'album » — a
   disparu avec la variante sans miniatures : le diaporama MONTRE les autres
   photos au lieu de les annoncer. */

@media (prefers-reduced-motion: reduce) {
  .gap-hero-main img, .gap-hero-zoom, .gap-hero-thumb { transition: none; }
}

@media (max-width: 575.98px) {
  .gap-hero-main img { max-height: 52vh; }
  .gap-hero-thumb { width: 64px; height: 46px; }
}

/* ── Modes d'affichage d'une galerie, posés sur la section (édition) ───────
   Hors du menu : un réglage qu'on essaie en boucle ne doit pas demander deux
   clics d'ouverture avant le premier essai. Un clic applique — l'action est
   instantanée et s'annule d'un autre clic, donc rien à confirmer. */
.gap-gallery-modes {
  /* En HAUT, JUSTE SOUS le bandeau d'outils de la section.
     Elle y était d'abord, mais rendue À L'INTÉRIEUR de ce bandeau : les deux
     se chevauchaient. Le problème n'était pas la position, c'était
     l'imbrication. Descendue en bas de section, elle devenait introuvable —
     on ne cherche pas un réglage à l'opposé des autres. Elle retrouve donc le
     haut, décalée sous le bandeau plutôt que dedans. */
  position: absolute;
  top: 3.1rem;
  /* Ancrée à DROITE : à gauche elle recouvrait le début du contenu — titre ou
     première photo, c'est-à-dire ce que l'oeil lit en premier. À droite elle
     tombe dans la marge, et suit le même bord que les outils de section. */
  right: .5rem;
  z-index: 30;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .2rem;
  padding: .3rem .45rem;
  border-radius: 10px;
  background: rgba(255, 255, 255, .94);
  backdrop-filter: blur(6px);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .12);
  /* Discrète tant qu'on ne s'occupe pas de cette section, franche au survol :
     en mode édition, plusieurs sections cohabitent et une barre pleinement
     opaque sur chacune transformerait la page en tableau de bord. */
  opacity: .45;
  transition: opacity .2s ease;
}
.page-section:hover > .gap-gallery-modes,
.gap-gallery-modes:hover,
.gap-gallery-modes:focus-within { opacity: 1; }

/* Mode survolé mais pas encore appliqué : l'aperçu est en cours, rien n'est
   enregistré. Un contour suffit à le distinguer de l'état actif (plein). */
.gap-gallery-mode.is-preview {
  border-color: var(--color-primary, #CE2B1E);
  color: var(--color-primary, #CE2B1E);
  box-shadow: inset 0 0 0 1px var(--color-primary, #CE2B1E);
}

.gap-gallery-modes-label {
  font-size: .66rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: #7a7a7a;
  margin-right: .15rem;
}
.gap-gallery-mode {
  border: 1px solid rgba(0, 0, 0, .12);
  background: #fff;
  color: var(--color-ink, #1A1A1A);
  border-radius: 6px;
  padding: .1rem .45rem;
  font-size: .72rem;
  line-height: 1.55;
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.gap-gallery-mode:hover { border-color: var(--color-primary, #CE2B1E); color: var(--color-primary, #CE2B1E); }
.gap-gallery-mode:focus-visible { outline: 2px solid var(--color-primary, #CE2B1E); outline-offset: 2px; }
.gap-gallery-mode.is-active {
  background: var(--color-primary, #CE2B1E);
  border-color: var(--color-primary, #CE2B1E);
  color: #fff;
  cursor: default;
}
@media (prefers-reduced-motion: reduce) {
  .gap-gallery-modes, .gap-gallery-mode { transition: none; }
}

/* ── Emplacement média d'une VIGNETTE (grille de cartes) ─────────
   La barre complète des rangées (emplacement, parallax, affichage, limite) ne
   tient pas — et n'a pas de sens — sur une carte de 240 px. Ici : un bouton
   vers la médiathèque et l'état de ce qui est rattaché, rien d'autre. */
.gap-media-edit--vignette {
  gap: .3rem .5rem; margin: 0 0 .4rem; padding: .3rem .45rem;
  border-radius: 8px; font-size: .78rem;
}
.gap-media-edit--vignette .gap-media-edit-choose { padding: .16rem .55rem; font-size: .72rem; }
.gap-media-edit--vignette .gap-media-edit-state  { font-size: .7rem; }

/* La barre est dans le FLUX, au-dessus de la carte : sans cela, elle ne
   pourrait pas grandir quand l'état est long. Mais `.gap-card.h-100` demande
   100 % de la hauteur de la cellule — carte + barre dépassaient donc la
   cellule, et la rangée perdait ses hauteurs égales.
   La cellule devient une colonne flexible et la carte prend ce qui reste.
   Le `!important` ne fait que répondre à celui de `.h-100` (Bootstrap) : sans
   lui, la hauteur en pourcentage reprend le dessus. */
/* `.h-100` et non `.gap-card` : le même problème vaut pour le portrait d'un
   trombinoscope, une formule ou un témoignage — tous portent cette classe,
   et c'est ELLE qui demande 100 % de la cellule. Viser la classe fautive
   plutôt que chaque gabarit évite d'y revenir au gabarit suivant. */
.admin-edit-mode [data-item-id]:has(> .gap-item-tools--dessus) > .h-100,
.admin-edit-mode [data-item-id]:has(> .gap-media-edit--vignette) > .h-100 {
  height: auto !important; flex: 1 1 auto;
}

/* Retirer le média d'un élément. Discret par défaut, rouge au survol : le
   geste est réversible (rien n'est détruit, on détache), il n'a donc pas à
   crier — mais il doit se distinguer du bouton qui OUVRE la médiathèque,
   juste à côté. */
.gap-media-edit-remove {
  border: 1px solid transparent; background: none;
  color: var(--t-ink, #1a1a1a); opacity: .55;
  border-radius: 999px; padding: .18rem .55rem; font-size: .74rem; cursor: pointer;
}
.gap-media-edit-remove:hover {
  opacity: 1; border-color: var(--color-primary, #ce2b1e); color: var(--color-primary, #ce2b1e);
}
.gap-media-edit-remove:focus-visible {
  opacity: 1; outline: 2px solid var(--color-primary, #ce2b1e); outline-offset: 1px;
}
.gap-media-edit--vignette .gap-media-edit-remove { padding: .14rem .4rem; font-size: .68rem; }

/* Tuile de logo en édition : elle s'élargit d'elle-même à la barre média, qui
   est plus large qu'elle. Le `min-width` de 205 px posé pour la seule barre
   d'outils est devenu sans objet. */

/* ── Zone média ÉTROITE : les mêmes boutons, sans un mot ────────────────────
   Une tuile de logo fait ~124 px. L'étiquette « MÉDIA » et l'état (« Photo
   simple ») en mangent 120 à eux deux : avec eux, la barre force la tuile à
   211 px et l'aperçu s'éloigne du rendu final.
   Deux pistes ont été essayées avant celle-ci, et mesurées :
     · EMPILER — mise en page réparée (tuiles à 124/121/146, trois par rangée)
       mais libellé coupé en trois lignes et retrait hors du panneau ;
     · un MENU repliant les groupes — correct, mais il ajoutait une seconde
       présentation pour les mêmes commandes.
   Les icônes seules tiennent partout et gardent UNE présentation. L'état n'est
   pas perdu : il devient l'infobulle du bloc. */
.gap-media-edit--compact {
  padding: .25rem .35rem; gap: .25rem; border-style: dashed; margin-bottom: .35rem;
}
.gap-media-edit--compact .gap-media-edit-lab,
.gap-media-edit--compact .gap-media-edit-state { display: none; }

/* Dans le menu, un groupe se lit de haut en bas : l'étiquette, puis ses
   commandes. En ligne, les 240 px du menu écrasaient le bouton à 71 px et son
   libellé se coupait en trois — mesuré. `white-space: nowrap` explicite : la
   valeur initiale est `normal`, la barre dépliée n'en souffrait pas faute de
   contrainte de largeur, le menu si. */
.gap-media-menu .gap-media-edit-grp {
  flex-direction: column; align-items: flex-start; gap: .3rem; width: 100%;
}
.gap-media-menu .gap-media-edit-choose,
.gap-media-menu .gap-media-edit-remove { white-space: nowrap; }
.gap-media-menu .gap-media-edit-state { white-space: normal; }

/* Boutons média en icône : même gabarit que les outils d'élément voisins,
   pour que les deux barres se lisent comme une seule famille. */
.gap-media-edit-choose,
.gap-media-edit-remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.65rem; height: 1.65rem; padding: 0;
}
.gap-media-edit-remove { border: 1px solid transparent; }

/* ── Page servie « portes fermées » (exception de maintenance) ────────────
 *
 * Le site est en maintenance, mais UNE page reste publique (App\Core\
 * Maintenance). nav.php et footer.php rendent alors une version réduite :
 * pas de menu, pas de formulaire d'infolettre, aucun lien vers une page
 * masquée — tous répondraient 503. Ces règles habillent ce cas.
 *
 * Elles vivent dans le SOCLE et non dans un thème : la situation est celle
 * du CMS, pas celle d'un client. Un thème qui veut s'en écarter les
 * surcharge, comme n'importe quelle autre règle du socle.
 */
.gap-navbar--reduite .nav-logo { cursor: default; }

/* Le bandeau qui dit pourquoi il n'y a pas de menu. Discret : c'est une
   explication, pas une alerte — la page qu'on est venu voir, elle, marche.
 *
 * ⚠️ `position: relative` + `z-index` NE SONT PAS DÉCORATIFS, ne pas les
 * retirer. Un héros pleine largeur remonte sous la barre par une marge
 * négative égale à `--nav-height` (t-page-hero du thème gap-berstett :
 * `margin-top: -78px`, mesuré le 05/09/2026 sur dev.gapberstett.fr). Cette
 * marge est un NOMBRE FIXE : elle ignore tout ce qu'on insère entre la barre
 * et <main>. Le bandeau se retrouvait donc parfaitement rendu — bonne
 * position, bonne taille, bonne couleur — et PEINT PAR-DESSUS par le héros.
 * Rien ne le signalait : il était bien dans le HTML, `elementsFromPoint` a
 * seul montré la section au-dessus de lui.
 *
 * 1029 : juste sous la barre (1030, --z-nav), au-dessus de tout le reste. Le
 * bandeau recouvre alors les premiers pixels du héros, ce qui est voulu — il
 * appartient au bandeau de tête, pas au contenu. */
.gap-avis-maintenance {
  position: relative;
  z-index: 1029;
  margin: 0;
  padding: .55rem 1rem;
  text-align: center;
  font-size: .82rem;
  color: var(--color-muted);
  background: var(--color-surface-alt);
  border-bottom: 1px solid var(--color-border);
}

/* Pied réduit : le gabarit complet réserve la hauteur de ses colonnes, qui
   n'existent plus ici. */
.gap-footer--reduit { padding-top: 1.5rem; padding-bottom: 1.5rem; }
.gap-footer--reduit .footer-bottom { border-top: 0; padding-top: 0; margin-top: 0; }

/* ── Agenda édité en place (19/09/2026) — mode édition seulement ──────────
   Les attributs data-event-* n'existent qu'en édition (AgendaEdition) : le
   visiteur ne rencontre aucune de ces règles. Palette des pastilles de statut
   des éléments (.gap-item-status) : même sens, mêmes couleurs. */
[data-event-date] { cursor: pointer; border-radius: .4rem; }
[data-event-date]:hover,
[data-event-date]:focus-visible { outline: 2px dashed currentColor; outline-offset: 3px; }
[data-event-etat~="masque"],
[data-event-etat~="passe"] { opacity: .62; }
[data-event-etat~="masque"]:hover,
[data-event-etat~="passe"]:hover,
[data-event-etat]:focus-within { opacity: 1; }
.gap-ev-barre {
  display: flex; flex-wrap: wrap; align-items: center; gap: .3rem;
  margin-bottom: .4rem; font: 600 .72rem/1.2 system-ui, sans-serif;
}
.gap-ev-etat {
  padding: .15rem .45rem; border-radius: 999px; text-transform: uppercase; letter-spacing: .04em;
}
.gap-ev-etat.is-masque { background: #8a5a00; color: #ffe9c0; }
.gap-ev-etat.is-passe  { background: #4a4f55; color: #e7e9eb; }
.gap-ev-btn {
  display: inline-flex; align-items: center; gap: .25rem;
  padding: .2rem .5rem; border: 1px solid rgba(0,0,0,.18); border-radius: .35rem;
  background: #1e2329; color: #fff; cursor: pointer; font: inherit;
}
.gap-ev-btn:hover { filter: brightness(1.25); }
.gap-ev-btn:disabled { opacity: .5; cursor: progress; }
.gap-ev-btn.is-danger { background: #7a1f1f; }
.gap-ev-ajout {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .45rem .9rem; border: 2px dashed currentColor; border-radius: .5rem;
  background: transparent; color: inherit; font-weight: 600; cursor: pointer; opacity: .8;
}
.gap-ev-ajout:hover, .gap-ev-ajout:focus-visible { opacity: 1; }
.gap-ev-fenetre-champs { display: flex; flex-wrap: wrap; gap: .75rem; margin: .5rem 0 .25rem; }
.gap-ev-fenetre-champs label { display: flex; flex-direction: column; gap: .25rem; flex: 1 1 9rem; font-weight: 600; font-size: .9rem; }
.gap-ev-fenetre-champs small { font-weight: 400; opacity: .7; }
.gap-ev-fenetre-erreur { color: #b42318; font-size: .85rem; margin: .25rem 0 0; }
/* Le lieu reste sur la ligne de son icône, même vide (une zone vide du mode
   édition s'affiche en bloc pour montrer son invite). */
[data-target="event"][data-field="location"] { display: inline-block; min-width: 10rem; vertical-align: middle; }

/* ── Actualités éditées en place (19/09/2026) — mode édition seulement ────
   Les attributs data-news-* n'existent qu'en édition (NewsEdition). Le badge
   d'état reprend la palette du statut des éléments de section : même geste,
   mêmes couleurs, pour ne pas avoir à réapprendre. */
[data-news-etat] { position: relative; }
[data-news-etat="draft"], [data-news-etat="unpublished"] { opacity: .72; }
[data-news-etat]:hover, [data-news-etat]:focus-within { opacity: 1; }
.gap-news-barre {
  display: flex; flex-wrap: wrap; align-items: center; gap: .3rem;
  padding: .35rem .5rem; font: 600 .72rem/1.2 system-ui, sans-serif;
}
.gap-news-btn {
  display: inline-flex; align-items: center; gap: .25rem;
  padding: .2rem .5rem; border: 1px solid rgba(0,0,0,.18); border-radius: .35rem;
  background: #1e2329; color: #fff; cursor: pointer; font: inherit; text-decoration: none;
}
.gap-news-btn:hover { filter: brightness(1.25); color: #fff; }
.gap-news-btn.is-danger { background: #7a1f1f; }
.gap-news-ajout {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .45rem .9rem; border: 2px dashed currentColor; border-radius: .5rem;
  background: transparent; color: inherit; font-weight: 600; cursor: pointer; opacity: .8;
}
.gap-news-ajout:hover, .gap-news-ajout:focus-visible { opacity: 1; }
/* Badge d'état + son menu : jumeaux de .gap-item-status (éléments de section). */
.gap-news-statut-wrap { position: relative; display: inline-block; }
.gap-news-statut {
  border: 0; border-radius: 999px; padding: .15rem .5rem; cursor: pointer;
  font: 700 .68rem/1.3 system-ui, sans-serif; text-transform: uppercase; letter-spacing: .04em;
  color: #fff; background: #4a4f55;
}
.gap-news-statut.is-published { background: rgba(34,102,68,.92); color: #d9f2e4; }
.gap-news-statut.is-draft     { background: #8a5a00; color: #ffe9c0; }
.gap-news-statut.is-scheduled { background: #1f4e79; color: #d5e8f7; }
.gap-news-statut:hover { filter: brightness(1.2); }
.gap-news-statut-menu {
  display: none; position: absolute; top: calc(100% + .35rem); left: 0; z-index: 100;
  background: #1e2329; border: 1px solid rgba(255,255,255,.1); border-radius: .4rem;
  box-shadow: 0 4px 16px rgba(0,0,0,.4); min-width: 230px; padding: .3rem;
  flex-direction: column; gap: .1rem; text-transform: none; letter-spacing: 0;
}
.gap-news-statut-menu.open { display: flex; }
.gap-news-statut-opt {
  display: flex; align-items: baseline; gap: .4rem; width: 100%;
  padding: .35rem .5rem; border: 0; border-radius: .3rem;
  background: transparent; color: #fff; text-align: left; cursor: pointer; font: inherit;
}
.gap-news-statut-opt:hover { background: rgba(255,255,255,.12); }
.gap-news-statut-opt small { opacity: .65; font-size: .75em; }
.gap-news-statut-sched { display: flex; flex-wrap: wrap; align-items: center; gap: .3rem; padding: .35rem .5rem; border-top: 1px solid rgba(255,255,255,.12); }
.gap-news-statut-sched label { color: #fff; font-size: .8rem; }
.gap-news-statut-sched input { flex: 1 1 10rem; font-size: .8rem; }

/* ── Galerie éditée en place (20/09/2026) — mode édition seulement ────────
   Les attributs data-album-* du mode édition (GalleryEdition). L'interrupteur
   .gap-album-vis existait déjà : on ne touche pas à ses règles. */
/* Sur une CARTE de la galerie, la barre se pose par-dessus la vignette ; sur
   la FICHE d'un album, elle est une barre comme une autre, dans le flux.
   (Posée en absolu partout, elle sortait de son cadre sur la fiche et
   interceptait les clics d'ailleurs — constaté au banc servi le 20/09.) */
.gap-album-barre {
  display: flex; align-items: center; gap: .3rem;
  margin-bottom: .75rem;
}
.gap-album-edit { position: relative; }
.gap-album-edit .gap-album-barre {
  position: absolute; z-index: 3; top: .5rem; right: .5rem; margin: 0;
}
/* L'interrupteur .gap-album-vis est posé EN ABSOLU par sa règle d'origine,
   écrite pour les vignettes de la galerie. Dans une barre (fiche d'album) il
   s'envolait dans le coin de la page, sous la barre d'administration, qui
   recevait les clics à sa place — constaté au banc servi le 20/09. */
.gap-album-barre .gap-album-vis { position: static; top: auto; right: auto; }
.gap-album-btn {
  display: inline-flex; align-items: center; gap: .25rem;
  padding: .3rem .45rem; border: 1px solid rgba(0,0,0,.18); border-radius: .35rem;
  background: #1e2329; color: #fff; cursor: pointer; font: 600 .72rem/1.2 system-ui, sans-serif;
}
.gap-album-btn:hover { filter: brightness(1.25); }
.gap-album-btn.is-danger { background: #7a1f1f; }
[data-album-etat="hors-galerie"] { opacity: .75; }
[data-album-etat="hors-galerie"]:hover, [data-album-etat]:focus-within { opacity: 1; }
.gap-album-ajout {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .45rem .9rem; border: 2px dashed currentColor; border-radius: .5rem;
  background: transparent; color: inherit; font-weight: 600; cursor: pointer; opacity: .8;
}
.gap-album-ajout:hover, .gap-album-ajout:focus-visible { opacity: 1; }
